Files
ViperC/App/lib/core/Gen/Call/CallGenBase.vp
T

1552 lines
86 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.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 win32.fileio as fileio
import win32.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 = 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(int(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(int(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(int(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(int(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(int(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(int(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(int(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(int(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(int(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(int(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
# ⚠ 同 lookup_ret_kind_via_vpi:**加载成功才记备忘**。失败也记会把该模块毒化成"已加载",
# 使之后所有跨模块查询(含 willFree 的 _vpi_willfree,它共用这条备忘)永远查空。
if VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir) == 0: return 0
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
# ⚠ 只有**加载成功**才能记入"已加载"备忘。_ensure 失败(该模块的 vpi 此刻还没生成 /
# 打不开)时若也记上,后续所有跨模块查询(逃逸摘要 / willFree / ret)都会**跳过真正的
# 加载** → 缓存恒空 → 判据静默失效。现场:本查询可能在极早的翻译阶段先跑(life_of_expr
# 判右值生命期),把尚无 vpi 的模块毒化成"已加载" → EscapeXmodTest(esc)/
# NewLifetimeWillFreeTest(wf)双双漏检。
if VpiReader._ensure_vpi_ret_annot_loaded(cls_sha1, self.Gen.CallGenBase.TempDir) == 0: return 0
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 = 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(int(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"