Files
ViperC/App/lib/core/Gen/Expr/ExprGenCdef.vp
T

919 lines
50 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdio
import stdlib
import viperlib
import win32.fileio as fileio
import lib.Stub.StubMerger as StubMerger
import lib.core.VLogger as VLogger
import lib.Projectrans.Config as Config
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
# ============================================================
# ExprGenCdef - ExprGenMixin split (stage D): cross-module CDefine lookup
novirtual class ExprGenCdef(ExprGenBase.ExprGenBase):
"""Cross-module CDefine lookup"""
# _build_source_path_from_sha1: build a source .vp/.py path from SHA1 via the StubMerger
# global store (.vpi missing -> source .py); "lib/..." -> SourceDir, else IncludesDir.
def _build_source_path_from_sha1(self, sha1: str) -> str:
"""Build a source .py path from a SHA1 via the global store"""
if sha1 is None: return None
if Config.IncludesDir is None: return None
# get the rel-path array from the StubMerger global store
rel_arr_ptr: bytes = StubMergerStore.GetSha1StoreRelArrPtr()
count: int = StubMergerStore.GetSha1StoreCount()
if rel_arr_ptr is None or count <= 0: return None
sha1_arr_ptr: bytes = StubMergerStore.GetSha1StoreArrPtr()
if sha1_arr_ptr is None: return None
# iterate to find the matching SHA1
idx_s: int = -1
for i in range(int(count)):
sidx: sizet = sizet(i) * 17
cur_sha1: str = sha1_arr_ptr + sidx
if cur_sha1[0] == '\0': continue
if string.strcmp(cur_sha1, sha1) == 0:
idx_s = i
break
if idx_s < 0: return None
# get the rel path (MAX_REL_PATH_LEN=256, avoids cross-module CDefine issues)
MAX_RPL: sizet = 256
ridx: sizet = sizet(idx_s) * MAX_RPL
rel_path: str = rel_arr_ptr + ridx
if rel_path[0] == '\0': return None
# choose base dir: 1) stored includes root IncDir (absolute, supports multiple dirs);
# 2) prefix heuristic: "lib/" -> SourceDir, "includes/" -> IncludesDir, else SourceDir then IncludesDir.
base_dir: str = None
rel_used: str = rel_path
inc_arr_ptr: bytes = StubMergerStore.GetSha1StoreIncdirArrPtr()
if inc_arr_ptr is not None:
iidx: sizet = sizet(idx_s) * StubMerger.MAX_INC_DIR_LEN
inc_dir: str = inc_arr_ptr + iidx
if inc_dir[0] != '\0': base_dir = inc_dir
if base_dir is None and string.strlen(rel_path) >= 4:
if (rel_path[0] == 'l' and rel_path[1] == 'i' and rel_path[2] == 'b' and rel_path[3] == '/'):
base_dir = Config.SourceDir if Config.SourceDir is not None else Config.IncludesDir
if base_dir is None and string.strlen(rel_path) >= 9:
if (rel_path[0] == 'i' and rel_path[1] == 'n' and rel_path[2] == 'c' and rel_path[3] == 'l' and rel_path[4] == 'u' and rel_path[5] == 'd' and rel_path[6] == 'e' and rel_path[7] == 's' and rel_path[8] == '/'):
rel_used = rel_path + 9
base_dir = Config.IncludesDir
if base_dir is None: base_dir = Config.SourceDir if Config.SourceDir is not None else Config.IncludesDir
# build full path: {base_dir}/{rel_used}
base_len: sizet = string.strlen(base_dir)
rel_len: sizet = string.strlen(rel_used)
path_len: sizet = base_len + rel_len + 2
buf: str = stdlib.malloc(path_len)
if buf is None: return None
viperlib.snprintf(buf, path_len, "%s/%s", base_dir, rel_used)
# existence check: if missing under SourceDir and IncludesDir differs, retry IncludesDir
pf_chk: fileio.File* = fileio.File(buf, fileio.MODE.R)
if pf_chk.closed:
pf_chk.close()
if Config.IncludesDir is not None and base_dir != Config.IncludesDir:
stdlib.free(buf)
base_len2: sizet = string.strlen(Config.IncludesDir)
path_len2: sizet = base_len2 + rel_len + 2
buf = stdlib.malloc(path_len2)
if buf is None: return None
viperlib.snprintf(buf, path_len2, "%s/%s", Config.IncludesDir, rel_used)
pf_chk2: fileio.File* = fileio.File(buf, fileio.MODE.R)
if pf_chk2.closed:
pf_chk2.close()
stdlib.free(buf)
return None
pf_chk2.close()
else:
stdlib.free(buf)
return None
else: pf_chk.close()
return buf
# _pyi_annot_float_kind - 从 .vpi/.pyi 行的注解段(`NAME:` 到 `=` 之间)判定浮点宽度种类:
# 独立单词 float → 1(f32);double / 未声明 / 非浮点 → 0。词边界判定避免命中 `double`/`CFloat32`。
def _pyi_annot_float_kind(self, buf: bytes, start: sizet, end: sizet) -> int:
"""Return 1 when the annotation text [start,end) declares a 32-bit `float`, else 0."""
i: sizet = start
while i + 5 <= end:
if (buf[i] == 'f' and buf[i + 1] == 'l' and buf[i + 2] == 'o' and buf[i + 3] == 'a' and buf[i + 4] == 't'):
ok_before: int = 1
ok_after: int = 1
if i > start:
pb: char = buf[i - 1]
if (pb >= 'A' and pb <= 'Z') or (pb >= 'a' and pb <= 'z') or (pb >= '0' and pb <= '9') or pb == '_': ok_before = 0
if i + 5 < end:
pa: char = buf[i + 5]
if (pa >= 'A' and pa <= 'Z') or (pa >= 'a' and pa <= 'z') or (pa >= '0' and pa <= '9') or pa == '_': ok_after = 0
if ok_before != 0 and ok_after != 0: return 1
i += 1
return 0
# _lookup_cross_module_cdefine - cross-module CDefine lookup: resolve the source module
# from from_imports, parse its pyi "NAME: t.CDefine = value", then cache in the current module.
def _lookup_cross_module_cdefine(self, pool: memhub.MemBuddy*, name: str, from_imports: str, imported_modules: str = None, depth: int = 0, node: ast.AST* = None) -> int64:
"""Look up a CDefine constant across modules, returning its 64-bit value or -1"""
# recursion-depth guard: re-export chains + imported_modules walk can loop and OOM;
# cap at 32 to fail fast on cyclic imports (error carries node line + module chain).
if depth > 32:
err_buf: str = pool.alloc(256)
if err_buf is not None:
viperlib.snprintf(err_buf, 256, "cross-module CDefine lookup recursion too deep (suspected module import/re-export cycle): %s (depth=%d fi=%s)", name, depth, from_imports if from_imports is not None else "(nil)")
self.Gen.TypeGenErr.fatal_error(node, err_buf, "InternalCompilerError")
self.Gen.TypeGenErr.fatal_error(node, "cross-module CDefine lookup recursion too deep (suspected module import/re-export cycle)", "InternalCompilerError")
# clear the found flag at entry so every early return reports "not found",
# avoiding a stale success flag left over from a previous recursive call.
self.Gen.TypeGenCDef.set_cdefine_found(0)
if name is None: return -1
if from_imports is None: from_imports = ""
# 1. resolve the module name for `name` from from_imports; try exact match first
# (allow_star_fallback=0) so a star import cannot mislead CDefine constants.
mod_name_raw: str = self.Gen.ImportsGen.lookup_from_import(from_imports, name, 0)
if mod_name_raw is None:
# exact match failed: try the star import fallback
mod_name_raw = self.Gen.ImportsGen.lookup_from_import(from_imports, name, 1)
# truncate module name at NUL: lookup_from_import returns a pointer into from_imports
# (not NUL-terminated); match _lookup_sha1_from_modname_raw (space/colon/NUL delimited).
base_mod: str = None
if mod_name_raw is not None:
mbl: sizet = 0
while mod_name_raw[mbl] != ' ' and mod_name_raw[mbl] != '\0' and mod_name_raw[mbl] != ':': mbl += 1
mb_buf: str = pool.alloc(mbl + 1)
if mb_buf is not None:
mi2: sizet
for mi2 in range(int(mbl)): mb_buf[mi2] = mod_name_raw[mi2]
mb_buf[mbl] = '\0'
base_mod = mb_buf
while base_mod is not None and base_mod[0] == '.': base_mod = base_mod + 1
# cross-module CDefine cache (perm_pool, reused across files): key = "name@srcModule"
# keeps modules isolated so same-name constants from different modules do not cross.
xkey_buf: str = None
if base_mod is not None:
xk_len: sizet = string.strlen(name) + string.strlen(base_mod) + 2
xkey_buf = pool.alloc(xk_len)
if xkey_buf is not None:
viperlib.snprintf(xkey_buf, xk_len, "%s@%s", name, base_mod)
xval: str = self.Gen.TypeGenCDef.lookup_xmod_cdefine(xkey_buf)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
if self.Gen.TypeGenCDef.is_cdefine_str() != 0: self.Gen.TypeGenCDef.register_cdefine_string(pool, name, xval)
elif self.Gen.TypeGenCDef.is_cdefine_double() != 0:
xd_v: double = string.atof(xval)
# 缓存里带宽度种类(f32 声明 → 1),否则使用方会把 f32 常量当 double 物化
xd_f32: int = self.Gen.TypeGenCDef.is_cdefine_float32()
self.Gen.TypeGenCDef.register_cdefine_double(pool, name, xd_v, xd_f32)
else:
xv_i: int64 = string.atoll(xval)
self.Gen.TypeGenCDef.register_cdefine_constant(pool, name, xv_i)
return string.atoll(xval)
# 2. from_imports failed: fall back to the imported_modules walk (step 9.6);
# a star fallback may name a wrong module, so keep scanning imported_modules.
if mod_name_raw is None:
# from_imports matched nothing (exact and star both failed):
# go straight to the imported_modules walk (step 9.6).
if imported_modules is None: return -1
# skip reading the from_imports module; go directly to the step 9.6 walk.
# result_found=0 skips steps 3-9 and jumps to 9.6.
self.Gen.TypeGenCDef.set_cdefine_found(0)
# iterate imported_modules directly
im_len: sizet = string.strlen(imported_modules)
im_ci: sizet = 0
while im_ci < im_len:
while im_ci < im_len and imported_modules[im_ci] == ' ': im_ci += 1
if im_ci >= im_len: break
im_start: sizet = im_ci
while im_ci < im_len and imported_modules[im_ci] != ' ': im_ci += 1
im_end: sizet = im_ci
im_nlen: sizet = im_end - im_start
if im_nlen > 0 and im_nlen < 256:
im_buf: str = pool.alloc(im_nlen + 1)
if im_buf is not None:
for mk in range(int(im_nlen)):
im_buf[mk] = imported_modules[im_start + mk]
im_buf[im_nlen] = '\0'
sub_fi: str = pool.alloc(im_nlen + 3)
if sub_fi is not None:
sub_fi[0] = '*'
sub_fi[1] = ':'
for mk2 in range(int(im_nlen)):
sub_fi[2 + mk2] = im_buf[mk2]
sub_fi[2 + im_nlen] = '\0'
sub_val: int64 = self._lookup_cross_module_cdefine(pool, name, sub_fi, None, depth + 1, node)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0: return sub_val
return -1
# truncate module name, strip leading dots, find SHA1 via exact+suffix (shared logic)
sha1: str = self.Gen.CallGenBase._lookup_sha1_from_modname_raw(pool, mod_name_raw)
if sha1 is None: return -1
# 5. get temp_dir
temp_dir: str = self.Gen.TypeGenBase.get_temp_dir()
if temp_dir is None: return -1
# 6. build the vpi file path
td_len: sizet = string.strlen(temp_dir)
pyi_path: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "vpi")
if pyi_path is None: return -1
# 7. read vpi; if missing or too short (empty stub), fall back to the source .vp/.py
PYI_READ_BUF_SIZE: sizet = 65536
pyi_buf: bytes = stdlib.malloc(PYI_READ_BUF_SIZE)
if pyi_buf is None:
stdlib.free(pyi_path)
return -1
pf: fileio.File* = fileio.File(pyi_path, fileio.MODE.R)
need_py_fallback: int = 0
bytes_read: LONG = 0
if pf.closed:
# .vpi missing: fall back to the source .vp/.py
need_py_fallback = 1
else:
bytes_read = pf.read_all(pyi_buf, PYI_READ_BUF_SIZE)
pf.close()
# vpi too short (< 10 bytes): treat as empty stub, fall back to .py
if bytes_read < 10: need_py_fallback = 1
else:
if bytes_read < PYI_READ_BUF_SIZE: pyi_buf[bytes_read] = 0
# vpi stubs may declare a CDefine name without a value; the real value lives
# in the source .vp/.py, so trust vpi only when the target name's line has a value.
cd_sig: int = 0
name_has_val: int = 0
cs_name_len: sizet = string.strlen(name)
cs_total: sizet = string.strlen(pyi_buf)
cs_i: sizet = 0
while cs_i + 9 < cs_total:
if (pyi_buf[cs_i] == 't' and pyi_buf[cs_i + 1] == '.' and pyi_buf[cs_i + 2] == 'C' and pyi_buf[cs_i + 3] == 'D' and pyi_buf[cs_i + 4] == 'e' and pyi_buf[cs_i + 5] == 'f' and pyi_buf[cs_i + 6] == 'i' and pyi_buf[cs_i + 7] == 'n' and pyi_buf[cs_i + 8] == 'e'):
# check whether the rest of the line has '=' (only valued decls match in vpi)
cd_eq: int = 0
cs_j: sizet = cs_i + 9
while cs_j < cs_total and pyi_buf[cs_j] != '\n':
if pyi_buf[cs_j] == '=':
cd_eq = 1
break
cs_j += 1
if cd_eq != 0:
cd_sig = 1
# locate line start; check it begins with "name:" (target has a value)
cs_line_start: sizet = cs_i
while cs_line_start > 0 and pyi_buf[cs_line_start - 1] != '\n': cs_line_start -= 1
if cs_i - cs_line_start >= cs_name_len + 1:
nm_ok: int = 1
for ki2 in range(int(cs_name_len)):
if pyi_buf[cs_line_start + ki2] != name[ki2]:
nm_ok = 0
break
if nm_ok == 1 and pyi_buf[cs_line_start + cs_name_len] == ':': name_has_val = 1
cs_i += 1
if cd_sig == 0 or name_has_val == 0: need_py_fallback = 1
stdlib.free(pyi_path)
if need_py_fallback != 0:
# look up the rel path from the SHA1 store; build {base_dir}/{rel_path}
src_path: str = self._build_source_path_from_sha1(sha1)
if src_path is None:
# SHA1 absent from the store = compiler bug (Phase1/Phase2 not populated):
# report and abort instead of silently returning -1 (avoids bad @UNDEFINED refs).
err_buf: str = pool.alloc_tagged(4096, memhub.DETSITE_EXPRGENCDEF_263)
if err_buf is not None:
viperlib.snprintf(err_buf, 512, "cross-module CDefine lookup failed: SHA1 %s is not in the store (base_mod=%s name=%s). " "Phase1/Phase2 did not correctly populate PopulateSha1MapStore/AppendToSha1MapStore", sha1, base_mod, name)
VLogger.error(err_buf, "XMOD-CD")
stdlib.free(pyi_buf)
return -1
pf = fileio.File(src_path, fileio.MODE.R)
stdlib.free(src_path)
if pf.closed:
stdlib.free(pyi_buf)
return -1
bytes_read = pf.read_all(pyi_buf, PYI_READ_BUF_SIZE)
pf.close()
if bytes_read <= 0:
stdlib.free(pyi_buf)
return -1
if bytes_read < PYI_READ_BUF_SIZE: pyi_buf[bytes_read] = 0
# 8. search pyi_buf for the "name: t.CDefine = value" line
# format: NAME: t.CDefine = value
name_len: sizet = string.strlen(name)
total_len: sizet = string.strlen(pyi_buf)
pos: sizet = 0
result_val: int64 = 0
result_found: int = 0
res_is_double: int = 0
# 浮点宽度种类(1 = 注解声明为 32 位 float):跨模块缓存与本地注册都要带上,
# 否则使用方把 f32 常量当 double 物化(`define PI: float = ...` 现场)。
res_is_f32: int = 0
res_is_str: int = 0
while pos < total_len:
# find line start
line_start: sizet = pos
# find line end
while pos < total_len and pyi_buf[pos] != '\n': pos += 1
line_len: sizet = pos - line_start
if pos < total_len:
pos += 1 # skip '\n'
# check whether the line starts with "name:"
if line_len < name_len + 1: continue
match_ok: int = 1
for ki in range(int(name_len)):
if pyi_buf[line_start + ki] != name[ki]:
match_ok = 0
break
if match_ok == 0: continue
if pyi_buf[line_start + name_len] != ':': continue
# check the line contains the CDefine marker
has_cdefine: int = 0
ci: sizet = line_start + name_len + 1
while ci + 7 <= line_start + line_len:
if (pyi_buf[ci] == 'C' and pyi_buf[ci + 1] == 'D' and pyi_buf[ci + 2] == 'e' and pyi_buf[ci + 3] == 'f' and pyi_buf[ci + 4] == 'i' and pyi_buf[ci + 5] == 'n' and pyi_buf[ci + 6] == 'e'):
has_cdefine = 1
break
ci += 1
if has_cdefine == 0: continue
# find the value after "="
eq_pos: sizet = line_start + name_len + 1
while eq_pos < line_start + line_len and pyi_buf[eq_pos] != '=': eq_pos += 1
if eq_pos >= line_start + line_len: continue
# 注解段 = [名字冒号后, '='),用于判定声明的浮点宽度(如 `PI: float | t.CDefine`)
annot_start: sizet = line_start + name_len + 1
annot_end: sizet = eq_pos
eq_pos += 1 # skip '='
# skip spaces
while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1
# check for type-ctor forms like t.CUnsignedLong(...) / t.CLong(...) / int(...);
# if so, skip the "t.CXxx(" prefix, parse inside the parens, ignore trailing ')'.
is_type_ctor: int = 0
if eq_pos + 2 < line_start + line_len:
if pyi_buf[eq_pos] == 't' and pyi_buf[eq_pos + 1] == '.':
# find the '(' position
paren_pos: sizet = eq_pos + 2
while paren_pos < line_start + line_len and pyi_buf[paren_pos] != '(': paren_pos += 1
if paren_pos < line_start + line_len and pyi_buf[paren_pos] == '(':
is_type_ctor = 1
eq_pos = paren_pos + 1 # skip '('
# skip possible leading spaces inside the parens
while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1
# paren-wrapped flag: (1 << 23) / (512) / (A | B)
is_paren_expr: int = 0
if eq_pos < line_start + line_len and pyi_buf[eq_pos] == '(':
is_paren_expr = 1
eq_pos += 1
while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1
# === parse left operand token (number / hex / sign / name) ===
# sign flag
is_neg: int = 0
if eq_pos < line_start + line_len and pyi_buf[eq_pos] == '-':
is_neg = 1
eq_pos += 1
val_str_start: sizet = eq_pos
val_str_len: sizet = 0
is_hex: int = 0
is_float_val: int = 0
is_name_val: int = 0
is_str_val: int = 0
# string value (e.g. ZLIB_VERSION = "1.3.2"): read inside the quotes; without this,
# cross-module read-back skipped it and reported "CDefine not found".
if eq_pos < line_start + line_len and pyi_buf[eq_pos] == '"':
is_str_val = 1
eq_pos += 1 # skip opening quote
val_str_start = eq_pos
while eq_pos < line_start + line_len and pyi_buf[eq_pos] != '"':
val_str_len += 1
eq_pos += 1
if eq_pos < line_start + line_len and pyi_buf[eq_pos] == '"':
eq_pos += 1 # skip closing quote
if eq_pos + 1 < line_start + line_len:
if pyi_buf[eq_pos] == '0' and (pyi_buf[eq_pos + 1] == 'x' or pyi_buf[eq_pos + 1] == 'X'):
is_hex = 1
val_str_len = 2
eq_pos += 2
elif (pyi_buf[eq_pos] >= 'A' and pyi_buf[eq_pos] <= 'Z') or (pyi_buf[eq_pos] >= 'a' and pyi_buf[eq_pos] <= 'z') or pyi_buf[eq_pos] == '_':
is_name_val = 1
while eq_pos < line_start + line_len:
c_tk: char = pyi_buf[eq_pos]
if (c_tk >= 'A' and c_tk <= 'Z') or (c_tk >= 'a' and c_tk <= 'z') or (c_tk >= '0' and c_tk <= '9') or c_tk == '_':
val_str_len += 1
eq_pos += 1
else: break
# continue reading digit/hex chars (string values are read whole, skipped)
while eq_pos < line_start + line_len and is_name_val == 0 and is_str_val == 0:
ch: char = pyi_buf[eq_pos]
if is_hex == 1:
if ((ch >= '0' and ch <= '9') or (ch >= 'a' and ch <= 'f') or (ch >= 'A' and ch <= 'F')):
val_str_len += 1
eq_pos += 1
else: break
else:
if ch >= '0' and ch <= '9':
val_str_len += 1
eq_pos += 1
elif ch == '.' and is_float_val == 0:
# float value: '3.14159' ('.' allowed only once)
is_float_val = 1
val_str_len += 1
eq_pos += 1
else: break
if val_str_len == 0 and is_str_val == 0: continue
# copy the left token into a buffer
val_buf: str = pool.alloc(val_str_len + 1)
if val_buf is None: continue
for vi in range(int(val_str_len)): val_buf[vi] = pyi_buf[val_str_start + vi]
val_buf[val_str_len] = '\0'
# evaluate the left token
lval: int64 = 0
l_is_double: int = 0
ldbl: double = 0.0
if is_str_val != 0:
# string CDefine (e.g. ZLIB_VERSION = "1.3.2"): register the string value
# (including ""), mark result_found + res_is_str so the tail skips integers.
self.Gen.TypeGenCDef.register_cdefine_string(pool, name, val_buf)
result_val = 0
result_found = 1
res_is_str = 1
elif is_name_val != 0:
# True/False literal -> 1/0 (TPC treats bool as integer in CDefine context)
if string.strcmp(val_buf, "True") == 0: lval = 1
elif string.strcmp(val_buf, "False") == 0: lval = 0
else:
# name reference: look up the current CDefine table (e.g. FOREGROUND_COMBO)
lval = self.Gen.TypeGenCDef.lookup_cdefine_constant(val_buf)
if self.Gen.TypeGenCDef.is_cdefine_found() == 0:
# name not registered: cannot evaluate, skip the line
continue
elif is_hex == 1:
# parse hex manually (skip the 0x prefix) with 64-bit accumulation so
# 0xFFFFFFFF=4294967295 keeps full precision (constant side truncates to i32 -1).
hex_result: int64 = 0
hex_buf: str = val_buf + 2 # skip "0x"
for hi in range(int(val_str_len - 2)):
hc: char = hex_buf[hi]
hd: int = 0
if hc >= '0' and hc <= '9': hd = hc - '0'
elif hc >= 'a' and hc <= 'f': hd = hc - 'a' + 10
elif hc >= 'A' and hc <= 'F': hd = hc - 'A' + 10
else: break
hex_result = hex_result * 16 + hd
lval = hex_result
elif is_float_val != 0:
l_is_double = 1
ldbl = string.atof(val_buf)
lval = string.atoll(val_buf)
else: lval = string.atoll(val_buf)
if is_neg != 0:
lval = -lval
if l_is_double != 0: ldbl = -ldbl
# skip spaces; check for a binary operator
while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1
# parse operator (<< >> | & ^ + - * /)
cd_op: int = 0
if eq_pos + 1 < line_start + line_len:
if pyi_buf[eq_pos] == '<' and pyi_buf[eq_pos + 1] == '<':
cd_op = 1 # LShift
eq_pos += 2
elif pyi_buf[eq_pos] == '>' and pyi_buf[eq_pos + 1] == '>':
cd_op = 2 # RShift
eq_pos += 2
if cd_op == 0 and eq_pos < line_start + line_len:
if pyi_buf[eq_pos] == '|':
cd_op = 3 # BitOr
eq_pos += 1
elif pyi_buf[eq_pos] == '&':
cd_op = 4 # BitAnd
eq_pos += 1
elif pyi_buf[eq_pos] == '^':
cd_op = 5 # BitXor
eq_pos += 1
elif pyi_buf[eq_pos] == '+':
cd_op = 6 # Add
eq_pos += 1
elif pyi_buf[eq_pos] == '-':
cd_op = 7 # Sub
eq_pos += 1
elif pyi_buf[eq_pos] == '*':
cd_op = 8 # Mult
eq_pos += 1
elif pyi_buf[eq_pos] == '/':
cd_op = 9 # Div
eq_pos += 1
if cd_op != 0:
# === parse right operand token ===
while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1
r_neg: int = 0
if eq_pos < line_start + line_len and pyi_buf[eq_pos] == '-':
r_neg = 1
eq_pos += 1
r_start: sizet = eq_pos
r_len: sizet = 0
r_hex: int = 0
r_name: int = 0
if eq_pos + 1 < line_start + line_len:
if pyi_buf[eq_pos] == '0' and (pyi_buf[eq_pos + 1] == 'x' or pyi_buf[eq_pos + 1] == 'X'):
r_hex = 1
r_len = 2
eq_pos += 2
elif (pyi_buf[eq_pos] >= 'A' and pyi_buf[eq_pos] <= 'Z') or (pyi_buf[eq_pos] >= 'a' and pyi_buf[eq_pos] <= 'z') or pyi_buf[eq_pos] == '_':
r_name = 1
while eq_pos < line_start + line_len:
c_r: char = pyi_buf[eq_pos]
if (c_r >= 'A' and c_r <= 'Z') or (c_r >= 'a' and c_r <= 'z') or (c_r >= '0' and c_r <= '9') or c_r == '_':
r_len += 1
eq_pos += 1
else: break
while eq_pos < line_start + line_len and r_name == 0:
c_r2: char = pyi_buf[eq_pos]
if r_hex == 1:
if ((c_r2 >= '0' and c_r2 <= '9') or (c_r2 >= 'a' and c_r2 <= 'f') or (c_r2 >= 'A' and c_r2 <= 'F')):
r_len += 1
eq_pos += 1
else: break
else:
if c_r2 >= '0' and c_r2 <= '9':
r_len += 1
eq_pos += 1
else: break
if r_len == 0:
# no right operand after operator: cannot evaluate, skip the line
continue
r_buf: str = pool.alloc(r_len + 1)
if r_buf is None: continue
for r_i in range(int(r_len)): r_buf[r_i] = pyi_buf[r_start + r_i]
r_buf[r_len] = '\0'
rval: int64 = 0
if r_name != 0:
# True/False literal -> 1/0
if string.strcmp(r_buf, "True") == 0: rval = 1
elif string.strcmp(r_buf, "False") == 0: rval = 0
else:
rval = self.Gen.TypeGenCDef.lookup_cdefine_constant(r_buf)
if self.Gen.TypeGenCDef.is_cdefine_found() == 0:
# name not registered: cannot evaluate, skip the line
continue
elif r_hex != 0:
rhex_v: int64 = 0
rh_buf: str = r_buf + 2
for rh_i in range(int(r_len - 2)):
rc: char = rh_buf[rh_i]
rd: int = 0
if rc >= '0' and rc <= '9': rd = rc - '0'
elif rc >= 'a' and rc <= 'f': rd = rc - 'a' + 10
elif rc >= 'A' and rc <= 'F': rd = rc - 'A' + 10
else: break
rhex_v = rhex_v * 16 + rd
rval = rhex_v
else: rval = string.atoll(r_buf)
if r_neg != 0: rval = -rval
# compute the binary expression result (aligned with TPC ConstEvaluator)
if cd_op == 1: result_val = lval << rval
elif cd_op == 2: result_val = lval >> rval
elif cd_op == 3: result_val = lval | rval
elif cd_op == 4: result_val = lval & rval
elif cd_op == 5: result_val = lval ^ rval
elif cd_op == 6: result_val = lval + rval
elif cd_op == 7: result_val = lval - rval
elif cd_op == 8: result_val = lval * rval
else:
if rval == 0: continue
result_val = lval // rval
result_found = 1
break
else:
# single value (no operator): parenthesized values like (512) / (-11) also land here
if is_paren_expr != 0:
while eq_pos < line_start + line_len and pyi_buf[eq_pos] == ' ': eq_pos += 1
if eq_pos < line_start + line_len and pyi_buf[eq_pos] == ')': eq_pos += 1
if is_float_val != 0:
# float CDefine (e.g. U_M_PI = 3.14159...): cache as double for double
# constant generation; int part serves the int path (is_cdefine_double flag).
# 注解声明为 float 时带上 f32 种类,使用方才能直接出 f32 常量。
res_is_f32 = self._pyi_annot_float_kind(pyi_buf, annot_start, annot_end)
self.Gen.TypeGenCDef.register_cdefine_double(pool, name, ldbl, res_is_f32)
result_val = lval
result_found = 1
res_is_double = 1
else:
result_val = lval
result_found = 1
break
# 9.5 if not found in this module, recurse into submodules for `from .X import NAME` /
# `from .X import *` (e.g. ast.__init__ re-exports CONST_INT from .base).
if result_found == 0:
# base_mod was computed at function entry (mod_name_raw with leading dots removed);
# the submodule recursion uses it to build the full "base_mod.X" name.
name_len_rc: sizet = string.strlen(name)
rc_pos: sizet = 0
while rc_pos < total_len and result_found == 0:
# find line start
rc_line_start: sizet = rc_pos
# find line end
while rc_pos < total_len and pyi_buf[rc_pos] != '\n': rc_pos += 1
rc_line_len: sizet = rc_pos - rc_line_start
if rc_pos < total_len: rc_pos += 1
# check whether the line starts with "from " (module import)
if rc_line_len < 12: continue
if pyi_buf[rc_line_start] != 'f' or pyi_buf[rc_line_start+1] != 'r' or pyi_buf[rc_line_start+2] != 'o' or pyi_buf[rc_line_start+3] != 'm' or pyi_buf[rc_line_start+4] != ' ': continue
# extract the module token (skip leading dots); support both relative
# `from .base import X` (join base_mod.base) and absolute `from vqt6._types import *`.
rc_is_rel: int = 0
rc_sub_start: sizet = rc_line_start + 5
while rc_sub_start < rc_line_start + rc_line_len and pyi_buf[rc_sub_start] == '.':
rc_is_rel = 1
rc_sub_start += 1
rc_sub_end: sizet = rc_sub_start
while rc_sub_end < rc_line_start + rc_line_len and pyi_buf[rc_sub_end] != ' ' and pyi_buf[rc_sub_end] != '\t': rc_sub_end += 1
rc_sub_len: sizet = rc_sub_end - rc_sub_start
if rc_sub_len == 0: continue
# check whether the line contains "import"
rc_has_import: int = 0
rc_ipos: sizet = rc_sub_end
while rc_ipos + 6 <= rc_line_start + rc_line_len:
if pyi_buf[rc_ipos] == 'i' and pyi_buf[rc_ipos+1] == 'm' and pyi_buf[rc_ipos+2] == 'p' and pyi_buf[rc_ipos+3] == 'o' and pyi_buf[rc_ipos+4] == 'r' and pyi_buf[rc_ipos+5] == 't':
rc_has_import = 1
break
rc_ipos += 1
if rc_has_import == 0: continue
# check NAME is in the import list, or the import is *; handle multi-line
# imports: scan from "import" up to ")" when a "(" is present.
rc_imp_start: sizet = rc_ipos + 6
rc_star: int = 0
rc_name_match: int = 0
# determine the scan range: single line or multi-line (inside parens)
rc_scan_end: sizet = rc_line_start + rc_line_len
rc_has_paren: int = 0
rc_pp: sizet = rc_imp_start
while rc_pp < rc_line_start + rc_line_len:
if pyi_buf[rc_pp] == '(':
rc_has_paren = 1
break
if pyi_buf[rc_pp] == '*': rc_star = 1
rc_pp += 1
if rc_has_paren != 0:
# multi-line import: scan from after "(" to ")"
rc_scan_end = rc_pp + 1
rc_paren_closed: int = 0
rc_sp: sizet = rc_pp + 1
while rc_sp < total_len and rc_paren_closed == 0:
if pyi_buf[rc_sp] == ')':
rc_paren_closed = 1
rc_scan_end = rc_sp
break
rc_sp += 1
if rc_paren_closed == 0: rc_scan_end = total_len
# scan from rc_imp_start to rc_scan_end for NAME and *
rc_cpos: sizet = rc_imp_start
while rc_cpos < rc_scan_end:
rc_ch: char = pyi_buf[rc_cpos]
if rc_ch == '*': rc_star = 1
# check NAME matches as a whole word
if rc_cpos + name_len_rc <= rc_scan_end:
rc_match: int = 1
rc_ki: sizet
for rc_ki in range(int(name_len_rc)):
if pyi_buf[rc_cpos + rc_ki] != name[rc_ki]:
rc_match = 0
break
if rc_match == 1:
# check the preceding char is a non-identifier char
rc_before_ok: int = 0
if rc_cpos == rc_imp_start: rc_before_ok = 1
else:
rc_before_ch: char = pyi_buf[rc_cpos - 1]
if rc_before_ch == ' ' or rc_before_ch == '\t' or rc_before_ch == '(' or rc_before_ch == ',' or rc_before_ch == '\n' or rc_before_ch == '\r': rc_before_ok = 1
# check the following char is a non-identifier char
rc_after_ok: int = 0
rc_after_pos: sizet = rc_cpos + name_len_rc
if rc_after_pos >= rc_scan_end: rc_after_ok = 1
else:
rc_after_ch: char = pyi_buf[rc_after_pos]
if rc_after_ch == ' ' or rc_after_ch == '\t' or rc_after_ch == ')' or rc_after_ch == ',' or rc_after_ch == '\r' or rc_after_ch == '\n': rc_after_ok = 1
if rc_before_ok == 1 and rc_after_ok == 1: rc_name_match = 1
rc_cpos += 1
if rc_star == 0 and rc_name_match == 0: continue
# copy the submodule name into a new buffer (pyi_buf + rc_sub_start is not NUL-terminated)
rc_sub_buf: str = pool.alloc(rc_sub_len + 1)
if rc_sub_buf is None: continue
rc_si: sizet
for rc_si in range(int(rc_sub_len)): rc_sub_buf[rc_si] = pyi_buf[rc_sub_start + rc_si]
rc_sub_buf[rc_sub_len] = '\0'
# build the full module name: relative import -> package + "." + token (the base
# must be the package, not the module name); absolute import -> token as-is.
rc_full_mod: str = None
if rc_is_rel != 0:
rc_base_mod: str = base_mod
rc_pkg: str = self.Gen.CallGenBase._module_package_from_sha1(pool, sha1)
if rc_pkg is not None: rc_base_mod = rc_pkg
rc_full_len: sizet = string.strlen(rc_base_mod) + 1 + rc_sub_len + 1
rc_full_buf: str = pool.alloc(rc_full_len)
if rc_full_buf is None: continue
viperlib.snprintf(rc_full_buf, rc_full_len, "%s.%s", rc_base_mod, rc_sub_buf)
rc_full_mod = rc_full_buf
else:
if string.strcmp(rc_sub_buf, base_mod) == 0: continue
rc_full_mod = rc_sub_buf
# loop guard: if base_mod already ends with "."+rc_sub_buf (ast.base + .base),
# the recursion is re-appending the same segment -> skip (infinite-growth root cause).
rc_blen: sizet = string.strlen(base_mod)
if rc_blen > rc_sub_len + 1:
rc_tail: str = base_mod + (rc_blen - rc_sub_len)
if rc_tail[0] == '.' and string.strncmp(rc_tail + 1, rc_sub_buf, rc_sub_len) == 0: continue
# build a temporary from_imports "name:full_mod"
rc_fi_len: sizet = name_len_rc + 1 + string.strlen(rc_full_mod) + 1
rc_fi: str = pool.alloc(rc_fi_len)
if rc_fi is None: continue
viperlib.snprintf(rc_fi, rc_fi_len, "%s:%s", name, rc_full_mod)
# recursive call to search the submodule
rc_sub_val: int64 = self._lookup_cross_module_cdefine(pool, name, rc_fi, None, depth + 1, node)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
result_val = rc_sub_val
result_found = 1
stdlib.free(pyi_buf)
# 9.6 star-fallback module had no match: fall back to walking imported_modules,
# e.g. `from stdint import *` names stdint but SHARE_READ lives in `import win32.fileio`.
if result_found == 0 and imported_modules is not None:
im2_len: sizet = string.strlen(imported_modules)
im2_ci: sizet = 0
while im2_ci < im2_len and result_found == 0:
while im2_ci < im2_len and imported_modules[im2_ci] == ' ': im2_ci += 1
if im2_ci >= im2_len: break
im2_start: sizet = im2_ci
while im2_ci < im2_len and imported_modules[im2_ci] != ' ': im2_ci += 1
im2_end: sizet = im2_ci
im2_nlen: sizet = im2_end - im2_start
if im2_nlen > 0 and im2_nlen < 256:
im2_buf: str = pool.alloc(im2_nlen + 1)
if im2_buf is not None:
for mk3 in range(int(im2_nlen)):
im2_buf[mk3] = imported_modules[im2_start + mk3]
im2_buf[im2_nlen] = '\0'
sub_fi2: str = pool.alloc(im2_nlen + 3)
if sub_fi2 is not None:
sub_fi2[0] = '*'
sub_fi2[1] = ':'
for mk4 in range(int(im2_nlen)):
sub_fi2[2 + mk4] = im2_buf[mk4]
sub_fi2[2 + im2_nlen] = '\0'
sub_val2: int64 = self._lookup_cross_module_cdefine(pool, name, sub_fi2, None, depth + 1, node)
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
result_val = sub_val2
result_found = 1
# 10. cache into the current module's CDefine table and the cross-module perm_pool;
# float CDefines are already cached as double, so only integers are written here.
if result_found != 0:
if res_is_str != 0:
# string CDefine: the value was already registered in the match loop (val_buf is
# the hit line's content, valid across the function); only write the cross cache.
if base_mod is not None:
ck_len_s: sizet = string.strlen(name) + string.strlen(base_mod) + 2
ckey_s: str = pool.alloc(ck_len_s)
if ckey_s is not None:
viperlib.snprintf(ckey_s, ck_len_s, "%s@%s", name, base_mod)
self.Gen.TypeGenCDef.register_xmod_cdefine(ckey_s, val_buf, 0, 1)
elif res_is_double == 0:
self.Gen.TypeGenCDef.register_cdefine_constant(pool, name, result_val)
# cross-module cache (perm_pool, reused across files), integers: key matches the
# entry query "name@srcModule" (truncated base_mod, leading dots removed).
if base_mod is not None:
ck_len: sizet = string.strlen(name) + string.strlen(base_mod) + 2
ckey_buf: str = pool.alloc(ck_len)
if ckey_buf is not None:
viperlib.snprintf(ckey_buf, ck_len, "%s@%s", name, base_mod)
cbuf_i: str = pool.alloc(32)
if cbuf_i is not None:
viperlib.snprintf(cbuf_i, 32, "%lld", result_val)
self.Gen.TypeGenCDef.register_xmod_cdefine(ckey_buf, cbuf_i, 0, 0)
else:
# float: fetch %.17g text from the local table and write the cross cache (lossless)
if base_mod is not None:
ck_len2: sizet = string.strlen(name) + string.strlen(base_mod) + 2
ckey2: str = pool.alloc(ck_len2)
if ckey2 is not None:
viperlib.snprintf(ckey2, ck_len2, "%s@%s", name, base_mod)
dval_cc: double = self.Gen.TypeGenCDef.lookup_cdefine_double(name)
if self.Gen.TypeGenCDef.is_cdefine_double() != 0:
cbuf_d: str = pool.alloc(64)
if cbuf_d is not None:
stdio.snprintf(cbuf_d, 64, "%.17g", dval_cc)
# 浮点种类随缓存一起带上(1=f64 / 2=f32),供后续使用方直接物化宽度
self.Gen.TypeGenCDef.register_xmod_cdefine(ckey2, cbuf_d, self.Gen.TypeGenCDef.xmod_float_kind(), 0)
self.Gen.TypeGenCDef.set_cdefine_found(1)
else: self.Gen.TypeGenCDef.set_cdefine_found(0)
return result_val
# _is_cdefine_name_pattern - CDefine names follow ALL_CAPS (A-Z, 0-9, _ with >=1 uppercase);
# lowercase keywords/variables won't match, avoiding fail-fast false positives. 1=match, 0=no.
def _is_cdefine_name_pattern(self, name: str) -> int:
"""Return 1 if name follows the CDefine convention (uppercase letters, digits, underscore)"""
if name is None: return 0
has_upper: int = 0
i: sizet = 0
while name[i] != '\0':
c: char = name[i]
if c >= 'a' and c <= 'z': return 0
if c >= 'A' and c <= 'Z': has_upper = 1
i += 1
return has_upper
# ============================================================
# Translate a variable reference (Name node) -> load
def _const_int_for_cdefine(self, pool: memhub.MemBuddy*, name: str, value: int64) -> mlirlite.Value*:
"""Materialize a CDefine integer constant as an LLVM constant (TPC TypeConvert aligned)
位宽优先用 define 的**已知位宽**(右侧显式强转或声明注解,见 register_cdefine_constant 的 bits);
没有位宽记录时按**推断位宽**物化(值的最小可存宽度;0x80000000 → 32)。
这样 `define X: WORD = WORD(...)` 的 X 保持 u16、`X | Y` 里两个同宽 u16 不会被提升成
i32;无注解的 `define A = 0x80000000` 也不会退化成 i64 把 `A | B` 放大到 64 位,
而小值 define 说成 8/16 也在算术提升下不会溢出。
"""
dbits: int = 0
if name is not None: dbits = self.Gen.TypeGenCDef.lookup_cdefine_bits(name)
if dbits == 8 or dbits == 16 or dbits == 32 or dbits == 64:
return mlirlite.const_int(pool, dbits, self._cdefine_bits_value(value, dbits))
# 无位宽记录(例如只从 .vpi 跨模块引入、未登记 bits)→ 按**推断位宽**物化:
# 值的最小可存宽度(2^31..2^32-1 取 32 位无符号位型,不退化成 i64;
# 小值取 8/16,参与算术时由整数提升先升到 i32,不会溢出)。
wmin: int = self.Gen.AssignGen.define_min_width(value)
return mlirlite.const_int(pool, wmin, self._cdefine_bits_value(value, wmin))
# ============================================================
# cdefine_use_width_value - materialize CDefine constants at the use-site width (TPC-aligned)
def _cdefine_bits_value(self, value: int64, bits: int) -> int64:
"""把值按位宽处理:**只有在当前位宽的两种解释下都装不下时才截断**,否则原样保留。
「左侧只会看到截断了的数据」:超宽的值先按 bits 取低位(如 WORD(0x80000000) → 0)。
但不该把"无符号可容、有符号装不下"的**正值**重解释成负数:旧实现无条件
`(value << 56) >> 56`,8 位下的 160 被读成 -96,正值静默变负 —— 现场
Gargantua `_start` 的 `string.memset(fb, 0, GW * GH * 4)`(GW=160, GH=90)
被折成 -34560,作为长度写穿映射区 → #PF。位型不变(const_int 本就按位宽截断),
只是不再翻转可容正值的语义。实现仍避开大掩码字面量(见下)。
"""
if bits == 8:
if value >= -128 and value <= 127: return value
if value > 127 and (value >> 8) == 0: return value
return (value << 56) >> 56
if bits == 16:
if value >= -32768 and value <= 32767: return value
if value > 32767 and (value >> 16) == 0: return value
return (value << 48) >> 48
if bits == 32:
if value >= -2147483647 - 1 and value <= 2147483647: return value
if value > 2147483647 and (value >> 32) == 0: return value
return (value << 32) >> 32
return value
def cdefine_use_width_value(self, pool: memhub.MemBuddy*, node: ast.AST*, name: str, value: int64, target_ty: mlirlite.LLVMType*, use_ctx: str = None) -> mlirlite.Value*:
"""Materialize a CDefine integer constant at a known use-site width, or None on fallback"""
bits: int = self.Gen.ExprGenBase.get_llvm_type_bits(target_ty)
if bits != 8 and bits != 16 and bits != 32 and bits != 64: return None
wmin: int = self.Gen.AssignGen.define_min_width(value)
if bits < wmin:
# "representable range at the use site" uses the same rule as the check = TPC
# DefineWidthLoHi = [-(2^(bits-1)), 2^bits - 1]; literals built from safe 2147483647.
m31: int64 = 2147483647
lo: int64 = -m31 - 1
hi: int64 = m31 * 2 + 1
if bits == 8:
lo = -128
hi = 255
elif bits == 16:
lo = -32768
hi = 65535
ctx: str = ""
if use_ctx is not None:
# this language's str is i8* with no `str + str`: `+` degrades to pointer add.
# Build strings with f-string interpolation / string.strcat / viperlib.snprintf.
ctx = f"({use_ctx})"
self.Gen.TypeGenErr.fatal_error(node, f"[VPC] define constant use site width insufficient: constant '{name}' actual value {value} " f"needs at least {wmin} bits (minimum representable width W_min), " f"but the use site{ctx} provides only {bits} bits " f"(the acceptable range for this bit width under both signed/unsigned interpretation: {lo}..{hi}). " f"silent truncation/downgrade is forbidden, compilation aborted. " f"fix: widen the type of the use site{ctx} to at least {wmin} bits " f"(e.g. int{wmin}T / CUInt{wmin}T or wider types), or use a smaller constant value.", "TypeError")
return None
return mlirlite.const_int(pool, bits, self._cdefine_bits_value(value, bits))