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 成员)。
1081 lines
66 KiB
Plaintext
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
|