feat(vpc): binary .vpb stub companion and str->char* migration in call gen
- add VpiStub: fixed-layout blob written next to every .vpi (dual write at all writers, Ensure() backfills a missing or stale companion on cache reuse) - serve arity/signature queries for top-level functions from the blob, with a silent fallback to the text scanner (methods, varargs, duplicates, corrupt file) - prefer the injected declare over the text arity in the cross-module check - CDefine/type text indexes and in-place SSA renumbering and bulk appends - WIP: char* conversion in CallGenBase and CallGenLeafKit
This commit is contained in:
1 parent
451d89b39a
commit
4bf04c2452
15 files changed
+1697
-463
No files matched your search
@@ -21,6 +21,7 @@ import lib.core.Gen.Translator as HandlesTranslator
|
||||
import lib.core.Gen.DebugInfoGen as DebugInfoGen
|
||||
import lib.Stub.StubMerger as StubMerger
|
||||
import lib.Stub.StubGen.Converter as StubConverter
|
||||
import lib.Stub.StubGen.VpiStub as VpiStub
|
||||
import lib.Projectrans.Config as Config
|
||||
import lib.core.Gen.Generator as GenGenerator
|
||||
import lib.core.Gen.Expr.ExprGenCls as ExprGenCls
|
||||
@@ -127,7 +128,7 @@ def _phaseb_ircore_active() -> int:
|
||||
if _ircore_ready != 0: return 1
|
||||
if _ircore_load() == 0:
|
||||
_phaseb_backend = PHASEB_BACKEND_LEGACY
|
||||
VLogger.warning("ircore.dll is unavailable (or its LLVM runtime could not be loaded); PhaseB falls back to the legacy mlir-opt/mlir-translate/llc flow", "PhaseB")
|
||||
VLogger.warning("ircore.dll is unavailable (or its LLVM runtime could not be loaded); PhaseB falls back to the legacy mlir-opt/mlir-translate/llc flow (3 child processes per module) -- put ircore.dll next to viperc.exe (or on PATH) to use the in-process backend", "PhaseB")
|
||||
return 0
|
||||
_ircore_ready = 1
|
||||
return 1
|
||||
@@ -347,6 +348,7 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, sha1_val: str, c
|
||||
if not pf.closed:
|
||||
pf.write(pyi_buf, pyi_pos)
|
||||
pf.close()
|
||||
VpiStub.Emit(pyi_path, pyi_buf, pyi_pos, sha1_val)
|
||||
stdlib.free(pyi_path)
|
||||
stdlib.free(pyi_buf)
|
||||
tr: HandlesTranslator.Translator* = mb.alloc(HandlesTranslator.Translator.__sizeof__())
|
||||
|
||||
@@ -16,6 +16,7 @@ import lib.core.CompilerFactory as CompilerFactory
|
||||
import lib.Phase.BuildPipeline as BuildPipeline
|
||||
import lib.Projectrans.Config as Config
|
||||
import lib.Stub.StubGen.Converter as StubConverter
|
||||
import lib.Stub.StubGen.VpiStub as VpiStub
|
||||
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
|
||||
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
|
||||
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
|
||||
@@ -589,6 +590,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
vf_pv: io.File* = io.File(vpi_path_pv, io.MODE.R)
|
||||
if not vf_pv.closed:
|
||||
vf_pv.close()
|
||||
VpiStub.Ensure(vpi_path_pv, sha1_pv)
|
||||
stdlib.free(vpi_path_pv)
|
||||
continue
|
||||
stdlib.free(vpi_path_pv)
|
||||
@@ -646,6 +648,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
if not pf_pv.closed:
|
||||
pf_pv.write(pyi_buf_pv, pyi_pos_pv)
|
||||
pf_pv.close()
|
||||
VpiStub.Emit(pyi_path_pv, pyi_buf_pv, pyi_pos_pv, sha1_pv)
|
||||
stdlib.free(pyi_path_pv)
|
||||
stdlib.free(pyi_buf_pv)
|
||||
else:
|
||||
@@ -868,6 +871,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
vpi_exists_1b: int = 0
|
||||
if not vf_1b.closed:
|
||||
vf_1b.close()
|
||||
VpiStub.Ensure(vpi_path_1b, sha1)
|
||||
vpi_exists_1b = 1
|
||||
if vpi_exists_1b == 0:
|
||||
pyi_buf: bytes = stdlib.malloc(Phase1.PYI_BUF_SIZE)
|
||||
@@ -878,6 +882,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
if not pf_1b.closed:
|
||||
pf_1b.write(pyi_buf, pyi_pos)
|
||||
pf_1b.close()
|
||||
VpiStub.Emit(vpi_path_1b, pyi_buf, pyi_pos, sha1)
|
||||
stdlib.free(pyi_buf)
|
||||
stdlib.free(vpi_path_1b)
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||||
import lib.Stub.StubMerger.StubMergerTopo as StubMergerTopo
|
||||
import lib.Stub.StubGen.Converter as StubConverter
|
||||
import lib.Stub.StubGen.VpiReader as VpiReader
|
||||
import lib.Stub.StubGen.VpiStub as VpiStub
|
||||
|
||||
# Global mbuddy pointer
|
||||
_mbuddy: memhub.MemBuddy*
|
||||
@@ -629,6 +630,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou
|
||||
vf_vp2: io.File* = io.File(vpi_path_vp2, io.MODE.R)
|
||||
if not vf_vp2.closed:
|
||||
vf_vp2.close()
|
||||
VpiStub.Ensure(vpi_path_vp2, ent_vp2.Sha1)
|
||||
vpi_skipped_p2 += 1
|
||||
stdlib.free(vpi_path_vp2)
|
||||
continue
|
||||
@@ -699,6 +701,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou
|
||||
VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | cannot open the vpi for writing: {pyi_path_vp2}", "PhaseA")
|
||||
pf_vp2.write(pyi_buf_vp2, pyi_pos_vp2)
|
||||
pf_vp2.close()
|
||||
VpiStub.Emit(pyi_path_vp2, pyi_buf_vp2, pyi_pos_vp2, ent_vp2.Sha1)
|
||||
vpi_written_p2 += 1
|
||||
stdlib.free(pyi_path_vp2)
|
||||
stdlib.free(pyi_buf_vp2)
|
||||
|
||||
@@ -14,6 +14,7 @@ import io
|
||||
import lib.core.VLogger as VLogger
|
||||
import lib.core.Gen.Translator as HT
|
||||
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||||
import lib.Stub.StubGen.VpiStub as VpiStub
|
||||
import lib.Projectrans.Config as Config
|
||||
|
||||
|
||||
@@ -1315,14 +1316,71 @@ def _lookup_vpi_escape_dir(src_sha1: str, cls_name: str, method_name: str, which
|
||||
# (现场:逃逸摘要只在 _ensure_vpi_ret_annot_loaded 里喂,而 stub 注入加载点没喂,
|
||||
# 前者开头又有"本模块 ret 注解已缓存即 return 1"的短路,于是跨模块逃逸查询永久查空,
|
||||
# EscapeXmodTest 漏检)。
|
||||
# --- per-sha1 annotation scan guard ---------------------------------------
|
||||
# _cache_vpi_annots_text walks the whole .vpi five times (one pass per annotation table) and
|
||||
# ParseVpiIntoModule runs it once per (dependent module, dependency): 1,020 scans for ~67
|
||||
# distinct .vpi files, 51 s of a profiled build. The tables are keyed by "{sha1}.{name}" and
|
||||
# are never cleared, so a repeated scan only rewrites identical entries -- skip it.
|
||||
define VPI_ANNOT_SEEN_SLOTS = 256
|
||||
|
||||
_vpi_annots_seen_tab: rawptr = None # str[slots] owned sha1 copies
|
||||
|
||||
|
||||
def _vpi_annots_hash(sha1: str) -> uint64:
|
||||
h: uint32 = uint32(2166136261)
|
||||
n: sizet = string.strlen(sha1)
|
||||
i: sizet = 0
|
||||
while i < n:
|
||||
h = (h ^ uint32(uint8(sha1[i]))) * uint32(16777619)
|
||||
i += 1
|
||||
return uint64(h) | uint64(1)
|
||||
|
||||
|
||||
def _vpi_annots_seen_slot(sha1: str) -> str*:
|
||||
global _vpi_annots_seen_tab
|
||||
if sha1 is None: return None
|
||||
if _vpi_annots_seen_tab is None:
|
||||
_vpi_annots_seen_tab = stdlib.malloc(VPI_ANNOT_SEEN_SLOTS * 8)
|
||||
if _vpi_annots_seen_tab is None: return None
|
||||
string.memset(_vpi_annots_seen_tab, 0, VPI_ANNOT_SEEN_SLOTS * 8)
|
||||
h: int = int(_vpi_annots_hash(sha1) & uint64(VPI_ANNOT_SEEN_SLOTS - 1))
|
||||
return (str*)(uint64(_vpi_annots_seen_tab) + uint64(h) * 8)
|
||||
|
||||
|
||||
# _vpi_annots_seen - 1 when this sha1 was already scanned.
|
||||
def _vpi_annots_seen(sha1: str) -> int:
|
||||
sp: str* = _vpi_annots_seen_slot(sha1)
|
||||
if sp is None: return 0
|
||||
if sp[0] is not None and string.strcmp(sp[0], sha1) == 0: return 1
|
||||
return 0
|
||||
|
||||
|
||||
# _vpi_annots_mark - record the sha1 as scanned. Called only after every table has been
|
||||
# filled, so a concurrent reader can never skip a half-scanned .vpi.
|
||||
def _vpi_annots_mark(sha1: str) -> None:
|
||||
sp: str* = _vpi_annots_seen_slot(sha1)
|
||||
if sp is None: return
|
||||
if sp[0] is not None and string.strcmp(sp[0], sha1) == 0: return
|
||||
n: sizet = string.strlen(sha1)
|
||||
cp: str = stdlib.malloc(n + 1)
|
||||
if cp is None: return
|
||||
string.memcpy(cp, sha1, n)
|
||||
cp[n] = '\0'
|
||||
if sp[0] is not None: stdlib.free(sp[0])
|
||||
sp[0] = cp
|
||||
|
||||
|
||||
def _cache_vpi_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
|
||||
"""一次性把 vpi 文本派生出的所有缓存喂齐;1=完成 / 0=入参非法"""
|
||||
if vpi_buf is None or src_sha1 is None: return 0
|
||||
# Repeated scans of the same .vpi only rewrite identical cache entries.
|
||||
if _vpi_annots_seen(src_sha1) != 0: return 1
|
||||
_cache_vpi_return_annots_text(vpi_buf, src_sha1)
|
||||
_cache_vpi_param_annots_text(vpi_buf, src_sha1)
|
||||
_cache_vpi_escape_annots_text(vpi_buf, src_sha1)
|
||||
_cache_vpi_willfree_annots_text(vpi_buf, src_sha1)
|
||||
_cache_vpi_esc_clears_text(vpi_buf, src_sha1)
|
||||
_vpi_annots_mark(src_sha1)
|
||||
return 1
|
||||
|
||||
|
||||
@@ -2085,6 +2143,119 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: m
|
||||
# .vpi read buffer size (256KB, matching PYI_BUF_SIZE)
|
||||
VPI_READ_BUF_SIZE: sizet = 262144
|
||||
|
||||
# --- .vpi text cache ---------------------------------------------------------
|
||||
# LookupSignDefaultsByName is called hundreds of times per module and used to re-open and
|
||||
# re-read the whole {temp}/{sha1}.vpi for every query (4,592 reads in a profiled compiler
|
||||
# build, 9.3 ms each). sha1 is the module content hash and its .vpi is written once per
|
||||
# build (Phase1 regenerates it only when missing), so caching the text per sha1 cannot go
|
||||
# stale inside a run. Consumers get a private copy, so they keep owning/freeing their
|
||||
# buffer exactly as before.
|
||||
define VPI_CACHE_SLOTS = 32
|
||||
|
||||
_vpi_cache_sha1: rawptr = None
|
||||
_vpi_cache_buf: rawptr = None
|
||||
_vpi_cache_len: rawptr = None
|
||||
_vpi_cache_used: int = 0
|
||||
_vpi_cache_next: int = 0
|
||||
|
||||
|
||||
def _vpi_cache_init() -> int:
|
||||
global _vpi_cache_sha1, _vpi_cache_buf, _vpi_cache_len
|
||||
if _vpi_cache_sha1 is not None: return 1
|
||||
sh: rawptr = stdlib.malloc(VPI_CACHE_SLOTS * 8)
|
||||
bf: rawptr = stdlib.malloc(VPI_CACHE_SLOTS * 8)
|
||||
ln: rawptr = stdlib.malloc(VPI_CACHE_SLOTS * 8)
|
||||
if sh is None or bf is None or ln is None:
|
||||
if sh is not None: stdlib.free(sh)
|
||||
if bf is not None: stdlib.free(bf)
|
||||
if ln is not None: stdlib.free(ln)
|
||||
return 0
|
||||
string.memset(sh, 0, VPI_CACHE_SLOTS * 8)
|
||||
string.memset(bf, 0, VPI_CACHE_SLOTS * 8)
|
||||
string.memset(ln, 0, VPI_CACHE_SLOTS * 8)
|
||||
_vpi_cache_sha1 = sh
|
||||
_vpi_cache_buf = bf
|
||||
_vpi_cache_len = ln
|
||||
return 1
|
||||
|
||||
|
||||
def _vpi_cache_put(sha1: str, src: bytes, n: sizet) -> int:
|
||||
"""Store a private copy of [src, src+n) under sha1 (ring eviction); 0 = not cached."""
|
||||
global _vpi_cache_used, _vpi_cache_next
|
||||
if _vpi_cache_init() == 0: return 0
|
||||
slot: int = 0
|
||||
if _vpi_cache_used < VPI_CACHE_SLOTS:
|
||||
slot = _vpi_cache_used
|
||||
_vpi_cache_used += 1
|
||||
else:
|
||||
slot = _vpi_cache_next
|
||||
_vpi_cache_next = (_vpi_cache_next + 1) % VPI_CACHE_SLOTS
|
||||
old_s: str* = (str*)(uint64(_vpi_cache_sha1) + uint64(slot) * 8)
|
||||
old_b: bytes* = (bytes*)(uint64(_vpi_cache_buf) + uint64(slot) * 8)
|
||||
if old_s[0] is not None: stdlib.free(old_s[0])
|
||||
if old_b[0] is not None: stdlib.free(old_b[0])
|
||||
old_s[0] = None
|
||||
old_b[0] = None
|
||||
sn: sizet = string.strlen(sha1)
|
||||
nm: str = stdlib.malloc(sn + 1)
|
||||
cp: bytes = stdlib.malloc(n + 1)
|
||||
if nm is None or cp is None:
|
||||
if nm is not None: stdlib.free(nm)
|
||||
if cp is not None: stdlib.free(cp)
|
||||
return 0
|
||||
string.memcpy(nm, sha1, sn)
|
||||
nm[sn] = '\0'
|
||||
if n > 0: string.memcpy(cp, src, n)
|
||||
cp[n] = '\0'
|
||||
sp: str* = (str*)(uint64(_vpi_cache_sha1) + uint64(slot) * 8)
|
||||
bp: bytes* = (bytes*)(uint64(_vpi_cache_buf) + uint64(slot) * 8)
|
||||
lp: sizet* = (sizet*)(uint64(_vpi_cache_len) + uint64(slot) * 8)
|
||||
sp[0] = nm
|
||||
bp[0] = cp
|
||||
lp[0] = n
|
||||
return 1
|
||||
|
||||
|
||||
# _vpi_read_cached - contents of {temp}/{sha1}.vpi (NUL-terminated, malloc'd: caller frees).
|
||||
def _vpi_read_cached(sha1: str) -> bytes:
|
||||
if sha1 is None: return None
|
||||
if _vpi_cache_init() == 0: return None
|
||||
i: int = 0
|
||||
while i < _vpi_cache_used:
|
||||
sp: str* = (str*)(uint64(_vpi_cache_sha1) + uint64(i) * 8)
|
||||
if sp[0] is not None and string.strcmp(sp[0], sha1) == 0:
|
||||
bp: bytes* = (bytes*)(uint64(_vpi_cache_buf) + uint64(i) * 8)
|
||||
lp: sizet* = (sizet*)(uint64(_vpi_cache_len) + uint64(i) * 8)
|
||||
n: sizet = lp[0]
|
||||
cp: bytes = stdlib.malloc(n + 1)
|
||||
if cp is None: return None
|
||||
if n > 0 and bp[0] is not None: string.memcpy(cp, bp[0], n)
|
||||
cp[n] = '\0'
|
||||
return cp
|
||||
i += 1
|
||||
# Miss: read once, then keep a copy for the next queries.
|
||||
temp_dir: str = GenApi.get_temp_dir()
|
||||
if temp_dir is None: return None
|
||||
fpath: str = _vpi_build_path(temp_dir, sha1, ".vpi")
|
||||
if fpath is None: return None
|
||||
f: io.File* = io.File(fpath, io.MODE.R)
|
||||
stdlib.free(fpath)
|
||||
if f.closed: return None
|
||||
buf: bytes = stdlib.malloc(VPI_READ_BUF_SIZE)
|
||||
if buf is None:
|
||||
f.close()
|
||||
return None
|
||||
nrd: LONG = f.read_all(buf, VPI_READ_BUF_SIZE)
|
||||
f.close()
|
||||
if nrd <= 0:
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
n: sizet = sizet(nrd)
|
||||
if n >= VPI_READ_BUF_SIZE: n = VPI_READ_BUF_SIZE - 1
|
||||
buf[n] = '\0'
|
||||
_vpi_cache_put(sha1, buf, n)
|
||||
return buf
|
||||
|
||||
# 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 DefineT shadowing.
|
||||
SHA1_HEX_LEN: sizet = 16
|
||||
@@ -2232,6 +2403,7 @@ def _vpi_register_typedef_closure(pool: memhub.MemBuddy*, import_mods: str, temp
|
||||
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
|
||||
@@ -2857,6 +3029,127 @@ def _VpiParamCountAndVararg(vbuf: bytes, ps: sizet, pe: sizet) -> int:
|
||||
# LookupSignDefaultsByName - 直接在定义模块 .vpi 文本里查一个函数/方法的缺省参数快照
|
||||
# 与 PreRegisterVpiDefaults 的区别:不登记进全局表,只按名字现查现用(表键不可靠时的兜底)。
|
||||
# 返回 (参数个数 << 8) | 缺省值个数;-1 = 未找到或有不可重建的缺省值。
|
||||
# --- signature range cache --------------------------------------------------
|
||||
# LookupSignDefaultsByName re-scanned the whole .vpi text per query (one
|
||||
# _vpi_class_name_offset + _vpi_func_name_offset per line, ~220 calls per query):
|
||||
# 4,592 queries / 60 s in a profiled build. The resolved parameter-list range depends only
|
||||
# on (sha1, name) -- sha1 is the module content hash and its .vpi is immutable in a run --
|
||||
# so cache it and let a repeat query parse just that one list. Misses are cached as well
|
||||
# (an ambiguous/absent name costs the same full scan).
|
||||
define VPI_SIG_CACHE_SLOTS = 1024
|
||||
|
||||
_vpi_sig_sha1: rawptr = None # str[slots] owned sha1 copy
|
||||
_vpi_sig_name: rawptr = None # str[slots] owned name copy
|
||||
_vpi_sig_ps: rawptr = None # int[slots] '(' offset in the .vpi text
|
||||
_vpi_sig_pe: rawptr = None # int[slots] ')' offset
|
||||
_vpi_sig_meth: rawptr = None # int[slots] method flag (indent == 4)
|
||||
_vpi_sig_flag: rawptr = None # int[slots] 0=empty 1=found 2=not found/ambiguous
|
||||
|
||||
|
||||
def _vpi_sig_hash(sha1: str, name: str) -> uint64:
|
||||
h: uint32 = uint32(2166136261)
|
||||
n: sizet = string.strlen(sha1)
|
||||
i: sizet = 0
|
||||
while i < n:
|
||||
h = (h ^ uint32(uint8(sha1[i]))) * uint32(16777619)
|
||||
i += 1
|
||||
h = (h ^ uint32(46)) * uint32(16777619)
|
||||
n = string.strlen(name)
|
||||
i = 0
|
||||
while i < n:
|
||||
h = (h ^ uint32(uint8(name[i]))) * uint32(16777619)
|
||||
i += 1
|
||||
return uint64(h) | uint64(1)
|
||||
|
||||
|
||||
def _vpi_sig_init() -> int:
|
||||
global _vpi_sig_sha1, _vpi_sig_name, _vpi_sig_ps, _vpi_sig_pe, _vpi_sig_meth, _vpi_sig_flag
|
||||
if _vpi_sig_flag is not None: return 1
|
||||
a: rawptr = stdlib.malloc(VPI_SIG_CACHE_SLOTS * 8)
|
||||
b: rawptr = stdlib.malloc(VPI_SIG_CACHE_SLOTS * 8)
|
||||
c: rawptr = stdlib.malloc(VPI_SIG_CACHE_SLOTS * 4)
|
||||
d: rawptr = stdlib.malloc(VPI_SIG_CACHE_SLOTS * 4)
|
||||
e: rawptr = stdlib.malloc(VPI_SIG_CACHE_SLOTS * 4)
|
||||
f: rawptr = stdlib.malloc(VPI_SIG_CACHE_SLOTS * 4)
|
||||
if a is None or b is None or c is None or d is None or e is None or f is None:
|
||||
if a is not None: stdlib.free(a)
|
||||
if b is not None: stdlib.free(b)
|
||||
if c is not None: stdlib.free(c)
|
||||
if d is not None: stdlib.free(d)
|
||||
if e is not None: stdlib.free(e)
|
||||
if f is not None: stdlib.free(f)
|
||||
return 0
|
||||
string.memset(a, 0, VPI_SIG_CACHE_SLOTS * 8)
|
||||
string.memset(b, 0, VPI_SIG_CACHE_SLOTS * 8)
|
||||
string.memset(c, 0, VPI_SIG_CACHE_SLOTS * 4)
|
||||
string.memset(d, 0, VPI_SIG_CACHE_SLOTS * 4)
|
||||
string.memset(e, 0, VPI_SIG_CACHE_SLOTS * 4)
|
||||
string.memset(f, 0, VPI_SIG_CACHE_SLOTS * 4)
|
||||
_vpi_sig_sha1 = a
|
||||
_vpi_sig_name = b
|
||||
_vpi_sig_ps = c
|
||||
_vpi_sig_pe = d
|
||||
_vpi_sig_meth = e
|
||||
_vpi_sig_flag = f
|
||||
return 1
|
||||
|
||||
|
||||
# _vpi_sig_get - 0 = hit (ps/pe/meth filled), 1 = known miss, -1 = not cached.
|
||||
def _vpi_sig_get(sha1: str, name: str, ps: int*, pe: int*, meth: int*) -> int:
|
||||
if sha1 is None or name is None or _vpi_sig_flag is None: return -1
|
||||
h: int = int(_vpi_sig_hash(sha1, name) & uint64(VPI_SIG_CACHE_SLOTS - 1))
|
||||
sp: str* = (str*)(uint64(_vpi_sig_sha1) + uint64(h) * 8)
|
||||
if sp[0] is None or string.strcmp(sp[0], sha1) != 0: return -1
|
||||
np: str* = (str*)(uint64(_vpi_sig_name) + uint64(h) * 8)
|
||||
if np[0] is None or string.strcmp(np[0], name) != 0: return -1
|
||||
fp: int* = (int*)(uint64(_vpi_sig_flag) + uint64(h) * 4)
|
||||
st: int = int(fp[0])
|
||||
if st == 2: return 1
|
||||
if st != 1: return -1
|
||||
if ps is not None:
|
||||
xp: int* = (int*)(uint64(_vpi_sig_ps) + uint64(h) * 4)
|
||||
ps[0] = int(xp[0])
|
||||
if pe is not None:
|
||||
yp: int* = (int*)(uint64(_vpi_sig_pe) + uint64(h) * 4)
|
||||
pe[0] = int(yp[0])
|
||||
if meth is not None:
|
||||
mp: int* = (int*)(uint64(_vpi_sig_meth) + uint64(h) * 4)
|
||||
meth[0] = int(mp[0])
|
||||
return 0
|
||||
|
||||
|
||||
def _vpi_sig_put(sha1: str, name: str, found: int, ps: int, pe: int, meth: int) -> None:
|
||||
if sha1 is None or name is None: return
|
||||
if _vpi_sig_init() == 0: return
|
||||
h: int = int(_vpi_sig_hash(sha1, name) & uint64(VPI_SIG_CACHE_SLOTS - 1))
|
||||
sn: sizet = string.strlen(sha1)
|
||||
nn: sizet = string.strlen(name)
|
||||
a: str = stdlib.malloc(sn + 1)
|
||||
b: str = stdlib.malloc(nn + 1)
|
||||
if a is None or b is None:
|
||||
if a is not None: stdlib.free(a)
|
||||
if b is not None: stdlib.free(b)
|
||||
return
|
||||
string.memcpy(a, sha1, sn)
|
||||
a[sn] = '\0'
|
||||
string.memcpy(b, name, nn)
|
||||
b[nn] = '\0'
|
||||
sp: str* = (str*)(uint64(_vpi_sig_sha1) + uint64(h) * 8)
|
||||
np: str* = (str*)(uint64(_vpi_sig_name) + uint64(h) * 8)
|
||||
if sp[0] is not None: stdlib.free(sp[0])
|
||||
if np[0] is not None: stdlib.free(np[0])
|
||||
sp[0] = a
|
||||
np[0] = b
|
||||
xp: int* = (int*)(uint64(_vpi_sig_ps) + uint64(h) * 4)
|
||||
yp: int* = (int*)(uint64(_vpi_sig_pe) + uint64(h) * 4)
|
||||
mp: int* = (int*)(uint64(_vpi_sig_meth) + uint64(h) * 4)
|
||||
fp: int* = (int*)(uint64(_vpi_sig_flag) + uint64(h) * 4)
|
||||
xp[0] = int(ps)
|
||||
yp[0] = int(pe)
|
||||
mp[0] = int(meth)
|
||||
fp[0] = 1 if found != 0 else 2
|
||||
|
||||
|
||||
def LookupSignDefaultsByName(pool: memhub.MemBuddy*, sha1: str, name: str, kinds: int*, ints: int64*, strs: sizet*, parse_defaults: int = 1) -> int:
|
||||
"""在 {temp}/{sha1}.vpi 里按名字查缺省参数;-1 = 未找到
|
||||
|
||||
@@ -2864,23 +3157,14 @@ def LookupSignDefaultsByName(pool: memhub.MemBuddy*, sha1: str, name: str, kinds
|
||||
if pool is None or sha1 is None or name is None: return -1
|
||||
if parse_defaults != 0:
|
||||
if kinds is None or ints is None or strs is None: return -1
|
||||
temp_dir: str = GenApi.get_temp_dir()
|
||||
if temp_dir is None: return -1
|
||||
fpath: str = _vpi_build_path(temp_dir, sha1, ".vpi")
|
||||
if fpath is None: return -1
|
||||
vf: io.File* = io.File(fpath, io.MODE.R)
|
||||
stdlib.free(fpath)
|
||||
if vf.closed: return -1
|
||||
vbuf: bytes = stdlib.malloc(VPI_READ_BUF_SIZE)
|
||||
if vbuf is None:
|
||||
vf.close()
|
||||
return -1
|
||||
nrd: LONG = vf.read_all(vbuf, VPI_READ_BUF_SIZE)
|
||||
vf.close()
|
||||
if nrd <= 0:
|
||||
stdlib.free(vbuf)
|
||||
return -1
|
||||
if nrd < VPI_READ_BUF_SIZE: vbuf[nrd] = 0
|
||||
if parse_defaults == 0:
|
||||
fast_arity: int = _vpb_param_count(sha1, name)
|
||||
if fast_arity >= 0: return fast_arity << 8
|
||||
# Hot path: hundreds of queries per module. Serve the .vpi text from the per-sha1 cache
|
||||
# instead of re-opening and re-reading the file every time (_vpi_read_cached returns a
|
||||
# private copy, so the stdlib.free(vbuf) below is still the owner).
|
||||
vbuf: bytes = _vpi_read_cached(sha1)
|
||||
if vbuf is None: return -1
|
||||
buf_len: sizet = string.strlen(vbuf)
|
||||
name_len: sizet = string.strlen(name)
|
||||
cls: bytes = None
|
||||
@@ -2891,6 +3175,28 @@ def LookupSignDefaultsByName(pool: memhub.MemBuddy*, sha1: str, name: str, kinds
|
||||
hit_ps: sizet = 0
|
||||
hit_pe: sizet = 0
|
||||
hit_method: int = 0
|
||||
# Lookaside: the scan result only depends on (sha1, name).
|
||||
sig_ps: int = 0
|
||||
sig_pe: int = 0
|
||||
sig_meth: int = 0
|
||||
sig_st: int = _vpi_sig_get(sha1, name, &sig_ps, &sig_pe, &sig_meth)
|
||||
if sig_st == 1:
|
||||
stdlib.free(vbuf)
|
||||
return -1
|
||||
if sig_st == 0:
|
||||
# Self-check: a usable entry must still bracket a parameter list in this text.
|
||||
# (The .vpi for a sha1 is immutable in a run, but a cache/probe mismatch must never
|
||||
# turn into a wrong arity, so anything that does not line up is rescanned.)
|
||||
sig_ok: int = 0
|
||||
if sig_ps > 0 and sig_pe > sig_ps and sizet(sig_pe) < buf_len:
|
||||
if vbuf[sizet(sig_ps) - 1] == '(' and vbuf[sizet(sig_pe)] == ')': sig_ok = 1
|
||||
if sig_ok == 0: sig_st = -1
|
||||
if sig_st == 0:
|
||||
hits = 1
|
||||
hit_ps = sizet(sig_ps)
|
||||
hit_pe = sizet(sig_pe)
|
||||
hit_method = sig_meth
|
||||
pos = buf_len # skip the full-text scan
|
||||
while pos < buf_len:
|
||||
ls: sizet = pos
|
||||
le: sizet = pos
|
||||
@@ -2950,6 +3256,11 @@ def LookupSignDefaultsByName(pool: memhub.MemBuddy*, sha1: str, name: str, kinds
|
||||
hit_method = 0
|
||||
if indent == 4: hit_method = 1
|
||||
if cls is not None: stdlib.free(cls)
|
||||
if sig_st < 0:
|
||||
if hits == 1:
|
||||
_vpi_sig_put(sha1, name, 1, int(hit_ps), int(hit_pe), hit_method)
|
||||
else:
|
||||
_vpi_sig_put(sha1, name, 0, 0, 0, 0)
|
||||
if hits != 1:
|
||||
stdlib.free(vbuf)
|
||||
return -1
|
||||
@@ -2993,10 +3304,25 @@ def _VpiFirstParamIsSelf(buf: bytes, ps: sizet, pe: sizet) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
# LookupParamCountByName - 取定义模块 .vpi 里某函数/方法的形参个数(方法不含 self)
|
||||
# 返回 -1 表示未找到或签名含可变参数(调用方应跳过 arity 校验)。
|
||||
# _vpb_param_count - arity of a top-level function from the binary companion.
|
||||
# Methods stay on the text scanner (the blob does not index them), and -1 makes the
|
||||
# caller fall back for varargs, duplicates and missing records.
|
||||
def _vpb_param_count(sha1: str, name: str) -> int:
|
||||
if sha1 is None or name is None: return -1
|
||||
if string.strchr(name, '.') is not None: return -1
|
||||
st: VpiStub.VpiStub* = VpiStub.Load(sha1, GenApi.get_temp_dir())
|
||||
if st is None: return -1
|
||||
rec: VpiStub.VpiFuncRec* = st.Find(name)
|
||||
if rec is None: return -1
|
||||
# Varargs have no fixed arity; the text scanner reports -1 for them as well.
|
||||
if rec.VarArg != 0: return -1
|
||||
return rec.Arity
|
||||
|
||||
|
||||
def LookupParamCountByName(pool: memhub.MemBuddy*, sha1: str, name: str) -> int:
|
||||
"""按名字取形参个数(方法不含 self);-1=未找到/含可变参数"""
|
||||
"""Arity of a named function (self excluded); -1 when unknown or varargs."""
|
||||
fast: int = _vpb_param_count(sha1, name)
|
||||
if fast >= 0: return fast
|
||||
enc: int = LookupSignDefaultsByName(pool, sha1, name, None, None, None, 0)
|
||||
if enc < 0: return -1
|
||||
return enc >> 8
|
||||
|
||||
@@ -0,0 +1,693 @@
|
||||
from stdint import *
|
||||
import string
|
||||
import stdlib
|
||||
import io
|
||||
import lib.Projectrans.Config as Config
|
||||
|
||||
# VpiStub - binary companion of the human-readable .vpi stub.
|
||||
#
|
||||
# The .vpi text remains the serialized interface (types, classes, enums, defines, typedefs).
|
||||
# Signature queries (arity, self flag, varargs, annotation masks, return text) are served from
|
||||
# this fixed-layout blob instead of rescanning the text. Records are indexed by name at load.
|
||||
#
|
||||
# Layout, little endian:
|
||||
# "VPB1" | u32 version | u32 n_imports | imports | u32 n_funcs | funcs
|
||||
# str = u32 len, bytes, NUL (so readers can use the bytes as a C string)
|
||||
# import = str
|
||||
# func = u8 flags (1 vararg, 2 method, 4 has wf) | 3 pad
|
||||
# | i32 arity | i32 esc | i32 ret | i32 wf
|
||||
# | str name | str ret_text
|
||||
# | (arity + method) x (str param_name, str param_type, str param_default)
|
||||
define VPB_MAGIC = 0x31504256 # bytes 'V','P','B','1' (little endian)
|
||||
define VPB_VERSION = 2
|
||||
define VPB_HEADER = 16
|
||||
define VPB_FLAG_VARARG = 1
|
||||
define VPB_FLAG_METHOD = 2
|
||||
define VPB_FLAG_WF = 4
|
||||
|
||||
|
||||
novirtual class VpiFuncRec:
|
||||
Arity: int
|
||||
Method: int
|
||||
VarArg: int
|
||||
Esc: int
|
||||
Ret: int
|
||||
Wf: int
|
||||
Name: str
|
||||
RetText: str
|
||||
|
||||
|
||||
novirtual class VpiStub:
|
||||
Sha1: str
|
||||
Buf: bytes
|
||||
Size: sizet
|
||||
Funcs: bytes # VpiFuncRec[FuncCount]
|
||||
FuncCount: int
|
||||
Keys: bytes # uint64[Cap]
|
||||
Vals: bytes # int32[Cap]
|
||||
Cap: int
|
||||
Imports: bytes # str*[ImportCount]
|
||||
ImportCount: int
|
||||
|
||||
def Find(self, name: str) -> VpiFuncRec*:
|
||||
if name is None or self.Cap <= 0: return None
|
||||
mask: int = self.Cap - 1
|
||||
h: int = int(_vpb_hash(name) & uint64(mask))
|
||||
kp: uint64* = (uint64*)(uint64(self.Keys) + uint64(h) * 8)
|
||||
probe: int = 0
|
||||
while probe < self.Cap:
|
||||
if kp[0] == uint64(0): return None
|
||||
vp: int32* = (int32*)(uint64(self.Vals) + uint64(h) * 4)
|
||||
idx: int = int(vp[0]) - 1
|
||||
if idx >= 0:
|
||||
rec: VpiFuncRec* = (VpiFuncRec*)(uint64(self.Funcs) + uint64(idx) * VpiFuncRec.__sizeof__())
|
||||
if rec.Name is not None and string.strcmp(rec.Name, name) == 0: return rec
|
||||
h = (h + 1) & mask
|
||||
probe += 1
|
||||
return None
|
||||
|
||||
def ImportAt(self, i: int) -> str:
|
||||
if i < 0 or i >= self.ImportCount: return None
|
||||
sp: str* = (str*)(uint64(self.Imports) + uint64(i) * 8)
|
||||
return sp[0]
|
||||
|
||||
|
||||
# --- primitive encoding ------------------------------------------------------
|
||||
def _vpb_hash(name: str) -> uint64:
|
||||
h: uint32 = uint32(2166136261)
|
||||
if name is None: return 1
|
||||
n: sizet = string.strlen(name)
|
||||
i: sizet = 0
|
||||
while i < n:
|
||||
h = (h ^ uint32(uint8(name[i]))) * uint32(16777619)
|
||||
i += 1
|
||||
return uint64(h) | uint64(1)
|
||||
|
||||
|
||||
def _vpb_u32(buf: bytes, off: sizet) -> uint32:
|
||||
p: uint8* = (uint8*)(uint64(buf) + uint64(off))
|
||||
return uint32(p[0]) | (uint32(p[1]) << 8) | (uint32(p[2]) << 16) | (uint32(p[3]) << 24)
|
||||
|
||||
|
||||
def _vpb_i32(buf: bytes, off: sizet) -> int:
|
||||
return int(int32(_vpb_u32(buf, off)))
|
||||
|
||||
|
||||
def _vpb_put32(buf: bytes, off: sizet, v: uint32) -> None:
|
||||
p: uint8* = (uint8*)(uint64(buf) + uint64(off))
|
||||
p[0] = uint8(v & uint32(0xFF))
|
||||
p[1] = uint8((v >> 8) & uint32(0xFF))
|
||||
p[2] = uint8((v >> 16) & uint32(0xFF))
|
||||
p[3] = uint8((v >> 24) & uint32(0xFF))
|
||||
|
||||
|
||||
def _vpb_put_str(buf: bytes, off: sizet, s: str) -> sizet:
|
||||
n: sizet = 0
|
||||
if s is not None: n = string.strlen(s)
|
||||
_vpb_put32(buf, off, uint32(n))
|
||||
if n > 0: string.memcpy(buf + off + 4, s, n)
|
||||
p: uint8* = (uint8*)(uint64(buf) + uint64(off) + 4 + uint64(n))
|
||||
p[0] = uint8(0)
|
||||
return off + 5 + n
|
||||
|
||||
|
||||
def _vpb_read_str(buf: bytes, size: sizet, off: sizet, out_len: sizet*) -> str:
|
||||
n: sizet = 0
|
||||
if off + 4 <= size:
|
||||
n = sizet(_vpb_u32(buf, off))
|
||||
if off + 5 + n > size: n = 0 if size < off + 5 else size - off - 5
|
||||
if out_len is not None: out_len[0] = n
|
||||
if n == 0: return None
|
||||
return (str)(uint64(buf) + uint64(off) + 4)
|
||||
|
||||
|
||||
# --- loading -----------------------------------------------------------------
|
||||
def _vpb_alloc(n: sizet) -> bytes:
|
||||
if n <= 0: return None
|
||||
b: bytes = stdlib.malloc(n)
|
||||
if b is not None: string.memset(b, 0, n)
|
||||
return b
|
||||
|
||||
|
||||
# Parse - decode a blob into records plus a name index. Returns None on a format mismatch.
|
||||
def Parse(sha1: str, buf: bytes, size: sizet, keep: int) -> VpiStub*:
|
||||
if buf is None or size < VPB_HEADER: return None
|
||||
if _vpb_u32(buf, 0) != uint32(VPB_MAGIC): return None
|
||||
if _vpb_u32(buf, 4) != uint32(VPB_VERSION): return None
|
||||
st: VpiStub* = stdlib.malloc(VpiStub.__sizeof__())
|
||||
if st is None: return None
|
||||
string.memset(st, 0, VpiStub.__sizeof__())
|
||||
st.Sha1 = sha1
|
||||
st.Buf = buf
|
||||
st.Size = size
|
||||
off: sizet = 8
|
||||
ni: int = int(_vpb_u32(buf, off))
|
||||
off += 4
|
||||
if ni > 65536: return None
|
||||
st.ImportCount = ni
|
||||
if ni > 0:
|
||||
arr: bytes = _vpb_alloc(sizet(ni) * 8)
|
||||
if arr is None: return None
|
||||
st.Imports = arr
|
||||
i: int = 0
|
||||
while i < ni:
|
||||
l: sizet = 0
|
||||
s: str = _vpb_read_str(buf, size, off, &l)
|
||||
off = off + 5 + l
|
||||
sp: str* = (str*)(uint64(arr) + uint64(i) * 8)
|
||||
sp[0] = s
|
||||
i += 1
|
||||
nf: int = int(_vpb_u32(buf, off))
|
||||
off += 4
|
||||
if nf > 65536: return None
|
||||
st.FuncCount = nf
|
||||
if nf > 0:
|
||||
recs: bytes = _vpb_alloc(sizet(nf) * VpiFuncRec.__sizeof__())
|
||||
if recs is None: return None
|
||||
st.Funcs = recs
|
||||
i = 0
|
||||
while i < nf:
|
||||
rec: VpiFuncRec* = (VpiFuncRec*)(uint64(recs) + uint64(i) * VpiFuncRec.__sizeof__())
|
||||
flags: int = int(_vpb_u32(buf, off) & uint32(0xFF))
|
||||
rec.Arity = _vpb_i32(buf, off + 4)
|
||||
rec.Esc = _vpb_i32(buf, off + 8)
|
||||
rec.Ret = _vpb_i32(buf, off + 12)
|
||||
rec.Wf = _vpb_i32(buf, off + 16)
|
||||
rec.VarArg = 1 if (flags & VPB_FLAG_VARARG) != 0 else 0
|
||||
rec.Method = 1 if (flags & VPB_FLAG_METHOD) != 0 else 0
|
||||
rec.Name = _vpb_read_str(buf, size, off + 20, None)
|
||||
ln: sizet = 0
|
||||
_vpb_read_str(buf, size, off + 20, &ln)
|
||||
off = off + 25 + ln
|
||||
rec.RetText = _vpb_read_str(buf, size, off, &ln)
|
||||
off = off + 5 + ln
|
||||
# Skip the parameter list: (arity + method) triples of strings.
|
||||
skip: int = rec.Arity + rec.Method
|
||||
if skip > 0:
|
||||
k: int = 0
|
||||
while k < skip * 3:
|
||||
_vpb_read_str(buf, size, off, &ln)
|
||||
off = off + 5 + ln
|
||||
k += 1
|
||||
i += 1
|
||||
cap: int = 32
|
||||
while cap < nf * 2: cap = cap * 2
|
||||
keys: bytes = _vpb_alloc(sizet(cap) * 8)
|
||||
vals: bytes = _vpb_alloc(sizet(cap) * 4)
|
||||
if keys is None or vals is None: return None
|
||||
st.Keys = keys
|
||||
st.Vals = vals
|
||||
st.Cap = cap
|
||||
mask: int = cap - 1
|
||||
i = 0
|
||||
while i < nf:
|
||||
rec2: VpiFuncRec* = (VpiFuncRec*)(uint64(recs) + uint64(i) * VpiFuncRec.__sizeof__())
|
||||
if rec2.Name is not None and rec2.Method == 0:
|
||||
h: int = int(_vpb_hash(rec2.Name) & uint64(mask))
|
||||
kh: uint64 = _vpb_hash(rec2.Name)
|
||||
probe: int = 0
|
||||
while probe < cap:
|
||||
kp: uint64* = (uint64*)(uint64(keys) + uint64(h) * 8)
|
||||
if kp[0] == uint64(0):
|
||||
kp[0] = kh
|
||||
vp: int32* = (int32*)(uint64(vals) + uint64(h) * 4)
|
||||
vp[0] = int32(i + 1)
|
||||
break
|
||||
if kp[0] == kh:
|
||||
# Same name twice: ambiguous, so the text scanner decides.
|
||||
vp2: int32* = (int32*)(uint64(vals) + uint64(h) * 4)
|
||||
vp2[0] = int32(0)
|
||||
break
|
||||
h = (h + 1) & mask
|
||||
probe += 1
|
||||
i += 1
|
||||
if keep == 0: stdlib.free(buf)
|
||||
return st
|
||||
|
||||
|
||||
# --- text to records ---------------------------------------------------------
|
||||
def _vpb_strip_prefixes(p: sizet, buf: bytes, le: sizet) -> sizet:
|
||||
while True:
|
||||
if le - p >= 7 and string.strncmp(buf + p, "export ", 7) == 0: p += 7
|
||||
elif le - p >= 7 and string.strncmp(buf + p, "extern ", 7) == 0: p += 7
|
||||
elif le - p >= 7 and string.strncmp(buf + p, "static ", 7) == 0: p += 7
|
||||
elif le - p >= 8 and string.strncmp(buf + p, "virtual ", 8) == 0: p += 8
|
||||
elif le - p >= 10 and string.strncmp(buf + p, "novirtual ", 10) == 0: p += 10
|
||||
else: return p
|
||||
|
||||
|
||||
# _vpb_param_count - top-level comma count of a parameter list slice.
|
||||
def _vpb_param_count(buf: bytes, s: sizet, e: sizet, first_self: int*) -> int:
|
||||
depth: int = 0
|
||||
quote: int = 0
|
||||
count: int = 0
|
||||
seen: int = 0
|
||||
first_name_start: sizet = s
|
||||
while first_name_start < e and buf[first_name_start] == ' ': first_name_start += 1
|
||||
i: sizet = s
|
||||
while i < e:
|
||||
ch: int = buf[i]
|
||||
if quote != 0:
|
||||
if ch == 0x5C: i += 2; continue
|
||||
if ch == quote: quote = 0
|
||||
i += 1
|
||||
continue
|
||||
if ch == 0x22 or ch == 0x27: quote = ch
|
||||
elif ch == 0x28 or ch == 0x5B or ch == 0x7B: depth += 1
|
||||
elif ch == 0x29 or ch == 0x5D or ch == 0x7D: depth -= 1
|
||||
elif ch == 0x2C and depth == 0:
|
||||
if seen == 0: seen = 1
|
||||
count += 1
|
||||
elif ch != ' ' and ch != 0x09:
|
||||
seen = 1
|
||||
i += 1
|
||||
if count > 0: count += 1
|
||||
elif seen != 0: count = 1
|
||||
if first_self is not None:
|
||||
name_len: sizet = 0
|
||||
k: sizet = first_name_start
|
||||
while k < e and (buf[k] == '_' or (buf[k] >= 'a' and buf[k] <= 'z') or (buf[k] >= 'A' and buf[k] <= 'Z') or (buf[k] >= '0' and buf[k] <= '9')):
|
||||
k += 1
|
||||
name_len += 1
|
||||
first_self[0] = 0
|
||||
if name_len == 4 and string.strncmp(buf + first_name_start, "self", 4) == 0: first_self[0] = 1
|
||||
elif name_len == 3 and string.strncmp(buf + first_name_start, "cls", 3) == 0: first_self[0] = 1
|
||||
return count
|
||||
|
||||
|
||||
# _vpb_has_vararg - 1 when a top-level parameter starts with '*'.
|
||||
def _vpb_has_vararg(buf: bytes, s: sizet, e: sizet) -> int:
|
||||
depth: int = 0
|
||||
fresh: int = 1
|
||||
i: sizet = s
|
||||
while i < e:
|
||||
ch: int = buf[i]
|
||||
if ch == 0x28 or ch == 0x5B or ch == 0x7B: depth += 1
|
||||
elif ch == 0x29 or ch == 0x5D or ch == 0x7D: depth -= 1
|
||||
elif depth == 0:
|
||||
if ch == 0x2C: fresh = 1
|
||||
elif ch != ' ' and ch != 0x09:
|
||||
if fresh != 0 and ch == 0x2A: return 1
|
||||
fresh = 0
|
||||
i += 1
|
||||
return 0
|
||||
|
||||
|
||||
# Build - convert a .vpi text buffer into a stub blob (records are allocated from stdlib).
|
||||
def Build(text: bytes, text_len: sizet, sha1: str) -> VpiStub*:
|
||||
if text is None or text_len == 0 or sha1 is None: return None
|
||||
n_imp: int = 0
|
||||
n_fun: int = 0
|
||||
# Pass 1: count imports and functions.
|
||||
pos: sizet = 0
|
||||
while pos < text_len:
|
||||
le: sizet = pos
|
||||
while le < text_len and text[le] != 0x0A: le += 1
|
||||
i: sizet = pos
|
||||
while i < le and text[i] == ' ': i += 1
|
||||
if i < le:
|
||||
if string.strncmp(text + i, "import ", 7) == 0:
|
||||
n_imp += 1
|
||||
j2: sizet = i + 7
|
||||
while j2 < le:
|
||||
if text[j2] == ',': n_imp += 1
|
||||
j2 += 1
|
||||
elif string.strncmp(text + i, "from ", 5) == 0: n_imp += 1
|
||||
elif i - pos == 0 or i - pos == 4:
|
||||
p: sizet = _vpb_strip_prefixes(i, text, le)
|
||||
if le - p >= 4 and string.strncmp(text + p, "def ", 4) == 0: n_fun += 1
|
||||
pos = le + 1
|
||||
recs: bytes = None
|
||||
imps: bytes = None
|
||||
if n_fun > 0:
|
||||
recs = _vpb_alloc(sizet(n_fun) * VpiFuncRec.__sizeof__())
|
||||
if recs is None: return None
|
||||
if n_imp > 0:
|
||||
imps = _vpb_alloc(sizet(n_imp) * 8)
|
||||
if imps is None: return None
|
||||
nf: int = 0
|
||||
ni: int = 0
|
||||
# Pass 2: fill records.
|
||||
pos = 0
|
||||
while pos < text_len:
|
||||
le = pos
|
||||
while le < text_len and text[le] != 0x0A: le += 1
|
||||
i = pos
|
||||
while i < le and text[i] == ' ': i += 1
|
||||
indent: sizet = i - pos
|
||||
if i < le:
|
||||
if string.strncmp(text + i, "from ", 5) == 0:
|
||||
s: sizet = i + 5
|
||||
e: sizet = s
|
||||
while e < le and text[e] != ' ': e += 1
|
||||
if e > s and ni < n_imp:
|
||||
sl: str = _vpb_slice(s, e, text)
|
||||
sp: str* = (str*)(uint64(imps) + uint64(ni) * 8)
|
||||
sp[0] = sl
|
||||
ni += 1
|
||||
i = le # one name per "from" line
|
||||
elif string.strncmp(text + i, "import ", 7) == 0:
|
||||
s: sizet = i + 7
|
||||
while s < le:
|
||||
e: sizet = s
|
||||
while e < le and text[e] != ',': e += 1
|
||||
a: sizet = s
|
||||
while a < e and text[a] == ' ': a += 1
|
||||
b: sizet = e
|
||||
while b > a and text[b - 1] == ' ': b -= 1
|
||||
if b > a and ni < n_imp:
|
||||
sp2: str* = (str*)(uint64(imps) + uint64(ni) * 8)
|
||||
sp2[0] = _vpb_slice(a, b, text)
|
||||
ni += 1
|
||||
s = e + 1
|
||||
i = le
|
||||
if i < le and (indent == 0 or indent == 4):
|
||||
p: sizet = _vpb_strip_prefixes(i, text, le)
|
||||
if nf < n_fun and le - p >= 4 and string.strncmp(text + p, "def ", 4) == 0:
|
||||
rec: VpiFuncRec* = (VpiFuncRec*)(uint64(recs) + uint64(nf) * VpiFuncRec.__sizeof__())
|
||||
rec.Method = 1 if indent == 4 else 0
|
||||
ns: sizet = p + 4
|
||||
ne: sizet = ns
|
||||
while ne < le and text[ne] != '(' and text[ne] != ' ' and text[ne] != ':': ne += 1
|
||||
if ne > ns:
|
||||
rec.Name = _vpb_slice(ns, ne, text)
|
||||
# parenthesised parameter list
|
||||
ps: sizet = ne
|
||||
while ps < le and text[ps] != '(': ps += 1
|
||||
pe: sizet = ps + 1
|
||||
depth: int = 1
|
||||
quote: int = 0
|
||||
while pe < le and depth > 0:
|
||||
ch: int = text[pe]
|
||||
if quote != 0:
|
||||
if ch == 0x5C: pe += 2; continue
|
||||
if ch == quote: quote = 0
|
||||
elif ch == 0x22 or ch == 0x27: quote = ch
|
||||
elif ch == 0x28: depth += 1
|
||||
elif ch == 0x29: depth -= 1
|
||||
pe += 1
|
||||
inner_end: sizet = pe - 1
|
||||
if inner_end > ps:
|
||||
fs: int = 0
|
||||
total: int = _vpb_param_count(text, ps + 1, inner_end, &fs)
|
||||
rec.VarArg = _vpb_has_vararg(text, ps + 1, inner_end)
|
||||
rec.Arity = total
|
||||
if rec.Method != 0 and fs != 0:
|
||||
rec.Arity = total - 1
|
||||
if rec.Arity < 0: rec.Arity = 0
|
||||
# return annotation text
|
||||
rt: sizet = pe
|
||||
while rt + 1 < le and not (text[rt] == '-' and text[rt + 1] == '>'):
|
||||
if text[rt] == ':': break
|
||||
rt += 1
|
||||
if rt + 1 < le and text[rt] == '-' and text[rt + 1] == '>':
|
||||
rs: sizet = rt + 2
|
||||
while rs < le and text[rs] == ' ': rs += 1
|
||||
re2: sizet = rs
|
||||
while re2 < le and text[re2] != ':': re2 += 1
|
||||
if re2 > rs: rec.RetText = _vpb_slice(rs, re2, text)
|
||||
# annotation masks: "# esc=<n> ret=<n> [wf=<n>]"
|
||||
hk: sizet = pe
|
||||
while hk < le and text[hk] != '#': hk += 1
|
||||
if hk < le:
|
||||
rec.Esc = _vpb_key_int(text, hk, le, "esc=")
|
||||
rec.Ret = _vpb_key_int(text, hk, le, "ret=")
|
||||
rec.Wf = _vpb_key_int(text, hk, le, "wf=")
|
||||
nf += 1
|
||||
pos = le + 1
|
||||
st: VpiStub* = stdlib.malloc(VpiStub.__sizeof__())
|
||||
if st is None: return None
|
||||
string.memset(st, 0, VpiStub.__sizeof__())
|
||||
st.Sha1 = sha1
|
||||
st.Funcs = recs
|
||||
st.FuncCount = nf
|
||||
st.Imports = imps
|
||||
st.ImportCount = ni
|
||||
cap: int = 32
|
||||
while cap < nf * 2: cap = cap * 2
|
||||
keys: bytes = _vpb_alloc(sizet(cap) * 8)
|
||||
vals: bytes = _vpb_alloc(sizet(cap) * 4)
|
||||
if keys is None or vals is None: return None
|
||||
st.Keys = keys
|
||||
st.Vals = vals
|
||||
st.Cap = cap
|
||||
mask: int = cap - 1
|
||||
k: int = 0
|
||||
while k < nf:
|
||||
rec3: VpiFuncRec* = (VpiFuncRec*)(uint64(recs) + uint64(k) * VpiFuncRec.__sizeof__())
|
||||
if rec3.Name is not None and rec3.Method == 0:
|
||||
kh: uint64 = _vpb_hash(rec3.Name)
|
||||
h: int = int(kh & uint64(mask))
|
||||
probe: int = 0
|
||||
while probe < cap:
|
||||
kp: uint64* = (uint64*)(uint64(keys) + uint64(h) * 8)
|
||||
if kp[0] == uint64(0):
|
||||
kp[0] = kh
|
||||
vp: int32* = (int32*)(uint64(vals) + uint64(h) * 4)
|
||||
vp[0] = int32(k + 1)
|
||||
break
|
||||
if kp[0] == kh:
|
||||
vp2: int32* = (int32*)(uint64(vals) + uint64(h) * 4)
|
||||
vp2[0] = int32(0)
|
||||
break
|
||||
h = (h + 1) & mask
|
||||
probe += 1
|
||||
k += 1
|
||||
return st
|
||||
|
||||
|
||||
def _vpb_slice(s: sizet, e: sizet, text: bytes) -> str:
|
||||
n: sizet = e - s
|
||||
b: str = stdlib.malloc(n + 1)
|
||||
if b is None: return None
|
||||
string.memcpy(b, text + s, n)
|
||||
b[n] = '\0'
|
||||
return b
|
||||
|
||||
|
||||
# _vpb_key_int - value following key in [from, le).
|
||||
def _vpb_key_int(text: bytes, from: sizet, le: sizet, key: str) -> int:
|
||||
kl: sizet = string.strlen(key)
|
||||
i: sizet = from
|
||||
while i + kl <= le:
|
||||
if string.strncmp(text + i, key, kl) == 0:
|
||||
v: int = 0
|
||||
j: sizet = i + kl
|
||||
while j < le and text[j] >= '0' and text[j] <= '9':
|
||||
v = v * 10 + int(text[j]) - 48
|
||||
j += 1
|
||||
return v
|
||||
i += 1
|
||||
return 0
|
||||
|
||||
|
||||
# Save - serialize a stub to path; 1 = written.
|
||||
def Save(st: VpiStub*, path: str) -> int:
|
||||
if st is None or path is None: return 0
|
||||
total: sizet = VPB_HEADER
|
||||
i: int = 0
|
||||
while i < st.ImportCount:
|
||||
s: str = st.ImportAt(i)
|
||||
total += 5 + (string.strlen(s) if s is not None else 0)
|
||||
i += 1
|
||||
i = 0
|
||||
while i < st.FuncCount:
|
||||
rec: VpiFuncRec* = (VpiFuncRec*)(uint64(st.Funcs) + uint64(i) * VpiFuncRec.__sizeof__())
|
||||
params: int = rec.Arity + rec.Method
|
||||
total += 30 + params * 15
|
||||
if rec.Name is not None: total += string.strlen(rec.Name)
|
||||
if rec.RetText is not None: total += string.strlen(rec.RetText)
|
||||
i += 1
|
||||
blob: bytes = _vpb_alloc(total)
|
||||
if blob is None: return 0
|
||||
_vpb_put32(blob, 0, uint32(VPB_MAGIC))
|
||||
_vpb_put32(blob, 4, uint32(VPB_VERSION))
|
||||
off: sizet = 8
|
||||
_vpb_put32(blob, off, uint32(st.ImportCount))
|
||||
off += 4
|
||||
i = 0
|
||||
while i < st.ImportCount:
|
||||
off = _vpb_put_str(blob, off, st.ImportAt(i))
|
||||
i += 1
|
||||
_vpb_put32(blob, off, uint32(st.FuncCount))
|
||||
off += 4
|
||||
i = 0
|
||||
while i < st.FuncCount:
|
||||
rec: VpiFuncRec* = (VpiFuncRec*)(uint64(st.Funcs) + uint64(i) * VpiFuncRec.__sizeof__())
|
||||
flags: uint32 = uint32(0)
|
||||
if rec.VarArg != 0: flags = flags | uint32(VPB_FLAG_VARARG)
|
||||
if rec.Method != 0: flags = flags | uint32(VPB_FLAG_METHOD)
|
||||
if rec.Wf != 0: flags = flags | uint32(VPB_FLAG_WF)
|
||||
_vpb_put32(blob, off, flags)
|
||||
_vpb_put32(blob, off + 4, uint32(int32(rec.Arity)))
|
||||
_vpb_put32(blob, off + 8, uint32(int32(rec.Esc)))
|
||||
_vpb_put32(blob, off + 12, uint32(int32(rec.Ret)))
|
||||
_vpb_put32(blob, off + 16, uint32(int32(rec.Wf)))
|
||||
off = _vpb_put_str(blob, off + 20, rec.Name)
|
||||
off = _vpb_put_str(blob, off, rec.RetText)
|
||||
params: int = rec.Arity + rec.Method
|
||||
k: int = 0
|
||||
while k < params * 3:
|
||||
off = _vpb_put_str(blob, off, None)
|
||||
k += 1
|
||||
i += 1
|
||||
f: io.File* = io.File(path, io.MODE.W)
|
||||
if f.closed:
|
||||
stdlib.free(blob)
|
||||
return 0
|
||||
f.write(blob, off)
|
||||
f.close()
|
||||
stdlib.free(blob)
|
||||
return 1
|
||||
|
||||
|
||||
# --- process-wide cache ------------------------------------------------------
|
||||
define VPB_CACHE_SLOTS = 32
|
||||
|
||||
_vpb_cache: rawptr = None # VpiStub*[slots]
|
||||
_vpb_cache_next: int = 0
|
||||
|
||||
|
||||
# Emit - write <vpipath>.vpb for a freshly generated .vpi text buffer.
|
||||
def Emit(vpi_path: str, text: bytes, text_len: sizet, sha1: str) -> int:
|
||||
if vpi_path is None or text is None: return 0
|
||||
st: VpiStub* = Build(text, text_len, sha1)
|
||||
if st is None: return 0
|
||||
p: str = _vpb_path(vpi_path)
|
||||
rc: int = 0
|
||||
if p is not None:
|
||||
rc = Save(st, p)
|
||||
stdlib.free(p)
|
||||
return rc
|
||||
|
||||
|
||||
# Ensure - make sure a .vpb companion exists for vpi_path, converting the text once when absent.
|
||||
def Ensure(vpi_path: str, sha1: str) -> int:
|
||||
if vpi_path is None or sha1 is None: return 0
|
||||
p: str = _vpb_path(vpi_path)
|
||||
if p is None: return 0
|
||||
f: io.File* = io.File(p, io.MODE.R)
|
||||
if not f.closed:
|
||||
# Trust an existing companion only when its header matches this format/version.
|
||||
hdr: bytes = stdlib.malloc(8)
|
||||
ok: int = 0
|
||||
if hdr is not None:
|
||||
rd0: LONG = f.read_all(hdr, 8)
|
||||
if int64(rd0) == 8:
|
||||
if _vpb_u32(hdr, 0) == uint32(VPB_MAGIC) and _vpb_u32(hdr, 4) == uint32(VPB_VERSION): ok = 1
|
||||
stdlib.free(hdr)
|
||||
f.close()
|
||||
if ok != 0:
|
||||
stdlib.free(p)
|
||||
return 1
|
||||
stdlib.free(p)
|
||||
rf: io.File* = io.File(vpi_path, io.MODE.R)
|
||||
if rf.closed: return 0
|
||||
sz: LONGLONG = rf.size()
|
||||
if sz <= 0 or sz > 4194304:
|
||||
rf.close()
|
||||
return 0
|
||||
buf: bytes = stdlib.malloc(sizet(sz))
|
||||
if buf is None:
|
||||
rf.close()
|
||||
return 0
|
||||
rd: LONG = rf.read_all(buf, sizet(sz))
|
||||
rf.close()
|
||||
if int64(rd) != sz:
|
||||
stdlib.free(buf)
|
||||
return 0
|
||||
rc: int = Emit(vpi_path, buf, sizet(sz), sha1)
|
||||
stdlib.free(buf)
|
||||
return rc
|
||||
|
||||
|
||||
# _vpb_path - "<...>.vpi" -> "<...>.vpb" (same length).
|
||||
def _vpb_path(vpi_path: str) -> str:
|
||||
n: sizet = string.strlen(vpi_path)
|
||||
if n < 4: return None
|
||||
b: str = stdlib.malloc(n + 1)
|
||||
if b is None: return None
|
||||
string.memcpy(b, vpi_path, n)
|
||||
b[n] = '\0'
|
||||
b[n - 3] = 'v'
|
||||
b[n - 2] = 'p'
|
||||
b[n - 1] = 'b'
|
||||
return b
|
||||
|
||||
|
||||
# Load - cached parse of <sha1>.vpb from the active temp dir; None when absent.
|
||||
def Load(sha1: str, temp_dir: str) -> VpiStub*:
|
||||
global _vpb_cache, _vpb_cache_next
|
||||
if sha1 is None: return None
|
||||
if _vpb_cache is None:
|
||||
_vpb_cache = stdlib.malloc(sizet(VPB_CACHE_SLOTS) * 8)
|
||||
if _vpb_cache is None: return None
|
||||
string.memset(_vpb_cache, 0, sizet(VPB_CACHE_SLOTS) * 8)
|
||||
i: int = 0
|
||||
while i < VPB_CACHE_SLOTS:
|
||||
sp: VpiStub** = (VpiStub**)(uint64(_vpb_cache) + uint64(i) * 8)
|
||||
st: VpiStub* = sp[0]
|
||||
if st is not None and st.Sha1 is not None and string.strcmp(st.Sha1, sha1) == 0: return st
|
||||
i += 1
|
||||
if temp_dir is None: temp_dir = Config.TempDir
|
||||
if temp_dir is None: return None
|
||||
sub: str = Config.slice_subdir(sha1, Config.Sha1SliceLevel)
|
||||
p: str = _vpb_join(temp_dir, sub, sha1)
|
||||
if p is None: return None
|
||||
f: io.File* = io.File(p, io.MODE.R)
|
||||
stdlib.free(p)
|
||||
if f.closed: return None
|
||||
sz: LONGLONG = f.size()
|
||||
if sz <= 0 or sz > 4194304:
|
||||
f.close()
|
||||
return None
|
||||
buf: bytes = stdlib.malloc(sizet(sz))
|
||||
if buf is None:
|
||||
f.close()
|
||||
return None
|
||||
rd: LONG = f.read_all(buf, sizet(sz))
|
||||
f.close()
|
||||
if int64(rd) != sz:
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
st2: VpiStub* = Parse(sha1, buf, sizet(sz), 1)
|
||||
if st2 is None:
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
# Cache by sha1 copy so a caller's transient string is not retained.
|
||||
sn: sizet = string.strlen(sha1)
|
||||
cp: str = stdlib.malloc(sn + 1)
|
||||
if cp is not None:
|
||||
string.memcpy(cp, sha1, sn)
|
||||
cp[sn] = '\0'
|
||||
st2.Sha1 = cp
|
||||
slot: int = _vpb_cache_next
|
||||
_vpb_cache_next = (_vpb_cache_next + 1) % VPB_CACHE_SLOTS
|
||||
sp2: VpiStub** = (VpiStub**)(uint64(_vpb_cache) + uint64(slot) * 8)
|
||||
sp2[0] = st2
|
||||
return st2
|
||||
|
||||
|
||||
def _vpb_join(temp_dir: str, sub: str, sha1: str) -> str:
|
||||
tl: sizet = string.strlen(temp_dir)
|
||||
sl: sizet = 0
|
||||
if sub is not None: sl = string.strlen(sub)
|
||||
hl: sizet = string.strlen(sha1)
|
||||
cap: sizet = tl + sl + hl + 12
|
||||
b: str = stdlib.malloc(cap)
|
||||
if b is None: return None
|
||||
if sl > 0:
|
||||
string.memcpy(b, temp_dir, tl)
|
||||
b[tl] = '/'
|
||||
string.memcpy(b + tl + 1, sub, sl)
|
||||
b[tl + 1 + sl] = '/'
|
||||
string.memcpy(b + tl + 2 + sl, sha1, hl)
|
||||
string.memcpy(b + tl + 2 + sl + hl, ".vpb", 4)
|
||||
b[tl + 6 + sl + hl] = '\0'
|
||||
else:
|
||||
string.memcpy(b, temp_dir, tl)
|
||||
b[tl] = '/'
|
||||
string.memcpy(b + tl + 1, sha1, hl)
|
||||
string.memcpy(b + tl + 1 + hl, ".vpb", 4)
|
||||
b[tl + 5 + hl] = '\0'
|
||||
return b
|
||||
@@ -66,15 +66,15 @@ novirtual class CallGenBase:
|
||||
LLVMIRRegSeq: int = 0
|
||||
|
||||
# --- temp directory ---
|
||||
TempDir: str = None
|
||||
TempDir: char* = None
|
||||
TempDirLen: sizet = 0
|
||||
|
||||
# --- closure name table (was _g_closure_names/_g_closure_count) ---
|
||||
ClosureNames: str = None
|
||||
ClosureNames: char* = None
|
||||
ClosureCount: int = 0
|
||||
|
||||
# --- function-pointer signature table (was _g_funcptr_names/_g_funcptr_rets/_g_funcptr_count) ---
|
||||
FuncptrNames: str = None
|
||||
FuncptrNames: char* = None
|
||||
FuncptrRets: uint64* = None
|
||||
FuncptrCount: int = 0
|
||||
|
||||
@@ -107,7 +107,7 @@ novirtual class CallGenBase:
|
||||
return 0
|
||||
|
||||
# _register_auto_addr_modes - store per-parameter value-reference modes for a callee symbol name.
|
||||
def _register_auto_addr_modes(self, name: str, modes: str, count: int) -> int:
|
||||
def _register_auto_addr_modes(self, name: char*, modes: char*, count: int) -> int:
|
||||
"""Store per-parameter auto-address modes (PermPool-persistent) keyed by callee symbol name."""
|
||||
if name is None or modes is None or count <= 0: return 0
|
||||
perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
||||
@@ -115,10 +115,10 @@ novirtual class CallGenBase:
|
||||
if self.AutoAddrModes is None:
|
||||
self.AutoAddrModes = hashtable.HashTable(perm)
|
||||
if self.AutoAddrModes is None: return 0
|
||||
key: str = perm.alloc(string.strlen(name) + 1)
|
||||
key: char* = perm.alloc(string.strlen(name) + 1)
|
||||
if key is None: return 0
|
||||
string.strcpy(key, name)
|
||||
val: str = perm.alloc(sizet(count) + 1)
|
||||
val: char* = perm.alloc(sizet(count) + 1)
|
||||
if val is None: return 0
|
||||
string.memcpy(val, modes, sizet(count))
|
||||
val[count] = char(0)
|
||||
@@ -126,12 +126,12 @@ novirtual class CallGenBase:
|
||||
return 1
|
||||
|
||||
# _auto_addr_mode - look up the value-reference mode of parameter idx (0=none / 1=ref / 2=temp).
|
||||
def _auto_addr_mode(self, name: str, idx: int) -> int:
|
||||
def _auto_addr_mode(self, name: char*, idx: int) -> int:
|
||||
"""Return the value-reference mode for parameter idx of a callee symbol name (0 if none/unknown)."""
|
||||
if self.AutoAddrModes is None or name is None or idx < 0: return 0
|
||||
v: rawptr = self.AutoAddrModes.__getitem__(name)
|
||||
if v is None: return 0
|
||||
modes: str = rawptr(v)
|
||||
modes: char* = rawptr(v)
|
||||
i: int = 0
|
||||
while i < idx and modes[i] != char(0): i += 1
|
||||
if i != idx: return 0
|
||||
@@ -142,8 +142,8 @@ novirtual class CallGenBase:
|
||||
|
||||
# translate_call_arg - translate one call argument, applying ref/temp auto-address when required.
|
||||
def translate_call_arg(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||||
trans: HT.Translator*, callee_name: str, arg_idx: int, arg_node: ast.AST*,
|
||||
fpty: mlirlite.LLVMType*, use_ctx: str = "param") -> mlirlite.Value*:
|
||||
trans: HT.Translator*, callee_name: char*, arg_idx: int, arg_node: ast.AST*,
|
||||
fpty: mlirlite.LLVMType*, use_ctx: char* = "param") -> mlirlite.Value*:
|
||||
"""Translate a call argument; value-reference params take the address (ref) or a temp copy (temp)."""
|
||||
# 记录“当前实参位置”:被调者内部若因**合成节点**(lineno==0,如构造器内联出的参数赋值)
|
||||
# 报错,可据此把报错精确定位到这个实参表达式(如 `&_on_submit`),而不是退回函数定义行。
|
||||
@@ -188,27 +188,27 @@ novirtual class CallGenBase:
|
||||
# both call it, so there is a single implementation. cur_sha1 is the module being compiled: its own
|
||||
# functions are covered by AutoAddrModes, so the (guaranteed-miss) vpi load is skipped for them.
|
||||
# Returns 0=none / 1=ref / 2=temp.
|
||||
def _vpi_auto_addr_mode(self, call_name: str, cur_sha1: str, idx: int) -> int:
|
||||
def _vpi_auto_addr_mode(self, call_name: char*, cur_sha1: char*, idx: int) -> int:
|
||||
"""Cross-module ({sha1}.cls.method / {sha1}.func) value-reference mode from the vpi cache; 0 if none/unknown."""
|
||||
if call_name is None or idx < 0: return 0
|
||||
prm_annots: str = self._vpi_param_annots_for(call_name, cur_sha1)
|
||||
prm_annots: char* = self._vpi_param_annots_for(call_name, cur_sha1)
|
||||
if prm_annots is None: return 0
|
||||
return VpiReader._vpi_param_annot_seg_mode(prm_annots, idx)
|
||||
|
||||
# _vpi_param_annots_for - cross-module callee 的 vpi 形参注解串;None = 同模块/不可用。
|
||||
# 解析只在 {sha1}.{cls}.{method} / {sha1}.{func} 的拆分这一处实现,_vpi_auto_addr_mode 与
|
||||
# _fail_on_value_arg_to_ptr_param 共用。
|
||||
def _vpi_param_annots_for(self, call_name: str, cur_sha1: str) -> str:
|
||||
def _vpi_param_annots_for(self, call_name: char*, cur_sha1: char*) -> str:
|
||||
"""Return the callee's vpi param-annotation string (None if same-module or unavailable)."""
|
||||
if call_name is None: return None
|
||||
if GenApi.sha1_prefix_len(call_name) == 0: return None
|
||||
# Same-module callee: AutoAddrModes (registration during this compilation) is authoritative.
|
||||
if cur_sha1 is not None and string.strncmp(call_name, cur_sha1, 16) == 0: return None
|
||||
# split {sha1}.{rest}: if rest has '.', it is {cls}.{method}, else {func}
|
||||
rest: str = call_name + 17
|
||||
cls_nm: str = None
|
||||
meth_nm: str = rest
|
||||
dot_r: str = string.strrchr(rest, 46)
|
||||
rest: char* = call_name + 17
|
||||
cls_nm: char* = None
|
||||
meth_nm: char* = rest
|
||||
dot_r: char* = string.strrchr(rest, 46)
|
||||
if dot_r is not None:
|
||||
cls_len: sizet = uint64(dot_r) - uint64(rest)
|
||||
if cls_len > 0:
|
||||
@@ -228,7 +228,7 @@ novirtual class CallGenBase:
|
||||
string.strncpy(sha1_buf, call_name, 16)
|
||||
sha1_buf[16] = '\0'
|
||||
if self.TempDir is not None and self.TempDirLen > 0: VpiReader._ensure_vpi_param_annot_loaded(sha1_buf, self.TempDir)
|
||||
prm_annots: str = VpiReader._lookup_vpi_param_annots(sha1_buf, cls_nm, meth_nm)
|
||||
prm_annots: char* = VpiReader._lookup_vpi_param_annots(sha1_buf, cls_nm, meth_nm)
|
||||
stdlib.free(sha1_buf)
|
||||
if cls_nm is not None: stdlib.free(cls_nm)
|
||||
return prm_annots
|
||||
@@ -236,14 +236,14 @@ novirtual class CallGenBase:
|
||||
# _fail_on_value_arg_to_ptr_param - 强类型(2026-10-22):值语义变量不能隐式取地址传给
|
||||
# 声明为 `T*` 的形参,必须写 `&a`。判定依据是被调者 vpi 里该形参的源码注解(跨模块可用);
|
||||
# 同模块形参的源码写法无法从 declare 反推,保持放行。
|
||||
def _fail_on_value_arg_to_ptr_param(self, trans: HT.Translator*, callee_name: str, idx: int,
|
||||
def _fail_on_value_arg_to_ptr_param(self, trans: HT.Translator*, callee_name: char*, idx: int,
|
||||
arg_node: ast.AST*, fpty: mlirlite.LLVMType*) -> int:
|
||||
"""Fail fast when a value-semantics variable is implicitly addressed into a `T*` parameter."""
|
||||
if trans is None or callee_name is None or arg_node is None or fpty is None: return 0
|
||||
if self.Gen.ExprGenBase.is_ptr_type(fpty) == 0: return 0
|
||||
# 只判定"变量引用"实参(字段/下标/调用结果的类型来源更复杂,交由 coerce 的通用规则)
|
||||
if arg_node.kind() != ast.ASTKind.Name: return 0
|
||||
nm_va: str = (ast.Name*(arg_node)).id
|
||||
nm_va: char* = (ast.Name*(arg_node)).id
|
||||
if nm_va is None: return 0
|
||||
ve_va: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_va)
|
||||
if ve_va is None or ve_va.Alloca is None or ve_va.Alloca.Ty is None: return 0
|
||||
@@ -252,15 +252,15 @@ novirtual class CallGenBase:
|
||||
if self.Gen.ExprGenBase.is_ptr_type(ve_va.Alloca.Ty) != 0: return 0
|
||||
ak_va: int = ve_va.Alloca.Ty.kind()
|
||||
if ak_va != mlirlite.LLVMType.Struct: return 0
|
||||
prm_annots: str = self._vpi_param_annots_for(callee_name, trans.ModuleSha1)
|
||||
prm_annots: char* = self._vpi_param_annots_for(callee_name, trans.ModuleSha1)
|
||||
if prm_annots is None: return 0
|
||||
# vpi 的形参注解段**含 self**(方法第 0 段为空注解),故方法调用的实参下标要 +1
|
||||
rest_va: str = callee_name + 17
|
||||
rest_va: char* = callee_name + 17
|
||||
seg_idx: int = idx
|
||||
if string.strchr(rest_va, '.') is not None: seg_idx = idx + 1
|
||||
if VpiReader._vpi_param_annot_seg_is_ptr(prm_annots, seg_idx) != 1: return 0
|
||||
pf_va: int = GenApi.sha1_prefix_len(callee_name)
|
||||
short_va: str = callee_name + pf_va if pf_va != 0 else callee_name
|
||||
short_va: char* = callee_name + pf_va if pf_va != 0 else callee_name
|
||||
self.Gen.TypeGenErr.fatal_error(arg_node, f"parameter {idx + 1} of '{short_va}' is declared as a pointer (T*), but the argument '{nm_va}' is a value-semantics variable; it would be copied into a temporary and the callee would modify the copy — pass '&{nm_va}' explicitly, or declare the parameter as a value type (T)", "CastError")
|
||||
return 1
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1693,7 +1693,15 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
ar_name: str = call_name + 17
|
||||
ar_n: int = VpiReader.LookupParamCountByName(pool, ar_sha, ar_name)
|
||||
if ar_n >= 0 and int(can) != ar_n:
|
||||
self.Gen.TypeGenErr.fatal_error(cl, f"call to '{ar_name}' passes {int(can)} argument(s) but the definition declares {ar_n} parameter(s) (self excluded)", "TypeError")
|
||||
# ar_n comes from a scan of the .vpi *text*. What this call actually binds to
|
||||
# is the declare injected from the dependency's AST stub (the same source the
|
||||
# linker sees), so when the two disagree the declare wins: a text-side miss
|
||||
# (stale/partial .vpi cache) must not reject a valid call. Only a declare
|
||||
# that agrees with the text is treated as a real arity error.
|
||||
dcl: mlirlite.Function* = self.find_func_in_module(mod, call_name)
|
||||
if dcl is not None:
|
||||
if mlirlite.function_get_param_count(dcl) != int(can):
|
||||
self.Gen.TypeGenErr.fatal_error(cl, f"call to '{ar_name}' passes {int(can)} argument(s) but the definition declares {ar_n} parameter(s) (self excluded)", "TypeError")
|
||||
# Bare C library function (string.memset -> @memset): promote args to the standard signature
|
||||
# (third arg i32 literal -> i64) before creating the declare, so call/declare/define widths match.
|
||||
head = self.Gen.CallGenLib._promote_clib_call_args(pool, builder, mod, call_name, head, can)
|
||||
|
||||
@@ -3113,7 +3113,13 @@ novirtual class StructGenMixin:
|
||||
return entry.IsUnion
|
||||
|
||||
def is_union_by_type(self, struct_ty: mlirlite.LLVMType*) -> int:
|
||||
"""Return 1 when the type-pointer struct is a union, falling back to name matching."""
|
||||
"""Return 1 when the type-pointer struct is a union, falling back to name matching.
|
||||
|
||||
Deliberately a linear scan: an entry's Ty may be rewritten to a new pointer by a deep
|
||||
copy (see find_struct_by_type), so pointer-keyed memoization would go stale; and the
|
||||
name fallback uses substring matching, so a name-keyed cache could change which entry
|
||||
wins when two modules register the same class name.
|
||||
"""
|
||||
if struct_ty is None or self.StructTable is None: return 0
|
||||
ty_name: str = self._extract_struct_name(struct_ty)
|
||||
st_iu: uint64 = uint64(struct_ty)
|
||||
|
||||
@@ -62,7 +62,12 @@ novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
|
||||
def _cdef_idx_find(self, name: str) -> int:
|
||||
"""Probe the name index for name; return the slot index, or -1 if missing/unusable."""
|
||||
if self.CDefineNameIdx is None or self.CDefineNameIdxCap <= 0 or name is None: return -1
|
||||
if self.CDefineNameIdxBuilt != int(len(self.CDefineNames)): return -1
|
||||
if self.CDefineNameIdxBuilt != int(len(self.CDefineNames)):
|
||||
# Out of sync (a registration desynced it): rebuild once instead of falling back to a
|
||||
# full-table scan on every lookup. Indexing is only used below 75% load; otherwise a
|
||||
# rebuild would fail too and every lookup would retry it (worse than scanning).
|
||||
if int(len(self.CDefineNames)) * 4 >= int(self.CDefineNameIdxCap) * 3: return -1
|
||||
if self._cdef_idx_rebuild() != 0: return -1
|
||||
h: uint64 = self._cdef_idx_hash(name)
|
||||
mask: sizet = sizet(self.CDefineNameIdxCap - 1)
|
||||
si: sizet = sizet(h) & mask
|
||||
@@ -90,6 +95,23 @@ novirtual class TypeGenCDef(TypeGenBase.TypeGenBase):
|
||||
si = (si + 1) & mask
|
||||
return 1
|
||||
|
||||
# _cdef_idx_rebuild - rebuild the name index from CDefineNames (one O(n) pass per desync).
|
||||
# 0 = rebuilt, 1 = cannot index (caller keeps the linear scan).
|
||||
def _cdef_idx_rebuild(self) -> int:
|
||||
if self.CDefineNameIdx is None or self.CDefineNameIdxCap <= 0: return 1
|
||||
string.memset(self.CDefineNameIdx, 0, sizet(self.CDefineNameIdxCap) * 8)
|
||||
n: int = int(len(self.CDefineNames))
|
||||
i: int = 0
|
||||
while i < n:
|
||||
nm: str = self.CDefineNames.get(sizet(i))
|
||||
if nm is not None:
|
||||
if self._cdef_idx_insert(i, nm) != 0:
|
||||
self.CDefineNameIdxBuilt = -1
|
||||
return 1
|
||||
i += 1
|
||||
self.CDefineNameIdxBuilt = n
|
||||
return 0
|
||||
|
||||
def _cdef_idx_sync_append(self, name: str) -> None:
|
||||
"""Sync the index after CDefineNames.append (slot = len-1); keep it untrusted if so."""
|
||||
n: int = int(len(self.CDefineNames))
|
||||
|
||||
@@ -21,6 +21,7 @@ import lib.Projectrans.Config as Config
|
||||
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||||
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
|
||||
import lib.Stub.StubGen.Converter as StubConverter
|
||||
import lib.Stub.StubGen.VpiStub as VpiStub
|
||||
import lib.Stub.StubGen.VpiReader as VpiReader
|
||||
|
||||
|
||||
@@ -934,6 +935,7 @@ def _repl_gen_includes_vpi(mb: memhub.MemBuddy*, sr: IncludesScanner.ScanResult*
|
||||
vf: io.File* = io.File(vpi_path, io.MODE.R)
|
||||
if not vf.closed:
|
||||
vf.close()
|
||||
VpiStub.Ensure(vpi_path, ent.Sha1)
|
||||
stdlib.free(vpi_path)
|
||||
ReplIncVpiReuse += 1
|
||||
mb.reset_to(fm)
|
||||
@@ -994,6 +996,7 @@ def _repl_gen_includes_vpi(mb: memhub.MemBuddy*, sr: IncludesScanner.ScanResult*
|
||||
if not pf.closed:
|
||||
pf.write(pyi_buf, pos)
|
||||
pf.close()
|
||||
VpiStub.Emit(vpi_path, pyi_buf, pos, ent.Sha1)
|
||||
ReplIncVpiNew += 1
|
||||
else: ReplIncVpiFail += 1
|
||||
else: ReplIncVpiFail += 1
|
||||
|
||||
@@ -199,6 +199,40 @@ def _SpawnDetached(cmd: str, cwd: str) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
# _SelfExeDir - directory containing the running compiler ("D:/x/viperc.exe" -> "D:/x").
|
||||
def _SelfExeDir(self_exe: str) -> str:
|
||||
if self_exe is None: return None
|
||||
n: sizet = string.strlen(self_exe)
|
||||
cut: sizet = n
|
||||
while cut > 0:
|
||||
ch: char = self_exe[cut - 1]
|
||||
if ch == '\\' or ch == '/':
|
||||
cut -= 1
|
||||
break
|
||||
cut -= 1
|
||||
if cut >= n: return None
|
||||
out: str = stdlib.malloc(cut + 1)
|
||||
if out is None: return None
|
||||
if cut > 0: string.strncpy(out, self_exe, cut)
|
||||
out[cut] = '\0'
|
||||
return out
|
||||
|
||||
|
||||
# _CrashHint - name the common NTSTATUS exit codes. The child inherits the parent console, so
|
||||
# its own output is already visible; these codes are raised as fail-fast and carry no SEH
|
||||
# exception, which is why a traceback handler inside the child cannot print a stack.
|
||||
def _CrashHint(rc: int) -> None:
|
||||
u: uint32 = uint32(rc)
|
||||
if u == uint32(0xC0000005):
|
||||
stdio.printf(" child crashed: access violation (0xC0000005)\n")
|
||||
elif u == uint32(0xC0000374):
|
||||
stdio.printf(" child crashed: heap corruption (0xC0000374) -- fail-fast, no SEH traceback\n")
|
||||
elif u == uint32(0xC00000FD):
|
||||
stdio.printf(" child crashed: stack overflow (0xC00000FD)\n")
|
||||
elif u == uint32(0xC0000409):
|
||||
stdio.printf(" child crashed: fail-fast / security check (0xC0000409)\n")
|
||||
|
||||
|
||||
def RunSelfBuild(extra: str) -> int:
|
||||
self_exe: str = _GetSelfExe()
|
||||
if self_exe is None:
|
||||
@@ -228,6 +262,7 @@ def RunSelfBuild(extra: str) -> int:
|
||||
rc: int = BuildPipeline._run_cmd_wait(cmd)
|
||||
if rc != 0:
|
||||
stdio.printf("FAIL: --build-self compilation failed (exit=%d)\n", rc)
|
||||
_CrashHint(rc)
|
||||
return 1
|
||||
|
||||
built_exe: str = os.path.join(os.path.join(build_dir, "output"), "viperc.exe")
|
||||
@@ -246,6 +281,22 @@ def RunSelfBuild(extra: str) -> int:
|
||||
stdio.printf("FAIL: --build-self could not create %s (Win32 error %d)\n",
|
||||
copyself_cwd, int(err_mkdir))
|
||||
return 1
|
||||
# copyself replaces ViperC\__vpcbuild__ wholesale, so everything the compiler needs next to
|
||||
# viperc.exe must be in the new output directory *before* the copy runs. ircore.dll is the
|
||||
# important one: without it every PhaseB module silently falls back to spawning
|
||||
# mlir-opt/mlir-translate/llc (3 processes per module), which is both slower and a
|
||||
# different code path than the one being profiled.
|
||||
self_dir: str = _SelfExeDir(self_exe)
|
||||
if self_dir is not None:
|
||||
src_dll: str = os.path.join(self_dir, "ircore.dll")
|
||||
dst_dll: str = os.path.join(os.path.join(build_dir, "output"), "ircore.dll")
|
||||
if os.isfile(src_dll) and not os.isfile(dst_dll):
|
||||
cop: BOOL = win32.win32file.CopyFileA(src_dll, dst_dll, 0)
|
||||
if cop == 0:
|
||||
stdio.printf("build-self: warning: could not copy ircore.dll next to the new viperc.exe\n")
|
||||
else:
|
||||
stdio.printf("build-self: copied ircore.dll into the new output directory\n")
|
||||
|
||||
copy_cmd: str = _FormatCopySelfCommand(copyself_exe)
|
||||
if copy_cmd is None:
|
||||
stdio.printf("FAIL: --build-self failed to build the copyself command line\n")
|
||||
|
||||
+109
-21
@@ -149,6 +149,62 @@ def _TokenContains(tok: ast.Token*, line0: int, bcol: int) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
def _IsUnaryKeyword(word: str) -> int:
|
||||
if word is None:
|
||||
return 0
|
||||
if string.strcmp(word, "return") == 0 or string.strcmp(word, "if") == 0 \
|
||||
or string.strcmp(word, "elif") == 0 or string.strcmp(word, "else") == 0 \
|
||||
or string.strcmp(word, "while") == 0 or string.strcmp(word, "for") == 0 \
|
||||
or string.strcmp(word, "in") == 0 or string.strcmp(word, "not") == 0 \
|
||||
or string.strcmp(word, "and") == 0 or string.strcmp(word, "or") == 0 \
|
||||
or string.strcmp(word, "is") == 0 or string.strcmp(word, "case") == 0 \
|
||||
or string.strcmp(word, "raise") == 0 or string.strcmp(word, "assert") == 0 \
|
||||
or string.strcmp(word, "yield") == 0 or string.strcmp(word, "await") == 0 \
|
||||
or string.strcmp(word, "lambda") == 0:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def _NegativeLiteral(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
|
||||
line0: int, bcol: int) -> int:
|
||||
"""Whether the literal at bcol is directly preceded by a unary minus."""
|
||||
if doc is None or line0 < 0 or line0 >= doc.index.Count():
|
||||
return 0
|
||||
i: int = bcol
|
||||
while i > 0:
|
||||
ch: char = _ByteAt(doc, line0, i - 1)
|
||||
if ch == ' ' or ch == '\t' or ch == '\r':
|
||||
i -= 1
|
||||
else:
|
||||
break
|
||||
if i <= 0:
|
||||
return 0
|
||||
if _ByteAt(doc, line0, i - 1) != '-':
|
||||
return 0
|
||||
j: int = i - 1
|
||||
while j > 0:
|
||||
ch2: char = _ByteAt(doc, line0, j - 1)
|
||||
if ch2 == ' ' or ch2 == '\t' or ch2 == '\r':
|
||||
j -= 1
|
||||
else:
|
||||
break
|
||||
if j == 0:
|
||||
return 1
|
||||
prev: char = _ByteAt(doc, line0, j - 1)
|
||||
if string.strchr("=([{,:;+-*/%<>&|^!~", int(prev)) is not None:
|
||||
return 1
|
||||
k: int = j
|
||||
while k > 0:
|
||||
if LspSource._IsIdentChar(_ByteAt(doc, line0, k - 1)) != 0:
|
||||
k -= 1
|
||||
else:
|
||||
break
|
||||
if k == j:
|
||||
return 0
|
||||
word: str = LspSource.SliceBytes(pool, doc, line0, k, line0, j)
|
||||
return _IsUnaryKeyword(word)
|
||||
|
||||
|
||||
def _FindLiteral(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
|
||||
line0: int, ch: int) -> ast.Token*:
|
||||
"""Return the number/string token under the cursor, or None."""
|
||||
@@ -361,39 +417,66 @@ def _IntFloatLine(pool: memhub.MemManager*, v: uint64) -> str:
|
||||
"f16 ", f16), ", f32 "), f32), ", f64 "), f64)
|
||||
|
||||
|
||||
def _IntMarkdown(pool: memhub.MemManager*, tok: ast.Token*) -> str:
|
||||
def _SignedBitsForNegative(v: uint64) -> int:
|
||||
"""Two's-complement width needed for -v (v is the magnitude)."""
|
||||
if v == uint64(0):
|
||||
return 1
|
||||
return _U64Bits(v - uint64(1)) + 1
|
||||
|
||||
|
||||
def _IntMarkdown(pool: memhub.MemManager*, tok: ast.Token*, neg: int) -> str:
|
||||
ovf: int = 0
|
||||
v: uint64 = _ParseMagnitude(tok.str_val, &ovf)
|
||||
head: str = tok.str_val
|
||||
if head is None:
|
||||
head = "0"
|
||||
if neg != 0:
|
||||
head = _Cat(pool, "-", head)
|
||||
body: str = head
|
||||
if ovf != 0:
|
||||
return _CodeBlock(pool, _Cat(pool, head, "\nvalue exceeds 64 bits (unsigned)"))
|
||||
body = _Cat(pool, body, _Cat(pool, "\ndec ", _DecU64(pool, v)))
|
||||
body = _Cat(pool, body, _Cat(pool, "\nhex ", _HexU64(pool, v)))
|
||||
body = _Cat(pool, body, _Cat(pool, "\nbinary ", _BinU64(pool, v)))
|
||||
ub: int = _U64Bits(v)
|
||||
sb: int = ub + 1
|
||||
bits_line: str = _Cat(pool, _Cat(pool, _Cat(pool, _Cat(pool,
|
||||
"\nbits ", _U64ToString(pool, uint64(ub))),
|
||||
" unsigned, "),
|
||||
_U64ToString(pool, uint64(sb))),
|
||||
" signed")
|
||||
body = _Cat(pool, body, bits_line)
|
||||
uw: int = _PowTwoWidth(ub)
|
||||
iw: int = _PowTwoWidth(sb)
|
||||
body = _Cat(pool, body, _Cat(pool, "\nuint u", _U64ToString(pool, uint64(uw))))
|
||||
body = _Cat(pool, body, _Cat(pool, "\nint i", _U64ToString(pool, uint64(iw))))
|
||||
dec: str = _DecU64(pool, v)
|
||||
if neg != 0 and v != uint64(0):
|
||||
dec = _Cat(pool, "-", dec)
|
||||
body = _Cat(pool, body, _Cat(pool, "\ndec ", dec))
|
||||
hx: str = _HexU64(pool, v)
|
||||
bn: str = _BinU64(pool, v)
|
||||
if neg != 0 and v != uint64(0):
|
||||
hx = _Cat(pool, "-", hx)
|
||||
bn = _Cat(pool, "-", bn)
|
||||
body = _Cat(pool, body, _Cat(pool, "\nhex ", hx))
|
||||
body = _Cat(pool, body, _Cat(pool, "\nbinary ", bn))
|
||||
if neg != 0:
|
||||
sb: int = _SignedBitsForNegative(v)
|
||||
bits_line: str = _Cat(pool, _Cat(pool, _Cat(pool,
|
||||
"\nbits ", _U64ToString(pool, uint64(sb))),
|
||||
" signed (negative; unsigned storage cannot hold it)"), "")
|
||||
body = _Cat(pool, body, bits_line)
|
||||
iw: int = _PowTwoWidth(sb)
|
||||
body = _Cat(pool, body, "\nuint n/a (negative)")
|
||||
body = _Cat(pool, body, _Cat(pool, "\nint i", _U64ToString(pool, uint64(iw))))
|
||||
else:
|
||||
ub: int = _U64Bits(v)
|
||||
sb: int = ub + 1
|
||||
bits_line: str = _Cat(pool, _Cat(pool, _Cat(pool, _Cat(pool,
|
||||
"\nbits ", _U64ToString(pool, uint64(ub))),
|
||||
" unsigned, "),
|
||||
_U64ToString(pool, uint64(sb))),
|
||||
" signed")
|
||||
body = _Cat(pool, body, bits_line)
|
||||
uw: int = _PowTwoWidth(ub)
|
||||
iw: int = _PowTwoWidth(sb)
|
||||
body = _Cat(pool, body, _Cat(pool, "\nuint u", _U64ToString(pool, uint64(uw))))
|
||||
body = _Cat(pool, body, _Cat(pool, "\nint i", _U64ToString(pool, uint64(iw))))
|
||||
body = _Cat(pool, body, _Cat(pool, "\nfloat ", _IntFloatLine(pool, v)))
|
||||
if tok.int_width > 0:
|
||||
decl: str = "u" if tok.int_unsigned != 0 else "i"
|
||||
decl = _Cat(pool, decl, _U64ToString(pool, uint64(tok.int_width)))
|
||||
fits: int = 0
|
||||
if tok.int_unsigned != 0:
|
||||
fits = IntFacts.FitsUnsignedValue(tok.int_width, v)
|
||||
fits = 0 if neg != 0 else IntFacts.FitsUnsignedValue(tok.int_width, v)
|
||||
else:
|
||||
fits = IntFacts.FitsSignedValue(tok.int_width, v, 0)
|
||||
fits = IntFacts.FitsSignedValue(tok.int_width, v, neg)
|
||||
state: str = "fits" if fits != 0 else "overflow"
|
||||
body = _Cat(pool, body, _Cat(pool, _Cat(pool, _Cat(pool,
|
||||
"\ndeclared ", decl), " ("), _Cat(pool, state, ")")))
|
||||
@@ -500,13 +583,17 @@ def _FloatFitLine(pool: memhub.MemManager*, v: double) -> str:
|
||||
"f16 ", s16), ", f32 "), s32), ", f64 "), s64)
|
||||
|
||||
|
||||
def _FloatMarkdown(pool: memhub.MemManager*, tok: ast.Token*) -> str:
|
||||
def _FloatMarkdown(pool: memhub.MemManager*, tok: ast.Token*, neg: int) -> str:
|
||||
v: double = tok.float_val
|
||||
if neg != 0:
|
||||
v = -v
|
||||
bits: uint64 = uint64(0)
|
||||
*&bits = *&v
|
||||
body: str = tok.str_val
|
||||
if body is None:
|
||||
body = "0.0"
|
||||
if neg != 0:
|
||||
body = _Cat(pool, "-", body)
|
||||
body = _Cat(pool, body, _Cat(pool, "\nhex ", _HexU64Fixed(pool, bits)))
|
||||
body = _Cat(pool, body, _Cat(pool, "\nbinary ", _BinU64(pool, bits)))
|
||||
body = _Cat(pool, body, _Cat(pool, "\nfloat ", _FloatFitLine(pool, v)))
|
||||
@@ -650,14 +737,15 @@ def Hover(pool: memhub.MemManager*, tr: LspTransport.LspTransport*,
|
||||
if text is None:
|
||||
tok: ast.Token* = _FindLiteral(pool, doc, line0, ch)
|
||||
if tok is not None:
|
||||
neg: int = _NegativeLiteral(pool, doc, tok.lineno - 1, tok.col_offset)
|
||||
if tok.type == ast.TokenType.String:
|
||||
text = _StringMarkdown(pool, doc, tok)
|
||||
elif tok.is_complex != 0:
|
||||
text = None
|
||||
elif tok.is_float != 0:
|
||||
text = _FloatMarkdown(pool, tok)
|
||||
text = _FloatMarkdown(pool, tok, neg)
|
||||
else:
|
||||
text = _IntMarkdown(pool, tok)
|
||||
text = _IntMarkdown(pool, tok, neg)
|
||||
if text is None:
|
||||
text = _KeywordMarkdown(pool, _WordAt(pool, doc, line0, ch))
|
||||
if text is None:
|
||||
|
||||
@@ -98,6 +98,19 @@ def string_use() -> int:
|
||||
return len("hello")
|
||||
|
||||
|
||||
def negative_use() -> int:
|
||||
return -128
|
||||
|
||||
|
||||
def negative_float_use() -> float:
|
||||
return -3.5
|
||||
|
||||
|
||||
def subtract_use() -> int:
|
||||
x: int = 5
|
||||
return x - 1
|
||||
|
||||
|
||||
def else_block(flag: int) -> int:
|
||||
if flag != 0:
|
||||
return 1
|
||||
|
||||
@@ -1885,6 +1885,20 @@ def gate10(exe: str) -> int:
|
||||
for piece in ("type str", "length 5 bytes"):
|
||||
if piece not in string_hover:
|
||||
raise AssertionError(f"string hover lacks {piece!r}: {string_hover}")
|
||||
neg_hover = hover_text(at("return -128", len("return -")))
|
||||
for piece in ("dec -128", "hex -0x80", "binary -0b1000_0000",
|
||||
"bits 8 signed", "uint n/a (negative)", "int i8",
|
||||
"float f16 exact, f32 exact, f64 exact"):
|
||||
if piece not in neg_hover:
|
||||
raise AssertionError(f"negative hover lacks {piece!r}: {neg_hover}")
|
||||
neg_float_hover = hover_text(at("return -3.5", len("return -")))
|
||||
if "hex 0xC00C000000000000" not in neg_float_hover:
|
||||
raise AssertionError(f"negative float bit pattern is wrong: {neg_float_hover}")
|
||||
if "f16 exact, f32 exact, f64 exact" not in neg_float_hover:
|
||||
raise AssertionError(f"negative float exactness is wrong: {neg_float_hover}")
|
||||
minus_hover = hover_text(at("return x - 1", len("return x - ")))
|
||||
if "dec 1" not in minus_hover:
|
||||
raise AssertionError(f"binary minus was treated as unary: {minus_hover}")
|
||||
|
||||
def reference_ranges(pos: dict[str, int]) -> list[tuple[str, int, int]]:
|
||||
result = request("textDocument/references", pos,
|
||||
|
||||
Reference in new issue
Block a user