Repository navigation
Expand file tree
/
Copy pathbuild.rs
More file actions
171 lines (167 loc) · 8.31 KB
/
Copy pathbuild.rs
File metadata and controls
171 lines (167 loc) · 8.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// 构建元数据:VERGEN_BUILD_* / VERGEN_GIT_*(调 git CLI,§2 禁 git2)。
// git 不可用时 vergen 默认 fail_on_error=false,降级为 "unknown" 并出 cargo:warning。
// 另:FFI 调用 shim 代码生成(bun:ffi,§13 手写件)——见本目录 emit_ffi_shims;
// napi:vendored Node 头 → sys 类型生成 + 宿主符号导出(plan-napi §2)。
use vergen_gitcl::{Build, Emitter, Gitcl};
fn main() {
let result = (|| -> Result<(), Box<dyn std::error::Error>> {
let build = Build::all_build();
let gitcl = Gitcl::all_git();
Emitter::default()
.add_instructions(&build)?
.add_instructions(&gitcl)?
.emit()?;
emit_ffi_shims()?;
emit_napi_sys()?;
emit_symbol_export()?;
Ok(())
})();
if let Err(e) = result {
println!("cargo:warning=winterjs2: build metadata unavailable: {e}");
}
}
// ── napi:vendored Node 头 → sys 类型 + 常量(plan-napi M0)──────────────
// 只生成类型/常量:函数声明 blocklist——133 个 napi_* 由 src/napi 以
// `#[no_mangle] pub unsafe extern "C" fn` **定义并导出**,bindgen 的 extern
// 块声明若保留会造未定义引用(rval 经 dlsym 查表的 addon 两边都要宿主导出)。
fn emit_napi_sys() -> Result<(), Box<dyn std::error::Error>> {
let inc = "src/napi/include";
for f in [
"js_native_api.h",
"js_native_api_types.h",
"node_api.h",
"node_api_types.h",
] {
println!("cargo:rerun-if-changed={inc}/{f}");
}
let bindings = bindgen::Builder::default()
.header(format!("{inc}/node_api.h"))
.clang_arg(format!("-I{inc}"))
// 类型 + 常量全要(napi_* 结构/枚举/typedef + NAPI_* 宏;node_api_*
// 是 Node-API 10 的 basic-finalize/错误族类型面——M2 wrap/external/
// syntax_error 用,函数同样 blocklist)。
.allowlist_type("napi_.*")
.allowlist_type("node_api_.*")
.allowlist_var("napi_.*")
.allowlist_var("NAPI_.*")
// 函数一律不要(理由见上)。
.blocklist_function("napi_.*")
.blocklist_function("uv_.*")
.blocklist_function("node_api_.*")
.allowlist_var("uv_.*")
.size_t_is_usize(true)
.default_enum_style(bindgen::EnumVariation::Consts)
.generate()?;
let out_dir = std::env::var("OUT_DIR")?;
bindings.write_to_file(format!("{out_dir}/napi_sys.rs"))?;
Ok(())
}
// ── napi:宿主符号导出 ───────────────────────────────────────────────────
// addon 对 napi_* 的解析两条路(plan-napi §1 实测)都最终落到**宿主进程全局
// 符号表**:napi-rs 3 运行期 dlsym 查表;老式 addon 链接期 undefined lookup。
// 可执行文件默认不导出符号。macOS 用 -exported_symbols_list(通配 `_napi_*`)——
// 全量 -export_dynamic 会保留全部符号的 unwind 信息,__eh_frame 超 16MB 触发
// 链接器警告(2026-09-13 实测),清单导出既精准又保住 0 警告。
// Windows dllexport/.def 留 CI(见 plan-napi §4)。
fn emit_symbol_export() -> Result<(), Box<dyn std::error::Error>> {
let target = std::env::var("TARGET")?;
let out_dir = std::env::var("OUT_DIR")?;
if target.contains("apple") {
// glob 而非字面量:字面量在 dead-strip 后会被清单判为未定义(测试
// 二进制 2026-09-13 实测);存在性自检由 dlsym fixture 黑盒覆盖。
// `_node_api_*`:Node-API 10 错误族(node_api_create_syntax_error 等)。
let list = format!("{out_dir}/napi_export.txt");
std::fs::write(&list, "_napi_*\n_node_api_*\n_uv_*\n")?;
println!("cargo:rustc-link-arg=-Wl,-exported_symbols_list,{list}");
} else if target.contains("linux") {
println!("cargo:rustc-link-arg=-rdynamic");
} else if target.contains("windows") {
println!("cargo:warning=winterjs2: napi symbol export for windows not wired yet (CI)");
}
Ok(())
}
// ── bun:ffi 调用 shim 生成 ────────────────────────────────────────────────
// 原理:C ABI(SysV x86_64/arm64、Win64)按参数独立分类——整数/指针走 INTEGER
// 寄存器,float/double 走 SSE 寄存器。`extern "C" fn(target, a0..an) -> R` 形状的
// 中转函数对目标函数完全 ABI 透明(编译器自己完成收/发两侧的寄存器搬移,含栈参)。
// 为每个 (元数, 双精度参数位置掩码, 返回类别) 生成一个 shim,运行时按签名查表调用
// —— 这就是无 libffi 的动态调用引擎(纯 Rust,零新依赖;limitations 见 ffi.rs 头注)。
//
// 类别:arg = I(usize,整数/指针/布尔) | D(f64);ret = 0:I(usize) | 1:D(f64) | 2:F(f32)。
// f32 参数不支持(SSE 单精度编码与 f64 不同,展开成 3^n 个 shim 不划算),
// 返回 f32 单独一类。结构体按值传参/返回不支持。
fn emit_ffi_shims() -> Result<(), Box<dyn std::error::Error>> {
const MAX_ARITY: usize = 6;
let mut out = String::from(
"// @generated by build.rs(bun:ffi 调用 shim)——勿手改,改生成器。\n\n\
/// FFI 调用结果(按返回类别)。\n\
#[derive(Debug, Clone, PartialEq)]\n\
pub enum FfiRet { I(u64), D(f64), F(f32) }\n\n",
);
for arity in 0..=MAX_ARITY {
for mask in 0..(1u64 << arity) {
for ret in 0..3u8 {
let mut params = Vec::with_capacity(arity);
let mut types = Vec::with_capacity(arity);
for i in 0..arity {
let ty = if mask & (1 << i) != 0 { "f64" } else { "usize" };
params.push(format!("a{i}: {ty}"));
types.push(ty.to_string());
}
let ret_ty = match ret { 0 => "usize", 1 => "f64", _ => "f32" };
out.push_str(&format!(
"#[allow(unused_variables)]\n\
extern \"C\" fn shim_{arity}_{mask}_{ret}(target: usize, {params}) -> {ret_ty} {{\n\
unsafe {{\n\
let f: extern \"C\" fn({types}) -> {ret_ty} = std::mem::transmute(target);\n\
f({call})\n\
}}\n\
}}\n\n",
params = params.join(", "),
types = types.join(", "),
call = (0..arity).map(|i| format!("a{i}")).collect::<Vec<_>>().join(", "),
));
}
}
}
out.push_str(
"/// 按 (元数, 双精度位置掩码, 返回类别) 查 shim 并调用。\n\
/// # Safety\n\
/// `target` 须为真实可调用的 C 函数地址,签名与 (arity, mask, ret) 一致;\n\
/// `iargs`/`dargs` 长度须与掩码展开后的实参数一致。\n\
pub unsafe fn invoke(arity: usize, mask: u64, ret: u8, target: usize, iargs: &[u64], dargs: &[f64]) -> Result<FfiRet, ()> {\n\
let r = match (arity, mask, ret) {\n",
);
for arity in 0..=MAX_ARITY {
for mask in 0..(1u64 << arity) {
for ret in 0..3u8 {
let (mut ii, mut di) = (0usize, 0usize);
let mut call = Vec::with_capacity(arity);
for i in 0..arity {
if mask & (1 << i) != 0 {
call.push(format!("dargs[{di}]"));
di += 1;
} else {
call.push(format!("iargs[{ii}] as usize"));
ii += 1;
}
}
let tag = match ret { 0 => "I", 1 => "D", _ => "F" };
out.push_str(&format!(
" ({arity}, {mask}, {ret}) => FfiRet::{tag}(shim_{arity}_{mask}_{ret}(target, {call}) as _),\n",
call = call.join(", "),
));
}
}
}
out.push_str(
" _ => return Err(()),\n\
};\n\
Ok(r)\n\
}\n",
);
let out_dir = std::env::var("OUT_DIR")?;
std::fs::write(format!("{out_dir}/ffi_shims.rs"), out)?;
println!("cargo:rerun-if-changed=build.rs");
Ok(())
}