发布基线
This commit is contained in:
1 parent
7385f9ed3b
commit
c3b322c7d1
44 files changed
+11797
-10553
No files matched your search
@@ -0,0 +1,8 @@
|
||||
def a(l: int):
|
||||
pass
|
||||
|
||||
def main2() -> int*:
|
||||
a(1)
|
||||
# 1024000000000000000000
|
||||
return a
|
||||
|
||||
+2903
-2917
File diff suppressed because it is too large.
Load diff
@@ -1,10 +1,10 @@
|
||||
from stdint import *
|
||||
import string
|
||||
import stdlib
|
||||
import sys
|
||||
import memhub
|
||||
import viperlib
|
||||
import io
|
||||
import win32.win32base
|
||||
import ast
|
||||
import mlirlite
|
||||
import lib.core.VLogger as VLogger
|
||||
@@ -233,7 +233,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
|
||||
# On-demand dependency scan (App-first): seed the queue from App imports, then BFS-resolve
|
||||
# reachable includes only (read + SHA1 + import parse); unreferenced ones are never read.
|
||||
t_scan0: uint64 = win32.win32base.GetTickCount64()
|
||||
t_scan0: uint64 = sys.monotonic_ms()
|
||||
reachable_set: str = None
|
||||
reachable_count: int = 0
|
||||
use_reachable: int = 0
|
||||
@@ -257,7 +257,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
else:
|
||||
VLogger.warning("SourceDir unavailable; cannot build the dependency graph; skipping Phase1", "Phase1")
|
||||
return 0
|
||||
t_scan1: uint64 = win32.win32base.GetTickCount64()
|
||||
t_scan1: uint64 = sys.monotonic_ms()
|
||||
VLogger.info(f"dependency-graph scan total time: {t_scan1 - t_scan0} ms (reachable {reachable_count} includes)", "Phase1")
|
||||
|
||||
# Build the reachable FileEntry list from the Sha1Store; 1a/1b-pre/1b iterate result
|
||||
@@ -348,7 +348,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
# Iterate files: topo order preferred, falling back to alphabetical
|
||||
iter_count: int = int(topo_count if topo_count > 0 else result.Count)
|
||||
# 1a total-time start (struct/enum/union registration)
|
||||
t_a0: uint64 = win32.win32base.GetTickCount64()
|
||||
t_a0: uint64 = sys.monotonic_ms()
|
||||
for i in range(int(iter_count)):
|
||||
# Per-file mark: free this file's AST after translating (registered data is in perm_pool)
|
||||
p1a_mark: sizet = mb.mark()
|
||||
@@ -382,7 +382,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
continue
|
||||
|
||||
# Per-file timer start (read + parse, to locate 1a stalls)
|
||||
t_fa0: uint64 = win32.win32base.GetTickCount64()
|
||||
t_fa0: uint64 = sys.monotonic_ms()
|
||||
|
||||
# Read file contents
|
||||
file_path_r: str = entry_r.Path
|
||||
@@ -427,7 +427,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
continue
|
||||
|
||||
# Timer 1: read + parse done
|
||||
t_fa1: uint64 = win32.win32base.GetTickCount64()
|
||||
t_fa1: uint64 = sys.monotonic_ms()
|
||||
|
||||
# Create Translator with _declare_only=1 (register struct/enum/union)
|
||||
tr_r: HandlesTranslator.Translator* = HandlesTranslator.Translator()
|
||||
@@ -446,7 +446,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
GenApi.reset_visible_structs(mb, 0)
|
||||
ret_r: int = tr_r.translate(tree_r)
|
||||
# Timer 2: translate(declare=1) returned
|
||||
t_fa2: uint64 = win32.win32base.GetTickCount64()
|
||||
t_fa2: uint64 = sys.monotonic_ms()
|
||||
if ret_r != 0: p1a_fl += 1
|
||||
else: p1a_reg += 1
|
||||
|
||||
@@ -471,7 +471,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
mb.reset_to(p1a_mark)
|
||||
|
||||
# 1a total time (struct/enum/union registration, incl. per-file read+parse+declaration)
|
||||
t_a1: uint64 = win32.win32base.GetTickCount64()
|
||||
t_a1: uint64 = sys.monotonic_ms()
|
||||
VLogger.info(f"1a total time: {t_a1-t_a0} ms (registering struct/enum, reg={p1a_reg} skp={p1a_skp})", "Phase1")
|
||||
|
||||
if p1a_fl > 0:
|
||||
@@ -498,7 +498,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
p1bpre_failed: int = 0
|
||||
dir_len_pv: sizet = string.strlen(temp_dir)
|
||||
# 1b-pre total-time start (vpi pre-generation)
|
||||
t_pv0: uint64 = win32.win32base.GetTickCount64()
|
||||
t_pv0: uint64 = sys.monotonic_ms()
|
||||
for i_pv in range(int(result.Count)):
|
||||
entry_addr_pv: uint64 = uint64(result.Entries) + i_pv * entry_size
|
||||
entry_pv: IncludesScanner.FileEntry* = IncludesScanner.FileEntry*(entry_addr_pv)
|
||||
@@ -596,7 +596,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
mb.reset_to(mark1)
|
||||
|
||||
# 1b-pre total time (vpi pre-generation)
|
||||
t_pv1: uint64 = win32.win32base.GetTickCount64()
|
||||
t_pv1: uint64 = sys.monotonic_ms()
|
||||
VLogger.info(f"1b-pre total time: {t_pv1-t_pv0} ms (vpi pre-generation)", "Phase1")
|
||||
|
||||
if p1bpre_failed > 0:
|
||||
@@ -609,7 +609,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
skipped: int = 0
|
||||
failed: int = 0
|
||||
# 1b total-time start (full translation)
|
||||
t_b0: uint64 = win32.win32base.GetTickCount64()
|
||||
t_b0: uint64 = sys.monotonic_ms()
|
||||
|
||||
loop_n_b: int = int(result.Count)
|
||||
if topo_count > 0: loop_n_b = topo_count
|
||||
@@ -732,7 +732,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
stdlib.free(stub_path)
|
||||
|
||||
# Per-file timer start: read (parse/translate/dump segments, matching Phase2 instrumentation)
|
||||
t_f0: uint64 = win32.win32base.GetTickCount64()
|
||||
t_f0: uint64 = sys.monotonic_ms()
|
||||
|
||||
# Read file contents
|
||||
file_path: str = entry.Path
|
||||
@@ -786,7 +786,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
continue
|
||||
|
||||
# Timer 1: read + lexer/tokenize/parse done
|
||||
t_f1: uint64 = win32.win32base.GetTickCount64()
|
||||
t_f1: uint64 = sys.monotonic_ms()
|
||||
|
||||
# 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: TypedefT = TYPE" lines.
|
||||
@@ -842,7 +842,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
|
||||
ret: int = tr.translate(tree)
|
||||
# Timer 2: translate returned
|
||||
t_f2: uint64 = win32.win32base.GetTickCount64()
|
||||
t_f2: uint64 = sys.monotonic_ms()
|
||||
# Diagnostic: translate returned normally (if it hangs inside, this line is not printed,
|
||||
# distinguishing an internal hang from a later dump hang).
|
||||
if ret == 0: VLogger.info(f"translation complete (translate returned): {file_path}", "Phase1")
|
||||
@@ -937,7 +937,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
stdlib.free(vpcall_text_p1)
|
||||
|
||||
# Timer 3: dump (stub+text) done; print the per-file three-segment timing
|
||||
t_f3: uint64 = win32.win32base.GetTickCount64()
|
||||
t_f3: uint64 = sys.monotonic_ms()
|
||||
VLogger.info(f"[{i+1}/{loop_n_b}] translating: {file_path} (parse={t_f1-t_f0} ms, translate={t_f2-t_f1} ms, dump={t_f3-t_f2} ms, total={t_f3-t_f0} ms)", "Phase1")
|
||||
|
||||
# save dependencies(_imported_modules) for Phase B
|
||||
@@ -971,7 +971,7 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, log: VLogg
|
||||
if sc_rc != 0: VLogger.error(f"memory corruption (free list, rc={sc_rc}): {file_path}", "Phase1")
|
||||
|
||||
# 1b total time (full translation + dump)
|
||||
t_b1: uint64 = win32.win32base.GetTickCount64()
|
||||
t_b1: uint64 = sys.monotonic_ms()
|
||||
VLogger.info(f"1b total time: {t_b1-t_b0} ms (full translation, trans={translated} skip={skipped} fail={failed})", "Phase1")
|
||||
|
||||
# Release the topo-sort resources used by Phase 1b
|
||||
|
||||
+1519
-1563
File diff suppressed because it is too large.
Load diff
@@ -626,7 +626,14 @@ def get_includes_binary_dir() -> str:
|
||||
# (sha1 subdirectories) while TPC's includes cache at {IncludesDir}.binary/ holds flat
|
||||
# "<sha1>.<triple>.pic.obj" files, so sharing one directory would mix the two layouts.
|
||||
def mlir_dir_suffix() -> str:
|
||||
"""Directory isolation suffix for the includes cache of the MLIR backend (always "_mlir")."""
|
||||
"""Directory isolation suffix for the includes cache of the MLIR backend.
|
||||
|
||||
The object format is target-specific, so a Linux-target build uses a separate
|
||||
cache from the Windows one (otherwise cross-builds would link the wrong objects):
|
||||
Windows target -> "_mlir" Linux target -> "_mlir_linux"
|
||||
"""
|
||||
if TargetTriple is not None and string.strstr(TargetTriple, "linux") is not None:
|
||||
return "_mlir_linux"
|
||||
return "_mlir"
|
||||
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@ import string
|
||||
import stdlib
|
||||
import viperlib
|
||||
import hashlib
|
||||
import win32.win32file
|
||||
import win32.win32base
|
||||
import io
|
||||
import os
|
||||
|
||||
|
||||
# Utils - project utility functions (SHA1 hashing, directory cleanup)
|
||||
@@ -140,50 +139,22 @@ def PathToModuleName(path: str, strip_includes_prefix: int) -> str:
|
||||
def CleanDir(dir_path: str) -> int:
|
||||
"""Recursively delete files/subdirs under dir_path, returning the count."""
|
||||
if dir_path is None: return -1
|
||||
dir_len: sizet = string.strlen(dir_path)
|
||||
pattern: bytes = stdlib.malloc(dir_len + 8)
|
||||
if pattern is None: return -1
|
||||
viperlib.snprintf(pattern, dir_len + 8, "%s/*", dir_path)
|
||||
|
||||
find_data: win32.win32file.WIN32_FIND_DATAA* = stdlib.malloc(win32.win32file.WIN32_FIND_DATAA.__sizeof__())
|
||||
if find_data is None:
|
||||
stdlib.free(pattern)
|
||||
return -1
|
||||
string.memset(find_data, 0, win32.win32file.WIN32_FIND_DATAA.__sizeof__())
|
||||
|
||||
handle: win32.win32base.HANDLE = win32.win32file.FindFirstFileA(pattern, find_data)
|
||||
if handle == win32.win32base.INVALID_HANDLE_VALUE:
|
||||
stdlib.free(pattern)
|
||||
stdlib.free(find_data)
|
||||
return 0
|
||||
|
||||
reader: os.DirReader* = os.scandir(dir_path)
|
||||
if reader is None: return 0
|
||||
entry: os.DirEntry = os.DirEntry()
|
||||
deleted: int = 0
|
||||
while True:
|
||||
fname: str = find_data.cFileName
|
||||
if fname is not None:
|
||||
# Skip . and ..
|
||||
if fname[0] == '.':
|
||||
if fname[1] == '\0': fname = None
|
||||
elif fname[1] == '.' and fname[2] == '\0': fname = None
|
||||
if fname is not None:
|
||||
full_path: bytes = JoinPath(dir_path, fname)
|
||||
if full_path is not None:
|
||||
# Check whether this is a directory
|
||||
is_dir: uint32 = find_data.dwFileAttributes & win32.win32file.FILE_ATTRIBUTE_DIRECTORY
|
||||
if is_dir != 0:
|
||||
# Recurse into the subdirectory
|
||||
sub_deleted: int = CleanDir(full_path)
|
||||
if sub_deleted > 0: deleted += sub_deleted
|
||||
# Remove the now-empty subdirectory
|
||||
win32.win32file.RemoveDirectoryA(full_path)
|
||||
else:
|
||||
# Delete the file
|
||||
if win32.win32file.DeleteFileA(full_path) != 0: deleted += 1
|
||||
stdlib.free(full_path)
|
||||
|
||||
if win32.win32file.FindNextFileA(handle, find_data) == 0: break
|
||||
|
||||
win32.win32file.FindClose(handle)
|
||||
stdlib.free(pattern)
|
||||
stdlib.free(find_data)
|
||||
rr: int = os.readdir(reader, &entry)
|
||||
if rr == 0: break
|
||||
if rr < 0: break
|
||||
full_path: bytes = JoinPath(dir_path, entry.name)
|
||||
if full_path is None: continue
|
||||
if entry.is_dir:
|
||||
sub_deleted: int = CleanDir(full_path)
|
||||
if sub_deleted > 0: deleted += sub_deleted
|
||||
os.rmdir(full_path)
|
||||
else:
|
||||
if os.remove(full_path) == 0: deleted += 1
|
||||
stdlib.free(full_path)
|
||||
os.closedir(reader)
|
||||
return deleted
|
||||
@@ -810,6 +810,25 @@ def _PyiDefaultStr(buf: bytes, buf_size: sizet, pos: sizet, node: ast.AST*, dept
|
||||
pos = _PyiEmit(buf, buf_size, pos, num_buf_dv)
|
||||
stdlib.free(num_buf_dv)
|
||||
return pos
|
||||
if cn.const_kind == ast.CONST_FLOAT:
|
||||
# %.17g round-trips an IEEE double exactly (used elsewhere in this file too). Without
|
||||
# this branch a float default emitted nothing, producing a malformed stub line
|
||||
# `def f(x: float = )` that crashes the vpi default scan.
|
||||
num_buf_df: str = stdlib.malloc(64)
|
||||
if num_buf_df is not None:
|
||||
viperlib.snprintf(num_buf_df, 64, "%.17g", cn.float_val)
|
||||
# %.17g prints an integral float as "-1"/"1" (no dot or exponent). The vpi default
|
||||
# scanner would classify that as an INTEGER and hand an i32 to a float parameter
|
||||
# (cross-module defaults then read 0.0). Keep the token unambiguously float.
|
||||
has_frac_df: int = 0
|
||||
j_df: sizet = 0
|
||||
while num_buf_df[j_df] != 0:
|
||||
if num_buf_df[j_df] == '.' or num_buf_df[j_df] == 'e' or num_buf_df[j_df] == 'E': has_frac_df = 1
|
||||
j_df += 1
|
||||
pos = _PyiEmit(buf, buf_size, pos, num_buf_df)
|
||||
if has_frac_df == 0: pos = _PyiEmit(buf, buf_size, pos, ".0")
|
||||
stdlib.free(num_buf_df)
|
||||
return pos
|
||||
if cn.const_kind == ast.CONST_STR:
|
||||
pos = _PyiEmit(buf, buf_size, pos, "\"")
|
||||
if cn.str_val is not None: pos = _PyiEmit(buf, buf_size, pos, cn.str_val)
|
||||
@@ -836,6 +855,32 @@ def _PyiDefaultStr(buf: bytes, buf_size: sizet, pos: sizet, node: ast.AST*, dept
|
||||
if uop.operand is not None: return _PyiDefaultStr(buf, buf_size, pos, uop.operand, depth + 1)
|
||||
return pos
|
||||
|
||||
# Call: struct-constructor / call default (e.g. Vec2(1.0, 2.0)). Emitting the expression
|
||||
# keeps the .vpi parameter list syntactically valid (an empty default used to drop the
|
||||
# whole parameter and produce an arity mismatch) and lets the default scanner encode the
|
||||
# literal field values for cross-module reconstruction.
|
||||
if k == ast.ASTKind.Call:
|
||||
cc: ast.Call* = ast.Call*(node)
|
||||
if cc.func is None: return pos
|
||||
pos = _PyiDefaultStr(buf, buf_size, pos, cc.func, depth + 1)
|
||||
pos = _PyiEmit(buf, buf_size, pos, "(")
|
||||
if cc.args is not None:
|
||||
cargs: list[ast.AST*]* = cc.args
|
||||
can: sizet = len(cargs)
|
||||
for cai in range(int(can)):
|
||||
if cai > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
||||
pos = _PyiDefaultStr(buf, buf_size, pos, cargs.get(cai), depth + 1)
|
||||
return _PyiEmit(buf, buf_size, pos, ")")
|
||||
|
||||
# Attribute: module-qualified constant / constructor (e.g. mod.Vec2, mod.SHARE)
|
||||
if k == ast.ASTKind.Attribute:
|
||||
at: ast.Attribute* = ast.Attribute*(node)
|
||||
if at.value is not None:
|
||||
pos = _PyiDefaultStr(buf, buf_size, pos, at.value, depth + 1)
|
||||
pos = _PyiEmit(buf, buf_size, pos, ".")
|
||||
if at.attr is not None: pos = _PyiEmit(buf, buf_size, pos, at.attr)
|
||||
return pos
|
||||
|
||||
# Other complex expressions (BinOp etc.): emit nothing; caller enforces fail-fast
|
||||
return pos
|
||||
|
||||
|
||||
@@ -456,14 +456,14 @@ def _lookup_vpi_typedef(src_sha1: str, name: str) -> str:
|
||||
def _resolve_vpi_annot_with_typedefs(pool: memhub.MemBuddy*, src_sha1: str, ret_annot: str, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*:
|
||||
"""Resolve a vpi annotation string, following same-module typedef aliases (max 4 levels)."""
|
||||
if ret_annot is None or pool is None: return None
|
||||
ty: mlirlite.LLVMType* = _resolve_vpi_ret_annot_str(pool, ret_annot, i32_ty, i8_ptr_ty)
|
||||
ty: mlirlite.LLVMType* = _resolve_vpi_ret_annot_str(pool, ret_annot, i32_ty, i8_ptr_ty, src_sha1)
|
||||
if ty is not None: return ty
|
||||
cur: str = ret_annot
|
||||
depth: int = 0
|
||||
while depth < 4:
|
||||
nxt: str = _lookup_vpi_typedef(src_sha1, cur)
|
||||
if nxt is None: return None
|
||||
ty = _resolve_vpi_ret_annot_str(pool, nxt, i32_ty, i8_ptr_ty)
|
||||
ty = _resolve_vpi_ret_annot_str(pool, nxt, i32_ty, i8_ptr_ty, src_sha1)
|
||||
if ty is not None: return ty
|
||||
cur = nxt
|
||||
depth += 1
|
||||
@@ -974,7 +974,7 @@ def _resolve_vpi_param_annot_seg_template(pool: memhub.MemBuddy*, src_sha1: str,
|
||||
if uint64(subst_t) != uint64(seg_buf_t): stdlib.free(seg_buf_t)
|
||||
seg_buf_t = subst_t
|
||||
stdlib.free(tparam_t)
|
||||
ty_t = _resolve_vpi_ret_annot_str(pool, seg_buf_t, i32_ty, i8_ptr_ty)
|
||||
ty_t = _resolve_vpi_ret_annot_str(pool, seg_buf_t, i32_ty, i8_ptr_ty, src_sha1)
|
||||
stdlib.free(seg_buf_t)
|
||||
break
|
||||
stdlib.free(base_t)
|
||||
@@ -1011,7 +1011,7 @@ def _repair_declare_params_from_text(pool: memhub.MemBuddy*, func: mlirlite.Func
|
||||
if seg_buf_r is not None:
|
||||
string.strncpy(seg_buf_r, prm_annots + seg_start2, seg_len2)
|
||||
seg_buf_r[seg_len2] = '\0'
|
||||
rt: mlirlite.LLVMType* = _resolve_vpi_ret_annot_str(pool, seg_buf_r, i32_ty, i8_ptr_ty)
|
||||
rt: mlirlite.LLVMType* = _resolve_vpi_ret_annot_str(pool, seg_buf_r, i32_ty, i8_ptr_ty, src_sha1)
|
||||
if rt is not None: param_ty_r = rt
|
||||
stdlib.free(seg_buf_r)
|
||||
mlirlite.add_param(pool, func, param_ty_r, "arg")
|
||||
@@ -1041,7 +1041,7 @@ def _resolve_vpi_param_annot_seg(pool: memhub.MemBuddy*, src_sha1: str, cls_name
|
||||
if seg_buf_p is None: return None
|
||||
string.strncpy(seg_buf_p, prm_annots_p + seg_start_p, seg_len_p)
|
||||
seg_buf_p[seg_len_p] = '\0'
|
||||
seg_ty_p: mlirlite.LLVMType* = _resolve_vpi_ret_annot_str(pool, seg_buf_p, i32_ty, i8_ptr_ty)
|
||||
seg_ty_p: mlirlite.LLVMType* = _resolve_vpi_ret_annot_str(pool, seg_buf_p, i32_ty, i8_ptr_ty, src_sha1)
|
||||
stdlib.free(seg_buf_p)
|
||||
return seg_ty_p
|
||||
|
||||
@@ -1913,9 +1913,29 @@ def _vpi_struct_or_enum(seg: str) -> mlirlite.LLVMType*:
|
||||
return struct_ty
|
||||
|
||||
|
||||
# _vpi_struct_or_enum_in_module - module-aware variant of _vpi_struct_or_enum.
|
||||
# A .vpi return/param annotation carries the DEFINING module's bare name (`-> Point`), so a
|
||||
# bare-name registry lookup returns the first same-named struct from another module and the
|
||||
# caller gets the wrong layout (cross-module same-name struct returned by value). Resolve by
|
||||
# (name, src_sha1) first; only fall back to the context-free lookup when the module has no entry.
|
||||
def _vpi_struct_or_enum_in_module(seg: str, src_sha1: str) -> mlirlite.LLVMType*:
|
||||
"""Module-aware struct/enum lookup for a .vpi annotation segment."""
|
||||
if seg is None: return None
|
||||
if src_sha1 is not None:
|
||||
se_m: GenTypes.StructEntry* = GenApi.find_struct_by_module(seg, src_sha1)
|
||||
if se_m is not None:
|
||||
en_m: mlirlite.LLVMType* = GenApi.get_enum_base_type_in_module(seg, src_sha1)
|
||||
# struct forward placeholder must not shadow a real enum in the same module
|
||||
if se_m.IsForwardDecl != 0: return en_m
|
||||
if se_m.Ty is not None: return se_m.Ty
|
||||
en_m2: mlirlite.LLVMType* = GenApi.get_enum_base_type_in_module(seg, src_sha1)
|
||||
if en_m2 is not None: return en_m2
|
||||
return _vpi_struct_or_enum(seg)
|
||||
|
||||
|
||||
# _resolve_single_vpi_type_str - resolve a single type segment to LLVMType.
|
||||
# Pure string parsing, no AST nodes.
|
||||
def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.LLVMType*:
|
||||
def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str, src_sha1: str = None) -> mlirlite.LLVMType*:
|
||||
"""Resolve a single type segment to LLVMType; None if unrecognized."""
|
||||
if seg is None or pool is None: return None
|
||||
# Source-syntax descriptors / value references: `const T` / `volatile T` / `&T` /
|
||||
@@ -1930,19 +1950,19 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
|
||||
if trim_buf is None: return None
|
||||
string.strncpy(trim_buf, seg + seg_sp0, seg_ep0 - seg_sp0)
|
||||
trim_buf[seg_ep0 - seg_sp0] = '\0'
|
||||
trim_ty: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, trim_buf)
|
||||
trim_ty: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, trim_buf, src_sha1)
|
||||
stdlib.free(trim_buf)
|
||||
return trim_ty
|
||||
if seg_l0 - seg_sp0 >= 6 and string.strncmp(seg + seg_sp0, "const ", 6) == 0:
|
||||
return _resolve_single_vpi_type_str(pool, seg + seg_sp0 + 6)
|
||||
return _resolve_single_vpi_type_str(pool, seg + seg_sp0 + 6, src_sha1)
|
||||
if seg_l0 - seg_sp0 >= 9 and string.strncmp(seg + seg_sp0, "volatile ", 9) == 0:
|
||||
return _resolve_single_vpi_type_str(pool, seg + seg_sp0 + 9)
|
||||
return _resolve_single_vpi_type_str(pool, seg + seg_sp0 + 9, src_sha1)
|
||||
if seg_sp0 < seg_l0 and seg[seg_sp0] == '&':
|
||||
# Value-reference parameter ABI: &T / &temp T -> T* (both modes use a pointer).
|
||||
ref_sp1: sizet = seg_sp0 + 1
|
||||
while ref_sp1 < seg_l0 and (seg[ref_sp1] == ' ' or seg[ref_sp1] == '\t'): ref_sp1 += 1
|
||||
if seg_l0 - ref_sp1 >= 5 and string.strncmp(seg + ref_sp1, "temp ", 5) == 0: ref_sp1 += 5
|
||||
inner_ref: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg + ref_sp1)
|
||||
inner_ref: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg + ref_sp1, src_sha1)
|
||||
if inner_ref is not None: return mlirlite.Ptr(pool, inner_ref)
|
||||
return None
|
||||
if seg_sp0 < seg_l0 and seg[seg_sp0] == '[':
|
||||
@@ -1950,7 +1970,7 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
|
||||
arr_rb: sizet = seg_sp0 + 1
|
||||
while arr_rb < seg_l0 and seg[arr_rb] != ']': arr_rb += 1
|
||||
if arr_rb < seg_l0:
|
||||
inner_arr: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg + arr_rb + 1)
|
||||
inner_arr: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg + arr_rb + 1, src_sha1)
|
||||
if inner_arr is not None: return mlirlite.Ptr(pool, inner_arr)
|
||||
return None
|
||||
# Canonical pointer form is `T*` (also rawptr[T]); rewrite trailing `*`
|
||||
@@ -1978,7 +1998,7 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
|
||||
star_buf_ps[star_off_ps + star_base_end_ps] = '\0'
|
||||
for star_k_ps in range(int(star_cnt_ps)):
|
||||
string.strcat(star_buf_ps, "]")
|
||||
star_ty_ps: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, star_buf_ps)
|
||||
star_ty_ps: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, star_buf_ps, src_sha1)
|
||||
stdlib.free(star_buf_ps)
|
||||
return star_ty_ps
|
||||
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||||
@@ -2030,7 +2050,7 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
|
||||
if inner_mk is not None:
|
||||
string.strncpy(inner_mk, seg + lb_off + 1, inner_len_mk)
|
||||
inner_mk[inner_len_mk] = '\0'
|
||||
inner_ty_mk: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, inner_mk)
|
||||
inner_ty_mk: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, inner_mk, src_sha1)
|
||||
stdlib.free(inner_mk)
|
||||
stdlib.free(mk_buf)
|
||||
return inner_ty_mk
|
||||
@@ -2045,7 +2065,7 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
|
||||
if inner_buf_rp is not None:
|
||||
string.strncpy(inner_buf_rp, seg + 7, inner_len_rp)
|
||||
inner_buf_rp[inner_len_rp] = '\0'
|
||||
inner_ty_rp: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, inner_buf_rp)
|
||||
inner_ty_rp: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, inner_buf_rp, src_sha1)
|
||||
stdlib.free(inner_buf_rp)
|
||||
if inner_ty_rp is not None: return mlirlite.Ptr(pool, inner_ty_rp)
|
||||
return None
|
||||
@@ -2059,7 +2079,7 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
|
||||
# Bare class / enum name (e.g. `-> Lexer*` written in the defining module's own vpi):
|
||||
# mirror _resolve_vpi_annot_direct and consult the struct/enum registries, otherwise
|
||||
# `X*` -> rawptr[X] fails on the inner name and the whole return type is lost.
|
||||
bare_cls_ty: mlirlite.LLVMType* = _vpi_struct_or_enum(seg)
|
||||
bare_cls_ty: mlirlite.LLVMType* = _vpi_struct_or_enum_in_module(seg, src_sha1)
|
||||
if bare_cls_ty is not None: return bare_cls_ty
|
||||
return None
|
||||
# module-qualified class name "memhub.MemBuddy" -> use last segment to look up struct
|
||||
@@ -2071,7 +2091,7 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
|
||||
if mod_struct_ty is not None: return mod_struct_ty
|
||||
return None
|
||||
# bare class name -> global struct registry
|
||||
bare_struct_ty: mlirlite.LLVMType* = _vpi_struct_or_enum(seg)
|
||||
bare_struct_ty: mlirlite.LLVMType* = _vpi_struct_or_enum_in_module(seg, src_sha1)
|
||||
if bare_struct_ty is not None: return bare_struct_ty
|
||||
# bare typedef name (e.g. VOIDPTR): TypedefT dynamic table first, on miss fall back to map_builtin_type
|
||||
mapped_bare: mlirlite.LLVMType* = GenApi.resolve_typedef_or_map(pool, seg, None, None)
|
||||
@@ -2081,7 +2101,7 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
|
||||
|
||||
# _resolve_vpi_ret_annot_str - resolve a vpi return-annotation string to LLVMType.
|
||||
# Pure string parsing (no AST nodes) to avoid corrupted kind() from cross-module AST reuse.
|
||||
def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*:
|
||||
def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*, src_sha1: str = None) -> mlirlite.LLVMType*:
|
||||
"""Resolve a vpi return-annotation string directly to LLVMType without building AST nodes."""
|
||||
if ret_annot is None or pool is None: return None
|
||||
# `T1 | T2` unions were retired; a stub containing one must be regenerated.
|
||||
@@ -2112,7 +2132,7 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: m
|
||||
if seg_buf_t is None: return None
|
||||
string.strncpy(seg_buf_t, ret_annot + 6 + seg_s_t, seg_len_t)
|
||||
seg_buf_t[seg_len_t] = '\0'
|
||||
seg_ty_t: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg_buf_t)
|
||||
seg_ty_t: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg_buf_t, src_sha1)
|
||||
stdlib.free(seg_buf_t)
|
||||
if seg_ty_t is None: return None
|
||||
tup_node_t: mlirlite.ParamNode* = mlirlite.new_param_node(pool, seg_ty_t)
|
||||
@@ -2136,7 +2156,7 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: m
|
||||
|
||||
# A stub return annotation is a single type. `T*` / `T**` and `const` / `volatile`
|
||||
# prefixes are handled by the shared single-segment resolver.
|
||||
return _resolve_single_vpi_type_str(pool, ret_annot)
|
||||
return _resolve_single_vpi_type_str(pool, ret_annot, src_sha1)
|
||||
|
||||
|
||||
# VpiReader - .vpi AST parser. Reads function signatures and TypedefT definitions from
|
||||
@@ -2563,7 +2583,195 @@ def _VpiParseIntRange(vbuf: bytes, s: sizet, e: sizet, out_v: int64*) -> int:
|
||||
out_v[0] = v
|
||||
return 1
|
||||
|
||||
# _VpiIsFloatLit - whether vbuf[a:b] is a float literal (optional sign, digits, '.', exponent).
|
||||
def _VpiIsFloatLit(vbuf: bytes, a: sizet, b: sizet) -> int:
|
||||
"""1 when the span is a plain float literal (1.5 / 1e3 / -2.5e-4), 0 otherwise."""
|
||||
if vbuf is None or b <= a: return 0
|
||||
i: sizet = a
|
||||
if vbuf[i] == 0x2D or vbuf[i] == 0x2B: i += 1
|
||||
digits: int = 0
|
||||
dot: int = 0
|
||||
has_frac: int = 0
|
||||
while i < b:
|
||||
chf: uint8 = vbuf[i]
|
||||
if chf >= 0x30 and chf <= 0x39: digits += 1
|
||||
elif chf == 0x2E and dot == 0:
|
||||
dot = 1
|
||||
has_frac = 1
|
||||
else: break
|
||||
i += 1
|
||||
if i < b and (vbuf[i] == 0x65 or vbuf[i] == 0x45):
|
||||
has_frac = 1
|
||||
i += 1
|
||||
if i < b and (vbuf[i] == 0x2D or vbuf[i] == 0x2B): i += 1
|
||||
ed: int = 0
|
||||
while i < b:
|
||||
chfe: uint8 = vbuf[i]
|
||||
if chfe >= 0x30 and chfe <= 0x39: ed += 1
|
||||
else: return 0
|
||||
i += 1
|
||||
if ed == 0: return 0
|
||||
if i != b: return 0
|
||||
if digits == 0: return 0
|
||||
# A plain digit run is an INTEGER default (handled by _VpiParseIntRange); only a dot or
|
||||
# exponent makes it a float. Without this guard "0"/"12" would be stored as 0.0 f64 and an
|
||||
# int parameter would receive an f64 operand (llvm.call type mismatch).
|
||||
if has_frac == 0: return 0
|
||||
return 1
|
||||
|
||||
|
||||
# _VpiFillDefaultLit - 把第 di 个缺省值字面量写入快照数组;1=成功(可重建)
|
||||
# ---- aggregate default literals (struct constructor defaults) ----
|
||||
# Encoded token format (one token per struct field, '|' separated):
|
||||
# i:<decimal> d:<float text> s:<len>:<text> b:<0|1> n:0
|
||||
# The call site rebuilds the aggregate from the parameter's struct type, so the .vpi only
|
||||
# needs the literal values (the constructor name is not used).
|
||||
|
||||
|
||||
def _VpiEncPut(dst: bytes, cap: sizet, pos: sizet*, src: char*, n: sizet) -> int:
|
||||
"""Append n bytes of src at dst[pos]; keep dst NUL-terminated. 0 = overflow/argument error."""
|
||||
if dst is None or pos is None or src is None: return 0
|
||||
if n == 0: return 1
|
||||
if pos[0] + n + 1 > cap: return 0
|
||||
string.memcpy(dst + pos[0], src, n)
|
||||
pos[0] += n
|
||||
dst[pos[0]] = '\0'
|
||||
return 1
|
||||
|
||||
|
||||
def _VpiEncInt(dst: bytes, cap: sizet, pos: sizet*, v: int64) -> int:
|
||||
"""Append the decimal form of v."""
|
||||
tmp: str = stdlib.malloc(32)
|
||||
if tmp is None: return 0
|
||||
viperlib.snprintf(tmp, 32, "%lld", v)
|
||||
r: int = _VpiEncPut(dst, cap, pos, tmp, string.strlen(tmp))
|
||||
stdlib.free(tmp)
|
||||
return r
|
||||
|
||||
|
||||
def _VpiEncodeAggArg(pool: memhub.MemBuddy*, vbuf: bytes, s: sizet, e: sizet, dst: bytes, cap: sizet, pos: sizet*) -> int:
|
||||
"""Encode one scalar constructor argument (already trimmed) into one token."""
|
||||
a: sizet = s
|
||||
b: sizet = e
|
||||
while a < b and (vbuf[a] == 0x20 or vbuf[a] == 0x09): a += 1
|
||||
while b > a and (vbuf[b - 1] == 0x20 or vbuf[b - 1] == 0x09): b -= 1
|
||||
if b <= a: return 0
|
||||
ch0: uint8 = vbuf[a]
|
||||
if ch0 == 0x22 or ch0 == 0x27:
|
||||
if b - a < 2 or vbuf[b - 1] != ch0: return 0
|
||||
ci: sizet = a + 1
|
||||
while ci < b - 1:
|
||||
if vbuf[ci] == 0x5C: return 0 # escapes are not supported here
|
||||
ci += 1
|
||||
clen: sizet = b - a - 2
|
||||
if _VpiEncPut(dst, cap, pos, "s:", 2) == 0: return 0
|
||||
if _VpiEncInt(dst, cap, pos, int64(clen)) == 0: return 0
|
||||
if _VpiEncPut(dst, cap, pos, ":", 1) == 0: return 0
|
||||
return _VpiEncPut(dst, cap, pos, vbuf + a + 1, clen)
|
||||
if b - a == 4 and string.strncmp(vbuf + a, "True", 4) == 0: return _VpiEncPut(dst, cap, pos, "b:1", 3)
|
||||
if b - a == 5 and string.strncmp(vbuf + a, "False", 5) == 0: return _VpiEncPut(dst, cap, pos, "b:0", 3)
|
||||
if b - a == 4 and string.strncmp(vbuf + a, "None", 4) == 0: return _VpiEncPut(dst, cap, pos, "n:0", 3)
|
||||
if _VpiIsFloatLit(vbuf, a, b) != 0:
|
||||
if _VpiEncPut(dst, cap, pos, "d:", 2) == 0: return 0
|
||||
return _VpiEncPut(dst, cap, pos, vbuf + a, b - a)
|
||||
iv: int64* = pool.alloc(8)
|
||||
if iv is None: return 0
|
||||
if _VpiParseIntRange(vbuf, a, b, iv) == 0: return 0
|
||||
if _VpiEncPut(dst, cap, pos, "i:", 2) == 0: return 0
|
||||
return _VpiEncInt(dst, cap, pos, iv[0])
|
||||
|
||||
|
||||
def _VpiEncodeAggDefault(pool: memhub.MemBuddy*, vbuf: bytes, a0: sizet, b0: sizet, kinds: int*, ints: int64*, strs: sizet*, di: int) -> int:
|
||||
"""Encode `Name(arg, ...)` as an aggregate literal snapshot; 0 = not a flat constructor."""
|
||||
if pool is None or vbuf is None or kinds is None or ints is None or strs is None or di < 0: return 0
|
||||
a: sizet = a0
|
||||
b: sizet = b0
|
||||
while a < b and (vbuf[a] == 0x20 or vbuf[a] == 0x09): a += 1
|
||||
while b > a and (vbuf[b - 1] == 0x20 or vbuf[b - 1] == 0x09): b -= 1
|
||||
if b - a < 3 or vbuf[b - 1] != 0x29: return 0 # ')'
|
||||
# Constructor name: identifier characters plus '.' qualifier; then '('.
|
||||
lp: sizet = a
|
||||
has_name: int = 0
|
||||
while lp < b:
|
||||
ch: uint8 = vbuf[lp]
|
||||
if ch == 0x28: break
|
||||
ok_ch: int = 0
|
||||
if (ch >= 0x41 and ch <= 0x5A) or (ch >= 0x61 and ch <= 0x7A) or ch == 0x5F: ok_ch = 1
|
||||
elif ch >= 0x30 and ch <= 0x39: ok_ch = 1
|
||||
elif ch == 0x2E: ok_ch = 1
|
||||
elif ch == 0x20 or ch == 0x09: pass
|
||||
else: return 0
|
||||
if ok_ch != 0: has_name = 1
|
||||
lp += 1
|
||||
if lp >= b - 1 or has_name == 0: return 0
|
||||
cap: sizet = 4096
|
||||
dst: bytes = CompilerFactory.factory.PermPool.alloc(cap)
|
||||
if dst is None: return 0
|
||||
dst[0] = '\0'
|
||||
pos: sizet = 0
|
||||
ai: sizet = lp + 1
|
||||
first: int = 1
|
||||
while True:
|
||||
while ai < b - 1 and (vbuf[ai] == 0x20 or vbuf[ai] == 0x09): ai += 1
|
||||
if ai >= b - 1: break
|
||||
j: sizet = ai
|
||||
quote: uint8 = 0
|
||||
while j < b - 1:
|
||||
c: uint8 = vbuf[j]
|
||||
if quote != 0:
|
||||
if c == 0x5C: j += 1
|
||||
elif c == quote: quote = 0
|
||||
j += 1
|
||||
continue
|
||||
if c == 0x22 or c == 0x27: quote = c
|
||||
elif c == 0x28 or c == 0x29: return 0 # nested calls are not supported
|
||||
elif c == 0x2C: break
|
||||
j += 1
|
||||
if first == 0:
|
||||
if _VpiEncPut(dst, cap, &pos, "|", 1) == 0: return 0
|
||||
if _VpiEncodeAggArg(pool, vbuf, ai, j, dst, cap, &pos) == 0: return 0
|
||||
first = 0
|
||||
ai = j + 1
|
||||
kinds[di] = GenTypes.CONST_AGG_DEFAULT
|
||||
ints[di] = 0
|
||||
strs[di] = sizet(dst)
|
||||
return 1
|
||||
|
||||
|
||||
def _VpiEncodeNameDefault(pool: memhub.MemBuddy*, vbuf: bytes, a0: sizet, b0: sizet, kinds: int*, ints: int64*, strs: sizet*, di: int) -> int:
|
||||
"""Encode a bare/qualified identifier default (enum member or CDefine) as text.
|
||||
|
||||
The .vpi default pre-scan runs before enums are registered, so the value cannot be resolved
|
||||
here. Keep the text in the snapshot; the call site resolves it after enum registration.
|
||||
"""
|
||||
if pool is None or vbuf is None or kinds is None or ints is None or strs is None or di < 0: return 0
|
||||
a: sizet = a0
|
||||
b: sizet = b0
|
||||
while a < b and (vbuf[a] == 0x20 or vbuf[a] == 0x09): a += 1
|
||||
while b > a and (vbuf[b - 1] == 0x20 or vbuf[b - 1] == 0x09): b -= 1
|
||||
if b <= a or b - a > 200: return 0
|
||||
i: sizet = a
|
||||
has_id: int = 0
|
||||
while i < b:
|
||||
ch: uint8 = vbuf[i]
|
||||
ok: int = 0
|
||||
if (ch >= 0x41 and ch <= 0x5A) or (ch >= 0x61 and ch <= 0x7A) or ch == 0x5F: ok = 1
|
||||
elif ch >= 0x30 and ch <= 0x39: ok = 1
|
||||
elif ch == 0x2E: ok = 1
|
||||
else: return 0
|
||||
if ch != 0x2E: has_id = 1
|
||||
i += 1
|
||||
if has_id == 0: return 0
|
||||
copy: str = CompilerFactory.factory.PermPool.alloc(b - a + 1)
|
||||
if copy is None: return 0
|
||||
string.strncpy(copy, vbuf + a, b - a)
|
||||
copy[b - a] = '\0'
|
||||
kinds[di] = GenTypes.CONST_EXPR_TEXT
|
||||
ints[di] = 0
|
||||
strs[di] = sizet(copy)
|
||||
return 1
|
||||
|
||||
|
||||
def _VpiFillDefaultLit(pool: memhub.MemBuddy*, vbuf: bytes, s: sizet, e: sizet, kinds: int*, ints: int64*, strs: sizet*, di: int) -> int:
|
||||
"""把第 di 个缺省值字面量写入快照;0=不可重建(字符串转义/复杂表达式)"""
|
||||
if kinds is None or ints is None or strs is None or di < 0: return 0
|
||||
@@ -2605,13 +2813,36 @@ def _VpiFillDefaultLit(pool: memhub.MemBuddy*, vbuf: bytes, s: sizet, e: sizet,
|
||||
ints[di] = 0
|
||||
strs[di] = 0
|
||||
return 1
|
||||
# Float literal: store the IEEE-754 bit pattern (int64 slot); the snapshot rebuild
|
||||
# reinterprets it back to double, then the call-site coercion narrows to the param type.
|
||||
if _VpiIsFloatLit(vbuf, a, b) != 0:
|
||||
flen: sizet = b - a
|
||||
fbuf: bytes = stdlib.malloc(flen + 1)
|
||||
if fbuf is None: return 0
|
||||
string.strncpy(fbuf, vbuf + a, flen)
|
||||
fbuf[flen] = '\0'
|
||||
dv: double = string.atof(fbuf)
|
||||
stdlib.free(fbuf)
|
||||
uv: uint64 = 0
|
||||
*&uv = *&dv
|
||||
kinds[di] = ast.CONST_FLOAT
|
||||
ints[di] = int64(uv)
|
||||
strs[di] = 0
|
||||
return 1
|
||||
# Struct-constructor literal (e.g. Vec2(1.0, 2.0)): encode the scalar field values so a
|
||||
# cross-module call site can rebuild the aggregate from its parameter's struct type.
|
||||
if _VpiEncodeAggDefault(pool, vbuf, a, b, kinds, ints, strs, di) != 0:
|
||||
return 1
|
||||
iv: int64* = pool.alloc(8)
|
||||
if iv is None: return 0
|
||||
if _VpiParseIntRange(vbuf, a, b, iv) == 0: return 0
|
||||
kinds[di] = ast.CONST_INT
|
||||
ints[di] = iv[0]
|
||||
strs[di] = 0
|
||||
return 1
|
||||
if _VpiParseIntRange(vbuf, a, b, iv) != 0:
|
||||
kinds[di] = ast.CONST_INT
|
||||
ints[di] = iv[0]
|
||||
strs[di] = 0
|
||||
return 1
|
||||
# Identifier default (enum member `Flags.Name_` / CDefine): keep the text; the call site
|
||||
# resolves it after enum registration (the pre-scan runs before enums exist).
|
||||
return _VpiEncodeNameDefault(pool, vbuf, a, b, kinds, ints, strs, di)
|
||||
|
||||
# _VpiParseParamDefaults - 解析参数区间:返回 (参数个数 << 8) | 缺省值个数;-1=有不可重建缺省值
|
||||
def _VpiParseParamDefaults(pool: memhub.MemBuddy*, vbuf: bytes, ps: sizet, pe: sizet, kinds: int*, ints: int64*, strs: sizet*) -> int:
|
||||
@@ -2718,8 +2949,7 @@ def _ScanVpiDefaults(pool: memhub.MemBuddy*, vbuf: bytes, buf_len: sizet, sha1:
|
||||
stdlib.free(strs)
|
||||
return reg
|
||||
|
||||
# 单个 .vpi 的枚举成员上限(与 ENUM_MEMBER_MAX 同量级)
|
||||
define VPI_ENUM_MEMBER_MAX = 128
|
||||
# 单个 .vpi 的枚举成员上限:直接复用 GenTypes.ENUM_MEMBER_MAX(唯一定义点),不再维护本地副本。
|
||||
|
||||
|
||||
# _vpi_slice_copy - 把 buf[a:b] 拷成 NUL 结尾的字符串(分配自 pool)
|
||||
@@ -2746,9 +2976,9 @@ def _ScanVpiEnumsText(pool: memhub.MemBuddy*, vbuf: bytes, vlen: sizet, sha1: st
|
||||
# 类型对象与枚举表都要活到进程结束:统一挂在 PermPool 上(EnumTable 本身就在 PermPool)
|
||||
ty_pool: memhub.MemBuddy* = pool
|
||||
if CompilerFactory.factory.PermPool is not None: ty_pool = CompilerFactory.factory.PermPool
|
||||
nm_buf: sizet* = pool.alloc(8 * VPI_ENUM_MEMBER_MAX)
|
||||
ty_buf: sizet* = pool.alloc(8 * VPI_ENUM_MEMBER_MAX)
|
||||
val_buf: int64* = pool.alloc(8 * VPI_ENUM_MEMBER_MAX)
|
||||
nm_buf: sizet* = pool.alloc(8 * GenTypes.ENUM_MEMBER_MAX)
|
||||
ty_buf: sizet* = pool.alloc(8 * GenTypes.ENUM_MEMBER_MAX)
|
||||
val_buf: int64* = pool.alloc(8 * GenTypes.ENUM_MEMBER_MAX)
|
||||
if nm_buf is None or ty_buf is None or val_buf is None: return 0
|
||||
reg: int = 0
|
||||
i: sizet = 0
|
||||
@@ -2800,7 +3030,7 @@ def _ScanVpiEnumsText(pool: memhub.MemBuddy*, vbuf: bytes, vlen: sizet, sha1: st
|
||||
ty_txt: str = _vpi_slice_copy(pool, vbuf, ts, ty_end)
|
||||
# `State` 是自动编号成员的占位类型(源码端 t.State -> i32)
|
||||
if string.strcmp(ty_txt, "State") != 0:
|
||||
rty: mlirlite.LLVMType* = _resolve_single_vpi_type_str(ty_pool, ty_txt)
|
||||
rty: mlirlite.LLVMType* = _resolve_single_vpi_type_str(ty_pool, ty_txt, sha1)
|
||||
rb: int = _vpi_int_ty_bits(rty)
|
||||
# 枚举存储必须可按字节寻址:bool 在定义端按 8 位算
|
||||
if rb == 1: mty = mlirlite.Int8(ty_pool)
|
||||
@@ -2821,7 +3051,7 @@ def _ScanVpiEnumsText(pool: memhub.MemBuddy*, vbuf: bytes, vlen: sizet, sha1: st
|
||||
if neg != 0: pv = -pv
|
||||
cur = pv
|
||||
next_val = cur + 1
|
||||
if cnt < VPI_ENUM_MEMBER_MAX:
|
||||
if cnt < GenTypes.ENUM_MEMBER_MAX:
|
||||
nm_buf[cnt] = sizet(mname)
|
||||
ty_buf[cnt] = sizet(mty)
|
||||
val_buf[cnt] = cur
|
||||
@@ -3674,7 +3904,7 @@ def _inject_reexport_class_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMMod
|
||||
|
||||
|
||||
# _vpi_tattr_name / _vpi_class_has_packed - detect the vpi class packed decorator.
|
||||
# The stub keeps @c.Attribute(t.attr.packed); struct injection must mirror the packed flag (B5).
|
||||
# The stub keeps @Attribute(attr.packed); struct injection must mirror the packed flag (B5).
|
||||
def _vpi_tattr_name(node: ast.AST*, attr_name: str) -> int:
|
||||
"""Extract the outermost attribute name from t.attr.<name> / t.attr.<name>(args) and compare."""
|
||||
if node is None: return 0
|
||||
@@ -4029,7 +4259,7 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModul
|
||||
existing_c.FieldCount = 0
|
||||
# 6. register into the current module (emit the type def) + global StructTable + visibility
|
||||
mlirlite.module_add_named_type(mod, pool, struct_ty_c)
|
||||
# B5: vpi keeps @c.Attribute(t.attr.packed); cross-module injected structs must be packed too
|
||||
# B5: vpi keeps @Attribute(attr.packed); cross-module injected structs must be packed too
|
||||
# (<{...}>), else the referencing module computes GEPs with a non-packed layout (fields misread).
|
||||
if _vpi_class_has_packed(class_cd.decorator_list) != 0: mlirlite.module_set_named_type_packed(mod, pool, struct_ty_c, 1)
|
||||
# Register under the flat real_name: cross-module `Outer.Inner` lookups query the flat-name
|
||||
@@ -4111,7 +4341,9 @@ def _AggregateParamTy(mod: mlirlite.LLVMModule*, pool: memhub.MemBuddy*, ty: mli
|
||||
pass
|
||||
if is_opaque_ap == 0 and GenApi.find_struct_by_type(ty) is not None:
|
||||
_ensure_full_value_types(mod, pool, ty)
|
||||
return ty
|
||||
# Win64: iN for exactly 1/2/4/8 bytes, Ptr(struct) (byref copy) for every other size.
|
||||
# Non-Windows keeps the struct value and lets the backend apply the platform ABI.
|
||||
return GenApi.extern_c_param_agg_ty(pool, ty)
|
||||
return mlirlite.Int64(pool)
|
||||
ptr_ty_ap: mlirlite.LLVMType* = mlirlite.Ptr(pool, ty)
|
||||
mlirlite.module_ensure_opaque_for_type(mod, pool, ptr_ty_ap)
|
||||
@@ -4244,7 +4476,7 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||||
# parse) and resolve it directly, avoiding corrupted kind() from cross-module AST reuse.
|
||||
ret_ty: mlirlite.LLVMType* = None
|
||||
ret_annot_fd: str = _lookup_vpi_ret_annot(src_sha1, None, fd.name)
|
||||
if ret_annot_fd is not None: ret_ty = _resolve_vpi_ret_annot_str(pool, ret_annot_fd, i32_ty, i8_ptr_ty)
|
||||
if ret_annot_fd is not None: ret_ty = _resolve_vpi_ret_annot_str(pool, ret_annot_fd, i32_ty, i8_ptr_ty, src_sha1)
|
||||
if ret_ty is None and fd.returns is not None:
|
||||
ret_ty = GenApi.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans)
|
||||
# if the return type is a pure t.State (no real type), use Void
|
||||
@@ -4272,6 +4504,12 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||||
# missing; an i32 default here would split "declare i32" from "define void".
|
||||
if ret_ty is None: ret_ty = mlirlite.Void(pool)
|
||||
|
||||
# C extern returning an aggregate: remember the symbol so the call site emits the C ABI
|
||||
# (Win64 integer register <=8B / hidden sret pointer otherwise) instead of Viper's own
|
||||
# aggregate-return convention. ExportT alone stays on the Viper convention.
|
||||
if (has_extern != 0 or has_state != 0) and ret_ty.kind() == mlirlite.LLVMType.Struct:
|
||||
GenApi.register_extern_agg(mangled_name)
|
||||
|
||||
# A value struct return type (e.g. Mat4) needs a full definition: declaring %Mat4 opaque makes
|
||||
# llc report "use of undefined type". Register the full definition.
|
||||
_ensure_full_value_types(mod, pool, ret_ty)
|
||||
@@ -4297,6 +4535,7 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||||
# convergence point: longjmp back to the REPL loop in a REPL session (diagnostics printed)
|
||||
VLogger.repl_abort()
|
||||
pi: sizet
|
||||
decl_pi: int = 0
|
||||
for pi in range(int(param_count)):
|
||||
ag: ast.Arg* = ast.Arg*(args_list.get(pi))
|
||||
if ag is None: continue
|
||||
@@ -4313,6 +4552,9 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||||
# Bare struct param: C ABI by value / internal Ptr(struct)
|
||||
# (criterion and fallback in _AggregateParamTy; keep the three sites in sync)
|
||||
param_ty = _AggregateParamTy(mod, pool, pseg_ty, agg_is_c_abi)
|
||||
# Win64 byref: mark the call site so it passes a copy, not the variable address.
|
||||
if agg_is_c_abi != 0 and param_ty is not None and param_ty.kind() == mlirlite.LLVMType.Ptr:
|
||||
GenApi.register_extern_byref_param(mangled_name, decl_pi)
|
||||
case mlirlite.LLVMType.Array(aelem_ty, _):
|
||||
param_ty = mlirlite.Ptr(pool, aelem_ty)
|
||||
case _:
|
||||
@@ -4325,6 +4567,8 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||||
match resolved_ty:
|
||||
case mlirlite.LLVMType.Struct(_, _, _):
|
||||
param_ty = _AggregateParamTy(mod, pool, resolved_ty, agg_is_c_abi)
|
||||
if agg_is_c_abi != 0 and param_ty is not None and param_ty.kind() == mlirlite.LLVMType.Ptr:
|
||||
GenApi.register_extern_byref_param(mangled_name, decl_pi)
|
||||
case mlirlite.LLVMType.Array(aelem_ty, _):
|
||||
param_ty = mlirlite.Ptr(pool, aelem_ty)
|
||||
case _:
|
||||
@@ -4334,6 +4578,7 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||||
# to i8* (no annotation is a legitimate default and keeps i8*).
|
||||
GenApi.fatal_error(ag.annotation, f"extern declaration '{fd.name}' parameter '{pname}' type annotation failed to resolve", "NameError")
|
||||
mlirlite.add_param(pool, func, param_ty, pname)
|
||||
decl_pi += 1
|
||||
# Text-cache repair: a corrupted vpi tree truncates args, leaving too few declare params;
|
||||
# complete them from the vpi text param annotations so declare matches define.
|
||||
param_count = _repair_declare_params_from_text(pool, func, src_sha1, None, fd.name, param_count, i32_ty, i8_ptr_ty)
|
||||
@@ -4395,7 +4640,7 @@ def _inject_method_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, m_
|
||||
# parse); resolving m_fd.returns directly may yield garbage from a corrupted vpi tree node.
|
||||
ret_ty: mlirlite.LLVMType* = None
|
||||
ret_annot: str = _lookup_vpi_ret_annot(src_sha1, cls_name, m_fd.name)
|
||||
if ret_annot is not None: ret_ty = _resolve_vpi_ret_annot_str(pool, ret_annot, i32_ty, i8_ptr_ty)
|
||||
if ret_annot is not None: ret_ty = _resolve_vpi_ret_annot_str(pool, ret_annot, i32_ty, i8_ptr_ty, src_sha1)
|
||||
if ret_ty is None and m_fd.returns is not None:
|
||||
ret_ty = GenApi.resolve_annotation_type(pool, m_fd.returns, imported_modules, from_imports, trans)
|
||||
# infer __new__ without annotation as a class pointer (it returns a self pointer; i32 would truncate)
|
||||
|
||||
@@ -5,8 +5,7 @@ import memhub
|
||||
import string
|
||||
import stdlib
|
||||
import mlirlite
|
||||
import win32.win32file as win32file
|
||||
import win32.win32base as win32base
|
||||
import os
|
||||
import io
|
||||
import viperlib
|
||||
import sys
|
||||
@@ -629,29 +628,15 @@ define OBJ_CACHE_STAMP_TAG_CAP = 128
|
||||
|
||||
|
||||
def _CompilerBuildTag(out: bytes, cap: sizet) -> int:
|
||||
"""Serialize the running compiler's build id (exe size + last write time) into out."""
|
||||
"""Serialize the running compiler's build id (exe size + mtime) into out."""
|
||||
if out is None or cap < 32: return 0
|
||||
out[0] = '\0'
|
||||
EXE_CAP: sizet = sizet(1024)
|
||||
exe_path: bytes = stdlib.malloc(EXE_CAP)
|
||||
exe_path: str = sys.executable()
|
||||
if exe_path is None: return 0
|
||||
string.memset(exe_path, 0, EXE_CAP)
|
||||
n: uint32 = win32file.GetModuleFileNameA(None, exe_path, uint32(1024))
|
||||
ok: int = 0
|
||||
if int(n) > 0 and int(n) < 1024:
|
||||
fd_size: sizet = sizet(win32file.WIN32_FIND_DATAA.__sizeof__())
|
||||
fd: win32file.WIN32_FIND_DATAA* = (win32file.WIN32_FIND_DATAA*)(stdlib.malloc(fd_size))
|
||||
if fd is not None:
|
||||
string.memset(fd, 0, fd_size)
|
||||
fh: win32base.HANDLE = win32file.FindFirstFileA(exe_path, fd)
|
||||
if fh != win32base.INVALID_HANDLE_VALUE:
|
||||
win32file.FindClose(fh)
|
||||
viperlib.snprintf(out, cap, "%u.%u-%u.%u", fd.nFileSizeLow, fd.nFileSizeHigh,
|
||||
fd.ftLastWriteTime.dwLowDateTime, fd.ftLastWriteTime.dwHighDateTime)
|
||||
ok = 1
|
||||
stdlib.free(fd)
|
||||
stdlib.free(exe_path)
|
||||
return ok
|
||||
st: os.stat_result* = os.stat(exe_path)
|
||||
if st is None: return 0
|
||||
viperlib.snprintf(out, cap, "%lld.%lld", st.st_size, st.st_mtime)
|
||||
return 1
|
||||
|
||||
|
||||
def _BuildObjCacheStamp(out: bytes, cap: sizet, shas: bytes, incdirs: bytes, count: int) -> sizet:
|
||||
|
||||
@@ -1,130 +1,128 @@
|
||||
from stdint import *
|
||||
import memhub
|
||||
import string
|
||||
import stdlib
|
||||
import mlirlite
|
||||
import win32.win32file as win32file
|
||||
import win32.win32base as win32base
|
||||
import io
|
||||
import viperlib
|
||||
import sys
|
||||
import ast
|
||||
import lib.core.VLogger as VLogger
|
||||
import lib.core.IncludesScanner as IncludesScanner
|
||||
import lib.core.Gen.Translator as HandlesTranslator
|
||||
import lib.Phase.BuildPipeline as BuildPipeline
|
||||
import lib.Stub.StubGen.VpiReader as VpiReader
|
||||
import lib.Projectrans.Utils as Utils
|
||||
import lib.Projectrans.Config as Config
|
||||
|
||||
|
||||
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||||
import lib.Stub.StubMerger.StubMergerTopo as StubMergerTopo
|
||||
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
|
||||
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
|
||||
import lib.Stub.StubMerger as StubMerger
|
||||
|
||||
# StubMergerCollect - slice-path construction and stub-file SHA1 collection.
|
||||
# Functions live in this module; cross-module globals are accessed via StubMerger._xxx.
|
||||
|
||||
# _sliced_path - build a slice path (stdlib.malloc; caller frees; creates subdir when level>0).
|
||||
# Shards by Config.Sha1SliceLevel into SHA1-prefix subdirs; returns the full path or None on failure.
|
||||
def _sliced_path(temp_dir: str, td_len: sizet, sha1: str, ext: str) -> str:
|
||||
"""Build a SHA1-sliced path (stdlib.malloc; caller frees; creates the subdir)."""
|
||||
if temp_dir is None or sha1 is None or ext is None: return None
|
||||
sha1_len: sizet = string.strlen(sha1)
|
||||
ext_len: sizet = string.strlen(ext)
|
||||
|
||||
# Subdir prefix: reuse Config.slice_subdir (the authoritative SHA1 slice impl; mbuddy-allocated, no free)
|
||||
subdir_buf: str = Config.slice_subdir(sha1, Config.Sha1SliceLevel)
|
||||
sub_len: sizet = 0
|
||||
if subdir_buf is not None:
|
||||
sub_len = string.strlen(subdir_buf)
|
||||
|
||||
# Ensure subdir {temp_dir}/{subdir} exists.
|
||||
# Otherwise io.File(MODE.W) fails silently (file.closed==True) and the reader returns 0.
|
||||
dir_path_len: sizet = td_len + sub_len + 2
|
||||
dir_path: bytes = stdlib.malloc(dir_path_len)
|
||||
if dir_path is not None:
|
||||
viperlib.snprintf(dir_path, dir_path_len, "%s/%s", temp_dir, subdir_buf)
|
||||
BuildPipeline.ensure_dir(dir_path)
|
||||
stdlib.free(dir_path)
|
||||
|
||||
# Build the full path
|
||||
path: str = None
|
||||
path_len: sizet = 0
|
||||
if sub_len > 0:
|
||||
path_len = td_len + sub_len + sha1_len + ext_len + 4
|
||||
path = stdlib.malloc(path_len)
|
||||
if path is not None: viperlib.snprintf(path, path_len, "%s/%s/%s.%s", temp_dir, subdir_buf, sha1, ext)
|
||||
else:
|
||||
path_len = td_len + sha1_len + ext_len + 3
|
||||
path = stdlib.malloc(path_len)
|
||||
if path is not None: viperlib.snprintf(path, path_len, "%s/%s.%s", temp_dir, sha1, ext)
|
||||
|
||||
return path
|
||||
|
||||
# _is_in_sha1_set - check whether a SHA1 is in the set.
|
||||
def _is_in_sha1_set(sha1: str, sha1_set: str, set_count: int) -> int:
|
||||
"""Whether sha1 is present in the set."""
|
||||
if sha1 is None or sha1_set is None: return 0
|
||||
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
|
||||
map_path: bytes = Sha1MapPath(mb, temp_dir)
|
||||
if map_path is None: return 1
|
||||
|
||||
# Open the file for writing (CREATE_ALWAYS)
|
||||
f: io.File* = io.File(map_path, io.MODE.W)
|
||||
stdlib.free(map_path)
|
||||
if f.closed: return 1
|
||||
|
||||
# Write each include entry: {sha1}:includes/{rel_path}\n
|
||||
entry_size_w: sizet = IncludesScanner.FileEntry.__sizeof__()
|
||||
line_buf: bytes = stdlib.malloc(512)
|
||||
if line_buf is None:
|
||||
f.close()
|
||||
return 1
|
||||
|
||||
written_count: int = 0
|
||||
for i in range(int(scan_result.Count)):
|
||||
ea_w: uint64 = uint64(scan_result.Entries) + i * entry_size_w
|
||||
ent_w: IncludesScanner.FileEntry* = (IncludesScanner.FileEntry*)(ea_w)
|
||||
if ent_w is None or ent_w.Sha1 is None or ent_w.RelPath is None: continue
|
||||
# When a filter set is provided, write only its entries
|
||||
if filter_set is not None and filter_count > 0:
|
||||
if _is_in_sha1_set(ent_w.Sha1, filter_set, filter_count) == 0: continue
|
||||
viperlib.snprintf(line_buf, 512, "%s:includes/%s\n", ent_w.Sha1, ent_w.RelPath)
|
||||
line_len_w: sizet = string.strlen(line_buf)
|
||||
f.write(line_buf, line_len_w)
|
||||
written_count += 1
|
||||
|
||||
f.close()
|
||||
return 0
|
||||
from stdint import *
|
||||
import memhub
|
||||
import string
|
||||
import stdlib
|
||||
import mlirlite
|
||||
import io
|
||||
import viperlib
|
||||
import sys
|
||||
import ast
|
||||
import lib.core.VLogger as VLogger
|
||||
import lib.core.IncludesScanner as IncludesScanner
|
||||
import lib.core.Gen.Translator as HandlesTranslator
|
||||
import lib.Phase.BuildPipeline as BuildPipeline
|
||||
import lib.Stub.StubGen.VpiReader as VpiReader
|
||||
import lib.Projectrans.Utils as Utils
|
||||
import lib.Projectrans.Config as Config
|
||||
|
||||
|
||||
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||||
import lib.Stub.StubMerger.StubMergerTopo as StubMergerTopo
|
||||
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
|
||||
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
|
||||
import lib.Stub.StubMerger as StubMerger
|
||||
|
||||
# StubMergerCollect - slice-path construction and stub-file SHA1 collection.
|
||||
# Functions live in this module; cross-module globals are accessed via StubMerger._xxx.
|
||||
|
||||
# _sliced_path - build a slice path (stdlib.malloc; caller frees; creates subdir when level>0).
|
||||
# Shards by Config.Sha1SliceLevel into SHA1-prefix subdirs; returns the full path or None on failure.
|
||||
def _sliced_path(temp_dir: str, td_len: sizet, sha1: str, ext: str) -> str:
|
||||
"""Build a SHA1-sliced path (stdlib.malloc; caller frees; creates the subdir)."""
|
||||
if temp_dir is None or sha1 is None or ext is None: return None
|
||||
sha1_len: sizet = string.strlen(sha1)
|
||||
ext_len: sizet = string.strlen(ext)
|
||||
|
||||
# Subdir prefix: reuse Config.slice_subdir (the authoritative SHA1 slice impl; mbuddy-allocated, no free)
|
||||
subdir_buf: str = Config.slice_subdir(sha1, Config.Sha1SliceLevel)
|
||||
sub_len: sizet = 0
|
||||
if subdir_buf is not None:
|
||||
sub_len = string.strlen(subdir_buf)
|
||||
|
||||
# Ensure subdir {temp_dir}/{subdir} exists.
|
||||
# Otherwise io.File(MODE.W) fails silently (file.closed==True) and the reader returns 0.
|
||||
dir_path_len: sizet = td_len + sub_len + 2
|
||||
dir_path: bytes = stdlib.malloc(dir_path_len)
|
||||
if dir_path is not None:
|
||||
viperlib.snprintf(dir_path, dir_path_len, "%s/%s", temp_dir, subdir_buf)
|
||||
BuildPipeline.ensure_dir(dir_path)
|
||||
stdlib.free(dir_path)
|
||||
|
||||
# Build the full path
|
||||
path: str = None
|
||||
path_len: sizet = 0
|
||||
if sub_len > 0:
|
||||
path_len = td_len + sub_len + sha1_len + ext_len + 4
|
||||
path = stdlib.malloc(path_len)
|
||||
if path is not None: viperlib.snprintf(path, path_len, "%s/%s/%s.%s", temp_dir, subdir_buf, sha1, ext)
|
||||
else:
|
||||
path_len = td_len + sha1_len + ext_len + 3
|
||||
path = stdlib.malloc(path_len)
|
||||
if path is not None: viperlib.snprintf(path, path_len, "%s/%s.%s", temp_dir, sha1, ext)
|
||||
|
||||
return path
|
||||
|
||||
# _is_in_sha1_set - check whether a SHA1 is in the set.
|
||||
def _is_in_sha1_set(sha1: str, sha1_set: str, set_count: int) -> int:
|
||||
"""Whether sha1 is present in the set."""
|
||||
if sha1 is None or sha1_set is None: return 0
|
||||
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
|
||||
map_path: bytes = Sha1MapPath(mb, temp_dir)
|
||||
if map_path is None: return 1
|
||||
|
||||
# Open the file for writing (CREATE_ALWAYS)
|
||||
f: io.File* = io.File(map_path, io.MODE.W)
|
||||
stdlib.free(map_path)
|
||||
if f.closed: return 1
|
||||
|
||||
# Write each include entry: {sha1}:includes/{rel_path}\n
|
||||
entry_size_w: sizet = IncludesScanner.FileEntry.__sizeof__()
|
||||
line_buf: bytes = stdlib.malloc(512)
|
||||
if line_buf is None:
|
||||
f.close()
|
||||
return 1
|
||||
|
||||
written_count: int = 0
|
||||
for i in range(int(scan_result.Count)):
|
||||
ea_w: uint64 = uint64(scan_result.Entries) + i * entry_size_w
|
||||
ent_w: IncludesScanner.FileEntry* = (IncludesScanner.FileEntry*)(ea_w)
|
||||
if ent_w is None or ent_w.Sha1 is None or ent_w.RelPath is None: continue
|
||||
# When a filter set is provided, write only its entries
|
||||
if filter_set is not None and filter_count > 0:
|
||||
if _is_in_sha1_set(ent_w.Sha1, filter_set, filter_count) == 0: continue
|
||||
viperlib.snprintf(line_buf, 512, "%s:includes/%s\n", ent_w.Sha1, ent_w.RelPath)
|
||||
line_len_w: sizet = string.strlen(line_buf)
|
||||
f.write(line_buf, line_len_w)
|
||||
written_count += 1
|
||||
|
||||
f.close()
|
||||
return 0
|
||||
@@ -4,8 +4,6 @@ import memhub
|
||||
import string
|
||||
import stdlib
|
||||
import mlirlite
|
||||
import win32.win32file as win32file
|
||||
import win32.win32base as win32base
|
||||
import io
|
||||
import viperlib
|
||||
import sys
|
||||
|
||||
@@ -3,8 +3,6 @@ import memhub
|
||||
import string
|
||||
import stdlib
|
||||
import mlirlite
|
||||
import win32.win32file as win32file
|
||||
import win32.win32base as win32base
|
||||
import io
|
||||
import viperlib
|
||||
import sys
|
||||
|
||||
@@ -1356,7 +1356,7 @@ novirtual class AssignGenMixin:
|
||||
cl: ast.Call* = ast.Call*(rhs)
|
||||
return self._pure_str_call_owned(trans, cl)
|
||||
if k == ast.ASTKind.BinOp:
|
||||
bo: ast.BinOp* = BinOp*(rhs)
|
||||
bo: ast.BinOp* = ast.BinOp*(rhs)
|
||||
if bo is not None and bo.op == ast.OpKind.Add and trans._in_pure != 0:
|
||||
return self._pure_str_expr(trans, bo.left) | self._pure_str_expr(trans, bo.right)
|
||||
return 0
|
||||
|
||||
@@ -12,13 +12,14 @@ import stdio
|
||||
import stdlib
|
||||
import viperlib
|
||||
import io
|
||||
import win32.win32file as win32file
|
||||
import os
|
||||
import lib.core.VLogger as VLogger
|
||||
import lib.Stub.StubMerger as StubMerger
|
||||
import lib.core.CompilerFactory as CompilerFactory
|
||||
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
|
||||
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||||
import lib.Stub.StubGen.VpiReader as VpiReader
|
||||
import lib.Projectrans.Config as Config
|
||||
|
||||
|
||||
# CallGenBase - split 1/9 of CallGenMixin (bottom parent): module symbol
|
||||
@@ -30,6 +31,9 @@ import lib.Stub.StubGen.VpiReader as VpiReader
|
||||
define GLOBAL_FUNC_MAX = 8192
|
||||
define GLOBAL_DEFAULTS_MAX = 4096
|
||||
define _CEXPORT_MAX = 256
|
||||
# C-extern by-value aggregate parameters passed by reference (Win64: sizes other than
|
||||
# 1/2/4/8). One symbol slot carries a 64-bit mask of the parameter indices needing a copy.
|
||||
define _EXTERN_BYREF_MAX = 1024
|
||||
define ASM_MAX_OPERANDS = 16
|
||||
define ASM_TEXT_BUF_SIZE = 2048
|
||||
define ASM_CONST_BUF_SIZE = 512
|
||||
@@ -56,6 +60,20 @@ novirtual class CallGenBase:
|
||||
CexportFuncArr: bytes = None
|
||||
CexportCount: int = 0
|
||||
|
||||
# --- C-extern aggregate-return registry (symbol names). A C function returning a struct
|
||||
# follows the target C ABI (Win64: integer register <=8B, hidden sret pointer otherwise),
|
||||
# which differs from Viper's own aggregate convention. Call emission consults this table.
|
||||
ExternAggArr: bytes = None
|
||||
ExternAggCount: int = 0
|
||||
|
||||
# --- C-extern by-value aggregate parameter registry (symbol -> 64-bit param mask).
|
||||
# Win64 passes aggregates whose size is not 1/2/4/8 by reference to a caller-owned copy;
|
||||
# Viper's default argument lowering passes the address of the variable, so the call site
|
||||
# must know which parameters need that copy.
|
||||
ExternByrefArr: bytes = None
|
||||
ExternByrefMasks: uint64* = None
|
||||
ExternByrefCount: int = 0
|
||||
|
||||
# --- static symbol registry (static / @t.StaticT -> defining module SHA1),
|
||||
# meaning no cross-module use (C internal linkage); PermPool fixed slots.
|
||||
StaticSha1Arr: bytes = None
|
||||
@@ -855,6 +873,224 @@ novirtual class CallGenBase:
|
||||
return self._is_cexport_func_from_vpi(sha1, func_name)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# C-extern aggregate return ABI (Win64)
|
||||
# ============================================================
|
||||
def register_extern_agg(self, sym: char*) -> int:
|
||||
"""Record that `sym` is a C extern returning an aggregate in the C ABI (idempotent)."""
|
||||
if self.Gen is None or sym is None: return 0
|
||||
if self.Gen.CallGenBase.ExternAggArr is None:
|
||||
self.Gen.CallGenBase.ExternAggArr = CompilerFactory.factory.PermPool.alloc(_CEXPORT_MAX * 64)
|
||||
if self.Gen.CallGenBase.ExternAggArr is None: return 0
|
||||
string.memset(self.Gen.CallGenBase.ExternAggArr, 0, _CEXPORT_MAX * 64)
|
||||
for i in range(int(self.Gen.CallGenBase.ExternAggCount)):
|
||||
idx: sizet = sizet(i) * 64
|
||||
if string.strcmp(self.Gen.CallGenBase.ExternAggArr + idx, sym) == 0: return 1
|
||||
if self.Gen.CallGenBase.ExternAggCount < _CEXPORT_MAX:
|
||||
idx2: sizet = sizet(self.Gen.CallGenBase.ExternAggCount) * 64
|
||||
string.strncpy(self.Gen.CallGenBase.ExternAggArr + idx2, sym, 63)
|
||||
self.Gen.CallGenBase.ExternAggArr[idx2 + 63] = '\0'
|
||||
self.Gen.CallGenBase.ExternAggCount += 1
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def is_extern_agg(self, sym: char*) -> int:
|
||||
"""1 when `sym` is a C extern returning an aggregate (C ABI at the call boundary)."""
|
||||
if self.Gen is None or sym is None: return 0
|
||||
if self.Gen.CallGenBase.ExternAggArr is None: return 0
|
||||
for i in range(int(self.Gen.CallGenBase.ExternAggCount)):
|
||||
idx: sizet = sizet(i) * 64
|
||||
if string.strcmp(self.Gen.CallGenBase.ExternAggArr + idx, sym) == 0: return 1
|
||||
return 0
|
||||
|
||||
def register_extern_byref_param(self, sym: char*, index: int) -> int:
|
||||
"""Mark param `index` of C extern `sym` as a by-value aggregate passed by reference."""
|
||||
if self.Gen is None or sym is None or index < 0 or index >= 64: return 0
|
||||
if self.Gen.CallGenBase.ExternByrefArr is None:
|
||||
self.Gen.CallGenBase.ExternByrefArr = CompilerFactory.factory.PermPool.alloc(_EXTERN_BYREF_MAX * 64)
|
||||
self.Gen.CallGenBase.ExternByrefMasks = CompilerFactory.factory.PermPool.alloc(_EXTERN_BYREF_MAX * 8)
|
||||
if self.Gen.CallGenBase.ExternByrefArr is None or self.Gen.CallGenBase.ExternByrefMasks is None: return 0
|
||||
string.memset(self.Gen.CallGenBase.ExternByrefArr, 0, _EXTERN_BYREF_MAX * 64)
|
||||
string.memset(self.Gen.CallGenBase.ExternByrefMasks, 0, _EXTERN_BYREF_MAX * 8)
|
||||
for i in range(int(self.Gen.CallGenBase.ExternByrefCount)):
|
||||
idx: sizet = sizet(i) * 64
|
||||
if string.strcmp(self.Gen.CallGenBase.ExternByrefArr + idx, sym) == 0:
|
||||
self.Gen.CallGenBase.ExternByrefMasks[i] = self.Gen.CallGenBase.ExternByrefMasks[i] | (uint64(1) << uint64(index))
|
||||
return 1
|
||||
if self.Gen.CallGenBase.ExternByrefCount < _EXTERN_BYREF_MAX:
|
||||
idx2: sizet = sizet(self.Gen.CallGenBase.ExternByrefCount) * 64
|
||||
string.strncpy(self.Gen.CallGenBase.ExternByrefArr + idx2, sym, 63)
|
||||
self.Gen.CallGenBase.ExternByrefArr[idx2 + 63] = '\0'
|
||||
self.Gen.CallGenBase.ExternByrefMasks[self.Gen.CallGenBase.ExternByrefCount] = uint64(1) << uint64(index)
|
||||
self.Gen.CallGenBase.ExternByrefCount += 1
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def is_extern_byref_param(self, sym: char*, index: int) -> int:
|
||||
"""1 when param `index` of C extern `sym` is a by-value aggregate passed by reference."""
|
||||
if self.Gen is None or sym is None or index < 0 or index >= 64: return 0
|
||||
if self.Gen.CallGenBase.ExternByrefArr is None: return 0
|
||||
for i in range(int(self.Gen.CallGenBase.ExternByrefCount)):
|
||||
idx: sizet = sizet(i) * 64
|
||||
if string.strcmp(self.Gen.CallGenBase.ExternByrefArr + idx, sym) == 0:
|
||||
if (self.Gen.CallGenBase.ExternByrefMasks[i] & (uint64(1) << uint64(index))) != 0: return 1
|
||||
return 0
|
||||
return 0
|
||||
|
||||
def _copy_byref_agg_arg(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, val: mlirlite.Value*, pty: mlirlite.LLVMType*) -> mlirlite.Value*:
|
||||
"""Pass a copy of a by-value aggregate argument that the C ABI hands over by reference."""
|
||||
if pool is None or builder is None or val is None or pty is None or val.Ty is None: return val
|
||||
if pty.kind() != mlirlite.LLVMType.Ptr: return val
|
||||
match val.Ty:
|
||||
case mlirlite.LLVMType.Ptr(elem):
|
||||
if elem is None or elem.kind() != mlirlite.LLVMType.Struct: return val
|
||||
loaded: mlirlite.Value* = mlirlite.build_load(builder, elem, val)
|
||||
if loaded is None: return val
|
||||
tmp: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, elem)
|
||||
if tmp is None: return val
|
||||
mlirlite.build_store(builder, loaded, tmp)
|
||||
if self._call_ty_equal(pool, pty, tmp.Ty) == 0:
|
||||
bc: mlirlite.Value* = mlirlite.build_bitcast(builder, tmp, pty)
|
||||
if bc is not None: return bc
|
||||
return tmp
|
||||
case _:
|
||||
return val
|
||||
|
||||
def _c_agg_is_packed(self, ty: mlirlite.LLVMType*) -> int:
|
||||
"""1 when the registered struct type is packed (no inter-field padding)."""
|
||||
if ty is None or self.Gen is None: return 0
|
||||
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(ty)
|
||||
if se is not None and se.IsPacked != 0: return 1
|
||||
return 0
|
||||
|
||||
def _c_agg_size(self, ty: mlirlite.LLVMType*) -> int:
|
||||
"""Byte size of a C aggregate (natural alignment, or exact layout when packed)."""
|
||||
if ty is None: return 0
|
||||
match ty:
|
||||
case mlirlite.LLVMType.Int(bits):
|
||||
return (bits + 7) // 8
|
||||
case mlirlite.LLVMType.Float(bits):
|
||||
return bits // 8
|
||||
case mlirlite.LLVMType.Ptr(_):
|
||||
return 8
|
||||
case mlirlite.LLVMType.Array(elem, count):
|
||||
return self._c_agg_size(elem) * count
|
||||
case mlirlite.LLVMType.Struct(fields, fcount, _):
|
||||
if fcount < 0: return 0
|
||||
packed: int = self._c_agg_is_packed(ty)
|
||||
total: int = 0
|
||||
max_align: int = 1
|
||||
cur: mlirlite.ParamNode* = fields
|
||||
i: int = 0
|
||||
while cur is not None and i < fcount:
|
||||
fty: mlirlite.LLVMType* = cur.Ty
|
||||
fsz: int = self._c_agg_size(fty)
|
||||
fal: int = 1 if packed != 0 else self._c_agg_align(fty)
|
||||
if fal < 1: fal = 1
|
||||
if fal > max_align: max_align = fal
|
||||
total = (total + fal - 1) // fal * fal
|
||||
total += fsz
|
||||
cur = cur.Next
|
||||
i += 1
|
||||
if packed == 0 and max_align > 1: total = (total + max_align - 1) // max_align * max_align
|
||||
return total
|
||||
case _:
|
||||
return 0
|
||||
|
||||
def _c_agg_align(self, ty: mlirlite.LLVMType*) -> int:
|
||||
"""Natural alignment of a C scalar/aggregate type."""
|
||||
if ty is None: return 1
|
||||
match ty:
|
||||
case mlirlite.LLVMType.Int(bits):
|
||||
sz_a: int = (bits + 7) // 8
|
||||
if sz_a < 1: return 1
|
||||
if sz_a > 8: return 8
|
||||
return sz_a
|
||||
case mlirlite.LLVMType.Float(bits):
|
||||
if bits <= 0: return 1
|
||||
return bits // 8
|
||||
case mlirlite.LLVMType.Ptr(_):
|
||||
return 8
|
||||
case mlirlite.LLVMType.Array(elem_a, _):
|
||||
return self._c_agg_align(elem_a)
|
||||
case mlirlite.LLVMType.Struct(fields_a, fcount_a, _):
|
||||
if self._c_agg_is_packed(ty) != 0: return 1
|
||||
max_a: int = 1
|
||||
cur_a: mlirlite.ParamNode* = fields_a
|
||||
i_a: int = 0
|
||||
while cur_a is not None and i_a < fcount_a:
|
||||
fa: int = self._c_agg_align(cur_a.Ty)
|
||||
if fa > max_a: max_a = fa
|
||||
cur_a = cur_a.Next
|
||||
i_a += 1
|
||||
return max_a
|
||||
case _:
|
||||
return 1
|
||||
|
||||
def is_win64_c_abi(self) -> int:
|
||||
"""1 when C ABI lowering follows Win64 (i.e. the target is not Linux/SysV)."""
|
||||
if Config.TargetTriple is not None:
|
||||
if string.strstr(Config.TargetTriple, "linux") is not None: return 0
|
||||
return 1
|
||||
|
||||
def c_agg_int_ty(self, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*:
|
||||
"""Win64: a 1/2/4/8-byte aggregate rides in one integer register; None = by reference."""
|
||||
if ty is None: return None
|
||||
sz: int = self._c_agg_size(ty)
|
||||
if sz == 1: return mlirlite.Int8(pool)
|
||||
if sz == 2: return mlirlite.Int16(pool)
|
||||
if sz == 4: return mlirlite.Int32(pool)
|
||||
if sz == 8: return mlirlite.Int64(pool)
|
||||
return None
|
||||
|
||||
def extern_c_param_agg_ty(self, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*:
|
||||
"""LLVM type of a C extern's by-value aggregate parameter.
|
||||
|
||||
Win64 passes exactly 1/2/4/8 bytes as an integer of that width (a struct's field types
|
||||
are irrelevant) and every other size by reference, with the caller owning the temporary
|
||||
copy. Emitting the struct value instead makes LLVM split the fields (floats to XMM,
|
||||
larger aggregates to stack slots), which no C callee expects. Non-Windows keeps the
|
||||
struct value and lets the backend apply the platform ABI.
|
||||
"""
|
||||
if ty is None: return ty
|
||||
if self.is_win64_c_abi() == 0: return ty
|
||||
it: mlirlite.LLVMType* = self.c_agg_int_ty(pool, ty)
|
||||
if it is not None: return it
|
||||
return mlirlite.Ptr(pool, ty)
|
||||
|
||||
def emit_extern_agg_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, call_name: char*, head: mlirlite.Value*, arg_count: int, ret_ty: mlirlite.LLVMType*) -> mlirlite.Value*:
|
||||
"""Call a C extern whose aggregate return uses the Win64 C ABI; return the Viper struct value."""
|
||||
if pool is None or builder is None or call_name is None or ret_ty is None: return None
|
||||
# Only the Windows x64 ABI is adapted here. On Linux/SysV keep the existing direct-call
|
||||
# path (its eightbyte classification is a separate implementation).
|
||||
if Config.TargetTriple is not None:
|
||||
if string.strstr(Config.TargetTriple, "linux") is not None: return None
|
||||
size_b: int = self._c_agg_size(ret_ty)
|
||||
if size_b <= 0: return None
|
||||
fp: mlirlite.Value* = mlirlite.build_addressof(builder, call_name)
|
||||
if fp is None: return None
|
||||
abi_int: mlirlite.LLVMType* = None
|
||||
if size_b == 1: abi_int = mlirlite.Int8(pool)
|
||||
elif size_b == 2: abi_int = mlirlite.Int16(pool)
|
||||
elif size_b == 4: abi_int = mlirlite.Int32(pool)
|
||||
elif size_b == 8: abi_int = mlirlite.Int64(pool)
|
||||
if abi_int is not None:
|
||||
# Exact 1/2/4/8 bytes: C returns the aggregate in RAX as an integer of that width.
|
||||
raw: mlirlite.Value* = mlirlite.build_call_indirect(builder, fp, head, arg_count, abi_int)
|
||||
if raw is None: return None
|
||||
slot: mlirlite.Value* = mlirlite.build_alloca_at_entry(builder, abi_int)
|
||||
if slot is None: return None
|
||||
mlirlite.build_store(builder, raw, slot)
|
||||
sp: mlirlite.Value* = mlirlite.build_bitcast(builder, slot, mlirlite.Ptr(pool, ret_ty))
|
||||
if sp is None: return None
|
||||
return mlirlite.build_load(builder, ret_ty, sp)
|
||||
# Any other size: C returns through a hidden sret pointer passed as the first argument.
|
||||
tmp: mlirlite.Value* = mlirlite.build_alloca_at_entry(builder, ret_ty)
|
||||
if tmp is None: return None
|
||||
tmp.Next = head
|
||||
mlirlite.build_call_indirect(builder, fp, tmp, arg_count + 1, mlirlite.Void(pool))
|
||||
return mlirlite.build_load(builder, ret_ty, tmp)
|
||||
|
||||
# RegisterStaticSymbol / IsStaticSymbol - static symbol registry: the four
|
||||
# syntaxes all normalize to t.StaticT; meaning = no cross-module use.
|
||||
def RegisterStaticSymbol(self, sha1: char*, name: char*) -> int:
|
||||
@@ -1223,6 +1459,7 @@ novirtual class CallGenBase:
|
||||
new_tail: mlirlite.Value* = None
|
||||
cur: mlirlite.Value* = head
|
||||
prm: mlirlite.Param* = mlirlite.function_get_param_head(func)
|
||||
pi_c: int = 0
|
||||
while cur is not None and prm is not None:
|
||||
nxt: mlirlite.Value* = cur.Next
|
||||
node_v: mlirlite.Value* = cur
|
||||
@@ -1230,12 +1467,18 @@ novirtual class CallGenBase:
|
||||
if pty is not None and cur.Ty is not None and self._call_ty_equal(pool, pty, cur.Ty) == 0:
|
||||
conv: mlirlite.Value* = self.Gen.CallGenLib._coerce_arg_value_to_ty(pool, builder, cur, pty, node, trans, 0)
|
||||
if conv is not None: node_v = conv
|
||||
# Win64 byref aggregate parameter: C takes a private copy, so never pass the
|
||||
# argument variable's own address (a callee write-back must not reach the caller).
|
||||
if self.is_extern_byref_param(call_name, pi_c) != 0:
|
||||
cp_v: mlirlite.Value* = self._copy_byref_agg_arg(pool, builder, node_v, pty)
|
||||
if cp_v is not None: node_v = cp_v
|
||||
mlirlite.value_set_next(node_v, None)
|
||||
if new_head is None: new_head = node_v
|
||||
else: mlirlite.value_set_next(new_tail, node_v)
|
||||
new_tail = node_v
|
||||
cur = nxt
|
||||
prm = mlirlite.param_get_next(prm)
|
||||
pi_c += 1
|
||||
while cur is not None:
|
||||
nxt2: mlirlite.Value* = cur.Next
|
||||
mlirlite.value_set_next(cur, None)
|
||||
@@ -1737,7 +1980,7 @@ novirtual class CallGenBase:
|
||||
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)
|
||||
rv_p: mlirlite.LLVMType* = VpiReader._resolve_vpi_ret_annot_str(pb_d, annot_d, i32_p, i8p_p, probe_sha)
|
||||
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
|
||||
@@ -2328,14 +2571,7 @@ novirtual class CallGenBase:
|
||||
subdir_buf[2] = '\0'
|
||||
viperlib.snprintf(path_buf, path_len, "%s/%s/%s.text.ll", self.Gen.CallGenBase.TempDir, subdir_buf, sha1)
|
||||
stdlib.free(subdir_buf)
|
||||
# existence check via GetFileAttributesA (no file handle opened)
|
||||
attr: ULONG = win32file.GetFileAttributesA(path_buf)
|
||||
exists: int = 1
|
||||
# INVALID_FILE_ATTRIBUTES = missing/inaccessible -> 0.
|
||||
if attr == 0xFFFFFFFF: exists = 0
|
||||
# The original CreateFileA rejected directories; exclude them explicitly to
|
||||
# preserve the equivalent "readable regular file" semantics.
|
||||
if (attr & win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: exists = 0
|
||||
exists: int = 1 if os.isfile(path_buf) else 0
|
||||
stdlib.free(path_buf)
|
||||
return exists
|
||||
|
||||
|
||||
@@ -1870,7 +1870,7 @@ novirtual class CallGenCall(CallGenBase.CallGenBase):
|
||||
match entry.Ty:
|
||||
case mlirlite.LLVMType.Struct(fields, fcount, name):
|
||||
offset_fb: int = 0
|
||||
# packed (@c.Attribute(t.attr.packed)) structs have no field padding:
|
||||
# packed (@Attribute(attr.packed)) structs have no field padding:
|
||||
# offset = sum of preceding field sizes (LLVM packed struct layout).
|
||||
is_packed_fb: int = entry.IsPacked
|
||||
cur_fb: mlirlite.ParamNode* = fields
|
||||
|
||||
@@ -678,11 +678,24 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
|
||||
self.Gen.TypeGenErr.fatal_error(new_node, f"`new`: storage allocation for the scalar type '{nm}' failed", "TypeError")
|
||||
return None
|
||||
# 零初始化:`new … T()` 走 __before_init__ 是零填充,标量/数组同语义(未写先读不再是垃圾)
|
||||
zero_v: mlirlite.Value* = mlirlite.ConstZero(pool, prim_ty)
|
||||
if zero_v is None:
|
||||
self.Gen.TypeGenErr.fatal_error(new_node, f"`new`: cannot build a zero initializer for the scalar type '{nm}'", "TypeError")
|
||||
return None
|
||||
mlirlite.build_store(builder, zero_v, storage)
|
||||
# 数组走 llvm.memset,不生成"整数组常量存储"IR(`store [N x i8] zeroinitializer`)。
|
||||
# N 较大时后者在 LLVM 22 llc/Win32 上会崩溃;语法/语义上它仍是数组,实际存储只是
|
||||
# malloc/memgr 给的字节块,不需要按数组聚合类型整体写一次。
|
||||
if elem_ty is not None:
|
||||
zero_byte: mlirlite.Value* = mlirlite.ConstInt(pool, mlirlite.Int8(pool), 0, "0")
|
||||
zero_len: mlirlite.Value* = mlirlite.const_int64(pool, size_bytes)
|
||||
if zero_byte is None or zero_len is None:
|
||||
self.Gen.TypeGenErr.fatal_error(new_node, f"`new`: cannot build a zero initializer for the array type '{nm}'", "TypeError")
|
||||
return None
|
||||
if self.Gen.CallGenLib._emit_llvm_memset_intrinsic(pool, builder, mod, storage, zero_byte, zero_len) is None:
|
||||
self.Gen.TypeGenErr.fatal_error(new_node, f"`new`: cannot zero-initialize the array type '{nm}'", "TypeError")
|
||||
return None
|
||||
else:
|
||||
zero_v: mlirlite.Value* = mlirlite.ConstZero(pool, prim_ty)
|
||||
if zero_v is None:
|
||||
self.Gen.TypeGenErr.fatal_error(new_node, f"`new`: cannot build a zero initializer for the scalar type '{nm}'", "TypeError")
|
||||
return None
|
||||
mlirlite.build_store(builder, zero_v, storage)
|
||||
# 返回值:标量 → 该指针本身;数组 → **首元素指针 Elem***(数组名退化,同址零成本 bitcast)
|
||||
ptr_ty: mlirlite.LLVMType* = None
|
||||
if elem_ty is not None: ptr_ty = mlirlite.Ptr(pool, elem_ty)
|
||||
|
||||
@@ -1426,6 +1426,122 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
|
||||
return 1
|
||||
|
||||
|
||||
# ---- aggregate default encoding (struct constructor with scalar arguments) ----
|
||||
# Token format shared with VpiReader._VpiEncodeAggDefault:
|
||||
# i:<decimal> d:<float> s:<len>:<text> b:<0|1> n:0 (one token per field, '|' split)
|
||||
def _agg_enc_put(self, dst: bytes, cap: sizet, pos: sizet*, src: char*, n: sizet) -> int:
|
||||
if dst is None or pos is None or src is None: return 0
|
||||
if n == 0: return 1
|
||||
if pos[0] + n + 1 > cap: return 0
|
||||
string.memcpy(dst + pos[0], src, n)
|
||||
pos[0] += n
|
||||
dst[pos[0]] = '\0'
|
||||
return 1
|
||||
|
||||
def _agg_enc_int(self, dst: bytes, cap: sizet, pos: sizet*, v: int64) -> int:
|
||||
tmp: str = stdlib.malloc(32)
|
||||
if tmp is None: return 0
|
||||
viperlib.snprintf(tmp, 32, "%lld", v)
|
||||
r: int = self._agg_enc_put(dst, cap, pos, tmp, string.strlen(tmp))
|
||||
stdlib.free(tmp)
|
||||
return r
|
||||
|
||||
def _agg_enc_float(self, dst: bytes, cap: sizet, pos: sizet*, v: double) -> int:
|
||||
tmp: str = stdlib.malloc(64)
|
||||
if tmp is None: return 0
|
||||
viperlib.snprintf(tmp, 64, "%.17g", v)
|
||||
r: int = self._agg_enc_put(dst, cap, pos, "d:", 2)
|
||||
if r != 0: r = self._agg_enc_put(dst, cap, pos, tmp, string.strlen(tmp))
|
||||
stdlib.free(tmp)
|
||||
return r
|
||||
|
||||
def _agg_enc_arg(self, dst: bytes, cap: sizet, pos: sizet*, node: ast.AST*) -> int:
|
||||
"""Encode one scalar constructor argument; 0 = unsupported."""
|
||||
if node is None: return 0
|
||||
k: int = node.kind()
|
||||
if k == ast.ASTKind.Constant:
|
||||
cn: ast.Constant* = ast.Constant*(node)
|
||||
if cn.const_kind == ast.CONST_INT or cn.const_kind == ast.CONST_CHAR:
|
||||
if self._agg_enc_put(dst, cap, pos, "i:", 2) == 0: return 0
|
||||
return self._agg_enc_int(dst, cap, pos, cn.int_val)
|
||||
if cn.const_kind == ast.CONST_FLOAT: return self._agg_enc_float(dst, cap, pos, cn.float_val)
|
||||
if cn.const_kind == ast.CONST_BOOL:
|
||||
if cn.int_val != 0: return self._agg_enc_put(dst, cap, pos, "b:1", 3)
|
||||
return self._agg_enc_put(dst, cap, pos, "b:0", 3)
|
||||
if cn.const_kind == ast.CONST_NONE: return self._agg_enc_put(dst, cap, pos, "n:0", 3)
|
||||
if cn.const_kind == ast.CONST_STR and cn.str_val is not None:
|
||||
sl: sizet = string.strlen(cn.str_val)
|
||||
if self._agg_enc_put(dst, cap, pos, "s:", 2) == 0: return 0
|
||||
if self._agg_enc_int(dst, cap, pos, int64(sl)) == 0: return 0
|
||||
if self._agg_enc_put(dst, cap, pos, ":", 1) == 0: return 0
|
||||
return self._agg_enc_put(dst, cap, pos, cn.str_val, sl)
|
||||
return 0
|
||||
if k == ast.ASTKind.UnaryOp:
|
||||
du: ast.UnaryOp* = ast.UnaryOp*(node)
|
||||
if du.operand is None or du.operand.kind() != ast.ASTKind.Constant: return 0
|
||||
dc: ast.Constant* = ast.Constant*(du.operand)
|
||||
if dc.const_kind == ast.CONST_INT or dc.const_kind == ast.CONST_CHAR:
|
||||
v: int64 = dc.int_val
|
||||
if du.op == ast.OpKind.USub: v = -v
|
||||
elif du.op == ast.OpKind.Invert: v = ~v
|
||||
if self._agg_enc_put(dst, cap, pos, "i:", 2) == 0: return 0
|
||||
return self._agg_enc_int(dst, cap, pos, v)
|
||||
if dc.const_kind == ast.CONST_FLOAT:
|
||||
fv: double = dc.float_val
|
||||
if du.op == ast.OpKind.USub: fv = -fv
|
||||
return self._agg_enc_float(dst, cap, pos, fv)
|
||||
return 0
|
||||
if k == ast.ASTKind.Name:
|
||||
nm: ast.Name* = ast.Name*(node)
|
||||
if nm.id is None: return 0
|
||||
cv: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(nm.id)
|
||||
if self.Gen.TypeGenCDef.is_cdefine_found() == 0: return 0
|
||||
if self._agg_enc_put(dst, cap, pos, "i:", 2) == 0: return 0
|
||||
return self._agg_enc_int(dst, cap, pos, cv)
|
||||
return 0
|
||||
|
||||
def _agg_expr_text_at(self, node: ast.AST*, dst: bytes, cap: sizet, pos: sizet*) -> int:
|
||||
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 None: return 0
|
||||
return self._agg_enc_put(dst, cap, pos, nm.id, string.strlen(nm.id))
|
||||
if k == ast.ASTKind.Attribute:
|
||||
at: ast.Attribute* = ast.Attribute*(node)
|
||||
if at.value is None: return 0
|
||||
if self._agg_expr_text_at(at.value, dst, cap, pos) == 0: return 0
|
||||
if self._agg_enc_put(dst, cap, pos, ".", 1) == 0: return 0
|
||||
if at.attr is None: return 0
|
||||
return self._agg_enc_put(dst, cap, pos, at.attr, string.strlen(at.attr))
|
||||
return 0
|
||||
|
||||
def _agg_expr_text(self, node: ast.AST*, dst: bytes, cap: sizet) -> int:
|
||||
"""Render a Name/Attribute default (enum member / CDefine) into dst; 0 = unsupported."""
|
||||
if dst is None or cap == 0: return 0
|
||||
dst[0] = '\0'
|
||||
pos: sizet = 0
|
||||
return self._agg_expr_text_at(node, dst, cap, &pos)
|
||||
|
||||
def _encode_agg_default(self, cl: ast.Call*, dst: bytes, cap: sizet) -> int:
|
||||
"""Encode a struct-constructor default's scalar arguments; 0 = unsupported."""
|
||||
if cl is None or dst is None or cl.func is None: return 0
|
||||
if cl.keywords is not None and len(cl.keywords) > 0: return 0
|
||||
if cl.func.kind() != ast.ASTKind.Name: return 0
|
||||
nm: ast.Name* = ast.Name*(cl.func)
|
||||
if nm.id is None: return 0
|
||||
if GenApi.find_struct(nm.id) is None: return 0
|
||||
if cl.args is None: return 0
|
||||
pos: sizet = 0
|
||||
dst[0] = '\0'
|
||||
al: list[ast.AST*]* = cl.args
|
||||
an: sizet = len(al)
|
||||
for ai in range(int(an)):
|
||||
if ai > 0:
|
||||
if self._agg_enc_put(dst, cap, &pos, "|", 1) == 0: return 0
|
||||
if self._agg_enc_arg(dst, cap, &pos, al.get(ai)) == 0: return 0
|
||||
return 1
|
||||
|
||||
def _register_global_defaults(self, name: char*, defaults: rawptr, default_count: int, param_count: int, def_sha1: char* = None) -> int:
|
||||
"""Register a function's default arguments in the global table; returns 0 on success"""
|
||||
if name is None or defaults is None: return 1
|
||||
@@ -1477,8 +1593,18 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
|
||||
dnode: ast.AST* = dl.get(di)
|
||||
if dnode is not None and dnode.kind() == ast.ASTKind.Constant:
|
||||
dcn: ast.Constant* = ast.Constant*(dnode)
|
||||
kinds[di] = dcn.const_kind
|
||||
ints[di] = dcn.int_val
|
||||
if dcn.const_kind == ast.CONST_FLOAT:
|
||||
# Float defaults live in float_val; int_val is always 0. Store the
|
||||
# IEEE-754 bit pattern so the snapshot rebuild can recover the value
|
||||
# (int defaults still use int_val).
|
||||
fv_reg: double = dcn.float_val
|
||||
fb_reg: uint64 = 0
|
||||
*&fb_reg = *&fv_reg
|
||||
kinds[di] = ast.CONST_FLOAT
|
||||
ints[di] = int64(fb_reg)
|
||||
else:
|
||||
kinds[di] = dcn.const_kind
|
||||
ints[di] = dcn.int_val
|
||||
if dcn.const_kind == ast.CONST_STR and dcn.str_val is not None:
|
||||
slen: sizet = string.strlen(dcn.str_val)
|
||||
scopy: char* = CompilerFactory.factory.PermPool.alloc(slen + 1)
|
||||
@@ -1494,6 +1620,16 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
|
||||
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
|
||||
kinds[di] = ast.CONST_INT
|
||||
ints[di] = cdef_v
|
||||
else:
|
||||
# Not a CDefine: keep the name text and let the call site resolve
|
||||
# it (e.g. an enum member imported unqualified).
|
||||
txt_n: bytes = CompilerFactory.factory.PermPool.alloc(256)
|
||||
if txt_n is not None and self._agg_expr_text(dnode, txt_n, 256) != 0:
|
||||
kinds[di] = GenTypes.CONST_EXPR_TEXT
|
||||
ints[di] = 0
|
||||
strs[di] = sizet(txt_n)
|
||||
else:
|
||||
kinds[di] = GenTypes.CONST_UNKNOWN_DEFAULT
|
||||
elif dnode is not None and dnode.kind() == ast.ASTKind.UnaryOp:
|
||||
# unary-literal default (e.g. ex3: int = -1): AST is UnaryOp over a
|
||||
# Constant; evaluate only when the operand is an int Constant.
|
||||
@@ -1510,6 +1646,44 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
|
||||
elif du.op == ast.OpKind.Invert:
|
||||
kinds[di] = ast.CONST_INT
|
||||
ints[di] = ~doc.int_val
|
||||
elif doc.const_kind == ast.CONST_FLOAT:
|
||||
# Negative/positive float literal (e.g. -1.5): store bits.
|
||||
fv_u: double = doc.float_val
|
||||
if du.op == ast.OpKind.USub: fv_u = -fv_u
|
||||
elif du.op == ast.OpKind.UAdd: pass
|
||||
else: fv_u = 0.0
|
||||
fb_u: uint64 = 0
|
||||
*&fb_u = *&fv_u
|
||||
kinds[di] = ast.CONST_FLOAT
|
||||
ints[di] = int64(fb_u)
|
||||
elif dnode is not None and dnode.kind() == ast.ASTKind.Attribute:
|
||||
# Qualified constant (enum member `Flags.Name_`, module constant):
|
||||
# store the text; the call site resolves it after enum registration.
|
||||
txt_a: bytes = CompilerFactory.factory.PermPool.alloc(256)
|
||||
if txt_a is not None and self._agg_expr_text(dnode, txt_a, 256) != 0:
|
||||
kinds[di] = GenTypes.CONST_EXPR_TEXT
|
||||
ints[di] = 0
|
||||
strs[di] = sizet(txt_a)
|
||||
else:
|
||||
kinds[di] = GenTypes.CONST_UNKNOWN_DEFAULT
|
||||
elif dnode is not None and dnode.kind() == ast.ASTKind.Call:
|
||||
# Struct-constructor default (e.g. Vec2(1.0, 2.0)): store the literal
|
||||
# field tokens; cross-module callers rebuild it from the parameter type.
|
||||
enc_buf: bytes = CompilerFactory.factory.PermPool.alloc(4096)
|
||||
if enc_buf is not None and self._encode_agg_default(ast.Call*(dnode), enc_buf, 4096) != 0:
|
||||
kinds[di] = GenTypes.CONST_AGG_DEFAULT
|
||||
ints[di] = 0
|
||||
strs[di] = sizet(enc_buf)
|
||||
else:
|
||||
kinds[di] = GenTypes.CONST_UNKNOWN_DEFAULT
|
||||
elif dnode is not None:
|
||||
txt_x: bytes = CompilerFactory.factory.PermPool.alloc(256)
|
||||
if txt_x is not None and self._agg_expr_text(dnode, txt_x, 256) != 0:
|
||||
kinds[di] = GenTypes.CONST_EXPR_TEXT
|
||||
ints[di] = 0
|
||||
strs[di] = sizet(txt_x)
|
||||
else:
|
||||
kinds[di] = GenTypes.CONST_UNKNOWN_DEFAULT
|
||||
entry.ConstKinds = kinds
|
||||
entry.ConstInts = ints
|
||||
entry.ConstStrs = strs
|
||||
@@ -1583,7 +1757,119 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
|
||||
return None
|
||||
|
||||
|
||||
def _build_const_from_snapshot(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, gd_entry: GenTypes.FuncDefaultsEntry*, di: int, trans: HT.Translator*, builder: mlirlite.IRBuilder*) -> mlirlite.Value*:
|
||||
def _agg_parse_i64(self, vbuf: bytes, s: sizet, e: sizet) -> int64:
|
||||
neg: int = 0
|
||||
i: sizet = s
|
||||
if i < e and vbuf[i] == '-': neg = 1; i += 1
|
||||
v: int64 = 0
|
||||
while i < e:
|
||||
ch: uint8 = vbuf[i]
|
||||
if ch < 0x30 or ch > 0x39: break
|
||||
v = v * 10 + int64(ch - 0x30)
|
||||
i += 1
|
||||
if neg != 0: v = -v
|
||||
return v
|
||||
|
||||
def _agg_parse_f64(self, vbuf: bytes, s: sizet, e: sizet) -> double:
|
||||
n: sizet = e - s
|
||||
tb: bytes = stdlib.malloc(n + 1)
|
||||
if tb is None: return 0.0
|
||||
string.memcpy(tb, vbuf + s, n)
|
||||
tb[n] = '\0'
|
||||
dv: double = string.atof(tb)
|
||||
stdlib.free(tb)
|
||||
return dv
|
||||
|
||||
def _agg_token_value(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, tag: uint8, vbuf: bytes, vs: sizet, ve: sizet, fty: mlirlite.LLVMType*) -> mlirlite.Value*:
|
||||
"""Convert one encoded default token to a constant of the struct field's LLVM type."""
|
||||
if fty is None: return None
|
||||
match fty:
|
||||
case mlirlite.LLVMType.Int(bits):
|
||||
if tag == 0x69: return mlirlite.const_int(pool, bits, self._agg_parse_i64(vbuf, vs, ve)) # 'i'
|
||||
if tag == 0x62: # 'b'
|
||||
bv: int64 = 1 if (ve > vs and vbuf[vs] == '1') else 0
|
||||
return mlirlite.const_int(pool, bits, bv)
|
||||
if tag == 0x6E: return mlirlite.const_int(pool, bits, 0) # 'n'
|
||||
return None
|
||||
case mlirlite.LLVMType.Float(bits):
|
||||
if tag == 0x64: return mlirlite.ConstFloat(pool, fty, self._agg_parse_f64(vbuf, vs, ve)) # 'd'
|
||||
if tag == 0x69: return mlirlite.ConstFloat(pool, fty, double(self._agg_parse_i64(vbuf, vs, ve)))
|
||||
return None
|
||||
case mlirlite.LLVMType.Ptr(_):
|
||||
if tag == 0x6E: return mlirlite.ConstNull(pool, fty, "null")
|
||||
if tag == 0x73: # 's'
|
||||
n: sizet = ve - vs
|
||||
sv: bytes = CompilerFactory.factory.PermPool.alloc(n + 1)
|
||||
if sv is None: return None
|
||||
string.memcpy(sv, vbuf + vs, n)
|
||||
sv[n] = '\0'
|
||||
gs: mlirlite.Value* = self.Gen.ExprGenConst.create_global_string(builder, pool, mod, sv, trans)
|
||||
if gs is None: return None
|
||||
if self._call_ty_equal(pool, gs.Ty, fty) == 0:
|
||||
bc: mlirlite.Value* = mlirlite.build_bitcast(builder, gs, fty)
|
||||
if bc is not None: return bc
|
||||
return gs
|
||||
return None
|
||||
case _:
|
||||
return None
|
||||
|
||||
def _build_agg_from_tokens(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, spec: char*, param_ty: mlirlite.LLVMType*) -> mlirlite.Value*:
|
||||
"""Rebuild an aggregate default from its field tokens and the parameter's struct type."""
|
||||
if spec is None or param_ty is None: return None
|
||||
sty: mlirlite.LLVMType* = param_ty
|
||||
match param_ty:
|
||||
case mlirlite.LLVMType.Ptr(pe): sty = pe
|
||||
case _: sty = param_ty
|
||||
if sty is None or sty.kind() != mlirlite.LLVMType.Struct: return None
|
||||
fields: mlirlite.ParamNode* = None
|
||||
fcount: int = 0
|
||||
match sty:
|
||||
case mlirlite.LLVMType.Struct(fs, fc, _):
|
||||
fields = fs
|
||||
fcount = fc
|
||||
case _:
|
||||
return None
|
||||
if fcount <= 0: return None
|
||||
slot: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, sty)
|
||||
if slot is None: return None
|
||||
sp: char* = spec
|
||||
cur: mlirlite.ParamNode* = fields
|
||||
i: int = 0
|
||||
while cur is not None and i < fcount:
|
||||
fty: mlirlite.LLVMType* = cur.Ty
|
||||
tag: uint8 = uint8(sp[0])
|
||||
if sp[1] != ':': return None
|
||||
sp = sp + 2
|
||||
token_base: char* = sp
|
||||
tl: sizet = 0
|
||||
if tag == 0x73: # 's': <len>:<text>
|
||||
while sp[0] >= '0' and sp[0] <= '9':
|
||||
tl = tl * 10 + sizet(int(sp[0]) - 48)
|
||||
sp += 1
|
||||
if sp[0] != ':': return None
|
||||
sp += 1
|
||||
token_base = sp
|
||||
sp = sp + tl
|
||||
else:
|
||||
while sp[0] != 0 and sp[0] != '|': sp += 1
|
||||
tl = sizet(uint64(sp) - uint64(token_base))
|
||||
val: mlirlite.Value* = self._agg_token_value(pool, builder, mod, trans, tag, token_base, 0, tl, fty)
|
||||
if val is None: return None
|
||||
fp: mlirlite.Value* = mlirlite.build_gep_struct(builder, sty, fty, slot, i)
|
||||
if fp is None: return None
|
||||
mlirlite.build_store(builder, val, fp)
|
||||
if sp[0] == '|': sp += 1
|
||||
cur = cur.Next
|
||||
i += 1
|
||||
if cur is not None or sp[0] != 0: return None
|
||||
if param_ty.kind() == mlirlite.LLVMType.Ptr:
|
||||
if self._call_ty_equal(pool, param_ty, slot.Ty) == 0:
|
||||
bc2: mlirlite.Value* = mlirlite.build_bitcast(builder, slot, param_ty)
|
||||
if bc2 is not None: return bc2
|
||||
return slot
|
||||
return mlirlite.build_load(builder, sty, slot)
|
||||
|
||||
def _build_const_from_snapshot(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, gd_entry: GenTypes.FuncDefaultsEntry*, di: int, trans: HT.Translator*, builder: mlirlite.IRBuilder*, param_ty: mlirlite.LLVMType* = None) -> mlirlite.Value*:
|
||||
"""Rebuild the di-th default-value constant IR from the snapshot"""
|
||||
if gd_entry is None or gd_entry.ConstKinds is None or gd_entry.ConstInts is None: return None
|
||||
if di < 0 or di >= gd_entry.DefaultCount: return None
|
||||
@@ -1603,7 +1889,44 @@ novirtual class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
|
||||
if gd_entry.ConstStrs is not None and gd_entry.ConstStrs[di] != 0:
|
||||
sv: char* = rawptr(gd_entry.ConstStrs[di])
|
||||
return self.Gen.ExprGenConst.create_global_string(builder, pool, mod, sv, trans)
|
||||
# return None for unreconstructable values (CONST_FLOAT / complex expr); caller falls back to AST
|
||||
elif kind == ast.CONST_FLOAT:
|
||||
# The snapshot stores the IEEE-754 bit pattern in the int64 slot.
|
||||
uv_f: uint64 = uint64(ival)
|
||||
dv_f: double = 0.0
|
||||
*&dv_f = *&uv_f
|
||||
return mlirlite.ConstFloat(pool, mlirlite.Double(pool), dv_f)
|
||||
elif kind == GenTypes.CONST_AGG_DEFAULT:
|
||||
if gd_entry.ConstStrs is None or gd_entry.ConstStrs[di] == 0: return None
|
||||
spec: char* = rawptr(gd_entry.ConstStrs[di])
|
||||
return self._build_agg_from_tokens(pool, builder, mod, trans, spec, param_ty)
|
||||
elif kind == GenTypes.CONST_EXPR_TEXT:
|
||||
# Resolved now (enums/CDefines are registered): enum member `Flags.Name_` or a
|
||||
# bare CDefine name.
|
||||
if gd_entry.ConstStrs is None or gd_entry.ConstStrs[di] == 0: return None
|
||||
sv: char* = rawptr(gd_entry.ConstStrs[di])
|
||||
dot: char* = string.strrchr(sv, '.')
|
||||
if dot is not None:
|
||||
slen: sizet = uint64(dot) - uint64(sv)
|
||||
if slen == 0: return None
|
||||
scope: bytes = pool.alloc(slen + 1)
|
||||
if scope is None: return None
|
||||
string.strncpy(scope, sv, slen)
|
||||
scope[slen] = '\0'
|
||||
me: GenTypes.EnumMember* = self.Gen.StmtGenDecl.lookup_enum_member(scope, dot + 1, None)
|
||||
if me is None: return None
|
||||
mbits: int = 32
|
||||
if me.Ty is not None:
|
||||
match me.Ty:
|
||||
case mlirlite.LLVMType.Int(bits_e): mbits = bits_e
|
||||
case _: mbits = 32
|
||||
return mlirlite.const_int(pool, mbits, me.Value)
|
||||
cv: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(sv)
|
||||
if self.Gen.TypeGenCDef.is_cdefine_found() == 0: return None
|
||||
if cv > 2147483647 or cv < -2147483648: return mlirlite.const_int64(pool, cv)
|
||||
return mlirlite.const_int32(pool, cv)
|
||||
elif kind == GenTypes.CONST_UNKNOWN_DEFAULT:
|
||||
return None
|
||||
# return None for unreconstructable values (complex expr); caller falls back to AST
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -346,7 +346,23 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
|
||||
return val
|
||||
case _:
|
||||
pass
|
||||
case mlirlite.LLVMType.Ptr(_):
|
||||
case mlirlite.LLVMType.Float(bits_fa):
|
||||
match tgt_ty:
|
||||
case mlirlite.LLVMType.Float(bits_fb):
|
||||
if bits_fb == bits_fa: return val
|
||||
if bits_fb > bits_fa:
|
||||
nv_fe: mlirlite.Value* = mlirlite.build_fpext(builder, val, tgt_ty)
|
||||
if nv_fe is not None: return nv_fe
|
||||
return val
|
||||
# Narrowing (double default literal -> float param): only constants, so an
|
||||
# SSA value's precision is never silently dropped.
|
||||
if val.IsConst != 0:
|
||||
nv_ft: mlirlite.Value* = mlirlite.build_fptrunc(builder, val, tgt_ty)
|
||||
if nv_ft is not None: return nv_ft
|
||||
return val
|
||||
case _:
|
||||
pass
|
||||
case mlirlite.LLVMType.Ptr(ptr_elem):
|
||||
match tgt_ty:
|
||||
case mlirlite.LLVMType.Ptr(_):
|
||||
# same-level pointers: bitcast when the arg type differs from the definition
|
||||
@@ -355,11 +371,21 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
|
||||
nv_p: mlirlite.Value* = mlirlite.build_bitcast(builder, val, tgt_ty)
|
||||
if nv_p is not None: return nv_p
|
||||
case mlirlite.LLVMType.Int(bits_pi):
|
||||
# A pointer passed where the callee's parameter degraded to an integer
|
||||
# (e.g. `rawptr` resolved as uint64 in a stale/mis-typed stub): convert at the
|
||||
# call boundary instead of failing the implicit-cast rule, so passing a
|
||||
# rawptr never breaks the build and the callee still receives the address.
|
||||
if bits_pi > 0:
|
||||
# Win64 C ABI: a by-value aggregate parameter of exactly 1/2/4/8 bytes
|
||||
# arrives here as the address of Viper's spilled copy. Pass the aggregate
|
||||
# BITS, not the address (a C callee reads the struct from the register).
|
||||
if ptr_elem is not None and ptr_elem.kind() == mlirlite.LLVMType.Struct:
|
||||
sz_pi: int = self.Gen.CallGenBase._c_agg_size(ptr_elem)
|
||||
if sz_pi > 0 and sz_pi * 8 == bits_pi and self.Gen.CallGenBase.is_win64_c_abi() != 0:
|
||||
pi_ip: mlirlite.Value* = mlirlite.build_bitcast(builder, val, mlirlite.Ptr(pool, tgt_ty))
|
||||
if pi_ip is not None:
|
||||
pi_ld: mlirlite.Value* = mlirlite.build_load(builder, tgt_ty, pi_ip)
|
||||
if pi_ld is not None: return pi_ld
|
||||
# A pointer passed where the callee's parameter degraded to an integer
|
||||
# (e.g. `rawptr` resolved as uint64 in a stale/mis-typed stub): convert at the
|
||||
# call boundary instead of failing the implicit-cast rule, so passing a
|
||||
# rawptr never breaks the build and the callee still receives the address.
|
||||
nv_p2: mlirlite.Value* = mlirlite.build_ptrtoint(builder, val, tgt_ty)
|
||||
if nv_p2 is not None: return nv_p2
|
||||
case _:
|
||||
@@ -374,6 +400,19 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
|
||||
if tmp_a is not None:
|
||||
mlirlite.build_store(builder, val, tmp_a)
|
||||
return tmp_a
|
||||
case mlirlite.LLVMType.Int(bits_agg):
|
||||
# Win64 C ABI: a 1/2/4/8-byte by-value aggregate is passed in one integer
|
||||
# register. Spill the value and load its bits at the parameter width.
|
||||
if bits_agg > 0 and self.Gen.CallGenBase.is_win64_c_abi() != 0 and self.Gen.CallGenBase._c_agg_size(val.Ty) * 8 == bits_agg:
|
||||
vk_b: int = val.Ty.kind()
|
||||
if vk_b == mlirlite.LLVMType.Struct or vk_b == mlirlite.LLVMType.Array:
|
||||
tmp_b: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, val.Ty)
|
||||
if tmp_b is not None:
|
||||
mlirlite.build_store(builder, val, tmp_b)
|
||||
ip_b: mlirlite.Value* = mlirlite.build_bitcast(builder, tmp_b, mlirlite.Ptr(pool, tgt_ty))
|
||||
if ip_b is not None:
|
||||
ld_b: mlirlite.Value* = mlirlite.build_load(builder, tgt_ty, ip_b)
|
||||
if ld_b is not None: return ld_b
|
||||
case _:
|
||||
pass
|
||||
return val
|
||||
@@ -582,6 +621,56 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
|
||||
|
||||
# Return dst (matches C memcpy semantics: returns the dest pointer)
|
||||
return dst_coerced
|
||||
|
||||
def _emit_llvm_memset_intrinsic(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, dst_val: mlirlite.Value*, byte_val: mlirlite.Value*, num_val: mlirlite.Value*) -> mlirlite.Value*:
|
||||
"""Fill memory via the @llvm.memset intrinsic (avoids whole-aggregate constant stores)."""
|
||||
if pool is None or builder is None or mod is None: return None
|
||||
if dst_val is None or byte_val is None or num_val is None: return None
|
||||
|
||||
void_ty: mlirlite.LLVMType* = mlirlite.Void(pool)
|
||||
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
|
||||
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
|
||||
i1_ty: mlirlite.LLVMType* = mlirlite.Int1(pool)
|
||||
|
||||
# Declare @llvm.memset (if not already declared): dst, val, len, isvolatile
|
||||
memset_func: mlirlite.Function* = self.find_func_in_module(mod, "llvm.memset")
|
||||
if memset_func is None:
|
||||
memset_func = mlirlite.create_declare(pool, mod, "llvm.memset", void_ty)
|
||||
if memset_func is not None:
|
||||
mlirlite.add_param(pool, memset_func, i8_ptr_ty, "dst")
|
||||
mlirlite.add_param(pool, memset_func, i8_ty, "val")
|
||||
mlirlite.add_param(pool, memset_func, i64_ty, "len")
|
||||
mlirlite.add_param(pool, memset_func, i1_ty, "isvolatile")
|
||||
|
||||
# dst -> i8*
|
||||
dst_coerced: mlirlite.Value* = dst_val
|
||||
dst_ty: mlirlite.LLVMType* = mlirlite.value_get_ty(dst_val)
|
||||
if self.Gen.ExprGenBase.is_ptr_type(dst_ty) != 0:
|
||||
dst_coerced = mlirlite.build_bitcast(builder, dst_val, i8_ptr_ty)
|
||||
else:
|
||||
dst_bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(dst_ty)
|
||||
if dst_bits != 0:
|
||||
if dst_bits < 64: dst_coerced = mlirlite.build_zext(builder, dst_val, i64_ty)
|
||||
dst_coerced = mlirlite.build_inttoptr(builder, dst_coerced, i8_ptr_ty)
|
||||
|
||||
# val -> i8; len -> i64
|
||||
byte_coerced: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, byte_val, i8_ty)
|
||||
num_coerced: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, num_val, i64_ty)
|
||||
if dst_coerced is None or byte_coerced is None or num_coerced is None: return None
|
||||
|
||||
# Append the i1 false arg (isvolatile)
|
||||
false_val: mlirlite.Value* = mlirlite.ConstInt(pool, i1_ty, 0, "false")
|
||||
mlirlite.value_set_next(dst_coerced, byte_coerced)
|
||||
mlirlite.value_set_next(byte_coerced, num_coerced)
|
||||
if false_val is not None:
|
||||
mlirlite.value_set_next(num_coerced, false_val)
|
||||
mlirlite.value_set_next(false_val, None)
|
||||
else: mlirlite.value_set_next(num_coerced, None)
|
||||
|
||||
mlirlite.build_call(builder, "llvm.memset", dst_coerced, 4, void_ty, 0)
|
||||
return dst_coerced
|
||||
|
||||
# _translate_call_with_kwargs: keyword-arg call; builds args in found_func.Params order (positional first, else match by param name)
|
||||
|
||||
# ============================================================
|
||||
@@ -682,4 +771,7 @@ novirtual class CallGenLib(CallGenBase.CallGenBase):
|
||||
|
||||
# memcpy -> @llvm.memcpy intrinsic (avoid a missing cross-module @memcpy declare under monomorphization)
|
||||
if string.strcmp(func_name, "memcpy") == 0 and actual_count >= 3: return self._emit_llvm_memcpy_intrinsic(pool, builder, mod, mc_dst1, mc_src1, mc_num1)
|
||||
if call_ret_ty is not None and call_ret_ty.kind() == mlirlite.LLVMType.Struct and self.Gen.CallGenBase.is_extern_agg(call_name) != 0:
|
||||
agg_kw: mlirlite.Value* = self.Gen.CallGenBase.emit_extern_agg_call(pool, builder, mod, call_name, head, int(actual_count), call_ret_ty)
|
||||
if agg_kw is not None: return agg_kw
|
||||
return mlirlite.build_call(builder, call_name, head, actual_count, call_ret_ty, 0)
|
||||
@@ -179,6 +179,55 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
# 早期补参用的是 dfx_callee_sha,链式接收者(`self.Gen.X.m(...)`)解析不出发起模块时它会
|
||||
# 退化成调用方 SHA1,表查不到;此时 call_name 已带定义模块 SHA1 前缀,是权威键,再补一次。
|
||||
# 返回补齐后的实参个数。
|
||||
def _default_param_llvm_ty(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, call_name: char*, idx: int, trans: HT.Translator*) -> mlirlite.LLVMType*:
|
||||
"""LLVM type of parameter `idx` in the callee's declaration (None when unavailable).
|
||||
|
||||
An aggregate default is rebuilt from this type's struct fields, so the declare (created
|
||||
by the vpi injection / forward declaration) must already exist at default-fill time.
|
||||
|
||||
The injected declare may have resolved a bare struct annotation against a same-named
|
||||
struct in THIS module. Re-resolve it by (name, callee module SHA1) when the declared
|
||||
struct belongs to another module; otherwise the default is built with the wrong layout.
|
||||
"""
|
||||
if mod is None or call_name is None or idx < 0: return None
|
||||
func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, call_name)
|
||||
if func is None: return None
|
||||
prm: mlirlite.Param* = mlirlite.function_get_param_head(func)
|
||||
j: int = 0
|
||||
while prm is not None:
|
||||
if j == idx: break
|
||||
prm = mlirlite.param_get_next(prm)
|
||||
j += 1
|
||||
if prm is None: return None
|
||||
pty: mlirlite.LLVMType* = mlirlite.param_get_ty(prm)
|
||||
if pty is None or pool is None: return pty
|
||||
pe: mlirlite.LLVMType* = None
|
||||
match pty:
|
||||
case mlirlite.LLVMType.Ptr(pe0): pe = pe0
|
||||
case _: pe = None
|
||||
if pe is None or pe.kind() != mlirlite.LLVMType.Struct: return pty
|
||||
sname: char* = None
|
||||
match pe:
|
||||
case mlirlite.LLVMType.Struct(_, _, sn): sname = sn
|
||||
case _: sname = None
|
||||
if sname is None: return pty
|
||||
sha_pfx: int = GenApi.sha1_prefix_len(call_name)
|
||||
if sha_pfx == 0: return pty
|
||||
sha_buf: char* = pool.alloc(17)
|
||||
if sha_buf is None: return pty
|
||||
string.strncpy(sha_buf, call_name, 16)
|
||||
sha_buf[16] = '\0'
|
||||
# Already the callee module's struct? (name is "{sha1}.Class")
|
||||
if string.strncmp(sname, sha_buf, 16) == 0 and sname[16] == '.': return pty
|
||||
dot_s: char* = string.strrchr(sname, '.')
|
||||
bare: char* = sname
|
||||
if dot_s is not None: bare = dot_s + 1
|
||||
VpiReader._ensure_dep_vpi_injected(pool, mod, sha_buf, trans)
|
||||
se: GenTypes.StructEntry* = GenApi.find_struct_by_module(bare, sha_buf)
|
||||
if se is not None and se.Ty is not None:
|
||||
return mlirlite.Ptr(pool, se.Ty)
|
||||
return pty
|
||||
|
||||
def _fill_defaults_late(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, builder: mlirlite.IRBuilder*, trans: HT.Translator*, cl: ast.Call*, call_name: char*, head: mlirlite.Value*, can: sizet) -> sizet:
|
||||
"""按 call_name 里的定义模块 SHA1 前缀补缺省参数;返回新的实参个数"""
|
||||
if head is None or can == 0 or call_name is None: return can
|
||||
@@ -207,7 +256,10 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
for df_i in range(int(de.DefaultCount)):
|
||||
df_pidx: int = df_off + df_i
|
||||
if df_pidx < n: continue
|
||||
df_val: mlirlite.Value* = self.Gen.CallGenLeafKit._build_const_from_snapshot(pool, mod, de, df_i, trans, builder)
|
||||
df_ty: mlirlite.LLVMType* = None
|
||||
if de.ConstKinds is not None and de.ConstKinds[df_i] == GenTypes.CONST_AGG_DEFAULT:
|
||||
df_ty = self._default_param_llvm_ty(pool, mod, call_name, df_pidx, trans)
|
||||
df_val: mlirlite.Value* = self.Gen.CallGenLeafKit._build_const_from_snapshot(pool, mod, de, df_i, trans, builder, df_ty)
|
||||
if df_val is None:
|
||||
self.Gen.TypeGenErr.fatal_error(cl, f"default argument {df_i + 1} of '{call_name}' cannot be rebuilt into a value; refusing to emit a call with fewer operands than the callee declares", "TypeError")
|
||||
df_val.Next = None
|
||||
@@ -1428,7 +1480,18 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
if df_param_idx >= can:
|
||||
# Prefer rebuilding from the stdlib snapshot (gd_entry.Defaults' in-pool AST may already be reclaimed).
|
||||
df_arg_val: mlirlite.Value* = None
|
||||
if gd_entry is not None: df_arg_val = self.Gen.CallGenLeafKit._build_const_from_snapshot(pool, mod, gd_entry, df_di, trans, builder)
|
||||
if gd_entry is not None:
|
||||
# Aggregate defaults need the callee's parameter type: the injected
|
||||
# declare may have bound a same-named struct from this module, and
|
||||
# building from that layout silently passes the wrong bytes.
|
||||
df_pty: mlirlite.LLVMType* = None
|
||||
if gd_entry.ConstKinds is not None and gd_entry.ConstKinds[df_di] == GenTypes.CONST_AGG_DEFAULT:
|
||||
df_call_nm: char* = func_name
|
||||
if found_func is not None:
|
||||
df_fn_nm: char* = mlirlite.function_get_name(found_func)
|
||||
if df_fn_nm is not None: df_call_nm = df_fn_nm
|
||||
df_pty = self._default_param_llvm_ty(pool, mod, df_call_nm, df_param_idx, trans)
|
||||
df_arg_val = self.Gen.CallGenLeafKit._build_const_from_snapshot(pool, mod, gd_entry, df_di, trans, builder, df_pty)
|
||||
if df_arg_val is None and df_defaults is not None:
|
||||
df_defaults_list: list[ast.AST*]* = list[ast.AST*]*(df_defaults)
|
||||
df_default_node: ast.AST* = df_defaults_list.get(df_di)
|
||||
@@ -1804,5 +1867,10 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
# instead of emitting an llvm.call the module's llvm.func does not accept.
|
||||
head = self.Gen.CallGenBase._conform_call_args(pool, builder, mod, call_name, head, cl, trans)
|
||||
call_ret_ty = self.Gen.CallGenBase._call_symbol_ret_ty(pool, mod, call_name, call_ret_ty, cl)
|
||||
# C extern returning an aggregate: the C ABI differs from Viper's aggregate convention
|
||||
# (Win64: i8/i16/i32/i64 return for exactly 1/2/4/8 bytes, hidden sret pointer otherwise).
|
||||
if call_ret_ty is not None and call_ret_ty.kind() == mlirlite.LLVMType.Struct and self.Gen.CallGenBase.is_extern_agg(call_name) != 0:
|
||||
agg_res: mlirlite.Value* = self.Gen.CallGenBase.emit_extern_agg_call(pool, builder, mod, call_name, head, int(can), call_ret_ty)
|
||||
if agg_res is not None: return agg_res
|
||||
# try-except: when the callee takes EH out-params, append the output args and check for exceptions after the call.
|
||||
return self.Gen.CallGenLeafKit._emit_eh_guarded_call(builder, pool, mod, trans, found_func, call_name, head, can, call_ret_ty, is_variadic_call)
|
||||
@@ -4,6 +4,7 @@ import memhub
|
||||
import string
|
||||
import stdlib
|
||||
import viperlib
|
||||
import mlirlite
|
||||
import lib.core.Gen.Translator as HT
|
||||
import lib.core.CompilerFactory as CompilerFactory
|
||||
import lib.core.Gen.GenTypes as GenTypes
|
||||
@@ -219,7 +220,7 @@ novirtual class ClassGenBase:
|
||||
return 1 if string.strcmp(at2.attr, attr_name) == 0 else 0
|
||||
return 0
|
||||
def _class_has_tattr(self, decorator_list: list[ast.AST*]*, attr_name: str) -> int:
|
||||
"""Return 1 if decorator_list contains t.attr.<attr_name> (@c.Attribute(t.attr.xxx) or @t.attr.xxx)."""
|
||||
"""Return 1 if decorator_list contains t.attr.<attr_name> (@Attribute(attr.xxx) or @t.attr.xxx)."""
|
||||
if decorator_list is None: return 0
|
||||
dn: sizet = len(decorator_list)
|
||||
if dn == 0: return 0
|
||||
@@ -242,6 +243,110 @@ novirtual class ClassGenBase:
|
||||
# Direct @t.attr.xxx form.
|
||||
if self._tattr_name(deco, attr_name) != 0: return 1
|
||||
return 0
|
||||
def _decor_expr_text(self, pool: memhub.MemBuddy*, node: ast.AST*, depth: int = 0) -> str:
|
||||
"""Dotted decorator text for diagnostics (Name / Attribute / Call)."""
|
||||
if node is None or depth > 8: return "?"
|
||||
k: int = node.kind()
|
||||
if k == ast.ASTKind.Name:
|
||||
nm: ast.Name* = ast.Name*(node)
|
||||
if nm.id is not None: return nm.id
|
||||
return "?"
|
||||
if k == ast.ASTKind.Call:
|
||||
cl: ast.Call* = ast.Call*(node)
|
||||
return self._decor_expr_text(pool, cl.func, depth + 1)
|
||||
if k == ast.ASTKind.Attribute:
|
||||
at: ast.Attribute* = ast.Attribute*(node)
|
||||
if at.attr is None: return "?"
|
||||
base: str = self._decor_expr_text(pool, at.value, depth + 1)
|
||||
if base is None or string.strcmp(base, "?") == 0: return at.attr
|
||||
cap: sizet = string.strlen(base) + string.strlen(at.attr) + 2
|
||||
buf: str = pool.alloc(cap)
|
||||
if buf is None: return at.attr
|
||||
viperlib.snprintf(buf, cap, "%s.%s", base, at.attr)
|
||||
return buf
|
||||
return "?"
|
||||
def _decor_is_builtin_name(self, name: str) -> int:
|
||||
"""1 when name is a compiler-recognized bare decorator marker (class/function/method)."""
|
||||
if name is None: return 0
|
||||
if string.strcmp(name, "staticmethod") == 0: return 1
|
||||
if string.strcmp(name, "property") == 0: return 1
|
||||
if string.strcmp(name, "classmethod") == 0: return 1
|
||||
if string.strcmp(name, "NoVTable") == 0: return 1
|
||||
if string.strcmp(name, "VTableT") == 0: return 1
|
||||
if string.strcmp(name, "TLS") == 0: return 1
|
||||
if string.strcmp(name, "StaticT") == 0: return 1
|
||||
if string.strcmp(name, "ExportT") == 0: return 1
|
||||
if string.strcmp(name, "ExternT") == 0: return 1
|
||||
if string.strcmp(name, "State") == 0: return 1
|
||||
if string.strcmp(name, "PureT") == 0: return 1
|
||||
if string.strcmp(name, "DefineT") == 0: return 1
|
||||
if string.strcmp(name, "InlineT") == 0: return 1
|
||||
if string.strcmp(name, "Object") == 0: return 1
|
||||
if string.strcmp(name, "EnumBase") == 0: return 1
|
||||
if string.strcmp(name, "UnionBase") == 0: return 1
|
||||
if string.strcmp(name, "REnum") == 0: return 1
|
||||
if string.strcmp(name, "life") == 0: return 1
|
||||
if string.strcmp(name, "willFree") == 0: return 1
|
||||
if string.strcmp(name, "Attribute") == 0: return 1
|
||||
return 0
|
||||
def _decor_is_attr_marker(self, name: str) -> int:
|
||||
"""1 when name is a recognized attribute marker or property accessor."""
|
||||
if name is None: return 0
|
||||
if string.strcmp(name, "setter") == 0: return 1
|
||||
if string.strcmp(name, "getter") == 0: return 1
|
||||
if string.strcmp(name, "deleter") == 0: return 1
|
||||
if string.strcmp(name, "packed") == 0: return 1
|
||||
if string.strcmp(name, "aligned") == 0: return 1
|
||||
if string.strcmp(name, "section") == 0: return 1
|
||||
if string.strcmp(name, "always_inline") == 0: return 1
|
||||
if string.strcmp(name, "noinline") == 0: return 1
|
||||
if string.strcmp(name, "noreturn") == 0: return 1
|
||||
if string.strcmp(name, "pure") == 0: return 1
|
||||
if string.strcmp(name, "const") == 0: return 1
|
||||
if string.strcmp(name, "nounwind") == 0: return 1
|
||||
if string.strcmp(name, "noredzone") == 0: return 1
|
||||
if string.strcmp(name, "stackrealign") == 0: return 1
|
||||
if string.strcmp(name, "willreturn") == 0: return 1
|
||||
if string.strcmp(name, "mustprogress") == 0: return 1
|
||||
if string.strcmp(name, "weak") == 0: return 1
|
||||
if string.strcmp(name, "visibility") == 0: return 1
|
||||
if string.strcmp(name, "naked") == 0: return 1
|
||||
return 0
|
||||
def _decor_handler_known(self, trans: HT.Translator*, mod: mlirlite.LLVMModule*, name: str) -> int:
|
||||
"""1 when a bare decorator name resolves to a declared decorator handler function."""
|
||||
if name is None or trans is None: return 0
|
||||
if mod is not None and self.Gen.CallGenBase.find_func_in_module(mod, name) is not None: return 1
|
||||
if self.Gen.CallGenLeafKit.find_func_entry_global(name) is not None: return 1
|
||||
return 0
|
||||
def validate_decorators(self, trans: HT.Translator*, mod: mlirlite.LLVMModule*, pool: memhub.MemBuddy*, owner_name: str, owner_kind: str, decorator_list: list[ast.AST*]*, is_class: int) -> int:
|
||||
"""Fail fast on decorators the compiler does not recognize (never silently ignore them)."""
|
||||
if decorator_list is None: return 0
|
||||
dn: sizet = len(decorator_list)
|
||||
if dn == 0: return 0
|
||||
for di in range(int(dn)):
|
||||
deco: ast.AST* = decorator_list.get(di)
|
||||
if deco is None: continue
|
||||
k: int = deco.kind()
|
||||
if k == ast.ASTKind.Name:
|
||||
nm: ast.Name* = ast.Name*(deco)
|
||||
if nm.id is not None and self._decor_is_builtin_name(nm.id) != 0: continue
|
||||
if is_class == 0 and self._decor_handler_known(trans, mod, nm.id) != 0: continue
|
||||
elif k == ast.ASTKind.Call:
|
||||
cl: ast.Call* = ast.Call*(deco)
|
||||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
|
||||
cnm: ast.Name* = ast.Name*(cl.func)
|
||||
if cnm.id is not None and self._decor_is_builtin_name(cnm.id) != 0: continue
|
||||
if is_class == 0 and self._decor_handler_known(trans, mod, cnm.id) != 0: continue
|
||||
elif cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||||
cat: ast.Attribute* = ast.Attribute*(cl.func)
|
||||
if cat.attr is not None and self._decor_is_attr_marker(cat.attr) != 0: continue
|
||||
elif k == ast.ASTKind.Attribute:
|
||||
dat: ast.Attribute* = ast.Attribute*(deco)
|
||||
if dat.attr is not None and self._decor_is_attr_marker(dat.attr) != 0: continue
|
||||
dtxt: str = self._decor_expr_text(pool, deco, 0)
|
||||
self.Gen.TypeGenErr.fatal_error(deco, f"unrecognized decorator '@{dtxt}' on {owner_kind} '{owner_name}': ViperC recognizes built-in decorators, `@Attribute(...)` / attribute markers, property accessors, and custom decorators with a declared handler function", "TypeError")
|
||||
return 1
|
||||
return 0
|
||||
def _tattr_aligned_int(self, node: ast.AST*) -> int:
|
||||
"""Return N for t.attr.aligned(N); 0 when not aligned or the argument is missing."""
|
||||
if node is None: return 0
|
||||
|
||||
@@ -29,6 +29,10 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
|
||||
pool: memhub.MemBuddy* = trans.Pool
|
||||
class_name: str = cd.name
|
||||
|
||||
# Reject unknown decorators instead of silently dropping them (e.g. the retired
|
||||
# `@c.Attribute(...)` spelling); only recognized built-ins/markers are accepted here.
|
||||
self.Gen.ClassGenBase.validate_decorators(trans, trans.Module, pool, class_name, "class", cd.decorator_list, 1)
|
||||
|
||||
if trans.ModuleSha1 is None:
|
||||
# fail-fast: ModuleSha1 missing while translating a class (namespace isolation depends on it)
|
||||
VLogger.error("ModuleSha1 missing while translating a class", "CLASS")
|
||||
@@ -466,7 +470,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
|
||||
# Register the named struct into the module (emit the %"name" = type { ... } line)
|
||||
mlirlite.module_add_named_type(trans.Module, pool, struct_ty)
|
||||
|
||||
# B5: @c.Attribute(t.attr.packed) emits <{...}> (LLVM packed struct, no padding) as
|
||||
# B5: @Attribute(attr.packed) emits <{...}> (LLVM packed struct, no padding) as
|
||||
# needed by binary-format structs like idt/gdt/elf/fat32.
|
||||
is_packed_cd: int = self.Gen.ClassGenBase._class_has_tattr(cd.decorator_list, "packed")
|
||||
if is_packed_cd != 0: mlirlite.module_set_named_type_packed(trans.Module, pool, struct_ty, 1)
|
||||
@@ -499,7 +503,7 @@ novirtual class ClassGenDef(ClassGenBase.ClassGenBase):
|
||||
|
||||
# B5: record the packed flag into StructEntry for __sizeof__/layout queries
|
||||
if is_packed_cd != 0: entry.IsPacked = 1
|
||||
# B5: record the aligned value (@c.Attribute(t.attr.aligned(N)), e.g. fpu_context 16)
|
||||
# B5: record the aligned value (@Attribute(attr.aligned(N)), e.g. fpu_context 16)
|
||||
# for global/local alloca alignment.
|
||||
align_cd: int = self.Gen.ClassGenBase._class_aligned_value(cd.decorator_list)
|
||||
if align_cd > 0: entry.Align = align_cd
|
||||
|
||||
@@ -14,7 +14,8 @@ from . import ClassGenBase
|
||||
|
||||
# Local constant copies (originally defined in HandlesEnum/HandlesClassDef; lost when the file was split).
|
||||
# Undefined names fold to 0 in constant context, silently skipping enum member collection and EnumBase registration.
|
||||
define ENUM_MEMBER_MAX_LOCAL = 128
|
||||
# ENUM_MEMBER_MAX is NOT defined here: it lives in GenTypes and is shared with EnumEntry storage
|
||||
# (StmtGenDeclMixin) and the .vpi reader (VpiReader), so the three cannot drift apart.
|
||||
define RENUM_CLASS_MAX = 64
|
||||
define RENUM_VARIANT_PER_CLASS = 32
|
||||
define RENUM_ALIAS_MAX = 512
|
||||
@@ -220,13 +221,13 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
|
||||
cn: sizet = len(children)
|
||||
|
||||
# Temporary arrays holding member info
|
||||
member_names_buf: sizet* = pool.alloc(8 * ENUM_MEMBER_MAX_LOCAL)
|
||||
member_types_buf: sizet* = pool.alloc(8 * ENUM_MEMBER_MAX_LOCAL)
|
||||
member_values_buf: int64* = pool.alloc(8 * ENUM_MEMBER_MAX_LOCAL)
|
||||
member_names_buf: sizet* = pool.alloc(8 * GenTypes.ENUM_MEMBER_MAX)
|
||||
member_types_buf: sizet* = pool.alloc(8 * GenTypes.ENUM_MEMBER_MAX)
|
||||
member_values_buf: int64* = pool.alloc(8 * GenTypes.ENUM_MEMBER_MAX)
|
||||
if member_names_buf is None or member_types_buf is None or member_values_buf is None: return 0
|
||||
string.memset(member_names_buf, 0, 8 * ENUM_MEMBER_MAX_LOCAL)
|
||||
string.memset(member_types_buf, 0, 8 * ENUM_MEMBER_MAX_LOCAL)
|
||||
string.memset(member_values_buf, 0, 8 * ENUM_MEMBER_MAX_LOCAL)
|
||||
string.memset(member_names_buf, 0, 8 * GenTypes.ENUM_MEMBER_MAX)
|
||||
string.memset(member_types_buf, 0, 8 * GenTypes.ENUM_MEMBER_MAX)
|
||||
string.memset(member_values_buf, 0, 8 * GenTypes.ENUM_MEMBER_MAX)
|
||||
|
||||
member_count: int = 0
|
||||
next_val: int64 = 0
|
||||
@@ -273,7 +274,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
|
||||
next_val = cur_val + 1
|
||||
|
||||
# Store
|
||||
if member_count < ENUM_MEMBER_MAX_LOCAL:
|
||||
if member_count < GenTypes.ENUM_MEMBER_MAX:
|
||||
member_names_buf[member_count] = sizet(tgt.id)
|
||||
member_types_buf[member_count] = sizet(member_ty)
|
||||
member_values_buf[member_count] = cur_val
|
||||
@@ -303,7 +304,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
|
||||
if t0 is None or t0.kind() != ast.ASTKind.Name: continue
|
||||
tgt_a: ast.Name* = ast.Name*(t0)
|
||||
if tgt_a.id is None: continue
|
||||
if member_count < ENUM_MEMBER_MAX_LOCAL:
|
||||
if member_count < GenTypes.ENUM_MEMBER_MAX:
|
||||
member_names_buf[member_count] = sizet(tgt_a.id)
|
||||
member_types_buf[member_count] = sizet(member_ty_a)
|
||||
member_values_buf[member_count] = cur_val_a
|
||||
|
||||
@@ -572,6 +572,10 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
|
||||
funcs_ptr: GenTypes.FuncEntry* = trans._funcs
|
||||
func_count: int = trans._func_count
|
||||
|
||||
# Method decorators are validated here as well: unknown markers must not be dropped
|
||||
# silently just because a method body uses a different translation entry point.
|
||||
self.Gen.ClassGenBase.validate_decorators(trans, mod, pool, fd.name, "method", fd.decorator_list, 0)
|
||||
|
||||
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||||
self_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, struct_ty)
|
||||
|
||||
@@ -818,6 +822,27 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
|
||||
# module-level function path). No-op when DI is disabled.
|
||||
dbg_pushed: int = DebugInfoGen.PushFunction(pool, func_builder, func, fd.lineno, fd.col_offset + 1)
|
||||
|
||||
# Constructors may be invoked on a null object when __new__ failed (pool exhausted).
|
||||
# __before_init__ is null-safe; __init__ must be too, or the first field store is an
|
||||
# access violation (ASan: LspRange.__init__ in the LSP semantic-token path).
|
||||
if fd.name is not None and string.strcmp(fd.name, "__init__") == 0 and is_static_m == 0:
|
||||
mi_sp: mlirlite.Param* = mlirlite.function_get_param_head(func)
|
||||
if mi_sp is not None:
|
||||
mi_sp_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(mi_sp)
|
||||
mi_sp_nm: char* = mlirlite.param_get_name(mi_sp)
|
||||
if mi_sp_ty is not None and mi_sp_nm is not None:
|
||||
mi_self: mlirlite.Value* = mlirlite.SSAValue(pool, mi_sp_ty, mi_sp_nm)
|
||||
mi_null: mlirlite.Value* = mlirlite.ConstNull(pool, mi_sp_ty, "null")
|
||||
mi_null_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "init.selfnull")
|
||||
mi_body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "init.body")
|
||||
if mi_self is not None and mi_null is not None and mi_null_bb is not None and mi_body_bb is not None:
|
||||
mi_is_null: mlirlite.Value* = mlirlite.build_icmp(func_builder, mlirlite.ICMP_EQ, mi_self, mi_null)
|
||||
if mi_is_null is not None:
|
||||
mlirlite.build_cond_br(func_builder, mi_is_null, mi_null_bb, mi_body_bb)
|
||||
mlirlite.position_at_end(func_builder, mi_null_bb)
|
||||
mlirlite.build_ret_void(func_builder)
|
||||
mlirlite.position_at_end(func_builder, mi_body_bb)
|
||||
|
||||
# @t.TLS one-time init guard (same implementation as FuncGenMixin.translate_function_def, also for methods).
|
||||
if is_tls_m != 0:
|
||||
# Exact capacities: the reference used to be 64 while the definition was 128, so a long
|
||||
@@ -965,6 +990,21 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
|
||||
# Register self as an SSA value.
|
||||
self_val: mlirlite.Value* = mlirlite.SSAValue(pool, self_ptr_ty, "%self")
|
||||
|
||||
# Null-safe: __new__ may return null when the pool is exhausted (e.g. a large LSP
|
||||
# semantic-token response), and zero-filling through a null self is an access violation
|
||||
# (ASan: JsonValue.__before_init__). Return immediately; the constructor then yields null
|
||||
# and callers that already check `if v == None` treat it as OOM instead of crashing.
|
||||
null_self: mlirlite.Value* = mlirlite.ConstNull(pool, self_ptr_ty, "null")
|
||||
body_blk: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "body")
|
||||
null_blk: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "nullret")
|
||||
if null_self is not None and body_blk is not None and null_blk is not None:
|
||||
is_null: mlirlite.Value* = mlirlite.build_icmp(func_builder, mlirlite.ICMP_EQ, self_val, null_self)
|
||||
if is_null is not None:
|
||||
mlirlite.build_cond_br(func_builder, is_null, null_blk, body_blk)
|
||||
mlirlite.position_at_end(func_builder, null_blk)
|
||||
mlirlite.build_ret_void(func_builder)
|
||||
mlirlite.position_at_end(func_builder, body_blk)
|
||||
|
||||
# Zero-fill: store zeroinitializer into %self. Empty structs ({}) cannot use zeroinitializer
|
||||
# (invalid type for null constant), so field-less classes skip it; @t.VTableT classes always have a slot.
|
||||
struct_fcount_zi: int = mlirlite.get_struct_fcount(struct_ty)
|
||||
|
||||
+368
-374
@@ -1,374 +1,368 @@
|
||||
from stdint import *
|
||||
import mlirlite
|
||||
import memhub
|
||||
import string
|
||||
import stdlib
|
||||
import viperlib
|
||||
import win32.win32file as win32file
|
||||
import lib.core.VLogger as VLogger
|
||||
|
||||
# DebugInfoGen - emits function-level DWARF metadata (mlirlite text IR), mirroring TPC LLVMCG/DebugInfo.py node-for-node.
|
||||
# Active only under --traceback (SetEnabled); hooks come from the Gen layer. When disabled every entry point returns before touching DI APIs, keeping IR byte-identical.
|
||||
|
||||
# ---- module-scope state (reset per module by BeginModule) ----
|
||||
_g_enabled: int = 0
|
||||
_g_finalized: int = 0
|
||||
_g_mod: mlirlite.LLVMModule* = None
|
||||
_g_file_path: str = None
|
||||
_g_abs_file_path: str = None
|
||||
_g_file_id: int = -1
|
||||
_g_null_id: int = -1
|
||||
_g_subtype_id: int = -1
|
||||
_g_cu_id: int = -1
|
||||
_g_cur_sp: int = -1
|
||||
_g_cur_entry_ref: str = None
|
||||
_g_cur_builder: mlirlite.IRBuilder* = None
|
||||
_g_depth: int = 0
|
||||
|
||||
# Push/Pop save stack: enclosing (sp id + 1, entry DILocation ref, builder) per level; +1 lets 0 mean "no enclosing function".
|
||||
# The saved builder makes SetStatement reject a builder not pushed by the current DISubprogram (e.g. an async state machine in a bracketed function); such instrs must carry no !dbg or LLVM's verifier fails.
|
||||
define DBG_STACK_MAX = 256
|
||||
_g_stk_sp: sizet* = None
|
||||
_g_stk_ref: str = None
|
||||
_g_stk_bld: str = None
|
||||
|
||||
# DILocation cache: direct-mapped, up to 8-way linear probe, key (sp, line, col).
|
||||
define DBG_LOC_SLOTS = 8192
|
||||
define DBG_LOC_PROBE = 8
|
||||
define DBG_LOC_KEY_STRIDE = 3
|
||||
_g_loc_key: sizet* = None
|
||||
_g_loc_ref: str = None
|
||||
_g_loc_used: bytes = None
|
||||
|
||||
|
||||
# SetEnabled - mirror the --traceback switch (flag -> _g_enabled, forwarded to mlirlite's debug call-site recorder).
|
||||
def SetEnabled(flag: bool) -> int:
|
||||
"""Enable/disable DI emission (mirrors BuildPipeline._traceback_mode)."""
|
||||
global _g_enabled
|
||||
if flag: _g_enabled = 1
|
||||
else: _g_enabled = 0
|
||||
# Forward the same switch to mlirlite's debug call-site recorder (under --traceback we emit a <sha1>.vpcall side file for WriteStackMap+objdump).
|
||||
mlirlite.DebugSetEnabled(flag)
|
||||
return 0
|
||||
|
||||
|
||||
# _EnsureStorage - lazily allocate the long-lived stack / cache buffers.
|
||||
def _EnsureStorage() -> int:
|
||||
global _g_stk_sp, _g_stk_ref, _g_stk_bld, _g_loc_key, _g_loc_ref, _g_loc_used
|
||||
if _g_stk_sp is not None: return 0
|
||||
sbuf1: bytes = stdlib.malloc(DBG_STACK_MAX * 8)
|
||||
sbuf2: bytes = stdlib.malloc(DBG_STACK_MAX * 8)
|
||||
sbuf3: bytes = stdlib.malloc(DBG_STACK_MAX * 8)
|
||||
kbuf: bytes = stdlib.malloc(DBG_LOC_SLOTS * DBG_LOC_KEY_STRIDE * 8)
|
||||
rbuf: bytes = stdlib.malloc(DBG_LOC_SLOTS * 8)
|
||||
ubuf: bytes = stdlib.malloc(DBG_LOC_SLOTS)
|
||||
if sbuf1 is None or sbuf2 is None or sbuf3 is None or kbuf is None or rbuf is None or ubuf is None:
|
||||
VLogger.error("--traceback: failed to allocate DWARF metadata buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
string.memset(sbuf1, 0, DBG_STACK_MAX * 8)
|
||||
string.memset(sbuf2, 0, DBG_STACK_MAX * 8)
|
||||
string.memset(sbuf3, 0, DBG_STACK_MAX * 8)
|
||||
string.memset(kbuf, 0, DBG_LOC_SLOTS * DBG_LOC_KEY_STRIDE * 8)
|
||||
string.memset(rbuf, 0, DBG_LOC_SLOTS * 8)
|
||||
string.memset(ubuf, 0, DBG_LOC_SLOTS)
|
||||
_g_stk_sp = (sizet*)(sbuf1)
|
||||
_g_stk_ref = sbuf2
|
||||
_g_stk_bld = sbuf3
|
||||
_g_loc_key = (sizet*)(kbuf)
|
||||
_g_loc_ref = rbuf
|
||||
_g_loc_used = ubuf
|
||||
return 0
|
||||
|
||||
|
||||
def _RefOf(pool: memhub.MemBuddy*, node_id: int) -> str:
|
||||
"""Allocate a persistent "!N" reference string for a DI node id."""
|
||||
buf: str = pool.alloc(24)
|
||||
if buf is None: return None
|
||||
viperlib.snprintf(buf, 24, "!%d", node_id)
|
||||
return buf
|
||||
|
||||
|
||||
# BeginModule - start of one module translation.
|
||||
def BeginModule(mod: mlirlite.LLVMModule*, file_path: str) -> int:
|
||||
"""Reset per-module DI state. Call once at the start of each module."""
|
||||
global _g_mod, _g_file_path, _g_abs_file_path, _g_finalized, _g_file_id, _g_null_id
|
||||
global _g_subtype_id, _g_cu_id, _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth
|
||||
if _g_enabled == 0: return 0
|
||||
if _EnsureStorage() != 0: return 1
|
||||
_g_mod = mod
|
||||
_g_file_path = file_path
|
||||
_g_abs_file_path = None
|
||||
# Module-boundary fallback: clear any un-Pop'd debug function context (mid-translation failure) so the previous module's caller is not attributed here.
|
||||
mlirlite.DebugResetFunc()
|
||||
_g_finalized = 0
|
||||
_g_file_id = -1
|
||||
_g_null_id = -1
|
||||
_g_subtype_id = -1
|
||||
_g_cu_id = -1
|
||||
_g_cur_sp = -1
|
||||
_g_cur_entry_ref = None
|
||||
_g_cur_builder = None
|
||||
_g_depth = 0
|
||||
if _g_loc_used is not None: string.memset(_g_loc_used, 0, DBG_LOC_SLOTS)
|
||||
return 0
|
||||
|
||||
|
||||
# _EnsureModuleNodes - lazily create DIFile / !{ null } / DISubroutineType / DICompileUnit at the first define (once per module).
|
||||
def _EnsureModuleNodes(pool: memhub.MemBuddy*) -> int:
|
||||
global _g_file_id, _g_null_id, _g_subtype_id, _g_cu_id, _g_abs_file_path
|
||||
if _g_cu_id >= 0: return 0
|
||||
mod: mlirlite.LLVMModule* = _g_mod
|
||||
if mod is None: return 1
|
||||
# DIFile: absolute path (forward slashes), filename keeps the whole path.
|
||||
# Must be absolute: relative paths make LLVM's DWARF line records degrade to a bare "main.vp" that runtime cannot reload; absolutizing works whether LLVM keeps the full name or splits dir+basename.
|
||||
src: str = _g_file_path
|
||||
if src is None: src = ""
|
||||
slen: sizet = string.strlen(src)
|
||||
cap_n: sizet = slen + 4112
|
||||
nbuf: str = pool.alloc(cap_n)
|
||||
if nbuf is None:
|
||||
VLogger.error("--traceback: failed to allocate DIFile path buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
if slen > 0: string.strncpy(nbuf, src, slen)
|
||||
nbuf[slen] = '\0'
|
||||
if slen > 0 and nbuf[0] != '/' and not (slen >= 2 and nbuf[1] == ':'):
|
||||
# Relative path -> prefix CWD via a win32file low-level call (as in Repl.vp), not os._mbuddy which the compiler process may not have initialized.
|
||||
CwdBuf: bytes = pool.alloc(4112)
|
||||
if CwdBuf is not None:
|
||||
CwdBuf[0] = '\0'
|
||||
nc: ULONG = win32file.GetCurrentDirectoryA(ULONG(4111), CwdBuf)
|
||||
if nc > 0:
|
||||
# Defensive: when the buffer is too small GetCurrentDirectoryA writes undefined content; force a NUL.
|
||||
CwdBuf[4110] = '\0'
|
||||
viperlib.snprintf(nbuf, cap_n, "%s/%s", CwdBuf, src)
|
||||
# Normalize all backslashes (the CWD segment may contain '\' too).
|
||||
flen: sizet = string.strlen(nbuf)
|
||||
ni: sizet = 0
|
||||
for ni in range(int(flen)):
|
||||
if nbuf[ni] == '\\': nbuf[ni] = '/'
|
||||
# Save the absolute path for mlirlite's debug call-site recorder (.vpcall file paths must match DIFile: absolute + forward slash).
|
||||
_g_abs_file_path = nbuf
|
||||
# filename = whole path; directory = its prefix (both NUL-terminated, non-overlapping).
|
||||
fname: str = nbuf
|
||||
directory: str = ""
|
||||
slash: str = string.strrchr(nbuf, '/')
|
||||
if slash is not None:
|
||||
dlen: sizet = string.strlen(nbuf) - string.strlen(slash + 1) - 1
|
||||
dirbuf: str = pool.alloc(dlen + 1)
|
||||
if dirbuf is not None:
|
||||
string.strncpy(dirbuf, nbuf, dlen)
|
||||
dirbuf[dlen] = '\0'
|
||||
directory = dirbuf
|
||||
dsize: sizet = string.strlen(fname) + string.strlen(directory) + 96
|
||||
dbuf: str = pool.alloc(dsize)
|
||||
if dbuf is None:
|
||||
VLogger.error("--traceback: failed to allocate DIFile node buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
viperlib.snprintf(dbuf, dsize, "!DIFile(filename: \"%s\", directory: \"%s\")", fname, directory)
|
||||
fid: int = mlirlite.module_add_di_node(mod, pool, dbuf)
|
||||
if fid < 0:
|
||||
VLogger.error("--traceback: failed to allocate DIFile metadata node, aborting compilation", "DWARF")
|
||||
return 1
|
||||
_g_file_id = fid
|
||||
# types: !{ null } placeholder list, then DISubroutineType referencing it
|
||||
nid: int = mlirlite.module_add_di_node(mod, pool, "!{ null }")
|
||||
if nid < 0:
|
||||
VLogger.error("--traceback: failed to allocate DISubroutineType type table, aborting compilation", "DWARF")
|
||||
return 1
|
||||
_g_null_id = nid
|
||||
tbuf: str = pool.alloc(64)
|
||||
if tbuf is None:
|
||||
VLogger.error("--traceback: failed to allocate DISubroutineType buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
viperlib.snprintf(tbuf, 64, "!DISubroutineType(types: !%d)", _g_null_id)
|
||||
tid: int = mlirlite.module_add_di_node(mod, pool, tbuf)
|
||||
if tid < 0:
|
||||
VLogger.error("--traceback: failed to allocate DISubroutineType metadata node, aborting compilation", "DWARF")
|
||||
return 1
|
||||
_g_subtype_id = tid
|
||||
cbuf: str = pool.alloc(256)
|
||||
if cbuf is None:
|
||||
VLogger.error("--traceback: failed to allocate DICompileUnit buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
viperlib.snprintf(cbuf, 256, "distinct !DICompileUnit(language: DW_LANG_C99, file: !%d, producer: \"TPC\", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)", _g_file_id)
|
||||
cid: int = mlirlite.module_add_di_node(mod, pool, cbuf)
|
||||
if cid < 0:
|
||||
VLogger.error("--traceback: failed to allocate DICompileUnit metadata node, aborting compilation", "DWARF")
|
||||
return 1
|
||||
_g_cu_id = cid
|
||||
return 0
|
||||
|
||||
|
||||
# _LocRefAt - read a cached DILocation ref string pointer.
|
||||
def _LocRefAt(slot: int) -> str:
|
||||
slotp: str* = (str*)(uint64(_g_loc_ref) + sizet(slot) * 8)
|
||||
r: str = slotp[0]
|
||||
return r
|
||||
|
||||
|
||||
# _EnsureLocation - DILocation ref for (sp, line, col), created + cached once.
|
||||
def _EnsureLocation(pool: memhub.MemBuddy*, sp: int, line: int, col: int) -> str:
|
||||
if line <= 0: return _g_cur_entry_ref
|
||||
c: int = col
|
||||
if c <= 0: c = 1
|
||||
if _g_loc_used is None or _g_mod is None: return None
|
||||
h: uint64 = (uint64(sp) * 1000003 + uint64(line) * 31 + uint64(c)) & uint64(DBG_LOC_SLOTS - 1)
|
||||
pi: int = 0
|
||||
while pi < DBG_LOC_PROBE:
|
||||
slot: int = int((h + uint64(pi)) & uint64(DBG_LOC_SLOTS - 1))
|
||||
if _g_loc_used[slot] == 0: break
|
||||
kbase: int = slot * DBG_LOC_KEY_STRIDE
|
||||
if int(_g_loc_key[kbase]) == sp and int(_g_loc_key[kbase + 1]) == line and int(_g_loc_key[kbase + 2]) == c: return _LocRefAt(slot)
|
||||
pi += 1
|
||||
lbuf: str = pool.alloc(96)
|
||||
if lbuf is None: return None
|
||||
viperlib.snprintf(lbuf, 96, "!DILocation(line: %d, column: %d, scope: !%d)", line, c, sp)
|
||||
lid: int = mlirlite.module_add_di_node(_g_mod, pool, lbuf)
|
||||
if lid < 0:
|
||||
VLogger.error("--traceback: failed to allocate DILocation metadata node, aborting compilation", "DWARF")
|
||||
return None
|
||||
lref: str = _RefOf(pool, lid)
|
||||
if lref is None:
|
||||
VLogger.error("--traceback: failed to allocate DILocation reference buffer, aborting compilation", "DWARF")
|
||||
return None
|
||||
if pi < DBG_LOC_PROBE:
|
||||
slot2: int = int((h + uint64(pi)) & uint64(DBG_LOC_SLOTS - 1))
|
||||
kbase2: int = slot2 * DBG_LOC_KEY_STRIDE
|
||||
_g_loc_key[kbase2] = sizet(sp)
|
||||
_g_loc_key[kbase2 + 1] = sizet(line)
|
||||
_g_loc_key[kbase2 + 2] = sizet(c)
|
||||
rslot: str* = (str*)(uint64(_g_loc_ref) + sizet(slot2) * 8)
|
||||
rslot[0] = lref
|
||||
_g_loc_used[slot2] = 1
|
||||
return lref
|
||||
|
||||
|
||||
# PushFunction - create the DISubprogram for one define.
|
||||
def PushFunction(pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, func: mlirlite.Function*, line: int, col: int) -> int:
|
||||
"""Attach a DISubprogram to one define and set the builder's entry DILocation."""
|
||||
global _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth
|
||||
if _g_enabled == 0: return 0
|
||||
if pool is None or builder is None or func is None or _g_mod is None: return 0
|
||||
if _g_depth >= DBG_STACK_MAX:
|
||||
VLogger.error("--traceback: DWARF function nesting depth exceeds the limit, aborting compilation", "DWARF")
|
||||
return 0
|
||||
if _EnsureModuleNodes(pool) != 0: return 0
|
||||
name: str = mlirlite.function_get_name(func)
|
||||
if name is None:
|
||||
VLogger.error("--traceback: function name is empty, cannot emit DISubprogram, aborting compilation", "DWARF")
|
||||
return 0
|
||||
dline: int = line
|
||||
if dline <= 0: dline = 1
|
||||
dcol: int = col
|
||||
if dcol <= 0: dcol = 1
|
||||
ssize: sizet = string.strlen(name) + 320
|
||||
spbuf: str = pool.alloc(ssize)
|
||||
if spbuf is None:
|
||||
VLogger.error("--traceback: failed to allocate DISubprogram buffer, aborting compilation", "DWARF")
|
||||
return 0
|
||||
viperlib.snprintf(spbuf, ssize, "distinct !DISubprogram(name: \"%s\", scope: !%d, file: !%d, type: !%d, line: %d, scopeLine: %d, unit: !%d, spFlags: DISPFlagDefinition)", name, _g_file_id, _g_file_id, _g_subtype_id, dline, dline, _g_cu_id)
|
||||
sp_id: int = mlirlite.module_add_di_node(_g_mod, pool, spbuf)
|
||||
if sp_id < 0:
|
||||
VLogger.error("--traceback: failed to allocate DISubprogram metadata node, aborting compilation", "DWARF")
|
||||
return 0
|
||||
sp_ref: str = _RefOf(pool, sp_id)
|
||||
if sp_ref is None:
|
||||
VLogger.error("--traceback: failed to allocate DISubprogram reference buffer, aborting compilation", "DWARF")
|
||||
return 0
|
||||
mlirlite.function_set_dbg(func, sp_ref)
|
||||
# save the enclosing function's DI context (LIFO), then switch to this one
|
||||
_g_stk_sp[_g_depth] = sizet(_g_cur_sp + 1)
|
||||
rslot: str* = (str*)(uint64(_g_stk_ref) + sizet(_g_depth) * 8)
|
||||
rslot[0] = _g_cur_entry_ref
|
||||
bslot: str* = (str*)(uint64(_g_stk_bld) + sizet(_g_depth) * 8)
|
||||
bslot[0] = _g_cur_builder
|
||||
_g_depth += 1
|
||||
_g_cur_sp = sp_id
|
||||
_g_cur_builder = builder
|
||||
entry_ref: str = _EnsureLocation(pool, sp_id, dline, dcol)
|
||||
if entry_ref is None:
|
||||
VLogger.error("--traceback: failed to generate entry DILocation, aborting compilation", "DWARF")
|
||||
return 0
|
||||
_g_cur_entry_ref = entry_ref
|
||||
mlirlite.builder_set_debug_location(builder, entry_ref)
|
||||
# Enter this define: tell mlirlite's call-site recorder the current symbol/file/def-line and write an F record (for the .vpcall file + WriteStackMap entry fallback line).
|
||||
mlirlite.DebugNoteFunc(name, _g_abs_file_path, dline)
|
||||
return 1
|
||||
|
||||
|
||||
# PopFunction - restore the enclosing function's DI context.
|
||||
def PopFunction(builder: mlirlite.IRBuilder*) -> int:
|
||||
global _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth
|
||||
if _g_enabled == 0: return 0
|
||||
if _g_depth <= 0: return 0
|
||||
_g_depth -= 1
|
||||
_g_cur_sp = int(_g_stk_sp[_g_depth]) - 1
|
||||
rslot: str* = (str*)(uint64(_g_stk_ref) + sizet(_g_depth) * 8)
|
||||
_g_cur_entry_ref = rslot[0]
|
||||
bslot: str* = (str*)(uint64(_g_stk_bld) + sizet(_g_depth) * 8)
|
||||
_g_cur_builder = (mlirlite.IRBuilder*)(bslot[0])
|
||||
if builder is not None: mlirlite.builder_clear_debug_location(builder)
|
||||
# Leave this define: restore mlirlite's outer function context (for nested defines).
|
||||
mlirlite.DebugClearFunc()
|
||||
return 0
|
||||
|
||||
|
||||
# SetStatement - point the current builder at a statement DILocation.
|
||||
def SetStatement(pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, line: int, col: int) -> int:
|
||||
"""Update the builder's DILocation to the current statement (line precision)."""
|
||||
if _g_enabled == 0 or pool is None or builder is None: return 0
|
||||
# Record the current statement line: C records from build_call in this statement use it as the call-site line.
|
||||
mlirlite.DebugNoteLine(line)
|
||||
if _g_cur_sp < 0: return 0
|
||||
ref: str = _EnsureLocation(pool, _g_cur_sp, line, col)
|
||||
if ref is None: return 0
|
||||
mlirlite.builder_set_debug_location(builder, ref)
|
||||
return 1
|
||||
|
||||
|
||||
# _ModuleHasDefine - does the module emit any define (IsDeclared == 0)?
|
||||
def _ModuleHasDefine(mod: mlirlite.LLVMModule*) -> int:
|
||||
if mod is None: return 0
|
||||
cur: mlirlite.Function* = mod.FuncHead
|
||||
steps: int = 0
|
||||
while cur is not None:
|
||||
steps += 1
|
||||
if steps > 100000: break
|
||||
if mlirlite.function_is_declared(cur) == 0: return 1
|
||||
cur = mlirlite.function_get_next(cur)
|
||||
return 0
|
||||
|
||||
|
||||
# Finalize - register !llvm.dbg.cu / !llvm.module.flags before dump_ir.
|
||||
def Finalize(pool: memhub.MemBuddy*) -> int:
|
||||
"""Module-level DI tail; idempotent per module, no-op when no SP was emitted."""
|
||||
global _g_finalized
|
||||
if _g_enabled == 0: return 0
|
||||
if _g_finalized != 0: return 0
|
||||
mod: mlirlite.LLVMModule* = _g_mod
|
||||
if mod is None or pool is None: return 0
|
||||
_g_finalized = 1
|
||||
# Self-check (mirrors TPC Phase2Translator.py:3444): a module that emits a
|
||||
# define but carries no DI at all loses line info for its frames.
|
||||
if mod.DIMetaCount == 0 and _ModuleHasDefine(mod) != 0: VLogger.warning("--traceback: this module emits a define but its IR carries no DWARF (!llvm.dbg.cu); its frames will show only addresses in the Ctrl+C stack", "DWARF")
|
||||
if _g_cu_id < 0: return 0
|
||||
cu_ref: str = _RefOf(pool, _g_cu_id)
|
||||
if cu_ref is None:
|
||||
VLogger.error("--traceback: failed to allocate DICompileUnit reference buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
mlirlite.module_add_named_metadata(mod, pool, "llvm.dbg.cu", cu_ref)
|
||||
fbuf: str = pool.alloc(64)
|
||||
if fbuf is None:
|
||||
VLogger.error("--traceback: failed to allocate module-level debug flags buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
viperlib.snprintf(fbuf, 64, "!{i32 2, !\"Debug Info Version\", i32 3}")
|
||||
flags_id: int = mlirlite.module_add_di_node(mod, pool, fbuf)
|
||||
if flags_id < 0:
|
||||
VLogger.error("--traceback: failed to allocate llvm.module.flags metadata node, aborting compilation", "DWARF")
|
||||
return 1
|
||||
fl_ref: str = _RefOf(pool, flags_id)
|
||||
if fl_ref is None:
|
||||
VLogger.error("--traceback: failed to allocate llvm.module.flags reference buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
mlirlite.module_add_named_metadata(mod, pool, "llvm.module.flags", fl_ref)
|
||||
return 0
|
||||
from stdint import *
|
||||
import mlirlite
|
||||
import memhub
|
||||
import string
|
||||
import stdlib
|
||||
import viperlib
|
||||
import os
|
||||
import lib.core.VLogger as VLogger
|
||||
|
||||
# DebugInfoGen - emits function-level DWARF metadata (mlirlite text IR), mirroring TPC LLVMCG/DebugInfo.py node-for-node.
|
||||
# Active only under --traceback (SetEnabled); hooks come from the Gen layer. When disabled every entry point returns before touching DI APIs, keeping IR byte-identical.
|
||||
|
||||
# ---- module-scope state (reset per module by BeginModule) ----
|
||||
_g_enabled: int = 0
|
||||
_g_finalized: int = 0
|
||||
_g_mod: mlirlite.LLVMModule* = None
|
||||
_g_file_path: str = None
|
||||
_g_abs_file_path: str = None
|
||||
_g_file_id: int = -1
|
||||
_g_null_id: int = -1
|
||||
_g_subtype_id: int = -1
|
||||
_g_cu_id: int = -1
|
||||
_g_cur_sp: int = -1
|
||||
_g_cur_entry_ref: str = None
|
||||
_g_cur_builder: mlirlite.IRBuilder* = None
|
||||
_g_depth: int = 0
|
||||
|
||||
# Push/Pop save stack: enclosing (sp id + 1, entry DILocation ref, builder) per level; +1 lets 0 mean "no enclosing function".
|
||||
# The saved builder makes SetStatement reject a builder not pushed by the current DISubprogram (e.g. an async state machine in a bracketed function); such instrs must carry no !dbg or LLVM's verifier fails.
|
||||
define DBG_STACK_MAX = 256
|
||||
_g_stk_sp: sizet* = None
|
||||
_g_stk_ref: str = None
|
||||
_g_stk_bld: str = None
|
||||
|
||||
# DILocation cache: direct-mapped, up to 8-way linear probe, key (sp, line, col).
|
||||
define DBG_LOC_SLOTS = 8192
|
||||
define DBG_LOC_PROBE = 8
|
||||
define DBG_LOC_KEY_STRIDE = 3
|
||||
_g_loc_key: sizet* = None
|
||||
_g_loc_ref: str = None
|
||||
_g_loc_used: bytes = None
|
||||
|
||||
|
||||
# SetEnabled - mirror the --traceback switch (flag -> _g_enabled, forwarded to mlirlite's debug call-site recorder).
|
||||
def SetEnabled(flag: bool) -> int:
|
||||
"""Enable/disable DI emission (mirrors BuildPipeline._traceback_mode)."""
|
||||
global _g_enabled
|
||||
if flag: _g_enabled = 1
|
||||
else: _g_enabled = 0
|
||||
# Forward the same switch to mlirlite's debug call-site recorder (under --traceback we emit a <sha1>.vpcall side file for WriteStackMap+objdump).
|
||||
mlirlite.DebugSetEnabled(flag)
|
||||
return 0
|
||||
|
||||
|
||||
# _EnsureStorage - lazily allocate the long-lived stack / cache buffers.
|
||||
def _EnsureStorage() -> int:
|
||||
global _g_stk_sp, _g_stk_ref, _g_stk_bld, _g_loc_key, _g_loc_ref, _g_loc_used
|
||||
if _g_stk_sp is not None: return 0
|
||||
sbuf1: bytes = stdlib.malloc(DBG_STACK_MAX * 8)
|
||||
sbuf2: bytes = stdlib.malloc(DBG_STACK_MAX * 8)
|
||||
sbuf3: bytes = stdlib.malloc(DBG_STACK_MAX * 8)
|
||||
kbuf: bytes = stdlib.malloc(DBG_LOC_SLOTS * DBG_LOC_KEY_STRIDE * 8)
|
||||
rbuf: bytes = stdlib.malloc(DBG_LOC_SLOTS * 8)
|
||||
ubuf: bytes = stdlib.malloc(DBG_LOC_SLOTS)
|
||||
if sbuf1 is None or sbuf2 is None or sbuf3 is None or kbuf is None or rbuf is None or ubuf is None:
|
||||
VLogger.error("--traceback: failed to allocate DWARF metadata buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
string.memset(sbuf1, 0, DBG_STACK_MAX * 8)
|
||||
string.memset(sbuf2, 0, DBG_STACK_MAX * 8)
|
||||
string.memset(sbuf3, 0, DBG_STACK_MAX * 8)
|
||||
string.memset(kbuf, 0, DBG_LOC_SLOTS * DBG_LOC_KEY_STRIDE * 8)
|
||||
string.memset(rbuf, 0, DBG_LOC_SLOTS * 8)
|
||||
string.memset(ubuf, 0, DBG_LOC_SLOTS)
|
||||
_g_stk_sp = (sizet*)(sbuf1)
|
||||
_g_stk_ref = sbuf2
|
||||
_g_stk_bld = sbuf3
|
||||
_g_loc_key = (sizet*)(kbuf)
|
||||
_g_loc_ref = rbuf
|
||||
_g_loc_used = ubuf
|
||||
return 0
|
||||
|
||||
|
||||
def _RefOf(pool: memhub.MemBuddy*, node_id: int) -> str:
|
||||
"""Allocate a persistent "!N" reference string for a DI node id."""
|
||||
buf: str = pool.alloc(24)
|
||||
if buf is None: return None
|
||||
viperlib.snprintf(buf, 24, "!%d", node_id)
|
||||
return buf
|
||||
|
||||
|
||||
# BeginModule - start of one module translation.
|
||||
def BeginModule(mod: mlirlite.LLVMModule*, file_path: str) -> int:
|
||||
"""Reset per-module DI state. Call once at the start of each module."""
|
||||
global _g_mod, _g_file_path, _g_abs_file_path, _g_finalized, _g_file_id, _g_null_id
|
||||
global _g_subtype_id, _g_cu_id, _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth
|
||||
if _g_enabled == 0: return 0
|
||||
if _EnsureStorage() != 0: return 1
|
||||
_g_mod = mod
|
||||
_g_file_path = file_path
|
||||
_g_abs_file_path = None
|
||||
# Module-boundary fallback: clear any un-Pop'd debug function context (mid-translation failure) so the previous module's caller is not attributed here.
|
||||
mlirlite.DebugResetFunc()
|
||||
_g_finalized = 0
|
||||
_g_file_id = -1
|
||||
_g_null_id = -1
|
||||
_g_subtype_id = -1
|
||||
_g_cu_id = -1
|
||||
_g_cur_sp = -1
|
||||
_g_cur_entry_ref = None
|
||||
_g_cur_builder = None
|
||||
_g_depth = 0
|
||||
if _g_loc_used is not None: string.memset(_g_loc_used, 0, DBG_LOC_SLOTS)
|
||||
return 0
|
||||
|
||||
|
||||
# _EnsureModuleNodes - lazily create DIFile / !{ null } / DISubroutineType / DICompileUnit at the first define (once per module).
|
||||
def _EnsureModuleNodes(pool: memhub.MemBuddy*) -> int:
|
||||
global _g_file_id, _g_null_id, _g_subtype_id, _g_cu_id, _g_abs_file_path
|
||||
if _g_cu_id >= 0: return 0
|
||||
mod: mlirlite.LLVMModule* = _g_mod
|
||||
if mod is None: return 1
|
||||
# DIFile: absolute path (forward slashes), filename keeps the whole path.
|
||||
# Must be absolute: relative paths make LLVM's DWARF line records degrade to a bare "main.vp" that runtime cannot reload; absolutizing works whether LLVM keeps the full name or splits dir+basename.
|
||||
src: str = _g_file_path
|
||||
if src is None: src = ""
|
||||
slen: sizet = string.strlen(src)
|
||||
cap_n: sizet = slen + 4112
|
||||
nbuf: str = pool.alloc(cap_n)
|
||||
if nbuf is None:
|
||||
VLogger.error("--traceback: failed to allocate DIFile path buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
if slen > 0: string.strncpy(nbuf, src, slen)
|
||||
nbuf[slen] = '\0'
|
||||
if slen > 0 and nbuf[0] != '/' and not (slen >= 2 and nbuf[1] == ':'):
|
||||
cwd: str = os.getcwd()
|
||||
if cwd is not None:
|
||||
viperlib.snprintf(nbuf, cap_n, "%s/%s", cwd, src)
|
||||
# Normalize all backslashes (the CWD segment may contain '\' too).
|
||||
flen: sizet = string.strlen(nbuf)
|
||||
ni: sizet = 0
|
||||
for ni in range(int(flen)):
|
||||
if nbuf[ni] == '\\': nbuf[ni] = '/'
|
||||
# Save the absolute path for mlirlite's debug call-site recorder (.vpcall file paths must match DIFile: absolute + forward slash).
|
||||
_g_abs_file_path = nbuf
|
||||
# filename = whole path; directory = its prefix (both NUL-terminated, non-overlapping).
|
||||
fname: str = nbuf
|
||||
directory: str = ""
|
||||
slash: str = string.strrchr(nbuf, '/')
|
||||
if slash is not None:
|
||||
dlen: sizet = string.strlen(nbuf) - string.strlen(slash + 1) - 1
|
||||
dirbuf: str = pool.alloc(dlen + 1)
|
||||
if dirbuf is not None:
|
||||
string.strncpy(dirbuf, nbuf, dlen)
|
||||
dirbuf[dlen] = '\0'
|
||||
directory = dirbuf
|
||||
dsize: sizet = string.strlen(fname) + string.strlen(directory) + 96
|
||||
dbuf: str = pool.alloc(dsize)
|
||||
if dbuf is None:
|
||||
VLogger.error("--traceback: failed to allocate DIFile node buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
viperlib.snprintf(dbuf, dsize, "!DIFile(filename: \"%s\", directory: \"%s\")", fname, directory)
|
||||
fid: int = mlirlite.module_add_di_node(mod, pool, dbuf)
|
||||
if fid < 0:
|
||||
VLogger.error("--traceback: failed to allocate DIFile metadata node, aborting compilation", "DWARF")
|
||||
return 1
|
||||
_g_file_id = fid
|
||||
# types: !{ null } placeholder list, then DISubroutineType referencing it
|
||||
nid: int = mlirlite.module_add_di_node(mod, pool, "!{ null }")
|
||||
if nid < 0:
|
||||
VLogger.error("--traceback: failed to allocate DISubroutineType type table, aborting compilation", "DWARF")
|
||||
return 1
|
||||
_g_null_id = nid
|
||||
tbuf: str = pool.alloc(64)
|
||||
if tbuf is None:
|
||||
VLogger.error("--traceback: failed to allocate DISubroutineType buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
viperlib.snprintf(tbuf, 64, "!DISubroutineType(types: !%d)", _g_null_id)
|
||||
tid: int = mlirlite.module_add_di_node(mod, pool, tbuf)
|
||||
if tid < 0:
|
||||
VLogger.error("--traceback: failed to allocate DISubroutineType metadata node, aborting compilation", "DWARF")
|
||||
return 1
|
||||
_g_subtype_id = tid
|
||||
cbuf: str = pool.alloc(256)
|
||||
if cbuf is None:
|
||||
VLogger.error("--traceback: failed to allocate DICompileUnit buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
viperlib.snprintf(cbuf, 256, "distinct !DICompileUnit(language: DW_LANG_C99, file: !%d, producer: \"TPC\", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)", _g_file_id)
|
||||
cid: int = mlirlite.module_add_di_node(mod, pool, cbuf)
|
||||
if cid < 0:
|
||||
VLogger.error("--traceback: failed to allocate DICompileUnit metadata node, aborting compilation", "DWARF")
|
||||
return 1
|
||||
_g_cu_id = cid
|
||||
return 0
|
||||
|
||||
|
||||
# _LocRefAt - read a cached DILocation ref string pointer.
|
||||
def _LocRefAt(slot: int) -> str:
|
||||
slotp: str* = (str*)(uint64(_g_loc_ref) + sizet(slot) * 8)
|
||||
r: str = slotp[0]
|
||||
return r
|
||||
|
||||
|
||||
# _EnsureLocation - DILocation ref for (sp, line, col), created + cached once.
|
||||
def _EnsureLocation(pool: memhub.MemBuddy*, sp: int, line: int, col: int) -> str:
|
||||
if line <= 0: return _g_cur_entry_ref
|
||||
c: int = col
|
||||
if c <= 0: c = 1
|
||||
if _g_loc_used is None or _g_mod is None: return None
|
||||
h: uint64 = (uint64(sp) * 1000003 + uint64(line) * 31 + uint64(c)) & uint64(DBG_LOC_SLOTS - 1)
|
||||
pi: int = 0
|
||||
while pi < DBG_LOC_PROBE:
|
||||
slot: int = int((h + uint64(pi)) & uint64(DBG_LOC_SLOTS - 1))
|
||||
if _g_loc_used[slot] == 0: break
|
||||
kbase: int = slot * DBG_LOC_KEY_STRIDE
|
||||
if int(_g_loc_key[kbase]) == sp and int(_g_loc_key[kbase + 1]) == line and int(_g_loc_key[kbase + 2]) == c: return _LocRefAt(slot)
|
||||
pi += 1
|
||||
lbuf: str = pool.alloc(96)
|
||||
if lbuf is None: return None
|
||||
viperlib.snprintf(lbuf, 96, "!DILocation(line: %d, column: %d, scope: !%d)", line, c, sp)
|
||||
lid: int = mlirlite.module_add_di_node(_g_mod, pool, lbuf)
|
||||
if lid < 0:
|
||||
VLogger.error("--traceback: failed to allocate DILocation metadata node, aborting compilation", "DWARF")
|
||||
return None
|
||||
lref: str = _RefOf(pool, lid)
|
||||
if lref is None:
|
||||
VLogger.error("--traceback: failed to allocate DILocation reference buffer, aborting compilation", "DWARF")
|
||||
return None
|
||||
if pi < DBG_LOC_PROBE:
|
||||
slot2: int = int((h + uint64(pi)) & uint64(DBG_LOC_SLOTS - 1))
|
||||
kbase2: int = slot2 * DBG_LOC_KEY_STRIDE
|
||||
_g_loc_key[kbase2] = sizet(sp)
|
||||
_g_loc_key[kbase2 + 1] = sizet(line)
|
||||
_g_loc_key[kbase2 + 2] = sizet(c)
|
||||
rslot: str* = (str*)(uint64(_g_loc_ref) + sizet(slot2) * 8)
|
||||
rslot[0] = lref
|
||||
_g_loc_used[slot2] = 1
|
||||
return lref
|
||||
|
||||
|
||||
# PushFunction - create the DISubprogram for one define.
|
||||
def PushFunction(pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, func: mlirlite.Function*, line: int, col: int) -> int:
|
||||
"""Attach a DISubprogram to one define and set the builder's entry DILocation."""
|
||||
global _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth
|
||||
if _g_enabled == 0: return 0
|
||||
if pool is None or builder is None or func is None or _g_mod is None: return 0
|
||||
if _g_depth >= DBG_STACK_MAX:
|
||||
VLogger.error("--traceback: DWARF function nesting depth exceeds the limit, aborting compilation", "DWARF")
|
||||
return 0
|
||||
if _EnsureModuleNodes(pool) != 0: return 0
|
||||
name: str = mlirlite.function_get_name(func)
|
||||
if name is None:
|
||||
VLogger.error("--traceback: function name is empty, cannot emit DISubprogram, aborting compilation", "DWARF")
|
||||
return 0
|
||||
dline: int = line
|
||||
if dline <= 0: dline = 1
|
||||
dcol: int = col
|
||||
if dcol <= 0: dcol = 1
|
||||
ssize: sizet = string.strlen(name) + 320
|
||||
spbuf: str = pool.alloc(ssize)
|
||||
if spbuf is None:
|
||||
VLogger.error("--traceback: failed to allocate DISubprogram buffer, aborting compilation", "DWARF")
|
||||
return 0
|
||||
viperlib.snprintf(spbuf, ssize, "distinct !DISubprogram(name: \"%s\", scope: !%d, file: !%d, type: !%d, line: %d, scopeLine: %d, unit: !%d, spFlags: DISPFlagDefinition)", name, _g_file_id, _g_file_id, _g_subtype_id, dline, dline, _g_cu_id)
|
||||
sp_id: int = mlirlite.module_add_di_node(_g_mod, pool, spbuf)
|
||||
if sp_id < 0:
|
||||
VLogger.error("--traceback: failed to allocate DISubprogram metadata node, aborting compilation", "DWARF")
|
||||
return 0
|
||||
sp_ref: str = _RefOf(pool, sp_id)
|
||||
if sp_ref is None:
|
||||
VLogger.error("--traceback: failed to allocate DISubprogram reference buffer, aborting compilation", "DWARF")
|
||||
return 0
|
||||
mlirlite.function_set_dbg(func, sp_ref)
|
||||
# save the enclosing function's DI context (LIFO), then switch to this one
|
||||
_g_stk_sp[_g_depth] = sizet(_g_cur_sp + 1)
|
||||
rslot: str* = (str*)(uint64(_g_stk_ref) + sizet(_g_depth) * 8)
|
||||
rslot[0] = _g_cur_entry_ref
|
||||
bslot: str* = (str*)(uint64(_g_stk_bld) + sizet(_g_depth) * 8)
|
||||
bslot[0] = _g_cur_builder
|
||||
_g_depth += 1
|
||||
_g_cur_sp = sp_id
|
||||
_g_cur_builder = builder
|
||||
entry_ref: str = _EnsureLocation(pool, sp_id, dline, dcol)
|
||||
if entry_ref is None:
|
||||
VLogger.error("--traceback: failed to generate entry DILocation, aborting compilation", "DWARF")
|
||||
return 0
|
||||
_g_cur_entry_ref = entry_ref
|
||||
mlirlite.builder_set_debug_location(builder, entry_ref)
|
||||
# Enter this define: tell mlirlite's call-site recorder the current symbol/file/def-line and write an F record (for the .vpcall file + WriteStackMap entry fallback line).
|
||||
mlirlite.DebugNoteFunc(name, _g_abs_file_path, dline)
|
||||
return 1
|
||||
|
||||
|
||||
# PopFunction - restore the enclosing function's DI context.
|
||||
def PopFunction(builder: mlirlite.IRBuilder*) -> int:
|
||||
global _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth
|
||||
if _g_enabled == 0: return 0
|
||||
if _g_depth <= 0: return 0
|
||||
_g_depth -= 1
|
||||
_g_cur_sp = int(_g_stk_sp[_g_depth]) - 1
|
||||
rslot: str* = (str*)(uint64(_g_stk_ref) + sizet(_g_depth) * 8)
|
||||
_g_cur_entry_ref = rslot[0]
|
||||
bslot: str* = (str*)(uint64(_g_stk_bld) + sizet(_g_depth) * 8)
|
||||
_g_cur_builder = (mlirlite.IRBuilder*)(bslot[0])
|
||||
if builder is not None: mlirlite.builder_clear_debug_location(builder)
|
||||
# Leave this define: restore mlirlite's outer function context (for nested defines).
|
||||
mlirlite.DebugClearFunc()
|
||||
return 0
|
||||
|
||||
|
||||
# SetStatement - point the current builder at a statement DILocation.
|
||||
def SetStatement(pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, line: int, col: int) -> int:
|
||||
"""Update the builder's DILocation to the current statement (line precision)."""
|
||||
if _g_enabled == 0 or pool is None or builder is None: return 0
|
||||
# Record the current statement line: C records from build_call in this statement use it as the call-site line.
|
||||
mlirlite.DebugNoteLine(line)
|
||||
if _g_cur_sp < 0: return 0
|
||||
ref: str = _EnsureLocation(pool, _g_cur_sp, line, col)
|
||||
if ref is None: return 0
|
||||
mlirlite.builder_set_debug_location(builder, ref)
|
||||
return 1
|
||||
|
||||
|
||||
# _ModuleHasDefine - does the module emit any define (IsDeclared == 0)?
|
||||
def _ModuleHasDefine(mod: mlirlite.LLVMModule*) -> int:
|
||||
if mod is None: return 0
|
||||
cur: mlirlite.Function* = mod.FuncHead
|
||||
steps: int = 0
|
||||
while cur is not None:
|
||||
steps += 1
|
||||
if steps > 100000: break
|
||||
if mlirlite.function_is_declared(cur) == 0: return 1
|
||||
cur = mlirlite.function_get_next(cur)
|
||||
return 0
|
||||
|
||||
|
||||
# Finalize - register !llvm.dbg.cu / !llvm.module.flags before dump_ir.
|
||||
def Finalize(pool: memhub.MemBuddy*) -> int:
|
||||
"""Module-level DI tail; idempotent per module, no-op when no SP was emitted."""
|
||||
global _g_finalized
|
||||
if _g_enabled == 0: return 0
|
||||
if _g_finalized != 0: return 0
|
||||
mod: mlirlite.LLVMModule* = _g_mod
|
||||
if mod is None or pool is None: return 0
|
||||
_g_finalized = 1
|
||||
# Self-check (mirrors TPC Phase2Translator.py:3444): a module that emits a
|
||||
# define but carries no DI at all loses line info for its frames.
|
||||
if mod.DIMetaCount == 0 and _ModuleHasDefine(mod) != 0: VLogger.warning("--traceback: this module emits a define but its IR carries no DWARF (!llvm.dbg.cu); its frames will show only addresses in the Ctrl+C stack", "DWARF")
|
||||
if _g_cu_id < 0: return 0
|
||||
cu_ref: str = _RefOf(pool, _g_cu_id)
|
||||
if cu_ref is None:
|
||||
VLogger.error("--traceback: failed to allocate DICompileUnit reference buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
mlirlite.module_add_named_metadata(mod, pool, "llvm.dbg.cu", cu_ref)
|
||||
fbuf: str = pool.alloc(64)
|
||||
if fbuf is None:
|
||||
VLogger.error("--traceback: failed to allocate module-level debug flags buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
viperlib.snprintf(fbuf, 64, "!{i32 2, !\"Debug Info Version\", i32 3}")
|
||||
flags_id: int = mlirlite.module_add_di_node(mod, pool, fbuf)
|
||||
if flags_id < 0:
|
||||
VLogger.error("--traceback: failed to allocate llvm.module.flags metadata node, aborting compilation", "DWARF")
|
||||
return 1
|
||||
fl_ref: str = _RefOf(pool, flags_id)
|
||||
if fl_ref is None:
|
||||
VLogger.error("--traceback: failed to allocate llvm.module.flags reference buffer, aborting compilation", "DWARF")
|
||||
return 1
|
||||
mlirlite.module_add_named_metadata(mod, pool, "llvm.module.flags", fl_ref)
|
||||
return 0
|
||||
@@ -439,7 +439,13 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
|
||||
submod_buf: str = pool.alloc(submod_len)
|
||||
if submod_buf is not None:
|
||||
viperlib.snprintf(submod_buf, submod_len, "%s.%s", nm_ma.id, at.attr)
|
||||
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, submod_buf) == 1: is_submod = 1
|
||||
# Imported package submodule (e.g. `import os` then `os.path.x`): the
|
||||
# submodule must be recognized even when the current file only imported
|
||||
# the package. `is_module_imported` alone misses it, so `path` was
|
||||
# treated as a data global of `os` (`@<os_sha>.path`), emitted a dead
|
||||
# addressof/load, and the linker reported the symbol as undefined
|
||||
# (`os/__init__` only aliases the submodule, it never defines it).
|
||||
if self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, submod_buf) == 1: is_submod = 1
|
||||
# also check from_imports: the at.attr alias maps to the real module win32.win32file
|
||||
if is_submod == 0 and trans._from_imports is not None:
|
||||
fi_mod_raw: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, at.attr, 0)
|
||||
@@ -609,6 +615,12 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
|
||||
|
||||
if obj_ptr is None or obj_ptr.Ty is None: return None
|
||||
|
||||
# Aggregate rvalue base (e.g. `make().field` / `a.make().field`): the translated base is a
|
||||
# struct/array SSA value, not an address, so GEP cannot be applied directly. Materialize it
|
||||
# into a frame temporary and continue on the address path below.
|
||||
obj_ptr = self._attr_base_as_ptr(builder, obj_ptr)
|
||||
if obj_ptr is None or obj_ptr.Ty is None: return None
|
||||
|
||||
# if obj_ptr is Ptr(Ptr(Struct)) (the alloca of an X* variable), load to deref and get Ptr(Struct)
|
||||
obj_ptr = self._deref_if_ptr_ptr(builder, obj_ptr)
|
||||
|
||||
@@ -794,6 +806,62 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
|
||||
# explicit return after fatal_error: the final match arm must be terminated, else falling off
|
||||
# the end is misjudged by the function tail as fail-fast
|
||||
return None
|
||||
# _attr_base_as_ptr - make an attribute base addressable: aggregate rvalues (call results,
|
||||
# literals, temporaries) are spilled to a frame alloca; pointers pass through unchanged.
|
||||
def _attr_base_as_ptr(self, builder: mlirlite.IRBuilder*, obj_ptr: mlirlite.Value*) -> mlirlite.Value*:
|
||||
"""Spill a non-pointer aggregate base into a frame temp; return its address."""
|
||||
if obj_ptr is None or obj_ptr.Ty is None: return obj_ptr
|
||||
if self.is_ptr_type(obj_ptr.Ty) != 0: return obj_ptr
|
||||
bk: int = obj_ptr.Ty.kind()
|
||||
if bk != mlirlite.LLVMType.Struct and bk != mlirlite.LLVMType.Array: return obj_ptr
|
||||
tmp: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, obj_ptr.Ty)
|
||||
if tmp is None: return None
|
||||
mlirlite.build_store(builder, obj_ptr, tmp)
|
||||
return tmp
|
||||
|
||||
# _call_ret_class_name - class name of a call's return type (`f().field` chains).
|
||||
# A normal function call is not a pointer cast: using the callee name (`make_o`) as the class
|
||||
# makes lookup_field_by_class search for a class named after the function instead of the
|
||||
# struct the call actually returns.
|
||||
def _call_ret_class_name(self, pool: memhub.MemBuddy*, trans: HT.Translator*, call_node: ast.AST*) -> str:
|
||||
"""Class name of a call's return struct/class, or None."""
|
||||
if pool is None or trans is None or call_node is None: return None
|
||||
if call_node.kind() != ast.ASTKind.Call: return None
|
||||
cl_cr: ast.Call* = ast.Call*(call_node)
|
||||
if cl_cr is None or cl_cr.func is None: return None
|
||||
fname_cr: str = None
|
||||
mod_sha1_cr: str = None
|
||||
if cl_cr.func.kind() == ast.ASTKind.Name:
|
||||
nm_cr: ast.Name* = ast.Name*(cl_cr.func)
|
||||
fname_cr = nm_cr.id
|
||||
mod_sha1_cr = trans.ModuleSha1
|
||||
elif cl_cr.func.kind() == ast.ASTKind.Attribute:
|
||||
at_cr: ast.Attribute* = ast.Attribute*(cl_cr.func)
|
||||
fname_cr = at_cr.attr
|
||||
if at_cr.value is not None:
|
||||
if at_cr.value.kind() == ast.ASTKind.Name:
|
||||
mn_cr: ast.Name* = ast.Name*(at_cr.value)
|
||||
if mn_cr.id is not None and self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, mn_cr.id) != 0:
|
||||
mod_sha1_cr = self.Gen.CallGen._lookup_module_sha1(mn_cr.id)
|
||||
elif at_cr.value.kind() == ast.ASTKind.Attribute:
|
||||
mp_cr: str = self._attr_module_path_str(pool, at_cr.value)
|
||||
if mp_cr is not None and self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, mp_cr) != 0:
|
||||
mod_sha1_cr = self.Gen.CallGen._lookup_module_sha1(mp_cr)
|
||||
if fname_cr is None or mod_sha1_cr is None: return None
|
||||
f_cr: mlirlite.Function* = None
|
||||
sym_len_cr: sizet = string.strlen(mod_sha1_cr) + string.strlen(fname_cr) + 2
|
||||
sym_buf_cr: str = pool.alloc(sym_len_cr)
|
||||
if sym_buf_cr is not None:
|
||||
viperlib.snprintf(sym_buf_cr, sym_len_cr, "%s.%s", mod_sha1_cr, fname_cr)
|
||||
f_cr = self.Gen.CallGen.find_func_in_module(trans.Module, sym_buf_cr)
|
||||
if f_cr is None: f_cr = self.Gen.CallGen.find_func_in_module(trans.Module, fname_cr)
|
||||
if f_cr is None: return None
|
||||
ret_cr: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(f_cr)
|
||||
if ret_cr is None: return None
|
||||
# class-instance return (`-> T` is T* at the ABI): take the pointee struct name
|
||||
if self.is_ptr_type(ret_cr) != 0 and ret_cr.Pointee is not None: ret_cr = ret_cr.Pointee
|
||||
return self.Gen.StructGen.get_class_name_by_type(pool, ret_cr)
|
||||
|
||||
# get_attribute_ptr - get the field pointer for an attribute access (no value load, for assignment);
|
||||
# for a Name obj, use the alloca pointer directly (no struct load).
|
||||
def _resolve_attr_field_class(self, pool: memhub.MemBuddy*, trans: HT.Translator*, attr_node: ast.AST*, allow_module_global: int = 0) -> str:
|
||||
@@ -818,9 +886,12 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
|
||||
parent_class = self.Gen.VarGen.var_annot_class_name(var_entry)
|
||||
elif at.value.kind() == ast.ASTKind.Attribute: parent_class = self._resolve_attr_field_class(pool, trans, at.value, allow_module_global)
|
||||
elif at.value.kind() == ast.ASTKind.Call:
|
||||
# pointer-cast base (the recursive parent of `(T*(x)).f.g`): extract the class name from the
|
||||
# cast annotation (see the _base_class_from_cast_base header)
|
||||
parent_class = self._base_class_from_cast_base(pool, trans, at.value)
|
||||
# Pointer-cast base OR normal call. A cast/constructor names a type (`T*(x)`, `T(...)`),
|
||||
# but a function call's class must come from its RETURN type: using the callee name
|
||||
# (`make_o`) makes lookup_field_by_class search for a class named after the function
|
||||
# (chained `a.make_o().inner.z`).
|
||||
parent_class = self._call_ret_class_name(pool, trans, at.value)
|
||||
if parent_class is None: parent_class = self._base_class_from_cast_base(pool, trans, at.value)
|
||||
else: return None
|
||||
if parent_class is None: return None
|
||||
# look up the at.attr field in the parent class and return its annotation class name
|
||||
@@ -1057,6 +1128,11 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
|
||||
|
||||
if obj_ptr is None or obj_ptr.Ty is None: return None
|
||||
|
||||
# Aggregate rvalue base (same rule as the read path): spill it to a frame temporary so
|
||||
# `f().field = v` resolves the field address instead of failing on a non-pointer base.
|
||||
obj_ptr = self._attr_base_as_ptr(builder, obj_ptr)
|
||||
if obj_ptr is None or obj_ptr.Ty is None: return None
|
||||
|
||||
# if obj_ptr is Ptr(Ptr(Struct)) (the alloca of an X* variable), load to deref and get Ptr(Struct)
|
||||
obj_ptr = self._deref_if_ptr_ptr(builder, obj_ptr)
|
||||
|
||||
|
||||
@@ -524,7 +524,41 @@ novirtual class ExprGenBase:
|
||||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||||
callee = at.attr
|
||||
if callee is None: return None
|
||||
ent: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(callee)
|
||||
# Module-aware resolution: the ctor dispatch (CallGenMixin) already resolves the struct
|
||||
# by module SHA1. A bare find_struct here returns the FIRST same-named struct (possibly
|
||||
# from another module), so this "construction materialization" type would disagree with
|
||||
# the value the ctor actually produced and `p: T = T()` would fail with a CastError
|
||||
# whenever the same-named T is defined in another imported module.
|
||||
ent: GenTypes.StructEntry* = None
|
||||
if trans is not None:
|
||||
if cl.func.kind() == ast.ASTKind.Name:
|
||||
if trans.ModuleSha1 is not None: ent = self.Gen.StructGen.find_struct_by_module(callee, trans.ModuleSha1)
|
||||
if ent is None and trans._from_imports is not None:
|
||||
# from X import T (or `as Alias`): resolve through the from-import table so the
|
||||
# defining module's T wins over a same-named struct from another module.
|
||||
fi_sha: str = self.Gen.CallGen._lookup_mod_sha1(trans.Pool, trans._from_imports, callee, 1)
|
||||
if fi_sha is not None:
|
||||
fi_name: str = callee
|
||||
fi_orig: str = self.Gen.ImportsGen.lookup_from_import_original(trans._from_imports, callee)
|
||||
if fi_orig is not None:
|
||||
fi_len: sizet = 0
|
||||
while fi_orig[fi_len] != ' ' and fi_orig[fi_len] != '\0': fi_len += 1
|
||||
fi_buf: str = trans.Pool.alloc(fi_len + 1)
|
||||
if fi_buf is not None:
|
||||
for fi_i in range(int(fi_len)): fi_buf[fi_i] = fi_orig[fi_i]
|
||||
fi_buf[fi_len] = '\0'
|
||||
fi_name = fi_buf
|
||||
ent = self.Gen.StructGen.find_struct_by_module(fi_name, fi_sha)
|
||||
else:
|
||||
at_c: ast.Attribute* = ast.Attribute*(cl.func)
|
||||
mod_path_c: str = None
|
||||
if at_c.value is not None:
|
||||
if at_c.value.kind() == ast.ASTKind.Name: mod_path_c = (ast.Name*(at_c.value)).id
|
||||
elif at_c.value.kind() == ast.ASTKind.Attribute: mod_path_c = self.Gen.ExprGenAttr._attr_module_path_str(trans.Pool, at_c.value)
|
||||
if mod_path_c is not None and self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, mod_path_c) != 0:
|
||||
mod_sha1_c: str = self.Gen.CallGen._lookup_module_sha1(mod_path_c)
|
||||
if mod_sha1_c is not None: ent = self.Gen.StructGen.find_struct_by_module(callee, mod_sha1_c)
|
||||
if ent is None: ent = self.Gen.StructGen.find_struct(callee)
|
||||
if ent is not None and ent.Ty is not None: return ent.Ty
|
||||
# REnum variant ctor `EnumType.Variant(...)`: the callee is not a struct
|
||||
# name, but materializes the base enum, so return that aggregate type.
|
||||
|
||||
@@ -20,10 +20,10 @@ define FG_SCOPE_FUNCTION = 1
|
||||
novirtual class FuncGenBase(CallGenBase.CallGenBase):
|
||||
"""Shared helpers for function attributes, naming, mangling and creation."""
|
||||
|
||||
# extract_func_attrs - extract c.Attribute attrs from decorator_list.
|
||||
# Supports @c.Attribute(t.attr.xxx()/t.attr.xxx/t.attr.llvm.xxx); returns LLVM IR attr string or None.
|
||||
# extract_func_attrs - extract Attribute attrs from decorator_list.
|
||||
# Supports @Attribute(attr.xxx()/attr.xxx/attr.llvm.xxx); returns LLVM IR attr string or None.
|
||||
def extract_func_attrs(self, pool: memhub.MemBuddy*, decorator_list: list[ast.AST*]*, imported_modules: str) -> str:
|
||||
"""Extract c.Attribute attrs from decorators; return the LLVM IR attr string."""
|
||||
"""Extract Attribute attrs from decorators; return the LLVM IR attr string."""
|
||||
if decorator_list is None: return None
|
||||
dn: sizet = len(decorator_list)
|
||||
if dn == 0: return None
|
||||
|
||||
@@ -683,6 +683,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
if ags.args is not None:
|
||||
alist: list[ast.AST*]* = ags.args
|
||||
an: sizet = len(alist)
|
||||
p_idx: int = 0
|
||||
for ai in range(int(an)):
|
||||
arg: ast.Arg* = ast.Arg*(alist.get(ai))
|
||||
if arg is None or arg.arg is None: continue
|
||||
@@ -698,7 +699,8 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
self.Gen.TypeGenErr.fatal_error(arg, f"forward declaration param '{arg.arg}' type annotation failed to resolve", "NameError")
|
||||
return 0
|
||||
# Bare class param -> Ptr(struct) (object is a pointer); ArrayT param -> element pointer ty* (C array decay).
|
||||
# Exception (keep in sync in 3 places): C ABI extern bare struct params pass by value %Struct with a full definition.
|
||||
# Exception (keep in sync in 3 places): C ABI extern bare struct params use the
|
||||
# C ABI type: Win64 iN for 1/2/4/8 bytes, otherwise Ptr(struct) (byref copy).
|
||||
match resolved:
|
||||
case mlirlite.LLVMType.Void():
|
||||
# void is not a legal param type (void is only for void* and typedef of an opaque struct);
|
||||
@@ -708,7 +710,10 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
case mlirlite.LLVMType.Struct(_, _, _):
|
||||
if is_extern_decl != 0 and self.Gen.StructGen.find_struct_by_type(resolved) is not None:
|
||||
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, resolved)
|
||||
param_ty = resolved
|
||||
param_ty = self.Gen.CallGenBase.extern_c_param_agg_ty(pool, resolved)
|
||||
# Win64 byref: the call site must pass a copy, not the variable address.
|
||||
if param_ty is not None and param_ty.kind() == mlirlite.LLVMType.Ptr:
|
||||
self.Gen.CallGenBase.register_extern_byref_param(mangled_name, p_idx)
|
||||
else: param_ty = mlirlite.Ptr(pool, resolved)
|
||||
case mlirlite.LLVMType.Array(aelem_ty, _):
|
||||
param_ty = mlirlite.Ptr(pool, aelem_ty)
|
||||
@@ -717,6 +722,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
pname: str = pool.alloc(32)
|
||||
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
|
||||
mlirlite.add_param(pool, func, param_ty, pname)
|
||||
p_idx += 1
|
||||
|
||||
# *args variadic: set IsVarArg=1 to emit the ... suffix
|
||||
if ags.vararg is not None: func.IsVarArg = 1
|
||||
@@ -737,6 +743,11 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
fd: ast.FunctionDef* = ast.FunctionDef*(node)
|
||||
if fd is None or fd.name is None: return 0
|
||||
|
||||
# Reject unknown decorators instead of silently dropping them (e.g. the retired
|
||||
# `@c.Attribute(...)` spelling). Runs before the generic/async early returns so every
|
||||
# function form is checked.
|
||||
self.Gen.ClassGenBase.validate_decorators(trans, trans.Module, trans.Pool, fd.name, "function", fd.decorator_list, 0)
|
||||
|
||||
# PEP 695 generic function templates are not emitted directly (same rule as forward_declare_functions):
|
||||
# clones are emitted at call sites by _specialize_generic_function; here we only ensure the template is registered.
|
||||
if fd.type_params is not None:
|
||||
@@ -878,6 +889,10 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
if (has_export != 0 or has_state != 0) and trans.ModuleSha1 is not None: self.Gen.CallGenBase.register_cexport_func(trans.ModuleSha1, fd.name)
|
||||
|
||||
if is_extern_decl != 0:
|
||||
# C extern returning an aggregate: remember the symbol so call sites emit the C ABI
|
||||
# (integer register / hidden sret) instead of Viper's aggregate-return convention.
|
||||
if ret_ty is not None and ret_ty.kind() == mlirlite.LLVMType.Struct:
|
||||
self.Gen.CallGenBase.register_extern_agg(mangled_name)
|
||||
# extern <lib> name(...):runtime=True 在这里也生成 thunk(Phase A 的 extern 往往不走
|
||||
# forward_declare_functions,两处都挂才覆盖全部路径;另一处靠 find_func_in_module 去重)。
|
||||
if fd.ffi_lib is not None:
|
||||
@@ -917,6 +932,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
if ags_e.args is not None:
|
||||
alist_e: list[ast.AST*]* = ags_e.args
|
||||
an_e: sizet = len(alist_e)
|
||||
p_idx_e: int = 0
|
||||
for ai_e in range(int(an_e)):
|
||||
arg_e: ast.Arg* = ast.Arg*(alist_e.get(ai_e))
|
||||
if arg_e is not None and arg_e.arg is not None:
|
||||
@@ -932,7 +948,8 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
resolved_e = i32_ty # unreachable (fatal_error already exited)
|
||||
if resolved_e is not None:
|
||||
# Bare class param -> Ptr(struct); ArrayT param -> element pointer ty* (C decay). This branch is
|
||||
# always a C ABI extern declare: bare struct params pass by value %Struct (keep in sync with 2 others).
|
||||
# always a C ABI extern declare: bare struct params use the C ABI type (Win64 iN for
|
||||
# 1/2/4/8 bytes, otherwise Ptr(struct) as a byref copy; keep in sync with 2 others).
|
||||
match resolved_e:
|
||||
case mlirlite.LLVMType.Void():
|
||||
# void is not a legal param type (see the same rule above)
|
||||
@@ -940,7 +957,10 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
case mlirlite.LLVMType.Struct(_, _, _):
|
||||
if self.Gen.StructGen.find_struct_by_type(resolved_e) is not None:
|
||||
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, resolved_e)
|
||||
param_ty_e = resolved_e
|
||||
param_ty_e = self.Gen.CallGenBase.extern_c_param_agg_ty(pool, resolved_e)
|
||||
# Win64 byref: the call site must pass a copy, not the variable address.
|
||||
if param_ty_e is not None and param_ty_e.kind() == mlirlite.LLVMType.Ptr:
|
||||
self.Gen.CallGenBase.register_extern_byref_param(mangled_name, p_idx_e)
|
||||
else: param_ty_e = mlirlite.Ptr(pool, resolved_e)
|
||||
case mlirlite.LLVMType.Array(aelem_ty_e, _):
|
||||
param_ty_e = mlirlite.Ptr(pool, aelem_ty_e)
|
||||
@@ -949,8 +969,9 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
pname_e: str = pool.alloc(32)
|
||||
if pname_e is not None: viperlib.snprintf(pname_e, 32, "%%%s", arg_e.arg)
|
||||
mlirlite.add_param(pool, func, param_ty_e, pname_e)
|
||||
p_idx_e += 1
|
||||
|
||||
# Extract @c.Attribute decorator attributes and set them on the function
|
||||
# Extract @Attribute decorator attributes and set them on the function
|
||||
func_attrs_e: str = self.Gen.FuncGenBase.extract_func_attrs(pool, fd.decorator_list, imported_modules)
|
||||
if func_attrs_e is not None: mlirlite.function_set_attrs(func, func_attrs_e)
|
||||
# B5: section string attribute goes through attribute group #N
|
||||
@@ -1010,7 +1031,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
VLogger.error(f"create_function {fd.name} failed", "FUNC")
|
||||
return 0
|
||||
|
||||
# Extract @c.Attribute decorator attributes and set them on the function
|
||||
# Extract @Attribute decorator attributes and set them on the function
|
||||
func_attrs: str = self.Gen.FuncGenBase.extract_func_attrs(pool, fd.decorator_list, imported_modules)
|
||||
if func_attrs is not None: mlirlite.function_set_attrs(func, func_attrs)
|
||||
# B5: section string attribute goes through attribute group #N (LLVM rejects inline section="...")
|
||||
@@ -1074,6 +1095,27 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
|
||||
# (refined per statement); no-op when DI is disabled.
|
||||
dbg_pushed: int = DebugInfoGen.PushFunction(pool, func_builder, func, fd.lineno, fd.col_offset + 1)
|
||||
|
||||
# Constructors may be invoked on a null object when __new__ failed (pool exhausted).
|
||||
# __before_init__ is null-safe; __init__ must be too, or its first field store is an
|
||||
# access violation (ASan: LspRange.__init__ in the LSP semantic-token path).
|
||||
if fd.name is not None and string.strcmp(fd.name, "__init__") == 0:
|
||||
init_sp: mlirlite.Param* = mlirlite.function_get_param_head(func)
|
||||
if init_sp is not None:
|
||||
init_sp_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(init_sp)
|
||||
init_sp_nm: char* = mlirlite.param_get_name(init_sp)
|
||||
if init_sp_ty is not None and init_sp_nm is not None and string.strcmp(init_sp_nm, "%self") == 0:
|
||||
init_self: mlirlite.Value* = mlirlite.SSAValue(pool, init_sp_ty, init_sp_nm)
|
||||
init_null: mlirlite.Value* = mlirlite.ConstNull(pool, init_sp_ty, "null")
|
||||
init_null_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "init.selfnull")
|
||||
init_body_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, "init.body")
|
||||
if init_self is not None and init_null is not None and init_null_bb is not None and init_body_bb is not None:
|
||||
init_is_null: mlirlite.Value* = mlirlite.build_icmp(func_builder, mlirlite.ICMP_EQ, init_self, init_null)
|
||||
if init_is_null is not None:
|
||||
mlirlite.build_cond_br(func_builder, init_is_null, init_null_bb, init_body_bb)
|
||||
mlirlite.position_at_end(func_builder, init_null_bb)
|
||||
mlirlite.build_ret_void(func_builder)
|
||||
mlirlite.position_at_end(func_builder, init_body_bb)
|
||||
|
||||
# @t.TLS one-time-init guard (aligned with TPC HandlesFunctions.py): the first call runs the body and sets
|
||||
# __tls_init_done_{name}=1; later calls return the default. Without it every call re-runs TlsAlloc and loses state.
|
||||
if has_tls != 0:
|
||||
|
||||
@@ -453,6 +453,46 @@ def get_enum_base_type_compat(enum_name: str) -> mlirlite.LLVMType*:
|
||||
return None
|
||||
|
||||
|
||||
def get_enum_base_type_in_module(enum_name: str, sha1: str) -> mlirlite.LLVMType*:
|
||||
"""按 (名字, 定义模块 SHA1) 取枚举基类型:跨模块同名枚举按模块区分,避免取到先登记的同名项。"""
|
||||
if _g_gen is not None: return _g_gen.StmtGenDecl.get_enum_base_type_in_module(enum_name, sha1)
|
||||
return None
|
||||
|
||||
|
||||
# ---------------- C-extern aggregate return registry ----------------
|
||||
# A C function returning a struct follows the C ABI (Win64: RAX integer <=8B, hidden sret
|
||||
# pointer otherwise), unlike Viper's own aggregate return. Register the bare symbol when the
|
||||
# extern declare is created; the call site consults it before emitting a direct call.
|
||||
def register_extern_agg(sym: str) -> int:
|
||||
"""Delegate to CallGenBase.register_extern_agg (idempotent)."""
|
||||
if _g_gen is not None: return _g_gen.CallGenBase.register_extern_agg(sym)
|
||||
return 0
|
||||
|
||||
|
||||
def is_extern_agg(sym: str) -> int:
|
||||
"""Delegate to CallGenBase.is_extern_agg."""
|
||||
if _g_gen is not None: return _g_gen.CallGenBase.is_extern_agg(sym)
|
||||
return 0
|
||||
|
||||
|
||||
def is_win64_c_abi() -> int:
|
||||
"""Delegate to CallGenBase.is_win64_c_abi."""
|
||||
if _g_gen is not None: return _g_gen.CallGenBase.is_win64_c_abi()
|
||||
return 1
|
||||
|
||||
|
||||
def extern_c_param_agg_ty(pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*:
|
||||
"""Delegate to CallGenBase.extern_c_param_agg_ty (C ABI by-value aggregate parameter type)."""
|
||||
if _g_gen is not None: return _g_gen.CallGenBase.extern_c_param_agg_ty(pool, ty)
|
||||
return ty
|
||||
|
||||
|
||||
def register_extern_byref_param(sym: str, index: int) -> int:
|
||||
"""Delegate to CallGenBase.register_extern_byref_param."""
|
||||
if _g_gen is not None: return _g_gen.CallGenBase.register_extern_byref_param(sym, index)
|
||||
return 0
|
||||
|
||||
|
||||
# ---------------- add_visible_struct (delegates to StructGenMixin.add_visible_struct) ----------------
|
||||
def add_visible_struct(pool: memhub.MemBuddy*, name: str):
|
||||
"""Delegate to gen.add_visible_struct"""
|
||||
|
||||
@@ -31,6 +31,12 @@ define FIELD_NAME_MAX = 64
|
||||
define PARENT_MAX = 64
|
||||
define MRO_MAX = 64
|
||||
|
||||
# ============================================================
|
||||
# Enum capacities
|
||||
# 单个枚举的成员数上限。唯一定义点:ClassGenEnum 的翻译期暂存数组、StmtGenDeclMixin 的
|
||||
# EnumEntry.Members 预分配、VpiReader 的 .vpi 预扫描都引用这一个值,禁止再写本地副本。
|
||||
define ENUM_MEMBER_MAX = 256
|
||||
|
||||
# ============================================================
|
||||
# for-iteration sequence kinds (formerly HandlesFor.SEQ_KIND_*)
|
||||
define SEQ_KIND_RANGE = 1
|
||||
@@ -150,8 +156,8 @@ novirtual class StructEntry:
|
||||
FieldCount: int # Field count
|
||||
Fields: FieldEntry* # Field array (FIELD_MAX slots)
|
||||
IsUnion: int # 1=union / 0=normal struct
|
||||
IsPacked: int # 1=@c.Attribute(t.attr.packed) packed struct (no padding)
|
||||
Align: int # @c.Attribute(t.attr.aligned(N)) alignment in bytes (0=default)
|
||||
IsPacked: int # 1=@Attribute(attr.packed) packed struct (no padding)
|
||||
Align: int # @Attribute(attr.aligned(N)) alignment in bytes (0=default)
|
||||
IsOOP: int # 1=OOP struct (has methods) / 0=plain memory struct
|
||||
HasInit: int # 1=has __init__ / 0=no
|
||||
HasNew: int # 1=has __new__ / 0=no
|
||||
@@ -224,6 +230,19 @@ define FUNC_RET_SIGN_UNSIGNED = 1
|
||||
define FUNC_RET_SIGN_SIGNED = 2
|
||||
|
||||
|
||||
# Snapshot kinds beyond ast.CONST_* (1..6):
|
||||
# CONST_AGG_DEFAULT - aggregate literal (struct constructor with scalar arguments); the
|
||||
# encoded field tokens live in FuncDefaultsEntry.ConstStrs[di].
|
||||
# CONST_UNKNOWN_DEFAULT - a default that cannot be rebuilt off-module; the call site fails
|
||||
# fast instead of silently passing a null/uninitialized value.
|
||||
define CONST_AGG_DEFAULT = 100
|
||||
define CONST_UNKNOWN_DEFAULT = 101
|
||||
# CONST_EXPR_TEXT - name/qualified-name default (enum member, CDefine); the text lives
|
||||
# in ConstStrs[di] and is resolved at the call site (the .vpi pre-scan
|
||||
# runs before enums are registered).
|
||||
define CONST_EXPR_TEXT = 102
|
||||
|
||||
|
||||
# ============================================================
|
||||
# FuncDefaultsEntry - default-argument entry (formerly HandlesExprCall.FuncDefaultsEntry)
|
||||
novirtual class FuncDefaultsEntry:
|
||||
|
||||
@@ -22,8 +22,8 @@ import lib.core.Gen.GenApi as GenApi
|
||||
# ============================================================
|
||||
# Constants
|
||||
# 枚举表容量:键是 (定义模块 SHA1, 名字),同名枚举按模块各占一条(不再合并),故留足余量
|
||||
# ENUM_MEMBER_MAX(单枚举成员上限)唯一定义在 GenTypes,与 ClassGenEnum 暂存数组和 .vpi 预扫描共用。
|
||||
define ENUM_MAX = 256
|
||||
define ENUM_MEMBER_MAX = 128
|
||||
|
||||
# ============================================================
|
||||
# StmtGenDeclMixin - declaration/enum/nonlocal (mid layer)
|
||||
@@ -123,6 +123,19 @@ novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
|
||||
if k == ast.ASTKind.AnnAssign: return self.Gen.AssignGen.translate_annassign_prescan(trans, node)
|
||||
if k == ast.ASTKind.If:
|
||||
ifn: ast.If* = ast.If*(node)
|
||||
# cif/celif: only prescan the compile-time-selected branch, otherwise a pruned
|
||||
# branch's platform-specific annotations (e.g. win32 types on a Linux target)
|
||||
# would still be resolved and fail the build.
|
||||
if self._is_cif_call(ifn.test) == 1:
|
||||
cif_val_pre: int = self._prune_cif_test(trans.Pool, trans, ifn.test)
|
||||
if cif_val_pre != 0:
|
||||
if ifn.children is not None:
|
||||
bn_pre_ci: sizet = len(ifn.children)
|
||||
for bi_pre_ci in range(int(bn_pre_ci)): self.pre_scan_allocas(trans, ifn.children.get(bi_pre_ci))
|
||||
elif ifn.orelse is not None:
|
||||
bn_pre_ce: sizet = len(ifn.orelse)
|
||||
for bi_pre_ce in range(int(bn_pre_ce)): self.pre_scan_allocas(trans, ifn.orelse.get(bi_pre_ce))
|
||||
return 0
|
||||
if ifn.children is not None:
|
||||
bn_pre_i: sizet = len(ifn.children)
|
||||
for bi_pre_i in range(int(bn_pre_i)): self.pre_scan_allocas(trans, ifn.children.get(bi_pre_i))
|
||||
@@ -364,7 +377,7 @@ novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
|
||||
|
||||
def _get_enum_member(self, enum_entry: GenTypes.EnumEntry*, i: int) -> GenTypes.EnumMember*:
|
||||
"""Get the i-th enum member entry"""
|
||||
if enum_entry is None or i < 0 or i >= ENUM_MEMBER_MAX: return None
|
||||
if enum_entry is None or i < 0 or i >= GenTypes.ENUM_MEMBER_MAX: return None
|
||||
member_size: sizet = GenTypes.EnumMember.__sizeof__()
|
||||
addr: uint64 = uint64(enum_entry.Members) + i * member_size
|
||||
return GenTypes.EnumMember*(addr)
|
||||
@@ -392,9 +405,9 @@ novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
|
||||
|
||||
# Allocate the member array (permanent pool: survives mark/reset like EnumTable)
|
||||
member_size: sizet = GenTypes.EnumMember.__sizeof__()
|
||||
entry.Members = CompilerFactory.factory.PermPool.alloc(member_size * ENUM_MEMBER_MAX)
|
||||
entry.Members = CompilerFactory.factory.PermPool.alloc(member_size * GenTypes.ENUM_MEMBER_MAX)
|
||||
if entry.Members is None: return None
|
||||
string.memset(entry.Members, 0, member_size * ENUM_MEMBER_MAX)
|
||||
string.memset(entry.Members, 0, member_size * GenTypes.ENUM_MEMBER_MAX)
|
||||
|
||||
# Copy the class name (permanent-pool deep copy)
|
||||
name_len: sizet = string.strlen(name)
|
||||
@@ -435,7 +448,7 @@ novirtual class StmtGenDeclMixin(StmtGenCifMixin.StmtGenCifMixin):
|
||||
if ex_d is not None and ex_d.Name is not None:
|
||||
if string.strcmp(ex_d.Name, member_name) == 0: return ei_d
|
||||
|
||||
if enum_entry.MemberCount >= ENUM_MEMBER_MAX:
|
||||
if enum_entry.MemberCount >= GenTypes.ENUM_MEMBER_MAX:
|
||||
VLogger.error(f"member table full for {enum_entry.Name}", "ENUM")
|
||||
return -1
|
||||
|
||||
|
||||
@@ -777,7 +777,7 @@ novirtual class TranslatorGenMixin:
|
||||
# Section name: write the field directly (_print_global adds the quotes for `, section "xxx"`).
|
||||
if gv_attr_section is not None: mlirlite.global_set_section(gv, gv_attr_section)
|
||||
|
||||
# B5: apply alignment to global instances of @c.Attribute(t.attr.aligned(N)) classes.
|
||||
# B5: apply alignment to global instances of @Attribute(attr.aligned(N)) classes.
|
||||
# An explicit variable-level `T | t.attr.aligned(N)` wins over the class-level struct Align.
|
||||
al_ty: mlirlite.LLVMType* = var_ty
|
||||
match var_ty:
|
||||
|
||||
+349
-388
@@ -1,388 +1,349 @@
|
||||
from stdint import *
|
||||
import memhub
|
||||
import string
|
||||
import stdlib
|
||||
import win32.win32file as win32file
|
||||
import win32.win32base as win32base
|
||||
import io
|
||||
import hashlib
|
||||
import viperlib
|
||||
import lib.Projectrans.Utils as Utils
|
||||
import lib.core.VLogger as VLogger
|
||||
|
||||
|
||||
# IncludesScanner - recursive scan of the includes directory (Win32 FindFirstFileA/
|
||||
# FindNextFileA); collects .vp paths and SHA1s for Phase1 stub gen / Phase2 merge.
|
||||
|
||||
# global mbuddy pointer
|
||||
_mbuddy: memhub.MemBuddy*
|
||||
|
||||
# max files per scan
|
||||
define MAX_FILES = 1024
|
||||
|
||||
|
||||
# FileEntry - file entry (path + SHA1)
|
||||
novirtual class FileEntry:
|
||||
"""A scanned file entry."""
|
||||
Path: str # absolute file path
|
||||
Sha1: str # first 16 SHA1 chars (16 bytes + null)
|
||||
RelPath: str # path relative to the includes root
|
||||
ModuleName: str # module name (e.g. "ast.parser")
|
||||
IncDir: str # owning includes root dir (multi-root support)
|
||||
|
||||
|
||||
# ScanResult - scan result
|
||||
novirtual class ScanResult:
|
||||
"""A collection of scan results."""
|
||||
Entries: FileEntry* # FileEntry array
|
||||
Count: int
|
||||
Capacity: int
|
||||
|
||||
|
||||
# create_scan_result - create the scan result container
|
||||
def create_scan_result(pool: memhub.MemBuddy*) -> ScanResult*:
|
||||
"""Create the scan result container with MAX_FILES preallocated slots."""
|
||||
if pool is None: return None
|
||||
size: sizet = MAX_FILES * FileEntry.__sizeof__()
|
||||
# use stdlib.malloc to avoid exhausting the mbuddy pool (consistent with StubMerger)
|
||||
entries: FileEntry* = stdlib.malloc(size)
|
||||
if entries is None: return None
|
||||
string.memset(entries, 0, size)
|
||||
|
||||
result: ScanResult* = stdlib.malloc(ScanResult.__sizeof__())
|
||||
if result is None: return None
|
||||
string.memset(result, 0, ScanResult.__sizeof__())
|
||||
result.Entries = entries
|
||||
result.Count = 0
|
||||
result.Capacity = MAX_FILES
|
||||
return result
|
||||
|
||||
|
||||
# add_file_entry - append a file entry to the scan result
|
||||
def add_file_entry(result: ScanResult*, pool: memhub.MemBuddy*, abs_path: str, rel_path: str, sha1: str, inc_dir: str) -> int:
|
||||
"""Append a file entry to the scan result; inc_dir is the owning includes root."""
|
||||
if result is None or pool is None: return 1
|
||||
if result.Count >= result.Capacity: return 1
|
||||
|
||||
# compute module name: '/' -> '.', strip .vp/.py (Utils handles __init__.vp)
|
||||
mod_buf: str = Utils.PathToModuleName(rel_path, 0)
|
||||
if mod_buf is None: return 1
|
||||
|
||||
# get the entry address
|
||||
entry_size: sizet = FileEntry.__sizeof__()
|
||||
entry_addr: uint64 = uint64(result.Entries) + result.Count * entry_size
|
||||
entry: FileEntry* = (FileEntry*)(entry_addr)
|
||||
if entry is None: return 1
|
||||
|
||||
# copy the path string
|
||||
abs_len: sizet = string.strlen(abs_path)
|
||||
abs_buf: str = stdlib.malloc(abs_len + 1)
|
||||
if abs_buf is None: return 1
|
||||
string.strcpy(abs_buf, abs_path)
|
||||
entry.Path = abs_buf
|
||||
|
||||
# copy the SHA1
|
||||
sha1_buf: str = stdlib.malloc(17)
|
||||
if sha1_buf is None: return 1
|
||||
string.strcpy(sha1_buf, sha1)
|
||||
entry.Sha1 = sha1_buf
|
||||
|
||||
# copy the relative path
|
||||
rel_buf: str = stdlib.malloc(string.strlen(rel_path) + 1)
|
||||
if rel_buf is None: return 1
|
||||
string.strcpy(rel_buf, rel_path)
|
||||
entry.RelPath = rel_buf
|
||||
|
||||
# copy the module name
|
||||
entry.ModuleName = mod_buf
|
||||
|
||||
# copy the owning includes root dir (used by Phase2 path joining)
|
||||
if inc_dir is not None:
|
||||
inc_len: sizet = string.strlen(inc_dir)
|
||||
inc_buf: str = stdlib.malloc(inc_len + 1)
|
||||
if inc_buf is None: return 1
|
||||
string.strcpy(inc_buf, inc_dir)
|
||||
entry.IncDir = inc_buf
|
||||
|
||||
result.Count += 1
|
||||
return 0
|
||||
|
||||
|
||||
# compute_file_sha1 - read file content and compute SHA1 (CRLF->LF, matching Projectrans.py)
|
||||
def compute_file_sha1(pool: memhub.MemBuddy*, file_path: str) -> str:
|
||||
"""Read a file, normalize CRLF to LF, and return the first 16 SHA1 hex chars."""
|
||||
if pool is None or file_path is None: return None
|
||||
|
||||
# read file content; BUF_SIZE must match SRC_BUF_SIZE (1MB) or trailing edits won't change SHA1
|
||||
BUF_SIZE: sizet = 1048576
|
||||
# Refuse larger files: hashing only the first 1 MiB gives two files that differ later the same
|
||||
# SHA1, and the SHA1 keys the stub/cache files (colliding modules would reuse each other's IR).
|
||||
sf_sz: io.File* = io.File(file_path, io.MODE.R)
|
||||
if sf_sz.closed: return None
|
||||
too_big: int = 0
|
||||
if sf_sz.size() >= BUF_SIZE: too_big = 1
|
||||
sf_sz.close()
|
||||
if too_big != 0:
|
||||
VLogger.error(f"source file exceeds the {BUF_SIZE}-byte limit and cannot be hashed completely: {file_path}", "IncludesScanner")
|
||||
return None
|
||||
buf: bytes = Utils.read_file_to_buf(file_path, BUF_SIZE)
|
||||
if buf is None: return None
|
||||
|
||||
# strip \r in place (CRLF -> LF), matching Projectrans.py text-mode reads
|
||||
buf_len: sizet = string.strlen(buf)
|
||||
write_pos: sizet = 0
|
||||
for ch in buf:
|
||||
if ch != '\r':
|
||||
buf[write_pos] = ch
|
||||
write_pos += 1
|
||||
|
||||
# add a null terminator
|
||||
buf[write_pos] = 0
|
||||
|
||||
# compute SHA1
|
||||
ctx: hashlib.sha1* = hashlib.sha1()
|
||||
if ctx is None:
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
ctx.update(buf)
|
||||
|
||||
digest: bytes = stdlib.malloc(hashlib.SHA1_DIGEST_LEN)
|
||||
if digest is None:
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
ctx.final(digest)
|
||||
|
||||
# convert to hex (first 8 bytes = 16 hex chars; stdlib allocation unchanged)
|
||||
hex_buf: str = Utils.bytes_to_hex(pool, digest, 8, 0, 1)
|
||||
stdlib.free(buf)
|
||||
stdlib.free(digest)
|
||||
return hex_buf
|
||||
|
||||
|
||||
# scan_directory_recursive - recursively scan a directory (FindFirstFileA/FindNextFileA)
|
||||
def scan_directory_recursive(pool: memhub.MemBuddy*, root_dir: str, rel_prefix: str, result: ScanResult*, inc_dir: str) -> int:
|
||||
"""Recursively collect .vp/.py files under a directory; inc_dir is the includes root."""
|
||||
if pool is None or root_dir is None or result is None: return 1
|
||||
# subdir recursion failure count (not ignored; fail-fast at the summary)
|
||||
subdir_failed: int = 0
|
||||
|
||||
# build search pattern: root_dir/*
|
||||
root_len: sizet = string.strlen(root_dir)
|
||||
pattern: bytes = stdlib.malloc(root_len + 4)
|
||||
if pattern is None: return 1
|
||||
viperlib.snprintf(pattern, root_len + 4, "%s/*", root_dir)
|
||||
|
||||
# find_data buffer +16 slack: VPC's WIN32_FIND_DATAA.__sizeof__() undercounts Windows writes
|
||||
fd_size_s: sizet = win32file.WIN32_FIND_DATAA.__sizeof__()
|
||||
find_data: win32file.WIN32_FIND_DATAA* = stdlib.malloc(fd_size_s + 16)
|
||||
if find_data is None: return 1
|
||||
string.memset(find_data, 0, fd_size_s + 16)
|
||||
|
||||
handle: win32base.HANDLE = win32file.FindFirstFileA(pattern, find_data)
|
||||
if handle == win32base.INVALID_HANDLE_VALUE: return 1
|
||||
|
||||
# iterate all files and subdirectories
|
||||
while True:
|
||||
# skip . and ..
|
||||
fname: str = find_data.cFileName
|
||||
if fname is not None:
|
||||
fname0: char = fname[0]
|
||||
if fname0 == '.':
|
||||
fname1: char = fname[1]
|
||||
if fname1 == '\0':
|
||||
# "."
|
||||
if win32file.FindNextFileA(handle, find_data) == 0: break
|
||||
continue
|
||||
elif fname1 == '.':
|
||||
fname2: char = fname[2]
|
||||
if fname2 == '\0':
|
||||
# ".."
|
||||
if win32file.FindNextFileA(handle, find_data) == 0: break
|
||||
continue
|
||||
|
||||
# check whether it is a directory
|
||||
is_dir: int = find_data.dwFileAttributes & win32file.FILE_ATTRIBUTE_DIRECTORY
|
||||
|
||||
# build the full path
|
||||
fname_len: sizet = string.strlen(fname)
|
||||
full_path: bytes = Utils.JoinPath(root_dir, fname)
|
||||
if full_path is None: break
|
||||
|
||||
# build the relative path
|
||||
prefix_len: sizet = 0
|
||||
if rel_prefix is not None: prefix_len = string.strlen(rel_prefix)
|
||||
rel_path: bytes = None
|
||||
if rel_prefix is not None and prefix_len > 0: rel_path = Utils.JoinPath(rel_prefix, fname)
|
||||
else:
|
||||
rel_path = stdlib.malloc(fname_len + 1)
|
||||
if rel_path is None: break
|
||||
string.strcpy(rel_path, fname)
|
||||
if rel_path is None: break
|
||||
|
||||
if is_dir != 0:
|
||||
# recurse into the subdirectory
|
||||
if scan_directory_recursive(pool, full_path, rel_path, result, inc_dir) != 0: subdir_failed += 1
|
||||
else:
|
||||
# check for a .vp source file (Viper uses .vp only; no longer .py)
|
||||
is_py: int = 0
|
||||
if Utils.is_vp_at(fname, 0, fname_len) != 0: is_py = 1
|
||||
|
||||
if is_py != 0:
|
||||
# skip files under __pycache__
|
||||
is_pycache: int = 0
|
||||
if rel_prefix is not None:
|
||||
if string.strcmp(rel_prefix, "__pycache__") == 0: is_pycache = 1
|
||||
|
||||
if is_pycache == 0:
|
||||
# compute SHA1
|
||||
sha1: str = compute_file_sha1(pool, full_path)
|
||||
if sha1 is not None:
|
||||
# fail-fast: a full table used to drop the file silently, so it never got a
|
||||
# SHA1 entry / dependency edge and was therefore never compiled.
|
||||
if add_file_entry(result, pool, full_path, rel_path, sha1, inc_dir) != 0:
|
||||
VLogger.error(f"includes scan could not register '{full_path}' (the {MAX_FILES}-file capacity or an allocation failed); raise MAX_FILES", "scan")
|
||||
VLogger.debug(f"scan: {rel_path} -> {sha1}", "scan")
|
||||
else: VLogger.warning(f"SHA1 computation failed: {full_path}", "scan")
|
||||
|
||||
# continue to the next file
|
||||
if win32file.FindNextFileA(handle, find_data) == 0: break
|
||||
|
||||
win32file.FindClose(handle)
|
||||
if subdir_failed > 0: VLogger.error(f"subdirectory scan failed for {subdir_failed} entries", "scan")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
# scan_includes - scan entry point: return all .vp file paths and SHA1s
|
||||
def scan_includes(pool: memhub.MemBuddy*, includes_dirs: str) -> ScanResult*:
|
||||
"""Scan space-separated includes dirs and return all scanned .vp/.py files."""
|
||||
if pool is None or includes_dirs is None: return None
|
||||
|
||||
result: ScanResult* = create_scan_result(pool)
|
||||
if result is None: return None
|
||||
|
||||
# parse space-separated includes dirs, recursively scanning and merging each
|
||||
dirs_total: sizet = string.strlen(includes_dirs)
|
||||
d_i: sizet = 0
|
||||
while d_i < dirs_total:
|
||||
while d_i < dirs_total and includes_dirs[d_i] == ' ': d_i += 1
|
||||
if d_i >= dirs_total: break
|
||||
d_start: sizet = d_i
|
||||
while d_i < dirs_total and includes_dirs[d_i] != ' ': d_i += 1
|
||||
d_len: sizet = d_i - d_start
|
||||
if d_len == 0: continue
|
||||
dir_buf: bytes = stdlib.malloc(d_len + 1)
|
||||
if dir_buf is None: VLogger.error("failed to allocate the includes directory; aborting scan", "scan")
|
||||
string.strncpy(dir_buf, includes_dirs + d_start, d_len)
|
||||
dir_buf[d_len] = '\0'
|
||||
scan_directory_recursive(pool, dir_buf, None, result, dir_buf)
|
||||
stdlib.free(dir_buf)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# _PathExistsA - path existence check (GetFileAttributesA)
|
||||
def _PathExistsA(path: str) -> int:
|
||||
"""Return 1 if the path exists, else 0, using GetFileAttributesA."""
|
||||
if path is None: return 0
|
||||
attr: ULONG = win32file.GetFileAttributesA(path)
|
||||
if attr == 0xFFFFFFFF: # INVALID_FILE_ATTRIBUTES
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
# _TryResolveModuleFile - try one candidate path and write outputs (1=hit, 0=miss);
|
||||
# out_abs_path/out_rel_path/out_inc_dir are rawptr output slots.
|
||||
def _TryResolveModuleFile(inc_dir: str, rel_seg: bytes, rel_len: sizet, suffix: str, out_abs_path: rawptr, out_rel_path: rawptr, out_inc_dir: rawptr) -> int:
|
||||
if inc_dir is None or rel_seg is None or suffix is None: return 0
|
||||
inc_len: sizet = string.strlen(inc_dir)
|
||||
suf_len: sizet = string.strlen(suffix)
|
||||
# candidate absolute path: {inc}/{rel}{suffix} (hand-built to avoid %. *s)
|
||||
cand_len: sizet = inc_len + 1 + rel_len + suf_len
|
||||
cand: bytes = stdlib.malloc(cand_len + 1)
|
||||
if cand is None: return 0
|
||||
string.strcpy(cand, inc_dir)
|
||||
cand[inc_len] = '/'
|
||||
string.strncpy(cand + inc_len + 1, rel_seg, rel_len)
|
||||
string.strcpy(cand + inc_len + 1 + rel_len, suffix)
|
||||
if _PathExistsA(cand) == 0:
|
||||
stdlib.free(cand)
|
||||
return 0
|
||||
# path relative to the includes root: {rel}{suffix}
|
||||
rel_path: bytes = stdlib.malloc(rel_len + suf_len + 1)
|
||||
if rel_path is None:
|
||||
stdlib.free(cand)
|
||||
return 0
|
||||
string.strncpy(rel_path, rel_seg, rel_len)
|
||||
rel_path[rel_len] = '\0'
|
||||
string.strcat(rel_path, suffix)
|
||||
# Deep-copy the owning includes root dir; allocate everything before writing
|
||||
# slots to avoid a half-written state when inc allocation fails.
|
||||
inc_buf: bytes = None
|
||||
if out_inc_dir is not None:
|
||||
inc_buf = stdlib.malloc(inc_len + 1)
|
||||
if inc_buf is None:
|
||||
stdlib.free(cand)
|
||||
stdlib.free(rel_path)
|
||||
return 0
|
||||
string.strcpy(inc_buf, inc_dir)
|
||||
# Write output slots (stdlib deep copies; caller frees). Slots must be cast to
|
||||
# (sizet*): rawptr* folds to rawptr and [0] becomes a 1-byte access.
|
||||
if out_abs_path is not None:
|
||||
abs_slot: sizet* = (sizet*)(out_abs_path)
|
||||
abs_slot[0] = sizet(cand)
|
||||
else: stdlib.free(cand)
|
||||
if out_rel_path is not None:
|
||||
rel_slot: sizet* = (sizet*)(out_rel_path)
|
||||
rel_slot[0] = sizet(rel_path)
|
||||
else: stdlib.free(rel_path)
|
||||
if out_inc_dir is not None and inc_buf is not None:
|
||||
inc_slot: sizet* = (sizet*)(out_inc_dir)
|
||||
inc_slot[0] = sizet(inc_buf)
|
||||
return 1
|
||||
|
||||
|
||||
# ResolveModuleToPath - module name -> file path, resolved on demand (no full scan).
|
||||
# Tries each includes dir, preferring file modules over packages. Returns 0 found, 1 not.
|
||||
def ResolveModuleToPath(includes_dirs: str, mod_name: str, out_abs_path: rawptr, out_rel_path: rawptr, out_inc_dir: rawptr) -> int:
|
||||
if includes_dirs is None or mod_name is None: return 1
|
||||
mn_len: sizet = string.strlen(mod_name)
|
||||
if mn_len == 0: return 1
|
||||
# mod_name -> relative path segment ('.' -> '/'): ast.parser -> ast/parser
|
||||
rel_seg: bytes = stdlib.malloc(mn_len + 1)
|
||||
if rel_seg is None: return 1
|
||||
rel_len: sizet = 0
|
||||
for mi in range(int(mn_len)):
|
||||
ch_m: char = mod_name[mi]
|
||||
if ch_m == '.': rel_seg[rel_len] = '/'
|
||||
else: rel_seg[rel_len] = ch_m
|
||||
rel_len += 1
|
||||
rel_seg[rel_len] = '\0'
|
||||
|
||||
found: int = 0
|
||||
d_total: sizet = string.strlen(includes_dirs)
|
||||
d_i: sizet = 0
|
||||
while d_i < d_total:
|
||||
while d_i < d_total and includes_dirs[d_i] == ' ': d_i += 1
|
||||
if d_i >= d_total: break
|
||||
d_start: sizet = d_i
|
||||
while d_i < d_total and includes_dirs[d_i] != ' ': d_i += 1
|
||||
d_len: sizet = d_i - d_start
|
||||
if d_len == 0: continue
|
||||
inc_dir_buf: bytes = stdlib.malloc(d_len + 1)
|
||||
if inc_dir_buf is None: continue
|
||||
string.strncpy(inc_dir_buf, includes_dirs + d_start, d_len)
|
||||
inc_dir_buf[d_len] = '\0'
|
||||
# candidate 1: {inc}/{rel}.vp (file module)
|
||||
if _TryResolveModuleFile(inc_dir_buf, rel_seg, rel_len, ".vp", out_abs_path, out_rel_path, out_inc_dir) != 0:
|
||||
found = 1
|
||||
stdlib.free(inc_dir_buf)
|
||||
break
|
||||
# candidate 2: {inc}/{rel}/__init__.vp (package module)
|
||||
if _TryResolveModuleFile(inc_dir_buf, rel_seg, rel_len, "/__init__.vp", out_abs_path, out_rel_path, out_inc_dir) != 0:
|
||||
found = 1
|
||||
stdlib.free(inc_dir_buf)
|
||||
if found != 0: break
|
||||
stdlib.free(rel_seg)
|
||||
if found != 0: return 0
|
||||
return 1
|
||||
from stdint import *
|
||||
import memhub
|
||||
import string
|
||||
import stdlib
|
||||
import os
|
||||
import io
|
||||
import hashlib
|
||||
import viperlib
|
||||
import lib.Projectrans.Utils as Utils
|
||||
import lib.core.VLogger as VLogger
|
||||
|
||||
|
||||
# IncludesScanner - recursive scan of the includes directory via os.scandir;
|
||||
# collects .vp paths and SHA1s for Phase1 stub gen / Phase2 merge.
|
||||
|
||||
# global mbuddy pointer
|
||||
_mbuddy: memhub.MemBuddy*
|
||||
|
||||
# max files per scan
|
||||
define MAX_FILES = 1024
|
||||
|
||||
|
||||
# FileEntry - file entry (path + SHA1)
|
||||
novirtual class FileEntry:
|
||||
"""A scanned file entry."""
|
||||
Path: str # absolute file path
|
||||
Sha1: str # first 16 SHA1 chars (16 bytes + null)
|
||||
RelPath: str # path relative to the includes root
|
||||
ModuleName: str # module name (e.g. "ast.parser")
|
||||
IncDir: str # owning includes root dir (multi-root support)
|
||||
|
||||
|
||||
# ScanResult - scan result
|
||||
novirtual class ScanResult:
|
||||
"""A collection of scan results."""
|
||||
Entries: FileEntry* # FileEntry array
|
||||
Count: int
|
||||
Capacity: int
|
||||
|
||||
|
||||
# create_scan_result - create the scan result container
|
||||
def create_scan_result(pool: memhub.MemBuddy*) -> ScanResult*:
|
||||
"""Create the scan result container with MAX_FILES preallocated slots."""
|
||||
if pool is None: return None
|
||||
size: sizet = MAX_FILES * FileEntry.__sizeof__()
|
||||
# use stdlib.malloc to avoid exhausting the mbuddy pool (consistent with StubMerger)
|
||||
entries: FileEntry* = stdlib.malloc(size)
|
||||
if entries is None: return None
|
||||
string.memset(entries, 0, size)
|
||||
|
||||
result: ScanResult* = stdlib.malloc(ScanResult.__sizeof__())
|
||||
if result is None: return None
|
||||
string.memset(result, 0, ScanResult.__sizeof__())
|
||||
result.Entries = entries
|
||||
result.Count = 0
|
||||
result.Capacity = MAX_FILES
|
||||
return result
|
||||
|
||||
|
||||
# add_file_entry - append a file entry to the scan result
|
||||
def add_file_entry(result: ScanResult*, pool: memhub.MemBuddy*, abs_path: str, rel_path: str, sha1: str, inc_dir: str) -> int:
|
||||
"""Append a file entry to the scan result; inc_dir is the owning includes root."""
|
||||
if result is None or pool is None: return 1
|
||||
if result.Count >= result.Capacity: return 1
|
||||
|
||||
# compute module name: '/' -> '.', strip .vp/.py (Utils handles __init__.vp)
|
||||
mod_buf: str = Utils.PathToModuleName(rel_path, 0)
|
||||
if mod_buf is None: return 1
|
||||
|
||||
# get the entry address
|
||||
entry_size: sizet = FileEntry.__sizeof__()
|
||||
entry_addr: uint64 = uint64(result.Entries) + result.Count * entry_size
|
||||
entry: FileEntry* = (FileEntry*)(entry_addr)
|
||||
if entry is None: return 1
|
||||
|
||||
# copy the path string
|
||||
abs_len: sizet = string.strlen(abs_path)
|
||||
abs_buf: str = stdlib.malloc(abs_len + 1)
|
||||
if abs_buf is None: return 1
|
||||
string.strcpy(abs_buf, abs_path)
|
||||
entry.Path = abs_buf
|
||||
|
||||
# copy the SHA1
|
||||
sha1_buf: str = stdlib.malloc(17)
|
||||
if sha1_buf is None: return 1
|
||||
string.strcpy(sha1_buf, sha1)
|
||||
entry.Sha1 = sha1_buf
|
||||
|
||||
# copy the relative path
|
||||
rel_buf: str = stdlib.malloc(string.strlen(rel_path) + 1)
|
||||
if rel_buf is None: return 1
|
||||
string.strcpy(rel_buf, rel_path)
|
||||
entry.RelPath = rel_buf
|
||||
|
||||
# copy the module name
|
||||
entry.ModuleName = mod_buf
|
||||
|
||||
# copy the owning includes root dir (used by Phase2 path joining)
|
||||
if inc_dir is not None:
|
||||
inc_len: sizet = string.strlen(inc_dir)
|
||||
inc_buf: str = stdlib.malloc(inc_len + 1)
|
||||
if inc_buf is None: return 1
|
||||
string.strcpy(inc_buf, inc_dir)
|
||||
entry.IncDir = inc_buf
|
||||
|
||||
result.Count += 1
|
||||
return 0
|
||||
|
||||
|
||||
# compute_file_sha1 - read file content and compute SHA1 (CRLF->LF, matching Projectrans.py)
|
||||
def compute_file_sha1(pool: memhub.MemBuddy*, file_path: str) -> str:
|
||||
"""Read a file, normalize CRLF to LF, and return the first 16 SHA1 hex chars."""
|
||||
if pool is None or file_path is None: return None
|
||||
|
||||
# read file content; BUF_SIZE must match SRC_BUF_SIZE (1MB) or trailing edits won't change SHA1
|
||||
BUF_SIZE: sizet = 1048576
|
||||
# Refuse larger files: hashing only the first 1 MiB gives two files that differ later the same
|
||||
# SHA1, and the SHA1 keys the stub/cache files (colliding modules would reuse each other's IR).
|
||||
sf_sz: io.File* = io.File(file_path, io.MODE.R)
|
||||
if sf_sz.closed: return None
|
||||
too_big: int = 0
|
||||
if sf_sz.size() >= BUF_SIZE: too_big = 1
|
||||
sf_sz.close()
|
||||
if too_big != 0:
|
||||
VLogger.error(f"source file exceeds the {BUF_SIZE}-byte limit and cannot be hashed completely: {file_path}", "IncludesScanner")
|
||||
return None
|
||||
buf: bytes = Utils.read_file_to_buf(file_path, BUF_SIZE)
|
||||
if buf is None: return None
|
||||
|
||||
# strip \r in place (CRLF -> LF), matching Projectrans.py text-mode reads
|
||||
buf_len: sizet = string.strlen(buf)
|
||||
write_pos: sizet = 0
|
||||
for ch in buf:
|
||||
if ch != '\r':
|
||||
buf[write_pos] = ch
|
||||
write_pos += 1
|
||||
|
||||
# add a null terminator
|
||||
buf[write_pos] = 0
|
||||
|
||||
# compute SHA1
|
||||
ctx: hashlib.sha1* = hashlib.sha1()
|
||||
if ctx is None:
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
ctx.update(buf)
|
||||
|
||||
digest: bytes = stdlib.malloc(hashlib.SHA1_DIGEST_LEN)
|
||||
if digest is None:
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
ctx.final(digest)
|
||||
|
||||
# convert to hex (first 8 bytes = 16 hex chars; stdlib allocation unchanged)
|
||||
hex_buf: str = Utils.bytes_to_hex(pool, digest, 8, 0, 1)
|
||||
stdlib.free(buf)
|
||||
stdlib.free(digest)
|
||||
return hex_buf
|
||||
|
||||
|
||||
# scan_directory_recursive - recursively scan a directory via os.scandir
|
||||
def scan_directory_recursive(pool: memhub.MemBuddy*, root_dir: str, rel_prefix: str, result: ScanResult*, inc_dir: str) -> int:
|
||||
"""Recursively collect .vp/.py files under a directory; inc_dir is the includes root."""
|
||||
if pool is None or root_dir is None or result is None: return 1
|
||||
subdir_failed: int = 0
|
||||
|
||||
reader: os.DirReader* = os.scandir(root_dir)
|
||||
if reader is None: return 1
|
||||
entry: os.DirEntry = os.DirEntry()
|
||||
|
||||
while True:
|
||||
rr: int = os.readdir(reader, &entry)
|
||||
if rr == 0: break
|
||||
if rr < 0:
|
||||
os.closedir(reader)
|
||||
return 1
|
||||
fname: str = entry.name
|
||||
fname_len: sizet = string.strlen(fname)
|
||||
is_dir: int = 1 if entry.is_dir else 0
|
||||
full_path: bytes = Utils.JoinPath(root_dir, fname)
|
||||
if full_path is None:
|
||||
os.closedir(reader)
|
||||
return 1
|
||||
|
||||
prefix_len: sizet = 0
|
||||
if rel_prefix is not None: prefix_len = string.strlen(rel_prefix)
|
||||
rel_path: bytes = None
|
||||
if rel_prefix is not None and prefix_len > 0: rel_path = Utils.JoinPath(rel_prefix, fname)
|
||||
else:
|
||||
rel_path = stdlib.malloc(fname_len + 1)
|
||||
if rel_path is None:
|
||||
os.closedir(reader)
|
||||
return 1
|
||||
string.strcpy(rel_path, fname)
|
||||
if rel_path is None:
|
||||
os.closedir(reader)
|
||||
return 1
|
||||
|
||||
if is_dir != 0:
|
||||
if scan_directory_recursive(pool, full_path, rel_path, result, inc_dir) != 0: subdir_failed += 1
|
||||
else:
|
||||
is_py: int = 0
|
||||
if Utils.is_vp_at(fname, 0, fname_len) != 0: is_py = 1
|
||||
if is_py != 0:
|
||||
is_pycache: int = 0
|
||||
if rel_prefix is not None:
|
||||
if string.strcmp(rel_prefix, "__pycache__") == 0: is_pycache = 1
|
||||
if is_pycache == 0:
|
||||
sha1: str = compute_file_sha1(pool, full_path)
|
||||
if sha1 is not None:
|
||||
if add_file_entry(result, pool, full_path, rel_path, sha1, inc_dir) != 0:
|
||||
VLogger.error(f"includes scan could not register '{full_path}' (the {MAX_FILES}-file capacity or an allocation failed); raise MAX_FILES", "scan")
|
||||
VLogger.debug(f"scan: {rel_path} -> {sha1}", "scan")
|
||||
else: VLogger.warning(f"SHA1 computation failed: {full_path}", "scan")
|
||||
|
||||
os.closedir(reader)
|
||||
if subdir_failed > 0: VLogger.error(f"subdirectory scan failed for {subdir_failed} entries", "scan")
|
||||
return 0
|
||||
|
||||
|
||||
# scan_includes - scan entry point: return all .vp file paths and SHA1s
|
||||
def scan_includes(pool: memhub.MemBuddy*, includes_dirs: str) -> ScanResult*:
|
||||
"""Scan space-separated includes dirs and return all scanned .vp/.py files."""
|
||||
if pool is None or includes_dirs is None: return None
|
||||
|
||||
result: ScanResult* = create_scan_result(pool)
|
||||
if result is None: return None
|
||||
|
||||
# parse space-separated includes dirs, recursively scanning and merging each
|
||||
dirs_total: sizet = string.strlen(includes_dirs)
|
||||
d_i: sizet = 0
|
||||
while d_i < dirs_total:
|
||||
while d_i < dirs_total and includes_dirs[d_i] == ' ': d_i += 1
|
||||
if d_i >= dirs_total: break
|
||||
d_start: sizet = d_i
|
||||
while d_i < dirs_total and includes_dirs[d_i] != ' ': d_i += 1
|
||||
d_len: sizet = d_i - d_start
|
||||
if d_len == 0: continue
|
||||
dir_buf: bytes = stdlib.malloc(d_len + 1)
|
||||
if dir_buf is None: VLogger.error("failed to allocate the includes directory; aborting scan", "scan")
|
||||
string.strncpy(dir_buf, includes_dirs + d_start, d_len)
|
||||
dir_buf[d_len] = '\0'
|
||||
scan_directory_recursive(pool, dir_buf, None, result, dir_buf)
|
||||
stdlib.free(dir_buf)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# _PathExistsA - path existence check (os.exists)
|
||||
def _PathExistsA(path: str) -> int:
|
||||
"""Return 1 if the path exists, else 0."""
|
||||
return 1 if os.exists(path) else 0
|
||||
|
||||
|
||||
# _TryResolveModuleFile - try one candidate path and write outputs (1=hit, 0=miss);
|
||||
# out_abs_path/out_rel_path/out_inc_dir are rawptr output slots.
|
||||
def _TryResolveModuleFile(inc_dir: str, rel_seg: bytes, rel_len: sizet, suffix: str, out_abs_path: rawptr, out_rel_path: rawptr, out_inc_dir: rawptr) -> int:
|
||||
if inc_dir is None or rel_seg is None or suffix is None: return 0
|
||||
inc_len: sizet = string.strlen(inc_dir)
|
||||
suf_len: sizet = string.strlen(suffix)
|
||||
# candidate absolute path: {inc}/{rel}{suffix} (hand-built to avoid %. *s)
|
||||
cand_len: sizet = inc_len + 1 + rel_len + suf_len
|
||||
cand: bytes = stdlib.malloc(cand_len + 1)
|
||||
if cand is None: return 0
|
||||
string.strcpy(cand, inc_dir)
|
||||
cand[inc_len] = '/'
|
||||
string.strncpy(cand + inc_len + 1, rel_seg, rel_len)
|
||||
string.strcpy(cand + inc_len + 1 + rel_len, suffix)
|
||||
if _PathExistsA(cand) == 0:
|
||||
stdlib.free(cand)
|
||||
return 0
|
||||
# path relative to the includes root: {rel}{suffix}
|
||||
rel_path: bytes = stdlib.malloc(rel_len + suf_len + 1)
|
||||
if rel_path is None:
|
||||
stdlib.free(cand)
|
||||
return 0
|
||||
string.strncpy(rel_path, rel_seg, rel_len)
|
||||
rel_path[rel_len] = '\0'
|
||||
string.strcat(rel_path, suffix)
|
||||
# Deep-copy the owning includes root dir; allocate everything before writing
|
||||
# slots to avoid a half-written state when inc allocation fails.
|
||||
inc_buf: bytes = None
|
||||
if out_inc_dir is not None:
|
||||
inc_buf = stdlib.malloc(inc_len + 1)
|
||||
if inc_buf is None:
|
||||
stdlib.free(cand)
|
||||
stdlib.free(rel_path)
|
||||
return 0
|
||||
string.strcpy(inc_buf, inc_dir)
|
||||
# Write output slots (stdlib deep copies; caller frees). Slots must be cast to
|
||||
# (sizet*): rawptr* folds to rawptr and [0] becomes a 1-byte access.
|
||||
if out_abs_path is not None:
|
||||
abs_slot: sizet* = (sizet*)(out_abs_path)
|
||||
abs_slot[0] = sizet(cand)
|
||||
else: stdlib.free(cand)
|
||||
if out_rel_path is not None:
|
||||
rel_slot: sizet* = (sizet*)(out_rel_path)
|
||||
rel_slot[0] = sizet(rel_path)
|
||||
else: stdlib.free(rel_path)
|
||||
if out_inc_dir is not None and inc_buf is not None:
|
||||
inc_slot: sizet* = (sizet*)(out_inc_dir)
|
||||
inc_slot[0] = sizet(inc_buf)
|
||||
return 1
|
||||
|
||||
|
||||
# ResolveModuleToPath - module name -> file path, resolved on demand (no full scan).
|
||||
# Tries each includes dir, preferring file modules over packages. Returns 0 found, 1 not.
|
||||
def ResolveModuleToPath(includes_dirs: str, mod_name: str, out_abs_path: rawptr, out_rel_path: rawptr, out_inc_dir: rawptr) -> int:
|
||||
if includes_dirs is None or mod_name is None: return 1
|
||||
mn_len: sizet = string.strlen(mod_name)
|
||||
if mn_len == 0: return 1
|
||||
# mod_name -> relative path segment ('.' -> '/'): ast.parser -> ast/parser
|
||||
rel_seg: bytes = stdlib.malloc(mn_len + 1)
|
||||
if rel_seg is None: return 1
|
||||
rel_len: sizet = 0
|
||||
for mi in range(int(mn_len)):
|
||||
ch_m: char = mod_name[mi]
|
||||
if ch_m == '.': rel_seg[rel_len] = '/'
|
||||
else: rel_seg[rel_len] = ch_m
|
||||
rel_len += 1
|
||||
rel_seg[rel_len] = '\0'
|
||||
|
||||
found: int = 0
|
||||
d_total: sizet = string.strlen(includes_dirs)
|
||||
d_i: sizet = 0
|
||||
while d_i < d_total:
|
||||
while d_i < d_total and includes_dirs[d_i] == ' ': d_i += 1
|
||||
if d_i >= d_total: break
|
||||
d_start: sizet = d_i
|
||||
while d_i < d_total and includes_dirs[d_i] != ' ': d_i += 1
|
||||
d_len: sizet = d_i - d_start
|
||||
if d_len == 0: continue
|
||||
inc_dir_buf: bytes = stdlib.malloc(d_len + 1)
|
||||
if inc_dir_buf is None: continue
|
||||
string.strncpy(inc_dir_buf, includes_dirs + d_start, d_len)
|
||||
inc_dir_buf[d_len] = '\0'
|
||||
# candidate 1: {inc}/{rel}.vp (file module)
|
||||
if _TryResolveModuleFile(inc_dir_buf, rel_seg, rel_len, ".vp", out_abs_path, out_rel_path, out_inc_dir) != 0:
|
||||
found = 1
|
||||
stdlib.free(inc_dir_buf)
|
||||
break
|
||||
# candidate 2: {inc}/{rel}/__init__.vp (package module)
|
||||
if _TryResolveModuleFile(inc_dir_buf, rel_seg, rel_len, "/__init__.vp", out_abs_path, out_rel_path, out_inc_dir) != 0:
|
||||
found = 1
|
||||
stdlib.free(inc_dir_buf)
|
||||
if found != 0: break
|
||||
stdlib.free(rel_seg)
|
||||
if found != 0: return 0
|
||||
return 1
|
||||
+2097
-2109
File diff suppressed because it is too large.
Load diff
+272
-307
@@ -1,307 +1,272 @@
|
||||
from stdint import *
|
||||
import stdio
|
||||
import stdlib
|
||||
import string
|
||||
import viperlib
|
||||
import os
|
||||
import os.path
|
||||
import win32.win32base
|
||||
import win32.win32file
|
||||
import win32.win32process
|
||||
import lib.Phase.BuildPipeline as BuildPipeline
|
||||
|
||||
|
||||
define SELF_EXE_CAP = 4096
|
||||
define SELF_BUILD_DIR = "__vpcbuild_2__"
|
||||
define COPY_DELAY_SECONDS = 5
|
||||
|
||||
|
||||
def _GetSelfExe() -> str:
|
||||
buf: bytes = stdlib.malloc(sizet(SELF_EXE_CAP))
|
||||
if buf is None:
|
||||
return None
|
||||
n: uint32 = win32.win32file.GetModuleFileNameA(None, buf, uint32(SELF_EXE_CAP))
|
||||
if int(n) <= 0:
|
||||
stdlib.free(buf)
|
||||
return None
|
||||
ni: int = int(n)
|
||||
if ni >= SELF_EXE_CAP:
|
||||
ni = SELF_EXE_CAP - 1
|
||||
buf[ni] = 0
|
||||
return str(buf)
|
||||
|
||||
|
||||
def _TryRoot(dir: str) -> str:
|
||||
if dir is None or len(dir) == 0:
|
||||
return None
|
||||
root: str = os.path.normpath(dir)
|
||||
if root is None:
|
||||
return None
|
||||
project: str = os.path.join(root, "project.vpj")
|
||||
copyself_project: str = os.path.join(os.path.join(root, "copyself"), "project.vpj")
|
||||
if project is not None and copyself_project is not None:
|
||||
if os.isfile(project) and os.isfile(copyself_project):
|
||||
return root
|
||||
return None
|
||||
|
||||
|
||||
def _FindViperCRoot(self_exe: str) -> str:
|
||||
base: str = os.path.dirname(self_exe)
|
||||
if base is None:
|
||||
base = ""
|
||||
parent1: str = os.path.dirname(base)
|
||||
parent2: str = os.path.dirname(parent1)
|
||||
|
||||
# Executable-derived candidates first: cwd is normally the workspace root,
|
||||
# not the compiler tree.
|
||||
root: str = _TryRoot(base)
|
||||
if root is not None:
|
||||
return root
|
||||
root = _TryRoot(parent1)
|
||||
if root is not None:
|
||||
return root
|
||||
root = _TryRoot(parent2)
|
||||
if root is not None:
|
||||
return root
|
||||
root = _TryRoot(os.path.join(base, "ViperC"))
|
||||
if root is not None:
|
||||
return root
|
||||
root = _TryRoot(os.path.join(base, "../ViperC"))
|
||||
if root is not None:
|
||||
return root
|
||||
root = _TryRoot(os.path.join(base, "../../ViperC"))
|
||||
if root is not None:
|
||||
return root
|
||||
|
||||
# Fallback only when the executable is detached from the compiler tree.
|
||||
cwd: str = os.getcwd()
|
||||
root = _TryRoot(cwd)
|
||||
if root is not None:
|
||||
return root
|
||||
if cwd is not None:
|
||||
root = _TryRoot(os.path.join(cwd, "ViperC"))
|
||||
if root is not None:
|
||||
return root
|
||||
root = _TryRoot(os.path.join(os.path.dirname(cwd), "ViperC"))
|
||||
if root is not None:
|
||||
return root
|
||||
return None
|
||||
|
||||
|
||||
def CollectForwardedArgs(argc: int, argv: str*) -> str:
|
||||
"""--build-self: argv minus --build-self / --project / --build-dir (and their values).
|
||||
|
||||
No whitelist: any current or future flag is forwarded verbatim, so a self-build can
|
||||
enable the same modes as a normal build (e.g. `viperc --build-self --spend-file p.txt
|
||||
--vpf 3`). The three excluded entries are the ones the self-build invocation owns.
|
||||
"""
|
||||
if argv is None or argc <= 1: return ""
|
||||
total: sizet = 1
|
||||
i: int = 1
|
||||
while i < argc:
|
||||
a: str = argv[i]
|
||||
if a is not None:
|
||||
if string.strcmp(a, "--build-self") == 0:
|
||||
i += 1
|
||||
continue
|
||||
if string.strcmp(a, "--project") == 0 or string.strcmp(a, "--build-dir") == 0:
|
||||
i += 2
|
||||
continue
|
||||
total += len(a) + 1
|
||||
i += 1
|
||||
buf: str = stdlib.malloc(total + 1)
|
||||
if buf is None: return ""
|
||||
buf[0] = '\0'
|
||||
i = 1
|
||||
while i < argc:
|
||||
a2: str = argv[i]
|
||||
if a2 is not None:
|
||||
if string.strcmp(a2, "--build-self") == 0:
|
||||
i += 1
|
||||
continue
|
||||
if string.strcmp(a2, "--project") == 0 or string.strcmp(a2, "--build-dir") == 0:
|
||||
i += 2
|
||||
continue
|
||||
if buf[0] != '\0': string.strcat(buf, " ")
|
||||
string.strcat(buf, a2)
|
||||
i += 1
|
||||
return buf
|
||||
|
||||
|
||||
def _FormatSelfBuildCommand(self_exe: str, project: str, build_dir: str, extra: str) -> str:
|
||||
# `extra` is the caller's forwarded argument string (empty when nothing was forwarded):
|
||||
# the rebuilt compiler then carries exactly the modes the user asked for.
|
||||
extra_len: sizet = 0
|
||||
if extra is not None: extra_len = len(extra)
|
||||
need: sizet = len(self_exe) + len(project) + len(build_dir) + extra_len + 64
|
||||
buf: bytes = stdlib.malloc(need)
|
||||
if buf is None:
|
||||
return None
|
||||
viperlib.snprintf(buf, need, "\"%s\" --project \"%s\" --clean --build-dir \"%s\"",
|
||||
self_exe, project, build_dir)
|
||||
if extra is not None and extra_len > 0:
|
||||
pos: sizet = string.strlen(str(buf))
|
||||
viperlib.snprintf(buf + pos, need - pos, " %s", extra)
|
||||
return str(buf)
|
||||
|
||||
|
||||
def _FindCopySelfExe(root: str) -> str:
|
||||
base: str = os.path.join(root, "copyself")
|
||||
standard: str = os.path.join(os.path.join(os.path.join(base, "__vpcbuild__"), "output"), "copyself.exe")
|
||||
if os.isfile(standard):
|
||||
return standard
|
||||
legacy: str = os.path.join(os.path.join(base, "output"), "copyself.exe")
|
||||
if os.isfile(legacy):
|
||||
return legacy
|
||||
return None
|
||||
|
||||
|
||||
def _FormatCopySelfCommand(copyself_exe: str) -> str:
|
||||
need: sizet = len(copyself_exe) + 128
|
||||
buf: bytes = stdlib.malloc(need)
|
||||
if buf is None:
|
||||
return None
|
||||
viperlib.snprintf(buf, need, "\"%s\" %d ../../__vpcbuild_2__ ../../__vpcbuild__",
|
||||
copyself_exe, COPY_DELAY_SECONDS)
|
||||
return str(buf)
|
||||
|
||||
|
||||
def _SpawnDetached(cmd: str, cwd: str) -> int:
|
||||
if cmd is None or cwd is None:
|
||||
stdio.printf("FAIL: --build-self cannot launch copyself without a command and working directory\n")
|
||||
return -1
|
||||
clen: sizet = len(cmd)
|
||||
cmd_line: bytes = stdlib.malloc(clen + 1)
|
||||
if cmd_line is None:
|
||||
stdio.printf("FAIL: --build-self failed to allocate the copyself command line\n")
|
||||
return -1
|
||||
string.strcpy(cmd_line, cmd)
|
||||
|
||||
si: win32.win32process.STARTUPINFOA = win32.win32process.STARTUPINFOA()
|
||||
pi: win32.win32process.PROCESS_INFORMATION = win32.win32process.PROCESS_INFORMATION()
|
||||
string.memset(&si, 0, win32.win32process.STARTUPINFOA.__sizeof__())
|
||||
string.memset(&pi, 0, win32.win32process.PROCESS_INFORMATION.__sizeof__())
|
||||
si.cb = win32.win32process.STARTUPINFOA.__sizeof__()
|
||||
|
||||
ok: int = win32.win32process.CreateProcessA(None, cmd_line, None, None, 1, 0,
|
||||
None, cwd, &si, &pi)
|
||||
if ok == 0:
|
||||
err: uint32 = win32.win32base.GetLastError()
|
||||
stdio.printf("FAIL: --build-self could not launch copyself (Win32 error %d)\n", int(err))
|
||||
stdio.printf(" command: %s\n", cmd)
|
||||
stdlib.free(cmd_line)
|
||||
return -1
|
||||
|
||||
stdio.printf("build-self: copyself launched (pid=%d)\n", int(pi.dwProcessId))
|
||||
win32.win32base.CloseHandle(pi.hProcess)
|
||||
win32.win32base.CloseHandle(pi.hThread)
|
||||
stdlib.free(cmd_line)
|
||||
return 0
|
||||
|
||||
|
||||
# _SelfExeDir - directory containing the running compiler ("D:/x/viperc.exe" -> "D:/x").
|
||||
def _SelfExeDir(self_exe: str) -> str:
|
||||
if self_exe is None: return None
|
||||
n: sizet = string.strlen(self_exe)
|
||||
cut: sizet = n
|
||||
while cut > 0:
|
||||
ch: char = self_exe[cut - 1]
|
||||
if ch == '\\' or ch == '/':
|
||||
cut -= 1
|
||||
break
|
||||
cut -= 1
|
||||
if cut >= n: return None
|
||||
out: str = stdlib.malloc(cut + 1)
|
||||
if out is None: return None
|
||||
if cut > 0: string.strncpy(out, self_exe, cut)
|
||||
out[cut] = '\0'
|
||||
return out
|
||||
|
||||
|
||||
# _CrashHint - name the common NTSTATUS exit codes. The child inherits the parent console, so
|
||||
# its own output is already visible; these codes are raised as fail-fast and carry no SEH
|
||||
# exception, which is why a traceback handler inside the child cannot print a stack.
|
||||
def _CrashHint(rc: int) -> None:
|
||||
u: uint32 = uint32(rc)
|
||||
if u == uint32(0xC0000005):
|
||||
stdio.printf(" child crashed: access violation (0xC0000005)\n")
|
||||
elif u == uint32(0xC0000374):
|
||||
stdio.printf(" child crashed: heap corruption (0xC0000374) -- fail-fast, no SEH traceback\n")
|
||||
elif u == uint32(0xC00000FD):
|
||||
stdio.printf(" child crashed: stack overflow (0xC00000FD)\n")
|
||||
elif u == uint32(0xC0000409):
|
||||
stdio.printf(" child crashed: fail-fast / security check (0xC0000409)\n")
|
||||
|
||||
|
||||
def RunSelfBuild(extra: str) -> int:
|
||||
self_exe: str = _GetSelfExe()
|
||||
if self_exe is None:
|
||||
stdio.printf("FAIL: --build-self could not resolve the compiler executable path\n")
|
||||
return 1
|
||||
root: str = _FindViperCRoot(self_exe)
|
||||
if root is None:
|
||||
stdio.printf("FAIL: --build-self could not locate the ViperC root\n")
|
||||
stdio.printf(" expected both: project.vpj and copyself/project.vpj\n")
|
||||
return 1
|
||||
|
||||
project: str = os.path.join(root, "project.vpj")
|
||||
build_dir: str = os.path.join(root, SELF_BUILD_DIR)
|
||||
copyself_exe: str = _FindCopySelfExe(root)
|
||||
if copyself_exe is None:
|
||||
stdio.printf("FAIL: copyself.exe not found under %s\n", os.path.join(root, "copyself"))
|
||||
stdio.printf(" expected: output/copyself.exe or __vpcbuild__/output/copyself.exe\n")
|
||||
stdio.printf(" build it first: viperc --project %s --clean\n",
|
||||
os.path.join(os.path.join(root, "copyself"), "project.vpj"))
|
||||
return 1
|
||||
|
||||
cmd: str = _FormatSelfBuildCommand(self_exe, project, build_dir, extra)
|
||||
if cmd is None:
|
||||
stdio.printf("FAIL: --build-self failed to build the compiler command line\n")
|
||||
return 1
|
||||
stdio.printf("build-self: %s\n", cmd)
|
||||
rc: int = BuildPipeline._run_cmd_wait(cmd)
|
||||
if rc != 0:
|
||||
stdio.printf("FAIL: --build-self compilation failed (exit=%d)\n", rc)
|
||||
_CrashHint(rc)
|
||||
return 1
|
||||
|
||||
built_exe: str = os.path.join(os.path.join(build_dir, "output"), "viperc.exe")
|
||||
if not os.isfile(built_exe):
|
||||
stdio.printf("FAIL: --build-self did not produce %s\n", built_exe)
|
||||
return 1
|
||||
|
||||
# The requested arguments are relative to ViperC/copyself/output. Use that
|
||||
# directory as the launch cwd even when copyself.exe lives under
|
||||
# copyself/__vpcbuild__/output.
|
||||
copyself_cwd: str = os.path.join(os.path.join(root, "copyself"), "output")
|
||||
if not os.isdir(copyself_cwd):
|
||||
if os.mkdir(copyself_cwd) != 0:
|
||||
if not os.isdir(copyself_cwd):
|
||||
err_mkdir: ULONG = win32.win32base.GetLastError()
|
||||
stdio.printf("FAIL: --build-self could not create %s (Win32 error %d)\n",
|
||||
copyself_cwd, int(err_mkdir))
|
||||
return 1
|
||||
# copyself replaces ViperC\__vpcbuild__ wholesale, so everything the compiler needs next to
|
||||
# viperc.exe must be in the new output directory *before* the copy runs. ircore.dll is the
|
||||
# important one: without it every PhaseB module silently falls back to spawning
|
||||
# mlir-opt/mlir-translate/llc (3 processes per module), which is both slower and a
|
||||
# different code path than the one being profiled.
|
||||
self_dir: str = _SelfExeDir(self_exe)
|
||||
if self_dir is not None:
|
||||
src_dll: str = os.path.join(self_dir, "ircore.dll")
|
||||
dst_dll: str = os.path.join(os.path.join(build_dir, "output"), "ircore.dll")
|
||||
if os.isfile(src_dll) and not os.isfile(dst_dll):
|
||||
cop: BOOL = win32.win32file.CopyFileA(src_dll, dst_dll, 0)
|
||||
if cop == 0:
|
||||
stdio.printf("build-self: warning: could not copy ircore.dll next to the new viperc.exe\n")
|
||||
else:
|
||||
stdio.printf("build-self: copied ircore.dll into the new output directory\n")
|
||||
|
||||
copy_cmd: str = _FormatCopySelfCommand(copyself_exe)
|
||||
if copy_cmd is None:
|
||||
stdio.printf("FAIL: --build-self failed to build the copyself command line\n")
|
||||
return 1
|
||||
if _SpawnDetached(copy_cmd, copyself_cwd) != 0:
|
||||
return 1
|
||||
stdio.printf("build-self: __vpcbuild__ will be replaced after %d seconds\n", COPY_DELAY_SECONDS)
|
||||
return 0
|
||||
from stdint import *
|
||||
import stdio
|
||||
import stdlib
|
||||
import string
|
||||
import viperlib
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import shutil
|
||||
import lib.Phase.BuildPipeline as BuildPipeline
|
||||
|
||||
|
||||
define SELF_EXE_CAP = 4096
|
||||
define SELF_BUILD_DIR = "__vpcbuild_2__"
|
||||
define COPY_DELAY_SECONDS = 5
|
||||
|
||||
|
||||
def _GetSelfExe() -> str:
|
||||
return sys.executable()
|
||||
|
||||
|
||||
def _TryRoot(dir: str) -> str:
|
||||
if dir is None or len(dir) == 0:
|
||||
return None
|
||||
root: str = os.path.normpath(dir)
|
||||
if root is None:
|
||||
return None
|
||||
project: str = os.path.join(root, "project.vpj")
|
||||
copyself_project: str = os.path.join(os.path.join(root, "copyself"), "project.vpj")
|
||||
if project is not None and copyself_project is not None:
|
||||
if os.isfile(project) and os.isfile(copyself_project):
|
||||
return root
|
||||
return None
|
||||
|
||||
|
||||
def _FindViperCRoot(self_exe: str) -> str:
|
||||
base: str = os.path.dirname(self_exe)
|
||||
if base is None:
|
||||
base = ""
|
||||
parent1: str = os.path.dirname(base)
|
||||
parent2: str = os.path.dirname(parent1)
|
||||
|
||||
# Executable-derived candidates first: cwd is normally the workspace root,
|
||||
# not the compiler tree.
|
||||
root: str = _TryRoot(base)
|
||||
if root is not None:
|
||||
return root
|
||||
root = _TryRoot(parent1)
|
||||
if root is not None:
|
||||
return root
|
||||
root = _TryRoot(parent2)
|
||||
if root is not None:
|
||||
return root
|
||||
root = _TryRoot(os.path.join(base, "ViperC"))
|
||||
if root is not None:
|
||||
return root
|
||||
root = _TryRoot(os.path.join(base, "../ViperC"))
|
||||
if root is not None:
|
||||
return root
|
||||
root = _TryRoot(os.path.join(base, "../../ViperC"))
|
||||
if root is not None:
|
||||
return root
|
||||
|
||||
# Fallback only when the executable is detached from the compiler tree.
|
||||
cwd: str = os.getcwd()
|
||||
root = _TryRoot(cwd)
|
||||
if root is not None:
|
||||
return root
|
||||
if cwd is not None:
|
||||
root = _TryRoot(os.path.join(cwd, "ViperC"))
|
||||
if root is not None:
|
||||
return root
|
||||
root = _TryRoot(os.path.join(os.path.dirname(cwd), "ViperC"))
|
||||
if root is not None:
|
||||
return root
|
||||
return None
|
||||
|
||||
|
||||
def CollectForwardedArgs(argc: int, argv: str*) -> str:
|
||||
"""--build-self: argv minus --build-self / --project / --build-dir (and their values).
|
||||
|
||||
No whitelist: any current or future flag is forwarded verbatim, so a self-build can
|
||||
enable the same modes as a normal build (e.g. `viperc --build-self --spend-file p.txt
|
||||
--vpf 3`). The three excluded entries are the ones the self-build invocation owns.
|
||||
"""
|
||||
if argv is None or argc <= 1: return ""
|
||||
total: sizet = 1
|
||||
i: int = 1
|
||||
while i < argc:
|
||||
a: str = argv[i]
|
||||
if a is not None:
|
||||
if string.strcmp(a, "--build-self") == 0:
|
||||
i += 1
|
||||
continue
|
||||
if string.strcmp(a, "--project") == 0 or string.strcmp(a, "--build-dir") == 0:
|
||||
i += 2
|
||||
continue
|
||||
total += len(a) + 1
|
||||
i += 1
|
||||
buf: str = stdlib.malloc(total + 1)
|
||||
if buf is None: return ""
|
||||
buf[0] = '\0'
|
||||
i = 1
|
||||
while i < argc:
|
||||
a2: str = argv[i]
|
||||
if a2 is not None:
|
||||
if string.strcmp(a2, "--build-self") == 0:
|
||||
i += 1
|
||||
continue
|
||||
if string.strcmp(a2, "--project") == 0 or string.strcmp(a2, "--build-dir") == 0:
|
||||
i += 2
|
||||
continue
|
||||
if buf[0] != '\0': string.strcat(buf, " ")
|
||||
string.strcat(buf, a2)
|
||||
i += 1
|
||||
return buf
|
||||
|
||||
|
||||
def _FormatSelfBuildCommand(self_exe: str, project: str, build_dir: str, extra: str) -> str:
|
||||
# `extra` is the caller's forwarded argument string (empty when nothing was forwarded):
|
||||
# the rebuilt compiler then carries exactly the modes the user asked for.
|
||||
extra_len: sizet = 0
|
||||
if extra is not None: extra_len = len(extra)
|
||||
need: sizet = len(self_exe) + len(project) + len(build_dir) + extra_len + 64
|
||||
buf: bytes = stdlib.malloc(need)
|
||||
if buf is None:
|
||||
return None
|
||||
viperlib.snprintf(buf, need, "\"%s\" --project \"%s\" --clean --build-dir \"%s\"",
|
||||
self_exe, project, build_dir)
|
||||
if extra is not None and extra_len > 0:
|
||||
pos: sizet = string.strlen(str(buf))
|
||||
viperlib.snprintf(buf + pos, need - pos, " %s", extra)
|
||||
return str(buf)
|
||||
|
||||
|
||||
def _FindCopySelfExe(root: str) -> str:
|
||||
base: str = os.path.join(root, "copyself")
|
||||
standard: str = os.path.join(os.path.join(os.path.join(base, "__vpcbuild__"), "output"), "copyself.exe")
|
||||
if os.isfile(standard):
|
||||
return standard
|
||||
legacy: str = os.path.join(os.path.join(base, "output"), "copyself.exe")
|
||||
if os.isfile(legacy):
|
||||
return legacy
|
||||
return None
|
||||
|
||||
|
||||
def _FormatCopySelfCommand(copyself_exe: str) -> str:
|
||||
need: sizet = len(copyself_exe) + 128
|
||||
buf: bytes = stdlib.malloc(need)
|
||||
if buf is None:
|
||||
return None
|
||||
viperlib.snprintf(buf, need, "\"%s\" %d ../../__vpcbuild_2__ ../../__vpcbuild__",
|
||||
copyself_exe, COPY_DELAY_SECONDS)
|
||||
return str(buf)
|
||||
|
||||
|
||||
def _SpawnDetached(cmd: str, cwd: str) -> int:
|
||||
if cmd is None or cwd is None:
|
||||
stdio.printf("FAIL: --build-self cannot launch copyself without a command and working directory\n")
|
||||
return -1
|
||||
pid: int = sys.spawn_detached(cmd, cwd)
|
||||
if pid < 0:
|
||||
stdio.printf("FAIL: --build-self could not launch copyself\n")
|
||||
stdio.printf(" command: %s\n", cmd)
|
||||
return -1
|
||||
stdio.printf("build-self: copyself launched (pid=%d)\n", pid)
|
||||
return 0
|
||||
|
||||
|
||||
# _SelfExeDir - directory containing the running compiler ("D:/x/viperc.exe" -> "D:/x").
|
||||
def _SelfExeDir(self_exe: str) -> str:
|
||||
if self_exe is None: return None
|
||||
n: sizet = string.strlen(self_exe)
|
||||
cut: sizet = n
|
||||
while cut > 0:
|
||||
ch: char = self_exe[cut - 1]
|
||||
if ch == '\\' or ch == '/':
|
||||
cut -= 1
|
||||
break
|
||||
cut -= 1
|
||||
if cut >= n: return None
|
||||
out: str = stdlib.malloc(cut + 1)
|
||||
if out is None: return None
|
||||
if cut > 0: string.strncpy(out, self_exe, cut)
|
||||
out[cut] = '\0'
|
||||
return out
|
||||
|
||||
|
||||
# _CrashHint - name the common NTSTATUS exit codes. The child inherits the parent console, so
|
||||
# its own output is already visible; these codes are raised as fail-fast and carry no SEH
|
||||
# exception, which is why a traceback handler inside the child cannot print a stack.
|
||||
def _CrashHint(rc: int) -> None:
|
||||
u: uint32 = uint32(rc)
|
||||
if u == uint32(0xC0000005):
|
||||
stdio.printf(" child crashed: access violation (0xC0000005)\n")
|
||||
elif u == uint32(0xC0000374):
|
||||
stdio.printf(" child crashed: heap corruption (0xC0000374) -- fail-fast, no SEH traceback\n")
|
||||
elif u == uint32(0xC00000FD):
|
||||
stdio.printf(" child crashed: stack overflow (0xC00000FD)\n")
|
||||
elif u == uint32(0xC0000409):
|
||||
stdio.printf(" child crashed: fail-fast / security check (0xC0000409)\n")
|
||||
|
||||
|
||||
def RunSelfBuild(extra: str) -> int:
|
||||
self_exe: str = _GetSelfExe()
|
||||
if self_exe is None:
|
||||
stdio.printf("FAIL: --build-self could not resolve the compiler executable path\n")
|
||||
return 1
|
||||
root: str = _FindViperCRoot(self_exe)
|
||||
if root is None:
|
||||
stdio.printf("FAIL: --build-self could not locate the ViperC root\n")
|
||||
stdio.printf(" expected both: project.vpj and copyself/project.vpj\n")
|
||||
return 1
|
||||
|
||||
project: str = os.path.join(root, "project.vpj")
|
||||
build_dir: str = os.path.join(root, SELF_BUILD_DIR)
|
||||
copyself_exe: str = _FindCopySelfExe(root)
|
||||
if copyself_exe is None:
|
||||
stdio.printf("FAIL: copyself.exe not found under %s\n", os.path.join(root, "copyself"))
|
||||
stdio.printf(" expected: output/copyself.exe or __vpcbuild__/output/copyself.exe\n")
|
||||
stdio.printf(" build it first: viperc --project %s --clean\n",
|
||||
os.path.join(os.path.join(root, "copyself"), "project.vpj"))
|
||||
return 1
|
||||
|
||||
cmd: str = _FormatSelfBuildCommand(self_exe, project, build_dir, extra)
|
||||
if cmd is None:
|
||||
stdio.printf("FAIL: --build-self failed to build the compiler command line\n")
|
||||
return 1
|
||||
stdio.printf("build-self: %s\n", cmd)
|
||||
rc: int = BuildPipeline._run_cmd_wait(cmd)
|
||||
if rc != 0:
|
||||
stdio.printf("FAIL: --build-self compilation failed (exit=%d)\n", rc)
|
||||
_CrashHint(rc)
|
||||
return 1
|
||||
|
||||
built_exe: str = os.path.join(os.path.join(build_dir, "output"), "viperc.exe")
|
||||
if not os.isfile(built_exe):
|
||||
stdio.printf("FAIL: --build-self did not produce %s\n", built_exe)
|
||||
return 1
|
||||
|
||||
# The requested arguments are relative to ViperC/copyself/output. Use that
|
||||
# directory as the launch cwd even when copyself.exe lives under
|
||||
# copyself/__vpcbuild__/output.
|
||||
copyself_cwd: str = os.path.join(os.path.join(root, "copyself"), "output")
|
||||
if not os.isdir(copyself_cwd):
|
||||
if os.mkdir(copyself_cwd) != 0:
|
||||
if not os.isdir(copyself_cwd):
|
||||
stdio.printf("FAIL: --build-self could not create %s\n", copyself_cwd)
|
||||
return 1
|
||||
# copyself replaces ViperC\__vpcbuild__ wholesale, so everything the compiler needs next to
|
||||
# viperc.exe must be in the new output directory *before* the copy runs. ircore.dll is the
|
||||
# important one: without it every PhaseB module silently falls back to spawning
|
||||
# mlir-opt/mlir-translate/llc (3 processes per module), which is both slower and a
|
||||
# different code path than the one being profiled.
|
||||
self_dir: str = _SelfExeDir(self_exe)
|
||||
if self_dir is not None:
|
||||
src_dll: str = os.path.join(self_dir, "ircore.dll")
|
||||
dst_dll: str = os.path.join(os.path.join(build_dir, "output"), "ircore.dll")
|
||||
if os.isfile(src_dll) and not os.isfile(dst_dll):
|
||||
if shutil.copyfile(src_dll, dst_dll) != 0:
|
||||
stdio.printf("build-self: warning: could not copy ircore.dll next to the new viperc.exe\n")
|
||||
else:
|
||||
stdio.printf("build-self: copied ircore.dll into the new output directory\n")
|
||||
|
||||
copy_cmd: str = _FormatCopySelfCommand(copyself_exe)
|
||||
if copy_cmd is None:
|
||||
stdio.printf("FAIL: --build-self failed to build the copyself command line\n")
|
||||
return 1
|
||||
if _SpawnDetached(copy_cmd, copyself_cwd) != 0:
|
||||
return 1
|
||||
stdio.printf("build-self: __vpcbuild__ will be replaced after %d seconds\n", COPY_DELAY_SECONDS)
|
||||
return 0
|
||||
+194
-197
@@ -1,197 +1,194 @@
|
||||
from stdint import *
|
||||
import stdio
|
||||
import memhub
|
||||
import sys
|
||||
import win32.win32console as win32cmd
|
||||
import win32.win32file as win32file
|
||||
import win32.win32base as win32base
|
||||
|
||||
|
||||
# VLogger - native logger with colored Windows console output.
|
||||
# Format: ├ LEVEL: message or ├ LEVEL[category]: message (aligned with TPC lib/core/VLogger.py).
|
||||
|
||||
# Error recovery (REPL/JIT): after error, longjmp back to the REPL setjmp point.
|
||||
# mingw-w64 x86_64: longjmp is void longjmp(jmp_buf, int); setjmp expands to _setjmp(buf, NULL).
|
||||
extern def longjmp(buf: rawptr, val: int)
|
||||
|
||||
|
||||
# REPL recovery-point pointer: registered at session start, cleared at session end.
|
||||
# Covers the whole fragment pipeline; batch mode never sets it, so it stays None and exits via sys.exit(1).
|
||||
_repl_jmpbuf: rawptr = None
|
||||
|
||||
|
||||
# repl_abort - the single "terminate after diagnosis" exit in VPC.
|
||||
# Caller already printed the diagnostic; returns to REPL via longjmp if a recovery point is set, else sys.exit(1).
|
||||
def repl_abort() -> int:
|
||||
"""Unified exit after diagnosis: longjmp to the REPL if a recovery point is set, else exit."""
|
||||
if _repl_jmpbuf is not None:
|
||||
stdio.fflush(None)
|
||||
longjmp(_repl_jmpbuf, 1)
|
||||
sys.exit(1)
|
||||
return 0
|
||||
|
||||
|
||||
def set_repl_jmpbuf(buf: rawptr) -> int:
|
||||
"""Register the REPL jmp_buf; idempotent at each fragment entry."""
|
||||
global _repl_jmpbuf
|
||||
_repl_jmpbuf = buf
|
||||
return 0
|
||||
|
||||
|
||||
def clear_repl_jmpbuf() -> int:
|
||||
"""Clear the registered jmp_buf at REPL session end."""
|
||||
global _repl_jmpbuf
|
||||
_repl_jmpbuf = None
|
||||
return 0
|
||||
|
||||
# Log levels
|
||||
enum LogLevel:
|
||||
DEBUG, INFO, WARNING, ERROR, SUCCESS
|
||||
|
||||
|
||||
# Win32 console foreground color attributes
|
||||
# 值用 WORD(...) 显式定宽为 uint16(与 _log 的 sym_color/level_color 形参一致)。
|
||||
# 注意:仅写 `define X: WORD = 0x0002` 不够 —— define 的宽度由**右侧表达式的类型**
|
||||
# 决定(win32file.vp 的 `define STD_OUTPUT_HANDLE: DWORD = DWORD(-10)` 同款),
|
||||
# 否则常量仍是 i32 → 传给 WORD 形参即 32→16 隐式窄化。
|
||||
define FOREGROUND_BLUE: WORD = WORD(0x0001)
|
||||
define FOREGROUND_GREEN: WORD = WORD(0x0002)
|
||||
define FOREGROUND_RED: WORD = WORD(0x0004)
|
||||
define FOREGROUND_INTENSITY: WORD = WORD(0x0008)
|
||||
define FOREGROUND_WHITE: WORD = WORD(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
|
||||
define FOREGROUND_YELLOW: WORD = WORD(FOREGROUND_RED | FOREGROUND_GREEN)
|
||||
|
||||
novirtual class Logger:
|
||||
_level: int
|
||||
_console_handle: win32base.HANDLE
|
||||
_use_color: int
|
||||
jit_flag: bool
|
||||
__mbuddy__: memhub.MemBuddy*
|
||||
|
||||
def __init__(self, level: int = 1):
|
||||
self._level = level
|
||||
self._console_handle = win32file.GetStdHandle(win32file.STD_OUTPUT_HANDLE)
|
||||
self._use_color = 1
|
||||
self.jit_flag = False
|
||||
self.__mbuddy__ = _mbuddy
|
||||
|
||||
def _set_color(self, attr: WORD) -> int:
|
||||
uc: int = self._use_color
|
||||
if uc:
|
||||
ch: win32base.HANDLE = self._console_handle
|
||||
ret: int = win32cmd.SetConsoleTextAttribute(ch, attr)
|
||||
return ret
|
||||
return 0
|
||||
|
||||
def _reset_color(self) -> int:
|
||||
if self._use_color: return win32cmd.SetConsoleTextAttribute(self._console_handle, FOREGROUND_WHITE)
|
||||
return 0
|
||||
|
||||
# _log - core log output with segmented coloring (prefix symbol / level tag / message).
|
||||
# Format: ├ LEVEL: msg or ├ LEVEL[category]: msg.
|
||||
def _log(self, level: int, prefix_sym: str, level_str: str, msg: str, sym_color: WORD, level_color: WORD, category: str = "") -> int:
|
||||
# Default level is INFO; DEBUG messages stay silent unless the level is lowered.
|
||||
if level < self._level: return 0
|
||||
# Color the prefix symbol
|
||||
self._set_color(sym_color)
|
||||
stdio.printf("%s ", prefix_sym)
|
||||
# Color the level tag
|
||||
self._set_color(level_color)
|
||||
stdio.printf("%s", level_str)
|
||||
self._reset_color()
|
||||
# Optional category
|
||||
if category is not None and category[0] != 0: stdio.printf(" [%s]", category)
|
||||
stdio.printf(": %s\n", msg)
|
||||
return 0
|
||||
|
||||
def debug(self, msg: str, category: str = "") -> int:
|
||||
return self._log(LogLevel.DEBUG, "├", "DEBUG", msg, FOREGROUND_INTENSITY, FOREGROUND_INTENSITY, category)
|
||||
|
||||
def info(self, msg: str, category: str = "") -> int:
|
||||
ret: int = self._log(LogLevel.INFO, "├", "INFO", msg, FOREGROUND_GREEN, WORD(FOREGROUND_GREEN | FOREGROUND_INTENSITY), category)
|
||||
return ret
|
||||
|
||||
def warning(self, msg: str, category: str = "") -> int:
|
||||
return self._log(LogLevel.WARNING, "├", "WARN", msg, FOREGROUND_YELLOW, WORD(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY), category)
|
||||
|
||||
def error(self, msg: str, category: str = "") -> int:
|
||||
ret: int = self._log(LogLevel.ERROR, "├", "ERROR", msg, FOREGROUND_RED, WORD(FOREGROUND_RED | FOREGROUND_INTENSITY), category)
|
||||
# JIT/REPL: diagnostic already printed; jump back to the REPL loop via the unified exit.
|
||||
if self.jit_flag:
|
||||
repl_abort()
|
||||
return ret
|
||||
# Batch mode: terminate immediately on error.
|
||||
sys.exit(1)
|
||||
return ret
|
||||
|
||||
def success(self, msg: str, category: str = "") -> int:
|
||||
return self._log(LogLevel.SUCCESS, "├", "SUCCESS", msg, FOREGROUND_GREEN, WORD(FOREGROUND_GREEN | FOREGROUND_INTENSITY), category)
|
||||
|
||||
def set_jit_flag(self, v: bool) -> int:
|
||||
"""Set the JIT/REPL flag: True makes error longjmp back to the REPL loop."""
|
||||
self.jit_flag = v
|
||||
return 0
|
||||
|
||||
# Advanced log methods.
|
||||
|
||||
def banner(self, msg: str) -> int:
|
||||
"""Print a blue-highlighted section banner."""
|
||||
self._set_color(FOREGROUND_BLUE | FOREGROUND_INTENSITY)
|
||||
stdio.printf("\n=== %s ===\n\n", msg)
|
||||
self._reset_color()
|
||||
return 0
|
||||
|
||||
# Global mbuddy pointer (injected by lib.InitLib).
|
||||
_mbuddy: memhub.MemBuddy*
|
||||
|
||||
# Global logger pointer
|
||||
logger: Logger*
|
||||
log: Logger*
|
||||
|
||||
def init_logger():
|
||||
"""Initialize the global logger instance."""
|
||||
global logger, log
|
||||
raw: rawptr = _mbuddy.alloc(Logger.__sizeof__())
|
||||
# if raw is None: return None (memory management still unclear)
|
||||
logger = raw
|
||||
logger.__before_init__()
|
||||
logger.__init__(LogLevel.INFO)
|
||||
log = logger
|
||||
# if fmtbuffer is None: return None
|
||||
|
||||
|
||||
# --nowarn-register switch: suppresses Phase A dependency WARN output (output only).
|
||||
_nowarn_register: int = 0
|
||||
|
||||
# Module-level convenience functions (aligned with TPC lib.core.VLogger; no get_logger() needed).
|
||||
|
||||
def info(msg: str, category: str = "") -> int:
|
||||
"""Log at INFO level."""
|
||||
if log is not None: return log.info(msg, category)
|
||||
return 0
|
||||
|
||||
|
||||
def warning(msg: str, category: str = "") -> int:
|
||||
"""Log at WARN level."""
|
||||
if log is not None: return log.warning(msg, category)
|
||||
return 0
|
||||
|
||||
|
||||
def error(msg: str, category: str = "") -> int:
|
||||
"""Log at ERROR level: exit in batch mode, longjmp to REPL under JIT/REPL."""
|
||||
if log is not None: return log.error(msg, category)
|
||||
# When the logger is unavailable, still use the unified exit (REPL can recover if registered)
|
||||
repl_abort()
|
||||
return 0
|
||||
|
||||
|
||||
def success(msg: str, category: str = "") -> int:
|
||||
"""Log at SUCCESS level."""
|
||||
if log is not None: return log.success(msg, category)
|
||||
return 0
|
||||
|
||||
|
||||
def debug(msg: str, category: str = "") -> int:
|
||||
"""Log at DEBUG level."""
|
||||
if log is not None: return log.debug(msg, category)
|
||||
return 0
|
||||
from stdint import *
|
||||
import stdio
|
||||
import memhub
|
||||
import sys
|
||||
import win32.win32console as win32cmd
|
||||
import win32.win32file as win32file
|
||||
import win32.win32base as win32base
|
||||
|
||||
|
||||
# VLogger - native logger with colored Windows console output.
|
||||
# Format: ├ LEVEL: message or ├ LEVEL[category]: message (aligned with TPC lib/core/VLogger.py).
|
||||
|
||||
# Error recovery (REPL/JIT): after error, longjmp back to the REPL setjmp point.
|
||||
# mingw-w64 x86_64: longjmp is void longjmp(jmp_buf, int); setjmp expands to _setjmp(buf, NULL).
|
||||
extern def longjmp(buf: rawptr, val: int)
|
||||
|
||||
|
||||
# REPL recovery-point pointer: registered at session start, cleared at session end.
|
||||
# Covers the whole fragment pipeline; batch mode never sets it, so it stays None and exits via sys.exit(1).
|
||||
_repl_jmpbuf: rawptr = None
|
||||
|
||||
|
||||
# repl_abort - the single "terminate after diagnosis" exit in VPC.
|
||||
# Caller already printed the diagnostic; returns to REPL via longjmp if a recovery point is set, else sys.exit(1).
|
||||
def repl_abort() -> int:
|
||||
"""Unified exit after diagnosis: longjmp to the REPL if a recovery point is set, else exit."""
|
||||
if _repl_jmpbuf is not None:
|
||||
stdio.fflush(None)
|
||||
longjmp(_repl_jmpbuf, 1)
|
||||
sys.exit(1)
|
||||
return 0
|
||||
|
||||
|
||||
def set_repl_jmpbuf(buf: rawptr) -> int:
|
||||
"""Register the REPL jmp_buf; idempotent at each fragment entry."""
|
||||
global _repl_jmpbuf
|
||||
_repl_jmpbuf = buf
|
||||
return 0
|
||||
|
||||
|
||||
def clear_repl_jmpbuf() -> int:
|
||||
"""Clear the registered jmp_buf at REPL session end."""
|
||||
global _repl_jmpbuf
|
||||
_repl_jmpbuf = None
|
||||
return 0
|
||||
|
||||
# Log levels
|
||||
enum LogLevel:
|
||||
DEBUG, INFO, WARNING, ERROR, SUCCESS
|
||||
|
||||
|
||||
# Console color attributes (restored: color output is enabled on the Windows console).
|
||||
# 值用 uint16(...) 显式定宽为 uint16(与 _log 的 sym_color/level_color 形参一致)。
|
||||
# 注意:仅写 `define X: WORD = 0x0002` 不够 —— define 的宽度由**右侧表达式的类型**
|
||||
# 决定(win32file.vp 的 `define STD_OUTPUT_HANDLE: DWORD = Duint16(-10)` 同款),
|
||||
# 否则常量仍是 i32 → 传给 WORD 形参即 32→16 隐式窄化。
|
||||
define FOREGROUND_BLUE: uint16 = uint16(0x0001)
|
||||
define FOREGROUND_GREEN: uint16 = uint16(0x0002)
|
||||
define FOREGROUND_RED: uint16 = uint16(0x0004)
|
||||
define FOREGROUND_INTENSITY: uint16 = uint16(0x0008)
|
||||
define FOREGROUND_WHITE: uint16 = uint16(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
|
||||
define FOREGROUND_YELLOW: uint16 = uint16(FOREGROUND_RED | FOREGROUND_GREEN)
|
||||
|
||||
novirtual class Logger:
|
||||
_level: int
|
||||
_console_handle: win32base.HANDLE
|
||||
_use_color: int
|
||||
jit_flag: bool
|
||||
__mbuddy__: memhub.MemBuddy*
|
||||
|
||||
def __init__(self, level: int = 1):
|
||||
self._level = level
|
||||
self._console_handle = win32file.GetStdHandle(win32file.STD_OUTPUT_HANDLE)
|
||||
self._use_color = 1
|
||||
self.jit_flag = False
|
||||
self.__mbuddy__ = _mbuddy
|
||||
|
||||
def _set_color(self, attr: uint16) -> int:
|
||||
if self._use_color:
|
||||
return win32cmd.SetConsoleTextAttribute(self._console_handle, attr)
|
||||
return 0
|
||||
|
||||
def _reset_color(self) -> int:
|
||||
if self._use_color: return win32cmd.SetConsoleTextAttribute(self._console_handle, FOREGROUND_WHITE)
|
||||
return 0
|
||||
|
||||
# _log - core log output with segmented coloring (prefix symbol / level tag / message).
|
||||
# Format: ├ LEVEL: msg or ├ LEVEL[category]: msg.
|
||||
def _log(self, level: int, prefix_sym: str, level_str: str, msg: str, sym_color: uint16, level_color: uint16, category: str = "") -> int:
|
||||
# Default level is INFO; DEBUG messages stay silent unless the level is lowered.
|
||||
if level < self._level: return 0
|
||||
# Color the prefix symbol
|
||||
self._set_color(sym_color)
|
||||
stdio.printf("%s ", prefix_sym)
|
||||
# Color the level tag
|
||||
self._set_color(level_color)
|
||||
stdio.printf("%s", level_str)
|
||||
self._reset_color()
|
||||
# Optional category
|
||||
if category is not None and category[0] != 0: stdio.printf(" [%s]", category)
|
||||
stdio.printf(": %s\n", msg)
|
||||
return 0
|
||||
|
||||
def debug(self, msg: str, category: str = "") -> int:
|
||||
return self._log(LogLevel.DEBUG, "├", "DEBUG", msg, FOREGROUND_INTENSITY, FOREGROUND_INTENSITY, category)
|
||||
|
||||
def info(self, msg: str, category: str = "") -> int:
|
||||
ret: int = self._log(LogLevel.INFO, "├", "INFO", msg, FOREGROUND_GREEN, uint16(FOREGROUND_GREEN | FOREGROUND_INTENSITY), category)
|
||||
return ret
|
||||
|
||||
def warning(self, msg: str, category: str = "") -> int:
|
||||
return self._log(LogLevel.WARNING, "├", "WARN", msg, FOREGROUND_YELLOW, uint16(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY), category)
|
||||
|
||||
def error(self, msg: str, category: str = "") -> int:
|
||||
ret: int = self._log(LogLevel.ERROR, "├", "ERROR", msg, FOREGROUND_RED, uint16(FOREGROUND_RED | FOREGROUND_INTENSITY), category)
|
||||
# JIT/REPL: diagnostic already printed; jump back to the REPL loop via the unified exit.
|
||||
if self.jit_flag:
|
||||
repl_abort()
|
||||
return ret
|
||||
# Batch mode: terminate immediately on error.
|
||||
sys.exit(1)
|
||||
return ret
|
||||
|
||||
def success(self, msg: str, category: str = "") -> int:
|
||||
return self._log(LogLevel.SUCCESS, "├", "SUCCESS", msg, FOREGROUND_GREEN, uint16(FOREGROUND_GREEN | FOREGROUND_INTENSITY), category)
|
||||
|
||||
def set_jit_flag(self, v: bool) -> int:
|
||||
"""Set the JIT/REPL flag: True makes error longjmp back to the REPL loop."""
|
||||
self.jit_flag = v
|
||||
return 0
|
||||
|
||||
# Advanced log methods.
|
||||
|
||||
def banner(self, msg: str) -> int:
|
||||
"""Print a blue-highlighted section banner."""
|
||||
self._set_color(FOREGROUND_BLUE | FOREGROUND_INTENSITY)
|
||||
stdio.printf("\n=== %s ===\n\n", msg)
|
||||
self._reset_color()
|
||||
return 0
|
||||
|
||||
# Global mbuddy pointer (injected by lib.InitLib).
|
||||
_mbuddy: memhub.MemBuddy*
|
||||
|
||||
# Global logger pointer
|
||||
logger: Logger*
|
||||
log: Logger*
|
||||
|
||||
def init_logger():
|
||||
"""Initialize the global logger instance."""
|
||||
global logger, log
|
||||
raw: rawptr = _mbuddy.alloc(Logger.__sizeof__())
|
||||
# if raw is None: return None (memory management still unclear)
|
||||
logger = raw
|
||||
logger.__before_init__()
|
||||
logger.__init__(LogLevel.INFO)
|
||||
log = logger
|
||||
# if fmtbuffer is None: return None
|
||||
|
||||
|
||||
# --nowarn-register switch: suppresses Phase A dependency WARN output (output only).
|
||||
_nowarn_register: int = 0
|
||||
|
||||
# Module-level convenience functions (aligned with TPC lib.core.VLogger; no get_logger() needed).
|
||||
|
||||
def info(msg: str, category: str = "") -> int:
|
||||
"""Log at INFO level."""
|
||||
if log is not None: return log.info(msg, category)
|
||||
return 0
|
||||
|
||||
|
||||
def warning(msg: str, category: str = "") -> int:
|
||||
"""Log at WARN level."""
|
||||
if log is not None: return log.warning(msg, category)
|
||||
return 0
|
||||
|
||||
|
||||
def error(msg: str, category: str = "") -> int:
|
||||
"""Log at ERROR level: exit in batch mode, longjmp to REPL under JIT/REPL."""
|
||||
if log is not None: return log.error(msg, category)
|
||||
# When the logger is unavailable, still use the unified exit (REPL can recover if registered)
|
||||
repl_abort()
|
||||
return 0
|
||||
|
||||
|
||||
def success(msg: str, category: str = "") -> int:
|
||||
"""Log at SUCCESS level."""
|
||||
if log is not None: return log.success(msg, category)
|
||||
return 0
|
||||
|
||||
|
||||
def debug(msg: str, category: str = "") -> int:
|
||||
"""Log at DEBUG level."""
|
||||
if log is not None: return log.debug(msg, category)
|
||||
return 0
|
||||
+1537
-1562
File diff suppressed because it is too large.
Load diff
@@ -578,7 +578,9 @@ def _AppendInt(pool: memhub.MemManager*, arr: json.JsonValue*, value: int,
|
||||
return tail
|
||||
node: json.JsonValue* = json.int_val(pool, int64(value))
|
||||
if node is None:
|
||||
return tail
|
||||
# Pool exhausted: signal failure so _Encode can stop at a token boundary instead of
|
||||
# emitting a partial token (the constructor is now null-safe and returns None).
|
||||
return None
|
||||
if tail is None:
|
||||
arr.child = node
|
||||
else:
|
||||
@@ -587,6 +589,35 @@ def _AppendInt(pool: memhub.MemManager*, arr: json.JsonValue*, value: int,
|
||||
return node
|
||||
|
||||
|
||||
def _AppendRollback(arr: json.JsonValue*, tail_before: json.JsonValue*, count_before: sizet) -> json.JsonValue*:
|
||||
"""Undo a partially appended token after an allocation failure."""
|
||||
if tail_before is None:
|
||||
arr.child = None
|
||||
else:
|
||||
tail_before.next = None
|
||||
arr.child_count = count_before
|
||||
return None
|
||||
|
||||
|
||||
def _AppendToken(pool: memhub.MemManager*, arr: json.JsonValue*,
|
||||
dl: int, dc: int, length: int, typ: int, mods: int,
|
||||
tail: json.JsonValue*) -> json.JsonValue*:
|
||||
"""Append one 5-int semantic token atomically; None when the pool is exhausted."""
|
||||
tail_before: json.JsonValue* = tail
|
||||
count_before: sizet = arr.child_count
|
||||
tail = _AppendInt(pool, arr, dl, tail)
|
||||
if tail is None: return _AppendRollback(arr, tail_before, count_before)
|
||||
tail = _AppendInt(pool, arr, dc, tail)
|
||||
if tail is None: return _AppendRollback(arr, tail_before, count_before)
|
||||
tail = _AppendInt(pool, arr, length, tail)
|
||||
if tail is None: return _AppendRollback(arr, tail_before, count_before)
|
||||
tail = _AppendInt(pool, arr, typ, tail)
|
||||
if tail is None: return _AppendRollback(arr, tail_before, count_before)
|
||||
tail = _AppendInt(pool, arr, mods, tail)
|
||||
if tail is None: return _AppendRollback(arr, tail_before, count_before)
|
||||
return tail
|
||||
|
||||
|
||||
def _Encode(pool: memhub.MemManager*, out: json.JsonValue*,
|
||||
ast_tokens: list[LspSemToken*]*, lex_tokens: list[LspSemToken*]*,
|
||||
has_range: int, start_line: int, end_line: int):
|
||||
@@ -628,11 +659,8 @@ def _Encode(pool: memhub.MemManager*, out: json.JsonValue*,
|
||||
length: int = t.ec - t.sc
|
||||
if length <= 0:
|
||||
continue
|
||||
tail = _AppendInt(pool, out, dl, tail)
|
||||
tail = _AppendInt(pool, out, dc, tail)
|
||||
tail = _AppendInt(pool, out, length, tail)
|
||||
tail = _AppendInt(pool, out, t.typ, tail)
|
||||
tail = _AppendInt(pool, out, t.mods, tail)
|
||||
tail = _AppendToken(pool, out, dl, dc, length, t.typ, t.mods, tail)
|
||||
if tail is None: break
|
||||
prev_line = t.sl
|
||||
prev_char = t.sc
|
||||
last = t
|
||||
|
||||
@@ -3,9 +3,6 @@ import string
|
||||
import json
|
||||
import memhub
|
||||
import os
|
||||
import os._win32
|
||||
import os._posix
|
||||
import os.path
|
||||
import lib.lsp.LspProtocol as LspProtocol
|
||||
import lib.lsp.LspTransport as LspTransport
|
||||
import lib.lsp.LspRpc as LspRpc
|
||||
@@ -448,10 +445,7 @@ def _WritePingResult(pool: memhub.MemManager*, tr: LspTransport.LspTransport*,
|
||||
|
||||
def Run(pool: memhub.MemBuddy*, perm_pool: memhub.MemBuddy*) -> int:
|
||||
global _cancel_int, _cancel_str
|
||||
os._mbuddy = perm_pool
|
||||
os._win32._mbuddy = perm_pool
|
||||
os._posix._mbuddy = perm_pool
|
||||
os.path._mbuddy = perm_pool
|
||||
os.set_mbuddy(perm_pool)
|
||||
|
||||
tr: LspTransport.LspTransport* = LspTransport.LspTransport(perm_pool, pool)
|
||||
if tr is None or not tr.IsValid():
|
||||
|
||||
+143
-165
@@ -1,165 +1,143 @@
|
||||
from stdint import *
|
||||
import stdlib
|
||||
import string
|
||||
import viperlib
|
||||
import memhub
|
||||
import win32.win32base
|
||||
import win32.win32file
|
||||
|
||||
|
||||
define LSP_READ_BUFFER_SIZE = 65536
|
||||
define LSP_HEADER_LINE_MAX = 512
|
||||
define LSP_MAX_MESSAGE_BYTES = 16777216
|
||||
|
||||
|
||||
class LspTransport:
|
||||
pool: memhub.MemManager*
|
||||
in_handle: win32.win32base.HANDLE
|
||||
out_handle: win32.win32base.HANDLE
|
||||
buf: bytes
|
||||
buf_cap: sizet
|
||||
buf_len: int
|
||||
buf_pos: int
|
||||
eof: int
|
||||
error: int
|
||||
|
||||
def __new__(self, owner: memhub.MemManager*, request: memhub.MemManager*) -> rawptr:
|
||||
ptr: LspTransport* = (LspTransport*)(owner.alloc(LspTransport.__sizeof__()))
|
||||
if ptr:
|
||||
string.memset(ptr, 0, LspTransport.__sizeof__())
|
||||
return rawptr(ptr)
|
||||
|
||||
def __init__(self, owner: memhub.MemManager*, request: memhub.MemManager*):
|
||||
self.pool = request
|
||||
self.in_handle = win32.win32file.GetStdHandle(win32.win32file.STD_INPUT_HANDLE)
|
||||
self.out_handle = win32.win32file.GetStdHandle(win32.win32file.STD_OUTPUT_HANDLE)
|
||||
self.buf_cap = sizet(LSP_READ_BUFFER_SIZE)
|
||||
self.buf = stdlib.malloc(self.buf_cap)
|
||||
self.buf_len = 0
|
||||
self.buf_pos = 0
|
||||
self.eof = 0
|
||||
self.error = 0
|
||||
|
||||
def IsValid(self) -> bool:
|
||||
if self.in_handle == win32.win32base.INVALID_HANDLE_VALUE:
|
||||
return False
|
||||
if self.out_handle == win32.win32base.INVALID_HANDLE_VALUE:
|
||||
return False
|
||||
if self.buf is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _ReadByte(self) -> int:
|
||||
if self.buf_pos >= self.buf_len:
|
||||
if self.eof != 0:
|
||||
return -1
|
||||
n: ULONG = 0
|
||||
ok: int = win32.win32file.ReadFile(self.in_handle, self.buf,
|
||||
ULONG(self.buf_cap), &n, None)
|
||||
if ok == 0 or int(n) == 0:
|
||||
self.eof = 1
|
||||
return -1
|
||||
self.buf_len = int(n)
|
||||
self.buf_pos = 0
|
||||
ch: char = self.buf[self.buf_pos]
|
||||
self.buf_pos += 1
|
||||
return int(ch)
|
||||
|
||||
def ReadLine(self) -> str:
|
||||
line: bytes = self.pool.alloc(sizet(LSP_HEADER_LINE_MAX))
|
||||
if line is None:
|
||||
self.error = 1
|
||||
return None
|
||||
i: int = 0
|
||||
while True:
|
||||
b: int = self._ReadByte()
|
||||
if b < 0:
|
||||
self.error = 1
|
||||
return None
|
||||
if b == 10:
|
||||
break
|
||||
if b == 13:
|
||||
continue
|
||||
if i >= LSP_HEADER_LINE_MAX - 1:
|
||||
self.error = 1
|
||||
return None
|
||||
line[i] = char(b)
|
||||
i += 1
|
||||
line[i] = 0
|
||||
return str(line)
|
||||
|
||||
def ReadExact(self, n: sizet) -> str:
|
||||
out: bytes = self.pool.alloc(n + 1)
|
||||
if out is None:
|
||||
self.error = 1
|
||||
return None
|
||||
copied: sizet = 0
|
||||
while copied < n:
|
||||
if self.buf_pos < self.buf_len:
|
||||
avail: sizet = sizet(self.buf_len - self.buf_pos)
|
||||
take: sizet = n - copied
|
||||
if take > avail:
|
||||
take = avail
|
||||
string.memcpy(out + copied, self.buf + self.buf_pos, take)
|
||||
self.buf_pos += int(take)
|
||||
copied += take
|
||||
else:
|
||||
got: ULONG = 0
|
||||
need: ULONG = ULONG(n - copied)
|
||||
ok: int = win32.win32file.ReadFile(self.in_handle, out + copied,
|
||||
need, &got, None)
|
||||
if ok == 0 or int(got) == 0:
|
||||
self.error = 1
|
||||
return None
|
||||
copied += sizet(int(got))
|
||||
out[n] = 0
|
||||
return str(out)
|
||||
|
||||
def ReadMessage(self) -> str:
|
||||
content_len: int = -1
|
||||
while True:
|
||||
line: str = self.ReadLine()
|
||||
if line is None:
|
||||
return None
|
||||
if len(line) == 0:
|
||||
break
|
||||
if string.strncmp(line, "Content-Length:", 15) == 0:
|
||||
content_len = string.atoi(line + 15)
|
||||
if content_len <= 0 or content_len > LSP_MAX_MESSAGE_BYTES:
|
||||
self.error = 1
|
||||
return None
|
||||
return self.ReadExact(sizet(content_len))
|
||||
|
||||
def _WriteAll(self, data: str, n: sizet) -> int:
|
||||
sent: sizet = 0
|
||||
while sent < n:
|
||||
written: ULONG = 0
|
||||
ok: int = win32.win32file.WriteFile(self.out_handle, data + sent,
|
||||
ULONG(n - sent), &written, None)
|
||||
if ok == 0 or int(written) == 0:
|
||||
return -1
|
||||
sent += sizet(int(written))
|
||||
return 0
|
||||
|
||||
def WriteMessage(self, body: str) -> int:
|
||||
if body is None:
|
||||
return -1
|
||||
blen: sizet = string.strlen(body)
|
||||
header: bytes = self.pool.alloc(sizet(64))
|
||||
if header is None:
|
||||
return -1
|
||||
viperlib.snprintf(header, sizet(64), "Content-Length: %d\r\n\r\n", int(blen))
|
||||
if self._WriteAll(str(header), string.strlen(header)) != 0:
|
||||
return -1
|
||||
return self._WriteAll(body, blen)
|
||||
|
||||
|
||||
def StderrWrite(msg: str) -> int:
|
||||
if msg is None:
|
||||
return 0
|
||||
h: win32.win32base.HANDLE = win32.win32file.GetStdHandle(win32.win32file.STD_ERROR_HANDLE)
|
||||
if h == win32.win32base.INVALID_HANDLE_VALUE:
|
||||
return -1
|
||||
written: ULONG = 0
|
||||
win32.win32file.WriteFile(h, msg, ULONG(string.strlen(msg)), &written, None)
|
||||
return 0
|
||||
from stdint import *
|
||||
import stdlib
|
||||
import string
|
||||
import viperlib
|
||||
import memhub
|
||||
import stdio
|
||||
|
||||
|
||||
define LSP_READ_BUFFER_SIZE = 65536
|
||||
define LSP_HEADER_LINE_MAX = 512
|
||||
define LSP_MAX_MESSAGE_BYTES = 16777216
|
||||
|
||||
|
||||
class LspTransport:
|
||||
pool: memhub.MemManager*
|
||||
buf: bytes
|
||||
buf_cap: sizet
|
||||
buf_len: int
|
||||
buf_pos: int
|
||||
eof: int
|
||||
error: int
|
||||
|
||||
def __new__(self, owner: memhub.MemManager*, request: memhub.MemManager*) -> rawptr:
|
||||
ptr: LspTransport* = (LspTransport*)(owner.alloc(LspTransport.__sizeof__()))
|
||||
if ptr:
|
||||
string.memset(ptr, 0, LspTransport.__sizeof__())
|
||||
return rawptr(ptr)
|
||||
|
||||
def __init__(self, owner: memhub.MemManager*, request: memhub.MemManager*):
|
||||
self.pool = request
|
||||
self.buf_cap = sizet(LSP_READ_BUFFER_SIZE)
|
||||
self.buf = stdlib.malloc(self.buf_cap)
|
||||
self.buf_len = 0
|
||||
self.buf_pos = 0
|
||||
self.eof = 0
|
||||
self.error = 0
|
||||
|
||||
def IsValid(self) -> bool:
|
||||
return self.buf is not None
|
||||
|
||||
def _ReadByte(self) -> int:
|
||||
if self.buf_pos >= self.buf_len:
|
||||
if self.eof != 0:
|
||||
return -1
|
||||
n: sizet = stdio.read_fd(0, self.buf, self.buf_cap)
|
||||
if n == 0:
|
||||
self.eof = 1
|
||||
return -1
|
||||
self.buf_len = int(n)
|
||||
self.buf_pos = 0
|
||||
ch: char = self.buf[self.buf_pos]
|
||||
self.buf_pos += 1
|
||||
return int(ch)
|
||||
|
||||
def ReadLine(self) -> str:
|
||||
line: bytes = self.pool.alloc(sizet(LSP_HEADER_LINE_MAX))
|
||||
if line is None:
|
||||
self.error = 1
|
||||
return None
|
||||
i: int = 0
|
||||
while True:
|
||||
b: int = self._ReadByte()
|
||||
if b < 0:
|
||||
self.error = 1
|
||||
return None
|
||||
if b == 10:
|
||||
break
|
||||
if b == 13:
|
||||
continue
|
||||
if i >= LSP_HEADER_LINE_MAX - 1:
|
||||
self.error = 1
|
||||
return None
|
||||
line[i] = char(b)
|
||||
i += 1
|
||||
line[i] = 0
|
||||
return str(line)
|
||||
|
||||
def ReadExact(self, n: sizet) -> str:
|
||||
out: bytes = self.pool.alloc(n + 1)
|
||||
if out is None:
|
||||
self.error = 1
|
||||
return None
|
||||
copied: sizet = 0
|
||||
while copied < n:
|
||||
if self.buf_pos < self.buf_len:
|
||||
avail: sizet = sizet(self.buf_len - self.buf_pos)
|
||||
take: sizet = n - copied
|
||||
if take > avail:
|
||||
take = avail
|
||||
string.memcpy(out + copied, self.buf + self.buf_pos, take)
|
||||
self.buf_pos += int(take)
|
||||
copied += take
|
||||
else:
|
||||
got: sizet = stdio.read_fd(0, out + copied, n - copied)
|
||||
if got == 0:
|
||||
self.error = 1
|
||||
return None
|
||||
copied += got
|
||||
out[n] = 0
|
||||
return str(out)
|
||||
|
||||
def ReadMessage(self) -> str:
|
||||
content_len: int = -1
|
||||
while True:
|
||||
line: str = self.ReadLine()
|
||||
if line is None:
|
||||
return None
|
||||
if len(line) == 0:
|
||||
break
|
||||
if string.strncmp(line, "Content-Length:", 15) == 0:
|
||||
content_len = string.atoi(line + 15)
|
||||
if content_len <= 0 or content_len > LSP_MAX_MESSAGE_BYTES:
|
||||
self.error = 1
|
||||
return None
|
||||
return self.ReadExact(sizet(content_len))
|
||||
|
||||
def _WriteAll(self, data: str, n: sizet) -> int:
|
||||
sent: sizet = 0
|
||||
while sent < n:
|
||||
written: sizet = stdio.write_fd(1, data + sent, n - sent)
|
||||
if written == 0:
|
||||
return -1
|
||||
sent += written
|
||||
return 0
|
||||
|
||||
def WriteMessage(self, body: str) -> int:
|
||||
if body is None:
|
||||
return -1
|
||||
blen: sizet = string.strlen(body)
|
||||
header: bytes = self.pool.alloc(sizet(64))
|
||||
if header is None:
|
||||
return -1
|
||||
viperlib.snprintf(header, sizet(64), "Content-Length: %d\r\n\r\n", int(blen))
|
||||
if self._WriteAll(str(header), string.strlen(header)) != 0:
|
||||
return -1
|
||||
return self._WriteAll(body, blen)
|
||||
|
||||
|
||||
def StderrWrite(msg: str) -> int:
|
||||
if msg is None:
|
||||
return 0
|
||||
written: sizet = stdio.write_fd(2, msg, string.strlen(msg))
|
||||
return 0 if written == string.strlen(msg) else -1
|
||||
@@ -1,8 +1,6 @@
|
||||
from stdint import *
|
||||
import string
|
||||
import os
|
||||
import os._win32
|
||||
import os._posix
|
||||
import os.path
|
||||
import memhub
|
||||
import lib.lsp.LspPools as LspPools
|
||||
|
||||
+638
-639
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# linux_link.sh - Windows -> Linux link wrapper for project.linux.vpj.
|
||||
#
|
||||
# ViperC emits ELF objects for x86_64-pc-linux-gnu, but Windows has no native
|
||||
# Linux linker available. Invoke this through WSL:
|
||||
#
|
||||
# wsl.exe -d Ubuntu -- bash Tools/linux_link.sh <clang++ args...>
|
||||
#
|
||||
# project.linux.vpj sets linker.cmd to exactly that command. WSL starts in the
|
||||
# calling Windows directory, so the relative script path resolves; every
|
||||
# Windows path argument (D:/... or D:\...) is converted with wslpath.
|
||||
set -e
|
||||
|
||||
args=()
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
[A-Za-z]:[\\/]*)
|
||||
args+=("$(wslpath -u "$a")")
|
||||
;;
|
||||
*)
|
||||
args+=("$a")
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
exec clang++ "${args[@]}"
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/TermiNexus/TransPyC/main/schemas/project-schema.json",
|
||||
"name": "ViperC-Linux",
|
||||
"version": "1.0.0",
|
||||
"source_dir": "./App",
|
||||
"build_dir": "./__vpcbuild_linux__",
|
||||
"compiler": {
|
||||
"cmd": "llc",
|
||||
"flags": [
|
||||
"-filetype=obj",
|
||||
"-relocation-model=pic",
|
||||
"-O0"
|
||||
]
|
||||
},
|
||||
"linker": {
|
||||
"cmd": "wsl.exe -d Ubuntu -- bash Tools/linux_link.sh",
|
||||
"flags": [
|
||||
"-fuse-ld=lld",
|
||||
"-lpthread",
|
||||
"-lm",
|
||||
"-ldl",
|
||||
"-g"
|
||||
],
|
||||
"output": "viperc"
|
||||
},
|
||||
"includes": [
|
||||
"../includes"
|
||||
],
|
||||
"target": {
|
||||
"triple": "x86_64-pc-linux-gnu",
|
||||
"datalayout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
|
||||
},
|
||||
"options": {
|
||||
"slice_level": 3,
|
||||
"sha1_slice_level": 1,
|
||||
"strict_mode": true
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user