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. 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