Files
ViperC/App/lib/core/Gen/Func/FuncGenDef.vp
T
TermiNexus eda8e4465f refactor(codegen): one body pipeline for module functions and class methods
Module-level functions and class methods each carried their own copy of "resolve the
return type", "create the parameter allocas" and "emit the implicit epilogue". The copies
drifted: an unannotated method was still typed i32 by the class-method body path while its
declaration said void, so the epilogue emitted `llvm.return 0 : i32` inside a void function
and the MLIR verifier rejected it ("'llvm.return' op expected no operands", Scheduler._yield).

- FuncGenBase.resolve_func_ret_ty: the single return-type policy (annotation -> storage
  marker -> __init__/__new__ hooks -> unannotated = procedure void -> fail-fast). Used by
  the forward-declare pass, the body pass, class methods and generic specializations
- FuncGenBase.param_llvm_ty: the single parameter mapping (CVoid skipped, bare class ->
  Ptr(struct), ArrayT -> element pointer), used by every signature and alloca site
- FuncGenBase.emit_func_epilogue: the single implicit-return implementation (procedure ->
  ret void, unreachable merge, fail-fast for an annotated fall-through), shared by module
  functions, methods and nested functions
- FuncGenBase.translate_func_body: the shared body pipeline (scope entry, self/parameter
  storage, *args va_list, try/except out-params, pre-scan, capture preparation, slife,
  statements, epilogue, state restore). ClassGenVTable._translate_method now only handles
  method naming, vtable/overload registration and add_param
- the same policy reaches cross-module and generic declares: unannotated vpi functions and
  methods declare void, generic specialization declares use the shared resolver, and the
  list.append call sites emit a void call
- method parameters now carry the const / volatile / pointee-volatile / pure markers that
  the module-level path always had
- FuncGenMixin: the two dry-run helpers that return ints are annotated, as the language
  now requires (an unannotated def is a procedure)

Verified: 41 Test projects pass (MethodProcTest, CrossModuleSigTest, RawPtrParamTest,
AstTest, ListTest, OopInheritTest, ...), ViperC self-hosts with the new compiler, and the
6 tests that still fail reproduce identically with the previous compiler.
2026-10-29 19:37:55 +08:00

