feat(ffilib): runtime CDLL 生成 thunk(绝对路径 LoadLibraryA + 间接调用)

_emit_runtime_ffi_thunk 生成 define <name>(%p0..): Load(path) -> Sym(h,name) -> bitcast 成 <ret>(<params>)* -> call_indirect -> ret;符号名与普通 extern 一致(裸名),调用点零改动。路径与符号名用 ExprGenConst.create_global_string 落成常量操作数(GEP 由该助手处理)。约束:仅标量参数/返回值,结构体按值与变参 fail-fast;declare-only 阶段仍走普通 declare,避免同模块 define/declare 同名并存。
This commit is contained in:
TermiNexus committed 2026-10-24 22:20:55 +08:00
1 parent 5e038eae5e
commit 0acbefafd8
1 file changed
+149 -2
+149 -2
View File
@@ -235,6 +235,138 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
viperlib.snprintf(out, sha1_len + base_len + 20, "%s.%s_v%s", sha1, base_name, h)
return out
# _emit_runtime_ffi_thunk - runtime CDLL 的本地 thunk。
# 生成 define <name>(params...),函数体:
# %h = call i8* @{_ffilib}.Load(i8* "<绝对路径>")
# %p = call i8* @{_ffilib}.Sym(i8* %h, i8* "<符号名>")
# %f = bitcast i8* %p to <ret>(<params>)*
# %r = call <ret> %f(<params>) ; ret
# 符号名与普通 extern 一致(裸名),所以调用点零改动;差异只在运行期按绝对路径加载。
# v1 约束:只支持标量参数/返回值;结构体按值与变参直接 fail-fast。
def _emit_runtime_ffi_thunk(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, fd: ast.FunctionDef*, lib_path: str, ret_ty: mlirlite.LLVMType*) -> mlirlite.Function*:
"""生成 runtime CDLL thunk;失败已报错并返回 None"""
if pool is None or mod is None or fd is None or fd.name is None or lib_path is None or ret_ty is None: return None
ffilib_sha1: str = self.Gen.StructGen._resolve_module_sha1("_ffilib", None)
if ffilib_sha1 is None:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL extern '{fd.name}' requires the _ffilib runtime library, but module '_ffilib' was not found in the includes scan", "NameError")
return None
# 返回值约束
match ret_ty:
case mlirlite.LLVMType.Struct(_, _, _):
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL extern '{fd.name}': returning a struct by value is not supported yet", "TypeError")
return None
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
# 形参类型(按注解解析;缺注解按 i32,与 declare 缺省一致)
args_node: ast.Arguments* = fd.args
ptypes: sizet* = None
pnames: sizet* = None
pcount: int = 0
if args_node is not None:
ags: ast.Arguments* = ast.Arguments*(args_node)
if ags.vararg is not None:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL extern '{fd.name}' cannot be variadic", "TypeError")
return None
if ags.args is not None:
alist: list[ast.AST*]* = ags.args
an: sizet = len(alist)
if an > 0:
ptypes = stdlib.malloc(8 * an)
pnames = stdlib.malloc(8 * an)
if ptypes is None or pnames is None: return None
ai: sizet = 0
while ai < an:
arg: ast.Arg* = ast.Arg*(alist.get(ai))
ai += 1
if arg is None or arg.arg is None: continue
pty: mlirlite.LLVMType* = None
if arg.annotation is not None:
if self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", trans._imported_modules) != 0: continue
pty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, trans._imported_modules, trans._from_imports, trans, 0)
if pty is None:
self.Gen.TypeGenErr.fatal_error(arg, f"runtime CDLL extern '{fd.name}' parameter '{arg.arg}' type annotation failed to resolve", "NameError")
return None
else: pty = mlirlite.Int32(pool)
match pty:
case mlirlite.LLVMType.Struct(_, _, _):
self.Gen.TypeGenErr.fatal_error(arg, f"runtime CDLL extern '{fd.name}' parameter '{arg.arg}': struct by value is not supported yet (use a pointer)", "TypeError")
return None
case mlirlite.LLVMType.Array(_, _):
pty = mlirlite.Ptr(pool, i8_ty)
ty_slot: sizet* = (sizet*)(uint64(ptypes) + uint64(pcount * 8))
ty_slot[0] = sizet(pty)
# 形参名 %p0/%p1...(AST 形参名可能缺失,且与 C 名无关)
nm_buf: str = pool.alloc(16)
if nm_buf is None: return None
viperlib.snprintf(nm_buf, 16, "%%p%d", pcount)
nm_slot: sizet* = (sizet*)(uint64(pnames) + uint64(pcount * 8))
nm_slot[0] = sizet(nm_buf)
pcount += 1
# thunk define + 形参
thunk: mlirlite.Function* = mlirlite.create_function(pool, mod, fd.name, ret_ty)
if thunk is None: return None
for i in range(pcount):
pty_a: mlirlite.LLVMType* = (mlirlite.LLVMType*)((sizet*)(uint64(ptypes) + uint64(i * 8))[0])
pnm_a: str = rawptr((sizet*)(uint64(pnames) + uint64(i * 8))[0])
mlirlite.add_param(pool, thunk, pty_a, pnm_a)
# 函数指针类型 <ret>(<params>)*
pnode_head: mlirlite.ParamNode* = None
pnode_tail: mlirlite.ParamNode* = None
for i in range(pcount):
pty_b: mlirlite.LLVMType* = (mlirlite.LLVMType*)((sizet*)(uint64(ptypes) + uint64(i * 8))[0])
nd: mlirlite.ParamNode* = mlirlite.new_param_node(pool, pty_b)
if nd is None: return None
if pnode_head is None: pnode_head = nd
else: pnode_tail.Next = nd
pnode_tail = nd
fn_ty: mlirlite.LLVMType* = mlirlite.Func(pool, ret_ty, pnode_head, pcount)
fn_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, fn_ty)
# 函数体
b: mlirlite.IRBuilder* = mlirlite.new_builder(pool, thunk)
if b is None: return None
v_path: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(b, pool, mod, lib_path, trans)
v_name: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(b, pool, mod, fd.name, trans)
if v_path is None or v_name is None: return None
# h = Load(path)
load_sym: str = stdlib.malloc(64)
if load_sym is None: return None
viperlib.snprintf(load_sym, 64, "%s.Load", ffilib_sha1)
v_h: mlirlite.Value* = mlirlite.build_call(b, load_sym, v_path, 1, i8_ptr_ty, 0)
if v_h is None: return None
# p = Sym(h, name)
sym_sym: str = stdlib.malloc(64)
if sym_sym is None: return None
viperlib.snprintf(sym_sym, 64, "%s.Sym", ffilib_sha1)
mlirlite.value_set_next(v_h, v_name)
v_p: mlirlite.Value* = mlirlite.build_call(b, sym_sym, v_h, 2, i8_ptr_ty, 0)
if v_p is None: return None
# f = bitcast p to fn_ptr_ty
v_f: mlirlite.Value* = mlirlite.build_bitcast(b, v_p, fn_ptr_ty)
if v_f is None: return None
# 实参列表 = thunk 自己的形参
arg_head: mlirlite.Value* = None
arg_prev: mlirlite.Value* = None
for i in range(pcount):
pty_c: mlirlite.LLVMType* = (mlirlite.LLVMType*)((sizet*)(uint64(ptypes) + uint64(i * 8))[0])
pnm_c: str = rawptr((sizet*)(uint64(pnames) + uint64(i * 8))[0])
av: mlirlite.Value* = mlirlite.SSAValue(pool, pty_c, pnm_c)
if av is None: return None
if arg_head is None: arg_head = av
else: mlirlite.value_set_next(arg_prev, av)
arg_prev = av
v_r: mlirlite.Value* = mlirlite.build_call_indirect(b, v_f, arg_head, pcount, ret_ty)
is_void: int = 0
match ret_ty:
case mlirlite.LLVMType.Void():
is_void = 1
case _:
is_void = 0
if is_void != 0: mlirlite.build_ret_void(b)
else:
if v_r is None: return None
mlirlite.build_ret(b, v_r)
return thunk
# forward_declare_functions - pre-scan all top-level FunctionDefs and emit forward declares.
# Solves same-module forward references; translate_function_def later reuses the declare via find_func_in_module.
def forward_declare_functions(self, trans: HT.Translator*, tree: ast.AST*) -> int:
@@ -344,8 +476,23 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
self.Gen.TypeGenErr.fatal_error(fd, f"extern library handle '{fd.ffi_lib}' is not a ffilib.CDLL binding (declare it with ffilib.CDLL(...) first)", "NameError")
return 0
if ffi_rt != 0:
self.Gen.TypeGenErr.fatal_error(fd, f"extern '{fd.name}' is bound to runtime CDLL '{fd.ffi_lib}' (runtime=True): runtime symbol binding is not implemented yet (requires _ffilib)", "NotImplementedError")
return 0
# runtime=True:生成 thunk(绝对路径 LoadLibraryA + 间接调用)。
# 只在实际翻译阶段生成(declare-only 阶段仍走普通 declare,避免同一模块里
# define 与 declare 同名并存)。
if trans._declare_only == 0:
if self.Gen.CallGenBase.find_func_in_module(mod, fd.name) is None:
ffi_path: str = ViperBuild.FfiHandlePath(fd.ffi_lib)
if ffi_path is None:
self.Gen.TypeGenErr.fatal_error(fd, f"extern '{fd.name}' is bound to '{fd.ffi_lib}' but its library path was not recorded by Pass 0", "InternalCompilerError")
return 0
thunk_f: mlirlite.Function* = self._emit_runtime_ffi_thunk(pool, mod, trans, fd, ffi_path, ret_ty)
if thunk_f is None: return 0
thunk_max: int = 1024
thunk_cnt: int = trans._func_count
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, thunk_cnt, fd.name, thunk_f, thunk_max, None, 0, 0, pool) == 0:
trans._func_count = thunk_cnt + 1
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, thunk_f)
continue
# Compute the mangled name. Overload groups (same name >=2 in this module body) get the
# signature suffix "{sha1}.{name}_v{参数签名hash}" so both defines can coexist in IR.