feat(vpc): literal-container membership, one vpi scanner, extern/default fixes
- `x in (a,b,c)` / `[..]` / `{..}` lowers to an OR of equality tests (string
literals via strcmp) and `not in` negates it; literal containers used to
silently evaluate to false
- add VpiText (ParenEnd/ParamSpans/ParamCount/HasVararg/FindDefaultEq) and
drop the five hand-written copies of the parameter scanner; escape skips no
longer jump past the parameter-list end
- fill omitted arguments from recorded defaults before any zero-fill, and fail
fast when no default is recorded (memcpy keeps its intrinsic lowering)
- resolve package-qualified `extern def` calls without a SHA1 prefix:
explicit allow_star=1, rel-path module fallback, raw vpi bare-declaration
scan, and `extern def`/`export def` flags honoured during declare injection
- fix exact-size read_all usage in VpiStub.Load/Ensure and the vpi scanner
This commit is contained in:
1 parent
84f11a69e7
commit
83d15289ef
6 files changed
+401
-233
No files matched your search
@@ -15,6 +15,7 @@ import lib.core.VLogger as VLogger
|
||||
import lib.core.Gen.Translator as HT
|
||||
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
||||
import lib.Stub.StubGen.VpiStub as VpiStub
|
||||
import lib.Stub.StubGen.VpiText as VpiText
|
||||
import lib.Projectrans.Config as Config
|
||||
|
||||
|
||||
@@ -1892,6 +1893,59 @@ def _lookup_vpi_bare(src_sha1: str, cls_name: str, method_name: str) -> int:
|
||||
return int(_g_vpi_bare_flags[hit_b])
|
||||
|
||||
|
||||
# _vpi_declares_bare - raw text scan for a no-mangle declaration of func_name.
|
||||
# The return-annotation cache is the fast path, but it can lack a module that was loaded
|
||||
# before the declaration existed; the file on disk is the authority for extern/export/state.
|
||||
def _vpi_declares_bare(src_sha1: str, temp_dir: str, func_name: str) -> int:
|
||||
"""1 when the module's .vpi declares func_name as extern/export/state (no SHA1 prefix)."""
|
||||
if src_sha1 is None or temp_dir is None or func_name is None: return 0
|
||||
vpi_path: str = _vpi_build_path(temp_dir, src_sha1, ".vpi")
|
||||
if vpi_path is None: return 0
|
||||
f: io.File* = io.File(vpi_path, io.MODE.R)
|
||||
stdlib.free(vpi_path)
|
||||
if f.closed: return 0
|
||||
sz: LONGLONG = f.size()
|
||||
if sz <= 0 or sz > 4194304:
|
||||
f.close()
|
||||
return 0
|
||||
buf: bytes = stdlib.malloc(sizet(sz) + 1)
|
||||
if buf is None:
|
||||
f.close()
|
||||
return 0
|
||||
rd: LONG = f.read_all(buf, sizet(sz) + 1)
|
||||
f.close()
|
||||
if int64(rd) != sz:
|
||||
stdlib.free(buf)
|
||||
return 0
|
||||
buf[sizet(sz)] = 0
|
||||
total: sizet = sizet(sz)
|
||||
nlen: sizet = string.strlen(func_name)
|
||||
pos: sizet = 0
|
||||
hit: int = 0
|
||||
while pos < total:
|
||||
le: sizet = pos
|
||||
while le < total and buf[le] != 0x0A: le += 1
|
||||
i: sizet = pos
|
||||
while i < le and buf[i] == ' ': i += 1
|
||||
prefixed: int = 0
|
||||
if le - i >= 7 and (string.strncmp(buf + i, "extern ", 7) == 0 or string.strncmp(buf + i, "export ", 7) == 0):
|
||||
i += 7
|
||||
prefixed = 1
|
||||
elif le - i >= 6 and string.strncmp(buf + i, "state ", 6) == 0:
|
||||
i += 6
|
||||
prefixed = 1
|
||||
if prefixed != 0 and le - i >= 4 and string.strncmp(buf + i, "def ", 4) == 0:
|
||||
ns: sizet = i + 4
|
||||
ne: sizet = ns
|
||||
while ne < le and buf[ne] != '(' and buf[ne] != ' ': ne += 1
|
||||
if ne - ns == nlen and string.strncmp(buf + ns, func_name, nlen) == 0:
|
||||
hit = 1
|
||||
break
|
||||
pos = le + 1
|
||||
stdlib.free(buf)
|
||||
return hit
|
||||
|
||||
|
||||
# _vpi_struct_or_enum - 结构体优先,但 **opaque 前向占位** 必须让位给已注册的枚举。
|
||||
# 占位来源:枚举名曾在别的模块被当成类名 ensure_forward_class(StructTable 是进程级的),
|
||||
# 若这里按裸名命中它,`-> types.FRESULT` 就会变成"返回一个空结构体",与函数本体的 i32 冲突。
|
||||
@@ -2529,26 +2583,8 @@ define VPI_DEF_SCAN_MAX = 128
|
||||
|
||||
# _VpiParamListEnd - 与 open_pos 处 '(' 配对的 ')' 下标;找不到返回 le
|
||||
def _VpiParamListEnd(vbuf: bytes, open_pos: sizet, le: sizet) -> sizet:
|
||||
"""返回与 open_pos 处 '(' 配对的 ')' 下标;找不到返回 le"""
|
||||
depth: int = 0
|
||||
q: uint8 = 0
|
||||
i: sizet = open_pos
|
||||
while i < le:
|
||||
ch: uint8 = vbuf[i]
|
||||
if q != 0:
|
||||
if ch == 0x5C:
|
||||
i += 2
|
||||
continue
|
||||
if ch == q: q = 0
|
||||
i += 1
|
||||
continue
|
||||
if ch == 0x22 or ch == 0x27: q = ch
|
||||
elif ch == 0x28 or ch == 0x5B or ch == 0x7B: depth += 1
|
||||
elif ch == 0x29 or ch == 0x5D or ch == 0x7D:
|
||||
depth -= 1
|
||||
if depth <= 0: return i
|
||||
i += 1
|
||||
return le
|
||||
"""Index of the ')' matching the '(' at open_pos (le when unbalanced)."""
|
||||
return VpiText.ParenEnd(vbuf, open_pos, le)
|
||||
|
||||
# _VpiParseIntRange - 把 [s, e) 解析成整数(十进制 / 0x 十六进制 / 带符号);1=成功
|
||||
def _VpiParseIntRange(vbuf: bytes, s: sizet, e: sizet, out_v: int64*) -> int:
|
||||
@@ -2633,72 +2669,20 @@ def _VpiFillDefaultLit(pool: memhub.MemBuddy*, vbuf: bytes, s: sizet, e: sizet,
|
||||
|
||||
# _VpiParseParamDefaults - 解析参数区间:返回 (参数个数 << 8) | 缺省值个数;-1=有不可重建缺省值
|
||||
def _VpiParseParamDefaults(pool: memhub.MemBuddy*, vbuf: bytes, ps: sizet, pe: sizet, kinds: int*, ints: int64*, strs: sizet*) -> int:
|
||||
"""解析 [ps, pe) 参数列表,统计参数个数与尾部缺省值字面量"""
|
||||
pc: int = 0
|
||||
"""Count parameters and snapshot trailing defaults; (count << 8) | defaults, or -1."""
|
||||
spans: sizet* = pool.alloc(sizet(VPI_DEF_SCAN_MAX) * 16)
|
||||
if spans is None: return -1
|
||||
pc: int = VpiText.ParamSpans(vbuf, ps, pe, spans, VPI_DEF_SCAN_MAX, None)
|
||||
if pc < 0: return -1
|
||||
dc: int = 0
|
||||
ss: sizet = ps
|
||||
i: sizet = ps
|
||||
depth: int = 0
|
||||
q: uint8 = 0
|
||||
while i <= pe:
|
||||
is_sep: int = 0
|
||||
if i == pe:
|
||||
is_sep = 1
|
||||
else:
|
||||
ch: uint8 = vbuf[i]
|
||||
if q != 0:
|
||||
if ch == 0x5C:
|
||||
i += 2
|
||||
continue
|
||||
if ch == q: q = 0
|
||||
i += 1
|
||||
continue
|
||||
if ch == 0x22 or ch == 0x27: q = ch
|
||||
elif ch == 0x28 or ch == 0x5B or ch == 0x7B: depth += 1
|
||||
elif ch == 0x29 or ch == 0x5D or ch == 0x7D: depth -= 1
|
||||
elif ch == 0x2C and depth == 0: is_sep = 1
|
||||
if is_sep == 0:
|
||||
i += 1
|
||||
continue
|
||||
a: sizet = ss
|
||||
b: sizet = i
|
||||
while a < b and vbuf[a] == 0x20: a += 1
|
||||
while b > a and vbuf[b - 1] == 0x20: b -= 1
|
||||
if b > a:
|
||||
pc += 1
|
||||
# 找顶层 '='(排除 == / >= / <= / !=)
|
||||
eq: sizet = b
|
||||
d2: int = 0
|
||||
q2: uint8 = 0
|
||||
j: sizet = a
|
||||
while j < b:
|
||||
cj: uint8 = vbuf[j]
|
||||
if q2 != 0:
|
||||
if cj == 0x5C:
|
||||
j += 2
|
||||
continue
|
||||
if cj == q2: q2 = 0
|
||||
j += 1
|
||||
continue
|
||||
if cj == 0x22 or cj == 0x27: q2 = cj
|
||||
elif cj == 0x28 or cj == 0x5B or cj == 0x7B: d2 += 1
|
||||
elif cj == 0x29 or cj == 0x5D or cj == 0x7D: d2 -= 1
|
||||
elif cj == 0x3D and d2 == 0:
|
||||
prev: uint8 = 0
|
||||
nxt: uint8 = 0
|
||||
if j > a: prev = vbuf[j - 1]
|
||||
if j + 1 < b: nxt = vbuf[j + 1]
|
||||
if nxt != 0x3D and prev != 0x3D and prev != 0x3C and prev != 0x3E and prev != 0x21:
|
||||
eq = j
|
||||
break
|
||||
j += 1
|
||||
if eq < b:
|
||||
if dc >= VPI_DEF_SCAN_MAX: return -1
|
||||
if _VpiFillDefaultLit(pool, vbuf, eq + 1, b, kinds, ints, strs, dc) == 0: return -1
|
||||
dc += 1
|
||||
if i >= pe: break
|
||||
i += 1
|
||||
ss = i
|
||||
for pi in range(int(pc)):
|
||||
a: sizet = spans[pi * 2]
|
||||
b: sizet = spans[pi * 2 + 1]
|
||||
eq: sizet = VpiText.FindDefaultEq(vbuf, a, b)
|
||||
if eq < b:
|
||||
if dc >= VPI_DEF_SCAN_MAX: return -1
|
||||
if _VpiFillDefaultLit(pool, vbuf, eq + 1, b, kinds, ints, strs, dc) == 0: return -1
|
||||
dc += 1
|
||||
if pc > 255 or dc > 255: return -1
|
||||
return (pc << 8) | dc
|
||||
|
||||
@@ -2988,43 +2972,11 @@ def PreRegisterVpiEnums(pool: memhub.MemBuddy*, temp_dir: str) -> int:
|
||||
|
||||
# _VpiParamCountAndVararg - 数参数个数;含 *args / **kwargs 时返回 -1(调用方跳过 arity 校验)
|
||||
def _VpiParamCountAndVararg(vbuf: bytes, ps: sizet, pe: sizet) -> int:
|
||||
"""数参数个数;含可变参数返回 -1"""
|
||||
pc: int = 0
|
||||
ss: sizet = ps
|
||||
i: sizet = ps
|
||||
depth: int = 0
|
||||
q: uint8 = 0
|
||||
while i <= pe:
|
||||
is_sep: int = 0
|
||||
if i == pe:
|
||||
is_sep = 1
|
||||
else:
|
||||
ch: uint8 = vbuf[i]
|
||||
if q != 0:
|
||||
if ch == 0x5C:
|
||||
i += 2
|
||||
continue
|
||||
if ch == q: q = 0
|
||||
i += 1
|
||||
continue
|
||||
if ch == 0x22 or ch == 0x27: q = ch
|
||||
elif ch == 0x28 or ch == 0x5B or ch == 0x7B: depth += 1
|
||||
elif ch == 0x29 or ch == 0x5D or ch == 0x7D: depth -= 1
|
||||
elif ch == 0x2C and depth == 0: is_sep = 1
|
||||
if is_sep == 0:
|
||||
i += 1
|
||||
continue
|
||||
a: sizet = ss
|
||||
b: sizet = i
|
||||
while a < b and vbuf[a] == 0x20: a += 1
|
||||
while b > a and vbuf[b - 1] == 0x20: b -= 1
|
||||
if b > a:
|
||||
if vbuf[a] == 0x2A: return -1
|
||||
pc += 1
|
||||
if i >= pe: break
|
||||
i += 1
|
||||
ss = i
|
||||
return pc
|
||||
"""Number of parameters; -1 when the list contains *args/**kwargs."""
|
||||
n: int = VpiText.ParamCount(vbuf, ps, pe)
|
||||
if n < 0: return -1
|
||||
if VpiText.HasVararg(vbuf, ps, pe) != 0: return -1
|
||||
return n
|
||||
|
||||
# LookupSignDefaultsByName - 直接在定义模块 .vpi 文本里查一个函数/方法的缺省参数快照
|
||||
# 与 PreRegisterVpiDefaults 的区别:不登记进全局表,只按名字现查现用(表键不可靠时的兜底)。
|
||||
@@ -4324,10 +4276,15 @@ def _inject_function_declare(pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*,
|
||||
has_extern: int = 0
|
||||
has_state: int = 0
|
||||
has_export: int = 0
|
||||
# `extern def` / `export def` recorded as the vpi keyword are the same storage markers as the
|
||||
# legacy t.ExternT / t.ExportT return annotations: the defining side emits the bare C name, so
|
||||
# the injected declare must be bare as well (a SHA1 prefix leaves the symbol undefined).
|
||||
if (fd.flags & ast.FLAG_IS_EXTERN) != 0: has_extern = 1
|
||||
if (fd.flags & ast.FLAG_IS_EXPORT) != 0: has_export = 1
|
||||
if fd.returns is not None:
|
||||
has_extern = GenApi.has_decorator_marker(fd.returns, "ExternT")
|
||||
has_state = GenApi.has_decorator_marker(fd.returns, "State")
|
||||
has_export = GenApi.has_decorator_marker(fd.returns, "ExportT")
|
||||
if GenApi.has_decorator_marker(fd.returns, "ExternT") != 0: has_extern = 1
|
||||
if GenApi.has_decorator_marker(fd.returns, "State") != 0: has_state = 1
|
||||
if GenApi.has_decorator_marker(fd.returns, "ExportT") != 0: has_export = 1
|
||||
# Bare-struct param rule (see _AggregateParamTy): C ABI externs (t.ExternT/t.State) by value;
|
||||
# the rest (incl. t.ExportT) keep Ptr(struct). Same source as FuncGenDef.is_extern_decl.
|
||||
agg_is_c_abi: int = 0
|
||||
|
||||
@@ -3,6 +3,7 @@ import string
|
||||
import stdlib
|
||||
import io
|
||||
import lib.Projectrans.Config as Config
|
||||
import lib.Stub.StubGen.VpiText as VpiText
|
||||
|
||||
# VpiStub - binary companion of the human-readable .vpi stub.
|
||||
#
|
||||
@@ -238,59 +239,25 @@ def _vpb_strip_prefixes(p: sizet, buf: bytes, le: sizet) -> sizet:
|
||||
|
||||
# _vpb_param_count - top-level comma count of a parameter list slice.
|
||||
def _vpb_param_count(buf: bytes, s: sizet, e: sizet, first_self: int*) -> int:
|
||||
depth: int = 0
|
||||
quote: int = 0
|
||||
count: int = 0
|
||||
seen: int = 0
|
||||
first_name_start: sizet = s
|
||||
while first_name_start < e and buf[first_name_start] == ' ': first_name_start += 1
|
||||
i: sizet = s
|
||||
while i < e:
|
||||
ch: int = buf[i]
|
||||
if quote != 0:
|
||||
if ch == 0x5C: i += 2; continue
|
||||
if ch == quote: quote = 0
|
||||
i += 1
|
||||
continue
|
||||
if ch == 0x22 or ch == 0x27: quote = ch
|
||||
elif ch == 0x28 or ch == 0x5B or ch == 0x7B: depth += 1
|
||||
elif ch == 0x29 or ch == 0x5D or ch == 0x7D: depth -= 1
|
||||
elif ch == 0x2C and depth == 0:
|
||||
if seen == 0: seen = 1
|
||||
count += 1
|
||||
elif ch != ' ' and ch != 0x09:
|
||||
seen = 1
|
||||
i += 1
|
||||
if count > 0: count += 1
|
||||
elif seen != 0: count = 1
|
||||
"""Arity from a parameter list slice; first_self (optional) reports self/cls."""
|
||||
if first_self is not None:
|
||||
name_len: sizet = 0
|
||||
k: sizet = first_name_start
|
||||
first_self[0] = 0
|
||||
ns: sizet = s
|
||||
while ns < e and buf[ns] == ' ': ns += 1
|
||||
nl: sizet = 0
|
||||
k: sizet = ns
|
||||
while k < e and (buf[k] == '_' or (buf[k] >= 'a' and buf[k] <= 'z') or (buf[k] >= 'A' and buf[k] <= 'Z') or (buf[k] >= '0' and buf[k] <= '9')):
|
||||
k += 1
|
||||
name_len += 1
|
||||
first_self[0] = 0
|
||||
if name_len == 4 and string.strncmp(buf + first_name_start, "self", 4) == 0: first_self[0] = 1
|
||||
elif name_len == 3 and string.strncmp(buf + first_name_start, "cls", 3) == 0: first_self[0] = 1
|
||||
return count
|
||||
nl += 1
|
||||
if nl == 4 and string.strncmp(buf + ns, "self", 4) == 0: first_self[0] = 1
|
||||
elif nl == 3 and string.strncmp(buf + ns, "cls", 3) == 0: first_self[0] = 1
|
||||
return VpiText.ParamCount(buf, s, e)
|
||||
|
||||
|
||||
# _vpb_has_vararg - 1 when a top-level parameter starts with '*'.
|
||||
def _vpb_has_vararg(buf: bytes, s: sizet, e: sizet) -> int:
|
||||
depth: int = 0
|
||||
fresh: int = 1
|
||||
i: sizet = s
|
||||
while i < e:
|
||||
ch: int = buf[i]
|
||||
if ch == 0x28 or ch == 0x5B or ch == 0x7B: depth += 1
|
||||
elif ch == 0x29 or ch == 0x5D or ch == 0x7D: depth -= 1
|
||||
elif depth == 0:
|
||||
if ch == 0x2C: fresh = 1
|
||||
elif ch != ' ' and ch != 0x09:
|
||||
if fresh != 0 and ch == 0x2A: return 1
|
||||
fresh = 0
|
||||
i += 1
|
||||
return 0
|
||||
"""1 when a top-level parameter starts with '*'."""
|
||||
return VpiText.HasVararg(buf, s, e)
|
||||
|
||||
|
||||
# Build - convert a .vpi text buffer into a stub blob (records are allocated from stdlib).
|
||||
@@ -374,18 +341,7 @@ def Build(text: bytes, text_len: sizet, sha1: str) -> VpiStub*:
|
||||
# parenthesised parameter list
|
||||
ps: sizet = ne
|
||||
while ps < le and text[ps] != '(': ps += 1
|
||||
pe: sizet = ps + 1
|
||||
depth: int = 1
|
||||
quote: int = 0
|
||||
while pe < le and depth > 0:
|
||||
ch: int = text[pe]
|
||||
if quote != 0:
|
||||
if ch == 0x5C: pe += 2; continue
|
||||
if ch == quote: quote = 0
|
||||
elif ch == 0x22 or ch == 0x27: quote = ch
|
||||
elif ch == 0x28: depth += 1
|
||||
elif ch == 0x29: depth -= 1
|
||||
pe += 1
|
||||
pe: sizet = VpiText.ParenEnd(text, ps, le) + 1
|
||||
inner_end: sizet = pe - 1
|
||||
if inner_end > ps:
|
||||
fs: int = 0
|
||||
@@ -569,10 +525,10 @@ def Ensure(vpi_path: str, sha1: str) -> int:
|
||||
f: io.File* = io.File(p, io.MODE.R)
|
||||
if not f.closed:
|
||||
# Trust an existing companion only when its header matches this format/version.
|
||||
hdr: bytes = stdlib.malloc(8)
|
||||
hdr: bytes = stdlib.malloc(9)
|
||||
ok: int = 0
|
||||
if hdr is not None:
|
||||
rd0: LONG = f.read_all(hdr, 8)
|
||||
rd0: LONG = f.read_all(hdr, 9)
|
||||
if int64(rd0) == 8:
|
||||
if _vpb_u32(hdr, 0) == uint32(VPB_MAGIC) and _vpb_u32(hdr, 4) == uint32(VPB_VERSION): ok = 1
|
||||
stdlib.free(hdr)
|
||||
@@ -587,11 +543,11 @@ def Ensure(vpi_path: str, sha1: str) -> int:
|
||||
if sz <= 0 or sz > 4194304:
|
||||
rf.close()
|
||||
return 0
|
||||
buf: bytes = stdlib.malloc(sizet(sz))
|
||||
buf: bytes = stdlib.malloc(sizet(sz) + 1)
|
||||
if buf is None:
|
||||
rf.close()
|
||||
return 0
|
||||
rd: LONG = rf.read_all(buf, sizet(sz))
|
||||
rd: LONG = rf.read_all(buf, sizet(sz) + 1)
|
||||
rf.close()
|
||||
if int64(rd) != sz:
|
||||
stdlib.free(buf)
|
||||
@@ -641,11 +597,11 @@ def Load(sha1: str, temp_dir: str) -> VpiStub*:
|
||||
if sz <= 0 or sz > 4194304:
|
||||
f.close()
|
||||
return None
|
||||
buf: bytes = stdlib.malloc(sizet(sz))
|
||||
buf: bytes = stdlib.malloc(sizet(sz) + 1)
|
||||
if buf is None:
|
||||
f.close()
|
||||
return None
|
||||
rd: LONG = f.read_all(buf, sizet(sz))
|
||||
rd: LONG = f.read_all(buf, sizet(sz) + 1)
|
||||
f.close()
|
||||
if int64(rd) != sz:
|
||||
stdlib.free(buf)
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
from stdint import *
|
||||
import string
|
||||
|
||||
# VpiText - shared scanning of .vpi signature text.
|
||||
#
|
||||
# Parenthesis matching, parameter splitting and vararg detection all use one
|
||||
# quote/escape aware state machine here; callers only add their own semantics
|
||||
# (counting, defaults parsing, stub building).
|
||||
|
||||
|
||||
def ParenEnd(buf: bytes, open_pos: sizet, le: sizet) -> sizet:
|
||||
"""Index of the ')' matching the '(' at open_pos; le when unbalanced."""
|
||||
depth: int = 0
|
||||
quote: int = 0
|
||||
i: sizet = open_pos
|
||||
while i < le:
|
||||
ch: int = buf[i]
|
||||
if quote != 0:
|
||||
if ch == 0x5C:
|
||||
i += 2
|
||||
continue
|
||||
if ch == quote: quote = 0
|
||||
i += 1
|
||||
continue
|
||||
if ch == 0x22 or ch == 0x27: quote = ch
|
||||
elif ch == 0x28 or ch == 0x5B or ch == 0x7B: depth += 1
|
||||
elif ch == 0x29 or ch == 0x5D or ch == 0x7D:
|
||||
depth -= 1
|
||||
if depth <= 0: return i
|
||||
i += 1
|
||||
return le
|
||||
|
||||
|
||||
def ParamSpans(buf: bytes, ps: sizet, pe: sizet, spans: sizet*, max: int, vararg: int* = None) -> int:
|
||||
"""Split [ps, pe) into top-level comma-separated parameter spans.
|
||||
|
||||
Each span is trimmed and stored as (start, end) at spans[2*i] / spans[2*i+1].
|
||||
spans may be None to count only; -1 means the caller's buffer is too small.
|
||||
When vararg is given it becomes 1 if any span starts with '*'.
|
||||
"""
|
||||
if vararg is not None: vararg[0] = 0
|
||||
if buf is None or pe <= ps: return 0
|
||||
depth: int = 0
|
||||
quote: int = 0
|
||||
count: int = 0
|
||||
start: sizet = ps
|
||||
i: sizet = ps
|
||||
while i <= pe:
|
||||
sep: int = 0
|
||||
if i == pe:
|
||||
sep = 1
|
||||
else:
|
||||
ch: int = buf[i]
|
||||
if quote != 0:
|
||||
if ch == 0x5C:
|
||||
i += 2
|
||||
if i > pe: i = pe
|
||||
continue
|
||||
if ch == quote: quote = 0
|
||||
i += 1
|
||||
continue
|
||||
if ch == 0x22 or ch == 0x27: quote = ch
|
||||
elif ch == 0x28 or ch == 0x5B or ch == 0x7B: depth += 1
|
||||
elif ch == 0x29 or ch == 0x5D or ch == 0x7D: depth -= 1
|
||||
elif ch == 0x2C and depth == 0: sep = 1
|
||||
if sep != 0:
|
||||
a: sizet = start
|
||||
b: sizet = i
|
||||
while a < b and (buf[a] == 0x20 or buf[a] == 0x09): a += 1
|
||||
while b > a and (buf[b - 1] == 0x20 or buf[b - 1] == 0x09): b -= 1
|
||||
if b > a:
|
||||
if spans is not None:
|
||||
if count >= max: return -1
|
||||
spans[count * 2] = a
|
||||
spans[count * 2 + 1] = b
|
||||
if vararg is not None and buf[a] == 0x2A: vararg[0] = 1
|
||||
count += 1
|
||||
if i >= pe: break
|
||||
start = i + 1
|
||||
i += 1
|
||||
return count
|
||||
|
||||
|
||||
def ParamCount(buf: bytes, ps: sizet, pe: sizet) -> int:
|
||||
return ParamSpans(buf, ps, pe, None, 0, None)
|
||||
|
||||
|
||||
def HasVararg(buf: bytes, ps: sizet, pe: sizet) -> int:
|
||||
flag: int = 0
|
||||
ParamSpans(buf, ps, pe, None, 0, &flag)
|
||||
return flag
|
||||
|
||||
|
||||
def FindDefaultEq(buf: bytes, a: sizet, b: sizet) -> sizet:
|
||||
"""Index of the top-level '=' that starts a default value; b when absent.
|
||||
|
||||
Single '=' only: '==' / '>=' / '<=' / '!=' do not start a default.
|
||||
"""
|
||||
depth: int = 0
|
||||
quote: int = 0
|
||||
j: sizet = a
|
||||
while j < b:
|
||||
ch: int = buf[j]
|
||||
if quote != 0:
|
||||
if ch == 0x5C:
|
||||
j += 2
|
||||
continue
|
||||
if ch == quote: quote = 0
|
||||
j += 1
|
||||
continue
|
||||
if ch == 0x22 or ch == 0x27: quote = ch
|
||||
elif ch == 0x28 or ch == 0x5B or ch == 0x7B: depth += 1
|
||||
elif ch == 0x29 or ch == 0x5D or ch == 0x7D: depth -= 1
|
||||
elif ch == 0x3D and depth == 0:
|
||||
prev: int = 0
|
||||
nxt: int = 0
|
||||
if j > a: prev = buf[j - 1]
|
||||
if j + 1 < b: nxt = buf[j + 1]
|
||||
if nxt != 0x3D and prev != 0x3D and prev != 0x3C and prev != 0x3E and prev != 0x21:
|
||||
return j
|
||||
j += 1
|
||||
return b
|
||||
@@ -327,6 +327,56 @@ novirtual class CallGenBase:
|
||||
"""Look up a module SHA1 by suffix (e.g. "__types" matches "mlirlite.__types") for relative imports, via StubMerger.LookupSha1ByModNameSuffix."""
|
||||
return StubMergerStore.LookupSha1ByModNameSuffix(mod_suffix)
|
||||
|
||||
def _lookup_module_sha1_by_rel(self, pool: memhub.MemBuddy*, mod_name: char*) -> str:
|
||||
"""Resolve a dotted module name through the SHA1 store's rel paths.
|
||||
|
||||
The module-name table is the fast path, but a package's star-imported submodule can be
|
||||
absent from it while the scanner still registered the file; the rel path
|
||||
("includes/vqt6/_qtwidgets.vp") identifies the module exactly and never drifts.
|
||||
"""
|
||||
if pool is None or mod_name is None: return None
|
||||
rel_arr: bytes = StubMergerStore.GetSha1StoreRelArrPtr()
|
||||
sha1_arr: bytes = StubMergerStore.GetSha1StoreArrPtr()
|
||||
count: int = StubMergerStore.GetSha1StoreCount()
|
||||
if rel_arr is None or sha1_arr is None or count <= 0: return None
|
||||
n: sizet = string.strlen(mod_name)
|
||||
cap: sizet = n + 24
|
||||
tgt: char* = pool.alloc(cap)
|
||||
if tgt is None: return None
|
||||
string.memcpy(tgt, "includes/", 9)
|
||||
ti: sizet = 9
|
||||
mi: sizet = 0
|
||||
while mi < n:
|
||||
ch: char = mod_name[mi]
|
||||
tgt[ti] = '/' if ch == '.' else ch
|
||||
ti += 1
|
||||
mi += 1
|
||||
tgt[ti] = '\0'
|
||||
rel_sz: sizet = StubMerger.MAX_REL_PATH_LEN
|
||||
i: int = 0
|
||||
while i < count:
|
||||
rel: char* = rel_arr + sizet(i) * rel_sz
|
||||
rl: sizet = string.strlen(rel)
|
||||
# workspace-relative ("includes/vqt6/x.vp") first, then include-root-relative ("vqt6/x.vp")
|
||||
if rl == ti + 3 and string.strncmp(rel, tgt, ti) == 0:
|
||||
return sha1_arr + sizet(i) * 17
|
||||
if ti > 9 and rl == ti - 6 and string.strncmp(rel, tgt + 9, ti - 9) == 0:
|
||||
return sha1_arr + sizet(i) * 17
|
||||
i += 1
|
||||
# package __init__ form: "vqt6" -> "includes/vqt6/__init__.vp"
|
||||
string.memcpy(tgt + ti, "/__init__.vp", 12)
|
||||
tgt[ti + 12] = '\0'
|
||||
init_len: sizet = ti + 12
|
||||
i = 0
|
||||
while i < count:
|
||||
rel2: char* = rel_arr + sizet(i) * rel_sz
|
||||
if string.strcmp(rel2, tgt) == 0:
|
||||
return sha1_arr + sizet(i) * 17
|
||||
if init_len > 9 and string.strcmp(rel2, tgt + 9) == 0:
|
||||
return sha1_arr + sizet(i) * 17
|
||||
i += 1
|
||||
return None
|
||||
|
||||
def _module_package_from_sha1(self, pool: memhub.MemBuddy*, mod_sha1: char*) -> str:
|
||||
"""Compute the module's package name (directory part, '/' to '.') from the SHA1 store's rel_path"""
|
||||
if pool is None or mod_sha1 is None: return None
|
||||
@@ -1123,7 +1173,7 @@ novirtual class CallGenBase:
|
||||
if owner_full_name[osuf - 1] != '.': return 0
|
||||
if string.strcmp(owner_full_name + osuf, func_name) != 0: return 0
|
||||
rr_src: char* = self.Gen.ImportsGen.lookup_reexport_call(mod_sha1, func_name)
|
||||
if rr_src is None: rr_src = self._lookup_reexport_from_vpi(pool, mod_sha1, func_name)
|
||||
if rr_src is None: rr_src = self._lookup_reexport_from_vpi(pool, mod_sha1, func_name, 1)
|
||||
if rr_src is None: return 0
|
||||
total: sizet = string.strlen(rr_src)
|
||||
ri: sizet = 0
|
||||
@@ -1155,7 +1205,7 @@ novirtual class CallGenBase:
|
||||
if depth >= 4 or pool is None or cls_sha1 is None or method_name is None: return None
|
||||
if cls_name is not None: return None
|
||||
rr_src: char* = self.Gen.ImportsGen.lookup_reexport_call(cls_sha1, method_name)
|
||||
if rr_src is None: rr_src = self._lookup_reexport_from_vpi(pool, cls_sha1, method_name)
|
||||
if rr_src is None: rr_src = self._lookup_reexport_from_vpi(pool, cls_sha1, method_name, 1)
|
||||
if rr_src is None: return None
|
||||
total: sizet = string.strlen(rr_src)
|
||||
ri: sizet = 0
|
||||
|
||||
@@ -182,17 +182,23 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
def _fill_defaults_late(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, builder: mlirlite.IRBuilder*, trans: HT.Translator*, cl: ast.Call*, call_name: char*, head: mlirlite.Value*, can: sizet) -> sizet:
|
||||
"""按 call_name 里的定义模块 SHA1 前缀补缺省参数;返回新的实参个数"""
|
||||
if head is None or can == 0 or call_name is None: return can
|
||||
if self.Gen.CallGenLib._is_sha1_mangled_call(call_name) == 0: return can
|
||||
sh_buf: char* = pool.alloc(17)
|
||||
if sh_buf is None: return can
|
||||
string.strncpy(sh_buf, call_name, 16)
|
||||
sh_buf[16] = '\0'
|
||||
cname: char* = call_name + 17
|
||||
# {sha1}.func / {sha1}.Class.method carry the defining module; a bare or Class.method
|
||||
# name is resolved against the current translator (same-module calls).
|
||||
sh_buf: char* = None
|
||||
cname: char* = call_name
|
||||
if self.Gen.CallGenLib._is_sha1_mangled_call(call_name) != 0:
|
||||
sh_buf = pool.alloc(17)
|
||||
if sh_buf is None: return can
|
||||
string.strncpy(sh_buf, call_name, 16)
|
||||
sh_buf[16] = '\0'
|
||||
cname = call_name + 17
|
||||
elif trans is not None and trans.ModuleSha1 is not None:
|
||||
sh_buf = trans.ModuleSha1
|
||||
if cname[0] == '\0': return can
|
||||
# 只做 SHA1 隔离的查找:表按 (名字, 定义模块 SHA1) 定位;表里没有就直接读该模块的 .vpi。
|
||||
# 绝不按裸名跨模块搜(`run` 这类名字在多个模块里都有,会拿错签名去补参/校验)。
|
||||
de: GenTypes.FuncDefaultsEntry* = self.Gen.CallGenLeafKit.find_func_defaults(cname, sh_buf)
|
||||
if de is None: de = self._defaults_entry_from_vpi(pool, sh_buf, cname)
|
||||
if de is None and sh_buf is not None: de = self._defaults_entry_from_vpi(pool, sh_buf, cname)
|
||||
if de is None: return can
|
||||
n: int = int(can)
|
||||
if de.DefaultCount <= 0 or n >= de.ParamCount: return can
|
||||
@@ -253,6 +259,26 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
self.Gen.TypeGenErr.fatal_error(at, f"call '{nm.id}.{at.attr}(...)' targets module '{nm.id}', but this file does not import it; add `import {nm.id}`. Without the import the call degrades into an external C-function call and its return type cannot be resolved (pointer returns would be truncated to i32)", "NameError")
|
||||
return 0
|
||||
|
||||
def _is_bare_module_symbol(self, pool: memhub.MemBuddy*, mod_sha1: char*, func_name: char*) -> int:
|
||||
"""1 when the module declares func_name as extern/export/state (no SHA1 on the symbol)."""
|
||||
if mod_sha1 is None or func_name is None: return 0
|
||||
if self.Gen.CallGenBase.is_cexport_func(mod_sha1, func_name) != 0: return 1
|
||||
return self._is_state_func_from_vpi(pool, mod_sha1, func_name)
|
||||
|
||||
def _is_bare_module_symbol_deep(self, pool: memhub.MemBuddy*, mod_sha1: char*, func_name: char*) -> int:
|
||||
"""Bare check that also scans the .vpi text.
|
||||
|
||||
Used for star-import candidates only: their names are resolved through a package, so the
|
||||
return-annotation cache may hold no entry for them while the file on disk does declare
|
||||
`extern def` (the pre-fix symptom: `89c1....vqt6_qwidget_...`). One read per candidate.
|
||||
"""
|
||||
if self._is_bare_module_symbol(pool, mod_sha1, func_name) != 0: return 1
|
||||
if mod_sha1 is None or func_name is None: return 0
|
||||
td: char* = self.Gen.CallGenBase.TempDir
|
||||
if td is None or self.Gen.CallGenBase.TempDirLen <= 0: td = self.Gen.TypeGenBase.get_temp_dir()
|
||||
if td is None: return 0
|
||||
return VpiReader._vpi_declares_bare(mod_sha1, td, func_name)
|
||||
|
||||
def translate_call(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, node: ast.AST*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||||
"""Translate a Call node and return the result Value."""
|
||||
cl: ast.Call* = ast.Call*(node)
|
||||
@@ -975,7 +1001,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
# Package vpi miss (the function is defined in a submodule, e.g. fltk.Fl_Input_value in
|
||||
# fltk._cfl_input): use re-export to locate the defining module, then query its vpi.
|
||||
rr_src: char* = self.Gen.ImportsGen.lookup_reexport_call(mod_ret_sha1, func_name)
|
||||
if rr_src is None: rr_src = self._lookup_reexport_from_vpi(pool, mod_ret_sha1, func_name)
|
||||
if rr_src is None: rr_src = self._lookup_reexport_from_vpi(pool, mod_ret_sha1, func_name, 1)
|
||||
if rr_src is not None:
|
||||
rr_total: sizet = string.strlen(rr_src)
|
||||
rr_i: sizet = 0
|
||||
@@ -1300,7 +1326,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
# table is keyed by the defining module SHA1, so re-resolve the re-export and query again (else defaults are skipped).
|
||||
if gd_entry is None and dfx_callee_sha is not None:
|
||||
rr_src_dx: char* = self.Gen.ImportsGen.lookup_reexport_call(dfx_callee_sha, func_name)
|
||||
if rr_src_dx is None: rr_src_dx = self._lookup_reexport_from_vpi(pool, dfx_callee_sha, func_name)
|
||||
if rr_src_dx is None: rr_src_dx = self._lookup_reexport_from_vpi(pool, dfx_callee_sha, func_name, 1)
|
||||
if rr_src_dx is not None:
|
||||
rr_sha_dx: char* = self._lookup_module_sha1(rr_src_dx)
|
||||
if rr_sha_dx is not None: gd_entry = self.Gen.CallGenLeafKit.find_func_defaults(func_name, rr_sha_dx)
|
||||
@@ -1355,7 +1381,10 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
if can < df_param_count:
|
||||
self.Gen.TypeGenErr.fatal_error(cl, f"Cross-module default argument cannot be rebuilt into a value (snapshot missing, or the default is not a simple constant): {func_name}; parameter {can + 1} would be missing, which emits an llvm.call with fewer operands than the callee declares", "TypeError")
|
||||
elif found_func is not None:
|
||||
# Zero-value fallback when defaults are missing: use found_func's real param count (excluding EH out-params %__eh_msg_out__/%__eh_code_out__) and fill missing args with zeros.
|
||||
# Defaults first: the callee's own name carries the defining module
|
||||
# ({sha1}.Class.method), so method defaults resolve through its vpi even when the
|
||||
# call site lives in another class/module. Zero-filling without a recorded default
|
||||
# silently changes program behaviour, so a short call without defaults is an error.
|
||||
fc_cnt: int = 0
|
||||
fc_p: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
|
||||
while fc_p is not None:
|
||||
@@ -1365,22 +1394,14 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
fc_p = mlirlite.param_get_next(fc_p)
|
||||
if fc_cnt == 0: fc_cnt = df_param_count if df_param_count > 0 else 0
|
||||
if can < fc_cnt:
|
||||
fp_z: mlirlite.Param* = mlirlite.function_get_param_head(found_func)
|
||||
fpi_z: int = 0
|
||||
while fp_z is not None and fpi_z < can:
|
||||
fp_z = mlirlite.param_get_next(fp_z)
|
||||
fpi_z += 1
|
||||
while fp_z is not None and fpi_z < fc_cnt:
|
||||
zero_ty_z: mlirlite.LLVMType* = mlirlite.param_get_ty(fp_z)
|
||||
zero_val_z: mlirlite.Value* = mlirlite.ConstZero(pool, zero_ty_z)
|
||||
if zero_val_z is not None:
|
||||
zero_val_z.Next = None
|
||||
if head is None: head = zero_val_z
|
||||
else: tail.Next = zero_val_z
|
||||
tail = zero_val_z
|
||||
can += 1 # Zero-value fallback args also count toward the arg count (like defaults).
|
||||
fp_z = mlirlite.param_get_next(fp_z)
|
||||
fpi_z += 1
|
||||
fc_late: char* = mlirlite.function_get_name(found_func)
|
||||
if fc_late is not None:
|
||||
can = self._fill_defaults_late(pool, mod, builder, trans, cl, fc_late, head, can)
|
||||
if head is not None:
|
||||
tail = head
|
||||
while tail.Next is not None: tail = tail.Next
|
||||
if can < fc_cnt and not (string.strcmp(func_name, "memcpy") == 0 and can >= 3):
|
||||
self.Gen.TypeGenErr.fatal_error(cl, f"call to '{func_name}' passes {can} argument(s) but the callee declares {fc_cnt}; no default value is recorded for the missing parameter(s) (refusing to zero-fill silently)", "TypeError")
|
||||
|
||||
# Use the actual function name from the module (may carry a SHA1 prefix); ExternT/State/ExportT
|
||||
# functions use the bare name. Both declare and define must use found_func.Name (mismatched names cause undefined references).
|
||||
@@ -1401,7 +1422,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
if cx_sha1_buf is not None:
|
||||
string.memcpy(cx_sha1_buf, found_name, 16)
|
||||
cx_sha1_buf[16] = '\0'
|
||||
if self.Gen.CallGenBase.is_cexport_func(cx_sha1_buf, func_name) != 0: call_name = func_name
|
||||
if self._is_bare_module_symbol(pool, cx_sha1_buf, func_name) != 0: call_name = func_name
|
||||
else:
|
||||
# Cross-module call: when found_func is None, check for a module-qualified call (string.strchr -> @{sha1_of_string}.strchr).
|
||||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||||
@@ -1433,7 +1454,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
if reexport_src is None:
|
||||
# Re-export table miss (includes modules with existing stub/text are not fully translated,
|
||||
# so the table may be empty): resolve the module's .vpi from-import to locate the defining module.
|
||||
reexport_src = self._lookup_reexport_from_vpi(pool, mod_sha1, func_name)
|
||||
reexport_src = self._lookup_reexport_from_vpi(pool, mod_sha1, func_name, 1)
|
||||
rs_found: int = 0
|
||||
if reexport_src is not None:
|
||||
# Star-import multiple candidates (space-separated, e.g. fltk/__init__ from _types/_enums/_cfl
|
||||
@@ -1452,8 +1473,9 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
cs_buf[cs_seg] = '\0'
|
||||
cand_sha1: char* = self._lookup_module_sha1(cs_buf)
|
||||
if cand_sha1 is None: cand_sha1 = self._lookup_module_sha1_suffix(cs_buf)
|
||||
if cand_sha1 is None: cand_sha1 = self.Gen.CallGenBase._lookup_module_sha1_by_rel(pool, cs_buf)
|
||||
if cand_sha1 is not None:
|
||||
if self.Gen.CallGenBase.is_cexport_func(cand_sha1, func_name) != 0 or self._is_state_func_from_vpi(pool, cand_sha1, func_name) != 0:
|
||||
if self._is_bare_module_symbol_deep(pool, cand_sha1, func_name) != 0:
|
||||
mod_sha1 = cand_sha1
|
||||
call_name = func_name
|
||||
rs_found = 1
|
||||
@@ -1461,7 +1483,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
if rs_found == 0:
|
||||
# All star-import candidates failed: func_name may be defined in the original module
|
||||
# itself (e.g. stdlib.malloc, while stdlib's `from stdint import *` adds a false candidate).
|
||||
if self.Gen.CallGenBase.is_cexport_func(mod_sha1, func_name) != 0 or self._is_state_func_from_vpi(pool, mod_sha1, func_name) != 0:
|
||||
if self._is_bare_module_symbol_deep(pool, mod_sha1, func_name) != 0:
|
||||
rs_found = 1
|
||||
call_name = func_name
|
||||
else:
|
||||
@@ -1479,10 +1501,9 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
# ExportT functions define with the bare name (@strlen), so the cross-module call must use the bare name too.
|
||||
if rs_found != 0:
|
||||
pass # Re-export defining module already verified and bare name set.
|
||||
elif self.Gen.CallGenBase.is_cexport_func(mod_sha1, func_name) != 0: call_name = func_name
|
||||
elif self._is_state_func_from_vpi(pool, mod_sha1, func_name) != 0:
|
||||
# t.State external C function (e.g. CPython PyList_Size): the definition uses the bare
|
||||
# name @PyList_Size, so the cross-module call must too (a sha1 prefix causes an undefined symbol).
|
||||
elif self._is_bare_module_symbol(pool, mod_sha1, func_name) != 0:
|
||||
# extern/export/state: the definition uses the bare name, so the
|
||||
# cross-module call must too (a SHA1 prefix is an undefined symbol).
|
||||
call_name = func_name
|
||||
else:
|
||||
mangled_len: sizet = string.strlen(mod_sha1) + string.strlen(func_name) + 2
|
||||
@@ -1504,10 +1525,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
alias_sha1: char* = None
|
||||
if trans is not None and trans._from_imports is not None: alias_sha1 = self._lookup_mod_sha1(pool, trans._from_imports, xmod_path, 0)
|
||||
if alias_sha1 is not None:
|
||||
if self.Gen.CallGenBase.is_cexport_func(alias_sha1, func_name) != 0: call_name = func_name
|
||||
elif self._is_state_func_from_vpi(pool, alias_sha1, func_name) != 0:
|
||||
# t.State external C function: use the bare name (same as the main path).
|
||||
call_name = func_name
|
||||
if self._is_bare_module_symbol(pool, alias_sha1, func_name) != 0: call_name = func_name
|
||||
else:
|
||||
AliasMangledLen: sizet = string.strlen(alias_sha1) + string.strlen(func_name) + 2
|
||||
alias_mangled: char* = pool.alloc(AliasMangledLen)
|
||||
@@ -1528,7 +1546,7 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
if trans is not None and trans._from_imports is not None: from_sha1 = self._lookup_mod_sha1(pool, trans._from_imports, xmod_path, 0)
|
||||
if from_sha1 is not None:
|
||||
# ExportT functions define with the bare name, so the cross-module call must use the bare name.
|
||||
if self.Gen.CallGenBase.is_cexport_func(from_sha1, func_name) != 0: call_name = func_name
|
||||
if self._is_bare_module_symbol(pool, from_sha1, func_name) != 0: call_name = func_name
|
||||
else:
|
||||
fmangled_len: sizet = string.strlen(from_sha1) + string.strlen(func_name) + 2
|
||||
fmangled: char* = pool.alloc(fmangled_len)
|
||||
@@ -1585,11 +1603,11 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
|
||||
reexport_src2: char* = self.Gen.ImportsGen.lookup_reexport_call(bare_sha1, bare_func_name)
|
||||
if reexport_src2 is None:
|
||||
# Re-export table miss: resolve the module's .vpi from-import on demand.
|
||||
reexport_src2 = self._lookup_reexport_from_vpi(pool, bare_sha1, bare_func_name)
|
||||
reexport_src2 = self._lookup_reexport_from_vpi(pool, bare_sha1, bare_func_name, 1)
|
||||
if reexport_src2 is not None:
|
||||
reexport_sha1_2: char* = self._lookup_module_sha1(reexport_src2)
|
||||
if reexport_sha1_2 is not None: bare_sha1 = reexport_sha1_2
|
||||
if self.Gen.CallGenBase.is_cexport_func(bare_sha1, bare_func_name) != 0: call_name = bare_func_name
|
||||
if self._is_bare_module_symbol(pool, bare_sha1, bare_func_name) != 0: call_name = bare_func_name
|
||||
else:
|
||||
bare_mangled_len: sizet = string.strlen(bare_sha1) + string.strlen(bare_func_name) + 2
|
||||
bare_mangled: char* = pool.alloc(bare_mangled_len)
|
||||
|
||||
@@ -485,6 +485,67 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
|
||||
if annot is not None:
|
||||
if self._spec_sign_from_annot(annot) == TSpec.TS_SIGN_UNSIGNED: return 1
|
||||
return 0
|
||||
# _translate_in_literal_container - `x in (a, b, c)` / `[a, b]` / `{a, b}`.
|
||||
# A literal container cannot own __contains__, so the membership test is the
|
||||
# short-circuit-free OR of `x == ai`. Returns None when rhs is not a literal
|
||||
# container (the caller falls back to __contains__ / i1 0).
|
||||
def _translate_in_literal_container(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, lhs_node: ast.AST*, rhs_node: ast.AST*, trans: HT.Translator*, invert: int) -> mlirlite.Value*:
|
||||
if lhs_node is None or rhs_node is None: return None
|
||||
k: int = rhs_node.kind()
|
||||
elts: list[ast.AST*]* = None
|
||||
if k == ast.ASTKind.Tuple: elts = (ast.Tuple*(rhs_node)).elts
|
||||
elif k == ast.ASTKind.List: elts = (ast.List*(rhs_node)).elts
|
||||
elif k == ast.ASTKind.Set: elts = (ast.Set*(rhs_node)).elts
|
||||
else: return None
|
||||
lhs: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, lhs_node, None, 0, trans)
|
||||
if lhs is None: return None
|
||||
acc: mlirlite.Value* = None
|
||||
if elts is not None:
|
||||
for ei in range(int(len(elts))):
|
||||
el: ast.AST* = elts.get(ei)
|
||||
ev: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, el, None, 0, trans)
|
||||
if ev is None: return None
|
||||
eq: mlirlite.Value* = self._eq_scalar_value(builder, pool, mod, lhs, ev, el, trans)
|
||||
if eq is None: return None
|
||||
if acc is None: acc = eq
|
||||
else: acc = mlirlite.build_or(builder, acc, eq)
|
||||
if acc is None:
|
||||
i1_empty: mlirlite.LLVMType* = mlirlite.Int1(pool)
|
||||
if i1_empty is None: return None
|
||||
acc = mlirlite.ConstInt(pool, i1_empty, 0, "0")
|
||||
if invert != 0:
|
||||
one: mlirlite.Value* = mlirlite.ConstInt(pool, mlirlite.Int1(pool), 1, "1")
|
||||
if one is not None: acc = mlirlite.build_xor(builder, acc, one)
|
||||
return acc
|
||||
|
||||
# _eq_scalar_value - `lhs == rhs` for a literal container element.
|
||||
# String literals compare by content (strcmp); everything else is coerced to the
|
||||
# lhs type and compared with icmp eq.
|
||||
def _eq_scalar_value(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, lhs: mlirlite.Value*, rhs: mlirlite.Value*, rhs_node: ast.AST*, trans: HT.Translator*) -> mlirlite.Value*:
|
||||
if lhs is None or rhs is None: return None
|
||||
is_str_lit: int = 0
|
||||
if rhs_node is not None and rhs_node.kind() == ast.ASTKind.Constant:
|
||||
rc: ast.Constant* = ast.Constant*(rhs_node)
|
||||
if rc is not None and rc.const_kind == ast.CONST_STR: is_str_lit = 1
|
||||
if is_str_lit != 0 and self.get_ptr_level(lhs.Ty) == 1:
|
||||
fn: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, "strcmp")
|
||||
if fn is None: fn = self.Gen.CallGenLib._ensure_c_lib_declare(pool, mod, "strcmp")
|
||||
if fn is not None:
|
||||
fn_name: str = mlirlite.function_get_name(fn)
|
||||
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||||
if fn_name is not None and i32_ty is not None:
|
||||
lhs.Next = rhs
|
||||
rhs.Next = None
|
||||
r: mlirlite.Value* = mlirlite.build_call(builder, fn_name, lhs, 2, i32_ty, 0)
|
||||
if r is not None:
|
||||
zero: mlirlite.Value* = mlirlite.ConstInt(pool, i32_ty, 0, "0")
|
||||
if zero is not None: return mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, r, zero)
|
||||
return None
|
||||
tgt_ty: mlirlite.LLVMType* = lhs.Ty
|
||||
tv: mlirlite.Value* = self.Gen.ExprGenBase.coerce_to_type(builder, rhs, tgt_ty, rhs_node, trans)
|
||||
if tv is None: return None
|
||||
return mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, lhs, tv)
|
||||
|
||||
def translate_compare(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||||
"""Translate a comparison expression and return an i1 result."""
|
||||
cmp: ast.Compare* = ast.Compare*(node)
|
||||
@@ -497,12 +558,16 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
|
||||
comparators: list[ast.AST*]* = cmp.comparators
|
||||
if comparators is None or len(comparators) == 0: return None
|
||||
rhs_node: ast.AST* = comparators.get(0)
|
||||
lhs_node: ast.AST* = cmp.left
|
||||
# Literal containers have no __contains__: lower `in` to an OR of equality tests.
|
||||
if (op == ast.OpKind.In or op == ast.OpKind.NotIn) and lhs_node is not None and rhs_node is not None:
|
||||
lit_in: mlirlite.Value* = self._translate_in_literal_container(builder, pool, mod, lhs_node, rhs_node, trans, 1 if op == ast.OpKind.NotIn else 0)
|
||||
if lit_in is not None: return lit_in
|
||||
rhs: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, rhs_node, None, 0, trans)
|
||||
if rhs is None: return None
|
||||
|
||||
# === None comparison optimization: x == None / x != None use identity comparison (icmp eq/ne null) ===
|
||||
# Avoids emitting invalid __eq__/__ne__ calls for struct pointers; Python treats == None as identity comparison.
|
||||
lhs_node: ast.AST* = cmp.left
|
||||
cmp_with_none: int = 0
|
||||
if op == ast.OpKind.Eq or op == ast.OpKind.Ne or op == ast.OpKind.Is or op == ast.OpKind.IsNot:
|
||||
if rhs_node is not None and rhs_node.kind() == ast.ASTKind.Constant:
|
||||
|
||||
Reference in new issue
Block a user