Files
ViperC/App/lib/core/Gen/TranslatorGenMixin.vp

1131 lines
70 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import lib.core.Gen.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