Files
ViperC/App/lib/core/Gen/Translator.vp
T
TermiNexus dd24dc670f fix(gen): P2 失效状态改存独立槽表,不再给 VarEntry 加字段
现象:任何工程编译都在函数形参注册刚结束处 access-violation(pc=0,逐字节可复现)。定位:逐层探针把崩点压到 FuncGenDef 的 pre_scan 段,而该段对 Check(body 仅 if+return)应为空操作,唯一变量是 VarEntry 这个热结构被加了 Freed 字段。修法:撤销 VarEntry.Freed,改用 VarGenMixin 的独立失效槽表 _g_freed_slots(键 = 变量槽 alloca,整次翻译内唯一,故全局累积、无需按函数 reset/restore);_FreedMark/_FreedCheck/_FreedClearSlot 三个 API 替换字段读写,P2 语义不变。同时清除全部 TEMP 探针。
2026-10-09 11:51:11 +08:00

1013 lines
54 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.GenTypes as GenTypes
import t, c
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import stdlib
import viperlib
import lib.core.VLogger as VLogger
import lib.core.CompilerFactory as CompilerFactory
import lib.Projectrans.Config as Config
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.core.Gen.Generator as Generator
import lib.core.Gen.DebugInfoGen as DebugInfoGen
# Gen/Translator.vp - translator state management and main entry point:
# Translator class (module/vars/funcs/imports), translate(), and dump_ir().
# HandlesTranslator - translator state management and main entry point, split from translator.py.
# Statement/expression translation is delegated to the Handles modules (Main/Body/Expr/Functions).
# Constants
# Scoped variable-table capacity (single source of truth), shared by VarGenMixin init_vars and add_var.
define MAX_VARS = 1024
# Function-table capacity (single source of truth). Allocation happens in _init_state;
# the registration-side max_count passed to add_func_to_table must be <= this capacity.
define MAX_FUNCS = 1024
# Reference convention: cross-module t.CDefine is readable only inside functions, so write
# qualified names there; the global/nonlocal name-table capacity must match on both guard sides.
define MAX_GLOBAL_NAMES = 128
# Coroutine table capacity (TPV-M1): async function count / coroutine var and local-name count
define MAX_CORO_FUNCS = 256
define MAX_CORO_VARS = 256
define MAX_CORO_TRY = 16
# Max recursion depth of the translation main loop (translate_stmt/translate_value nesting).
# Guards against infinite recursion from AST cycles or bugs (fail-fast, prevents ASan stack-overflow).
define TRANSLATE_MAX_DEPTH = 256
# ============================================================
# Global gen singleton (module-level mirror, same pattern as HandlesType/HandlesStruct)
novirtual class Translator:
"""Translator: manages compilation state and delegates translation to Handles modules."""
# LLVM context (shared state)
Module: mlirlite.LLVMModule*
Pool: memhub.MemBuddy*
# Function table
_funcs: GenTypes.FuncEntry*
_func_count: int
# Coroutines (TPV-M1 async def / await / coro.coro_run):
# tables for async function names, var->async mapping, locals, and async->frame types.
_async_funcs: str
_async_func_count: int
_coro_var_async: str
_coro_var_async_count: int
_coro_locals: str
_coro_local_count: int
_coro_frames: str
_coro_frame_count: int
# resume_<name> translation context (shared by AsyncGenMixin segment translation)
_coro_state_ptr: mlirlite.Value*
_coro_done_ptr: mlirlite.Value*
_coro_ret_ptr: mlirlite.Value*
_coro_exit_bb: mlirlite.BasicBlock*
_coro_await_slot: mlirlite.Value*
_coro_frame: mlirlite.Value*
_coro_next_state: int
_coro_nseg: int
# M3 硬约束锚点:正在翻译其 body 的协程函数(= create_/resume_ 那个合成函数)。
# 只有 body 翻译期间非 None,且与 _cur_func 同一对象才算"在协程体里"——嵌套的普通函数
# 会换掉 _cur_func,故不会误报。用于禁止协程体内的 `new heap slife`(释放点无法确定)。
_coro_body_func: mlirlite.Function*
# M2: async parameter table (char* array, 8 bytes/item) + pre-scan counts
_coro_params: str
_coro_param_count: int
_coro_hoist_count: int # __await_N slot count (shared by pre-scan and translation, same order)
_coro_await_count: int # Total await count (= resume blocks = switch cases)
_coro_local_base: int # Local slot start index (= 3 + nparams)
_coro_extra_base: int # Extra slot (__await_N) start index
_coro_break_bb: mlirlite.BasicBlock* # While break target (M2 single level)
_coro_continue_bb: mlirlite.BasicBlock* # While continue target
_coro_state_bbs: sizet* # Pre-built state block array (locates await resume blocks)
# M3-M7 coroutine extensions
_coro_exc_ptr: mlirlite.Value* # Frame exc slot (i32, M4)
_coro_msg_ptr: mlirlite.Value* # Frame msg slot (i8*, M7)
_coro_wake_ptr: mlirlite.Value* # Frame wake slot (i64, M3 coro_sleep)
_coro_try_stack: sizet* # try stack: except/finally block pointer array (8B/item)
_coro_try_stack_count: int
_coro_for_counter: int # range-For id (M3, incremented during translation)
_coro_scan_for_count: int # Pre-scan For count
_coro_uses_sleep: int # Whether the body uses coro_sleep (set during pre-scan)
_coro_extra_types: str # Extra slot type table (i8 array: 1=i32, 2=i64)
_coro_extra_count: int # Total extra slots
_coro_await_slots: int* # Frame-relative slot index of __await_N (filled by pre-scan)
_coro_for_slots: int* # Frame-relative __f_idx slot index of range-For N (filled by pre-scan)
# per-async wake slot index table (reverse lookup for coro_run/gather polling)
_coro_wake_names: str # char* array (8B/item)
_coro_wake_idxs: int* # i32 array (4B/item, -1 = no wake slot)
_coro_wake_count: int
# Imported module tracking
_imported_modules: str
_from_imports: str
# Current translation context
_cur_func: mlirlite.Function*
_cur_builder: mlirlite.IRBuilder*
# Annotation node of the current assignment (set before translating the AnnAssign rhs, cleared for Assign).
# Lets bare generic constructors infer type args from the annotation (see CallGenCtor.vp).
CurAssignAnnot: ast.AST*
# RHS node of the current assignment (set before translating the AnnAssign rhs, cleared after).
# Bare generic constructors use CurAssignAnnot only when this identity matches the direct RHS.
CurAssignNode: ast.AST*
# Return annotation of the current function (set before translating its body, restored on exit).
# Fallback for bare generic constructors: `return Stack()` under `-> Stack[int]` uses the int.
CurFuncRetAnnot: ast.AST*
# Direct value node of the current Return statement (set before/cleared after translating it).
# Bare generic constructors use CurFuncRetAnnot only for a direct return value (not return f(Stack())).
CurRetNode: ast.AST*
# Loop control-flow targets (break/continue). A break inside a match case exits the match:
# MatchGenMixin temporarily overrides _break_bb with the match end_bb while in a case body.
_break_bb: mlirlite.BasicBlock*
_continue_bb: mlirlite.BasicBlock*
# Whether we are inside a switch case body (1=yes). `fallthrough` is legal only there;
# match case bodies and non-switch code fail-fast. Set from node.is_switch by MatchGenMixin.
_in_switch_case: int
# Standalone label counter (not shared with builder.Counter, keeps SSA numbering monotonic)
_label_counter: int
# Control-flow merge block reachability (1=reachable via br, 0=unreachable, all branches return)
_ctrl_merge_reachable: int
# match translation recursion depth (match -> case body -> translate_value -> nested match).
# Guards against infinite recursion from AST self-cycles or bugs (fail-fast, prevents stack overflow).
MatchTranslateDepth: int
# Translation main-loop recursion depth (counted at translate_stmt/translate_value entry).
# Guards statement/expression/call recursion; exceeding TRANSLATE_MAX_DEPTH fails fast.
TranslateDepth: int
# Set of global-declared variable names (current function)
_global_names: str
_global_name_count: int
# Set of nonlocal-declared variable names (current function)
_nonlocal_names: str
_nonlocal_name_count: int
# Type info for nonlocal variables (LLVMType pointer array aligned with _nonlocal_names).
# Used by the env pointer array scheme: inner functions need the original type.
_nonlocal_types: str
# Closure: current function name (used to name hoisted nested functions)
_cur_func_name: str
# *args support: va_list pointer of the current function (i8* alloca), set only when the
# function declares *args; used by the arg(type) sugar.
_va_list_ptr: mlirlite.Value*
# *args parameter name (e.g. "args"), used for symbol-table registration
_vararg_name: str
# OOP iter protocol: hidden stop-flag parameter (i1*) of the current __next__ method.
# translate_raise stores 1 there on raise StopIteration; None outside __next__.
_stop_iter_flag_ptr: mlirlite.Value*
# try-except state: dispatch block plus exception code/message allocas of the innermost try.
# Set by translate_try; written by translate_raise and call-site EH checks. None outside try.
_eh_dispatch_bb: mlirlite.BasicBlock*
_eh_code_ptr: mlirlite.Value*
_eh_msg_ptr: mlirlite.Value*
# Current function's __eh_msg_out__ (i8**) / __eh_code_out__ (i32*) output parameters,
# injected when the signature involves raise. _cur_func_has_eh == 0 when there are none.
_eh_msg_out_param: mlirlite.Value*
_eh_code_out_param: mlirlite.Value*
_cur_func_has_eh: int
# Module SHA1 prefix (16 chars) used for function-name mangling (SHA1 namespace)
ModuleSha1: str
# Package name of the current file (e.g. "mlirlite"), used to resolve relative imports.
# None means a top-level module (no package), so relative imports cannot be resolved.
CurrentPackage: str
# Byte offset of each nonlocal variable in the closure env
_closure_env_offsets: int
_closure_env_count: int
# Globally unique lambda ID counter (generates hoisted names __lambda_{id}).
# Kept independent of _closure_env_count, which resets per function, so IDs stay unique.
_lambda_count: int
# Set of nonlocal variables across the function chain (collected by pre-scan).
# The outermost function allocates their heaps and stores addresses in @nlheap.{sha1}.{name}.
_nl_chain_names: t.CPtr
_nl_chain_count: int
# Chain-nonlocal heap built flag (built once per function): reset to 0 on entering a function,
# set to 1 by _prepare_chain_nonlocals. Shared by nested definitions to avoid double malloc.
_nl_chain_done: int
# Generic specialization context: type parameter and argument name lists of the class
# currently being specialized. Set by _specialize_generic_class; None when not specializing.
GenericTypeParamNames: list[str]*
GenericTypeArgs: list[str]*
# Phase 1a declare mode: 1=register struct/enum/union only (no body), 0=full translation
_declare_only: int
# App pre-registration (Phase A-pre) switch: when 1, imports and LoadDepVpiIntoModule run even
# if _declare_only != 0, keeping class field annotations in phase A's context. App-only.
_pre_allow_dep_inject: int
# Nested-scope symbol table
SymTab: GenTypes.SymbolTable*
# REPL mode context (M2 session variables): None means non-REPL (normal path unchanged).
# Only Repl.vp sets it; Gen-side session-slot interception checks `trans.ReplCtx is not None`.
ReplCtx: GenTypes.ReplCtx*
def __init__(self):
self.Module = None
self.Pool = None
self._funcs = None
self._func_count = 0
self._cur_func = None
self._cur_builder = None
self._break_bb = None
self._continue_bb = None
self._in_switch_case = 0
self._label_counter = 0
self._imported_modules = None
self._from_imports = None
self._global_names = None
self._global_name_count = 0
self._nonlocal_names = None
self._nonlocal_name_count = 0
self._nonlocal_types = None
self._cur_func_name = None
self._va_list_ptr = None
self._vararg_name = None
self._stop_iter_flag_ptr = None
self._eh_dispatch_bb = None
self._eh_code_ptr = None
self._eh_msg_ptr = None
self._eh_msg_out_param = None
self._eh_code_out_param = None
self._cur_func_has_eh = 0
self.ModuleSha1 = None
self.CurrentPackage = None
self._closure_env_offsets = 0
self._closure_env_count = 0
self._lambda_count = 0
self._nl_chain_names = None
self._nl_chain_count = 0
self._nl_chain_done = 0
self.GenericTypeParamNames = None
self.GenericTypeArgs = None
self._declare_only = 0
self._pre_allow_dep_inject = 0
self.SymTab = None
# REPL mode context: unset by default (= non-REPL, all Gen-side interception bypassed)
self.ReplCtx = None
self._async_funcs = None
self._async_func_count = 0
self._coro_var_async = None
self._coro_var_async_count = 0
self._coro_locals = None
self._coro_local_count = 0
self._coro_frames = None
self._coro_frame_count = 0
self._coro_state_ptr = None
self._coro_done_ptr = None
self._coro_ret_ptr = None
self._coro_exit_bb = None
self._coro_await_slot = None
self._coro_frame = None
self._coro_next_state = 1
self._coro_nseg = 0
self._coro_body_func = None
self._coro_params = None
self._coro_param_count = 0
self._coro_hoist_count = 0
self._coro_await_count = 0
self._coro_local_base = 3
self._coro_extra_base = 3
self._coro_break_bb = None
self._coro_continue_bb = None
self._coro_state_bbs = None
self._coro_exc_ptr = None
self._coro_msg_ptr = None
self._coro_wake_ptr = None
self._coro_try_stack = None
self._coro_try_stack_count = 0
self._coro_for_counter = 0
self._coro_scan_for_count = 0
self._coro_uses_sleep = 0
self._coro_extra_types = None
self._coro_extra_count = 0
self._coro_await_slots = None
self._coro_for_slots = None
self._coro_wake_names = None
self._coro_wake_idxs = None
self._coro_wake_count = 0
# ============================================================
# State initialization
def _init_state(self, pool: memhub.MemBuddy*):
"""Initialize the variable table, function table, and sub-handles."""
self.Pool = pool
if self._funcs is None: self._funcs = GenApi.init_func_table(pool, MAX_FUNCS)
# Nested-scope symbol table (new)
if self.SymTab is None: self.SymTab = GenApi.init_symbol_table(pool)
# global/nonlocal name buffers (MAX_GLOBAL_NAMES char* pointers = 128*8 bytes)
if self._global_names is None:
self._global_names = stdlib.malloc(MAX_GLOBAL_NAMES * 8)
if self._global_names is not None: string.memset(self._global_names, 0, MAX_GLOBAL_NAMES * 8)
self._global_name_count = 0
if self._nonlocal_names is None:
self._nonlocal_names = stdlib.malloc(MAX_GLOBAL_NAMES * 8)
if self._nonlocal_names is not None: string.memset(self._nonlocal_names, 0, MAX_GLOBAL_NAMES * 8)
self._nonlocal_name_count = 0
# nonlocal variable type array (LLVMType pointers, aligned with _nonlocal_names)
if self._nonlocal_types is None:
self._nonlocal_types = stdlib.malloc(MAX_GLOBAL_NAMES * 8)
if self._nonlocal_types is not None: string.memset(self._nonlocal_types, 0, MAX_GLOBAL_NAMES * 8)
# Coroutine tables (TPV-M1): async names / var->async map / locals / frame types
if self._async_funcs is None:
self._async_funcs = stdlib.malloc(MAX_CORO_FUNCS * 8)
if self._async_funcs is not None: string.memset(self._async_funcs, 0, MAX_CORO_FUNCS * 8)
self._async_func_count = 0
if self._coro_var_async is None:
self._coro_var_async = stdlib.malloc(MAX_CORO_VARS * 16)
if self._coro_var_async is not None: string.memset(self._coro_var_async, 0, MAX_CORO_VARS * 16)
self._coro_var_async_count = 0
if self._coro_locals is None:
self._coro_locals = stdlib.malloc(MAX_CORO_VARS * 8)
if self._coro_locals is not None: string.memset(self._coro_locals, 0, MAX_CORO_VARS * 8)
if self._coro_params is None:
self._coro_params = stdlib.malloc(MAX_CORO_VARS * 8)
if self._coro_params is not None: string.memset(self._coro_params, 0, MAX_CORO_VARS * 8)
if self._coro_try_stack is None:
self._coro_try_stack = stdlib.malloc(MAX_CORO_TRY * 8)
if self._coro_try_stack is not None: string.memset(self._coro_try_stack, 0, MAX_CORO_TRY * 8)
if self._coro_extra_types is None:
self._coro_extra_types = stdlib.malloc(MAX_CORO_VARS)
if self._coro_extra_types is not None: string.memset(self._coro_extra_types, 0, MAX_CORO_VARS)
if self._coro_await_slots is None:
self._coro_await_slots = stdlib.malloc(MAX_CORO_VARS * 4)
if self._coro_await_slots is not None: string.memset(self._coro_await_slots, 0, MAX_CORO_VARS * 4)
if self._coro_for_slots is None:
self._coro_for_slots = stdlib.malloc(MAX_CORO_VARS * 4)
if self._coro_for_slots is not None: string.memset(self._coro_for_slots, 0, MAX_CORO_VARS * 4)
if self._coro_wake_names is None:
self._coro_wake_names = stdlib.malloc(MAX_CORO_FUNCS * 8)
if self._coro_wake_names is not None: string.memset(self._coro_wake_names, 0, MAX_CORO_FUNCS * 8)
if self._coro_wake_idxs is None:
self._coro_wake_idxs = stdlib.malloc(MAX_CORO_FUNCS * 4)
if self._coro_wake_idxs is not None: string.memset(self._coro_wake_idxs, 0, MAX_CORO_FUNCS * 4)
self._coro_local_count = 0
if self._coro_frames is None:
self._coro_frames = stdlib.malloc(MAX_CORO_FUNCS * 16)
if self._coro_frames is not None: string.memset(self._coro_frames, 0, MAX_CORO_FUNCS * 16)
self._coro_frame_count = 0
# ============================================================
# Main translation entry
def translate(self, tree: ast.AST*) -> int:
"""Translate the AST into LLVM IR."""
if tree is None: return 1
if _mbuddy is None: return 1
pool: memhub.MemBuddy* = _mbuddy
# OOM attribution: record the phase/current module in memhub's allocation context so
# exhaustion reports ctx=phase/module (SetAllocCtx stores the pointer only).
memhub.SetAllocCtx("translate", self.ModuleSha1)
# Initialize state
self._init_state(pool)
# Create LLVM module
mod: mlirlite.LLVMModule* = mlirlite.new_module(pool, "main")
if mod is None:
VLogger.error("NewModule returned NULL", "TR")
return 1
self.Module = mod
# --traceback: reset per-module DWARF state (no-op when DI is disabled).
DebugInfoGen.BeginModule(mod, GenApi.get_current_file())
# Set target (prefer project.vpj / viperbuild config)
triple: str = Config.TargetTriple
if triple is None: triple = "x86_64-pc-windows-msvc"
mlirlite.module_set_target(mod, triple)
# Data layout from project.vpj / viperbuild target.datalayout (omitted when None).
# Written into the .ll module header so the IR carries layout info matching the target triple.
if Config.TargetDataLayout is not None: mlirlite.module_set_datalayout(mod, Config.TargetDataLayout)
# Types
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
# 模块前言里 C 库声明的**名字**应当落在长生命周期池(PermPool):create_declare 会
# _str_dup(pool, name) 把名字拷进传入的池,而本模块池(trans.Pool)在大模块上会被
# 复用/回绕,覆盖早期分配 → 名字悬空(现场:@malloc 渲染成乱码)。
# **但本函数跑得非常早**(每个模块翻译的开头),此时 CompilerFactory.factory 或它的
# PermPool 可能尚未就绪:直接 `factory.PermPool.alloc(...)` 会解引用空指针
# (现场:TransPyV.exe 编译任何工程都 access-violation on address 0x0 —— PermPool 是
# factory 的首字段,偏移 0)。故逐级判空并回退到模块池。
decl_pool: memhub.MemBuddy* = None
if CompilerFactory.factory is not None: decl_pool = CompilerFactory.factory.PermPool
if decl_pool is None: decl_pool = pool
# Declare printf
printf_func: mlirlite.Function* = mlirlite.create_declare(decl_pool, mod, "printf", i32_ty)
if printf_func is None:
VLogger.error("CreateDeclare printf returned NULL", "TR")
return 1
mlirlite.add_param(decl_pool, printf_func, i8_ptr_ty, "fmt")
printf_func.IsVarArg = 1
# Declare LLVM va_start/va_end intrinsics (used inside *args function bodies):
# declare void @llvm.va_start(i8*) / declare void @llvm.va_end(i8*)
void_ty_decl: mlirlite.LLVMType* = mlirlite.Void(pool)
va_start_func: mlirlite.Function* = mlirlite.create_declare(decl_pool, mod, "llvm.va_start", void_ty_decl)
if va_start_func is not None: mlirlite.add_param(decl_pool, va_start_func, i8_ptr_ty, "ap")
va_end_func: mlirlite.Function* = mlirlite.create_declare(decl_pool, mod, "llvm.va_end", void_ty_decl)
if va_end_func is not None: mlirlite.add_param(decl_pool, va_end_func, i8_ptr_ty, "ap")
# Declare malloc (for closure allocation)
malloc_func: mlirlite.Function* = mlirlite.create_declare(decl_pool, mod, "malloc", i8_ptr_ty)
if malloc_func is not None:
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
mlirlite.add_param(decl_pool, malloc_func, i64_ty, "size")
# Declare free (counterpart of malloc): M3 owned slife 的出口释放要用它。
# 必须在这里(模块前言)声明:释放点只做**查表 + fail-fast**(VarGenMixin._SlifeEmitGuardedFree),不再现场建 declare——
# 否则同一模块的每个出口各建一份重复 declare。缺了它 mlir-opt 会报
# "'llvm.call' op 'free' does not reference a symbol in the current scope"
# (现场:Test/NewLifetimeTest 未 import stdlib,拿不到 stdlib vpi 的 declare)。
free_func: mlirlite.Function* = mlirlite.create_declare(decl_pool, mod, "free", void_ty_decl)
if free_func is not None: mlirlite.add_param(decl_pool, free_func, i8_ptr_ty, "p")
# Check whether the user defined a main function
has_user_main: int = 0
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 and child.kind() == ast.ASTKind.FunctionDef:
fd: ast.FunctionDef* = ast.FunctionDef*(child)
if fd is not None and fd.name is not None:
if string.strcmp(fd.name, "main") == 0:
has_user_main = 1
break
if self._declare_only != 0:
# Phase 1a-pre(2=import scan) / Phase 1a(1=struct registration): module level only, no main/builder
self._translate_module_level(pool, mod, tree)
elif has_user_main == 0:
# No user main -> translate module-level statements only, no wrapper main
# (avoids define i32 @main() in every file; only the def main() entry file emits it).
self._translate_module_level(pool, mod, tree)
else:
# User main defined -> delegate module-level translation to HandlesMain
self._translate_module_level(pool, mod, tree)
# --traceback: register module-level !llvm.dbg.cu / !llvm.module.flags before the caller
# dumps the IR. Idempotent; no-op with no DISubprogram or when DI is disabled.
DebugInfoGen.Finalize(pool)
return 0
# ============================================================
# Wrapper main (when there is no user main) -> delegate to HandlesMain
def _translate_wrapper_main(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, tree: ast.AST*, i32_ty: mlirlite.LLVMType*):
"""Delegate to GenApi.create_wrapper_main() to build the wrapper main (trans arg)."""
added: int = GenApi.create_wrapper_main(self, tree, i32_ty)
# ============================================================
# Module-level translation (user main defined) -> delegate to HandlesMain
def _translate_module_level(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, tree: ast.AST*):
"""Delegate to GenApi.translate_children() to translate module-level statements (trans arg)."""
# File-level class-name pre-registration must run in all three modes: it is the only source of
# evidence #5 for `_norm_type_arg_name` rule 5.5 and is hoisted above the _declare_only gate.
GenApi.register_file_classes(pool, self.ModuleSha1, tree)
# Pre-register this file's top-level function names: otherwise a bare-name vpi declaration
# sharing a name shadows the local one in forward_declare_functions (calls coerce to the dep).
GenApi.register_file_funcs(pool, tree)
# Erase viperbuild macro statements (proj = vb.Project(...), field assigns, config/build calls)
# from tree.children so later passes see the same erased tree; Pass 0 already consumed them.
self._erase_viperbuild_macros(pool, tree)
# Two gates: (1) import handling + auto container visibility + vpi injection run in full
# translation and App pre-registration; (2) module var pre-registration + forward declarations run only in full translation.
if self._declare_only == 0 or self._pre_allow_dep_inject != 0:
# Pre-process imports so _imported_modules/_from_imports are filled (forward declarations need them).
# Track the last import lineno for vpi injection error-context frames.
cur_imp_lineno: int = 0
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 None: continue
kd: int = child.kind()
if kd == ast.ASTKind.Import:
cur_imp_lineno = child.lineno
GenApi.translate_import(self, child)
elif kd == ast.ASTKind.ImportFrom:
cur_imp_lineno = child.lineno
GenApi.translate_import_from_module(self, child)
GenApi.translate_import_from_names(self, child)
# Mirror TPC _inject_auto_imports: detect list/dict container usage (bare annotations,
# constructors, subscripts) and mark the container classes visible so bare names resolve.
self._inject_auto_container_visibility(pool, tree)
# A module using `assert` behaves as if it had `import testcheck`: inject the module
# before dependency-vpi loading so testcheck.check resolves across modules.
self._inject_auto_assert_import(pool, tree)
# CTypedef declarations are registered by Phase1 text scanning (AST string tokens are unstable).
# Cross-module .vpi injection loads dep .vpi declares when full-translating or during App pre-registration.
if self._imported_modules is not None:
# Error-context frame: import site that triggered dependency stub loading
GenApi.push_trace(GenApi.get_current_file(), cur_imp_lineno)
StubMergerBuild.LoadDepVpiIntoModule(pool, mod, self._imported_modules, self._from_imports, GenApi.get_temp_dir(), self)
GenApi.pop_trace()
# File-level class pre-registration already ran at the top of this function.
# Module var pre-registration + forward declarations run only in full translation.
if self._declare_only == 0:
# Pre-register module-level variables (AnnAssign/Assign) into SymTab to fix ordering: a class
# may reference a module variable declared later in the AST. translate_children later skips existing ones.
if ch is not None:
for ci in range(cn_count):
child: ast.AST* = ch.get(ci)
if child is None: continue
kd_mv: int = child.kind()
if kd_mv == ast.ASTKind.AnnAssign or kd_mv == ast.ASTKind.Assign: GenApi.handle_module_level_var(self, child)
# Create forward declarations
GenApi.forward_declare_functions(self, tree)
added: int = GenApi.translate_children(self, tree)
# _erase_viperbuild_macros - erase module-level viperbuild macro statements.
# viperbuild is a compile-time macro library consumed by Pass 0; leaving its statements would codegen undefined calls.
def _vb_in_proj_vars(self, name: str, proj_vars: list[str]*) -> int:
"""Return 1 if name is a registered Project macro variable."""
if name is None or proj_vars is None: return 0
n: sizet = len(proj_vars)
for i in range(n):
s: str = proj_vars.get(i)
if s is not None and string.strcmp(s, name) == 0: return 1
return 0
def _vb_is_project_ctor(self, value: ast.AST*, alias: str, ctor: str) -> int:
"""Return 1 if value is `vb.Project(...)` or `Project(...)`."""
if value is None or value.kind() != ast.ASTKind.Call: return 0
cl: ast.Call* = ast.Call*(value)
if cl is None or cl.func is None: return 0
fk: int = cl.func.kind()
if fk == ast.ASTKind.Attribute:
if alias is None: return 0
at: ast.Attribute* = ast.Attribute*(cl.func)
if at is None or at.attr is None or at.value is None: return 0
if at.value.kind() != ast.ASTKind.Name: return 0
bn: ast.Name* = ast.Name*(at.value)
if bn is None or bn.id is None: return 0
if string.strcmp(bn.id, alias) != 0: return 0
if string.strcmp(at.attr, "Project") == 0: return 1
return 0
if fk == ast.ASTKind.Name:
if ctor is None: return 0
nm: ast.Name* = ast.Name*(cl.func)
if nm is None or nm.id is None: return 0
if string.strcmp(nm.id, ctor) == 0: return 1
return 0
def _vb_is_macro_assign(self, target: ast.AST*, value: ast.AST*, alias: str, ctor: str, proj_vars: list[str]*) -> int:
"""Return 1 if the assignment is a viperbuild macro statement and register it."""
if target is None or value is None: return 0
tk: int = target.kind()
if tk == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(target)
if nm is None or nm.id is None: return 0
if self._vb_is_project_ctor(value, alias, ctor) != 0:
proj_vars.append(nm.id)
return 1
return 0
if tk == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(target)
if at is None or at.value is None: return 0
if at.value.kind() != ast.ASTKind.Name: return 0
bn: ast.Name* = ast.Name*(at.value)
if bn is None or bn.id is None: return 0
if self._vb_in_proj_vars(bn.id, proj_vars) != 0: return 1
return 0
def _vb_is_macro_call(self, value: ast.AST*, proj_vars: list[str]*) -> int:
"""Return 1 if the expression is `proj.config(...)` or `proj.build(...)`."""
if value is None or value.kind() != ast.ASTKind.Call: return 0
cl: ast.Call* = ast.Call*(value)
if cl is None or cl.func is None: return 0
if cl.func.kind() != ast.ASTKind.Attribute: return 0
at: ast.Attribute* = ast.Attribute*(cl.func)
if at is None or at.attr is None or at.value is None: return 0
if at.value.kind() != ast.ASTKind.Name: return 0
bn: ast.Name* = ast.Name*(at.value)
if bn is None or bn.id is None: return 0
if self._vb_in_proj_vars(bn.id, proj_vars) == 0: return 0
if string.strcmp(at.attr, "config") == 0: return 1
if string.strcmp(at.attr, "build") == 0: return 1
return 0
def _vb_is_macro_stmt(self, child: ast.AST*, alias: str, ctor: str, proj_vars: list[str]*) -> int:
"""Return 1 if a module-level statement is an erasable viperbuild macro statement."""
if child is None: return 0
kd: int = child.kind()
if kd == ast.ASTKind.Assign:
a: ast.Assign* = ast.Assign*(child)
if a is None or a.targets is None or a.value is None: return 0
if len(a.targets) != 1: return 0
return self._vb_is_macro_assign(a.targets.get(0), a.value, alias, ctor, proj_vars)
if kd == ast.ASTKind.AnnAssign:
an: ast.AnnAssign* = ast.AnnAssign*(child)
if an is None or an.target is None or an.value is None: return 0
return self._vb_is_macro_assign(an.target, an.value, alias, ctor, proj_vars)
if kd == ast.ASTKind.Expr:
ex: ast.Expr* = ast.Expr*(child)
if ex is None or ex.value is None: return 0
return self._vb_is_macro_call(ex.value, proj_vars)
return 0
def _erase_viperbuild_macros(self, pool: memhub.MemBuddy*, tree: ast.AST*) -> int:
"""Erase module-level viperbuild macro statements (replacing tree.children); 1 if erased."""
if tree is None or tree.children is None: return 0
ch: list[ast.AST*]* = tree.children
cn: sizet = len(ch)
if cn == 0: return 0
# (1) Find viperbuild's local name / Project constructor name
alias: str = None
ctor: str = None
ci: sizet = 0
while ci < cn:
child: ast.AST* = ch.get(ci)
if child is not None:
kd: int = child.kind()
if kd == ast.ASTKind.Import:
names: list[ast.AST*]* = ast.Import*(child).names
if names is not None:
for ni in range(len(names)):
al: ast.Alias* = ast.Alias*(names.get(ni))
if al is not None and al.name is not None:
if string.strcmp(al.name, "viperbuild") == 0:
if al.asname is not None: alias = al.asname
else: alias = "viperbuild"
elif kd == ast.ASTKind.ImportFrom:
ifr: ast.ImportFrom* = ast.ImportFrom*(child)
if ifr is not None and ifr.level == 0 and ifr.names is not None:
if ifr.module is not None and string.strcmp(ifr.module, "viperbuild") == 0:
for ni in range(len(ifr.names)):
al: ast.Alias* = ast.Alias*(ifr.names.get(ni))
if al is not None and al.name is not None:
if string.strcmp(al.name, "Project") == 0:
if al.asname is not None: ctor = al.asname
else: ctor = "Project"
ci += 1
if alias is None and ctor is None: return 0
# (2) Filter: register Project macro variables, then drop the related statements
proj_vars: list[str]* = list[str](pool, 8)
kept: list[ast.AST*]* = list[ast.AST*](pool, 8)
if proj_vars is None or kept is None: return 0
erased: int = 0
for ci in range(cn):
child = ch.get(ci)
if child is not None:
if self._vb_is_macro_stmt(child, alias, ctor, proj_vars) != 0: erased = 1
else: kept.append(child)
if erased != 0: tree.children = kept
return erased
# Auto container visibility injection (mirrors TPC _inject_auto_imports): mark list/dict classes
# visible so bare annotations/constructors resolve. Detects AnnAssign annotation, Call.func, Subscript.value.
def _inject_auto_container_visibility(self, pool: memhub.MemBuddy*, tree: ast.AST*):
"""Detect list/dict container usage and inject container-class visibility."""
if tree is None or tree.children is None: return
# Use stack buffers to pass detection results (4-byte int pointers across helper calls)
need_list_box: t.CPtr = stdlib.malloc(4)
need_dict_box: t.CPtr = stdlib.malloc(4)
if need_list_box is None or need_dict_box is None:
if need_list_box is not None: stdlib.free(need_list_box)
if need_dict_box is not None: stdlib.free(need_dict_box)
return
need_list_box[0] = 0
need_dict_box[0] = 0
self._scan_container_usage(tree, need_list_box, need_dict_box)
if need_dict_box[0] != 0:
# dict class visible + _dict module imported (SHA1 resolution for constructors/method calls)
GenApi.add_visible_struct(pool, "dict")
if self._imported_modules is None or GenApi.is_module_imported(self._imported_modules, "_dict") == 0:
# Break cycles: skip injection when _dict already depends on the current module (avoids a
# memhub<->_list 2-cycle that stops Phase A from converging). Mirrors Phase1Main._InjectedCreatesCycle.
if self._ContainerInjectionCreatesCycle("_dict") == 0: self._imported_modules = GenApi.add_imported_module(pool, self._imported_modules, "_dict")
if need_list_box[0] != 0:
GenApi.add_visible_struct(pool, "list")
if self._imported_modules is None or GenApi.is_module_imported(self._imported_modules, "_list") == 0:
if self._ContainerInjectionCreatesCycle("_list") == 0: self._imported_modules = GenApi.add_imported_module(pool, self._imported_modules, "_list")
stdlib.free(need_list_box)
stdlib.free(need_dict_box)
# Auto assert-import injection: a module that uses `assert` is equivalent to having
# `import testcheck` at the top, since assert lowers to testcheck.check(...). Injected
# after real imports (so de-duplication works) and before dependency-vpi loading.
def _inject_auto_assert_import(self, pool: memhub.MemBuddy*, tree: ast.AST*):
"""Ensure `testcheck` is imported when the tree contains an assert statement."""
if tree is None: return
if self._tree_has_assert(tree, 0) == 0: return
if self._imported_modules is not None and GenApi.is_module_imported(self._imported_modules, "testcheck") != 0: return
# Break cycles: skip when testcheck already depends on the current module (mirrors the
# _list/_dict guard; the residual case is cleaned up by Phase1 _BreakContainerCyclesInDeps).
if self._ContainerInjectionCreatesCycle("testcheck") != 0: return
self._imported_modules = GenApi.add_imported_module(pool, self._imported_modules, "testcheck")
# _tree_has_assert - scan statement blocks for an `assert` node (depth-guarded).
def _tree_has_assert(self, node: ast.AST*, depth: int) -> int:
"""Return 1 when the subtree contains an assert statement, else 0."""
if node is None: return 0
# Deep-recursion guard: Translator has no Gen facade, so report through stdio/stdlib
# (same idiom as Generator.__init__'s allocation failures).
if depth > 512:
stdio.printf("FATAL: assert-scan recursion too deep (AST cycle or nesting over 512 levels)\n")
stdlib.exit(1)
k_a: int = node.kind()
if k_a == ast.ASTKind.Assert: return 1
# Try: handlers/orelse/finalbody are separate lists, not part of children.
if k_a == ast.ASTKind.Try:
tr_a: ast.Try* = ast.Try*(node)
if tr_a is not None:
if self._list_has_assert(tr_a.handlers, depth) != 0: return 1
if self._list_has_assert(tr_a.orelse, depth) != 0: return 1
if self._list_has_assert(tr_a.finalbody, depth) != 0: return 1
# Match: the case bodies live in cases, not children.
elif k_a == ast.ASTKind.Match:
mt_a: ast.Match* = ast.Match*(node)
if mt_a is not None and self._list_has_assert(mt_a.cases, depth) != 0: return 1
# Recurse into this node's statement block (module/function/class/if/for/while/with/except bodies).
if node.children is not None:
ch_a: list[ast.AST*]* = node.children
n_a: sizet = len(ch_a)
for bi_a in range(n_a):
if self._tree_has_assert(ch_a.get(bi_a), depth + 1) != 0: return 1
return 0
# _list_has_assert - apply _tree_has_assert over a statement list.
def _list_has_assert(self, lst: list[ast.AST*]*, depth: int) -> int:
"""Return 1 when any node in the statement list contains an assert statement."""
if lst is None: return 0
n_l: sizet = len(lst)
for i_l in range(n_l):
if self._tree_has_assert(lst.get(i_l), depth + 1) != 0: return 1
return 0
def _ContainerInjectionCreatesCycle(self, mod_name: str) -> int:
"""Return 1 if injecting mod_name (_list/_dict) would create a dependency cycle."""
if mod_name is None or self.ModuleSha1 is None: return 0
inj_sha1: str = StubMergerStore.LookupSha1ByModName(mod_name)
if inj_sha1 is None: return 0
inj_deps: str = StubMergerStore.LookupIncludesDeps(inj_sha1)
if inj_deps is None: return 0
# Current module name = RelPath basename (after last '/' or '\\') without extension
rel_path: str = StubMergerStore.LookupSha1RelPath(self.ModuleSha1)
if rel_path is None: return 0
base: str = string.strrchr(rel_path, '/')
if base is None: base = string.strrchr(rel_path, '\\')
if base is not None: base = base + 1
else: base = rel_path
dot: str = string.strchr(base, '.')
nm_buf: bytes = stdlib.malloc(128)
if nm_buf is None: return 0
if dot is not None:
nm_len: sizet = sizet(uint64(dot) - uint64(base))
if nm_len > 127: nm_len = 127
string.strncpy(nm_buf, base, nm_len)
nm_buf[nm_len] = '\0'
else:
string.strncpy(nm_buf, base, 127)
nm_buf[127] = '\0'
# Word-boundary match: whether inj_deps contains a token equal to the current module name
nm_len2: sizet = string.strlen(nm_buf)
d_len: sizet = string.strlen(inj_deps)
dp: sizet = 0
while dp < d_len:
ts: sizet = dp
while dp < d_len and inj_deps[dp] != ' ': dp += 1
tl: sizet = dp - ts
if dp < d_len: dp += 1
if tl > 0 and tl == nm_len2 and string.strncmp(inj_deps + ts, nm_buf, tl) == 0:
stdlib.free(nm_buf)
return 1
stdlib.free(nm_buf)
return 0
def _scan_container_usage(self, node: ast.AST*, need_list_box: t.CPtr, need_dict_box: t.CPtr):
"""Recursively scan AST statement structure for list/dict container usage."""
if node is None: return
k: int = node.kind()
if k == ast.ASTKind.AnnAssign:
aa_s: ast.AnnAssign* = ast.AnnAssign*(node)
if aa_s.annotation is not None: self._check_container_type_node(aa_s.annotation, need_list_box, need_dict_box)
elif k == ast.ASTKind.Call:
cl_s: ast.Call* = ast.Call*(node)
if cl_s.func is not None: self._check_container_type_node(cl_s.func, need_list_box, need_dict_box)
elif k == ast.ASTKind.Subscript:
sub_s: ast.Subscript* = ast.Subscript*(node)
if sub_s.value is not None: self._check_container_type_node(sub_s.value, need_list_box, need_dict_box)
elif k == ast.ASTKind.FunctionDef:
fd_s: ast.FunctionDef* = ast.FunctionDef*(node)
if fd_s.returns is not None: self._check_container_type_node(fd_s.returns, need_list_box, need_dict_box)
if fd_s.args is not None:
args_s: ast.Arguments* = ast.Arguments*(fd_s.args)
if args_s.args is not None:
args_list_s: list[ast.AST*]* = args_s.args
an_s: sizet = len(args_list_s)
ai_s: sizet
for ai_s in range(an_s):
arg_s: ast.AST* = args_list_s.get(ai_s)
if arg_s is not None:
ag_s: ast.Arg* = ast.Arg*(arg_s)
if ag_s.annotation is not None: self._check_container_type_node(ag_s.annotation, need_list_box, need_dict_box)
# `[...]`/`{}` literals do not trigger _list/_dict imports (widely used for compile-time constants).
# Real container usage is detected in type positions; then recurse into statement-block children.
if node.children is not None:
ch_s: list[ast.AST*]* = node.children
n_s: sizet = len(ch_s)
for ci_s in range(n_s): self._scan_container_usage(ch_s.get(ci_s), need_list_box, need_dict_box)
def _check_container_type_node(self, node: ast.AST*, need_list_box: t.CPtr, need_dict_box: t.CPtr):
"""Check whether a type-position node is list/dict, recursing through Name/Subscript/BinOp."""
if node is None: return
k: int = node.kind()
if k == ast.ASTKind.Name:
nm_s: ast.Name* = ast.Name*(node)
if nm_s.id is not None:
if string.strcmp(nm_s.id, "dict") == 0: need_dict_box[0] = 1
elif string.strcmp(nm_s.id, "list") == 0: need_list_box[0] = 1
elif k == ast.ASTKind.Subscript:
sub_s: ast.Subscript* = ast.Subscript*(node)
if sub_s.value is not None: self._check_container_type_node(sub_s.value, need_list_box, need_dict_box)
# rawptr[list[int]] / list[AST]: the slice may contain list, so recurse into it
if sub_s.slice is not None: self._check_container_type_node(sub_s.slice, need_list_box, need_dict_box)
elif k == ast.ASTKind.BinOp:
bop_s: ast.BinOp* = ast.BinOp*(node)
self._check_container_type_node(ast.binop_get_left(bop_s), need_list_box, need_dict_box)
self._check_container_type_node(ast.binop_get_right(bop_s), need_list_box, need_dict_box)
def _annotation_is_container(self, ann: ast.AST*) -> int:
"""Return 1 if the annotation is a list/dict container; array annotations return 0."""
while ann is not None:
ak: int = ann.kind()
if ak == ast.ASTKind.Name:
nm_c: ast.Name* = ast.Name*(ann)
if nm_c.id is not None:
if string.strcmp(nm_c.id, "list") == 0: return 1
if string.strcmp(nm_c.id, "dict") == 0: return 1
return 0
elif ak == ast.ASTKind.Subscript:
sub_c: ast.Subscript* = ast.Subscript*(ann)
ann = sub_c.value
else: return 0
return 0
# ============================================================
# IR output
def dump_ir(self, buf: bytes, size: sizet, mode: int):
"""Write the LLVM IR into a buffer: mode 0=full, 1=stub (decls only), 2=text (code only)."""
if self.Module is None or buf is None: return
if _mbuddy is None: return
buf[0] = '\0'
mlirlite.LLVMModulePrint(buf, size, self.Module, _mbuddy, mode)
# --traceback: inject an appending-linkage @llvm.global_ctors calling the traceback module's Install()
# once at process start; fail fast if Install is missing or @llvm.global_ctors already exists.
# _tb_module_has_global_ctors - does the module already define @llvm.global_ctors
def _tb_module_has_global_ctors(mod: mlirlite.LLVMModule*) -> int:
if mod is None: return 0
g: mlirlite.GlobalVariable* = mod.GlobalHead
steps: sizet = 0
while g is not None:
steps += 1
if steps > 100000: break
if g.Name is not None and string.strcmp(g.Name, "llvm.global_ctors") == 0: return 1
g = g.Next
return 0
# _tb_find_install_function - locate this module's own Install()
# Accepts "<16hex>.Install" (usual SHA1-mangled form) or the bare name.
def _tb_find_install_function(mod: mlirlite.LLVMModule*) -> mlirlite.Function*:
if mod is None: return None
cur: mlirlite.Function* = mod.FuncHead
steps: sizet = 0
while cur is not None:
steps += 1
if steps > 100000: return None
nm: str = mlirlite.function_get_name(cur)
if nm is not None:
if string.strcmp(nm, "Install") == 0: return cur
nl: sizet = mlirlite.function_get_name_len(cur)
if nl == 24 and string.strcmp(nm + 16, ".Install") == 0: return cur
cur = mlirlite.function_get_next(cur)
return None
# InjectTracebackInstallCtor - inject the startup Install() ctor into the module.
# NOTE: emitted with default (external) linkage since FunctionPrint has no linkage field; no clash.
def InjectTracebackInstallCtor(tr: Translator*) -> int:
if tr is None or tr.Module is None: return 0
if tr._declare_only != 0: return 0
pool: memhub.MemBuddy* = tr.Pool
if pool is None:
VLogger.error("--traceback: memory pool is not initialized, cannot inject the Install constructor", "TRACEBACK")
return 1
mod: mlirlite.LLVMModule* = tr.Module
if _tb_module_has_global_ctors(mod) != 0:
VLogger.error("--traceback: @llvm.global_ctors already exists in the module, cannot inject the traceback startup constructor (no silent fallback)", "TRACEBACK")
return 1
install_fn: mlirlite.Function* = _tb_find_install_function(mod)
if install_fn is None:
VLogger.error("--traceback: Install() function not found in the traceback module, cannot inject the startup constructor (no silent fallback)", "TRACEBACK")
return 1
# 1) ctor: entry { call <ret> @Install(); ret void }
void_ty: mlirlite.LLVMType* = mlirlite.Void(pool)
ctor: mlirlite.Function* = mlirlite.new_function(pool, "__traceback_install_ctor", void_ty)
if ctor is None:
VLogger.error("--traceback: failed to allocate the constructor function", "TRACEBACK")
return 1
blk: mlirlite.BasicBlock* = mlirlite.new_basic_block(pool, "entry")
if blk is None:
VLogger.error("--traceback: failed to allocate the constructor basic block", "TRACEBACK")
return 1
mlirlite.function_add_block(ctor, blk)
mlirlite.module_add_function(mod, ctor)
builder: mlirlite.IRBuilder* = mlirlite.new_builder(pool, ctor)
if builder is None:
VLogger.error("--traceback: failed to allocate the constructor IRBuilder", "TRACEBACK")
return 1
mlirlite.position_at_end(builder, blk)
iname: str = mlirlite.function_get_name(install_fn)
install_ret: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(install_fn)
mlirlite.build_call(builder, iname, None, 0, install_ret, 0)
mlirlite.build_ret_void(builder)
# 2) @llvm.global_ctors = appending global [1 x {i32, void ()*, i8*}] [ ... ]
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
i8p_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
fnv_ty: mlirlite.LLVMType* = mlirlite.Func(pool, void_ty, None, 0)
fnp_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, fnv_ty)
f1: mlirlite.ParamNode* = mlirlite.new_param_node(pool, i32_ty)
f2: mlirlite.ParamNode* = mlirlite.new_param_node(pool, fnp_ty)
f3: mlirlite.ParamNode* = mlirlite.new_param_node(pool, i8p_ty)
if f1 is None or f2 is None or f3 is None:
VLogger.error("--traceback: failed to allocate global_ctors struct fields", "TRACEBACK")
return 1
mlirlite.paramnode_set_next(f1, f2)
mlirlite.paramnode_set_next(f2, f3)
elem_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, f1, 3, None)
arr_ty: mlirlite.LLVMType* = mlirlite.Array(pool, elem_ty, 1)
if elem_ty is None or arr_ty is None:
VLogger.error("--traceback: failed to build the global_ctors type", "TRACEBACK")
return 1
gv: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, "llvm.global_ctors", arr_ty)
if gv is None:
VLogger.error("--traceback: failed to allocate the @llvm.global_ctors global variable", "TRACEBACK")
return 1
mlirlite.global_set_linkage(gv, "appending")
gv.Initializer = "[{ i32, void ()*, i8* } { i32 65535, void ()* @__traceback_install_ctor, i8* null }]"
mlirlite.module_add_global(mod, gv)
VLogger.info("--traceback: injected the startup Install() constructor (@llvm.global_ctors)", "TRACEBACK")
return 0
# Mixin - non-polymorphic base class for handlers (moved from HandlesBase):
# holds the Translator back-pointer; cross-module handler classes inherit `class X(Translator.Mixin)`.
novirtual class Mixin:
"""Non-polymorphic base for all Handles: holds the Translator back-pointer and shared delegates."""
Trans: Translator*
def InitMixin(self, trans: Translator*) -> int:
"""Initialize the Mixin fields, called from a subclass __init__."""
self.Trans = trans
return 0
# ============================================================
# Global mbuddy pointer
_mbuddy: memhub.MemBuddy*