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.
1348 lines
88 KiB
Plaintext
1348 lines
88 KiB
Plaintext
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 |