Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ test_results.log
.cache/
base_cov/
new_cov/
.workbuddy/
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ ctest --test-dir build -V
```bash
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build --parallel
ctest --test-dir build -V
# Run from test/lua so relative Lua file paths resolve correctly
(cd test/lua && ../build/bin/unit_tests)
./build/bin/bench_mark
```

Expand All @@ -227,6 +228,8 @@ ctest --test-dir build -V
> - macOS: `brew install lua`
> - Windows MSYS2: `pacman -S mingw-w64-x86_64-lua`

> Tests must be enabled explicitly: `cmake -S . -B build -DFAKELUA_BUILD_TESTS=ON`.

### CLI Tool `flua`

```bash
Expand Down
3 changes: 2 additions & 1 deletion README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ ctest --test-dir build -V
```bash
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build --parallel
ctest --test-dir build -V
# 需要在 test/lua 下运行,让相对路径的 Lua 文件能解析
(cd test/lua && ../build/bin/unit_tests)
./build/bin/bench_mark
```

Expand Down
11 changes: 11 additions & 0 deletions include/fakelua.h
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,17 @@ struct StateGCCConfig {
std::vector<std::string> include_paths;
std::vector<std::string> library_paths;
std::vector<std::string> libraries;
// 编译产物磁盘缓存(仅 macOS 上有实际收益,详见下方说明)。
//
// 在 macOS 上,首次 dlopen 某个新路径的 dylib 需要约 4.5~5.4 秒走代码签名验证,
// 而同一路径后续加载只要约 0.2 毫秒。当测试或业务反复编译同一份 C 代码时
// (例如 6 次 JIT 类型 × debug 模式组合),这笔开销会成倍累积。
//
// 开启后按「生成的 C 代码 + 编译参数」的摘要缓存 .so,命中时复用同一路径,
// 从而命中 dyld 的路径缓存。缓存文件不会自动删除,可手动清理缓存目录。
//
// 设为 false 可完全关闭(此时行为与之前一致)。
bool enable_compile_cache = true;
};

struct StateConfig {
Expand Down
188 changes: 156 additions & 32 deletions src/compile/c_gen.cpp

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions src/compile/c_gen.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,21 @@ class CGen {
// 根据当前缩进深度生成 C 代码缩进空白字符
[[nodiscard]] std::string GenTab() const;

// 返回一个「常量字符串 ID 的静态变量名」,并登记一条 __fakelua_const_init 注册语句。
//
// 不能把 ConstString::Alloc 的返回值直接内联进代码:那是进程级递增 ID,
// 同一份输入每次编译得到的数字都不同,构建既不可复现也无法按内容缓存。
// 改为在 __fakelua_const_init 里注册后写入静态变量,代码其他地方引用变量名。
// 相同的字符串会复用同一个变量。
[[nodiscard]] const char *ConstStrRef(const std::string_view &str);

// 与 ConstStrRef 相同,但用于含非文本字节(如 string.charpattern 里的 \0 与 0xFF)
// 的字符串。这类字节无法用 C 字符串字面量表达(\0 会截断),改成长度 + 字节数组常量。
[[nodiscard]] const char *ConstStrRefBinary(const std::string_view &str);

// 发射 __fakelua_const_init 的定义(含此前登记的全部注册语句)。
void EmitConstInit();

// 返回当前 active 代码段(section)的输出流引用
std::ostream &Out() {
return sections_[static_cast<size_t>(cur_section_)];
Expand Down Expand Up @@ -292,6 +307,11 @@ class CGen {
Section cur_section_ = Section::Headers;// 当前输出对应的 CGen 逻辑代码段

std::array<std::stringstream, static_cast<size_t>(Section::Count)> sections_;// C 代码分区输出流数组
std::unordered_map<std::string, std::string> const_str_var_of_;// 字符串 -> 生成的静态变量名(去重)
std::vector<std::string> const_str_regs_;// __fakelua_const_init 的注册语句
std::vector<std::string> const_str_decls_;// 常量字符串 ID 的静态变量声明(Build 收尾时前置到头段)
std::vector<std::string> const_str_binary_;// 二进制常量(长度 + 字节数组)定义
std::vector<std::string> const_str_deferred_inits_;// 依赖常量字符串 ID 的全局变量,改为在 const_init 里赋值

const struct SpecFuncContext *cur_spec_ctx_ = nullptr;// 当前特化版本的上下文(func_name/bitmask/snapshot)

Expand Down
4 changes: 4 additions & 0 deletions src/compile/c_runtime_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,10 @@ extern CVar FakeluaInterpCall(State *state, VarClosure *cl, int arg_num, const C
// pop 恢复。native 对象方法等在闭包内执行时据此登记派回本引擎的异步回调。
extern int FakeluaJitContextPush(State *s, int jit);
extern void FakeluaJitContextPop(State *s, int prev);
// 把字符串注册进当前 State 的常量池并返回其 ID。生成代码用它替代内联常量 ID,
// 使同一份输入编译出相同字节(可复现构建 + 可缓存)。
extern int64_t FakeluaConstStrAlloc(State *s, const char *str);
extern int64_t FakeluaConstStrAllocN(State *s, const char *str, size_t len);
#ifdef __cplusplus
}
#endif
Expand Down
8 changes: 8 additions & 0 deletions src/compile/compile_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ namespace fakelua {

inline constexpr const char *kInitFunctionName = "__fakelua_init";

// 宿主在加载产物后、调用 kInitFunctionName 之前调用它注入 State 指针。
// 生成代码不再硬编码 State 地址,从而保证同一输入产生相同字节(可复现构建 + 可缓存)。
inline constexpr const char *kSetStateFunctionName = "__fakelua_set_state";

// 注册生成代码里用到的常量字符串 ID。必须在注入 State 之后、init 之前调用:
// 生成代码把这些 ID 存在静态变量里,业务代码直接引用变量。
inline constexpr const char *kConstInitFunctionName = "__fakelua_const_init";

// Lua 标识符已是 [A-Za-z_][A-Za-z0-9_]*,用作 C 标识符时只需避开关键字和运行时保留名。
inline std::string SanitizeCIdent(const std::string &lua_name) {
static const std::unordered_set<std::string> kReserved = {
Expand Down
73 changes: 73 additions & 0 deletions src/jit/gcc_cache.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "jit/gcc_cache.h"

#include <boost/filesystem.hpp>
#include <boost/hash2/xxhash.hpp>
#include <boost/system/error_code.hpp>
#include <array>
#include <cstdio>
#include <fstream>
#include <string>

namespace fakelua {

namespace {

// 缓存格式版本。参与摘要计算,便于将来改变布局时让旧缓存自然失效。
constexpr int kCacheFormatVersion = 1;

std::string ToHex(std::uint64_t v) {
static const char* kDigits = "0123456789abcdef";
std::string out(16, '0');
for (int i = 15; i >= 0; i--) {
out[static_cast<size_t>(i)] = kDigits[v & 0xF];
v >>= 4;
}
return out;
}

}// namespace

std::string ComputeGccCacheKey(std::string_view c_code, std::string_view compile_flags) {
boost::hash2::xxhash_64 hasher;

// 长度前缀,避免 "ab"+"c" 与 "a"+"bc" 撞成同一个键。
const auto mix = [&hasher](std::string_view s) {
const auto len = static_cast<std::uint64_t>(s.size());
hasher.update(reinterpret_cast<const unsigned char *>(&len), sizeof(len));
hasher.update(reinterpret_cast<const unsigned char *>(s.data()), s.size());
};

mix(std::to_string(kCacheFormatVersion));
mix(c_code);
// 编译参数(-O0/-O3、include/library 路径等)不同则产物不同,必须进键。
mix(compile_flags);

return ToHex(hasher.result());
}

std::string GccCacheDir() {
boost::system::error_code ec;
auto dir = boost::filesystem::temp_directory_path(ec);
if (ec) {
dir = boost::filesystem::path(".");
}
// 刻意不放在 <tmp>/fakelua/ 下:那个目录属于 GenerateTmpFilename 的临时产物,
// 测试(如 runtime.generate_tmp_filename_creates_dir)会 remove_all 整个目录,
// 连带把编译缓存一起清掉。独立命名可避免这种互相干扰。
dir /= "fakelua-jit-cache";
boost::filesystem::create_directories(dir, ec);
if (ec && !boost::filesystem::is_directory(dir)) {
return {};
}
return dir.string();
}

std::string GccCacheArtifactPath(std::string_view key, std::string_view suffix) {
const std::string dir = GccCacheDir();
if (dir.empty()) {
return {};
}
return (boost::filesystem::path(dir) / (std::string(key) + std::string(suffix))).string();
}

}// namespace fakelua
30 changes: 30 additions & 0 deletions src/jit/gcc_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <string>
#include <string_view>

namespace fakelua {

// GCC JIT 编译产物的磁盘缓存。
//
// 背景:在 macOS 上,首次 dlopen 一个新路径的 dylib 需要约 4.5~5.4 秒走代码签名验证
// (amfid / syspolicyd IPC),而同路径的后续加载只要约 0.2 毫秒。当同一份 C 代码被
// 反复编译时(例如测试里 JIT 类型 × debug 模式的组合),这笔固定开销会成倍累积,
// 使测试套件慢到不可用。
//
// 做法:以「生成的 C 代码 + 影响产物的编译参数」的摘要作为缓存键,命中时直接复用
// 同一路径的 .so,从而命中 dyld 的路径缓存。实测 dlclose 之后重新 dlopen 同一路径
// 仍是 0.2 毫秒,说明 dyld 的记账按路径且不随 dlclose 失效,因此缓存文件可以走
// 正常的 GCCHandle 生命周期,无需常驻内存。

// 计算缓存摘要。相同输入必定返回相同字符串。
std::string ComputeGccCacheKey(std::string_view c_code, std::string_view compile_flags);

// 返回缓存目录下该摘要对应的产物路径(不保证文件已存在)。
// suffix 为 ".dylib" / ".so" / ".dll"。
std::string GccCacheArtifactPath(std::string_view key, std::string_view suffix);

// 返回缓存根目录,并确保目录已创建。失败时返回空字符串表示不可用。
std::string GccCacheDir();

}// namespace fakelua
Loading
Loading