1348 lines
88 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Func.FuncGenMixin as FuncGenMixin
from stdint import *
import ast
import mlirlite
import memhub
import string
import viperlib
import stdio
import stdlib
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.core.Gen.Call.CallGenBase as CallGenBase
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.DebugInfoGen as DebugInfoGen
import lib.core.Gen.Type.TypeSpec as TSpec
import lib.core.builtin.ViperBuild as ViperBuild
import lib.core.CompilerFactory as CompilerFactory
# Canonical parameter-text buffer used to hash an overload's signature (see _overload_sig_text).
define OVERLOAD_SIG_TEXT_MAX = 4096
# FuncGenDef - function declaration / translate_function_def / decorator wrapper
# ============================================================
novirtual class FuncGenDef(CallGenBase.CallGenBase):
"""Function declarations, translate_function_def, and decorator wrappers."""
# _register_func_auto_addr_modes - record which parameters of fd are value references (&T / &temp T).
# Registered under the emitted symbol name so call sites can auto-address the argument.
def _register_func_auto_addr_modes(self, trans: HT.Translator*, name: str, fd: ast.FunctionDef*) -> int:
"""Register per-parameter value-reference modes for a compiled function definition."""
if trans is None or name is None or fd is None or fd.args is None: return 0
ags: ast.Arguments* = ast.Arguments*(fd.args)
if ags.args is None: return 0
alist: list[ast.AST*]* = ags.args
an: sizet = len(alist)
buf: bytes = stdlib.malloc(an + 1)
if buf is None: return 0
cnt: int = 0
has_ref: int = 0
imp: str = trans._imported_modules
for i in range(int(an)):
arg: ast.Arg* = ast.Arg*(alist.get(i))
if arg is None or arg.arg is None: continue
# CVoid params are not emitted into the signature, so they must not occupy a mode slot.
if arg.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imp) != 0: continue
m: int = 0
if arg.annotation is not None: m = self.Gen.TypeGenResolve.ref_param_mode(arg.annotation, imp)
if m == 1: buf[cnt] = char(49)
elif m == 2: buf[cnt] = char(50)
else: buf[cnt] = char(48)
if m != 0: has_ref = 1
cnt += 1
buf[cnt] = char(0)
if has_ref != 0: self.Gen.CallGenBase._register_auto_addr_modes(name, buf, cnt)
stdlib.free(buf)
return 1
# ============================================================
# 重载(overload):签名文本 / 签名哈希 / 符号名
# 同名 def 构成重载组时,符号名为 "{模块sha1}.{name}_v{参数签名hash}";非重载组保持
# "{模块sha1}.{name}"(既有 vpi 缓存与外部 ABI 完全不变)。
# ============================================================
# _overload_name_count - 统计同一作用域(模块体/类体)内同名的顶层 FunctionDef 个数
def _overload_name_count(self, body: list[ast.AST*]*, name: str) -> int:
"""同名的顶层 FunctionDef 个数;body 或 name 缺失时返回 0"""
if body is None or name is None: return 0
n: sizet = len(body)
cnt: int = 0
for i in range(int(n)):
child: ast.AST* = body.get(i)
if child is None: continue
if child.kind() != ast.ASTKind.FunctionDef: continue
fd: ast.FunctionDef* = ast.FunctionDef*(child)
if fd is None or fd.name is None: continue
if string.strcmp(fd.name, name) == 0: cnt += 1
return cnt
# _overload_group_in_table - 定义阶段的重载组判定:本模块函数表里该名字已登记 >=2 项。
# 前置声明阶段已按签名建立了全部 declare,因此定义阶段用表计数与模块体扫描等价,
# 且天然只统计"本模块定义"(按符号名的模块 sha1 前缀过滤,排除依赖 stub 的同名项)。
def _overload_group_in_table(self, trans: HT.Translator*, name: str) -> int:
"""本模块函数表内同名项 >=2 时返回 1"""
if trans is None or name is None: return 0
if self.Gen.CallGenLeafKit.count_owned_overloads(trans._funcs, trans._func_count, name, trans.ModuleSha1) >= 2: return 1
return 0
# _overload_append - 有界追加(本类局部工具)。
# 不能调用 FuncGenBase._append_str:FuncGenDef 与 FuncGenBase 是同一层的兄弟 mixin,
# FuncGenDef 的 MRO 里没有 FuncGenBase(编译期会报 "method return type cannot be obtained")。
def _overload_append(self, buf: str, s: str) -> int:
"""把 s 追加到 buf(上限 OVERLOAD_SIG_TEXT_MAX);0=成功 / 1=超长"""
if buf is None or s is None: return 1
cur: sizet = string.strlen(buf)
ln: sizet = string.strlen(s)
if cur + ln + 1 > OVERLOAD_SIG_TEXT_MAX: return 1
string.strcpy(buf + cur, s)
return 0
# _overload_sig_text - 重载签名文本:把每个形参的类型写成 SpecToText 规范文本后逗号连接。
# 用注解(而不是 LLVM 类型)生成:LLVM 没有符号性,int/uint 会撞;走 SpecFromLLVMType +
# SpecWithSign 后别名展开为 i32/u32,指针是 p:i32,结构体是 st:{模块sha1}.类名
# (跨模块引用天然展开,不会同名相撞)。可变参数追加 ",..."。
def _overload_sig_text(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, trans: HT.Translator*, skip_first: int = 0) -> str:
"""返回规范形参签名文本(stdlib 缓冲,调用方 free);失败时已报错并返回 None"""
if pool is None or fd is None or trans is None: return None
# hash 与上下文无关:固定传 None(import 表 / translator),只靠全局结构体表解析。
# 这样定义端、vpi 写入端(translator 尚未创建)、vpi 读取端天然算出同一个 hash,
# 不需要把 import 上下文跨阶段搬运;解析不到的类型会 fail-fast,不会静默算出别的 hash。
return self._overload_sig_text_ctx(pool, fd, None, None, None, skip_first)
# _overload_sig_text_ctx - 同上,但显式传入 import 上下文,供没有 translator 的场景复用:
# vpi 生成(写 vpi 时 translator 还没创建)与 vpi 读取(注入 declare 时持有可能为 None 的 trans)。
# 三者(定义端 / vpi 写入端 / vpi 读取端)必须用同一个函数,否则跨模块符号名对不上。
def _overload_sig_text_ctx(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, imported_modules: str, from_imports: str, trans: HT.Translator*, skip_first: int) -> str:
"""按注解生成规范形参签名文本;失败时已报错并返回 None"""
if pool is None or fd is None: return None
arena: TSpec.TypeSpecArena* = self.Gen.TypeGenBase.ensure_spec_arena()
if arena is None: return None
buf: str = stdlib.malloc(OVERLOAD_SIG_TEXT_MAX)
if buf is None: return None
buf[0] = '\0'
one: str = stdlib.malloc(TSpec.SPEC_TEXT_MAX)
if one is None:
stdlib.free(buf)
return None
first: int = 1
args_node: ast.Arguments* = fd.args
if args_node is not None:
ags: ast.Arguments* = ast.Arguments*(args_node)
if ags.args is not None:
alist: list[ast.AST*]* = ags.args
an: sizet = len(alist)
ai: sizet = 0
pi: int = 0
while ai < an:
arg: ast.Arg* = ast.Arg*(alist.get(ai))
ai += 1
pi += 1
# 方法重载跳过 self(所有重载共享同一个 self,不参与签名)
if pi <= skip_first: continue
if arg is None or arg.arg is None: continue
ty: mlirlite.LLVMType* = None
if arg.annotation is not None:
# CVoid 是"空形参"占位,不参与签名
if self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imported_modules) != 0: continue
ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0)
if ty is None:
stdlib.free(buf)
stdlib.free(one)
self.Gen.TypeGenErr.fatal_error(arg, f"overloaded function '{fd.name}' parameter '{arg.arg}' type annotation failed to resolve", "NameError")
return None
else:
# 无注解形参默认 i32(与 create_declare 的缺省一致)
ty = mlirlite.Int32(pool)
if ty is None:
stdlib.free(buf)
stdlib.free(one)
return None
sp: TSpec.TypeSpec* = TSpec.SpecFromLLVMType(arena, ty)
if sp is None:
stdlib.free(buf)
stdlib.free(one)
self.Gen.TypeGenErr.fatal_error(arg, f"overloaded function '{fd.name}' parameter '{arg.arg}' has an unsupported type", "TypeError")
return None
if arg.annotation is not None:
an_text: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(arg.annotation, imported_modules, pool, from_imports)
if an_text is not None:
sign: int = self.Gen.TypeGenCType.spec_sign_of_annot(an_text)
if sign != TSpec.TS_SIGN_UNKNOWN: sp = TSpec.SpecWithSign(arena, sp, sign)
if TSpec.SpecToText(sp, one, TSpec.SPEC_TEXT_MAX) != 0:
stdlib.free(buf)
stdlib.free(one)
self.Gen.TypeGenErr.fatal_error(arg, f"overloaded function '{fd.name}' parameter '{arg.arg}' signature text too long", "InternalCompilerError")
return None
if first == 0:
if self._overload_append(buf, ",") != 0:
stdlib.free(buf)
stdlib.free(one)
self.Gen.TypeGenErr.fatal_error(fd, f"overloaded function '{fd.name}' parameter list is too long", "InternalCompilerError")
return None
first = 0
if self._overload_append(buf, one) != 0:
stdlib.free(buf)
stdlib.free(one)
self.Gen.TypeGenErr.fatal_error(fd, f"overloaded function '{fd.name}' parameter list is too long", "InternalCompilerError")
return None
# *args -> 可变参数标记,必须与定参区分开(ORFunc(int) 与 ORFunc(int, ...) 是两个重载)
if ags.vararg is not None:
if first == 0:
if self._overload_append(buf, ",") != 0:
stdlib.free(buf)
stdlib.free(one)
self.Gen.TypeGenErr.fatal_error(fd, f"overloaded function '{fd.name}' parameter list is too long", "InternalCompilerError")
return None
if self._overload_append(buf, "...") != 0:
stdlib.free(buf)
stdlib.free(one)
self.Gen.TypeGenErr.fatal_error(fd, f"overloaded function '{fd.name}' parameter list is too long", "InternalCompilerError")
return None
first = 0
stdlib.free(one)
return buf
# _overload_sig_suffix_ctx - 只返回符号后缀 "_v{参数签名hash}"。
# 供没有 translator 的两端复用:vpi 生成(把后缀写进 def 名)与 vpi 读取(核对注入的 declare 名)。
def _overload_sig_suffix_ctx(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, imported_modules: str, from_imports: str, skip_first: int) -> str:
"""返回 "_v{16位签名hash}";失败已报错并返回 None"""
if pool is None or fd is None: return None
sig: str = self._overload_sig_text_ctx(pool, fd, imported_modules, from_imports, None, skip_first)
if sig is None: return None
h: str = TSpec.SpecSigHash16(sig)
stdlib.free(sig)
if h is None: return None
out: str = CompilerFactory.factory.PermPool.alloc(20)
if out is None: return None
viperlib.snprintf(out, 20, "_v%s", h)
return out
# _overload_symbol - 拼出 "{模块sha1}.{name}_v{参数签名hash}"(stdlib 缓冲)
def _overload_symbol(self, trans: HT.Translator*, base_name: str, sig_text: str) -> str:
"""构造重载符号名;模块 sha1 或哈希失败时返回 None"""
if trans is None or base_name is None or sig_text is None: return None
sha1: str = trans.ModuleSha1
if sha1 is None: return None
h: str = TSpec.SpecSigHash16(sig_text)
if h is None: return None
sha1_len: sizet = string.strlen(sha1)
base_len: sizet = string.strlen(base_name)
out: str = stdlib.malloc(sha1_len + base_len + 20)
if out is None: return None
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*, existing: mlirlite.Function* = None) -> 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)
# 形参来源:
# - 已有 declare(vpi 注入 / declaration 阶段先建好):复用它,IsDeclared 置 0 后写函数体,
# 形参类型与**名称**都沿用它的,否则函数体里引用不到入参;
# - 没有则按注解解析(缺注解按 i32,与 declare 缺省一致)并新建 define。
args_node: ast.Arguments* = fd.args
ptypes: sizet* = None
pnames: sizet* = None
pcount: int = 0
if existing is not None:
existing.IsDeclared = 0
cnt_e: int = 0
curp: mlirlite.Param* = mlirlite.function_get_param_head(existing)
while curp is not None and cnt_e < 256:
cnt_e += 1
curp = mlirlite.param_get_next(curp)
if cnt_e > 0:
ptypes = stdlib.malloc(8 * cnt_e)
pnames = stdlib.malloc(8 * cnt_e)
if ptypes is None or pnames is None:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': parameter scratch allocation failed", "InternalCompilerError")
return None
curp = mlirlite.function_get_param_head(existing)
ei: int = 0
while curp is not None and ei < cnt_e:
pty_e: mlirlite.LLVMType* = mlirlite.param_get_ty(curp)
pnm_e: str = mlirlite.param_get_name(curp)
if pty_e is None or pnm_e is None:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': reused declaration has a parameter without a type or name", "InternalCompilerError")
return None
match pty_e:
case mlirlite.LLVMType.Struct(_, _, _):
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL extern '{fd.name}': struct by value is not supported yet (use a pointer)", "TypeError")
return None
((sizet*)(uint64(ptypes) + uint64(ei * 8)))[0] = sizet(pty_e)
((sizet*)(uint64(pnames) + uint64(ei * 8)))[0] = sizet(pnm_e)
curp = mlirlite.param_get_next(curp)
ei += 1
pcount = cnt_e
elif 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:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': parameter scratch allocation failed", "InternalCompilerError")
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:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': parameter name allocation failed", "InternalCompilerError")
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:复用已有 declare,或新建 define(仅新建时添加形参)
thunk: mlirlite.Function* = existing
if thunk is None:
thunk = mlirlite.create_function(pool, mod, fd.name, ret_ty)
if thunk is None:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': function creation failed", "InternalCompilerError")
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:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': function-type parameter node allocation failed", "InternalCompilerError")
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)
# 函数体(每一步失败都 fail-fast:静默 return None 会留下空块,
# mlir-opt 只会报 "empty block: expect at least a terminator",看不出断点)
b: mlirlite.IRBuilder* = mlirlite.new_builder(pool, thunk)
if b is None:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': IRBuilder creation failed", "InternalCompilerError")
return None
# 必须有 entry block 并定位插入点:IRBuilder 的 CurBlock 初值为 None,
# 而 _emit 在 CurBlock 为 None 时会静默丢弃指令(表现就是空函数体)。
entry_thunk: mlirlite.BasicBlock* = mlirlite.create_block(pool, thunk, "entry")
if entry_thunk is None:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': entry block creation failed", "InternalCompilerError")
return None
mlirlite.position_at_end(b, entry_thunk)
v_path: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(b, pool, mod, lib_path, trans)
if v_path is None:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': emitting the library-path string constant failed", "InternalCompilerError")
return None
v_name: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(b, pool, mod, fd.name, trans)
if v_name is None:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': emitting the symbol-name string constant failed", "InternalCompilerError")
return None
# h = Load(path)
load_sym: str = stdlib.malloc(64)
if load_sym is None:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': symbol-name buffer allocation failed", "InternalCompilerError")
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:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': call to _ffilib.Load failed", "InternalCompilerError")
return None
# p = Sym(h, name)
sym_sym: str = stdlib.malloc(64)
if sym_sym is None:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': symbol-name buffer allocation failed", "InternalCompilerError")
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:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': call to _ffilib.Sym failed", "InternalCompilerError")
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:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': bitcast to the function-pointer type failed", "InternalCompilerError")
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:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': forwarding parameter {i} failed", "InternalCompilerError")
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:
self.Gen.TypeGenErr.fatal_error(fd, f"runtime CDLL thunk '{fd.name}': indirect call produced no value", "InternalCompilerError")
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:
"""Pre-scan all top-level FunctionDefs and create forward declarations."""
if trans is None or tree is None: return 0
pool: memhub.MemBuddy* = trans.Pool
mod: mlirlite.LLVMModule* = trans.Module
if pool is None or mod is None: return 0
# Pre-register this module's own DefineT constants before any signature/body is
# translated (Translator._preregister_module_defines, before forward declarations).
imported_modules: str = trans._imported_modules
from_imports: str = trans._from_imports
funcs_ptr: GenTypes.FuncEntry* = trans._funcs
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
ch: list[ast.AST*]* = tree.children
if ch is None: return 0
# Temporarily switch to declare-only mode while parsing signatures: generic class annotations trigger
# specialization, and translating method bodies now would forward-reference not-yet-declared functions.
saved_declare_only: int = trans._declare_only
trans._declare_only = 1
cn_count: sizet = len(ch)
# Pre-register this module's ClassDefs in declare-only mode (struct+fields+method declares, no bodies):
# stage 1a cannot resolve dependent TypedefT (vpi not yet injected); re-registering here keeps annotations resolvable.
pre_i: sizet
for pre_i in range(int(cn_count)):
pre_child: ast.AST* = ch.get(pre_i)
if pre_child is None: continue
if pre_child.kind() != ast.ASTKind.ClassDef: continue
pre_cd: ast.ClassDef* = ast.ClassDef*(pre_child)
if pre_cd is None or pre_cd.name is None: continue
pre_ex: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(pre_cd.name, trans.ModuleSha1)
if pre_ex is not None and pre_ex.FieldCount > 0: continue
self.Gen.ClassGenDef.translate_class_def(trans, pre_child)
for ci in range(int(cn_count)):
child: ast.AST* = ch.get(ci)
if child is None: continue
if child.kind() != ast.ASTKind.FunctionDef: continue
fd: ast.FunctionDef* = ast.FunctionDef*(child)
if fd is None or fd.name is None: continue
# PEP 695 generic functions emit no forward declare; they register as templates and are cloned by
# _specialize_generic_function at call sites. Old behavior (mapping T to int, compiling as ordinary) is gone.
if fd.type_params is not None:
# 泛型函数本轮不参与重载:同名多def 里出现泛型直接 fail-fast(避免一半模板一半普通实现)
if self._overload_name_count(ch, fd.name) >= 2:
self.Gen.TypeGenErr.fatal_error(fd, f"generic function '{fd.name}' cannot be overloaded (generic overloads are not supported)", "TypeError")
return 0
self.Gen.ClassGenGeneric._register_generic_func_template(pool, fd, trans.ModuleSha1)
continue
tp_names_fd: list[str]* = None
# Infer the return type (shared policy: annotation -> marker -> unannotated = procedure).
# The value-return check runs in the body pass, where DefineT entries are registered.
fd_ctx_fwd: str = f"function '{fd.name}'"
ret_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.resolve_func_ret_ty(trans, fd, fd_ctx_fwd, 0, None, None, 0)
if ret_ty is None: return 0
# Detect ExternT/State/ExportT
has_extern: int = 0
has_state: int = 0
has_export: int = 0
if fd.returns is not None:
has_extern = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "ExternT", 0)
has_state = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0)
has_export = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "ExportT", 0)
# Decorator-form storage markers (@t.ExportT/@t.ExternT/@t.State/@t.StaticT), aligned with TPC
# HandlesFunctions._extract_decorator_storage_flags. Use ClassGenBase._has_decorator, not self. (NoVTable).
if fd.decorator_list is not None:
if has_extern == 0 and self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "ExternT") == 1: has_extern = 1
if has_state == 0 and self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "State") == 1: has_state = 1
if has_export == 0 and self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "ExportT") == 1: has_export = 1
is_extern_decl: int = 0
if has_extern != 0 or has_state != 0: is_extern_decl = 1
# static (no cross-module use): recognize both the annotation form and the decorator form and register;
# logic is kept in _RegisterStaticFunc (inlining a new branch would disturb this function's type inference).
self._RegisterStaticFunc(trans, fd)
# extern <lib> name(...):库句柄必须绑定到 ffilib.CDLL(Pass 0 登记)。
# runtime=False -> 链接期解析,行为等同普通 extern;runtime=True -> 需要 _ffilib 运行时
# 符号绑定,尚未实现,fail-fast(绝不静默退化成静态链接)。
if fd.ffi_lib is not None:
ffi_rt: int = ViperBuild.FfiHandleRuntime(fd.ffi_lib)
if ffi_rt < 0:
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:
# runtime=True:生成 thunk(绝对路径 LoadLibraryA + 间接调用)。
# 只在实际翻译阶段生成(declare-only 阶段仍走普通 declare,避免同一模块里
# define 与 declare 同名并存)。
if trans._declare_only == 0:
existing_fd: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, fd.name)
if existing_fd is None or existing_fd.IsDeclared != 0:
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, existing_fd)
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, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
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.
is_overload_fd: int = 0
if self._overload_name_count(ch, fd.name) >= 2: is_overload_fd = 1
if is_overload_fd != 0 and (has_extern != 0 or has_state != 0 or has_export != 0 or string.strcmp(fd.name, "main") == 0):
# 这四类走裸名(C ABI / 入口点),无法承载签名后缀,因此不允许重载
self.Gen.TypeGenErr.fatal_error(fd, f"function '{fd.name}' is declared more than once but is extern/state/export/main, which cannot be overloaded", "TypeError")
return 0
if has_extern != 0 or has_state != 0 or has_export != 0 or string.strcmp(fd.name, "main") == 0: mangled_name: str = fd.name
elif is_overload_fd != 0:
sig_text_fd: str = self._overload_sig_text(pool, fd, trans)
if sig_text_fd is None:
if tp_names_fd is not None: self.Gen.TypeGenBase.clear_generic_context()
return 0
mangled_name = self._overload_symbol(trans, fd.name, sig_text_fd)
stdlib.free(sig_text_fd)
if mangled_name is None:
if tp_names_fd is not None: self.Gen.TypeGenBase.clear_generic_context()
return 0
else: mangled_name = self.Gen.FuncGenBase._mangle_func_name(trans, fd.name, 0)
# Record value-reference (&T / &temp T) parameter modes for call-site auto-address.
self._register_func_auto_addr_modes(trans, mangled_name, fd)
# Skip if the function already exists (e.g. duplicate definition). Inside an overload group
# a same-name entry with the same signature hash means a real duplicate definition -> fail-fast.
existing: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name)
if existing is not None:
if is_overload_fd != 0:
self.Gen.TypeGenErr.fatal_error(fd, f"duplicate overload: function '{fd.name}' already has a definition with the same parameter types", "TypeError")
return 0
if tp_names_fd is not None: self.Gen.TypeGenBase.clear_generic_context()
continue
# Create the declare
func: mlirlite.Function* = mlirlite.create_declare(pool, mod, mangled_name, ret_ty)
if func is None:
if tp_names_fd is not None: self.Gen.TypeGenBase.clear_generic_context()
continue
# Extract default-argument info. Note: assign the attribute to an explicitly typed list[...]* local before
# calling __len__(), otherwise the list type is not recognized and the GEP base becomes i32 0 (llc error).
fd_args_node: ast.Arguments* = fd.args
fd_defaults: rawptr = None
fd_default_count: int = 0
fd_param_count: int = 0
if fd_args_node is not None:
fd_ags: ast.Arguments* = ast.Arguments*(fd_args_node)
if fd_ags.args is not None:
fd_alist: list[ast.AST*]* = fd_ags.args
fd_param_count = int(len(fd_alist))
if fd_ags.defaults is not None:
fd_dlist: list[ast.AST*]* = fd_ags.defaults
fd_defaults = fd_dlist
fd_default_count = int(len(fd_dlist))
# Register in the function table (using bare fd.name, not mangled_name)
max_funcs: int = 1024
cur_count: int = trans._func_count
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, cur_count, fd.name, func, max_funcs, fd_defaults, fd_default_count, fd_param_count, trans.Pool) == 0:
trans._func_count = cur_count + 1
# Register the ExportT function in the global table
if (has_export != 0 or has_state != 0) and trans.ModuleSha1 is not None: self.Gen.CallGenBase.register_cexport_func(trans.ModuleSha1, fd.name)
# Add parameters
args_node: ast.Arguments* = fd.args
if args_node is not None:
ags: ast.Arguments* = ast.Arguments*(args_node)
if ags.args is not None:
alist: list[ast.AST*]* = ags.args
an: sizet = len(alist)
for ai in range(int(an)):
arg: ast.Arg* = ast.Arg*(alist.get(ai))
if arg is None or arg.arg is None: continue
# CVoid means empty param: skip
if arg.annotation is not None:
if self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imported_modules) != 0: continue
param_ty: mlirlite.LLVMType* = i32_ty
if arg.annotation is not None:
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0)
if resolved is None:
# [S4 fail-fast] Annotation present but unresolvable: no silent i32
# (declare/define signature mismatch -> ABI misalignment, runtime reads garbage from the stack)
self.Gen.TypeGenErr.fatal_error(arg, f"forward declaration param '{arg.arg}' type annotation failed to resolve", "NameError")
return 0
# Bare class param -> Ptr(struct) (object is a pointer); ArrayT param -> element pointer ty* (C array decay).
# Exception (keep in sync in 3 places): C ABI extern bare struct params pass by value %Struct with a full definition.
match resolved:
case mlirlite.LLVMType.Void():
# void is not a legal param type (void is only for void* and typedef of an opaque struct);
# fail-fast at compile time instead of emitting invalid IR. `void*` is already i8* upstream.
self.Gen.TypeGenErr.fatal_error(arg, f"param '{arg.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
return 0
case mlirlite.LLVMType.Struct(_, _, _):
if is_extern_decl != 0 and self.Gen.StructGen.find_struct_by_type(resolved) is not None:
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, resolved)
param_ty = resolved
else: param_ty = mlirlite.Ptr(pool, resolved)
case mlirlite.LLVMType.Array(aelem_ty, _):
param_ty = mlirlite.Ptr(pool, aelem_ty)
case _:
param_ty = resolved
pname: str = pool.alloc(32)
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
mlirlite.add_param(pool, func, param_ty, pname)
# *args variadic: set IsVarArg=1 to emit the ... suffix
if ags.vararg is not None: func.IsVarArg = 1
# Params added; fill the global func-table Spec (lets vtable derive type text after func is reset)
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
# Clear the generic-func context once used (prevents leaking into later functions' annotation resolution)
if tp_names_fd is not None: self.Gen.TypeGenBase.clear_generic_context()
# Restore the original declare mode
trans._declare_only = saved_declare_only
return 0
# translate_function_def - translate a function definition FunctionDef(name, args, body, ...).
# Single-trans arg mode: all shared state comes from trans; local scope via enter_scope/exit_scope.
def translate_function_def(self, trans: HT.Translator*, node: ast.AST*) -> int:
"""Translate a function definition; returns the number of added variables (usually 0)."""
fd: ast.FunctionDef* = ast.FunctionDef*(node)
if fd is None or fd.name is None: return 0
# PEP 695 generic function templates are not emitted directly (same rule as forward_declare_functions):
# clones are emitted at call sites by _specialize_generic_function; here we only ensure the template is registered.
if fd.type_params is not None:
self.Gen.ClassGenGeneric._register_generic_func_template(trans.Pool, fd, trans.ModuleSha1)
return 0
# async def (VPC-M1 coroutine): FLAG_IS_ASYNC=1 (parser stores async def as FunctionDef with flags & 1)
# -> AsyncGenMixin state machine (create_<name>/resume_<name>); early-return so the plain path cannot suspend/resume.
if fd.flags is not None and (fd.flags & 1) != 0: return self.Gen.AsyncGen.translate_async_function(trans, node)
pool: memhub.MemBuddy* = trans.Pool
mod: mlirlite.LLVMModule* = trans.Module
imported_modules: str = trans._imported_modules
from_imports: str = trans._from_imports
funcs_ptr: GenTypes.FuncEntry* = trans._funcs
func_count: int = trans._func_count
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
# Return type: one shared policy (annotation -> marker -> unannotated = procedure void).
tfd_ctx: str = f"function '{fd.name}'"
ret_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.resolve_func_ret_ty(trans, fd, tfd_ctx, 0, None, None, 1)
if ret_ty is None: return 0
# Detect an extern declaration (t.ExternT or t.State): t.ExternT ignores the body and emits only a declare;
# t.State = t.ExternT + t.ExportT. t.ExternT/t.State/t.ExportT get no SHA1 prefix and map to C library names.
is_extern_decl: int = 0
has_export: int = 0
has_extern: int = 0
has_state: int = 0
if fd.returns is not None:
has_extern = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "ExternT", 0)
has_state = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0)
has_export = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "ExportT", 0)
# t.ExternT/t.State ignore the body and only declare (whether the body is pass or not)
if has_extern != 0 or has_state != 0: is_extern_decl = 1
# Decorator-form storage markers (@t.ExportT/@t.ExternT/@t.State/@t.StaticT), aligned with TPC
# HandlesFunctions._extract_decorator_storage_flags (same as forward_declare_functions). Use ClassGenBase._has_decorator.
if fd.decorator_list is not None:
if has_extern == 0 and self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "ExternT") == 1: has_extern = 1
if has_state == 0 and self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "State") == 1: has_state = 1
if has_export == 0 and self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "ExportT") == 1: has_export = 1
if (has_extern != 0 or has_state != 0) and is_extern_decl == 0: is_extern_decl = 1
# @t.TLS one-time-init marker (aligned with TPC HandlesFunctions.py L1223-1231)
has_tls: int = 0
if fd.decorator_list is not None: has_tls = self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "TLS")
# @PureT: a pure function (or a method of a pure class). The parser has already
# rewritten str/bytes annotations to pure_str and string literals to _pure_from(...).
has_pure: int = 0
if fd.decorator_list is not None: has_pure = self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "PureT")
# Pure code never exposes a pointer: parameters and results are value copies (pure_str
# for strings, a class value for pure classes). Arrays decay to pointers, so they are
# rejected as well; a pure container must hold values and stay inside the call.
if has_pure != 0 and ret_ty is not None:
if self.Gen.ExprGenBase.is_ptr_type(ret_ty) != 0 or ret_ty.kind() == mlirlite.LLVMType.Array:
self.Gen.TypeGenErr.fatal_error(fd, f"pure function '{fd.name}' cannot return a pointer or array: pure results are value copies (use str/bytes for a pure_str value, or a pure class value); a pointer could outlive the callee frame or alias mutable storage", "TypeError")
return 0
# Parameter check runs here (not only in the create-function branch): a definition that
# reuses a forward declaration never reaches that branch.
if has_pure != 0 and fd.args is not None:
pure_ags: ast.Arguments* = ast.Arguments*(fd.args)
if pure_ags.args is not None:
for pai in range(int(len(pure_ags.args))):
parg: ast.Arg* = ast.Arg*(pure_ags.args.get(pai))
if parg is None or parg.arg is None or parg.annotation is None: continue
if self.Gen.TypeGenResolve._is_builtin_marker(parg.annotation, "CVoid", imported_modules) != 0: continue
pty_pa: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, parg.annotation, imported_modules, from_imports, trans, 0)
if pty_pa is not None and (self.Gen.ExprGenBase.is_ptr_type(pty_pa) != 0 or pty_pa.kind() == mlirlite.LLVMType.Array):
self.Gen.TypeGenErr.fatal_error(parg, f"pure function '{fd.name}' parameter '{parg.arg}' cannot be a pointer or array: pure parameters are immutable value copies (use str/bytes for a pure_str value, or a pure class value)", "TypeError")
return 0
# Custom behavior decorator detection (@log/@skip/@repeat(3) etc.): Name/Name(args) decorators that are neither
# t storage markers nor builtins. Aligned with TPC HandlesFunctions.py L1161-1189 + DecoratorPass.py; first one wins.
has_custom_decor: int = 0
decor_handler: str = None
decor_arg0: int64 = 0
if fd.decorator_list is not None:
ddn: sizet = len(fd.decorator_list)
for ddi in range(int(ddn)):
deco: ast.AST* = fd.decorator_list.get(ddi)
if deco is None: continue
dk: int = deco.kind()
dname: str = None
if dk == ast.ASTKind.Name:
dnm: ast.Name* = ast.Name*(deco)
dname = dnm.id
elif dk == ast.ASTKind.Call:
dcl: ast.Call* = ast.Call*(deco)
if dcl.func is not None and dcl.func.kind() == ast.ASTKind.Name:
dnm2: ast.Name* = ast.Name*(dcl.func)
dname = dnm2.id
# @repeat(3): take the first constant int argument (aligned with TPC L1182-1184)
if dcl.args is not None and len(dcl.args) >= 1:
da0: ast.AST* = dcl.args.get(0)
if da0 is not None and da0.kind() == ast.ASTKind.Constant:
dc0: ast.Constant* = ast.Constant*(da0)
if dc0.const_kind == ast.CONST_INT: decor_arg0 = dc0.int_val
if dname is None: continue
# Skip builtin decorators
if string.strcmp(dname, "staticmethod") == 0 or string.strcmp(dname, "property") == 0 or string.strcmp(dname, "classmethod") == 0: continue
# Skip t-module storage markers (@t.ExportT/@t.ExternT/@t.State/@t.TLS/@t.StaticT/@t.InlineT/@t.VTableT/
# @t.NoVTable/@t.Object/@t.EnumBase/@t.UnionBase/@t.REnum/@t.DefineT). Attribute-form @t.xxx is not handled here.
# 另跳过**编译期生命期标注**(P2:规范形式 @life(willFree(...)) 与兼容叠加形式 @willFree(...)):
# 它们是纯标注、没有运行时装饰器函数。漏在这里会让 VPC 生成 __decor_wrap_<name> wrapper,
# 去调用并不存在的 @life/@willFree,并把被装饰的 extern def(如 stdlib.free)变成 internal 声明
# (现场:llc 报 "invalid linkage for function declaration")。
if string.strcmp(dname, "ExportT") == 0 or string.strcmp(dname, "ExternT") == 0 or string.strcmp(dname, "State") == 0 or string.strcmp(dname, "TLS") == 0 or string.strcmp(dname, "StaticT") == 0 or string.strcmp(dname, "InlineT") == 0 or string.strcmp(dname, "VTableT") == 0 or string.strcmp(dname, "NoVTable") == 0 or string.strcmp(dname, "Object") == 0 or string.strcmp(dname, "EnumBase") == 0 or string.strcmp(dname, "UnionBase") == 0 or string.strcmp(dname, "REnum") == 0 or string.strcmp(dname, "DefineT") == 0 or string.strcmp(dname, "PureT") == 0 or string.strcmp(dname, "life") == 0 or string.strcmp(dname, "willFree") == 0:
continue
# `@__attribute__(...)` 由 parser 脱糖成 `@Attribute(t.attr.<x>, ...)`(见
# ast/parser.vp _DesugarMagicAttribute),纯编译期标注、没有运行时函数。
# 漏掉这里会生成 __decor_wrap_<name>,去调用不存在的 Attribute 符号
# (现场:ld.lld undefined symbol: Attribute,__decor_wrap__rdmsr_naked)。
if string.strcmp(dname, "Attribute") == 0:
continue
has_custom_decor = 1
decor_handler = dname
break
# SHA1 namespace: t.ExternT/t.State/t.ExportT use bare names (bare maps to C library symbols; a sha1 prefix
# would cause undefined reference). main also gets no prefix (program entry point).
# 重载组使用 "{sha1}.{name}_v{参数签名hash}",与前置声明阶段算出的符号名逐一对应。
is_overload_d: int = 0
if has_extern == 0 and has_state == 0 and has_export == 0 and string.strcmp(fd.name, "main") != 0:
is_overload_d = self._overload_group_in_table(trans, fd.name)
if has_extern != 0 or has_state != 0 or has_export != 0 or string.strcmp(fd.name, "main") == 0: mangled_name: str = fd.name
elif is_overload_d != 0:
sig_text_d: str = self._overload_sig_text(pool, fd, trans)
if sig_text_d is None: return 0
mangled_name = self._overload_symbol(trans, fd.name, sig_text_d)
stdlib.free(sig_text_d)
if mangled_name is None: return 0
else: mangled_name: str = self.Gen.FuncGenBase._mangle_func_name(trans, fd.name, 0)
# Record value-reference (&T / &temp T) parameter modes for call-site auto-address.
self._register_func_auto_addr_modes(trans, mangled_name, fd)
# Register the ExportT function in the global table (for cross-module calls). t.ExportT definitions use bare
# names (@strlen); cross-module calls consult the table to use the bare name instead of @{sha1}.func.
if (has_export != 0 or has_state != 0) and trans.ModuleSha1 is not None: self.Gen.CallGenBase.register_cexport_func(trans.ModuleSha1, fd.name)
if is_extern_decl != 0:
# extern <lib> name(...):runtime=True 在这里也生成 thunk(Phase A 的 extern 往往不走
# forward_declare_functions,两处都挂才覆盖全部路径;另一处靠 find_func_in_module 去重)。
if fd.ffi_lib is not None:
ffi_rt_d: int = ViperBuild.FfiHandleRuntime(fd.ffi_lib)
if ffi_rt_d < 0:
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_d != 0:
existing_d: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name)
if existing_d is None or existing_d.IsDeclared != 0:
ffi_path_d: str = ViperBuild.FfiHandlePath(fd.ffi_lib)
if ffi_path_d 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_d: mlirlite.Function* = self._emit_runtime_ffi_thunk(pool, mod, trans, fd, ffi_path_d, ret_ty, existing_d)
if thunk_d is None: return 0
thunk_max_d: int = 256
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, func_count, fd.name, thunk_d, thunk_max_d, None, 0, 0, pool) == 0: trans._func_count = func_count + 1
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, thunk_d, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
return 0
# Extern declaration function: emit a declare (declare only, no definition). First check whether
# forward_declare_functions already created it to avoid a duplicate declare.
existing_extern: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name)
if existing_extern is not None: return 0
func: mlirlite.Function* = mlirlite.create_declare(pool, mod, mangled_name, ret_ty)
if func is None: return 0
# Register in the function table
max_funcs_extern: int = 256
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, func_count, fd.name, func, max_funcs_extern, None, 0, 0, pool) == 0: trans._func_count = func_count + 1
# Add parameters (with type annotation support)
args_node_extern: ast.Arguments* = fd.args
if args_node_extern is not None:
ags_e: ast.Arguments* = ast.Arguments*(args_node_extern)
if ags_e.args is not None:
alist_e: list[ast.AST*]* = ags_e.args
an_e: sizet = len(alist_e)
for ai_e in range(int(an_e)):
arg_e: ast.Arg* = ast.Arg*(alist_e.get(ai_e))
if arg_e is not None and arg_e.arg is not None:
# CVoid means empty param: skip, do not add to the function signature
if arg_e.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(arg_e.annotation, "CVoid", imported_modules) != 0: continue
param_ty_e: mlirlite.LLVMType* = i32_ty
if arg_e.annotation is not None:
resolved_e: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg_e.annotation, imported_modules, from_imports, trans, 0)
if resolved_e is None:
# fail-fast: extern function param has an annotation but is unresolvable; no silent i32
# (a wrong declare signature shows up at the ABI layer)
self.Gen.TypeGenErr.fatal_error(arg_e, f"argument '{arg_e.arg}' failed to resolve type: {self.Gen.TypeGenResolve.annotation_to_str(arg_e.annotation)}", "NameError")
resolved_e = i32_ty # unreachable (fatal_error already exited)
if resolved_e is not None:
# Bare class param -> Ptr(struct); ArrayT param -> element pointer ty* (C decay). This branch is
# always a C ABI extern declare: bare struct params pass by value %Struct (keep in sync with 2 others).
match resolved_e:
case mlirlite.LLVMType.Void():
# void is not a legal param type (see the same rule above)
self.Gen.TypeGenErr.fatal_error(arg_e, f"param '{arg_e.arg}' cannot be void type (void is only allowed for void* and typedef aliases)", "TypeError")
case mlirlite.LLVMType.Struct(_, _, _):
if self.Gen.StructGen.find_struct_by_type(resolved_e) is not None:
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, resolved_e)
param_ty_e = resolved_e
else: param_ty_e = mlirlite.Ptr(pool, resolved_e)
case mlirlite.LLVMType.Array(aelem_ty_e, _):
param_ty_e = mlirlite.Ptr(pool, aelem_ty_e)
case _:
param_ty_e = resolved_e
pname_e: str = pool.alloc(32)
if pname_e is not None: viperlib.snprintf(pname_e, 32, "%%%s", arg_e.arg)
mlirlite.add_param(pool, func, param_ty_e, pname_e)
# Extract @c.Attribute decorator attributes and set them on the function
func_attrs_e: str = self.Gen.FuncGenBase.extract_func_attrs(pool, fd.decorator_list, imported_modules)
if func_attrs_e is not None: mlirlite.function_set_attrs(func, func_attrs_e)
# B5: section string attribute goes through attribute group #N
func_section_e: str = self.Gen.FuncGenBase.extract_func_section(pool, fd.decorator_list, imported_modules)
if func_section_e is not None: mlirlite.function_set_attr_group(func, func_section_e)
# Params added; fill the global func-table Spec
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
return 0
# args_node is used later for alloca creation; assign it early
args_node: ast.Arguments* = fd.args
# Check for an existing forward declare (created by forward_declare_functions)
func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, mangled_name)
if func is not None and mlirlite.function_is_declared(func) != 0:
# Reuse the forward declare: clear IsDeclared and turn it into a define
func.IsDeclared = 0
# 参数名对齐(根因修复):复用的前置声明未必是 forward_declare_functions 按源码建的,也可能是
# **模块前言**里 Translator 建的 C 库声明(如 free 用合成名 "p")。而函数体是用**源码形参名**
# %<arg.arg> 生成参数 SSA 引用的(见下方 alloca 段的 SSAValue)。若签名沿用声明里的旧名,就会
# 打印出 `llvm.func @f(%p) { ... %ptr ... }`:入口实参是 %p,体内 %ptr 却在 f 内未定义,
# mlir-opt 会把 %ptr 解析到后续同名函数的入口实参并报 "region entry argument '%ptr' is already
# in use"(现场:VKernel 的 free 头 %p / 体 %ptr,被解析到 _slab_find_page(%ptr))。
# 故这里按源码形参名重写复用到的形参名,使签名与函数体一致。forward_declare_functions 建的前置
# 声明本就用源码名,此处对其幂等(同名重写)。
reused_p: mlirlite.Param* = mlirlite.function_get_param_head(func)
if args_node is not None:
reuse_ags: ast.Arguments* = ast.Arguments*(args_node)
if reuse_ags.args is not None:
reuse_list: list[ast.AST*]* = reuse_ags.args
reuse_n: sizet = len(reuse_list)
for reuse_i in range(int(reuse_n)):
rearg: ast.Arg* = ast.Arg*(reuse_list.get(reuse_i))
if rearg is None or rearg.arg is None: continue
# CVoid means empty param: not part of the signature (mirrors the add_param loops above)
if rearg.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(rearg.annotation, "CVoid", imported_modules) != 0: continue
if reused_p is None:
# 复用声明形参少于定义:签名与函数体必然错配,禁止静默,fail-fast
self.Gen.TypeGenErr.fatal_error(rearg, f"reused declaration for '{fd.name}' has fewer parameters than the definition", "TypeError")
return 0
reuse_pname: str = pool.alloc(32)
if reuse_pname is not None: viperlib.snprintf(reuse_pname, 32, "%%%s", rearg.arg)
reused_p.Name = reuse_pname
reused_p = mlirlite.param_get_next(reused_p)
# Set decorator attributes (not set when the forward declare was created)
func_attrs_reuse: str = self.Gen.FuncGenBase.extract_func_attrs(pool, fd.decorator_list, imported_modules)
if func_attrs_reuse is not None: mlirlite.function_set_attrs(func, func_attrs_reuse)
# B5: section string attribute goes through attribute group #N
func_section_reuse: str = self.Gen.FuncGenBase.extract_func_section(pool, fd.decorator_list, imported_modules)
if func_section_reuse is not None: mlirlite.function_set_attr_group(func, func_section_reuse)
else:
# Create a new LLVM function (using the SHA1-mangled name)
func = mlirlite.create_function(pool, mod, mangled_name, ret_ty)
if func is None:
VLogger.error(f"create_function {fd.name} failed", "FUNC")
return 0
# Extract @c.Attribute decorator attributes and set them on the function
func_attrs: str = self.Gen.FuncGenBase.extract_func_attrs(pool, fd.decorator_list, imported_modules)
if func_attrs is not None: mlirlite.function_set_attrs(func, func_attrs)
# B5: section string attribute goes through attribute group #N (LLVM rejects inline section="...")
func_section: str = self.Gen.FuncGenBase.extract_func_section(pool, fd.decorator_list, imported_modules)
if func_section is not None: mlirlite.function_set_attr_group(func, func_section)
# Extract default-argument info
tfd_defaults: rawptr = None
tfd_default_count: int = 0
tfd_param_count: int = 0
if args_node is not None:
tfd_ags: ast.Arguments* = ast.Arguments*(args_node)
if tfd_ags.args is not None:
tfd_alist: list[ast.AST*]* = tfd_ags.args
tfd_param_count = int(len(tfd_alist))
if tfd_ags.defaults is not None:
tfd_dlist: list[ast.AST*]* = tfd_ags.defaults
tfd_defaults = tfd_dlist
tfd_default_count = int(len(tfd_dlist))
# Register in the function table
max_funcs: int = 1024
if self.Gen.CallGenLeafKit.add_func_to_table(funcs_ptr, func_count, fd.name, func, max_funcs, tfd_defaults, tfd_default_count, tfd_param_count, trans.Pool) == 0:
trans._func_count = func_count + 1
# Add parameters (one shared type mapping: bare class -> Ptr(struct), ArrayT -> element pointer).
if args_node is not None:
ags: ast.Arguments* = ast.Arguments*(args_node)
if ags.args is not None:
alist: list[ast.AST*]* = ags.args
an: sizet = len(alist)
for ai in range(int(an)):
arg: ast.Arg* = ast.Arg*(alist.get(ai))
if arg is None or arg.arg is None: continue
param_ty: mlirlite.LLVMType* = self.Gen.FuncGenBase.param_llvm_ty(trans, arg, tfd_ctx, has_pure)
if param_ty is None: continue
pname: str = pool.alloc(32)
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
mlirlite.add_param(pool, func, param_ty, pname)
# *args variadic: set IsVarArg=1 to emit the ... suffix
if ags.vararg is not None: func.IsVarArg = 1
# try-except: a body containing raise gets extra __eh_msg_out__ (i8**) / __eh_code_out__ (i32*) output
# params (never main). The body pipeline wires them onto trans.
eh_added: int = self.Gen.FuncGenBase.add_eh_params(trans, fd, func)
# Params added; fill the global func-table Spec
self.Gen.CallGenLeafKit.update_global_func_spec(fd.name, func, self.Gen.TypeGenResolve.annot_ret_sign(fd.returns))
# Create the entry block
entry_blk: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "entry")
if entry_blk is None: return 0
# Create the function-specific builder
func_builder: mlirlite.IRBuilder* = mlirlite.new_builder(pool, func)
if func_builder is None: return 0
mlirlite.position_at_end(func_builder, entry_blk)
# --traceback: attach a DISubprogram to this define and set every instruction in the body to the entry DILocation
# (refined per statement); no-op when DI is disabled.
dbg_pushed: int = DebugInfoGen.PushFunction(pool, func_builder, func, fd.lineno, fd.col_offset + 1)
# @t.TLS one-time-init guard (aligned with TPC HandlesFunctions.py): the first call runs the body and sets
# __tls_init_done_{name}=1; later calls return the default. Without it every call re-runs TlsAlloc and loses state.
if has_tls != 0:
# Exact capacity: the reference below used to be 64 while the definition was 128, so a
# long function name produced a reference to a different (nonexistent) global.
tls_flag_cap: sizet = string.strlen(fd.name) + 24
tls_flag_name: str = pool.alloc(tls_flag_cap)
if tls_flag_name is not None:
viperlib.snprintf(tls_flag_name, tls_flag_cap, "__tls_init_done_%s", fd.name)
tls_flag_gv: mlirlite.GlobalVariable* = mlirlite.create_global_int(pool, mod, tls_flag_name, 1, 0)
if tls_flag_gv is not None:
i1_ty_t: mlirlite.LLVMType* = mlirlite.Int1(pool)
if i1_ty_t is not None:
# GlobalVariable is not a Value subclass (different field layout), so build_load/store reading
# ptr.Ty/ptr.Name as a Value misaligns. As in create_global_string, build a Value view (Ty=Ptr(i1), Name="@name").
tls_gv_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i1_ty_t)
tls_ref_cap: sizet = string.strlen(tls_flag_name) + 2
tls_ref_name: str = pool.alloc(tls_ref_cap)
if tls_ref_name is not None:
viperlib.snprintf(tls_ref_name, tls_ref_cap, "@%s", tls_flag_name)
tls_flag_ref: mlirlite.Value* = mlirlite.SSAValue(pool, tls_gv_ptr_ty, tls_ref_name)
if tls_flag_ref is not None:
tls_flag_ld: mlirlite.Value* = mlirlite.build_load(func_builder, i1_ty_t, tls_flag_ref)
# Unique block name (create_block does not auto-number; same-label conflict within a function)
lc_t: int = trans._label_counter
trans._label_counter = lc_t + 1
tls_bb_buf: str = pool.alloc(64)
if tls_bb_buf is not None:
viperlib.snprintf(tls_bb_buf, 64, "tls.skip.%d", lc_t)
tls_skip_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, tls_bb_buf)
viperlib.snprintf(tls_bb_buf, 64, "tls.cont.%d", lc_t)
tls_cont_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, tls_bb_buf)
if tls_skip_bb is not None and tls_cont_bb is not None:
mlirlite.build_cond_br(func_builder, tls_flag_ld, tls_skip_bb, tls_cont_bb)
# skip block: return a default value matching the return type
mlirlite.position_at_end(func_builder, tls_skip_bb)
match ret_ty:
case mlirlite.LLVMType.Void():
mlirlite.build_ret_void(func_builder)
case mlirlite.LLVMType.Int(_):
tlz0: mlirlite.Value* = mlirlite.ConstInt(pool, ret_ty, 0, "0")
if tlz0 is not None: mlirlite.build_ret(func_builder, tlz0)
case mlirlite.LLVMType.Float(_):
tlf0: mlirlite.Value* = mlirlite.ConstFloat(pool, ret_ty, 0.0)
if tlf0 is not None: mlirlite.build_ret(func_builder, tlf0)
case _:
tln0: mlirlite.Value* = mlirlite.ConstNull(pool, ret_ty, "null")
if tln0 is not None: mlirlite.build_ret(func_builder, tln0)
# cont block: set the flag=1 and continue the body
mlirlite.position_at_end(func_builder, tls_cont_bb)
tlone: mlirlite.Value* = mlirlite.ConstInt(pool, i1_ty_t, 1, "1")
if tlone is not None: mlirlite.build_store(func_builder, tlone, tls_flag_ref)
# The body itself - scope entry, parameter storage, pre-scan, capture preparation,
# statement translation, implicit return and state restore - is one shared pipeline
# with class methods (FuncGenBase.translate_func_body). Keeping a single copy is what
# stops a declare and its body from disagreeing on the return type.
self.Gen.FuncGenBase.translate_func_body(trans, fd, func, func_builder, ret_ty, tfd_ctx, 0, None, None, None, has_pure, eh_added, 1, None)
# Custom decorator: register and generate a wrapper (aligned with TPC DecoratorPass.run). Module-level functions
# only; wrapper name is __decor_wrap_{bare name}; call sites are rewritten via lookup_decor_wrapper.
if has_custom_decor != 0:
wb_name: str = pool.alloc(128)
if wb_name is not None:
viperlib.snprintf(wb_name, 128, "__decor_wrap_%s", fd.name)
self.Gen.ClassGenBase.register_decorator(pool, fd.name, wb_name, decor_arg0)
self._gen_decor_wrapper(trans, pool, mod, func, fd.name, wb_name, decor_handler, decor_arg0)
if dbg_pushed != 0: DebugInfoGen.PopFunction(func_builder)
return 0
# _RegisterStaticFunc - register a static function (no cross-module use). Two equivalent forms (normalized by the
# parser): annotation form or decorator form; on hit, register (name, defining module SHA1) in the static symbol table.
def _RegisterStaticFunc(self, trans: HT.Translator*, fd: ast.FunctionDef*) -> int:
"""Register a static function when its annotation or @t.StaticT matches; 1=registered, 0=miss."""
if trans is None or fd is None or fd.name is None: return 0
if trans.ModuleSha1 is None: return 0
has: int = 0
if fd.returns is not None: has = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "StaticT", 0)
if has == 0 and fd.decorator_list is not None:
if self.Gen.ClassGenBase._has_decorator(fd.decorator_list, "StaticT") == 1: has = 1
if has == 0: return 0
return self.Gen.CallGenBase.RegisterStaticSymbol(trans.ModuleSha1, fd.name)
# _gen_decor_wrapper - generate a decorator wrapper (aligned with TPC DecoratorPass.py _generate_single_wrapper v4).
# Blocks: entry -> recursive_call/decorated_entry -> loop.header/loop.body -> post; handler signature i32 decor(i8*, i8*, i32, i8*, i8*, i8*).
def _gen_decor_wrapper(self, trans: HT.Translator*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, func: mlirlite.Function*, func_name: str, wrapper_name: str, handler_name: str, decor_arg0: int64) -> mlirlite.Function*:
"""Generate a decorator wrapper function; returns the wrapper or None."""
if trans is None or pool is None or mod is None or func is None: return None
if func_name is None or wrapper_name is None or handler_name is None: return None
ret_ty: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(func)
if ret_ty is None: return None
orig_call_name: str = mlirlite.function_get_name(func)
if orig_call_name is None: return None
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
# ---- Collect the original function's param types (by position, up to 16) ----
n_params: int = 0
param_tys: sizet* = pool.alloc(16 * 8)
if param_tys is None: return None
string.memset(param_tys, 0, 16 * 8)
pp: mlirlite.Param* = mlirlite.function_get_param_head(func)
while pp is not None:
pty: mlirlite.LLVMType* = mlirlite.param_get_ty(pp)
if pty is not None and n_params < 16:
param_tys[n_params] = sizet(pty)
n_params += 1
pp = mlirlite.param_get_next(pp)
# ---- Create the wrapper function and add params (named %a0..%aN) ----
wrapper: mlirlite.Function* = mlirlite.create_function(pool, mod, wrapper_name, ret_ty)
if wrapper is None: return None
pi: int = 0
for pi in range(int(n_params)):
pname_w: str = pool.alloc(16)
if pname_w is not None:
viperlib.snprintf(pname_w, 16, "%%a%d", pi)
mlirlite.add_param(pool, wrapper, (mlirlite.LLVMType*)(param_tys[pi]), pname_w)
# ---- Create blocks ----
entry_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, wrapper, "entry")
loop_h_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, wrapper, "loop.header")
loop_b_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, wrapper, "loop.body")
post_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, wrapper, "post")
if entry_bb is None or loop_h_bb is None or loop_b_bb is None or post_bb is None: return None
# Recursion guard flag (outermost): @__decor_rec_{wrapper} = global i8 0
rec_flag_ref: mlirlite.Value* = None
# Exact capacities: definition and reference used different fixed sizes (128 vs 96), so a
# long wrapper name produced two different globals (guard silently bypassed / undefined).
rec_flag_cap: sizet = string.strlen(wrapper_name) + 24
rec_flag_name: str = pool.alloc(rec_flag_cap)
if rec_flag_name is not None:
viperlib.snprintf(rec_flag_name, rec_flag_cap, "__decor_rec_%s", wrapper_name)
mlirlite.create_global_int(pool, mod, rec_flag_name, 8, 0)
rec_ref_cap: sizet = string.strlen(rec_flag_name) + 2
rec_ref_nm: str = pool.alloc(rec_ref_cap)
if rec_ref_nm is not None:
viperlib.snprintf(rec_ref_nm, rec_ref_cap, "@%s", rec_flag_name)
rec_flag_ref = mlirlite.SSAValue(pool, mlirlite.Ptr(pool, i8_ty), rec_ref_nm)
wb: mlirlite.IRBuilder* = mlirlite.new_builder(pool, wrapper)
if wb is None: return None
# Classify the return type
ret_is_void: int = 0
match ret_ty:
case mlirlite.LLVMType.Void():
ret_is_void = 1
case _:
pass
# ---- entry: recursion guard check (outermost) ----
mlirlite.position_at_end(wb, entry_bb)
loop_pred_bb: mlirlite.BasicBlock* = entry_bb
if rec_flag_ref is not None:
rec_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, wrapper, "recursive_call")
dec_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, wrapper, "decorated_entry")
if rec_bb is None or dec_bb is None: return None
rec_val: mlirlite.Value* = mlirlite.build_load(wb, i8_ty, rec_flag_ref)
rec_zero: mlirlite.Value* = mlirlite.ConstInt(pool, i8_ty, 0, "0")
rec_is: mlirlite.Value* = mlirlite.build_icmp(wb, mlirlite.ICMP_NE, rec_val, rec_zero)
mlirlite.build_cond_br(wb, rec_is, rec_bb, dec_bb)
# recursive_call: call the original function directly (skipping all decoration logic)
mlirlite.position_at_end(wb, rec_bb)
rec_args: mlirlite.Value* = None
ri: int = 0
for ri in range(int(n_params)):
an_r: str = pool.alloc(16)
if an_r is None: continue
viperlib.snprintf(an_r, 16, "%%a%d", ri)
av_r: mlirlite.Value* = mlirlite.SSAValue(pool, (mlirlite.LLVMType*)(param_tys[ri]), an_r)
if av_r is not None:
mlirlite.value_set_next(av_r, None)
if rec_args is None: rec_args = av_r
else:
# Append to the tail
rt: mlirlite.Value* = rec_args
while rt.Next is not None: rt = rt.Next
mlirlite.value_set_next(rt, av_r)
if ret_is_void != 0:
mlirlite.build_call(wb, orig_call_name, rec_args, n_params, ret_ty, 0)
mlirlite.build_ret_void(wb)
else:
rc_res: mlirlite.Value* = mlirlite.build_call(wb, orig_call_name, rec_args, n_params, ret_ty, 0)
if rc_res is not None: mlirlite.build_ret(wb, rc_res)
else: mlirlite.build_ret_void(wb)
# decorated_entry: set the recursion flag and continue the decoration path
mlirlite.position_at_end(wb, dec_bb)
rec_one: mlirlite.Value* = mlirlite.ConstInt(pool, i8_ty, 1, "1")
if rec_one is not None: mlirlite.build_store(wb, rec_one, rec_flag_ref)
loop_pred_bb = dec_bb
# ---- Common decoration path (entry or decorated_entry) ----
# 1. ctx = alloca [32 x i8]; ctx_ptr = bitcast i8*
ctx_ty: mlirlite.LLVMType* = mlirlite.Array(pool, i8_ty, 32)
ctx_alloca: mlirlite.Value* = mlirlite.build_alloca(wb, ctx_ty)
ctx_ptr: mlirlite.Value* = mlirlite.build_bitcast(wb, ctx_alloca, i8_ptr_ty)
# 2. Pack function params into a struct {param_types} (the decorator can read/write args_ptr)
args_alloca: mlirlite.Value* = None
args_ptr: mlirlite.Value* = None
if n_params > 0:
fields_head: mlirlite.ParamNode* = None
fk: int = 0
for fk in range(int(n_params - 1), -1, -1):
pn_n: mlirlite.ParamNode* = mlirlite.new_param_node(pool, (mlirlite.LLVMType*)(param_tys[fk]))
if pn_n is not None:
pn_n.Next = fields_head
fields_head = pn_n
args_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, fields_head, n_params, None)
if args_ty is not None:
args_alloca = mlirlite.build_alloca(wb, args_ty)
fi2: int = 0
for fi2 in range(int(n_params)):
fld_ty: mlirlite.LLVMType* = (mlirlite.LLVMType*)(param_tys[fi2])
fld_ptr: mlirlite.Value* = mlirlite.build_gep_struct(wb, args_ty, fld_ty, args_alloca, fi2)
if fld_ptr is not None:
fld_nm: str = pool.alloc(16)
if fld_nm is not None:
viperlib.snprintf(fld_nm, 16, "%%a%d", fi2)
fld_v: mlirlite.Value* = mlirlite.SSAValue(pool, fld_ty, fld_nm)
if fld_v is not None: mlirlite.build_store(wb, fld_v, fld_ptr)
args_ptr = mlirlite.build_bitcast(wb, args_alloca, i8_ptr_ty)
else: args_ptr = mlirlite.ConstNull(pool, i8_ptr_ty, "null")
# 3. Return value storage (zero-initialized; a safe default is returned when skipping the original)
ret_alloca: mlirlite.Value* = None
if ret_is_void == 0:
ret_alloca = mlirlite.build_alloca(wb, ret_ty)
ret_zero: mlirlite.Value* = None
match ret_ty:
case mlirlite.LLVMType.Int(_):
ret_zero = mlirlite.ConstInt(pool, ret_ty, 0, "0")
case mlirlite.LLVMType.Float(_):
ret_zero = mlirlite.ConstFloat(pool, ret_ty, 0.0)
case _:
ret_zero = mlirlite.ConstNull(pool, ret_ty, "null")
if ret_zero is not None and ret_alloca is not None: mlirlite.build_store(wb, ret_zero, ret_alloca)
# 4. Function-name string constant
fn_str: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(wb, pool, mod, func_name, trans)
if fn_str is None: fn_str = mlirlite.ConstNull(pool, i8_ptr_ty, "null")
# 5. handler declare (declared when untranslated; fixed signature i32 (i8*,i8*,i32,i8*,i8*,i8*))
if self.Gen.CallGenBase.find_func_in_module(mod, handler_name) is None:
handler_d: mlirlite.Function* = mlirlite.create_declare(pool, mod, handler_name, i32_ty)
if handler_d is not None:
mlirlite.add_param(pool, handler_d, i8_ptr_ty, "ctx")
mlirlite.add_param(pool, handler_d, i8_ptr_ty, "func_name")
mlirlite.add_param(pool, handler_d, i32_ty, "phase")
mlirlite.add_param(pool, handler_d, i8_ptr_ty, "args_ptr")
mlirlite.add_param(pool, handler_d, i8_ptr_ty, "ret_ptr")
mlirlite.add_param(pool, handler_d, i8_ptr_ty, "decor_args_ptr")
# 6. Pre-phase: handler returns the call count n_calls
ph0: mlirlite.Value* = mlirlite.ConstInt(pool, i32_ty, 0, "0")
null_ret: mlirlite.Value* = mlirlite.ConstNull(pool, i8_ptr_ty, "null")
null_dec: mlirlite.Value* = mlirlite.ConstNull(pool, i8_ptr_ty, "null")
# Arg linked list (6 separate values): ctx_ptr -> fn_str -> 0 -> args_ptr -> null -> null.
# Note: no self-link (null.Next = null would make build_call loop forever).
if ctx_ptr is not None: mlirlite.value_set_next(ctx_ptr, fn_str)
if fn_str is not None: mlirlite.value_set_next(fn_str, ph0)
if ph0 is not None: mlirlite.value_set_next(ph0, args_ptr)
if args_ptr is not None: mlirlite.value_set_next(args_ptr, null_ret)
if null_ret is not None: mlirlite.value_set_next(null_ret, null_dec)
n_calls: mlirlite.Value* = mlirlite.build_call(wb, handler_name, ctx_ptr, 6, i32_ty, 0)
# 7. Counter alloca (VPC uses an alloca counter instead of phi)
cnt_alloca: mlirlite.Value* = mlirlite.build_alloca(wb, i32_ty)
cnt_zero: mlirlite.Value* = mlirlite.ConstInt(pool, i32_ty, 0, "0")
if cnt_alloca is not None and cnt_zero is not None: mlirlite.build_store(wb, cnt_zero, cnt_alloca)
mlirlite.build_br(wb, loop_h_bb)
# 8. loop.header:cur = load cnt; cur < n_calls → body / post
mlirlite.position_at_end(wb, loop_h_bb)
cur_i: mlirlite.Value* = mlirlite.build_load(wb, i32_ty, cnt_alloca)
loop_cond: mlirlite.Value* = mlirlite.build_icmp(wb, mlirlite.ICMP_SLT, cur_i, n_calls)
mlirlite.build_cond_br(wb, loop_cond, loop_b_bb, post_bb)
# 9. loop.body: read back params (the decorator may have modified them) -> call the original -> store ret
mlirlite.position_at_end(wb, loop_b_bb)
call_args: mlirlite.Value* = None
if n_params > 0:
k: int = 0
for k in range(int(n_params)):
fld_ty_k: mlirlite.LLVMType* = (mlirlite.LLVMType*)(param_tys[k])
fld_pk: mlirlite.Value* = mlirlite.build_gep_struct(wb, args_ty, fld_ty_k, args_alloca, k)
if fld_pk is None: continue
fld_vk: mlirlite.Value* = mlirlite.build_load(wb, fld_ty_k, fld_pk)
if fld_vk is not None:
mlirlite.value_set_next(fld_vk, None)
if call_args is None: call_args = fld_vk
else:
ct: mlirlite.Value* = call_args
while ct.Next is not None: ct = ct.Next
mlirlite.value_set_next(ct, fld_vk)
res_v: mlirlite.Value* = mlirlite.build_call(wb, orig_call_name, call_args, n_params, ret_ty, 0)
if ret_alloca is not None and res_v is not None: mlirlite.build_store(wb, res_v, ret_alloca)
nxt_i: mlirlite.Value* = mlirlite.build_add(wb, cur_i, mlirlite.ConstInt(pool, i32_ty, 1, "1"))
if nxt_i is not None and cnt_alloca is not None: mlirlite.build_store(wb, nxt_i, cnt_alloca)
mlirlite.build_br(wb, loop_h_bb)
# 10. post: post-phase handler -> clear the recursion flag -> return the result
mlirlite.position_at_end(wb, post_bb)
ret_ptr: mlirlite.Value* = null_ret
if ret_alloca is not None: ret_ptr = mlirlite.build_bitcast(wb, ret_alloca, i8_ptr_ty)
ph1: mlirlite.Value* = mlirlite.ConstInt(pool, i32_ty, 1, "1")
# Post-phase arg chain: ctx_ptr -> fn_str -> 1 -> args_ptr -> ret_ptr -> null
if ctx_ptr is not None: mlirlite.value_set_next(ctx_ptr, fn_str)
if fn_str is not None: mlirlite.value_set_next(fn_str, ph1)
if ph1 is not None: mlirlite.value_set_next(ph1, args_ptr)
if args_ptr is not None: mlirlite.value_set_next(args_ptr, ret_ptr)
if ret_ptr is not None: mlirlite.value_set_next(ret_ptr, null_dec)
mlirlite.build_call(wb, handler_name, ctx_ptr, 6, i32_ty, 0)
if rec_flag_ref is not None:
rec_zero2: mlirlite.Value* = mlirlite.ConstInt(pool, i8_ty, 0, "0")
if rec_zero2 is not None: mlirlite.build_store(wb, rec_zero2, rec_flag_ref)
if ret_is_void != 0: mlirlite.build_ret_void(wb)
else:
final_v: mlirlite.Value* = mlirlite.build_load(wb, ret_ty, ret_alloca)
if final_v is not None: mlirlite.build_ret(wb, final_v)
else: mlirlite.build_ret_void(wb)
return wrapper