R14:rawptr[X] -> X*(56 文件 / 834 行改动);保留 6 处 rawptr[t.CPtr](存纯裸指针的规范例外)。 修复:class X(t.REnum) 的变体字段写成自引用 X* 时,字段解析在变体扫描期经 ensure_forward_class 建了一个 opaque 前向占位;末尾 register_struct 按 Ty 指针去重会再建第二个同名 entry,而 find_struct 取首个(占位,0 字段)→ X.__sizeof__() == 0,报「已注册但大小为 0(字段链表未填充)」。改为注册前若命中同模块前向占位则就地填充(与 ensure_forward_class 文档口径一致)。TPC 无此问题,属 TPV 侧缺口。 验证:自举三重重编 SUCCESS;全量 77/77 顺序回归通过(修复前卡在 LLvmLiteTest)。
3324 lines
183 KiB
Plaintext
3324 lines
183 KiB
Plaintext
import lib.core.Gen.GenApi as GenApi
|
||
import lib.core.Gen.GenTypes as GenTypes
|
||
import lib.core.CompilerFactory as CompilerFactory
|
||
import t, c
|
||
from stdint import *
|
||
import ast
|
||
import string
|
||
import stdio
|
||
import stdlib
|
||
import sys
|
||
import memhub
|
||
import mlirlite
|
||
import viperlib
|
||
import w32.fileio as fileio
|
||
import lib.core.VLogger as VLogger
|
||
import lib.core.Gen.Translator as HT
|
||
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||
import lib.Projectrans.Config as Config
|
||
|
||
|
||
# REPL stub layout recovery: constants and class entries (impl at file end).
|
||
# Declared first so type annotations are visible before the REPL hook uses them; REPL-only.
|
||
|
||
define _REPL_STUB_CLS_MAX = 1024
|
||
_REPL_STUB_FIELD_MAX: int = 128
|
||
_REPL_STUB_VT_MAX: int = 128
|
||
_REPL_STUB_TXT_CAP: sizet = 4194304
|
||
_REPL_STUB_WARN_MAX: int = 10
|
||
_REPL_STUB_FINISH_PASSES: int = 8
|
||
|
||
# Two sentinel values for ReplStubClass.LayoutCount (not field counts).
|
||
# Layout metadata now comes from {sha1}.meta STRUCT ... FIELDS n / OPAQUE records, not .stub.ll text.
|
||
_REPL_STUB_NOCOUNT_NOCLASS: int = -1 # no STRUCT line for this key in meta (or meta not read)
|
||
_REPL_STUB_NOCOUNT_OPAQUE: int = -2 # key is OPAQUE in meta (no body)
|
||
|
||
# Reason codes for "layout unavailable -> explicit refuse" (_repl_stub_warn_nodata):
|
||
# distinguish missing data from untrusted data; both skip StructEntry registration.
|
||
define _REPL_SU_NOSTUB = 1 # missing data: module has no {sha1}.meta
|
||
define _REPL_SU_NOCLASS = 2 # refused: meta exists but has no STRUCT line for this class
|
||
define _REPL_SU_OPAQUE = 3 # refused: class is OPAQUE in meta (no body)
|
||
define _REPL_SU_PARENT = 4 # upstream no data: base layout unavailable/not registered (goes to _repl_stub_warn_parent)
|
||
|
||
# ReplStubClass.Flags / MethodFlags bits
|
||
define _REPL_SF_CVTABLE = 1
|
||
define _REPL_SF_NOVTABLE = 2
|
||
define _REPL_SF_ANYFUNC = 4
|
||
define _REPL_SF_PACKED = 8
|
||
|
||
# ReplStubClass.State
|
||
define _REPL_SS_REFUSED = 0 # layout untrusted: never register (field access fails explicitly)
|
||
define _REPL_SS_OK = 1 # full layout registered
|
||
define _REPL_SS_PENDING = 2 # base not ready yet: defer to final pass
|
||
define _REPL_SS_EMPTY = 3 # no base/no vtable/unresolvable fields: use existing vpi injection
|
||
|
||
class ReplStubClass:
|
||
Key: str # "<sha1>.<class name>" (= definition-side named struct name)
|
||
ClassName: str
|
||
Sha1: str
|
||
Flags: int
|
||
OwnNames: sizet* # elements = str pointers (this class field names, AnnAssign order)
|
||
OwnTypes: sizet* # elements = LLVMType pointers
|
||
OwnCount: int
|
||
MethodNames: sizet*
|
||
MethodFlags: sizet*
|
||
MethodCount: int
|
||
Bases: sizet*
|
||
BaseCount: int
|
||
LayoutCount: int # layout field count (>=0 = meta STRUCT ... FIELDS n; negative = sentinel, not count)
|
||
# see _REPL_STUB_NOCOUNT_NOCLASS/-OPAQUE: no such class / meta unread / opaque
|
||
VtArity: int # meta VTABLE ... SLOTS n (0 = no record in meta)
|
||
Names: sizet*
|
||
Types: sizet*
|
||
Count: int
|
||
HasVt: int
|
||
IsNoVTable: int
|
||
IsOOP: int
|
||
HasInit: int
|
||
HasNew: int
|
||
VtNames: sizet*
|
||
VtCount: int
|
||
VtTrust: int # 1 = vtable slot count matches stub (VTableMethods can be registered)
|
||
OwnFail: int # 1 = a field annotation failed to resolve, so OwnCount is only partially parsed (incomplete)
|
||
State: int
|
||
|
||
|
||
# Method return-annotation cache (stdlib-persistent). Deep vpi AST nodes may be
|
||
# corrupted later, so return annotations are parsed from raw vpi_buf text by line.
|
||
define VPI_RET_MAX = 4096
|
||
define VPI_KEY_LEN = 224
|
||
define VPI_RET_LEN = 224
|
||
|
||
_g_vpi_ret_keys: bytes = None
|
||
_g_vpi_ret_strs: bytes = None
|
||
_g_vpi_ret_count: int = 0
|
||
_g_vpi_ret_cap: int = 0
|
||
|
||
# Method parameter-annotation cache (stdlib-persistent, mirroring the return cache).
|
||
# Deep param lists may be corrupted and truncated, so params are parsed from vpi_buf text.
|
||
define VPI_PARAM_MAX = 4096
|
||
define VPI_PARAM_KEY_LEN = 224
|
||
define VPI_PARAM_LEN = 1024
|
||
|
||
# Open-addressing index for the key tables (name -> slot index). Slots store indices
|
||
# (stable across realloc); each registration appends, lookup keeps first-registered.
|
||
define VPI_RET_IDX_CAP = 8192
|
||
define VPI_PARAM_IDX_CAP = 8192
|
||
# Index hash constants (32-bit FNV-1a, same as StructGenMixin).
|
||
define VPI_IDX_FNV_OFFSET = 2166136261
|
||
define VPI_IDX_FNV_PRIME = 16777619
|
||
define VPI_IDX_FNV_MASK = 4294967295
|
||
|
||
_g_vpi_param_keys: bytes = None
|
||
_g_vpi_param_strs: bytes = None
|
||
_g_vpi_param_count: int = 0
|
||
_g_vpi_param_cap: int = 0
|
||
|
||
# Return-annotation key-table index (same malloc lifetime; trusted iff Built == Count)
|
||
_g_vpi_ret_idx: bytes = None
|
||
_g_vpi_ret_idx_cap: int = 0
|
||
_g_vpi_ret_idx_built: int = 0
|
||
# Parameter-annotation key-table index (same as above)
|
||
_g_vpi_param_idx: bytes = None
|
||
_g_vpi_param_idx_cap: int = 0
|
||
_g_vpi_param_idx_built: int = 0
|
||
|
||
|
||
# Index probe/insert/rebuild (module-level functions shared by both key tables).
|
||
def _vpi_idx_hash(key: bytes) -> uint64:
|
||
"""Hash key bytes to a 64-bit FNV-1a value until NUL; empty key returns the offset basis."""
|
||
h: uint64 = uint64(VPI_IDX_FNV_OFFSET)
|
||
if key is None: return h
|
||
i: sizet = 0
|
||
while key[i] != '\0':
|
||
h = (h ^ uint64(int(key[i]))) * uint64(VPI_IDX_FNV_PRIME)
|
||
h = h & uint64(VPI_IDX_FNV_MASK)
|
||
i += 1
|
||
return h
|
||
|
||
|
||
def _vpi_idx_slot(idx_base: bytes, si: sizet) -> sizet*:
|
||
"""Return the si-th 8-byte index slot (stores slot_index+1; 0 means empty)."""
|
||
return (sizet*)(uint64(idx_base) + uint64(si) * 8)
|
||
|
||
|
||
def _vpi_idx_find(keys: bytes, key_stride: int, idx_base: bytes, cap: int, key: bytes) -> int:
|
||
"""Probe the index for key and return the slot index (>=0); miss or unusable index returns -1."""
|
||
if keys is None or idx_base is None or cap <= 0 or key is None: return -1
|
||
h: uint64 = _vpi_idx_hash(key)
|
||
mask: sizet = sizet(cap - 1)
|
||
si: sizet = sizet(h) & mask
|
||
for i in range(cap):
|
||
slot: sizet* = _vpi_idx_slot(idx_base, si)
|
||
sv: sizet = slot[0]
|
||
if sv == 0: return -1
|
||
se: int = int(sv) - 1
|
||
ek: bytes = keys + se * key_stride
|
||
if string.strcmp(ek, key) == 0: return se
|
||
si = (si + 1) & mask
|
||
return -1
|
||
|
||
|
||
def _vpi_idx_insert(idx_base: bytes, cap: int, slot_index: int, key: bytes) -> int:
|
||
"""Insert (key -> slot_index) into the index, one slot per registration; 0=success / 1=failure."""
|
||
if idx_base is None or cap <= 0 or key is None: return 1
|
||
h: uint64 = _vpi_idx_hash(key)
|
||
mask: sizet = sizet(cap - 1)
|
||
si: sizet = sizet(h) & mask
|
||
for i in range(cap):
|
||
slot: sizet* = _vpi_idx_slot(idx_base, si)
|
||
if slot[0] == 0:
|
||
slot[0] = sizet(slot_index + 1)
|
||
return 0
|
||
si = (si + 1) & mask
|
||
return 1
|
||
|
||
|
||
def _vpi_rebuild_ret_idx(new_cap: int) -> int:
|
||
"""Rebuild the return-annotation index for a new capacity (power of two); 0=trusted / 1=untrusted."""
|
||
global _g_vpi_ret_idx, _g_vpi_ret_idx_cap, _g_vpi_ret_idx_built
|
||
if new_cap <= 0:
|
||
_g_vpi_ret_idx = None
|
||
_g_vpi_ret_idx_cap = 0
|
||
_g_vpi_ret_idx_built = -1
|
||
return 1
|
||
new_idx: bytes = None
|
||
if _g_vpi_ret_idx is None: new_idx = stdlib.malloc(new_cap * 8)
|
||
else: new_idx = stdlib.realloc(_g_vpi_ret_idx, new_cap * 8)
|
||
if new_idx is None:
|
||
_g_vpi_ret_idx = None
|
||
_g_vpi_ret_idx_cap = 0
|
||
_g_vpi_ret_idx_built = -1
|
||
return 1
|
||
string.memset(new_idx, 0, sizet(new_cap) * 8)
|
||
_g_vpi_ret_idx = new_idx
|
||
_g_vpi_ret_idx_cap = new_cap
|
||
fail: int = 0
|
||
ri: int
|
||
for ri in range(_g_vpi_ret_count):
|
||
if _vpi_idx_insert(new_idx, new_cap, ri, _g_vpi_ret_keys + ri * VPI_KEY_LEN) != 0:
|
||
fail = 1
|
||
break
|
||
if fail != 0:
|
||
_g_vpi_ret_idx_built = -1
|
||
return 1
|
||
_g_vpi_ret_idx_built = _g_vpi_ret_count
|
||
return 0
|
||
|
||
|
||
def _vpi_rebuild_param_idx(new_cap: int) -> int:
|
||
"""Rebuild the param-annotation index for a new capacity; 0=trusted / 1=untrusted."""
|
||
global _g_vpi_param_idx, _g_vpi_param_idx_cap, _g_vpi_param_idx_built
|
||
if new_cap <= 0:
|
||
_g_vpi_param_idx = None
|
||
_g_vpi_param_idx_cap = 0
|
||
_g_vpi_param_idx_built = -1
|
||
return 1
|
||
new_idx: bytes = None
|
||
if _g_vpi_param_idx is None: new_idx = stdlib.malloc(new_cap * 8)
|
||
else: new_idx = stdlib.realloc(_g_vpi_param_idx, new_cap * 8)
|
||
if new_idx is None:
|
||
_g_vpi_param_idx = None
|
||
_g_vpi_param_idx_cap = 0
|
||
_g_vpi_param_idx_built = -1
|
||
return 1
|
||
string.memset(new_idx, 0, sizet(new_cap) * 8)
|
||
_g_vpi_param_idx = new_idx
|
||
_g_vpi_param_idx_cap = new_cap
|
||
fail: int = 0
|
||
ri: int
|
||
for ri in range(_g_vpi_param_count):
|
||
if _vpi_idx_insert(new_idx, new_cap, ri, _g_vpi_param_keys + ri * VPI_PARAM_KEY_LEN) != 0:
|
||
fail = 1
|
||
break
|
||
if fail != 0:
|
||
_g_vpi_param_idx_built = -1
|
||
return 1
|
||
_g_vpi_param_idx_built = _g_vpi_param_count
|
||
return 0
|
||
|
||
|
||
# _cache_vpi_return_annots_text - extract method return annotations from vpi_buf text.
|
||
# Does not rely on the AST tree. Tracks "class X:" and "def ... -> Ret:" lines.
|
||
def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
|
||
"""Extract method return-annotation strings from vpi_buf text into a persistent stdlib cache."""
|
||
global _g_vpi_ret_keys, _g_vpi_ret_strs, _g_vpi_ret_count, _g_vpi_ret_cap
|
||
global _g_vpi_ret_idx, _g_vpi_ret_idx_cap, _g_vpi_ret_idx_built
|
||
if vpi_buf is None or src_sha1 is None: return 0
|
||
if _g_vpi_ret_keys is None:
|
||
_g_vpi_ret_keys = stdlib.malloc(VPI_RET_MAX * VPI_KEY_LEN)
|
||
_g_vpi_ret_strs = stdlib.malloc(VPI_RET_MAX * VPI_RET_LEN)
|
||
if _g_vpi_ret_keys is None or _g_vpi_ret_strs is None: return 0
|
||
string.memset(_g_vpi_ret_keys, 0, VPI_RET_MAX * VPI_KEY_LEN)
|
||
string.memset(_g_vpi_ret_strs, 0, VPI_RET_MAX * VPI_RET_LEN)
|
||
_g_vpi_ret_cap = VPI_RET_MAX
|
||
# Name index: slots = key-table capacity * 2. On alloc failure stay untrusted
|
||
# (Built=-1) and fall back to linear scan (semantics unchanged).
|
||
_g_vpi_ret_idx = stdlib.malloc(VPI_RET_IDX_CAP * 8)
|
||
if _g_vpi_ret_idx is not None:
|
||
string.memset(_g_vpi_ret_idx, 0, VPI_RET_IDX_CAP * 8)
|
||
_g_vpi_ret_idx_cap = VPI_RET_IDX_CAP
|
||
_g_vpi_ret_idx_built = _g_vpi_ret_count
|
||
else:
|
||
_g_vpi_ret_idx_cap = 0
|
||
_g_vpi_ret_idx_built = -1
|
||
sha1_len: sizet = string.strlen(src_sha1)
|
||
buf_len: sizet = string.strlen(vpi_buf)
|
||
cur_cls: bytes = None
|
||
pos: sizet = 0
|
||
line_no: int = 0
|
||
while pos < buf_len:
|
||
ls: sizet = pos
|
||
le: sizet = pos
|
||
while le < buf_len and vpi_buf[le] != '\n': le += 1
|
||
line_len: sizet = le - ls
|
||
# skip leading spaces, then check class/def
|
||
i: sizet = ls
|
||
while i < le and vpi_buf[i] == ' ': i += 1
|
||
if i < le:
|
||
# class X:
|
||
if line_len >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0:
|
||
cstart: sizet = i + 6
|
||
cend: sizet = cstart
|
||
# Extract class name: stop at ':' / ' ' / '(' so base-class parens
|
||
# are excluded ("class MemBuddy(MemManager):" -> "MemBuddy").
|
||
while cend < le and vpi_buf[cend] != ':' and vpi_buf[cend] != ' ' and vpi_buf[cend] != '(': cend += 1
|
||
if cend > cstart:
|
||
if cur_cls is not None: stdlib.free(cur_cls)
|
||
cur_cls = stdlib.malloc(cend - cstart + 1)
|
||
if cur_cls is not None:
|
||
string.strncpy(cur_cls, vpi_buf + cstart, cend - cstart)
|
||
cur_cls[cend - cstart] = '\0'
|
||
# def name(...) -> Ret: # noqa
|
||
elif line_len >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0:
|
||
# Module-level function (no indent): clear class context to avoid
|
||
# leftovers after a class ends (e.g. json null/int_val).
|
||
if i == ls and cur_cls is not None:
|
||
stdlib.free(cur_cls)
|
||
cur_cls = None
|
||
dstart: sizet = i + 4
|
||
dend: sizet = dstart
|
||
while dend < le and vpi_buf[dend] != '(': dend += 1
|
||
# method name = [dstart, dend)
|
||
# find "->"
|
||
rpos: sizet = dstart
|
||
arrow_pos: sizet = le
|
||
while rpos < le - 1:
|
||
if vpi_buf[rpos] == '-' and vpi_buf[rpos + 1] == '>':
|
||
arrow_pos = rpos
|
||
break
|
||
rpos += 1
|
||
if dend > dstart:
|
||
# Return type: extract "-> Ret" annotation; unannotated non-special
|
||
# methods default to "i32" (matches HandlesType.infer_return_type).
|
||
ret_src: str = None
|
||
rtype_len: sizet = 0
|
||
if arrow_pos < le:
|
||
# return type = from arrow_pos+2 (skip spaces) up to ':'
|
||
rstart2: sizet = arrow_pos + 2
|
||
while rstart2 < le and vpi_buf[rstart2] == ' ': rstart2 += 1
|
||
rend2: sizet = rstart2
|
||
while rend2 < le and vpi_buf[rend2] != ':': rend2 += 1
|
||
if rend2 > rstart2:
|
||
rtype_len = rend2 - rstart2
|
||
ret_src = vpi_buf + rstart2
|
||
else:
|
||
# no return annotation: default i32 (except special methods filtered below)
|
||
rtype_len = 3
|
||
ret_src = "i32"
|
||
if ret_src is not None:
|
||
# build key and ret_str, store into cache
|
||
mname_len: sizet = dend - dstart
|
||
cls_len2: sizet = 0
|
||
if cur_cls is not None: cls_len2 = string.strlen(cur_cls)
|
||
key_len: sizet = sha1_len + 1 + cls_len2 + 1 + mname_len
|
||
if key_len < VPI_KEY_LEN and rtype_len < VPI_RET_LEN:
|
||
# copy method name to a temp buffer (vpi_buf+dstart is not NUL-terminated)
|
||
mname_buf: bytes = stdlib.malloc(mname_len + 1)
|
||
if mname_buf is not None:
|
||
string.strncpy(mname_buf, vpi_buf + dstart, mname_len)
|
||
mname_buf[mname_len] = '\0'
|
||
# skip unannotated special methods (init/deinit): they have dedicated paths
|
||
skip_def: int = 0
|
||
if arrow_pos >= le and (string.strcmp(mname_buf, "__init__") == 0 or string.strcmp(mname_buf, "__new__") == 0 or string.strcmp(mname_buf, "__before_init__") == 0 or string.strcmp(mname_buf, "__after_init__") == 0):
|
||
skip_def = 1
|
||
if skip_def == 0:
|
||
key_buf: bytes = stdlib.malloc(key_len + 1)
|
||
if key_buf is not None:
|
||
if cur_cls is not None: viperlib.snprintf(key_buf, key_len + 1, "%s.%s.%s", src_sha1, cur_cls, mname_buf)
|
||
else: viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, mname_buf)
|
||
# dedup: skip if key already exists (same file may load multiple times)
|
||
dup_key: int = 0
|
||
dki: int
|
||
# Index fast path: when trusted (Built==Count) a single
|
||
# probe detects duplicates; otherwise fall back to linear scan.
|
||
if _g_vpi_ret_idx_built == _g_vpi_ret_count and _g_vpi_ret_idx is not None and _g_vpi_ret_idx_cap > 0:
|
||
if _vpi_idx_find(_g_vpi_ret_keys, VPI_KEY_LEN, _g_vpi_ret_idx, _g_vpi_ret_idx_cap, key_buf) >= 0:
|
||
dup_key = 1
|
||
else:
|
||
# Fast path: equal keys share the first byte, so skip
|
||
# strcmp when the candidate's first byte differs.
|
||
dup_first: int = key_buf[0]
|
||
for dki in range(_g_vpi_ret_count):
|
||
ent_d: bytes = _g_vpi_ret_keys + dki * VPI_KEY_LEN
|
||
if ent_d[0] == dup_first and string.strcmp(ent_d, key_buf) == 0:
|
||
dup_key = 1
|
||
break
|
||
grow_ok: int = 1
|
||
if dup_key == 0 and _g_vpi_ret_count >= _g_vpi_ret_cap: grow_ok = _grow_vpi_ret_cache()
|
||
if dup_key == 0 and grow_ok != 0:
|
||
string.strcpy(_g_vpi_ret_keys + _g_vpi_ret_count * VPI_KEY_LEN, key_buf)
|
||
string.strncpy(_g_vpi_ret_strs + _g_vpi_ret_count * VPI_RET_LEN, ret_src, rtype_len)
|
||
(_g_vpi_ret_strs + _g_vpi_ret_count * VPI_RET_LEN)[rtype_len] = '\0'
|
||
_g_vpi_ret_count += 1
|
||
# Sync index only if trusted before insert (Built==Count-1,
|
||
# incl. rebuild) and insert succeeded; else mark untrusted.
|
||
if _g_vpi_ret_idx_built == _g_vpi_ret_count - 1 and _vpi_idx_insert(_g_vpi_ret_idx, _g_vpi_ret_idx_cap, _g_vpi_ret_count - 1, key_buf) == 0:
|
||
_g_vpi_ret_idx_built = _g_vpi_ret_count
|
||
else: _g_vpi_ret_idx_built = -1
|
||
stdlib.free(key_buf)
|
||
stdlib.free(mname_buf)
|
||
pos = le + 1
|
||
line_no += 1
|
||
if cur_cls is not None: stdlib.free(cur_cls)
|
||
return 1
|
||
|
||
|
||
# _cache_vpi_param_annots_text - extract function/method param annotations from vpi_buf text.
|
||
# Stored as key="sha1.ClassName.method"/"sha1.method", val='@'-separated annotation strings.
|
||
def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
|
||
"""Extract function/method param-annotation strings from vpi_buf text into a persistent stdlib cache."""
|
||
global _g_vpi_param_keys, _g_vpi_param_strs, _g_vpi_param_count, _g_vpi_param_cap
|
||
global _g_vpi_param_idx, _g_vpi_param_idx_cap, _g_vpi_param_idx_built
|
||
if vpi_buf is None or src_sha1 is None: return 0
|
||
if _g_vpi_param_keys is None:
|
||
_g_vpi_param_keys = stdlib.malloc(VPI_PARAM_MAX * VPI_PARAM_KEY_LEN)
|
||
_g_vpi_param_strs = stdlib.malloc(VPI_PARAM_MAX * VPI_PARAM_LEN)
|
||
if _g_vpi_param_keys is None or _g_vpi_param_strs is None: return 0
|
||
string.memset(_g_vpi_param_keys, 0, VPI_PARAM_MAX * VPI_PARAM_KEY_LEN)
|
||
string.memset(_g_vpi_param_strs, 0, VPI_PARAM_MAX * VPI_PARAM_LEN)
|
||
_g_vpi_param_cap = VPI_PARAM_MAX
|
||
# Name index: slots = key-table capacity * 2. On alloc failure stay untrusted
|
||
# (Built=-1) and fall back to linear scan (semantics unchanged).
|
||
_g_vpi_param_idx = stdlib.malloc(VPI_PARAM_IDX_CAP * 8)
|
||
if _g_vpi_param_idx is not None:
|
||
string.memset(_g_vpi_param_idx, 0, VPI_PARAM_IDX_CAP * 8)
|
||
_g_vpi_param_idx_cap = VPI_PARAM_IDX_CAP
|
||
_g_vpi_param_idx_built = _g_vpi_param_count
|
||
else:
|
||
_g_vpi_param_idx_cap = 0
|
||
_g_vpi_param_idx_built = -1
|
||
sha1_len: sizet = string.strlen(src_sha1)
|
||
buf_len: sizet = string.strlen(vpi_buf)
|
||
cur_cls: bytes = None
|
||
pos: sizet = 0
|
||
while pos < buf_len:
|
||
ls: sizet = pos
|
||
le: sizet = pos
|
||
while le < buf_len and vpi_buf[le] != '\n': le += 1
|
||
line_len: sizet = le - ls
|
||
i: sizet = ls
|
||
while i < le and vpi_buf[i] == ' ': i += 1
|
||
if i < le:
|
||
if line_len >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0:
|
||
cstart: sizet = i + 6
|
||
cend: sizet = cstart
|
||
while cend < le and vpi_buf[cend] != ':' and vpi_buf[cend] != ' ' and vpi_buf[cend] != '(': cend += 1
|
||
if cend > cstart:
|
||
if cur_cls is not None: stdlib.free(cur_cls)
|
||
cur_cls = stdlib.malloc(cend - cstart + 1)
|
||
if cur_cls is not None:
|
||
string.strncpy(cur_cls, vpi_buf + cstart, cend - cstart)
|
||
cur_cls[cend - cstart] = '\0'
|
||
elif line_len >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0:
|
||
# module-level function (no indent): clear class context
|
||
if i == ls and cur_cls is not None:
|
||
stdlib.free(cur_cls)
|
||
cur_cls = None
|
||
dstart: sizet = i + 4
|
||
dend: sizet = dstart
|
||
while dend < le and vpi_buf[dend] != '(': dend += 1
|
||
if dend >= le:
|
||
pos = le + 1
|
||
continue
|
||
# find matching ')' (depth-aware: annotations may contain [ ] / { } brackets)
|
||
p: sizet = dend + 1
|
||
pdepth: int = 0
|
||
pend: sizet = dend
|
||
while p < le:
|
||
ch_p: char = vpi_buf[p]
|
||
if ch_p == '(' or ch_p == '[' or ch_p == '{': pdepth += 1
|
||
elif ch_p == ')' or ch_p == ']' or ch_p == '}':
|
||
pdepth -= 1
|
||
if pdepth < 0:
|
||
pend = p
|
||
break
|
||
p += 1
|
||
# parse params segment by segment (top-level commas; skip *args/**kwargs)
|
||
plist: bytes = stdlib.malloc(VPI_PARAM_LEN)
|
||
if plist is None:
|
||
pos = le + 1
|
||
continue
|
||
plist[0] = '\0'
|
||
plist_pos: sizet = 0
|
||
seg_written: int = 0
|
||
seg_cur: sizet = dend + 1
|
||
while seg_cur < pend:
|
||
while seg_cur < pend and (vpi_buf[seg_cur] == ' ' or vpi_buf[seg_cur] == ','): seg_cur += 1
|
||
if seg_cur >= pend: break
|
||
s_start: sizet = seg_cur
|
||
sd: int = 0
|
||
while seg_cur < pend:
|
||
ch_s: char = vpi_buf[seg_cur]
|
||
if ch_s == '(' or ch_s == '[' or ch_s == '{': sd += 1
|
||
elif ch_s == ')' or ch_s == ']' or ch_s == '}': sd -= 1
|
||
elif ch_s == ',' and sd == 0: break
|
||
seg_cur += 1
|
||
s_end: sizet = seg_cur
|
||
# skip * / ** (vararg/kwarg not added to args list)
|
||
st: sizet = s_start
|
||
while st < s_end and vpi_buf[st] == ' ': st += 1
|
||
if st < s_end and vpi_buf[st] == '*':
|
||
if seg_cur < pend and vpi_buf[seg_cur] == ',': seg_cur += 1
|
||
continue
|
||
# cut default value (top-level '=', e.g. ": str = None")
|
||
e_pos: sizet = st
|
||
ed: int = 0
|
||
while e_pos < s_end:
|
||
ch_e: char = vpi_buf[e_pos]
|
||
if ch_e == '(' or ch_e == '[' or ch_e == '{': ed += 1
|
||
elif ch_e == ')' or ch_e == ']' or ch_e == '}': ed -= 1
|
||
elif ch_e == '=' and ed == 0: break
|
||
e_pos += 1
|
||
s_end = e_pos
|
||
# extract annotation after top-level ':' (none -> empty, e.g. self)
|
||
c_pos: sizet = st
|
||
cd2: int = 0
|
||
annot_start: sizet = s_end
|
||
while c_pos < s_end:
|
||
ch_c: char = vpi_buf[c_pos]
|
||
if ch_c == '(' or ch_c == '[' or ch_c == '{': cd2 += 1
|
||
elif ch_c == ')' or ch_c == ']' or ch_c == '}': cd2 -= 1
|
||
elif ch_c == ':' and cd2 == 0:
|
||
annot_start = c_pos + 1
|
||
break
|
||
c_pos += 1
|
||
a_start: sizet = annot_start
|
||
while a_start < s_end and vpi_buf[a_start] == ' ': a_start += 1
|
||
a_end: sizet = s_end
|
||
while a_end > a_start and vpi_buf[a_end - 1] == ' ': a_end -= 1
|
||
if plist_pos + (a_end - a_start) + 1 < VPI_PARAM_LEN:
|
||
if seg_written > 0:
|
||
plist[plist_pos] = '@'
|
||
plist_pos += 1
|
||
ai2: sizet
|
||
for ai2 in range(a_end - a_start):
|
||
plist[plist_pos] = vpi_buf[a_start + ai2]
|
||
plist_pos += 1
|
||
plist[plist_pos] = '\0'
|
||
seg_written += 1
|
||
if seg_cur < pend and vpi_buf[seg_cur] == ',': seg_cur += 1
|
||
# store into cache (key = sha1.class.method / sha1.method, dedup)
|
||
if plist_pos > 0 and plist[0] != '\0':
|
||
mname_len: sizet = dend - dstart
|
||
cls_len2: sizet = 0
|
||
if cur_cls is not None: cls_len2 = string.strlen(cur_cls)
|
||
key_len: sizet = sha1_len + 1 + cls_len2 + 1 + mname_len
|
||
if key_len < VPI_PARAM_KEY_LEN:
|
||
mname_buf: bytes = stdlib.malloc(mname_len + 1)
|
||
if mname_buf is not None:
|
||
string.strncpy(mname_buf, vpi_buf + dstart, mname_len)
|
||
mname_buf[mname_len] = '\0'
|
||
key_buf: bytes = stdlib.malloc(key_len + 1)
|
||
if key_buf is not None:
|
||
if cur_cls is not None: viperlib.snprintf(key_buf, key_len + 1, "%s.%s.%s", src_sha1, cur_cls, mname_buf)
|
||
else: viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, mname_buf)
|
||
dup_key: int = 0
|
||
dki: int
|
||
# Index fast path: when trusted (Built==Count) a single
|
||
# probe detects duplicates; otherwise fall back to linear scan.
|
||
if _g_vpi_param_idx_built == _g_vpi_param_count and _g_vpi_param_idx is not None and _g_vpi_param_idx_cap > 0:
|
||
if _vpi_idx_find(_g_vpi_param_keys, VPI_PARAM_KEY_LEN, _g_vpi_param_idx, _g_vpi_param_idx_cap, key_buf) >= 0:
|
||
dup_key = 1
|
||
else:
|
||
# Fast path: equal keys share the first byte, so skip
|
||
# strcmp when the candidate's first byte differs.
|
||
dup_first: int = key_buf[0]
|
||
for dki in range(_g_vpi_param_count):
|
||
ent_d: bytes = _g_vpi_param_keys + dki * VPI_PARAM_KEY_LEN
|
||
if ent_d[0] == dup_first and string.strcmp(ent_d, key_buf) == 0:
|
||
dup_key = 1
|
||
break
|
||
grow_ok: int = 1
|
||
if dup_key == 0 and _g_vpi_param_count >= _g_vpi_param_cap: grow_ok = _grow_vpi_param_cache()
|
||
if dup_key == 0 and grow_ok != 0:
|
||
string.strcpy(_g_vpi_param_keys + _g_vpi_param_count * VPI_PARAM_KEY_LEN, key_buf)
|
||
string.strncpy(_g_vpi_param_strs + _g_vpi_param_count * VPI_PARAM_LEN, plist, plist_pos)
|
||
(_g_vpi_param_strs + _g_vpi_param_count * VPI_PARAM_LEN)[plist_pos] = '\0'
|
||
_g_vpi_param_count += 1
|
||
# Sync index only if trusted before insert (Built==Count-1,
|
||
# incl. rebuild) and insert succeeded; else mark untrusted.
|
||
if _g_vpi_param_idx_built == _g_vpi_param_count - 1 and _vpi_idx_insert(_g_vpi_param_idx, _g_vpi_param_idx_cap, _g_vpi_param_count - 1, key_buf) == 0:
|
||
_g_vpi_param_idx_built = _g_vpi_param_count
|
||
else: _g_vpi_param_idx_built = -1
|
||
stdlib.free(key_buf)
|
||
stdlib.free(mname_buf)
|
||
stdlib.free(plist)
|
||
pos = le + 1
|
||
if cur_cls is not None: stdlib.free(cur_cls)
|
||
return 1
|
||
|
||
|
||
# _grow_vpi_param_cache - grow the param-annotation cache (capacity doubles).
|
||
def _grow_vpi_param_cache() -> int:
|
||
"""Grow the param-annotation cache to double capacity; returns 1=success / 0=failure."""
|
||
global _g_vpi_param_keys, _g_vpi_param_strs, _g_vpi_param_cap
|
||
if _g_vpi_param_keys is None or _g_vpi_param_cap <= 0: return 0
|
||
new_cap: int = _g_vpi_param_cap * 2
|
||
if new_cap > 1048576: return 0
|
||
new_keys: bytes = stdlib.realloc(_g_vpi_param_keys, new_cap * VPI_PARAM_KEY_LEN)
|
||
if new_keys is None: return 0
|
||
new_strs: bytes = stdlib.realloc(_g_vpi_param_strs, new_cap * VPI_PARAM_LEN)
|
||
if new_strs is None: return 0
|
||
_g_vpi_param_keys = new_keys
|
||
_g_vpi_param_strs = new_strs
|
||
_g_vpi_param_cap = new_cap
|
||
# grow the index with the table and rebuild it (slots = capacity * 2, power of 2)
|
||
_vpi_rebuild_param_idx(new_cap * 2)
|
||
return 1
|
||
|
||
|
||
# _lookup_vpi_param_annots - look up the param-annotation cache.
|
||
def _lookup_vpi_param_annots(src_sha1: str, cls_name: str, method_name: str) -> str:
|
||
"""Look up the param-annotation cache; returns the '@'-separated string (leading self placeholder) or None."""
|
||
if src_sha1 is None or method_name is None or _g_vpi_param_keys is None: return None
|
||
sha1_len: sizet = string.strlen(src_sha1)
|
||
mlen: sizet = string.strlen(method_name)
|
||
clen: sizet = 0
|
||
if cls_name is not None: clen = string.strlen(cls_name)
|
||
key_len: sizet = sha1_len + clen + mlen + 2
|
||
if key_len >= VPI_PARAM_KEY_LEN: return None
|
||
key_buf: bytes = stdlib.malloc(key_len + 1)
|
||
if key_buf is None: return None
|
||
if cls_name is not None: viperlib.snprintf(key_buf, key_len + 1, "%s.%s.%s", src_sha1, cls_name, method_name)
|
||
else: viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, method_name)
|
||
r: str = None
|
||
ki: int
|
||
# Index fast path: when trusted (Built==Count) one probe returns the hit slot;
|
||
# a miss means truly absent, so no linear scan. Otherwise fall back to linear scan.
|
||
hit_p: int = -1
|
||
if _g_vpi_param_idx_built == _g_vpi_param_count and _g_vpi_param_idx is not None and _g_vpi_param_idx_cap > 0:
|
||
hit_p = _vpi_idx_find(_g_vpi_param_keys, VPI_PARAM_KEY_LEN, _g_vpi_param_idx, _g_vpi_param_idx_cap, key_buf)
|
||
else:
|
||
# Linear-scan fallback: equal keys share the first byte, so skip strcmp when
|
||
# the candidate's first byte differs (negative check only, result unchanged).
|
||
key_first: int = key_buf[0]
|
||
for ki in range(_g_vpi_param_count):
|
||
ent_p: bytes = _g_vpi_param_keys + ki * VPI_PARAM_KEY_LEN
|
||
if ent_p[0] == key_first and string.strcmp(ent_p, key_buf) == 0:
|
||
hit_p = ki
|
||
break
|
||
if hit_p >= 0: r = _g_vpi_param_strs + hit_p * VPI_PARAM_LEN
|
||
stdlib.free(key_buf)
|
||
return r
|
||
|
||
|
||
# _resolve_vpi_param_annot_seg_template - param-annotation segment for specialized generics.
|
||
# Locates the {sha1}.{base}[T].{method} template entry and substitutes T with the concrete arg.
|
||
def _resolve_vpi_param_annot_seg_template(pool: memhub.MemBuddy*, src_sha1: str, cls_name: str, method_name: str, seg_idx: int, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*:
|
||
"""Resolve a param-annotation segment for specialized class X[arg] by substituting T with arg; None on miss."""
|
||
if pool is None or src_sha1 is None or cls_name is None or method_name is None or _g_vpi_param_keys is None: return None
|
||
lbkt_t: str = string.strchr(cls_name, 91) # '[' = 91
|
||
if lbkt_t is None: return None
|
||
rbkt_t: str = string.strrchr(cls_name, 93) # ']' = 93
|
||
if rbkt_t is None or rbkt_t <= lbkt_t: return None
|
||
base_len_t: sizet = uint64(lbkt_t) - uint64(cls_name)
|
||
arg_len_t: sizet = uint64(rbkt_t) - uint64(lbkt_t) - 1
|
||
if base_len_t <= 0 or arg_len_t <= 0: return None
|
||
base_t: bytes = stdlib.malloc(base_len_t + 1)
|
||
if base_t is None: return None
|
||
arg_t: bytes = stdlib.malloc(arg_len_t + 1)
|
||
if arg_t is None:
|
||
stdlib.free(base_t)
|
||
return None
|
||
string.strncpy(base_t, cls_name, base_len_t)
|
||
base_t[base_len_t] = '\0'
|
||
string.strncpy(arg_t, lbkt_t + 1, arg_len_t)
|
||
arg_t[arg_len_t] = '\0'
|
||
sha1_len_t: sizet = string.strlen(src_sha1)
|
||
ty_t: mlirlite.LLVMType* = None
|
||
ki_t: int
|
||
for ki_t in range(_g_vpi_param_count):
|
||
gk_t: bytes = _g_vpi_param_keys + ki_t * VPI_PARAM_KEY_LEN
|
||
if string.strncmp(gk_t, src_sha1, sha1_len_t) != 0 or gk_t[sha1_len_t] != '.': continue
|
||
if string.strncmp(gk_t + sha1_len_t + 1, base_t, base_len_t) != 0: continue
|
||
if gk_t[sha1_len_t + 1 + base_len_t] != 91: continue
|
||
gklb_t: str = string.strchr(gk_t + sha1_len_t + 1 + base_len_t, 93)
|
||
if gklb_t is None: continue
|
||
tplen_t: sizet = uint64(gklb_t) - uint64(gk_t + sha1_len_t + 1 + base_len_t) - 1
|
||
# method name must match fully (gklb_t is followed by ']' and '.'; mangled, so unambiguous)
|
||
if tplen_t <= 0 or string.strcmp(gklb_t + 2, method_name) != 0: continue
|
||
annots_t: str = _g_vpi_param_strs + ki_t * VPI_PARAM_LEN
|
||
if annots_t is None or annots_t[0] == '\0': break
|
||
# locate segment (same scheme as _resolve_vpi_param_annot_seg: '@'-separated, seg 0 = self empty)
|
||
cur_t: sizet = 0
|
||
cur_seg_t: int = 0
|
||
while cur_seg_t < seg_idx and annots_t[cur_t] != '\0':
|
||
while annots_t[cur_t] != '\0' and annots_t[cur_t] != '@': cur_t += 1
|
||
if annots_t[cur_t] == '@': cur_t += 1
|
||
cur_seg_t += 1
|
||
seg_start_t: sizet = cur_t
|
||
seg_len_t: sizet = 0
|
||
while annots_t[seg_start_t + seg_len_t] != '\0' and annots_t[seg_start_t + seg_len_t] != '@': seg_len_t += 1
|
||
if seg_len_t == 0: break
|
||
seg_buf_t: bytes = stdlib.malloc(seg_len_t + 1)
|
||
if seg_buf_t is None: break
|
||
string.strncpy(seg_buf_t, annots_t + seg_start_t, seg_len_t)
|
||
seg_buf_t[seg_len_t] = '\0'
|
||
# Replace the type-param token (e.g. T) with the concrete arg (e.g. str / AST*):
|
||
# template annotations are cached verbatim, so without this "item: T" resolves to nothing.
|
||
tparam_t: bytes = stdlib.malloc(tplen_t + 1)
|
||
if tparam_t is not None:
|
||
string.strncpy(tparam_t, gk_t + sha1_len_t + 1 + base_len_t + 1, tplen_t)
|
||
tparam_t[tplen_t] = '\0'
|
||
subst_t: str = _subst_template_arg(seg_buf_t, tparam_t, arg_t)
|
||
if subst_t is not None:
|
||
# _subst_template_arg may return its input unchanged (tplen==0 or tplen>alen),
|
||
# so only free the old buffer when subst_t is a different pointer (avoid double free).
|
||
if uint64(subst_t) != uint64(seg_buf_t): stdlib.free(seg_buf_t)
|
||
seg_buf_t = subst_t
|
||
stdlib.free(tparam_t)
|
||
ty_t = _resolve_vpi_ret_annot_str(pool, seg_buf_t, i32_ty, i8_ptr_ty)
|
||
stdlib.free(seg_buf_t)
|
||
break
|
||
stdlib.free(base_t)
|
||
stdlib.free(arg_t)
|
||
return ty_t
|
||
|
||
|
||
# _repair_declare_params_from_text - complete declare params from the text cache.
|
||
# When the vpi tree param list is truncated, append missing params so declare matches define.
|
||
def _repair_declare_params_from_text(pool: memhub.MemBuddy*, func: mlirlite.Function*, src_sha1: str, cls_name: str, method_name: str, param_count: int, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*) -> int:
|
||
"""Complete declare params from the vpi text cache; returns the completed param count."""
|
||
if func is None or src_sha1 is None or method_name is None: return param_count
|
||
prm_annots: str = _lookup_vpi_param_annots(src_sha1, cls_name, method_name)
|
||
if prm_annots is None or prm_annots[0] == '\0': return param_count
|
||
# count text params ('@'-separated; segments = separators + 1, incl. first param)
|
||
pa_count: int = 1
|
||
pi2: sizet = 0
|
||
while prm_annots[pi2] != '\0':
|
||
if prm_annots[pi2] == '@': pa_count += 1
|
||
pi2 += 1
|
||
if pa_count <= param_count: return param_count
|
||
# append missing params: segments with index >= param_count are the missing ones
|
||
seg_idx: int = 0
|
||
seg_start2: sizet = 0
|
||
cur2: sizet = 0
|
||
while prm_annots[cur2] != '\0':
|
||
while prm_annots[cur2] != '\0' and prm_annots[cur2] != '@': cur2 += 1
|
||
seg_len2: sizet = cur2 - seg_start2
|
||
if prm_annots[cur2] == '@': cur2 += 1
|
||
if seg_idx >= param_count:
|
||
param_ty_r: mlirlite.LLVMType* = i8_ptr_ty
|
||
if seg_len2 > 0:
|
||
seg_buf_r: bytes = stdlib.malloc(seg_len2 + 1)
|
||
if seg_buf_r is not None:
|
||
string.strncpy(seg_buf_r, prm_annots + seg_start2, seg_len2)
|
||
seg_buf_r[seg_len2] = '\0'
|
||
rt: mlirlite.LLVMType* = _resolve_vpi_ret_annot_str(pool, seg_buf_r, i32_ty, i8_ptr_ty)
|
||
if rt is not None: param_ty_r = rt
|
||
stdlib.free(seg_buf_r)
|
||
mlirlite.add_param(pool, func, param_ty_r, "arg")
|
||
seg_start2 = cur2
|
||
seg_idx += 1
|
||
return pa_count
|
||
|
||
|
||
# _resolve_vpi_param_annot_seg - resolve a vpi param-annotation segment to LLVMType.
|
||
# Parses the seg_idx-th '@'-separated segment (0 = self) via _resolve_vpi_ret_annot_str.
|
||
def _resolve_vpi_param_annot_seg(pool: memhub.MemBuddy*, src_sha1: str, cls_name: str, method_name: str, seg_idx: int, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*:
|
||
"""Resolve a vpi param-annotation segment by index to LLVMType; empty segment or failure returns None."""
|
||
if pool is None or src_sha1 is None or method_name is None: return None
|
||
prm_annots_p: str = _lookup_vpi_param_annots(src_sha1, cls_name, method_name)
|
||
if prm_annots_p is None or prm_annots_p[0] == '\0': return None
|
||
cur_p: sizet = 0
|
||
cur_seg_p: int = 0
|
||
while cur_seg_p < seg_idx and prm_annots_p[cur_p] != '\0':
|
||
while prm_annots_p[cur_p] != '\0' and prm_annots_p[cur_p] != '@': cur_p += 1
|
||
if prm_annots_p[cur_p] == '@': cur_p += 1
|
||
cur_seg_p += 1
|
||
seg_start_p: sizet = cur_p
|
||
seg_len_p: sizet = 0
|
||
while prm_annots_p[seg_start_p + seg_len_p] != '\0' and prm_annots_p[seg_start_p + seg_len_p] != '@': seg_len_p += 1
|
||
if seg_len_p == 0: return None
|
||
seg_buf_p: bytes = stdlib.malloc(seg_len_p + 1)
|
||
if seg_buf_p is None: return None
|
||
string.strncpy(seg_buf_p, prm_annots_p + seg_start_p, seg_len_p)
|
||
seg_buf_p[seg_len_p] = '\0'
|
||
seg_ty_p: mlirlite.LLVMType* = _resolve_vpi_ret_annot_str(pool, seg_buf_p, i32_ty, i8_ptr_ty)
|
||
stdlib.free(seg_buf_p)
|
||
return seg_ty_p
|
||
|
||
|
||
# _grow_vpi_ret_cache - grow the return-annotation cache (capacity doubles).
|
||
# Doubles via stdlib.realloc (cap 1M) so entries are not silently dropped when full.
|
||
def _grow_vpi_ret_cache() -> int:
|
||
"""Grow the return-annotation cache to double capacity; returns 1=success / 0=failure."""
|
||
global _g_vpi_ret_keys, _g_vpi_ret_strs, _g_vpi_ret_cap
|
||
if _g_vpi_ret_keys is None or _g_vpi_ret_cap <= 0: return 0
|
||
new_cap: int = _g_vpi_ret_cap * 2
|
||
if new_cap > 1048576: return 0
|
||
new_keys: bytes = stdlib.realloc(_g_vpi_ret_keys, new_cap * VPI_KEY_LEN)
|
||
if new_keys is None: return 0
|
||
new_strs: bytes = stdlib.realloc(_g_vpi_ret_strs, new_cap * VPI_RET_LEN)
|
||
if new_strs is None: return 0
|
||
_g_vpi_ret_keys = new_keys
|
||
_g_vpi_ret_strs = new_strs
|
||
_g_vpi_ret_cap = new_cap
|
||
# grow the index with the table and rebuild it (slots = capacity * 2, power of 2)
|
||
_vpi_rebuild_ret_idx(new_cap * 2)
|
||
return 1
|
||
|
||
|
||
# _ensure_vpi_ret_annot_loaded - lazily load a module's vpi stub return-annotation cache.
|
||
# On cache miss reads {temp_dir}/{sha1[0:2]}/{sha1}.vpi directly; idempotent.
|
||
def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int:
|
||
"""Lazily load the module's vpi stub return-annotation cache; returns 1=ready / 0=failure."""
|
||
if src_sha1 is None or temp_dir is None: return 0
|
||
sha1_len: sizet = string.strlen(src_sha1)
|
||
if sha1_len < 2: return 0
|
||
# module already cached -> skip (idempotent)
|
||
if _g_vpi_ret_keys is not None and _g_vpi_ret_count > 0:
|
||
pre_i: int
|
||
# Fast path: equal prefixes share the first two bytes (the module subdir name),
|
||
# so checking them skips strncmp for most entries.
|
||
s0: int = src_sha1[0]
|
||
s1: int = src_sha1[1]
|
||
for pre_i in range(_g_vpi_ret_count):
|
||
pre_key: bytes = _g_vpi_ret_keys + pre_i * VPI_KEY_LEN
|
||
if pre_key[0] == s0 and pre_key[1] == s1 and string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.': return 1
|
||
# build path {temp_dir}/{sha1[0:2]}/{sha1}.vpi (char by char, avoiding %c formatting)
|
||
td_len: sizet = string.strlen(temp_dir)
|
||
path_len: sizet = td_len + sha1_len + 12
|
||
vpi_path: bytes = stdlib.malloc(path_len)
|
||
if vpi_path is None: return 0
|
||
string.strcpy(vpi_path, temp_dir)
|
||
vpi_path[td_len] = '/'
|
||
vpi_path[td_len + 1] = src_sha1[0]
|
||
vpi_path[td_len + 2] = src_sha1[1]
|
||
vpi_path[td_len + 3] = '/'
|
||
string.strcpy(vpi_path + td_len + 4, src_sha1)
|
||
string.strcpy(vpi_path + td_len + 4 + sha1_len, ".vpi")
|
||
f: fileio.File* = fileio.File(vpi_path, fileio.MODE.R)
|
||
if f.closed:
|
||
stdlib.free(vpi_path)
|
||
return 0
|
||
vpi_buf: bytes = stdlib.malloc(VPI_READ_BUF_SIZE)
|
||
if vpi_buf is None:
|
||
f.close()
|
||
stdlib.free(vpi_path)
|
||
return 0
|
||
bytes_read: LONG = f.read_all(vpi_buf, VPI_READ_BUF_SIZE)
|
||
f.close()
|
||
if bytes_read > 0:
|
||
if bytes_read < VPI_READ_BUF_SIZE: vpi_buf[bytes_read] = 0
|
||
_cache_vpi_return_annots_text(vpi_buf, src_sha1)
|
||
stdlib.free(vpi_buf)
|
||
stdlib.free(vpi_path)
|
||
return 1
|
||
|
||
|
||
# _vpi_param_annot_seg_is - equality check for a param-annotation segment (for the "str/bytes is not a generic pointer" rule).
|
||
def _vpi_param_annot_seg_is(src_sha1: str, cls_name: str, method_name: str, seg_idx: int, want: str) -> int:
|
||
"""Check whether the definition-side param-annotation segment equals want exactly; 1=yes / 0=no."""
|
||
if want is None or seg_idx < 0: return 0
|
||
prm: str = _lookup_vpi_param_annots(src_sha1, cls_name, method_name)
|
||
if prm is None or prm[0] == '\0': return 0
|
||
cur: sizet = 0
|
||
# locate segment start: skip seg_idx '@'-separated segments
|
||
for seg in range(seg_idx):
|
||
while prm[cur] != '\0' and prm[cur] != '@': cur += 1
|
||
if prm[cur] == '\0': return 0
|
||
cur += 1
|
||
slen: sizet = 0
|
||
while prm[cur + slen] != '\0' and prm[cur + slen] != '@': slen += 1
|
||
wlen: sizet = string.strlen(want)
|
||
if slen != wlen: return 0
|
||
i: sizet
|
||
for i in range(slen):
|
||
if prm[cur + i] != want[i]: return 0
|
||
return 1
|
||
|
||
|
||
# _ensure_vpi_param_annot_loaded - lazily load a module's vpi stub param-annotation cache.
|
||
# Mirror of the return variant; reads the .vpi file on miss, idempotent.
|
||
def _ensure_vpi_param_annot_loaded(src_sha1: str, temp_dir: str) -> int:
|
||
"""Lazily load the module's vpi stub param-annotation cache; returns 1=ready / 0=failure."""
|
||
if src_sha1 is None or temp_dir is None: return 0
|
||
sha1_len: sizet = string.strlen(src_sha1)
|
||
if sha1_len < 2: return 0
|
||
# module already has param cache entries -> skip (idempotent)
|
||
if _g_vpi_param_keys is not None and _g_vpi_param_count > 0:
|
||
pre_i: int
|
||
# Fast path: equal prefixes share the first two bytes (the module subdir name),
|
||
# so checking them skips strncmp for most entries.
|
||
s0: int = src_sha1[0]
|
||
s1: int = src_sha1[1]
|
||
for pre_i in range(_g_vpi_param_count):
|
||
pre_key: bytes = _g_vpi_param_keys + pre_i * VPI_PARAM_KEY_LEN
|
||
if pre_key[0] == s0 and pre_key[1] == s1 and string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.': return 1
|
||
# build path {temp_dir}/{sha1[0:2]}/{sha1}.vpi (char by char, avoiding %c formatting)
|
||
td_len: sizet = string.strlen(temp_dir)
|
||
path_len: sizet = td_len + sha1_len + 12
|
||
vpi_path: bytes = stdlib.malloc(path_len)
|
||
if vpi_path is None: return 0
|
||
string.strcpy(vpi_path, temp_dir)
|
||
vpi_path[td_len] = '/'
|
||
vpi_path[td_len + 1] = src_sha1[0]
|
||
vpi_path[td_len + 2] = src_sha1[1]
|
||
vpi_path[td_len + 3] = '/'
|
||
string.strcpy(vpi_path + td_len + 4, src_sha1)
|
||
string.strcpy(vpi_path + td_len + 4 + sha1_len, ".vpi")
|
||
f: fileio.File* = fileio.File(vpi_path, fileio.MODE.R)
|
||
if f.closed:
|
||
stdlib.free(vpi_path)
|
||
return 0
|
||
vpi_buf: bytes = stdlib.malloc(VPI_READ_BUF_SIZE)
|
||
if vpi_buf is None:
|
||
f.close()
|
||
stdlib.free(vpi_path)
|
||
return 0
|
||
bytes_read: LONG = f.read_all(vpi_buf, VPI_READ_BUF_SIZE)
|
||
f.close()
|
||
if bytes_read > 0:
|
||
if bytes_read < VPI_READ_BUF_SIZE: vpi_buf[bytes_read] = 0
|
||
_cache_vpi_param_annots_text(vpi_buf, src_sha1)
|
||
stdlib.free(vpi_buf)
|
||
stdlib.free(vpi_path)
|
||
return 1
|
||
|
||
|
||
# _subst_template_arg - replace standalone template-parameter tokens in an annotation string.
|
||
# Replaces only boundary-delimited tokens (not inside sizet etc.); returns a new stdlib string.
|
||
def _subst_template_arg(annot: str, tparam: str, targ: str) -> str:
|
||
"""Replace standalone tparam tokens in annot with targ."""
|
||
if annot is None or tparam is None or targ is None: return None
|
||
alen: sizet = string.strlen(annot)
|
||
tplen: sizet = string.strlen(tparam)
|
||
tglen: sizet = string.strlen(targ)
|
||
if tplen == 0 or tplen > alen: return annot
|
||
# Allocate the worst-case upper bound: each replacement consumes tplen and writes tglen,
|
||
# so worst case is alen + (alen/tplen)*tglen + 1 (avoids writing past the heap).
|
||
max_sub: sizet = alen / tplen
|
||
if max_sub == 0: max_sub = 1
|
||
out_size: sizet = alen + max_sub * tglen + 1
|
||
out: bytes = stdlib.malloc(out_size)
|
||
if out is None: return None
|
||
opos: sizet = 0
|
||
p: sizet = 0
|
||
while p < alen:
|
||
# guard: fail fast (return None) if opos would exceed the allocation (never overrun)
|
||
if opos >= out_size:
|
||
stdlib.free(out)
|
||
return None
|
||
matched: int = 0
|
||
if annot[p] == tparam[0] and p + tplen <= alen:
|
||
if string.strncmp(annot + p, tparam, tplen) == 0:
|
||
prev_ch: int = 0
|
||
next_ch: int = 0
|
||
if p > 0: prev_ch = annot[p - 1]
|
||
if p + tplen < alen: next_ch = annot[p + tplen]
|
||
prev_ok: int = 1
|
||
if (prev_ch >= 'a' and prev_ch <= 'z') or (prev_ch >= 'A' and prev_ch <= 'Z') or (prev_ch >= '0' and prev_ch <= '9') or prev_ch == '_': prev_ok = 0
|
||
next_ok: int = 1
|
||
if (next_ch >= 'a' and next_ch <= 'z') or (next_ch >= 'A' and next_ch <= 'Z') or (next_ch >= '0' and next_ch <= '9') or next_ch == '_': next_ok = 0
|
||
if prev_ok != 0 and next_ok != 0:
|
||
string.strncpy(out + opos, targ, tglen)
|
||
opos += tglen
|
||
p += tplen
|
||
matched = 1
|
||
if matched == 0:
|
||
out[opos] = annot[p]
|
||
opos += 1
|
||
p += 1
|
||
out[opos] = '\0'
|
||
return out
|
||
|
||
|
||
# _lookup_vpi_ret_annot - look up a method's cached return-annotation string.
|
||
def _lookup_vpi_ret_annot(src_sha1: str, cls_name: str, method_name: str) -> str:
|
||
"""Look up a method's cached return-annotation string; None if not found."""
|
||
if _g_vpi_ret_keys is None or _g_vpi_ret_count == 0: return None
|
||
if src_sha1 is None or method_name is None: return None
|
||
sha1_len: sizet = string.strlen(src_sha1)
|
||
cl_len: sizet = 0
|
||
if cls_name is not None: cl_len = string.strlen(cls_name)
|
||
me_len: sizet = string.strlen(method_name)
|
||
key_len: sizet = sha1_len + 1 + cl_len + 1 + me_len
|
||
key_buf: bytes = stdlib.malloc(key_len + 1)
|
||
if key_buf is None: return None
|
||
if cls_name is not None: viperlib.snprintf(key_buf, key_len + 1, "%s.%s.%s", src_sha1, cls_name, method_name)
|
||
else: viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, method_name)
|
||
i: int
|
||
# Index fast path: when trusted (Built==Count) one probe returns the hit slot;
|
||
# a miss means truly absent, so skip the exact linear scan. Otherwise fall back.
|
||
hit_r: int = -1
|
||
if _g_vpi_ret_idx_built == _g_vpi_ret_count and _g_vpi_ret_idx is not None and _g_vpi_ret_idx_cap > 0:
|
||
hit_r = _vpi_idx_find(_g_vpi_ret_keys, VPI_KEY_LEN, _g_vpi_ret_idx, _g_vpi_ret_idx_cap, key_buf)
|
||
else:
|
||
# Linear-scan fallback: equal keys share the first byte; skip candidates whose first byte differs.
|
||
key_first: int = key_buf[0]
|
||
for i in range(_g_vpi_ret_count):
|
||
ent_r: bytes = _g_vpi_ret_keys + i * VPI_KEY_LEN
|
||
if ent_r[0] == key_first and string.strcmp(ent_r, key_buf) == 0:
|
||
hit_r = i
|
||
break
|
||
if hit_r >= 0:
|
||
stdlib.free(key_buf)
|
||
return _g_vpi_ret_strs + hit_r * VPI_RET_LEN
|
||
# Generic specialization fallback: list[AST*].get -> list[T].get template cache.
|
||
# Substitutes the type-param name (e.g. T) with the concrete arg; no registration needed.
|
||
if cls_name is not None:
|
||
lbkt_v: str = string.strchr(cls_name, '[')
|
||
if lbkt_v is not None:
|
||
rbkt_v: str = string.strrchr(cls_name, ']')
|
||
if rbkt_v is not None and rbkt_v > lbkt_v:
|
||
base_len_v: sizet = sizet(uint64(lbkt_v) - uint64(cls_name))
|
||
param_len_v: sizet = sizet(uint64(rbkt_v) - uint64(lbkt_v) - 1)
|
||
if base_len_v > 0 and param_len_v > 0:
|
||
base_v: str = stdlib.malloc(base_len_v + 1)
|
||
param_v: str = stdlib.malloc(param_len_v + 1)
|
||
if base_v is not None and param_v is not None:
|
||
string.strncpy(base_v, cls_name, base_len_v)
|
||
base_v[base_len_v] = '\0'
|
||
string.strncpy(param_v, lbkt_v + 1, param_len_v)
|
||
param_v[param_len_v] = '\0'
|
||
# scan cache for {sha1}.{base}[xxx].{method}
|
||
gi: int
|
||
for gi in range(_g_vpi_ret_count):
|
||
gk: bytes = _g_vpi_ret_keys + gi * VPI_KEY_LEN
|
||
if string.strncmp(gk, src_sha1, sha1_len) == 0 and gk[sha1_len] == '.' and string.strncmp(gk + sha1_len + 1, base_v, base_len_v) == 0 and gk[sha1_len + 1 + base_len_v] == '[':
|
||
gklb: str = string.strchr(gk + sha1_len + 1 + base_len_v, ']')
|
||
if gklb is not None:
|
||
tparam_len_v: sizet = sizet(uint64(gklb) - uint64(gk + sha1_len + 1 + base_len_v) - 1)
|
||
meth_start_v: str = gklb + 2 # skip ']' and '.'
|
||
if tparam_len_v > 0 and string.strcmp(meth_start_v, method_name) == 0:
|
||
tparam_v: str = stdlib.malloc(tparam_len_v + 1)
|
||
if tparam_v is not None:
|
||
string.strncpy(tparam_v, gk + sha1_len + 1 + base_len_v + 1, tparam_len_v)
|
||
tparam_v[tparam_len_v] = '\0'
|
||
new_annot_v: str = _subst_template_arg(_g_vpi_ret_strs + gi * VPI_RET_LEN, tparam_v, param_v)
|
||
stdlib.free(tparam_v)
|
||
stdlib.free(base_v)
|
||
stdlib.free(param_v)
|
||
stdlib.free(key_buf)
|
||
return new_annot_v
|
||
stdlib.free(base_v)
|
||
stdlib.free(param_v)
|
||
stdlib.free(key_buf)
|
||
return None
|
||
|
||
|
||
# _resolve_single_vpi_type_str - resolve a single type segment to LLVMType.
|
||
# Pure string parsing, no AST nodes.
|
||
def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.LLVMType*:
|
||
"""Resolve a single type segment to LLVMType; None if unrecognized."""
|
||
if seg is None or pool is None: return None
|
||
# Canonical pointer form is `T*` (also rawptr[T] / t.CPtr[T]); rewrite trailing `*`
|
||
# into rawptr[...] so specialized args like "{sha}.AST*" resolve instead of silently returning None.
|
||
seg_len_ps: sizet = string.strlen(seg)
|
||
star_end_ps: sizet = seg_len_ps
|
||
while star_end_ps > 0 and seg[star_end_ps - 1] == ' ': star_end_ps -= 1
|
||
star_cnt_ps: sizet = 0
|
||
star_base_end_ps: sizet = star_end_ps
|
||
while star_base_end_ps > 0 and seg[star_base_end_ps - 1] == '*':
|
||
star_cnt_ps += 1
|
||
star_base_end_ps -= 1
|
||
if star_cnt_ps > 0 and star_base_end_ps > 0:
|
||
# base segment length = star_base_end_ps; each rawptr[ + ] layer is 8 chars
|
||
star_buf_len_ps: sizet = star_base_end_ps + 8 * star_cnt_ps + 1
|
||
star_buf_ps: bytes = stdlib.malloc(star_buf_len_ps)
|
||
if star_buf_ps is None: return None
|
||
star_buf_ps[0] = '\0'
|
||
star_k_ps: sizet = 0
|
||
while star_k_ps < star_cnt_ps:
|
||
string.strcat(star_buf_ps, "rawptr[")
|
||
star_k_ps += 1
|
||
star_off_ps: sizet = string.strlen(star_buf_ps)
|
||
string.strncpy(star_buf_ps + star_off_ps, seg, star_base_end_ps)
|
||
star_buf_ps[star_off_ps + star_base_end_ps] = '\0'
|
||
for star_k_ps in range(star_cnt_ps):
|
||
string.strcat(star_buf_ps, "]")
|
||
star_ty_ps: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, star_buf_ps)
|
||
stdlib.free(star_buf_ps)
|
||
return star_ty_ps
|
||
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
|
||
# built-in aliases
|
||
if string.strcmp(seg, "str") == 0 or string.strcmp(seg, "bytes") == 0: return i8_ptr_ty
|
||
if string.strcmp(seg, "int") == 0: return mlirlite.Int32(pool)
|
||
if string.strcmp(seg, "bool") == 0: return mlirlite.Int1(pool)
|
||
# LLVM type strings: vpi stores "i32" for unannotated methods
|
||
# (see _cache_vpi_return_annots_text); parse it so generic template calls do not fail fast.
|
||
if string.strcmp(seg, "i32") == 0: return mlirlite.Int32(pool)
|
||
if string.strcmp(seg, "i64") == 0: return mlirlite.Int64(pool)
|
||
if string.strcmp(seg, "i16") == 0: return mlirlite.Int16(pool)
|
||
if string.strcmp(seg, "i8") == 0: return i8_ty
|
||
if string.strcmp(seg, "i1") == 0: return mlirlite.Int1(pool)
|
||
if string.strcmp(seg, "f32") == 0: return mlirlite.Float(pool)
|
||
if string.strcmp(seg, "f64") == 0: return mlirlite.Double(pool)
|
||
if string.strcmp(seg, "void") == 0: return mlirlite.Void(pool)
|
||
# vpi caches `-> None` verbatim as "None"; map None to Void so cross-module
|
||
# `-> None` methods do not fail fast with an unresolvable return type.
|
||
if string.strcmp(seg, "None") == 0: return mlirlite.Void(pool)
|
||
# rawptr[T] built-in raw pointer (equivalent to t.CPtr[T] -> Ptr(T)); vpi keeps the
|
||
# Subscript form verbatim. Same as TypeGenResolve's rawptr branch: Ptr(resolve(T)).
|
||
if string.strncmp(seg, "rawptr[", 7) == 0:
|
||
seg_len_rp: sizet = string.strlen(seg)
|
||
if seg_len_rp > 8 and seg[seg_len_rp - 1] == ']':
|
||
inner_len_rp: sizet = seg_len_rp - 8
|
||
inner_buf_rp: bytes = stdlib.malloc(inner_len_rp + 1)
|
||
if inner_buf_rp is not None:
|
||
string.strncpy(inner_buf_rp, seg + 7, inner_len_rp)
|
||
inner_buf_rp[inner_len_rp] = '\0'
|
||
inner_ty_rp: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, inner_buf_rp)
|
||
stdlib.free(inner_buf_rp)
|
||
if inner_ty_rp is not None: return mlirlite.Ptr(pool, inner_ty_rp)
|
||
return None
|
||
# t.CPtr[T] / c.CPtr[T] subscript pointers (nesting allowed, e.g. t.CPtr[t.CPtr]
|
||
# -> Ptr(Ptr(i8))); same as TypeGenResolve's Subscript(CPtr) branch: Ptr(resolve(T)).
|
||
cptr_sub_pfx: sizet = 0
|
||
if string.strncmp(seg, "t.CPtr[", 7) == 0 or string.strncmp(seg, "c.CPtr[", 7) == 0: cptr_sub_pfx = 7
|
||
if cptr_sub_pfx > 0:
|
||
seg_len_cp: sizet = string.strlen(seg)
|
||
if seg_len_cp > cptr_sub_pfx + 1 and seg[seg_len_cp - 1] == ']':
|
||
inner_len_cp: sizet = seg_len_cp - cptr_sub_pfx - 1
|
||
inner_buf_cp: bytes = stdlib.malloc(inner_len_cp + 1)
|
||
if inner_buf_cp is not None:
|
||
string.strncpy(inner_buf_cp, seg + cptr_sub_pfx, inner_len_cp)
|
||
inner_buf_cp[inner_len_cp] = '\0'
|
||
inner_ty_cp: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, inner_buf_cp)
|
||
stdlib.free(inner_buf_cp)
|
||
if inner_ty_cp is not None: return mlirlite.Ptr(pool, inner_ty_cp)
|
||
return None
|
||
# t.XXX / c.XXX built-in type mapping
|
||
if string.strncmp(seg, "t.", 2) == 0 or string.strncmp(seg, "c.", 2) == 0:
|
||
attr_nm: str = seg + 2
|
||
# t.CExport is an export marker, not a real type; FuncGenDef maps it to Void,
|
||
# so normalize it here too or cross-module return types fail fast.
|
||
if string.strcmp(attr_nm, "CExport") == 0: return mlirlite.Void(pool)
|
||
# [stage 2] unified: CTypedef dynamic table first (library typedefs analyzed normally),
|
||
# on miss fall back to map_t_type (t/c library internal special-casing).
|
||
mapped_ty: mlirlite.LLVMType* = GenApi.resolve_typedef_or_map(pool, attr_nm, None, None)
|
||
if mapped_ty is not None: return mapped_ty
|
||
return None
|
||
# module-qualified class name "memhub.MemBuddy" -> use last segment to look up struct
|
||
last_dot: str = string.strrchr(seg, '.')
|
||
if last_dot is not None:
|
||
cls_nm2: str = last_dot + 1
|
||
mod_struct_ty: mlirlite.LLVMType* = GenApi.get_struct_type(cls_nm2)
|
||
if mod_struct_ty is not None: return mod_struct_ty
|
||
# Enum fallback: t.CEnum registers in EnumTable, not StructTable, so get_struct_type
|
||
# misses; fall back to the enum base type (usually i32) instead of returning None.
|
||
mod_enum_ty: mlirlite.LLVMType* = GenApi.get_enum_base_type(cls_nm2)
|
||
if mod_enum_ty is not None: return mod_enum_ty
|
||
return None
|
||
# bare class name -> global struct registry
|
||
bare_struct_ty: mlirlite.LLVMType* = GenApi.get_struct_type(seg)
|
||
if bare_struct_ty is not None: return bare_struct_ty
|
||
# bare enum name -> EnumTable fallback (as above: enums are not in StructTable)
|
||
bare_enum_ty: mlirlite.LLVMType* = GenApi.get_enum_base_type(seg)
|
||
if bare_enum_ty is not None: return bare_enum_ty
|
||
# bare typedef name (e.g. VOIDPTR): CTypedef dynamic table first, on miss fall back to map_t_type
|
||
mapped_bare: mlirlite.LLVMType* = GenApi.resolve_typedef_or_map(pool, seg, None, None)
|
||
if mapped_bare is not None: return mapped_bare
|
||
return None
|
||
|
||
|
||
# _resolve_vpi_ret_annot_str - resolve a vpi return-annotation string to LLVMType.
|
||
# Pure string parsing (no AST nodes) to avoid corrupted kind() from cross-module AST reuse.
|
||
def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*:
|
||
"""Resolve a vpi return-annotation string directly to LLVMType without building AST nodes."""
|
||
if ret_annot is None or pool is None: return None
|
||
|
||
# tuple[T1, T2, ...] -> anonymous struct {T1, T2, ...} (multi-return packing),
|
||
# matching TypeGenMixin._resolve_generic_subscript_ty's tuple branch.
|
||
if string.strncmp(ret_annot, "tuple[", 6) == 0:
|
||
inner_len_t: sizet = 0
|
||
while ret_annot[6 + inner_len_t] != ']' and ret_annot[6 + inner_len_t] != '\0': inner_len_t += 1
|
||
if ret_annot[6 + inner_len_t] != ']' or inner_len_t == 0: return None
|
||
# The tuple return-type field list is mlirlite.ParamNode (new_param_node /
|
||
# paramnode_set_next / Struct all take rawptr[ParamNode]), not Function*.
|
||
tup_head_t: mlirlite.ParamNode* = None
|
||
tup_tail_t: mlirlite.ParamNode* = None
|
||
tup_count_t: int = 0
|
||
p_t: sizet = 0
|
||
while p_t < inner_len_t:
|
||
# skip leading spaces/commas
|
||
while p_t < inner_len_t and (ret_annot[6 + p_t] == ' ' or ret_annot[6 + p_t] == ','): p_t += 1
|
||
seg_s_t: sizet = p_t
|
||
while p_t < inner_len_t and ret_annot[6 + p_t] != ',': p_t += 1
|
||
seg_e_t: sizet = p_t
|
||
while seg_e_t > seg_s_t and ret_annot[6 + seg_e_t - 1] == ' ': seg_e_t -= 1
|
||
if seg_e_t > seg_s_t:
|
||
seg_len_t: sizet = seg_e_t - seg_s_t
|
||
seg_buf_t: bytes = stdlib.malloc(seg_len_t + 1)
|
||
if seg_buf_t is None: return None
|
||
string.strncpy(seg_buf_t, ret_annot + 6 + seg_s_t, seg_len_t)
|
||
seg_buf_t[seg_len_t] = '\0'
|
||
seg_ty_t: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg_buf_t)
|
||
stdlib.free(seg_buf_t)
|
||
if seg_ty_t is None: return None
|
||
tup_node_t: mlirlite.ParamNode* = mlirlite.new_param_node(pool, seg_ty_t)
|
||
if tup_node_t is None: return None
|
||
if tup_head_t is None: tup_head_t = tup_node_t
|
||
else: mlirlite.paramnode_set_next(tup_tail_t, tup_node_t)
|
||
tup_tail_t = tup_node_t
|
||
tup_count_t += 1
|
||
if tup_head_t is None: return None
|
||
return mlirlite.Struct(pool, tup_head_t, tup_count_t, None)
|
||
|
||
# Pure marker annotations (t.State / t.CExtern / t.CExport alone, optional t./c. prefix)
|
||
# -> void: decorator markers, not real return types, matching _inject_module_declare.
|
||
marker_len: sizet = string.strlen(ret_annot)
|
||
mk_s: sizet = 0
|
||
while mk_s < marker_len and ret_annot[mk_s] == ' ': mk_s += 1
|
||
mk_e: sizet = marker_len
|
||
while mk_e > mk_s and ret_annot[mk_e - 1] == ' ': mk_e -= 1
|
||
mk_off_m: sizet = 0
|
||
if mk_e - mk_s >= 2 and (ret_annot[mk_s] == 't' or ret_annot[mk_s] == 'c') and ret_annot[mk_s + 1] == '.': mk_off_m = 2
|
||
mk_nlen: sizet = mk_e - mk_s - mk_off_m
|
||
if (mk_nlen == 5 and string.strncmp(ret_annot + mk_s + mk_off_m, "State", 5) == 0) or (mk_nlen == 7 and string.strncmp(ret_annot + mk_s + mk_off_m, "CExtern", 7) == 0) or (mk_nlen == 7 and string.strncmp(ret_annot + mk_s + mk_off_m, "CExport", 7) == 0):
|
||
return mlirlite.Void(pool)
|
||
|
||
has_cptr: int = 0
|
||
has_cvoid: int = 0
|
||
first_ty: mlirlite.LLVMType* = None
|
||
# Attributes of the segment that produced first_ty (for the has_cptr branch, N12):
|
||
# first_qual=qualified name; first_wl=whitelisted bare scalar; first_td=resolved bare typedef.
|
||
first_qual: int = 0
|
||
first_wl: int = 0
|
||
first_td: int = 0
|
||
# Union-merge params for CPtr subscript segments (t.CPtr[T]/c.CPtr[T], nesting allowed):
|
||
# cptr_sub_lvl = outer Ptr level N, cptr_sub_ty = the segment's own type. -> Ptr^N(base).
|
||
cptr_sub_lvl: int = 0
|
||
cptr_sub_ty: mlirlite.LLVMType* = None
|
||
|
||
buf_len: sizet = string.strlen(ret_annot)
|
||
pos: sizet = 0
|
||
while pos <= buf_len:
|
||
# skip leading spaces
|
||
while pos < buf_len and ret_annot[pos] == ' ': pos += 1
|
||
seg_start: sizet = pos
|
||
# Split `|` only at the top level: `|` inside `[...]` belongs to a generic arg,
|
||
# so naive splitting would truncate `list[ast.AST*]`; depth-count on `[`/`]`.
|
||
depth_ra: int = 0
|
||
while pos < buf_len:
|
||
ch_ra: char = ret_annot[pos]
|
||
if ch_ra == '[': depth_ra += 1
|
||
elif ch_ra == ']':
|
||
if depth_ra > 0: depth_ra -= 1
|
||
elif ch_ra == '|':
|
||
if depth_ra == 0: break
|
||
pos += 1
|
||
seg_end: sizet = pos
|
||
# trim trailing spaces
|
||
while seg_end > seg_start and ret_annot[seg_end - 1] == ' ': seg_end -= 1
|
||
if seg_end > seg_start:
|
||
seg_len2: sizet = seg_end - seg_start
|
||
seg_buf: bytes = stdlib.malloc(seg_len2 + 1)
|
||
if seg_buf is None: break
|
||
string.strncpy(seg_buf, ret_annot + seg_start, seg_len2)
|
||
seg_buf[seg_len2] = '\0'
|
||
# CPtr subscript segments must be recognized first: the definition side treats
|
||
# `X*[T]` as "CPtr side contributes N pointer levels, the other side the base".
|
||
if string.strncmp(seg_buf, "t.CPtr[", 7) == 0 or string.strncmp(seg_buf, "c.CPtr[", 7) == 0:
|
||
seg_ty_cs: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg_buf)
|
||
if seg_ty_cs is not None:
|
||
# Count outer Ptr nesting (same as the definition side). Do not update the
|
||
# loop variable inside a case body: match sinks into a subject snapshot.
|
||
lvl_cs: int = 0
|
||
cur_cs: mlirlite.LLVMType* = seg_ty_cs
|
||
while cur_cs is not None:
|
||
nxt_cs: mlirlite.LLVMType* = None
|
||
match cur_cs:
|
||
case mlirlite.LLVMType.Ptr(pointee_cs):
|
||
lvl_cs += 1
|
||
nxt_cs = pointee_cs
|
||
case _:
|
||
break
|
||
cur_cs = nxt_cs
|
||
# take the max level across subscript segments (equivalent to level accumulation)
|
||
if lvl_cs > cptr_sub_lvl:
|
||
cptr_sub_lvl = lvl_cs
|
||
cptr_sub_ty = seg_ty_cs
|
||
elif string.strcmp(seg_buf, "t.CPtr") == 0 or string.strcmp(seg_buf, "CPtr") == 0: has_cptr = 1
|
||
elif string.strcmp(seg_buf, "void") == 0 or string.strcmp(seg_buf, "CVoid") == 0 or string.strcmp(seg_buf, "void") == 0:
|
||
# Bare void is an alias of t.CVoid and must be handled the same, else the
|
||
# else branch sets first_td and produces the illegal Ptr(Void) (void* = i8*).
|
||
has_cvoid = 1
|
||
else:
|
||
seg_ty: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg_buf)
|
||
if seg_ty is not None and first_ty is None:
|
||
first_ty = seg_ty
|
||
# Record the segment attribute (N12): the definition side keeps a bare pointee
|
||
# only for registered structs, generic params or whitelisted scalars; else i8*.
|
||
if string.strchr(seg_buf, 46) is not None: first_qual = 1
|
||
if string.strcmp(seg_buf, "int") == 0 or string.strcmp(seg_buf, "char") == 0 or string.strcmp(seg_buf, "str") == 0 or string.strcmp(seg_buf, "bytes") == 0 or string.strcmp(seg_buf, "bool") == 0 or string.strcmp(seg_buf, "float") == 0 or string.strcmp(seg_buf, "double") == 0:
|
||
first_wl = 1
|
||
# Preserve bare typedef aliases (N13): use GenApi.resolve_typedef_or_map (same API
|
||
# as the definition side); set only for non-pointer results (pointer aliases -> i8*).
|
||
if first_qual == 0 and first_wl == 0:
|
||
if first_ty.kind() != mlirlite.LLVMType.Ptr:
|
||
if GenApi.resolve_typedef_or_map(pool, seg_buf, None, None) is not None: first_td = 1
|
||
stdlib.free(seg_buf)
|
||
pos += 1 # skip '|'
|
||
|
||
if has_cptr != 0:
|
||
if has_cvoid != 0: return i8_ptr_ty
|
||
if first_ty is not None:
|
||
# The result itself is void (e.g. an opaque typedef resolving to void): CPtr merge
|
||
# gives void* = i8*, matching has_cvoid (otherwise it would build illegal Ptr(void)).
|
||
match first_ty:
|
||
case mlirlite.LLVMType.Void():
|
||
return i8_ptr_ty
|
||
case _:
|
||
pass
|
||
# Mirror the definition-side rules (N12/N13): qualified name / whitelisted scalar /
|
||
# registered struct / resolvable non-pointer typedef keeps pointee; else collapse to i8*.
|
||
if first_qual != 0 or first_wl != 0 or first_td != 0: return mlirlite.Ptr(pool, first_ty)
|
||
if first_ty.kind() == mlirlite.LLVMType.Struct: return mlirlite.Ptr(pool, first_ty)
|
||
if first_ty.kind() == mlirlite.LLVMType.Ptr:
|
||
# `| t.CPtr` always appends exactly one pointer level (same as the definition
|
||
# side's BinOp branch); no struct-absorption, so `T*` stays `T*` (never AST*).
|
||
return mlirlite.Ptr(pool, first_ty)
|
||
return i8_ptr_ty
|
||
return i8_ptr_ty
|
||
# CPtr subscript union merge: with another side's base type first_ty -> Ptr^N(first_ty);
|
||
# with only the subscript segment -> the segment's own type. No struct absorption here.
|
||
if cptr_sub_lvl > 0:
|
||
if first_ty is None: return cptr_sub_ty
|
||
res_ty_cs: mlirlite.LLVMType* = first_ty
|
||
for wrap_i_cs in range(cptr_sub_lvl):
|
||
res_ty_cs = mlirlite.Ptr(pool, res_ty_cs)
|
||
return res_ty_cs
|
||
if has_cvoid != 0:
|
||
# t.CVoid | t.State: State is a decorator marker (ignored), t.CVoid -> void.
|
||
# Without this branch first_ty is None and cross-module return types fail fast.
|
||
return mlirlite.Void(pool)
|
||
if first_ty is not None: return first_ty
|
||
return None
|
||
|
||
|
||
# VpiReader - .vpi AST parser. Reads function signatures and CTypedef definitions from
|
||
# .vpi stub files and injects declares, filling in missing cross-module declarations.
|
||
|
||
# .vpi read buffer size (256KB, matching PYI_BUF_SIZE)
|
||
VPI_READ_BUF_SIZE: sizet = 262144
|
||
|
||
# SHA1 hex char count (16, filename hex only, no trailing NUL). Named uniquely vs Phase1's
|
||
# SHA1_LEN (17 = 16 chars + NUL) to avoid cross-module CDefine shadowing.
|
||
SHA1_HEX_LEN: sizet = 16
|
||
|
||
|
||
# _extract_sha1_from_path - extract SHA1 from a .vpi path. Takes the first 16 chars of the
|
||
# filename after the last separator; returns a malloc'd string (caller frees) or None.
|
||
def _extract_sha1_from_path(vpi_path: str) -> str:
|
||
"""Extract the SHA1 from a .vpi path (first 16 chars of the filename, malloc'd)."""
|
||
if vpi_path is None: return None
|
||
# find the last path separator / or \
|
||
total_len: sizet = string.strlen(vpi_path)
|
||
if total_len < SHA1_HEX_LEN + 4: # need at least "xxxx.vpi" length
|
||
return None
|
||
# scan backwards for the last / or \
|
||
sep_pos: sizet = total_len
|
||
while sep_pos > 0:
|
||
sep_pos -= 1
|
||
ch: char = vpi_path[sep_pos]
|
||
if ch == '/' or ch == '\\':
|
||
sep_pos += 1
|
||
break
|
||
if sep_pos + SHA1_HEX_LEN > total_len: return None
|
||
# allocate buffer and copy SHA1 (16 chars + null terminator)
|
||
sha1_buf: str = stdlib.malloc(SHA1_HEX_LEN + 1)
|
||
if sha1_buf is None: return None
|
||
string.strncpy(sha1_buf, vpi_path + sep_pos, SHA1_HEX_LEN)
|
||
sha1_buf[SHA1_HEX_LEN] = '\0'
|
||
return sha1_buf
|
||
|
||
|
||
# Stub import-context bookkeeping goes through StubMergerStore (single source of truth).
|
||
# Unified to AppendImportedModule / AppendFromImport / NotePlainImport; pure bookkeeping.
|
||
|
||
|
||
# Recursive typedef pre-registration over the import closure. CTypedef tables are per-file,
|
||
# so unimported typedefs would degrade param types; this registers them (cycle/depth guarded).
|
||
define _TYPEDEF_VISIT_MAX = 1024
|
||
|
||
_g_vpi_typedef_visited: bytes = None
|
||
_g_vpi_typedef_visited_count: int = 0
|
||
# Current visit-table capacity (elements). Must grow when full rather than silently dropping
|
||
# marks: dropping breaks cycle detection and re-parses the closure, risking pool OOM.
|
||
_g_vpi_typedef_visited_cap: int = 0
|
||
|
||
|
||
def ResetVpiTypedefVisited() -> None:
|
||
"""Clear the recursive-typedef cycle-detection marks before each module's stub injection."""
|
||
global _g_vpi_typedef_visited, _g_vpi_typedef_visited_count, _g_vpi_typedef_visited_cap
|
||
if _g_vpi_typedef_visited is not None:
|
||
stdlib.free(_g_vpi_typedef_visited)
|
||
_g_vpi_typedef_visited = None
|
||
_g_vpi_typedef_visited_count = 0
|
||
_g_vpi_typedef_visited_cap = 0
|
||
|
||
|
||
def _vpi_typedef_visited_check(sha1: str) -> int:
|
||
"""Check whether sha1 was already recursively registered; 1=visited / 0=not visited (query only)."""
|
||
global _g_vpi_typedef_visited, _g_vpi_typedef_visited_count
|
||
if sha1 is None: return 1
|
||
if _g_vpi_typedef_visited is None: return 0
|
||
vi: int
|
||
for vi in range(_g_vpi_typedef_visited_count):
|
||
if string.strcmp(_g_vpi_typedef_visited + vi * 17, sha1) == 0: return 1
|
||
return 0
|
||
|
||
|
||
def _vpi_typedef_visited_mark(sha1: str) -> None:
|
||
"""Mark sha1 as recursively registered (called after success, for cycle detection)."""
|
||
global _g_vpi_typedef_visited, _g_vpi_typedef_visited_count, _g_vpi_typedef_visited_cap
|
||
if sha1 is None: return
|
||
if _g_vpi_typedef_visited is None:
|
||
_g_vpi_typedef_visited = stdlib.malloc(_TYPEDEF_VISIT_MAX * 17)
|
||
if _g_vpi_typedef_visited is None:
|
||
# fail-fast: without the cycle table the closure re-parses and exhausts the pool, so do not continue
|
||
stdio.printf("[VPI] 防环访问表分配失败(无表则闭包重解析爆炸,终止)\n")
|
||
stdio.fflush(None)
|
||
stdlib.exit(1)
|
||
string.memset(_g_vpi_typedef_visited, 0, _TYPEDEF_VISIT_MAX * 17)
|
||
_g_vpi_typedef_visited_count = 0
|
||
_g_vpi_typedef_visited_cap = _TYPEDEF_VISIT_MAX
|
||
if _g_vpi_typedef_visited_count >= _g_vpi_typedef_visited_cap:
|
||
# Table full: grow (x2); never silently drop marks, which would break cycle detection
|
||
# and re-parse the same .vpi batch, allocating from the pool until it OOMs.
|
||
old_cap: int = _g_vpi_typedef_visited_cap
|
||
new_cap: int = old_cap * 2
|
||
nb: bytes = stdlib.realloc(_g_vpi_typedef_visited, new_cap * 17)
|
||
if nb is None:
|
||
stdio.printf("[VPI] 防环访问表扩容失败 cap=%d(丢标记引发重解析爆炸,终止)\n", old_cap)
|
||
stdio.fflush(None)
|
||
stdlib.exit(1)
|
||
_g_vpi_typedef_visited = nb
|
||
string.memset(nb + old_cap * 17, 0, old_cap * 17)
|
||
_g_vpi_typedef_visited_cap = new_cap
|
||
string.strcpy(_g_vpi_typedef_visited + _g_vpi_typedef_visited_count * 17, sha1)
|
||
_g_vpi_typedef_visited_count += 1
|
||
|
||
|
||
def _vpi_register_typedef_closure(pool: memhub.MemBuddy*, import_mods: str, temp_dir: str, depth: int) -> int:
|
||
"""Recursively register CTypedef/CDefine from import-closure modules; always returns 0 (side-effect only)."""
|
||
if pool is None or import_mods is None or temp_dir is None: return 0
|
||
if depth > 8: return 0
|
||
td_len: sizet = string.strlen(temp_dir)
|
||
total_len: sizet = string.strlen(import_mods)
|
||
pos: sizet = 0
|
||
while pos < total_len:
|
||
while pos < total_len and import_mods[pos] == ' ': pos += 1
|
||
if pos >= total_len: break
|
||
m_start: sizet = pos
|
||
while pos < total_len and import_mods[pos] != ' ': pos += 1
|
||
m_len: sizet = pos - m_start
|
||
if m_len == 0: continue
|
||
# t/c built-in libs are not compiled, so they have no vpi stub
|
||
if m_len == 1 and (import_mods[m_start] == 't' or import_mods[m_start] == 'c'): continue
|
||
mod_buf: bytes = stdlib.malloc(m_len + 1)
|
||
if mod_buf is None: continue
|
||
string.strncpy(mod_buf, import_mods + m_start, m_len)
|
||
mod_buf[m_len] = '\0'
|
||
sha1_r: str = StubMergerStore.LookupSha1ByModName(mod_buf)
|
||
stdlib.free(mod_buf)
|
||
if sha1_r is None: continue
|
||
if _vpi_typedef_visited_check(sha1_r) != 0: continue
|
||
# build {temp_dir}/{sha1[0:2]}/{sha1}.vpi by hand (same as _ensure_vpi_ret_annot_loaded,
|
||
# avoiding a StubMergerCollect cyclic dependency)
|
||
path_len_r: sizet = td_len + 16 + 12
|
||
vpi_path_r: bytes = stdlib.malloc(path_len_r)
|
||
if vpi_path_r is None: continue
|
||
string.strcpy(vpi_path_r, temp_dir)
|
||
vpi_path_r[td_len] = '/'
|
||
vpi_path_r[td_len + 1] = sha1_r[0]
|
||
vpi_path_r[td_len + 2] = sha1_r[1]
|
||
vpi_path_r[td_len + 3] = '/'
|
||
string.strcpy(vpi_path_r + td_len + 4, sha1_r)
|
||
string.strcpy(vpi_path_r + td_len + 4 + 16, ".vpi")
|
||
f_r: fileio.File* = fileio.File(vpi_path_r, fileio.MODE.R)
|
||
if f_r.closed:
|
||
stdlib.free(vpi_path_r)
|
||
continue
|
||
vpi_buf_r: bytes = stdlib.malloc(VPI_READ_BUF_SIZE)
|
||
if vpi_buf_r is None:
|
||
f_r.close()
|
||
stdlib.free(vpi_path_r)
|
||
continue
|
||
bytes_read_r: LONG = f_r.read_all(vpi_buf_r, VPI_READ_BUF_SIZE)
|
||
f_r.close()
|
||
if bytes_read_r > 0:
|
||
# mark as processed only after successfully reading the vpi (missing/read failure
|
||
# is not marked, so a later pass's stub injection can retry)
|
||
_vpi_typedef_visited_mark(sha1_r)
|
||
if bytes_read_r < VPI_READ_BUF_SIZE: vpi_buf_r[bytes_read_r] = 0
|
||
lx_r: ast.Lexer* = ast.new_lexer(pool)
|
||
if lx_r is not None:
|
||
ast._lexer_init(lx_r, vpi_buf_r, pool)
|
||
tokens_r: ast.Token* = ast.tokenize(lx_r)
|
||
tree_r: ast.AST* = ast.parse_tokens(pool, tokens_r)
|
||
if tree_r is not None and tree_r.children is not None:
|
||
ch_r: list[ast.AST*]* = tree_r.children
|
||
cn_r: sizet = len(ch_r)
|
||
# Collect child imports (recursive closure) in two passes: first sum the required
|
||
# length, then allocate exactly and fill (no fixed cap, so nothing is dropped).
|
||
sub_need: sizet = 1 # trailing NUL
|
||
ci_c: sizet
|
||
for ci_c in range(cn_r):
|
||
child_c: ast.AST* = ch_r.get(ci_c)
|
||
if child_c is None: continue
|
||
kk_c: int = child_c.kind()
|
||
if kk_c == ast.ASTKind.Import:
|
||
imp_c: ast.Import* = ast.Import*(child_c)
|
||
if imp_c.names is not None:
|
||
in_nc: sizet = len(imp_c.names)
|
||
for ii_c in range(in_nc):
|
||
al_c: ast.Alias* = ast.Alias*(imp_c.names.get(ii_c))
|
||
if al_c is not None and al_c.name is not None: sub_need += string.strlen(al_c.name) + 1
|
||
elif kk_c == ast.ASTKind.ImportFrom:
|
||
ifr_c: ast.ImportFrom* = ast.ImportFrom*(child_c)
|
||
if ifr_c.module is not None: sub_need += string.strlen(ifr_c.module) + 1
|
||
sub_imports: bytes = stdlib.malloc(sub_need)
|
||
if sub_imports is not None:
|
||
sub_imports[0] = '\0'
|
||
sub_len: sizet = 0
|
||
ci_r: sizet
|
||
for ci_r in range(cn_r):
|
||
child_r: ast.AST* = ch_r.get(ci_r)
|
||
if child_r is None: continue
|
||
kk_r: int = child_r.kind()
|
||
if kk_r == ast.ASTKind.Import:
|
||
imp_r: ast.Import* = ast.Import*(child_r)
|
||
if imp_r.names is not None:
|
||
in_n: sizet = len(imp_r.names)
|
||
ii_r: sizet
|
||
for ii_r in range(in_n):
|
||
al_r: ast.Alias* = ast.Alias*(imp_r.names.get(ii_r))
|
||
if al_r is not None and al_r.name is not None:
|
||
il_r: sizet = string.strlen(al_r.name)
|
||
if sub_len > 0:
|
||
sub_imports[sub_len] = ' '
|
||
sub_len += 1
|
||
string.strcpy(sub_imports + sub_len, al_r.name)
|
||
sub_len += il_r
|
||
sub_imports[sub_len] = '\0'
|
||
elif kk_r == ast.ASTKind.ImportFrom:
|
||
ifr_r: ast.ImportFrom* = ast.ImportFrom*(child_r)
|
||
if ifr_r.module is not None:
|
||
ml_r: sizet = string.strlen(ifr_r.module)
|
||
if sub_len > 0:
|
||
sub_imports[sub_len] = ' '
|
||
sub_len += 1
|
||
string.strcpy(sub_imports + sub_len, ifr_r.module)
|
||
sub_len += ml_r
|
||
sub_imports[sub_len] = '\0'
|
||
# second pass: register this module's AnnAssign CTypedef/CDefine
|
||
ci2_r: sizet
|
||
for ci2_r in range(cn_r):
|
||
child2_r: ast.AST* = ch_r.get(ci2_r)
|
||
if child2_r is None: continue
|
||
if child2_r.kind() != ast.ASTKind.AnnAssign: continue
|
||
aa_r: ast.AnnAssign* = ast.AnnAssign*(child2_r)
|
||
if aa_r.annotation is None or aa_r.target is None or aa_r.value is None: continue
|
||
if GenApi._is_ctypedef_annotation(aa_r.annotation) != 0: _register_ctypedef_from_annassign(pool, aa_r)
|
||
elif GenApi.is_cdefine_annotation(aa_r.annotation) != 0:
|
||
if aa_r.target.kind() != ast.ASTKind.Name: continue
|
||
cdef_t: ast.Name* = ast.Name*(aa_r.target)
|
||
if cdef_t.id is None: continue
|
||
if aa_r.value.kind() == ast.ASTKind.Constant:
|
||
cn_v: ast.Constant* = ast.Constant*(aa_r.value)
|
||
if cn_v.const_kind == ast.CONST_FLOAT: GenApi.register_cdefine_double(pool, cdef_t.id, cn_v.float_val)
|
||
elif cn_v.const_kind == ast.CONST_INT:
|
||
cdef_val: int64 = GenApi.extract_cdefine_int_value(aa_r.value)
|
||
GenApi.register_cdefine_constant(pool, cdef_t.id, cdef_val)
|
||
# recurse into child imports (cycle guarded by the visited marks)
|
||
if sub_imports[0] != '\0': _vpi_register_typedef_closure(pool, sub_imports, temp_dir, depth + 1)
|
||
stdlib.free(sub_imports)
|
||
stdlib.free(vpi_buf_r)
|
||
stdlib.free(vpi_path_r)
|
||
return 0
|
||
|
||
|
||
# ParseVpiIntoModule - parse a .vpi file and inject function declares into the current module.
|
||
# Walks top-level nodes, creates declares for FunctionDefs (deduped) and registers CTypedefs.
|
||
def ParseVpiIntoModule(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, vpi_path: str, trans: HT.Translator*) -> int:
|
||
"""Parse a .vpi file and inject function declares into the current module."""
|
||
if pool is None or mod is None or vpi_path is None or trans is None: return 0
|
||
saved_cur: str = GenApi.get_current_file()
|
||
GenApi.set_current_file_type(vpi_path)
|
||
ret: int = _ParseVpiIntoModuleImpl(pool, mod, vpi_path, trans)
|
||
GenApi.set_current_file_type(saved_cur)
|
||
return ret
|
||
|
||
|
||
def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, vpi_path: str, trans: HT.Translator*) -> int:
|
||
"""Actual implementation of ParseVpiIntoModule: parse a .vpi file and inject function declares."""
|
||
if pool is None or mod is None or vpi_path is None or trans is None: return 0
|
||
|
||
# 1. read the .vpi file
|
||
f: fileio.File* = fileio.File(vpi_path, fileio.MODE.R)
|
||
if f.closed: return 0
|
||
vpi_buf: bytes = stdlib.malloc(VPI_READ_BUF_SIZE)
|
||
if vpi_buf is None:
|
||
f.close()
|
||
return 0
|
||
bytes_read: LONG = f.read_all(vpi_buf, VPI_READ_BUF_SIZE)
|
||
f.close()
|
||
if bytes_read <= 0:
|
||
stdlib.free(vpi_buf)
|
||
return 0
|
||
if bytes_read < VPI_READ_BUF_SIZE: vpi_buf[bytes_read] = 0
|
||
|
||
# 2. parse into AST
|
||
lx: ast.Lexer* = ast.new_lexer(pool)
|
||
if lx is None:
|
||
stdlib.free(vpi_buf)
|
||
return 0
|
||
ast._lexer_init(lx, vpi_buf, pool)
|
||
tokens: ast.Token* = ast.tokenize(lx)
|
||
tree: ast.AST* = ast.parse_tokens(pool, tokens)
|
||
# Delay freeing vpi_buf: AST string fields may point into it (zero-copy tokens),
|
||
# so freeing early would dangle Name.id/Attribute.attr; freed at the end instead.
|
||
if tree is None:
|
||
stdlib.free(vpi_buf)
|
||
return 0
|
||
|
||
# extract the source module SHA1 for name mangling (after AST parse success to avoid leaks on early return)
|
||
src_sha1: str = _extract_sha1_from_path(vpi_path)
|
||
|
||
# Cache method return annotations from vpi_buf text into stdlib (no AST tree: deep nodes
|
||
# may be corrupted later, but the raw vpi_buf text stays valid until the function ends).
|
||
_cache_vpi_return_annots_text(vpi_buf, src_sha1)
|
||
# Likewise cache param annotations (tree param lists may be truncated;
|
||
# _inject_*_declare uses this cache to complete declare param counts).
|
||
_cache_vpi_param_annots_text(vpi_buf, src_sha1)
|
||
|
||
# 3. walk top-level AST nodes
|
||
ch: list[ast.AST*]* = tree.children
|
||
if ch is None:
|
||
if src_sha1 is not None: stdlib.free(src_sha1)
|
||
return 1
|
||
n: sizet = len(ch)
|
||
|
||
# Collect the read module's own imports from the stub to build an isolated annotation
|
||
# context, replacing the current module's imports (e.g. t.CArray[str] needs 't' imported).
|
||
stub_imported_modules: str = None
|
||
stub_from_imports: str = None
|
||
# Bookkeeping goes through StubMergerStore (same as ImportsGenMixin.translate_import):
|
||
# `import X as Y -> from_imports "Y:X"`; `import a.b.c -> imported_modules "a"`.
|
||
stub_ctx: StubMergerStore.ImportCtx* = StubMergerStore.AllocImportCtx(pool, None, None)
|
||
for si in range(n):
|
||
schild: ast.AST* = ch.get(si)
|
||
if schild is None: continue
|
||
sk: int = schild.kind()
|
||
if sk == ast.ASTKind.Import:
|
||
simp: ast.Import* = ast.Import*(schild)
|
||
if simp.names is not None:
|
||
sn_n: sizet = len(simp.names)
|
||
for sni in range(sn_n):
|
||
sal: ast.Alias* = ast.Alias*(simp.names.get(sni))
|
||
if sal is not None and sal.name is not None: StubMergerStore.NotePlainImport(pool, stub_ctx, sal.name, sal.asname)
|
||
elif sk == ast.ASTKind.ImportFrom:
|
||
sifr: ast.ImportFrom* = ast.ImportFrom*(schild)
|
||
sifr_mod: str = sifr.module
|
||
# Relative imports: resolve the package prefix and __init__ detection from the module
|
||
# name table. Falls back to the old rel_path route when no module-name evidence exists.
|
||
pkg_prefix_rt: str = None
|
||
if sifr.level > 0 and sifr_mod is not None and sifr_mod[0] != '\0':
|
||
_sib_sha_rt: str = StubMergerStore.LookupSha1ByModNameSuffix(sifr_mod)
|
||
if _sib_sha_rt is not None:
|
||
_sub_mod_rt: str = StubMergerStore.LookupModNameBySha1(_sib_sha_rt)
|
||
if _sub_mod_rt is not None:
|
||
_dot_rt: str = string.strrchr(_sub_mod_rt, '.')
|
||
if _dot_rt is not None:
|
||
_pfx_len_rt: sizet = sizet(uint64(_dot_rt) - uint64(_sub_mod_rt))
|
||
if _pfx_len_rt > 0:
|
||
_my_mod_rt: str = StubMergerStore.LookupModNameBySha1(src_sha1)
|
||
# Confirm: this stub's module name must equal the prefix exactly
|
||
# (i.e. this stub is the package __init__); else defer to rel_path.
|
||
_conf_rt: int = 0
|
||
if _my_mod_rt is not None and string.strlen(_my_mod_rt) == _pfx_len_rt:
|
||
if string.strncmp(_my_mod_rt, _sub_mod_rt, _pfx_len_rt) == 0: _conf_rt = 1
|
||
if _conf_rt != 0:
|
||
pkg_prefix_rt = pool.alloc(_pfx_len_rt + 1)
|
||
if pkg_prefix_rt is not None:
|
||
string.strncpy(pkg_prefix_rt, _sub_mod_rt, _pfx_len_rt)
|
||
pkg_prefix_rt[_pfx_len_rt] = '\0'
|
||
sifr_mod_q: str = sifr_mod
|
||
if sifr_mod is not None and sifr_mod[0] != '\0' and sifr.level == 1:
|
||
if pkg_prefix_rt is not None:
|
||
qtotal_p: sizet = string.strlen(pkg_prefix_rt) + 1 + string.strlen(sifr_mod) + 1
|
||
qbuf_p: str = pool.alloc(qtotal_p)
|
||
if qbuf_p is not None:
|
||
qbuf_p[0] = '\0'
|
||
string.strcat(qbuf_p, pkg_prefix_rt)
|
||
string.strcat(qbuf_p, ".")
|
||
string.strcat(qbuf_p, sifr_mod)
|
||
sifr_mod_q = qbuf_p
|
||
else:
|
||
rel_p_r: str = StubMergerStore.LookupSha1RelPath(src_sha1)
|
||
if rel_p_r is not None:
|
||
slash_r: str = string.strrchr(rel_p_r, '/')
|
||
if slash_r is not None:
|
||
pkg_len_r: sizet = string.strlen(rel_p_r) - string.strlen(slash_r)
|
||
if pkg_len_r > 0:
|
||
qtotal_r: sizet = pkg_len_r + 1 + string.strlen(sifr_mod) + 1
|
||
qbuf_r: str = pool.alloc(qtotal_r)
|
||
if qbuf_r is not None:
|
||
string.strncpy(qbuf_r, rel_p_r, pkg_len_r)
|
||
qbuf_r[pkg_len_r] = '.'
|
||
string.strcpy(qbuf_r + pkg_len_r + 1, sifr_mod)
|
||
sifr_mod_q = qbuf_r
|
||
if sifr_mod is not None:
|
||
if stub_ctx is not None:
|
||
stub_ctx.ImportedModules = StubMergerStore.AppendImportedModule(pool, stub_ctx.ImportedModules, sifr_mod_q)
|
||
# Plan A: package __init__ re-exported classes are registered as alias entries under
|
||
# the package sha1 (only for __init__; uses pkg_prefix_rt, falling back to rel_path).
|
||
is_pkg_init_rt: int = 0
|
||
if pkg_prefix_rt is not None: is_pkg_init_rt = 1
|
||
if is_pkg_init_rt == 0:
|
||
rel_path_rt: str = StubMergerStore.LookupSha1RelPath(src_sha1)
|
||
if rel_path_rt is not None:
|
||
# [FIX] Use strstr(rel_path, "__init__.vp") instead of an offset-based strcmp,
|
||
# which was always false for real package paths and never triggered re-export.
|
||
if string.strstr(rel_path_rt, "__init__.vp") is not None: is_pkg_init_rt = 1
|
||
# Resolve the from-imported module sha1: relative imports put leading dots in `level`,
|
||
# not in `module`, so prefix-qualified name lookup must be used.
|
||
sub_sha1_rt: str = None
|
||
if sifr.level > 0:
|
||
if sifr_mod is not None and sifr_mod[0] != '\0':
|
||
# Prefer the prefix-qualified exact name (sifr_mod_q, e.g. ast.base); the
|
||
# suffix match is only a fallback since it can hit another same-suffix module.
|
||
sub_sha1_rt = StubMergerStore.LookupSha1ByModName(sifr_mod_q)
|
||
if sub_sha1_rt is None: sub_sha1_rt = StubMergerStore.LookupSha1ByModNameSuffix(sifr_mod)
|
||
else: sub_sha1_rt = StubMergerStore.LookupSha1ByModName(sifr_mod)
|
||
# [D26 fail-fast] Do not silently skip when a package __init__ from-import cannot
|
||
# resolve the submodule sha1: report package and submodule names for diagnosis.
|
||
if is_pkg_init_rt != 0 and sub_sha1_rt is None and sifr_mod is not None and sifr_mod[0] != '\0':
|
||
GenApi.fatal_error(sifr, f"包 stub 的 from-import 子模块 sha1 查找失败:" f"包={rel_path_rt if rel_path_rt is not None else '?'}," f"子模块='{sifr_mod}'(补前缀后='{sifr_mod_q}');" f"该子模块的类无法注册,引用它们的注解会在后续解析时落空", "NameError")
|
||
if sifr.names is not None:
|
||
sifr_nn: sizet = len(sifr.names)
|
||
sifri: sizet
|
||
for sifri in range(sifr_nn):
|
||
sial: ast.Alias* = ast.Alias*(sifr.names.get(sifri))
|
||
if sial is not None and sial.name is not None:
|
||
slocal: str = sial.name
|
||
sorig: str = None
|
||
if sial.asname is not None:
|
||
slocal = sial.asname
|
||
sorig = sial.name
|
||
# `from X import *` also writes "*:mod" (same as
|
||
# ImportsGenMixin.translate_from_import_names); lookup_from_import relies on it.
|
||
if stub_ctx is not None:
|
||
stub_ctx.FromImports = StubMergerStore.AppendFromImport(pool, stub_ctx.FromImports, slocal, sifr_mod_q, sorig)
|
||
# Register re-exported classes under the package sha1 (by alias if asname);
|
||
# star has no concrete class name and is only recorded above.
|
||
if string.strcmp(sial.name, "*") != 0:
|
||
if is_pkg_init_rt != 0 and sub_sha1_rt is not None:
|
||
_inject_reexport_class_declare(pool, mod, sial.name, slocal, sub_sha1_rt, src_sha1, trans)
|
||
|
||
# replace the current module's imports with the stub's own (correct annotation context)
|
||
if stub_ctx is not None:
|
||
stub_imported_modules = stub_ctx.ImportedModules
|
||
stub_from_imports = stub_ctx.FromImports
|
||
imported_modules: str = trans._imported_modules
|
||
from_imports: str = trans._from_imports
|
||
if stub_imported_modules is not None: imported_modules = stub_imported_modules
|
||
if stub_from_imports is not None: from_imports = stub_from_imports
|
||
# Add the source module itself to the stub annotation context: stub annotations may
|
||
# reference their own CDefines, but stub imports only list imported modules.
|
||
if src_sha1 is not None:
|
||
stub_rel_src: str = StubMergerStore.LookupSha1RelPath(src_sha1)
|
||
if stub_rel_src is not None:
|
||
sr_len_src: sizet = string.strlen(stub_rel_src)
|
||
if sr_len_src > 3 and string.strncmp(stub_rel_src + sr_len_src - 3, ".vp", 3) == 0: sr_len_src -= 3
|
||
if sr_len_src > 0:
|
||
src_mod_buf: str = stdlib.malloc(sr_len_src + 1)
|
||
if src_mod_buf is not None:
|
||
sri_src: sizet
|
||
for sri_src in range(sr_len_src):
|
||
ch_src: char = stub_rel_src[sri_src]
|
||
if ch_src == '/' or ch_src == '\\': src_mod_buf[sri_src] = '.'
|
||
else: src_mod_buf[sri_src] = ch_src
|
||
src_mod_buf[sr_len_src] = '\0'
|
||
imported_modules = StubMergerStore.AppendImportedModule(pool, imported_modules, src_mod_buf)
|
||
stdlib.free(src_mod_buf)
|
||
|
||
# Recursively pre-register the stub import closure's CTypedef/CDefine: CTypedef tables are
|
||
# per-file, so an unimported typedef would degrade param types to i8* and misreport CastError.
|
||
if imported_modules is not None: _vpi_register_typedef_closure(pool, imported_modules, GenApi.get_temp_dir(), 0)
|
||
|
||
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
|
||
|
||
# function table info
|
||
funcs_ptr: GenTypes.FuncEntry* = trans._funcs
|
||
max_funcs: int = 1024
|
||
|
||
# REPL (ReplStubLayoutMode): restore full class layouts and vtable method indices from
|
||
# {sha1}.meta first, then run the vpi injection below. Always 0 in batch mode.
|
||
if ReplStubLayoutMode != 0:
|
||
_repl_stub_collect_module(pool, mod, ch, n, trans, src_sha1, stub_imported_modules, stub_from_imports)
|
||
|
||
# Pass 1A: register ALL ClassDefs (fields + structs) first, without methods, so a class
|
||
# defined after a global's AnnAssign is still registered before that AnnAssign resolves.
|
||
i0: sizet
|
||
for i0 in range(n):
|
||
child0: ast.AST* = ch.get(i0)
|
||
if child0 is None: continue
|
||
if child0.kind() != ast.ASTKind.ClassDef: continue
|
||
class_cd0: ast.ClassDef* = ast.ClassDef*(child0)
|
||
if class_cd0 is None or class_cd0.name is None: continue
|
||
cls_name_v0: str = class_cd0.name
|
||
# Skip generic classes (class X[T]): the stub template signature has a class-level type
|
||
# param T that cannot be resolved; specialized method defines are already emitted in Phase1.
|
||
tp_stub0: list[str]* = class_cd0.type_params
|
||
if tp_stub0 is not None and len(tp_stub0) > 0: continue
|
||
_inject_class_struct_declare(pool, mod, class_cd0, cls_name_v0, trans, src_sha1, imported_modules, from_imports)
|
||
|
||
# App pre-registration (Phase A-pre, _pre_allow_dep_inject) skips method/function declare
|
||
# injection: its only goal is structs/enums and package re-export aliases, not transient declares.
|
||
pre_skip_fn_declares: int = 0
|
||
if trans is not None and trans._pre_allow_dep_inject != 0: pre_skip_fn_declares = 1
|
||
|
||
# Pass 1B: process method declares only after all classes are registered, since method
|
||
# params/returns may reference classes defined later in the file (forward references).
|
||
for i1 in range(n):
|
||
child1: ast.AST* = ch.get(i1)
|
||
if child1 is None: continue
|
||
if child1.kind() != ast.ASTKind.ClassDef: continue
|
||
class_cd1: ast.ClassDef* = ast.ClassDef*(child1)
|
||
if class_cd1 is None or class_cd1.name is None: continue
|
||
cls_name_v1: str = class_cd1.name
|
||
# skip generic classes (same criterion as pass 1A)
|
||
tp_stub1: list[str]* = class_cd1.type_params
|
||
if tp_stub1 is not None and len(tp_stub1) > 0: continue
|
||
cls_children1: list[ast.AST*]* = class_cd1.children
|
||
if cls_children1 is None: continue
|
||
cn21: sizet = len(cls_children1)
|
||
for ci21 in range(cn21):
|
||
m_child1: ast.AST* = cls_children1.get(ci21)
|
||
if m_child1 is None: continue
|
||
if m_child1.kind() != ast.ASTKind.FunctionDef: continue
|
||
m_fd1: ast.FunctionDef* = ast.FunctionDef*(m_child1)
|
||
if m_fd1 is None or m_fd1.name is None: continue
|
||
# App pre-registration skips method declares (see pre_skip_fn_declares above).
|
||
if pre_skip_fn_declares == 0:
|
||
_inject_method_declare(pool, mod, m_fd1, cls_name_v1, trans, src_sha1, imported_modules, from_imports, i32_ty, i8_ptr_ty, funcs_ptr, max_funcs)
|
||
|
||
i: sizet
|
||
for i in range(n):
|
||
child: ast.AST* = ch.get(i)
|
||
if child is None: continue
|
||
kd: int = child.kind()
|
||
|
||
# FunctionDef -> extract name/params/return type -> inject declare
|
||
if kd == ast.ASTKind.FunctionDef:
|
||
fd: ast.FunctionDef* = ast.FunctionDef*(child)
|
||
if fd is None or fd.name is None: continue
|
||
# App pre-registration skips function declares (see pre_skip_fn_declares):
|
||
# their annotations may reference classes not yet registered this pass.
|
||
if pre_skip_fn_declares != 0: continue
|
||
_inject_function_declare(pool, mod, fd, trans, src_sha1, imported_modules, from_imports, i32_ty, i8_ptr_ty, funcs_ptr, max_funcs)
|
||
continue
|
||
|
||
# AnnAssign with t.CTypedef -> register CTypedef;
|
||
# plain module-level globals (e.g. tokens._op_head) -> external global declare
|
||
if kd == ast.ASTKind.AnnAssign:
|
||
aa: ast.AnnAssign* = ast.AnnAssign*(child)
|
||
if aa is None: continue
|
||
if aa.target is not None and aa.target.kind() == ast.ASTKind.Name:
|
||
dbg_tn: ast.Name* = ast.Name*(aa.target)
|
||
if dbg_tn.id is not None and dbg_tn.id[0] != '\0': # questionable
|
||
if string.strcmp(dbg_tn.id, "irq_handler_t") == 0 or string.strcmp(dbg_tn.id, "gdt") == 0 or string.strcmp(dbg_tn.id, "KBD_BUF_SIZE") == 0:
|
||
dbg_ct: int = GenApi._is_ctypedef_annotation(aa.annotation)
|
||
dbg_cd: int = GenApi.is_cdefine_annotation(aa.annotation)
|
||
if GenApi._is_ctypedef_annotation(aa.annotation) != 0: _register_ctypedef_from_annassign(pool, aa)
|
||
elif GenApi.is_cdefine_annotation(aa.annotation) != 0:
|
||
# Register vpi CDefine definitions (e.g. keyboard.vp KBD_BUF_SIZE) into the current
|
||
# file's CDefine table so later t.CArray counts in the same vpi can resolve them.
|
||
if aa.target is not None and aa.target.kind() == ast.ASTKind.Name and aa.value is not None:
|
||
cdef_tgt: ast.Name* = ast.Name*(aa.target)
|
||
if cdef_tgt.id is not None:
|
||
# Skip string CDefines (version numbers etc.): extract_cdefine_int_value
|
||
# fails fast on string constants, and count refs only need integer CDefines.
|
||
cdef_is_str: int = 0
|
||
cdef_is_float: int = 0
|
||
cdef_float_val: double = 0.0
|
||
if aa.value.kind() == ast.ASTKind.Constant:
|
||
cn_v: ast.Constant* = ast.Constant*(aa.value)
|
||
if cn_v.const_kind == ast.CONST_STR: cdef_is_str = 1
|
||
elif cn_v.const_kind == ast.CONST_FLOAT:
|
||
cdef_is_float = 1
|
||
cdef_float_val = cn_v.float_val
|
||
if cdef_is_str == 0 and cdef_is_float != 0:
|
||
# Float CDefine (e.g. FRAGMENTATION_THRESHOLD: t.CDefine = 0.7): register via
|
||
# register_cdefine_double (integer extraction would fail fast).
|
||
GenApi.register_cdefine_double(pool, cdef_tgt.id, cdef_float_val)
|
||
elif cdef_is_str == 0:
|
||
cdef_val: int64 = GenApi.extract_cdefine_int_value(aa.value)
|
||
GenApi.register_cdefine_constant(pool, cdef_tgt.id, cdef_val)
|
||
else:
|
||
_inject_global_variable_declare(pool, mod, aa, trans, stub_imported_modules, stub_from_imports, i32_ty, i8_ptr_ty, src_sha1)
|
||
continue
|
||
|
||
# ClassDef -> already registered in the first passes (fields + method declares), skip here.
|
||
# (A single pass would fail when classes are defined after globals; see pass 1A above.)
|
||
|
||
# Import/ImportFrom -> skip (nothing to process)
|
||
|
||
# free the SHA1 buffer (allocated by _extract_sha1_from_path via stdlib.malloc)
|
||
if src_sha1 is not None: stdlib.free(src_sha1)
|
||
# free vpi_buf only after all tree processing (see the parsing comment)
|
||
stdlib.free(vpi_buf)
|
||
return 1
|
||
|
||
|
||
# On-demand dependency vpi injection (resolves the mutual wait between package __init__
|
||
# re-exports and submodule stubs). Re-entrancy table dedups by sha1; failures stay non-fatal.
|
||
define VPI_INJ_MAX = 1024
|
||
_vpi_inj_sha1s: bytes = None
|
||
_vpi_inj_count: int = 0
|
||
_vpi_inj_depth: int = 0
|
||
|
||
|
||
def ResetVpiInjectVisited() -> None:
|
||
"""Clear the on-demand injection reentry table before each module's stub-injection session."""
|
||
global _vpi_inj_sha1s, _vpi_inj_count, _vpi_inj_depth
|
||
if _vpi_inj_sha1s is not None:
|
||
stdlib.free(_vpi_inj_sha1s)
|
||
_vpi_inj_sha1s = None
|
||
_vpi_inj_count = 0
|
||
_vpi_inj_depth = 0
|
||
|
||
|
||
def _ensure_dep_vpi_injected(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, dep_sha1: str, trans: HT.Translator*) -> int:
|
||
"""Inject dep_sha1's .vpi into the current module on demand (reentry-guarded); 1=ready / 0=failure."""
|
||
global _vpi_inj_sha1s, _vpi_inj_count, _vpi_inj_depth
|
||
if pool is None or mod is None or dep_sha1 is None or trans is None: return 0
|
||
if dep_sha1[0] == '\0': return 0
|
||
if _vpi_inj_sha1s is None:
|
||
_vpi_inj_sha1s = stdlib.malloc(VPI_INJ_MAX * 17)
|
||
if _vpi_inj_sha1s is None: return 0
|
||
string.memset(_vpi_inj_sha1s, 0, VPI_INJ_MAX * 17)
|
||
_vpi_inj_count = 0
|
||
# already injected / currently injecting (cycle) -> treat as ready and short-circuit
|
||
ii: int
|
||
for ii in range(_vpi_inj_count):
|
||
if string.strcmp(_vpi_inj_sha1s + sizet(ii) * 17, dep_sha1) == 0: return 1
|
||
if _vpi_inj_count >= VPI_INJ_MAX or _vpi_inj_depth >= 32: return 0
|
||
temp_dir: str = GenApi.get_temp_dir()
|
||
if temp_dir is None: return 0
|
||
td_len_i: sizet = string.strlen(temp_dir)
|
||
sh_len_i: sizet = string.strlen(dep_sha1)
|
||
sub_i: str = Config.slice_subdir(dep_sha1, Config.Sha1SliceLevel)
|
||
sub_len_i: sizet = 0
|
||
if sub_i is not None: sub_len_i = string.strlen(sub_i)
|
||
path_len_i: sizet = td_len_i + sub_len_i + sh_len_i + 8
|
||
path_i: bytes = stdlib.malloc(path_len_i)
|
||
if path_i is None: return 0
|
||
if sub_len_i > 0: viperlib.snprintf(path_i, path_len_i, "%s/%s/%s.vpi", temp_dir, sub_i, dep_sha1)
|
||
else: viperlib.snprintf(path_i, path_len_i, "%s/%s.vpi", temp_dir, dep_sha1)
|
||
vf_i: fileio.File* = fileio.File(path_i, fileio.MODE.R)
|
||
if vf_i.closed:
|
||
stdlib.free(path_i)
|
||
return 0
|
||
vf_i.close()
|
||
# register before injecting: recursive injection of the same sha1 short-circuits (cycle guard)
|
||
my_idx_i: int = _vpi_inj_count
|
||
string.strcpy(_vpi_inj_sha1s + sizet(my_idx_i) * 17, dep_sha1)
|
||
_vpi_inj_count += 1
|
||
_vpi_inj_depth += 1
|
||
ret_i: int = ParseVpiIntoModule(pool, mod, path_i, trans)
|
||
_vpi_inj_depth -= 1
|
||
stdlib.free(path_i)
|
||
if ret_i != 1:
|
||
# Injection failed: unregister (move the last entry into this slot, keeping LIFO order),
|
||
# else later requests for the same sha1 stay short-circuited and the class never registers.
|
||
_vpi_inj_count -= 1
|
||
if my_idx_i != _vpi_inj_count:
|
||
string.strcpy(_vpi_inj_sha1s + sizet(my_idx_i) * 17, _vpi_inj_sha1s + sizet(_vpi_inj_count) * 17)
|
||
return 0
|
||
return 1
|
||
|
||
|
||
# _inject_reexport_class_declare - register a package re-exported class as an alias entry under the
|
||
# package sha1, reusing the submodule's registered entry so ast.Attribute resolves by package.
|
||
def _inject_reexport_class_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, src_name: str, local_name: str, sub_sha1: str, pkg_sha1: str, trans: HT.Translator*) -> int:
|
||
"""Register a package re-exported class as an alias entry under the package sha1."""
|
||
if pool is None or mod is None or src_name is None or local_name is None: return 0
|
||
if sub_sha1 is None or sub_sha1[0] == '\0' or pkg_sha1 is None or pkg_sha1[0] == '\0': return 0
|
||
if src_name[0] == '\0' or local_name[0] == '\0': return 0
|
||
# idempotent: skip if local_name is already registered under the package sha1
|
||
if GenApi.find_struct_by_module(local_name, pkg_sha1) is not None: return 1
|
||
# find the submodule source entry (only classes have entries; functions/constants are skipped)
|
||
src_entry: GenTypes.StructEntry* = GenApi.find_struct_by_module(src_name, sub_sha1)
|
||
if src_entry is None or src_entry.Ty is None:
|
||
# Source stub not injected yet (package __init__ and submodule wait on each other; see
|
||
# _ensure_dep_vpi_injected): inject sub_sha1's vpi on demand and retry once; else skip.
|
||
if _ensure_dep_vpi_injected(pool, mod, sub_sha1, trans) != 0: src_entry = GenApi.find_struct_by_module(src_name, sub_sha1)
|
||
if src_entry is None or src_entry.Ty is None: return 0
|
||
# register the alias entry (bypassing register_struct's Ty dedup)
|
||
if GenApi.register_struct_alias(pool, local_name, pkg_sha1, src_entry) is None: return 0
|
||
GenApi.add_visible_struct(pool, local_name)
|
||
return 1
|
||
|
||
|
||
# _vpi_tattr_name / _vpi_class_has_packed - detect the vpi class packed decorator.
|
||
# The stub keeps @c.Attribute(t.attr.packed); struct injection must mirror the packed flag (B5).
|
||
def _vpi_tattr_name(node: ast.AST*, attr_name: str) -> int:
|
||
"""Extract the outermost attribute name from t.attr.<name> / t.attr.<name>(args) and compare."""
|
||
if node is None: return 0
|
||
if node.kind() == ast.ASTKind.Call:
|
||
cl2: ast.Call* = ast.Call*(node)
|
||
if cl2.func is None: return 0
|
||
return _vpi_tattr_name(cl2.func, attr_name)
|
||
if node.kind() == ast.ASTKind.Attribute:
|
||
at2: ast.Attribute* = ast.Attribute*(node)
|
||
if at2.attr is None: return 0
|
||
return 1 if string.strcmp(at2.attr, attr_name) == 0 else 0
|
||
return 0
|
||
|
||
|
||
def _vpi_class_has_packed(decorator_list: list[ast.AST*]*) -> int:
|
||
"""Check whether a vpi ClassDef decorator list contains t.attr.packed."""
|
||
if decorator_list is None: return 0
|
||
dn: sizet = len(decorator_list)
|
||
if dn == 0: return 0
|
||
for di in range(dn):
|
||
deco: ast.AST* = decorator_list.get(di)
|
||
if deco is None: continue
|
||
if deco.kind() == ast.ASTKind.Call:
|
||
cl: ast.Call* = ast.Call*(deco)
|
||
if cl.func is None or cl.func.kind() != ast.ASTKind.Attribute: continue
|
||
at0: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if at0.attr is None or string.strcmp(at0.attr, "Attribute") != 0: continue
|
||
if cl.args is None: continue
|
||
an: sizet = len(cl.args)
|
||
for ai in range(an):
|
||
ag: ast.AST* = cl.args.get(ai)
|
||
if ag is not None and _vpi_tattr_name(ag, "packed") != 0: return 1
|
||
else:
|
||
if _vpi_tattr_name(deco, "packed") != 0: return 1
|
||
return 0
|
||
|
||
|
||
def _vpi_class_is_renum(class_cd: ast.ClassDef*) -> int:
|
||
"""Check whether a vpi ClassDef is a REnum (base class contains t.REnum); 1=yes / 0=no."""
|
||
if class_cd is None or class_cd.bases is None: return 0
|
||
bases: list[ast.AST*]* = class_cd.bases
|
||
bn: sizet = len(bases)
|
||
for bi in range(bn):
|
||
base_node: ast.AST* = bases.get(bi)
|
||
if base_node is None: continue
|
||
bk: int = base_node.kind()
|
||
# Attribute(t.REnum) form
|
||
if bk == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(base_node)
|
||
if at.attr is not None and string.strcmp(at.attr, "REnum") == 0: return 1
|
||
# Name(REnum) form (from t import REnum)
|
||
elif bk == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(base_node)
|
||
if nm.id is not None and string.strcmp(nm.id, "REnum") == 0: return 1
|
||
return 0
|
||
|
||
|
||
# _inject_class_struct_declare - register a vpi ClassDef as a struct type (with fields) so bare
|
||
# class-name annotations in the same vpi resolve. Mirrors ClassGenDef.translate_class_def.
|
||
def _inject_class_struct_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, class_cd: ast.ClassDef*, cls_name: str, trans: HT.Translator*, src_sha1: str, imported_modules: str, from_imports: str, name_prefix: str = None) -> int:
|
||
"""Register a vpi ClassDef as a struct type (with fields) so bare class-name annotations resolve."""
|
||
if pool is None or mod is None or class_cd is None or cls_name is None: return 0
|
||
# Flattened name for nested classes (`{outer}_{inner}`); register even if the base class is not
|
||
# registered (the "no registration on field failure" rule lets multi-pass convergence handle it).
|
||
real_name: str = cls_name
|
||
if name_prefix is not None and name_prefix[0] != '\0':
|
||
p_len: sizet = string.strlen(name_prefix)
|
||
c_len: sizet = string.strlen(cls_name)
|
||
flat_buf_c: str = pool.alloc(p_len + 1 + c_len + 1)
|
||
if flat_buf_c is not None:
|
||
string.strcpy(flat_buf_c, name_prefix)
|
||
flat_buf_c[p_len] = '_'
|
||
string.strcpy(flat_buf_c + p_len + 1, cls_name)
|
||
real_name = flat_buf_c
|
||
# REPL (ReplStubLayoutMode): if this class is already driven by the {sha1}.meta path, yield to it:
|
||
# mark visible if registered, defer if the base is pending, skip if the layout is untrusted.
|
||
if ReplStubLayoutMode != 0:
|
||
se: ReplStubClass* = _repl_stub_find(_repl_stub_key(pool, src_sha1, real_name))
|
||
if se is not None and se.State != _REPL_SS_EMPTY:
|
||
if se.State == _REPL_SS_PENDING: _repl_stub_try(se, mod)
|
||
if se.State == _REPL_SS_OK: GenApi.add_visible_struct(pool, real_name)
|
||
return 1
|
||
# 1. Already-registered check: just mark visible, except forward-decl placeholders
|
||
# (IsForwardDecl != 0) which must go through the registration path to fill the same Ty.
|
||
existing_c: GenTypes.StructEntry* = None
|
||
fill_fwd_c: int = 0
|
||
if src_sha1 is not None and src_sha1[0] != '\0':
|
||
existing_c = GenApi.find_struct_by_module(real_name, src_sha1)
|
||
if existing_c is not None and existing_c.Ty is not None:
|
||
if existing_c.IsForwardDecl != 0: fill_fwd_c = 1
|
||
else:
|
||
GenApi.add_visible_struct(pool, real_name)
|
||
return 1
|
||
# Skip REnum classes (class X(t.REnum)): their layout is computed from variants by
|
||
# ClassGenEnum._translate_renum_def; registering as a plain struct would be wrong.
|
||
if _vpi_class_is_renum(class_cd) != 0: return 0
|
||
# Rule 5.5 evidence must be set before field annotation resolution: mark_module_class
|
||
# records "{src_sha1}.{real_name}" and points the class-definition window at this class.
|
||
GenApi.mark_module_class(pool, src_sha1, real_name)
|
||
# 1.5 Nested classes/structs: vpi keeps them as indented ClassDefs; the definition side emits
|
||
# flat names (`{outer}_{inner}`), so recurse first with the same naming before field resolution.
|
||
_inject_nested_class_declares(pool, mod, class_cd, real_name, trans, src_sha1, imported_modules, from_imports)
|
||
# Restore the "defining class" window: the nested recursion's mark_module_class changes
|
||
# CurDefClassName to the nested name (A_B), which would corrupt rule 5.5 self-reference evidence.
|
||
GenApi.mark_module_class(pool, src_sha1, real_name)
|
||
# 2. collect field types (AnnAssign children, mirroring ClassGenDef.translate_class_def)
|
||
field_names_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
|
||
field_types_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
|
||
if field_names_buf is None or field_types_buf is None: return 0
|
||
string.memset(field_names_buf, 0, 8 * GenTypes.FIELD_MAX)
|
||
string.memset(field_types_buf, 0, 8 * GenTypes.FIELD_MAX)
|
||
field_count: int = 0
|
||
cls_children_c: list[ast.AST*]* = class_cd.children
|
||
if cls_children_c is not None:
|
||
cn_c: sizet = len(cls_children_c)
|
||
for ci_c in range(cn_c):
|
||
m_child_c: ast.AST* = cls_children_c.get(ci_c)
|
||
if m_child_c is None: continue
|
||
if m_child_c.kind() != ast.ASTKind.AnnAssign: continue
|
||
m_aa_c: ast.AnnAssign* = ast.AnnAssign*(m_child_c)
|
||
if m_aa_c is None or m_aa_c.target is None: continue
|
||
if m_aa_c.target.kind() != ast.ASTKind.Name: continue
|
||
m_tgt_c: ast.Name* = ast.Name*(m_aa_c.target)
|
||
if m_tgt_c.id is None: continue
|
||
field_ty_c: mlirlite.LLVMType* = GenApi.resolve_annotation_type(pool, m_aa_c.annotation, imported_modules, from_imports, trans, 0)
|
||
if field_ty_c is None:
|
||
# field type parse failed: do not register this class (leave it to the source module), not fatal
|
||
return 0
|
||
if field_count < GenTypes.FIELD_MAX:
|
||
field_names_buf[field_count] = sizet(m_tgt_c.id)
|
||
field_types_buf[field_count] = sizet(field_ty_c)
|
||
field_count += 1
|
||
# 3. The vpi stub has no class fields -> field_count is 0; registering an empty class would make
|
||
# the source module hit an empty struct and raise InternalCompilerError, so do not register.
|
||
if field_count == 0: return 0
|
||
# 4. build the ParamNode list
|
||
first_node_c: mlirlite.ParamNode* = None
|
||
prev_node_c: mlirlite.ParamNode* = None
|
||
for fi_c in range(field_count):
|
||
fty_c: mlirlite.LLVMType* = (mlirlite.LLVMType*)(field_types_buf[fi_c])
|
||
pnode_c: mlirlite.ParamNode* = pool.alloc(mlirlite.ParamNode.__sizeof__())
|
||
if pnode_c is None: continue
|
||
string.memset(pnode_c, 0, mlirlite.ParamNode.__sizeof__())
|
||
pnode_c.Ty = fty_c
|
||
if first_node_c is None: first_node_c = pnode_c
|
||
if prev_node_c is not None: prev_node_c.Next = pnode_c
|
||
prev_node_c = pnode_c
|
||
# 4. Named struct type name "sha1.ClassName" (matching ClassGenDef); nested classes use the
|
||
# flat real_name, consistent with ClassGenDef._translate_nested_classdefs.
|
||
type_name_c: str = real_name
|
||
if src_sha1 is not None and src_sha1[0] != '\0':
|
||
# [D7 truncation discipline] The old fixed 64-byte buffer silently truncated class names
|
||
# over 46 chars; allocate the exact length so the struct name matches ClassGenDef.
|
||
sh_len_c: sizet = string.strlen(src_sha1)
|
||
cn_len_c: sizet = string.strlen(real_name)
|
||
name_buf_c: str = pool.alloc(sh_len_c + 1 + cn_len_c + 1)
|
||
if name_buf_c is not None:
|
||
string.strcpy(name_buf_c, src_sha1)
|
||
name_buf_c[sh_len_c] = '.'
|
||
string.strcpy(name_buf_c + sh_len_c + 1, real_name)
|
||
type_name_c = name_buf_c
|
||
struct_ty_c: mlirlite.LLVMType* = mlirlite.Struct(pool, first_node_c, field_count, type_name_c)
|
||
if struct_ty_c is None: return 0
|
||
# 5. Under StdlibAlloc (Phase B) deep-copy into perm_pool to survive cross-file resets
|
||
# (matching ClassGenDef.translate_class_def).
|
||
if GenApi.get_stdlib_alloc() != 0:
|
||
st_copy_c: mlirlite.LLVMType* = mlirlite.DeepCopyType(CompilerFactory.factory.PermPool, struct_ty_c)
|
||
if st_copy_c is not None: struct_ty_c = st_copy_c
|
||
# 5.5 Forward-decl placeholder hit -> write in place into *existing.Ty (same as ClassGenDef's
|
||
# fill_fwd) so register_struct's Ty-pointer dedup works and the StructTable does not overflow.
|
||
if fill_fwd_c != 0 and existing_c is not None and existing_c.Ty is not None:
|
||
fwd_name_c: str = mlirlite.get_struct_name(existing_c.Ty)
|
||
fwd_fields_c: mlirlite.ParamNode* = mlirlite.get_struct_fields(struct_ty_c)
|
||
fwd_fc_c: int = mlirlite.get_struct_fcount(struct_ty_c)
|
||
*existing_c.Ty = mlirlite.LLVMType.Struct(fwd_fields_c, fwd_fc_c, fwd_name_c)
|
||
struct_ty_c = existing_c.Ty
|
||
type_name_c = fwd_name_c
|
||
existing_c.IsForwardDecl = 0
|
||
existing_c.FieldCount = 0
|
||
# 6. register into the current module (emit the type def) + global StructTable + visibility
|
||
mlirlite.module_add_named_type(mod, pool, struct_ty_c)
|
||
# B5: vpi keeps @c.Attribute(t.attr.packed); cross-module injected structs must be packed too
|
||
# (<{...}>), else the referencing module computes GEPs with a non-packed layout (fields misread).
|
||
if _vpi_class_has_packed(class_cd.decorator_list) != 0: mlirlite.module_set_named_type_packed(mod, pool, struct_ty_c, 1)
|
||
# Register under the flat real_name: cross-module `Outer.Inner` lookups query the flat-name
|
||
# entry (same source as the definition-side StructGen registration name).
|
||
entry_c: GenTypes.StructEntry* = GenApi.register_struct(pool, real_name, struct_ty_c, src_sha1)
|
||
if entry_c is None: return 0
|
||
# B5: sync IsPacked on the registered entry; ensure_struct_def_in_module reads it to decide
|
||
# packing, otherwise the injected cross-module struct is non-packed (GEP layout mismatch).
|
||
if _vpi_class_has_packed(class_cd.decorator_list) != 0: entry_c.IsPacked = 1
|
||
# Also register fields into the StructEntry field table (name + type): a set Ty with an empty
|
||
# field table makes lookup_field_by_class fail by name, which silently zeroes reads.
|
||
afi_c: int = 0
|
||
for afi_c in range(field_count):
|
||
af_name_c: str = str(field_names_buf[afi_c])
|
||
af_ty_c: mlirlite.LLVMType* = (mlirlite.LLVMType*)(field_types_buf[afi_c])
|
||
GenApi.add_field(pool, entry_c, af_name_c, af_ty_c)
|
||
GenApi.add_visible_struct(pool, real_name)
|
||
return 1
|
||
|
||
|
||
# _inject_nested_class_declares - recursively register nested classes of a vpi class.
|
||
# Uses the flat name {outer}_{inner} (matching ClassGenDef._translate_nested_classdefs).
|
||
def _inject_nested_class_declares(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, class_cd: ast.ClassDef*, prefix_name: str, trans: HT.Translator*, src_sha1: str, imported_modules: str, from_imports: str) -> None:
|
||
"""Recursively register all nested ClassDefs of class_cd as flat-named types."""
|
||
if pool is None or mod is None or class_cd is None or prefix_name is None: return
|
||
children: list[ast.AST*]* = class_cd.children
|
||
if children is None: return
|
||
cn: sizet = len(children)
|
||
for ci in range(cn):
|
||
child_ast: ast.AST* = children.get(ci)
|
||
if child_ast is None: continue
|
||
if child_ast.kind() != ast.ASTKind.ClassDef: continue
|
||
child_cd: ast.ClassDef* = ast.ClassDef*(child_ast)
|
||
if child_cd is None or child_cd.name is None or child_cd.name[0] == '\0': continue
|
||
_inject_class_struct_declare(pool, mod, child_cd, child_cd.name, trans, src_sha1, imported_modules, from_imports, prefix_name)
|
||
|
||
|
||
# _ensure_full_value_types - register full definitions for cross-module value types.
|
||
# Opaque is not enough for llc (value structs/arrays/full def needed); pointer refs need only opaque.
|
||
def _ensure_full_value_types(mod: mlirlite.LLVMModule*, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*) -> None:
|
||
"""Recursively register full definitions for value structs and array element structs."""
|
||
if mod is None or pool is None or ty is None: return
|
||
match ty:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
full_se_h: GenTypes.StructEntry* = GenApi.find_struct_by_type(ty)
|
||
if full_se_h is not None and full_se_h.Ty is not None:
|
||
mlirlite.module_add_named_type(mod, pool, full_se_h.Ty)
|
||
# B5: sync the packed flag when injecting value types (matching
|
||
# ensure_struct_def_in_module); without it the referencing module emits a non-packed {..}.
|
||
if full_se_h.IsPacked != 0: mlirlite.module_set_named_type_packed(mod, pool, full_se_h.Ty, 1)
|
||
case mlirlite.LLVMType.Array(aelem_h, _):
|
||
if aelem_h is not None: _ensure_full_value_types(mod, pool, aelem_h)
|
||
case _:
|
||
pass
|
||
|
||
|
||
# _AggregateParamTy - unified rule for bare-struct (aggregate) param types; must stay in sync with
|
||
# FuncGenDef's extern branch and the call side. C ABI -> by value (or i64 fallback); internal -> Ptr(struct).
|
||
def _AggregateParamTy(mod: mlirlite.LLVMModule*, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*, is_c_abi: int) -> mlirlite.LLVMType*:
|
||
"""Bare-struct param type: C ABI by value / internal Ptr(struct)."""
|
||
if mod is None or pool is None or ty is None: return ty
|
||
if is_c_abi != 0:
|
||
# true opaque sentinel: fcount < 0
|
||
is_opaque_ap: int = 0
|
||
match ty:
|
||
case mlirlite.LLVMType.Struct(_, fcount_ap, _):
|
||
if fcount_ap < 0: is_opaque_ap = 1
|
||
case _:
|
||
pass
|
||
if is_opaque_ap == 0 and GenApi.find_struct_by_type(ty) is not None:
|
||
_ensure_full_value_types(mod, pool, ty)
|
||
return ty
|
||
return mlirlite.Int64(pool)
|
||
ptr_ty_ap: mlirlite.LLVMType* = mlirlite.Ptr(pool, ty)
|
||
mlirlite.module_ensure_opaque_for_type(mod, pool, ptr_ty_ap)
|
||
return ptr_ty_ap
|
||
|
||
|
||
# _inject_global_variable_declare - create a module-level global variable declare from a vpi AnnAssign.
|
||
# Needed so cross-module from-import reads of globals can find them via find_global_in_module.
|
||
def _inject_global_variable_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, aa: ast.AnnAssign*, trans: HT.Translator*, imported_modules: str, from_imports: str, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*, src_sha1: str) -> None:
|
||
"""Create a module-level global variable external declare from a vpi AnnAssign."""
|
||
if pool is None or mod is None or aa is None or trans is None: return
|
||
tgt_v: ast.AST* = aa.target
|
||
if tgt_v is None or tgt_v.kind() != ast.ASTKind.Name: return
|
||
nm_v: ast.Name* = ast.Name*(tgt_v)
|
||
if nm_v is None or nm_v.id is None: return
|
||
gname_v: str = nm_v.id
|
||
if gname_v[0] == '\0': return
|
||
# Self-injection guard (full-translation stage only): a module's own globals are emitted by
|
||
# TranslatorGenMixin.handle_module_level_var; re-injecting its own vpi would cause llc redefinition.
|
||
if trans is not None and trans._declare_only == 0 and trans.ModuleSha1 is not None and src_sha1 is not None:
|
||
if string.strcmp(src_sha1, trans.ModuleSha1) == 0: return
|
||
# Mangled name: prefix with the source module SHA1 so the define side and from-import lookup
|
||
# agree; a bare short name would remain undefined.
|
||
gv_name_m: str = gname_v
|
||
# CExport globals: the definition side uses the bare name (@gp), so the stub declare must too,
|
||
# else cross-module references emit @sha1.gp and ld.lld reports an undefined symbol.
|
||
is_gv_cexport: int = 0
|
||
if aa.annotation is not None: is_gv_cexport = GenApi.has_decorator_marker(aa.annotation, "CExport")
|
||
if src_sha1 is not None and src_sha1[0] != '\0' and is_gv_cexport == 0:
|
||
ml_v: sizet = string.strlen(src_sha1) + 1 + string.strlen(gname_v) + 1
|
||
gv_name_m = pool.alloc(ml_v)
|
||
if gv_name_m is not None: viperlib.snprintf(gv_name_m, ml_v, "%s.%s", src_sha1, gname_v)
|
||
# dedup: walk the module's global variable list (matching the mangled name)
|
||
cur_v: mlirlite.GlobalVariable* = mod.GlobalHead
|
||
while cur_v is not None:
|
||
if cur_v.Name is not None and string.strcmp(cur_v.Name, gv_name_m) == 0: return
|
||
cur_v = cur_v.Next
|
||
# resolve the annotation type (bare class -> Ptr(struct), CArray -> element pointer)
|
||
resolved_v: mlirlite.LLVMType* = None
|
||
if aa.annotation is not None:
|
||
resolved_v = GenApi.resolve_annotation_type(pool, aa.annotation, imported_modules, from_imports, trans)
|
||
if resolved_v is None:
|
||
return # type parse failed, skip: the call side has a fail-fast location
|
||
gv_ty_v: mlirlite.LLVMType* = resolved_v
|
||
match resolved_v:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
# Object classes (@t.Object/@t.CVTable: object-as-pointer) -> Ptr(struct), matching
|
||
# handle_module_level_var; plain t.CStruct value classes keep a value instance.
|
||
gv_entry_v: GenTypes.StructEntry* = GenApi.find_struct_by_type(resolved_v)
|
||
if gv_entry_v is not None and (gv_entry_v.HasNew != 0 or gv_entry_v.HasVTable != 0 or gv_entry_v.IsOOP != 0): gv_ty_v = mlirlite.Ptr(pool, resolved_v)
|
||
else: gv_ty_v = resolved_v
|
||
case mlirlite.LLVMType.Array(aelem_v, acount_v):
|
||
# Fixed-size arrays (CArray[T, N]) keep the full [N x T] type, matching
|
||
# handle_module_level_var; decaying to an element pointer would later read garbage.
|
||
if acount_v > 0: gv_ty_v = resolved_v
|
||
else:
|
||
# variable-length array (CArray[T, None]): element pointer (C array decay)
|
||
gv_ty_v = mlirlite.Ptr(pool, aelem_v)
|
||
case _:
|
||
gv_ty_v = resolved_v
|
||
# Ensure embedded structs have an opaque declaration (llc "use of undefined type"),
|
||
# matching TranslatorGenMixin; value types get full definitions via _ensure_full_value_types.
|
||
_ensure_full_value_types(mod, pool, gv_ty_v)
|
||
mlirlite.module_ensure_opaque_for_type(mod, pool, gv_ty_v)
|
||
gv_v: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, gv_name_m, gv_ty_v)
|
||
if gv_v is None: return
|
||
mlirlite.module_add_global(mod, gv_v)
|
||
gv_v.Linkage = "external"
|
||
|
||
|
||
# _inject_function_declare - create a declare for a single FunctionDef: detect CExport/CExtern/State,
|
||
# compute the mangled name, dedup via find_func_in_module, create the declare and register it.
|
||
def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, fd: ast.FunctionDef*, trans: HT.Translator*, src_sha1: str, imported_modules: str, from_imports: str, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*, funcs_ptr: GenTypes.FuncEntry*, max_funcs: int) -> None:
|
||
"""Create and inject a declare for a FunctionDef into the module."""
|
||
if fd.name is None: return
|
||
|
||
# Self-injection guard (full-translation stage only): a module's own top-level functions are
|
||
# emitted by FuncGenDef; re-injecting its own vpi would create a conflicting duplicate declare.
|
||
if trans is not None and trans._declare_only == 0 and trans.ModuleSha1 is not None:
|
||
if string.strcmp(src_sha1, trans.ModuleSha1) == 0: return
|
||
|
||
# detect CExtern/State/CExport markers (from the return-type annotation)
|
||
has_extern: int = 0
|
||
has_state: int = 0
|
||
has_export: int = 0
|
||
if fd.returns is not None:
|
||
has_extern = GenApi.has_decorator_marker(fd.returns, "CExtern")
|
||
has_state = GenApi.has_decorator_marker(fd.returns, "State")
|
||
has_export = GenApi.has_decorator_marker(fd.returns, "CExport")
|
||
# Bare-struct param rule (see _AggregateParamTy): C ABI externs (t.CExtern/t.State) by value;
|
||
# the rest (incl. t.CExport) keep Ptr(struct). Same source as FuncGenDef.is_extern_decl.
|
||
agg_is_c_abi: int = 0
|
||
if has_extern != 0 or has_state != 0: agg_is_c_abi = 1
|
||
|
||
# compute the mangled name:
|
||
# CExport/CExtern/State/main functions use the bare name, others get the source module SHA1 prefix
|
||
use_bare_name: int = 0
|
||
if has_extern != 0 or has_state != 0 or has_export != 0 or string.strcmp(fd.name, "main") == 0:
|
||
mangled_name: str = fd.name
|
||
use_bare_name = 1
|
||
else: mangled_name = GenApi._mangle_name_with_sha1(src_sha1, fd.name)
|
||
|
||
# This file's own bare-name declarations take priority: extern/state/export symbols carry no SHA1
|
||
# prefix, so an injected dependency of the same name would silently win. Only bare names matter.
|
||
if use_bare_name != 0 and GenApi.is_file_func(fd.name) != 0: return
|
||
|
||
# dedup: skip if the function already exists
|
||
existing: mlirlite.Function* = GenApi.find_func_in_module(mod, mangled_name)
|
||
if existing is not None: return
|
||
# also check the bare name (in non-bare mode a CExport function may have been registered another way)
|
||
if use_bare_name == 0:
|
||
existing_bare: mlirlite.Function* = GenApi.find_func_in_module(mod, fd.name)
|
||
if existing_bare is not None: return
|
||
|
||
# Infer the return type: prefer the cached return-annotation string (extracted right after tree
|
||
# parse) and resolve it directly, avoiding corrupted kind() from cross-module AST reuse.
|
||
ret_ty: mlirlite.LLVMType* = None
|
||
ret_annot_fd: str = _lookup_vpi_ret_annot(src_sha1, None, fd.name)
|
||
if ret_annot_fd is not None: ret_ty = _resolve_vpi_ret_annot_str(pool, ret_annot_fd, i32_ty, i8_ptr_ty)
|
||
if ret_ty is None and fd.returns is not None:
|
||
ret_ty = GenApi.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans)
|
||
# if the return type is a pure t.State (no real type), use Void
|
||
if ret_ty is None and fd.returns is not None:
|
||
# t.State/t.CExtern are storage-class decorator markers, not real return types. An annotation
|
||
# like "HANDLE | t.State" has a real type and must not map to Void; only pure markers do.
|
||
is_pure_marker_rt: int = 0
|
||
rk2_rt: int = fd.returns.kind()
|
||
if rk2_rt == ast.ASTKind.Name or rk2_rt == ast.ASTKind.Constant: is_pure_marker_rt = 1
|
||
elif rk2_rt == ast.ASTKind.Attribute:
|
||
pm_at: ast.Attribute* = ast.Attribute*(fd.returns)
|
||
if pm_at.attr is not None and (string.strcmp(pm_at.attr, "State") == 0 or string.strcmp(pm_at.attr, "CExtern") == 0 or string.strcmp(pm_at.attr, "CExport") == 0):
|
||
is_pure_marker_rt = 1
|
||
if is_pure_marker_rt != 0: ret_ty = mlirlite.Void(pool)
|
||
elif has_state != 0 or has_extern != 0 or has_export != 0:
|
||
# Has a real type but resolution failed: fall back to the external function return-type
|
||
# whitelist (e.g. FindFirstFileA/GetStdHandle -> i8* HANDLE) instead of truncating to i32.
|
||
ret_ty = GenApi.infer_external_func_ret_ty(pool, fd.name)
|
||
# [S4 fail-fast] A return annotation that does not resolve is not silently downgraded to i32;
|
||
# no annotation (fd.returns is None) is a legitimate default and keeps i32 below.
|
||
if ret_ty is None and fd.returns is not None:
|
||
GenApi.fatal_error(fd.returns, f"extern 声明 '{fd.name}' 返回类型注解解析失败", "NameError")
|
||
# default return i32
|
||
if ret_ty is None: ret_ty = i32_ty
|
||
|
||
# A value struct return type (e.g. Mat4) needs a full definition: declaring %Mat4 opaque makes
|
||
# llc report "use of undefined type". Register the full definition.
|
||
_ensure_full_value_types(mod, pool, ret_ty)
|
||
mlirlite.module_ensure_opaque_for_type(mod, pool, ret_ty)
|
||
|
||
# create the declare
|
||
func: mlirlite.Function* = mlirlite.create_declare(pool, mod, mangled_name, ret_ty)
|
||
if func is None: return
|
||
|
||
# extract params and add them to the declare
|
||
param_count: int = 0
|
||
fd_defaults: t.CPtr = None
|
||
fd_default_count: int = 0
|
||
if fd.args is not None:
|
||
args_node: ast.Arguments* = ast.Arguments*(fd.args)
|
||
if args_node.args is not None:
|
||
args_list: list[ast.AST*]* = args_node.args
|
||
param_count = len(args_list)
|
||
# Param-count upper-bound sentinel (fail-fast): a corrupted vpi tree node makes __len__()
|
||
# return a huge value, so add_param runs massively and the Params chain balloons.
|
||
if param_count < 0 or param_count > 128:
|
||
stdio.printf("[VPIS] 参数数异常: 函数名=%s param_count=%d(fd.args=%llu,tree 可能被覆盖,终止)\n", fd.name, param_count, uint64(fd.args))
|
||
# convergence point: longjmp back to the REPL loop in a REPL session (diagnostics printed)
|
||
VLogger.repl_abort()
|
||
pi: sizet
|
||
for pi in range(param_count):
|
||
ag: ast.Arg* = ast.Arg*(args_list.get(pi))
|
||
if ag is None: continue
|
||
pname: str = ag.arg
|
||
if pname is None: pname = "arg"
|
||
# resolve the param type
|
||
param_ty: mlirlite.LLVMType* = i8_ptr_ty
|
||
# Prefer the vpi text annotation cache (same source as return types): corrupted tree
|
||
# nodes make AST resolve return garbage types and mis-size cross-module declare params.
|
||
pseg_ty: mlirlite.LLVMType* = _resolve_vpi_param_annot_seg(pool, src_sha1, None, fd.name, pi, i32_ty, i8_ptr_ty)
|
||
if pseg_ty is not None:
|
||
match pseg_ty:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
# Bare struct param: C ABI by value / internal Ptr(struct)
|
||
# (criterion and fallback in _AggregateParamTy; keep the three sites in sync)
|
||
param_ty = _AggregateParamTy(mod, pool, pseg_ty, agg_is_c_abi)
|
||
case mlirlite.LLVMType.Array(aelem_ty, _):
|
||
param_ty = mlirlite.Ptr(pool, aelem_ty)
|
||
case _:
|
||
param_ty = pseg_ty
|
||
elif ag.annotation is not None:
|
||
resolved_ty: mlirlite.LLVMType* = GenApi.resolve_annotation_type(pool, ag.annotation, imported_modules, from_imports, trans)
|
||
if resolved_ty is not None:
|
||
# CArray param -> element pointer ty* (C array decay, matching the compiler side).
|
||
# Bare struct param -> _AggregateParamTy (same criterion).
|
||
match resolved_ty:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
param_ty = _AggregateParamTy(mod, pool, resolved_ty, agg_is_c_abi)
|
||
case mlirlite.LLVMType.Array(aelem_ty, _):
|
||
param_ty = mlirlite.Ptr(pool, aelem_ty)
|
||
case _:
|
||
param_ty = resolved_ty
|
||
else:
|
||
# [S4 fail-fast] An annotation that does not resolve is not silently downgraded
|
||
# to i8* (no annotation is a legitimate default and keeps i8*).
|
||
GenApi.fatal_error(ag.annotation, f"extern 声明 '{fd.name}' 形参 '{pname}' 类型注解解析失败", "NameError")
|
||
mlirlite.add_param(pool, func, param_ty, pname)
|
||
# Text-cache repair: a corrupted vpi tree truncates args, leaving too few declare params;
|
||
# complete them from the vpi text param annotations so declare matches define.
|
||
param_count = _repair_declare_params_from_text(pool, func, src_sha1, None, fd.name, param_count, i32_ty, i8_ptr_ty)
|
||
# *args varargs: set IsVarArg=1 so the call side applies C ABI promotions (i8/i16 -> i32,
|
||
# float -> double), avoiding misread %d/%x formats.
|
||
if args_node.vararg is not None: func.IsVarArg = 1
|
||
# Default params: vpi signatures include '= <default>'; register the defaults list so
|
||
# cross-module calls missing args fill from the table instead of reading stack garbage.
|
||
if args_node.defaults is not None:
|
||
fd_dlist: list[ast.AST*]* = args_node.defaults
|
||
fd_default_count = len(fd_dlist)
|
||
if fd_default_count < 0 or fd_default_count > 128:
|
||
# A corrupted args.defaults node makes len return a huge value, so
|
||
# _register_global_defaults would over-allocate and write out of bounds; skip it.
|
||
stdio.printf("[VPIS] 默认参数数异常: 函数名=%s default_count=%d(tree 可能被覆盖,跳过默认值注册)\n", fd.name, fd_default_count)
|
||
fd_defaults = None
|
||
fd_default_count = 0
|
||
else: fd_defaults = fd_dlist
|
||
|
||
# register into the function table (using the bare fd.name, not mangled_name)
|
||
if funcs_ptr is not None:
|
||
cur_count: int = trans._func_count
|
||
if GenApi.add_func_to_table(funcs_ptr, cur_count, fd.name, func, max_funcs, fd_defaults, fd_default_count, param_count, None, src_sha1) == 0:
|
||
trans._func_count = cur_count + 1
|
||
# Complete the global function-table Spec (params all added, matching FuncGenDef): without it
|
||
# find_func_entry_global() sees an empty Spec and vtable generation fails to find the type.
|
||
GenApi.update_global_func_spec(fd.name, func)
|
||
|
||
|
||
# _inject_method_declare - create a declare for a ClassDef method. Mangled name is
|
||
# {src_sha1}.{ClassName}.{method}; param types come from vpi annotations (self defaults to i8*).
|
||
def _inject_method_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, m_fd: ast.FunctionDef*, cls_name: str, trans: HT.Translator*, src_sha1: str, imported_modules: str, from_imports: str, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*, funcs_ptr: GenTypes.FuncEntry*, max_funcs: int) -> None:
|
||
"""Create and inject a declare for a ClassDef method into the module."""
|
||
if m_fd is None or m_fd.name is None or cls_name is None: return
|
||
if src_sha1 is None: return
|
||
|
||
# Self-injection guard (full-translation stage only): a module's own method defines are emitted
|
||
# by ClassGenVTable; re-injecting its own vpi would create conflicting duplicate declares.
|
||
if trans is not None and trans._declare_only == 0 and trans.ModuleSha1 is not None:
|
||
if string.strcmp(src_sha1, trans.ModuleSha1) == 0: return
|
||
|
||
# build the method lookup name: ClassName.method
|
||
lookup_name_len: sizet = string.strlen(cls_name) + 1 + string.strlen(m_fd.name) + 1
|
||
lookup_name: str = pool.alloc(lookup_name_len)
|
||
if lookup_name is None: return
|
||
viperlib.snprintf(lookup_name, lookup_name_len, "%s.%s", cls_name, m_fd.name)
|
||
|
||
# mangled name: {src_sha1}.{ClassName}.{method}
|
||
mangled_len: sizet = string.strlen(src_sha1) + 1 + lookup_name_len
|
||
mangled_name: str = pool.alloc(mangled_len)
|
||
if mangled_name is None: return
|
||
viperlib.snprintf(mangled_name, mangled_len, "%s.%s", src_sha1, lookup_name)
|
||
|
||
# dedup: skip if the method already exists
|
||
existing: mlirlite.Function* = GenApi.find_func_in_module(mod, mangled_name)
|
||
if existing is not None: return
|
||
|
||
# Infer the return type: prefer the cached return-annotation string (extracted right after tree
|
||
# parse); resolving m_fd.returns directly may yield garbage from a corrupted vpi tree node.
|
||
ret_ty: mlirlite.LLVMType* = None
|
||
ret_annot: str = _lookup_vpi_ret_annot(src_sha1, cls_name, m_fd.name)
|
||
if ret_annot is not None: ret_ty = _resolve_vpi_ret_annot_str(pool, ret_annot, i32_ty, i8_ptr_ty)
|
||
if ret_ty is None and m_fd.returns is not None:
|
||
ret_ty = GenApi.resolve_annotation_type(pool, m_fd.returns, imported_modules, from_imports, trans)
|
||
# infer __new__ without annotation as a class pointer (it returns a self pointer; i32 would truncate)
|
||
if ret_ty is None and string.strcmp(m_fd.name, "__new__") == 0:
|
||
cls_ty: mlirlite.LLVMType* = GenApi.get_struct_type(cls_name)
|
||
if cls_ty is not None: ret_ty = mlirlite.Ptr(pool, cls_ty)
|
||
# [S4 fail-fast] A return annotation that does not resolve is not silently downgraded to i32;
|
||
# unannotated __new__ / normal methods keep i32 as the legitimate default below.
|
||
if ret_ty is None and m_fd.returns is not None:
|
||
GenApi.fatal_error(m_fd.returns, f"extern 方法 '{cls_name}.{m_fd.name}' 返回类型注解解析失败", "NameError")
|
||
# Unannotated __init__ -> Void, matching the definition side (ClassGenVTable always uses Void);
|
||
# defaulting to i32 would split declare i32 / define void.
|
||
if ret_ty is None and string.strcmp(m_fd.name, "__init__") == 0: ret_ty = mlirlite.Void(pool)
|
||
if ret_ty is None: ret_ty = i32_ty
|
||
|
||
# create the declare
|
||
func: mlirlite.Function* = mlirlite.create_declare(pool, mod, mangled_name, ret_ty)
|
||
if func is None: return
|
||
|
||
# extract params and add them to the declare
|
||
param_count: int = 0
|
||
md_defaults: t.CPtr = None
|
||
md_default_count: int = 0
|
||
if m_fd.args is not None:
|
||
args_node: ast.Arguments* = ast.Arguments*(m_fd.args)
|
||
if args_node.args is not None:
|
||
args_list: list[ast.AST*]* = args_node.args
|
||
param_count = len(args_list)
|
||
# Param-count upper-bound sentinel (fail-fast): same cause as _inject_function_declare
|
||
# (a corrupted vpi tree node makes __len__() return a huge value).
|
||
if param_count < 0 or param_count > 128:
|
||
stdio.printf("[VPIS] 参数数异常: 方法名=%s.%s param_count=%d(m_fd.args=%llu,tree 可能被覆盖,终止)\n", cls_name, m_fd.name, param_count, uint64(m_fd.args))
|
||
# convergence point: longjmp back to the REPL loop in a REPL session (diagnostics printed)
|
||
VLogger.repl_abort()
|
||
pi: sizet
|
||
for pi in range(param_count):
|
||
ag: ast.Arg* = ast.Arg*(args_list.get(pi))
|
||
if ag is None: continue
|
||
pname: str = ag.arg
|
||
if pname is None: pname = "arg"
|
||
# self/cls (first param): cross-module declares use the class pointer (%"sha1.Class"*)
|
||
# instead of i8*, matching _translate_method's Ptr(StructTy) and removing ABI ambiguity.
|
||
if pi == 0 and (string.strcmp(pname, "self") == 0 or string.strcmp(pname, "cls") == 0):
|
||
cls_ty_sd: mlirlite.LLVMType* = GenApi.get_struct_type(cls_name)
|
||
if cls_ty_sd is not None:
|
||
mlirlite.add_param(pool, func, mlirlite.Ptr(pool, cls_ty_sd), pname)
|
||
continue
|
||
param_ty: mlirlite.LLVMType* = i8_ptr_ty
|
||
# Prefer the vpi text annotation cache (same source as return types): corrupted tree
|
||
# nodes mis-size declare params, causing trunc i64->i32 and a caller/callee mismatch.
|
||
pseg_ty: mlirlite.LLVMType* = _resolve_vpi_param_annot_seg(pool, src_sha1, cls_name, m_fd.name, pi, i32_ty, i8_ptr_ty)
|
||
if pseg_ty is not None:
|
||
match pseg_ty:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
# Bare struct param: methods are always internal TPV symbols (mangled
|
||
# {sha1}.{Class}.{method}, no C ABI define), so is_c_abi=0 -> Ptr(struct).
|
||
param_ty = _AggregateParamTy(mod, pool, pseg_ty, 0)
|
||
case mlirlite.LLVMType.Array(aelem_ty, _):
|
||
param_ty = mlirlite.Ptr(pool, aelem_ty)
|
||
case _:
|
||
param_ty = pseg_ty
|
||
elif ag.annotation is not None:
|
||
resolved_ty: mlirlite.LLVMType* = GenApi.resolve_annotation_type(pool, ag.annotation, imported_modules, from_imports, trans)
|
||
if resolved_ty is not None:
|
||
# CArray param -> element pointer ty* (C array decay, matching the compiler side).
|
||
# Bare struct param -> _AggregateParamTy (same criterion, is_c_abi=0).
|
||
match resolved_ty:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
param_ty = _AggregateParamTy(mod, pool, resolved_ty, 0)
|
||
case mlirlite.LLVMType.Array(aelem_ty, _):
|
||
param_ty = mlirlite.Ptr(pool, aelem_ty)
|
||
case _:
|
||
param_ty = resolved_ty
|
||
else:
|
||
# [S4 fail-fast] An annotation that does not resolve is not silently downgraded
|
||
# to i8* (no annotation is a legitimate default and keeps i8*).
|
||
GenApi.fatal_error(ag.annotation, f"extern 方法 '{cls_name}.{m_fd.name}' 形参 '{pname}' 类型注解解析失败", "NameError")
|
||
mlirlite.add_param(pool, func, param_ty, pname)
|
||
|
||
# Text-cache repair: a corrupted vpi tree truncates args, leaving too few declare params;
|
||
# complete them from the vpi text param annotations so declare matches define.
|
||
param_count = _repair_declare_params_from_text(pool, func, src_sha1, cls_name, m_fd.name, param_count, i32_ty, i8_ptr_ty)
|
||
|
||
# OOP iter protocol: the __next__ stub declare carries a hidden __stop_iter_flag__ (i1*),
|
||
# matching _translate_method's define; `for x in obj` calls __next__(self, flag).
|
||
if m_fd.name is not None and string.strcmp(m_fd.name, "__next__") == 0:
|
||
i1_ty_si: mlirlite.LLVMType* = mlirlite.Int1(pool)
|
||
if i1_ty_si is not None:
|
||
i1_ptr_ty_si: mlirlite.LLVMType* = mlirlite.Ptr(pool, i1_ty_si)
|
||
if i1_ptr_ty_si is not None: mlirlite.add_param(pool, func, i1_ptr_ty_si, "__stop_iter_flag__")
|
||
# Default params: register the defaults list so cross-module calls missing args fill from the
|
||
# table instead of reading stack garbage.
|
||
if m_fd.args is not None:
|
||
md_args_node: ast.Arguments* = ast.Arguments*(m_fd.args)
|
||
if md_args_node.defaults is not None:
|
||
md_dlist: list[ast.AST*]* = md_args_node.defaults
|
||
md_default_count = len(md_dlist)
|
||
if md_default_count < 0 or md_default_count > 128:
|
||
# Same as _inject_function_declare: a corrupted tree node makes defaults len huge,
|
||
# which would write out of bounds into perm_pool; skip registration.
|
||
stdio.printf("[VPIS] 默认参数数异常: 方法名=%s.%s default_count=%d(tree 可能被覆盖,跳过默认值注册)\n", cls_name, m_fd.name, md_default_count)
|
||
md_defaults = None
|
||
md_default_count = 0
|
||
else: md_defaults = md_dlist
|
||
|
||
# Register into the function table (bare ClassName.method, matching the caller's lookup_name).
|
||
# ParamCount excludes self (matching _translate_method's len(args) - 1), so default filling aligns.
|
||
if funcs_ptr is not None:
|
||
cur_count: int = trans._func_count
|
||
param_count_no_self: int = param_count
|
||
if param_count_no_self > 0: param_count_no_self -= 1
|
||
if GenApi.add_func_to_table(funcs_ptr, cur_count, lookup_name, func, max_funcs, md_defaults, md_default_count, param_count_no_self, None, src_sha1) == 0:
|
||
trans._func_count = cur_count + 1
|
||
# Complete the global function-table Spec (matching ClassGenVTable): otherwise
|
||
# find_func_entry_global().Spec is empty and vtable generation fails to find the method type.
|
||
GenApi.update_global_func_spec(lookup_name, func)
|
||
|
||
|
||
# _register_ctypedef_from_annassign - register a CTypedef from an AnnAssign node.
|
||
# Extracts the NAME and type expression from `NAME: t.CTypedef = Type` and registers it.
|
||
def _register_ctypedef_from_annassign(pool: memhub.MemBuddy*, aa: ast.AnnAssign*) -> None:
|
||
"""Extract and register a CTypedef from an AnnAssign node."""
|
||
if aa is None or pool is None: return
|
||
# extract the target name (must be a Name node)
|
||
if aa.target is None or aa.target.kind() != ast.ASTKind.Name: return
|
||
tgt: ast.Name* = ast.Name*(aa.target)
|
||
if tgt.id is None: return
|
||
# extract the type expression string (from the value AST node)
|
||
if aa.value is None: return
|
||
type_str: str = GenApi._get_type_name_from_annotation(pool, aa.value)
|
||
if type_str is None: return
|
||
# register the CTypedef
|
||
GenApi.register_ctypedef(pool, tgt.id, type_str)
|
||
|
||
|
||
# REPL: restore full layouts and vtables for includes classes, driven by {sha1}.meta.
|
||
# Only active when ReplStubLayoutMode != 0; layouts are validated against meta field/slot counts.
|
||
|
||
# session-level state (PermPool; valid during session init)
|
||
ReplStubLayoutMode: int = 0
|
||
ReplStubPool: memhub.MemBuddy* = None
|
||
ReplStubTempDir: str = None
|
||
ReplStubTable: sizet* = None
|
||
ReplStubCount: int = 0
|
||
ReplStubOk: int = 0
|
||
ReplStubVtOk: int = 0
|
||
ReplStubRefused: int = 0
|
||
ReplStubRefusedNoStub: int = 0 # refusal cause distribution (_REPL_SU_*): missing data (module has no {sha1}.meta)
|
||
ReplStubRefusedNoClass: int = 0 # refused: meta has the module but not this class
|
||
ReplStubRefusedOpaque: int = 0 # refused: the class is OPAQUE in meta
|
||
ReplStubRefusedParent: int = 0 # upstream no data: base unavailable/not registered
|
||
ReplStubNoStub: int = 0
|
||
ReplStubWarnLeft: int = 0
|
||
ReplStubTmpCount: int = -1
|
||
ReplStubTmpPacked: int = 0
|
||
ReplStubTmpArity: int = 0
|
||
|
||
|
||
def _repl_stub_dupstr(pool: memhub.MemBuddy*, src: str) -> str:
|
||
"""Deep-copy a string into the given pool."""
|
||
if src is None or pool is None: return None
|
||
ln: sizet = string.strlen(src)
|
||
dst: str = pool.alloc(ln + 1)
|
||
if dst is None: return None
|
||
string.strcpy(dst, src)
|
||
return dst
|
||
|
||
|
||
def _repl_stub_key(pool: memhub.MemBuddy*, sha1: str, cls: str) -> str:
|
||
"""Build "<sha1>.<class name>", matching the definition-side named struct exactly."""
|
||
if pool is None or sha1 is None or cls is None: return None
|
||
if sha1[0] == '\0' or cls[0] == '\0': return None
|
||
ln: sizet = string.strlen(sha1) + 1 + string.strlen(cls) + 1
|
||
buf: str = pool.alloc(ln)
|
||
if buf is None: return None
|
||
viperlib.snprintf(buf, ln, "%s.%s", sha1, cls)
|
||
return buf
|
||
|
||
|
||
def _repl_stub_path(sha1: str, ext: str) -> str:
|
||
"""Build the slice path <temp_dir>/<sub>/<sha1>.<ext> under the REPL temp dir (stdlib-allocated)."""
|
||
if ReplStubTempDir is None or sha1 is None: return None
|
||
td_len: sizet = string.strlen(ReplStubTempDir)
|
||
sh_len: sizet = string.strlen(sha1)
|
||
sub: str = Config.slice_subdir(sha1, Config.Sha1SliceLevel)
|
||
sub_len: sizet = 0
|
||
if sub is not None: sub_len = string.strlen(sub)
|
||
cap: sizet = td_len + sub_len + sh_len + 8
|
||
buf: str = stdlib.malloc(cap)
|
||
if buf is None: return None
|
||
if sub_len > 0: viperlib.snprintf(buf, cap, "%s/%s/%s.%s", ReplStubTempDir, sub, sha1, ext)
|
||
else: viperlib.snprintf(buf, cap, "%s/%s.%s", ReplStubTempDir, sha1, ext)
|
||
return buf
|
||
|
||
|
||
def _repl_stub_readtext(path: str) -> str:
|
||
"""Read stub text (stdlib.malloc, caller frees); None if missing or over the size cap."""
|
||
if path is None: return None
|
||
f: fileio.File* = fileio.File(path, fileio.MODE.R)
|
||
if f is None or f.closed: return None
|
||
sz: sizet = f.size()
|
||
if sz <= 0 or sz >= _REPL_STUB_TXT_CAP:
|
||
f.close()
|
||
return None
|
||
buf: str = stdlib.malloc(sz + 1)
|
||
if buf is None:
|
||
f.close()
|
||
return None
|
||
rd: LONG = f.read_all(buf, sz + 1)
|
||
f.close()
|
||
if rd <= 0:
|
||
stdlib.free(buf)
|
||
return None
|
||
if rd < LONG(sz + 1): buf[rd] = 0
|
||
return buf
|
||
|
||
|
||
# parse a decimal integer from txt[start, end) (skip leading spaces; no digits -> 0)
|
||
def _meta_atoi(txt: str, start: sizet, end: sizet) -> int:
|
||
i: sizet = start
|
||
while i < end and (txt[i] == ' ' or txt[i] == '\t'): i += 1
|
||
neg: int = 0
|
||
if i < end and txt[i] == '-':
|
||
neg = 1
|
||
i += 1
|
||
v: int = 0
|
||
while i < end and txt[i] >= '0' and txt[i] <= '9':
|
||
v = v * 10 + (int(txt[i]) - 48)
|
||
i += 1
|
||
if neg != 0: return -v
|
||
return v
|
||
|
||
|
||
# Scan {sha1}.meta text for `STRUCT <key> FIELDS <n> PACKED <0|1>` (or `OPAQUE`) and
|
||
# `VTABLE <key> SLOTS <n>`; results go to global scratch. Exact prefix match per line.
|
||
def _repl_meta_scan_layout(txt: str, key: str) -> None:
|
||
global ReplStubTmpCount, ReplStubTmpPacked, ReplStubTmpArity
|
||
ReplStubTmpCount = -1
|
||
ReplStubTmpPacked = 0
|
||
ReplStubTmpArity = 0
|
||
if txt is None or key is None: return
|
||
n: sizet = string.strlen(txt)
|
||
key_len: sizet = string.strlen(key)
|
||
if key_len == 0: return
|
||
i: sizet = 0
|
||
while i < n:
|
||
ls: sizet = i
|
||
while i < n and txt[i] != '\n': i += 1
|
||
le: sizet = i
|
||
if i < n: i += 1
|
||
if le > ls and txt[le - 1] == '\r': le -= 1
|
||
if ls + 7 <= le and string.strncmp(txt + ls, "STRUCT ", 7) == 0:
|
||
ns: sizet = ls + 7
|
||
ne: sizet = ns + key_len
|
||
if ne > le or string.strncmp(txt + ns, key, key_len) != 0: continue
|
||
if ne >= le or txt[ne] != ' ': continue
|
||
# OPAQUE
|
||
if ne + 7 <= le and string.strncmp(txt + ne + 1, "OPAQUE", 6) == 0:
|
||
ReplStubTmpCount = -2
|
||
continue
|
||
# FIELDS <n> PACKED <0|1>
|
||
if ne + 8 <= le and string.strncmp(txt + ne + 1, "FIELDS ", 7) == 0:
|
||
ReplStubTmpCount = _meta_atoi(txt, ne + 8, le)
|
||
ps: sizet = ne + 8
|
||
while ps < le and txt[ps] >= '0' and txt[ps] <= '9': ps += 1
|
||
if ps + 8 <= le and string.strncmp(txt + ps + 1, "PACKED ", 7) == 0: ReplStubTmpPacked = _meta_atoi(txt, ps + 8, le)
|
||
continue
|
||
if ls + 7 <= le and string.strncmp(txt + ls, "VTABLE ", 7) == 0:
|
||
vs: sizet = ls + 7
|
||
ve: sizet = vs + key_len
|
||
if ve > le or string.strncmp(txt + vs, key, key_len) != 0: continue
|
||
if ve >= le or txt[ve] != ' ': continue
|
||
if ve + 7 <= le and string.strncmp(txt + ve + 1, "SLOTS ", 6) == 0: ReplStubTmpArity = _meta_atoi(txt, ve + 7, le)
|
||
continue
|
||
return
|
||
|
||
|
||
def ReplStubLayoutBegin(temp_dir: str) -> int:
|
||
"""Enable session-level stub layout restore (called once before registering includes classes); 0=success."""
|
||
global ReplStubLayoutMode, ReplStubPool, ReplStubTempDir, ReplStubTable
|
||
global ReplStubCount, ReplStubOk, ReplStubVtOk, ReplStubRefused, ReplStubNoStub
|
||
global ReplStubWarnLeft
|
||
global ReplStubRefusedNoStub, ReplStubRefusedNoClass, ReplStubRefusedOpaque, ReplStubRefusedParent
|
||
pool: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
||
if pool is None or temp_dir is None: return 1
|
||
ReplStubPool = pool
|
||
ReplStubTempDir = _repl_stub_dupstr(pool, temp_dir)
|
||
if ReplStubTempDir is None: return 1
|
||
ReplStubTable = pool.alloc(8 * _REPL_STUB_CLS_MAX)
|
||
if ReplStubTable is None: return 1
|
||
string.memset(ReplStubTable, 0, 8 * _REPL_STUB_CLS_MAX)
|
||
ReplStubCount = 0
|
||
ReplStubOk = 0
|
||
ReplStubVtOk = 0
|
||
ReplStubRefused = 0
|
||
ReplStubRefusedNoStub = 0
|
||
ReplStubRefusedNoClass = 0
|
||
ReplStubRefusedOpaque = 0
|
||
ReplStubRefusedParent = 0
|
||
ReplStubNoStub = 0
|
||
ReplStubWarnLeft = _REPL_STUB_WARN_MAX
|
||
ReplStubLayoutMode = 1
|
||
return 0
|
||
|
||
|
||
def _repl_stub_at(i: int) -> ReplStubClass*:
|
||
"""Return the i-th class-table entry, or None."""
|
||
if ReplStubTable is None or i < 0 or i >= ReplStubCount: return None
|
||
addr: sizet = ReplStubTable[i]
|
||
if addr == 0: return None
|
||
return ReplStubClass*(uint64(addr))
|
||
|
||
|
||
def _repl_stub_find(key: str) -> ReplStubClass*:
|
||
"""Look up a class-table entry by "<sha1>.<class name>"; None if not found."""
|
||
if key is None: return None
|
||
i: int
|
||
for i in range(ReplStubCount):
|
||
e: ReplStubClass* = _repl_stub_at(i)
|
||
if e is None or e.Key is None: continue
|
||
if string.strcmp(e.Key, key) == 0: return e
|
||
return None
|
||
|
||
|
||
def _repl_stub_find_base(e: ReplStubClass*, bi: int) -> ReplStubClass*:
|
||
"""Find a base-class entry: same-module SHA1 exact match first, then first bare-name match."""
|
||
if e is None or e.Bases is None or bi < 0 or bi >= e.BaseCount: return None
|
||
pname: str = rawptr(e.Bases[bi])
|
||
if pname is None: return None
|
||
i: int
|
||
for i in range(ReplStubCount):
|
||
pe: ReplStubClass* = _repl_stub_at(i)
|
||
if pe is None or pe.ClassName is None: continue
|
||
if string.strcmp(pe.ClassName, pname) != 0: continue
|
||
if e.Sha1 is not None and pe.Sha1 is not None:
|
||
if string.strcmp(pe.Sha1, e.Sha1) == 0: return pe
|
||
for i in range(ReplStubCount):
|
||
pe2: ReplStubClass* = _repl_stub_at(i)
|
||
if pe2 is None or pe2.ClassName is None: continue
|
||
if string.strcmp(pe2.ClassName, pname) == 0: return pe2
|
||
return None
|
||
|
||
|
||
def _repl_stub_has_name(arr: sizet*, cnt: int, name: str) -> int:
|
||
"""Check whether the name is already in the list (first-declaration wins)."""
|
||
if arr is None or name is None: return 0
|
||
i: int
|
||
for i in range(cnt):
|
||
addr: sizet = arr[i]
|
||
if addr == 0: continue
|
||
ex: str = rawptr(addr)
|
||
if ex is not None and string.strcmp(ex, name) == 0: return 1
|
||
return 0
|
||
|
||
|
||
def _repl_stub_deco_flags(deco_list: list[ast.AST*]*) -> int:
|
||
"""Return class/method decorator flags (bit 0=@t.NoVTable, bit 1=@t.CVTable)."""
|
||
if deco_list is None: return 0
|
||
flags: int = 0
|
||
dn: sizet = len(deco_list)
|
||
di: sizet
|
||
for di in range(dn):
|
||
deco: ast.AST* = deco_list.get(di)
|
||
if deco is None: continue
|
||
nm: str = None
|
||
k: int = deco.kind()
|
||
if k == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(deco)
|
||
if at.attr is not None and at.value is not None and at.value.kind() == ast.ASTKind.Name:
|
||
vn: ast.Name* = ast.Name*(at.value)
|
||
if vn.id is not None and string.strcmp(vn.id, "t") == 0: nm = at.attr
|
||
elif k == ast.ASTKind.Name:
|
||
nn: ast.Name* = ast.Name*(deco)
|
||
nm = nn.id
|
||
if nm is None: continue
|
||
if string.strcmp(nm, "NoVTable") == 0: flags = flags | 1
|
||
elif string.strcmp(nm, "CVTable") == 0: flags = flags | 2
|
||
return flags
|
||
|
||
|
||
def _repl_stub_is_virtual(e: ReplStubClass*, mi: int, first: ReplStubClass*) -> int:
|
||
"""Return whether this class's mi-th method goes into the vtable."""
|
||
if e is None or e.MethodFlags is None or mi < 0 or mi >= e.MethodCount: return 0
|
||
mf: int = int(e.MethodFlags[mi])
|
||
if (mf & 1) != 0: return 0
|
||
if (mf & 2) != 0: return 1
|
||
if (e.Flags & _REPL_SF_CVTABLE) != 0: return 1
|
||
if first is not None:
|
||
if first.IsNoVTable != 0: return 0
|
||
return first.HasVt
|
||
return 0
|
||
|
||
|
||
def _repl_stub_warn_refuse(e: ReplStubClass*, why: str, a: int, b: int) -> None:
|
||
"""Warn on a criterion rejection: stub field count a differs from flattened+vtable inference b."""
|
||
global ReplStubWarnLeft, ReplStubRefused
|
||
ReplStubRefused += 1
|
||
if ReplStubWarnLeft <= 0: return
|
||
ReplStubWarnLeft -= 1
|
||
stdio.printf("includes 类布局: %s.%s 布局不可信 ⇒ 明确拒绝(原因=判据拒绝:%s;" "存根字段数=%d ≠ 展平字段数+虚表位=%d)。该类字段访问被明确拒绝" "(方法调用不受影响)\n", e.Sha1, e.ClassName, why, a, b)
|
||
|
||
|
||
def _repl_stub_warn_nodata(e: ReplStubClass*, cause: int) -> None:
|
||
"""Warn on a missing-data refusal: no comparison was possible, so print the data sources instead."""
|
||
global ReplStubWarnLeft, ReplStubRefused
|
||
global ReplStubRefusedNoStub, ReplStubRefusedNoClass
|
||
global ReplStubRefusedOpaque, ReplStubRefusedParent
|
||
ReplStubRefused += 1
|
||
if cause == _REPL_SU_NOSTUB: ReplStubRefusedNoStub += 1
|
||
elif cause == _REPL_SU_NOCLASS: ReplStubRefusedNoClass += 1
|
||
elif cause == _REPL_SU_OPAQUE: ReplStubRefusedOpaque += 1
|
||
else: ReplStubRefusedParent += 1
|
||
if ReplStubWarnLeft <= 0: return
|
||
ReplStubWarnLeft -= 1
|
||
if cause == _REPL_SU_NOSTUB:
|
||
# missing data: not even module layout metadata (LayoutCount = -1 is just a "not read" sentinel)
|
||
stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『数据缺失』:" "模块 %s 没有 {sha1}.meta 布局元数据;meta 字段数=无 / 推断字段数=未计算 ⇒ " "两侧都没数据,『未做比较』,不是字段数不符)。该类字段访问被" "拒绝(方法调用不受影响);补上该模块 meta(同一工程先跑一次批处理," "Phase A/B 会落盘 {sha1}.meta)后用同一 --project 重跑 REPL 即恢复\n", e.Sha1, e.ClassName, e.Sha1)
|
||
elif cause == _REPL_SU_OPAQUE:
|
||
# refused: the key is OPAQUE in meta (no body, so no field count to compare)
|
||
stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『判据拒绝』:" "meta %s.meta 里键 %s 为 OPAQUE 声明(无 body);meta 字段数=不可得 / " "推断字段数=未计算 ⇒ 『未做比较』)。该类字段访问被拒绝" "(方法调用不受影响)\n", e.Sha1, e.ClassName, e.Sha1, e.Key)
|
||
else:
|
||
# refused: meta exists but has no STRUCT line for this class (LayoutCount = -1 is a "not found" sentinel)
|
||
stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『判据拒绝』:" "meta %s.meta 存在但其中没有键 %s 的 `STRUCT … FIELDS n / OPAQUE` 记录;" "meta 字段数=未找到 / 推断字段数=未计算 ⇒ 『未做比较』)。该类字段访问" "被拒绝(方法调用不受影响);若该类确实定义于该模块,说明 meta 陈旧/" "不完整,请重建\n", e.Sha1, e.ClassName, e.Sha1, e.Key)
|
||
|
||
|
||
def _repl_stub_warn_parent(e: ReplStubClass*, detail: str, pname: str) -> None:
|
||
"""Warn on an upstream-no-data refusal: the base layout is unavailable or unregistered."""
|
||
global ReplStubWarnLeft, ReplStubRefused, ReplStubRefusedParent
|
||
ReplStubRefused += 1
|
||
ReplStubRefusedParent += 1
|
||
if ReplStubWarnLeft <= 0: return
|
||
ReplStubWarnLeft -= 1
|
||
stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『上游无数据』:%s," "父类=%s;父类字段与虚表位无法展平 ⇒ 本类未做字段数比较)。该类字段访问" "被拒绝(方法调用不受影响)\n", e.Sha1, e.ClassName, detail, pname)
|
||
|
||
|
||
def _repl_stub_try(e: ReplStubClass*, mod: mlirlite.LLVMModule*) -> int:
|
||
"""Validate and register the full layout from the stub: 1=registered / 0=base pending / -1=refused."""
|
||
global ReplStubOk, ReplStubVtOk, ReplStubWarnLeft
|
||
if e is None: return -1
|
||
if e.State == _REPL_SS_OK: return 1
|
||
if e.State == _REPL_SS_REFUSED: return -1
|
||
if e.State != _REPL_SS_PENDING: return -1
|
||
pool: memhub.MemBuddy* = ReplStubPool
|
||
if pool is None:
|
||
e.State = _REPL_SS_REFUSED
|
||
return -1
|
||
# (1) base classes must be ready (not ready -> defer; refused -> this layout is untrusted)
|
||
bi: int
|
||
for bi in range(e.BaseCount):
|
||
pe: ReplStubClass* = _repl_stub_find_base(e, bi)
|
||
if pe is None: return 0
|
||
if pe.State == _REPL_SS_PENDING: return 0
|
||
if pe.State == _REPL_SS_REFUSED:
|
||
e.State = _REPL_SS_REFUSED
|
||
_repl_stub_warn_parent(e, "父类布局不可信", pe.ClassName)
|
||
return -1
|
||
if pe.State == _REPL_SS_EMPTY and pe.OwnFail != 0:
|
||
# The base itself had field-annotation failures, so its Count is only partially parsed:
|
||
# the flattened field count is unusable, so neither compare nor allow it (M4.2: refuse).
|
||
e.State = _REPL_SS_REFUSED
|
||
_repl_stub_warn_parent(e, "父类的字段类型未全部解析(其字段数不完整)", pe.ClassName)
|
||
return -1
|
||
first: ReplStubClass* = None
|
||
if e.BaseCount > 0: first = _repl_stub_find_base(e, 0)
|
||
# (2) HasVTable (transcribed from ClassGenBase._detect_vtable_status)
|
||
hv: int = 0
|
||
if (e.Flags & _REPL_SF_CVTABLE) != 0: hv = 1
|
||
elif (e.Flags & _REPL_SF_NOVTABLE) != 0: hv = 1 if (e.Flags & _REPL_SF_ANYFUNC) != 0 else 0
|
||
elif first is not None:
|
||
if first.IsNoVTable != 0: hv = 1 if (e.Flags & _REPL_SF_ANYFUNC) != 0 else 0
|
||
else: hv = first.HasVt
|
||
else: hv = 1 if (e.Flags & _REPL_SF_ANYFUNC) != 0 else 0
|
||
# (3) field flattening (base fields first + own fields, deduped by short name, first wins)
|
||
cn: int = 0
|
||
for bi in range(e.BaseCount):
|
||
pe: ReplStubClass* = _repl_stub_find_base(e, bi)
|
||
fi: int
|
||
for fi in range(pe.Count):
|
||
if cn >= _REPL_STUB_FIELD_MAX: break
|
||
nm: str = rawptr(pe.Names[fi])
|
||
if nm is None: continue
|
||
if _repl_stub_has_name(e.Names, cn, nm) != 0: continue
|
||
e.Names[cn] = pe.Names[fi]
|
||
e.Types[cn] = pe.Types[fi]
|
||
cn += 1
|
||
oi: int
|
||
for oi in range(e.OwnCount):
|
||
if cn >= _REPL_STUB_FIELD_MAX: break
|
||
nm2: str = rawptr(e.OwnNames[oi])
|
||
if nm2 is None: continue
|
||
if _repl_stub_has_name(e.Names, cn, nm2) != 0: continue
|
||
e.Names[cn] = e.OwnNames[oi]
|
||
e.Types[cn] = e.OwnTypes[oi]
|
||
cn += 1
|
||
e.Count = cn
|
||
e.HasVt = hv
|
||
# (4) layout check (reached only when both sides are usable): stub field count ==
|
||
# __vtable__ (if any) + flattened field count.
|
||
if e.LayoutCount != cn + hv:
|
||
e.State = _REPL_SS_REFUSED
|
||
_repl_stub_warn_refuse(e, "存根字段数与字段名单不符", e.LayoutCount, cn + hv)
|
||
return -1
|
||
# (5) virtual method short-name order (base virtuals first + own eligible methods in class order)
|
||
vn: int = 0
|
||
if hv == 1:
|
||
for bi in range(e.BaseCount):
|
||
pe: ReplStubClass* = _repl_stub_find_base(e, bi)
|
||
vi: int
|
||
for vi in range(pe.VtCount):
|
||
if vn >= _REPL_STUB_VT_MAX: break
|
||
mn: str = rawptr(pe.VtNames[vi])
|
||
if mn is None: continue
|
||
if _repl_stub_has_name(e.VtNames, vn, mn) != 0: continue
|
||
e.VtNames[vn] = pe.VtNames[vi]
|
||
vn += 1
|
||
mi: int
|
||
for mi in range(e.MethodCount):
|
||
if vn >= _REPL_STUB_VT_MAX: break
|
||
mn2: str = rawptr(e.MethodNames[mi])
|
||
if mn2 is None: continue
|
||
if string.strcmp(mn2, "__init__") == 0: continue
|
||
if string.strcmp(mn2, "__before_init__") == 0: continue
|
||
if string.strcmp(mn2, "__new__") == 0: continue
|
||
if _repl_stub_has_name(e.VtNames, vn, mn2) != 0: continue
|
||
if _repl_stub_is_virtual(e, mi, first) == 0: continue
|
||
e.VtNames[vn] = e.MethodNames[mi]
|
||
vn += 1
|
||
e.VtCount = vn
|
||
e.VtTrust = 0
|
||
if hv == 1 and vn > 0:
|
||
if vn == e.VtArity: e.VtTrust = 1
|
||
elif ReplStubWarnLeft > 0:
|
||
ReplStubWarnLeft -= 1
|
||
stdio.printf("includes 类布局: %s.%s 的虚表槽数与存根不符(%d vs %d)" "——该类不做虚分派(退化为直接调用,会话继续)\n", e.Sha1, e.ClassName, vn, e.VtArity)
|
||
# (6) build the LLVM struct (named "%<sha1>.<ClassName>", same as the definition side)
|
||
total: int = cn + hv
|
||
i8_ptr: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
|
||
if i8_ptr is None: return 0
|
||
first_node: mlirlite.ParamNode* = None
|
||
prev_node: mlirlite.ParamNode* = None
|
||
if hv == 1:
|
||
nd0: mlirlite.ParamNode* = pool.alloc(mlirlite.ParamNode.__sizeof__())
|
||
if nd0 is None: return 0
|
||
string.memset(nd0, 0, mlirlite.ParamNode.__sizeof__())
|
||
nd0.Ty = i8_ptr
|
||
first_node = nd0
|
||
prev_node = nd0
|
||
fi2: int
|
||
for fi2 in range(cn):
|
||
fty: mlirlite.LLVMType* = (mlirlite.LLVMType*)(e.Types[fi2])
|
||
if fty is None: continue
|
||
nd: mlirlite.ParamNode* = pool.alloc(mlirlite.ParamNode.__sizeof__())
|
||
if nd is None: continue
|
||
string.memset(nd, 0, mlirlite.ParamNode.__sizeof__())
|
||
nd.Ty = fty
|
||
if first_node is None: first_node = nd
|
||
if prev_node is not None: prev_node.Next = nd
|
||
prev_node = nd
|
||
struct_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, first_node, total, e.Key)
|
||
if struct_ty is None: return 0
|
||
# (7) register (under StdlibAlloc=1 registration goes to PermPool, surviving across fragments)
|
||
entry: GenTypes.StructEntry* = GenApi.register_struct(pool, e.ClassName, struct_ty, e.Sha1)
|
||
if entry is None:
|
||
e.State = _REPL_SS_REFUSED
|
||
return -1
|
||
if mod is not None: mlirlite.module_add_named_type(mod, pool, struct_ty)
|
||
if (e.Flags & _REPL_SF_PACKED) != 0:
|
||
entry.IsPacked = 1
|
||
if mod is not None: mlirlite.module_set_named_type_packed(mod, pool, struct_ty, 1)
|
||
if hv == 1:
|
||
entry.HasVTable = 1
|
||
GenApi.add_field(pool, entry, "__vtable__", i8_ptr)
|
||
fi3: int
|
||
for fi3 in range(cn):
|
||
fname: str = rawptr(e.Names[fi3])
|
||
fty3: mlirlite.LLVMType* = (mlirlite.LLVMType*)(e.Types[fi3])
|
||
if fname is None or fty3 is None: continue
|
||
GenApi.add_field(pool, entry, fname, fty3)
|
||
if e.IsNoVTable != 0: entry.IsNoVTable = 1
|
||
entry.IsOOP = e.IsOOP
|
||
entry.HasInit = e.HasInit
|
||
entry.HasNew = e.HasNew
|
||
# parent names (multiple-inheritance array; declaration order, for vtable/MRO/field-inheritance queries)
|
||
if e.BaseCount > 0:
|
||
arr: sizet* = pool.alloc(8 * e.BaseCount)
|
||
if arr is not None:
|
||
string.memset(arr, 0, 8 * e.BaseCount)
|
||
bi3: int
|
||
for bi3 in range(e.BaseCount):
|
||
bnm: str = rawptr(e.Bases[bi3])
|
||
bdup: str = _repl_stub_dupstr(pool, bnm)
|
||
if bdup is not None: arr[bi3] = sizet(bdup)
|
||
entry.ParentNames = arr
|
||
entry.ParentCount = e.BaseCount
|
||
pn: str = rawptr(e.Bases[0])
|
||
entry.ParentName = _repl_stub_dupstr(pool, pn)
|
||
if hv == 1 and e.VtTrust == 1 and e.VtCount > 0:
|
||
entry.VTableMethods = e.VtNames
|
||
entry.VTableMethodCount = e.VtCount
|
||
ReplStubVtOk += 1
|
||
e.State = _REPL_SS_OK
|
||
ReplStubOk += 1
|
||
return 1
|
||
|
||
|
||
def _repl_stub_add_class(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, cd: ast.ClassDef*, sha1: str, txt: str, have_stub: int, trans: HT.Translator*, imported_modules: str, from_imports: str) -> int:
|
||
"""Collect layout elements of one vpi ClassDef and try to register it; 1=registered/deferred, 0=skipped."""
|
||
global ReplStubCount
|
||
if pool is None or cd is None or cd.name is None or ReplStubTable is None: return 0
|
||
if cd.name[0] == '\0' or ReplStubCount >= _REPL_STUB_CLS_MAX: return 0
|
||
key: str = _repl_stub_key(pool, sha1, cd.name)
|
||
if key is None: return 0
|
||
if _repl_stub_find(key) is not None: return 0
|
||
e: ReplStubClass* = pool.alloc(ReplStubClass.__sizeof__())
|
||
if e is None: return 0
|
||
string.memset(e, 0, ReplStubClass.__sizeof__())
|
||
e.Key = key
|
||
e.ClassName = _repl_stub_dupstr(pool, cd.name)
|
||
e.Sha1 = _repl_stub_dupstr(pool, sha1)
|
||
e.OwnNames = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
|
||
e.OwnTypes = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
|
||
e.MethodNames = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
|
||
e.MethodFlags = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
|
||
e.Bases = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
|
||
e.Names = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
|
||
e.Types = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
|
||
e.VtNames = pool.alloc(8 * _REPL_STUB_VT_MAX)
|
||
if e.OwnNames is None or e.OwnTypes is None or e.MethodNames is None or e.MethodFlags is None or e.Bases is None or e.Names is None or e.Types is None or e.VtNames is None: return 0
|
||
string.memset(e.OwnNames, 0, 8 * _REPL_STUB_FIELD_MAX)
|
||
string.memset(e.OwnTypes, 0, 8 * _REPL_STUB_FIELD_MAX)
|
||
string.memset(e.MethodNames, 0, 8 * _REPL_STUB_FIELD_MAX)
|
||
string.memset(e.MethodFlags, 0, 8 * _REPL_STUB_FIELD_MAX)
|
||
string.memset(e.Bases, 0, 8 * _REPL_STUB_FIELD_MAX)
|
||
string.memset(e.Names, 0, 8 * _REPL_STUB_FIELD_MAX)
|
||
string.memset(e.Types, 0, 8 * _REPL_STUB_FIELD_MAX)
|
||
string.memset(e.VtNames, 0, 8 * _REPL_STUB_VT_MAX)
|
||
# class decorators
|
||
cf: int = _repl_stub_deco_flags(cd.decorator_list)
|
||
if (cf & 2) != 0: e.Flags = e.Flags | _REPL_SF_CVTABLE
|
||
if (cf & 1) != 0:
|
||
e.Flags = e.Flags | _REPL_SF_NOVTABLE
|
||
e.IsNoVTable = 1
|
||
if _vpi_class_has_packed(cd.decorator_list) != 0: e.Flags = e.Flags | _REPL_SF_PACKED
|
||
# real bases (reuse ClassGenBase._collect_parents: alias resolution + marker removal)
|
||
pb: sizet* = pool.alloc(8 * GenTypes.PARENT_MAX)
|
||
bcnt: int = 0
|
||
if pb is not None and GenApi._g_gen is not None:
|
||
string.memset(pb, 0, 8 * GenTypes.PARENT_MAX)
|
||
bcnt = GenApi._g_gen.ClassGenBase._collect_parents(pool, cd, pb, GenTypes.PARENT_MAX, trans)
|
||
if bcnt > 0:
|
||
if bcnt > _REPL_STUB_FIELD_MAX: bcnt = _REPL_STUB_FIELD_MAX
|
||
bi: int
|
||
for bi in range(bcnt):
|
||
bnm: str = rawptr(pb[bi])
|
||
bdup: str = _repl_stub_dupstr(pool, bnm)
|
||
if bdup is not None: e.Bases[bi] = sizet(bdup)
|
||
e.BaseCount = bcnt
|
||
# scan the class body: fields (AnnAssign) + methods (FunctionDef)
|
||
children: list[ast.AST*]* = cd.children
|
||
own_fail: int = 0
|
||
if children is not None:
|
||
cn: sizet = len(children)
|
||
ci: sizet
|
||
for ci in range(cn):
|
||
ch2: ast.AST* = children.get(ci)
|
||
if ch2 is None: continue
|
||
k2: int = ch2.kind()
|
||
if k2 == ast.ASTKind.AnnAssign:
|
||
if e.OwnCount >= _REPL_STUB_FIELD_MAX: continue
|
||
aa2: ast.AnnAssign* = ast.AnnAssign*(ch2)
|
||
if aa2 is None or aa2.target is None or aa2.annotation is None: continue
|
||
if aa2.target.kind() != ast.ASTKind.Name: continue
|
||
tg: ast.Name* = ast.Name*(aa2.target)
|
||
if tg.id is None or tg.id[0] == '\0': continue
|
||
fty2: mlirlite.LLVMType* = GenApi.resolve_annotation_type(pool, aa2.annotation, imported_modules, from_imports, trans, 0)
|
||
if fty2 is None:
|
||
own_fail = 1
|
||
continue
|
||
fdup: str = _repl_stub_dupstr(pool, tg.id)
|
||
if fdup is None:
|
||
own_fail = 1
|
||
continue
|
||
e.OwnNames[e.OwnCount] = sizet(fdup)
|
||
e.OwnTypes[e.OwnCount] = sizet(fty2)
|
||
e.OwnCount += 1
|
||
continue
|
||
if k2 == ast.ASTKind.FunctionDef:
|
||
fd2: ast.FunctionDef* = ast.FunctionDef*(ch2)
|
||
if fd2 is None or fd2.name is None or fd2.name[0] == '\0': continue
|
||
mf2: int = _repl_stub_deco_flags(fd2.decorator_list)
|
||
if (mf2 & 2) != 0: e.Flags = e.Flags | _REPL_SF_ANYFUNC
|
||
if string.strcmp(fd2.name, "__init__") == 0: e.HasInit = 1
|
||
elif string.strcmp(fd2.name, "__new__") == 0: e.HasNew = 1
|
||
e.IsOOP = 1
|
||
if e.MethodCount < _REPL_STUB_FIELD_MAX:
|
||
e.MethodNames[e.MethodCount] = sizet(_repl_stub_dupstr(pool, fd2.name))
|
||
e.MethodFlags[e.MethodCount] = sizet(mf2)
|
||
e.MethodCount += 1
|
||
continue
|
||
# stub metadata (LayoutCount stays at the "not found" sentinel when missing; have_stub distinguishes causes)
|
||
e.LayoutCount = _REPL_STUB_NOCOUNT_NOCLASS
|
||
if txt is not None:
|
||
_repl_meta_scan_layout(txt, key)
|
||
e.LayoutCount = ReplStubTmpCount
|
||
e.VtArity = ReplStubTmpArity
|
||
if ReplStubTmpPacked != 0: e.Flags = e.Flags | _REPL_SF_PACKED
|
||
# state decision
|
||
if own_fail != 0:
|
||
# Field-type resolution failed: defer to the existing vpi injection (which skips the class);
|
||
# OwnFail records that OwnCount is only partially parsed and unusable for subclass flattening.
|
||
e.OwnFail = 1
|
||
e.State = _REPL_SS_EMPTY
|
||
e.Count = e.OwnCount
|
||
e.Names = e.OwnNames
|
||
e.Types = e.OwnTypes
|
||
elif e.LayoutCount >= 0: e.State = _REPL_SS_PENDING
|
||
elif e.BaseCount == 0 and (e.Flags & (_REPL_SF_CVTABLE | _REPL_SF_ANYFUNC)) == 0:
|
||
# no base, no vtable -> this class's own fields are the full layout (equivalent to the existing rule)
|
||
e.State = _REPL_SS_EMPTY
|
||
e.Count = e.OwnCount
|
||
e.Names = e.OwnNames
|
||
e.Types = e.OwnTypes
|
||
else:
|
||
# No layout data -> explicitly refuse (no StructEntry). Report the causes separately:
|
||
# missing module meta (recoverable) vs OPAQUE/no STRUCT record (untrusted).
|
||
e.State = _REPL_SS_REFUSED
|
||
if have_stub == 0: _repl_stub_warn_nodata(e, _REPL_SU_NOSTUB)
|
||
elif e.LayoutCount == _REPL_STUB_NOCOUNT_OPAQUE: _repl_stub_warn_nodata(e, _REPL_SU_OPAQUE)
|
||
else: _repl_stub_warn_nodata(e, _REPL_SU_NOCLASS)
|
||
ReplStubTable[ReplStubCount] = sizet(e)
|
||
ReplStubCount += 1
|
||
if e.State == _REPL_SS_PENDING: _repl_stub_try(e, mod)
|
||
return 1
|
||
|
||
|
||
def _repl_stub_collect_module(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, ch: list[ast.AST*]*, n: sizet, trans: HT.Translator*, src_sha1: str, imported_modules: str, from_imports: str) -> int:
|
||
"""(REPL) Collect layout elements for the current module's classes and try to register them."""
|
||
global ReplStubNoStub, ReplStubWarnLeft
|
||
if ReplStubLayoutMode == 0 or pool is None or ch is None: return 0
|
||
if src_sha1 is None or src_sha1[0] == '\0' or ReplStubTable is None: return 0
|
||
# layout metadata ({sha1}.meta; if missing, classes with bases/vtables are refused as "missing data")
|
||
txt: str = None
|
||
sp: str = _repl_stub_path(src_sha1, "meta")
|
||
if sp is not None:
|
||
txt = _repl_stub_readtext(sp)
|
||
stdlib.free(sp)
|
||
have_stub: int = 0
|
||
if txt is not None: have_stub = 1
|
||
else:
|
||
ReplStubNoStub += 1
|
||
if ReplStubWarnLeft > 0:
|
||
ReplStubWarnLeft -= 1
|
||
stdio.printf("includes 类布局: 模块 %s 无 .meta 存根(数据缺失)—— 该模块" "带父类/虚表的类将逐个按『数据缺失』明确拒绝(读不到布局元数据 ⇒ 无" "字段数可比,不是字段数不符)。用同一工程先跑一次批处理生成存根," "再 `--project .\\project.vpj --repl`(TransPyV 目录)即可复用\n", src_sha1)
|
||
cnt: int = 0
|
||
i: sizet
|
||
for i in range(n):
|
||
child: ast.AST* = ch.get(i)
|
||
if child is None: continue
|
||
if child.kind() != ast.ASTKind.ClassDef: continue
|
||
cd: ast.ClassDef* = ast.ClassDef*(child)
|
||
if cd is None or cd.name is None: continue
|
||
# Generic template classes are skipped (same as _inject_class_struct_declare / pass 1A),
|
||
# but the owning module must be registered so generic subscripts can build type placeholders.
|
||
tp: list[str]* = cd.type_params
|
||
if tp is not None and len(tp) > 0:
|
||
StubMergerStore.RegisterGenericOwner(cd.name, src_sha1)
|
||
continue
|
||
# REnum classes: layout {i32 tag, payload} is computed by ClassGenEnum, so skip them here
|
||
if _vpi_class_is_renum(cd) != 0: continue
|
||
if _repl_stub_add_class(pool, mod, cd, src_sha1, txt, have_stub, trans, imported_modules, from_imports) != 0:
|
||
cnt += 1
|
||
if txt is not None: stdlib.free(txt)
|
||
return cnt
|
||
|
||
|
||
def ReplStubLayoutFinish() -> int:
|
||
"""Finish: converge remaining class registrations, print a summary, and return the registered count."""
|
||
global ReplStubRefused, ReplStubWarnLeft
|
||
if ReplStubLayoutMode == 0: return 0
|
||
rounds: int
|
||
for rounds in range(_REPL_STUB_FINISH_PASSES):
|
||
prog: int = 0
|
||
i: int
|
||
for i in range(ReplStubCount):
|
||
e: ReplStubClass* = _repl_stub_at(i)
|
||
if e is None or e.State != _REPL_SS_PENDING: continue
|
||
if _repl_stub_try(e, None) != 0: prog += 1
|
||
if prog == 0: break
|
||
# still not ready -> the base was never registered (missing stub/refused): explicitly refuse
|
||
i2: int
|
||
for i2 in range(ReplStubCount):
|
||
e2: ReplStubClass* = _repl_stub_at(i2)
|
||
if e2 is None or e2.State != _REPL_SS_PENDING: continue
|
||
e2.State = _REPL_SS_REFUSED
|
||
# find the still-unready base class name (to identify the upstream)
|
||
pbad: str = "(未定位)"
|
||
bi2: int
|
||
for bi2 in range(e2.BaseCount):
|
||
peb: ReplStubClass* = _repl_stub_find_base(e2, bi2)
|
||
if peb is not None and peb.State == _REPL_SS_OK: continue
|
||
if peb is not None and peb.ClassName is not None: pbad = peb.ClassName
|
||
break
|
||
_repl_stub_warn_parent(e2, "父类未在会话注册或其布局被拒", pbad)
|
||
stdio.printf("includes 类布局: 完整布局 %d 个(其中虚表索引可用 %d 个)/ " "被拒 %d 个 / 缺布局元数据模块 %d 个\n", ReplStubOk, ReplStubVtOk, ReplStubRefused, ReplStubNoStub)
|
||
stdio.printf(" 拒绝原因分布(自证数据来源):数据缺失-模块无 {sha1}.meta %d 个类 / " "判据拒绝-meta 无此类 %d 个 / 判据拒绝-meta 为 opaque %d 个 / " "上游无数据-父类不可用 %d 个\n", ReplStubRefusedNoStub, ReplStubRefusedNoClass, ReplStubRefusedOpaque, ReplStubRefusedParent)
|
||
if ReplStubRefused > 0 or ReplStubNoStub > 0:
|
||
stdio.printf(" 说明:被拒类的字段访问会报『类未注册/属性访问失败』" "(不按错偏移静默访问);方法调用不受影响。\n")
|
||
if ReplStubRefusedNoStub > 0:
|
||
stdio.printf(" 其中『数据缺失』的 %d 个类只是没读到模块布局元数据(本会话临时目录 %s " "缺这些 {sha1}.meta):批处理才会落盘 meta(REPL 分支在 Phase1 之前" "返回,故 --repl 自身不生成存根)。修法:用同一个工程跑一次批处理" "(TransPyV 目录:.\\tpv_build\\output\\TransPyV.exe --project " ".\\project.vpj;includes 有改动时加 --rebuild-includes),随后 " "--project .\\project.vpj --repl 复用其临时目录即可\n", ReplStubRefusedNoStub, ReplStubTempDir)
|
||
if ReplStubRefusedNoClass + ReplStubRefusedOpaque > 0:
|
||
stdio.printf(" 其中『判据拒绝』的 %d 个类:数据在但不可信(存根里没有该类的字段数、" "或其声明为 opaque),按 M4.2 一律不放行——请确认该类是否本应按结构体" "登记:应按结构体登记而存根里查不到 ⇒ 存根陈旧/不完整(重建 includes " "存根)\n", ReplStubRefusedNoClass + ReplStubRefusedOpaque)
|
||
return ReplStubOk
|
||
|
||
|
||
def ReplStubLayoutEnd() -> None:
|
||
"""Disable stub layout restore (the batch path is unaffected afterwards)."""
|
||
global ReplStubLayoutMode
|
||
ReplStubLayoutMode = 0
|