四个探针:PhaseA translating <path> / vpi.gen <rel_path> / TR.enter sha1 / TR.prelude.done。用于把崩溃点夹在 解析→vpi生成→模块前言→函数体翻译 四段之间。
1017 lines
54 KiB
Plaintext
1017 lines
54 KiB
Plaintext
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
|
||
# [TEMP dbg] 阶段日志:定位崩溃点(定位后移除)
|
||
VLogger.info(f"TR.enter sha1={self.ModuleSha1}", "TR")
|
||
|
||
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")
|
||
# [TEMP dbg] 阶段日志:模块前言完成(定位崩溃点,定位后移除)
|
||
VLogger.info("TR.prelude.done", "TR")
|
||
|
||
# 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*
|
||
|