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))