refactor(app): win32.fileio 全部切换到跨平台 io

20 个文件把 'import win32.fileio as fileio' 改为 'import io as fileio',
文件 IO 统一走 includes/io.vp(编译期 cif 选择 win32.fileio / posix.fileio)。
依赖编译器新增的模块级 cif 条件 import(a282918)。

注:这些文件中存在此前未提交的既有改动,按整文件一并提交。
This commit is contained in:
TermiNexus committed 2026-10-25 12:45:49 +08:00
1 parent a2829182b8
commit 94515ddf12
20 files changed
+2184 -859

No files matched your search

+9 -7
View File
@@ -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
+12 -6
View File
@@ -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
+1 -1
View File
@@ -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
+95 -30
View File
@@ -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'
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -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
+24 -5
View File
@@ -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
+10 -5
View File
@@ -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
+20 -13
View File
@@ -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
+1 -1
View File
@@ -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
+198 -183
View File
@@ -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* = <degraded>` 的修正)。
# 注解解析成非指针(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 = "<anonymous struct>"
@@ -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'
+231 -23
View File
@@ -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
+36 -12
View File
@@ -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)
+116 -70
View File
@@ -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)
+1 -1
View File
@@ -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
+200 -203
View File
@@ -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 "<arg>*" 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.<name>` 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 "<subscript>"
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 "<subscript>"
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.<name>` 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)
+1 -1
View File
@@ -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
+19 -19
View File
@@ -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 (<exe name>.dll.a) not found -- " "cross-module symbols (of the form <16-hex>.<func name>) cannot be resolved; " "build the host with ViperC/project.vpj (including -Wl,--export-all-symbols), " "and keep <exe name>.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>.<name>, " "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>.<name>, " "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