919 lines
50 KiB
Plaintext
919 lines
50 KiB
Plaintext
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)) |