From 94515ddf12bc4d9d23152bf52f3594fdea255a7a Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sun, 25 Oct 2026 12:45:49 +0800 Subject: [PATCH] =?UTF-8?q?refactor(app):=20win32.fileio=20=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E5=88=87=E6=8D=A2=E5=88=B0=E8=B7=A8=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=20io?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 20 个文件把 'import win32.fileio as fileio' 改为 'import io as fileio', 文件 IO 统一走 includes/io.vp(编译期 cif 选择 win32.fileio / posix.fileio)。 依赖编译器新增的模块级 cif 条件 import(a282918)。 注:这些文件中存在此前未提交的既有改动,按整文件一并提交。 --- App/lib/Phase/BuildPipeline.vp | 16 +- App/lib/Phase/Phase1/Phase1Main.vp | 18 +- App/lib/Phase/Phase1/Phase1Scan.vp | 2 +- App/lib/Phase/Phase2/__init__.vp | 125 +- App/lib/Projectrans/Config.vp | 2 +- App/lib/Projectrans/Utils.vp | 2 +- App/lib/Stub/StubGen/VpiReader.vp | 1479 ++++++++++++++---- App/lib/Stub/StubMerger/StubMergerBuild.vp | 6 +- App/lib/Stub/StubMerger/StubMergerCollect.vp | 29 +- App/lib/Stub/StubMerger/StubMergerIR.vp | 15 +- App/lib/Stub/StubMerger/StubMergerStore.vp | 33 +- App/lib/core/Diag.vp | 2 +- App/lib/core/Gen/AssignGenMixin.vp | 381 ++--- App/lib/core/Gen/Call/CallGenBase.vp | 254 ++- App/lib/core/Gen/Call/CallGenDispatch.vp | 48 +- App/lib/core/Gen/Expr/ExprGenCdef.vp | 186 ++- App/lib/core/Gen/ImportsGenMixin.vp | 2 +- App/lib/core/Gen/Type/TypeGenResolve.vp | 403 +++-- App/lib/core/IncludesScanner.vp | 2 +- App/lib/core/Repl.vp | 38 +- 20 files changed, 2184 insertions(+), 859 deletions(-) diff --git a/App/lib/Phase/BuildPipeline.vp b/App/lib/Phase/BuildPipeline.vp index 3251b25..ae74869 100644 --- a/App/lib/Phase/BuildPipeline.vp +++ b/App/lib/Phase/BuildPipeline.vp @@ -6,7 +6,7 @@ import stdlib import memhub import viperlib import vthreading -import win32.fileio as fileio +import io as fileio import win32.win32file import win32.win32base import win32.win32process as win32process @@ -151,7 +151,9 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, sha1_val: str, c """Translate one file and return a Translator (caller dumps IR); None on failure.""" if file_path is None: return None f: fileio.File* = fileio.File(file_path, fileio.MODE.R) - if f.closed: return None + # Fail-fast: a path that cannot be opened (e.g. an App source resolved against the wrong root) + # used to return None silently, and the caller only reported "cannot be translated". + if f.closed: VLogger.error(f"cannot open source file: {file_path}", "Translate") # read_all's capacity includes the terminator (reads cap-1 bytes max) and silently # truncates longer files; reject by size first, else parsing gets confusing syntax errors. if f.size() >= SRC_BUF_SIZE: @@ -161,12 +163,12 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, sha1_val: str, c src_buf: bytes = stdlib.malloc(SRC_BUF_SIZE) if src_buf is None: f.close() - return None + VLogger.error(f"source buffer allocation failed (OOM): {file_path}", "Translate") bytes_read: LONG = f.read_all(src_buf, SRC_BUF_SIZE) f.close() if bytes_read <= 0: stdlib.free(src_buf) - return None + VLogger.error(f"source file read failed or empty: {file_path}", "Translate") if bytes_read < SRC_BUF_SIZE: src_buf[bytes_read] = 0 lx: ast.Lexer* = ast.new_lexer(mb) ast._lexer_init(lx, src_buf, mb) @@ -183,7 +185,7 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, sha1_val: str, c VLogger.error(f"parse failed: {file_path} (line {err_line} col {err_col})", "PARSE") return None # Generate the .vpi stub from the AST before tr.translate (same as Phase1); it lets - # other modules query function signatures and CTypedef definitions. + # other modules query function signatures and TypedefT definitions. if temp_dir is not None: pyi_buf: bytes = stdlib.malloc(PYI_BUF_SIZE) if pyi_buf is not None: @@ -235,9 +237,9 @@ def TranslateFileGetTrans(mb: memhub.MemBuddy*, file_path: str, sha1_val: str, c GenApi.set_current_file_imports(file_path) # clear the call stack (reset before each file translation) GenApi.clear_trace() - # module switch: clear the CDefine constant table to isolate each module's constants + # module switch: clear the DefineT constant table to isolate each module's constants GenApi.clear_cdefine_constants() - # module switch: clear the CTypedef table to isolate each module's typedefs + # module switch: clear the TypedefT table to isolate each module's typedefs GenApi.clear_ctypedefs() # namespace isolation: includes loose (all visible), user files strict (local + imports only) strict_mode: int = 1 diff --git a/App/lib/Phase/Phase1/Phase1Main.vp b/App/lib/Phase/Phase1/Phase1Main.vp index c431327..a4cb3f3 100644 --- a/App/lib/Phase/Phase1/Phase1Main.vp +++ b/App/lib/Phase/Phase1/Phase1Main.vp @@ -3,7 +3,7 @@ import string import stdlib import memhub import viperlib -import win32.fileio as fileio +import io as fileio import win32.win32base import ast import mlirlite @@ -256,7 +256,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg VLogger.error("includes_dir or temp_dir is empty; cannot run Phase1", "Phase1") return 1 - # Set the global temp_dir (used for cross-module CDefine lookup) + # Set the global temp_dir (used for cross-module DefineT lookup) GenApi.set_temp_dir(temp_dir) # Also set HandlesExprCall's temp_dir (for the on-demand vpi return-annotation cache); # must run before includes translation since Phase2's SetTempDir is too late. @@ -362,7 +362,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg VLogger.info(f"[EVIDENCE] order(class_names {p1_sz_cn}B+16)={p1_o_cn} fl={p1_fl_cn} " f"order(def_sha1s {p1_sz_ds}B+16)={p1_o_ds} fl={p1_fl_ds} " f"order(topo {p1_sz_tp}B+16)={p1_o_tp} order(box {p1_sz_bx}B+16)={p1_o_bx} " f"(fl==0 only means that order is empty and can still be split by a higher order; if alloc fails while dump_stats shows free>0 that is the definitive proof of metadata corruption; " f"any order>max_order={mb.max_order} means an abnormal size)", "Phase1") # Pre-scan class inheritance and build the topo order (flat byte arrays avoid str-pointer NULL crashes) - # 取消整数提升后,模块属性 CDefine 按最小位宽物化为 i16/i8;512*64=32768 恰好越出 i16 有符号上限而回绕成负值, + # 取消整数提升后,模块属性 DefineT 按最小位宽物化为 i16/i8;512*64=32768 恰好越出 i16 有符号上限而回绕成负值, # 首乘数显式 sizet() 加宽到 64 位。下面 def_sha1s_buf 的 512*17=8704 未溢出,无需改。 class_names_buf: bytes = mb.alloc(sizet(Phase1.MAX_CLASSES) * Phase1.CLASS_NAME_LEN) parent_names_buf: bytes = mb.alloc(sizet(Phase1.MAX_CLASSES) * Phase1.CLASS_NAME_LEN) @@ -739,7 +739,13 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg if vpos_1b < vtotal_1b: vpos_1b += 1 vi2_1b: sizet = vls_1b while vi2_1b < vls_1b + vll_1b and vbuf_1b[vi2_1b] == ' ': vi2_1b += 1 - if vll_1b >= 6 and string.strncmp(vbuf_1b + vi2_1b, "class ", 6) == 0: + vcls_1b: int = 0 + if vll_1b >= 14 and string.strncmp(vbuf_1b + vi2_1b, "virtual class ", 14) == 0: vcls_1b = 1 + elif vll_1b >= 16 and string.strncmp(vbuf_1b + vi2_1b, "novirtual class ", 16) == 0: vcls_1b = 1 + elif vll_1b >= 6 and (string.strncmp(vbuf_1b + vi2_1b, "class ", 6) == 0 or string.strncmp(vbuf_1b + vi2_1b, "union ", 6) == 0 or string.strncmp(vbuf_1b + vi2_1b, "renum ", 6) == 0): vcls_1b = 1 + elif vll_1b >= 5 and string.strncmp(vbuf_1b + vi2_1b, "enum ", 5) == 0: vcls_1b = 1 + elif vll_1b >= 7 and string.strncmp(vbuf_1b + vi2_1b, "struct ", 7) == 0: vcls_1b = 1 + if vcls_1b != 0: force_translate = 1 break stdlib.free(vbuf_1b) @@ -850,8 +856,8 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg # Timer 1: read + lexer/tokenize/parse done t_f1: uint64 = win32.win32base.GetTickCount64() - # Register CTypedef by scanning source text: AST string fields (attr/id) point into src_buf - # and dangle across phases (reading attr faults), so match "NAME: t.CTypedef = TYPE" lines. + # Register TypedefT by scanning source text: AST string fields (attr/id) point into src_buf + # and dangle across phases (reading attr faults), so match "NAME: t.TypedefT = TYPE" lines. GenApi.RegisterCTypedefFromSource(mb, src_buf) # Generate the .vpi stub (walk the AST directly). 1b-pre already generated one, so this diff --git a/App/lib/Phase/Phase1/Phase1Scan.vp b/App/lib/Phase/Phase1/Phase1Scan.vp index 0cf3573..64c507d 100644 --- a/App/lib/Phase/Phase1/Phase1Scan.vp +++ b/App/lib/Phase/Phase1/Phase1Scan.vp @@ -2,7 +2,7 @@ from stdint import * import string import stdlib import memhub -import win32.fileio as fileio +import io as fileio import ast import lib.core.VLogger as VLogger import lib.core.IncludesScanner as IncludesScanner diff --git a/App/lib/Phase/Phase2/__init__.vp b/App/lib/Phase/Phase2/__init__.vp index 0ec4c0f..8b6d176 100644 --- a/App/lib/Phase/Phase2/__init__.vp +++ b/App/lib/Phase/Phase2/__init__.vp @@ -5,7 +5,7 @@ import string import stdlib import memhub import viperlib -import win32.fileio as fileio +import io as fileio import win32.win32file import win32.win32base import sys @@ -28,6 +28,7 @@ import lib.Stub.StubMerger.StubMergerIR as StubMergerIR 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 # Global mbuddy pointer _mbuddy: memhub.MemBuddy* @@ -564,12 +565,11 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(rs_05) # Append App source SHA1s to _sha1_map.txt (human-readable only; format {sha1}:{rel_path}) and to the - # in-memory store (AppendToSha1MapStore) for cross-module CDefine lookups. - td_len_am: sizet = string.strlen(temp_dir) + # in-memory store (AppendToSha1MapStore) for cross-module DefineT lookups. src_dir_len_am: sizet = string.strlen(source_dir) - map_path_am: bytes = stdlib.malloc(td_len_am + 32) + # _sha1_map.txt 落在构建目录(temp 的上一级),方便直接按 SHA1 反查源文件 + map_path_am: bytes = StubMergerCollect.Sha1MapPath(mb, temp_dir) if map_path_am is not None: - viperlib.snprintf(map_path_am, td_len_am + 32, "%s/_sha1_map.txt", temp_dir) mf: fileio.File* = fileio.File(map_path_am, fileio.MODE.A) line_am: bytes = stdlib.malloc(512) # First fill the in-memory store (no file I/O), then write _sha1_map.txt @@ -584,7 +584,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou rel_path_am: str = p_str if p_len > src_dir_len_am + 1: if string.strncmp(p_str, source_dir, src_dir_len_am) == 0: rel_path_am = p_str + src_dir_len_am + 1 - # Also append to the in-memory store (for cross-module CDefine lookups) + # Also append to the in-memory store (for cross-module DefineT lookups) # rel_path_am looks like "lib/core/StubMerger.vp", matching _sha1_map.txt if StubMergerStore.AppendToSha1MapStore(ent_am.Sha1, rel_path_am) != 0: VLogger.error(f"AppendToSha1MapStore failed: {rel_path_am}", "project") app_filled += 1 @@ -606,8 +606,13 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou # === Phase A-pre-vpi: pre-generate .vpi stubs for all App sources === # Deps' .vpi must exist before their dependents translate; pre-generating avoids missing dep declarations. td_len_vpa: sizet = string.strlen(temp_dir) - PYI_BUF_SIZE_P2: sizet = 262144 + # Reuse BuildPipeline's DefineT (proven to be non-zero: the same buffer size generates ViperBuild.vp's + # vpi in Phase A). A large literal stored in a local used to come through as 0 here, and + # _GeneratePyiFromAst then returns immediately (buf_size == 0) -> every App .vpi silently missing. + PYI_BUF_SIZE_P2: sizet = BuildPipeline.PYI_BUF_SIZE vpi_failed_p2: int = 0 + vpi_written_p2: int = 0 + vpi_skipped_p2: int = 0 for i_vp2 in range(int(file_count)): ea_vp2: uint64 = uint64(entries) + i_vp2 * entry_size ent_vp2: SrcFileEntry* = (SrcFileEntry*)(ea_vp2) @@ -624,6 +629,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou vf_vp2: fileio.File* = fileio.File(vpi_path_vp2, fileio.MODE.R) if not vf_vp2.closed: vf_vp2.close() + vpi_skipped_p2 += 1 stdlib.free(vpi_path_vp2) continue stdlib.free(vpi_path_vp2) @@ -676,13 +682,24 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou # 文件级名字窗口(FileFuncNames 等)由 _GeneratePyiFromAst 入口统一 reset+重建, # 不再在此处重复调用;契约见 StructGenMixin.reset_file_name_window。 pyi_pos_vp2: sizet = StubConverter._GeneratePyiFromAst(mb, tree_vp2, ent_vp2.Path, pyi_buf_vp2, PYI_BUF_SIZE_P2) + if pyi_pos_vp2 == 0: + # The generator always emits at least a newline once buf_size > 1; 0 means the + # buffer size collapsed (or the generator failed) and no .vpi would be produced — + # every later cross-module lookup for this module would then fail. + vpi_failed_p2 += 1 + VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | vpi generation produced nothing (buf_size={PYI_BUF_SIZE_P2})", "PhaseA") if pyi_pos_vp2 > 0: pyi_path_vp2: str = StubMergerCollect._sliced_path(temp_dir, td_len_vpa, ent_vp2.Sha1, "vpi") if pyi_path_vp2 is not None: pf_vp2: fileio.File* = fileio.File(pyi_path_vp2, fileio.MODE.W) - if not pf_vp2.closed: - pf_vp2.write(pyi_buf_vp2, pyi_pos_vp2) - pf_vp2.close() + if pf_vp2.closed: + # Silent here used to mean "no App .vpi at all" and every cross-module + # return-type lookup later failed. Report the exact path instead. + vpi_failed_p2 += 1 + 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() + vpi_written_p2 += 1 stdlib.free(pyi_path_vp2) stdlib.free(pyi_buf_vp2) else: @@ -691,6 +708,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(src_buf_vp2) # OOM cleanup: free this file's AST (the vpi is already on disk; persistent data is stdlib-allocated) mb.reset_to(mark2) + VLogger.info(f"[Phase A-pre-vpi] generated={vpi_written_p2} skipped_existing={vpi_skipped_p2} failed={vpi_failed_p2} total={file_count}", "PhaseA") if vpi_failed_p2 > 0: VLogger.error(f"Phase A-pre-vpi: {vpi_failed_p2} files failed vpi pre-generation (see the per-file [Phase A-pre-vpi] logs above for failing files and causes)", "PhaseA") return 1 @@ -706,6 +724,14 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou # Initialize the Phase A convergence-failure diagnostic table (stdlib, survives reset) _PhaseADiagInit(sizet(file_count) + StubMerger.MAX_INCLUDES) + # 枚举全量预扫(最早的一次):所有已落盘 .vpi 里的 `enum X:` 先登记进 EnumTable, + # 再进入任何预注册/翻译。否则依赖 stub 里的 `types.FRESULT` 会在枚举模块的 vpi + # 被注入之前被当成类名 ensure_forward_class,占位结构体常驻进程级 StructTable, + # 之后所有模块的 `FRESULT` 都解析成空结构体(llvm.call 返回 + # !llvm.struct<"...FRESULT", ()>,函数本体却是 i32 -> mlir-opt: llvm.return 类型不匹配)。 + preenum_early: int = VpiReader.PreRegisterVpiEnums(mb, temp_dir) + VLogger.info(f"enum pre-scan (early): {preenum_early} enums registered", "PhaseA") + # === Phase A-pre-inc: pre-register includes structs + fill deps === # Must run before App pre-registration (multi-pass resolves cross-file inheritance) and fills PopulateIncludesDeps. if includes_dir is not None and includes_binary_dir is not None: @@ -738,15 +764,24 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou inc_fail_flags[si_inc] = 0 continue rel_len_pre: sizet = string.strlen(inc_rel_pre) - # The includes root dir (per-entry from the IncDir array; empty falls back to includes_dir) + # Empty IncDir marks an App source (AppendToSha1MapStore / _BuildReachableSha1Set): + # its rel path is source_dir-relative, so resolving it against includes_dir opens a + # non-existent file and the translation fails with no diagnostic. App declarations + # are pre-registered by Phase A-pre below (against source_dir) — skip them here. incdir_pre: str = store_incdir_inc_pre + sizet(si_inc) * StubMerger.MAX_INC_DIR_LEN - if incdir_pre is None or incdir_pre[0] == '\0': incdir_pre = includes_dir + if incdir_pre is None or incdir_pre[0] == '\0': + if inc_fail_flags is not None: inc_fail_flags[si_inc] = 0 + continue incdir_len_pre: sizet = string.strlen(incdir_pre) full_path_pre: bytes = stdlib.malloc(incdir_len_pre + rel_len_pre + 2) if full_path_pre is None: continue viperlib.snprintf(full_path_pre, incdir_len_pre + rel_len_pre + 2, "%s/%s", incdir_pre, inc_rel_pre) pkg_inc_pre: str = GenApi.compute_package_from_relpath(mb, inc_rel_pre) - tr_inc_pre: HandlesTranslator.Translator* = BuildPipeline.TranslateFileGetTrans(mb, full_path_pre, inc_sha1_pre, pkg_inc_pre, 1) + # Pass temp_dir so the include's .vpi is materialized here: Phase A resolves + # cross-module method return types (and escape contracts) from the dependency's + # .vpi, and in a build without Phase1 artifacts none exists yet — the lookup then + # fails hard ("method return type cannot be obtained precisely"). + tr_inc_pre: HandlesTranslator.Translator* = BuildPipeline.TranslateFileGetTrans(mb, full_path_pre, inc_sha1_pre, pkg_inc_pre, 1, temp_dir) stdlib.free(full_path_pre) if tr_inc_pre is not None: # Record this file's deps (for convergence diagnostics; stdlib table survives reset) @@ -789,7 +824,10 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou pkg_pre = GenApi.compute_package_from_relpath(mb, rel_path_pre) # App pre-registration passes pre_allow_dep_inject=1: imports and dep vpi injection happen at # declaration time so field annotations share Phase A's context (else struct layouts diverge). - tr_pre: HandlesTranslator.Translator* = BuildPipeline.TranslateFileGetTrans(mb, ent_pre.Path, ent_pre.Sha1, pkg_pre, 1, None, 1) + # temp_dir is passed so every App module's .vpi is materialized here (right after its AST is + # parsed), before Phase A translates anything: a module referenced by an earlier file must + # have its declarations on disk, else the call site cannot resolve its return type. + tr_pre: HandlesTranslator.Translator* = BuildPipeline.TranslateFileGetTrans(mb, ent_pre.Path, ent_pre.Sha1, pkg_pre, 1, temp_dir, 1) if tr_pre is None: VLogger.error(f"pre-registration failed: {ent_pre.Path}", "PhaseA") # OOM cleanup: free this App file's transient pre-registration data mb.reset_to(mark2) @@ -798,6 +836,22 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou if struct_count_after == struct_count_before: break VLogger.info("pre-registration complete", "PhaseA") + # 缺省参数全量预扫(TPC 式):所有模块 .vpi 里带缺省值的函数/方法一次性登记进全局 + # defaults 表,避免“谁先翻译谁登记”的时序依赖。stub-only 依赖(如包 mlirlite 的 + # __builder.vp,只产 .vpi 不产 .ll)走不到 FuncGenDef,缺了这一步跨模块少传实参时 + # 会补不出缺省值,直接发出操作数不足的 llvm.call(mlir-opt: incorrect number of + # operands;现场 build_load / build_store)。 + predef_count: int = VpiReader.PreRegisterVpiDefaults(mb, temp_dir) + VLogger.info(f"default-argument pre-scan: {predef_count} functions registered", "PhaseA") + + # 枚举全量预扫(与缺省值同理):所有模块 .vpi 里的 `enum X:` 一次性登记进 EnumTable, + # 消除"谁先注入谁登记"的时序依赖。否则依赖 stub 里的 `types.FRESULT` 会在枚举模块 + # 的 vpi 被注入之前解析,被当成类名 ensure_forward_class;占位结构体常驻进程级 + # StructTable,之后所有模块的 `FRESULT` 都变成空结构体(llvm.call 返回 + # !llvm.struct<"...FRESULT", ()>,而函数本体是 i32 -> mlir-opt: llvm.return 类型不匹配)。 + preenum_count: int = VpiReader.PreRegisterVpiEnums(mb, temp_dir) + VLogger.info(f"enum pre-scan: {preenum_count} enums registered", "PhaseA") + # Phase A IR buffer: 1MB is not enough, ast/parser.vp's text.ll exceeds 1MB and got truncated -> # merged IR lost the tail (opt: "end of file in global variable name"). PHASE_A_IR_SIZE: sizet = 8388608 @@ -1104,7 +1158,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou ti += 1 stdlib.free(llc_tasks) - # Assemble .s files: llvm-mc -> .obj (isr.s etc., bare symbols matching the .vp CExport/State stubs). + # Assemble .s files: llvm-mc -> .obj (isr.s etc., bare symbols matching the .vp ExportT/State stubs). # The obj is added to obj_paths for Phase C linking. od_len_as: sizet = string.strlen(output_dir) asm_compiled: int = 0 @@ -1179,12 +1233,20 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou # issues from passing the store's union return (bytes*) across functions; rel/incdir keep pointers. td_len_pb: sizet = string.strlen(temp_dir) store_sha1_arr_mi: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 17) - store_mod_arr_mi: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 64) + store_mod_arr_mi: bytes = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * 64) store_count_mi: int = 0 if store_sha1_arr_mi is not None and store_mod_arr_mi is not None: store_count_mi = StubMergerIR._BuildIncludesSha1Map(temp_dir, td_len_pb, store_sha1_arr_mi, store_mod_arr_mi) store_rel_arr_mi: bytes = StubMergerStore.GetSha1StoreRelArrPtr() store_incdir_arr_mi: bytes = StubMergerStore.GetSha1StoreIncdirArrPtr() + # 诊断/失败前置:store 为空或拓扑为空时,下面的 include 编译循环会整体跳过, + # 但 PhaseC 仍会把这些 include 的 .obj 塞进链接命令 → 只看到一串 + # "no such file or directory: includes.binary/xx/xxxx.obj"。这里直接给出根因。 + if store_count_mi <= 0: + VLogger.error(f"PhaseB+: Sha1Store is empty (store_count=0) but {reachable_count} include(s) are reachable; the include compile loop would be skipped and every include .obj would be missing at link time (check the Phase1 dependency scan / cache flags)", "PhaseB+") + if store_sha1_arr_mi is not None: stdlib.free(store_sha1_arr_mi) + if store_mod_arr_mi is not None: stdlib.free(store_mod_arr_mi) + return 1 if store_count_mi > 0: # Topologically sort reachable includes: dependencies are fully translated first. Cross-module @@ -1196,6 +1258,11 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou topo_count = 0 else: topo_count = StubMergerTopo.BuildTopoSortedIndices(store_count_mi, store_sha1_arr_mi, store_mod_arr_mi, reachable_set, reachable_count, topo_idx) + # 拓扑为空 ⇒ 下面的编译循环整体不执行,但 PhaseC 仍会引用这些 .obj → 链接必然失败。 + # 在这里给根因,而不是让人去看一串 "no such file or directory"。 + if topo_count <= 0: + VLogger.error(f"PhaseB+: topological sort produced 0 entries (store_count={store_count_mi}, reachable={reachable_count}); include .obj files would be missing at link time", "PhaseB+") + return 1 for si_mi in range(int(topo_count)): # Current entry (topological index; reachable modules only) @@ -1321,6 +1388,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou stdlib.free(vpcall_text_mi) # Retry reading full.ll inc_combined_len_mi = StubMergerBuild.LoadFullIR(temp_dir, inc_sha1_mi, inc_combined_mi, COMBINED_IR_SIZE) + if tr_mi is None: + VLogger.error(f"include translation returned no module: {src_fp_mi} (sha1={inc_sha1_mi})", "PhaseB+") # OOM cleanup: stub/text/meta/full are on disk, free this includes file's transient # translation data (AST/tokens/lexer/IR/Handler); reads only touch files. mb.reset_to(mark2) @@ -1351,12 +1420,14 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou diag_text_ex = 1 df_diag_t.close() stdlib.free(diag_text) - VLogger.error(f"failed to read full.ll: {src_fp_mi} (full={diag_full_ex} stub={diag_stub_ex} text={diag_text_ex} is_decl={is_decl_mi})", "PhaseB+") + # fail-fast:该 include 既没有 full.ll、翻译也没产出 → 它的 .obj 必然缺失, + # 继续下去只会得到一串链接器 "no such file or directory"。这里直接停下并指出文件。 + VLogger.error(f"failed to read full.ll: {src_fp_mi} (full={diag_full_ex} stub={diag_stub_ex} text={diag_text_ex} is_decl={is_decl_mi}); aborting so the include .obj is not silently missing at link time", "PhaseB+") if inc_combined_mi is not None: stdlib.free(inc_combined_mi) stdlib.free(check_pat_mi) if check_fd_mi is not None: stdlib.free(check_fd_mi) stdlib.free(src_fp_mi) - continue + return 1 # Compile to .obj (output to includes_binary_dir, consistent with the existence check and link collection) inc_cret_mi: int = BuildPipeline.compile_module_to_obj(inc_combined_mi, inc_combined_len_mi, temp_dir, includes_binary_dir, inc_sha1_mi, cc_cmd, cc_flags) @@ -1475,18 +1546,12 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, ou fop_pos += op_len_pc final_obj_paths[fop_pos] = '\0' else: - # .obj missing: a declaration-only module (no define lines) needs no .obj, so skip silently; - # under phase2command mode the missing .obj is expected and the path is still added. - if BuildPipeline._phase2_cmd_path is not None: - op_len_p2: sizet = string.strlen(obj_path_pc) - if fop_pos + op_len_p2 + 2 < OBJ_PATHS_SIZE + 512: - if fop_pos > 0: - final_obj_paths[fop_pos] = ' ' - fop_pos += 1 - string.strcpy(final_obj_paths + fop_pos, obj_path_pc) - fop_pos += op_len_p2 - final_obj_paths[fop_pos] = '\0' - elif StubMergerIR.IsDeclOnlyModule(temp_dir, td_len_pc, rsha1_pc) != 1: VLogger.error(f"reachable module .obj missing: {obj_path_pc}", "PhaseC") + # .obj 缺失:声明-only 模块本来就不需要 .obj,静默跳过;其余情况无论是否 + # --phase2command,链接都会因缺文件失败 —— 直接报根因并中止,不要拖到链接器。 + if StubMergerIR.IsDeclOnlyModule(temp_dir, td_len_pc, rsha1_pc) != 1: + VLogger.error(f"reachable module .obj missing: {obj_path_pc} (not declaration-only); aborting before link", "PhaseC") + stdlib.free(obj_path_pc) + return 1 stdlib.free(obj_path_pc) final_obj_paths[fop_pos] = '\0' diff --git a/App/lib/Projectrans/Config.vp b/App/lib/Projectrans/Config.vp index 60dd966..ec2ab04 100644 --- a/App/lib/Projectrans/Config.vp +++ b/App/lib/Projectrans/Config.vp @@ -6,7 +6,7 @@ import viperlib import lib.core.VLogger as VLogger from json.__parser import parse as json_parse from json import JsonValue, JSON_OBJECT, JSON_INT -import win32.fileio as fileio +import io as fileio # Config - project configuration diff --git a/App/lib/Projectrans/Utils.vp b/App/lib/Projectrans/Utils.vp index 0f9c316..e0b971d 100644 --- a/App/lib/Projectrans/Utils.vp +++ b/App/lib/Projectrans/Utils.vp @@ -6,7 +6,7 @@ import viperlib import hashlib import win32.win32file import win32.win32base -import win32.fileio as fileio +import io as fileio # Utils - project utility functions (SHA1 hashing, directory cleanup) diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 017f453..2ab8370 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -10,7 +10,7 @@ import sys import memhub import mlirlite import viperlib -import win32.fileio as fileio +import io as fileio import lib.core.VLogger as VLogger import lib.core.Gen.Translator as HT import lib.Stub.StubMerger.StubMergerStore as StubMergerStore @@ -92,6 +92,18 @@ _g_vpi_ret_keys: bytes = None _g_vpi_ret_strs: bytes = None _g_vpi_ret_count: int = 0 _g_vpi_ret_cap: int = 0 +# Bare-name flags parallel to _g_vpi_ret_strs: 1 = the vpi declaration is `export def` / +# `extern def`, i.e. the defining side emits the bare C/module-export name (no sha1 prefix). +# Cross-module calls must use the bare name too, otherwise the call references a mangled +# symbol that nobody defines (现场: stdlib.exit -> ab4ed42e48881c97.exit, link error). +_g_vpi_bare_flags: bytes = None +# Module-local typedef aliases, keyed "{sha1}.{NAME}" -> RHS type text. A vpi keeps the +# source spelling of return/param annotations, so an alias (`posix.getpid -> pid_t`) can +# only be resolved by expanding it through the defining module's own `typedef` lines. +define VPI_TD_MAX = 4096 +_g_vpi_td_keys: bytes = None +_g_vpi_td_vals: bytes = None +_g_vpi_td_count: int = 0 # Method parameter-annotation cache (stdlib-persistent, mirroring the return cache). # Deep param lists may be corrupted and truncated, so params are parsed from vpi_buf text. @@ -150,6 +162,57 @@ define VPI_ESC_MOD_MAX = 512 _g_vpi_esc_mods: bytes = None _g_vpi_esc_mod_count: int = 0 +def _vpi_class_name_offset(vpi_buf: bytes, i: sizet, le: sizet) -> int: + """行首(缩进后)类名起始偏移;支持 `class` / `enum` / `struct` / `union` / `renum` + 以及 `virtual` / `novirtual` 前缀。返回 -1 表示非类定义行。""" + if vpi_buf is None or i >= le: return -1 + if le - i >= 10 and string.strncmp(vpi_buf + i, "novirtual ", 10) == 0: i += 10 + elif le - i >= 8 and string.strncmp(vpi_buf + i, "virtual ", 8) == 0: i += 8 + if le - i >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0: return int(i + 6) + if le - i >= 5 and string.strncmp(vpi_buf + i, "enum ", 5) == 0: return int(i + 5) + if le - i >= 7 and string.strncmp(vpi_buf + i, "struct ", 7) == 0: return int(i + 7) + if le - i >= 6 and string.strncmp(vpi_buf + i, "union ", 6) == 0: return int(i + 6) + if le - i >= 6 and string.strncmp(vpi_buf + i, "renum ", 6) == 0: return int(i + 6) + return -1 + +def _vpi_func_name_offset(vpi_buf: bytes, i: sizet, le: sizet) -> int: + """行首(缩进后)函数名起始偏移;支持 `def` / `define` 以及 + `export` / `extern` / `static` / `virtual` / `novirtual` 前缀。返回 -1 表示非函数定义行。""" + if vpi_buf is None or i >= le: return -1 + while True: + if le - i >= 7 and string.strncmp(vpi_buf + i, "export ", 7) == 0: i += 7 + elif le - i >= 7 and string.strncmp(vpi_buf + i, "extern ", 7) == 0: i += 7 + elif le - i >= 7 and string.strncmp(vpi_buf + i, "static ", 7) == 0: i += 7 + elif le - i >= 8 and string.strncmp(vpi_buf + i, "virtual ", 8) == 0: i += 8 + elif le - i >= 10 and string.strncmp(vpi_buf + i, "novirtual ", 10) == 0: i += 10 + elif le - i >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0: return int(i + 4) + elif le - i >= 7 and string.strncmp(vpi_buf + i, "define ", 7) == 0: + # `define NAME(...)` is a macro function; `define NAME = ...` is a constant. + k_dm: sizet = i + 7 + while k_dm < le and vpi_buf[k_dm] != '(' and vpi_buf[k_dm] != '=' and vpi_buf[k_dm] != ' ': k_dm += 1 + if k_dm < le and vpi_buf[k_dm] == '(': return int(i + 7) + return -1 + else: return -1 + + +def _vpi_build_path(temp_dir: str, sha1: str, ext: str) -> str: + """Build `{temp_dir}/{slice}/{sha1}{ext}` using Config.Sha1SliceLevel, byte-for-byte matching + the writer (StubMergerCollect._sliced_path). Hardcoding the two-hex subdir breaks every lookup + when the project runs with slice level 0 (flat temp) or 2 (nested). stdlib buffer; caller frees.""" + if temp_dir is None or sha1 is None or ext is None: return None + sub: str = Config.slice_subdir(sha1, Config.Sha1SliceLevel) + sub_len: sizet = 0 + if sub is not None: sub_len = string.strlen(sub) + td_len: sizet = string.strlen(temp_dir) + sh_len: sizet = string.strlen(sha1) + ex_len: sizet = string.strlen(ext) + path_len: sizet = td_len + sub_len + sh_len + ex_len + 4 + buf: bytes = stdlib.malloc(path_len) + if buf is None: return None + if sub_len > 0: viperlib.snprintf(buf, path_len, "%s/%s/%s%s", temp_dir, sub, sha1, ext) + else: viperlib.snprintf(buf, path_len, "%s/%s%s", temp_dir, sha1, ext) + return buf + def _vpi_escape_module_loaded(src_sha1: str) -> int: """该模块的 vpi 是否已由逃逸查询路径加载过;1=是 / 0=否""" if _g_vpi_esc_mods is None or src_sha1 is None: return 0 @@ -305,19 +368,132 @@ def _vpi_rebuild_param_idx(new_cap: int) -> int: return 0 +# _vpi_td_store - record one `typedef NAME = RHS` line into the per-module alias table. +# The emitter may duplicate the RHS (`typedef pid_t = int32 = int32`): take the first segment. +def _vpi_td_store(vpi_buf: bytes, start: sizet, le: sizet, src_sha1: str) -> int: + """Store a module-local typedef alias ("{sha1}.{NAME}" -> RHS text).""" + global _g_vpi_td_count + if vpi_buf is None or src_sha1 is None or _g_vpi_td_keys is None: return 0 + ns: sizet = start + while ns < le and vpi_buf[ns] == ' ': ns += 1 + ne: sizet = ns + while ne < le and vpi_buf[ne] != ' ' and vpi_buf[ne] != '=': ne += 1 + if ne <= ns: return 0 + eq: sizet = ne + while eq < le and vpi_buf[eq] != '=': eq += 1 + if eq >= le: return 0 + rs: sizet = eq + 1 + while rs < le and vpi_buf[rs] == ' ': rs += 1 + re: sizet = rs + while re < le and vpi_buf[re] != '=': re += 1 + while re > rs and vpi_buf[re - 1] == ' ': re -= 1 + if re <= rs: return 0 + name_len: sizet = ne - ns + sha1_len: sizet = string.strlen(src_sha1) + key_len: sizet = sha1_len + 1 + name_len + if key_len >= VPI_KEY_LEN or (re - rs) >= VPI_RET_LEN: + VLogger.error(f"typedef alias cache slot too small (key {key_len}/{VPI_KEY_LEN}, rhs {re - rs}/{VPI_RET_LEN}); shorten the alias name or its RHS", "VpiReader") + key_buf: bytes = stdlib.malloc(key_len + 1) + if key_buf is None: return 0 + string.strncpy(key_buf, src_sha1, sha1_len) + key_buf[sha1_len] = '.' + string.strncpy(key_buf + sha1_len + 1, vpi_buf + ns, name_len) + key_buf[key_len] = '\0' + # dedup: the same module vpi may be loaded more than once + hit_i: int = -1 + key_first: int = key_buf[0] + for i in range(int(_g_vpi_td_count)): + ent_t: bytes = _g_vpi_td_keys + i * VPI_KEY_LEN + if ent_t[0] == key_first and string.strcmp(ent_t, key_buf) == 0: + hit_i = i + break + if hit_i < 0: + if _g_vpi_td_count >= VPI_TD_MAX: + VLogger.error(f"typedef alias table full ({VPI_TD_MAX} entries); raise VPI_TD_MAX", "VpiReader") + hit_i = _g_vpi_td_count + string.strcpy(_g_vpi_td_keys + hit_i * VPI_KEY_LEN, key_buf) + _g_vpi_td_count += 1 + string.strncpy(_g_vpi_td_vals + hit_i * VPI_RET_LEN, vpi_buf + rs, re - rs) + (_g_vpi_td_vals + hit_i * VPI_RET_LEN)[re - rs] = '\0' + stdlib.free(key_buf) + return 1 + + +# _lookup_vpi_typedef - resolve a bare alias name through the defining module's typedefs. +def _lookup_vpi_typedef(src_sha1: str, name: str) -> str: + """Return the RHS text of a module-local typedef alias; None on miss.""" + if _g_vpi_td_keys is None or _g_vpi_td_count == 0 or src_sha1 is None or name is None: return None + nl: sizet = string.strlen(name) + if nl == 0: return None + ns_v: sizet = 0 + while ns_v < nl and (name[ns_v] == ' ' or name[ns_v] == '\t'): ns_v += 1 + ne_v: sizet = nl + while ne_v > ns_v and (name[ne_v - 1] == ' ' or name[ne_v - 1] == '\t'): ne_v -= 1 + if ne_v <= ns_v: return None + sl: sizet = string.strlen(src_sha1) + name_len: sizet = ne_v - ns_v + key_len: sizet = sl + 1 + name_len + if key_len >= VPI_KEY_LEN: return None + kb: bytes = stdlib.malloc(key_len + 1) + if kb is None: return None + string.strncpy(kb, src_sha1, sl) + kb[sl] = '.' + string.strncpy(kb + sl + 1, name + ns_v, name_len) + kb[key_len] = '\0' + hit: int = -1 + key_first_v: int = kb[0] + for i in range(int(_g_vpi_td_count)): + ent_q: bytes = _g_vpi_td_keys + i * VPI_KEY_LEN + if ent_q[0] == key_first_v and string.strcmp(ent_q, kb) == 0: + hit = i + break + stdlib.free(kb) + if hit < 0: return None + return _g_vpi_td_vals + hit * VPI_RET_LEN + + +# _resolve_vpi_annot_with_typedefs - resolve an annotation, expanding module-local typedef aliases +# (现场: posix.getpid -> pid_t could not be resolved, so the call fell back to a placeholder i32). +def _resolve_vpi_annot_with_typedefs(pool: memhub.MemBuddy*, src_sha1: str, ret_annot: str, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*: + """Resolve a vpi annotation string, following same-module typedef aliases (max 4 levels).""" + if ret_annot is None or pool is None: return None + ty: mlirlite.LLVMType* = _resolve_vpi_ret_annot_str(pool, ret_annot, i32_ty, i8_ptr_ty) + if ty is not None: return ty + cur: str = ret_annot + depth: int = 0 + while depth < 4: + nxt: str = _lookup_vpi_typedef(src_sha1, cur) + if nxt is None: return None + ty = _resolve_vpi_ret_annot_str(pool, nxt, i32_ty, i8_ptr_ty) + if ty is not None: return ty + cur = nxt + depth += 1 + return None + + # _cache_vpi_return_annots_text - extract method return annotations from vpi_buf text. # Does not rely on the AST tree. Tracks "class X:" and "def ... -> Ret:" lines. def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: """Extract method return-annotation strings from vpi_buf text into a persistent stdlib cache.""" global _g_vpi_ret_keys, _g_vpi_ret_strs, _g_vpi_ret_count, _g_vpi_ret_cap global _g_vpi_ret_idx, _g_vpi_ret_idx_cap, _g_vpi_ret_idx_built + global _g_vpi_bare_flags + global _g_vpi_td_keys, _g_vpi_td_vals, _g_vpi_td_count if vpi_buf is None or src_sha1 is None: return 0 + if _g_vpi_td_keys is None: + _g_vpi_td_keys = stdlib.malloc(VPI_TD_MAX * VPI_KEY_LEN) + _g_vpi_td_vals = stdlib.malloc(VPI_TD_MAX * VPI_RET_LEN) + if _g_vpi_td_keys is None or _g_vpi_td_vals is None: return 0 + string.memset(_g_vpi_td_keys, 0, VPI_TD_MAX * VPI_KEY_LEN) + string.memset(_g_vpi_td_vals, 0, VPI_TD_MAX * VPI_RET_LEN) if _g_vpi_ret_keys is None: _g_vpi_ret_keys = stdlib.malloc(VPI_RET_MAX * VPI_KEY_LEN) _g_vpi_ret_strs = stdlib.malloc(VPI_RET_MAX * VPI_RET_LEN) - if _g_vpi_ret_keys is None or _g_vpi_ret_strs is None: return 0 + _g_vpi_bare_flags = stdlib.malloc(VPI_RET_MAX) + if _g_vpi_ret_keys is None or _g_vpi_ret_strs is None or _g_vpi_bare_flags is None: return 0 string.memset(_g_vpi_ret_keys, 0, VPI_RET_MAX * VPI_KEY_LEN) string.memset(_g_vpi_ret_strs, 0, VPI_RET_MAX * VPI_RET_LEN) + string.memset(_g_vpi_bare_flags, 0, VPI_RET_MAX) _g_vpi_ret_cap = VPI_RET_MAX # Name index: slots = key-table capacity * 2. On alloc failure stay untrusted # (Built=-1) and fall back to linear scan (semantics unchanged). @@ -342,10 +518,22 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: # skip leading spaces, then check class/def i: sizet = ls while i < le and vpi_buf[i] == ' ': i += 1 + # Typedef alias line (`typedef NAME = RHS`): recorded per module so a return/param + # annotation spelled with the alias can be expanded at the call site. + if i < le and le - i >= 8 and string.strncmp(vpi_buf + i, "typedef ", 8) == 0: + _vpi_td_store(vpi_buf, i + 8, le, src_sha1) + pos = le + 1 + line_no += 1 + continue if i < le: - # class X: - if line_len >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0: - cstart: sizet = i + 6 + # `export def` / `extern def`: the defining side uses the bare name. + bare_flag: int = 0 + if le - i >= 7 and (string.strncmp(vpi_buf + i, "export ", 7) == 0 or string.strncmp(vpi_buf + i, "extern ", 7) == 0): + bare_flag = 1 + # class X: (also `virtual class` / `novirtual class` from older stub dialects) + cls_off_c: int = _vpi_class_name_offset(vpi_buf, i, le) + if cls_off_c >= 0: + cstart: sizet = sizet(cls_off_c) cend: sizet = cstart # Extract class name: stop at ':' / ' ' / '(' so base-class parens # are excluded ("class MemBuddy(MemManager):" -> "MemBuddy"). @@ -357,13 +545,13 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: string.strncpy(cur_cls, vpi_buf + cstart, cend - cstart) cur_cls[cend - cstart] = '\0' # def name(...) -> Ret: # noqa - elif line_len >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0: + elif line_len >= 4 and _vpi_func_name_offset(vpi_buf, i, le) >= 0: # Module-level function (no indent): clear class context to avoid # leftovers after a class ends (e.g. json null/int_val). if i == ls and cur_cls is not None: stdlib.free(cur_cls) cur_cls = None - dstart: sizet = i + 4 + dstart: sizet = sizet(_vpi_func_name_offset(vpi_buf, i, le)) dend: sizet = dstart while dend < le and vpi_buf[dend] != '(': dend += 1 # method name = [dstart, dend) @@ -425,8 +613,10 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: # Index fast path: when trusted (Built==Count) a single # probe detects duplicates; otherwise fall back to linear scan. if _g_vpi_ret_idx_built == _g_vpi_ret_count and _g_vpi_ret_idx is not None and _g_vpi_ret_idx_cap > 0: - if _vpi_idx_find(_g_vpi_ret_keys, VPI_KEY_LEN, _g_vpi_ret_idx, _g_vpi_ret_idx_cap, key_buf) >= 0: + dup_hit_i: int = _vpi_idx_find(_g_vpi_ret_keys, VPI_KEY_LEN, _g_vpi_ret_idx, _g_vpi_ret_idx_cap, key_buf) + if dup_hit_i >= 0: dup_key = 1 + if bare_flag != 0 and _g_vpi_bare_flags is not None: _g_vpi_bare_flags[dup_hit_i] = int8(1) else: # Fast path: equal keys share the first byte, so skip # strcmp when the candidate's first byte differs. @@ -435,6 +625,7 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: ent_d: bytes = _g_vpi_ret_keys + dki * VPI_KEY_LEN if ent_d[0] == dup_first and string.strcmp(ent_d, key_buf) == 0: dup_key = 1 + if bare_flag != 0 and _g_vpi_bare_flags is not None: _g_vpi_bare_flags[dki] = int8(1) break grow_ok: int = 1 if dup_key == 0 and _g_vpi_ret_count >= _g_vpi_ret_cap: grow_ok = _grow_vpi_ret_cache() @@ -442,6 +633,7 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: string.strcpy(_g_vpi_ret_keys + _g_vpi_ret_count * VPI_KEY_LEN, key_buf) string.strncpy(_g_vpi_ret_strs + _g_vpi_ret_count * VPI_RET_LEN, ret_src, rtype_len) (_g_vpi_ret_strs + _g_vpi_ret_count * VPI_RET_LEN)[rtype_len] = '\0' + if _g_vpi_bare_flags is not None: _g_vpi_bare_flags[_g_vpi_ret_count] = int8(bare_flag) _g_vpi_ret_count += 1 # Sync index only if trusted before insert (Built==Count-1, # incl. rebuild) and insert succeeded; else mark untrusted. @@ -492,8 +684,9 @@ def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int: i: sizet = ls while i < le and vpi_buf[i] == ' ': i += 1 if i < le: - if line_len >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0: - cstart: sizet = i + 6 + cls_off_p: int = _vpi_class_name_offset(vpi_buf, i, le) + if cls_off_p >= 0: + cstart: sizet = sizet(cls_off_p) cend: sizet = cstart while cend < le and vpi_buf[cend] != ':' and vpi_buf[cend] != ' ' and vpi_buf[cend] != '(': cend += 1 if cend > cstart: @@ -502,12 +695,12 @@ def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int: if cur_cls is not None: string.strncpy(cur_cls, vpi_buf + cstart, cend - cstart) cur_cls[cend - cstart] = '\0' - elif line_len >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0: + elif line_len >= 4 and _vpi_func_name_offset(vpi_buf, i, le) >= 0: # module-level function (no indent): clear class context if i == ls and cur_cls is not None: stdlib.free(cur_cls) cur_cls = None - dstart: sizet = i + 4 + dstart: sizet = sizet(_vpi_func_name_offset(vpi_buf, i, le)) dend: sizet = dstart while dend < le and vpi_buf[dend] != '(': dend += 1 if dend >= le: @@ -844,7 +1037,7 @@ def _resolve_vpi_param_annot_seg(pool: memhub.MemBuddy*, src_sha1: str, cls_name # Doubles via stdlib.realloc (cap 1M) so entries are not silently dropped when full. def _grow_vpi_ret_cache() -> int: """Grow the return-annotation cache to double capacity; returns 1=success / 0=failure.""" - global _g_vpi_ret_keys, _g_vpi_ret_strs, _g_vpi_ret_cap + global _g_vpi_ret_keys, _g_vpi_ret_strs, _g_vpi_ret_cap, _g_vpi_bare_flags if _g_vpi_ret_keys is None or _g_vpi_ret_cap <= 0: return 0 new_cap: int = _g_vpi_ret_cap * 2 if new_cap > 1048576: return 0 @@ -852,8 +1045,11 @@ def _grow_vpi_ret_cache() -> int: if new_keys is None: return 0 new_strs: bytes = stdlib.realloc(_g_vpi_ret_strs, new_cap * VPI_RET_LEN) if new_strs is None: return 0 + new_flags: bytes = stdlib.realloc(_g_vpi_bare_flags, new_cap) + if new_flags is None: return 0 _g_vpi_ret_keys = new_keys _g_vpi_ret_strs = new_strs + _g_vpi_bare_flags = new_flags _g_vpi_ret_cap = new_cap # grow the index with the table and rebuild it (slots = capacity * 2, power of 2) _vpi_rebuild_ret_idx(new_cap * 2) @@ -877,18 +1073,11 @@ def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int: for pre_i in range(int(_g_vpi_ret_count)): pre_key: bytes = _g_vpi_ret_keys + pre_i * VPI_KEY_LEN if pre_key[0] == s0 and pre_key[1] == s1 and string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.': return 1 - # build path {temp_dir}/{sha1[0:2]}/{sha1}.vpi (char by char, avoiding %c formatting) - td_len: sizet = string.strlen(temp_dir) - path_len: sizet = td_len + sha1_len + 12 - vpi_path: bytes = stdlib.malloc(path_len) + # build {temp_dir}/{slice}/{sha1}.vpi — the slice must follow Config.Sha1SliceLevel, + # exactly like the writer (StubMergerCollect._sliced_path); hardcoding two hex chars + # makes every lookup miss when the project uses slice level 0 or 2. + vpi_path: str = _vpi_build_path(temp_dir, src_sha1, ".vpi") if vpi_path is None: return 0 - string.strcpy(vpi_path, temp_dir) - vpi_path[td_len] = '/' - vpi_path[td_len + 1] = src_sha1[0] - vpi_path[td_len + 2] = src_sha1[1] - vpi_path[td_len + 3] = '/' - string.strcpy(vpi_path + td_len + 4, src_sha1) - string.strcpy(vpi_path + td_len + 4 + sha1_len, ".vpi") f: fileio.File* = fileio.File(vpi_path, fileio.MODE.R) if f.closed: stdlib.free(vpi_path) @@ -944,8 +1133,9 @@ def _cache_vpi_esc_clears_text(vpi_buf: bytes, src_sha1: str) -> int: i: sizet = ls while i < le and vpi_buf[i] == ' ': i += 1 if i < le: - if le - i >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0: - cstart: sizet = i + 6 + cls_off_r: int = _vpi_class_name_offset(vpi_buf, i, le) + if cls_off_r >= 0: + cstart: sizet = sizet(cls_off_r) cend: sizet = cstart while cend < le and vpi_buf[cend] != ':' and vpi_buf[cend] != ' ' and vpi_buf[cend] != '(': cend += 1 if cend > cstart: @@ -954,7 +1144,7 @@ def _cache_vpi_esc_clears_text(vpi_buf: bytes, src_sha1: str) -> int: if cur_cls is not None: string.strncpy(cur_cls, vpi_buf + cstart, cend - cstart) cur_cls[cend - cstart] = '\0' - elif le - i >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0: + elif le - i >= 4 and _vpi_func_name_offset(vpi_buf, i, le) >= 0: # 模块级函数(无缩进)清掉类上下文,避免上一类泄漏 if i == ls and cur_cls is not None: stdlib.free(cur_cls) @@ -967,7 +1157,7 @@ def _cache_vpi_esc_clears_text(vpi_buf: bytes, src_sha1: str) -> int: hm = j break j += 1 - dstart: sizet = i + 4 + dstart: sizet = sizet(_vpi_func_name_offset(vpi_buf, i, le)) dend: sizet = dstart while dend < le and vpi_buf[dend] != '(': dend += 1 if hm < le and dend > dstart: @@ -1145,9 +1335,10 @@ def _cache_vpi_escape_annots_text(vpi_buf: bytes, src_sha1: str) -> int: i: sizet = ls while i < le and vpi_buf[i] == ' ': i += 1 if i < le: - # class X: -> 记录当前类上下文(与方法键一致) - if le - i >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0: - cstart: sizet = i + 6 + # class X: -> 记录当前类上下文(与方法键一致;兼容 virtual/novirtual 前缀) + cls_off_e: int = _vpi_class_name_offset(vpi_buf, i, le) + if cls_off_e >= 0: + cstart: sizet = sizet(cls_off_e) cend: sizet = cstart while cend < le and vpi_buf[cend] != ':' and vpi_buf[cend] != ' ' and vpi_buf[cend] != '(': cend += 1 if cend > cstart: @@ -1157,7 +1348,7 @@ def _cache_vpi_escape_annots_text(vpi_buf: bytes, src_sha1: str) -> int: string.strncpy(cur_cls, vpi_buf + cstart, cend - cstart) cur_cls[cend - cstart] = '\0' # def name(...) ... # esc= ret= - elif le - i >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0: + elif le - i >= 4 and _vpi_func_name_offset(vpi_buf, i, le) >= 0: # 模块级函数(无缩进)清掉类上下文,避免上一类泄漏 if i == ls and cur_cls is not None: stdlib.free(cur_cls) @@ -1184,7 +1375,7 @@ def _cache_vpi_escape_annots_text(vpi_buf: bytes, src_sha1: str) -> int: ret_v = ret_v * 10 + (int(vpi_buf[kp]) - 48) kp += 1 if (esc_v != 0 or ret_v != 0) and _g_vpi_esc_count < VPI_ESC_MAX: - dstart: sizet = i + 4 + dstart: sizet = sizet(_vpi_func_name_offset(vpi_buf, i, le)) dend: sizet = dstart while dend < le and vpi_buf[dend] != '(': dend += 1 if dend > dstart: @@ -1260,9 +1451,10 @@ def _cache_vpi_willfree_annots_text(vpi_buf: bytes, src_sha1: str) -> int: i: sizet = ls while i < le and vpi_buf[i] == ' ': i += 1 if i < le: - # class X: -> 记录当前类上下文(与方法键一致) - if le - i >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0: - cstart: sizet = i + 6 + # class X: -> 记录当前类上下文(与方法键一致;兼容 virtual/novirtual 前缀) + cls_off_e: int = _vpi_class_name_offset(vpi_buf, i, le) + if cls_off_e >= 0: + cstart: sizet = sizet(cls_off_e) cend: sizet = cstart while cend < le and vpi_buf[cend] != ':' and vpi_buf[cend] != ' ' and vpi_buf[cend] != '(': cend += 1 if cend > cstart: @@ -1272,7 +1464,7 @@ def _cache_vpi_willfree_annots_text(vpi_buf: bytes, src_sha1: str) -> int: string.strncpy(cur_cls, vpi_buf + cstart, cend - cstart) cur_cls[cend - cstart] = '\0' # def name(...) ... # [esc= ret=] wf= - elif le - i >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0: + elif le - i >= 4 and _vpi_func_name_offset(vpi_buf, i, le) >= 0: # 模块级函数(无缩进)清掉类上下文,避免上一类泄漏 if i == ls and cur_cls is not None: stdlib.free(cur_cls) @@ -1300,7 +1492,7 @@ def _cache_vpi_willfree_annots_text(vpi_buf: bytes, src_sha1: str) -> int: wf_v = wf_v * 10 + (int(vpi_buf[kp]) - 48) kp += 1 if wf_v != 0 and _g_vpi_wf_count < VPI_WF_MAX: - dstart: sizet = i + 4 + dstart: sizet = sizet(_vpi_func_name_offset(vpi_buf, i, le)) dend: sizet = dstart while dend < le and vpi_buf[dend] != '(': dend += 1 if dend > dstart: @@ -1376,7 +1568,7 @@ def _vpi_param_annot_seg_is(src_sha1: str, cls_name: str, method_name: str, seg_ # _vpi_param_annot_seg_mode - value-reference mode of a param-annotation segment: 0=none / 1=ref / 2=temp. -# The definition side writes `T | t.CRef` / `T | t.CTempRef`; the call side mirrors it (auto-address). +# The definition side writes `&T` / `&temp T`; the call side mirrors it (auto-address). def _vpi_param_annot_seg_mode(prm_annots: str, seg_idx: int) -> int: """Return the value-reference mode encoded in a vpi param-annotation segment.""" if prm_annots is None or seg_idx < 0: return 0 @@ -1393,23 +1585,48 @@ def _vpi_param_annot_seg_mode(prm_annots: str, seg_idx: int) -> int: string.strncpy(seg_buf, prm_annots + cur, slen) seg_buf[slen] = '\0' mode: int = 0 - if string.strstr(seg_buf, "CTempRef") is not None: mode = 2 - elif string.strstr(seg_buf, "CRef") is not None: mode = 1 - elif seg_buf[0] == '&': - # The stub may carry the union marker (`T | t.CRef`, written by this compiler's StubGen) or - # the source syntax (`&T` / `&temp T`, written by TPC's StubGen). Accept both, else a - # cross-module value-reference call would silently lose its auto-address. + if slen > 0 and seg_buf[0] == '&': + # Stubs carry the source syntax `&T` / `&temp T`; the retired RefT markers are not read. q: sizet = 0 - while seg_buf[q] == ' ' or seg_buf[q] == '\t': q += 1 - if seg_buf[q] == '&': + while q < slen and (seg_buf[q] == ' ' or seg_buf[q] == '\t'): q += 1 + if q < slen and seg_buf[q] == '&': q += 1 - while seg_buf[q] == ' ' or seg_buf[q] == '\t': q += 1 + while q < slen and (seg_buf[q] == ' ' or seg_buf[q] == '\t'): q += 1 if slen - q >= 4 and string.strncmp(seg_buf + q, "temp", 4) == 0: mode = 2 else: mode = 1 stdlib.free(seg_buf) return mode +# _vpi_param_annot_seg_is_ptr - 某形参注解段是否**声明为指针类型**。 +# 返回 1=指针(`T*` / `PtrT[T]` / `rawptr`)、0=裸值类型(`T`)、-1=无注解或值引用段(`&T`/`&temp T`, +# 由调用侧的 auto-address 处理,不参与"值→指针"判定)。 +def _vpi_param_annot_seg_is_ptr(prm_annots: str, seg_idx: int) -> int: + """Return 1 if the param's declared type is a pointer, 0 if a bare value type, -1 if unknown/ref.""" + if prm_annots is None or seg_idx < 0: return -1 + cur: sizet = 0 + for seg in range(int(seg_idx)): + while prm_annots[cur] != '\0' and prm_annots[cur] != '@': cur += 1 + if prm_annots[cur] == '\0': return -1 + cur += 1 + slen: sizet = 0 + while prm_annots[cur + slen] != '\0' and prm_annots[cur + slen] != '@': slen += 1 + if slen == 0: return -1 + # 值引用形参(&T / &temp T):实参自动取地址/拷贝,不属于"值 → T*"的隐式转换 + if prm_annots[cur] == '&': return -1 + depth: int = 0 + i: sizet = 0 + while i < slen: + c: uint8 = prm_annots[cur + i] + if c == '(' or c == '[': depth += 1 + elif c == ')' or c == ']': depth -= 1 + elif c == '*' and depth == 0: return 1 + i += 1 + if slen >= 6 and string.strncmp(prm_annots + cur, "rawptr", 6) == 0: return 1 + if slen >= 4 and string.strncmp(prm_annots + cur, "PtrT", 4) == 0: return 1 + return 0 + + # _ensure_vpi_param_annot_loaded - lazily load a module's vpi stub param-annotation cache. # Mirror of the return variant; reads the .vpi file on miss, idempotent. def _ensure_vpi_param_annot_loaded(src_sha1: str, temp_dir: str) -> int: @@ -1427,18 +1644,11 @@ def _ensure_vpi_param_annot_loaded(src_sha1: str, temp_dir: str) -> int: for pre_i in range(int(_g_vpi_param_count)): pre_key: bytes = _g_vpi_param_keys + pre_i * VPI_PARAM_KEY_LEN if pre_key[0] == s0 and pre_key[1] == s1 and string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.': return 1 - # build path {temp_dir}/{sha1[0:2]}/{sha1}.vpi (char by char, avoiding %c formatting) - td_len: sizet = string.strlen(temp_dir) - path_len: sizet = td_len + sha1_len + 12 - vpi_path: bytes = stdlib.malloc(path_len) + # build {temp_dir}/{slice}/{sha1}.vpi — the slice must follow Config.Sha1SliceLevel, + # exactly like the writer (StubMergerCollect._sliced_path); hardcoding two hex chars + # makes every lookup miss when the project uses slice level 0 or 2. + vpi_path: str = _vpi_build_path(temp_dir, src_sha1, ".vpi") if vpi_path is None: return 0 - string.strcpy(vpi_path, temp_dir) - vpi_path[td_len] = '/' - vpi_path[td_len + 1] = src_sha1[0] - vpi_path[td_len + 2] = src_sha1[1] - vpi_path[td_len + 3] = '/' - string.strcpy(vpi_path + td_len + 4, src_sha1) - string.strcpy(vpi_path + td_len + 4 + sha1_len, ".vpi") f: fileio.File* = fileio.File(vpi_path, fileio.MODE.R) if f.closed: stdlib.free(vpi_path) @@ -1579,12 +1789,94 @@ def _lookup_vpi_ret_annot(src_sha1: str, cls_name: str, method_name: str) -> str return None +# _lookup_vpi_bare - whether the vpi declared this function with `export def` / `extern def` +# (defining side uses the bare symbol name). Independent of the return annotation, which for +# unannotated externs is absent. Returns 1=bare / 0=not found or not bare. +def _lookup_vpi_bare(src_sha1: str, cls_name: str, method_name: str) -> int: + """Look up the bare-name flag of a cached vpi function declaration.""" + if _g_vpi_ret_keys is None or _g_vpi_bare_flags is None or _g_vpi_ret_count == 0: return 0 + if src_sha1 is None or method_name is None: return 0 + sha1_len: sizet = string.strlen(src_sha1) + cl_len: sizet = 0 + if cls_name is not None: cl_len = string.strlen(cls_name) + me_len: sizet = string.strlen(method_name) + key_len: sizet = sha1_len + 1 + cl_len + 1 + me_len + key_buf: bytes = stdlib.malloc(key_len + 1) + if key_buf is None: return 0 + if cls_name is not None: viperlib.snprintf(key_buf, key_len + 1, "%s.%s.%s", src_sha1, cls_name, method_name) + else: viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, method_name) + hit_b: int = -1 + if _g_vpi_ret_idx_built == _g_vpi_ret_count and _g_vpi_ret_idx is not None and _g_vpi_ret_idx_cap > 0: + hit_b = _vpi_idx_find(_g_vpi_ret_keys, VPI_KEY_LEN, _g_vpi_ret_idx, _g_vpi_ret_idx_cap, key_buf) + else: + key_first_b: int = key_buf[0] + for i in range(int(_g_vpi_ret_count)): + ent_b: bytes = _g_vpi_ret_keys + i * VPI_KEY_LEN + if ent_b[0] == key_first_b and string.strcmp(ent_b, key_buf) == 0: + hit_b = i + break + stdlib.free(key_buf) + if hit_b < 0: return 0 + return int(_g_vpi_bare_flags[hit_b]) + + +# _vpi_struct_or_enum - 结构体优先,但 **opaque 前向占位** 必须让位给已注册的枚举。 +# 占位来源:枚举名曾在别的模块被当成类名 ensure_forward_class(StructTable 是进程级的), +# 若这里按裸名命中它,`-> types.FRESULT` 就会变成"返回一个空结构体",与函数本体的 i32 冲突。 +def _vpi_struct_or_enum(seg: str) -> mlirlite.LLVMType*: + """Struct type for seg, yielding to a registered enum when the struct is only a forward placeholder.""" + if seg is None: return None + struct_ty: mlirlite.LLVMType* = GenApi.get_struct_type(seg) + # 字符串解析路径没有模块上下文:同名多条枚举时只在"基类型完全一致"时才用(见 compat 注释) + enum_ty: mlirlite.LLVMType* = GenApi.get_enum_base_type_compat(seg) + if struct_ty is None: return enum_ty + if enum_ty is None: return struct_ty + se: GenTypes.StructEntry* = GenApi.find_struct(seg) + if se is not None and se.IsForwardDecl != 0: return enum_ty + return struct_ty + + # _resolve_single_vpi_type_str - resolve a single type segment to LLVMType. # Pure string parsing, no AST nodes. def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.LLVMType*: """Resolve a single type segment to LLVMType; None if unrecognized.""" if seg is None or pool is None: return None - # Canonical pointer form is `T*` (also rawptr[T] / t.CPtr[T]); rewrite trailing `*` + # Source-syntax descriptors / value references: `const T` / `volatile T` / `&T` / + # `&temp T`. The retired t.* spellings (ConstT[...] / RefT[...]) are not accepted. + seg_l0: sizet = string.strlen(seg) + seg_sp0: sizet = 0 + while seg_sp0 < seg_l0 and (seg[seg_sp0] == ' ' or seg[seg_sp0] == '\t'): seg_sp0 += 1 + seg_ep0: sizet = seg_l0 + while seg_ep0 > seg_sp0 and (seg[seg_ep0 - 1] == ' ' or seg[seg_ep0 - 1] == '\t'): seg_ep0 -= 1 + if seg_sp0 > 0 or seg_ep0 < seg_l0: + trim_buf: bytes = stdlib.malloc(seg_ep0 - seg_sp0 + 1) + if trim_buf is None: return None + string.strncpy(trim_buf, seg + seg_sp0, seg_ep0 - seg_sp0) + trim_buf[seg_ep0 - seg_sp0] = '\0' + trim_ty: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, trim_buf) + stdlib.free(trim_buf) + return trim_ty + if seg_l0 - seg_sp0 >= 6 and string.strncmp(seg + seg_sp0, "const ", 6) == 0: + return _resolve_single_vpi_type_str(pool, seg + seg_sp0 + 6) + if seg_l0 - seg_sp0 >= 9 and string.strncmp(seg + seg_sp0, "volatile ", 9) == 0: + return _resolve_single_vpi_type_str(pool, seg + seg_sp0 + 9) + if seg_sp0 < seg_l0 and seg[seg_sp0] == '&': + # Value-reference parameter ABI: &T / &temp T -> T* (both modes use a pointer). + ref_sp1: sizet = seg_sp0 + 1 + while ref_sp1 < seg_l0 and (seg[ref_sp1] == ' ' or seg[ref_sp1] == '\t'): ref_sp1 += 1 + if seg_l0 - ref_sp1 >= 5 and string.strncmp(seg + ref_sp1, "temp ", 5) == 0: ref_sp1 += 5 + inner_ref: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg + ref_sp1) + if inner_ref is not None: return mlirlite.Ptr(pool, inner_ref) + return None + if seg_sp0 < seg_l0 and seg[seg_sp0] == '[': + # `[N]T` / `[]T` in a parameter position decays to an element pointer. + arr_rb: sizet = seg_sp0 + 1 + while arr_rb < seg_l0 and seg[arr_rb] != ']': arr_rb += 1 + if arr_rb < seg_l0: + inner_arr: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg + arr_rb + 1) + if inner_arr is not None: return mlirlite.Ptr(pool, inner_arr) + return None + # Canonical pointer form is `T*` (also rawptr[T]); rewrite trailing `*` # into rawptr[...] so specialized args like "{sha}.AST*" resolve instead of silently returning None. seg_len_ps: sizet = string.strlen(seg) star_end_ps: sizet = seg_len_ps @@ -1631,8 +1923,8 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L # vpi caches `-> None` verbatim as "None"; map None to Void so cross-module # `-> None` methods do not fail fast with an unresolvable return type. if string.strcmp(seg, "None") == 0: return mlirlite.Void(pool) - # Descriptor / storage / value-reference marker wrappers: CConst[T], State[T], CExport[T], ... - # The marker carries no value type, so resolve the wrapped type. + # Storage / descriptor marker wrappers (StaticT[T], ExportT[T], State[T], ...): + # the marker carries no value type, so resolve the wrapped type. if seg_len_ps > 0 and seg[seg_len_ps - 1] == ']': lb_mk: str = string.strchr(seg, '[') if lb_mk is not None: @@ -1650,12 +1942,9 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L dot_mk: str = string.strrchr(mk_name, '.') if dot_mk is not None: mk_name = dot_mk + 1 is_marker: int = is_attr_marker - if string.strcmp(mk_name, "CConst") == 0 or string.strcmp(mk_name, "CVolatile") == 0 \ - or string.strcmp(mk_name, "BigEndian") == 0 or string.strcmp(mk_name, "LittleEndian") == 0 \ - or string.strcmp(mk_name, "CStatic") == 0 or string.strcmp(mk_name, "CExport") == 0 \ - or string.strcmp(mk_name, "CExtern") == 0 or string.strcmp(mk_name, "State") == 0 \ - or string.strcmp(mk_name, "CDefine") == 0 or string.strcmp(mk_name, "CRef") == 0 \ - or string.strcmp(mk_name, "CTempRef") == 0 or string.strcmp(mk_name, "CAutoPtr") == 0 \ + if string.strcmp(mk_name, "BigEndian") == 0 or string.strcmp(mk_name, "LittleEndian") == 0 \ + or string.strcmp(mk_name, "StaticT") == 0 or string.strcmp(mk_name, "ExportT") == 0 \ + or string.strcmp(mk_name, "ExternT") == 0 or string.strcmp(mk_name, "State") == 0 \ or string.strcmp(mk_name, "attr") == 0: is_marker = 1 if is_marker != 0: @@ -1666,15 +1955,10 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L inner_mk[inner_len_mk] = '\0' inner_ty_mk: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, inner_mk) stdlib.free(inner_mk) - # Value-reference parameters use the pointer ABI: &T / &temp T -> T*. - if inner_ty_mk is not None and (string.strcmp(mk_name, "CRef") == 0 or string.strcmp(mk_name, "CTempRef") == 0): - inner_ty_mk = mlirlite.Ptr(pool, inner_ty_mk) - elif string.strcmp(mk_name, "CAutoPtr") == 0: - inner_ty_mk = i8_ptr_ty stdlib.free(mk_buf) return inner_ty_mk stdlib.free(mk_buf) - # rawptr[T] built-in raw pointer (equivalent to t.CPtr[T] -> Ptr(T)); vpi keeps the + # rawptr[T] built-in raw pointer (equivalent to rawptr[T] -> Ptr(T)); vpi keeps the # Subscript form verbatim. Same as TypeGenResolve's rawptr branch: Ptr(resolve(T)). if string.strncmp(seg, "rawptr[", 7) == 0: seg_len_rp: sizet = string.strlen(seg) @@ -1688,48 +1972,31 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L stdlib.free(inner_buf_rp) if inner_ty_rp is not None: return mlirlite.Ptr(pool, inner_ty_rp) return None - # CPtr[T] subscript pointers (nesting allowed, e.g. CPtr[CPtr] -> Ptr(Ptr(i8))); - # same as TypeGenResolve's Subscript(CPtr) branch: Ptr(resolve(T)). - cptr_sub_pfx: sizet = 0 - if string.strncmp(seg, "CPtr[", 6) == 0: cptr_sub_pfx = 6 - if cptr_sub_pfx > 0: - seg_len_cp: sizet = string.strlen(seg) - if seg_len_cp > cptr_sub_pfx + 1 and seg[seg_len_cp - 1] == ']': - inner_len_cp: sizet = seg_len_cp - cptr_sub_pfx - 1 - inner_buf_cp: bytes = stdlib.malloc(inner_len_cp + 1) - if inner_buf_cp is not None: - string.strncpy(inner_buf_cp, seg + cptr_sub_pfx, inner_len_cp) - inner_buf_cp[inner_len_cp] = '\0' - inner_ty_cp: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, inner_buf_cp) - stdlib.free(inner_buf_cp) - if inner_ty_cp is not None: return mlirlite.Ptr(pool, inner_ty_cp) - return None - # Bare builtin type names (int8 / uint64 / sizet / CPtr / CExport ...) + # Bare builtin type names (int8 / uint64 / sizet / ExportT ...) if string.strchr(seg, '.') is None: - # CExport is an export marker, not a real type; FuncGenDef maps it to Void, + # ExportT is an export marker, not a real type; FuncGenDef maps it to Void, # so normalize it here too or cross-module return types fail fast. - if string.strcmp(seg, "CExport") == 0: return mlirlite.Void(pool) + if string.strcmp(seg, "ExportT") == 0: return mlirlite.Void(pool) mapped_ty: mlirlite.LLVMType* = GenApi.resolve_typedef_or_map(pool, seg, None, None) if mapped_ty is not None: return mapped_ty + # Bare class / enum name (e.g. `-> Lexer*` written in the defining module's own vpi): + # mirror _resolve_vpi_annot_direct and consult the struct/enum registries, otherwise + # `X*` -> rawptr[X] fails on the inner name and the whole return type is lost. + bare_cls_ty: mlirlite.LLVMType* = _vpi_struct_or_enum(seg) + if bare_cls_ty is not None: return bare_cls_ty return None # module-qualified class name "memhub.MemBuddy" -> use last segment to look up struct last_dot: str = string.strrchr(seg, '.') if last_dot is not None: cls_nm2: str = last_dot + 1 - mod_struct_ty: mlirlite.LLVMType* = GenApi.get_struct_type(cls_nm2) + # Enum 必须在结构体之前判定:opaque 前向占位(枚举名曾被当成类名)不得压过枚举 + mod_struct_ty: mlirlite.LLVMType* = _vpi_struct_or_enum(cls_nm2) if mod_struct_ty is not None: return mod_struct_ty - # Enum fallback: t.CEnum registers in EnumTable, not StructTable, so get_struct_type - # misses; fall back to the enum base type (usually i32) instead of returning None. - mod_enum_ty: mlirlite.LLVMType* = GenApi.get_enum_base_type(cls_nm2) - if mod_enum_ty is not None: return mod_enum_ty return None # bare class name -> global struct registry - bare_struct_ty: mlirlite.LLVMType* = GenApi.get_struct_type(seg) + bare_struct_ty: mlirlite.LLVMType* = _vpi_struct_or_enum(seg) if bare_struct_ty is not None: return bare_struct_ty - # bare enum name -> EnumTable fallback (as above: enums are not in StructTable) - bare_enum_ty: mlirlite.LLVMType* = GenApi.get_enum_base_type(seg) - if bare_enum_ty is not None: return bare_enum_ty - # bare typedef name (e.g. VOIDPTR): CTypedef dynamic table first, on miss fall back to map_builtin_type + # bare typedef name (e.g. VOIDPTR): TypedefT dynamic table first, on miss fall back to map_builtin_type mapped_bare: mlirlite.LLVMType* = GenApi.resolve_typedef_or_map(pool, seg, None, None) if mapped_bare is not None: return mapped_bare return None @@ -1780,7 +2047,7 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: m if tup_head_t is None: return None return mlirlite.Struct(pool, tup_head_t, tup_count_t, None) - # Pure marker annotations (t.State / t.CExtern / t.CExport alone, optional t./c. prefix) + # Pure marker annotations (t.State / t.ExternT / t.ExportT alone, optional t./c. prefix) # -> void: decorator markers, not real return types, matching _inject_module_declare. marker_len: sizet = string.strlen(ret_annot) mk_s: sizet = 0 @@ -1790,139 +2057,22 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: m mk_off_m: sizet = 0 if mk_e - mk_s >= 2 and (ret_annot[mk_s] == 't' or ret_annot[mk_s] == 'c') and ret_annot[mk_s + 1] == '.': mk_off_m = 2 mk_nlen: sizet = mk_e - mk_s - mk_off_m - if (mk_nlen == 5 and string.strncmp(ret_annot + mk_s + mk_off_m, "State", 5) == 0) or (mk_nlen == 7 and string.strncmp(ret_annot + mk_s + mk_off_m, "CExtern", 7) == 0) or (mk_nlen == 7 and string.strncmp(ret_annot + mk_s + mk_off_m, "CExport", 7) == 0): + if (mk_nlen == 5 and string.strncmp(ret_annot + mk_s + mk_off_m, "State", 5) == 0) or (mk_nlen == 7 and string.strncmp(ret_annot + mk_s + mk_off_m, "ExternT", 7) == 0) or (mk_nlen == 7 and string.strncmp(ret_annot + mk_s + mk_off_m, "ExportT", 7) == 0): return mlirlite.Void(pool) - has_cptr: int = 0 - has_cref: int = 0 - has_cvoid: int = 0 - first_ty: mlirlite.LLVMType* = None - # Attributes of the segment that produced first_ty (for the has_cptr branch, N12): - # first_qual=qualified name; first_wl=whitelisted bare scalar; first_td=resolved bare typedef. - first_qual: int = 0 - first_wl: int = 0 - first_td: int = 0 - # Union-merge params for CPtr subscript segments (t.CPtr[T]/c.CPtr[T], nesting allowed): - # cptr_sub_lvl = outer Ptr level N, cptr_sub_ty = the segment's own type. -> Ptr^N(base). - cptr_sub_lvl: int = 0 - cptr_sub_ty: mlirlite.LLVMType* = None - - buf_len: sizet = string.strlen(ret_annot) - pos: sizet = 0 - while pos <= buf_len: - # skip leading spaces - while pos < buf_len and ret_annot[pos] == ' ': pos += 1 - seg_start: sizet = pos - # Split `|` only at the top level: `|` inside `[...]` belongs to a generic arg, - # so naive splitting would truncate `list[ast.AST*]`; depth-count on `[`/`]`. - depth_ra: int = 0 - while pos < buf_len: - ch_ra: char = ret_annot[pos] - if ch_ra == '[': depth_ra += 1 - elif ch_ra == ']': - if depth_ra > 0: depth_ra -= 1 - elif ch_ra == '|': - if depth_ra == 0: break - pos += 1 - seg_end: sizet = pos - # trim trailing spaces - while seg_end > seg_start and ret_annot[seg_end - 1] == ' ': seg_end -= 1 - if seg_end > seg_start: - seg_len2: sizet = seg_end - seg_start - seg_buf: bytes = stdlib.malloc(seg_len2 + 1) - if seg_buf is None: break - string.strncpy(seg_buf, ret_annot + seg_start, seg_len2) - seg_buf[seg_len2] = '\0' - # CPtr subscript segments must be recognized first: the definition side treats - # `X*[T]` as "CPtr side contributes N pointer levels, the other side the base". - if string.strncmp(seg_buf, "CPtr[", 6) == 0: - seg_ty_cs: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg_buf) - if seg_ty_cs is not None: - # Count outer Ptr nesting (same as the definition side). Do not update the - # loop variable inside a case body: match sinks into a subject snapshot. - lvl_cs: int = 0 - cur_cs: mlirlite.LLVMType* = seg_ty_cs - while cur_cs is not None: - nxt_cs: mlirlite.LLVMType* = None - match cur_cs: - case mlirlite.LLVMType.Ptr(pointee_cs): - lvl_cs += 1 - nxt_cs = pointee_cs - case _: - break - cur_cs = nxt_cs - # take the max level across subscript segments (equivalent to level accumulation) - if lvl_cs > cptr_sub_lvl: - cptr_sub_lvl = lvl_cs - cptr_sub_ty = seg_ty_cs - elif string.strcmp(seg_buf, "CPtr") == 0: has_cptr = 1 - elif string.strcmp(seg_buf, "CRef") == 0: has_cref = 1 - elif string.strcmp(seg_buf, "CTempRef") == 0: has_cref = 1 - elif string.strcmp(seg_buf, "void") == 0 or string.strcmp(seg_buf, "CVoid") == 0: - # Bare void is an alias of t.CVoid and must be handled the same, else the - # else branch sets first_td and produces the illegal Ptr(Void) (void* = i8*). - has_cvoid = 1 - else: - seg_ty: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg_buf) - if seg_ty is not None and first_ty is None: - first_ty = seg_ty - # Record the segment attribute (N12): the definition side keeps a bare pointee - # only for registered structs, generic params or whitelisted scalars; else i8*. - if string.strchr(seg_buf, 46) is not None: first_qual = 1 - if string.strcmp(seg_buf, "int") == 0 or string.strcmp(seg_buf, "char") == 0 or string.strcmp(seg_buf, "str") == 0 or string.strcmp(seg_buf, "bytes") == 0 or string.strcmp(seg_buf, "bool") == 0 or string.strcmp(seg_buf, "float") == 0 or string.strcmp(seg_buf, "double") == 0: - first_wl = 1 - # Preserve bare typedef aliases (N13): use GenApi.resolve_typedef_or_map (same API - # as the definition side); set only for non-pointer results (pointer aliases -> i8*). - if first_qual == 0 and first_wl == 0: - if first_ty.kind() != mlirlite.LLVMType.Ptr: - if GenApi.resolve_typedef_or_map(pool, seg_buf, None, None) is not None: first_td = 1 - stdlib.free(seg_buf) - pos += 1 # skip '|' - - if has_cptr != 0 or has_cref != 0: - if has_cvoid != 0: return i8_ptr_ty - if first_ty is not None: - # The result itself is void (e.g. an opaque typedef resolving to void): CPtr merge - # gives void* = i8*, matching has_cvoid (otherwise it would build illegal Ptr(void)). - match first_ty: - case mlirlite.LLVMType.Void(): - return i8_ptr_ty - case _: - pass - # Mirror the definition-side rules (N12/N13): qualified name / whitelisted scalar / - # registered struct / resolvable non-pointer typedef keeps pointee; else collapse to i8*. - if first_qual != 0 or first_wl != 0 or first_td != 0: return mlirlite.Ptr(pool, first_ty) - if first_ty.kind() == mlirlite.LLVMType.Struct: return mlirlite.Ptr(pool, first_ty) - if first_ty.kind() == mlirlite.LLVMType.Ptr: - # `| t.CPtr` always appends exactly one pointer level (same as the definition - # side's BinOp branch); no struct-absorption, so `T*` stays `T*` (never AST*). - return mlirlite.Ptr(pool, first_ty) - return i8_ptr_ty - return i8_ptr_ty - # CPtr subscript union merge: with another side's base type first_ty -> Ptr^N(first_ty); - # with only the subscript segment -> the segment's own type. No struct absorption here. - if cptr_sub_lvl > 0: - if first_ty is None: return cptr_sub_ty - res_ty_cs: mlirlite.LLVMType* = first_ty - for wrap_i_cs in range(int(cptr_sub_lvl)): - res_ty_cs = mlirlite.Ptr(pool, res_ty_cs) - return res_ty_cs - if has_cvoid != 0: - # t.CVoid | t.State: State is a decorator marker (ignored), t.CVoid -> void. - # Without this branch first_ty is None and cross-module return types fail fast. - return mlirlite.Void(pool) - if first_ty is not None: return first_ty - return None + # Unions were retired, so a stub return annotation is a single type. `T*` / `T**` and + # `const` / `volatile` prefixes are handled by the shared single-segment resolver. + return _resolve_single_vpi_type_str(pool, ret_annot) -# VpiReader - .vpi AST parser. Reads function signatures and CTypedef definitions from +# VpiReader - .vpi AST parser. Reads function signatures and TypedefT definitions from # .vpi stub files and injects declares, filling in missing cross-module declarations. # .vpi read buffer size (256KB, matching PYI_BUF_SIZE) VPI_READ_BUF_SIZE: sizet = 262144 # SHA1 hex char count (16, filename hex only, no trailing NUL). Named uniquely vs Phase1's -# SHA1_LEN (17 = 16 chars + NUL) to avoid cross-module CDefine shadowing. +# SHA1_LEN (17 = 16 chars + NUL) to avoid cross-module DefineT shadowing. SHA1_HEX_LEN: sizet = 16 @@ -1956,7 +2106,7 @@ def _extract_sha1_from_path(vpi_path: str) -> str: # Unified to AppendImportedModule / AppendFromImport / NotePlainImport; pure bookkeeping. -# Recursive typedef pre-registration over the import closure. CTypedef tables are per-file, +# Recursive typedef pre-registration over the import closure. TypedefT tables are per-file, # so unimported typedefs would degrade param types; this registers them (cycle/depth guarded). define _TYPEDEF_VISIT_MAX = 1024 @@ -2020,10 +2170,9 @@ def _vpi_typedef_visited_mark(sha1: str) -> None: def _vpi_register_typedef_closure(pool: memhub.MemBuddy*, import_mods: str, temp_dir: str, depth: int) -> int: - """Recursively register CTypedef/CDefine from import-closure modules; always returns 0 (side-effect only).""" + """Recursively register TypedefT/DefineT from import-closure modules; always returns 0 (side-effect only).""" if pool is None or import_mods is None or temp_dir is None: return 0 if depth > 8: return 0 - td_len: sizet = string.strlen(temp_dir) total_len: sizet = string.strlen(import_mods) pos: sizet = 0 while pos < total_len: @@ -2043,18 +2192,10 @@ def _vpi_register_typedef_closure(pool: memhub.MemBuddy*, import_mods: str, temp stdlib.free(mod_buf) if sha1_r is None: continue if _vpi_typedef_visited_check(sha1_r) != 0: continue - # build {temp_dir}/{sha1[0:2]}/{sha1}.vpi by hand (same as _ensure_vpi_ret_annot_loaded, + # build {temp_dir}/{slice}/{sha1}.vpi (same helper as _ensure_vpi_ret_annot_loaded, # avoiding a StubMergerCollect cyclic dependency) - path_len_r: sizet = td_len + 16 + 12 - vpi_path_r: bytes = stdlib.malloc(path_len_r) + vpi_path_r: str = _vpi_build_path(temp_dir, sha1_r, ".vpi") if vpi_path_r is None: continue - string.strcpy(vpi_path_r, temp_dir) - vpi_path_r[td_len] = '/' - vpi_path_r[td_len + 1] = sha1_r[0] - vpi_path_r[td_len + 2] = sha1_r[1] - vpi_path_r[td_len + 3] = '/' - string.strcpy(vpi_path_r + td_len + 4, sha1_r) - string.strcpy(vpi_path_r + td_len + 4 + 16, ".vpi") f_r: fileio.File* = fileio.File(vpi_path_r, fileio.MODE.R) if f_r.closed: stdlib.free(vpi_path_r) @@ -2131,7 +2272,7 @@ def _vpi_register_typedef_closure(pool: memhub.MemBuddy*, import_mods: str, temp string.strcpy(sub_imports + sub_len, ifr_r.module) sub_len += ml_r sub_imports[sub_len] = '\0' - # second pass: register this module's AnnAssign CTypedef/CDefine + # second pass: register this module's AnnAssign TypedefT/DefineT ci2_r: sizet for ci2_r in range(int(cn_r)): child2_r: ast.AST* = ch_r.get(ci2_r) @@ -2182,6 +2323,644 @@ def _vpi_register_typedef_closure(pool: memhub.MemBuddy*, import_mods: str, temp return 0 +# ============================================================ +# 缺省参数全量预扫(与 TPC 的 _extract_app_default_args 同构) +# +# 背景:全局 defaults 表原先只靠“翻译到哪个模块就登记哪个函数”的副作用填充 +# (FuncGenDef / _inject_function_declare)。stub-only 依赖(例如包 mlirlite 的 +# __builder.vp:本代只产 .vpi、不产 .ll)永远走不到 FuncGenDef;跨模块调用少传实参时 +# 补不出缺省值,于是直接发出操作数不足的 llvm.call,一路拖到 mlir-opt 才报 +# "'llvm.call' op incorrect number of operands"(现场:build_load / build_store)。 +# +# 这里在翻译开始前把 store 里所有模块的 .vpi 文本扫一遍,按 {定义模块 SHA1} 登记缺省值, +# 与编译顺序无关。只登记“缺省值全是可重建字面量”的函数:否则 _build_const_from_snapshot +# 重建失败会把本来能过的调用变成 Diag。方法登记名与 _inject_method_declare 一致 +# ("ClassName.method",ParamCount 不含 self)。 +define VPI_DEF_SCAN_MAX = 128 + +# _VpiParamListEnd - 与 open_pos 处 '(' 配对的 ')' 下标;找不到返回 le +def _VpiParamListEnd(vbuf: bytes, open_pos: sizet, le: sizet) -> sizet: + """返回与 open_pos 处 '(' 配对的 ')' 下标;找不到返回 le""" + depth: int = 0 + q: uint8 = 0 + i: sizet = open_pos + while i < le: + ch: uint8 = vbuf[i] + if q != 0: + if ch == 0x5C: + i += 2 + continue + if ch == q: q = 0 + i += 1 + continue + if ch == 0x22 or ch == 0x27: q = ch + elif ch == 0x28 or ch == 0x5B or ch == 0x7B: depth += 1 + elif ch == 0x29 or ch == 0x5D or ch == 0x7D: + depth -= 1 + if depth <= 0: return i + i += 1 + return le + +# _VpiParseIntRange - 把 [s, e) 解析成整数(十进制 / 0x 十六进制 / 带符号);1=成功 +def _VpiParseIntRange(vbuf: bytes, s: sizet, e: sizet, out_v: int64*) -> int: + """把 [s, e) 解析成整数;1=成功 / 0=不是整数字面量""" + if out_v is None or s >= e: return 0 + neg: int = 0 + i: sizet = s + if vbuf[i] == 0x2D: neg = 1; i += 1 + elif vbuf[i] == 0x2B: i += 1 + if i >= e: return 0 + base: int64 = 10 + if e - i > 2 and vbuf[i] == 0x30 and (vbuf[i + 1] == 0x78 or vbuf[i + 1] == 0x58): + base = 16 + i += 2 + v: int64 = 0 + digits: int = 0 + while i < e: + ch: uint8 = vbuf[i] + d: int64 = -1 + if ch >= 0x30 and ch <= 0x39: d = int64(ch) - 48 + elif ch >= 0x61 and ch <= 0x66: d = int64(ch) - 87 + elif ch >= 0x41 and ch <= 0x46: d = int64(ch) - 55 + if d < 0: return 0 + if d >= base: return 0 + v = v * base + d + digits += 1 + i += 1 + if digits == 0: return 0 + if neg != 0: v = -v + out_v[0] = v + return 1 + +# _VpiFillDefaultLit - 把第 di 个缺省值字面量写入快照数组;1=成功(可重建) +def _VpiFillDefaultLit(pool: memhub.MemBuddy*, vbuf: bytes, s: sizet, e: sizet, kinds: int*, ints: int64*, strs: sizet*, di: int) -> int: + """把第 di 个缺省值字面量写入快照;0=不可重建(字符串转义/复杂表达式)""" + if kinds is None or ints is None or strs is None or di < 0: return 0 + a: sizet = s + b: sizet = e + while a < b and vbuf[a] == 0x20: a += 1 + while b > a and vbuf[b - 1] == 0x20: b -= 1 + if b <= a: return 0 + ch0: uint8 = vbuf[a] + # 字符串字面量:只接受不含转义的简单形式(转义需要反解,交给 AST 路径) + if ch0 == 0x22 or ch0 == 0x27: + if b - a < 2: return 0 + if vbuf[b - 1] != ch0: return 0 + ci: sizet = a + 1 + while ci < b - 1: + if vbuf[ci] == 0x5C: return 0 + ci += 1 + clen: sizet = b - a - 2 + scopy: str = CompilerFactory.factory.PermPool.alloc(clen + 1) + if scopy is None: return 0 + string.strncpy(scopy, vbuf + a + 1, clen) + scopy[clen] = '\0' + kinds[di] = ast.CONST_STR + ints[di] = 0 + strs[di] = sizet(scopy) + return 1 + if b - a == 4 and string.strncmp(vbuf + a, "True", 4) == 0: + kinds[di] = ast.CONST_BOOL + ints[di] = 1 + strs[di] = 0 + return 1 + if b - a == 5 and string.strncmp(vbuf + a, "False", 5) == 0: + kinds[di] = ast.CONST_BOOL + ints[di] = 0 + strs[di] = 0 + return 1 + if b - a == 4 and string.strncmp(vbuf + a, "None", 4) == 0: + kinds[di] = ast.CONST_NONE + ints[di] = 0 + strs[di] = 0 + return 1 + iv: int64* = pool.alloc(8) + if iv is None: return 0 + if _VpiParseIntRange(vbuf, a, b, iv) == 0: return 0 + kinds[di] = ast.CONST_INT + ints[di] = iv[0] + strs[di] = 0 + return 1 + +# _VpiParseParamDefaults - 解析参数区间:返回 (参数个数 << 8) | 缺省值个数;-1=有不可重建缺省值 +def _VpiParseParamDefaults(pool: memhub.MemBuddy*, vbuf: bytes, ps: sizet, pe: sizet, kinds: int*, ints: int64*, strs: sizet*) -> int: + """解析 [ps, pe) 参数列表,统计参数个数与尾部缺省值字面量""" + pc: int = 0 + dc: int = 0 + ss: sizet = ps + i: sizet = ps + depth: int = 0 + q: uint8 = 0 + while i <= pe: + is_sep: int = 0 + if i == pe: + is_sep = 1 + else: + ch: uint8 = vbuf[i] + if q != 0: + if ch == 0x5C: + i += 2 + continue + if ch == q: q = 0 + i += 1 + continue + if ch == 0x22 or ch == 0x27: q = 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: is_sep = 1 + if is_sep == 0: + i += 1 + continue + a: sizet = ss + b: sizet = i + while a < b and vbuf[a] == 0x20: a += 1 + while b > a and vbuf[b - 1] == 0x20: b -= 1 + if b > a: + pc += 1 + # 找顶层 '='(排除 == / >= / <= / !=) + eq: sizet = b + d2: int = 0 + q2: uint8 = 0 + j: sizet = a + while j < b: + cj: uint8 = vbuf[j] + if q2 != 0: + if cj == 0x5C: + j += 2 + continue + if cj == q2: q2 = 0 + j += 1 + continue + if cj == 0x22 or cj == 0x27: q2 = cj + elif cj == 0x28 or cj == 0x5B or cj == 0x7B: d2 += 1 + elif cj == 0x29 or cj == 0x5D or cj == 0x7D: d2 -= 1 + elif cj == 0x3D and d2 == 0: + prev: uint8 = 0 + nxt: uint8 = 0 + if j > a: prev = vbuf[j - 1] + if j + 1 < b: nxt = vbuf[j + 1] + if nxt != 0x3D and prev != 0x3D and prev != 0x3C and prev != 0x3E and prev != 0x21: + eq = j + break + j += 1 + if eq < b: + if dc >= VPI_DEF_SCAN_MAX: return -1 + if _VpiFillDefaultLit(pool, vbuf, eq + 1, b, kinds, ints, strs, dc) == 0: return -1 + dc += 1 + if i >= pe: break + i += 1 + ss = i + if pc > 255 or dc > 255: return -1 + return (pc << 8) | dc + +# _ScanVpiDefaults - 扫描单个 .vpi 文本并登记带缺省值的函数/方法;返回登记个数 +def _ScanVpiDefaults(pool: memhub.MemBuddy*, vbuf: bytes, buf_len: sizet, sha1: str) -> int: + """扫描一个 .vpi 文本并登记其中带缺省值的函数/方法""" + if pool is None or vbuf is None or sha1 is None or buf_len == 0: return 0 + kinds: int* = stdlib.malloc(sizet(VPI_DEF_SCAN_MAX) * 4) + ints: int64* = stdlib.malloc(sizet(VPI_DEF_SCAN_MAX) * 8) + strs: sizet* = stdlib.malloc(sizet(VPI_DEF_SCAN_MAX) * 8) + if kinds is None or ints is None or strs is None: + if kinds is not None: stdlib.free(kinds) + if ints is not None: stdlib.free(ints) + if strs is not None: stdlib.free(strs) + return 0 + cls: bytes = None + reg: int = 0 + pos: sizet = 0 + while pos < buf_len: + ls: sizet = pos + le: sizet = pos + while le < buf_len and vbuf[le] != 0x0A: le += 1 + pos = le + 1 + i: sizet = ls + while i < le and vbuf[i] == 0x20: i += 1 + if i >= le: continue + indent: sizet = i - ls + cls_off: int = _vpi_class_name_offset(vbuf, i, le) + if cls_off >= 0: + # 顶层类定义:记录类名,供缩进 4 的方法归属 + if indent != 0: continue + cstart: sizet = sizet(cls_off) + cend: sizet = cstart + while cend < le and vbuf[cend] != 0x3A and vbuf[cend] != 0x20 and vbuf[cend] != 0x28: cend += 1 + if cls is not None: + stdlib.free(cls) + cls = None + if cend > cstart: + cls = stdlib.malloc(cend - cstart + 1) + if cls is not None: + string.strncpy(cls, vbuf + cstart, cend - cstart) + cls[cend - cstart] = '\0' + continue + if indent != 0 and indent != 4: continue + fn_off: int = _vpi_func_name_offset(vbuf, i, le) + if fn_off < 0: continue + if indent == 0: + # 顶层函数:类作用域已结束 + if cls is not None: + stdlib.free(cls) + cls = None + elif cls is None: continue + fstart: sizet = sizet(fn_off) + fend: sizet = fstart + while fend < le and vbuf[fend] != 0x28 and vbuf[fend] != 0x20 and vbuf[fend] != 0x3A: fend += 1 + if fend <= fstart: continue + p: sizet = fend + while p < le and vbuf[p] != 0x28: p += 1 + if p >= le: continue + pend: sizet = _VpiParamListEnd(vbuf, p, le) + if pend <= p: continue + enc: int = _VpiParseParamDefaults(pool, vbuf, p + 1, pend, kinds, ints, strs) + if enc < 0: continue + dc: int = enc & 0xFF + pc: int = enc >> 8 + if dc <= 0: continue + name_buf: bytes = None + nlen: sizet = fend - fstart + if indent == 0: + name_buf = pool.alloc(nlen + 1) + if name_buf is None: continue + string.strncpy(name_buf, vbuf + fstart, nlen) + name_buf[nlen] = '\0' + else: + clen: sizet = string.strlen(cls) + name_buf = pool.alloc(clen + nlen + 2) + if name_buf is None: continue + string.strncpy(name_buf, cls, clen) + name_buf[clen] = '.' + string.strncpy(name_buf + clen + 1, vbuf + fstart, nlen) + name_buf[clen + 1 + nlen] = '\0' + if pc > 0: pc -= 1 + if GenApi.register_global_defaults_literals(name_buf, kinds, ints, strs, dc, pc, sha1) == 0: reg += 1 + if cls is not None: stdlib.free(cls) + stdlib.free(kinds) + stdlib.free(ints) + stdlib.free(strs) + return reg + +# PreRegisterVpiDefaults - 在翻译开始前,把 store 里所有模块的 .vpi 缺省值一次性登记 +def PreRegisterVpiDefaults(pool: memhub.MemBuddy*, temp_dir: str) -> int: + """预登记所有模块 .vpi 里的函数缺省值;返回登记的函数个数""" + if pool is None or temp_dir is None: return 0 + n: int = StubMergerStore.GetSha1StoreCount() + if n <= 0: return 0 + sha1_arr: bytes = StubMergerStore.GetSha1StoreArrPtr() + if sha1_arr is None: return 0 + total_reg: int = 0 + si: int + for si in range(int(n)): + sha1: str = sha1_arr + sizet(si) * 17 + if sha1[0] == '\0': continue + fpath: str = _vpi_build_path(temp_dir, sha1, ".vpi") + if fpath is None: continue + vf: fileio.File* = fileio.File(fpath, fileio.MODE.R) + stdlib.free(fpath) + if vf.closed: continue + vbuf: bytes = stdlib.malloc(VPI_READ_BUF_SIZE) + if vbuf is None: + vf.close() + continue + nrd: LONG = vf.read_all(vbuf, VPI_READ_BUF_SIZE) + vf.close() + if nrd <= 0: + stdlib.free(vbuf) + continue + if nrd < VPI_READ_BUF_SIZE: vbuf[nrd] = 0 + total_reg += _ScanVpiDefaults(pool, vbuf, string.strlen(vbuf), sha1) + stdlib.free(vbuf) + return total_reg + + +# 单个 .vpi 的枚举成员上限(与 ENUM_MEMBER_MAX 同量级) +define VPI_ENUM_MEMBER_MAX = 128 + + +# _vpi_slice_copy - 把 buf[a:b] 拷成 NUL 结尾的字符串(分配自 pool) +def _vpi_slice_copy(pool: memhub.MemBuddy*, buf: bytes, a: sizet, b: sizet) -> str: + """Copy buf[a:b] into a NUL-terminated buffer allocated from pool.""" + if pool is None or buf is None or b <= a: return None + out: str = pool.alloc(b - a + 1) + if out is None: return None + string.strncpy(out, buf + a, b - a) + out[b - a] = '\0' + return out + + +# _ScanVpiEnumsText - 从 .vpi 文本里扫出所有 `enum X:` 块并登记进 EnumTable;返回登记个数。 +# 为什么必须在翻译前对所有模块跑一遍:依赖 stub 里会写 `-> types.FRESULT`,而"先注入谁" +# 取决于源文件的 import 顺序(如 fat32 在 fat32_types 之前)。若那时枚举还没登记, +# resolve_annotation_type 把它当类名 ensure_forward_class,造出以枚举定义模块 SHA1 命名的 +# opaque 结构体;StructTable 是进程级的,占位会一直活到所有后续模块, +# 表现为 `llvm.call ... -> !llvm.struct<"...FRESULT", ()>` 而函数本体是 `-> i32` +# (mlir-opt: 'llvm.return' op mismatching result types)。 +def _ScanVpiEnumsText(pool: memhub.MemBuddy*, vbuf: bytes, vlen: sizet, sha1: str) -> int: + """Scan all `enum X:` blocks in one .vpi text and register them; returns the count.""" + if pool is None or vbuf is None: return 0 + # 类型对象与枚举表都要活到进程结束:统一挂在 PermPool 上(EnumTable 本身就在 PermPool) + ty_pool: memhub.MemBuddy* = pool + if CompilerFactory.factory.PermPool is not None: ty_pool = CompilerFactory.factory.PermPool + nm_buf: sizet* = pool.alloc(8 * VPI_ENUM_MEMBER_MAX) + ty_buf: sizet* = pool.alloc(8 * VPI_ENUM_MEMBER_MAX) + val_buf: int64* = pool.alloc(8 * VPI_ENUM_MEMBER_MAX) + if nm_buf is None or ty_buf is None or val_buf is None: return 0 + reg: int = 0 + i: sizet = 0 + while i < vlen: + line_start: sizet = i + le: sizet = i + while le < vlen and vbuf[le] != 0x0A: le += 1 + # 顶格的 `enum NAME:` + if le - line_start >= 5 and string.strncmp(vbuf + line_start, "enum ", 5) == 0: + ni: sizet = line_start + 5 + ne: sizet = ni + while ne < le and vbuf[ne] != 0x3A and vbuf[ne] != 0x20 and vbuf[ne] != 0x09: ne += 1 + if ne > ni: + enum_nm: str = _vpi_slice_copy(ty_pool, vbuf, ni, ne) + cnt: int = 0 + next_val: int64 = 0 + j: sizet = le + 1 + while j < vlen: + ml: sizet = j + me: sizet = j + while me < vlen and vbuf[me] != 0x0A: me += 1 + # 空行跳过但不结束块 + if me == ml: + j = me + 1 + continue + # 非缩进行 = 块结束 + if vbuf[ml] != 0x20 and vbuf[ml] != 0x09: break + a: sizet = ml + while a < me and (vbuf[a] == 0x20 or vbuf[a] == 0x09): a += 1 + if a < me and vbuf[a] == 0x23: # '#' 注释行 + j = me + 1 + continue + # 成员名到 ':' + cn: sizet = a + while cn < me and vbuf[cn] != 0x3A: cn += 1 + if cn <= a or cn >= me: + j = me + 1 + continue + mname: str = _vpi_slice_copy(ty_pool, vbuf, a, cn) + # 类型文本到 '=' 或行尾(去掉尾随空白) + ts: sizet = cn + 1 + while ts < me and (vbuf[ts] == 0x20 or vbuf[ts] == 0x09): ts += 1 + te: sizet = ts + while te < me and vbuf[te] != 0x3D: te += 1 + ty_end: sizet = te + while ty_end > ts and (vbuf[ty_end - 1] == 0x20 or vbuf[ty_end - 1] == 0x09): ty_end -= 1 + mty: mlirlite.LLVMType* = mlirlite.Int32(ty_pool) + if ty_end > ts: + ty_txt: str = _vpi_slice_copy(pool, vbuf, ts, ty_end) + # `State` 是自动编号成员的占位类型(源码端 t.State -> i32) + if string.strcmp(ty_txt, "State") != 0: + rty: mlirlite.LLVMType* = _resolve_single_vpi_type_str(ty_pool, ty_txt) + rb: int = _vpi_int_ty_bits(rty) + # 枚举存储必须可按字节寻址:bool 在定义端按 8 位算 + if rb == 1: mty = mlirlite.Int8(ty_pool) + elif rb > 0: mty = rty + # 值:`= [-]<十进制>`;没有就用自动编号(与 ClassGenEnum 一致) + cur: int64 = next_val + if te < me: + vs: sizet = te + 1 + while vs < me and (vbuf[vs] == 0x20 or vbuf[vs] == 0x09): vs += 1 + neg: int = 0 + if vs < me and vbuf[vs] == 0x2D: + neg = 1 + vs += 1 + pv: int64 = 0 + while vs < me and vbuf[vs] >= 0x30 and vbuf[vs] <= 0x39: + pv = pv * 10 + int64(vbuf[vs] - 0x30) + vs += 1 + if neg != 0: pv = -pv + cur = pv + next_val = cur + 1 + if cnt < VPI_ENUM_MEMBER_MAX: + nm_buf[cnt] = sizet(mname) + ty_buf[cnt] = sizet(mty) + val_buf[cnt] = cur + cnt += 1 + j = me + 1 + # 基类型 = 最宽的成员类型(默认 i32,与 ClassGenEnum 同口径) + base_ty: mlirlite.LLVMType* = mlirlite.Int32(ty_pool) + base_bits: int = 32 + for bi in range(int(cnt)): + mty_b: mlirlite.LLVMType* = (mlirlite.LLVMType*)(ty_buf[bi]) + bits_b: int = _vpi_int_ty_bits(mty_b) + if bits_b > base_bits: + base_bits = bits_b + base_ty = mty_b + entry: GenTypes.EnumEntry* = GenApi.register_enum(ty_pool, enum_nm, base_ty, sha1) + if entry is not None: + for bi2 in range(int(cnt)): + m_nm2: str = rawptr(nm_buf[bi2]) + m_ty2: mlirlite.LLVMType* = (mlirlite.LLVMType*)(ty_buf[bi2]) + GenApi.add_enum_member(ty_pool, entry, m_nm2, val_buf[bi2], m_ty2) + reg += 1 + i = j + continue + i = le + 1 + return reg + + +# PreRegisterVpiEnums - 翻译开始前,把所有模块 .vpi 里的 `enum X:` 一次性登记进 EnumTable。 +# 与 PreRegisterVpiDefaults 同理:消除"谁先注入谁登记"的时序依赖。 +def PreRegisterVpiEnums(pool: memhub.MemBuddy*, temp_dir: str) -> int: + """预登记所有模块 .vpi 里的枚举;返回登记的枚举个数""" + if pool is None or temp_dir is None: return 0 + n: int = StubMergerStore.GetSha1StoreCount() + if n <= 0: return 0 + sha1_arr: bytes = StubMergerStore.GetSha1StoreArrPtr() + if sha1_arr is None: return 0 + total_reg: int = 0 + si: int + for si in range(int(n)): + sha1: str = sha1_arr + sizet(si) * 17 + if sha1[0] == '\0': continue + fpath: str = _vpi_build_path(temp_dir, sha1, ".vpi") + if fpath is None: continue + vf: fileio.File* = fileio.File(fpath, fileio.MODE.R) + stdlib.free(fpath) + if vf.closed: continue + vbuf: bytes = stdlib.malloc(VPI_READ_BUF_SIZE) + if vbuf is None: + vf.close() + continue + nrd: LONG = vf.read_all(vbuf, VPI_READ_BUF_SIZE) + vf.close() + if nrd <= 0: + stdlib.free(vbuf) + continue + if nrd < VPI_READ_BUF_SIZE: vbuf[nrd] = 0 + total_reg += _ScanVpiEnumsText(pool, vbuf, string.strlen(vbuf), sha1) + stdlib.free(vbuf) + return total_reg + + +# _VpiParamCountAndVararg - 数参数个数;含 *args / **kwargs 时返回 -1(调用方跳过 arity 校验) +def _VpiParamCountAndVararg(vbuf: bytes, ps: sizet, pe: sizet) -> int: + """数参数个数;含可变参数返回 -1""" + pc: int = 0 + ss: sizet = ps + i: sizet = ps + depth: int = 0 + q: uint8 = 0 + while i <= pe: + is_sep: int = 0 + if i == pe: + is_sep = 1 + else: + ch: uint8 = vbuf[i] + if q != 0: + if ch == 0x5C: + i += 2 + continue + if ch == q: q = 0 + i += 1 + continue + if ch == 0x22 or ch == 0x27: q = 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: is_sep = 1 + if is_sep == 0: + i += 1 + continue + a: sizet = ss + b: sizet = i + while a < b and vbuf[a] == 0x20: a += 1 + while b > a and vbuf[b - 1] == 0x20: b -= 1 + if b > a: + if vbuf[a] == 0x2A: return -1 + pc += 1 + if i >= pe: break + i += 1 + ss = i + return pc + +# LookupSignDefaultsByName - 直接在定义模块 .vpi 文本里查一个函数/方法的缺省参数快照 +# 与 PreRegisterVpiDefaults 的区别:不登记进全局表,只按名字现查现用(表键不可靠时的兜底)。 +# 返回 (参数个数 << 8) | 缺省值个数;-1 = 未找到或有不可重建的缺省值。 +def LookupSignDefaultsByName(pool: memhub.MemBuddy*, sha1: str, name: str, kinds: int*, ints: int64*, strs: sizet*, parse_defaults: int = 1) -> int: + """在 {temp}/{sha1}.vpi 里按名字查缺省参数;-1 = 未找到 + + parse_defaults=0 时只数形参个数(不解析缺省值,kinds/ints/strs 可为 None)。""" + 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: fileio.File* = fileio.File(fpath, fileio.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 + buf_len: sizet = string.strlen(vbuf) + name_len: sizet = string.strlen(name) + cls: bytes = None + pos: sizet = 0 + # 唯一命中跟踪:同一签名在本模块出现多次视为歧义,宁返回 -1 让调用方跳过, + # 也不能按错签名去校验/补参。裸名只匹配顶层函数(方法在 call_name 里带 Class. 前缀)。 + hits: int = 0 + hit_ps: sizet = 0 + hit_pe: sizet = 0 + hit_method: int = 0 + while pos < buf_len: + ls: sizet = pos + le: sizet = pos + while le < buf_len and vbuf[le] != 0x0A: le += 1 + pos = le + 1 + i: sizet = ls + while i < le and vbuf[i] == 0x20: i += 1 + if i >= le: continue + indent: sizet = i - ls + cls_off: int = _vpi_class_name_offset(vbuf, i, le) + if cls_off >= 0: + if indent != 0: continue + cstart: sizet = sizet(cls_off) + cend: sizet = cstart + while cend < le and vbuf[cend] != 0x3A and vbuf[cend] != 0x20 and vbuf[cend] != 0x28: cend += 1 + if cls is not None: + stdlib.free(cls) + cls = None + if cend > cstart: + cls = stdlib.malloc(cend - cstart + 1) + if cls is not None: + string.strncpy(cls, vbuf + cstart, cend - cstart) + cls[cend - cstart] = '\0' + continue + if indent != 0 and indent != 4: continue + fn_off: int = _vpi_func_name_offset(vbuf, i, le) + if fn_off < 0: continue + fstart: sizet = sizet(fn_off) + fend: sizet = fstart + while fend < le and vbuf[fend] != 0x28 and vbuf[fend] != 0x20 and vbuf[fend] != 0x3A: fend += 1 + if fend <= fstart: continue + if indent == 0: + if cls is not None: + stdlib.free(cls) + cls = None + elif cls is None: continue + # 名字比对:顶层函数比裸名,方法比 "Class.method" + name_hit: int = 0 + nlen: sizet = fend - fstart + if indent == 0: + if nlen == name_len and string.strncmp(vbuf + fstart, name, name_len) == 0: name_hit = 1 + else: + clen: sizet = string.strlen(cls) + if clen + 1 + nlen == name_len: + if string.strncmp(vbuf + fstart, name + clen + 1, nlen) == 0: + if string.strncmp(cls, name, clen) == 0 and name[clen] == '.': name_hit = 1 + if name_hit == 0: continue + p: sizet = fend + while p < le and vbuf[p] != 0x28: p += 1 + if p >= le: continue + pend: sizet = _VpiParamListEnd(vbuf, p, le) + if pend <= p: continue + hits += 1 + if hits == 1: + hit_ps = p + 1 + hit_pe = pend + hit_method = 0 + if indent == 4: hit_method = 1 + if cls is not None: stdlib.free(cls) + if hits != 1: + stdlib.free(vbuf) + return -1 + if parse_defaults == 0: + # 只数形参个数(含可变参数则放弃:调用方跳过 arity 校验) + pc_only: int = _VpiParamCountAndVararg(vbuf, hit_ps, hit_pe) + stdlib.free(vbuf) + if pc_only < 0: return -1 + if hit_method != 0 and pc_only > 0: pc_only -= 1 + return pc_only << 8 + enc: int = _VpiParseParamDefaults(pool, vbuf, hit_ps, hit_pe, kinds, ints, strs) + stdlib.free(vbuf) + if enc < 0: return -1 + pc_f: int = enc >> 8 + dc_f: int = enc & 0xFF + if hit_method != 0 and pc_f > 0: pc_f -= 1 + return (pc_f << 8) | dc_f + + +# LookupParamCountByName - 取定义模块 .vpi 里某函数/方法的形参个数(方法不含 self) +# 返回 -1 表示未找到或签名含可变参数(调用方应跳过 arity 校验)。 +def LookupParamCountByName(pool: memhub.MemBuddy*, sha1: str, name: str) -> int: + """按名字取形参个数(方法不含 self);-1=未找到/含可变参数""" + enc: int = LookupSignDefaultsByName(pool, sha1, name, None, None, None, 0) + if enc < 0: return -1 + return enc >> 8 + + # ParseVpiIntoModule - parse a .vpi file and inject function declares into the current module. # Walks top-level nodes, creates declares for FunctionDefs (deduped) and registers CTypedefs. def ParseVpiIntoModule(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, vpi_path: str, trans: HT.Translator*) -> int: @@ -2243,7 +3022,7 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, v n: sizet = len(ch) # Collect the read module's own imports from the stub to build an isolated annotation - # context, replacing the current module's imports (e.g. t.CArray[str] needs 't' imported). + # context, replacing the current module's imports (e.g. t.ArrayT[str] needs 't' imported). stub_imported_modules: str = None stub_from_imports: str = None # Bookkeeping goes through StubMergerStore (same as ImportsGenMixin.translate_import): @@ -2386,7 +3165,7 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, v imported_modules = StubMergerStore.AppendImportedModule(pool, imported_modules, src_mod_buf) stdlib.free(src_mod_buf) - # Recursively pre-register the stub import closure's CTypedef/CDefine: CTypedef tables are + # Recursively pre-register the stub import closure's TypedefT/DefineT: TypedefT tables are # per-file, so an unimported typedef would degrade param types to i8* and misreport CastError. if imported_modules is not None: _vpi_register_typedef_closure(pool, imported_modules, GenApi.get_temp_dir(), 0) @@ -2403,6 +3182,19 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, v if ReplStubLayoutMode != 0: _repl_stub_collect_module(pool, mod, ch, n, trans, src_sha1, stub_imported_modules, stub_from_imports) + # Pass 0: 先登记 stub 自带的 `enum X:`(名字/基类型/成员),再做任何注解解析。 + # 否则依赖 stub 里的 `types.FRESULT` 还查不到枚举,会被当成类名做前向声明, + # 而那个占位结构体是进程级 StructTable 里的常驻项(详见 _inject_enum_declare 注释)。 + i_e0: sizet + for i_e0 in range(int(n)): + child_e0: ast.AST* = ch.get(i_e0) + if child_e0 is None: continue + if child_e0.kind() != ast.ASTKind.ClassDef: continue + cd_e0: ast.ClassDef* = ast.ClassDef*(child_e0) + if cd_e0 is None or cd_e0.name is None: continue + if _vpi_class_is_enum(cd_e0) == 0: continue + _inject_enum_declare(pool, cd_e0, trans, imported_modules, from_imports, src_sha1) + # Pass 1A: register ALL ClassDefs (fields + structs) first, without methods, so a class # defined after a global's AnnAssign is still registered before that AnnAssign resolves. i0: sizet @@ -2465,7 +3257,7 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, v _inject_function_declare(pool, mod, fd, trans, src_sha1, imported_modules, from_imports, i32_ty, i8_ptr_ty, funcs_ptr, max_funcs) continue - # AnnAssign with t.CTypedef -> register CTypedef; + # AnnAssign with t.TypedefT -> register TypedefT; # plain module-level globals (e.g. tokens._op_head) -> external global declare if kd == ast.ASTKind.AnnAssign: aa: ast.AnnAssign* = ast.AnnAssign*(child) @@ -2478,8 +3270,8 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, v dbg_cd: int = GenApi.is_cdefine_annotation(aa.annotation) if GenApi._is_ctypedef_annotation(aa.annotation) != 0: _register_ctypedef_from_annassign(pool, aa) elif GenApi.is_cdefine_annotation(aa.annotation) != 0: - # Register vpi CDefine definitions (e.g. keyboard.vp KBD_BUF_SIZE) into the current - # file's CDefine table so later t.CArray counts in the same vpi can resolve them. + # Register vpi DefineT definitions (e.g. keyboard.vp KBD_BUF_SIZE) into the current + # file's DefineT table so later t.ArrayT counts in the same vpi can resolve them. if aa.target is not None and aa.target.kind() == ast.ASTKind.Name and aa.value is not None: cdef_tgt: ast.Name* = ast.Name*(aa.target) if cdef_tgt.id is not None: @@ -2513,7 +3305,7 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, v cdef_float_val = cn_v.float_val if cdef_neg_f != 0: cdef_float_val = -cdef_float_val if cdef_is_str == 0 and cdef_is_float != 0: - # Float CDefine (e.g. FRAGMENTATION_THRESHOLD: t.CDefine = 0.7): register via + # Float DefineT (e.g. FRAGMENTATION_THRESHOLD: t.DefineT = 0.7): register via # register_cdefine_double (integer extraction would fail fast). # 注解声明 float 时带上 f32 种类(与定义方同一套裁决)。 GenApi.register_cdefine_double(pool, cdef_tgt.id, cdef_float_val, GenApi.cdefine_decl_float_bits(pool, aa.annotation)) @@ -2689,6 +3481,142 @@ def _vpi_class_is_renum(class_cd: ast.ClassDef*) -> int: return 0 +def _vpi_class_is_enum(class_cd: ast.ClassDef*) -> int: + """Check whether a vpi ClassDef came from `enum X:` sugar (base EnumBase); 1=yes / 0=no.""" + if class_cd is None or class_cd.bases is None: return 0 + bases: list[ast.AST*]* = class_cd.bases + bn: sizet = len(bases) + for bi in range(int(bn)): + base_node: ast.AST* = bases.get(bi) + if base_node is None: continue + bk: int = base_node.kind() + # Attribute(t.EnumBase) form + if bk == ast.ASTKind.Attribute: + at: ast.Attribute* = ast.Attribute*(base_node) + if at.attr is not None and string.strcmp(at.attr, "EnumBase") == 0: return 1 + # Name(EnumBase) form: the `enum X:` header is parsed into exactly this base + elif bk == ast.ASTKind.Name: + nm: ast.Name* = ast.Name*(base_node) + if nm.id is not None and string.strcmp(nm.id, "EnumBase") == 0: return 1 + return 0 + + +def _vpi_int_ty_bits(ty: mlirlite.LLVMType*) -> int: + """Return an integer LLVM type's bit width; 0 for non-integer types.""" + if ty is None: return 0 + match ty: + case mlirlite.LLVMType.Int(bits): + return bits + case _: + return 0 + + +def _vpi_enum_member_ty(pool: memhub.MemBuddy*, ann: ast.AST*, imported_modules: str, from_imports: str, trans: HT.Translator*) -> mlirlite.LLVMType*: + """Resolve a vpi enum member's type annotation to a non-empty integer type; None if not an int.""" + if ann is None: return mlirlite.Int32(pool) + # vpi writes auto-numbered members as `NAME: State` (bare Name; t.State may also survive as + # an Attribute), i.e. source `t.State` -> i32. + ak_s: int = ann.kind() + if ak_s == ast.ASTKind.Name: + nm_s: ast.Name* = ast.Name*(ann) + if nm_s.id is not None and string.strcmp(nm_s.id, "State") == 0: return mlirlite.Int32(pool) + elif ak_s == ast.ASTKind.Attribute: + at_s: ast.Attribute* = ast.Attribute*(ann) + if at_s.attr is not None and string.strcmp(at_s.attr, "State") == 0: return mlirlite.Int32(pool) + ty_s: mlirlite.LLVMType* = GenApi.resolve_annotation_type(pool, ann, imported_modules, from_imports, trans, 0) + bits_s: int = _vpi_int_ty_bits(ty_s) + # Enum storage must be byte-addressable: the defining side turns bool members into 8 bits + # (ClassGenEnum's enum-bool rule), so mirror that here instead of i1. + if bits_s == 1: return mlirlite.Int8(pool) + if bits_s <= 0: return None + return ty_s + + +def _vpi_enum_member_value(node: ast.AST*, auto_val: int64) -> int64: + """Return a vpi enum member's integer literal; auto_val when the value is not a literal.""" + if node is None: return auto_val + k: int = node.kind() + if k == ast.ASTKind.Constant: + c: ast.Constant* = ast.Constant*(node) + if c.const_kind == ast.CONST_INT: return c.int_val + if c.const_kind == ast.CONST_BOOL: return c.int_val + return auto_val + if k == ast.ASTKind.UnaryOp: + u: ast.UnaryOp* = ast.UnaryOp*(node) + if u.op == ast.OpKind.USub and u.operand is not None and u.operand.kind() == ast.ASTKind.Constant: + c2: ast.Constant* = ast.Constant*(u.operand) + if c2.const_kind == ast.CONST_INT: return -c2.int_val + return auto_val + return auto_val + + +# _inject_enum_declare - publish a vpi `enum X:` into the EnumTable (name + base type + members). +# 背景:EnumTable 只有定义端 ClassGenEnum 会登记,于是"别的模块认不认识某个枚举"取决于文件顺序。 +# 依赖 stub 在本文件翻译前注入;此时 `types.FRESULT` 若还不在 EnumTable 里, +# resolve_annotation_type 会把它当类名 ensure_forward_class,造出以枚举定义模块 SHA1 命名的 +# opaque 结构体。StructTable 是进程级的,占位会活到枚举自己的模块,把 `res: FRESULT` 变成 +# "结构体槽 -> 右值取地址 -> 指针",最后在 `int = res` 处报 CastError。这里在注入阶段就登记, +# 与文件顺序无关(成员值/自动编号语义与 ClassGenEnum 完全同口径)。 +def _inject_enum_declare(pool: memhub.MemBuddy*, class_cd: ast.ClassDef*, trans: HT.Translator*, imported_modules: str, from_imports: str, src_sha1: str) -> int: + """Register a vpi enum into the EnumTable; 1=registered, 0=failed.""" + if pool is None or class_cd is None or class_cd.name is None: return 0 + enum_name: str = class_cd.name + if enum_name[0] == '\0': return 0 + # EnumTable/成员表常驻 PermPool,类型对象也必须同寿命(瞬时池的类型在 reset 后会悬挂) + ty_pool: memhub.MemBuddy* = pool + if CompilerFactory.factory.PermPool is not None: ty_pool = CompilerFactory.factory.PermPool + kids: list[ast.AST*]* = class_cd.children + if kids is None: return 0 + kn: sizet = len(kids) + i32_ty: mlirlite.LLVMType* = mlirlite.Int32(ty_pool) + # 基类型 = 最宽的成员类型(与 ClassGenEnum 同口径),默认 i32 + base_ty: mlirlite.LLVMType* = i32_ty + base_bits: int = 32 + for ki in range(int(kn)): + stmt_k: ast.AST* = kids.get(ki) + if stmt_k is None or stmt_k.kind() != ast.ASTKind.AnnAssign: continue + aa_k: ast.AnnAssign* = ast.AnnAssign*(stmt_k) + mty_k: mlirlite.LLVMType* = _vpi_enum_member_ty(ty_pool, aa_k.annotation, imported_modules, from_imports, trans) + b_k: int = _vpi_int_ty_bits(mty_k) + if b_k > base_bits: + base_bits = b_k + base_ty = mty_k + entry: GenTypes.EnumEntry* = GenApi.register_enum(ty_pool, enum_name, base_ty, src_sha1) + if entry is None: return 0 + # 成员按声明顺序登记:带值用值,不带值用自动编号(与 ClassGenEnum 一致) + next_val: int64 = 0 + for ki in range(int(kn)): + stmt_m: ast.AST* = kids.get(ki) + if stmt_m is None: continue + km: int = stmt_m.kind() + if km == ast.ASTKind.AnnAssign: + aa_m: ast.AnnAssign* = ast.AnnAssign*(stmt_m) + if aa_m.target is None or aa_m.target.kind() != ast.ASTKind.Name: continue + tg_m: ast.Name* = ast.Name*(aa_m.target) + if tg_m.id is None: continue + cur_m: int64 = next_val + if aa_m.value is not None: cur_m = _vpi_enum_member_value(aa_m.value, next_val) + next_val = cur_m + 1 + mty_m: mlirlite.LLVMType* = _vpi_enum_member_ty(ty_pool, aa_m.annotation, imported_modules, from_imports, trans) + if mty_m is None: mty_m = i32_ty + GenApi.add_enum_member(pool, entry, tg_m.id, cur_m, mty_m) + elif km == ast.ASTKind.Assign: + as_m: ast.Assign* = ast.Assign*(stmt_m) + if as_m.targets is None or as_m.value is None: continue + if len(as_m.targets) == 0: continue + # 同一语句的所有目标共享一个值(与 ClassGenEnum 一致) + cur_a: int64 = _vpi_enum_member_value(as_m.value, next_val) + next_val = cur_a + 1 + tn_a: sizet = len(as_m.targets) + for ti_a in range(int(tn_a)): + t_a: ast.AST* = as_m.targets.get(ti_a) + if t_a is None or t_a.kind() != ast.ASTKind.Name: continue + nm_a: ast.Name* = ast.Name*(t_a) + if nm_a.id is None: continue + GenApi.add_enum_member(pool, entry, nm_a.id, cur_a, i32_ty) + return 1 + + # _inject_class_struct_declare - register a vpi ClassDef as a struct type (with fields) so bare # class-name annotations in the same vpi resolve. Mirrors ClassGenDef.translate_class_def. def _inject_class_struct_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, class_cd: ast.ClassDef*, cls_name: str, trans: HT.Translator*, src_sha1: str, imported_modules: str, from_imports: str, name_prefix: str = None) -> int: @@ -2734,6 +3662,9 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModul # Skip REnum classes (class X(t.REnum)): their layout is computed from variants by # ClassGenEnum._translate_renum_def; registering as a plain struct would be wrong. if _vpi_class_is_renum(class_cd) != 0: return 0 + # Skip enums (`enum X:`): they live in the EnumTable (registered by Pass 0 above), never in the + # StructTable. Registering one as an empty/opaque class here would shadow the enum with a struct. + if _vpi_class_is_enum(class_cd) != 0: return 0 # Rule 5.5 evidence must be set before field annotation resolution: mark_module_class # records "{src_sha1}.{real_name}" and points the class-definition window at this class. GenApi.mark_module_class(pool, src_sha1, real_name) @@ -2748,7 +3679,7 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModul field_types_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX) # Per-field marker buffers, index-aligned with field_names_buf / add_field order. The .vpi text # keeps each field's raw annotation (Converter._PyiTypeStr renders BinOp/Attribute verbatim, so - # `X | t.BigEndian` / `t.CVolatile | int` survive), so the same markers ClassGenDef extracts on + # `X | t.BigEndian` / `t.VolatileT | int` survive), so the same markers ClassGenDef extracts on # the definition side must be re-extracted here; otherwise a struct referenced across a module # boundary loses its volatile/const/byte-order semantics and the same field compiles differently # per module (the bug this fixes). @@ -2790,7 +3721,7 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModul # precedence), so a cross-module reference gets identical field semantics. annot_fc: ast.AST* = m_aa_c.annotation if annot_fc is not None: - if GenApi.has_decorator_marker(annot_fc, "CVolatile", 0) != 0: field_volatile_buf[field_count] = 1 + if GenApi.has_decorator_marker(annot_fc, "VolatileT", 0) != 0: field_volatile_buf[field_count] = 1 if GenApi.has_pointee_volatile_marker(annot_fc) != 0: field_pointee_volatile_buf[field_count] = 1 if GenApi.is_const_object_marker(annot_fc, field_ty_c) != 0: field_const_buf[field_count] = 1 # Byte order: 1 = t.BigEndian / 2 = t.LittleEndian (0 stays native); @@ -2954,10 +3885,10 @@ def _inject_global_variable_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMMo # Mangled name: prefix with the source module SHA1 so the define side and from-import lookup # agree; a bare short name would remain undefined. gv_name_m: str = gname_v - # CExport globals: the definition side uses the bare name (@gp), so the stub declare must too, + # ExportT globals: the definition side uses the bare name (@gp), so the stub declare must too, # else cross-module references emit @sha1.gp and ld.lld reports an undefined symbol. is_gv_cexport: int = 0 - if aa.annotation is not None: is_gv_cexport = GenApi.has_decorator_marker(aa.annotation, "CExport") + if aa.annotation is not None: is_gv_cexport = GenApi.has_decorator_marker(aa.annotation, "ExportT") if src_sha1 is not None and src_sha1[0] != '\0' and is_gv_cexport == 0: ml_v: sizet = string.strlen(src_sha1) + 1 + string.strlen(gname_v) + 1 gv_name_m = pool.alloc(ml_v) @@ -2967,7 +3898,7 @@ def _inject_global_variable_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMMo while cur_v is not None: if cur_v.Name is not None and string.strcmp(cur_v.Name, gv_name_m) == 0: return cur_v = cur_v.Next - # resolve the annotation type (bare class -> Ptr(struct), CArray -> element pointer) + # resolve the annotation type (bare class -> Ptr(struct), ArrayT -> element pointer) resolved_v: mlirlite.LLVMType* = None if aa.annotation is not None: resolved_v = GenApi.resolve_annotation_type(pool, aa.annotation, imported_modules, from_imports, trans) @@ -2982,11 +3913,11 @@ def _inject_global_variable_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMMo if gv_entry_v is not None and gv_entry_v.HasNew != 0: gv_ty_v = mlirlite.Ptr(pool, resolved_v) else: gv_ty_v = resolved_v case mlirlite.LLVMType.Array(aelem_v, acount_v): - # Fixed-size arrays (CArray[T, N]) keep the full [N x T] type, matching + # Fixed-size arrays (ArrayT[T, N]) keep the full [N x T] type, matching # handle_module_level_var; decaying to an element pointer would later read garbage. if acount_v > 0: gv_ty_v = resolved_v else: - # variable-length array (CArray[T, None]): element pointer (C array decay) + # variable-length array (ArrayT[T, None]): element pointer (C array decay) gv_ty_v = mlirlite.Ptr(pool, aelem_v) case _: gv_ty_v = resolved_v @@ -3000,7 +3931,7 @@ def _inject_global_variable_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMMo gv_v.Linkage = "external" -# _inject_function_declare - create a declare for a single FunctionDef: detect CExport/CExtern/State, +# _inject_function_declare - create a declare for a single FunctionDef: detect ExportT/ExternT/State, # compute the mangled name, dedup via find_func_in_module, create the declare and register it. def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, fd: ast.FunctionDef*, trans: HT.Translator*, src_sha1: str, imported_modules: str, from_imports: str, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*, funcs_ptr: GenTypes.FuncEntry*, max_funcs: int) -> None: """Create and inject a declare for a FunctionDef into the module.""" @@ -3011,21 +3942,21 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, if trans is not None and trans._declare_only == 0 and trans.ModuleSha1 is not None: if string.strcmp(src_sha1, trans.ModuleSha1) == 0: return - # detect CExtern/State/CExport markers (from the return-type annotation) + # detect ExternT/State/ExportT markers (from the return-type annotation) has_extern: int = 0 has_state: int = 0 has_export: int = 0 if fd.returns is not None: - has_extern = GenApi.has_decorator_marker(fd.returns, "CExtern") + has_extern = GenApi.has_decorator_marker(fd.returns, "ExternT") has_state = GenApi.has_decorator_marker(fd.returns, "State") - has_export = GenApi.has_decorator_marker(fd.returns, "CExport") - # Bare-struct param rule (see _AggregateParamTy): C ABI externs (t.CExtern/t.State) by value; - # the rest (incl. t.CExport) keep Ptr(struct). Same source as FuncGenDef.is_extern_decl. + has_export = GenApi.has_decorator_marker(fd.returns, "ExportT") + # Bare-struct param rule (see _AggregateParamTy): C ABI externs (t.ExternT/t.State) by value; + # the rest (incl. t.ExportT) keep Ptr(struct). Same source as FuncGenDef.is_extern_decl. agg_is_c_abi: int = 0 if has_extern != 0 or has_state != 0: agg_is_c_abi = 1 # compute the mangled name: - # CExport/CExtern/State/main functions use the bare name, others get the source module SHA1 prefix + # ExportT/ExternT/State/main functions use the bare name, others get the source module SHA1 prefix use_bare_name: int = 0 if has_extern != 0 or has_state != 0 or has_export != 0 or string.strcmp(fd.name, "main") == 0: mangled_name: str = fd.name @@ -3039,7 +3970,7 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, # dedup: skip if the function already exists existing: mlirlite.Function* = GenApi.find_func_in_module(mod, mangled_name) if existing is not None: return - # also check the bare name (in non-bare mode a CExport function may have been registered another way) + # also check the bare name (in non-bare mode a ExportT function may have been registered another way) if use_bare_name == 0: existing_bare: mlirlite.Function* = GenApi.find_func_in_module(mod, fd.name) if existing_bare is not None: return @@ -3053,14 +3984,14 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, ret_ty = GenApi.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans) # if the return type is a pure t.State (no real type), use Void if ret_ty is None and fd.returns is not None: - # t.State/t.CExtern are storage-class decorator markers, not real return types. An annotation + # t.State/t.ExternT are storage-class decorator markers, not real return types. An annotation # like "HANDLE | t.State" has a real type and must not map to Void; only pure markers do. is_pure_marker_rt: int = 0 rk2_rt: int = fd.returns.kind() if rk2_rt == ast.ASTKind.Name or rk2_rt == ast.ASTKind.Constant: is_pure_marker_rt = 1 elif rk2_rt == ast.ASTKind.Attribute: pm_at: ast.Attribute* = ast.Attribute*(fd.returns) - if pm_at.attr is not None and (string.strcmp(pm_at.attr, "State") == 0 or string.strcmp(pm_at.attr, "CExtern") == 0 or string.strcmp(pm_at.attr, "CExport") == 0): + if pm_at.attr is not None and (string.strcmp(pm_at.attr, "State") == 0 or string.strcmp(pm_at.attr, "ExternT") == 0 or string.strcmp(pm_at.attr, "ExportT") == 0): is_pure_marker_rt = 1 if is_pure_marker_rt != 0: ret_ty = mlirlite.Void(pool) elif has_state != 0 or has_extern != 0 or has_export != 0: @@ -3085,7 +4016,7 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, # extract params and add them to the declare param_count: int = 0 - fd_defaults: t.CPtr = None + fd_defaults: rawptr = None fd_default_count: int = 0 if fd.args is not None: args_node: ast.Arguments* = ast.Arguments*(fd.args) @@ -3122,7 +4053,7 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, elif ag.annotation is not None: resolved_ty: mlirlite.LLVMType* = GenApi.resolve_annotation_type(pool, ag.annotation, imported_modules, from_imports, trans) if resolved_ty is not None: - # CArray param -> element pointer ty* (C array decay, matching the compiler side). + # ArrayT param -> element pointer ty* (C array decay, matching the compiler side). # Bare struct param -> _AggregateParamTy (same criterion). match resolved_ty: case mlirlite.LLVMType.Struct(_, _, _): @@ -3224,7 +4155,7 @@ def _inject_method_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, m_ # extract params and add them to the declare param_count: int = 0 - md_defaults: t.CPtr = None + md_defaults: rawptr = None md_default_count: int = 0 if m_fd.args is not None: args_node: ast.Arguments* = ast.Arguments*(m_fd.args) @@ -3267,7 +4198,7 @@ def _inject_method_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, m_ elif ag.annotation is not None: resolved_ty: mlirlite.LLVMType* = GenApi.resolve_annotation_type(pool, ag.annotation, imported_modules, from_imports, trans) if resolved_ty is not None: - # CArray param -> element pointer ty* (C array decay, matching the compiler side). + # ArrayT param -> element pointer ty* (C array decay, matching the compiler side). # Bare struct param -> _AggregateParamTy (same criterion, is_c_abi=0). match resolved_ty: case mlirlite.LLVMType.Struct(_, _, _): @@ -3321,10 +4252,10 @@ def _inject_method_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, m_ GenApi.update_global_func_spec(lookup_name, func) -# _register_ctypedef_from_annassign - register a CTypedef from an AnnAssign node. -# Extracts the NAME and type expression from `NAME: t.CTypedef = Type` and registers it. +# _register_ctypedef_from_annassign - register a TypedefT from an AnnAssign node. +# Extracts the NAME and type expression from `NAME: t.TypedefT = Type` and registers it. def _register_ctypedef_from_annassign(pool: memhub.MemBuddy*, aa: ast.AnnAssign*) -> None: - """Extract and register a CTypedef from an AnnAssign node.""" + """Extract and register a TypedefT from an AnnAssign node.""" if aa is None or pool is None: return # extract the target name (must be a Name node) if aa.target is None or aa.target.kind() != ast.ASTKind.Name: return @@ -3334,7 +4265,7 @@ def _register_ctypedef_from_annassign(pool: memhub.MemBuddy*, aa: ast.AnnAssign* if aa.value is None: return type_str: str = GenApi._get_type_name_from_annotation(pool, aa.value) if type_str is None: return - # register the CTypedef + # register the TypedefT GenApi.register_ctypedef(pool, tgt.id, type_str) @@ -3559,7 +4490,7 @@ def _repl_stub_has_name(arr: sizet*, cnt: int, name: str) -> int: def _repl_stub_deco_flags(deco_list: list[ast.AST*]*) -> int: - """Return class/method decorator flags (bit 0=@t.NoVTable, bit 1=@t.CVTable).""" + """Return class/method decorator flags (bit 0=@t.NoVTable, bit 1=@t.VTableT).""" if deco_list is None: return 0 flags: int = 0 dn: sizet = len(deco_list) @@ -3574,7 +4505,7 @@ def _repl_stub_deco_flags(deco_list: list[ast.AST*]*) -> int: nm = nn.id if nm is None: continue if string.strcmp(nm, "NoVTable") == 0: flags = flags | 1 - elif string.strcmp(nm, "CVTable") == 0: flags = flags | 2 + elif string.strcmp(nm, "VTableT") == 0: flags = flags | 2 return flags diff --git a/App/lib/Stub/StubMerger/StubMergerBuild.vp b/App/lib/Stub/StubMerger/StubMergerBuild.vp index 8ef997d..14e3d5e 100644 --- a/App/lib/Stub/StubMerger/StubMergerBuild.vp +++ b/App/lib/Stub/StubMerger/StubMergerBuild.vp @@ -6,7 +6,7 @@ import stdlib import mlirlite import win32.win32file as win32file import win32.win32base as win32base -import win32.fileio as fileio +import io as fileio import viperlib import sys import ast @@ -187,7 +187,7 @@ def _BuildReachableSha1Set(mb: memhub.MemBuddy*, source_dir: str, temp_dir: str, wl_len -= 1 # 2. Pre-add base modules (builtins): builtins is always compiled in; _list/_dict are injected only for - # explicit list/dict annotations/ctors (list literals to t.CArray are array init and do not trigger _list). + # explicit list/dict annotations/ctors (list literals to t.ArrayT are array init and do not trigger _list). wl_len = _AppendDepsToWorklist(worklist, wl_len, "builtins", None) if wl_len < 0: stdlib.free(worklist) @@ -524,7 +524,7 @@ def LoadDepVpiIntoModule(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, impo if pool is None or mod is None or temp_dir is None or trans is None: return 0 if imported_modules is None: return 0 - # Reset the typedef recursion guard before injecting each module's stub: the CTypedef table is + # Reset the typedef recursion guard before injecting each module's stub: the TypedefT table is # per-file, so stale visited state registers stdint typedefs only once and degrades param types (FuncTest root cause). VpiReader.ResetVpiTypedefVisited() # Reset the on-demand injection reentry table too: the target mod differs per module, so carrying it diff --git a/App/lib/Stub/StubMerger/StubMergerCollect.vp b/App/lib/Stub/StubMerger/StubMergerCollect.vp index 9239d72..543faa6 100644 --- a/App/lib/Stub/StubMerger/StubMergerCollect.vp +++ b/App/lib/Stub/StubMerger/StubMergerCollect.vp @@ -5,7 +5,7 @@ import stdlib import mlirlite import win32.win32file as win32file import win32.win32base as win32base -import win32.fileio as fileio +import io as fileio import viperlib import sys import ast @@ -84,20 +84,39 @@ def _is_in_sha1_set(sha1: str, sha1_set: str, set_count: int) -> int: for i in range(int(set_count)): if string.strcmp(sha1_set + i * 17, sha1) == 0: return 1 return 0 +# Sha1MapPath - _sha1_map.txt 的落盘路径:放在**构建目录**(temp 的上一级), +# 便于直接按 SHA1 反查源文件;temp 内的 sliced 产物(.ll/.vpi/.meta)位置不变。 +# 返回 stdlib 缓冲区(调用方 free);temp_dir 无上一级(相对路径)时退回 temp 目录。 +def Sha1MapPath(pool: memhub.MemBuddy*, temp_dir: str) -> str: + """返回 {temp_dir 的上一级}/_sha1_map.txt""" + if temp_dir is None: return None + td: sizet = string.strlen(temp_dir) + end: sizet = td + while end > 0 and (temp_dir[end - 1] == '/' or temp_dir[end - 1] == '\\'): end -= 1 + cut: sizet = end + while cut > 0 and temp_dir[cut - 1] != '/' and temp_dir[cut - 1] != '\\': cut -= 1 + parent_len: sizet = 0 + if cut > 0: parent_len = cut - 1 + else: parent_len = end # 没有上一级(相对路径):退回 temp 目录本身 + buf: bytes = stdlib.malloc(parent_len + 32) + if buf is None: return None + if parent_len > 0: string.strncpy(buf, temp_dir, parent_len) + buf[parent_len] = '\0' + viperlib.snprintf(buf + parent_len, 32, "/_sha1_map.txt") + return buf # WriteIncludesSha1Map - write includes scan results to _sha1_map.txt (human-readable debug output only). # Internally the in-memory store filled by PopulateSha1MapStore is used; returns 0 on success, non-zero on failure. def WriteIncludesSha1Map(mb: memhub.MemBuddy*, temp_dir: str, scan_result: IncludesScanner.ScanResult*, filter_set: str, filter_count: int) -> int: """Write the includes scan result to _sha1_map.txt (human-readable debug output).""" if temp_dir is None or scan_result is None: return 1 - # Build the path temp_dir/_sha1_map.txt - dir_len: sizet = string.strlen(temp_dir) - map_path: bytes = stdlib.malloc(dir_len + 32) + # Build the path {temp_dir 的上一级}/_sha1_map.txt + map_path: bytes = Sha1MapPath(mb, temp_dir) if map_path is None: return 1 - viperlib.snprintf(map_path, dir_len + 32, "%s/_sha1_map.txt", temp_dir) # Open the file for writing (CREATE_ALWAYS) f: fileio.File* = fileio.File(map_path, fileio.MODE.W) + stdlib.free(map_path) if f.closed: return 1 # Write each include entry: {sha1}:includes/{rel_path}\n diff --git a/App/lib/Stub/StubMerger/StubMergerIR.vp b/App/lib/Stub/StubMerger/StubMergerIR.vp index 03e68bc..1eed783 100644 --- a/App/lib/Stub/StubMerger/StubMergerIR.vp +++ b/App/lib/Stub/StubMerger/StubMergerIR.vp @@ -6,7 +6,7 @@ import stdlib import mlirlite import win32.win32file as win32file import win32.win32base as win32base -import win32.fileio as fileio +import io as fileio import viperlib import sys import ast @@ -38,7 +38,9 @@ def WriteModuleMeta(mod: mlirlite.LLVMModule*, temp_dir: str, td_len: sizet, sha if mpath is None: VLogger.error("meta write: path construction failed", "Meta") return 1 - mf: fileio.File = fileio.File(mpath, fileio.MODE.W) + # 与其他模块一致用指针形式:File 是类(构造函数返回实例指针),值形式在部分编译器 + # 上会读不到真实句柄状态(closed 恒为默认值)。 + mf: fileio.File* = fileio.File(mpath, fileio.MODE.W) if mf.closed: VLogger.error(f"meta write: cannot open {mpath}", "Meta") return 1 @@ -122,7 +124,9 @@ def _MetaHasDef(temp_dir: str, td_len: sizet, sha1: str) -> int: if temp_dir is None or sha1 is None: return -1 mpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "meta") if mpath is None: return -1 - mf: fileio.File = fileio.File(mpath, fileio.MODE.R) + # 指针形式(见 WriteModuleMeta 注释):值形式读不到真实的 closed/handle 状态, + # 文件不存在时 mf.closed 仍为默认 false,会把"读不到 meta"误判成"可读且无 DEF"。 + mf: fileio.File* = fileio.File(mpath, fileio.MODE.R) stdlib.free(mpath) if mf.closed: return -1 mbuf: bytes = stdlib.malloc(StubMerger.STUB_READ_BUF_SIZE) @@ -131,9 +135,10 @@ def _MetaHasDef(temp_dir: str, td_len: sizet, sha1: str) -> int: mf.close() if mbuf is None: return -1 if mbr <= 0: - # Empty meta: no DEF, decl-only + # 读不到/空 meta 一律视为"未知",绝不能当作 decl-only:那会让 include 被整段跳过、 + # 不产生 .obj,最后只在链接阶段报一堆 "no such file or directory"。 stdlib.free(mbuf) - return 0 + return -1 if mbr < StubMerger.STUB_READ_BUF_SIZE: mbuf[mbr] = '\0' # Scan line by line: a trailing " DEF" marks an impl file mpos: sizet = 0 diff --git a/App/lib/Stub/StubMerger/StubMergerStore.vp b/App/lib/Stub/StubMerger/StubMergerStore.vp index ccbb94f..5b6bd0c 100644 --- a/App/lib/Stub/StubMerger/StubMergerStore.vp +++ b/App/lib/Stub/StubMerger/StubMergerStore.vp @@ -5,7 +5,7 @@ import stdlib import mlirlite import win32.win32file as win32file import win32.win32base as win32base -import win32.fileio as fileio +import io as fileio import viperlib import sys import ast @@ -134,14 +134,14 @@ def PopulateSha1MapStore(scan_result: IncludesScanner.ScanResult*) -> int: if StubMerger._sha1_store_rel is not None: stdlib.free(StubMerger._sha1_store_rel) if StubMerger._sha1_store_incdir is not None: stdlib.free(StubMerger._sha1_store_incdir) StubMerger._sha1_store_arr = stdlib.malloc(StubMerger.MAX_INCLUDES * 17) - StubMerger._sha1_store_mod = stdlib.malloc(StubMerger.MAX_INCLUDES * 64) - # 取消 C 整数提升后:MAX_INCLUDES/MAX_REL_PATH_LEN/MAX_INC_DIR_LEN 这些模块属性 CDefine 按最小位宽物化为 i16, + StubMerger._sha1_store_mod = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * 64) + # 取消 C 整数提升后:MAX_INCLUDES/MAX_REL_PATH_LEN/MAX_INC_DIR_LEN 这些模块属性 DefineT 按最小位宽物化为 i16, # 两侧都 i16 的乘积(1024*256 / 1024*512)会静默回绕成 0;显式 sizet() 把乘数加宽到 64 位,结果与旧「提升到 int」一致。 StubMerger._sha1_store_rel = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_REL_PATH_LEN) StubMerger._sha1_store_incdir = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_INC_DIR_LEN) if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None or StubMerger._sha1_store_incdir is None: return -1 string.memset(StubMerger._sha1_store_arr, 0, StubMerger.MAX_INCLUDES * 17) - string.memset(StubMerger._sha1_store_mod, 0, StubMerger.MAX_INCLUDES * 64) + string.memset(StubMerger._sha1_store_mod, 0, sizet(StubMerger.MAX_INCLUDES) * 64) string.memset(StubMerger._sha1_store_rel, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_REL_PATH_LEN) string.memset(StubMerger._sha1_store_incdir, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_INC_DIR_LEN) StubMerger._sha1_store_count = 0 @@ -160,7 +160,14 @@ def PopulateSha1MapStore(scan_result: IncludesScanner.ScanResult*) -> int: if full_path_buf is None: p_bad += 1 continue - viperlib.snprintf(full_path_buf, rp_len + 10, "includes/%s", ent.RelPath) + # The scanner may already hand back a workspace-relative path ("includes/ast/lexer.vp") + # when the scan root is the workspace; prepending again yields "includes/includes/..." + # and PathToModuleName (which strips a single prefix) produces "includes.ast.lexer", + # so exact module lookups ("ast.lexer") miss and re-export resolution breaks. + if rp_len >= 9 and string.strncmp(ent.RelPath, "includes/", 9) == 0: + string.strcpy(full_path_buf, ent.RelPath) + else: + viperlib.snprintf(full_path_buf, rp_len + 10, "includes/%s", ent.RelPath) mod_name: str = Utils.PathToModuleName(full_path_buf, 1) stdlib.free(full_path_buf) if mod_name is not None: @@ -198,13 +205,13 @@ def AppendToSha1MapStore(sha1: str, rel_path: str) -> int: # init store if empty (Phase2 may run before Phase1) if StubMerger._sha1_store_arr is None: StubMerger._sha1_store_arr = stdlib.malloc(StubMerger.MAX_INCLUDES * 17) - StubMerger._sha1_store_mod = stdlib.malloc(StubMerger.MAX_INCLUDES * 64) + StubMerger._sha1_store_mod = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * 64) # sizet(): 取消整数提升后 i16(MAX_INCLUDES)*i16(长度) 会回绕,显式加宽到 64 位 StubMerger._sha1_store_rel = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_REL_PATH_LEN) StubMerger._sha1_store_incdir = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_INC_DIR_LEN) if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None or StubMerger._sha1_store_incdir is None: return 1 string.memset(StubMerger._sha1_store_arr, 0, StubMerger.MAX_INCLUDES * 17) - string.memset(StubMerger._sha1_store_mod, 0, StubMerger.MAX_INCLUDES * 64) + string.memset(StubMerger._sha1_store_mod, 0, sizet(StubMerger.MAX_INCLUDES) * 64) string.memset(StubMerger._sha1_store_rel, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_REL_PATH_LEN) string.memset(StubMerger._sha1_store_incdir, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_INC_DIR_LEN) StubMerger._sha1_store_count = 0 @@ -256,13 +263,13 @@ def AddIncludeToSha1MapStore(sha1: str, rel_path: str, inc_dir: str, mod_name: s # init store if empty if StubMerger._sha1_store_arr is None: StubMerger._sha1_store_arr = stdlib.malloc(StubMerger.MAX_INCLUDES * 17) - StubMerger._sha1_store_mod = stdlib.malloc(StubMerger.MAX_INCLUDES * 64) + StubMerger._sha1_store_mod = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * 64) # sizet(): 取消整数提升后 i16(MAX_INCLUDES)*i16(长度) 会回绕,显式加宽到 64 位 StubMerger._sha1_store_rel = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_REL_PATH_LEN) StubMerger._sha1_store_incdir = stdlib.malloc(sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_INC_DIR_LEN) if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None or StubMerger._sha1_store_incdir is None: return 1 string.memset(StubMerger._sha1_store_arr, 0, StubMerger.MAX_INCLUDES * 17) - string.memset(StubMerger._sha1_store_mod, 0, StubMerger.MAX_INCLUDES * 64) + string.memset(StubMerger._sha1_store_mod, 0, sizet(StubMerger.MAX_INCLUDES) * 64) string.memset(StubMerger._sha1_store_rel, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_REL_PATH_LEN) string.memset(StubMerger._sha1_store_incdir, 0, sizet(StubMerger.MAX_INCLUDES) * StubMerger.MAX_INC_DIR_LEN) StubMerger._sha1_store_count = 0 @@ -536,15 +543,15 @@ def GetSha1StoreIncdirArrPtr() -> bytes: return StubMerger._sha1_store_incdir # ============================================================ # GetSha1StoreArr - return the store's sha1/mod/rel array pointers and entry count; do not free. -def GetSha1StoreArr(out_sha1_arr: rawptr[t.CPtr], out_mod_arr: rawptr[t.CPtr], out_rel_arr: rawptr[t.CPtr]) -> int: +def GetSha1StoreArr(out_sha1_arr: void**, out_mod_arr: void**, out_rel_arr: void**) -> int: """Get the global store array pointers and entry count; returns -1 on failure""" if out_sha1_arr is None or out_mod_arr is None or out_rel_arr is None: return -1 if StubMerger._sha1_store_arr is None or StubMerger._sha1_store_mod is None or StubMerger._sha1_store_rel is None: return -1 - sp: rawptr[t.CPtr] = out_sha1_arr + sp: void** = out_sha1_arr if sp is not None: sp[0] = StubMerger._sha1_store_arr - mp: rawptr[t.CPtr] = out_mod_arr + mp: void** = out_mod_arr if mp is not None: mp[0] = StubMerger._sha1_store_mod - rp: rawptr[t.CPtr] = out_rel_arr + rp: void** = out_rel_arr if rp is not None: rp[0] = StubMerger._sha1_store_rel return StubMerger._sha1_store_count diff --git a/App/lib/core/Diag.vp b/App/lib/core/Diag.vp index da91469..ba0ad55 100644 --- a/App/lib/core/Diag.vp +++ b/App/lib/core/Diag.vp @@ -2,7 +2,7 @@ from stdint import * import string import stdio import stdlib -import win32.fileio as fileio +import io as fileio import lib.core.VLogger as VLogger diff --git a/App/lib/core/Gen/AssignGenMixin.vp b/App/lib/core/Gen/AssignGenMixin.vp index 6dc9a21..8738373 100644 --- a/App/lib/core/Gen/AssignGenMixin.vp +++ b/App/lib/core/Gen/AssignGenMixin.vp @@ -6,7 +6,7 @@ import string import stdio import stdlib import viperlib -import win32.fileio as fileio +import io as fileio import lib.core.VLogger as VLogger import lib.core.Gen.Translator as HT import lib.Stub.StubMerger.StubMergerStore as StubMergerStore @@ -23,20 +23,20 @@ novirtual class AssignGenMixin: RecoverFound: int - # Check whether an annotation is the CDefine marker (module-level function) + # Check whether an annotation is the DefineT marker (module-level function) def is_cdefine_annotation(self, annot: ast.AST*) -> int: - """Return 1 if the annotation is the CDefine marker (bare or CDefine[T]), else 0.""" + """Return 1 if the annotation is the DefineT marker (bare or DefineT[T]), else 0.""" if annot is None: return 0 k: int = annot.kind() if k == ast.ASTKind.Name: nm: ast.Name* = ast.Name*(annot) - if nm.id is not None and string.strcmp(nm.id, "CDefine") == 0: return 1 + if nm.id is not None and string.strcmp(nm.id, "DefineT") == 0: return 1 return 0 if k == ast.ASTKind.Subscript: sub_cd: ast.Subscript* = ast.Subscript*(annot) if sub_cd.value is not None and sub_cd.value.kind() == ast.ASTKind.Name: mk_cd: ast.Name* = ast.Name*(sub_cd.value) - if mk_cd.id is not None and string.strcmp(mk_cd.id, "CDefine") == 0: return 1 + if mk_cd.id is not None and string.strcmp(mk_cd.id, "DefineT") == 0: return 1 return 0 # extract_cdefine_int_value - extract an integer constant from AnnAssign.value @@ -67,11 +67,11 @@ novirtual class AssignGenMixin: # Case 1: Constant(INT) - e.g. 0x0002 if k == ast.ASTKind.Constant: cn: ast.Constant* = ast.Constant*(val_node) - # True/False boolean constant -> 1/0 (bool treated as int in CDefine context) + # True/False boolean constant -> 1/0 (bool treated as int in DefineT context) if cn.const_kind == ast.CONST_BOOL: return 1 if cn.int_val != 0 else 0 if cn.const_kind != ast.CONST_INT: # Non-INT constant: fail fast - self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") + self.Gen.TypeGenErr.fatal_error(val_node, "DefineT value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") return cn.int_val # Case 2: BinOp - e.g. FOREGROUND_RED | FOREGROUND_GREEN, (1 << N) @@ -108,13 +108,13 @@ novirtual class AssignGenMixin: else: res = left_val // right_val else: # Unknown BinOp operator: fail fast(不能静默返回 0) - self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") + self.Gen.TypeGenErr.fatal_error(val_node, "DefineT value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") if bits_out is not None: # 静态计算**自行拓宽**:字面量/define 的声明宽度仍保持最小(不提升),但 `+ - * <<` # 的精确结果可能比操作数宽 —— 若照操作数宽度物化就会静默回绕 # (现场:define A=8192(最小 16 位)的 `A*24+512` 被按 16 位物化成 512)。 # 规则:以操作数的“较宽”为基准,再用**结果值**算精确可容纳位宽,只有装不下才加宽 - # (不无故提升,避免把 `CDefine + 1` 这类算式整体抬成 i64 引出新的“隐式窄化”报错); + # (不无故提升,避免把 `DefineT + 1` 这类算式整体抬成 i64 引出新的“隐式窄化”报错); # 基准未知(wbase==0,如纯字面量算式)时保持“未知”,仍由物化端按值推断。 w: int = wbase if w > 0 and res != 0: @@ -136,11 +136,11 @@ novirtual class AssignGenMixin: else: bits_out[0] = 64 return res - # Case 3: Name - a registered CDefine constant or True/False literal + # Case 3: Name - a registered DefineT constant or True/False literal if k == ast.ASTKind.Name: nm: ast.Name* = ast.Name*(val_node) if nm.id is not None: - # True/False literal -> 1/0 (bool treated as int in CDefine context) + # True/False literal -> 1/0 (bool treated as int in DefineT context) if string.strcmp(nm.id, "True") == 0: return 1 if string.strcmp(nm.id, "False") == 0: return 0 looked_up: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(nm.id) @@ -149,7 +149,7 @@ novirtual class AssignGenMixin: if bits_out is not None: bits_out[0] = self.Gen.TypeGenCDef.lookup_cdefine_bits(nm.id) return looked_up # Unregistered reference: fail fast - self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") + self.Gen.TypeGenErr.fatal_error(val_node, "DefineT value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") # Case 4: Call - 整型/typedef 强转(如 WORD(0x0080) / t.CUnsignedLong(-11)): # 取实参值,并按**目标类型的位宽**规范化(这赋予常量"已知宽度")。 @@ -169,7 +169,7 @@ novirtual class AssignGenMixin: if bits_out is not None: bits_out[0] = ib return arg_val # Call with no args: fail fast - self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") + self.Gen.TypeGenErr.fatal_error(val_node, "DefineT value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") # Case 5: UnaryOp - e.g. -11 if k == ast.ASTKind.UnaryOp: @@ -181,10 +181,10 @@ novirtual class AssignGenMixin: if uop.op == ast.OpKind.UAdd: return operand_val if uop.op == ast.OpKind.Invert: return ~operand_val # Unknown UnaryOp operator: fail fast - self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") + self.Gen.TypeGenErr.fatal_error(val_node, "DefineT value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") # Unknown node kind: fail fast - self.Gen.TypeGenErr.fatal_error(val_node, "CDefine value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") + self.Gen.TypeGenErr.fatal_error(val_node, "DefineT value extraction failed: cannot parse a compile-time constant from the expression", "TypeError") # After fatal_error add an explicit return so the block is properly terminated return 0 @@ -229,7 +229,7 @@ novirtual class AssignGenMixin: if rhs_bits != 0: return rhs_bits return self.define_min_width(val) - # define / t.CDefine integer constant storable and width checks + # define / t.DefineT integer constant storable and width checks # Auto-derived => 64-bit pattern; explicit type T => T's width range. def define_min_width(self, value: int64) -> int: """Return the minimal storable width (8/16/32/64) for an integer value.""" @@ -263,7 +263,7 @@ novirtual class AssignGenMixin: """1=通用求值器(extract_cdefine_int_value_bits)一定能求出整数;0=不确定,**不得调用求值器**。 判定范围与求值器严格对齐(否则求值器会 fail-fast,变成误报): - 整型/布尔字面量、True/False、**已注册的** CDefine 名、其 +/-/~、求值器支持的整型二元运算、 + 整型/布尔字面量、True/False、**已注册的** DefineT 名、其 +/-/~、求值器支持的整型二元运算、 以及带实参的强转调用。变量、普通函数调用、求值器不支持的算子一律判 0 —— 即"求不出来就不检查"。 """ if node is None: return 0 @@ -326,27 +326,27 @@ novirtual class AssignGenMixin: self.Gen.TypeGenErr.fatal_error(node, f"[VPC] shift count out of range: the count {v} is >= the left operand width {lhs_bits} bits; " f"the result would be 0 or undefined (not a value the compiler may guess). " f"Fix: use a count < {lhs_bits}, or widen the left operand.", "ValueError") def _is_cdefine_marker_node(self, node: ast.AST*) -> int: - """Return 1 if the node is the CDefine marker node (bare or CDefine[T]).""" + """Return 1 if the node is the DefineT marker node (bare or DefineT[T]).""" if node is None: return 0 k: int = node.kind() if k == ast.ASTKind.Name: nm: ast.Name* = ast.Name*(node) - if nm.id is not None and string.strcmp(nm.id, "CDefine") == 0: return 1 + if nm.id is not None and string.strcmp(nm.id, "DefineT") == 0: return 1 if k == ast.ASTKind.Subscript: sub_cm: ast.Subscript* = ast.Subscript*(node) if sub_cm.value is not None and sub_cm.value.kind() == ast.ASTKind.Name: mk_cm: ast.Name* = ast.Name*(sub_cm.value) - if mk_cm.id is not None and string.strcmp(mk_cm.id, "CDefine") == 0: return 1 + if mk_cm.id is not None and string.strcmp(mk_cm.id, "DefineT") == 0: return 1 return 0 def _define_explicit_annot(self, annot: ast.AST*) -> ast.AST*: - """Return the explicit type T of a `CDefine[T]` annotation, or None.""" + """Return the explicit type T of a `DefineT[T]` annotation, or None.""" if annot is None: return None if annot.kind() != ast.ASTKind.Subscript: return None sub_d: ast.Subscript* = ast.Subscript*(annot) if sub_d.value is None or sub_d.value.kind() != ast.ASTKind.Name: return None mk_d: ast.Name* = ast.Name*(sub_d.value) - if mk_d.id is None or string.strcmp(mk_d.id, "CDefine") != 0: return None + if mk_d.id is None or string.strcmp(mk_d.id, "DefineT") != 0: return None return sub_d.slice def _annot_int_bits(self, pool: memhub.MemBuddy*, annot: ast.AST*) -> int: @@ -361,7 +361,7 @@ novirtual class AssignGenMixin: n2: ast.Name* = ast.Name*(annot) nm = n2.id elif k == ast.ASTKind.Subscript: - # Descriptor wrapper (CConst[T] etc.): the width comes from the wrapped type. + # Descriptor wrapper (ConstT[T] etc.): the width comes from the wrapped type. sub_ib: ast.Subscript* = ast.Subscript*(annot) return self._annot_int_bits(pool, sub_ib.slice) if nm is None: return 0 @@ -371,7 +371,7 @@ novirtual class AssignGenMixin: """类型名 → 整数位宽(0=未知/非整数)。depth 防 typedef 环。 覆盖:内建名(int/bool/int8../uint64/sizet)、`t.CXxx`(map_builtin_type)、 - 以及**用户 typedef**(如 stdint 的 `WORD: t.CTypedef = uint16` —— 经 lookup_ctypedef + 以及**用户 typedef**(如 stdint 的 `WORD: t.TypedefT = uint16` —— 经 lookup_ctypedef 取其底层类型文本再递归定宽)。此前用户 typedef 一律返回 0,导致 `define X: WORD = ...` 的声明宽度不参与校验/物化。 """ @@ -396,7 +396,7 @@ novirtual class AssignGenMixin: return 0 case _: return 0 - # 用户/include typedef(如 stdint.vp 的 `WORD: t.CTypedef = uint16`): + # 用户/include typedef(如 stdint.vp 的 `WORD: t.TypedefT = uint16`): # 表内值可能是模块限定 ref(stdint.uint16),先用 _expand_typedef_ref 展开; # 本地表未命中再走 builtins 源扫描兜底。 underlying: str = self.Gen.TypeGenCType.lookup_ctypedef(nm) @@ -410,7 +410,7 @@ novirtual class AssignGenMixin: return self._int_bits_of_type_name(pool, underlying, depth + 1) def _define_annot_bits(self, pool: memhub.MemBuddy*, annot: ast.AST*) -> int: - """Return the bit width of a CDefine declaration's explicit type T.""" + """Return the bit width of a DefineT declaration's explicit type T.""" return self._annot_int_bits(pool, self._define_explicit_annot(annot)) # cdefine_decl_float_bits - define 声明的**浮点**宽度裁决(定义方与跨模块使用方共用同一套) @@ -459,7 +459,7 @@ novirtual class AssignGenMixin: return 0 case _: return 0 - # 用户/include typedef(如 `MYF: t.CTypedef = float`):展开后再递归判定 + # 用户/include typedef(如 `MYF: t.TypedefT = float`):展开后再递归判定 underlying: str = self.Gen.TypeGenCType.lookup_ctypedef(nm) if underlying is None: underlying = self.Gen.TypeGenResolve._lookup_builtins_typedef(pool, nm) @@ -471,7 +471,7 @@ novirtual class AssignGenMixin: return self._float_kind_of_type_name(pool, underlying, depth + 1) def _cdefine_int_literal_text(self, name: str) -> str: - """Return the raw value text of a CDefine line, or None if not found.""" + """Return the raw value text of a DefineT line, or None if not found.""" if name is None: return None fp: str = self.Gen.TypeGenResolve.get_current_file() if fp is None: return None @@ -588,7 +588,7 @@ novirtual class AssignGenMixin: return 0 def require_cdefine_storable(self, pool: memhub.MemBuddy*, node: ast.AST*, name: str, value: int64, annot: ast.AST*, rhs_bits: int = 0) -> None: - """Fail fast if a CDefine value is not storable in its declared type. + """Fail fast if a DefineT value is not storable in its declared type. rhs_bits: 右侧表达式自身的位宽(0=未定宽)。右侧带显式强转(如 WORD(...))且不宽于声明注解时, 截断是**显式**的(左侧只看到截断后的数据),此时不再按"存不下"报错——这正是 @@ -640,10 +640,10 @@ novirtual class AssignGenMixin: if bits >= dw: return self.Gen.TypeGenErr.fatal_error(value_node, f"[VPC] define constant use-site width insufficient: constant '{rn.id}' is {dw} bits wide " f"(declared width; actual value {cdef_val}), but the use site (variable declaration annotation) provides only {bits} bits. " f"Silent truncation/downgrade is forbidden; compilation aborted. " f"Fix: widen the variable declaration annotation to at least {dw} bits (such as int{dw}T / CUInt{dw}T), " f"or narrow the constant at its define with an explicit cast.", "TypeError") - # eval_cdefine_float_expr - 编译期**浮点**常量表达式求值(CDefine 值为浮点表达式时用)。 + # eval_cdefine_float_expr - 编译期**浮点**常量表达式求值(DefineT 值为浮点表达式时用)。 # 支持 Constant(float/int)、UnaryOp(+/-)、Call 强转包裹(float(x)/double(x)/t.CDouble(x))、 # BinOp(+ - * /)。成功写 out[0] 并返回 1;不是纯浮点常量表达式时返回 0(交回整型路径)。 - # 与 extract_cdefine_int_value_bits 的分工:后者只服务整型 CDefine,遇到 float 字面量会 fail-fast。 + # 与 extract_cdefine_int_value_bits 的分工:后者只服务整型 DefineT,遇到 float 字面量会 fail-fast。 def eval_cdefine_float_expr(self, node: ast.AST*, out: double*, pool: memhub.MemBuddy*) -> int: if node is None: return 0 k: int = node.kind() @@ -690,14 +690,14 @@ novirtual class AssignGenMixin: return 1 if bop.op == ast.OpKind.Div: if rv == 0.0: - self.Gen.TypeGenErr.fatal_error(node, "constant expression division by zero: the right operand of '/' evaluates to 0 at compile time (float CDefine).", "ValueError") + self.Gen.TypeGenErr.fatal_error(node, "constant expression division by zero: the right operand of '/' evaluates to 0 at compile time (float DefineT).", "ValueError") out[0] = lv / rv return 1 return 0 return 0 # expr_has_float_literal - 表达式里是否**含浮点字面量**(递归)。 - # 用于把"浮点 CDefine"与"整型 CDefine"分开:只有含浮点字面量时才允许走浮点求值, + # 用于把"浮点 DefineT"与"整型 DefineT"分开:只有含浮点字面量时才允许走浮点求值, # 否则纯整型表达式会被登记成 double,破坏整型位宽与整型语义。 def expr_has_float_literal(self, node: ast.AST*) -> int: if node is None: return 0 @@ -808,12 +808,12 @@ novirtual class AssignGenMixin: return prev def _register_cdefine_from_value(self, pool: memhub.MemBuddy*, name: str, val_node: ast.AST*, annot: ast.AST* = None) -> None: - """Register a CDefine constant (double for floats, else integer).""" + """Register a DefineT constant (double for floats, else integer).""" if pool is None or name is None: return # 浮点宽度种类(f32 与否)必须**在拆掉右侧包裹前**判定: - # ① 注解有显式类型(`define PI: float = ...` / `PI: float | t.CDefine = ...`)→ 注解说了算 + # ① 注解有显式类型(`define PI: float = ...` / `PI: float | t.DefineT = ...`)→ 注解说了算 # (`define X: double = float(1.5)` 按 double 走); - # ② 注解没有显式类型(`PI: t.CDefine = float(3.14)`)→ 看右侧强转包裹 float(...) / double(...)。 + # ② 注解没有显式类型(`PI: t.DefineT = float(3.14)`)→ 看右侧强转包裹 float(...) / double(...)。 f32_kind: int = 0 if self._define_explicit_annot(annot) is not None: f32_kind = self.cdefine_decl_float_bits(pool, annot) @@ -850,7 +850,7 @@ novirtual class AssignGenMixin: if val_node is not None and val_node.kind() == ast.ASTKind.Constant: cn_fp: ast.Constant* = ast.Constant*(val_node) if cn_fp.const_kind == ast.CONST_STR: - # String CDefine: register as a string constant (reader returns a global string) + # String DefineT: register as a string constant (reader returns a global string) self.Gen.TypeGenCDef.register_cdefine_string(pool, name, cn_fp.str_val) return if cn_fp.const_kind == ast.CONST_FLOAT: @@ -860,13 +860,13 @@ novirtual class AssignGenMixin: if self._recover_cdefine_float(name, &rec_d) != 0: use_val = rec_d else: # Source recovery failed: fail fast to avoid registering a corrupted float - self.Gen.TypeGenErr.fatal_error(val_node, "CDefine float value source-file recovery failed", "InternalCompilerError") + self.Gen.TypeGenErr.fatal_error(val_node, "DefineT float value source-file recovery failed", "InternalCompilerError") return self.Gen.TypeGenCDef.register_cdefine_double(pool, name, use_val, f32_kind) return if val_node is None: - # CDefine declaration without a value node: fail fast - self.Gen.TypeGenErr.fatal_error(None, f"CDefine value extraction failed: '{name}' is missing an initial value", "TypeError") + # DefineT declaration without a value node: fail fast + self.Gen.TypeGenErr.fatal_error(None, f"DefineT value extraction failed: '{name}' is missing an initial value", "TypeError") return # 浮点常量表达式(如 `define GW_F = 160.0 * 4`):整型提取器只认整型,遇到其中的 float # 字面量会 fail-fast("cannot parse a compile-time constant")。这里先按浮点求值; @@ -877,7 +877,7 @@ novirtual class AssignGenMixin: self.Gen.TypeGenCDef.register_cdefine_double(pool, name, fv_try, f32_kind) return # 含浮点字面量但不是编译期常量(含变量/函数调用)⇒ fail-fast,不静默降级 - self.Gen.TypeGenErr.fatal_error(val_node, f"CDefine float expression is not a compile-time constant: '{name}'", "TypeError") + self.Gen.TypeGenErr.fatal_error(val_node, f"DefineT float expression is not a compile-time constant: '{name}'", "TypeError") return # 位宽裁决:与跨模块使用方共用 cdefine_decl_bits(注解 → 右侧显式强转 → 值的最小可存宽度)。 # 有注解而存不下时:右侧带显式强转截断则不报错(左侧只看到截断值),否则由 @@ -889,7 +889,7 @@ novirtual class AssignGenMixin: self.require_cdefine_storable(pool, val_node, name, val_fp, annot, rhs_bits) self.Gen.TypeGenCDef.register_cdefine_constant(pool, name, val_fp, vb) - # Recover a CDefine float value by re-reading its source line with atof + # Recover a DefineT float value by re-reading its source line with atof def _recover_found(self) -> int: """Return whether the last _recover_cdefine_float succeeded.""" @@ -948,7 +948,7 @@ novirtual class AssignGenMixin: if sep >= line_start + line_len: continue if buf[sep] != ':' and buf[sep] != '=': continue - # CDefine path requires t.CDefine in the line, or the define keyword form + # DefineT path requires t.DefineT in the line, or the define keyword form if require_cdefine != 0 and is_define_line == 0: has_cd: int = 0 ci: sizet = line_start + name_len + 1 @@ -998,7 +998,7 @@ novirtual class AssignGenMixin: return 0 def _recover_cdefine_float(self, name: str, out: double*) -> int: - """Recover a CDefine float value (requiring a t.CDefine line).""" + """Recover a DefineT float value (requiring a t.DefineT line).""" return self._recover_float_literal(name, out, 1) # _annotation_contains_name - recursively check whether an annotation contains a name @@ -1020,13 +1020,13 @@ novirtual class AssignGenMixin: return 0 def _annotation_has_cptr_marker(self, annot: ast.AST*) -> int: - """Return 1 if the annotation is a CPtr pointer wrapper (the `T*` form).""" + """Return 1 if the annotation is a PtrT pointer wrapper (the `T*` form).""" if annot is None: return 0 if annot.kind() != ast.ASTKind.Subscript: return 0 sub_cp: ast.Subscript* = ast.Subscript*(annot) if sub_cp.value is not None and sub_cp.value.kind() == ast.ASTKind.Name: nm_cp: ast.Name* = ast.Name*(sub_cp.value) - if nm_cp.id is not None and (string.strcmp(nm_cp.id, "CPtr") == 0 or string.strcmp(nm_cp.id, "rawptr") == 0): return 1 + if nm_cp.id is not None and (string.strcmp(nm_cp.id, "PtrT") == 0 or string.strcmp(nm_cp.id, "rawptr") == 0): return 1 if sub_cp.slice is not None: return self._annotation_has_cptr_marker(sub_cp.slice) return 0 @@ -1041,14 +1041,14 @@ novirtual class AssignGenMixin: pe_nm: ast.Name* = ast.Name*(sub_pe.value) if pe_nm.id is None: return 0 # Descriptor / storage / ref wrapper: the pointer form lives in the wrapped type. - if string.strcmp(pe_nm.id, "CConst") == 0 or string.strcmp(pe_nm.id, "CVolatile") == 0 \ + if string.strcmp(pe_nm.id, "ConstT") == 0 or string.strcmp(pe_nm.id, "VolatileT") == 0 \ or string.strcmp(pe_nm.id, "BigEndian") == 0 or string.strcmp(pe_nm.id, "LittleEndian") == 0 \ - or string.strcmp(pe_nm.id, "CStatic") == 0 or string.strcmp(pe_nm.id, "CExport") == 0 \ - or string.strcmp(pe_nm.id, "CExtern") == 0 or string.strcmp(pe_nm.id, "State") == 0 \ - or string.strcmp(pe_nm.id, "CDefine") == 0 or string.strcmp(pe_nm.id, "CRef") == 0 \ - or string.strcmp(pe_nm.id, "CTempRef") == 0 or string.strcmp(pe_nm.id, "CAutoPtr") == 0: + or string.strcmp(pe_nm.id, "StaticT") == 0 or string.strcmp(pe_nm.id, "ExportT") == 0 \ + or string.strcmp(pe_nm.id, "ExternT") == 0 or string.strcmp(pe_nm.id, "State") == 0 \ + or string.strcmp(pe_nm.id, "DefineT") == 0 or string.strcmp(pe_nm.id, "RefT") == 0 \ + or string.strcmp(pe_nm.id, "TempRefT") == 0 or string.strcmp(pe_nm.id, "AutoPtrT") == 0: return self._is_ptr_element_annotation(sub_pe.slice) - if string.strcmp(pe_nm.id, "CPtr") != 0 and string.strcmp(pe_nm.id, "rawptr") != 0: return 0 + if string.strcmp(pe_nm.id, "PtrT") != 0 and string.strcmp(pe_nm.id, "rawptr") != 0: return 0 inner_pe: ast.AST* = sub_pe.slice if inner_pe is None: return 0 # str/bytes are already i8*; the pointer layer makes them i8** and needs 8-byte slots. @@ -1060,9 +1060,15 @@ novirtual class AssignGenMixin: gp_nm: str = inner_nm_pe.id if gp_nm is not None: gp_resolved: str = self.Gen.TypeGenBase.resolve_generic_type_name(gp_nm) + # resolve_generic_type_name 对非泛型名原样返回入参,所以"是否为泛型形参"只能用 + # 解析结果与入参名是否相同来判定。此前 return 1 落在 if 外,任何具名 pointee + # (如 `uint8*`)都会被误判成 ptr-element,下标按 8 字节步长 GEP 并 load 指针: + # 现场 includes/hashlib/__sha1.vp 的 `data: uint8* = block` 被编成 + # `getelementptr !llvm.ptr, ptr %block, i32 idx` + `load !llvm.ptr` + ptrtoint, + # 越界读堆 → VPC2 的 SHA1 全部算错(每次运行值都不同)。 if gp_resolved is not None and string.strcmp(gp_resolved, gp_nm) != 0: if self._is_scalar_ty_name(gp_resolved) != 0: return 0 - if gp_resolved is not None: return 1 + return 1 # 64-bit scalar pointee: element is 8 bytes (else GEP truncation). if self._is_64bit_scalar_ty(inner_pe) != 0: return 1 return 0 @@ -1198,29 +1204,6 @@ novirtual class AssignGenMixin: # life_of_expr - 求表达式当前值的生命期标签。 # target_ty 参与判定:裸本帧数组名在**指针**目标位置 = 数组退让(交出本帧栈地址), # 在同型聚合目标位置 = 按值拷贝(LIFE_VALUE)。 - # [TEMP-DIAG] life_of_expr 的 Name 分支判定输入快照(定位完删除)。 - # hit 语义:1=已登记 HEAP/STATIC 直接返回 / 2=裸数组名+指针目标(FRAME) / 3=裸数组名+值目标(VALUE) - # 4=值类型变量+值目标(VALUE,本次新增分支) / 5=指针或异型落到登记值 / 6=变量非指针落到登记值 - def _life_diag_name(self, trans: HT.Translator*, nm: str, rg: int, v: mlirlite.Value*, target_ty: mlirlite.LLVMType*, hit: int) -> int: - vptr: int = -1 - iptr: int = -1 - ikind: int = -1 - tptr: int = -1 - if v is not None and v.Ty is not None: - vptr = self.Gen.ExprGenBase.is_ptr_type(v.Ty) - if vptr != 0 and v.Ty.Pointee is not None: - iptr = self.Gen.ExprGenBase.is_ptr_type(v.Ty.Pointee) - ikind = int(v.Ty.Pointee.kind()) - if target_ty is not None: tptr = self.Gen.ExprGenBase.is_ptr_type(target_ty) - fn_s: str = None - if trans._cur_func is not None: fn_s = mlirlite.function_get_name(trans._cur_func) - if fn_s is None: fn_s = "?" - nm_s: str = nm - if nm_s is None: nm_s = "?" - stdio.printf("[LIFE-DIAG] fn=%s name=%s rg=%d hit=%d vptr=%d iptr=%d ikind=%d tptr=%d\n", - fn_s, nm_s, rg, hit, vptr, iptr, ikind, tptr) - return 0 - def life_of_expr(self, trans: HT.Translator*, node: ast.AST*, target_ty: mlirlite.LLVMType*) -> int: """表达式的 LifeTag;无法判定时 LIFE_UNKNOWN(保守放行)""" if node is None or trans is None: return GenTypes.LIFE_VALUE @@ -1276,9 +1259,6 @@ novirtual class AssignGenMixin: hit = 4 else: hit = 5 - # [TEMP-DIAG] 只打"结果不是值拷贝"或"新增分支命中"的情况,避免刷屏(定位完删除) - if res != GenTypes.LIFE_VALUE or hit == 4: - self._life_diag_name(trans, nm.id, rg, v, target_ty, hit) return res if k == ast.ASTKind.UnaryOp: uo: ast.UnaryOp* = ast.UnaryOp*(node) @@ -1335,27 +1315,6 @@ novirtual class AssignGenMixin: # 目标位置是地址(`&T(...)`、指针目标、指针槽)才交出本帧地址 ⇒ LIFE_FRAME。 if target_ty is not None and self.Gen.ExprGenBase.is_ptr_type(target_ty) == 0: return GenTypes.LIFE_VALUE - # [TEMP-DIAG] 打印构造类型与目标类型的**真实 LLVM 文本**(定位完删除): - # ct_ty 是 `T(...)` 自身的类型(`%"..T"` = 值本体 / `ptr` = 已被解析成指针) - # tgt 是目标位置(模块级全局/值槽)的类型文本 - ctb: str = "?" - bt1: str = trans.Pool.alloc_tagged(512, memhub.DETSITE_ASSIGNGEN_578) - if bt1 is not None: - mlirlite.TypePrint(bt1, 512, ct_ty, trans.Pool) - ctb = bt1 - ttb: str = "(none)" - if target_ty is not None: - bt2: str = trans.Pool.alloc_tagged(512, memhub.DETSITE_ASSIGNGEN_579) - if bt2 is not None: - mlirlite.TypePrint(bt2, 512, target_ty, trans.Pool) - ttb = bt2 - hv: int = -1 - if ct_en is not None: hv = ct_en.HasNew - fn_c: str = None - if trans._cur_func is not None: fn_c = mlirlite.function_get_name(trans._cur_func) - if fn_c is None: fn_c = "?" - stdio.printf("[CTOR-DIAG] fn=%s ctor=%s hasnew=%d ct_ty=%s tgt=%s\n", - fn_c, ctor_nm, hv, ctb, ttb) return GenTypes.LIFE_FRAME return GenTypes.LIFE_UNKNOWN if k == ast.ASTKind.New: @@ -1499,16 +1458,6 @@ novirtual class AssignGenMixin: if life_r != GenTypes.LIFE_FRAME: return 0 rk: int = GenApi.get_ret_kind_by_func(fd) if rk == GenApi.RET_KIND_FRAME: return 0 - # [TEMP-DIAG] 定义端拦截点输入(只在"判为帧生命期且未被 @life(ret(frame)) 放行"时打,定位完删除) - fn_d: str = None - if trans._cur_func is not None: fn_d = mlirlite.function_get_name(trans._cur_func) - if fn_d is None: fn_d = "?" - rvp: int = -1 - rvk: int = -1 - if rv_ty is not None: - rvp = self.Gen.ExprGenBase.is_ptr_type(rv_ty) - rvk = int(rv_ty.kind()) - stdio.printf("[RT-DIAG] fn=%s life=%d rk=%d rvptr=%d rvkind=%d\n", fn_d, life_r, rk, rvp, rvk) is_borrow_kind: int = 0 pi: int = -1 if rk >= GenApi.RET_KIND_BORROW_BASE: @@ -1672,7 +1621,7 @@ novirtual class AssignGenMixin: if string.strchr(ty_name, '*') is not None: return 0 if string.strcmp(ty_name, "str") == 0: return 0 if string.strcmp(ty_name, "bytes") == 0: return 0 - if string.strcmp(ty_name, "CPtr") == 0: return 0 + if string.strcmp(ty_name, "PtrT") == 0: return 0 if string.strstr(ty_name, "Ptr") is not None: return 0 # builtin short type names if string.strcmp(ty_name, "sizet") == 0: return 1 @@ -1790,17 +1739,21 @@ novirtual class AssignGenMixin: return 0 # _init_global_array_from_list - initialize a global array from a list literal - def _init_global_array_from_list(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, array_alloca: mlirlite.Value*, list_node: ast.AST*, trans: HT.Translator*, owner_ctx: str = None, byte_order: int = 0) -> int: + def _init_global_array_from_list(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, array_alloca: mlirlite.Value*, list_node: ast.AST*, trans: HT.Translator*, owner_ctx: str = None, byte_order: int = 0, arr_ty_opt: mlirlite.LLVMType* = None) -> int: """Initialize a global array from a list literal via per-element GEP+store. byte_order (0=native / 1=BigEndian / 2=LittleEndian): when the array variable is big-endian every element is stored byte-swapped, so a later element read (which un-swaps) sees the same - value; left at 0 for module globals / struct-field defaults (not registered).""" + value; left at 0 for module globals / struct-field defaults (not registered). + arr_ty_opt: the declared array type, used when the destination value carries no pointee + type (struct-field defaults go through a GEP whose Value.Ty may be absent, which used to + bail out silently and leave the field zero-initialized — 现场: sha1.state).""" if builder is None or array_alloca is None or list_node is None: return 1 # Get the array type (pointee of [N x elem_ty]*) arr_ty: mlirlite.LLVMType* = None if array_alloca.Ty is not None: arr_ty = array_alloca.Ty.Pointee + if arr_ty is None: arr_ty = arr_ty_opt if arr_ty is None: return 1 # Match the array type to get the element type and declared length @@ -2031,9 +1984,9 @@ novirtual class AssignGenMixin: # translate_augassign - translate augmented assignment statements def const_assign_msg(self, kind: str, name: str, src_name: str) -> str: """Build the const-assignment error message used by const_assign_fail.""" - decl: str = "(annotation contains t.CConst; the source name at the declaration was not recorded)" - if src_name is not None: decl = f"CConst | {src_name}" - return GenApi.escape_fstring_copy(f"cannot assign to const variable '{name}' ({kind}); " f"declaration type (including const): {decl}" f"; fix: remove the const descriptor / t.CConst from the declaration, or assign once at the declaration " f"and afterwards use only non-const copies/temporaries; a const object forbids assignment, " f"augmented assignment and element assignment within its scope, and this never degrades to a warning") + decl: str = "(annotation contains t.ConstT; the source name at the declaration was not recorded)" + if src_name is not None: decl = f"ConstT | {src_name}" + return GenApi.escape_fstring_copy(f"cannot assign to const variable '{name}' ({kind}); " f"declaration type (including const): {decl}" f"; fix: remove the const descriptor / t.ConstT from the declaration, or assign once at the declaration " f"and afterwards use only non-const copies/temporaries; a const object forbids assignment, " f"augmented assignment and element assignment within its scope, and this never degrades to a warning") def const_assign_fail(self, node: ast.AST*, kind: str, name: str, src_name: str) -> int: """Fail fast when a const target is written.""" @@ -2076,12 +2029,12 @@ novirtual class AssignGenMixin: if target_ty is None: # fail-fast: field type missing, no i32 fallback self.Gen.TypeGenErr.fatal_error(target, "field type information missing", "TypeError") - # t.CVolatile field: both load and store carry the volatile qualifier + # t.VolatileT field: both load and store carry the volatile qualifier vol_fa: int = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target) # BigEndian field: the memory slot holds the byte-swapped pattern, so the current value must # be un-swapped before it takes part in the arithmetic, and the result re-swapped on store. bo_fa: int = self.Gen.ExprGenAttr.attr_field_byte_order(pool, trans, target) - # rawptr[t.CVolatile|T] base: field load/store also carry volatile + # rawptr[t.VolatileT|T] base: field load/store also carry volatile at_aug: ast.Attribute* = ast.Attribute*(target) if at_aug is not None: if self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, at_aug.value) != 0: vol_fa = 1 @@ -2092,7 +2045,7 @@ novirtual class AssignGenMixin: cur_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, cur_val, bo_fa) # 3. Translate the RHS value - # 与 `x = x op y` 同口径:RHS 在左值类型(target_ty)上下文中翻译,嵌套 binop / CDefine 的 + # 与 `x = x op y` 同口径:RHS 在左值类型(target_ty)上下文中翻译,嵌套 binop / DefineT 的 # 目标感知与常量适配据此生效(旧行为 target_ty=None 使 RHS 口径与二元形态不一致)。 rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans, target_ty) if rhs_val is None: @@ -2149,7 +2102,7 @@ novirtual class AssignGenMixin: if target_ty is None: # fail-fast: element type missing, no i32 fallback self.Gen.TypeGenErr.fatal_error(target, "element type information missing", "TypeError") - # rawptr[t.CVolatile|T] base: p[i] load/store carry volatile when the base is resolvable + # rawptr[t.VolatileT|T] base: p[i] load/store carry volatile when the base is resolvable vol_sa: int = 0 sub_aug: ast.Subscript* = ast.Subscript*(target) if sub_aug is not None: vol_sa = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, sub_aug.value) @@ -2167,7 +2120,7 @@ novirtual class AssignGenMixin: cur_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, cur_val, bo_sa) # 3. Translate the RHS value - # 与 `x = x op y` 同口径:RHS 在左值类型(target_ty)上下文中翻译,嵌套 binop / CDefine 的 + # 与 `x = x op y` 同口径:RHS 在左值类型(target_ty)上下文中翻译,嵌套 binop / DefineT 的 # 目标感知与常量适配据此生效(旧行为 target_ty=None 使 RHS 口径与二元形态不一致)。 rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aug.value, None, 0, trans, target_ty) if rhs_val is None: @@ -2242,7 +2195,7 @@ novirtual class AssignGenMixin: if GenApi.is_global_name(trans, nm.id) != 0: scope_type = 1 elif GenApi.is_nonlocal_name(trans, nm.id) != 0: scope_type = 2 - # t.CVolatile variable: both load and store carry the volatile qualifier + # t.VolatileT variable: both load and store carry the volatile qualifier vol_aa: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) # BigEndian local variable: un-swap on load / re-swap on store so the arithmetic sees the native # value (local scope only; global/nonlocal are not registered in the byte-order table, like TPC). @@ -2350,23 +2303,23 @@ novirtual class AssignGenMixin: return 0 - # is_ctypedef_annotation - check whether an annotation is t.CTypedef + # is_ctypedef_annotation - check whether an annotation is t.TypedefT def is_ctypedef_annotation(self, annot: ast.AST*) -> int: - """Return 1 if the annotation is t.CTypedef, else 0.""" + """Return 1 if the annotation is t.TypedefT, else 0.""" return self.Gen.TypeGenCType._is_ctypedef_annotation(annot) - # _is_single_param_carray_annot - check for single-param t.CArray[elem_ty] + # _is_single_param_carray_annot - check for single-param t.ArrayT[elem_ty] def _is_single_param_carray_annot(self, annot: ast.AST*) -> int: - """Return 1 for a single-param or None-length t.CArray annotation.""" + """Return 1 for a single-param or None-length t.ArrayT annotation.""" if annot is None or annot.kind() != ast.ASTKind.Subscript: return 0 sub: ast.Subscript* = ast.Subscript*(annot) if sub.value is None or sub.slice is None: return 0 if sub.value.kind() == ast.ASTKind.Name: - # Bare CArray / builtin rawarray / builtin array are all equivalent. + # Bare ArrayT / builtin rawarray / builtin array are all equivalent. bn: ast.Name* = ast.Name*(sub.value) - if bn.id is None or (string.strcmp(bn.id, "CArray") != 0 and string.strcmp(bn.id, "rawarray") != 0 and string.strcmp(bn.id, "array") != 0): return 0 + if bn.id is None or (string.strcmp(bn.id, "ArrayT") != 0 and string.strcmp(bn.id, "rawarray") != 0 and string.strcmp(bn.id, "array") != 0): return 0 else: return 0 - # Single-param form: slice is not a Tuple (e.g. CArray[u64]) + # Single-param form: slice is not a Tuple (e.g. ArrayT[u64]) if sub.slice.kind() != ast.ASTKind.Tuple: return 1 # Two-param form with count=None is equivalent to single-param tup: ast.Tuple* = ast.Tuple*(sub.slice) @@ -2377,9 +2330,9 @@ novirtual class AssignGenMixin: if cn0.const_kind == ast.CONST_NONE: return 1 return 0 - # _infer_static_array_ty_from_literal - single-param CArray + list literal -> [len x elem_ty] + # _infer_static_array_ty_from_literal - single-param ArrayT + list literal -> [len x elem_ty] def _infer_static_array_ty_from_literal(self, pool: memhub.MemBuddy*, annot: ast.AST*, value: ast.AST*, resolved_ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*: - """Return Array(elem_ty, len) for a single-param CArray with a list literal.""" + """Return Array(elem_ty, len) for a single-param ArrayT with a list literal.""" if value is not None and value.kind() == ast.ASTKind.List and resolved_ty is not None: if self._is_single_param_carray_annot(annot) != 0: elem_ty_arr: mlirlite.LLVMType* = resolved_ty.Pointee @@ -2428,13 +2381,13 @@ novirtual class AssignGenMixin: nm: ast.Name* = ast.Name*(target) if nm.id is None: return 0 - # CDefine annotation: register the constant instead of creating an alloca + # DefineT annotation: register the constant instead of creating an alloca if self.is_cdefine_annotation(aa.annotation) != 0: pool_ps: memhub.MemBuddy* = trans.Pool self._register_cdefine_from_value(pool_ps, nm.id, aa.value) return 0 - # CTypedef annotation: register the alias only, no storage (avoids duplicate symbols) + # TypedefT annotation: register the alias only, no storage (avoids duplicate symbols) if self.is_ctypedef_annotation(aa.annotation) != 0: return 0 # REPL fragment entry: AnnAssign goes to the session slot; skip pre-creating a local alloca @@ -2466,7 +2419,7 @@ novirtual class AssignGenMixin: resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, annot, trans._imported_modules, trans._from_imports, trans, 0) if resolved is not None: var_ty = resolved - # Single-param CArray + list literal -> static array; keep PreScan and Handle types in sync + # Single-param ArrayT + list literal -> static array; keep PreScan and Handle types in sync var_ty = self._infer_static_array_ty_from_literal(pool, annot, aa.value, var_ty) # [D31 fail-fast] generic annotation degraded to a bare base class self._fail_on_degenerated_generic_annotation(aa, nm.id, annot, var_ty) @@ -2480,23 +2433,6 @@ novirtual class AssignGenMixin: # Ensure the full cross-module struct definition is available (for alloca) self.Gen.StructGen.ensure_struct_def_in_module(pool, trans.Module, var_ty) alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, var_ty) - # [TEMP-DIAG] 变量声明的槽类型 + **alloca 地址**(定位完删除): - # `npos = pos + Δ` 之后 pos.y 变化 ⇒ 若 pos/npos 的 slot 地址相同,说明两个变量 - # 共用了同一块存储(槽复用/RVO 回填错槽),值语义被破坏。 - vtb: str = "?" - bt0: str = pool.alloc_tagged(512, memhub.DETSITE_ASSIGNGEN_578) - if bt0 is not None: - mlirlite.TypePrint(bt0, 512, var_ty, pool) - vtb = bt0 - vptr0: int = -1 - if var_ty is not None: vptr0 = self.Gen.ExprGenBase.is_ptr_type(var_ty) - nm0: str = nm.id - if nm0 is None: nm0 = "?" - fn0: str = None - if trans._cur_func is not None: fn0 = mlirlite.function_get_name(trans._cur_func) - if fn0 is None: fn0 = "?" - stdio.printf("[VAR-DIAG] fn=%s name=%s vptr=%d slot=0x%x var_ty=%s\n", - fn0, nm0, vptr0, uint64(alloca) if alloca is not None else uint64(0), vtb) if alloca is not None: if self.Gen.VarGen.define_var(trans.SymTab, nm.id, alloca) == 0: # Store the annotation's class name; pass pool so generic Subscript yields the mangled name @@ -2505,11 +2441,11 @@ novirtual class AssignGenMixin: if cls_nm_ps is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, nm.id, cls_nm_ps) # Check for bytes* or str* (i8** semantics, 8-byte stride) if self._is_ptr_element_annotation(annot) != 0: self.Gen.VarGen.set_var_ptr_element(trans.SymTab, nm.id) - # const object (annotation has t.CConst and the type is not a pointer): no writes in scope + # const object (annotation has t.ConstT and the type is not a pointer): no writes in scope if self.Gen.TypeGenErr.is_const_object_marker(annot, var_ty) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, nm.id) - # t.CVolatile: mark the variable at declaration; its load/store carry volatile - if self.Gen.TypeGenErr.has_decorator_marker(annot, "CVolatile", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id) - # rawptr[t.CVolatile | T]: dereference load/store carry volatile + # t.VolatileT: mark the variable at declaration; its load/store carry volatile + if self.Gen.TypeGenErr.has_decorator_marker(annot, "VolatileT", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id) + # rawptr[t.VolatileT | T]: dereference load/store carry volatile if self.Gen.TypeGenErr.has_pointee_volatile_marker(annot) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, nm.id) return 0 return 0 @@ -2518,13 +2454,13 @@ novirtual class AssignGenMixin: def _prespecialize_generic_annotation(self, trans: HT.Translator*, pool: memhub.MemBuddy*, annot: ast.AST*) -> int: """Pre-specialize a generic annotation; return 1 if attempted.""" if annot is None or trans is None: return 0 - # Take the generic Subscript side: the Subscript itself or the Subscript wrapped by CPtr[T]. + # Take the generic Subscript side: the Subscript itself or the Subscript wrapped by PtrT[T]. ann_ps: ast.AST* = annot if ann_ps.kind() == ast.ASTKind.Subscript: sub_wrap_ps: ast.Subscript* = ast.Subscript*(ann_ps) if sub_wrap_ps.value is not None and sub_wrap_ps.value.kind() == ast.ASTKind.Name: wk_nm_ps: ast.Name* = ast.Name*(sub_wrap_ps.value) - if wk_nm_ps.id is not None and (string.strcmp(wk_nm_ps.id, "CPtr") == 0 or string.strcmp(wk_nm_ps.id, "rawptr") == 0): + if wk_nm_ps.id is not None and (string.strcmp(wk_nm_ps.id, "PtrT") == 0 or string.strcmp(wk_nm_ps.id, "rawptr") == 0): if sub_wrap_ps.slice is not None: ann_ps = sub_wrap_ps.slice spec_annot: ast.Subscript* = None if ann_ps.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(ann_ps) @@ -2544,13 +2480,13 @@ novirtual class AssignGenMixin: def _fail_on_degenerated_generic_annotation(self, aa: ast.AnnAssign*, var_name: str, annot: ast.AST*, var_ty: mlirlite.LLVMType*) -> int: """Fail fast when a generic annotation degrades to a bare base pointer.""" if aa is None or annot is None or var_ty is None: return 0 - # Take the generic Subscript side: the Subscript itself or the Subscript wrapped by CPtr[T]. + # Take the generic Subscript side: the Subscript itself or the Subscript wrapped by PtrT[T]. ann_ff: ast.AST* = annot if ann_ff.kind() == ast.ASTKind.Subscript: sub_wrap_ff: ast.Subscript* = ast.Subscript*(ann_ff) if sub_wrap_ff.value is not None and sub_wrap_ff.value.kind() == ast.ASTKind.Name: wk_nm_ff: ast.Name* = ast.Name*(sub_wrap_ff.value) - if wk_nm_ff.id is not None and (string.strcmp(wk_nm_ff.id, "CPtr") == 0 or string.strcmp(wk_nm_ff.id, "rawptr") == 0): + if wk_nm_ff.id is not None and (string.strcmp(wk_nm_ff.id, "PtrT") == 0 or string.strcmp(wk_nm_ff.id, "rawptr") == 0): if sub_wrap_ff.slice is not None: ann_ff = sub_wrap_ff.slice ann_sub: ast.Subscript* = None if ann_ff.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(ann_ff) @@ -2583,20 +2519,20 @@ novirtual class AssignGenMixin: nm: ast.Name* = ast.Name*(target) if nm.id is None: return 0 - # CDefine annotation: compile-time constant, no runtime code; register it (idempotent) + # DefineT annotation: compile-time constant, no runtime code; register it (idempotent) if self.is_cdefine_annotation(aa.annotation) != 0: pool_cd: memhub.MemBuddy* = trans.Pool self._register_cdefine_from_value(pool_cd, nm.id, aa.value, aa.annotation) return 0 - # CTypedef annotation: register the alias on demand; no storage (avoids duplicate symbols) + # TypedefT annotation: register the alias on demand; no storage (avoids duplicate symbols) if self.is_ctypedef_annotation(aa.annotation) != 0: pool_ct: memhub.MemBuddy* = trans.Pool ctd_str: str = self.Gen.TypeGenResolve._get_type_name_from_annotation(pool_ct, aa.value) if ctd_str is not None: self.Gen.TypeGenCType.register_ctypedef(pool_ct, nm.id, ctd_str) return 0 - # Use-site width check for define/CDefine constants (a: T = C): T must be >= W_min + # Use-site width check for define/DefineT constants (a: T = C): T must be >= W_min self.require_cdefine_use_width(trans.Pool, trans, aa.annotation, aa.value) # REPL session variable: annotated declaration at fragment entry goes to the session slot @@ -2720,7 +2656,7 @@ novirtual class AssignGenMixin: # str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node) rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans) - # t.CVolatile global: the initializing store carries the volatile qualifier + # t.VolatileT global: the initializing store carries the volatile qualifier vol_gaa: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) mlirlite.build_store(builder, rhs_val, mod_alloca, vol_gaa) # Clear the assignment context (all four branches must stay in sync) @@ -2802,7 +2738,7 @@ novirtual class AssignGenMixin: # str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val_mg, aa.annotation, node) rhs_val_mg = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val_mg, target_ty_mg, node) - # t.CVolatile module-level global: the initializing store carries volatile + # t.VolatileT module-level global: the initializing store carries volatile vol_mga: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) mlirlite.build_store(builder, rhs_val_mg, mod_glob, vol_mga) # Clear the assignment context (all four branches must stay in sync) @@ -2810,7 +2746,7 @@ novirtual class AssignGenMixin: trans.CurAssignNode = None return 0 - # Single-param CArray + list literal -> static array; match PreScan so the alloca is reused + # Single-param ArrayT + list literal -> static array; match PreScan so the alloca is reused var_ty = self._infer_static_array_ty_from_literal(pool, annot, aa.value, var_ty) # [D31 fail-fast] generic annotation degraded to a bare base class self._fail_on_degenerated_generic_annotation(aa, nm.id, annot, var_ty) @@ -2911,7 +2847,16 @@ novirtual class AssignGenMixin: # P1-A: use the local alloca pointee as the target type so integer literals materialize at target width lv_target_ty: mlirlite.LLVMType* = None if alloca.Ty is not None: lv_target_ty = alloca.Ty.Pointee + # `x: T = T(...)`:把目标存储交给构造序列,让构造直接落在 x 上(原地构造)。 + ip_slot: mlirlite.Value* = self._inplace_ctor_slot(pool, trans, aa.value, alloca) + ip_prev_st: mlirlite.Value* = None + ip_prev_life: int = -1 + if ip_slot is not None: + ip_prev_st = self.Gen.CallGenCtor.CurCtorStorage() + ip_prev_life = self.Gen.CallGenCtor.CurCtorLife() + self.Gen.CallGenCtor.SetCtorStorage(ip_slot, GenTypes.LIFE_FRAME) rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, aa.value, None, 0, trans, lv_target_ty, "local variable annotation") + if ip_slot is not None: self.Gen.CallGenCtor.SetCtorStorage(ip_prev_st, ip_prev_life) if rhs_val is None: # fail-fast: 右值翻译失败时绝不能静默跳过整条声明(此前直接落到 `if rhs_val is # not None` 之外,变量永远保持未初始化,运行期读到栈垃圾后断言崩溃)。 @@ -2935,12 +2880,12 @@ novirtual class AssignGenMixin: ann_gen_base: str = None if annot is not None: ag_side: ast.AST* = annot - # Unwrap a CPtr[T] pointer layer to inspect the generic base. + # Unwrap a PtrT[T] pointer layer to inspect the generic base. if ag_side.kind() == ast.ASTKind.Subscript: ag_wrap: ast.Subscript* = ast.Subscript*(ag_side) if ag_wrap.value is not None and ag_wrap.value.kind() == ast.ASTKind.Name: ag_wnm: ast.Name* = ast.Name*(ag_wrap.value) - if ag_wnm.id is not None and (string.strcmp(ag_wnm.id, "CPtr") == 0 or string.strcmp(ag_wnm.id, "rawptr") == 0): + if ag_wnm.id is not None and (string.strcmp(ag_wnm.id, "PtrT") == 0 or string.strcmp(ag_wnm.id, "rawptr") == 0): if ag_wrap.slice is not None: ag_side = ag_wrap.slice if ag_side.kind() == ast.ASTKind.Subscript: ag_sub: ast.Subscript* = ast.Subscript*(ag_side) @@ -2982,7 +2927,13 @@ novirtual class AssignGenMixin: av_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(alloca_pt) if av_entry is None or av_entry.HasVTable == 0: ann_agg_value = 1 elif ak_av == mlirlite.LLVMType.Array: ann_agg_value = 1 - if alloca_pt is not None and uint64(alloca_pt) != uint64(precise_rhs_ty) and rb_is_bare_generic == 0 and rb_is_upcast == 0 and ann_agg_value == 0: + # 只在**槽本身已是指针**时才允许按右值重建(`x: T* = ` 的修正)。 + # 注解解析成非指针(int/enum/无 vtable 聚合)却拿到 Ptr(T) 右值时,重建会 + # 把变量悄悄改成指针类型:后续 `f = x` 才在别处报"指针不能赋给整数",真正的 + # 错误(右值类型不对)被掩盖。这里不重建,让下面的 coerce 在本行给出精确诊断。 + rb_slot_is_ptr: int = 0 + if alloca_pt is not None and self.Gen.ExprGenBase.is_ptr_type(alloca_pt) != 0: rb_slot_is_ptr = 1 + if alloca_pt is not None and uint64(alloca_pt) != uint64(precise_rhs_ty) and rb_is_bare_generic == 0 and rb_is_upcast == 0 and ann_agg_value == 0 and rb_slot_is_ptr != 0: new_alloca: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, precise_rhs_ty) if new_alloca is not None: v_entry_rb: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id) @@ -2996,15 +2947,15 @@ novirtual class AssignGenMixin: # str/bytes is not a generic pointer: bare str/bytes annotation with an aggregate-pointer RHS self.Gen.ExprGenBase.fail_if_str_annot_ast_agg_ptr(rhs_val, aa.annotation, node) rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans) - # t.CVolatile local variable: mark the VarEntry and store with volatile + # t.VolatileT local variable: mark the VarEntry and store with volatile vol_laa: int = 0 if aa.annotation is not None and nm.id is not None: # const object local variable: mark VarEntry.IsConst (idempotent with prescan) if self.Gen.TypeGenErr.is_const_object_marker(aa.annotation, target_ty) != 0: self.Gen.VarGen.set_var_const(trans.SymTab, nm.id) - if self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "CVolatile", 0) != 0: + if self.Gen.TypeGenErr.has_decorator_marker(aa.annotation, "VolatileT", 0) != 0: self.Gen.VarGen.set_var_volatile(trans.SymTab, nm.id) vol_laa = 1 - # rawptr[t.CVolatile | T]: mark PointeeVolatile (this store writes the pointer itself) + # rawptr[t.VolatileT | T]: mark PointeeVolatile (this store writes the pointer itself) if self.Gen.TypeGenErr.has_pointee_volatile_marker(aa.annotation) != 0: self.Gen.VarGen.set_var_pointee_volatile(trans.SymTab, nm.id) # Value-position volatile call prefix: the result slot is accessed with volatile if aa.value is not None and aa.value.kind() == ast.ASTKind.Call and (ast.Call*(aa.value)).IsVolatile != 0: @@ -3120,7 +3071,7 @@ novirtual class AssignGenMixin: sub_nm_lv: ast.Name* = ast.Name*(sub_lv_b.value) if sub_nm_lv.id is not None: bo_lv = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, sub_nm_lv.id)) store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, bo_lv) - # t.CVolatile 字段 / rawptr[t.CVolatile|T] 基址:写出同样带 volatile。 + # t.VolatileT 字段 / rawptr[t.VolatileT|T] 基址:写出同样带 volatile。 vol_lv: int = 0 if is_attr != 0: vol_lv = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target) @@ -3130,6 +3081,54 @@ novirtual class AssignGenMixin: mlirlite.build_store(builder, store_val, ptr, vol_lv) return 0 + # _inplace_ctor_slot - `x: T = T(...)` 的原地构造成立吗?成立则返回 x 的存储,否则 None。 + # 只在"目标存储地址已固定、且 RHS 就是同一类的构造调用"时成立: + # · 目标是本地变量(入口块 alloca,地址稳定且支配构造点); + # · RHS 的调用名就是该类本身:`T(...)`(Name)或 `mod.T(...)`(Attribute,左侧必须是 + # 已导入模块),排除同名普通函数与 `obj.method()`; + # · 目标存储的 pointee 是结构体,其短名(剥掉 sha1 前缀)与构造名逐字相同, + # 且该结构体在 StructTable 里确实登记过。 + # 为什么必须原地:`__init__` 可能把 `self` 存进字段(vpui 的 `Tk.tk = self`)。若先在临时 + # alloca 上构造再整块拷贝,字段里的自指针仍指向那个死栈槽,之后经它写数据会写坏别的局部 + # 变量(现场:Tk.widget_count 永远读到 0,所有控件都没注册,窗口内容只剩一层底色)。 + def _inplace_ctor_slot(self, pool: memhub.MemBuddy*, trans: HT.Translator*, value: ast.AST*, slot: mlirlite.Value*) -> mlirlite.Value*: + """返回可原地构造的目标存储;不满足条件返回 None(保持原有"临时对象 + 按值拷贝"语义)""" + if value is None or slot is None or trans is None: return None + if slot.Ty is None: return None + if value.kind() != ast.ASTKind.Call: return None + cl: ast.Call* = ast.Call*(value) + if cl.func is None: return None + # 取构造名:`T(...)` 直接取 Name;`mod.T(...)` 取 Attribute 的末段,但左侧必须是 + # 已导入模块(否则是 `obj.method()`,不能把 obj 的槽当成构造存储)。 + ctor_name: str = None + if cl.func.kind() == ast.ASTKind.Name: + cnm: ast.Name* = ast.Name*(cl.func) + if cnm.id is not None: ctor_name = cnm.id + elif cl.func.kind() == ast.ASTKind.Attribute: + cat: ast.Attribute* = ast.Attribute*(cl.func) + if cat.attr is not None and cat.value is not None and cat.value.kind() == ast.ASTKind.Name: + mnm: ast.Name* = ast.Name*(cat.value) + if mnm.id is not None and self.Gen.StructGen._resolve_module_sha1(mnm.id, trans._from_imports) is not None: + ctor_name = cat.attr + if ctor_name is None: return None + st: mlirlite.LLVMType* = slot.Ty.Pointee + if st is None: return None + sname: str = None + match st: + case mlirlite.LLVMType.Struct(_, _, _): sname = mlirlite.get_struct_name(st) + case _: return None + if sname is None: return None + # 结构体名形如 "sha1.ClassName":剥掉 sha1 前缀后与构造名比较 + pf: int = GenApi.sha1_prefix_len(sname) + short: str = sname + pf if pf != 0 else sname + if string.strcmp(short, ctor_name) != 0: return None + # 只有确实登记过的类才走构造序列;否则 `T(...)` 可能是同名普通函数调用。 + # 先按类型指针精确匹配,落空再按全名查 —— 条目里的 Ty 会被深拷贝改写成新指针 + # (见 StructGenMixin.find_struct_by_type 的注释),只靠指针比较会假阴性。 + if self.Gen.StructGen.find_struct_by_type(st) is None: + if self.Gen.StructGen.find_struct(sname) is None: return None + return slot + # translate_assign - handle an Assign statement; returns the number of new variables def translate_assign(self, trans: HT.Translator*, node: ast.AST*) -> int: """Translate an Assign statement; return the number of new variables.""" @@ -3214,7 +3213,23 @@ novirtual class AssignGenMixin: # REPL: a typed session variable also uses its slot type as the target type asg_re: GenTypes.ReplVarEntry* = self.Gen.VarGen.repl_find_entry(trans.ReplCtx, asg_nm0.id) if asg_re is not None and asg_re.Kind != GenTypes.REPL_KIND_UNTYPED: asg_target_ty = self.Gen.VarGen.repl_slot_ty(pool, asg_re) + # `x = T(...)`(x 已有本地存储):与 `x: T = T(...)` 同口径原地构造 —— 自指针必须 + # 指向 x 本身,不能指向一个随后被按值拷贝走的临时对象。 + ip_slot: mlirlite.Value* = None + if len(targets) == 1: + ip_t0: ast.AST* = targets.get(0) + if ip_t0 is not None and ip_t0.kind() == ast.ASTKind.Name: + ip_nm: ast.Name* = ast.Name*(ip_t0) + if ip_nm.id is not None: + ip_slot = self._inplace_ctor_slot(pool, trans, asgn.value, self.Gen.VarGen.lookup_var(trans.SymTab, ip_nm.id)) + ip_prev_st: mlirlite.Value* = None + ip_prev_life: int = -1 + if ip_slot is not None: + ip_prev_st = self.Gen.CallGenCtor.CurCtorStorage() + ip_prev_life = self.Gen.CallGenCtor.CurCtorLife() + self.Gen.CallGenCtor.SetCtorStorage(ip_slot, GenTypes.LIFE_FRAME) rhs_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, asgn.value, None, 0, trans, asg_target_ty) + if ip_slot is not None: self.Gen.CallGenCtor.SetCtorStorage(ip_prev_st, ip_prev_life) if rhs_val is None: # RHS is None usually means allocation failure; dump both pool statistics mb_diag: memhub.MemBuddy* = CompilerFactory.factory.Pool @@ -3345,7 +3360,7 @@ novirtual class AssignGenMixin: sub_nm_sw: ast.Name* = ast.Name*(sub_wr.value) if sub_nm_sw.id is not None: bo_sw = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, sub_nm_sw.id)) store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, bo_sw) - # rawptr[t.CVolatile|T] base: p[i] = v stores with volatile when the base is resolvable + # rawptr[t.VolatileT|T] base: p[i] = v stores with volatile when the base is resolvable vol_sw: int = 0 if sub_wr is not None: vol_sw = self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, sub_wr.value) mlirlite.build_store(builder, store_val, elem_ptr, vol_sw) @@ -3522,9 +3537,9 @@ novirtual class AssignGenMixin: # (pairs the un-swap done by ExprGenAttr.translate_attribute on the load path). bo_fw: int = self.Gen.ExprGenAttr.attr_field_byte_order(pool, trans, target) store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, bo_fw) - # t.CVolatile field: store with the volatile qualifier + # t.VolatileT field: store with the volatile qualifier vol_fw: int = self.Gen.ExprGenAttr.attr_field_is_volatile(pool, trans, target) - # rawptr[t.CVolatile|T] base: p.field = v also stores with volatile + # rawptr[t.VolatileT|T] base: p.field = v also stores with volatile at_wr: ast.Attribute* = ast.Attribute*(target) if at_wr is not None: if self.Gen.ExprGenAttr.base_pointee_is_volatile(pool, trans, at_wr.value) != 0: vol_fw = 1 @@ -3582,7 +3597,7 @@ novirtual class AssignGenMixin: target_ty: mlirlite.LLVMType* = None if mod_alloca.Ty is not None: target_ty = mod_alloca.Ty.Pointee if target_ty is not None: rhs_val = self.Gen.ExprGenBase.coerce_to_type(builder, rhs_val, target_ty, node, trans) - # t.CVolatile global: store with the volatile qualifier + # t.VolatileT global: store with the volatile qualifier vol_gas: int = 0 if nm.id is not None: vol_gas = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) mlirlite.build_store(builder, rhs_val, mod_alloca, vol_gas) @@ -3635,7 +3650,7 @@ novirtual class AssignGenMixin: # memory (pairs the un-swap done by ExprGenName.translate_name_value on the read path). bo_las: int = self.Gen.VarGen.var_byte_order(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) store_val = self.Gen.ExprGenBase.apply_byte_order_bswap(pool, builder, store_val, bo_las) - # t.CVolatile local variable: store with the volatile qualifier + # t.VolatileT local variable: store with the volatile qualifier vol_las: int = self.Gen.VarGen.var_is_volatile(self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)) # M3 所有权记账:必须先记账再写值(覆盖释放要读到旧值); # `p = new heap slife …` → 释放旧对象 + 影子置 1;`q = p` / 外来指针 → 交还所有权 @@ -3679,7 +3694,7 @@ novirtual class AssignGenMixin: if rhs_inner_wo.kind() != mlirlite.LLVMType.Struct: return # The pointee type must match the element type if self._stg_ty_equal(pool, elem_ty, rhs_inner_wo) == 0: return - # Array slots (t.CArray[T,N]) are legal; only block when the base is a pointer store + # Array slots (t.ArrayT[T,N]) are legal; only block when the base is a pointer store if self.Gen.ExprGenName.subscript_base_store_kind(pool, trans, target) != 0: return elem_nm_wo: str = mlirlite.get_struct_name(elem_ty) if elem_nm_wo is None: elem_nm_wo = "" @@ -3694,7 +3709,7 @@ novirtual class AssignGenMixin: if target is not None: col_s_wo = target.col_offset + 1 col_e_wo = target.end_col_offset + 1 - self.Gen.TypeGenErr.fatal_error(target, f"the lvalue element of the subscript assignment is object '{elem_nm_wo}' ({elem_sz_wo} bytes), " f"but the rhs is a pointer to '{rhs_nm_wo}': the compiler would silently emit a whole-object copy " f"(load {elem_nm_wo} + store {elem_nm_wo}, writing {elem_sz_wo} bytes), " f"instead of writing {ptr_sz_wo} bytes as a pointer. If that address actually stores pointers in 8-byte slots " f"(such as a rawptr[T] buffer/free list), every element written overflows by {over_wo} bytes, " f"writing through adjacent blocks. Fix: declare the base type of the pointer slot in pointer-element form " f"(`rawptr[T]*` or `rawptr[rawptr[T]]`) before assigning; " f"or explicitly cast the base to t.CPtr (untyped pointer) and write with 8-byte pointer semantics.", "TypeError", col_s_wo, col_e_wo) + self.Gen.TypeGenErr.fatal_error(target, f"the lvalue element of the subscript assignment is object '{elem_nm_wo}' ({elem_sz_wo} bytes), " f"but the rhs is a pointer to '{rhs_nm_wo}': the compiler would silently emit a whole-object copy " f"(load {elem_nm_wo} + store {elem_nm_wo}, writing {elem_sz_wo} bytes), " f"instead of writing {ptr_sz_wo} bytes as a pointer. If that address actually stores pointers in 8-byte slots " f"(such as a rawptr[T] buffer/free list), every element written overflows by {over_wo} bytes, " f"writing through adjacent blocks. Fix: declare the base type of the pointer slot in pointer-element form " f"(`rawptr[T]*` or `rawptr[rawptr[T]]`) before assigning; " f"or explicitly cast the base to rawptr (untyped pointer) and write with 8-byte pointer semantics.", "TypeError", col_s_wo, col_e_wo) # _infer_rhs_annot - infer a variable's annotation class name from the RHS expression def _infer_rhs_annot(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator*, rhs_val: mlirlite.Value* = None) -> str: @@ -3796,8 +3811,8 @@ novirtual class AssignGenMixin: if seglen > 0 and seglen < 256: string.strncpy(seg, annot + s, seglen) seg[seglen] = '\0' - # Unwrap the CPtr[T] pointer marker: the class name is inside. - while seglen > 6 and string.strncmp(seg, "CPtr[", 5) == 0 and seg[seglen - 1] == ']': + # Unwrap the PtrT[T] pointer marker: the class name is inside. + while seglen > 6 and string.strncmp(seg, "PtrT[", 5) == 0 and seg[seglen - 1] == ']': seg = seg + 5 seglen -= 6 seg[seglen] = '\0' @@ -3814,8 +3829,8 @@ novirtual class AssignGenMixin: if seglen > 0 and seglen < 256: string.strncpy(seg, annot + s, seglen) seg[seglen] = '\0' - # Unwrap the CPtr[T] pointer marker: the class name is inside. - while seglen > 6 and string.strncmp(seg, "CPtr[", 5) == 0 and seg[seglen - 1] == ']': + # Unwrap the PtrT[T] pointer marker: the class name is inside. + while seglen > 6 and string.strncmp(seg, "PtrT[", 5) == 0 and seg[seglen - 1] == ']': seg = seg + 5 seglen -= 6 seg[seglen] = '\0' diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index 1b3cc89..25a8fe0 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -11,7 +11,7 @@ import string import stdio import stdlib import viperlib -import win32.fileio as fileio +import io as fileio import win32.win32file as win32file import lib.core.VLogger as VLogger import lib.Stub.StubMerger as StubMerger @@ -56,7 +56,7 @@ novirtual class CallGenBase: CexportFuncArr: bytes = None CexportCount: int = 0 - # --- static symbol registry (static / @t.CStatic -> defining module SHA1), + # --- static symbol registry (static / @t.StaticT -> defining module SHA1), # meaning no cross-module use (C internal linkage); PermPool fixed slots. StaticSha1Arr: bytes = None StaticNameArr: bytes = None @@ -149,6 +149,9 @@ novirtual class CallGenBase: # 报错,可据此把报错精确定位到这个实参表达式(如 `&_on_submit`),而不是退回函数定义行。 if arg_node is not None: self.Gen.TypeGenBase.set_cur_arg(self.Gen.TypeGenResolve.get_current_file(), arg_node.lineno, arg_node.col_offset + 1) + # 强类型(2026-10-22):值语义变量传给声明为 `T*` 的形参必须显式 &a —— 否则会被拷进 + # 临时槽再传地址,被调者改的是副本(跨模块按 vpi 形参注解判定,见 helper)。 + self._fail_on_value_arg_to_ptr_param(trans, callee_name, arg_idx, arg_node, fpty) mode: int = self._auto_addr_mode(callee_name, arg_idx) if mode == 0: return self.Gen.ExprGen.translate_value(builder, pool, mod, arg_node, None, 0, trans, fpty, use_ctx) @@ -188,9 +191,19 @@ novirtual class CallGenBase: def _vpi_auto_addr_mode(self, call_name: str, cur_sha1: str, idx: int) -> int: """Cross-module ({sha1}.cls.method / {sha1}.func) value-reference mode from the vpi cache; 0 if none/unknown.""" if call_name is None or idx < 0: return 0 - if GenApi.sha1_prefix_len(call_name) == 0: return 0 + prm_annots: str = 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: + """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 0 + 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 @@ -207,20 +220,49 @@ novirtual class CallGenBase: meth_nm = dot_r + 1 if meth_nm is None or meth_nm[0] == '\0': if cls_nm is not None: stdlib.free(cls_nm) - return 0 + return None sha1_buf: bytes = stdlib.malloc(17) if sha1_buf is None: if cls_nm is not None: stdlib.free(cls_nm) - return 0 + return None string.strncpy(sha1_buf, call_name, 16) sha1_buf[16] = '\0' if self.TempDir is not None and self.TempDirLen > 0: VpiReader._ensure_vpi_param_annot_loaded(sha1_buf, self.TempDir) prm_annots: str = VpiReader._lookup_vpi_param_annots(sha1_buf, cls_nm, meth_nm) - mode: int = 0 - if prm_annots is not None: mode = VpiReader._vpi_param_annot_seg_mode(prm_annots, idx) stdlib.free(sha1_buf) if cls_nm is not None: stdlib.free(cls_nm) - return mode + return prm_annots + + # _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, + 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 + 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 + # 实参本身必须是"聚合值语义"的**结构体**槽;指针变量与数组都不受影响 + # (数组退让成元素指针是 C 语义,传的就是它自己的地址,不存在拷贝副本) + 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) + if prm_annots is None: return 0 + # vpi 的形参注解段**含 self**(方法第 0 段为空注解),故方法调用的实参下标要 +1 + rest_va: str = 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 + 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 # _apply_auto_addr_to_call_args - auto-address the user arguments of a call whose definition params # are value references (&T -> take the lvalue address / &temp T -> copy into a temp slot). The @@ -283,6 +325,13 @@ novirtual class CallGenBase: if string.strcmp(sha1_arr_ptr + sidx, mod_sha1) != 0: continue ridx: sizet = sizet(i) * StubMerger.MAX_REL_PATH_LEN rel: str = rel_arr_ptr + ridx + # Includes are registered with a workspace-relative path ("includes/ast/__init__.vp") + # because the scan root is the workspace, while the module name drops that prefix + # ("ast", "ast.lexer"). Dropping it here too keeps re-export resolution (`from .lexer + # import ...` -> "ast.lexer") from building "includes.ast.lexer", which resolves nowhere. + rel_off: sizet = 0 + if string.strncmp(rel, "includes/", 9) == 0: rel_off = 9 + rel = rel + rel_off last_slash: str = string.strrchr(rel, 47) # 47 = ord('/') if last_slash is None: return None pkg_len: sizet = sizet(uint64(last_slash) - uint64(rel)) @@ -468,7 +517,7 @@ novirtual class CallGenBase: def register_cexport_func(self, sha1: str, func_name: str) -> int: - """Register a CExport function into the global table (single source of truth in Gen.CallGenBase)""" + """Register a ExportT function into the global table (single source of truth in Gen.CallGenBase)""" if self.Gen is None or sha1 is None or func_name is None: return 0 if self.Gen.CallGenBase.CexportSha1Arr is None: self.Gen.CallGenBase.CexportSha1Arr = CompilerFactory.factory.PermPool.alloc(_CEXPORT_MAX * 17) @@ -490,7 +539,7 @@ novirtual class CallGenBase: def is_cexport_func(self, sha1: str, func_name: str) -> int: - """Whether (sha1, func_name) is a CExport function (reads the Gen.CallGenBase source of truth), falling back to the vpi return annotation on a registry miss.""" + """Whether (sha1, func_name) is a ExportT function (reads the Gen.CallGenBase source of truth), falling back to the vpi return annotation on a registry miss.""" if self.Gen is None or sha1 is None or func_name is None: return 0 if self.Gen.CallGenBase.CexportSha1Arr is not None: for i in range(int(self.Gen.CallGenBase.CexportCount)): @@ -502,7 +551,7 @@ novirtual class CallGenBase: # RegisterStaticSymbol / IsStaticSymbol - static symbol registry: the four - # syntaxes all normalize to t.CStatic; meaning = no cross-module use. + # syntaxes all normalize to t.StaticT; meaning = no cross-module use. def RegisterStaticSymbol(self, sha1: str, name: str) -> int: """Register a static symbol (defining module SHA1 + symbol name); 1=registered (idempotent) / 0=failure""" if self.Gen is None or sha1 is None or name is None: return 0 @@ -544,20 +593,25 @@ novirtual class CallGenBase: def _is_cexport_func_from_vpi(self, sha1: str, func_name: str) -> int: - """The vpi return annotation contains t.CExport, so the defining side uses a bare name (no sha1 prefix cross-module).""" + """Whether the vpi declares a bare-name function (`export def` / `extern def` / legacy ExportT).""" if sha1 is None or func_name is None: return 0 if self.Gen is None or self.Gen.CallGenBase.TempDir is None or self.Gen.CallGenBase.TempDirLen <= 0: return 0 VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir) + # New vpi carries the storage marker as the `export`/`extern` prefix, not in the + # return annotation; without this, cross-module calls mangle the name and link fails. + if VpiReader._lookup_vpi_bare(sha1, None, func_name) != 0: return 1 cexp_annot: str = VpiReader._lookup_vpi_ret_annot(sha1, None, func_name) if cexp_annot is None: return 0 - if string.strstr(cexp_annot, "CExport") is not None: return 1 + if string.strstr(cexp_annot, "ExportT") is not None: return 1 return 0 def _is_state_func_from_vpi(self, pool: memhub.MemBuddy*, sha1: str, func_name: str) -> int: - """The vpi return annotation contains t.State, an external C function marker, so bare names are used cross-module to avoid undefined symbols.""" + """Whether the vpi declares a bare-name external C function (`extern def` / legacy State).""" if pool is None or sha1 is None or func_name is None: return 0 if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: VpiReader._ensure_vpi_ret_annot_loaded(sha1, self.Gen.CallGenBase.TempDir) + # `extern def` prefix (current vpi) => bare C symbol name. + if VpiReader._lookup_vpi_bare(sha1, None, func_name) != 0: return 1 state_annot: str = VpiReader._lookup_vpi_ret_annot(sha1, None, func_name) if state_annot is None: return 0 # ret annotation contains "State" -> external C function @@ -783,8 +837,118 @@ novirtual class CallGenBase: # _lookup_method_ret_ty_via_vpi: resolve method return type from the vpi annotation cache - # ============================================================ - def _lookup_method_ret_ty_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str) -> mlirlite.LLVMType*: + # ============================================================ + # _resolve_vpi_annot_direct: 直接解析 vpi 注解里的常见写法 + # `X*` / `X**` / `module.Class` / 裸类名 / 内建名 / typedef。 + # 只用最基础的字符串操作,不依赖 VpiReader 的多分支分段解析器:自举时该解析器可能 + # 误编译,让一个已注册的类解析失败。无法判定时返回 None,由后续完整解析器继续处理。 + def _resolve_vpi_annot_direct(self, pool: memhub.MemBuddy*, annot: str) -> mlirlite.LLVMType*: + """解析 vpi 注解里的指针写法:`X*` / `X**` / `const X*` / `module.Class`。 + 不递归:先把 `const` / `volatile` 前缀剥掉,再统计尾部 `*` 的层数, + 最后只做一次类/枚举/typedef 查找并按层数包 Ptr。""" + if annot is None or pool is None: return None + a_len: sizet = string.strlen(annot) + st_i: sizet = 0 + end_i: sizet = a_len + while st_i < end_i and annot[st_i] == ' ': st_i += 1 + while end_i > st_i and annot[end_i - 1] == ' ': end_i -= 1 + # 存储修饰符不改变 LLVM 类型 + while True: + if end_i - st_i >= 6 and string.strncmp(annot + st_i, "const ", 6) == 0: + st_i += 6 + elif end_i - st_i >= 9 and string.strncmp(annot + st_i, "volatile ", 9) == 0: + st_i += 9 + else: break + while st_i < end_i and annot[st_i] == ' ': st_i += 1 + ptr_layers: int = 0 + # 尾部 '*' 逐层计入(`X**` = 两层) + while end_i > st_i and annot[end_i - 1] == '*': + ptr_layers += 1 + end_i -= 1 + while end_i > st_i and annot[end_i - 1] == ' ': end_i -= 1 + if end_i <= st_i: return None + nm_buf: bytes = stdlib.malloc(end_i - st_i + 1) + if nm_buf is None: return None + string.strncpy(nm_buf, annot + st_i, end_i - st_i) + nm_buf[end_i - st_i] = '\0' + dot_n: str = string.strrchr(nm_buf, '.') + cls_n: str = nm_buf + if dot_n is not None: cls_n = dot_n + 1 + base_ty: mlirlite.LLVMType* = GenApi.get_struct_type(cls_n) + if base_ty is None: base_ty = GenApi.get_enum_base_type(cls_n) + if base_ty is None: base_ty = GenApi.resolve_typedef_or_map(pool, cls_n, None, None) + stdlib.free(nm_buf) + if base_ty is None: return None + for _pi in range(ptr_layers): base_ty = mlirlite.Ptr(pool, base_ty) + return base_ty + + # _resolve_generic_annot_str - resolve a vpi annotation that instantiates a generic class + # (`GSList[Param]*`, `dict[str, X]*`) by specializing it on demand. VpiReader's string + # resolver handles markers/named types only, so without this a generic return type falls + # through to the i32 placeholder (现场: mlirlite.function_get_params -> GSList[Param]*). + def _resolve_generic_annot_str(self, trans: HT.Translator*, pool: memhub.MemBuddy*, annot: str) -> mlirlite.LLVMType*: + """Specialize `Base[Arg, ...]` (+ trailing '*') from an annotation string; None if not applicable.""" + if trans is None or pool is None or annot is None: return None + a_len: sizet = string.strlen(annot) + st_i: sizet = 0 + end_i: sizet = a_len + while st_i < end_i and annot[st_i] == ' ': st_i += 1 + while end_i > st_i and annot[end_i - 1] == ' ': end_i -= 1 + while True: + if end_i - st_i >= 6 and string.strncmp(annot + st_i, "const ", 6) == 0: st_i += 6 + elif end_i - st_i >= 9 and string.strncmp(annot + st_i, "volatile ", 9) == 0: st_i += 9 + else: break + while st_i < end_i and annot[st_i] == ' ': st_i += 1 + ptr_layers: int = 0 + while end_i > st_i and annot[end_i - 1] == '*': + ptr_layers += 1 + end_i -= 1 + while end_i > st_i and annot[end_i - 1] == ' ': end_i -= 1 + if end_i <= st_i + 2: return None + if annot[end_i - 1] != ']': return None + lb: sizet = st_i + while lb < end_i and annot[lb] != '[': lb += 1 + if lb >= end_i - 1: return None + base_len: sizet = lb - st_i + if base_len == 0: return None + base_buf: str = pool.alloc(base_len + 1) + if base_buf is None: return None + string.strncpy(base_buf, annot + st_i, base_len) + base_buf[base_len] = '\0' + ta_list: list[str]* = list[str](pool, 4) + if ta_list is None: return None + arg_lim: sizet = end_i - 1 + ai: sizet = lb + 1 + depth_g: int = 0 + seg_s: sizet = ai + while ai <= arg_lim: + if ai == arg_lim or (annot[ai] == ',' and depth_g == 0): + seg_e: sizet = ai + while seg_s < seg_e and annot[seg_s] == ' ': seg_s += 1 + while seg_e > seg_s and annot[seg_e - 1] == ' ': seg_e -= 1 + if seg_e > seg_s: + ab: str = pool.alloc(seg_e - seg_s + 1) + if ab is None: return None + string.strncpy(ab, annot + seg_s, seg_e - seg_s) + ab[seg_e - seg_s] = '\0' + ta_list.append(ab) + seg_s = ai + 1 + elif annot[ai] == '[': depth_g += 1 + elif annot[ai] == ']': depth_g -= 1 + ai += 1 + if len(ta_list) == 0: return None + spec: str = self.Gen.ClassGenGeneric._specialize_generic_class(trans, base_buf, ta_list) + if spec is None: return None + ty: mlirlite.LLVMType* = GenApi.get_struct_type(spec) + if ty is None: ty = GenApi.get_enum_base_type(spec) + if ty is None: return None + k_l: int = 0 + while k_l < ptr_layers: + ty = mlirlite.Ptr(pool, ty) + k_l += 1 + return ty + + def _lookup_method_ret_ty_via_vpi(self, pool: memhub.MemBuddy*, cls_sha1: str, cls_name: str, method_name: str, trans: HT.Translator* = None) -> mlirlite.LLVMType*: """Get a method return type from the vpi stub annotation cache; None on miss""" if pool is None or cls_sha1 is None or method_name is None: return None # lazy import inside the function: VpiReader imports HandlesExprCall, a top-level import would cycle @@ -795,7 +959,21 @@ novirtual class CallGenBase: if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: VpiReader._ensure_vpi_ret_annot_loaded(cls_sha1, self.Gen.CallGenBase.TempDir) annot: str = VpiReader._lookup_vpi_ret_annot(cls_sha1, cls_name, method_name) if annot is None: return None - return VpiReader._resolve_vpi_ret_annot_str(pool, annot, i32_ty, i8_ptr_ty) + # Fast path for pointer spellings (`X*` / `X**` / `const X*`): plain character ops, no + # recursive subscript unwrapping, so a registered class is always found for these forms. + # Restricted to pointer forms; bare / sha1-prefixed names keep the full parser's + # cross-module disambiguation. + a_l0: sizet = string.strlen(annot) + is_ptr_form: int = 0 + if a_l0 > 0 and annot[a_l0 - 1] == '*': is_ptr_form = 1 + if is_ptr_form != 0: + direct_ty: mlirlite.LLVMType* = self._resolve_vpi_annot_direct(pool, annot) + if direct_ty is not None: return direct_ty + # Typedef-aware: the vpi keeps the source spelling, so a return annotation spelled with + # a module-local alias (`posix.getpid -> pid_t`) must be expanded before resolving. + ret_ty: mlirlite.LLVMType* = VpiReader._resolve_vpi_annot_with_typedefs(pool, cls_sha1, annot, i32_ty, i8_ptr_ty) + if ret_ty is None: ret_ty = self._resolve_generic_annot_str(trans, pool, annot) + return ret_ty def _vpi_escape_packed(self, sha1: str, cls_name: str, method_name: str) -> int: """跨模块逃逸摘要查询(v3):按 (定义模块 sha1, Class?, method) 查 .vpi 缓存;0=无摘要。 @@ -910,12 +1088,13 @@ novirtual class CallGenBase: return VpiReader._lookup_vpi_param_annots(cls_sha1, cls_name, method_name) # _vpi_resolve_param_annot_ty: parse a vpi param annotation segment into LLVMType - def _vpi_resolve_param_annot_ty(self, pool: memhub.MemBuddy*, seg: str) -> mlirlite.LLVMType*: + def _vpi_resolve_param_annot_ty(self, pool: memhub.MemBuddy*, seg: str, src_sha1: str = None) -> mlirlite.LLVMType*: """Parse a single vpi parameter annotation segment into an LLVMType; None on failure""" if pool is None or seg is None: return None i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool) i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool)) - return VpiReader._resolve_vpi_ret_annot_str(pool, seg, i32_ty, i8_ptr_ty) + # Typedef-aware: params may be spelled with the defining module's alias (`waitpid(pid: pid_t)`). + return VpiReader._resolve_vpi_annot_with_typedefs(pool, src_sha1, seg, i32_ty, i8_ptr_ty) # _special_method_ret_ty: fixed return types for compiler built-in special methods @@ -934,7 +1113,7 @@ novirtual class CallGenBase: # _fail_method_ret_ty: ERROR + abort when the method return type cannot be resolved # ============================================================ - def _fail_method_ret_ty(self, method_name: str, node: ast.AST* = None, cls: str = None, anc: str = None, sha1: str = None) -> mlirlite.LLVMType*: + def _fail_method_ret_ty(self, method_name: str, node: ast.AST* = None, cls: str = None, anc: str = None, sha1: str = None, pool: memhub.MemBuddy* = None) -> mlirlite.LLVMType*: """Fail fast when a method return type cannot be resolved precisely (module function, text.ll define, then vpi cache); never default to i32.""" # NOTE: VLogger.error terminates the process in batch mode (sys.exit(1)), so the whole # report must be a SINGLE call -- anything after it is dead code (this is why a separate @@ -960,12 +1139,41 @@ novirtual class CallGenBase: if st_sha_d is not None: c_st_sha = st_sha_d c_tll: int = 0 c_vpi: str = "-" + c_tail_ty: str = "-" + c_annot_res: str = "-" if probe_sha is not None: c_tll = self._dep_text_ll_exists(None, probe_sha) if self.Gen.CallGenBase.TempDir is not None and self.Gen.CallGenBase.TempDirLen > 0: VpiReader._ensure_vpi_ret_annot_loaded(probe_sha, self.Gen.CallGenBase.TempDir) annot_d: str = VpiReader._lookup_vpi_ret_annot(probe_sha, cls, method_name) - if annot_d is not None: c_vpi = annot_d + if annot_d is not None: + c_vpi = annot_d + # Probe the string resolver here (caller's pool when available, same annotation) + # so the report separates "class missing" from "resolver returned nothing". + pb_d: memhub.MemBuddy* = pool + if pb_d is None: pb_d = CompilerFactory.factory.PermPool + i32_p: mlirlite.LLVMType* = mlirlite.Int32(pb_d) + i8_p: mlirlite.LLVMType* = mlirlite.Int8(pb_d) + i8p_p: mlirlite.LLVMType* = mlirlite.Ptr(pb_d, i8_p) + rv_p: mlirlite.LLVMType* = VpiReader._resolve_vpi_ret_annot_str(pb_d, annot_d, i32_p, i8p_p) + if rv_p is None: c_annot_res = "none" + else: c_annot_res = "ok" + # Annotate the tail class of the raw annotation: "missing" = the class was never + # registered (upstream bug); "struct"/"enum" = registered but the string resolver + # still failed (resolver bug). Distinguishes the two without guessing. + dot_d: str = string.strrchr(annot_d, '.') + if dot_d is not None: + tail_len_d: sizet = string.strlen(dot_d + 1) + tb_d: bytes = stdlib.malloc(tail_len_d + 1) + if tb_d is not None: + rb_d: str = string.strchr(dot_d + 1, ']') + if rb_d is not None: tail_len_d = sizet(uint64(rb_d) - uint64(dot_d + 1)) + string.strncpy(tb_d, dot_d + 1, tail_len_d) + tb_d[tail_len_d] = '\0' + if GenApi.get_struct_type(tb_d) is not None: c_tail_ty = "struct" + elif GenApi.get_enum_base_type(tb_d) is not None: c_tail_ty = "enum" + else: c_tail_ty = "missing" + stdlib.free(tb_d) # Same-named struct entries: find_struct returns the FIRST exact match, so an earlier bogus # registration (alias/forward/stale, different ModuleSha1) silently shadows the real one. dup_n_d: int = 0 @@ -993,7 +1201,7 @@ novirtual class CallGenBase: if cur_file_d is None: cur_file_d = "-" cur_def_d: str = self.Gen.StructGen.CurDefModuleSha1 if cur_def_d is None: cur_def_d = "-" - VLogger.error(f"method return type cannot be obtained precisely (function/text.ll/vpi stub all missed): {method_name} | [diag] cls={c_cls} struct_sha1={c_st_sha} lookup_anc={c_anc} lookup_sha1={c_sha} text_ll={c_tll} vpi_annot={c_vpi} same_name_entries={dup_n_d} last_same_sha1={dup_last_d} entry_ty={dup_ty_nm_d} cur_file_sha1={cur_file_d} cur_def_sha1={cur_def_d}", "HandlesExprCall") + VLogger.error(f"method return type cannot be obtained precisely (function/text.ll/vpi stub all missed): {method_name} | [diag] cls={c_cls} struct_sha1={c_st_sha} lookup_anc={c_anc} lookup_sha1={c_sha} text_ll={c_tll} vpi_annot={c_vpi} tail_ty={c_tail_ty} annot_res={c_annot_res} same_name_entries={dup_n_d} last_same_sha1={dup_last_d} entry_ty={dup_ty_nm_d} cur_file_sha1={cur_file_d} cur_def_sha1={cur_def_d}", "HandlesExprCall") self.Gen.TypeGenErr.fatal_error(node, f"method return type cannot be obtained precisely; defaulting to i32 would truncate pointers/i64: {method_name}", "TypeError") return None diff --git a/App/lib/core/Gen/Call/CallGenDispatch.vp b/App/lib/core/Gen/Call/CallGenDispatch.vp index e6ca04a..1dfe3f8 100644 --- a/App/lib/core/Gen/Call/CallGenDispatch.vp +++ b/App/lib/core/Gen/Call/CallGenDispatch.vp @@ -10,7 +10,7 @@ import string import stdlib import sys import viperlib -import win32.fileio as fileio +import io as fileio import lib.core.VLogger as VLogger import lib.core.Gen.Translator as HT import lib.core.Gen.Type.TypeSpec as TSpec @@ -221,11 +221,25 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase): return None def _tc_h2_c_special(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*, func_name: str, cargs: list[ast.AST*]*, can: sizet, hit_box: rawptr = None) -> mlirlite.Value*: - """Special c.Deref/Asm/LLVMIR calls; marks hit_box for void calls, else returns None.""" + """Special __asm__/__llvmir__ calls; marks hit_box for void calls, else returns None.""" + # Retired c.* pointer builtins: fail fast with the replacement syntax. + if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute: + ra_c: ast.Attribute* = ast.Attribute*(cl.func) + if ra_c.value is not None and ra_c.value.kind() == ast.ASTKind.Name and ra_c.attr is not None: + rn_c: ast.Name* = ast.Name*(ra_c.value) + if rn_c.id is not None and string.strcmp(rn_c.id, "c") == 0: + if string.strcmp(ra_c.attr, "Deref") == 0 or string.strcmp(ra_c.attr, "DerefAs") == 0 \ + or string.strcmp(ra_c.attr, "Set") == 0 or string.strcmp(ra_c.attr, "Addr") == 0 \ + or string.strcmp(ra_c.attr, "Load") == 0: + self.Gen.TypeGenErr.fatal_error(cl, f"c.{ra_c.attr} was removed; use `*p` (read), `*p = v` (write) or `&v` (address-of)", "NameError") + return None # __asm__(...) / __llvmir__(...) sugar: bare calls equal c.Asm(...) / c.LLVMIR(...). Must # intercept before the c.* attribute branch, since the bare cl.func is a Name. if func_name is not None and cl.func is not None and cl.func.kind() == ast.ASTKind.Name: bare_nm: ast.Name* = ast.Name*(cl.func) + if bare_nm.id is not None and (string.strcmp(bare_nm.id, "Deref") == 0 or string.strcmp(bare_nm.id, "DerefAs") == 0): + self.Gen.TypeGenErr.fatal_error(cl, f"{bare_nm.id} was removed; use `*p` (read), `*p = v` (write) or `&v` (address-of)", "NameError") + return None if bare_nm.id is not None and string.strcmp(bare_nm.id, "defined") == 0: # defined(NAME) is a compile-time predicate valid only inside a cif/celif condition; # it is evaluated there and never lowered as a runtime call. @@ -377,7 +391,7 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase): mt_arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(0), funcs_ptr, func_count, trans, mod_td_ty) if mt_arg_val is not None: return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, mt_arg_val, mod_td_ty, cargs.get(0), trans) - # Bare typedef casts (BYTEPTR, HANDLE, CHARPTR, ...): all CTypedef names and builtin primitives + # Bare typedef casts (BYTEPTR, HANDLE, CHARPTR, ...): all TypedefT names and builtin primitives # are castable. In generic contexts T(x) first resolves T, then goes through resolve_typedef_or_map. if cl.func is not None and cl.func.kind() == ast.ASTKind.Name: bare_fn_name: str = self.Gen.TypeGenBase.resolve_generic_type_name(func_name) @@ -387,7 +401,7 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase): if string.strcmp(bare_fn_name, "double") == 0: bare_ty = mlirlite.Double(pool) elif string.strcmp(bare_fn_name, "float") == 0: bare_ty = mlirlite.Float(pool) else: - # Prefer the CTypedef dynamic table (library typedef casts); on miss fall back to + # Prefer the TypedefT dynamic table (library typedef casts); on miss fall back to # map_builtin_type. bare_imp_c: str = None bare_fi_c: str = None @@ -412,10 +426,10 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase): bare_arg2_node: ast.AST* = cargs.get(1) bare_arg2_is_cptr: int = 0 if bare_arg2_node is not None: - # Builtin pointer marker: CPtr / rawptr + # Builtin pointer marker: PtrT / rawptr if bare_arg2_node.kind() == ast.ASTKind.Name: bare_arg2_bn: ast.Name* = ast.Name*(bare_arg2_node) - if bare_arg2_bn.id is not None and (string.strcmp(bare_arg2_bn.id, "CPtr") == 0 or string.strcmp(bare_arg2_bn.id, "rawptr") == 0): bare_arg2_is_cptr = 1 + if bare_arg2_bn.id is not None and (string.strcmp(bare_arg2_bn.id, "PtrT") == 0 or string.strcmp(bare_arg2_bn.id, "rawptr") == 0): bare_arg2_is_cptr = 1 if bare_arg2_is_cptr != 0: # CVoid* -> i8* (void* is represented as i8* in LLVM) match bare_ty: @@ -531,7 +545,7 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase): elif string.strcmp(sz_cls_name, "CChar") == 0: sz_builtin = 1 elif string.strcmp(sz_cls_name, "CDouble") == 0: sz_builtin = 8 elif string.strcmp(sz_cls_name, "CFloat") == 0: sz_builtin = 4 - elif string.strcmp(sz_cls_name, "CPtr") == 0: sz_builtin = 8 + elif string.strcmp(sz_cls_name, "PtrT") == 0: sz_builtin = 8 elif string.strcmp(sz_cls_name, "CSizeT") == 0: sz_builtin = 8 elif string.strcmp(sz_cls_name, "CBool") == 0: sz_builtin = 1 elif string.strcmp(sz_cls_name, "bool") == 0: sz_builtin = 1 @@ -583,7 +597,7 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase): sz_resolved_name = spec_sz sz_resolved_ty = self.Gen.StructGen.get_struct_type(spec_sz) # Fallback 2: instance receiver self.__sizeof__() / obj.__sizeof__() uses the actual - # value type; array receivers (t.CArray[T,N]) size as N*elem_size. + # value type; array receivers (t.ArrayT[T,N]) size as N*elem_size. if sz_resolved_ty is None: inst_sz_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, sz_at.value, None, 0, trans) if inst_sz_val is not None and inst_sz_val.Ty is not None: @@ -622,7 +636,7 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase): obj_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, len_at.value, funcs_ptr, func_count, trans) if obj_val is not None and obj_val.Ty is not None: # Defensive check: obj_val must be a pointer for GEP. If translate_value returns - # i32 (misparsed CDefine constant, module-attr fallback), llc reports a non-pointer GEP base. + # i32 (misparsed DefineT constant, module-attr fallback), llc reports a non-pointer GEP base. if self.Gen.ExprGenBase.is_ptr_type(obj_val.Ty) == 0: # Type inference failed (not a pointer/struct): fail fast rather than return 0 self.Gen.TypeGenErr.fatal_error(len_at.value, "len() argument type inference failed (not a pointer/struct)", "TypeError") @@ -641,7 +655,7 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase): if len_arg_node is not None: len_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, len_arg_node, None, 0, trans) if len_val is not None: - # len(array): t.CArray[T, N] -> constant N (TPC _HandleLenLlvm ArrayType.count). + # len(array): t.ArrayT[T, N] -> constant N (TPC _HandleLenLlvm ArrayType.count). # Array variables come back as Ptr([N x T]) or [N x T]; recognize both. len_is_arr: int = 0 len_arr_n: int = 0 @@ -714,7 +728,7 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase): return mlirlite.build_call(builder, len_mangled_x, len_self_x, 1, len_ret_x, 0) # fail-fast: the class has no __len__ (compile-time error, matching TPC # HandlesExprBuiltin._FailFastLenUnsupported wording). - self.Gen.TypeGenErr.fatal_error(len_arg_node, f"len() argument type '{len_cls_nm}' has no __len__ method. len() only supports fixed-size arrays/array pointers (t.CArray[T, N]), i8* (str/bytes), or structs with __len__; non-i8 bare pointers/scalar pointers have no length semantics: use str/bytes for strings, record the length yourself when the element count is needed, or use rawptr[Element] and maintain the element count explicitly", "TypeError") + self.Gen.TypeGenErr.fatal_error(len_arg_node, f"len() argument type '{len_cls_nm}' has no __len__ method. len() only supports fixed-size arrays/array pointers (t.ArrayT[T, N]), i8* (str/bytes), or structs with __len__; non-i8 bare pointers/scalar pointers have no length semantics: use str/bytes for strings, record the length yourself when the element count is needed, or use rawptr[Element] and maintain the element count explicitly", "TypeError") # Non-struct: str (Ptr(Int8)) uses strlen; other types fail fast len_arg_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(len_val) len_is_str: int = 0 @@ -747,7 +761,7 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase): return mlirlite.build_call(builder, "strlen", len_coerced, 1, i64_ty_ln2, 0) # fail-fast: not a string/array/object, so length is unavailable (matching TPC # HandlesExprBuiltin._FailFastLenUnsupported wording). - self.Gen.TypeGenErr.fatal_error(len_arg_node, "len() argument type not supported (not a string/array/object). len() only supports fixed-size arrays/array pointers (t.CArray[T, N]), i8* (str/bytes), or structs with __len__; non-i8 bare pointers/scalar pointers have no length semantics: use str/bytes for strings, record the length yourself when the element count is needed, or use rawptr[Element] and maintain the element count explicitly", "TypeError") + self.Gen.TypeGenErr.fatal_error(len_arg_node, "len() argument type not supported (not a string/array/object). len() only supports fixed-size arrays/array pointers (t.ArrayT[T, N]), i8* (str/bytes), or structs with __len__; non-i8 bare pointers/scalar pointers have no length semantics: use str/bytes for strings, record the length yourself when the element count is needed, or use rawptr[Element] and maintain the element count explicitly", "TypeError") # Detect isinstance(): runtime vtable subclass-set comparison. obj's head vtable pointer must # be in {Cls and all subclass vtables}; NoVTable targets fall back to a compile-time check. @@ -983,6 +997,16 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase): if nl_obj is not None and nl_obj.Ty is not None: nl_deref: mlirlite.Value* = self.Gen.ExprGenBase._deref_if_ptr_ptr(builder, nl_obj) if nl_deref is None: nl_deref = nl_obj + # 值语义捕获(`entry: vpui.Entry`):闭包槽里存的是结构体本身, + # load 出来是**值**。方法接收者必须是槽地址(可写、可作指针), + # 把结构体值直接当接收者会让虚调用 build_bitcast(struct -> ptr), + # 那是非法 IR(mlir-opt: operand #0 must be LLVM-compatible + # non-aggregate type)。与本地结构体变量返回 alloca 的规则对齐。 + if nl_deref.Ty is not None: + nlk: int = nl_deref.Ty.kind() + if nlk == mlirlite.LLVMType.Struct or nlk == mlirlite.LLVMType.Array: + nl_slot: mlirlite.Value* = self.Gen.StmtGenDecl.get_nonlocal_var_ptr(trans, method_obj_nm.id) + if nl_slot is not None: nl_deref = nl_slot nl_struct: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(nl_deref) if nl_struct is not None: nl_cls: str = self.Gen.StructGen.get_class_name_by_type(pool, nl_struct) diff --git a/App/lib/core/Gen/Expr/ExprGenCdef.vp b/App/lib/core/Gen/Expr/ExprGenCdef.vp index 58df8c3..ec60d4c 100644 --- a/App/lib/core/Gen/Expr/ExprGenCdef.vp +++ b/App/lib/core/Gen/Expr/ExprGenCdef.vp @@ -6,7 +6,7 @@ import string import stdio import stdlib import viperlib -import win32.fileio as fileio +import io as fileio import lib.Stub.StubMerger as StubMerger import lib.core.VLogger as VLogger import lib.Projectrans.Config as Config @@ -17,9 +17,9 @@ import lib.Stub.StubMerger.StubMergerStore as StubMergerStore # ============================================================ -# ExprGenCdef - ExprGenMixin split (stage D): cross-module CDefine lookup +# ExprGenCdef - ExprGenMixin split (stage D): cross-module DefineT lookup novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): - """Cross-module CDefine lookup""" + """Cross-module DefineT lookup""" # _build_source_path_from_sha1: build a source .vp/.py path from SHA1 via the StubMerger # global store (.vpi missing -> source .py); "lib/..." -> SourceDir, else IncludesDir. @@ -43,7 +43,7 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): idx_s = i break if idx_s < 0: return None - # get the rel path (MAX_REL_PATH_LEN=256, avoids cross-module CDefine issues) + # get the rel path (MAX_REL_PATH_LEN=256, avoids cross-module DefineT issues) MAX_RPL: sizet = 256 ridx: sizet = sizet(idx_s) * MAX_RPL rel_path: str = rel_arr_ptr + ridx @@ -112,18 +112,18 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): if ok_before != 0 and ok_after != 0: return 1 i += 1 return 0 - # _lookup_cross_module_cdefine - cross-module CDefine lookup: resolve the source module - # from from_imports, parse its pyi "NAME: t.CDefine = value", then cache in the current module. + # _lookup_cross_module_cdefine - cross-module DefineT lookup: resolve the source module + # from from_imports, parse its pyi "NAME: t.DefineT = value", then cache in the current module. def _lookup_cross_module_cdefine(self, pool: memhub.MemBuddy*, name: str, from_imports: str, imported_modules: str = None, depth: int = 0, node: ast.AST* = None) -> int64: - """Look up a CDefine constant across modules, returning its 64-bit value or -1""" + """Look up a DefineT constant across modules, returning its 64-bit value or -1""" # recursion-depth guard: re-export chains + imported_modules walk can loop and OOM; # cap at 32 to fail fast on cyclic imports (error carries node line + module chain). if depth > 32: err_buf: str = pool.alloc(256) if err_buf is not None: - viperlib.snprintf(err_buf, 256, "cross-module CDefine lookup recursion too deep (suspected module import/re-export cycle): %s (depth=%d fi=%s)", name, depth, from_imports if from_imports is not None else "(nil)") + viperlib.snprintf(err_buf, 256, "cross-module DefineT lookup recursion too deep (suspected module import/re-export cycle): %s (depth=%d fi=%s)", name, depth, from_imports if from_imports is not None else "(nil)") self.Gen.TypeGenErr.fatal_error(node, err_buf, "InternalCompilerError") - self.Gen.TypeGenErr.fatal_error(node, "cross-module CDefine lookup recursion too deep (suspected module import/re-export cycle)", "InternalCompilerError") + self.Gen.TypeGenErr.fatal_error(node, "cross-module DefineT lookup recursion too deep (suspected module import/re-export cycle)", "InternalCompilerError") # clear the found flag at entry so every early return reports "not found", # avoiding a stale success flag left over from a previous recursive call. self.Gen.TypeGenCDef.set_cdefine_found(0) @@ -131,7 +131,7 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): if from_imports is None: from_imports = "" # 1. resolve the module name for `name` from from_imports; try exact match first - # (allow_star_fallback=0) so a star import cannot mislead CDefine constants. + # (allow_star_fallback=0) so a star import cannot mislead DefineT constants. mod_name_raw: str = self.Gen.ImportsGen.lookup_from_import(from_imports, name, 0) if mod_name_raw is None: # exact match failed: try the star import fallback @@ -151,7 +151,7 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): base_mod = mb_buf while base_mod is not None and base_mod[0] == '.': base_mod = base_mod + 1 - # cross-module CDefine cache (perm_pool, reused across files): key = "name@srcModule" + # cross-module DefineT cache (perm_pool, reused across files): key = "name@srcModule" # keeps modules isolated so same-name constants from different modules do not cross. xkey_buf: str = None if base_mod is not None: @@ -240,36 +240,64 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): if bytes_read < 10: need_py_fallback = 1 else: if bytes_read < PYI_READ_BUF_SIZE: pyi_buf[bytes_read] = 0 - # vpi stubs may declare a CDefine name without a value; the real value lives + # vpi stubs may declare a DefineT name without a value; the real value lives # in the source .vp/.py, so trust vpi only when the target name's line has a value. cd_sig: int = 0 name_has_val: int = 0 cs_name_len: sizet = string.strlen(name) cs_total: sizet = string.strlen(pyi_buf) cs_i: sizet = 0 - while cs_i + 9 < cs_total: - if (pyi_buf[cs_i] == 't' and pyi_buf[cs_i + 1] == '.' and pyi_buf[cs_i + 2] == 'C' and pyi_buf[cs_i + 3] == 'D' and pyi_buf[cs_i + 4] == 'e' and pyi_buf[cs_i + 5] == 'f' and pyi_buf[cs_i + 6] == 'i' and pyi_buf[cs_i + 7] == 'n' and pyi_buf[cs_i + 8] == 'e'): - # check whether the rest of the line has '=' (only valued decls match in vpi) - cd_eq: int = 0 - cs_j: sizet = cs_i + 9 - while cs_j < cs_total and pyi_buf[cs_j] != '\n': - if pyi_buf[cs_j] == '=': - cd_eq = 1 + while cs_i < cs_total: + cs_ls: sizet = cs_i + while cs_ls > 0 and pyi_buf[cs_ls - 1] != '\n': cs_ls -= 1 + cs_le: sizet = cs_i + while cs_le < cs_total and pyi_buf[cs_le] != '\n': cs_le += 1 + cs_off: sizet = cs_ls + while cs_off < cs_le and pyi_buf[cs_off] == ' ': cs_off += 1 + # New source-style line: `define NAME[: T] = value` + if cs_le - cs_off >= 7 and string.strncmp(pyi_buf + cs_off, "define ", 7) == 0: + cs_ns: sizet = cs_off + 7 + while cs_ns < cs_le and pyi_buf[cs_ns] == ' ': cs_ns += 1 + if cs_le - cs_ns >= cs_name_len + 1: + cs_nm_ok: int = 1 + for ki2 in range(int(cs_name_len)): + if pyi_buf[cs_ns + ki2] != name[ki2]: + cs_nm_ok = 0 + break + if cs_nm_ok != 0: + cs_next: char = pyi_buf[cs_ns + cs_name_len] + if cs_next == ':' or cs_next == ' ' or cs_next == '=': + cs_eq: int = 0 + cs_j: sizet = cs_ns + cs_name_len + while cs_j < cs_le: + if pyi_buf[cs_j] == '=': + cs_eq = 1 + break + cs_j += 1 + if cs_eq != 0: + cd_sig = 1 + name_has_val = 1 + # Legacy marker line: `NAME: ... DefineT ... = value` + elif cs_le - cs_off >= cs_name_len + 1: + cs_nm_ok2: int = 1 + for ki2b in range(int(cs_name_len)): + if pyi_buf[cs_off + ki2b] != name[ki2b]: + cs_nm_ok2 = 0 break - cs_j += 1 - if cd_eq != 0: - cd_sig = 1 - # locate line start; check it begins with "name:" (target has a value) - cs_line_start: sizet = cs_i - while cs_line_start > 0 and pyi_buf[cs_line_start - 1] != '\n': cs_line_start -= 1 - if cs_i - cs_line_start >= cs_name_len + 1: - nm_ok: int = 1 - for ki2 in range(int(cs_name_len)): - if pyi_buf[cs_line_start + ki2] != name[ki2]: - nm_ok = 0 - break - if nm_ok == 1 and pyi_buf[cs_line_start + cs_name_len] == ':': name_has_val = 1 - cs_i += 1 + if cs_nm_ok2 != 0 and pyi_buf[cs_off + cs_name_len] == ':': + cs_marker: int = 0 + cs_eq2: int = 0 + cs_j2: sizet = cs_off + cs_name_len + 1 + while cs_j2 < cs_le: + if pyi_buf[cs_j2] == '=': + cs_eq2 = 1 + break + if cs_j2 + 9 <= cs_le and string.strncmp(pyi_buf + cs_j2, "t.DefineT", 9) == 0: cs_marker = 1 + cs_j2 += 1 + if cs_marker != 0 and cs_eq2 != 0: + cd_sig = 1 + name_has_val = 1 + cs_i = cs_le + 1 if cd_sig == 0 or name_has_val == 0: need_py_fallback = 1 stdlib.free(pyi_path) @@ -281,7 +309,7 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): # report and abort instead of silently returning -1 (avoids bad @UNDEFINED refs). err_buf: str = pool.alloc_tagged(4096, memhub.DETSITE_EXPRGENCDEF_263) if err_buf is not None: - viperlib.snprintf(err_buf, 512, "cross-module CDefine lookup failed: SHA1 %s is not in the store (base_mod=%s name=%s). " "Phase1/Phase2 did not correctly populate PopulateSha1MapStore/AppendToSha1MapStore", sha1, base_mod, name) + viperlib.snprintf(err_buf, 512, "cross-module DefineT lookup failed: SHA1 %s is not in the store (base_mod=%s name=%s). " "Phase1/Phase2 did not correctly populate PopulateSha1MapStore/AppendToSha1MapStore", sha1, base_mod, name) VLogger.error(err_buf, "XMOD-CD") stdlib.free(pyi_buf) return -1 @@ -297,8 +325,8 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): return -1 if bytes_read < PYI_READ_BUF_SIZE: pyi_buf[bytes_read] = 0 - # 8. search pyi_buf for the "name: t.CDefine = value" line - # format: NAME: t.CDefine = value + # 8. search pyi_buf for the "name: t.DefineT = value" line + # format: NAME: t.DefineT = value name_len: sizet = string.strlen(name) total_len: sizet = string.strlen(pyi_buf) pos: sizet = 0 @@ -318,32 +346,50 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): if pos < total_len: pos += 1 # skip '\n' - # check whether the line starts with "name:" - if line_len < name_len + 1: continue - match_ok: int = 1 - for ki in range(int(name_len)): - if pyi_buf[line_start + ki] != name[ki]: - match_ok = 0 - break + # Match `define NAME[: T] = value` or legacy `NAME: ... DefineT ... = value`. + match_ok: int = 0 + eq_scan_start: sizet = 0 + line_off: sizet = line_start + while line_off < line_start + line_len and pyi_buf[line_off] == ' ': line_off += 1 + if line_len >= 7 and string.strncmp(pyi_buf + line_off, "define ", 7) == 0: + ns: sizet = line_off + 7 + while ns < line_start + line_len and pyi_buf[ns] == ' ': ns += 1 + if line_start + line_len >= ns + name_len + 1: + match_ok = 1 + for ki in range(int(name_len)): + if pyi_buf[ns + ki] != name[ki]: + match_ok = 0 + break + if match_ok != 0: + nx: char = pyi_buf[ns + name_len] + if nx != ':' and nx != ' ' and nx != '=': match_ok = 0 + if match_ok != 0: eq_scan_start = ns + name_len + elif line_len >= name_len + 1: + match_ok = 1 + for ki_ld in range(int(name_len)): + if pyi_buf[line_start + ki_ld] != name[ki_ld]: + match_ok = 0 + break + if match_ok != 0 and pyi_buf[line_start + name_len] == ':': + has_cdefine: int = 0 + ci: sizet = line_start + name_len + 1 + while ci + 7 <= line_start + line_len: + if (pyi_buf[ci] == 'C' and pyi_buf[ci + 1] == 'D' and pyi_buf[ci + 2] == 'e' and pyi_buf[ci + 3] == 'f' and pyi_buf[ci + 4] == 'i' and pyi_buf[ci + 5] == 'n' and pyi_buf[ci + 6] == 'e'): + has_cdefine = 1 + break + ci += 1 + if has_cdefine != 0: eq_scan_start = line_start + name_len + 1 + else: match_ok = 0 + else: match_ok = 0 if match_ok == 0: continue - if pyi_buf[line_start + name_len] != ':': continue - - # check the line contains the CDefine marker - has_cdefine: int = 0 - ci: sizet = line_start + name_len + 1 - while ci + 7 <= line_start + line_len: - if (pyi_buf[ci] == 'C' and pyi_buf[ci + 1] == 'D' and pyi_buf[ci + 2] == 'e' and pyi_buf[ci + 3] == 'f' and pyi_buf[ci + 4] == 'i' and pyi_buf[ci + 5] == 'n' and pyi_buf[ci + 6] == 'e'): - has_cdefine = 1 - break - ci += 1 - if has_cdefine == 0: continue # find the value after "=" - eq_pos: sizet = line_start + name_len + 1 + eq_pos: sizet = eq_scan_start while eq_pos < line_start + line_len and pyi_buf[eq_pos] != '=': eq_pos += 1 if eq_pos >= line_start + line_len: continue - # 注解段 = [名字冒号后, '='),用于判定声明的浮点宽度(如 `PI: float | t.CDefine`) - annot_start: sizet = line_start + name_len + 1 + # Annotation segment between the name and '=', used for the declared float width. + annot_start: sizet = eq_scan_start + while annot_start < eq_pos and (pyi_buf[annot_start] == ':' or pyi_buf[annot_start] == ' '): annot_start += 1 annot_end: sizet = eq_pos eq_pos += 1 # skip '=' @@ -384,7 +430,7 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): is_name_val: int = 0 is_str_val: int = 0 # string value (e.g. ZLIB_VERSION = "1.3.2"): read inside the quotes; without this, - # cross-module read-back skipped it and reported "CDefine not found". + # cross-module read-back skipped it and reported "DefineT not found". if eq_pos < line_start + line_len and pyi_buf[eq_pos] == '"': is_str_val = 1 eq_pos += 1 # skip opening quote @@ -439,18 +485,18 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): l_is_double: int = 0 ldbl: double = 0.0 if is_str_val != 0: - # string CDefine (e.g. ZLIB_VERSION = "1.3.2"): register the string value + # string DefineT (e.g. ZLIB_VERSION = "1.3.2"): register the string value # (including ""), mark result_found + res_is_str so the tail skips integers. self.Gen.TypeGenCDef.register_cdefine_string(pool, name, val_buf) result_val = 0 result_found = 1 res_is_str = 1 elif is_name_val != 0: - # True/False literal -> 1/0 (TPC treats bool as integer in CDefine context) + # True/False literal -> 1/0 (TPC treats bool as integer in DefineT context) if string.strcmp(val_buf, "True") == 0: lval = 1 elif string.strcmp(val_buf, "False") == 0: lval = 0 else: - # name reference: look up the current CDefine table (e.g. FOREGROUND_COMBO) + # name reference: look up the current DefineT table (e.g. FOREGROUND_COMBO) lval = self.Gen.TypeGenCDef.lookup_cdefine_constant(val_buf) if self.Gen.TypeGenCDef.is_cdefine_found() == 0: # name not registered: cannot evaluate, skip the line @@ -600,7 +646,7 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1 if eq_pos < line_start + line_len and pyi_buf[eq_pos] == ')': eq_pos += 1 if is_float_val != 0: - # float CDefine (e.g. U_M_PI = 3.14159...): cache as double for double + # float DefineT (e.g. U_M_PI = 3.14159...): cache as double for double # constant generation; int part serves the int path (is_cdefine_double flag). # 注解声明为 float 时带上 f32 种类,使用方才能直接出 f32 常量。 res_is_f32 = self._pyi_annot_float_kind(pyi_buf, annot_start, annot_end) @@ -789,11 +835,11 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): result_val = sub_val2 result_found = 1 - # 10. cache into the current module's CDefine table and the cross-module perm_pool; + # 10. cache into the current module's DefineT table and the cross-module perm_pool; # float CDefines are already cached as double, so only integers are written here. if result_found != 0: if res_is_str != 0: - # string CDefine: the value was already registered in the match loop (val_buf is + # string DefineT: the value was already registered in the match loop (val_buf is # the hit line's content, valid across the function); only write the cross cache. if base_mod is not None: ck_len_s: sizet = string.strlen(name) + string.strlen(base_mod) + 2 @@ -832,10 +878,10 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): else: self.Gen.TypeGenCDef.set_cdefine_found(0) return result_val - # _is_cdefine_name_pattern - CDefine names follow ALL_CAPS (A-Z, 0-9, _ with >=1 uppercase); + # _is_cdefine_name_pattern - DefineT names follow ALL_CAPS (A-Z, 0-9, _ with >=1 uppercase); # lowercase keywords/variables won't match, avoiding fail-fast false positives. 1=match, 0=no. def _is_cdefine_name_pattern(self, name: str) -> int: - """Return 1 if name follows the CDefine convention (uppercase letters, digits, underscore)""" + """Return 1 if name follows the DefineT convention (uppercase letters, digits, underscore)""" if name is None: return 0 has_upper: int = 0 i: sizet = 0 @@ -848,7 +894,7 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): # ============================================================ # Translate a variable reference (Name node) -> load def _const_int_for_cdefine(self, pool: memhub.MemBuddy*, name: str, value: int64) -> mlirlite.Value*: - """Materialize a CDefine integer constant as an LLVM constant (TPC TypeConvert aligned) + """Materialize a DefineT integer constant as an LLVM constant (TPC TypeConvert aligned) 位宽优先用 define 的**已知位宽**(右侧显式强转或声明注解,见 register_cdefine_constant 的 bits); 没有位宽记录时按**推断位宽**物化(值的最小可存宽度;0x80000000 → 32)。 @@ -867,7 +913,7 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): return mlirlite.const_int(pool, wmin, self._cdefine_bits_value(value, wmin)) # ============================================================ - # cdefine_use_width_value - materialize CDefine constants at the use-site width (TPC-aligned) + # cdefine_use_width_value - materialize DefineT constants at the use-site width (TPC-aligned) def _cdefine_bits_value(self, value: int64, bits: int) -> int64: """把值按位宽处理:**只有在当前位宽的两种解释下都装不下时才截断**,否则原样保留。 @@ -893,7 +939,7 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase): return value def cdefine_use_width_value(self, pool: memhub.MemBuddy*, node: ast.AST*, name: str, value: int64, target_ty: mlirlite.LLVMType*, use_ctx: str = None) -> mlirlite.Value*: - """Materialize a CDefine integer constant at a known use-site width, or None on fallback""" + """Materialize a DefineT integer constant at a known use-site width, or None on fallback""" bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(target_ty) if bits != 8 and bits != 16 and bits != 32 and bits != 64: return None wmin: int = self.Gen.AssignGen.define_min_width(value) diff --git a/App/lib/core/Gen/ImportsGenMixin.vp b/App/lib/core/Gen/ImportsGenMixin.vp index dec614a..48c578e 100644 --- a/App/lib/core/Gen/ImportsGenMixin.vp +++ b/App/lib/core/Gen/ImportsGenMixin.vp @@ -8,7 +8,7 @@ import stdlib import viperlib import stdio import sys -import win32.fileio as fileio +import io as fileio import lib.Stub.StubMerger as StubMerger import lib.core.CompilerFactory as CompilerFactory import lib.core.Gen.Translator as HT diff --git a/App/lib/core/Gen/Type/TypeGenResolve.vp b/App/lib/core/Gen/Type/TypeGenResolve.vp index c9a6e15..c5d8ac9 100644 --- a/App/lib/core/Gen/Type/TypeGenResolve.vp +++ b/App/lib/core/Gen/Type/TypeGenResolve.vp @@ -17,7 +17,7 @@ import lib.core.CompilerFactory as CompilerFactory import lib.Projectrans.Config as Config import lib.core.Gen.Type.TypeGenBase as TypeGenBase -import win32.fileio as fileio +import io as fileio # ============================================================ @@ -60,7 +60,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): return buf return at.attr # Subscript: build "value[slice]" (recursive slice, List/Tuple kept fully). - # CTypedef extraction rebuilds the expression, so the slice must not be dropped. + # TypedefT extraction rebuilds the expression, so the slice must not be dropped. if k == ast.ASTKind.Subscript: sub_s: ast.Subscript* = ast.Subscript*(node) val_s: str = self._get_type_name_from_annotation(pool, sub_s.value) @@ -87,7 +87,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): k2: int = node.kind() # Name / Attribute / BinOp / nested Subscript: reuse type-name extraction if k2 == ast.ASTKind.Name or k2 == ast.ASTKind.Attribute or k2 == ast.ASTKind.BinOp or k2 == ast.ASTKind.Subscript: return self._get_type_name_from_annotation(pool, node) - # Constant: numeric/string literal (e.g. the count in t.CArray[elem, 104]) + # Constant: numeric/string literal (e.g. the count in t.ArrayT[elem, 104]) if k2 == ast.ASTKind.Constant: cn2: ast.Constant* = ast.Constant*(node) if cn2.const_kind == ast.CONST_INT: @@ -101,7 +101,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if k2 == ast.ASTKind.List: lst: ast.List* = ast.List*(node) return self._list_elts_to_type_str(pool, lst.elts, "[", "]") - # Tuple: two-arg forms like t.CArray[elem, N]. Nested comma slices are packed as + # Tuple: two-arg forms like t.ArrayT[elem, N]. Nested comma slices are packed as # Tuple, so output the slice without parens ("x, y") to keep ast.parse_expression valid. if k2 == ast.ASTKind.Tuple: tup: ast.Tuple* = ast.Tuple*(node) @@ -237,12 +237,12 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): _cname: str = container _c_dot: str = string.strrchr(container, '.') if _c_dot is not None: _cname = _c_dot + 1 - if string.strcmp(_cname, "CArray") == 0: return 1 + if string.strcmp(_cname, "ArrayT") == 0: return 1 if string.strcmp(_cname, "list") == 0: return 1 return 0 def _is_non_class_type_name(self, pool: memhub.MemBuddy*, name: str) -> int: - """Whether a type name has no class ownership (CTypedef/builtin/generic param).""" + """Whether a type name has no class ownership (TypedefT/builtin/generic param).""" if name is None or name[0] == '\0': return 0 if self.Gen.TypeGenCType is not None: _ts_nc: str = self.Gen.TypeGenCType.lookup_ctypedef(name) @@ -266,17 +266,17 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if name[0] >= 'A' and name[0] <= 'Z': return 1 return 0 - # _is_ptr_marker_text - whether text is the builtin pointer marker (CPtr). + # _is_ptr_marker_text - whether text is the builtin pointer marker (PtrT). def _is_ptr_marker_text(self, txt: str) -> int: """Whether txt is a bare pointer marker (one pointer layer): 1=yes / 0=no.""" if txt is None: return 0 - if string.strcmp(txt, "CPtr") == 0: return 1 + if string.strcmp(txt, "PtrT") == 0: return 1 return 0 # _is_ptr_subscript_container_text - whether text is a pointer subscript container name - # (rawptr / CPtr); subscript forms then fold to `T*`. + # (rawptr / PtrT); subscript forms then fold to `T*`. def _is_ptr_subscript_container_text(self, txt: str) -> int: - """Whether txt names a pointer subscript container (rawptr/CPtr): 1=yes / 0=no.""" + """Whether txt names a pointer subscript container (rawptr/PtrT): 1=yes / 0=no.""" if txt is None: return 0 if self._is_ptr_marker_text(txt) != 0: return 1 if string.strcmp(txt, "rawptr") == 0: return 1 @@ -312,7 +312,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if seg_len == 0: return type_name if string.strchr(type_name, '|') is not None: return None # ---- 1.5 nested generic args "Name[A,B]...": split inner args on top-level ',', - # normalize each; t.CArray / list keep their count arg verbatim. + # normalize each; t.ArrayT / list keep their count arg verbatim. lb_idx: sizet = seg_len ld: int = 0 for li2 in range(int(seg_len)): @@ -347,7 +347,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if arg_list_g is not None: arg_start_g: sizet = 0 cur_dep_g = 0 - # The second arg of a fixed-size container is an element count (CDefine + # The second arg of a fixed-size container is an element count (DefineT # name / literal / const expr), not a type name, so keep it verbatim. _cntc_g: int = self._is_count_arg_container(container_g) _argi_g: int = 0 @@ -374,8 +374,6 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): else: arg_list_g.append(abuf_g) _argi_g += 1 arg_start_g = ci_g + 1 - # [pointer-marker container normalization] Single-arg `rawptr[T]` / - # `t.CPtr[T]` / `CPtr[T]` yields "*" so it folds with `T*`. if self._is_ptr_subscript_container_text(container_g) != 0 and len(arg_list_g) == 1: _only_arg_g: str = arg_list_g.get(0) _star_cg: str = self._ptr_star_text(pool, _only_arg_g) @@ -514,7 +512,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if _def_mod is not None and _w_cls_cur is not None: if string.strcmp(_w_cls_cur, type_name) != 0: _w_need_log = 1 # Gate: only class names can split into the "{module SHA1}.Class" form, so - # exempt CTypedef / builtin scalars / generic params to avoid noise ERRORs. + # exempt TypedefT / builtin scalars / generic params to avoid noise ERRORs. if _w_need_log != 0 and _bare_entry is None and self._is_non_class_type_name(pool, type_name) != 0: _w_need_log = 0 if _w_need_log != 0: _w_mod: str = "None" @@ -568,6 +566,17 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): """Parse a type-name string back into an AST node, trimming spaces around '|' and '.'.""" if type_str is None or pool is None: return None + # Type strings from line scans may keep an inline comment (`rawptr # opaque handle`): + # cut at '#' before parsing so the comment can never become part of a type name. + hash_p: str = string.strchr(type_str, '#') + if hash_p is not None: + cut_l: sizet = sizet(uint64(hash_p) - uint64(type_str)) + cut_buf: str = pool.alloc(cut_l + 1) + if cut_buf is not None: + string.strncpy(cut_buf, type_str, cut_l) + cut_buf[cut_l] = '\0' + type_str = cut_buf + # Skip leading spaces (the right segment after '|' may have them) while type_str[0] == ' ': type_str += 1 @@ -701,7 +710,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): """Get the file currently being compiled (used for error attribution).""" return self.CurrentFile def resolve_typedef_or_map(self, pool: memhub.MemBuddy*, name: str, imported_modules: str, from_imports: str, trans: HT.Translator* = None) -> mlirlite.LLVMType*: - """Resolve a name as a CTypedef first, falling back to map_builtin_type.""" + """Resolve a name as a TypedefT first, falling back to map_builtin_type.""" if name is None: return None if self.Gen.TypeGenCType.CTypedefResolveDepth < TypeGenMixin.MAX_CTYPEDEF_DEPTH: ts_rt: str = self.Gen.TypeGenCType.lookup_ctypedef(name) @@ -714,8 +723,6 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): self.Gen.TypeGenCType.CTypedefResolveDepth -= 1 self.Gen.TypeGenCType._ctypedef_pop_visiting() if ity_rt is not None: return ity_rt - # [builtins last match] After the typedef table and map_builtin_type both miss, check - # includes/builtins.vp (e.g. rawptr: t.CTypedef = t.CPtr) and parse recursively. btd_r: str = self._lookup_builtins_typedef(pool, name) if btd_r is not None: btd_node_r: ast.AST* = self._parse_type_string(pool, btd_r, ast.ASTCtx.Load) @@ -725,8 +732,6 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): btd_ty_r: mlirlite.LLVMType* = self.resolve_annotation_type(pool, btd_node_r, imported_modules, from_imports, trans, 1) self.Gen.TypeGenCType.CTypedefResolveDepth -= 1 if btd_ty_r is not None: return btd_ty_r - # Library typedef fallback: read library sources (e.g. stdint UINT/DWORD) and - # register them when _vpi_register_typedef_closure did not inject them in time. lib_ts: str = self._lookup_library_typedef(pool, "stdint", name) if lib_ts is None: lib_ts = self._lookup_library_typedef(pool, "win32.win32base", name) if lib_ts is not None: @@ -737,9 +742,6 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): lib_ty: mlirlite.LLVMType* = self.resolve_annotation_type(pool, lib_node, imported_modules, from_imports, trans, 1) self.Gen.TypeGenCType.CTypedefResolveDepth -= 1 if lib_ty is not None: return lib_ty - # Bare Python/C builtin aliases: map_builtin_type only knows the t.CXxx spelling, so - # resolve them here to match resolve_annotation_type's bare-name branch. Without - # this the two-arg form int(x, t.CPtr) misses _tc_h4 and fails fast as "int". if string.strcmp(name, "str") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) if string.strcmp(name, "bytes") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) if string.strcmp(name, "int") == 0: return mlirlite.Int32(pool) @@ -872,12 +874,17 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): while name_l > 0 and buf[line_start + name_l - 1] == ' ': name_l -= 1 if name_l == 0 or name_l >= 128: return 0 rest: str = buf + line_start + j + 1 - if string.strstr(rest, "CTypedef") is None: return 0 + if string.strstr(rest, "TypedefT") is None: return 0 eq: str = string.strstr(rest, "=") if eq is None: return 0 vs = sizet(uint64(eq) - uint64(buf)) - line_start + 1 while vs < line_len and (buf[line_start + vs] == ' ' or buf[line_start + vs] == '\t'): vs += 1 ve: sizet = line_len + # Strip an inline comment first: `typedef DIR = rawptr # opaque handle`. + for ci_cm in range(int(vs), int(ve)): + if buf[line_start + ci_cm] == '#': + ve = ci_cm + break while ve > vs and (buf[line_start + ve - 1] == ' ' or buf[line_start + ve - 1] == '\t' or buf[line_start + ve - 1] == '\r'): ve -= 1 if ve <= vs: return 0 vlen: sizet = ve - vs @@ -888,19 +895,19 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): ci += 1 out_buf[name_l] = '\0' # Normalize a C-style typedef RHS (`const T` / `T*`) into marker-wrapper text: - # leading const/volatile become `CConst[` / `CVolatile[`, trailing `*` runs become `CPtr[`, + # leading const/volatile become `ConstT[` / `VolatileT[`, trailing `*` runs become `PtrT[`, # and the matching closing brackets are appended after the type text. pos: sizet = name_l + 1 k: sizet = vs descr: sizet = 0 while True: if k + 6 <= ve and string.strncmp(buf + line_start + k, "const", 5) == 0 and (buf[line_start + k + 5] == ' ' or buf[line_start + k + 5] == '\t'): - string.memcpy(out_buf + pos, "CConst[", 7) + string.memcpy(out_buf + pos, "ConstT[", 7) pos += 7 descr += 1 k += 6 elif k + 9 <= ve and string.strncmp(buf + line_start + k, "volatile", 8) == 0 and (buf[line_start + k + 8] == ' ' or buf[line_start + k + 8] == '\t'): - string.memcpy(out_buf + pos, "CVolatile[", 10) + string.memcpy(out_buf + pos, "VolatileT[", 10) pos += 10 descr += 1 k += 9 @@ -913,9 +920,9 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): ptrs += 1 ve2 -= 1 while ve2 > k and (buf[line_start + ve2 - 1] == ' ' or buf[line_start + ve2 - 1] == '\t'): ve2 -= 1 - # Pointer prefixes wrap the value: CPtr[ repeatedly. + # Pointer prefixes wrap the value: PtrT[ repeatedly. for pi in range(int(ptrs)): - string.memcpy(out_buf + pos, "CPtr[", 5) + string.memcpy(out_buf + pos, "PtrT[", 5) pos += 5 vlen2: sizet = ve2 - k if vlen2 > 190: vlen2 = 190 @@ -936,17 +943,17 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): out_buf[pos] = '\0' return 1 def _lookup_builtins_typedef(self, pool: memhub.MemBuddy*, name: str) -> str: - """Last-match fallback: look up a CTypedef declared in includes/builtins.vp.""" + """Last-match fallback: look up a TypedefT declared in includes/builtins.vp.""" if name is None: return None # Record the builtins last-match request/result. name is often a caller's # temporary buffer, so copy it via store_builtins_diag to avoid a dangling pointer. self.Gen.TypeGenCType.LastBuiltinsTypedefName = self.Gen.TypeGenCType.store_builtins_diag(0, name) self.Gen.TypeGenCType.LastBuiltinsTypedefResult = None # rawptr is built into the compiler (a reserved word on both TPC/VPC) and takes - # priority over any external declaration; always return CPtr. + # priority over any external declaration; always return PtrT. if string.strcmp(name, "rawptr") == 0: - self.Gen.TypeGenCType.LastBuiltinsTypedefResult = "CPtr" - return "CPtr" + self.Gen.TypeGenCType.LastBuiltinsTypedefResult = "PtrT" + return "PtrT" ts_b: str = self.Gen.TypeGenCType.lookup_ctypedef(name) if ts_b is not None and self.Gen.TypeGenCType.CTypedefFound != 0: # The table value may still be a module-qualified ref (size_t -> stdint.size_t); expand it @@ -976,7 +983,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): stdlib.free(b_buf) return None if b_read < LONG(BUF_B): b_buf[sizet(b_read)] = 0 - # Scan line-by-line and register all builtins "NAME: t.CTypedef = TYPE" entries at + # Scan line-by-line and register all builtins "NAME: t.TypedefT = TYPE" entries at # once; existing entries (module/import-injected) take priority and are not overwritten. b_total: sizet = string.strlen(b_buf) # Pre-scan import lines to register aliases (import stdint as __stdint), so later @@ -1029,7 +1036,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if b_i == b_total or b_buf[b_i] == '\n': b_line_len: sizet = b_i - b_line_start if b_line_len > 0 and b_td_buf is not None: - # Parse both equivalent forms (`typedef N = V` and `N: t.CTypedef = V`); + # Parse both equivalent forms (`typedef N = V` and `N: t.TypedefT = V`); # builtins.vp now uses the sugar form, so accept both. if self._parse_typedef_line(b_td_buf, b_buf, b_line_start, b_line_len) != 0: b_nb: str = b_td_buf @@ -1116,14 +1123,14 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if v is None: return None return rawptr(v) def _lookup_library_typedef(self, pool: memhub.MemBuddy*, mod: str, attr: str) -> str: - """Look up "attr: t.CTypedef = TYPE" in a library module source, returning TYPE or None.""" + """Look up "attr: t.TypedefT = TYPE" in a library module source, returning TYPE or None.""" if mod is None or attr is None: return None # Restore the real module name from a builtins import alias (__stdint -> stdint); # without this, SHA1 lookup and path resolution all miss. m_real: str = self._resolve_import_alias(mod) if m_real is None: m_real = mod # [gate hit] This library module was already scanned: query the persistent cache - # only, but still register hits into the current file's CTypedef table. + # only, but still register hits into the current file's TypedefT table. if self.LibTypedefScannedMods is not None: if self.LibTypedefScannedMods.__contains__(m_real) != 0: ts_hit: str = self._lookup_lib_typedef_cache(m_real, attr) @@ -1160,7 +1167,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if m_i == m_total or m_buf[m_i] == '\n': m_line_len: sizet = m_i - m_ls if m_line_len > 0 and m_td_buf is not None: - # Parse both equivalent forms (`typedef N = V` and `N: t.CTypedef = V`), + # Parse both equivalent forms (`typedef N = V` and `N: t.TypedefT = V`), # both of which occur in library module sources. if self._parse_typedef_line(m_td_buf, m_buf, m_ls, m_line_len) != 0: m_nb: str = m_td_buf @@ -1294,6 +1301,10 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): """Map a builtin type name to its LLVMType. Note: str = i8* = bytes.""" if type_name is None: return None + # Bare pointer marker: `rawptr` (source) and `PtrT` (internal carrier for `T*`) + # both mean one opaque pointer layer, i.e. i8*. + if string.strcmp(type_name, "rawptr") == 0 or string.strcmp(type_name, "PtrT") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) + # Builtin short names (no import / typedef required) if string.strcmp(type_name, "sizet") == 0 or string.strcmp(type_name, "size_t") == 0: return mlirlite.Int64(pool) elif string.strcmp(type_name, "int8") == 0 or string.strcmp(type_name, "intfast8") == 0 or string.strcmp(type_name, "intleast8") == 0: return mlirlite.Int8(pool) @@ -1322,85 +1333,20 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): elif string.strcmp(type_name, "char8") == 0: return mlirlite.Int8(pool) elif string.strcmp(type_name, "char16") == 0 or string.strcmp(type_name, "wchar") == 0: return mlirlite.Int16(pool) elif string.strcmp(type_name, "char32") == 0: return mlirlite.Int32(pool) - # Basic integer types - if string.strcmp(type_name, "CInt") == 0: return mlirlite.Int32(pool) - elif string.strcmp(type_name, "CChar") == 0: return mlirlite.Int8(pool) - elif string.strcmp(type_name, "CShort") == 0: return mlirlite.Int16(pool) - elif string.strcmp(type_name, "CLong") == 0: - # long width depends on target: Windows LLP64 = 32-bit (as TPC), LP64 = 64-bit - return self._long_llvm_ty(pool) - elif string.strcmp(type_name, "CLongLong") == 0: return mlirlite.Int64(pool) - # Exact-width signed types - elif string.strcmp(type_name, "CInt8T") == 0: return mlirlite.Int8(pool) - elif string.strcmp(type_name, "CInt16T") == 0: return mlirlite.Int16(pool) - elif string.strcmp(type_name, "CInt32T") == 0: return mlirlite.Int32(pool) - elif string.strcmp(type_name, "CInt64T") == 0: return mlirlite.Int64(pool) - # Exact-width unsigned types - elif string.strcmp(type_name, "CUInt8T") == 0: return mlirlite.Int8(pool) - elif string.strcmp(type_name, "CUInt16T") == 0: return mlirlite.Int16(pool) - elif string.strcmp(type_name, "CUInt32T") == 0: return mlirlite.Int32(pool) - elif string.strcmp(type_name, "CUInt64T") == 0: return mlirlite.Int64(pool) - # Floating-point types - elif string.strcmp(type_name, "CFloat") == 0: return mlirlite.Float(pool) - elif string.strcmp(type_name, "CDouble") == 0: return mlirlite.Double(pool) - # Exact-width float types (t.py float_map: 8/16->half, 32->float, 64->double, 128->fp128) - elif string.strcmp(type_name, "CFloat8T") == 0: return mlirlite.Half(pool) - elif string.strcmp(type_name, "CFloat16T") == 0: return mlirlite.Half(pool) - elif string.strcmp(type_name, "CFloat32T") == 0: return mlirlite.Float(pool) - elif string.strcmp(type_name, "CFloat64T") == 0: return mlirlite.Double(pool) - elif string.strcmp(type_name, "CFloat128T") == 0: return mlirlite.FP128(pool) - # Exact-width char types (C11 char8_t/char16_t/char32_t, Windows wchar_t = 16-bit) - elif string.strcmp(type_name, "CChar8T") == 0: return mlirlite.Int8(pool) - elif string.strcmp(type_name, "CChar16T") == 0 or string.strcmp(type_name, "CWCharT") == 0: return mlirlite.Int16(pool) - elif string.strcmp(type_name, "CChar32T") == 0: return mlirlite.Int32(pool) - # Pointer / void: t.CPtr = i8*, str = i8* = str = bytes - elif string.strcmp(type_name, "CPtr") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) - # Unsigned function pointer: bare t.Callable -> i8* (same LLVM repr as the full - # t.Callable[[A, B], Ret]); no signature means it is only for taking addresses. - elif string.strcmp(type_name, "Callable") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) + + # 存疑 + # Lowercase alias callable: equivalent to t.Callable in annotations. elif string.strcmp(type_name, "callable") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) - # Bare void is an alias of t.CVoid: alone it is LLVM void; `void*` is merged to i8* - # by the BinOp branch below. - elif string.strcmp(type_name, "CVoid") == 0 or string.strcmp(type_name, "void") == 0: return mlirlite.Void(pool) - # CDefine: compile-time constant marker, backed by i32 (TPC-aligned). Global/field - # CDefine annotations are short-circuited earlier, so only return-position reaches here. - elif string.strcmp(type_name, "CDefine") == 0: return mlirlite.Int32(pool) - # CAutoPtr: unconditional-address-of parameter marker; maps to i8* at the LLVM level - # (t.CNeedPtr was removed in favor of the `&T` / `&temp T` parameter annotation). - elif string.strcmp(type_name, "CAutoPtr") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) - # Unsigned modifiers - elif string.strcmp(type_name, "CUnsigned") == 0: return mlirlite.Int32(pool) - elif string.strcmp(type_name, "CUnsignedInt") == 0: return mlirlite.Int32(pool) - elif string.strcmp(type_name, "CUnsignedChar") == 0: return mlirlite.Int8(pool) - elif string.strcmp(type_name, "CUnsignedShort") == 0: return mlirlite.Int16(pool) - elif string.strcmp(type_name, "CUnsignedLong") == 0: - # unsigned long as above: Windows LLP64 = 32-bit (ULONG/DWORD width), LP64 = 64-bit - return self._long_llvm_ty(pool) - elif string.strcmp(type_name, "CUnsignedLongLong") == 0: return mlirlite.Int64(pool) - # size_t / ptr types - elif string.strcmp(type_name, "CSizeT") == 0: return mlirlite.Int64(pool) - elif string.strcmp(type_name, "CIntPtrT") == 0: return mlirlite.Int64(pool) - elif string.strcmp(type_name, "CUIntPtrT") == 0: return mlirlite.Int64(pool) - # bool - elif string.strcmp(type_name, "CBool") == 0: - # bool = t.CBool = i1 (1-bit LLVM integer, same as C _Bool) - return mlirlite.Int1(pool) - # ptrdiff_t - elif string.strcmp(type_name, "CPtrDiffT") == 0: return mlirlite.Int64(pool) - # Byte-order markers written alone (no real type on the other side) degrade to CInt, - # matching TPC: `t.BigEndian` alone -> BaseType=CInt() + ByteOrder='big'. - elif string.strcmp(type_name, "BigEndian") == 0: return mlirlite.Int32(pool) - elif string.strcmp(type_name, "LittleEndian") == 0: return mlirlite.Int32(pool) # stdint.vp aliases (INT/BYTE/DWORD/int8_t/size_t/_Bool ...) now come from its - # CTypedef declarations; lowercase bare char/float/double stay here, int/bool use Name. + # TypedefT declarations; lowercase bare char/float/double stay here, int/bool use Name. elif string.strcmp(type_name, "char") == 0: return mlirlite.Int8(pool) elif string.strcmp(type_name, "float") == 0: return mlirlite.Float(pool) elif string.strcmp(type_name, "double") == 0: return mlirlite.Double(pool) return None def _is_builtin_marker(self, node: ast.AST*, attr_name: str, imported_modules: str) -> int: - """Whether a node is the bare builtin marker name (e.g. CPtr / CRef).""" + """Whether a node is the bare builtin marker name (e.g. PtrT / RefT).""" if node is None or attr_name is None: return 0 if node.kind() != ast.ASTKind.Name: return 0 nm: ast.Name* = ast.Name*(node) @@ -1408,16 +1354,16 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if string.strcmp(nm.id, attr_name) != 0: return 0 return 1 def _is_cptr_marker(self, node: ast.AST*, imported_modules: str) -> int: - """Whether a node is a CPtr pointer marker: 1=yes / 0=no.""" - if self._is_builtin_marker(node, "CPtr", imported_modules) != 0: return 1 + """Whether a node is a PtrT pointer marker: 1=yes / 0=no.""" + if self._is_builtin_marker(node, "PtrT", imported_modules) != 0: return 1 if node is not None and node.kind() == ast.ASTKind.Name: nm: ast.Name* = ast.Name*(node) - if nm.id is not None and string.strcmp(nm.id, "CPtr") == 0: return 1 + if nm.id is not None and string.strcmp(nm.id, "PtrT") == 0: return 1 return 0 def _is_ref_marker(self, node: ast.AST*, imported_modules: str) -> int: - """Whether a node is a CRef/CTempRef value-reference marker: 1=yes / 0=no.""" - if self._is_builtin_marker(node, "CRef", imported_modules) != 0: return 1 - if self._is_builtin_marker(node, "CTempRef", imported_modules) != 0: return 1 + """Whether a node is a RefT/TempRefT value-reference marker: 1=yes / 0=no.""" + if self._is_builtin_marker(node, "RefT", imported_modules) != 0: return 1 + if self._is_builtin_marker(node, "TempRefT", imported_modules) != 0: return 1 return 0 def _is_byteorder_marker(self, node: ast.AST*, imported_modules: str) -> int: """Whether a node is a byte-order marker: 0=none / 1=BigEndian / 2=LittleEndian.""" @@ -1443,7 +1389,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if ei >= 3 and tr[ei - 3] == '_' and tr[ei - 2] == 'b' and tr[ei - 1] == 'e': return 1 return 0 def _ref_marker_name_mode(self, node: ast.AST*) -> int: - """Syntactic value-reference marker test (no import context): 0=none / 1=CRef / 2=CTempRef.""" + """Syntactic value-reference marker test (no import context): 0=none / 1=RefT / 2=TempRefT.""" if node is None: return 0 if node.kind() != ast.ASTKind.Subscript: return 0 sub_r: ast.Subscript* = ast.Subscript*(node) @@ -1451,8 +1397,8 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): nm_node_r: ast.Name* = ast.Name*(sub_r.value) nm: str = nm_node_r.id if nm is None: return 0 - if string.strcmp(nm, "CRef") == 0: return 1 - if string.strcmp(nm, "CTempRef") == 0: return 2 + if string.strcmp(nm, "RefT") == 0: return 1 + if string.strcmp(nm, "TempRefT") == 0: return 2 return 0 def ref_param_mode(self, node: ast.AST*, imported_modules: str) -> int: """Value-reference mode of a parameter annotation: 0=none / 1=ref (&T) / 2=temp (&temp T).""" @@ -1463,12 +1409,12 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): self.Gen.TypeGenErr.fatal_error(node, f"value-reference parameter annotation '&T' / '&temp T' is only allowed in function parameter position, not for {where}", "TypeError") return 1 def _is_cptr_subscript(self, node: ast.AST*) -> int: - """Whether a node is a CPtr[T] subscript pointer type (e.g. CPtr[CPtr]).""" + """Whether a node is a PtrT[T] subscript pointer type (e.g. PtrT[PtrT]).""" if node is None or node.kind() != ast.ASTKind.Subscript: return 0 sub: ast.Subscript* = ast.Subscript*(node) if sub.value is None or sub.value.kind() != ast.ASTKind.Name: return 0 nm: ast.Name* = ast.Name*(sub.value) - if nm.id is None or string.strcmp(nm.id, "CPtr") != 0: return 0 + if nm.id is None or string.strcmp(nm.id, "PtrT") != 0: return 0 return 1 def _is_cattr_marker(self, node: ast.AST*) -> int: """Whether a node is an `attr.` builtin marker (including the call form).""" @@ -1497,12 +1443,12 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): sub_v: ast.Subscript* = ast.Subscript*(node) if sub_v.value is not None and sub_v.value.kind() == ast.ASTKind.Name: sw_nm: ast.Name* = ast.Name*(sub_v.value) - if sw_nm.id is not None and (string.strcmp(sw_nm.id, "CConst") == 0 or string.strcmp(sw_nm.id, "CVolatile") == 0 \ + if sw_nm.id is not None and (string.strcmp(sw_nm.id, "ConstT") == 0 or string.strcmp(sw_nm.id, "VolatileT") == 0 \ or string.strcmp(sw_nm.id, "BigEndian") == 0 or string.strcmp(sw_nm.id, "LittleEndian") == 0 \ - or string.strcmp(sw_nm.id, "CStatic") == 0 or string.strcmp(sw_nm.id, "CExport") == 0 \ - or string.strcmp(sw_nm.id, "CExtern") == 0 or string.strcmp(sw_nm.id, "State") == 0 \ - or string.strcmp(sw_nm.id, "CDefine") == 0 or string.strcmp(sw_nm.id, "CRef") == 0 \ - or string.strcmp(sw_nm.id, "CTempRef") == 0 or string.strcmp(sw_nm.id, "CAutoPtr") == 0): + or string.strcmp(sw_nm.id, "StaticT") == 0 or string.strcmp(sw_nm.id, "ExportT") == 0 \ + or string.strcmp(sw_nm.id, "ExternT") == 0 or string.strcmp(sw_nm.id, "State") == 0 \ + or string.strcmp(sw_nm.id, "DefineT") == 0 or string.strcmp(sw_nm.id, "RefT") == 0 \ + or string.strcmp(sw_nm.id, "TempRefT") == 0 or string.strcmp(sw_nm.id, "AutoPtrT") == 0): if sub_v.slice is not None: return self._is_cvoid_marker(sub_v.slice, imported_modules) return 0 def annotation_to_str(self, annot: ast.AST*) -> str: @@ -1535,14 +1481,14 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): return buf return at.attr - # Subscript node: list[...], CArray[...], CPtr[...] (rendered as T*), marker wrappers. + # Subscript node: list[...], ArrayT[...], PtrT[...] (rendered as T*), marker wrappers. if k == ast.ASTKind.Subscript: sub: ast.Subscript* = ast.Subscript*(annot) if self._is_cattr_marker(sub.value) != 0: return self.annotation_to_str(sub.slice) if sub.value.kind() == ast.ASTKind.Name: sv_nm: ast.Name* = ast.Name*(sub.value) if sv_nm.id is not None: - if string.strcmp(sv_nm.id, "CPtr") == 0: + if string.strcmp(sv_nm.id, "PtrT") == 0: inner_cp: str = self.annotation_to_str(sub.slice) if inner_cp is not None: cp_buf: str = stdlib.malloc(string.strlen(inner_cp) + 2) @@ -1550,20 +1496,20 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): viperlib.snprintf(cp_buf, string.strlen(inner_cp) + 2, "%s*", inner_cp) return cp_buf return "" - if string.strcmp(sv_nm.id, "CRef") == 0 or string.strcmp(sv_nm.id, "CTempRef") == 0: + if string.strcmp(sv_nm.id, "RefT") == 0 or string.strcmp(sv_nm.id, "TempRefT") == 0: inner_rf: str = self.annotation_to_str(sub.slice) if inner_rf is not None: - rf_prefix: str = "&temp " if string.strcmp(sv_nm.id, "CTempRef") == 0 else "&" + rf_prefix: str = "&temp " if string.strcmp(sv_nm.id, "TempRefT") == 0 else "&" rf_buf: str = stdlib.malloc(string.strlen(inner_rf) + string.strlen(rf_prefix) + 1) if rf_buf is not None: viperlib.snprintf(rf_buf, string.strlen(inner_rf) + string.strlen(rf_prefix) + 1, "%s%s", rf_prefix, inner_rf) return rf_buf return "" - if string.strcmp(sv_nm.id, "CConst") == 0 or string.strcmp(sv_nm.id, "CVolatile") == 0 \ + if string.strcmp(sv_nm.id, "ConstT") == 0 or string.strcmp(sv_nm.id, "VolatileT") == 0 \ or string.strcmp(sv_nm.id, "BigEndian") == 0 or string.strcmp(sv_nm.id, "LittleEndian") == 0 \ - or string.strcmp(sv_nm.id, "CStatic") == 0 or string.strcmp(sv_nm.id, "CExport") == 0 \ - or string.strcmp(sv_nm.id, "CExtern") == 0 or string.strcmp(sv_nm.id, "State") == 0 \ - or string.strcmp(sv_nm.id, "CDefine") == 0 or string.strcmp(sv_nm.id, "CAutoPtr") == 0: + or string.strcmp(sv_nm.id, "StaticT") == 0 or string.strcmp(sv_nm.id, "ExportT") == 0 \ + or string.strcmp(sv_nm.id, "ExternT") == 0 or string.strcmp(sv_nm.id, "State") == 0 \ + or string.strcmp(sv_nm.id, "DefineT") == 0 or string.strcmp(sv_nm.id, "AutoPtrT") == 0: return self.annotation_to_str(sub.slice) base: str = self.annotation_to_str(sub.value) if base is not None: @@ -1578,11 +1524,11 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): def _is_storage_class_qualifier(self, name: str) -> int: """Whether a name is a storage-class qualifier macro (not a type): 1=yes / 0=no.""" if name is None: return 0 - if string.strcmp(name, "CStatic") == 0: return 1 - if string.strcmp(name, "CVolatile") == 0: return 1 - if string.strcmp(name, "CExport") == 0: return 1 - if string.strcmp(name, "CExtern") == 0: return 1 - if string.strcmp(name, "CInline") == 0: return 1 + if string.strcmp(name, "StaticT") == 0: return 1 + if string.strcmp(name, "VolatileT") == 0: return 1 + if string.strcmp(name, "ExportT") == 0: return 1 + if string.strcmp(name, "ExternT") == 0: return 1 + if string.strcmp(name, "InlineT") == 0: return 1 # Byte-order markers are decorators, not types: never let them be extracted as a # class name (which would make the field degrade to an unknown struct lookup). if string.strcmp(name, "BigEndian") == 0: return 1 @@ -1667,17 +1613,17 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if sub.value.kind() == ast.ASTKind.Name: rp_nm: ast.Name* = ast.Name*(sub.value) if rp_nm.id is not None: - # rawptr[T] / CPtr[T]: AnnotClassName takes the pointee class T. - if string.strcmp(rp_nm.id, "rawptr") == 0 or string.strcmp(rp_nm.id, "CPtr") == 0: + # rawptr[T] / PtrT[T]: AnnotClassName takes the pointee class T. + if string.strcmp(rp_nm.id, "rawptr") == 0 or string.strcmp(rp_nm.id, "PtrT") == 0: if sub.slice is not None: return self.extract_class_name_from_annotation(sub.slice, imported_modules, pool, from_imports) return None # Descriptor / storage / value-reference marker wrappers: unwrap to the type. - if string.strcmp(rp_nm.id, "CConst") == 0 or string.strcmp(rp_nm.id, "CVolatile") == 0 \ + if string.strcmp(rp_nm.id, "ConstT") == 0 or string.strcmp(rp_nm.id, "VolatileT") == 0 \ or string.strcmp(rp_nm.id, "BigEndian") == 0 or string.strcmp(rp_nm.id, "LittleEndian") == 0 \ - or string.strcmp(rp_nm.id, "CStatic") == 0 or string.strcmp(rp_nm.id, "CExport") == 0 \ - or string.strcmp(rp_nm.id, "CExtern") == 0 or string.strcmp(rp_nm.id, "State") == 0 \ - or string.strcmp(rp_nm.id, "CDefine") == 0 or string.strcmp(rp_nm.id, "CRef") == 0 \ - or string.strcmp(rp_nm.id, "CTempRef") == 0 or string.strcmp(rp_nm.id, "CAutoPtr") == 0: + or string.strcmp(rp_nm.id, "StaticT") == 0 or string.strcmp(rp_nm.id, "ExportT") == 0 \ + or string.strcmp(rp_nm.id, "ExternT") == 0 or string.strcmp(rp_nm.id, "State") == 0 \ + or string.strcmp(rp_nm.id, "DefineT") == 0 or string.strcmp(rp_nm.id, "RefT") == 0 \ + or string.strcmp(rp_nm.id, "TempRefT") == 0 or string.strcmp(rp_nm.id, "AutoPtrT") == 0: if sub.slice is not None: return self.extract_class_name_from_annotation(sub.slice, imported_modules, pool, from_imports) return None # When recursing into base, from_imports must be passed down too, else the mangled @@ -1695,7 +1641,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if sub_tup.elts is not None: tup_elts_sn: list[ast.AST*]* = sub_tup.elts tn_sn: sizet = len(tup_elts_sn) - # The 2nd arg of a fixed-size container (t.CArray / list) is an element count, + # The 2nd arg of a fixed-size container (t.ArrayT / list) is an element count, # not a type name, so keep it verbatim (this is a separate entry from rule 1.5). _cntc_sn: int = self._is_count_arg_container(base_name) # Compute the total length @@ -1899,6 +1845,18 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if self.Gen.ClassGenGeneric._find_generic_template(base_nm) is None: return 0 self.Gen.TypeGenErr.fatal_error(node, f"The annotation of generic class '{base_nm}' must carry type arguments (e.g. {base_nm}[T]); " f"a bare name is treated as a bare generic base class without arguments, and once the element type is lost, method calls specialize on the wrong element " f"type and truncate pointers. Correct examples: vector.Vector[CInt], GSList[G5Node].", "TypeError") return 0 + + # _resolve_enum_base_ty - 解析枚举基类型(EnumTable 的键是 (定义模块 SHA1, 名字)) + def _resolve_enum_base_ty(self, name: str, mod_sha1: str, trans: HT.Translator*) -> mlirlite.LLVMType*: + """按 (名字, 模块 SHA1) 解析枚举基类型;模块未知时退化为"同名唯一 / 同名基类型一致"。""" + if name is None: return None + if mod_sha1 is not None: + ty_m: mlirlite.LLVMType* = self.Gen.StmtGenDecl.get_enum_base_type_in_module(name, mod_sha1) + if ty_m is not None: return ty_m + ctx_sha1: str = None + if trans is not None: ctx_sha1 = trans.ModuleSha1 + return self.Gen.StmtGenDecl.get_enum_base_type_ctx(name, ctx_sha1) + def resolve_annotation_type(self, pool: memhub.MemBuddy*, node: ast.AST*, imported_modules: str, from_imports: str, trans: HT.Translator* = None, depth: int = 0) -> mlirlite.LLVMType*: """Resolve a type-annotation node into an LLVMType.""" if node is None: return None @@ -1951,7 +1909,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): for gp_j in range(int(gp_n)): gp_pn: str = tpn_g.get(gp_j) # Token-boundary match (not strstr): the param name must be an - # independent identifier, else chars inside AST/CPtr misjudge as a param. + # independent identifier, else chars inside AST/PtrT misjudge as a param. if gp_pn is not None and self._str_contains_ident(gp_ta, gp_pn) != 0: polluted = 1 break @@ -1985,6 +1943,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): # str = bytes = str = i8* if string.strcmp(nm.id, "str") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) elif string.strcmp(nm.id, "None") == 0: return mlirlite.Void(pool) + elif string.strcmp(nm.id, "void") == 0: return mlirlite.Void(pool) elif string.strcmp(nm.id, "int") == 0: return mlirlite.Int32(pool) elif string.strcmp(nm.id, "char") == 0: # char = i8 (consistent with CChar/char in map_builtin_type) @@ -1996,11 +1955,24 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): # Bare float types: float = t.CFloat = f32, double = t.CDouble = f64 elif string.strcmp(nm.id, "float") == 0: return mlirlite.Float(pool) elif string.strcmp(nm.id, "double") == 0: return mlirlite.Double(pool) + # Enum base type, needed by the struct branches below (forward-placeholder guard). + # EnumTable 的键是 (定义模块 SHA1, 名字):先按本文件模块精确解析,再退化到 + # "同名唯一 / 同名基类型一致";同名枚举(win32.fileio 与 fat32_types 都有 FRESULT) + # 因此不会互相覆盖成员表。 + enum_base_rn: mlirlite.LLVMType* = self._resolve_enum_base_ty(nm.id, None, trans) # class name: return the registered StructType itself (no Ptr wrapping); prefer the # current module's class before falling back to a global bare-name lookup. if trans is not None and trans.ModuleSha1 is not None: own_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(nm.id, trans.ModuleSha1) if own_entry is not None and own_entry.Ty is not None: + # StructTable 是**进程级**的(init_struct_table 只建一次,reset_visible_structs + # 不清 StructCount):别的文件在本模块的 `enum FRESULT:` 被翻译之前引用了 + # `types.FRESULT`,会在本模块 SHA1 名下留下一个 opaque 的**前向**占位结构体。 + # 若这里返回它,槽位就成了结构体,而结构体右值取的是自身地址(指针), + # `res: FRESULT` 于是静默变成指针,`self.last_error = res` 报 + # "implicit pointer-to-integer conversion"。故:已注册的枚举压过前向占位; + # 真正的定义(IsForwardDecl == 0)仍然压过枚举。 + if own_entry.IsForwardDecl != 0 and enum_base_rn is not None: return enum_base_rn # A bare class name is always the prototype struct (type/layout), not # auto-promoted to Ptr(struct) by HasNew; heap refs must be written as `T*`. return own_entry.Ty @@ -2025,22 +1997,29 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if fi_sha1 is not None: fi_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(nm.id, fi_sha1) if fi_entry is not None and fi_entry.Ty is not None: + # Same forward-placeholder guard: a registered enum wins over a stub + # placeholder created before the enum's module was translated. + if fi_entry.IsForwardDecl != 0 and enum_base_rn is not None: return enum_base_rn # Bare class name is always the prototype (see above) return fi_entry.Ty if self.Gen.StructGen.is_struct_visible(nm.id) != 0: + # Same guard as above: a forward placeholder (possibly keyed by another module's SHA1 + # via the bare-name scan in get_struct_type) must not shadow a registered enum. + vis_e_rn: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(nm.id) + if vis_e_rn is not None and vis_e_rn.IsForwardDecl != 0 and enum_base_rn is not None: return enum_base_rn struct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(nm.id) if struct_ty is not None: # Bare class name is always the prototype (see above): no HasNew promotion to Ptr return struct_ty - # Enum class (t.CEnum) bare-name annotation -> underlying integer type + # Enum class (t.EnumBase) bare-name annotation -> underlying integer type # (e.g. `res: FRESULT = ...` where FRESULT is an enum, not a struct). - if self.Gen.StmtGenDecl.is_enum_class(nm.id) == 1: return self.Gen.StmtGenDecl.get_enum_base_type(nm.id) - # CTypedef dynamic lookup: resolve the expression recursively; depth is capped by + if enum_base_rn is not None: return enum_base_rn + # TypedefT dynamic lookup: resolve the expression recursively; depth is capped by # MAX_CTYPEDEF_DEPTH and cycle detection prevents A->B->A. if self.Gen.TypeGenCType.CTypedefResolveDepth < TypeGenMixin.MAX_CTYPEDEF_DEPTH: ctypedef_str: str = self.Gen.TypeGenCType.lookup_ctypedef(nm.id) if ctypedef_str is not None and self.Gen.TypeGenCType.CTypedefFound != 0: - # Cycle detection: add the current CTypedef name to the in-progress list + # Cycle detection: add the current TypedefT name to the in-progress list if self.Gen.TypeGenCType._ctypedef_push_visiting(pool, nm.id) == 0: # Convert the type string back to an AST with _parse_type_string, then recurse inner_node: ast.AST* = self._parse_type_string(pool, ctypedef_str, nm.ctx) @@ -2074,16 +2053,12 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if orig_node_a is not None: alias_ty: mlirlite.LLVMType* = self.resolve_annotation_type(pool, orig_node_a, imported_modules, from_imports, trans, depth + 1) if alias_ty is not None: return alias_ty - # [builtins last match] Final fallback after all normal paths miss: check - # includes/builtins.vp CTypedefs (e.g. rawptr: t.CTypedef = t.CPtr). btd_n: str = self._lookup_builtins_typedef(pool, nm.id) if btd_n is not None: btd_node_n: ast.AST* = self._parse_type_string(pool, btd_n, nm.ctx) if btd_node_n is not None: btd_ty_n: mlirlite.LLVMType* = self.resolve_annotation_type(pool, btd_node_n, imported_modules, from_imports, trans, depth + 1) if btd_ty_n is not None: return btd_ty_n - # Library typedef fallback: read library sources (stdint / win32.win32base bare - # typedefs) when _vpi_register_typedef_closure did not inject them, same as above. lib_ts_n: str = self._lookup_library_typedef(pool, "stdint", nm.id) if lib_ts_n is None: lib_ts_n = self._lookup_library_typedef(pool, "win32.win32base", nm.id) if lib_ts_n is not None: @@ -2091,12 +2066,8 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if lib_node_n is not None: lib_ty_n: mlirlite.LLVMType* = self.resolve_annotation_type(pool, lib_node_n, imported_modules, from_imports, trans, depth + 1) if lib_ty_n is not None: return lib_ty_n - # [REPL session] The owner module is known but the class is not in the session - # StructTable, so use an opaque placeholder (no StructTable write); batch mode skips. if trans is not None and trans.ReplCtx is not None: own_sha_rn: str = fi_sha1_rn - # This-module bare names need trans.ModuleSha1 to be the stub's source sha1; the - # session sha1 must not be used as owner, so exclude it and keep the fail-fast. if own_sha_rn is None: if trans.ModuleSha1 is not None and string.strcmp(trans.ModuleSha1, GenTypes.REPL_SESSION_SHA1) != 0: own_sha_rn = trans.ModuleSha1 if own_sha_rn is not None: @@ -2104,17 +2075,13 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if ty_repl_n is not None: return ty_repl_n return None - # Attribute node: int, uint64, namespace_defs.PlainStruct, etc. if k == ast.ASTKind.Attribute: at: ast.Attribute* = ast.Attribute*(node) if at.attr is None: return None if at.value is not None and at.value.kind() == ast.ASTKind.Name: val_name: ast.Name* = ast.Name*(at.value) if val_name.id is not None: - # `attr.` is a builtin attribute marker, never a type. if string.strcmp(val_name.id, "attr") == 0: return None - # SHA1 prefix ("{16hex}.Class"): the module segment is a SHA1 (already - # compiled), so skip the imported_modules check via GenApi.is_sha1_hex16. sha1_attr: int = GenApi.is_sha1_hex16(val_name.id) if sha1_attr == 0: mod_imp: int = self.Gen.ImportsGen.is_module_imported(imported_modules, val_name.id) @@ -2147,6 +2114,15 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): self.Gen.TypeGenCType.CTypedefResolveDepth -= 1 self.Gen.TypeGenCType._ctypedef_pop_visiting() if inner_ty_attr is not None: return inner_ty_attr + # Module-qualified typedef fallback: read the library module source even when + # the module is explicitly imported (its typedefs may not be injected into this + # file's table yet, e.g. `import posix` + `posix.DIR`). + lib_td_q: str = self._lookup_library_typedef(pool, val_name.id, at.attr) + if lib_td_q is not None: + lib_td_node_q: ast.AST* = self._parse_type_string(pool, lib_td_q, at.ctx) + if lib_td_node_q is not None: + lib_td_ty_q: mlirlite.LLVMType* = self.resolve_annotation_type(pool, lib_td_node_q, imported_modules, from_imports, trans, depth + 1) + if lib_td_ty_q is not None: return lib_td_ty_q # First try the builtin type mapping (int etc.) mapped_ty: mlirlite.LLVMType* = self.map_builtin_type(pool, at.attr) if mapped_ty is not None: return mapped_ty @@ -2184,14 +2160,22 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if nm_eq_a == 0 or seg_eq_a == 0: mod_sha1_v = inj_mod_a mod_sha1_rv = mod_sha1_v if mod_sha1_v is not None: + # 枚举按 (名字, 目标模块 SHA1) 精确解析:`types.FRESULT` 只认 fat32_types 的 + # 那一条,不会和 win32.fileio 的同名 FRESULT 混用。 + enum_mod_ty: mlirlite.LLVMType* = self._resolve_enum_base_ty(at.attr, mod_sha1_v, trans) full_q_v: str = self._concat_sha1_class(pool, mod_sha1_v, at.attr) if full_q_v is not None: full_ty_v: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(full_q_v) - if full_ty_v is not None: return full_ty_v + if full_ty_v is not None: + # 同 Name 分支:opaque 前向占位不得压过已注册的枚举 + if enum_mod_ty is None: return full_ty_v + fq_e_v: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(full_q_v) + if fq_e_v is None or fq_e_v.IsForwardDecl == 0: return full_ty_v mod_entry_v: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(at.attr, mod_sha1_v) if mod_entry_v is not None and mod_entry_v.Ty is not None: - # Bare class names (incl. mod.Class) are always the prototype (see above) - return mod_entry_v.Ty + if mod_entry_v.IsForwardDecl == 0 or enum_mod_ty is None: + # Bare class names (incl. mod.Class) are always the prototype (see above) + return mod_entry_v.Ty # On miss, the module may just re-export the class; correct to the true # defining module via _class_true_def_sha1 and look up once more. mod_sha1_td: str = self._class_true_def_sha1(pool, at.attr, mod_sha1_v) @@ -2202,6 +2186,16 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if full_ty_td is not None: return full_ty_td mod_entry_td: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(at.attr, mod_sha1_td) if mod_entry_td is not None and mod_entry_td.Ty is not None: return mod_entry_td.Ty + # Enum in a resolvable module: never forward-declare a class for it. The + # StructTable is process-global, so the placeholder would survive into + # every later file (in particular the enum's own defining module, where + # it would shadow the real enum -- see the Name branch guard above). + if enum_mod_ty is not None: return enum_mod_ty + # Cyclic imports: the dependency's vpi may not be injected yet, so the + # class is absent from StructTable. Forward-declare it; the real + # definition later fills the same Ty (ensure_forward_class is idempotent). + fwd_e_v: GenTypes.StructEntry* = self.Gen.StructGen.ensure_forward_class(pool, mod_sha1_td, at.attr) + if fwd_e_v is not None and fwd_e_v.Ty is not None: return fwd_e_v.Ty # Module resolvable but class unregistered, or module unregistered: # do not fall back to bare names; fall through to enum/typedef attempts. else: @@ -2210,8 +2204,10 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if mod_struct_ty is not None: return mod_struct_ty # Module-qualified enum type: Module.EnumClass (e.g. fat32_types.FRESULT); # an enum class is not a struct, so get_struct_type misses it. - if self.Gen.StmtGenDecl.is_enum_class(at.attr) == 1: return self.Gen.StmtGenDecl.get_enum_base_type(at.attr) - # Module-qualified typedef (posix.DIR = t.CTypedef = t.CVoid* etc.): same + # 模块段无法解析时退回"同名唯一 / 同名基类型一致"。 + enum_fb_ty: mlirlite.LLVMType* = self._resolve_enum_base_ty(at.attr, None, trans) + if enum_fb_ty is not None: return enum_fb_ty + # Module-qualified typedef (posix.DIR = t.TypedefT = t.CVoid* etc.): same # lookup_ctypedef + recursive resolution as the Name branch. if self.Gen.TypeGenCType.CTypedefResolveDepth < TypeGenMixin.MAX_CTYPEDEF_DEPTH: mod_td_str: str = self.Gen.TypeGenCType.lookup_ctypedef(at.attr) @@ -2237,7 +2233,8 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): nested_struct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type(at.attr) if nested_struct_ty is not None: return nested_struct_ty # Nested module-qualified enum class (three-level name): look up the enum base type - if self.Gen.StmtGenDecl.is_enum_class(at.attr) == 1: return self.Gen.StmtGenDecl.get_enum_base_type(at.attr) + nested_enum_ty: mlirlite.LLVMType* = self._resolve_enum_base_ty(at.attr, None, trans) + if nested_enum_ty is not None: return nested_enum_ty # Nested module-qualified typedef (win32.win32base.HANDLE = VOIDPTR): same # lookup_ctypedef + recursion + map_builtin_type fallback as the Name branch. if self.Gen.TypeGenCType.CTypedefResolveDepth < TypeGenMixin.MAX_CTYPEDEF_DEPTH: @@ -2272,8 +2269,8 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): self.Gen.TypeGenErr.fatal_type_error(node, "type union `T1 | T2` was removed; use `T*`, a prefix descriptor (const/volatile/bigend/littlend), `&T` / `&temp T`, or export/extern/static instead") return None - # Subscript node: CArray[elem, count] -> [count x elem]; CArray[elem] -> Ptr(elem); - # list[int] -> specialized struct type; marker wrappers (CPtr[T], CConst[T], ...) -> unwrap. + # Subscript node: ArrayT[elem, count] -> [count x elem]; ArrayT[elem] -> Ptr(elem); + # list[int] -> specialized struct type; marker wrappers (PtrT[T], ConstT[T], ...) -> unwrap. if k == ast.ASTKind.Subscript: sub: ast.Subscript* = ast.Subscript*(node) if sub.value is None: self.Gen.TypeGenErr.fatal_type_error(node, "Subscript node missing value") @@ -2281,7 +2278,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): # pointers (handled as i8* below); other types still require a slice. if sub.slice is None and self._IsCallableSubscriptValue(sub.value) == 0: self.Gen.TypeGenErr.fatal_type_error(node, "Subscript node missing value or slice") # Function pointers (bare callable / Callable) equal t.Callable point-for-point and - # map to i8*; must be checked before the Name path (generic/CArray/builtins fallback). + # map to i8*; must be checked before the Name path (generic/ArrayT/builtins fallback). if sub.value.kind() == ast.ASTKind.Name and self._IsCallableSubscriptValue(sub.value) != 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) # Generic class annotation (list[int]): look up the specialized struct for a Name value. if sub.value.kind() == ast.ASTKind.Name: @@ -2289,18 +2286,18 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if gen_nm.id is not None: # Builtin marker wrappers: descriptor / storage / value-reference markers carry # no LLVM type of their own, so resolve the wrapped type. - if string.strcmp(gen_nm.id, "CConst") == 0 or string.strcmp(gen_nm.id, "CVolatile") == 0 \ + if string.strcmp(gen_nm.id, "ConstT") == 0 or string.strcmp(gen_nm.id, "VolatileT") == 0 \ or string.strcmp(gen_nm.id, "BigEndian") == 0 or string.strcmp(gen_nm.id, "LittleEndian") == 0 \ - or string.strcmp(gen_nm.id, "CStatic") == 0 or string.strcmp(gen_nm.id, "CExport") == 0 \ - or string.strcmp(gen_nm.id, "CExtern") == 0 or string.strcmp(gen_nm.id, "State") == 0 \ - or string.strcmp(gen_nm.id, "CDefine") == 0 or string.strcmp(gen_nm.id, "CAutoPtr") == 0: + or string.strcmp(gen_nm.id, "StaticT") == 0 or string.strcmp(gen_nm.id, "ExportT") == 0 \ + or string.strcmp(gen_nm.id, "ExternT") == 0 or string.strcmp(gen_nm.id, "State") == 0 \ + or string.strcmp(gen_nm.id, "DefineT") == 0 or string.strcmp(gen_nm.id, "AutoPtrT") == 0: return self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1) # Value-reference parameters use the pointer ABI: &T / &temp T -> T*. - if string.strcmp(gen_nm.id, "CRef") == 0 or string.strcmp(gen_nm.id, "CTempRef") == 0: + if string.strcmp(gen_nm.id, "RefT") == 0 or string.strcmp(gen_nm.id, "TempRefT") == 0: ref_inner: mlirlite.LLVMType* = self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1) if ref_inner is None: return None return mlirlite.Ptr(pool, ref_inner) - # Builtin fixed-array constructor array[E, N] (soft keyword, like t.CArray[E, N]): + # Builtin fixed-array constructor array[E, N] (soft keyword, like t.ArrayT[E, N]): # active only in type positions; only exactly two args with a positive length. if string.strcmp(gen_nm.id, "array") == 0: if sub.slice.kind() != ast.ASTKind.Tuple: @@ -2313,23 +2310,23 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if count_node_arr.kind() == ast.ASTKind.Constant: cnt_arr0: ast.Constant* = ast.Constant*(count_node_arr) if cnt_arr0.const_kind == ast.CONST_NONE: - self.Gen.TypeGenErr.fatal_type_error(count_node_arr, "Built-in fixed-size array constructor 'array' length must be a positive integer constant (>0); None is not allowed (use t.CArray[E] for automatic length inference). Correct example: state: array[uint32, 4]") + self.Gen.TypeGenErr.fatal_type_error(count_node_arr, "Built-in fixed-size array constructor 'array' length must be a positive integer constant (>0); None is not allowed (use t.ArrayT[E] for automatic length inference). Correct example: state: array[uint32, 4]") elem_ty_arr: mlirlite.LLVMType* = self.resolve_annotation_type(pool, elem_node_arr, imported_modules, from_imports, trans, depth + 1) if elem_ty_arr is None: return None count_arr: int = self.Gen.TypeGenCType._eval_const_int_expr(count_node_arr, pool, from_imports, imported_modules) if self.Gen.TypeGenCType.ConstEvalOk == 0: - self.Gen.TypeGenErr.fatal_type_error(count_node_arr, "Built-in fixed-size array constructor 'array' length must be an integer constant expression (literal / CDefine constant name / constant arithmetic expression). Correct example: state: array[uint8, MAX_LEN]") + self.Gen.TypeGenErr.fatal_type_error(count_node_arr, "Built-in fixed-size array constructor 'array' length must be an integer constant expression (literal / DefineT constant name / constant arithmetic expression). Correct example: state: array[uint8, MAX_LEN]") if count_arr <= 0: self.Gen.TypeGenErr.fatal_type_error(count_node_arr, "Built-in fixed-size array constructor 'array' length must be positive. Correct example: state: array[uint32, 4]") return mlirlite.Array(pool, elem_ty_arr, count_arr) - # Bare CArray (from t import *): equivalent to t.CArray; otherwise generic + # Bare ArrayT (from t import *): equivalent to t.ArrayT; otherwise generic # lookup fails, the field type degrades to Ptr(i8), and linking fails. - if string.strcmp(gen_nm.id, "CArray") == 0 or string.strcmp(gen_nm.id, "rawarray") == 0: - # Single arg: CArray[elem_ty] -> Ptr(elem_ty) (statically inferred length array) + if string.strcmp(gen_nm.id, "ArrayT") == 0 or string.strcmp(gen_nm.id, "rawarray") == 0: + # Single arg: ArrayT[elem_ty] -> Ptr(elem_ty) (statically inferred length array) if sub.slice.kind() != ast.ASTKind.Tuple: elem_n: mlirlite.LLVMType* = self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1) if elem_n is None: return None return mlirlite.Ptr(pool, elem_n) - # Two args: CArray[elem_ty, count] -> [count x elem_ty] + # Two args: ArrayT[elem_ty, count] -> [count x elem_ty] tup_n: ast.Tuple* = ast.Tuple*(sub.slice) if tup_n.elts is None or len(tup_n.elts) < 2: return None elem_node_n: ast.AST* = tup_n.elts.get(0) @@ -2344,9 +2341,9 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): count_n: int = self.Gen.TypeGenCType._eval_const_int_expr(count_node_n, pool, from_imports, imported_modules) if self.Gen.TypeGenCType.ConstEvalOk == 0 or count_n <= 0: return None return mlirlite.Array(pool, elem_ty_n, count_n) - # rawptr[T] / CPtr[T] builtin bare pointer: equivalent to `T*` -> Ptr(T), + # rawptr[T] / PtrT[T] builtin bare pointer: equivalent to `T*` -> Ptr(T), # always appending exactly 1 layer. The old "absorb a layer" compensation is removed. - if string.strcmp(gen_nm.id, "rawptr") == 0 or string.strcmp(gen_nm.id, "CPtr") == 0: + if string.strcmp(gen_nm.id, "rawptr") == 0 or string.strcmp(gen_nm.id, "PtrT") == 0: if sub.slice.kind() != ast.ASTKind.Tuple: rp_elem: mlirlite.LLVMType* = self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1) if rp_elem is None: return None @@ -2381,7 +2378,7 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if self._is_cattr_marker(sub.value) != 0: return self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1) # Check whether value is an Attribute (module-qualified generic like numpy.ndarray[T]) - if sub.value.kind() != ast.ASTKind.Attribute: self.Gen.TypeGenErr.fatal_type_error(node, "Unsupported Subscript type annotation (only CArray / Callable are supported)") + if sub.value.kind() != ast.ASTKind.Attribute: self.Gen.TypeGenErr.fatal_type_error(node, "Unsupported Subscript type annotation (only ArrayT / Callable are supported)") arr_at: ast.Attribute* = ast.Attribute*(sub.value) if arr_at.attr is None: self.Gen.TypeGenErr.fatal_type_error(node, "Subscript Attribute missing attr") # Module-qualified generic class: numpy.ndarray[CDouble] -> use the bare class name @@ -2389,17 +2386,17 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if trans is not None: if self.Gen.ClassGenGeneric._find_generic_template(arr_at.attr) is not None: return self._resolve_generic_subscript_ty(pool, sub, arr_at.attr, trans, imported_modules, from_imports, depth + 1) - if arr_at.value is None or arr_at.value.kind() != ast.ASTKind.Name: self.Gen.TypeGenErr.fatal_type_error(node, "CArray / Callable must be qualified by the module name") + if arr_at.value is None or arr_at.value.kind() != ast.ASTKind.Name: self.Gen.TypeGenErr.fatal_type_error(node, "ArrayT / Callable must be qualified by the module name") arr_val_name: ast.Name* = ast.Name*(arr_at.value) if arr_val_name.id is None: self.Gen.TypeGenErr.fatal_type_error(node, "Subscript module name is None") # Function pointer type: Callable[[A, B], Ret] -> i8* (function values are bitcast to # i8* for storage and the signature is rebuilt at calls, matching VPC convention). if string.strcmp(arr_at.attr, "Callable") == 0: return mlirlite.Ptr(pool, mlirlite.Int8(pool)) - # Pointer type: CPtr[T] -> Ptr(T); always appends exactly 1 layer, no exceptions. - if string.strcmp(arr_at.attr, "CPtr") == 0: + # Pointer type: PtrT[T] -> Ptr(T); always appends exactly 1 layer, no exceptions. + if string.strcmp(arr_at.attr, "PtrT") == 0: if self.Gen.ImportsGen.is_module_imported(imported_modules, arr_val_name.id) == 0: - self.Gen.TypeGenErr.fatal_type_error(node, "CPtr module not imported") + self.Gen.TypeGenErr.fatal_type_error(node, "PtrT module not imported") inner_cptr_ty: mlirlite.LLVMType* = self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1) if inner_cptr_ty is None: # Element type not ready (class not yet registered): prefer a forward declaration @@ -2408,43 +2405,43 @@ novirtual class TypeGenResolve(TypeGenBase.TypeGenBase): if _fwd_cptr_ty is not None: return _fwd_cptr_ty return None return mlirlite.Ptr(pool, inner_cptr_ty) - if string.strcmp(arr_at.attr, "CArray") != 0: self.Gen.TypeGenErr.fatal_type_error(node, "Unsupported Subscript type annotation (only CArray / Callable are supported)") + if string.strcmp(arr_at.attr, "ArrayT") != 0: self.Gen.TypeGenErr.fatal_type_error(node, "Unsupported Subscript type annotation (only ArrayT / Callable are supported)") if self.Gen.ImportsGen.is_module_imported(imported_modules, arr_val_name.id) == 0: - self.Gen.TypeGenErr.fatal_type_error(node, "CArray module not imported") - # Single-arg form: t.CArray[elem_ty] -> Ptr(elem_ty), equivalent to - # t.CArray[elem_ty, None] (statically inferred length array). + self.Gen.TypeGenErr.fatal_type_error(node, "ArrayT module not imported") + # Single-arg form: t.ArrayT[elem_ty] -> Ptr(elem_ty), equivalent to + # t.ArrayT[elem_ty, None] (statically inferred length array). if sub.slice.kind() != ast.ASTKind.Tuple: elem_ty_single: mlirlite.LLVMType* = self.resolve_annotation_type(pool, sub.slice, imported_modules, from_imports, trans, depth + 1) if elem_ty_single is None: # Element type not ready: return None for multi-pass convergence return None return mlirlite.Ptr(pool, elem_ty_single) - # Two-arg form: t.CArray[elem_ty, count] -> [count x elem_ty] + # Two-arg form: t.ArrayT[elem_ty, count] -> [count x elem_ty] tup: ast.Tuple* = ast.Tuple*(sub.slice) - if tup.elts is None: self.Gen.TypeGenErr.fatal_type_error(node, "t.CArray Tuple missing elts") + if tup.elts is None: self.Gen.TypeGenErr.fatal_type_error(node, "t.ArrayT Tuple missing elts") elts_list: list[ast.AST*]* = tup.elts elts_n: sizet = len(elts_list) - if elts_n < 2: self.Gen.TypeGenErr.fatal_type_error(node, "t.CArray Tuple has too few elements") + if elts_n < 2: self.Gen.TypeGenErr.fatal_type_error(node, "t.ArrayT Tuple has too few elements") elem_node: ast.AST* = elts_list.get(0) count_node: ast.AST* = elts_list.get(1) - if elem_node is None or count_node is None: self.Gen.TypeGenErr.fatal_type_error(node, "t.CArray Tuple element is None") + if elem_node is None or count_node is None: self.Gen.TypeGenErr.fatal_type_error(node, "t.ArrayT Tuple element is None") # Recursively resolve the element type elem_ty: mlirlite.LLVMType* = self.resolve_annotation_type(pool, elem_node, imported_modules, from_imports, trans, depth + 1) if elem_ty is None: - # Element type not ready (t.CArray[class, N] with the class unregistered): return + # Element type not ready (t.ArrayT[class, N] with the class unregistered): return # None for multi-pass convergence rather than fatal erroring in Phase 1a. return None - # Resolve count (integer constant, None, CDefine name, or const expression like 2*MAX): + # Resolve count (integer constant, None, DefineT name, or const expression like 2*MAX): # None equals the single-arg form -> Ptr(elem); a name/expression yields [count x elem]. count_val: int = 0 if count_node.kind() == ast.ASTKind.Constant: cnt_cn: ast.Constant* = ast.Constant*(count_node) if cnt_cn.const_kind == ast.CONST_NONE: return mlirlite.Ptr(pool, elem_ty) # Use _eval_const_int_expr for the constant integer expression (Constant/Name/BinOp); - # pool/from_imports/imported_modules enable cross-module CDefine lookup. + # pool/from_imports/imported_modules enable cross-module DefineT lookup. count_val = self.Gen.TypeGenCType._eval_const_int_expr(count_node, pool, from_imports, imported_modules) - if self.Gen.TypeGenCType.ConstEvalOk == 0: self.Gen.TypeGenErr.fatal_type_error(count_node, "t.CArray count must be an integer constant, None, a CDefine constant name, or a constant expression") - if count_val <= 0: self.Gen.TypeGenErr.fatal_type_error(count_node, "t.CArray count must be positive") + if self.Gen.TypeGenCType.ConstEvalOk == 0: self.Gen.TypeGenErr.fatal_type_error(count_node, "t.ArrayT count must be an integer constant, None, a DefineT constant name, or a constant expression") + if count_val <= 0: self.Gen.TypeGenErr.fatal_type_error(count_node, "t.ArrayT count must be positive") # Create the ArrayType (no Ptr wrapping), so build_alloca allocates the array # and alloca returns [N x elem_ty]*. return mlirlite.Array(pool, elem_ty, count_val) diff --git a/App/lib/core/IncludesScanner.vp b/App/lib/core/IncludesScanner.vp index fd6a9b4..d1d36df 100644 --- a/App/lib/core/IncludesScanner.vp +++ b/App/lib/core/IncludesScanner.vp @@ -4,7 +4,7 @@ import string import stdlib import win32.win32file as win32file import win32.win32base as win32base -import win32.fileio as fileio +import io as fileio import hashlib import viperlib import lib.Projectrans.Utils as Utils diff --git a/App/lib/core/Repl.vp b/App/lib/core/Repl.vp index 5e8050a..a5a0c77 100644 --- a/App/lib/core/Repl.vp +++ b/App/lib/core/Repl.vp @@ -6,7 +6,7 @@ import viperlib import mlirlite import memhub import ast -import win32.fileio as fileio +import io as fileio import win32.win32file as win32file import win32.win32base as win32base import win32.win32process as win32process @@ -78,14 +78,14 @@ define _REPL_CMD_CAP = 16384 define _REPL_SRC_BUF_CAP = 1048576 # .vpi stub buffer (matches BuildPipeline.PYI_BUF_SIZE). define _REPL_VPI_BUF_CAP = 262144 -# ---- M4: session sha1 / fragment import lib / session baseline (CDefine + CTypedef) ---- +# ---- M4: session sha1 / fragment import lib / session baseline (DefineT + TypedefT) ---- # Max prior fragment import lib slots (a fragment takes one if its IR mentions a session symbol). define _REPL_FRAG_IMPLIB_MAX = 128 # Fragment import lib record array capacity (slots x 8-byte pointers). define _REPL_FRAG_IMPLIB_ARR_CAP = 1024 -# Session baseline (CDefine) entry limit; replayed when each fragment rebuilds its table. +# Session baseline (DefineT) entry limit; replayed when each fragment rebuilds its table. define _REPL_BASE_CDEFINE_MAX = 512 -# Session baseline (CTypedef) entry limit. +# Session baseline (TypedefT) entry limit. define _REPL_BASE_CTYPEDEF_MAX = 512 # includes class registration (done once at session init): max modules injected via vpi. define _REPL_INC_CLS_MOD_MAX = 512 @@ -293,7 +293,7 @@ ReplSessionSha1: bytes = None ReplFragImplibs: sizet* = None ReplFragImplibCount: int = 0 -# ==== M4: session baseline (CDefine/CTypedef survive across fragments) ==== +# ==== M4: session baseline (DefineT/TypedefT survive across fragments) ==== # Replayed each fragment after clear_*; only successful fragments append; failures truncate. ReplBaseCDefineNames: sizet* = None ReplBaseCDefineValues: sizet* = None @@ -514,10 +514,10 @@ def ReplInitSessionSha1() -> int: return 0 -# ---- Session baseline (CDefine/CTypedef): find / restore / append / truncate ---- +# ---- Session baseline (DefineT/TypedefT): find / restore / append / truncate ---- def _repl_base_cdefine_find(name: str) -> int: - """Find a baseline CDefine entry index by name; -1 when not found.""" + """Find a baseline DefineT entry index by name; -1 when not found.""" if name is None or ReplBaseCDefineNames is None: return -1 i: int for i in range(int(ReplBaseCDefineCount)): @@ -527,7 +527,7 @@ def _repl_base_cdefine_find(name: str) -> int: def ReplBaselineRestore(pool: memhub.MemBuddy*) -> int: - """Restore the session baseline (CDefine + CTypedef) into the just-cleared tables; 0=success.""" + """Restore the session baseline (DefineT + TypedefT) into the just-cleared tables; 0=success.""" if pool is None or ReplBaseCDefineNames is None: return 1 if GenApi._g_gen is None: return 1 i: int @@ -539,7 +539,7 @@ def ReplBaselineRestore(pool: memhub.MemBuddy*) -> int: if kind == 0: GenApi.register_cdefine_constant(pool, nm, int64(string.atoll(val))) elif kind == 1: GenApi.register_cdefine_double(pool, nm, string.atof(val)) else: - # String CDefine: GenApi has no wrapper, so call gen directly (same shape as + # String DefineT: GenApi has no wrapper, so call gen directly (same shape as # the clear_cdefine_constants wrapper). GenApi._g_gen.TypeGenCDef.register_cdefine_string(pool, nm, val) for i in range(int(ReplBaseCTypedefCount)): @@ -551,7 +551,7 @@ def ReplBaselineRestore(pool: memhub.MemBuddy*) -> int: def ReplBaselineCapture() -> int: - """Append baseline-missing CDefine/CTypedef entries from the current tables into the session baseline; 0=success.""" + """Append baseline-missing DefineT/TypedefT entries from the current tables into the session baseline; 0=success.""" global ReplBaseCDefineCount, ReplBaseCTypedefCount if _perm_pool is None or GenApi._g_gen is None: return 1 cdef_names: list[str]* = GenApi._g_gen.TypeGenCDef.CDefineNames @@ -882,7 +882,7 @@ def ReplInitSession() -> int: head.B = 0 head.VarCount = 0 head.LastFrag = 0 - # 8) M4 permanent state: session sha1 + fragment import lib table + session baseline (CDefine/CTypedef). + # 8) M4 permanent state: session sha1 + fragment import lib table + session baseline (DefineT/TypedefT). if ReplInitSessionSha1() != 0: return 1 ReplFragImplibs = _perm_pool.alloc(_REPL_FRAG_IMPLIB_ARR_CAP) ReplBaseCDefineNames = _perm_pool.alloc(8 * _REPL_BASE_CDEFINE_MAX) @@ -1678,7 +1678,7 @@ def _exec_repl_src(mb: memhub.MemBuddy*, src: str) -> int: if ReplCtxObj is not None: ReplCtxObj.Count = ReplSavedVarCount ReplCtxObj.FuncCount = ReplSavedFuncCount - # M4: roll back the session baseline (CDefine/CTypedef) watermarks; a no-op on the normal + # M4: roll back the session baseline (DefineT/TypedefT) watermarks; a no-op on the normal # path, it keeps the "failed fragment never changes the baseline" invariant. ReplBaselineTruncate(ReplSavedCDefineCount, ReplSavedCTypedefCount) return 0 @@ -1706,7 +1706,7 @@ def ReplExecFragment(mb: memhub.MemBuddy*, src: str) -> int: VLogger.set_repl_jmpbuf(ReplJmpBuf) ReplSavedVarCount = ReplCtxObj.Count ReplSavedFuncCount = ReplCtxObj.FuncCount - # M4: session baseline watermarks (CDefine/CTypedef); a failed fragment must not add + # M4: session baseline watermarks (DefineT/TypedefT); a failed fragment must not add # increments, same idea as the var/func watermarks (only success calls ReplBaselineCapture). ReplSavedCDefineCount = ReplBaseCDefineCount ReplSavedCTypedefCount = ReplBaseCTypedefCount @@ -1745,7 +1745,7 @@ def ReplExecFragment(mb: memhub.MemBuddy*, src: str) -> int: vpi_dir: str = Config.TempDir if vpi_dir is None: vpi_dir = ReplTmpDir GenApi.set_temp_dir(vpi_dir) - # CallGenBase.TempDir/TempDirLen (cross-module .vpi/.text.ll lookup, CExport checks) is only + # CallGenBase.TempDir/TempDirLen (cross-module .vpi/.text.ll lookup, ExportT checks) is only # set by SetTempDir; set it here once per fragment, same value as set_temp_dir. GenApi.SetTempDir(vpi_dir) GenApi.set_current_file_type(ReplSrcPath) @@ -1759,7 +1759,7 @@ def ReplExecFragment(mb: memhub.MemBuddy*, src: str) -> int: # Struct registration uses permanent allocation (like Phase1b/Phase2 StdlibAlloc=1): table, # entry names and field types go into PermPool, else later fragments read freed memory. GenApi.set_stdlib_alloc(1) - # M4: replay the CDefine/CTypedef session baseline; the clear_* above emptied the tables, and + # M4: replay the DefineT/TypedefT session baseline; the clear_* above emptied the tables, and # this fragment's additions are captured on success by ReplBaselineCapture. ReplBaselineRestore(mb) @@ -1875,7 +1875,7 @@ def ReplExecFragment(mb: memhub.MemBuddy*, src: str) -> int: if ReplHostImplib is None: stdio.printf(" Hint: host import lib (.dll.a) not found -- " "cross-module symbols (of the form <16-hex>.) cannot be resolved; " "build the host with ViperC/project.vpj (including -Wl,--export-all-symbols), " "and keep .dll.a in the same dir as the exe\n") else: - stdio.printf(" Hint: if the output contains undefined symbol of the form <16-hex>., " "that symbol is not defined/exported by the host (the corresponding includes module was not linked when building the host); " "if the function is `export def` in source (CExport, bare-name definition) yet still carries a prefix, " "the include stub .vpi is probably missing (see the includes scan line at session start)\n") + stdio.printf(" Hint: if the output contains undefined symbol of the form <16-hex>., " "that symbol is not defined/exported by the host (the corresponding includes module was not linked when building the host); " "if the function is `export def` in source (ExportT, bare-name definition) yet still carries a prefix, " "the include stub .vpi is probably missing (see the includes scan line at session start)\n") return 1 # 11') M4: record this fragment's import lib (if it defined session-prefixed symbols) so later @@ -1920,7 +1920,7 @@ def ReplExecFragment(mb: memhub.MemBuddy*, src: str) -> int: stdio.printf("fragment %s: failed to read the result area\n", ReplNameBuf) return 1 if r_rc != 0: stdio.printf("fragment %s: fragment returned non-zero status %d\n", ReplNameBuf, r_rc) - # 14') M4: on success, merge this fragment's new CDefine/CTypedef entries into the session + # 14') M4: on success, merge this fragment's new DefineT/TypedefT entries into the session # baseline; failure paths skip this, so the baseline never holds a failed fragment's leftovers. ReplBaselineCapture() return 0 @@ -1950,7 +1950,7 @@ def run(mb: memhub.MemBuddy*) -> int: ReplSavedFuncCount = 0 VLogger.set_repl_jmpbuf(jb) # jit_flag is set True by main before calling run (see main.vp --repl); run resets it to False. - lg: VLogger.Logger = VLogger.logger + lg: VLogger.Logger* = VLogger.logger # Arm the recovery point before use: session init may longjmp via VLogger.error, and an # un-armed jmp_buf would be UB; init is the only stage that must succeed, so exit on failure. @@ -2056,7 +2056,7 @@ def run(mb: memhub.MemBuddy*) -> int: if ReplCtxObj is not None: ReplCtxObj.Count = ReplSavedVarCount ReplCtxObj.FuncCount = ReplSavedFuncCount - # M4: roll back the session baseline (CDefine/CTypedef) watermarks (same idea as above). + # M4: roll back the session baseline (DefineT/TypedefT) watermarks (same idea as above). ReplBaselineTruncate(ReplSavedCDefineCount, ReplSavedCTypedefCount) mb.reset_to(mark) if eof_hit != 0: break