diff --git a/App/lib/Phase/BuildPipeline.vp b/App/lib/Phase/BuildPipeline.vp index 29dc6de..ec41553 100644 --- a/App/lib/Phase/BuildPipeline.vp +++ b/App/lib/Phase/BuildPipeline.vp @@ -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__()) diff --git a/App/lib/Phase/Phase1/Phase1Main.vp b/App/lib/Phase/Phase1/Phase1Main.vp index a3cb92c..4e467b0 100644 --- a/App/lib/Phase/Phase1/Phase1Main.vp +++ b/App/lib/Phase/Phase1/Phase1Main.vp @@ -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) diff --git a/App/lib/Phase/Phase2/__init__.vp b/App/lib/Phase/Phase2/__init__.vp index eca9fc7..7e6043a 100644 --- a/App/lib/Phase/Phase2/__init__.vp +++ b/App/lib/Phase/Phase2/__init__.vp @@ -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) diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 609c845..e0dc0e0 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -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 diff --git a/App/lib/Stub/StubGen/VpiStub.vp b/App/lib/Stub/StubGen/VpiStub.vp new file mode 100644 index 0000000..eef1064 --- /dev/null +++ b/App/lib/Stub/StubGen/VpiStub.vp @@ -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= ret= [wf=]" + 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 .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 .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 diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index 0056681..e4c1529 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -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 diff --git a/App/lib/core/Gen/Call/CallGenLeafKit.vp b/App/lib/core/Gen/Call/CallGenLeafKit.vp index 3d3e6c6..e82a6a1 100644 --- a/App/lib/core/Gen/Call/CallGenLeafKit.vp +++ b/App/lib/core/Gen/Call/CallGenLeafKit.vp @@ -45,7 +45,7 @@ define GFN_FNV_PRIME = 16777619 define GFN_FNV_MASK = 4294967295 -def _gfn_hash(name: str) -> uint64: +def _gfn_hash(name: char*) -> uint64: """Function name to 64-bit hash (32-bit FNV-1a, byte-wise until NUL)""" h: uint64 = uint64(GFN_FNV_OFFSET) if name is None: return h @@ -62,7 +62,7 @@ def _gfn_idx_slot(idx_base: sizet*, si: sizet) -> sizet*: return (sizet*)(uint64(idx_base) + uint64(si) * 8) -def _gfn_idx_find(funcs: GenTypes.FuncEntry*, idx_base: sizet*, cap: int, name: str) -> int: +def _gfn_idx_find(funcs: GenTypes.FuncEntry*, idx_base: sizet*, cap: int, name: char*) -> int: """Probe a name in the global func-name index; return the matching slot (>=0) or -1""" if funcs is None or idx_base is None or cap <= 0 or name is None: return -1 h: uint64 = _gfn_hash(name) @@ -82,7 +82,7 @@ def _gfn_idx_find(funcs: GenTypes.FuncEntry*, idx_base: sizet*, cap: int, name: return -1 -def _gfn_idx_insert(idx_base: sizet*, cap: int, slot_index: int, name: str) -> int: +def _gfn_idx_insert(idx_base: sizet*, cap: int, slot_index: int, name: char*) -> int: """Insert (name -> slot_index) into the index; 0 = success, 1 = failure""" if idx_base is None or cap <= 0 or name is None: return 1 h: uint64 = _gfn_hash(name) @@ -110,7 +110,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): if func is None: return 0 cur_p: mlirlite.Param* = mlirlite.function_get_param_head(func) while cur_p is not None: - pn: str = mlirlite.param_get_name(cur_p) + pn: char* = mlirlite.param_get_name(cur_p) if pn is not None and string.strcmp(pn, "%__eh_msg_out__") == 0: return 1 cur_p = mlirlite.param_get_next(cur_p) return 0 @@ -119,7 +119,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): # HandlesExprCall). Reuses an existing msg/code alloca or out-param, else a local null alloca. # ============================================================ - def _emit_eh_guarded_call(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, func: mlirlite.Function*, call_name: str, head: mlirlite.Value*, count: int, ret_ty: mlirlite.LLVMType*, is_variadic: int) -> mlirlite.Value*: + def _emit_eh_guarded_call(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, func: mlirlite.Function*, call_name: char*, head: mlirlite.Value*, count: int, ret_ty: mlirlite.LLVMType*, is_variadic: int) -> mlirlite.Value*: """Call a function with try-except EH detection; returns the call result (positioned at ok block)""" # Argument-count check (fail-fast at the front end instead of leaking to the LLVM verifier). # Callee params include the injected EH out-params (%__eh_msg_out__/__eh_code_out__), which the @@ -137,7 +137,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): p_chk = mlirlite.param_get_next(p_chk) if self._func_has_eh_params(chk_func) == 1: n_declared = n_declared - 2 if n_declared >= 0 and count != n_declared: - fname_chk: str = mlirlite.function_get_name(chk_func) + fname_chk: char* = mlirlite.function_get_name(chk_func) if fname_chk is None: fname_chk = call_name self.Gen.TypeGenErr.fatal_error( None, @@ -184,7 +184,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): if is_err_g is None: return result lc_g: int = trans._label_counter trans._label_counter = lc_g + 1 - bb_buf_g: str = pool.alloc(64) + bb_buf_g: char* = pool.alloc(64) if bb_buf_g is None: return result viperlib.snprintf(bb_buf_g, 64, "eh.err.%d", lc_g) err_bb_g: mlirlite.BasicBlock* = mlirlite.create_block(pool, cur_func_g, bb_buf_g) @@ -258,7 +258,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): self.Gen.TypeGenErr.fatal_error(cls_node, "isinstance() argument type inference failed", "TypeError") ci: int = 0 while cls_names[ci] != 0: - cn: str = rawptr(cls_names[ci]) + cn: char* = rawptr(cls_names[ci]) if cn is not None: if self.Gen.StructGen.is_novtable_by_name(cn) != 0 or self.Gen.StructGen.has_vtable_by_name(cn) == 0: # NoVTable / unknown class -> compile-time check (obj static-type MRO) @@ -277,7 +277,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): if subs is not None: k: int = 0 while subs[k] != 0: - scn: str = rawptr(subs[k]) + scn: char* = rawptr(subs[k]) if scn is not None: sub_i8: mlirlite.Value* = self._make_vtable_ref_i8(pool, builder, mod, scn, trans) if sub_i8 is not None: @@ -334,7 +334,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return arr - def _collect_subclass_vtable_classes(self, pool: memhub.MemBuddy*, class_name: str) -> sizet*: + def _collect_subclass_vtable_classes(self, pool: memhub.MemBuddy*, class_name: char*) -> sizet*: """Reverse BFS collect the target class and all subclasses (0-terminated array)""" if class_name is None: return None names: sizet* = pool.alloc(8 * (CallGenBase.ISINSTANCE_CLASS_MAX + 1)) @@ -351,13 +351,13 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): while qcount > 0: qcount -= 1 cur_addr: sizet = queue[qcount] - cur: str = rawptr(cur_addr) + cur: char* = rawptr(cur_addr) if cur is None: continue dup: int = 0 for vi in range(int(vcount)): va: sizet = visited[vi] if va != 0: - vn: str = rawptr(va) + vn: char* = rawptr(va) if vn is not None and string.strcmp(vn, cur) == 0: dup = 1 break @@ -381,7 +381,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): for j in range(int(e2.ParentCount)): pa: sizet = parr[j] if pa != 0: - pn: str = rawptr(pa) + pn: char* = rawptr(pa) if pn is not None and string.strcmp(pn, cur) == 0: if qcount < CallGenBase.ISINSTANCE_CLASS_MAX: queue[qcount] = sizet(e2.Name) @@ -391,22 +391,22 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return names - def _make_vtable_ref_i8(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: str, trans: HT.Translator* = None) -> mlirlite.Value*: + def _make_vtable_ref_i8(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, class_name: char*, trans: HT.Translator* = None) -> mlirlite.Value*: """Build a class vtable address reference (i8*): defined here directly, external cross-module""" if class_name is None: return None i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool) i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty) - buf: str = pool.alloc(256) + buf: char* = pool.alloc(256) if buf is None: return None viperlib.snprintf(buf, 256, "%s.vtable", class_name) - vn: str = rawptr(buf) - sha1: str = self.Gen.StructGen.get_struct_sha1(class_name) + vn: char* = rawptr(buf) + sha1: char* = self.Gen.StructGen.get_struct_sha1(class_name) if sha1 is None: if trans is None: return None sha1 = trans.ModuleSha1 if sha1 is None: return None - mangled: str = self.Gen.FuncGenBase._mangle_name_with_sha1(sha1, vn) - ref_name: str = pool.alloc(256) + mangled: char* = self.Gen.FuncGenBase._mangle_name_with_sha1(sha1, vn) + ref_name: char* = pool.alloc(256) if ref_name is None: return None # vtable names carry the sha1 dot prefix, so always use the @"..." quoted form viperlib.snprintf(ref_name, 256, "@\"%s\"", mangled) @@ -426,19 +426,19 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return mlirlite.build_bitcast(builder, ref, i8_ptr_ty) - def _isinstance_compile_time(self, pool: memhub.MemBuddy*, obj_ptr: mlirlite.Value*, class_name: str) -> int: + def _isinstance_compile_time(self, pool: memhub.MemBuddy*, obj_ptr: mlirlite.Value*, class_name: char*) -> int: """Static check: whether obj's static type equals class_name or lists it in its MRO""" if obj_ptr is None or obj_ptr.Ty is None or class_name is None: return 0 match obj_ptr.Ty: case mlirlite.LLVMType.Ptr(obj_struct_ty): - sname: str = self.Gen.StructGen.get_class_name_by_type(pool, obj_struct_ty) + sname: char* = self.Gen.StructGen.get_class_name_by_type(pool, obj_struct_ty) if sname is not None: if string.strcmp(sname, class_name) == 0: return 1 mro: sizet* = self.Gen.StructGen.get_mro_chain(pool, sname) if mro is not None: k: int = 0 while mro[k] != 0: - mn: str = rawptr(mro[k]) + mn: char* = rawptr(mro[k]) if mn is not None and string.strcmp(mn, class_name) == 0: return 1 k += 1 case _: @@ -527,7 +527,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return 1 - def register_closure_name(self, name: str) -> int: + def register_closure_name(self, name: char*) -> int: """Register a nested function (closure variable) name; returns 0 on success""" if name is None: return 1 if self._ensure_closure_names() == 0: return 1 @@ -538,7 +538,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return 0 - def is_closure_name(self, name: str) -> int: + def is_closure_name(self, name: char*) -> int: """Return whether name is registered as a closure variable name (1 = yes, 0 = no)""" if name is None or self.ClosureNames is None: return 0 i: sizet @@ -557,23 +557,23 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return 1 - def _func_ptr_sig_fatal(self, pool: memhub.MemBuddy*, node: ast.AST*, name: str, ret_ty: mlirlite.LLVMType*, reason: str) -> None: + def _func_ptr_sig_fatal(self, pool: memhub.MemBuddy*, node: ast.AST*, name: char*, ret_ty: mlirlite.LLVMType*, reason: char*) -> None: """Fail compilation when a function-pointer signature registration cannot be honored""" cap: int = CallGenBase.MAX_FUNC_PTR_SIGS - ret_str: str = "?" + ret_str: char* = "?" if ret_ty is not None: - typed_buf: str = pool.alloc_tagged(4096, memhub.DETSITE_LEAFKIT_539) + typed_buf: char* = pool.alloc_tagged(4096, memhub.DETSITE_LEAFKIT_539) if typed_buf is not None: mlirlite.TypePrint(typed_buf, 4096, ret_ty, pool) if typed_buf[0] != '\0': ret_str = typed_buf - msg_buf: str = pool.alloc(1024) + msg_buf: char* = pool.alloc(1024) if msg_buf is not None: viperlib.snprintf(msg_buf, 1024, "Function pointer signature table registration failed: variable '%s' (return type %s) -- %s. " "Current limit MAX_FUNC_PTR_SIGS=%d, %d entries registered. " "Duplicate registration with the same name and return type hits in place and does not occupy a new slot; " "if more function pointer variables are really needed, raise the " "MAX_FUNC_PTR_SIGS (the single repo-wide source; changing this one place takes effect everywhere)", name, ret_str, reason, cap, self.FuncptrCount) self.Gen.TypeGenErr.fatal_error(node, msg_buf, "TypeError") return self.Gen.TypeGenErr.fatal_error(node, "Function pointer signature table registration failed (table full or variable name conflict)", "TypeError") - def register_func_ptr_sig(self, pool: memhub.MemBuddy*, name: str, ret_ty: mlirlite.LLVMType*, node: ast.AST* = None) -> int: + def register_func_ptr_sig(self, pool: memhub.MemBuddy*, name: char*, ret_ty: mlirlite.LLVMType*, node: ast.AST* = None) -> int: """Register a function-pointer variable's return type; 0 = success, 1 = failed (already reported)""" if name is None or ret_ty is None: return 1 if self._ensure_funcptr_sigs() == 0: @@ -603,7 +603,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return 0 - def lookup_func_ptr_ret(self, name: str) -> mlirlite.LLVMType*: + def lookup_func_ptr_ret(self, name: char*) -> mlirlite.LLVMType*: """Look up a function-pointer variable's return type; None = unregistered (defaults to i32)""" if name is None or self.FuncptrNames is None: return None for i in range(int(self.FuncptrCount)): @@ -612,7 +612,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return None - def _translate_func_ptr_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, fp_alloca: mlirlite.Value*, func_name: str, cargs: list[ast.AST*]*, can: sizet, trans: HT.Translator*) -> mlirlite.Value*: + def _translate_func_ptr_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, fp_alloca: mlirlite.Value*, func_name: char*, cargs: list[ast.AST*]*, can: sizet, trans: HT.Translator*) -> mlirlite.Value*: """Indirect call through a function-pointer variable; returns the result Value""" if builder is None or fp_alloca is None: return None i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool) @@ -677,7 +677,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): arg0: ast.AST* = cargs.get(0) if arg0 is None: return - fmt_str: str = None + fmt_str: char* = None arg0_kind: int = arg0.kind() if arg0_kind == ast.ASTKind.Constant: cn: ast.Constant* = ast.Constant*(arg0) @@ -778,7 +778,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): case _: pass - token: str = None + token: char* = None chain_arg: int = 1 # 1 = this token consumes an arg, 0 = text only arg_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(arg_val.Ty) arg_fbits: int = self.Gen.ExprGenBase.get_llvm_float_bits(arg_val.Ty) @@ -862,7 +862,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return funcs_ptr - def find_func_in_table(self, funcs: GenTypes.FuncEntry*, func_count: int, name: str, owner_sha1: str = None) -> mlirlite.Function*: + def find_func_in_table(self, funcs: GenTypes.FuncEntry*, func_count: int, name: char*, owner_sha1: char* = None) -> mlirlite.Function*: """Look up a function by name in a func table, preferring this module's entry when owner_sha1 is set""" if name is None or funcs is None: return None entry_size: sizet = GenTypes.FuncEntry.__sizeof__() @@ -890,14 +890,14 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): if entry.Name[0] == name[0] and string.strcmp(entry.Name, name) == 0: if fallback_entry is None: fallback_entry = entry if entry.Func is not None and owner_sha1 is not None: - fn_nm_o: str = mlirlite.function_get_name(entry.Func) + fn_nm_o: char* = mlirlite.function_get_name(entry.Func) if fn_nm_o is not None: if string.strncmp(fn_nm_o, owner_sha1, 16) == 0 and fn_nm_o[16] == '.': return entry.Func if fallback_entry is not None: return fallback_entry.Func return None - def add_func_to_table(self, funcs: GenTypes.FuncEntry*, func_count: int, name: str, func: mlirlite.Function*, max_count: int, defaults: rawptr = None, default_count: int = 0, param_count: int = 0, pool: memhub.MemBuddy* = None, def_sha1: str = None) -> int: + def add_func_to_table(self, funcs: GenTypes.FuncEntry*, func_count: int, name: char*, func: mlirlite.Function*, max_count: int, defaults: rawptr = None, default_count: int = 0, param_count: int = 0, pool: memhub.MemBuddy* = None, def_sha1: char* = None) -> int: """Add a function to a func table; returns 0 on success""" if name is None or func is None or funcs is None: return 1 if func_count >= max_count: return 1 @@ -942,7 +942,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return 1 - def _global_func_registered(self, name: str, func: mlirlite.Function*) -> int: + def _global_func_registered(self, name: char*, func: mlirlite.Function*) -> int: """Whether this exact (name, Func) pair is already in the global table; return 1=yes""" if name is None or func is None or self.GlobalFuncs is None: return 0 entry_size: sizet = GenTypes.FuncEntry.__sizeof__() @@ -954,7 +954,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return 0 - def _register_global_func(self, name: str, func: mlirlite.Function*, pool: memhub.MemBuddy* = None) -> int: + def _register_global_func(self, name: char*, func: mlirlite.Function*, pool: memhub.MemBuddy* = None) -> int: """Register a function in the global table, skipping duplicates; returns 0 on success""" if name is None or func is None: return 1 if self._init_global_func_table() == 0: return 1 @@ -969,7 +969,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): # deep-copy Name into stdlib: the global table outlives files, and pool names would # dangle after the per-file reset_to(mark1) (strcmp crash after find_func_global). name_len: sizet = string.strlen(name) - name_copy: str = CompilerFactory.factory.PermPool.alloc(name_len + 1) + name_copy: char* = CompilerFactory.factory.PermPool.alloc(name_len + 1) if name_copy is None: return 1 string.strcpy(name_copy, name) entry.Name = name_copy @@ -987,7 +987,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return 0 - def update_global_func_spec(self, name: str, func: mlirlite.Function*, ret_sign: int = 0) -> int: + def update_global_func_spec(self, name: char*, func: mlirlite.Function*, ret_sign: int = 0) -> int: """Update a global table entry's Spec after all its params are added. ret_sign: GenTypes.FUNC_RET_SIGN_* of the declared return annotation (0 = keep the @@ -1047,353 +1047,353 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return TSpec.NewTypeFunc(arena, ret_s, params_arr, cnt, func.IsVarArg) - # ============================================================ - # 重载决议(C++ 风格:精确 > 提升/拓宽 > 转换 > 省略号;返回值不参与) - # 规则:候选集 = 同名且属于本模块(符号名以 {模块sha1}. 开头)的函数表项; - # 唯一"不劣于所有其它且至少一个实参更优"的候选胜出,否则判歧义 fail-fast; - # 任一实参类型无法静态确定时,候选 >=2 直接 fail-fast(必须显式强转)。 - # ============================================================ - - # _overload_name_matches - 候选名匹配:exact 模式要求同名;prefix 模式接受 - # "键" 本身或 "键_v{hash}"(方法重载:类内同名方法的表键带签名后缀)。 - def _overload_name_matches(self, entry_name: str, key: str, prefix_mode: int) -> int: - """函数表项名是否属于该重载组;1=是""" - if entry_name is None or key is None: return 0 - if prefix_mode == 0: - if string.strcmp(entry_name, key) == 0: return 1 - return 0 - kn: sizet = string.strlen(key) - if string.strncmp(entry_name, key, kn) != 0: return 0 - if entry_name[kn] == '\0': return 1 - if entry_name[kn] == '_' and entry_name[kn + 1] == 'v': return 1 - return 0 - - # count_owned_overloads - 统计某名字在本模块下的函数表项数 - def count_owned_overloads(self, funcs: GenTypes.FuncEntry*, func_count: int, name: str, owner_sha1: str, prefix_mode: int = 0) -> int: - """同名且符号属于 owner_sha1 模块的函数表项个数""" - if funcs is None or name is None: return 0 - entry_size: sizet = GenTypes.FuncEntry.__sizeof__() - cnt: int = 0 - for i in range(int(func_count)): - entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(funcs) + uint64(i * entry_size)) - if entry is None or entry.Name is None or entry.Func is None: continue - if self._overload_name_matches(entry.Name, name, prefix_mode) == 0: continue - if owner_sha1 is not None: - fn_nm: str = mlirlite.function_get_name(entry.Func) - if fn_nm is None: continue - if string.strncmp(fn_nm, owner_sha1, 16) != 0: continue - cnt += 1 - if cnt >= OVERLOAD_CAND_MAX: break - return cnt - - # _overload_arg_spec - 实参的静态类型(不生成 IR);无法确定时返回 None - def _overload_arg_spec(self, pool: memhub.MemBuddy*, trans: HT.Translator*, node: ast.AST*) -> TSpec.TypeSpec*: - """尽力而为的实参类型推导;None = 无法静态确定""" - if node is None or pool is None: return None - arena: TSpec.TypeSpecArena* = self.Gen.TypeGenBase.ensure_spec_arena() - if arena is None: return None - k: int = node.kind() - if k == ast.ASTKind.Constant: - c: ast.Constant* = ast.Constant*(node) - if c is None: return None - if c.const_kind == ast.CONST_INT: - bits: int = int(c.int_width) - if bits <= 0: bits = 32 - if c.int_unsigned != 0: return TSpec.NewTypeSpec(arena, TSpec.TS_UINT, bits, TSpec.TS_SIGN_UNSIGNED) - return TSpec.NewTypeSpec(arena, TSpec.TS_INT, bits, TSpec.TS_SIGN_SIGNED) - if c.const_kind == ast.CONST_BOOL: return TSpec.NewTypeSpec(arena, TSpec.TS_BOOL, 1, TSpec.TS_SIGN_UNSIGNED) - if c.const_kind == ast.CONST_FLOAT: return TSpec.NewTypeSpec(arena, TSpec.TS_FLOAT, 64, TSpec.TS_SIGN_SIGNED) - if c.const_kind == ast.CONST_CHAR: return TSpec.NewTypeSpec(arena, TSpec.TS_INT, 8, TSpec.TS_SIGN_SIGNED) - if c.const_kind == ast.CONST_STR: - i8_s: TSpec.TypeSpec* = TSpec.NewTypeSpec(arena, TSpec.TS_INT, 8, TSpec.TS_SIGN_SIGNED) - if i8_s is None: return None - return TSpec.NewTypePtr(arena, i8_s) - # None 及其它常量:类型不可定 - return None - if k == ast.ASTKind.Name: - nm: ast.Name* = ast.Name*(node) - if nm is None or nm.id is None or trans is None or trans.SymTab is None: return None - ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id) - if ve is None or ve.Spec is None: return None - if ve.Spec.Kind == TSpec.TS_UNKNOWN: return None - return ve.Spec - if k == ast.ASTKind.UnaryOp: - uo: ast.UnaryOp* = ast.UnaryOp*(node) - if uo is None or uo.operand is None: return None - inner_u: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, uo.operand) - if inner_u is None: return None - if uo.op == ast.OpKind.AddrOf: - # &x -> 指针(PtrLevel+1);数组取地址退化为元素指针(与实参翻译的数组衰变一致) - if inner_u.Kind == TSpec.TS_ARRAY: - if inner_u.Elem is None: return None - return TSpec.NewTypePtr(arena, inner_u.Elem) - return TSpec.NewTypePtr(arena, inner_u) - if uo.op == ast.OpKind.Deref: - if inner_u.Kind == TSpec.TS_PTR: return inner_u.Elem - if inner_u.Kind == TSpec.TS_ARRAY: return inner_u.Elem - return None - return None - if k == ast.ASTKind.Subscript: - sb: ast.Subscript* = ast.Subscript*(node) - if sb is None or sb.value is None: return None - base_s: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, sb.value) - if base_s is None: return None - if base_s.Kind == TSpec.TS_PTR: return base_s.Elem - if base_s.Kind == TSpec.TS_ARRAY: return base_s.Elem - return None - if k == ast.ASTKind.Attribute: - # 字段访问:按接收者的类名找 StructEntry,再按字段名取 FieldEntry.Spec - # (字段 Spec 在 add_field 时已用注解符号性修正,uint 字段不会退化成 i32)。 - at: ast.Attribute* = ast.Attribute*(node) - if at is None or at.value is None or at.attr is None or trans is None: return None - cls_nm: str = self.Gen.ExprGenOps._attr_receiver_class_name(pool, at.value, trans) - if cls_nm is None: return None - bare: str = cls_nm - dot_a: str = self.Gen.StructGen._last_dot_outside_brackets(cls_nm) - if dot_a is not None: bare = dot_a + 1 - if bare is None: return None - st_a: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(bare) - if st_a is None: return None - for fi_a in range(int(st_a.FieldCount)): - fe_a: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(st_a, fi_a) - if fe_a is None or fe_a.Name is None: continue - if string.strcmp(fe_a.Name, at.attr) != 0: continue - return fe_a.Spec - return None - if k == ast.ASTKind.Call: - # 函数调用结果:只处理裸名调用(模块级函数);返回值取被调者 Spec.Ret。 - # 被调者本身同名重载时无法在此再决议(避免递归),按"未知"处理。 - cl_r: ast.Call* = ast.Call*(node) - if cl_r is None or cl_r.func is None: return None - if cl_r.func.kind() != ast.ASTKind.Name: return None - fn_nm: ast.Name* = ast.Name*(cl_r.func) - if fn_nm is None or fn_nm.id is None or trans is None: return None - if trans._funcs is None: return None - if self.count_owned_overloads(trans._funcs, trans._func_count, fn_nm.id, trans.ModuleSha1) >= 2: return None - e_r: GenTypes.FuncEntry* = self.find_func_entry_in_table_owned(trans._funcs, trans._func_count, fn_nm.id, trans.ModuleSha1) - if e_r is None or e_r.Func is None: return None - if e_r.Spec is None: e_r.Spec = self._build_func_spec(e_r.Func) - if e_r.Spec is None or e_r.Spec.Ret is None: return None - return e_r.Spec.Ret - if k == ast.ASTKind.Compare: - # 比较结果恒为 bool - return TSpec.NewTypeSpec(arena, TSpec.TS_BOOL, 1, TSpec.TS_SIGN_UNSIGNED) - if k == ast.ASTKind.BinOp: - bo: ast.BinOp* = ast.BinOp*(node) - if bo is None or bo.left is None or bo.right is None: return None - if bo.op == ast.OpKind.And or bo.op == ast.OpKind.Or: - # 布尔逻辑:结果与左值同型(两边同型是语言保证) - return self._overload_arg_spec(pool, trans, bo.left) - ls: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, bo.left) - rs: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, bo.right) - if ls is None or rs is None: return None - # 字符串拼接(两边都是指针)按左值类型 - if ls.Kind == TSpec.TS_PTR and rs.Kind == TSpec.TS_PTR: return ls - if ls.Kind == TSpec.TS_FLOAT or rs.Kind == TSpec.TS_FLOAT: - if ls.Kind == TSpec.TS_FLOAT and rs.Kind == TSpec.TS_FLOAT: - if ls.Bits >= rs.Bits: return ls - return rs - # 混合整浮:按 Viper 缺省浮点宽度 f64 - return TSpec.NewTypeSpec(arena, TSpec.TS_FLOAT, 64, TSpec.TS_SIGN_SIGNED) - if ls.Kind == TSpec.TS_INT or ls.Kind == TSpec.TS_UINT: - if rs.Kind != TSpec.TS_INT and rs.Kind != TSpec.TS_UINT: return None - if ls.Bits > rs.Bits: return ls - if rs.Bits > ls.Bits: return rs - # 同宽:无符号优先(C++ 常规算术转换) - if ls.Kind == TSpec.TS_UINT: return ls - if rs.Kind == TSpec.TS_UINT: return rs - return ls - return None - return None - - # _overload_rank - 实参类型对形参类型的档位;-1 = 不可行 - # 0 精确 / 1 提升与拓宽 / 2 转换(同宽符号性变化、任意指针→void*)/ 3 省略号 - def _overload_rank(self, arg: TSpec.TypeSpec*, param: TSpec.TypeSpec*) -> int: - """实参→形参的匹配档位;-1 表示该候选不可行""" - if arg is None or param is None: return -1 - if param.Kind == TSpec.TS_PTR: - if arg.Kind != TSpec.TS_PTR: return -1 - if arg.PtrLevel != param.PtrLevel: return -1 - inner: int = self._overload_rank(arg.Elem, param.Elem) - if inner == 0: return 0 - # void*(本编译器中 void* 与 i8* 同一表示):任意同级指针可转换 - if param.Elem is not None: - if param.Elem.Kind == TSpec.TS_INT and param.Elem.Bits == 8 and param.PtrLevel == 1: return 2 - if inner == 1: return 1 - return -1 - if param.Kind == TSpec.TS_INT or param.Kind == TSpec.TS_UINT: - if arg.Kind == TSpec.TS_INT or arg.Kind == TSpec.TS_UINT: - if arg.Kind == param.Kind and arg.Bits == param.Bits: return 0 - if arg.Bits == param.Bits: return 2 # 同宽符号性变化(有符号↔无符号) - if param.Bits > arg.Bits: return 1 # 拓宽 - return -1 # 收窄不允许 - if arg.Kind == TSpec.TS_BOOL: return 1 - return -1 # 浮点→整数有损,禁止 - if param.Kind == TSpec.TS_FLOAT: - if arg.Kind == TSpec.TS_FLOAT: - if arg.Bits == param.Bits: return 0 - if param.Bits > arg.Bits: return 1 - return -1 - if arg.Kind == TSpec.TS_INT or arg.Kind == TSpec.TS_UINT: - if param.Bits >= arg.Bits: return 1 # 整→浮 需浮点位宽 >= 整数位宽(无损) - return -1 - return -1 - if param.Kind == TSpec.TS_BOOL: - if arg.Kind == TSpec.TS_BOOL: return 0 - return -1 - if param.Kind == TSpec.TS_STRUCT: - if arg.Kind != TSpec.TS_STRUCT: return -1 - if param.BaseName is None or arg.BaseName is None: return -1 - if string.strcmp(param.BaseName, arg.BaseName) == 0: return 0 - return -1 # 类层次上转型:本轮不支持 - return -1 - - # _overload_better - a 是否严格优于 b(逐实参不劣且至少一个更优) - def _overload_better(self, ma: int*, mb: int*, argc: int) -> int: - """a 的部分序严格优于 b 时返回 1""" - if ma is None or mb is None: return 0 - better: int = 0 - for i in range(argc): - if ma[i] > mb[i]: return 0 - if ma[i] < mb[i]: better = 1 - return better - - # resolve_overload_call - 在候选集中决议唯一的重载;歧义/无法定型时 fail-fast - def resolve_overload_call(self, pool: memhub.MemBuddy*, trans: HT.Translator*, funcs: GenTypes.FuncEntry*, func_count: int, name: str, owner_sha1: str, cargs: list[ast.AST*]*, can: sizet, call_node: ast.AST*, prefix_mode: int = 0, param_base: int = 0, arg_start: int = 0) -> mlirlite.Function*: - """按实参静态类型选择唯一最优重载;返回 None 表示已报错。 - prefix_mode=1 用于方法(表键 "Class.m" / "Class.m_v{hash}"); - param_base=1 表示候选签名第 0 个形参是 self/cls,需跳过; - arg_start 表示 cargs 前若干个是预置实参(obj/cls),不参与匹配。""" - if funcs is None or name is None: return None - entry_size: sizet = GenTypes.FuncEntry.__sizeof__() - argv_n: int = int(can) - if argv_n > OVERLOAD_ARG_MAX: - self.Gen.TypeGenErr.fatal_error(call_node, f"overloaded call '{name}' has too many arguments ({argv_n} > {OVERLOAD_ARG_MAX})", "TypeError") - return None - cand: sizet* = stdlib.malloc(8 * OVERLOAD_CAND_MAX) - if cand is None: return None - n: int = 0 - for i in range(int(func_count)): - entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(funcs) + uint64(i * entry_size)) - if entry is None or entry.Name is None or entry.Func is None: continue - if self._overload_name_matches(entry.Name, name, prefix_mode) == 0: continue - if owner_sha1 is not None: - fn_nm_c: str = mlirlite.function_get_name(entry.Func) - if fn_nm_c is None: continue - if string.strncmp(fn_nm_c, owner_sha1, 16) != 0: continue - if n < OVERLOAD_CAND_MAX: - cand[n] = sizet(entry) - n += 1 - if n == 0: - stdlib.free(cand) - return None - if n == 1: - only: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(cand[0])) - stdlib.free(cand) - if only is None: return None - return only.Func - # 实参类型:任一无法确定 -> 候选 >=2 时 fail-fast(必须显式强转) - argspec: sizet* = stdlib.malloc(8 * argv_n) - if argspec is None: - stdlib.free(cand) - return None - for ai in range(argv_n): - node_a: ast.AST* = None - if cargs is not None: node_a = cargs.get(arg_start + ai) - argspec[ai] = sizet(self._overload_arg_spec(pool, trans, node_a)) - for ai in range(argv_n): - if argspec[ai] == 0: - arg_bad: ast.AST* = None - if cargs is not None: arg_bad = cargs.get(arg_start + ai) - if arg_bad is None: arg_bad = call_node - stdlib.free(argspec) - stdlib.free(cand) - self.Gen.TypeGenErr.fatal_error(arg_bad, f"cannot resolve overload '{name}': argument {ai + 1} type is unknown; add an explicit cast", "TypeError") - return None - # 档位矩阵 + 可行候选 - mat: int* = stdlib.malloc(4 * n * argv_n) - ok: int* = stdlib.malloc(4 * n) - if mat is None or ok is None: - if mat is not None: stdlib.free(mat) - if ok is not None: stdlib.free(ok) - stdlib.free(argspec) - stdlib.free(cand) - return None - viable: int = 0 - for ci in range(n): - e: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(cand[ci])) - ok[ci] = 0 - if e is None or e.Func is None: continue - # 模块函数表项从不填 Spec(只有全局表由 update_global_func_spec 填写), - # 这里按需构建并缓存,避免决议时所有候选看起来都"无签名"。 - if e.Spec is None: e.Spec = self._build_func_spec(e.Func) - if e.Spec is None: continue - pc: int = e.ParamCount - dc: int = e.DefaultCount - vararg: int = 0 - if e.Spec.IsVarArg == 1: vararg = 1 - if argv_n > pc and vararg == 0: continue - if argv_n < pc - dc: continue - good: int = 1 - for ai in range(argv_n): - r: int = 0 - if ai >= pc: - if vararg == 0: - good = 0 - break - r = 3 - else: - p: TSpec.TypeSpec* = TSpec.SpecParamAt(e.Spec, param_base + ai) - r = self._overload_rank((TSpec.TypeSpec*)(uint64(argspec[ai])), p) - if r < 0: - good = 0 - break - mat[ci * argv_n + ai] = r - if good != 0: - ok[ci] = 1 - viable += 1 - stdlib.free(argspec) - if viable == 0: - stdlib.free(mat) - stdlib.free(ok) - stdlib.free(cand) - self.Gen.TypeGenErr.fatal_error(call_node, f"no overload of '{name}' matches the argument types", "TypeError") - return None - best: int = -1 - ambiguous: int = 0 - for ci in range(n): - if ok[ci] == 0: continue - dom: int = 1 - for cj in range(n): - if cj == ci or ok[cj] == 0: continue - ma: int* = (int*)(uint64(mat) + uint64(ci * argv_n * 4)) - mb: int* = (int*)(uint64(mat) + uint64(cj * argv_n * 4)) - if self._overload_better(ma, mb, argv_n) == 0: - dom = 0 - break - if dom != 0: - if best >= 0: ambiguous = 1 - else: best = ci - if best < 0 or ambiguous != 0: - stdlib.free(mat) - stdlib.free(ok) - stdlib.free(cand) - self.Gen.TypeGenErr.fatal_error(call_node, f"ambiguous call to overloaded function '{name}': no unique best match", "TypeError") - return None - pick: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(cand[best])) - stdlib.free(mat) - stdlib.free(ok) - stdlib.free(cand) - if pick is None: return None - return pick.Func - - def find_func_global(self, name: str) -> mlirlite.Function*: + # ============================================================ + # 重载决议(C++ 风格:精确 > 提升/拓宽 > 转换 > 省略号;返回值不参与) + # 规则:候选集 = 同名且属于本模块(符号名以 {模块sha1}. 开头)的函数表项; + # 唯一"不劣于所有其它且至少一个实参更优"的候选胜出,否则判歧义 fail-fast; + # 任一实参类型无法静态确定时,候选 >=2 直接 fail-fast(必须显式强转)。 + # ============================================================ + + # _overload_name_matches - 候选名匹配:exact 模式要求同名;prefix 模式接受 + # "键" 本身或 "键_v{hash}"(方法重载:类内同名方法的表键带签名后缀)。 + def _overload_name_matches(self, entry_name: char*, key: char*, prefix_mode: int) -> int: + """函数表项名是否属于该重载组;1=是""" + if entry_name is None or key is None: return 0 + if prefix_mode == 0: + if string.strcmp(entry_name, key) == 0: return 1 + return 0 + kn: sizet = string.strlen(key) + if string.strncmp(entry_name, key, kn) != 0: return 0 + if entry_name[kn] == '\0': return 1 + if entry_name[kn] == '_' and entry_name[kn + 1] == 'v': return 1 + return 0 + + # count_owned_overloads - 统计某名字在本模块下的函数表项数 + def count_owned_overloads(self, funcs: GenTypes.FuncEntry*, func_count: int, name: char*, owner_sha1: char*, prefix_mode: int = 0) -> int: + """同名且符号属于 owner_sha1 模块的函数表项个数""" + if funcs is None or name is None: return 0 + entry_size: sizet = GenTypes.FuncEntry.__sizeof__() + cnt: int = 0 + for i in range(int(func_count)): + entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(funcs) + uint64(i * entry_size)) + if entry is None or entry.Name is None or entry.Func is None: continue + if self._overload_name_matches(entry.Name, name, prefix_mode) == 0: continue + if owner_sha1 is not None: + fn_nm: char* = mlirlite.function_get_name(entry.Func) + if fn_nm is None: continue + if string.strncmp(fn_nm, owner_sha1, 16) != 0: continue + cnt += 1 + if cnt >= OVERLOAD_CAND_MAX: break + return cnt + + # _overload_arg_spec - 实参的静态类型(不生成 IR);无法确定时返回 None + def _overload_arg_spec(self, pool: memhub.MemBuddy*, trans: HT.Translator*, node: ast.AST*) -> TSpec.TypeSpec*: + """尽力而为的实参类型推导;None = 无法静态确定""" + if node is None or pool is None: return None + arena: TSpec.TypeSpecArena* = self.Gen.TypeGenBase.ensure_spec_arena() + if arena is None: return None + k: int = node.kind() + if k == ast.ASTKind.Constant: + c: ast.Constant* = ast.Constant*(node) + if c is None: return None + if c.const_kind == ast.CONST_INT: + bits: int = int(c.int_width) + if bits <= 0: bits = 32 + if c.int_unsigned != 0: return TSpec.NewTypeSpec(arena, TSpec.TS_UINT, bits, TSpec.TS_SIGN_UNSIGNED) + return TSpec.NewTypeSpec(arena, TSpec.TS_INT, bits, TSpec.TS_SIGN_SIGNED) + if c.const_kind == ast.CONST_BOOL: return TSpec.NewTypeSpec(arena, TSpec.TS_BOOL, 1, TSpec.TS_SIGN_UNSIGNED) + if c.const_kind == ast.CONST_FLOAT: return TSpec.NewTypeSpec(arena, TSpec.TS_FLOAT, 64, TSpec.TS_SIGN_SIGNED) + if c.const_kind == ast.CONST_CHAR: return TSpec.NewTypeSpec(arena, TSpec.TS_INT, 8, TSpec.TS_SIGN_SIGNED) + if c.const_kind == ast.CONST_STR: + i8_s: TSpec.TypeSpec* = TSpec.NewTypeSpec(arena, TSpec.TS_INT, 8, TSpec.TS_SIGN_SIGNED) + if i8_s is None: return None + return TSpec.NewTypePtr(arena, i8_s) + # None 及其它常量:类型不可定 + return None + if k == ast.ASTKind.Name: + nm: ast.Name* = ast.Name*(node) + if nm is None or nm.id is None or trans is None or trans.SymTab is None: return None + ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id) + if ve is None or ve.Spec is None: return None + if ve.Spec.Kind == TSpec.TS_UNKNOWN: return None + return ve.Spec + if k == ast.ASTKind.UnaryOp: + uo: ast.UnaryOp* = ast.UnaryOp*(node) + if uo is None or uo.operand is None: return None + inner_u: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, uo.operand) + if inner_u is None: return None + if uo.op == ast.OpKind.AddrOf: + # &x -> 指针(PtrLevel+1);数组取地址退化为元素指针(与实参翻译的数组衰变一致) + if inner_u.Kind == TSpec.TS_ARRAY: + if inner_u.Elem is None: return None + return TSpec.NewTypePtr(arena, inner_u.Elem) + return TSpec.NewTypePtr(arena, inner_u) + if uo.op == ast.OpKind.Deref: + if inner_u.Kind == TSpec.TS_PTR: return inner_u.Elem + if inner_u.Kind == TSpec.TS_ARRAY: return inner_u.Elem + return None + return None + if k == ast.ASTKind.Subscript: + sb: ast.Subscript* = ast.Subscript*(node) + if sb is None or sb.value is None: return None + base_s: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, sb.value) + if base_s is None: return None + if base_s.Kind == TSpec.TS_PTR: return base_s.Elem + if base_s.Kind == TSpec.TS_ARRAY: return base_s.Elem + return None + if k == ast.ASTKind.Attribute: + # 字段访问:按接收者的类名找 StructEntry,再按字段名取 FieldEntry.Spec + # (字段 Spec 在 add_field 时已用注解符号性修正,uint 字段不会退化成 i32)。 + at: ast.Attribute* = ast.Attribute*(node) + if at is None or at.value is None or at.attr is None or trans is None: return None + cls_nm: char* = self.Gen.ExprGenOps._attr_receiver_class_name(pool, at.value, trans) + if cls_nm is None: return None + bare: char* = cls_nm + dot_a: char* = self.Gen.StructGen._last_dot_outside_brackets(cls_nm) + if dot_a is not None: bare = dot_a + 1 + if bare is None: return None + st_a: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(bare) + if st_a is None: return None + for fi_a in range(int(st_a.FieldCount)): + fe_a: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(st_a, fi_a) + if fe_a is None or fe_a.Name is None: continue + if string.strcmp(fe_a.Name, at.attr) != 0: continue + return fe_a.Spec + return None + if k == ast.ASTKind.Call: + # 函数调用结果:只处理裸名调用(模块级函数);返回值取被调者 Spec.Ret。 + # 被调者本身同名重载时无法在此再决议(避免递归),按"未知"处理。 + cl_r: ast.Call* = ast.Call*(node) + if cl_r is None or cl_r.func is None: return None + if cl_r.func.kind() != ast.ASTKind.Name: return None + fn_nm: ast.Name* = ast.Name*(cl_r.func) + if fn_nm is None or fn_nm.id is None or trans is None: return None + if trans._funcs is None: return None + if self.count_owned_overloads(trans._funcs, trans._func_count, fn_nm.id, trans.ModuleSha1) >= 2: return None + e_r: GenTypes.FuncEntry* = self.find_func_entry_in_table_owned(trans._funcs, trans._func_count, fn_nm.id, trans.ModuleSha1) + if e_r is None or e_r.Func is None: return None + if e_r.Spec is None: e_r.Spec = self._build_func_spec(e_r.Func) + if e_r.Spec is None or e_r.Spec.Ret is None: return None + return e_r.Spec.Ret + if k == ast.ASTKind.Compare: + # 比较结果恒为 bool + return TSpec.NewTypeSpec(arena, TSpec.TS_BOOL, 1, TSpec.TS_SIGN_UNSIGNED) + if k == ast.ASTKind.BinOp: + bo: ast.BinOp* = ast.BinOp*(node) + if bo is None or bo.left is None or bo.right is None: return None + if bo.op == ast.OpKind.And or bo.op == ast.OpKind.Or: + # 布尔逻辑:结果与左值同型(两边同型是语言保证) + return self._overload_arg_spec(pool, trans, bo.left) + ls: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, bo.left) + rs: TSpec.TypeSpec* = self._overload_arg_spec(pool, trans, bo.right) + if ls is None or rs is None: return None + # 字符串拼接(两边都是指针)按左值类型 + if ls.Kind == TSpec.TS_PTR and rs.Kind == TSpec.TS_PTR: return ls + if ls.Kind == TSpec.TS_FLOAT or rs.Kind == TSpec.TS_FLOAT: + if ls.Kind == TSpec.TS_FLOAT and rs.Kind == TSpec.TS_FLOAT: + if ls.Bits >= rs.Bits: return ls + return rs + # 混合整浮:按 Viper 缺省浮点宽度 f64 + return TSpec.NewTypeSpec(arena, TSpec.TS_FLOAT, 64, TSpec.TS_SIGN_SIGNED) + if ls.Kind == TSpec.TS_INT or ls.Kind == TSpec.TS_UINT: + if rs.Kind != TSpec.TS_INT and rs.Kind != TSpec.TS_UINT: return None + if ls.Bits > rs.Bits: return ls + if rs.Bits > ls.Bits: return rs + # 同宽:无符号优先(C++ 常规算术转换) + if ls.Kind == TSpec.TS_UINT: return ls + if rs.Kind == TSpec.TS_UINT: return rs + return ls + return None + return None + + # _overload_rank - 实参类型对形参类型的档位;-1 = 不可行 + # 0 精确 / 1 提升与拓宽 / 2 转换(同宽符号性变化、任意指针→void*)/ 3 省略号 + def _overload_rank(self, arg: TSpec.TypeSpec*, param: TSpec.TypeSpec*) -> int: + """实参→形参的匹配档位;-1 表示该候选不可行""" + if arg is None or param is None: return -1 + if param.Kind == TSpec.TS_PTR: + if arg.Kind != TSpec.TS_PTR: return -1 + if arg.PtrLevel != param.PtrLevel: return -1 + inner: int = self._overload_rank(arg.Elem, param.Elem) + if inner == 0: return 0 + # void*(本编译器中 void* 与 i8* 同一表示):任意同级指针可转换 + if param.Elem is not None: + if param.Elem.Kind == TSpec.TS_INT and param.Elem.Bits == 8 and param.PtrLevel == 1: return 2 + if inner == 1: return 1 + return -1 + if param.Kind == TSpec.TS_INT or param.Kind == TSpec.TS_UINT: + if arg.Kind == TSpec.TS_INT or arg.Kind == TSpec.TS_UINT: + if arg.Kind == param.Kind and arg.Bits == param.Bits: return 0 + if arg.Bits == param.Bits: return 2 # 同宽符号性变化(有符号↔无符号) + if param.Bits > arg.Bits: return 1 # 拓宽 + return -1 # 收窄不允许 + if arg.Kind == TSpec.TS_BOOL: return 1 + return -1 # 浮点→整数有损,禁止 + if param.Kind == TSpec.TS_FLOAT: + if arg.Kind == TSpec.TS_FLOAT: + if arg.Bits == param.Bits: return 0 + if param.Bits > arg.Bits: return 1 + return -1 + if arg.Kind == TSpec.TS_INT or arg.Kind == TSpec.TS_UINT: + if param.Bits >= arg.Bits: return 1 # 整→浮 需浮点位宽 >= 整数位宽(无损) + return -1 + return -1 + if param.Kind == TSpec.TS_BOOL: + if arg.Kind == TSpec.TS_BOOL: return 0 + return -1 + if param.Kind == TSpec.TS_STRUCT: + if arg.Kind != TSpec.TS_STRUCT: return -1 + if param.BaseName is None or arg.BaseName is None: return -1 + if string.strcmp(param.BaseName, arg.BaseName) == 0: return 0 + return -1 # 类层次上转型:本轮不支持 + return -1 + + # _overload_better - a 是否严格优于 b(逐实参不劣且至少一个更优) + def _overload_better(self, ma: int*, mb: int*, argc: int) -> int: + """a 的部分序严格优于 b 时返回 1""" + if ma is None or mb is None: return 0 + better: int = 0 + for i in range(argc): + if ma[i] > mb[i]: return 0 + if ma[i] < mb[i]: better = 1 + return better + + # resolve_overload_call - 在候选集中决议唯一的重载;歧义/无法定型时 fail-fast + def resolve_overload_call(self, pool: memhub.MemBuddy*, trans: HT.Translator*, funcs: GenTypes.FuncEntry*, func_count: int, name: char*, owner_sha1: char*, cargs: list[ast.AST*]*, can: sizet, call_node: ast.AST*, prefix_mode: int = 0, param_base: int = 0, arg_start: int = 0) -> mlirlite.Function*: + """按实参静态类型选择唯一最优重载;返回 None 表示已报错。 + prefix_mode=1 用于方法(表键 "Class.m" / "Class.m_v{hash}"); + param_base=1 表示候选签名第 0 个形参是 self/cls,需跳过; + arg_start 表示 cargs 前若干个是预置实参(obj/cls),不参与匹配。""" + if funcs is None or name is None: return None + entry_size: sizet = GenTypes.FuncEntry.__sizeof__() + argv_n: int = int(can) + if argv_n > OVERLOAD_ARG_MAX: + self.Gen.TypeGenErr.fatal_error(call_node, f"overloaded call '{name}' has too many arguments ({argv_n} > {OVERLOAD_ARG_MAX})", "TypeError") + return None + cand: sizet* = stdlib.malloc(8 * OVERLOAD_CAND_MAX) + if cand is None: return None + n: int = 0 + for i in range(int(func_count)): + entry: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(funcs) + uint64(i * entry_size)) + if entry is None or entry.Name is None or entry.Func is None: continue + if self._overload_name_matches(entry.Name, name, prefix_mode) == 0: continue + if owner_sha1 is not None: + fn_nm_c: char* = mlirlite.function_get_name(entry.Func) + if fn_nm_c is None: continue + if string.strncmp(fn_nm_c, owner_sha1, 16) != 0: continue + if n < OVERLOAD_CAND_MAX: + cand[n] = sizet(entry) + n += 1 + if n == 0: + stdlib.free(cand) + return None + if n == 1: + only: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(cand[0])) + stdlib.free(cand) + if only is None: return None + return only.Func + # 实参类型:任一无法确定 -> 候选 >=2 时 fail-fast(必须显式强转) + argspec: sizet* = stdlib.malloc(8 * argv_n) + if argspec is None: + stdlib.free(cand) + return None + for ai in range(argv_n): + node_a: ast.AST* = None + if cargs is not None: node_a = cargs.get(arg_start + ai) + argspec[ai] = sizet(self._overload_arg_spec(pool, trans, node_a)) + for ai in range(argv_n): + if argspec[ai] == 0: + arg_bad: ast.AST* = None + if cargs is not None: arg_bad = cargs.get(arg_start + ai) + if arg_bad is None: arg_bad = call_node + stdlib.free(argspec) + stdlib.free(cand) + self.Gen.TypeGenErr.fatal_error(arg_bad, f"cannot resolve overload '{name}': argument {ai + 1} type is unknown; add an explicit cast", "TypeError") + return None + # 档位矩阵 + 可行候选 + mat: int* = stdlib.malloc(4 * n * argv_n) + ok: int* = stdlib.malloc(4 * n) + if mat is None or ok is None: + if mat is not None: stdlib.free(mat) + if ok is not None: stdlib.free(ok) + stdlib.free(argspec) + stdlib.free(cand) + return None + viable: int = 0 + for ci in range(n): + e: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(cand[ci])) + ok[ci] = 0 + if e is None or e.Func is None: continue + # 模块函数表项从不填 Spec(只有全局表由 update_global_func_spec 填写), + # 这里按需构建并缓存,避免决议时所有候选看起来都"无签名"。 + if e.Spec is None: e.Spec = self._build_func_spec(e.Func) + if e.Spec is None: continue + pc: int = e.ParamCount + dc: int = e.DefaultCount + vararg: int = 0 + if e.Spec.IsVarArg == 1: vararg = 1 + if argv_n > pc and vararg == 0: continue + if argv_n < pc - dc: continue + good: int = 1 + for ai in range(argv_n): + r: int = 0 + if ai >= pc: + if vararg == 0: + good = 0 + break + r = 3 + else: + p: TSpec.TypeSpec* = TSpec.SpecParamAt(e.Spec, param_base + ai) + r = self._overload_rank((TSpec.TypeSpec*)(uint64(argspec[ai])), p) + if r < 0: + good = 0 + break + mat[ci * argv_n + ai] = r + if good != 0: + ok[ci] = 1 + viable += 1 + stdlib.free(argspec) + if viable == 0: + stdlib.free(mat) + stdlib.free(ok) + stdlib.free(cand) + self.Gen.TypeGenErr.fatal_error(call_node, f"no overload of '{name}' matches the argument types", "TypeError") + return None + best: int = -1 + ambiguous: int = 0 + for ci in range(n): + if ok[ci] == 0: continue + dom: int = 1 + for cj in range(n): + if cj == ci or ok[cj] == 0: continue + ma: int* = (int*)(uint64(mat) + uint64(ci * argv_n * 4)) + mb: int* = (int*)(uint64(mat) + uint64(cj * argv_n * 4)) + if self._overload_better(ma, mb, argv_n) == 0: + dom = 0 + break + if dom != 0: + if best >= 0: ambiguous = 1 + else: best = ci + if best < 0 or ambiguous != 0: + stdlib.free(mat) + stdlib.free(ok) + stdlib.free(cand) + self.Gen.TypeGenErr.fatal_error(call_node, f"ambiguous call to overloaded function '{name}': no unique best match", "TypeError") + return None + pick: GenTypes.FuncEntry* = GenTypes.FuncEntry*(uint64(cand[best])) + stdlib.free(mat) + stdlib.free(ok) + stdlib.free(cand) + if pick is None: return None + return pick.Func + + def find_func_global(self, name: char*) -> mlirlite.Function*: """Look up a name in the global func table; returns a Function pointer or None""" if name is None or self.GlobalFuncs is None: return None return self.find_func_in_table(self.GlobalFuncs, self.GlobalFuncCount, name) - def find_func_entry_global(self, name: str) -> GenTypes.FuncEntry*: + def find_func_entry_global(self, name: char*) -> GenTypes.FuncEntry*: """Look up a name in the global func table; returns the full FuncEntry (with Spec) or None""" if name is None or self.GlobalFuncs is None: return None entry_size: sizet = GenTypes.FuncEntry.__sizeof__() @@ -1426,13 +1426,13 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return 1 - def _register_global_defaults(self, name: str, defaults: rawptr, default_count: int, param_count: int, def_sha1: str = None) -> int: + def _register_global_defaults(self, name: char*, defaults: rawptr, default_count: int, param_count: int, def_sha1: char* = None) -> int: """Register a function's default arguments in the global table; returns 0 on success""" if name is None or defaults is None: return 1 if self._init_global_defaults_table() == 0: return 1 # qualified key = the defining module's SHA1: one entry per (name, module) removes # cross-module same-name collisions. def_sha1 comes from the injection path. - cur_sha: str = def_sha1 + cur_sha: char* = def_sha1 if cur_sha is None and self.Gen is not None and self.Gen.TypeGenBase is not None: cur_sha = self.Gen.TypeGenBase.CurrentModuleSha1 # skip if the same name already exists for this module if self.find_func_defaults(name, cur_sha) is not None: return 0 @@ -1442,14 +1442,14 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): entry: GenTypes.FuncDefaultsEntry* = GenTypes.FuncDefaultsEntry*(entry_addr) # deep-copy Name into stdlib (the global table outlives files) nm_len: sizet = string.strlen(name) - nm_copy: str = CompilerFactory.factory.PermPool.alloc(nm_len + 1) + nm_copy: char* = CompilerFactory.factory.PermPool.alloc(nm_len + 1) if nm_copy is None: return 1 string.strcpy(nm_copy, name) entry.Name = nm_copy entry.ModuleSha1 = None if cur_sha is not None: sh_len: sizet = string.strlen(cur_sha) - sh_copy: str = CompilerFactory.factory.PermPool.alloc(sh_len + 1) + sh_copy: char* = CompilerFactory.factory.PermPool.alloc(sh_len + 1) if sh_copy is not None: string.strcpy(sh_copy, cur_sha) entry.ModuleSha1 = sh_copy @@ -1481,7 +1481,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): ints[di] = dcn.int_val if dcn.const_kind == ast.CONST_STR and dcn.str_val is not None: slen: sizet = string.strlen(dcn.str_val) - scopy: str = CompilerFactory.factory.PermPool.alloc(slen + 1) + scopy: char* = CompilerFactory.factory.PermPool.alloc(slen + 1) if scopy is not None: string.strcpy(scopy, dcn.str_val) strs[di] = sizet(scopy) @@ -1517,7 +1517,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return 0 - def register_global_defaults_literals(self, name: str, kinds: int*, ints: int64*, strs: sizet*, count: int, param_count: int, def_sha1: str) -> int: + def register_global_defaults_literals(self, name: char*, kinds: int*, ints: int64*, strs: sizet*, count: int, param_count: int, def_sha1: char*) -> int: """Register default args already parsed into a (kinds, ints, strs) snapshot; 0=ok Used by the .vpi pre-scan (VpiReader.PreRegisterVpiDefaults), which has literal values @@ -1533,14 +1533,14 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): entry_addr: uint64 = uint64(self.GlobalDefaults) + sizet(self.GlobalDefaultsCount) * entry_size entry: GenTypes.FuncDefaultsEntry* = GenTypes.FuncDefaultsEntry*(entry_addr) nm_len: sizet = string.strlen(name) - nm_copy: str = CompilerFactory.factory.PermPool.alloc(nm_len + 1) + nm_copy: char* = CompilerFactory.factory.PermPool.alloc(nm_len + 1) if nm_copy is None: return 1 string.strcpy(nm_copy, name) entry.Name = nm_copy entry.ModuleSha1 = None if def_sha1 is not None: sh_len: sizet = string.strlen(def_sha1) - sh_copy: str = CompilerFactory.factory.PermPool.alloc(sh_len + 1) + sh_copy: char* = CompilerFactory.factory.PermPool.alloc(sh_len + 1) if sh_copy is not None: string.strcpy(sh_copy, def_sha1) entry.ModuleSha1 = sh_copy @@ -1567,7 +1567,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return 0 - def find_func_defaults(self, name: str, module_sha1: str = None) -> GenTypes.FuncDefaultsEntry*: + def find_func_defaults(self, name: char*, module_sha1: char* = None) -> GenTypes.FuncDefaultsEntry*: """Find default-argument info by name and optional defining-module SHA1, or None""" if name is None or self.GlobalDefaults is None: return None entry_size: sizet = GenTypes.FuncDefaultsEntry.__sizeof__() @@ -1601,13 +1601,13 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return mlirlite.ConstNull(pool, i8_ptr_ty, "null") elif kind == ast.CONST_STR: if gd_entry.ConstStrs is not None and gd_entry.ConstStrs[di] != 0: - sv: str = rawptr(gd_entry.ConstStrs[di]) + sv: char* = rawptr(gd_entry.ConstStrs[di]) return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, sv, trans) # return None for unreconstructable values (CONST_FLOAT / complex expr); caller falls back to AST return None - def find_func_entry_in_table(self, funcs: GenTypes.FuncEntry*, func_count: int, name: str) -> GenTypes.FuncEntry*: + def find_func_entry_in_table(self, funcs: GenTypes.FuncEntry*, func_count: int, name: char*) -> GenTypes.FuncEntry*: """Look up a name in a func table; returns the full FuncEntry or None""" if name is None or funcs is None: return None entry_size: sizet = GenTypes.FuncEntry.__sizeof__() @@ -1619,7 +1619,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): if entry.Name[0] == name[0] and string.strcmp(entry.Name, name) == 0: return entry return None - def find_func_entry_in_table_owned(self, funcs: GenTypes.FuncEntry*, func_count: int, name: str, owner_sha1: str) -> GenTypes.FuncEntry*: + def find_func_entry_in_table_owned(self, funcs: GenTypes.FuncEntry*, func_count: int, name: char*, owner_sha1: char*) -> GenTypes.FuncEntry*: """Find a func table entry by bare name and defining-module SHA1, or None""" if name is None or funcs is None: return None entry_size: sizet = GenTypes.FuncEntry.__sizeof__() @@ -1632,7 +1632,7 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): if entry.Name[0] != name[0] or string.strcmp(entry.Name, name) != 0: continue if owner_sha1 is None: return entry if entry.Func is not None: - fn_nm_o: str = mlirlite.function_get_name(entry.Func) + fn_nm_o: char* = mlirlite.function_get_name(entry.Func) if fn_nm_o is not None: if string.strncmp(fn_nm_o, owner_sha1, 16) == 0 and fn_nm_o[16] == '.': return entry return None diff --git a/App/lib/core/Gen/Call/CallGenMixin.vp b/App/lib/core/Gen/Call/CallGenMixin.vp index 8257343..c7bc3b2 100644 --- a/App/lib/core/Gen/Call/CallGenMixin.vp +++ b/App/lib/core/Gen/Call/CallGenMixin.vp @@ -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) diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index 323ccd9..eb537ae 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -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) diff --git a/App/lib/core/Gen/Type/TypeGenCDef.vp b/App/lib/core/Gen/Type/TypeGenCDef.vp index 95a06ee..f3e41c1 100644 --- a/App/lib/core/Gen/Type/TypeGenCDef.vp +++ b/App/lib/core/Gen/Type/TypeGenCDef.vp @@ -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)) diff --git a/App/lib/core/Repl.vp b/App/lib/core/Repl.vp index d003f67..23a7510 100644 --- a/App/lib/core/Repl.vp +++ b/App/lib/core/Repl.vp @@ -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 diff --git a/App/lib/core/SelfBuild.vp b/App/lib/core/SelfBuild.vp index 006e69b..8a1c9a8 100644 --- a/App/lib/core/SelfBuild.vp +++ b/App/lib/core/SelfBuild.vp @@ -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") diff --git a/App/lib/lsp/LspHover.vp b/App/lib/lsp/LspHover.vp index c0a716f..5140299 100644 --- a/App/lib/lsp/LspHover.vp +++ b/App/lib/lsp/LspHover.vp @@ -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: diff --git a/Tools/lsp/fixtures/gate10_use.vp b/Tools/lsp/fixtures/gate10_use.vp index 9c69772..67bf121 100644 --- a/Tools/lsp/fixtures/gate10_use.vp +++ b/Tools/lsp/fixtures/gate10_use.vp @@ -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 diff --git a/Tools/lsp/probe.py b/Tools/lsp/probe.py index d1bf719..2e37ae8 100644 --- a/Tools/lsp/probe.py +++ b/Tools/lsp/probe.py @@ -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,