- GenApi:新增 compute_life_anno / _RetKindFromTag 与 RET_KIND_* 编码, 提供 get_ret_kind / get_ret_kind_method / lookup_ret_kind_via_vpi; _WillFreeMergeTag 不再拦截 ret(改由 compute_life_anno 收集校验)。 - StructGenMixin:EscDirTable 由 3 槽扩到 4 槽(新增 ret_kind 列), compute_escape_summary 把契约落在 box[5] 并登记。 - Stub:.vpi 行尾新增 escr=<ret_kind>(Converter 写、VpiReader 读 _g_vpi_esd_r)。 - AssignGenMixin.life_of_expr(Call):@life(ret(...)) 声明优先于任何推断; frame/heap/static/borrows=none 直接映射,borrows=x 取对应实参生命期, 方法调用按接收者静态类型解析类名。 - StmtGenCifMixin:同批移除定义端 return <本帧地址> 的 fail-fast, 返回值去向改由调用点按被调者契约判定。
1545 lines
85 KiB
Plaintext
1545 lines
85 KiB
Plaintext
import lib.core.Gen.GenTypes as GenTypes
|
||
import lib.core.Gen.Generator as Generator
|
||
import lib.core.Gen.GenApi as GenApi
|
||
import lib.core.Gen.Translator as HT
|
||
import t, c
|
||
from stdint import *
|
||
import ast
|
||
import mlirlite
|
||
import memhub
|
||
import hashtable
|
||
import string
|
||
import stdio
|
||
import stdlib
|
||
import viperlib
|
||
import w32.fileio as fileio
|
||
import w32.win32file as win32file
|
||
import lib.core.VLogger as VLogger
|
||
import lib.Stub.StubMerger as StubMerger
|
||
import lib.core.CompilerFactory as CompilerFactory
|
||
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
|
||
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||
import lib.Stub.StubGen.VpiReader as VpiReader
|
||
|
||
|
||
# CallGenBase - split 1/9 of CallGenMixin (bottom parent): module symbol
|
||
# lookup + return-type resolution infrastructure
|
||
|
||
|
||
# Constants (capacity limits used across the Gen layer)
|
||
# Global func table / defaults table capacity
|
||
define GLOBAL_FUNC_MAX = 8192
|
||
define GLOBAL_DEFAULTS_MAX = 4096
|
||
define _CEXPORT_MAX = 256
|
||
define ASM_MAX_OPERANDS = 16
|
||
define ASM_TEXT_BUF_SIZE = 2048
|
||
define ASM_CONST_BUF_SIZE = 512
|
||
define ASM_ARGS_BUF_SIZE = 512
|
||
define ASM_LINE_BUF_SIZE = 4096
|
||
define LLVMIR_MAX_OPS = 16
|
||
define LLVMIR_TEMPLATE_BUF = 512
|
||
# Closure name table size (CallGenLeafKit._ensure_closure_names/register_closure_name)
|
||
define MAX_CLOSURE_NAMES = 128
|
||
# Function-pointer signature table size (CallGenLeafKit._ensure_funcptr_sigs/register_func_ptr_sig)
|
||
define MAX_FUNC_PTR_SIGS = 64
|
||
# isinstance subclass collection cap (bounds check); used by
|
||
# CallGenLeafKit._collect_subclass_vtable_classes
|
||
define ISINSTANCE_CLASS_MAX = 64
|
||
|
||
|
||
# CallGenBase - bottom parent class (fields + module lookup + return-type resolution)
|
||
novirtual class CallGenBase:
|
||
"""Base class for call dispatch (fields + module symbol lookup + return-type resolution infrastructure)"""
|
||
|
||
# --- c.cexport registry (was _g_cexport_sha1_arr/_g_cexport_func_arr/_g_cexport_count) ---
|
||
Gen: Generator.Generator*
|
||
CexportSha1Arr: bytes = None
|
||
CexportFuncArr: bytes = None
|
||
CexportCount: int = 0
|
||
|
||
# --- static symbol registry (static / @t.CStatic -> defining module SHA1),
|
||
# meaning no cross-module use (C internal linkage); PermPool fixed slots.
|
||
StaticSha1Arr: bytes = None
|
||
StaticNameArr: bytes = None
|
||
StaticCount: int = 0
|
||
|
||
# --- LLVMIR register sequence ---
|
||
LLVMIRRegSeq: int = 0
|
||
|
||
# --- temp directory ---
|
||
TempDir: str = None
|
||
TempDirLen: sizet = 0
|
||
|
||
# --- closure name table (was _g_closure_names/_g_closure_count) ---
|
||
ClosureNames: str = None
|
||
ClosureCount: int = 0
|
||
|
||
# --- function-pointer signature table (was _g_funcptr_names/_g_funcptr_rets/_g_funcptr_count) ---
|
||
FuncptrNames: str = None
|
||
FuncptrRets: uint64* = None
|
||
FuncptrCount: int = 0
|
||
|
||
# --- global function registry (was _global_funcs/_global_func_count) ---
|
||
GlobalFuncs: GenTypes.FuncEntry* = None
|
||
GlobalFuncCount: int = 0
|
||
|
||
# --- global func table "name -> slot index" index (open addressing; slot
|
||
# stores index+1, 0=empty). GlobalFuncs only appends; Built==Count is trusted.
|
||
GlobalFuncNameIdx: sizet* = None
|
||
GlobalFuncNameIdxCap: int = 0
|
||
GlobalFuncNameIdxBuilt: int = -1
|
||
|
||
# --- global defaults table (was _global_defaults/_global_defaults_count) ---
|
||
GlobalDefaults: GenTypes.FuncDefaultsEntry* = None
|
||
GlobalDefaultsCount: int = 0
|
||
|
||
# --- cached method return-type text from dependency text.ll (perf opt for
|
||
# _LookupMethodRetTyFromTextLL); key = full mangled name, value = text only.
|
||
MethodRetTyMap: hashtable.HashTable* = None
|
||
|
||
# --- parameter auto-address modes for value-reference params (`&T` / `&temp T`) ---
|
||
# key = callee symbol name -> byte-per-parameter mode string: 0=none, '1'=ref, '2'=temp
|
||
# (PermPool-persistent; index = emitted parameter index, self included for methods).
|
||
AutoAddrModes: hashtable.HashTable* = None
|
||
|
||
def InitCallGen(self) -> int:
|
||
"""Initialize the call mixin fields (inlined by subclass constructors since NoVTable does not forward inherited symbols)"""
|
||
if self is None: return 1
|
||
return 0
|
||
|
||
# _register_auto_addr_modes - store per-parameter value-reference modes for a callee symbol name.
|
||
def _register_auto_addr_modes(self, name: str, modes: str, count: int) -> int:
|
||
"""Store per-parameter auto-address modes (PermPool-persistent) keyed by callee symbol name."""
|
||
if name is None or modes is None or count <= 0: return 0
|
||
perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
||
if perm is None: return 0
|
||
if self.AutoAddrModes is None:
|
||
self.AutoAddrModes = hashtable.HashTable(perm)
|
||
if self.AutoAddrModes is None: return 0
|
||
key: str = perm.alloc(string.strlen(name) + 1)
|
||
if key is None: return 0
|
||
string.strcpy(key, name)
|
||
val: str = perm.alloc(sizet(count) + 1)
|
||
if val is None: return 0
|
||
string.memcpy(val, modes, sizet(count))
|
||
val[count] = char(0)
|
||
self.AutoAddrModes[key] = val
|
||
return 1
|
||
|
||
# _auto_addr_mode - look up the value-reference mode of parameter idx (0=none / 1=ref / 2=temp).
|
||
def _auto_addr_mode(self, name: str, idx: int) -> int:
|
||
"""Return the value-reference mode for parameter idx of a callee symbol name (0 if none/unknown)."""
|
||
if self.AutoAddrModes is None or name is None or idx < 0: return 0
|
||
v: t.CPtr = self.AutoAddrModes.__getitem__(name)
|
||
if v is None: return 0
|
||
modes: str = t.CPtr(v)
|
||
i: int = 0
|
||
while i < idx and modes[i] != char(0): i += 1
|
||
if i != idx: return 0
|
||
code: int = int(modes[idx])
|
||
if code == 49: return 1 # '1' = ref
|
||
if code == 50: return 2 # '2' = temp
|
||
return 0
|
||
|
||
# translate_call_arg - translate one call argument, applying ref/temp auto-address when required.
|
||
def translate_call_arg(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||
trans: HT.Translator*, callee_name: str, arg_idx: int, arg_node: ast.AST*,
|
||
fpty: mlirlite.LLVMType*, use_ctx: str = "param") -> mlirlite.Value*:
|
||
"""Translate a call argument; value-reference params take the address (ref) or a temp copy (temp)."""
|
||
mode: int = self._auto_addr_mode(callee_name, arg_idx)
|
||
if mode == 0:
|
||
return self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, None, 0, trans, fpty, use_ctx)
|
||
# Value-reference param: translate without the pointer target hint (a char/str literal must
|
||
# not be turned into a string), then apply the auto-address transform.
|
||
av: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, None, 0, trans, None, use_ctx)
|
||
return self._apply_auto_addr_arg(builder, pool, mod, trans, mode, arg_node, av)
|
||
|
||
# _apply_auto_addr_arg - ref: take the lvalue address; temp: copy into a fresh entry-block slot.
|
||
def _apply_auto_addr_arg(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||
trans: HT.Translator*, mode: int, node: ast.AST*, av: mlirlite.Value*) -> mlirlite.Value*:
|
||
"""Apply a value-reference mode to a translated argument value."""
|
||
if av is None: return None
|
||
if self.Gen.ExprGenBase.is_ptr_type(av.Ty) != 0: return av # already a pointer: pass through
|
||
if mode == 1:
|
||
if node is None: return av
|
||
nk: int = node.kind()
|
||
if nk == ast.ASTKind.Name or nk == ast.ASTKind.Attribute or nk == ast.ASTKind.Subscript:
|
||
ao_node: ast.AST* = ast.UnaryOp(pool, ast.OpKind.AddrOf, node)
|
||
if ao_node is None: return av
|
||
return self.Gen.ExprGen.translate_value(builder, pool, mod, ao_node, None, 0, trans, None, None)
|
||
self.Gen.TypeGenErr.fatal_error(node, "value-reference parameter '&T' needs an lvalue argument (variable / field / element) or an existing pointer; a literal or expression result has no address (use '&temp T' to pass a temporary copy)", "CastError")
|
||
return av
|
||
if mode == 2:
|
||
slot: mlirlite.Value* = mlirlite.build_alloca_at_entry(builder, av.Ty)
|
||
if slot is None: return av
|
||
mlirlite.build_store(builder, av, slot)
|
||
return slot
|
||
return av
|
||
|
||
# _vpi_auto_addr_mode - value-reference mode of parameter idx for a cross-module ({sha1}-mangled)
|
||
# callee, read from its vpi definition-side param annotations. The parsing (split / ensure-load /
|
||
# lookup / segment decode) lives here only; _coerce_args_to_def_params and _apply_auto_addr_to_call_args
|
||
# both call it, so there is a single implementation. cur_sha1 is the module being compiled: its own
|
||
# functions are covered by AutoAddrModes, so the (guaranteed-miss) vpi load is skipped for them.
|
||
# Returns 0=none / 1=ref / 2=temp.
|
||
def _vpi_auto_addr_mode(self, call_name: str, cur_sha1: str, idx: int) -> int:
|
||
"""Cross-module ({sha1}.cls.method / {sha1}.func) value-reference mode from the vpi cache; 0 if none/unknown."""
|
||
if call_name is None or idx < 0: return 0
|
||
if GenApi.sha1_prefix_len(call_name) == 0: return 0
|
||
# Same-module callee: AutoAddrModes (registration during this compilation) is authoritative.
|
||
if cur_sha1 is not None and string.strncmp(call_name, cur_sha1, 16) == 0: return 0
|
||
# split {sha1}.{rest}: if rest has '.', it is {cls}.{method}, else {func}
|
||
rest: str = call_name + 17
|
||
cls_nm: str = None
|
||
meth_nm: str = rest
|
||
dot_r: str = string.strrchr(rest, 46)
|
||
if dot_r is not None:
|
||
cls_len: sizet = uint64(dot_r) - uint64(rest)
|
||
if cls_len > 0:
|
||
cls_buf: bytes = stdlib.malloc(cls_len + 1)
|
||
if cls_buf is not None:
|
||
string.strncpy(cls_buf, rest, cls_len)
|
||
cls_buf[cls_len] = '\0'
|
||
cls_nm = cls_buf
|
||
meth_nm = dot_r + 1
|
||
if meth_nm is None or meth_nm[0] == '\0':
|
||
if cls_nm is not None: stdlib.free(cls_nm)
|
||
return 0
|
||
sha1_buf: bytes = stdlib.malloc(17)
|
||
if sha1_buf is None:
|
||
if cls_nm is not None: stdlib.free(cls_nm)
|
||
return 0
|
||
string.strncpy(sha1_buf, call_name, 16)
|
||
sha1_buf[16] = '\0'
|
||
if self.TempDir is not None and self.TempDirLen > 0: VpiReader._ensure_vpi_param_annot_loaded(sha1_buf, self.TempDir)
|
||
prm_annots: str = VpiReader._lookup_vpi_param_annots(sha1_buf, cls_nm, meth_nm)
|
||
mode: int = 0
|
||
if prm_annots is not None: mode = VpiReader._vpi_param_annot_seg_mode(prm_annots, idx)
|
||
stdlib.free(sha1_buf)
|
||
if cls_nm is not None: stdlib.free(cls_nm)
|
||
return mode
|
||
|
||
# _apply_auto_addr_to_call_args - auto-address the user arguments of a call whose definition params
|
||
# are value references (&T -> take the lvalue address / &temp T -> copy into a temp slot). The
|
||
# per-parameter mode is the same-module AutoAddrModes table first, then (for a {sha1}-mangled callee)
|
||
# its vpi annotations. head/count is the emitted argument chain; the first start_idx slots are
|
||
# non-user (self/obj/cls); arg_nodes holds the source ASTs of the user arguments, indexed by
|
||
# (param index - start_idx). Returns the rebuilt chain head; nodes beyond count (e.g. defaults
|
||
# appended by the caller) are preserved. Idempotent: _apply_auto_addr_arg passes an already-pointer
|
||
# argument through, so a caller that also ran translate_call_arg does not double-address.
|
||
def _apply_auto_addr_to_call_args(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||
trans: HT.Translator*, callee_name: str, arg_nodes: list[ast.AST*]*,
|
||
head: mlirlite.Value*, count: int, start_idx: int) -> mlirlite.Value*:
|
||
"""Apply value-reference auto-address (&T / &temp T) to a call's user args; returns the chain head."""
|
||
if head is None or count <= 0: return head
|
||
if callee_name is None or callee_name[0] == '\0': return head
|
||
if trans is None: return head
|
||
new_head: mlirlite.Value* = None
|
||
new_tail: mlirlite.Value* = None
|
||
cur: mlirlite.Value* = head
|
||
idx: int = 0
|
||
while cur is not None and idx < count:
|
||
node_v: mlirlite.Value* = cur
|
||
if idx >= start_idx:
|
||
# Redirect to the canonical CallGenBase sub-object: AutoAddrModes / TempDir live there
|
||
# (registration targets self.Gen.CallGenBase), not on the sibling Call* sub-object.
|
||
mode: int = self.Gen.CallGenBase._auto_addr_mode(callee_name, idx)
|
||
if mode == 0: mode = self.Gen.CallGenBase._vpi_auto_addr_mode(callee_name, trans.ModuleSha1, idx)
|
||
if mode != 0:
|
||
arg_i: int = idx - start_idx
|
||
arg_node: ast.AST* = None
|
||
if arg_nodes is not None and arg_i < len(arg_nodes): arg_node = arg_nodes.get(arg_i)
|
||
node_v = self.Gen.CallGenBase._apply_auto_addr_arg(builder, pool, mod, trans, mode, arg_node, cur)
|
||
if node_v is None: node_v = cur
|
||
if new_head is None: new_head = node_v
|
||
else: mlirlite.value_set_next(new_tail, node_v)
|
||
new_tail = node_v
|
||
cur = mlirlite.value_get_next(cur)
|
||
idx += 1
|
||
if new_head is None: return head
|
||
mlirlite.value_set_next(new_tail, cur)
|
||
return new_head
|
||
|
||
def _lookup_module_sha1(self, mod_name: str) -> str:
|
||
"""Look up the SHA1 for a module name via StubMerger.LookupSha1ByModName (exact match + package fallback); None if absent."""
|
||
return StubMergerStore.LookupSha1ByModName(mod_name)
|
||
|
||
def _lookup_module_sha1_suffix(self, mod_suffix: str) -> str:
|
||
"""Look up a module SHA1 by suffix (e.g. "__types" matches "mlirlite.__types") for relative imports, via StubMerger.LookupSha1ByModNameSuffix."""
|
||
return StubMergerStore.LookupSha1ByModNameSuffix(mod_suffix)
|
||
|
||
def _module_package_from_sha1(self, pool: memhub.MemBuddy*, mod_sha1: str) -> str:
|
||
"""Compute the module's package name (directory part, '/' to '.') from the SHA1 store's rel_path"""
|
||
if pool is None or mod_sha1 is None: return None
|
||
rel_arr_ptr: bytes = StubMergerStore.GetSha1StoreRelArrPtr()
|
||
sha1_arr_ptr: bytes = StubMergerStore.GetSha1StoreArrPtr()
|
||
count: int = StubMergerStore.GetSha1StoreCount()
|
||
if rel_arr_ptr is None or sha1_arr_ptr is None or count <= 0: return None
|
||
for i in range(count):
|
||
sidx: sizet = sizet(i) * 17
|
||
if string.strcmp(sha1_arr_ptr + sidx, mod_sha1) != 0: continue
|
||
ridx: sizet = sizet(i) * StubMerger.MAX_REL_PATH_LEN
|
||
rel: str = rel_arr_ptr + ridx
|
||
last_slash: str = string.strrchr(rel, 47) # 47 = ord('/')
|
||
if last_slash is None: return None
|
||
pkg_len: sizet = sizet(uint64(last_slash) - uint64(rel))
|
||
if pkg_len == 0: return None
|
||
pkg_buf: str = pool.alloc(pkg_len + 1)
|
||
if pkg_buf is None: return None
|
||
for pi in range(pkg_len):
|
||
if rel[pi] == '/': pkg_buf[pi] = '.'
|
||
else: pkg_buf[pi] = rel[pi]
|
||
pkg_buf[pkg_len] = '\0'
|
||
return pkg_buf
|
||
return None
|
||
|
||
|
||
def _lookup_reexport_from_vpi(self, pool: memhub.MemBuddy*, mod_sha1: str, func_name: str, allow_star: int = 1) -> str:
|
||
"""On a re-export table miss, parse the module's .vpi from-import statements to locate the defining module (the table may be empty since it is only populated during full translation).
|
||
allow_star=0 skips `from X import *` candidates: they are unverified hints (the caller must
|
||
check them), and a CLASS owner must never be taken from one -- nearly every file star-imports
|
||
stdint, so accepting one would attribute arbitrary class names to stdint (TypeGenErr was
|
||
forward-declared as "stdint.TypeGenErr" this way, then every vpi/text.ll lookup missed)."""
|
||
if pool is None or mod_sha1 is None or func_name is None: return None
|
||
# accumulate star-import candidates (space-separated); caller validates each
|
||
star_cands: str = None
|
||
star_cands_len: sizet = 0
|
||
temp_dir: str = self.Gen.TypeGenBase.get_temp_dir()
|
||
if temp_dir is None: return None
|
||
td_len: sizet = string.strlen(temp_dir)
|
||
vpi_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, mod_sha1, "vpi")
|
||
if vpi_path is None: return None
|
||
READ_SZ: sizet = 1048576
|
||
buf: bytes = stdlib.malloc(READ_SZ)
|
||
if buf is None:
|
||
stdlib.free(vpi_path)
|
||
return None
|
||
vf: fileio.File* = fileio.File(vpi_path, fileio.MODE.R)
|
||
stdlib.free(vpi_path)
|
||
if vf.closed:
|
||
stdlib.free(buf)
|
||
return None
|
||
nrd: LONG = vf.read_all(buf, READ_SZ)
|
||
vf.close()
|
||
if nrd <= 0:
|
||
stdlib.free(buf)
|
||
return None
|
||
if nrd < READ_SZ: buf[nrd] = 0
|
||
name_len: sizet = string.strlen(func_name)
|
||
# own-definition flag: a line-start "def {func_name}(" in vpi not overridden
|
||
# by a same-named from-import means this module defines it (not a re-export).
|
||
has_own_def: int = 0
|
||
total: sizet = string.strlen(buf)
|
||
pos: sizet = 0
|
||
while pos < total:
|
||
ls: sizet = pos
|
||
while pos < total and buf[pos] != '\n': pos += 1
|
||
ll: sizet = pos - ls
|
||
if pos < total: pos += 1
|
||
# own-def detection (before from-import match): module-level def has no indent
|
||
if ll > 4 and buf[ls] == 'd' and buf[ls + 1] == 'e' and buf[ls + 2] == 'f' and buf[ls + 3] == ' ':
|
||
fstart_od: sizet = ls + 4
|
||
fend_od: sizet = fstart_od
|
||
while fend_od < ls + ll and buf[fend_od] != '(' and buf[fend_od] != ' ': fend_od += 1
|
||
fl2_od: sizet = fend_od - fstart_od
|
||
if fl2_od == name_len:
|
||
fd_eq_od: int = 1
|
||
for fdi_od in range(fl2_od):
|
||
if buf[fstart_od + fdi_od] != func_name[fdi_od]:
|
||
fd_eq_od = 0
|
||
break
|
||
if fd_eq_od != 0: has_own_def = 1
|
||
continue
|
||
# line must start with "from "
|
||
if ll < 6: continue
|
||
if buf[ls] != 'f' or buf[ls + 1] != 'r' or buf[ls + 2] != 'o' or buf[ls + 3] != 'm' or buf[ls + 4] != ' ': continue
|
||
# locate the " import " separator
|
||
imp_pos: sizet = ls + 5
|
||
while imp_pos + 7 <= ls + ll:
|
||
if buf[imp_pos] == ' ' and buf[imp_pos + 1] == 'i' and buf[imp_pos + 2] == 'm' and buf[imp_pos + 3] == 'p' and buf[imp_pos + 4] == 'o' and buf[imp_pos + 5] == 'r' and buf[imp_pos + 6] == 't' and buf[imp_pos + 7] == ' ':
|
||
break
|
||
imp_pos += 1
|
||
if imp_pos + 7 > ls + ll: continue
|
||
mod_start: sizet = ls + 5
|
||
mod_end: sizet = imp_pos
|
||
# name list after "import " (comma-separated; "A as B" takes original A)
|
||
nm_start: sizet = imp_pos + 8
|
||
# star import (from X import *): collect all candidates; caller verifies
|
||
if nm_start < ls + ll and buf[nm_start] == '*':
|
||
# Class-owner queries opt out: a star target is not evidence that it owns the name.
|
||
if allow_star == 0: continue
|
||
star_dot: int = 0
|
||
if buf[mod_start] == '.': star_dot = 1
|
||
star_buf: str = pool.alloc(mod_end - mod_start + 1)
|
||
if star_buf is not None:
|
||
si2: sizet = 0
|
||
while mod_start + si2 < mod_end:
|
||
star_buf[si2] = buf[mod_start + si2]
|
||
si2 += 1
|
||
star_buf[si2] = '\0'
|
||
while star_buf[0] == '.': star_buf = star_buf + 1
|
||
if string.strlen(star_buf) > 0:
|
||
if star_dot != 0:
|
||
spkg: str = self._module_package_from_sha1(pool, mod_sha1)
|
||
if spkg is not None:
|
||
srl: sizet = string.strlen(spkg) + string.strlen(star_buf) + 2
|
||
sres: str = pool.alloc(srl)
|
||
if sres is not None:
|
||
viperlib.snprintf(sres, srl, "%s.%s", spkg, star_buf)
|
||
star_buf = sres
|
||
# accumulate candidates (space-separated)
|
||
sbl: sizet = string.strlen(star_buf)
|
||
new_cands: str = pool.alloc(star_cands_len + sbl + 2)
|
||
if new_cands is not None:
|
||
if star_cands is not None and star_cands_len > 0:
|
||
string.strcpy(new_cands, star_cands)
|
||
new_cands[star_cands_len] = ' '
|
||
string.strcpy(new_cands + star_cands_len + 1, star_buf)
|
||
star_cands_len += sbl + 1
|
||
else:
|
||
string.strcpy(new_cands, star_buf)
|
||
star_cands_len = sbl
|
||
star_cands = new_cands
|
||
continue
|
||
scan: sizet = nm_start
|
||
while scan < ls + ll:
|
||
while scan < ls + ll and (buf[scan] == ' ' or buf[scan] == ','): scan += 1
|
||
if scan >= ls + ll: break
|
||
n_end: sizet = scan
|
||
while n_end < ls + ll and buf[n_end] != ',' and buf[n_end] != ' ': n_end += 1
|
||
cand_start: sizet = scan
|
||
cand_len: sizet = n_end - scan
|
||
as_pos: sizet = scan
|
||
while as_pos + 4 <= n_end:
|
||
if buf[as_pos] == ' ' and buf[as_pos + 1] == 'a' and buf[as_pos + 2] == 's' and buf[as_pos + 3] == ' ':
|
||
cand_start = as_pos + 4
|
||
cand_len = n_end - cand_start
|
||
break
|
||
as_pos += 1
|
||
if cand_len == name_len:
|
||
m_ok: int = 1
|
||
for ki in range(name_len):
|
||
if buf[cand_start + ki] != func_name[ki]:
|
||
m_ok = 0
|
||
break
|
||
if m_ok == 1:
|
||
# hit: parse module name (leading dot -> relative, add pkg prefix)
|
||
has_dot: int = 0
|
||
if buf[mod_start] == '.': has_dot = 1
|
||
mod_buf: str = pool.alloc(mod_end - mod_start + 1)
|
||
if mod_buf is None:
|
||
stdlib.free(buf)
|
||
return None
|
||
mi: sizet = 0
|
||
while mod_start + mi < mod_end:
|
||
mod_buf[mi] = buf[mod_start + mi]
|
||
mi += 1
|
||
mod_buf[mi] = '\0'
|
||
while mod_buf[0] == '.': mod_buf = mod_buf + 1
|
||
if string.strlen(mod_buf) == 0:
|
||
stdlib.free(buf)
|
||
return None
|
||
if has_dot != 0:
|
||
pkg: str = self._module_package_from_sha1(pool, mod_sha1)
|
||
if pkg is None:
|
||
stdlib.free(buf)
|
||
return None
|
||
rl: sizet = string.strlen(pkg) + string.strlen(mod_buf) + 2
|
||
res: str = pool.alloc(rl)
|
||
if res is None:
|
||
stdlib.free(buf)
|
||
return None
|
||
viperlib.snprintf(res, rl, "%s.%s", pkg, mod_buf)
|
||
stdlib.free(buf)
|
||
return res
|
||
stdlib.free(buf)
|
||
return mod_buf
|
||
scan = n_end
|
||
# scan done: return star candidates, or None if this module defines it
|
||
if star_cands is not None:
|
||
stdlib.free(buf)
|
||
if has_own_def == 0: return star_cands
|
||
return None
|
||
stdlib.free(buf)
|
||
return None
|
||
|
||
|
||
def register_cexport_func(self, sha1: str, func_name: str) -> int:
|
||
"""Register a CExport function into the global table (single source of truth in Gen.CallGenBase)"""
|
||
if self.Gen is None or sha1 is None or func_name is None: return 0
|
||
if self.Gen.CallGenBase.CexportSha1Arr is None:
|
||
self.Gen.CallGenBase.CexportSha1Arr = CompilerFactory.factory.PermPool.alloc(_CEXPORT_MAX * 17)
|
||
self.Gen.CallGenBase.CexportFuncArr = CompilerFactory.factory.PermPool.alloc(_CEXPORT_MAX * 64)
|
||
if self.Gen.CallGenBase.CexportSha1Arr is None or self.Gen.CallGenBase.CexportFuncArr is None: return 0
|
||
string.memset(self.Gen.CallGenBase.CexportSha1Arr, 0, _CEXPORT_MAX * 17)
|
||
string.memset(self.Gen.CallGenBase.CexportFuncArr, 0, _CEXPORT_MAX * 64)
|
||
if self.Gen.CallGenBase.CexportCount < _CEXPORT_MAX:
|
||
idx: sizet = sizet(self.Gen.CallGenBase.CexportCount) * 17
|
||
# fixed 17/64-byte slots; unbounded strcpy would overrun adjacent slots
|
||
string.strncpy(self.Gen.CallGenBase.CexportSha1Arr + idx, sha1, 16)
|
||
self.Gen.CallGenBase.CexportSha1Arr[idx + 16] = '\0'
|
||
idx2: sizet = sizet(self.Gen.CallGenBase.CexportCount) * 64
|
||
string.strncpy(self.Gen.CallGenBase.CexportFuncArr + idx2, func_name, 63)
|
||
self.Gen.CallGenBase.CexportFuncArr[idx2 + 63] = '\0'
|
||
self.Gen.CallGenBase.CexportCount += 1
|
||
return 1
|
||
return 0
|
||
|
||
|
||
def is_cexport_func(self, sha1: str, func_name: str) -> int:
|
||
"""Whether (sha1, func_name) is a CExport function (reads the Gen.CallGenBase source of truth), falling back to the vpi return annotation on a registry miss."""
|
||
if self.Gen is None or sha1 is None or func_name is None: return 0
|
||
if self.Gen.CallGenBase.CexportSha1Arr is not None:
|
||
for i in range(self.Gen.CallGenBase.CexportCount):
|
||
idx: sizet = sizet(i) * 17
|
||
if string.strcmp(self.Gen.CallGenBase.CexportSha1Arr + idx, sha1) == 0:
|
||
idx2: sizet = sizet(i) * 64
|
||
if string.strcmp(self.Gen.CallGenBase.CexportFuncArr + idx2, func_name) == 0: return 1
|
||
return self._is_cexport_func_from_vpi(sha1, func_name)
|
||
|
||
|
||
# RegisterStaticSymbol / IsStaticSymbol - static symbol registry: the four
|
||
# syntaxes all normalize to t.CStatic; meaning = no cross-module use.
|
||
def RegisterStaticSymbol(self, sha1: str, name: str) -> int:
|
||
"""Register a static symbol (defining module SHA1 + symbol name); 1=registered (idempotent) / 0=failure"""
|
||
if self.Gen is None or sha1 is None or name is None: return 0
|
||
if self.Gen.CallGenBase.StaticSha1Arr is None:
|
||
self.Gen.CallGenBase.StaticSha1Arr = CompilerFactory.factory.PermPool.alloc(_CEXPORT_MAX * 17)
|
||
self.Gen.CallGenBase.StaticNameArr = CompilerFactory.factory.PermPool.alloc(_CEXPORT_MAX * 64)
|
||
if self.Gen.CallGenBase.StaticSha1Arr is None or self.Gen.CallGenBase.StaticNameArr is None: return 0
|
||
string.memset(self.Gen.CallGenBase.StaticSha1Arr, 0, _CEXPORT_MAX * 17)
|
||
string.memset(self.Gen.CallGenBase.StaticNameArr, 0, _CEXPORT_MAX * 64)
|
||
# idempotent: repeated (sha1, name) registration returns a hit
|
||
for i in range(self.Gen.CallGenBase.StaticCount):
|
||
si: sizet = sizet(i) * 17
|
||
if string.strcmp(self.Gen.CallGenBase.StaticSha1Arr + si, sha1) == 0:
|
||
sn: sizet = sizet(i) * 64
|
||
if string.strcmp(self.Gen.CallGenBase.StaticNameArr + sn, name) == 0: return 1
|
||
if self.Gen.CallGenBase.StaticCount < _CEXPORT_MAX:
|
||
idx: sizet = sizet(self.Gen.CallGenBase.StaticCount) * 17
|
||
# fixed 17/64-byte slots; unbounded strcpy would overrun adjacent slots
|
||
string.strncpy(self.Gen.CallGenBase.StaticSha1Arr + idx, sha1, 16)
|
||
self.Gen.CallGenBase.StaticSha1Arr[idx + 16] = '\0'
|
||
idx2: sizet = sizet(self.Gen.CallGenBase.StaticCount) * 64
|
||
string.strncpy(self.Gen.CallGenBase.StaticNameArr + idx2, name, 63)
|
||
self.Gen.CallGenBase.StaticNameArr[idx2 + 63] = '\0'
|
||
self.Gen.CallGenBase.StaticCount += 1
|
||
return 1
|
||
return 0
|
||
|
||
|
||
def IsStaticSymbol(self, sha1: str, name: str) -> int:
|
||
"""Whether (sha1, name) is a registered static symbol, the cross-module reference blocker; pairs (sha1, name) exactly to avoid same-name mistakes."""
|
||
if self.Gen is None or sha1 is None or name is None: return 0
|
||
if self.Gen.CallGenBase.StaticSha1Arr is None: return 0
|
||
for i in range(self.Gen.CallGenBase.StaticCount):
|
||
si: sizet = sizet(i) * 17
|
||
if string.strcmp(self.Gen.CallGenBase.StaticSha1Arr + si, sha1) == 0:
|
||
sn: sizet = sizet(i) * 64
|
||
if string.strcmp(self.Gen.CallGenBase.StaticNameArr + sn, name) == 0: return 1
|
||
return 0
|
||
|
||
|
||
def _is_cexport_func_from_vpi(self, sha1: str, func_name: str) -> int:
|
||
"""The vpi return annotation contains t.CExport, so the defining side uses a bare name (no sha1 prefix cross-module)."""
|
||
if sha1 is None or func_name is None: return 0
|
||
if self.Gen is None or self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen <= 0: return 0
|
||
VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir)
|
||
cexp_annot: str = VpiReader._lookup_vpi_ret_annot(sha1, None, func_name)
|
||
if cexp_annot is None: return 0
|
||
if string.strstr(cexp_annot, "CExport") is not None: return 1
|
||
return 0
|
||
|
||
|
||
def _is_state_func_from_vpi(self, pool: memhub.MemBuddy*, sha1: str, func_name: str) -> int:
|
||
"""The vpi return annotation contains t.State, an external C function marker, so bare names are used cross-module to avoid undefined symbols."""
|
||
if pool is None or sha1 is None or func_name is None: return 0
|
||
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir)
|
||
state_annot: str = VpiReader._lookup_vpi_ret_annot(sha1, None, func_name)
|
||
if state_annot is None: return 0
|
||
# ret annotation contains "State" -> external C function
|
||
if string.strstr(state_annot, "State") is not None: return 1
|
||
return 0
|
||
|
||
|
||
def _lookup_mod_sha1(self, pool: memhub.MemBuddy*, from_imports: str, class_name: str, allow_star_fallback: int = 1) -> str:
|
||
"""Look up the SHA1 of the module owning a class found in from_imports; None if not found. allow_star_fallback=0 disables star-import fallback."""
|
||
mod_name_raw: str = self.Gen.ImportsGen.lookup_from_import(from_imports, class_name, allow_star_fallback)
|
||
if mod_name_raw is None: return None
|
||
return self._lookup_sha1_from_modname_raw(pool, mod_name_raw)
|
||
|
||
|
||
def _lookup_sha1_from_modname_raw(self, pool: memhub.MemBuddy*, mod_name_raw: str) -> str:
|
||
"""Look up SHA1 from a raw module name; None if not found"""
|
||
# find module-name end (space/null/':' alias sep), copy into a new buffer
|
||
mod_len: sizet = 0
|
||
while mod_name_raw[mod_len] != ' ' and mod_name_raw[mod_len] != '\0' and mod_name_raw[mod_len] != ':': mod_len += 1
|
||
mod_name_buf: str = pool.alloc(mod_len + 1)
|
||
if mod_name_buf is None: return None
|
||
for mi in range(mod_len): mod_name_buf[mi] = mod_name_raw[mi]
|
||
mod_name_buf[mod_len] = '\0'
|
||
# strip leading dots of relative import
|
||
base_mod: str = mod_name_buf
|
||
while base_mod[0] == '.': base_mod = base_mod + 1
|
||
sha1: str = self._lookup_module_sha1(base_mod)
|
||
if sha1 is None: sha1 = self._lookup_module_sha1_suffix(base_mod)
|
||
return sha1
|
||
|
||
|
||
def _FuncTyFromFunction(self, pool: memhub.MemBuddy*, f_ref: mlirlite.Function*) -> mlirlite.LLVMType*:
|
||
"""Build an LLVMType.Func from a mlirlite.Function's Param chain, rebuilding it as a ParamNode chain since the two list types are unrelated."""
|
||
pn_head: mlirlite.ParamNode* = None
|
||
pn_prev: mlirlite.ParamNode* = None
|
||
p_cnt: sizet = 0
|
||
p_cur: mlirlite.Param* = mlirlite.function_get_param_head(f_ref)
|
||
while p_cur is not None:
|
||
p_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(p_cur)
|
||
if p_ty is None: return None
|
||
pn: mlirlite.ParamNode* = mlirlite.new_param_node(pool, p_ty)
|
||
if pn is None: return None
|
||
if pn_head is None: pn_head = pn
|
||
if pn_prev is not None: mlirlite.paramnode_set_next(pn_prev, pn)
|
||
pn_prev = pn
|
||
p_cnt += 1
|
||
p_cur = mlirlite.param_get_next(p_cur)
|
||
return mlirlite.Func(pool, f_ref.RetTy, pn_head, p_cnt)
|
||
|
||
def find_func_in_module(self, mod: mlirlite.LLVMModule*, name: str, owner_sha1: str = None) -> mlirlite.Function*:
|
||
"""Find a function by name in the module's function list: exact then suffix match, honoring owner_sha1 for same-module priority."""
|
||
if mod is None or name is None: return None
|
||
name_len: sizet = string.strlen(name)
|
||
# Hot-path: merge the former two full list passes into one, taking
|
||
# function_get_name/NameLen/next once per node; priority order unchanged.
|
||
cur: mlirlite.Function* = mod.FuncHead
|
||
mangle_hit: mlirlite.Function* = None
|
||
declare_hit: mlirlite.Function* = None
|
||
any_hit: mlirlite.Function* = None
|
||
while cur is not None:
|
||
cur_name: str = mlirlite.function_get_name(cur)
|
||
if cur_name is not None:
|
||
# fast path: skip strcmp when first chars differ (mangled names start
|
||
# with sha1 hex, bare names with letters/underscore)
|
||
if cur_name[0] == name[0] and string.strcmp(cur_name, name) == 0: return cur
|
||
# use Function.NameLen (computed once) instead of per-node strlen
|
||
cur_len: sizet = mlirlite.function_get_name_len(cur)
|
||
# own-module candidate: exactly "{owner_sha1}.{name}" (not "{sha1}.Class.method")
|
||
if owner_sha1 is not None and mangle_hit is None and cur_len == 17 + name_len:
|
||
if string.strncmp(cur_name, owner_sha1, 16) == 0 and cur_name[16] == '.':
|
||
suffix_start_o: sizet = cur_len - name_len
|
||
if cur_name[suffix_start_o - 1] == '.':
|
||
match_o: int = 1
|
||
for i in range(name_len):
|
||
if cur_name[suffix_start_o + i] != name[i]:
|
||
match_o = 0
|
||
break
|
||
if match_o == 1: mangle_hit = cur
|
||
# suffix match (.name): do not skip declares (method stubs are declares too)
|
||
if cur_len > name_len + 1:
|
||
suffix_start: sizet = cur_len - name_len
|
||
if cur_name[suffix_start - 1] == '.':
|
||
Matched: int = 1
|
||
for i in range(name_len):
|
||
if cur_name[suffix_start + i] != name[i]:
|
||
Matched = 0
|
||
break
|
||
if Matched == 1:
|
||
# first any-suffix hit (former pass-3 fallback)
|
||
if any_hit is None: any_hit = cur
|
||
# first IsDeclared==0 hit (former pass-2 priority)
|
||
if cur.IsDeclared == 0 and declare_hit is None: declare_hit = cur
|
||
cur = mlirlite.function_get_next(cur)
|
||
if mangle_hit is not None: return mangle_hit
|
||
if declare_hit is not None: return declare_hit
|
||
return any_hit
|
||
|
||
|
||
# _ensure_method_declare: create a declare in the current module when the stub is missing
|
||
def _ensure_method_declare(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, call_name: str, ret_ty: mlirlite.LLVMType*, param_head: mlirlite.Value*, param_count: int):
|
||
"""Create a declare for a cross-module method call, appending missing params to an existing shorter declare (defines are left untouched)."""
|
||
if pool is None or mod is None or call_name is None: return
|
||
# list-walk sentinel (fail-fast): an unbounded walk would loop forever if a
|
||
# Next pointer were corrupted into a cycle, so abort past the limit.
|
||
DECLARE_CHAIN_LIMIT: int = 100000
|
||
# check whether a same-named function (declare or define) already exists
|
||
existing: mlirlite.Function* = mod.FuncHead
|
||
existing_steps: int = 0
|
||
while existing is not None:
|
||
existing_steps += 1
|
||
if existing_steps > DECLARE_CHAIN_LIMIT:
|
||
stdio.printf("[CALLGEN] linked-list anomaly: module funcs (call_name=%s, traversal limit exceeded, aborting)\n", call_name)
|
||
# converge: longjmp back to the REPL loop (diagnostic already printed)
|
||
VLogger.repl_abort()
|
||
if existing.Name is not None:
|
||
# fast path: skip strcmp when first chars differ
|
||
if existing.Name[0] == call_name[0] and string.strcmp(existing.Name, call_name) == 0:
|
||
# exists: append missing params only if the declare has too few
|
||
if existing.IsDeclared == 1:
|
||
# variadic functions (*args) get no params appended: extra
|
||
# args are handled by "..." and typed params would corrupt them.
|
||
if existing.IsVarArg == 1: return
|
||
# count existing params by walking (avoid GSList.Count type inference)
|
||
cur_count: int = 0
|
||
cur_p: mlirlite.Param* = mlirlite.function_get_param_head(existing)
|
||
while cur_p is not None:
|
||
cur_count += 1
|
||
if cur_count > DECLARE_CHAIN_LIMIT:
|
||
stdio.printf("[CALLGEN] linked-list anomaly: func params (call_name=%s, param chain traversal limit exceeded, aborting)\n", call_name)
|
||
# converge: longjmp back to the REPL loop (diagnostic already printed)
|
||
VLogger.repl_abort()
|
||
cur_p = cur_p.Next
|
||
# append only the missing params
|
||
if cur_count < param_count:
|
||
# start at param_head's cur_count-th node
|
||
pnode2: mlirlite.Value* = param_head
|
||
pi2: int = 0
|
||
# skip the existing cur_count params
|
||
while pnode2 is not None and pi2 < cur_count:
|
||
pnode2 = pnode2.Next
|
||
pi2 += 1
|
||
# append the remaining params
|
||
while pnode2 is not None and pi2 < param_count:
|
||
if pnode2.Ty is not None:
|
||
param2: mlirlite.Param* = mlirlite.new_param(pool, pnode2.Ty, None)
|
||
if param2 is not None: mlirlite.function_add_param(existing, param2)
|
||
pnode2 = pnode2.Next
|
||
pi2 += 1
|
||
return
|
||
existing = existing.Next
|
||
# bare C library fn (e.g. string.memset -> @memset): declare by standard signature
|
||
if call_name is not None and string.strchr(call_name, 46) is None:
|
||
std_lib_fn: mlirlite.Function* = self.Gen.CallGenLib._ensure_c_lib_declare(pool, mod, call_name)
|
||
if std_lib_fn is not None: return
|
||
# create the function declaration
|
||
func: mlirlite.Function* = mlirlite.new_function(pool, call_name, ret_ty)
|
||
if func is None: return
|
||
func.IsDeclared = 1
|
||
# Prefer definition-side param types (from {sha1}.{cls}.{method} vpi
|
||
# annotations): wider ints win, pointers always use the definition side.
|
||
sha1_d: str = None
|
||
cls_d: str = None
|
||
meth_d: str = None
|
||
if call_name is not None:
|
||
# SHA1 prefix via single source GenApi.sha1_prefix_len (no hardcoded 17/16)
|
||
sha1_pfx_d: int = GenApi.sha1_prefix_len(call_name)
|
||
if sha1_pfx_d != 0:
|
||
pfx_len_d: sizet = sizet(sha1_pfx_d)
|
||
sha1_buf_d: bytes = stdlib.malloc(pfx_len_d)
|
||
if sha1_buf_d is not None:
|
||
string.strncpy(sha1_buf_d, call_name, pfx_len_d - 1)
|
||
sha1_buf_d[pfx_len_d - 1] = '\0'
|
||
sha1_d = sha1_buf_d
|
||
rest_d: str = call_name + pfx_len_d
|
||
# take the last '.': method has no dot; the first would truncate module-qualified args
|
||
dot2_d: str = string.strrchr(rest_d, '.')
|
||
if dot2_d is not None:
|
||
cls_len_d: sizet = uint64(dot2_d) - uint64(rest_d)
|
||
if cls_len_d > 0:
|
||
cls_buf_d: bytes = stdlib.malloc(cls_len_d + 1)
|
||
if cls_buf_d is not None:
|
||
string.strncpy(cls_buf_d, rest_d, cls_len_d)
|
||
cls_buf_d[cls_len_d] = '\0'
|
||
cls_d = cls_buf_d
|
||
meth_d = dot2_d + 1
|
||
else: meth_d = rest_d
|
||
prm_annots_d: str = None
|
||
if sha1_d is not None and meth_d is not None: prm_annots_d = self._vpi_method_param_annot(pool, sha1_d, cls_d, meth_d)
|
||
i32_ty_d: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
i8_ptr_d: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
# add params (names are meaningless in a declare; None emits types only)
|
||
pnode: mlirlite.Value* = param_head
|
||
pi: int = 0
|
||
while pnode is not None and pi < param_count:
|
||
param_ty_d: mlirlite.LLVMType* = pnode.Ty
|
||
if prm_annots_d is not None and prm_annots_d[0] != '\0' and pnode.Ty is not None:
|
||
seg_ty_d: mlirlite.LLVMType* = VpiReader._resolve_vpi_param_annot_seg(pool, sha1_d, cls_d, meth_d, pi, i32_ty_d, i8_ptr_d)
|
||
if seg_ty_d is not None:
|
||
match pnode.Ty:
|
||
case mlirlite.LLVMType.Int(bits_a):
|
||
match seg_ty_d:
|
||
case mlirlite.LLVMType.Int(bits_b):
|
||
if bits_b > bits_a: param_ty_d = seg_ty_d
|
||
case _:
|
||
pass
|
||
case mlirlite.LLVMType.Ptr(_):
|
||
# pointer params: prefer definition side; keep arg type on miss
|
||
match seg_ty_d:
|
||
case mlirlite.LLVMType.Ptr(_):
|
||
param_ty_d = seg_ty_d
|
||
case _:
|
||
pass
|
||
case _:
|
||
pass
|
||
if param_ty_d is not None:
|
||
param: mlirlite.Param* = mlirlite.new_param(pool, param_ty_d, None)
|
||
if param is not None: mlirlite.function_add_param(func, param)
|
||
pnode = pnode.Next
|
||
pi += 1
|
||
if sha1_d is not None: stdlib.free(sha1_d)
|
||
if cls_d is not None: stdlib.free(cls_d)
|
||
mlirlite.module_add_function(mod, func)
|
||
|
||
# _lookup_method_ret_ty_via_vpi: resolve method return type from the vpi annotation cache
|
||
|
||
# ============================================================
|
||
def _lookup_method_ret_ty_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> mlirlite.LLVMType*:
|
||
"""Get a method return type from the vpi stub annotation cache; None on miss"""
|
||
if pool is None or cls_sha1 is None or method_name is None: return None
|
||
# lazy import inside the function: VpiReader imports HandlesExprCall, a top-level import would cycle
|
||
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
# on-demand load: read {temp_dir}/{sha1[0:2]}/{sha1}.vpi on cache miss,
|
||
# covering method calls whose stub is not yet injected.
|
||
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: VpiReader._ensure_vpi_ret_annot_loaded(cls_sha1, self.Gen.CallGenBase.TempDir)
|
||
annot: str = VpiReader._lookup_vpi_ret_annot(cls_sha1, cls_name, method_name)
|
||
if annot is None: return None
|
||
return VpiReader._resolve_vpi_ret_annot_str(pool, annot, i32_ty, i8_ptr_ty)
|
||
|
||
def _vpi_escape_packed(self, sha1: str, cls_name: str, method_name: str) -> int:
|
||
"""跨模块逃逸摘要查询(v3):按 (定义模块 sha1, Class?, method) 查 .vpi 缓存;0=无摘要。
|
||
|
||
用"本模块已加载"备忘包住 _ensure_vpi_ret_annot_loaded(其内部是 O(N) 前缀判定):
|
||
调用点每次都要查摘要,不做备忘会把编译拖慢一个量级。"""
|
||
if sha1 is None or method_name is None: return 0
|
||
if VpiReader._vpi_escape_module_loaded(sha1) == 0:
|
||
if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0: return 0
|
||
VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir)
|
||
VpiReader._vpi_escape_mark_module(sha1)
|
||
return VpiReader._lookup_vpi_escape_packed(sha1, cls_name, method_name)
|
||
|
||
# lookup_escape_dir_via_vpi - 跨模块声明式指令契约(@life(escape(...)) / @life(noescape(...)))。
|
||
# 本文件指令表按文件重置,跨模块调用点必为空,只能靠 .vpi 行尾的 esce=/escn= 传递;
|
||
# noescape != 0 取"强制不逃逸位",否则取"强制逃逸位"。0 = 无该标注(含该模块未加载过)。
|
||
def lookup_escape_dir_via_vpi(self, cls_sha1: str, class_name: str, func_name: str, noescape: int) -> int:
|
||
"""对方模块 .vpi 里落的契约掩码;0 = 无该标注"""
|
||
if func_name is None or cls_sha1 is None: return 0
|
||
return VpiReader._lookup_vpi_escape_dir(cls_sha1, class_name, func_name, noescape)
|
||
|
||
# lookup_ret_kind_via_vpi - 跨模块 `@life(ret(...))` 返回值生命期契约(A2,§14)。
|
||
# 与 escape/noescape 同一条 .vpi 通道(行尾 escr=);0 = 无声明(含该模块未加载过)。
|
||
# 注意:本查询可能在调用点**之前**(life_of_expr 判右值生命期)发生,不能假定调用点的
|
||
# 逃逸查询已把对方 vpi 载入,故这里自己走一次"本模块已加载"备忘 + 按需加载。
|
||
def lookup_ret_kind_via_vpi(self, cls_sha1: str, class_name: str, func_name: str) -> int:
|
||
"""对方模块 .vpi 里落的 ret_kind;0 = 无声明"""
|
||
if func_name is None or cls_sha1 is None: return 0
|
||
if VpiReader._vpi_escape_module_loaded(cls_sha1) == 0:
|
||
if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0: return 0
|
||
VpiReader._ensure_vpi_ret_annot_loaded(cls_sha1, self.Gen.CallGenBase.TempDir)
|
||
VpiReader._vpi_escape_mark_module(cls_sha1)
|
||
return VpiReader._lookup_vpi_escape_dir(cls_sha1, class_name, func_name, 2)
|
||
|
||
def lookup_escape_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> int:
|
||
"""跨模块逃逸摘要(v3):本类 + MRO 祖先各自的模块 sha1 依次查 .vpi,返回打包值(0=无摘要)。
|
||
|
||
本文件的摘要表按文件重置,跨模块调用点上必为空,只能靠 .vpi 传递;
|
||
键与方法路径一致:"Class.method"(方法)/ 裸名(模块级函数,此时 cls_name=None)。"""
|
||
if method_name is None: return 0
|
||
if cls_sha1 is not None:
|
||
p0: int = self._vpi_escape_packed(cls_sha1, cls_name, method_name)
|
||
if p0 != 0: return p0
|
||
if cls_name is not None:
|
||
mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, cls_name)
|
||
mi: int = 1
|
||
while mro is not None and mro[mi] != 0:
|
||
anc: str = rawptr(mro[mi])
|
||
if anc is not None and string.strcmp(anc, cls_name) != 0:
|
||
sha_a: str = self.Gen.StructGen.get_struct_sha1(anc)
|
||
if sha_a is not None:
|
||
pa: int = self._vpi_escape_packed(sha_a, anc, method_name)
|
||
if pa != 0: return pa
|
||
mi += 1
|
||
return 0
|
||
|
||
def _vpi_willfree(self, sha1: str, cls_name: str, method_name: str) -> int:
|
||
"""跨模块 willFree 标注查询(P2):按 (定义模块 sha1, Class?, method) 查 .vpi 缓存;0=无标注。
|
||
|
||
与逃逸摘要共用"本模块已加载"备忘:_ensure_vpi_ret_annot_loaded 的单一入口会把
|
||
ret/param/esc/wf 一起喂齐,所以这里只需保证模块被加载过一次。"""
|
||
if sha1 is None or method_name is None: return 0
|
||
if VpiReader._vpi_escape_module_loaded(sha1) == 0:
|
||
if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0: return 0
|
||
VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir)
|
||
VpiReader._vpi_escape_mark_module(sha1)
|
||
return VpiReader._lookup_vpi_willfree(sha1, cls_name, method_name)
|
||
|
||
def lookup_escape_clears_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, class_name: str, func_name: str, cargs: list[ast.AST*]*, can: sizet, param_base: int) -> int:
|
||
"""跨模块常量特化:把实参字面量装进常量环境,再向 vpi 缓存要"该清掉的位掩码";0 = 无命中
|
||
|
||
跨模块拿不到被调者函数体(只有 .vpi),所以用落盘的守卫表(`escg=`)而不是重算摘要。
|
||
没有常量实参、或该函数没落过守卫记录 → 返回 0(调用方原样保留掩码,保守)。"""
|
||
if cls_sha1 is None or func_name is None or cargs is None or can == 0: return 0
|
||
if GenApi.build_const_env(cargs, can, param_base) == 0: return 0
|
||
fenv: sizet* = GenApi.const_env_ptr()
|
||
if fenv is None: return 0
|
||
return VpiReader._lookup_vpi_escape_clears(cls_sha1, class_name, func_name, fenv)
|
||
|
||
def lookup_willfree_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> int:
|
||
"""跨模块 willFree 掩码(P2):本类 + MRO 祖先各自的模块 sha1 依次查 .vpi;0=无标注"""
|
||
if method_name is None: return 0
|
||
if cls_sha1 is not None:
|
||
p0: int = self._vpi_willfree(cls_sha1, cls_name, method_name)
|
||
if p0 != 0: return p0
|
||
if cls_name is not None:
|
||
mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, cls_name)
|
||
mi: int = 1
|
||
while mro is not None and mro[mi] != 0:
|
||
anc: str = rawptr(mro[mi])
|
||
if anc is not None and string.strcmp(anc, cls_name) != 0:
|
||
sha_a: str = self.Gen.StructGen.get_struct_sha1(anc)
|
||
if sha_a is not None:
|
||
pa: int = self._vpi_willfree(sha_a, anc, method_name)
|
||
if pa != 0: return pa
|
||
mi += 1
|
||
return 0
|
||
|
||
# _vpi_method_param_annot: on-demand lookup of the vpi param annotation string
|
||
def _vpi_method_param_annot(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> str:
|
||
"""Load and query the vpi parameter annotation string on demand; None on miss"""
|
||
if pool is None or cls_sha1 is None or cls_name is None or method_name is None: return None
|
||
# on-demand load: read {temp_dir}/{sha1[0:2]}/{sha1}.vpi on cache miss
|
||
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: VpiReader._ensure_vpi_param_annot_loaded(cls_sha1, self.Gen.CallGenBase.TempDir)
|
||
return VpiReader._lookup_vpi_param_annots(cls_sha1, cls_name, method_name)
|
||
|
||
# _vpi_resolve_param_annot_ty: parse a vpi param annotation segment into LLVMType
|
||
def _vpi_resolve_param_annot_ty(self, pool: memhub.MemBuddy*, seg: str) -> mlirlite.LLVMType*:
|
||
"""Parse a single vpi parameter annotation segment into an LLVMType; None on failure"""
|
||
if pool is None or seg is None: return None
|
||
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
return VpiReader._resolve_vpi_ret_annot_str(pool, seg, i32_ty, i8_ptr_ty)
|
||
|
||
# _special_method_ret_ty: fixed return types for compiler built-in special methods
|
||
|
||
# ============================================================
|
||
def _special_method_ret_ty(self, pool: memhub.MemBuddy*, class_name: str, method_name: str) -> mlirlite.LLVMType*:
|
||
"""Fixed return type for compiler builtin special methods; None for non-special ones"""
|
||
if pool is None or method_name is None: return None
|
||
if string.strcmp(method_name, "__before_init__") == 0 or string.strcmp(method_name, "__init__") == 0: return mlirlite.Void(pool)
|
||
if string.strcmp(method_name, "__new__") == 0:
|
||
if class_name is not None:
|
||
sp_struct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(class_name)
|
||
if sp_struct_ty is not None: return mlirlite.Ptr(pool, sp_struct_ty)
|
||
return None
|
||
return None
|
||
|
||
# _fail_method_ret_ty: ERROR + abort when the method return type cannot be resolved
|
||
|
||
# ============================================================
|
||
def _fail_method_ret_ty(self, method_name: str, node: ast.AST* = None, cls: str = None, anc: str = None, sha1: str = None) -> mlirlite.LLVMType*:
|
||
"""Fail fast when a method return type cannot be resolved precisely (module function, text.ll define, then vpi cache); never default to i32."""
|
||
# NOTE: VLogger.error terminates the process in batch mode (sys.exit(1)), so the whole
|
||
# report must be a SINGLE call -- anything after it is dead code (this is why a separate
|
||
# diagnostic line never showed up, and why the following fatal_error traceback is never
|
||
# printed either). The context dump is therefore merged into this one message.
|
||
# Reports: receiver class, its registered struct sha1, the class/sha1 actually used for
|
||
# the lookup (MRO fallback candidate), whether that module's text.ll exists, and the raw
|
||
# vpi return-annotation string if the cache holds one.
|
||
c_cls: str = cls
|
||
if c_cls is None: c_cls = "?"
|
||
c_anc: str = anc
|
||
if c_anc is None: c_anc = "?"
|
||
# Probe with the sha1 actually used for the lookup, falling back to the receiver class's
|
||
# own registered sha1 when the caller passed none (so the dump is informative everywhere).
|
||
probe_sha: str = sha1
|
||
if probe_sha is None and anc is not None: probe_sha = self.Gen.StructGen.get_struct_sha1(anc)
|
||
if probe_sha is None and cls is not None: probe_sha = self.Gen.StructGen.get_struct_sha1(cls)
|
||
c_sha: str = probe_sha
|
||
if c_sha is None: c_sha = "?"
|
||
c_st_sha: str = "?"
|
||
if cls is not None:
|
||
st_sha_d: str = self.Gen.StructGen.get_struct_sha1(cls)
|
||
if st_sha_d is not None: c_st_sha = st_sha_d
|
||
c_tll: int = 0
|
||
c_vpi: str = "-"
|
||
if probe_sha is not None:
|
||
c_tll = self._dep_text_ll_exists(None, probe_sha)
|
||
if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0:
|
||
VpiReader._ensure_vpi_ret_annot_loaded(probe_sha, self.Gen.CallGenBase.TempDir)
|
||
annot_d: str = VpiReader._lookup_vpi_ret_annot(probe_sha, cls, method_name)
|
||
if annot_d is not None: c_vpi = annot_d
|
||
# Same-named struct entries: find_struct returns the FIRST exact match, so an earlier bogus
|
||
# registration (alias/forward/stale, different ModuleSha1) silently shadows the real one.
|
||
dup_n_d: int = 0
|
||
dup_last_d: str = "-"
|
||
# The entry's LLVM type name embeds "{sha1}.{Class}", i.e. the sha1 used when the entry
|
||
# was first created (registration / forward-declare / stub injection) -- it reveals
|
||
# provenance even if a later path overwrote ModuleSha1.
|
||
dup_ty_nm_d: str = "-"
|
||
sc_n_d: int = self.Gen.StructGen.get_struct_count()
|
||
sc_i_d: int = 0
|
||
while sc_i_d < sc_n_d:
|
||
e_d: GenTypes.StructEntry* = self.Gen.StructGen._get_struct_entry(sc_i_d)
|
||
if e_d is not None and e_d.Name is not None:
|
||
if string.strcmp(e_d.Name, c_cls) == 0:
|
||
dup_n_d += 1
|
||
if e_d.ModuleSha1 is not None: dup_last_d = e_d.ModuleSha1
|
||
if e_d.Ty is not None:
|
||
nm_d: str = mlirlite.get_struct_name(e_d.Ty)
|
||
if nm_d is not None: dup_ty_nm_d = nm_d
|
||
sc_i_d += 1
|
||
# Current-file / defining-module sha1 at the moment of failure: distinguishes
|
||
# "wrong sha1 captured during (vpi injection) forward-declare" from "correct sha1
|
||
# overwritten later".
|
||
cur_file_d: str = self.Gen.StructGen.get_file_module_sha1()
|
||
if cur_file_d is None: cur_file_d = "-"
|
||
cur_def_d: str = self.Gen.StructGen.CurDefModuleSha1
|
||
if cur_def_d is None: cur_def_d = "-"
|
||
VLogger.error(f"method return type cannot be obtained precisely (function/text.ll/vpi stub all missed): {method_name} | [diag] cls={c_cls} struct_sha1={c_st_sha} lookup_anc={c_anc} lookup_sha1={c_sha} text_ll={c_tll} vpi_annot={c_vpi} same_name_entries={dup_n_d} last_same_sha1={dup_last_d} entry_ty={dup_ty_nm_d} cur_file_sha1={cur_file_d} cur_def_sha1={cur_def_d}", "HandlesExprCall")
|
||
self.Gen.TypeGenErr.fatal_error(node, f"method return type cannot be obtained precisely; defaulting to i32 would truncate pointers/i64: {method_name}", "TypeError")
|
||
return None
|
||
|
||
# SetTempDir: set the temp dir path (called before Phase A to read dependency text.ll)
|
||
|
||
# ============================================================
|
||
def SetTempDir(self, temp_dir: str) -> None:
|
||
"""Set the temp directory path (single source of truth in Gen.CallGenBase)"""
|
||
if self.Gen is None: return
|
||
self.Gen.CallGenBase.TempDir = temp_dir
|
||
if temp_dir is not None: self.Gen.CallGenBase.TempDirLen = string.strlen(temp_dir)
|
||
else: self.Gen.CallGenBase.TempDirLen = 0
|
||
|
||
# _ParseLLVMRetTyStr: convert an LLVM IR return type string to LLVMType
|
||
|
||
# ============================================================
|
||
def _ParseLLVMRetTyStr(self, pool: memhub.MemBuddy*, buf: bytes, start: sizet, end: sizet) -> mlirlite.LLVMType*:
|
||
"""Convert an LLVM IR return type string into a mlirlite.LLVMType"""
|
||
if pool is None or buf is None or start >= end: return None
|
||
|
||
# trim leading whitespace
|
||
s: sizet = start
|
||
while s < end:
|
||
c: int = buf[s]
|
||
if c != ' ' and c != '\t': break
|
||
s += 1
|
||
|
||
# trim trailing whitespace
|
||
e: sizet = end
|
||
while e > s:
|
||
c2: int = buf[e - 1]
|
||
if c2 != ' ' and c2 != '\t': break
|
||
e -= 1
|
||
|
||
ty_len: sizet = e - s
|
||
if ty_len == 0: return None
|
||
|
||
# struct literal {T1, T2, ...} must be checked before "*", else inner * misfires
|
||
if s < e and buf[s] == '{':
|
||
# Field list nodes must be mlirlite.ParamNode; the old Function*
|
||
# annotation triggered "struct<->struct implicit conversion forbidden".
|
||
th_st: mlirlite.ParamNode* = None
|
||
tt_st: mlirlite.ParamNode* = None
|
||
tc_st: int = 0
|
||
ps_st: sizet = s + 1
|
||
while ps_st < e and buf[ps_st] != '}':
|
||
while ps_st < e and (buf[ps_st] == ' ' or buf[ps_st] == ','): ps_st += 1
|
||
if ps_st >= e or buf[ps_st] == '}': break
|
||
es_st: sizet = ps_st
|
||
while ps_st < e and buf[ps_st] != ',' and buf[ps_st] != '}': ps_st += 1
|
||
ee_st: sizet = ps_st
|
||
if ee_st > es_st:
|
||
et_st: mlirlite.LLVMType* = self._ParseLLVMRetTyStr(pool, buf, es_st, ee_st)
|
||
if et_st is None: return None
|
||
en_st: mlirlite.ParamNode* = mlirlite.new_param_node(pool, et_st)
|
||
if en_st is None: return None
|
||
if th_st is None: th_st = en_st
|
||
else: mlirlite.paramnode_set_next(tt_st, en_st)
|
||
tt_st = en_st
|
||
tc_st += 1
|
||
if th_st is None or tc_st == 0: return None
|
||
st_ty_st: mlirlite.LLVMType* = mlirlite.Struct(pool, th_st, tc_st, None)
|
||
# {i32, i8}* -> Ptr(Struct); a plain tuple {i8*, i16} has no trailing
|
||
# * and returns the Struct itself.
|
||
if ps_st < e and buf[ps_st] == '*': return mlirlite.Ptr(pool, st_ty_st)
|
||
return st_ty_st
|
||
|
||
# Named type (%"name") with pointers: resolve to the real struct/enum type
|
||
# before wrapping Ptr; unknown names keep the generic i8* behavior.
|
||
if s < e and buf[s] == '%':
|
||
nm_s: sizet = s + 1
|
||
nm_e: sizet = e
|
||
nm_star: int = 0
|
||
while nm_e > nm_s and buf[nm_e - 1] == '*':
|
||
nm_star += 1
|
||
nm_e -= 1
|
||
while nm_e > nm_s and (buf[nm_e - 1] == ' ' or buf[nm_e - 1] == '\t'): nm_e -= 1
|
||
if nm_star > 0 and nm_e > nm_s:
|
||
if buf[nm_s] == '"' and buf[nm_e - 1] == '"':
|
||
nm_s += 1
|
||
nm_e -= 1
|
||
if nm_e > nm_s:
|
||
nm_len: sizet = nm_e - nm_s
|
||
nm_buf: bytes = stdlib.malloc(nm_len + 1)
|
||
if nm_buf is not None:
|
||
string.strncpy(nm_buf, buf + nm_s, nm_len)
|
||
nm_buf[nm_len] = '\0'
|
||
nm_ty: mlirlite.LLVMType* = GenApi.get_struct_type(nm_buf)
|
||
if nm_ty is None: nm_ty = GenApi.get_enum_base_type(nm_buf)
|
||
stdlib.free(nm_buf)
|
||
if nm_ty is not None:
|
||
nm_wrap: mlirlite.LLVMType* = nm_ty
|
||
for nm_i in range(nm_star):
|
||
nm_wrap = mlirlite.Ptr(pool, nm_wrap)
|
||
return nm_wrap
|
||
|
||
# contains "*" -> generic i8* (i8*, i32*, { i32, i8 }*, unregistered %name*)
|
||
ci: sizet = s
|
||
while ci < e:
|
||
if buf[ci] == '*': return mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
ci += 1
|
||
|
||
# void
|
||
if ty_len == 4 and string.strncmp(buf + s, "void", 4) == 0: return mlirlite.Void(pool)
|
||
|
||
# double
|
||
if ty_len == 6 and string.strncmp(buf + s, "double", 6) == 0: return mlirlite.Double(pool)
|
||
|
||
# float
|
||
if ty_len == 5 and string.strncmp(buf + s, "float", 5) == 0: return mlirlite.Float(pool)
|
||
|
||
# iN (i8, i16, i32, i64)
|
||
if ty_len >= 2 and buf[s] == 'i':
|
||
bits: int = 0
|
||
di: sizet = s + 1
|
||
valid: int = 1
|
||
while di < e:
|
||
d: int = buf[di]
|
||
if d >= '0' and d <= '9': bits = bits * 10 + (d - '0')
|
||
else:
|
||
valid = 0
|
||
break
|
||
di += 1
|
||
if valid != 0 and bits > 0:
|
||
if bits == 8: return mlirlite.Int8(pool)
|
||
if bits == 16: return mlirlite.Int16(pool)
|
||
if bits == 32: return mlirlite.Int32(pool)
|
||
if bits == 64: return mlirlite.Int64(pool)
|
||
|
||
# unparsable
|
||
return None
|
||
|
||
# ============================================================
|
||
def _ParseMlirRetTyStr(self, pool: memhub.MemBuddy*, buf: bytes, start: sizet, end: sizet) -> mlirlite.LLVMType*:
|
||
"""Convert mlirlite's MLIR return-type text into a mlirlite.LLVMType; None if unparseable, never a silent degradation."""
|
||
if pool is None or buf is None or start >= end: return None
|
||
s: sizet = start
|
||
while s < end and (buf[s] == ' ' or buf[s] == '\t'): s += 1
|
||
e: sizet = end
|
||
while e > s and (buf[e - 1] == ' ' or buf[e - 1] == '\t' or buf[e - 1] == '{'): e -= 1
|
||
if s >= e: return None
|
||
n: sizet = e - s
|
||
# none -> Void (MLIR void notation)
|
||
if n == 4 and string.strncmp(buf + s, "none", 4) == 0: return mlirlite.Void(pool)
|
||
# scalar: iN / fN
|
||
if buf[s] == 'i' or buf[s] == 'f':
|
||
bits: int = 0
|
||
di: sizet = s + 1
|
||
ok: int = 1
|
||
while di < e:
|
||
d: int = buf[di]
|
||
if d >= '0' and d <= '9': bits = bits * 10 + (d - '0')
|
||
else:
|
||
ok = 0
|
||
break
|
||
di += 1
|
||
if ok != 0 and bits > 0:
|
||
if buf[s] == 'i':
|
||
if bits == 1: return mlirlite.Int1(pool)
|
||
if bits == 8: return mlirlite.Int8(pool)
|
||
if bits == 16: return mlirlite.Int16(pool)
|
||
if bits == 32: return mlirlite.Int32(pool)
|
||
if bits == 64: return mlirlite.Int64(pool)
|
||
else:
|
||
if bits == 16: return mlirlite.Half(pool)
|
||
if bits == 32: return mlirlite.Float(pool)
|
||
if bits == 64: return mlirlite.Double(pool)
|
||
if bits == 128: return mlirlite.FP128(pool)
|
||
return None
|
||
# opaque pointer -> generic i8*
|
||
if n >= 9 and string.strncmp(buf + s, "!llvm.ptr", 9) == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
# !llvm.array<N x T>
|
||
if n >= 12 and string.strncmp(buf + s, "!llvm.array<", 12) == 0:
|
||
is_a: sizet = s + 12
|
||
depth_a: int = 0
|
||
iea: sizet = e
|
||
iia: sizet = is_a
|
||
while iia < e:
|
||
ca: int = buf[iia]
|
||
if ca == '<': depth_a += 1
|
||
elif ca == '>':
|
||
if depth_a == 0:
|
||
iea = iia
|
||
break
|
||
depth_a -= 1
|
||
iia += 1
|
||
if iea <= is_a: return None
|
||
xs: sizet = is_a
|
||
while xs < iea and buf[xs] != 'x': xs += 1
|
||
if xs >= iea: return None
|
||
cnt_a: int = 0
|
||
ok_a: int = 0
|
||
ci_a: sizet = is_a
|
||
while ci_a < xs:
|
||
da: int = buf[ci_a]
|
||
if da >= '0' and da <= '9':
|
||
cnt_a = cnt_a * 10 + (da - '0')
|
||
ok_a = 1
|
||
ci_a += 1
|
||
if ok_a == 0 or cnt_a <= 0: return None
|
||
et_a: mlirlite.LLVMType* = self._ParseMlirRetTyStr(pool, buf, xs + 1, iea)
|
||
if et_a is None: return None
|
||
return mlirlite.Array(pool, et_a, cnt_a)
|
||
# !llvm.struct<...> (anonymous body / named inline body)
|
||
if n >= 13 and string.strncmp(buf + s, "!llvm.struct<", 13) == 0:
|
||
is_s: sizet = s + 13
|
||
depth_s: int = 0
|
||
ies: sizet = e
|
||
iis: sizet = is_s
|
||
while iis < e:
|
||
cs: int = buf[iis]
|
||
if cs == '<': depth_s += 1
|
||
elif cs == '>':
|
||
if depth_s == 0:
|
||
ies = iis
|
||
break
|
||
depth_s -= 1
|
||
iis += 1
|
||
if ies <= is_s: return None
|
||
a: sizet = is_s
|
||
while a < ies and (buf[a] == ' ' or buf[a] == '\t'): a += 1
|
||
b: sizet = ies
|
||
while b > a and (buf[b - 1] == ' ' or buf[b - 1] == '\t'): b -= 1
|
||
nm_buf: bytes = None
|
||
if a < b and buf[a] == '"':
|
||
ns: sizet = a + 1
|
||
ne: sizet = ns
|
||
while ne < b and buf[ne] != '"': ne += 1
|
||
if ne <= ns: return None
|
||
nm_buf = stdlib.malloc(ne - ns + 1)
|
||
if nm_buf is None: return None
|
||
string.strncpy(nm_buf, buf + ns, ne - ns)
|
||
nm_buf[ne - ns] = '\0'
|
||
a = ne + 1
|
||
while a < b and (buf[a] == ' ' or buf[a] == ','): a += 1
|
||
if a >= b or buf[a] != '(' or buf[b - 1] != ')':
|
||
if nm_buf is not None: stdlib.free(nm_buf)
|
||
return None
|
||
# field list: split by top-level commas (parens/angle brackets count depth)
|
||
head: mlirlite.ParamNode* = None
|
||
tail: mlirlite.ParamNode* = None
|
||
cnt_f: int = 0
|
||
seg_s: sizet = a + 1
|
||
fe_f: sizet = b - 1
|
||
cur_f: sizet = a + 1
|
||
depth_f: int = 0
|
||
done: int = 0
|
||
while done == 0:
|
||
at_end: int = 0
|
||
if cur_f >= fe_f: at_end = 1
|
||
elif buf[cur_f] == ',' and depth_f == 0: at_end = 1
|
||
if at_end != 0:
|
||
gs: sizet = seg_s
|
||
ge: sizet = cur_f
|
||
while gs < ge and (buf[gs] == ' ' or buf[gs] == '\t'): gs += 1
|
||
while ge > gs and (buf[ge - 1] == ' ' or buf[ge - 1] == '\t'): ge -= 1
|
||
if ge > gs:
|
||
ft: mlirlite.LLVMType* = self._ParseMlirRetTyStr(pool, buf, gs, ge)
|
||
if ft is None:
|
||
if nm_buf is not None: stdlib.free(nm_buf)
|
||
return None
|
||
fn: mlirlite.ParamNode* = mlirlite.new_param_node(pool, ft)
|
||
if fn is None:
|
||
if nm_buf is not None: stdlib.free(nm_buf)
|
||
return None
|
||
if head is None: head = fn
|
||
else: mlirlite.paramnode_set_next(tail, fn)
|
||
tail = fn
|
||
cnt_f += 1
|
||
seg_s = cur_f + 1
|
||
if cur_f >= fe_f: done = 1
|
||
else:
|
||
c3: int = buf[cur_f]
|
||
if c3 == '(' or c3 == '<': depth_f += 1
|
||
elif c3 == ')' or c3 == '>':
|
||
if depth_f > 0: depth_f -= 1
|
||
cur_f += 1
|
||
# named: look up the real type in the registry; None if unregistered
|
||
if nm_buf is not None:
|
||
reg: mlirlite.LLVMType* = GenApi.get_struct_type(nm_buf)
|
||
stdlib.free(nm_buf)
|
||
return reg
|
||
return mlirlite.Struct(pool, head, cnt_f, None)
|
||
return None
|
||
|
||
# _LookupCachedRetTyText: lookup the persistent "dependency text.ll return type" cache
|
||
def _LookupCachedRetTyText(self, class_sha1: str, class_name: str, method_name: str, use_bare: int) -> str:
|
||
"""Look up the return-type text cache: return the text on a hit, None on a miss"""
|
||
if self.Gen is None or class_sha1 is None or method_name is None: return None
|
||
ht: hashtable.HashTable* = self.Gen.CallGenBase.MethodRetTyMap
|
||
if ht is None: return None
|
||
sha1_len: sizet = string.strlen(class_sha1)
|
||
cls_len: sizet = 0
|
||
if class_name is not None: cls_len = string.strlen(class_name)
|
||
meth_len: sizet = string.strlen(method_name)
|
||
# key length formula matches search_buf construction in _LookupMethodRetTyFromTextLL
|
||
key_len: sizet = sha1_len + meth_len + 2
|
||
if use_bare == 0: key_len = sha1_len + cls_len + meth_len + 4
|
||
key_buf: bytes = stdlib.malloc(key_len)
|
||
if key_buf is None: return None
|
||
if use_bare != 0: viperlib.snprintf(key_buf, key_len, "%s.%s", class_sha1, method_name)
|
||
else:
|
||
viperlib.snprintf(key_buf, key_len, "%s.%s.%s", class_sha1, class_name, method_name)
|
||
v: t.CPtr = ht.__getitem__(key_buf)
|
||
stdlib.free(key_buf)
|
||
if v is None: return None
|
||
return rawptr(v)
|
||
|
||
# _CacheModuleRetTyTexts: one pass to register all define lines and find the first match
|
||
def _CacheModuleRetTyTexts(self, class_sha1: str, text_buf: bytes, text_len: sizet, pool: memhub.MemBuddy*, search_buf: str, search_str_len: sizet) -> mlirlite.LLVMType*:
|
||
"""Single-pass register and lookup over all text.ll define lines (lookup only if table allocation fails)"""
|
||
if text_buf is None or text_len == 0: return None
|
||
# Merge the former two passes (register all, then scan again) into one
|
||
# file traversal; caching failure does not affect the in-pass lookup.
|
||
cache_on: int = 0
|
||
ht: hashtable.HashTable* = None
|
||
perm: memhub.MemBuddy* = None
|
||
sha1_len: sizet = 0
|
||
if self.Gen is not None and class_sha1 is not None:
|
||
perm = CompilerFactory.factory.PermPool
|
||
if perm is not None:
|
||
ht = self.Gen.CallGenBase.MethodRetTyMap
|
||
if ht is None:
|
||
ht = hashtable.HashTable(perm)
|
||
if ht is not None: self.Gen.CallGenBase.MethodRetTyMap = ht
|
||
if ht is not None:
|
||
cache_on = 1
|
||
sha1_len = string.strlen(class_sha1)
|
||
pos: sizet = 0
|
||
result_ty: mlirlite.LLVMType* = None
|
||
while pos < text_len:
|
||
ls: sizet = pos
|
||
le: sizet = pos
|
||
while le < text_len:
|
||
if text_buf[le] == '\n': break
|
||
le += 1
|
||
ll: sizet = le - ls
|
||
# Accept both function-def line forms: LLVM "define <ret> @name(...)"
|
||
# and MLIR "llvm.func @name(...) -> <ret> {" (void omits "->").
|
||
fs: sizet = ls
|
||
while fs < ls + ll and (text_buf[fs] == ' ' or text_buf[fs] == '\t'): fs += 1
|
||
is_mlir_line: int = 0
|
||
mlir_none: int = 0
|
||
matched: int = 0
|
||
at_pos: sizet = 0
|
||
ret_s: sizet = 0
|
||
ret_e: sizet = 0
|
||
if ll >= 7 and text_buf[ls] == 'd' and string.strncmp(text_buf + ls, "define ", 7) == 0:
|
||
matched = 1
|
||
at_pos = ls + 7
|
||
while at_pos < ls + ll:
|
||
if text_buf[at_pos] == '@': break
|
||
at_pos += 1
|
||
ret_s = ls + 7
|
||
ret_e = at_pos
|
||
elif ll >= 11 and fs + 11 <= ls + ll and string.strncmp(text_buf + fs, "llvm.func @", 11) == 0:
|
||
matched = 1
|
||
is_mlir_line = 1
|
||
at_pos = fs + 10 # right after "llvm.func " is '@'
|
||
# return type: ` -> <ret>` (up to line end or " {"); no `->` = void
|
||
ar_pos: sizet = at_pos
|
||
arrow: sizet = 0
|
||
while ar_pos + 2 < ls + ll:
|
||
if text_buf[ar_pos] == ' ' and text_buf[ar_pos + 1] == '-' and text_buf[ar_pos + 2] == '>':
|
||
arrow = ar_pos
|
||
break
|
||
ar_pos += 1
|
||
if arrow == 0:
|
||
mlir_none = 1
|
||
ret_s = ls + ll
|
||
ret_e = ls + ll
|
||
else:
|
||
ret_s = arrow + 3
|
||
while ret_s < ls + ll and text_buf[ret_s] == ' ': ret_s += 1
|
||
ret_e = ls + ll
|
||
kk: sizet = ret_s
|
||
while kk + 1 < ls + ll:
|
||
if text_buf[kk] == ' ' and text_buf[kk + 1] == '{':
|
||
ret_e = kk
|
||
break
|
||
kk += 1
|
||
if matched != 0 and at_pos < ls + ll:
|
||
name_start: sizet = at_pos + 1
|
||
quoted: int = 0
|
||
if name_start < ls + ll and text_buf[name_start] == '"':
|
||
name_start += 1
|
||
quoted = 1
|
||
# delimit: quoted name to '"', bare name to '(' (matches lookup side)
|
||
name_end: sizet = name_start
|
||
if quoted != 0:
|
||
while name_end < ls + ll:
|
||
if text_buf[name_end] == '"': break
|
||
name_end += 1
|
||
else:
|
||
while name_end < ls + ll:
|
||
if text_buf[name_end] == '(': break
|
||
name_end += 1
|
||
name_len: sizet = name_end - name_start
|
||
# register only own-module mangled names "{sha1}.{suffix}"; skip when table unavailable
|
||
if cache_on != 0 and name_len > sha1_len + 1:
|
||
if string.strncmp(text_buf + name_start, class_sha1, sha1_len) == 0 and text_buf[name_start + sha1_len] == '.':
|
||
key_buf: bytes = stdlib.malloc(name_len + 1)
|
||
if key_buf is not None:
|
||
string.strncpy(key_buf, text_buf + name_start, name_len)
|
||
key_buf[name_len] = '\0'
|
||
# first name wins (not overwritten by later same names)
|
||
if ht.__contains__(key_buf) == 0:
|
||
# cached value: LLVM form stores the raw return type; MLIR
|
||
# form stores '#' + type text, dispatched by _ParseMlirRetTyStr.
|
||
rt_len: sizet = 0
|
||
if is_mlir_line != 0:
|
||
if mlir_none != 0:
|
||
rt_len = 5 # '#' + "none"
|
||
else: rt_len = 1 + (ret_e - ret_s)
|
||
else: rt_len = at_pos - (ls + 7)
|
||
kcopy: str = perm.alloc(name_len + 1)
|
||
tcopy: str = perm.alloc(rt_len + 1)
|
||
if kcopy is not None and tcopy is not None:
|
||
string.strncpy(kcopy, text_buf + name_start, name_len)
|
||
kcopy[name_len] = '\0'
|
||
if is_mlir_line != 0:
|
||
tcopy[0] = '#'
|
||
if mlir_none != 0: string.strncpy(tcopy + 1, "none", 4)
|
||
else: string.strncpy(tcopy + 1, text_buf + ret_s, ret_e - ret_s)
|
||
else: string.strncpy(tcopy, text_buf + ls + 7, rt_len)
|
||
tcopy[rt_len] = '\0'
|
||
ht.set_str(kcopy, tcopy)
|
||
stdlib.free(key_buf)
|
||
# in-pass lookup: first define line matching search_buf and followed by
|
||
# '"' / '(' / line end wins.
|
||
if result_ty is None and name_start + search_str_len <= ls + ll:
|
||
if string.strncmp(text_buf + name_start, search_buf, search_str_len) == 0:
|
||
end_ch: int = 0
|
||
if name_start + search_str_len < ls + ll: end_ch = text_buf[name_start + search_str_len]
|
||
if end_ch == 34 or end_ch == 40 or end_ch == 0:
|
||
if is_mlir_line != 0:
|
||
if mlir_none != 0: result_ty = mlirlite.Void(pool)
|
||
else: result_ty = self._ParseMlirRetTyStr(pool, text_buf, ret_s, ret_e)
|
||
else: result_ty = self._ParseLLVMRetTyStr(pool, text_buf, ls + 7, at_pos)
|
||
pos = le + 1
|
||
return result_ty
|
||
|
||
# _LookupMethodRetTyFromTextLL: read {temp_dir}/{sha1[0:2]}/{sha1}.text.ll and find the define line
|
||
|
||
# ============================================================
|
||
def _LookupMethodRetTyFromTextLL(self, pool: memhub.MemBuddy*, class_sha1: str, class_name: str, method_name: str, use_bare: int = 0) -> mlirlite.LLVMType*:
|
||
"""Look up a method return type in a dependency module's text.ll; use_bare=1 searches a module-level "{sha1}.{method_name}" define."""
|
||
if pool is None or class_sha1 is None or method_name is None: return None
|
||
if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0: return None
|
||
|
||
sha1_len: sizet = string.strlen(class_sha1)
|
||
if sha1_len < 2: return None
|
||
|
||
# cache hit: O(1) text retrieval, rebuilt with the caller pool; miss falls through
|
||
cached_text: str = self._LookupCachedRetTyText(class_sha1, class_name, method_name, use_bare)
|
||
if cached_text is not None:
|
||
# '#' prefix = MLIR type text (form marker written by _CacheModuleRetTyTexts)
|
||
if cached_text[0] == '#': return self._ParseMlirRetTyStr(pool, cached_text, 1, string.strlen(cached_text))
|
||
return self._ParseLLVMRetTyStr(pool, cached_text, 0, string.strlen(cached_text))
|
||
|
||
# build text.ll path: {temp_dir}/{sha1[0:2]}/{sha1}.text.ll; use a subdir
|
||
# buffer to avoid %c formatting issues (as in StubMerger._sliced_path)
|
||
path_len: sizet = self.Gen.CallGenBase.TempDirLen + sha1_len + 14
|
||
path_buf: bytes = stdlib.malloc(path_len)
|
||
if path_buf is None: return None
|
||
subdir_buf: bytes = stdlib.malloc(3)
|
||
if subdir_buf is None:
|
||
stdlib.free(path_buf)
|
||
return None
|
||
subdir_buf[0] = class_sha1[0]
|
||
subdir_buf[1] = class_sha1[1]
|
||
subdir_buf[2] = '\0'
|
||
viperlib.snprintf(path_buf, path_len, "%s/%s/%s.text.ll", self.Gen.CallGenBase.TempDir, subdir_buf, class_sha1)
|
||
stdlib.free(subdir_buf)
|
||
|
||
# read text.ll by actual size (a fixed buffer would truncate large modules); 64MB cap
|
||
tf: fileio.File* = fileio.File(path_buf, fileio.MODE.R)
|
||
if tf.closed:
|
||
stdlib.free(path_buf)
|
||
return None
|
||
tfsize: LONGLONG = tf.size()
|
||
if tfsize <= 0 or tfsize > 67108864:
|
||
tf.close()
|
||
stdlib.free(path_buf)
|
||
return None
|
||
read_size: sizet = sizet(tfsize)
|
||
read_buf: bytes = stdlib.malloc(read_size + 1)
|
||
if read_buf is None:
|
||
tf.close()
|
||
stdlib.free(path_buf)
|
||
return None
|
||
text_len: int64 = tf.read_all(read_buf, ULONG(read_size + 1))
|
||
tf.close()
|
||
stdlib.free(path_buf)
|
||
|
||
if text_len <= 0:
|
||
stdlib.free(read_buf)
|
||
return None
|
||
read_buf[text_len] = '\0'
|
||
|
||
# build search pattern: "{sha1}.{class_name}.{method_name}" or bare "{sha1}.{method_name}"
|
||
cls_len: sizet = 0
|
||
if class_name is not None: cls_len = string.strlen(class_name)
|
||
meth_len: sizet = string.strlen(method_name)
|
||
search_len: sizet = sha1_len + meth_len + 2
|
||
if use_bare == 0: search_len = sha1_len + cls_len + meth_len + 4
|
||
search_buf: bytes = stdlib.malloc(search_len)
|
||
if search_buf is None:
|
||
stdlib.free(read_buf)
|
||
return None
|
||
if use_bare != 0:
|
||
viperlib.snprintf(search_buf, search_len, "%s.%s", class_sha1, method_name)
|
||
else:
|
||
viperlib.snprintf(search_buf, search_len, "%s.%s.%s", class_sha1, class_name, method_name)
|
||
search_str_len: sizet = string.strlen(search_buf)
|
||
|
||
# single pass over text.ll: register all define lines for later O(1) hits and
|
||
# return the first match in the same pass; reuse read_all's return length.
|
||
result_ty: mlirlite.LLVMType* = self._CacheModuleRetTyTexts(class_sha1, read_buf, sizet(text_len), pool, search_buf, search_str_len)
|
||
|
||
stdlib.free(search_buf)
|
||
stdlib.free(read_buf)
|
||
return result_ty
|
||
|
||
# _dep_text_ll_exists: probe whether {temp_dir}/{sha1[0:2]}/{sha1}.text.ll exists
|
||
|
||
# ============================================================
|
||
def _dep_text_ll_exists(self, pool: memhub.MemBuddy*, sha1: str) -> int:
|
||
"""Probe whether {temp_dir}/{sha1[0:2]}/{sha1}.text.ll exists, 1=yes"""
|
||
if self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen == 0 or sha1 is None: return 0
|
||
sha1_len: sizet = string.strlen(sha1)
|
||
if sha1_len < 2: return 0
|
||
path_len: sizet = self.Gen.CallGenBase.TempDirLen + sha1_len + 14
|
||
path_buf: bytes = stdlib.malloc(path_len)
|
||
subdir_buf: bytes = stdlib.malloc(3)
|
||
if path_buf is None or subdir_buf is None:
|
||
if path_buf is not None: stdlib.free(path_buf)
|
||
if subdir_buf is not None: stdlib.free(subdir_buf)
|
||
return 0
|
||
subdir_buf[0] = sha1[0]
|
||
subdir_buf[1] = sha1[1]
|
||
subdir_buf[2] = '\0'
|
||
viperlib.snprintf(path_buf, path_len, "%s/%s/%s.text.ll", self.Gen.CallGenBase.TempDir, subdir_buf, sha1)
|
||
stdlib.free(subdir_buf)
|
||
# existence check via GetFileAttributesA (no file handle opened)
|
||
attr: ULONG = win32file.GetFileAttributesA(path_buf)
|
||
exists: int = 1
|
||
# INVALID_FILE_ATTRIBUTES = missing/inaccessible -> 0.
|
||
if attr == 0xFFFFFFFF: exists = 0
|
||
# The original CreateFileA rejected directories; exclude them explicitly to
|
||
# preserve the equivalent "readable regular file" semantics.
|
||
if (attr & win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: exists = 0
|
||
stdlib.free(path_buf)
|
||
return exists
|
||
|
||
# property (@property/@x.setter/@x.deleter) and static/class method call helpers
|
||
|
||
# _translate_property_call: prefer an existing module function, else build "{sha1}.ClassName.method"
|