Files
ViperC/App/lib/core/Gen/Translator.vp
T
TermiNexus fb53f88893 refactor(gen): R12/R13 同步——下标 while 循环改 for range、去 c.Set
与 includes 同步执行两批源改写:R12 把「i: int = 0 与 while i < N: … i += 1」形式的计数循环改写为「for i in range(N):」;R13 把 c.Set(*p, v) 迁移为 *p = v、c.Set(a, b) 迁移为 a = b(c.Set 是正在退化的路径)。

验证:bootstrap 三重重编 SUCCESS(无 vast_c 解析回退);全量 77/77 顺序回归通过(WebTest 首轮网络超时,单独重跑通过)。
2026-10-05 15:33:58 +08:00

933 lines
48 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.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: rawptr[mlirlite.BasicBlock]
_coro_await_slot: mlirlite.Value*
_coro_frame: mlirlite.Value*
_coro_next_state: int
_coro_nseg: int
# 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: rawptr[mlirlite.BasicBlock] # While break target (M2 single level)
_coro_continue_bb: rawptr[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: rawptr[mlirlite.BasicBlock]
_continue_bb: rawptr[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: rawptr[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: rawptr[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: rawptr[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_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)
# Declare printf
printf_func: mlirlite.Function* = mlirlite.create_declare(pool, mod, "printf", i32_ty)
if printf_func is None:
VLogger.error("CreateDeclare printf returned NULL", "TR")
return 1
mlirlite.add_param(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(pool, mod, "llvm.va_start", void_ty_decl)
if va_start_func is not None: mlirlite.add_param(pool, va_start_func, i8_ptr_ty, "ap")
va_end_func: mlirlite.Function* = mlirlite.create_declare(pool, mod, "llvm.va_end", void_ty_decl)
if va_end_func is not None: mlirlite.add_param(pool, va_end_func, i8_ptr_ty, "ap")
# Declare malloc (for closure allocation)
malloc_func: mlirlite.Function* = mlirlite.create_declare(pool, mod, "malloc", i8_ptr_ty)
if malloc_func is not None:
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
mlirlite.add_param(pool, malloc_func, i64_ty, "size")
# 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)
# 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)
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: rawptr[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: 内存池未初始化,无法注入 Install 构造器", "TRACEBACK")
return 1
mod: mlirlite.LLVMModule* = tr.Module
if _tb_module_has_global_ctors(mod) != 0:
VLogger.error("--traceback: 模块内已存在 @llvm.global_ctors,无法注入 traceback 启动期构造器(不静默降级)", "TRACEBACK")
return 1
install_fn: mlirlite.Function* = _tb_find_install_function(mod)
if install_fn is None:
VLogger.error("--traceback: 在 traceback 模块内找不到 Install() 函数,无法注入启动期构造器(不静默降级)", "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: 构造器函数分配失败", "TRACEBACK")
return 1
blk: mlirlite.BasicBlock* = mlirlite.new_basic_block(pool, "entry")
if blk is None:
VLogger.error("--traceback: 构造器基本块分配失败", "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: 构造器 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: global_ctors 结构体字段分配失败", "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: global_ctors 类型构造失败", "TRACEBACK")
return 1
gv: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, "llvm.global_ctors", arr_ty)
if gv is None:
VLogger.error("--traceback: @llvm.global_ctors 全局变量分配失败", "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: 已注入启动期 Install() 构造器(@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*