Files
ViperC/App/lib/core/Gen/TranslatorGenMixin.vp
T
TermiNexus 2fb648d68a feat(lang): 新增 cif/celif/celse 条件编译与值引用注解 &T/&temp T,assert 改译 testcheck.check
cif 三件套:StmtGenCifMixin 负责常量求值(含 defined(模块宏) 的 Attribute 形式),链式错配与含运行时变量一律 fail-fast;删掉 c.CIf 家族。值引用:TypeGenResolve 让 CRef/CTempRef 与 t.CPtr 走同一解析路径(&void 折叠为 i8*,删除 t.CNeedPtr),CallGenBase 在调用点按形参模式自动取址(ref 只接受左值,右值 fail-fast;temp 物化临时栈),跨模块经 VpiReader/StubMergerBuild 传递模式。assert:StmtGenMixin 合成 testcheck.check(cond, None, msg),Translator 检测到 assert 时自动注入 testcheck 导入(带去重与防环),深递归守卫改用 stdio.printf + stdlib.exit(Translator 无 Gen 成员)。
2026-10-07 19:03:16 +08:00

1081 lines
66 KiB
Plaintext

import lib.core.Gen.Generator as Generator
import t, c
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(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:
added: int = self.Gen.FuncGenDef.translate_function_def(trans, child)
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(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 TPV 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(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(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(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(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 t.attr.* extraction: attributes live in the annotation union
# (`T | t.attr.section(".x")`) and are written directly to gv.Section/Align/Linkage fields.
def _scan_global_tattr(self, node: ast.AST*, want_name: str) -> ast.AST*:
"""Find the t.attr.<want_name> node in a global type annotation; None when absent."""
if node is None: return None
if node.kind() == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop.op == ast.OpKind.BitOr:
hit_l: ast.AST* = self._scan_global_tattr(bop.left, want_name)
if hit_l is not None: return hit_l
return self._scan_global_tattr(bop.right, want_name)
return None
if self.Gen.ClassGenBase._tattr_name(node, want_name) != 0: return node
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)
if gv_entry is not None and (gv_entry.HasNew != 0 or gv_entry.HasVTable != 0 or gv_entry.IsOOP != 0):
# 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:
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:
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:
# List literal -> array initializer [N x elem_ty] [elem_ty val0, ...]
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 to zeroinitializer
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, 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 aa_ml.annotation.kind() == ast.ASTKind.BinOp:
bop_ml: ast.BinOp* = ast.BinOp*(aa_ml.annotation)
if bop_ml.op == ast.OpKind.BitOr:
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
# 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(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(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(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(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(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