1131 lines
70 KiB
Plaintext
1131 lines
70 KiB
Plaintext
import lib.core.Gen.Generator as Generator
|
||
from stdint import *
|
||
import ast
|
||
import mlirlite
|
||
import memhub
|
||
import string
|
||
import stdio
|
||
import viperlib
|
||
import lib.core.VLogger as VLogger
|
||
import lib.core.Gen.Translator as HT
|
||
import lib.core.Gen.GenTypes as GenTypes
|
||
import lib.core.Gen.GenApi as GenApi
|
||
|
||
|
||
# TranslatorGenMixin - translation driver Mixin: module-level translation entry points
|
||
# (translate_children/create_wrapper_main/handle_module_level_var) and scope name management.
|
||
|
||
|
||
# TranslatorGenMixin - translation driver (module-level entry + scope name management)
|
||
novirtual class TranslatorGenMixin:
|
||
"""Translation driver Mixin."""
|
||
|
||
# _register_cexport_from_funcdef - Phase 1a: pre-register every CExport/State function
|
||
# name in the global table so Phase 1b's alphabetical order cannot miss bare-name calls.
|
||
Gen: Generator.Generator*
|
||
def _register_cexport_from_funcdef(self, trans: HT.Translator*, node: ast.AST*) -> int:
|
||
"""Pre-register CExport/State function names into the global table (no IR)."""
|
||
fd: ast.FunctionDef* = ast.FunctionDef*(node)
|
||
if fd is None or fd.name is None: return 0
|
||
if trans is None or trans.ModuleSha1 is None: return 0
|
||
|
||
# Check whether the return type carries a CExport or State marker
|
||
has_export: int = 0
|
||
has_state: int = 0
|
||
if fd.returns is not None:
|
||
has_export = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "CExport", 0)
|
||
has_state = self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0)
|
||
|
||
if has_export != 0 or has_state != 0: self.Gen.CallGenBase.register_cexport_func(trans.ModuleSha1, fd.name)
|
||
|
||
return 0
|
||
|
||
# translate_children - walk AST children and dispatch translation; shared state is
|
||
# read from trans, while imported_modules/from_imports are written back to trans.
|
||
def translate_children(self, trans: HT.Translator*, tree: ast.AST*) -> int:
|
||
"""Translate every child of tree.children; return the number of variables added."""
|
||
ch: list[ast.AST*]* = tree.children
|
||
if ch is None: return 0
|
||
cn_count: sizet = len(ch)
|
||
added_total: int = 0
|
||
for ci in range(int(cn_count)):
|
||
child: ast.AST* = ch.get(ci)
|
||
if child is None: continue
|
||
kd: int = child.kind()
|
||
# When _declare_only == 0, _translate_module_level already handled imports;
|
||
# skip here to avoid duplicating from_imports entries.
|
||
if trans._declare_only == 0 and (kd == ast.ASTKind.Import or kd == ast.ASTKind.ImportFrom): continue
|
||
if kd == ast.ASTKind.Import: self.Gen.ImportsGen.translate_import(trans, child)
|
||
elif kd == ast.ASTKind.ImportFrom:
|
||
self.Gen.ImportsGen.translate_import_from_module(trans, child)
|
||
self.Gen.ImportsGen.translate_import_from_names(trans, child)
|
||
elif kd == ast.ASTKind.FunctionDef:
|
||
# PEP 695 generic functions (def f[T]): skip at the definition site (the
|
||
# template is registered by Phase1Scan/Phase2) and monomorphize at call sites.
|
||
fd_tc: ast.FunctionDef* = ast.FunctionDef*(child)
|
||
if fd_tc is not None and fd_tc.type_params is not None: continue
|
||
# Phase 1a declare mode registers only CExport/State names; Phase 1b fully translates bodies.
|
||
if trans._declare_only == 0:
|
||
# 函数体翻译 = 编译器下潜进一个源码上下文:推一帧(def 位置),
|
||
# 让函数体内部的错误在 traceback 里回溯到"正在翻译哪个函数"(调用链)。
|
||
tr_f: str = self.Gen.TypeGenResolve.get_current_file()
|
||
# §13 NLL:记录当前函数体(谓词 NllFrameConfined 要遍历它)
|
||
prev_nf: ast.AST* = self.Gen.StructGen.NllCurFunc
|
||
self.Gen.StructGen.NllCurFunc = child
|
||
self.Gen.TypeGenBase.push_trace(tr_f, fd_tc.lineno)
|
||
added: int = self.Gen.FuncGenDef.translate_function_def(trans, child)
|
||
self.Gen.TypeGenBase.pop_trace()
|
||
self.Gen.StructGen.NllCurFunc = prev_nf
|
||
added_total += added
|
||
elif trans._declare_only == 1:
|
||
self._register_cexport_from_funcdef(trans, child)
|
||
# Prescan signature types to trigger generic specializations before mark1,
|
||
# so their Ty/Fields are not recycled by Phase 1b mark/reset (avoids UAF).
|
||
self._prescan_funcdef_signatures(trans, child)
|
||
elif kd == ast.ASTKind.ClassDef:
|
||
# ClassDef is handled directly at module level (no builder needed); skip in
|
||
# _declare_only=2 (import-scan) mode, which only handles import dependencies.
|
||
if trans._declare_only != 2: self.Gen.ClassGenDef.translate_class_def(trans, child)
|
||
elif trans._declare_only == 0 and trans._cur_builder is not None:
|
||
# Has a builder -> delegate statement dispatch to HandlesBody
|
||
added = self.Gen.StmtGen.translate_stmt(trans, child)
|
||
added_total += added
|
||
elif kd == ast.ASTKind.AnnAssign and trans._declare_only != 2:
|
||
# Module-level AnnAssign: skip in import-scan mode, handle only CDefine in
|
||
# struct-registration mode, and handle all in full-translation mode.
|
||
added = self.handle_module_level_var(trans, child)
|
||
added_total += added
|
||
elif trans._declare_only == 0 and kd == ast.ASTKind.Assign:
|
||
# Module-level Assign without a builder -> create a global (full-translation mode only)
|
||
added = self.handle_module_level_var(trans, child)
|
||
added_total += added
|
||
|
||
return added_total
|
||
|
||
# _prescan_funcdef_signatures - Phase 1a: resolve module-level function signatures to
|
||
# trigger generic specializations before mark1 (avoids dangling structs / UAF in Phase 2).
|
||
def _prescan_funcdef_signatures(self, trans: HT.Translator*, node: ast.AST*) -> None:
|
||
"""Pre-resolve module-level function signature types to trigger generic specializations."""
|
||
if trans is None or node is None: return
|
||
# The entry takes the base ast.AST* (matching sibling dispatchers) and narrows it here
|
||
# explicitly to ast.FunctionDef*: implicit downcasting is disallowed under strict rules.
|
||
fd: ast.FunctionDef* = ast.FunctionDef*(node)
|
||
if fd is None: return
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
if pool is None: return
|
||
imp: str = trans._imported_modules
|
||
fi: str = trans._from_imports
|
||
if fd.returns is not None: self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imp, fi, trans, 0)
|
||
if fd.args is not None:
|
||
ags: ast.Arguments* = ast.Arguments*(fd.args)
|
||
if ags.args is not None:
|
||
al: list[ast.AST*]* = ags.args
|
||
an: sizet = len(al)
|
||
ai: sizet
|
||
for ai in range(int(an)):
|
||
ag: ast.Arg* = ast.Arg*(al.get(ai))
|
||
if ag is not None and ag.annotation is not None: self.Gen.TypeGenResolve.resolve_annotation_type(pool, ag.annotation, imp, fi, trans, 0)
|
||
|
||
# _build_array_initializer_text - build an LLVM IR array initializer from an AST List,
|
||
# formatted as [N x elem_ty] [elem_ty val0, ...], for module-level t.CArray literals.
|
||
def _build_array_initializer_text(self, pool: memhub.MemBuddy*, var_ty: mlirlite.LLVMType*, list_node: ast.List*) -> str:
|
||
"""Build LLVM IR array initializer text from an AST List node; None on failure."""
|
||
if var_ty is None or list_node is None: return None
|
||
|
||
# Re-cast so the VPC compiler recognizes ast.List, avoiding type inference degrading
|
||
# to t.CPtr when init_list_node's initial value is None.
|
||
ln: ast.List* = ast.List*(list_node)
|
||
if ln is None: return None
|
||
|
||
# Extract array element type and count from var_ty
|
||
elem_ty: mlirlite.LLVMType* = None
|
||
arr_count: int = 0
|
||
match var_ty:
|
||
case mlirlite.LLVMType.Array(et, cnt):
|
||
elem_ty = et
|
||
arr_count = cnt
|
||
|
||
if elem_ty is None or arr_count <= 0: return None
|
||
|
||
# Determine the IR representation of the element type
|
||
elem_ir_ty: str = "i8"
|
||
is_int_elem: int = 1
|
||
match elem_ty:
|
||
case mlirlite.LLVMType.Int(bits):
|
||
is_int_elem = 1
|
||
if bits == 8: elem_ir_ty = "i8"
|
||
elif bits == 16: elem_ir_ty = "i16"
|
||
elif bits == 32: elem_ir_ty = "i32"
|
||
elif bits == 64: elem_ir_ty = "i64"
|
||
case _:
|
||
is_int_elem = 0
|
||
|
||
# Get list elements via ln.elts (not list_node.elts) to ensure correct type recognition
|
||
elts: list[ast.AST*]* = ln.elts
|
||
if elts is None: return None
|
||
elts_count: sizet = len(elts)
|
||
|
||
# More elements than the declared length -> fail-fast (never silently truncate or drop).
|
||
if sizet(arr_count) < elts_count:
|
||
self.Gen.TypeGenErr.fatal_error(list_node, f"array initializer has {elts_count} elements, exceeding the declared array length {arr_count}; silent truncation is not allowed", "OverflowError")
|
||
return None
|
||
|
||
# Non-integer element types (aggregates): only an all-zero list yields the whole
|
||
# "zeroinitializer"; any non-zero constant is not expressible as text -> None.
|
||
if is_int_elem == 0:
|
||
# Nested array element type: build sub-array initializer text recursively with the
|
||
# inner type omitted (inferred from the outer global type), which llc accepts.
|
||
is_nested_arr: int = 0
|
||
match elem_ty:
|
||
case mlirlite.LLVMType.Array(_, _):
|
||
is_nested_arr = 1
|
||
case _:
|
||
pass
|
||
if is_nested_arr != 0:
|
||
cnt_total_n: sizet = sizet(arr_count)
|
||
buf_size_n: sizet = 128 + cnt_total_n * 768
|
||
buf_n: str = pool.alloc(buf_size_n)
|
||
if buf_n is None: return None
|
||
written_n: int = viperlib.snprintf(buf_n, buf_size_n, "[")
|
||
pos_n: sizet = sizet(written_n)
|
||
# Sub-array type prefix (e.g. "[16 x i8]"): nested initializers must spell out the
|
||
# inner type, so serialize elem_ty via TypePrint for the [[16 x i8][...]] form.
|
||
elem_ty_str_n: str = pool.alloc_tagged(4096, memhub.DETSITE_TRANSLATORGEN_185)
|
||
if elem_ty_str_n is None: return None
|
||
mlirlite.TypePrint(elem_ty_str_n, 4096, elem_ty, pool)
|
||
for i_n in range(int(cnt_total_n)):
|
||
sub_n: str = None
|
||
if i_n < elts_count:
|
||
en_n: ast.AST* = elts.get(i_n)
|
||
if en_n is not None:
|
||
ek_n: int = en_n.kind()
|
||
if ek_n == ast.ASTKind.List: sub_n = self._build_array_initializer_text(pool, elem_ty, ast.List*(en_n))
|
||
elif ek_n == ast.ASTKind.Set:
|
||
# Brace literals (e.g. font tables {0x00, ...}): Set.elts is
|
||
# isomorphic to List, so wrap it in a temp List and recurse.
|
||
se_n: ast.Set* = ast.Set*(en_n)
|
||
if se_n is not None and se_n.elts is not None: sub_n = self._build_array_initializer_text(pool, elem_ty, ast.List(pool, se_n.elts, ast.ASTCtx.Load))
|
||
if sub_n is None:
|
||
# Missing/unevaluable element: zero-initialize the whole sub-array
|
||
sub_n = "zeroinitializer"
|
||
if i_n > 0:
|
||
written_n = viperlib.snprintf(buf_n + pos_n, buf_size_n - pos_n, ", ")
|
||
pos_n += sizet(written_n)
|
||
written_n = viperlib.snprintf(buf_n + pos_n, buf_size_n - pos_n, "%s%s", elem_ty_str_n, sub_n)
|
||
pos_n += sizet(written_n)
|
||
viperlib.snprintf(buf_n + pos_n, buf_size_n - pos_n, "]")
|
||
return buf_n
|
||
for zi in range(int(elts_count)):
|
||
ze: ast.AST* = elts.get(zi)
|
||
if ze is None: continue
|
||
zk: int = ze.kind()
|
||
if zk == ast.ASTKind.Constant:
|
||
zc: ast.Constant* = ast.Constant*(ze)
|
||
if zc is None: continue
|
||
# Treat None (CONST_NONE) as zero: function-pointer arrays use [None] to mean
|
||
# all-empty, so None must normalize to zero fill rather than fail-fast.
|
||
if zc.const_kind == ast.CONST_NONE: continue
|
||
if zc.const_kind != ast.CONST_INT or zc.int_val != 0: return None
|
||
elif zk == ast.ASTKind.UnaryOp:
|
||
if self.Gen.AssignGen.extract_cdefine_int_value(ze) != 0: return None
|
||
else: return None
|
||
return "zeroinitializer"
|
||
|
||
# Size the buffer from the declared length arr_count (missing elements are zero-filled,
|
||
# so it may exceed elts_count); each element needs about 26 chars.
|
||
cnt_total: sizet = sizet(arr_count)
|
||
buf_size: sizet = 64 + cnt_total * 32
|
||
buf: str = pool.alloc(buf_size)
|
||
if buf is None: return None
|
||
|
||
# Write the "[" prefix (the type prefix is emitted by _print_global; only the element list here)
|
||
written: int = viperlib.snprintf(buf, buf_size, "[")
|
||
pos: sizet = sizet(written)
|
||
|
||
# Write elements up to the declared length arr_count, zero-padding short lists so the
|
||
# initializer element count always matches the type llc expects.
|
||
for i in range(int(cnt_total)):
|
||
elem_val: int64 = 0
|
||
if i < elts_count:
|
||
elem_node: ast.AST* = elts.get(i)
|
||
# Read int_val directly from the Constant node (avoids int truncation in extract_cdefine_int_value)
|
||
if elem_node is not None:
|
||
ek: int = elem_node.kind()
|
||
if ek == ast.ASTKind.Constant:
|
||
ec: ast.Constant* = ast.Constant*(elem_node)
|
||
# CONST_CHAR literals (e.g. `_ps2_set1 = ['\x1b', '1', ...]`): int_val is
|
||
# the character code (unrecognized chars would all be compiled as 0).
|
||
if ec is not None and (ec.const_kind == ast.CONST_INT or ec.const_kind == ast.CONST_CHAR): elem_val = ec.int_val
|
||
# Single-char CONST_STR (double quotes like "'" / '"'): the parser treats
|
||
# them as CONST_STR; take the first char code when strlen == 1, else 0.
|
||
elif ec is not None and ec.const_kind == ast.CONST_STR:
|
||
if ec.str_val is not None and string.strlen(ec.str_val) == 1: elem_val = int64(ec.str_val[0])
|
||
elif ek == ast.ASTKind.UnaryOp:
|
||
# Negative: UnaryOp(USub, Constant)
|
||
elem_val = self.Gen.AssignGen.extract_cdefine_int_value(elem_node)
|
||
if i > 0:
|
||
written = viperlib.snprintf(buf + pos, buf_size - pos, ", ")
|
||
pos += sizet(written)
|
||
written = viperlib.snprintf(buf + pos, buf_size - pos, "%s %lld", elem_ir_ty, elem_val)
|
||
pos += sizet(written)
|
||
|
||
# Write the closing "]" suffix
|
||
viperlib.snprintf(buf + pos, buf_size - pos, "]")
|
||
return buf
|
||
|
||
# _build_string_array_initializer_text - initialize a char array global from a string literal:
|
||
# t.CArray[char, N] = "..." -> [i8 b0, ..., i8 0, ...] zero-padded; empty -> zeroinitializer.
|
||
def _build_string_array_initializer_text(self, pool: memhub.MemBuddy*, var_ty: mlirlite.LLVMType*, str_val: str) -> str:
|
||
"""Build initializer text for an array global from a string literal; None on failure."""
|
||
if var_ty is None or str_val is None: return None
|
||
arr_count: int = 0
|
||
match var_ty:
|
||
case mlirlite.LLVMType.Array(_, cnt):
|
||
arr_count = cnt
|
||
if arr_count <= 0: return None
|
||
slen: sizet = string.strlen(str_val)
|
||
if slen == 0: return "zeroinitializer"
|
||
buf_size: sizet = 64 + sizet(arr_count) * 16
|
||
buf: str = pool.alloc(buf_size)
|
||
if buf is None: return None
|
||
written: int = viperlib.snprintf(buf, buf_size, "[")
|
||
pos: sizet = sizet(written)
|
||
for i in range(int(sizet(arr_count))):
|
||
if i > 0:
|
||
written = viperlib.snprintf(buf + pos, buf_size - pos, ", ")
|
||
pos += sizet(written)
|
||
bv: int64 = 0
|
||
if i < slen: bv = int64(str_val[i])
|
||
written = viperlib.snprintf(buf + pos, buf_size - pos, "i8 %lld", bv)
|
||
pos += sizet(written)
|
||
viperlib.snprintf(buf + pos, buf_size - pos, "]")
|
||
return buf
|
||
|
||
# _build_string_ptr_initializer - initialize a pointer-typed global from a string literal by
|
||
# creating an internal constant `@.str.{var_name}` and returning a GEP initializer text.
|
||
def _build_string_ptr_initializer(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, var_name: str, str_val: str) -> str:
|
||
"""Create an internal constant global for a string literal; return GEP init text."""
|
||
if str_val is None or var_name is None: return None
|
||
|
||
# Build the internal string constant name: .str.{var_name}
|
||
str_name_buf: str = pool.alloc(64)
|
||
if str_name_buf is None: return None
|
||
viperlib.snprintf(str_name_buf, 64, ".str.%s", var_name)
|
||
|
||
# Create the string constant global via mlirlite.create_global_string
|
||
str_gv: mlirlite.GlobalVariable* = mlirlite.create_global_string(pool, mod, str_name_buf, str_val)
|
||
if str_gv is None: return None
|
||
|
||
# Generate the GEP initializer text
|
||
str_len: sizet = string.strlen(str_val)
|
||
init_buf: str = pool.alloc(128)
|
||
if init_buf is None: return None
|
||
viperlib.snprintf(init_buf, 128, "getelementptr inbounds ([%d x i8], [%d x i8]* @%s, i32 0, i32 0)", str_len + 1, str_len + 1, str_name_buf)
|
||
return init_buf
|
||
|
||
def _stg_field_zero_text(self, pool: memhub.MemBuddy*, fty: mlirlite.LLVMType*, ty_str: str) -> str:
|
||
"""Build the zero initializer text for a storage struct field by LLVM type."""
|
||
if fty is None or ty_str is None: return None
|
||
# Size by the actual type text: TypePrint inlines full struct bodies (up to 4096 bytes), and
|
||
# the historic fixed 160-byte buffer was written with a 576-byte snprintf limit, so a long
|
||
# type text overflowed the pool block and corrupted the neighbouring free block.
|
||
cap: sizet = string.strlen(ty_str) + 24
|
||
buf: str = pool.alloc(cap)
|
||
if buf is None: return None
|
||
match fty:
|
||
case mlirlite.LLVMType.Ptr(_):
|
||
viperlib.snprintf(buf, cap, "%s null", ty_str)
|
||
case mlirlite.LLVMType.Int(_):
|
||
viperlib.snprintf(buf, cap, "%s 0", ty_str)
|
||
case mlirlite.LLVMType.Float(_):
|
||
viperlib.snprintf(buf, cap, "%s 0.0", ty_str)
|
||
case _:
|
||
# Aggregates such as Array / Struct -> zeroinitializer
|
||
viperlib.snprintf(buf, cap, "%s zeroinitializer", ty_str)
|
||
return buf
|
||
|
||
# _default_val_const_text - field default value -> LLVM constant element text; only foldable
|
||
# integer defaults (CONST_INT/CONST_CHAR, negated literals) are handled; others return None.
|
||
def _default_val_const_text(self, pool: memhub.MemBuddy*, fty: mlirlite.LLVMType*, ty_str: str, dval: ast.AST*) -> str:
|
||
"""Render a field default as "ty value" text; None when not foldable."""
|
||
if dval is None or fty is None or ty_str is None: return None
|
||
is_int: int = 0
|
||
dv_int: int64 = 0
|
||
if dval.kind() == ast.ASTKind.Constant:
|
||
dc: ast.Constant* = ast.Constant*(dval)
|
||
if dc is not None:
|
||
if dc.const_kind == ast.CONST_INT or dc.const_kind == ast.CONST_CHAR:
|
||
dv_int = dc.int_val
|
||
is_int = 1
|
||
elif dval.kind() == ast.ASTKind.UnaryOp:
|
||
# Negative / bitwise-invert literals (e.g. -1 / 0xFFFFFFFFFFFFFFFF)
|
||
dv_int = self.Gen.AssignGen.extract_cdefine_int_value(dval)
|
||
is_int = 1
|
||
if is_int == 0: return None
|
||
# Only integer fields accept an integer constant; float/pointer/aggregate fall back to zero
|
||
match fty:
|
||
case mlirlite.LLVMType.Int(_):
|
||
pass
|
||
case _:
|
||
return None
|
||
buf: str = pool.alloc(576)
|
||
if buf is None: return None
|
||
viperlib.snprintf(buf, 576, "%s %lld", ty_str, dv_int)
|
||
return buf
|
||
|
||
# _build_stg_struct_body_text - storage struct field element text (no braces), used by
|
||
# _build_stg_struct_init_text and vtable storage; None when all field defaults are zero.
|
||
def _build_stg_struct_body_text(self, pool: memhub.MemBuddy*, se: GenTypes.StructEntry*, start_idx: int) -> str:
|
||
"""Render "e0, e1, ..." element text from field defaults; None when all zero."""
|
||
if se is None or se.Fields is None: return None
|
||
fc: int = se.FieldCount
|
||
if fc <= start_idx: return None
|
||
# Each field element = type prefix (up to _TY_TEXT_CAP bytes in MLIR form) + value text;
|
||
# the old 160-byte estimate truncated longer types.
|
||
buf_size: sizet = 256 + sizet(fc) * 576
|
||
buf: str = pool.alloc(buf_size)
|
||
if buf is None: return None
|
||
pos: sizet = 0
|
||
first: int = 1
|
||
has_nonzero: int = 0
|
||
i: int = start_idx
|
||
while i < fc:
|
||
fe: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(se, i)
|
||
i += 1
|
||
if fe is None or fe.Ty is None: continue
|
||
ty_str: str = pool.alloc_tagged(4096, memhub.DETSITE_TRANSLATORGEN_384)
|
||
if ty_str is None: continue
|
||
mlirlite.TypePrint(ty_str, 4096, fe.Ty, pool)
|
||
elem: str = self._default_val_const_text(pool, fe.Ty, ty_str, fe.DefaultVal)
|
||
is_zero: int = 0
|
||
if elem is None:
|
||
elem = self._stg_field_zero_text(pool, fe.Ty, ty_str)
|
||
is_zero = 1
|
||
if elem is None: continue
|
||
written: int = 0
|
||
if first == 0:
|
||
written = viperlib.snprintf(buf + pos, buf_size - pos, ", ")
|
||
pos += sizet(written)
|
||
written = viperlib.snprintf(buf + pos, buf_size - pos, "%s", elem)
|
||
pos += sizet(written)
|
||
first = 0
|
||
if is_zero == 0: has_nonzero = 1
|
||
if has_nonzero == 0: return None
|
||
return buf
|
||
|
||
# _build_stg_struct_init_text - full constant initializer for a storage struct (all fields,
|
||
# with defaults) for module-level `X: T = T()`; None when no non-zero default exists.
|
||
def _build_stg_struct_init_text(self, pool: memhub.MemBuddy*, se: GenTypes.StructEntry*, start_idx: int) -> str:
|
||
"""Render a full struct initializer from field defaults; None when all zero."""
|
||
body: str = self._build_stg_struct_body_text(pool, se, start_idx)
|
||
if body is None: return None
|
||
body_len: sizet = sizet(string.strlen(body))
|
||
buf: str = pool.alloc(body_len + 8)
|
||
if buf is None: return None
|
||
viperlib.snprintf(buf, body_len + 8, "{ %s }", body)
|
||
return buf
|
||
|
||
# Module-level global attr.* extraction: attributes are marker wrappers
|
||
# (`attr.section(".x")[T]`) written directly to gv.Section/Align/Linkage fields.
|
||
def _scan_global_tattr(self, node: ast.AST*, want_name: str) -> ast.AST*:
|
||
"""Find the attr.<want_name> marker in a global type annotation; None when absent."""
|
||
if node is None: return None
|
||
if self.Gen.ClassGenBase._tattr_name(node, want_name) != 0: return node
|
||
if node.kind() == ast.ASTKind.Subscript:
|
||
sub_t: ast.Subscript* = ast.Subscript*(node)
|
||
hit_s: ast.AST* = self._scan_global_tattr(sub_t.value, want_name)
|
||
if hit_s is not None: return hit_s
|
||
return self._scan_global_tattr(sub_t.slice, want_name)
|
||
return None
|
||
def _extract_global_attr_section(self, node: ast.AST*, var_name: str) -> str:
|
||
"""Extract the section name from `T | t.attr.section("xxx")`; fail-fast when empty."""
|
||
hit: ast.AST* = self._scan_global_tattr(node, "section")
|
||
if hit is None: return None
|
||
sec_val: str = self.Gen.FuncGenBase._get_attr_call_arg_str(hit)
|
||
if sec_val is None or sec_val[0] == 0:
|
||
self.Gen.TypeGenErr.fatal_error(node, f"section name of t.attr.section(...) for module-level variable '{var_name}' is missing or empty", "ValueError")
|
||
return None
|
||
return sec_val
|
||
def _extract_global_attr_aligned(self, node: ast.AST*, var_name: str) -> int:
|
||
"""Extract alignment bytes from `T | t.attr.aligned(N)`; 0 when absent, fail-fast when invalid."""
|
||
hit: ast.AST* = self._scan_global_tattr(node, "aligned")
|
||
if hit is None: return 0
|
||
al_val: int = self.Gen.ClassGenBase._tattr_aligned_int(hit)
|
||
if al_val <= 0:
|
||
self.Gen.TypeGenErr.fatal_error(node, f"t.attr.aligned(N) for module-level variable '{var_name}' requires a positive integer literal N", "ValueError")
|
||
return 0
|
||
return al_val
|
||
def _extract_global_attr_weak(self, node: ast.AST*) -> int:
|
||
"""Detect `T | t.attr.weak` / `t.attr.weak()` (1 present, 0 absent)."""
|
||
if self._scan_global_tattr(node, "weak") is not None: return 1
|
||
return 0
|
||
|
||
# handle_module_level_var - module-level var declaration -> LLVM global (int/list/string init).
|
||
# _RegisterStaticModuleVar - register (name, def module SHA1) of t.CStatic vars for cross-module access checks.
|
||
def _RegisterStaticModuleVar(self, trans: HT.Translator*, annot_node: ast.AST*, var_name: str) -> int:
|
||
if trans is None or annot_node is None or var_name is None: return 0
|
||
if trans.ModuleSha1 is None: return 0
|
||
if self.Gen.TypeGenErr.has_decorator_marker(annot_node, "CStatic", 0) == 0: return 0
|
||
self.Gen.CallGenBase.RegisterStaticSymbol(trans.ModuleSha1, var_name)
|
||
return 0
|
||
|
||
|
||
def handle_module_level_var(self, trans: HT.Translator*, node: ast.AST*) -> int:
|
||
"""Handle a module-level variable declaration by creating its global."""
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
mod: mlirlite.LLVMModule* = trans.Module
|
||
|
||
# CDefine annotation: a compile-time constant, no global is created; register it in the
|
||
# global CDefine table so t.CArray[elem_ty, NAME] can resolve it.
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.AnnAssign:
|
||
aa_cd: ast.AnnAssign* = ast.AnnAssign*(node)
|
||
if aa_cd is not None and aa_cd.target is not None:
|
||
if aa_cd.target.kind() == ast.ASTKind.Name:
|
||
nm_cd: ast.Name* = ast.Name*(aa_cd.target)
|
||
if nm_cd.id is not None:
|
||
if self.Gen.AssignGen.is_cdefine_annotation(aa_cd.annotation) != 0:
|
||
# Auto-detect and register float/int CDefine values (e.g. vipermath U_M_PI)
|
||
self.Gen.AssignGen._register_cdefine_from_value(pool, nm_cd.id, aa_cd.value, aa_cd.annotation)
|
||
return 0
|
||
# CTypedef annotation: no global is created (would duplicate the symbol);
|
||
# register it in the CTypedef table instead, or annotation lookups fail.
|
||
if self.Gen.TypeGenCType._is_ctypedef_annotation(aa_cd.annotation) != 0:
|
||
ctd_str: str = self.Gen.TypeGenResolve._get_type_name_from_annotation(pool, aa_cd.value)
|
||
if ctd_str is not None: self.Gen.TypeGenCType.register_ctypedef(pool, nm_cd.id, ctd_str)
|
||
return 0
|
||
|
||
# In _declare_only=1 (struct-registration) mode only CDefine is handled; reaching here
|
||
# means it is not a CDefine, so skip.
|
||
if trans._declare_only == 1: return 0
|
||
|
||
var_name: str = None
|
||
var_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
resolved_stg_ty: mlirlite.LLVMType* = None # Object-class global: static storage type (BSS instance)
|
||
resolved_stg_entry: GenTypes.StructEntry* = None # Object-class global: matching StructEntry (for vtable init)
|
||
init_val: int64 = 0
|
||
init_fval: double = 0.0 # Value when init_kind==4 (float literal)
|
||
init_kind: int = 0 # 0=none, 1=int, 2=list, 3=str, 4=float, 5=symbol address
|
||
init_sym_node: ast.AST* = None # Operand of `= &global` (init_kind==5)
|
||
init_str_val: str = None
|
||
init_list_node: ast.List* = None
|
||
# Explicit `= None` init keeps lazy-initialization pointer semantics: the global must stay
|
||
# NULL, so no BSS storage is created (storage would break the `!= None` guard).
|
||
is_none_init: int = 0
|
||
annot_node: ast.AST* = None # Annotation node (used to detect t.CExport markers)
|
||
|
||
if k == ast.ASTKind.AnnAssign:
|
||
aa: ast.AnnAssign* = ast.AnnAssign*(node)
|
||
if aa is None or aa.target is None: return 0
|
||
if aa.target.kind() != ast.ASTKind.Name: return 0
|
||
nm: ast.Name* = ast.Name*(aa.target)
|
||
var_name = nm.id
|
||
annot_node = aa.annotation
|
||
# f-string escape: a module-level initial value is global, but the f-string result
|
||
# points into the current function's stack buffer -> fail-fast (use escape_fstring_copy).
|
||
GenApi.fail_if_fstring_escape(aa.value, "global variable initialization")
|
||
# t.CStatic vars (no cross-module use) are registered as (name, def module SHA1);
|
||
# the logic stays inside _RegisterStaticModuleVar to avoid disturbing type inference.
|
||
self._RegisterStaticModuleVar(trans, annot_node, var_name)
|
||
# Module-level t.Callable function pointers register (name -> return type) here so
|
||
# indirect calls resolve; done before any body translation, same as in-function AnnAssign.
|
||
if annot_node is not None:
|
||
ml_callable_ret: mlirlite.LLVMType* = self.Gen.TypeGenErr.try_extract_callable_ret(pool, annot_node, trans._imported_modules,trans._from_imports, trans)
|
||
if ml_callable_ret is not None:
|
||
# 0 = success/idempotent; non-zero already fatal-errored inside register_func_ptr_sig,
|
||
# so this branch is only defensive.
|
||
if self.Gen.CallGenLeafKit.register_func_ptr_sig(pool, var_name, ml_callable_ret, aa) != 0: return 0
|
||
# Resolve the type
|
||
if aa.annotation is not None:
|
||
# Value-reference markers are parameter-only: a module variable `x: &T` fails fast.
|
||
self.Gen.TypeGenResolve.reject_ref_marker_position(aa.annotation, trans._imported_modules, "a module-level variable declaration")
|
||
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, aa.annotation, trans._imported_modules, trans._from_imports, trans, 0)
|
||
if resolved is not None:
|
||
# Object-class module vars (@t.Object/@t.CVTable: object-as-pointer) wrap the
|
||
# resolved struct value in Ptr; pure t.CStruct keeps a value instance.
|
||
if resolved.kind() == mlirlite.LLVMType.Struct:
|
||
gv_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(resolved)
|
||
# [TEMP-DIAG] 模块级 struct 全局的类型决策(定位完删除):
|
||
# hasnew/hasvtable/isoop 任一为真 ⇒ 全局被包成 Ptr(指针槽),
|
||
# 与"局部变量=值本体"不一致 ⇒ `CAM = V3(...)` 被当成把栈地址交给全局。
|
||
gv_hn: int = -1; gv_hv: int = -1; gv_oo: int = -1
|
||
if gv_entry is not None:
|
||
gv_hn = gv_entry.HasNew; gv_hv = gv_entry.HasVTable; gv_oo = gv_entry.IsOOP
|
||
stdio.printf("[GVAR-DIAG] name=%s hasnew=%d hasvtable=%d isoop=%d\n",
|
||
var_name, gv_hn, gv_hv, gv_oo)
|
||
if gv_entry is not None and gv_entry.HasNew != 0:
|
||
# 只有定义了 `__new__`(自定义分配器,对象存储可能不在本帧)的类才**必须**
|
||
# 以指针表示。仅"有方法 / 带 vtable"(IsOOP / HasVTable)的类,值本体内联
|
||
# 存储同样可工作(方法调用把 &obj 作 self 传入,vptr 亦可内联在值里),
|
||
# 因此模块级全局也按**值本位**存储,与局部变量(`alloca %T`)语义一致:
|
||
# `CAM: V3` ⇒ `@CAM = global %V3`,`CAM = V3(...)` 即整块就地写入
|
||
# (不再产生"把本帧对象地址存进全局"的悬垂,也不再让
|
||
# check_return_frame_decl 误报 LifetimeError)。
|
||
# Object-class global (object-as-pointer): remember the Struct value type
|
||
# so a BSS storage global can hold the instance; otherwise @g is NULL.
|
||
resolved_stg_ty = resolved
|
||
resolved_stg_entry = gv_entry
|
||
resolved = mlirlite.Ptr(pool, resolved)
|
||
elif resolved.kind() == mlirlite.LLVMType.Ptr:
|
||
# Object-class union annotation (T* -> T*): also create storage, else the
|
||
# virtual call loads NULL @g and faults; var_ty stays Ptr.
|
||
pointee_uv: mlirlite.LLVMType* = resolved.Pointee
|
||
if pointee_uv is not None and pointee_uv.kind() == mlirlite.LLVMType.Struct:
|
||
gv_entry_u: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(pointee_uv)
|
||
if gv_entry_u is not None and (gv_entry_u.HasNew != 0 or gv_entry_u.HasVTable != 0 or gv_entry_u.IsOOP != 0):
|
||
resolved_stg_ty = pointee_uv
|
||
resolved_stg_entry = gv_entry_u
|
||
var_ty = resolved
|
||
else:
|
||
# [S4 fail-fast] An annotated module var that fails to resolve errors out instead
|
||
# of silently using i32 (a wrong type corrupts storage size/field access/ABI).
|
||
self.Gen.TypeGenErr.fatal_error(aa.annotation, f"failed to resolve the type annotation of module-level variable '{var_name}'", "NameError")
|
||
return 0
|
||
# Parse the initial value (integer, negative literal, list literal, string literal)
|
||
if aa.value is not None:
|
||
val_kind: int = aa.value.kind()
|
||
if val_kind == ast.ASTKind.Constant:
|
||
cn: ast.Constant* = ast.Constant*(aa.value)
|
||
if cn.const_kind == ast.CONST_NONE: is_none_init = 1
|
||
elif cn.const_kind == ast.CONST_INT:
|
||
init_val = cn.int_val
|
||
init_kind = 1
|
||
elif cn.const_kind == ast.CONST_STR:
|
||
init_str_val = cn.str_val
|
||
init_kind = 3
|
||
elif cn.const_kind == ast.CONST_FLOAT:
|
||
# Float literal: init_kind must be set or the global degrades to
|
||
# zeroinitializer; the value is re-read from source at init_kind==4 below.
|
||
init_kind = 4
|
||
elif val_kind == ast.ASTKind.UnaryOp:
|
||
# Negative/bitwise-invert literal: the AST is UnaryOp(USub, Constant); unhandled
|
||
# it would compile to zeroinitializer and lose the initial value. `&global`
|
||
# (UnaryOp(AddrOf, Name)) is a symbol-address initializer instead and must not be
|
||
# fed to the integer constant extractor.
|
||
uop_g: ast.UnaryOp* = ast.UnaryOp*(aa.value)
|
||
if uop_g is not None and uop_g.op == ast.OpKind.AddrOf:
|
||
init_sym_node = uop_g.operand
|
||
init_kind = 5
|
||
else:
|
||
# 负浮点字面量 `-100.0` = UnaryOp(USub, Constant(float)):按**浮点初始化**
|
||
# 处理(init_kind=4,值稍后从源码重读,故负号会被保留);
|
||
# 否则会被送进整型常量提取器 ⇒ TypeError: CDefine value extraction failed。
|
||
is_neg_f: int = 0
|
||
if uop_g.op == ast.OpKind.USub and uop_g.operand is not None and uop_g.operand.kind() == ast.ASTKind.Constant:
|
||
opf_d: ast.Constant* = ast.Constant*(uop_g.operand)
|
||
if opf_d.const_kind == ast.CONST_FLOAT: is_neg_f = 1
|
||
if is_neg_f != 0:
|
||
init_kind = 4
|
||
else:
|
||
init_val = self.Gen.AssignGen.extract_cdefine_int_value(aa.value)
|
||
init_kind = 1
|
||
elif val_kind == ast.ASTKind.List:
|
||
init_list_node = ast.List*(aa.value)
|
||
init_kind = 2
|
||
elif k == ast.ASTKind.Assign:
|
||
asgn: ast.Assign* = ast.Assign*(node)
|
||
if asgn is None or asgn.targets is None: return 0
|
||
targets: list[ast.AST*]* = asgn.targets
|
||
if len(targets) < 1: return 0
|
||
t0: ast.AST* = targets.get(0)
|
||
if t0 is None or t0.kind() != ast.ASTKind.Name: return 0
|
||
nm2: ast.Name* = ast.Name*(t0)
|
||
var_name = nm2.id
|
||
if asgn.value is not None:
|
||
val_kind2: int = asgn.value.kind()
|
||
if val_kind2 == ast.ASTKind.Constant:
|
||
cn2: ast.Constant* = ast.Constant*(asgn.value)
|
||
if cn2.const_kind == ast.CONST_NONE: is_none_init = 1
|
||
elif cn2.const_kind == ast.CONST_INT:
|
||
init_val = cn2.int_val
|
||
init_kind = 1
|
||
elif cn2.const_kind == ast.CONST_STR:
|
||
init_str_val = cn2.str_val
|
||
init_kind = 3
|
||
elif cn2.const_kind == ast.CONST_FLOAT:
|
||
# Unannotated module-level float assignment: the literal itself implies the
|
||
# type, so promote to double (default var_ty is i32).
|
||
init_kind = 4
|
||
var_ty = mlirlite.Double(pool)
|
||
elif val_kind2 == ast.ASTKind.UnaryOp:
|
||
# Negative literal (same as the AnnAssign branch above): UnaryOp(USub, Constant).
|
||
# `= &global` is a symbol-address initializer instead (see the AnnAssign branch).
|
||
uop_g2: ast.UnaryOp* = ast.UnaryOp*(asgn.value)
|
||
if uop_g2 is not None and uop_g2.op == ast.OpKind.AddrOf:
|
||
init_sym_node = uop_g2.operand
|
||
init_kind = 5
|
||
else:
|
||
# 同 AnnAssign 分支:负浮点字面量按**浮点初始化**处理;无注解时还要把类型
|
||
# 推断为 double(否则默认 i32),否则 value 会被送进整型常量提取器 ⇒ fatal。
|
||
is_neg_f2: int = 0
|
||
if uop_g2.op == ast.OpKind.USub and uop_g2.operand is not None and uop_g2.operand.kind() == ast.ASTKind.Constant:
|
||
opf_d2: ast.Constant* = ast.Constant*(uop_g2.operand)
|
||
if opf_d2.const_kind == ast.CONST_FLOAT: is_neg_f2 = 1
|
||
if is_neg_f2 != 0:
|
||
init_kind = 4
|
||
var_ty = mlirlite.Double(pool)
|
||
else:
|
||
init_val = self.Gen.AssignGen.extract_cdefine_int_value(asgn.value)
|
||
init_kind = 1
|
||
elif val_kind2 == ast.ASTKind.List:
|
||
init_list_node = ast.List*(asgn.value)
|
||
init_kind = 2
|
||
elif val_kind2 == ast.ASTKind.Attribute:
|
||
# Module-level re-export alias (e.g. `_read32 = types._read32`): the RHS is an
|
||
# entity reference with no value semantics, so emit no LLVM global (same as __all__).
|
||
return 0
|
||
|
||
if var_name is None: return 0
|
||
|
||
# __all__ is Python module metadata with no runtime semantics, so skip global creation;
|
||
# otherwise the unannotated list-of-strings global would falsely fail initializer building.
|
||
if string.strcmp(var_name, "__all__") == 0: return 0
|
||
|
||
# Float literal init (init_kind==4): re-read the value from source by name instead of the
|
||
# AST float_val (which pool ops may overwrite); also validate that the annotation is float.
|
||
if init_kind == 4:
|
||
is_fty_g: int = 0
|
||
match var_ty:
|
||
case mlirlite.LLVMType.Float(_):
|
||
is_fty_g = 1
|
||
case _:
|
||
pass
|
||
if is_fty_g == 0:
|
||
self.Gen.TypeGenErr.fatal_error(node, f"module-level variable '{var_name}' is initialized with a float literal, but its annotation is not a float type", "TypeError")
|
||
return 0
|
||
if self.Gen.AssignGen._recover_float_literal(var_name, &init_fval, 0) != 1:
|
||
self.Gen.TypeGenErr.fatal_error(node, f"failed to recover the initializer value of module-level float variable '{var_name}' from the source file", "InternalCompilerError")
|
||
return 0
|
||
|
||
# Check whether it is already registered
|
||
existing: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, var_name)
|
||
if existing is not None: return 0
|
||
|
||
# Create the global @var_name, first ensuring opaque declarations for any cross-module
|
||
# struct type in var_ty, or llc reports "use of undefined type named 'sha1.ClassName'".
|
||
mlirlite.module_ensure_opaque_for_type(mod, pool, var_ty)
|
||
# Module-level globals are SHA1-prefixed (like function mangling) to avoid duplicate
|
||
# symbols; SymTab keys stay bare, and t.CExport globals keep bare names for asm references.
|
||
is_global_cexport: int = 0
|
||
if annot_node is not None: is_global_cexport = self.Gen.TypeGenErr.has_decorator_marker(annot_node, "CExport", 0)
|
||
# extern globals (annotation has t.State/t.CExtern) are declared only: `@bare = external
|
||
# global T` with external linkage, no initializer and no storage; export instead defines them.
|
||
is_global_extern: int = 0
|
||
if annot_node is not None:
|
||
is_global_extern = self.Gen.TypeGenErr.has_decorator_marker(annot_node, "CExtern", 0)
|
||
if is_global_extern == 0: is_global_extern = self.Gen.TypeGenErr.has_decorator_marker(annot_node, "State", 0)
|
||
# Global-side t.attr.* attributes (`T | t.attr.xxx` union): section->gv.Section,
|
||
# aligned->gv.Align, weak->gv.Linkage; only present for AnnAssign (annot_node not None).
|
||
gv_attr_section: str = None
|
||
gv_attr_align: int = 0
|
||
gv_attr_weak: int = 0
|
||
if annot_node is not None:
|
||
gv_attr_section = self._extract_global_attr_section(annot_node, var_name)
|
||
gv_attr_align = self._extract_global_attr_aligned(annot_node, var_name)
|
||
gv_attr_weak = self._extract_global_attr_weak(annot_node)
|
||
mangled_name: str = var_name
|
||
if is_global_cexport == 0 and is_global_extern == 0: mangled_name = self.Gen.FuncGenBase._mangle_name(trans, var_name)
|
||
gv: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, mangled_name, var_ty)
|
||
if gv is None: return 0
|
||
mlirlite.module_add_global(mod, gv)
|
||
|
||
# Section name: write the field directly (_print_global adds the quotes for `, section "xxx"`).
|
||
if gv_attr_section is not None: mlirlite.global_set_section(gv, gv_attr_section)
|
||
|
||
# B5: apply alignment to global instances of @c.Attribute(t.attr.aligned(N)) classes.
|
||
# An explicit variable-level `T | t.attr.aligned(N)` wins over the class-level struct Align.
|
||
al_ty: mlirlite.LLVMType* = var_ty
|
||
match var_ty:
|
||
case mlirlite.LLVMType.Ptr(pointee_al):
|
||
al_ty = pointee_al
|
||
case _:
|
||
pass
|
||
al_entry: GenTypes.StructEntry* = None
|
||
if al_ty is not None and al_ty.kind() == mlirlite.LLVMType.Struct: al_entry = self.Gen.StructGen.find_struct_by_type(al_ty)
|
||
if gv_attr_align > 0: gv.Align = gv_attr_align
|
||
elif al_entry is not None and al_entry.Align > 0: gv.Align = al_entry.Align
|
||
|
||
# Set the initializer (clearing external linkage, since LLVM 22+ forbids external globals
|
||
# with initializers); extern globals are the exception and stay external.
|
||
if is_global_extern != 0: gv.Linkage = "external"
|
||
else:
|
||
gv.Linkage = None
|
||
# t.attr.weak -> weak linkage (must be set after Linkage is cleared).
|
||
if gv_attr_weak != 0: mlirlite.global_set_linkage(gv, "weak")
|
||
if init_kind == 1:
|
||
# Integer initializer
|
||
init_buf: str = pool.alloc(48)
|
||
if init_buf is not None:
|
||
viperlib.snprintf(init_buf, 48, "%lld", init_val)
|
||
gv.Initializer = init_buf
|
||
elif init_kind == 2:
|
||
# 空字面量 `= []`:整块清零 ⇒ 直接 zeroinitializer。
|
||
# 若走下面的"逐元素展开",`[N]uint8` 这类大数组会生成**百万级**元素文本
|
||
# (`[i8 0, i8 0, …]`)——既撞上 MLIR 侧上限(too many global array elements),
|
||
# 又让每个元素 ~5KB 的渲染开销把编译池吃光
|
||
# (现场:n=1048576、src_head=[i8 0, i8 0, …]、[MEM] 池耗尽)。
|
||
ln_c: ast.List* = ast.List*(init_list_node)
|
||
elts_c: list[ast.AST*]* = None
|
||
if ln_c is not None: elts_c = ln_c.elts
|
||
if init_list_node is None or elts_c is None or len(elts_c) == 0:
|
||
gv.Initializer = "zeroinitializer"
|
||
else:
|
||
arr_init: str = self._build_array_initializer_text(pool, var_ty, init_list_node)
|
||
if arr_init is not None: gv.Initializer = arr_init
|
||
else:
|
||
# Initializer build failed: error out (fail-fast) instead of falling back
|
||
self.Gen.TypeGenErr.fatal_error(node, f"failed to build the initializer of module-level variable '{var_name}'", "TypeError")
|
||
elif init_kind == 3:
|
||
# String literal initializer: for array types (t.CArray[char, N]) use a byte-array
|
||
# initializer (content + zero padding); a string-pointer GEP initializer would mismatch.
|
||
is_arr_g: int = 0
|
||
match var_ty:
|
||
case mlirlite.LLVMType.Array(_, _):
|
||
is_arr_g = 1
|
||
case _:
|
||
pass
|
||
if is_arr_g != 0:
|
||
arr_str_init: str = self._build_string_array_initializer_text(pool, var_ty, init_str_val)
|
||
if arr_str_init is not None: gv.Initializer = arr_str_init
|
||
else: self.Gen.TypeGenErr.fatal_error(node, f"failed to build the string initializer of module-level array variable '{var_name}'", "TypeError")
|
||
else:
|
||
# Pointer type (e.g. b64_tab: t.CArray[char, None] = "ABC..."): create an internal
|
||
# string constant global and reference it via a GEP initializer.
|
||
str_init: str = self._build_string_ptr_initializer(pool, mod, var_name, init_str_val)
|
||
if str_init is not None: gv.Initializer = str_init
|
||
else: self.Gen.TypeGenErr.fatal_error(node, f"failed to build the initializer of module-level variable '{var_name}'", "TypeError")
|
||
elif init_kind == 4:
|
||
# Float initializer: LLVM float constants must be written as bit-pattern text
|
||
# (0x%016llx / 0x%08x); decimal text has ulp errors, and omitting this zeroes the global.
|
||
fc_g: mlirlite.Value* = mlirlite.ConstFloat(pool, var_ty, init_fval)
|
||
if fc_g is not None and fc_g.Name is not None: gv.Initializer = fc_g.Name
|
||
else: self.Gen.TypeGenErr.fatal_error(node, f"failed to build the float initializer of module-level variable '{var_name}'", "TypeError")
|
||
elif init_kind == 5:
|
||
# Symbol-address initializer (`= &global`): the referenced global's mangled symbol is
|
||
# emitted as `@name` (LLVM-quoted when required); the MLIR layer renders it as
|
||
# llvm.mlir.addressof. Only a pointer-typed global can hold an address.
|
||
is_ptr_g: int = 0
|
||
match var_ty:
|
||
case mlirlite.LLVMType.Ptr(_):
|
||
is_ptr_g = 1
|
||
case _:
|
||
pass
|
||
if is_ptr_g == 0:
|
||
self.Gen.TypeGenErr.fatal_error(node, f"module-level variable '{var_name}' is initialized with an address-of expression, but its type is not a pointer (add a pointer annotation or drop the '&')", "TypeError")
|
||
return 0
|
||
if init_sym_node is None or init_sym_node.kind() != ast.ASTKind.Name:
|
||
self.Gen.TypeGenErr.fatal_error(node, f"module-level variable '{var_name}' is initialized with an address-of expression whose operand is not a variable name", "TypeError")
|
||
return 0
|
||
sym_nm: ast.Name* = ast.Name*(init_sym_node)
|
||
sym_val: mlirlite.Value* = None
|
||
if sym_nm.id is not None: sym_val = self.Gen.VarGen.lookup_module_var(trans.SymTab, sym_nm.id)
|
||
if sym_val is None or sym_val.Name is None:
|
||
self.Gen.TypeGenErr.fatal_error(node, f"module-level variable '{var_name}' is initialized with &{sym_nm.id}, but '{sym_nm.id}' is not a module-level variable declared before this line", "NameError")
|
||
return 0
|
||
sym_buf: str = pool.alloc(256)
|
||
if sym_buf is None: return 0
|
||
# The module-scope entry already holds the LLVM reference text of that global
|
||
# (`@mangled` or `@"mangled"`), so it is used verbatim; only a bare name (no '@') needs
|
||
# the reference prefix added here.
|
||
if sym_val.Name[0] == '@': viperlib.snprintf(sym_buf, 256, "%s", sym_val.Name)
|
||
elif self.Gen.ClassGenVTable._vtable_name_needs_quote(sym_val.Name) != 0: viperlib.snprintf(sym_buf, 256, "@\"%s\"", sym_val.Name)
|
||
else: viperlib.snprintf(sym_buf, 256, "@%s", sym_val.Name)
|
||
gv.Initializer = sym_buf
|
||
else:
|
||
# Use zeroinitializer (valid for pointer/integer/aggregate types). Object-class globals
|
||
# with storage get &@storage below; `= None` lazy globals still need it to stay complete.
|
||
if (resolved_stg_ty is None or is_none_init != 0) and is_global_extern == 0: gv.Initializer = "zeroinitializer"
|
||
|
||
# Object-class globals: create a BSS storage global holding the instance (@g references it,
|
||
# so it is never NULL); `= None` lazy globals and extern globals skip storage instead.
|
||
if resolved_stg_ty is not None and is_none_init == 0 and is_global_extern == 0:
|
||
# Value-type storage needs a full struct definition, since cross-module classes are
|
||
# opaque by default and a zeroinitializer global would report "use of undefined type".
|
||
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, resolved_stg_ty)
|
||
# @t.CVTable object classes initialize storage field 0 (vtable pointer) to the vtable
|
||
# address; otherwise the entry virtual call jumps to 0x0. @t.Object stays zeroinitializer.
|
||
stg_init_text: str = "zeroinitializer"
|
||
if resolved_stg_entry is not None and resolved_stg_entry.Name is not None and resolved_stg_entry.HasVTable != 0:
|
||
vt_name_buf: str = pool.alloc(128)
|
||
if vt_name_buf is not None:
|
||
viperlib.snprintf(vt_name_buf, 128, "%s.vtable", resolved_stg_entry.Name)
|
||
# The vtable symbol must use the defining module's sha1: mangling with the current
|
||
# module would reference a nonexistent symbol (llc "use of undefined value").
|
||
vt_sha1: str = None
|
||
if resolved_stg_entry.ModuleSha1 is not None: vt_sha1 = resolved_stg_entry.ModuleSha1
|
||
if vt_sha1 is None: vt_sha1 = trans.ModuleSha1
|
||
vt_mangled: str = self.Gen.FuncGenBase._mangle_name_with_sha1(vt_sha1, vt_name_buf)
|
||
if vt_mangled is not None:
|
||
# A cross-module vtable is referenced only by this initializer text, so emit an
|
||
# external i8* declaration here (same-module vtables are already defined).
|
||
vt_cross: int = 0
|
||
if resolved_stg_entry.ModuleSha1 is not None and trans.ModuleSha1 is not None:
|
||
if string.strncmp(resolved_stg_entry.ModuleSha1, trans.ModuleSha1, 16) != 0: vt_cross = 1
|
||
if vt_cross != 0 and self.Gen.ExprGenAttr.find_global_in_module(mod, vt_mangled) is None:
|
||
vt_decl_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
vt_gv: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, vt_mangled, vt_decl_ty)
|
||
if vt_gv is not None: mlirlite.module_add_global(mod, vt_gv)
|
||
# Full struct initializer (LLVM rejects partial): field 0 = __vtable__ (i8* @vtable),
|
||
# remaining user fields zeroed by type.
|
||
init_full_len: sizet = 16384
|
||
init_full: str = pool.alloc(init_full_len)
|
||
if init_full is not None:
|
||
written_v: int = viperlib.snprintf(init_full, init_full_len, "{ ")
|
||
pos_v: sizet = sizet(written_v)
|
||
if self.Gen.ClassGenVTable._vtable_name_needs_quote(vt_mangled) != 0: written_v = viperlib.snprintf(init_full + pos_v, init_full_len - pos_v, "i8* @\"%s\"", vt_mangled)
|
||
else: written_v = viperlib.snprintf(init_full + pos_v, init_full_len - pos_v, "i8* @%s", vt_mangled)
|
||
pos_v += sizet(written_v)
|
||
# Fields 1..N prefer folded default values (e.g. `_mbuddy: MemBuddy`), falling
|
||
# back to per-field zeros when all defaults are zero; both list every field.
|
||
rest_v: str = self._build_stg_struct_body_text(pool, resolved_stg_entry, 1)
|
||
if rest_v is not None:
|
||
written_v = viperlib.snprintf(init_full + pos_v, init_full_len - pos_v, ", %s", rest_v)
|
||
pos_v += sizet(written_v)
|
||
else:
|
||
fc_v: int = resolved_stg_entry.FieldCount
|
||
for fi_v in range(1, int(fc_v)):
|
||
fe_v: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(resolved_stg_entry, fi_v)
|
||
if fe_v is None or fe_v.Ty is None: continue
|
||
ty_buf_v: str = pool.alloc_tagged(4096, memhub.DETSITE_TRANSLATORGEN_832)
|
||
if ty_buf_v is None: continue
|
||
mlirlite.TypePrint(ty_buf_v, 4096, fe_v.Ty, pool)
|
||
zero_v: str = self._stg_field_zero_text(pool, fe_v.Ty, ty_buf_v)
|
||
if zero_v is None: continue
|
||
written_v = viperlib.snprintf(init_full + pos_v, init_full_len - pos_v, ", %s", zero_v)
|
||
pos_v += sizet(written_v)
|
||
viperlib.snprintf(init_full + pos_v, init_full_len - pos_v, " }")
|
||
stg_init_text = init_full
|
||
elif resolved_stg_entry is not None:
|
||
# Object globals without a vtable (@t.Object / plain classes): build the initializer
|
||
# from field defaults, since the module-level X: T = T() constructor call emits nothing.
|
||
stg_default_text: str = self._build_stg_struct_init_text(pool, resolved_stg_entry, 0)
|
||
if stg_default_text is not None: stg_init_text = stg_default_text
|
||
stg_name_len: sizet = sizet(string.strlen(mangled_name)) + 8
|
||
stg_name_buf: str = pool.alloc(stg_name_len)
|
||
if stg_name_buf is not None:
|
||
viperlib.snprintf(stg_name_buf, stg_name_len, "%s.stg", mangled_name)
|
||
stg_gv: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, stg_name_buf, resolved_stg_ty)
|
||
if stg_gv is not None:
|
||
mlirlite.module_add_global(mod, stg_gv)
|
||
stg_gv.Linkage = None
|
||
stg_gv.Initializer = stg_init_text
|
||
# B5: the storage (BSS object instance) also gets the aligned alignment
|
||
if resolved_stg_entry is not None and resolved_stg_entry.Align > 0: stg_gv.Align = resolved_stg_entry.Align
|
||
# @g initializer = &@storage (the object reference points to the BSS instance)
|
||
stg_init_len: sizet = sizet(string.strlen(mangled_name)) + 8
|
||
stg_init_buf: str = pool.alloc(stg_init_len)
|
||
if stg_init_buf is not None:
|
||
if self.Gen.ClassGenVTable._vtable_name_needs_quote(stg_name_buf) != 0: viperlib.snprintf(stg_init_buf, stg_init_len, "@\"%s\"", stg_name_buf)
|
||
else: viperlib.snprintf(stg_init_buf, stg_init_len, "@%s", stg_name_buf)
|
||
gv.Initializer = stg_init_buf
|
||
# Register the storage reference (key: var_name.__stg) so AssignGenMixin's global
|
||
# assignment branch can detect object-class globals and copy objects into storage.
|
||
stg_ref: mlirlite.Value* = self.Gen.ExprGenAttr.make_global_ref(pool, stg_name_buf, resolved_stg_ty)
|
||
if stg_ref is not None:
|
||
stg_key_len: sizet = sizet(string.strlen(var_name)) + 8
|
||
stg_key: str = pool.alloc(stg_key_len)
|
||
if stg_key is not None:
|
||
viperlib.snprintf(stg_key, stg_key_len, "%s.__stg", var_name)
|
||
self.Gen.VarGen.define_module_var(trans.SymTab, stg_key, stg_ref)
|
||
|
||
# Create the Value reference (@mangled_name, type var_ty*)
|
||
var_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, var_ty)
|
||
ref_name: str = pool.alloc(96)
|
||
if ref_name is not None:
|
||
# The mangled name has a SHA1 prefix (leading digit), so the reference must be quoted.
|
||
if self.Gen.ClassGenVTable._vtable_name_needs_quote(mangled_name) != 0: viperlib.snprintf(ref_name, 96, "@\"%s\"", mangled_name)
|
||
else: viperlib.snprintf(ref_name, 96, "@%s", mangled_name)
|
||
gv_ref: mlirlite.Value* = mlirlite.SSAValue(pool, var_ptr_ty, ref_name)
|
||
|
||
# Register in the module scope
|
||
if self.Gen.VarGen.define_module_var(trans.SymTab, var_name, gv_ref) == 0:
|
||
# t.CVolatile storage marker (union annotation): mark the global volatile so all
|
||
# loads/stores carry LLVM volatile and busy-wait loops are not optimized away.
|
||
if annot_node is not None:
|
||
# const object global: a non-pointer annotation with t.CConst registers IsConst,
|
||
# making later assignment/aug-assign/element-assign to the name fail-fast.
|
||
if self.Gen.TypeGenErr.is_const_object_marker(annot_node, var_ty) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, var_name)
|
||
if self.Gen.TypeGenErr.has_decorator_marker(annot_node, "CVolatile", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, var_name)
|
||
# rawptr[t.CVolatile | T] (volatile pointee): loads/stores through p[i]/p.field/
|
||
# c.Deref(p) carry volatile; distinct from the variable's own volatile qualifier.
|
||
if self.Gen.TypeGenErr.has_pointee_volatile_marker(annot_node) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, var_name)
|
||
# Set AnnotClassName so attribute access can fall back to the class name when a union
|
||
# type like Logger* is simplified to Ptr(i8).
|
||
if k == ast.ASTKind.AnnAssign:
|
||
aa_ml: ast.AnnAssign* = ast.AnnAssign*(node)
|
||
if aa_ml is not None and aa_ml.annotation is not None:
|
||
cls_nm_ml: str = self.Gen.TypeGenResolve.extract_class_name_from_annotation(aa_ml.annotation, trans._imported_modules, pool, trans._from_imports)
|
||
if cls_nm_ml is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, var_name, cls_nm_ml)
|
||
# Module-level str*/bytes* globals: mark IsPtrElement so var[i] steps by 8-byte
|
||
# pointers; otherwise it degrades to a 1-byte GEP + load i8 that truncates pointers.
|
||
if self.Gen.AssignGen._annotation_has_cptr_marker(aa_ml.annotation) != 0:
|
||
if self.Gen.AssignGen._annotation_contains_name(aa_ml.annotation, "str") != 0 or self.Gen.AssignGen._annotation_contains_name(aa_ml.annotation, "bytes") != 0:
|
||
self.Gen.VarGen.set_var_ptr_element(trans.SymTab, var_name)
|
||
return 1
|
||
return 0
|
||
|
||
# create_wrapper_main - create a wrapper main() -> i32 when the user defines none; sets the
|
||
# current func/builder, prescans allocas, translates children and returns 0.
|
||
def create_wrapper_main(self, trans: HT.Translator*, tree: ast.AST*, i32_ty: mlirlite.LLVMType*) -> int:
|
||
"""Create a wrapper main function and translate all top-level statements."""
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
mod: mlirlite.LLVMModule* = trans.Module
|
||
|
||
main_func: mlirlite.Function* = mlirlite.create_function(pool, mod, "main", i32_ty)
|
||
if main_func is None:
|
||
VLogger.error("CreateFunction main returned NULL", "TR")
|
||
return 0
|
||
|
||
entry_blk: mlirlite.BasicBlock* = mlirlite.create_block(pool, main_func, "entry")
|
||
if entry_blk is None:
|
||
VLogger.error("CreateBlock returned NULL", "TR")
|
||
return 0
|
||
|
||
builder: mlirlite.IRBuilder* = mlirlite.new_builder(pool, main_func)
|
||
if builder is None:
|
||
VLogger.error("NewBuilder returned NULL", "TR")
|
||
return 0
|
||
mlirlite.position_at_end(builder, entry_blk)
|
||
|
||
# Set the current translation context (Handles access it via self.Trans._cur_builder)
|
||
trans._cur_func = main_func
|
||
trans._cur_builder = builder
|
||
# P2 willFree 失效表:进入 main 体前复位(避免上一翻译单元残留的栈槽失效态泄漏进来)
|
||
self.Gen.VarGen.freed_clear()
|
||
|
||
# Pre-process module-level variables (create LLVM globals) before pre_scan_allocas, or
|
||
# PreScan would create local allocas that other functions reference by SSA number.
|
||
ch: list[ast.AST*]* = tree.children
|
||
if ch is not None:
|
||
cn_count: sizet = len(ch)
|
||
for ci in range(int(cn_count)):
|
||
child: ast.AST* = ch.get(ci)
|
||
if child is not None:
|
||
kd: int = child.kind()
|
||
if kd == ast.ASTKind.AnnAssign or kd == ast.ASTKind.Assign: self.handle_module_level_var(trans, child)
|
||
|
||
# Prescan top-level statements to pre-create allocas for in-function AnnAssign; module-level
|
||
# variables are already registered so PreScan's lookup_current skips them.
|
||
if ch is not None:
|
||
for ci in range(int(cn_count)):
|
||
child: ast.AST* = ch.get(ci)
|
||
if child is not None: self.Gen.StmtGenDecl.pre_scan_allocas(trans, child)
|
||
|
||
# Translate children
|
||
added_total: int = self.translate_children(trans, tree)
|
||
|
||
# Return 0
|
||
zero_val: mlirlite.Value* = mlirlite.const_int32(pool, 0)
|
||
mlirlite.build_ret(builder, zero_val)
|
||
|
||
return added_total
|
||
|
||
# global/nonlocal name management (migrated from HandlesTranslator.vp module-level functions)
|
||
def add_global_name(self, trans: HT.Translator*, name: str):
|
||
"""Add a global variable name."""
|
||
if trans is None or name is None: return
|
||
if trans._global_names is None: return
|
||
# Check whether it already exists
|
||
if self.is_global_name(trans, name) != 0: return
|
||
if trans._global_name_count >= HT.MAX_GLOBAL_NAMES:
|
||
pool_err: memhub.MemBuddy* = trans.Pool
|
||
err_buf: str = None
|
||
if pool_err is not None: err_buf = pool_err.alloc(256)
|
||
if err_buf is not None:
|
||
viperlib.snprintf(err_buf, 256, "the number of global variables exceeds the limit MAX_GLOBAL_NAMES=%d; reduce global declarations or increase MAX_GLOBAL_NAMES", HT.MAX_GLOBAL_NAMES)
|
||
VLogger.error(err_buf, "ScopeDecl")
|
||
else: VLogger.error("the number of global variables exceeds the limit; reduce global declarations or increase MAX_GLOBAL_NAMES", "ScopeDecl")
|
||
return
|
||
entry_addr: uint64 = uint64(trans._global_names) + trans._global_name_count * 8
|
||
slot_ptr: str* = (str*)(entry_addr)
|
||
slot_ptr[0] = name
|
||
trans._global_name_count += 1
|
||
|
||
def is_global_name(self, trans: HT.Translator*, name: str) -> int:
|
||
"""Return 1 when name is in the current function's global set."""
|
||
if trans is None or name is None or trans._global_names is None: return 0
|
||
for i in range(int(trans._global_name_count)):
|
||
entry_addr: uint64 = uint64(trans._global_names) + i * 8
|
||
slot_ptr: str* = (str*)(entry_addr)
|
||
entry: str = slot_ptr[0]
|
||
if entry is not None:
|
||
if string.strcmp(entry, name) == 0: return 1
|
||
return 0
|
||
|
||
def add_nonlocal_name(self, trans: HT.Translator*, name: str, var_ty: mlirlite.LLVMType* = None):
|
||
"""Add a nonlocal variable name together with its LLVM type."""
|
||
if trans is None or name is None: return
|
||
if trans._nonlocal_names is None: return
|
||
if self.is_nonlocal_name(trans, name) != 0:
|
||
# Already present: refresh the type (possibly unknown at prescan time). _nonlocal_types
|
||
# stores one LLVMType* per 8-byte slot, accessed via i8** to copy just the pointer.
|
||
if var_ty is not None and trans._nonlocal_types is not None:
|
||
idx: int = self.get_nonlocal_index(trans, name)
|
||
if idx >= 0:
|
||
ty_addr: uint64 = uint64(trans._nonlocal_types) + idx * 8
|
||
ty_slot: str* = (str*)(ty_addr)
|
||
ty_slot[0] = var_ty
|
||
return
|
||
if trans._nonlocal_name_count >= HT.MAX_GLOBAL_NAMES:
|
||
pool_err_nl: memhub.MemBuddy* = trans.Pool
|
||
err_buf_nl: str = None
|
||
if pool_err_nl is not None: err_buf_nl = pool_err_nl.alloc(256)
|
||
if err_buf_nl is not None:
|
||
viperlib.snprintf(err_buf_nl, 256, "the number of nonlocal variables exceeds the limit MAX_GLOBAL_NAMES=%d; reduce nonlocal declarations or increase MAX_GLOBAL_NAMES", HT.MAX_GLOBAL_NAMES)
|
||
VLogger.error(err_buf_nl, "ScopeDecl")
|
||
else: VLogger.error("the number of nonlocal variables exceeds the limit; reduce nonlocal declarations or increase MAX_GLOBAL_NAMES", "ScopeDecl")
|
||
return
|
||
entry_addr: uint64 = uint64(trans._nonlocal_names) + trans._nonlocal_name_count * 8
|
||
slot_ptr: str* = (str*)(entry_addr)
|
||
slot_ptr[0] = name
|
||
# Store type info (str* = i8**, same pattern as _nonlocal_names)
|
||
if trans._nonlocal_types is not None:
|
||
ty_addr2: uint64 = uint64(trans._nonlocal_types) + trans._nonlocal_name_count * 8
|
||
ty_slot2: str* = (str*)(ty_addr2)
|
||
# A None var_ty errors out (fail-fast) instead of defaulting to i32
|
||
stored_ty: mlirlite.LLVMType* = var_ty
|
||
if stored_ty is None:
|
||
self.Gen.TypeGenErr.fatal_error(None, f"nonlocal variable '{name}' has no type, cannot register", "TypeError")
|
||
return
|
||
ty_slot2[0] = stored_ty
|
||
trans._nonlocal_name_count += 1
|
||
trans._closure_env_count += 1
|
||
|
||
def get_nonlocal_type(self, trans: HT.Translator*, name: str) -> mlirlite.LLVMType*:
|
||
"""Return the LLVM type of a nonlocal variable; None when not found."""
|
||
if trans is None or name is None or trans._nonlocal_types is None: return None
|
||
idx: int = self.get_nonlocal_index(trans, name)
|
||
if idx < 0: return None
|
||
ty_addr: uint64 = uint64(trans._nonlocal_types) + idx * 8
|
||
ty_slot: str* = (str*)(ty_addr)
|
||
return (mlirlite.LLVMType*)(ty_slot[0])
|
||
|
||
def is_nonlocal_name(self, trans: HT.Translator*, name: str) -> int:
|
||
"""Return 1 when name is in the current function's nonlocal set."""
|
||
if trans is None or name is None or trans._nonlocal_names is None: return 0
|
||
for i in range(int(trans._nonlocal_name_count)):
|
||
entry_addr: uint64 = uint64(trans._nonlocal_names) + i * 8
|
||
slot_ptr: str* = (str*)(entry_addr)
|
||
entry: str = slot_ptr[0]
|
||
if entry is not None:
|
||
if string.strcmp(entry, name) == 0: return 1
|
||
return 0
|
||
|
||
def get_nonlocal_index(self, trans: HT.Translator*, name: str) -> int:
|
||
"""Return the 0-based env index of a nonlocal variable; -1 when not found."""
|
||
if trans is None or name is None or trans._nonlocal_names is None: return -1
|
||
for i in range(int(trans._nonlocal_name_count)):
|
||
entry_addr: uint64 = uint64(trans._nonlocal_names) + i * 8
|
||
slot_ptr: str* = (str*)(entry_addr)
|
||
entry: str = slot_ptr[0]
|
||
if entry is not None:
|
||
if string.strcmp(entry, name) == 0: return i
|
||
return -1
|
||
|
||
def clear_scope_names(self, trans: HT.Translator*):
|
||
"""Clear the current function's global/nonlocal name sets on entering a new function."""
|
||
if trans is None: return
|
||
trans._global_name_count = 0
|
||
trans._nonlocal_name_count = 0
|
||
trans._closure_env_count = 0
|