现象:任何工程编译都在函数形参注册刚结束处 access-violation(pc=0,逐字节可复现)。定位:逐层探针把崩点压到 FuncGenDef 的 pre_scan 段,而该段对 Check(body 仅 if+return)应为空操作,唯一变量是 VarEntry 这个热结构被加了 Freed 字段。修法:撤销 VarEntry.Freed,改用 VarGenMixin 的独立失效槽表 _g_freed_slots(键 = 变量槽 alloca,整次翻译内唯一,故全局累积、无需按函数 reset/restore);_FreedMark/_FreedCheck/_FreedClearSlot 三个 API 替换字段读写,P2 语义不变。同时清除全部 TEMP 探针。
973 lines
54 KiB
Plaintext
973 lines
54 KiB
Plaintext
import lib.core.Gen.GenApi as GenApi
|
|
import lib.core.VLogger as VLogger
|
|
import t, c
|
|
from stdint import *
|
|
import ast
|
|
import string
|
|
import memhub
|
|
import viperlib
|
|
import stdlib
|
|
import sys
|
|
|
|
# ============================================================
|
|
# StubGen.Converter - .vpi stub file generator
|
|
|
|
|
|
# _eval_binop_int / _eval_binop_valid - recursively evaluate BinOp constant values.
|
|
# Handles BitOr/BitAnd/Add/Sub and nested Name refs to registered CDefine constants.
|
|
def _eval_binop_valid(node: ast.AST*) -> int:
|
|
"""Return 1 if a BinOp node is fully computable from constants or registered CDefines"""
|
|
if node is None: return 0
|
|
k: int = node.kind()
|
|
if k == ast.ASTKind.Constant:
|
|
cn: ast.Constant* = ast.Constant*(node)
|
|
if cn.const_kind == ast.CONST_INT: return 1
|
|
return 0
|
|
if k == ast.ASTKind.UnaryOp:
|
|
uop: ast.UnaryOp* = ast.UnaryOp*(node)
|
|
if uop.operand is not None: return _eval_binop_valid(uop.operand)
|
|
return 0
|
|
if k == ast.ASTKind.BinOp:
|
|
bop: ast.BinOp* = ast.BinOp*(node)
|
|
if bop.left is not None and bop.right is not None:
|
|
if _eval_binop_valid(bop.left) != 0:
|
|
if _eval_binop_valid(bop.right) != 0: return 1
|
|
return 0
|
|
if k == ast.ASTKind.Name:
|
|
# Name reference: check whether it is a registered CDefine constant
|
|
nm: ast.Name* = ast.Name*(node)
|
|
if nm.id is not None:
|
|
# Look up in the local CDefine table
|
|
val: int = _lookup_cdefine_for_pyi(nm.id)
|
|
if _pyi_cdefine_found() != 0: return 1
|
|
return 0
|
|
return 0
|
|
|
|
|
|
def _eval_binop_int(node: ast.AST*) -> int64:
|
|
"""Recursively evaluate a BinOp node as a 64-bit integer value"""
|
|
if node is None: return 0
|
|
k: int = node.kind()
|
|
if k == ast.ASTKind.Constant:
|
|
cn: ast.Constant* = ast.Constant*(node)
|
|
if cn.const_kind == ast.CONST_INT: return cn.int_val
|
|
return 0
|
|
if k == ast.ASTKind.UnaryOp:
|
|
uop: ast.UnaryOp* = ast.UnaryOp*(node)
|
|
if uop.op == ast.OpKind.USub and uop.operand is not None: return -_eval_binop_int(uop.operand)
|
|
if uop.op == ast.OpKind.UAdd and uop.operand is not None: return _eval_binop_int(uop.operand)
|
|
if uop.op == ast.OpKind.Invert and uop.operand is not None: return ~_eval_binop_int(uop.operand)
|
|
return 0
|
|
if k == ast.ASTKind.BinOp:
|
|
bop: ast.BinOp* = ast.BinOp*(node)
|
|
if bop.left is None or bop.right is None: return 0
|
|
lv: int64 = _eval_binop_int(bop.left)
|
|
rv: int64 = _eval_binop_int(bop.right)
|
|
if bop.op == ast.OpKind.BitOr: return lv | rv
|
|
if bop.op == ast.OpKind.BitAnd: return lv & rv
|
|
if bop.op == ast.OpKind.BitXor: return lv ^ rv
|
|
if bop.op == ast.OpKind.Add: return lv + rv
|
|
if bop.op == ast.OpKind.Sub: return lv - rv
|
|
if bop.op == ast.OpKind.Mult: return lv * rv
|
|
if bop.op == ast.OpKind.Div: return lv // rv
|
|
if bop.op == ast.OpKind.FloorDiv: return lv // rv
|
|
if bop.op == ast.OpKind.Mod: return lv % rv
|
|
if bop.op == ast.OpKind.LShift: return lv << rv
|
|
if bop.op == ast.OpKind.RShift: return lv >> rv
|
|
return 0
|
|
if k == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(node)
|
|
if nm.id is not None: return _lookup_cdefine_for_pyi(nm.id)
|
|
return 0
|
|
return 0
|
|
|
|
|
|
# Forward declaration for CDefine lookup (real impl in HandlesType);
|
|
# weak refs avoid circular dependencies.
|
|
_g_pyi_cdefine_found: int = 0
|
|
|
|
# Per-file CDefine value cache (vpi only): vpi runs before the translate phase, so
|
|
# _GeneratePyiFromAst registers in order; C arrays dodge list.get misdispatch.
|
|
_g_pyi_cdef_names: str* = None
|
|
_g_pyi_cdef_vals: int64* = None
|
|
_g_pyi_cdef_count: int = 0
|
|
|
|
|
|
def _lookup_cdefine_for_pyi(name: str) -> int64:
|
|
"""Look up a constant value from this file's CDefine cache or the local CDefine table"""
|
|
global _g_pyi_cdefine_found
|
|
# Look up this file's CDefine cache first (globals are empty during vpi gen)
|
|
if _g_pyi_cdef_names is not None and _g_pyi_cdef_vals is not None:
|
|
# Bound loop by _g_pyi_cdef_count; never len()/.get on i8** (len falls back
|
|
# to strlen, .get misdispatches to HashTable.get) -- index via t.CPtr(base + i*8).
|
|
pn: sizet = sizet(_g_pyi_cdef_count)
|
|
for pi in range(pn):
|
|
entry_pc: str* = (str*)(uint64(_g_pyi_cdef_names) + pi * 8)
|
|
pnm: str = entry_pc[0]
|
|
if pnm is not None and string.strcmp(pnm, name) == 0:
|
|
_g_pyi_cdefine_found = 1
|
|
return _g_pyi_cdef_vals[pi]
|
|
val: int64 = GenApi.lookup_cdefine_constant(name)
|
|
if GenApi.is_cdefine_found() != 0:
|
|
_g_pyi_cdefine_found = 1
|
|
return val
|
|
_g_pyi_cdefine_found = 0
|
|
return 0
|
|
|
|
|
|
def _eval_cdefine_value_cache(node: ast.AST*) -> int64:
|
|
"""Evaluate a CDefine value for preprocessing registration (Constant/UnaryOp/BinOp/Call/Name)"""
|
|
if node is None: return 0
|
|
k: int = node.kind()
|
|
if k == ast.ASTKind.Constant:
|
|
cn_c: ast.Constant* = ast.Constant*(node)
|
|
if cn_c.const_kind == ast.CONST_INT: return cn_c.int_val
|
|
if cn_c.const_kind == ast.CONST_BOOL: return cn_c.int_val
|
|
# STR/FLOAT/NONE do not participate in integer BinOp refs (registering 0 is harmless)
|
|
return 0
|
|
if k == ast.ASTKind.UnaryOp: return _eval_binop_int(node)
|
|
if k == ast.ASTKind.BinOp: return _eval_binop_int(node)
|
|
if k == ast.ASTKind.Call:
|
|
cl_c: ast.Call* = ast.Call*(node)
|
|
if cl_c.args is not None and len(cl_c.args) > 0: return _eval_cdefine_value_cache(cl_c.args.get(0))
|
|
return 0
|
|
if k == ast.ASTKind.Name:
|
|
nm_c: ast.Name* = ast.Name*(node)
|
|
if nm_c.id is not None: return _lookup_cdefine_for_pyi(nm_c.id)
|
|
return 0
|
|
return 0
|
|
|
|
|
|
def _pyi_cdefine_found() -> int:
|
|
"""Return the result status of the last _lookup_cdefine_for_pyi call"""
|
|
return _g_pyi_cdefine_found
|
|
|
|
|
|
# ============================================================
|
|
# _PyiEmit - append text to buf at pos, returning the new pos (truncates on overflow)
|
|
def _PyiEmit(buf: bytes, buf_size: sizet, pos: sizet, text: str) -> sizet:
|
|
"""Append text to buf at pos and return the new pos"""
|
|
if buf is None or text is None or pos >= buf_size: return pos
|
|
tlen: sizet = string.strlen(text)
|
|
if tlen == 0: return pos
|
|
remain: sizet = buf_size - pos
|
|
if tlen >= remain: tlen = remain - 1
|
|
if tlen > 0: string.memcpy(buf + pos, text, tlen)
|
|
return pos + tlen
|
|
|
|
|
|
# ============================================================
|
|
# _PyiIndent - append level*4 spaces to buf at pos
|
|
def _PyiIndent(buf: bytes, buf_size: sizet, pos: sizet, level: int) -> sizet:
|
|
"""Append level*4 spaces to buf at pos and return the new pos"""
|
|
for i in range(level):
|
|
pos = _PyiEmit(buf, buf_size, pos, " ")
|
|
return pos
|
|
|
|
|
|
# _PyiBinOpOpText - emit the " OP " text for a binary operator.
|
|
# Kept byte-identical to TPC's BinOp operator table; unknown ops fail fast.
|
|
def _PyiBinOpOpText(buf: bytes, buf_size: sizet, pos: sizet, op: int, node: ast.AST*) -> sizet:
|
|
"""Emit the " OP " text for a binary operator; unknown operators fail fast"""
|
|
if op == ast.OpKind.BitOr: return _PyiEmit(buf, buf_size, pos, " | ")
|
|
if op == ast.OpKind.Add: return _PyiEmit(buf, buf_size, pos, " + ")
|
|
if op == ast.OpKind.Sub: return _PyiEmit(buf, buf_size, pos, " - ")
|
|
if op == ast.OpKind.Mult: return _PyiEmit(buf, buf_size, pos, " * ")
|
|
if op == ast.OpKind.Div: return _PyiEmit(buf, buf_size, pos, " / ")
|
|
if op == ast.OpKind.Mod: return _PyiEmit(buf, buf_size, pos, " % ")
|
|
if op == ast.OpKind.BitAnd: return _PyiEmit(buf, buf_size, pos, " & ")
|
|
if op == ast.OpKind.RShift: return _PyiEmit(buf, buf_size, pos, " >> ")
|
|
GenApi.fatal_error(node, "unknown vpi type-annotation node type", "TypeError")
|
|
return pos # unreachable (fatal_error already exited)
|
|
|
|
|
|
# _PyiTypeStr - recursively render an AST annotation node into buf at pos.
|
|
# Support set matches TPC; TPV additionally accepts a bare Tuple.
|
|
def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, annot: ast.AST*, depth: int = 0) -> sizet:
|
|
"""Recursively render a type annotation node into buf and return the new pos"""
|
|
# Recursion guard: a self-referential AST would recurse until stack exhaustion.
|
|
# Fail fast instead of silently falling back.
|
|
if depth > 64:
|
|
VLogger.error("vpi type annotation recursion too deep (AST cycle or nesting over 64 levels)", "StubGen")
|
|
sys.exit(1)
|
|
if annot is None: return _PyiEmit(buf, buf_size, pos, "int")
|
|
|
|
k: int = annot.kind()
|
|
|
|
# Name: simple type name (e.g. int, str, MyClass)
|
|
if k == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(annot)
|
|
if nm.id is not None: return _PyiEmit(buf, buf_size, pos, nm.id)
|
|
return _PyiEmit(buf, buf_size, pos, "int")
|
|
|
|
# Attribute: attribute reference (e.g. int, memhub.MemBuddy)
|
|
if k == ast.ASTKind.Attribute:
|
|
at: ast.Attribute* = ast.Attribute*(annot)
|
|
pos = _PyiTypeStr(buf, buf_size, pos, at.value, depth + 1)
|
|
pos = _PyiEmit(buf, buf_size, pos, ".")
|
|
if at.attr is not None: pos = _PyiEmit(buf, buf_size, pos, at.attr)
|
|
return pos
|
|
|
|
# Subscript: subscripted type (e.g. list[int], t.CArray[elem, count])
|
|
if k == ast.ASTKind.Subscript:
|
|
sub: ast.Subscript* = ast.Subscript*(annot)
|
|
pos = _PyiTypeStr(buf, buf_size, pos, sub.value, depth + 1)
|
|
pos = _PyiEmit(buf, buf_size, pos, "[")
|
|
pos = _PyiTypeStr(buf, buf_size, pos, sub.slice, depth + 1)
|
|
pos = _PyiEmit(buf, buf_size, pos, "]")
|
|
return pos
|
|
|
|
# BinOp: union types and CArray count expressions (MAX_ORDER + 1,
|
|
# 2 * ZHUFF_MAX_CODES); operator text via _PyiBinOpOpText, byte-identical to TPC.
|
|
if k == ast.ASTKind.BinOp:
|
|
bop: ast.BinOp* = ast.BinOp*(annot)
|
|
pos = _PyiTypeStr(buf, buf_size, pos, bop.left, depth + 1)
|
|
pos = _PyiBinOpOpText(buf, buf_size, pos, bop.op, annot)
|
|
pos = _PyiTypeStr(buf, buf_size, pos, bop.right, depth + 1)
|
|
return pos
|
|
|
|
# UnaryOp: count expressions (-1 / ~0) and macro args (not ct.isptr(x)).
|
|
# Not/Invert/USub match TPC; other unary ops fail fast.
|
|
if k == ast.ASTKind.UnaryOp:
|
|
uop_t: ast.UnaryOp* = ast.UnaryOp*(annot)
|
|
if uop_t.op == ast.OpKind.Not: pos = _PyiEmit(buf, buf_size, pos, "not ")
|
|
elif uop_t.op == ast.OpKind.Invert: pos = _PyiEmit(buf, buf_size, pos, "~")
|
|
elif uop_t.op == ast.OpKind.USub: pos = _PyiEmit(buf, buf_size, pos, "-")
|
|
else:
|
|
GenApi.fatal_error(annot, "unknown vpi type-annotation node type", "TypeError")
|
|
return pos # unreachable (fatal_error already exited)
|
|
return _PyiTypeStr(buf, buf_size, pos, uop_t.operand, depth + 1)
|
|
|
|
# Compare: comparison expressions (MAX_SIZE > 512 / val == 1).
|
|
# == != < <= > >= match TPC; unknown ops emit " ? ".
|
|
if k == ast.ASTKind.Compare:
|
|
cmp_t: ast.Compare* = ast.Compare*(annot)
|
|
pos = _PyiTypeStr(buf, buf_size, pos, cmp_t.left, depth + 1)
|
|
ops_ct: list[int]* = (list[int]*)(cmp_t.ops)
|
|
cmps_ct: list[ast.AST*]* = cmp_t.comparators
|
|
if ops_ct is not None:
|
|
on_ct: sizet = len(ops_ct)
|
|
for oi_ct in range(on_ct):
|
|
oid_ct: int = ops_ct.get(oi_ct)
|
|
if oid_ct == ast.OpKind.Eq: pos = _PyiEmit(buf, buf_size, pos, " == ")
|
|
elif oid_ct == ast.OpKind.Ne: pos = _PyiEmit(buf, buf_size, pos, " != ")
|
|
elif oid_ct == ast.OpKind.Lt: pos = _PyiEmit(buf, buf_size, pos, " < ")
|
|
elif oid_ct == ast.OpKind.Le: pos = _PyiEmit(buf, buf_size, pos, " <= ")
|
|
elif oid_ct == ast.OpKind.Gt: pos = _PyiEmit(buf, buf_size, pos, " > ")
|
|
elif oid_ct == ast.OpKind.Ge: pos = _PyiEmit(buf, buf_size, pos, " >= ")
|
|
else: pos = _PyiEmit(buf, buf_size, pos, " ? ")
|
|
# TPC indexes comparators[i] by ops; guard the length here so a
|
|
# malformed AST (unequal ops/comparators) cannot overrun.
|
|
if cmps_ct is not None and oi_ct < len(cmps_ct): pos = _PyiTypeStr(buf, buf_size, pos, cmps_ct.get(oi_ct), depth + 1)
|
|
return pos
|
|
|
|
# BoolOp: boolean ops (ENABLED and MAX_SIZE > 0 / A or B).
|
|
# Matches TPC: and -> " and ", or -> " or ".
|
|
if k == ast.ASTKind.BoolOp:
|
|
bool_t: ast.BoolOp* = ast.BoolOp*(annot)
|
|
vals_bt: list[ast.AST*]* = bool_t.values
|
|
if vals_bt is not None:
|
|
vn_bt: sizet = len(vals_bt)
|
|
for vi_bt in range(vn_bt):
|
|
if vi_bt > 0:
|
|
if bool_t.op == ast.OpKind.Or: pos = _PyiEmit(buf, buf_size, pos, " or ")
|
|
else: pos = _PyiEmit(buf, buf_size, pos, " and ")
|
|
pos = _PyiTypeStr(buf, buf_size, pos, vals_bt.get(vi_bt), depth + 1)
|
|
return pos
|
|
|
|
# Constant: string forward refs (e.g. 'MyClass'), None or numeric constants
|
|
if k == ast.ASTKind.Constant:
|
|
cn: ast.Constant* = ast.Constant*(annot)
|
|
if cn.const_kind == ast.CONST_STR:
|
|
pos = _PyiEmit(buf, buf_size, pos, "'")
|
|
if cn.str_val is not None: pos = _PyiEmit(buf, buf_size, pos, cn.str_val)
|
|
pos = _PyiEmit(buf, buf_size, pos, "'")
|
|
return pos
|
|
if cn.const_kind == ast.CONST_NONE: return _PyiEmit(buf, buf_size, pos, "None")
|
|
# Numeric constants (e.g. the count in t.CArray[uint32, 64]): emit the value
|
|
# itself; falling back to int makes stub loading raise TypeError.
|
|
if cn.const_kind == ast.CONST_INT or cn.const_kind == ast.CONST_CHAR:
|
|
num_buf_ct: str = stdlib.malloc(24)
|
|
if num_buf_ct is not None:
|
|
viperlib.snprintf(num_buf_ct, 24, "%d", cn.int_val)
|
|
pos = _PyiEmit(buf, buf_size, pos, num_buf_ct)
|
|
stdlib.free(num_buf_ct)
|
|
return pos
|
|
return _PyiEmit(buf, buf_size, pos, "int")
|
|
|
|
# List: function pointer parameter table (e.g. the inner [t.CPtr, ULONG] of
|
|
# t.Callable[[t.CPtr, ULONG], BOOL]); must emit "[a, b]" or it fails fast.
|
|
if k == ast.ASTKind.List:
|
|
lst: ast.List* = ast.List*(annot)
|
|
pos = _PyiEmit(buf, buf_size, pos, "[")
|
|
if lst.elts is not None:
|
|
lst_elts: list[ast.AST*]* = lst.elts
|
|
ln: sizet = len(lst_elts)
|
|
for li in range(ln):
|
|
if li > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
|
le: ast.AST* = lst_elts.get(li)
|
|
pos = _PyiTypeStr(buf, buf_size, pos, le, depth + 1)
|
|
pos = _PyiEmit(buf, buf_size, pos, "]")
|
|
return pos
|
|
|
|
# Tuple: multi-argument subscript (e.g. Dict[str, int])
|
|
if k == ast.ASTKind.Tuple:
|
|
tup: ast.Tuple* = ast.Tuple*(annot)
|
|
if tup.elts is not None:
|
|
tup_elts: list[ast.AST*]* = tup.elts
|
|
tn: sizet = len(tup_elts)
|
|
for ti in range(tn):
|
|
if ti > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
|
el: ast.AST* = tup_elts.get(ti)
|
|
pos = _PyiTypeStr(buf, buf_size, pos, el, depth + 1)
|
|
return pos
|
|
|
|
# Call: call expression (used by decorators such as @t.NoVTable())
|
|
if k == ast.ASTKind.Call:
|
|
cl: ast.Call* = ast.Call*(annot)
|
|
pos = _PyiTypeStr(buf, buf_size, pos, cl.func, depth + 1)
|
|
pos = _PyiEmit(buf, buf_size, pos, "(")
|
|
if cl.args is not None:
|
|
cl_args: list[ast.AST*]* = cl.args
|
|
can: sizet = len(cl_args)
|
|
for cai in range(can):
|
|
if cai > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
|
ca: ast.AST* = cl_args.get(cai)
|
|
pos = _PyiTypeStr(buf, buf_size, pos, ca, depth + 1)
|
|
pos = _PyiEmit(buf, buf_size, pos, ")")
|
|
return pos
|
|
|
|
# Unknown node type: fail fast rather than silently writing "int" and producing a wrong signature.
|
|
GenApi.fatal_error(annot, "unknown vpi type-annotation node type", "TypeError")
|
|
return _PyiEmit(buf, buf_size, pos, "int") # unreachable (fatal_error already exited)
|
|
|
|
|
|
# _PyiDefaultStr - render a default-value expression for '= <default>' in vpi.
|
|
# Stubs must keep defaults so VpiReader can register the default-parameter table.
|
|
def _PyiDefaultStr(buf: bytes, buf_size: sizet, pos: sizet, node: ast.AST*, depth: int = 0) -> sizet:
|
|
"""Render a default-value expression into buf and return the new pos"""
|
|
# Recursion guard: fail fast on a self-referential AST (same as _PyiTypeStr)
|
|
if depth > 64:
|
|
VLogger.error("vpi default-value expression recursion too deep (AST cycle or nesting over 64 levels)", "StubGen")
|
|
sys.exit(1)
|
|
if node is None: return pos
|
|
|
|
k: int = node.kind()
|
|
# Constant: emit numeric/string/bool/None literals directly
|
|
if k == ast.ASTKind.Constant:
|
|
cn: ast.Constant* = ast.Constant*(node)
|
|
if cn.const_kind == ast.CONST_INT or cn.const_kind == ast.CONST_CHAR:
|
|
num_buf_dv: str = stdlib.malloc(24)
|
|
if num_buf_dv is not None:
|
|
viperlib.snprintf(num_buf_dv, 24, "%d", cn.int_val)
|
|
pos = _PyiEmit(buf, buf_size, pos, num_buf_dv)
|
|
stdlib.free(num_buf_dv)
|
|
return pos
|
|
if cn.const_kind == ast.CONST_STR:
|
|
pos = _PyiEmit(buf, buf_size, pos, "\"")
|
|
if cn.str_val is not None: pos = _PyiEmit(buf, buf_size, pos, cn.str_val)
|
|
return _PyiEmit(buf, buf_size, pos, "\"")
|
|
if cn.const_kind == ast.CONST_BOOL:
|
|
if cn.int_val != 0: return _PyiEmit(buf, buf_size, pos, "True")
|
|
return _PyiEmit(buf, buf_size, pos, "False")
|
|
if cn.const_kind == ast.CONST_NONE: return _PyiEmit(buf, buf_size, pos, "None")
|
|
return pos
|
|
|
|
# Name: CDefine constant name (e.g. share: ULONG = SHARE_READ)
|
|
if k == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(node)
|
|
if nm.id is not None: return _PyiEmit(buf, buf_size, pos, nm.id)
|
|
return pos
|
|
|
|
# UnaryOp: unary operation (e.g. -1, ~0)
|
|
if k == ast.ASTKind.UnaryOp:
|
|
uop: ast.UnaryOp* = ast.UnaryOp*(node)
|
|
if uop.op == ast.OpKind.USub: pos = _PyiEmit(buf, buf_size, pos, "-")
|
|
elif uop.op == ast.OpKind.UAdd: pos = _PyiEmit(buf, buf_size, pos, "+")
|
|
elif uop.op == ast.OpKind.Invert: pos = _PyiEmit(buf, buf_size, pos, "~")
|
|
else: return pos
|
|
if uop.operand is not None: return _PyiDefaultStr(buf, buf_size, pos, uop.operand, depth + 1)
|
|
return pos
|
|
|
|
# Other complex expressions (BinOp etc.): emit nothing; caller enforces fail-fast
|
|
return pos
|
|
|
|
|
|
# _PyiRejectCReturnDecorator - fail if @c.CReturn appears in the decorator list.
|
|
# The decorator was removed; a stale stub must be regenerated. 0 = none found.
|
|
def _PyiRejectCReturnDecorator(node: ast.AST*, decorators: list[ast.AST*]*) -> int:
|
|
"""Fail fast on a @c.CReturn decorator (returns 0 when absent)"""
|
|
if decorators is None: return 0
|
|
dn: sizet = len(decorators)
|
|
di: sizet = 0
|
|
while di < dn:
|
|
dnode: ast.AST* = decorators.get(di)
|
|
di += 1
|
|
if dnode is None: continue
|
|
# Handles both @c.CReturn(...) (Call with func=Attribute) and bare @c.CReturn
|
|
dattr: ast.Attribute* = None
|
|
if dnode.kind() == ast.ASTKind.Call:
|
|
dcall: ast.Call* = ast.Call*(dnode)
|
|
if dcall.func is not None and dcall.func.kind() == ast.ASTKind.Attribute: dattr = ast.Attribute*(dcall.func)
|
|
elif dnode.kind() == ast.ASTKind.Attribute: dattr = ast.Attribute*(dnode)
|
|
if dattr is None or dattr.attr is None: continue
|
|
if string.strcmp(dattr.attr, "CReturn") == 0:
|
|
GenApi.fatal_error(node, "vpi still contains `@c.CReturn` (removed): regenerate the .vpi using a return annotation `-> tuple[ty1, ty2, ...]` (you may add --clean)", "TypeError")
|
|
return 1
|
|
return 0
|
|
|
|
|
|
# ============================================================
|
|
# 逃逸摘要计算用的复用临时区(74 槽:gset[0]=global 计数、gset[1..65)=名哈希、box=gset+65)。
|
|
# 只分配一次,避免每个 def 都分配;布局与 StructGen 的 EscScratch 完全一致。
|
|
_g_pyi_esc_scratch: sizet* = None
|
|
|
|
|
|
def _PyiEnsureEscScratch() -> int:
|
|
"""惰性分配摘要临时区;1=可用 / 0=失败"""
|
|
# 必须声明 global:否则赋值会落到**局部**,调用方读到的仍是 NULL 全局,
|
|
# box 指针算成 0+520 → ASan: WRITE on 0x208(本文件它处同款写法见 _g_pyi_cdefine_found)。
|
|
global _g_pyi_esc_scratch
|
|
if _g_pyi_esc_scratch is not None: return 1
|
|
_g_pyi_esc_scratch = sizet*(stdlib.malloc(74 * 8))
|
|
if _g_pyi_esc_scratch is None: return 0
|
|
string.memset(_g_pyi_esc_scratch, 0, 74 * 8)
|
|
return 1
|
|
|
|
|
|
# _PyiFunction - emit a function signature (def name(...) -> Ret: pass)
|
|
def _PyiFunction(buf: bytes, buf_size: sizet, pos: sizet, fn: ast.FunctionDef*, indent_level: int, class_name: str) -> sizet:
|
|
"""Emit a function signature into buf and return the new pos"""
|
|
# Decorators
|
|
if fn.decorator_list is not None:
|
|
fn_decorator_list: list[ast.AST*]* = fn.decorator_list
|
|
dn: sizet = len(fn_decorator_list)
|
|
for di in range(dn):
|
|
dec: ast.AST* = fn_decorator_list.get(di)
|
|
if dec is not None:
|
|
pos = _PyiIndent(buf, buf_size, pos, indent_level)
|
|
pos = _PyiEmit(buf, buf_size, pos, "@")
|
|
pos = _PyiTypeStr(buf, buf_size, pos, dec)
|
|
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
|
|
|
pos = _PyiIndent(buf, buf_size, pos, indent_level)
|
|
pos = _PyiEmit(buf, buf_size, pos, "def ")
|
|
if fn.name is not None: pos = _PyiEmit(buf, buf_size, pos, fn.name)
|
|
pos = _PyiEmit(buf, buf_size, pos, "(")
|
|
|
|
# Parameter list
|
|
args_node: ast.AST* = fn.args
|
|
if args_node is not None:
|
|
args: ast.Arguments* = ast.Arguments*(args_node)
|
|
first: int = 1
|
|
|
|
# Positional parameters
|
|
if args.args is not None:
|
|
args_args: list[ast.AST*]* = args.args
|
|
an: sizet = len(args_args)
|
|
# Defaults right-align to args tail: defaults[i] maps to args[an - dn + i];
|
|
# emit '= <default>' and declare arg_defaults inline to avoid .get misdispatch.
|
|
arg_defaults: list[ast.AST*]* = args.defaults
|
|
dn: sizet = 0
|
|
if arg_defaults is not None: dn = len(arg_defaults)
|
|
ai: sizet = 0
|
|
while ai < an:
|
|
if first == 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
|
first = 0
|
|
ag: ast.Arg* = ast.Arg*(args_args.get(ai))
|
|
if ag.arg is not None: pos = _PyiEmit(buf, buf_size, pos, ag.arg)
|
|
if ag.annotation is not None:
|
|
pos = _PyiEmit(buf, buf_size, pos, ": ")
|
|
pos = _PyiTypeStr(buf, buf_size, pos, ag.annotation)
|
|
if arg_defaults is not None and an - dn <= ai:
|
|
di: sizet = ai - (an - dn)
|
|
dnode: ast.AST* = arg_defaults.get(di)
|
|
if dnode is not None:
|
|
pos = _PyiEmit(buf, buf_size, pos, " = ")
|
|
pos = _PyiDefaultStr(buf, buf_size, pos, dnode)
|
|
ai += 1
|
|
|
|
# *args
|
|
if args.vararg is not None:
|
|
if first == 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
|
first = 0
|
|
pos = _PyiEmit(buf, buf_size, pos, "*")
|
|
va: ast.Arg* = ast.Arg*(args.vararg)
|
|
if va.arg is not None: pos = _PyiEmit(buf, buf_size, pos, va.arg)
|
|
|
|
# **kwargs
|
|
if args.kwarg is not None:
|
|
if first == 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
|
first = 0
|
|
pos = _PyiEmit(buf, buf_size, pos, "**")
|
|
kw: ast.Arg* = ast.Arg*(args.kwarg)
|
|
if kw.arg is not None: pos = _PyiEmit(buf, buf_size, pos, kw.arg)
|
|
|
|
pos = _PyiEmit(buf, buf_size, pos, ")")
|
|
|
|
# Return type
|
|
if fn.returns is not None:
|
|
pos = _PyiEmit(buf, buf_size, pos, " -> ")
|
|
pos = _PyiTypeStr(buf, buf_size, pos, fn.returns)
|
|
else:
|
|
# Without a `->` annotation, @c.CReturn is removed: fail fast to regenerate vpi
|
|
_PyiRejectCReturnDecorator(fn, fn.decorator_list)
|
|
|
|
pos = _PyiEmit(buf, buf_size, pos, ": pass")
|
|
# v3 跨模块逃逸摘要:把形参逃逸掩码与返回借用位写成**同一行**的行尾注释,供 VpiReader
|
|
# 解析后在别的模块的调用点查询(跨模块摘要表按文件重置,只能靠 vpi 传递)。
|
|
# 只在真有逃逸/返回借用时写,保持 vpi 干净:无标记 = 无摘要 = 调用点保守放行。
|
|
# P2 willFree:同行走 `# ... wf=<mask>`(第 k 位=第 k 个形参在本调用后失效),
|
|
# 纯声明式、不依赖逃逸扫描的 scratch,所以单独算、独立决定是否落盘。
|
|
wf_mask_c: int = GenApi.compute_willfree_mask(fn)
|
|
esc_v_c: int = 0
|
|
ret_v_c: int = 0
|
|
if _PyiEnsureEscScratch() != 0:
|
|
esc_box_c: sizet* = sizet*(uint64(_g_pyi_esc_scratch) + 520)
|
|
if GenApi.compute_escape_summary(None, fn, _g_pyi_esc_scratch, esc_box_c) == 0:
|
|
esc_v_c = int(esc_box_c[0])
|
|
ret_v_c = int(esc_box_c[1])
|
|
if esc_v_c != 0 or ret_v_c != 0 or wf_mask_c != 0:
|
|
tmp_c: bytes = stdlib.malloc(96)
|
|
if tmp_c is not None:
|
|
if esc_v_c != 0 or ret_v_c != 0:
|
|
if wf_mask_c != 0: viperlib.snprintf(tmp_c, 96, " # esc=%d ret=%d wf=%d", esc_v_c, ret_v_c, wf_mask_c)
|
|
else: viperlib.snprintf(tmp_c, 96, " # esc=%d ret=%d", esc_v_c, ret_v_c)
|
|
else: viperlib.snprintf(tmp_c, 96, " # wf=%d", wf_mask_c)
|
|
pos = _PyiEmit(buf, buf_size, pos, tmp_c)
|
|
stdlib.free(tmp_c)
|
|
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
|
return pos
|
|
|
|
|
|
# _PyiClass - emit a class definition (fields and method stubs).
|
|
def _PyiClass(buf: bytes, buf_size: sizet, pos: sizet, cls: ast.ClassDef*, indent_level: int) -> sizet:
|
|
"""Emit a class definition into buf and return the new pos"""
|
|
# Decorators
|
|
if cls.decorator_list is not None:
|
|
cls_decorator_list: list[ast.AST*]* = cls.decorator_list
|
|
dcn: sizet = len(cls_decorator_list)
|
|
for dci in range(dcn):
|
|
ddec: ast.AST* = cls_decorator_list.get(dci)
|
|
if ddec is not None:
|
|
pos = _PyiIndent(buf, buf_size, pos, indent_level)
|
|
pos = _PyiEmit(buf, buf_size, pos, "@")
|
|
pos = _PyiTypeStr(buf, buf_size, pos, ddec)
|
|
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
|
|
|
pos = _PyiIndent(buf, buf_size, pos, indent_level)
|
|
pos = _PyiEmit(buf, buf_size, pos, "class ")
|
|
if cls.name is not None: pos = _PyiEmit(buf, buf_size, pos, cls.name)
|
|
|
|
# PEP 695 type parameters (class Foo[T]:)
|
|
if cls.type_params is not None:
|
|
# ClassDef.type_params is really rawptr[list[str]] (a list of name strings),
|
|
# not list[rawptr[AST]] (that is decorator_list's type); align with the rest of the repo.
|
|
cls_type_params: list[str]* = cls.type_params
|
|
tpn: sizet = len(cls_type_params)
|
|
if tpn > 0:
|
|
pos = _PyiEmit(buf, buf_size, pos, "[")
|
|
for tpi in range(tpn):
|
|
if tpi > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
|
tpname: str = cls_type_params.get(tpi)
|
|
if tpname is not None: pos = _PyiEmit(buf, buf_size, pos, tpname)
|
|
pos = _PyiEmit(buf, buf_size, pos, "]")
|
|
|
|
# Base classes
|
|
if cls.bases is not None:
|
|
cls_bases: list[ast.AST*]* = cls.bases
|
|
bn: sizet = len(cls_bases)
|
|
if bn > 0:
|
|
pos = _PyiEmit(buf, buf_size, pos, "(")
|
|
for bi in range(bn):
|
|
if bi > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
|
base: ast.AST* = cls_bases.get(bi)
|
|
pos = _PyiTypeStr(buf, buf_size, pos, base)
|
|
pos = _PyiEmit(buf, buf_size, pos, ")")
|
|
|
|
pos = _PyiEmit(buf, buf_size, pos, ":\n")
|
|
|
|
# Class members
|
|
has_member: int = 0
|
|
|
|
if cls.children is not None:
|
|
cls_children: list[ast.AST*]* = cls.children
|
|
cn: sizet = len(cls_children)
|
|
ci: sizet = 0
|
|
while ci < cn:
|
|
stmt: ast.AST* = cls_children.get(ci)
|
|
if stmt is not None:
|
|
sk: int = stmt.kind()
|
|
|
|
# Methods
|
|
if sk == ast.ASTKind.FunctionDef:
|
|
mfn: ast.FunctionDef* = ast.FunctionDef*(stmt)
|
|
# __init__ method: first extract self.x: Type attributes
|
|
if mfn.name is not None and string.strcmp(mfn.name, "__init__") == 0:
|
|
if mfn.children is not None:
|
|
mfn_children: list[ast.AST*]* = mfn.children
|
|
icn: sizet = len(mfn_children)
|
|
ici: sizet = 0
|
|
while ici < icn:
|
|
ibody: ast.AST* = mfn_children.get(ici)
|
|
if ibody is not None and ibody.kind() == ast.ASTKind.AnnAssign:
|
|
iaa: ast.AnnAssign* = ast.AnnAssign*(ibody)
|
|
if iaa.target is not None and iaa.target.kind() == ast.ASTKind.Attribute:
|
|
iat: ast.Attribute* = ast.Attribute*(iaa.target)
|
|
# Check whether target.value is Name("self")
|
|
if iat.value is not None and iat.value.kind() == ast.ASTKind.Name:
|
|
iavn: ast.Name* = ast.Name*(iat.value)
|
|
if iavn.id is None or string.strcmp(iavn.id, "self") != 0:
|
|
ici += 1
|
|
continue
|
|
pos = _PyiIndent(buf, buf_size, pos, indent_level + 1)
|
|
if iat.attr is not None: pos = _PyiEmit(buf, buf_size, pos, iat.attr)
|
|
pos = _PyiEmit(buf, buf_size, pos, ": ")
|
|
pos = _PyiTypeStr(buf, buf_size, pos, iaa.annotation)
|
|
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
|
has_member = 1
|
|
ici += 1
|
|
pos = _PyiFunction(buf, buf_size, pos, mfn, indent_level + 1, cls.name)
|
|
has_member = 1
|
|
|
|
# Annotated attributes
|
|
elif sk == ast.ASTKind.AnnAssign:
|
|
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
|
|
if aa.target is not None and aa.target.kind() == ast.ASTKind.Name:
|
|
tn: ast.Name* = ast.Name*(aa.target)
|
|
pos = _PyiIndent(buf, buf_size, pos, indent_level + 1)
|
|
if tn.id is not None: pos = _PyiEmit(buf, buf_size, pos, tn.id)
|
|
pos = _PyiEmit(buf, buf_size, pos, ": ")
|
|
pos = _PyiTypeStr(buf, buf_size, pos, aa.annotation)
|
|
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
|
has_member = 1
|
|
|
|
# Nested classes
|
|
elif sk == ast.ASTKind.ClassDef:
|
|
ncls: ast.ClassDef* = ast.ClassDef*(stmt)
|
|
pos = _PyiClass(buf, buf_size, pos, ncls, indent_level + 1)
|
|
has_member = 1
|
|
ci += 1
|
|
|
|
# Emit pass for an empty class
|
|
if has_member == 0:
|
|
pos = _PyiIndent(buf, buf_size, pos, indent_level + 1)
|
|
pos = _PyiEmit(buf, buf_size, pos, "pass\n")
|
|
|
|
return pos
|
|
|
|
|
|
# _GeneratePyiFromAst - walk the AST Module and emit .vpi stub content into buf.
|
|
# Returns bytes written (0 on failure); rel_path should be absolute for error attribution.
|
|
def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, rel_path: str, buf: bytes, buf_size: sizet) -> sizet:
|
|
"""Generate .vpi stub content from an AST into buf"""
|
|
if tree is None or buf is None or buf_size == 0: return 0
|
|
|
|
# Error attribution: point "current file" at the file being stubbed. Callers must pass
|
|
# an absolute path (fatal_error opens it directly). Set once at entry to cover all branches.
|
|
if rel_path is not None: GenApi.set_current_file_type(rel_path)
|
|
|
|
pos: sizet = 0
|
|
|
|
# Pre-pass: register this file's CDefines into the _g_pyi cache in declaration order
|
|
# (vpi runs before the translate phase; without this, BinOp Name refs degrade to 0).
|
|
global _g_pyi_cdef_names, _g_pyi_cdef_vals, _g_pyi_cdef_count
|
|
if tree.children is not None:
|
|
pre_children: list[ast.AST*]* = tree.children
|
|
pcn: sizet = len(pre_children)
|
|
# Pass 1: count t.CDefine declarations (C arrays need a fixed size first)
|
|
_g_pyi_cdef_count = 0
|
|
for pci0 in range(pcn):
|
|
stmt_pc0: ast.AST* = pre_children.get(pci0)
|
|
if stmt_pc0 is not None and stmt_pc0.kind() == ast.ASTKind.AnnAssign:
|
|
aa_pc0: ast.AnnAssign* = ast.AnnAssign*(stmt_pc0)
|
|
if aa_pc0.target is not None and aa_pc0.target.kind() == ast.ASTKind.Name and aa_pc0.annotation is not None:
|
|
tgt_pc0: ast.Name* = ast.Name*(aa_pc0.target)
|
|
if tgt_pc0.id is not None and aa_pc0.annotation.kind() == ast.ASTKind.Attribute:
|
|
ann_pc0: ast.Attribute* = ast.Attribute*(aa_pc0.annotation)
|
|
if ann_pc0.attr is not None and string.strcmp(ann_pc0.attr, "CDefine") == 0: _g_pyi_cdef_count += 1
|
|
if _g_pyi_cdef_count > 0:
|
|
_g_pyi_cdef_names = (str*)(mb.alloc(sizet(_g_pyi_cdef_count) * 8))
|
|
_g_pyi_cdef_vals = (int64*)(mb.alloc(sizet(_g_pyi_cdef_count) * 8))
|
|
# Pass 2: register in declaration order (BinOp Name refs depend on earlier entries)
|
|
pci: sizet = 0
|
|
cdi: int = 0
|
|
for pci in range(pcn):
|
|
stmt_pc: ast.AST* = pre_children.get(pci)
|
|
if stmt_pc is not None and stmt_pc.kind() == ast.ASTKind.AnnAssign:
|
|
aa_pc: ast.AnnAssign* = ast.AnnAssign*(stmt_pc)
|
|
if aa_pc.target is not None and aa_pc.target.kind() == ast.ASTKind.Name and aa_pc.annotation is not None:
|
|
tgt_pc: ast.Name* = ast.Name*(aa_pc.target)
|
|
if tgt_pc.id is not None and aa_pc.annotation.kind() == ast.ASTKind.Attribute:
|
|
ann_pc: ast.Attribute* = ast.Attribute*(aa_pc.annotation)
|
|
if ann_pc.attr is not None and string.strcmp(ann_pc.attr, "CDefine") == 0:
|
|
if _g_pyi_cdef_names is not None and _g_pyi_cdef_vals is not None:
|
|
slot_pc: str* = (str*)(uint64(_g_pyi_cdef_names) + cdi * 8)
|
|
slot_pc[0] = tgt_pc.id
|
|
_g_pyi_cdef_vals[cdi] = _eval_cdefine_value_cache(aa_pc.value)
|
|
cdi += 1
|
|
|
|
# File header
|
|
pos = _PyiEmit(buf, buf_size, pos, "\"\"\"Auto-generated Python stub file\"\"\"\n\n")
|
|
|
|
if tree.children is None:
|
|
if pos < buf_size: buf[pos] = 0
|
|
return pos
|
|
|
|
tree_children: list[ast.AST*]* = tree.children
|
|
n: sizet = len(tree_children)
|
|
|
|
# Pass 1: detect import t/c
|
|
has_import_t: int = 0
|
|
has_import_c: int = 0
|
|
for i in range(n):
|
|
stmt: ast.AST* = tree_children.get(i)
|
|
if stmt is not None and stmt.kind() == ast.ASTKind.Import:
|
|
imp: ast.Import* = ast.Import*(stmt)
|
|
if imp.names is not None:
|
|
imp_names: list[ast.AST*]* = imp.names
|
|
an: sizet = len(imp_names)
|
|
for ai in range(an):
|
|
al: ast.Alias* = ast.Alias*(imp_names.get(ai))
|
|
if al is not None and al.name is not None:
|
|
if string.strcmp(al.name, "t") == 0: has_import_t = 1
|
|
elif string.strcmp(al.name, "c") == 0: has_import_c = 1
|
|
|
|
# Add missing import t/c
|
|
if has_import_t == 0 and has_import_c == 0: pos = _PyiEmit(buf, buf_size, pos, "import t, c\n\n")
|
|
elif has_import_t == 0: pos = _PyiEmit(buf, buf_size, pos, "import t\n\n")
|
|
elif has_import_c == 0: pos = _PyiEmit(buf, buf_size, pos, "import c\n\n")
|
|
else: pos = _PyiEmit(buf, buf_size, pos, "\n")
|
|
|
|
# Pass 2: emit declarations
|
|
for i in range(n):
|
|
stmt = tree_children.get(i)
|
|
if stmt is not None:
|
|
k: int = stmt.kind()
|
|
|
|
# Import statement
|
|
if k == ast.ASTKind.Import:
|
|
imp2: ast.Import* = ast.Import*(stmt)
|
|
pos = _PyiEmit(buf, buf_size, pos, "import ")
|
|
# fail-fast: an empty alias list would write a broken `import ` line and
|
|
# lose all names, breaking package re-export registration.
|
|
if imp2.names is None or len(imp2.names) == 0:
|
|
GenApi.fatal_error(stmt, "vpi generation: the import statement alias list is empty (expected >=1 alias) -- AST construction/parse anomaly", "NameError")
|
|
if imp2.names is not None:
|
|
imp2_names: list[ast.AST*]* = imp2.names
|
|
an2: sizet = len(imp2_names)
|
|
for ai2 in range(an2):
|
|
if ai2 > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
|
al2: ast.Alias* = ast.Alias*(imp2_names.get(ai2))
|
|
# fail-fast: never silently write "import , ". Import names are the
|
|
# only input for package re-export registration. Report the two causes separately.
|
|
if al2 is None:
|
|
GenApi.fatal_error(stmt, "vpi generation: import alias **node pointer is null** (Alias(...) construction produced no object: " "pool.alloc failed or __init__ was not run) -- losing the import name breaks package re-export registration", "NameError")
|
|
if al2.name is None or al2.name[0] == '\0':
|
|
GenApi.fatal_error(stmt, "vpi generation: the import alias node exists but **name is empty** (_copy_str returned None / " "__init__ did not set name) -- losing the import name breaks package re-export registration", "NameError")
|
|
pos = _PyiEmit(buf, buf_size, pos, al2.name)
|
|
if al2.asname is not None and string.strlen(al2.asname) > 0:
|
|
pos = _PyiEmit(buf, buf_size, pos, " as ")
|
|
pos = _PyiEmit(buf, buf_size, pos, al2.asname)
|
|
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
|
|
|
# ImportFrom statement
|
|
elif k == ast.ASTKind.ImportFrom:
|
|
ifr: ast.ImportFrom* = ast.ImportFrom*(stmt)
|
|
pos = _PyiEmit(buf, buf_size, pos, "from ")
|
|
lvl: int = ifr.level
|
|
while lvl > 0:
|
|
pos = _PyiEmit(buf, buf_size, pos, ".")
|
|
lvl -= 1
|
|
if ifr.module is not None: pos = _PyiEmit(buf, buf_size, pos, ifr.module)
|
|
pos = _PyiEmit(buf, buf_size, pos, " import ")
|
|
# fail-fast: same as Import; `from X import *` uses Alias("*"). An empty
|
|
# list or empty name means a broken AST and would break re-export registration.
|
|
if ifr.names is None or len(ifr.names) == 0:
|
|
GenApi.fatal_error(stmt, "vpi generation: the from-import statement alias list is empty (expected >=1 alias, including star)" "-- AST construction/parse anomaly", "NameError")
|
|
if ifr.names is not None:
|
|
ifr_names: list[ast.AST*]* = ifr.names
|
|
an3: sizet = len(ifr_names)
|
|
for ai3 in range(an3):
|
|
if ai3 > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
|
|
al3: ast.Alias* = ast.Alias*(ifr_names.get(ai3))
|
|
# fail-fast: same as Import (report the two causes separately).
|
|
if al3 is None:
|
|
GenApi.fatal_error(stmt, "vpi generation: from-import alias **node pointer is null** (Alias(...) construction produced no object: " "pool.alloc failed or __init__ was not run) -- package re-export registration will therefore fail", "NameError")
|
|
if al3.name is None or al3.name[0] == '\0':
|
|
GenApi.fatal_error(stmt, "vpi generation: the from-import alias node exists but **name is empty** (_copy_str returned None / " "__init__ did not set name) -- package re-export registration will therefore fail", "NameError")
|
|
pos = _PyiEmit(buf, buf_size, pos, al3.name)
|
|
if al3.asname is not None and string.strlen(al3.asname) > 0:
|
|
pos = _PyiEmit(buf, buf_size, pos, " as ")
|
|
pos = _PyiEmit(buf, buf_size, pos, al3.asname)
|
|
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
|
|
|
# Function definition (_PyiFunction already ends with a newline)
|
|
elif k == ast.ASTKind.FunctionDef:
|
|
fn: ast.FunctionDef* = ast.FunctionDef*(stmt)
|
|
# Generic function templates (def f[T]) emit no vpi: the signature has an
|
|
# unbound T. Specializations are monomorphized per call site, not here.
|
|
if fn.type_params is not None: continue
|
|
pos = _PyiFunction(buf, buf_size, pos, fn, 0, "")
|
|
|
|
# Class definition
|
|
elif k == ast.ASTKind.ClassDef:
|
|
cls: ast.ClassDef* = ast.ClassDef*(stmt)
|
|
pos = _PyiClass(buf, buf_size, pos, cls, 0)
|
|
|
|
# Annotated variable declaration
|
|
elif k == ast.ASTKind.AnnAssign:
|
|
aa: ast.AnnAssign* = ast.AnnAssign*(stmt)
|
|
if aa.target is not None and aa.target.kind() == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(aa.target)
|
|
if nm.id is not None:
|
|
pos = _PyiEmit(buf, buf_size, pos, nm.id)
|
|
pos = _PyiEmit(buf, buf_size, pos, ": ")
|
|
pos = _PyiTypeStr(buf, buf_size, pos, aa.annotation)
|
|
# If there is a value (e.g. a CDefine constant) emit = value.
|
|
# CTypedef is special: its value is a type expression, rendered by _PyiTypeStr.
|
|
is_ctypedef_ann: int = 0
|
|
if aa.annotation is not None and aa.annotation.kind() == ast.ASTKind.Attribute:
|
|
ctd_at: ast.Attribute* = ast.Attribute*(aa.annotation)
|
|
if ctd_at.attr is not None and string.strcmp(ctd_at.attr, "CTypedef") == 0:
|
|
if ctd_at.value is not None and ctd_at.value.kind() == ast.ASTKind.Name:
|
|
ctd_mod: ast.Name* = ast.Name*(ctd_at.value)
|
|
if ctd_mod.id is not None and string.strcmp(ctd_mod.id, "t") == 0: is_ctypedef_ann = 1
|
|
if is_ctypedef_ann != 0 and aa.value is not None:
|
|
pos = _PyiEmit(buf, buf_size, pos, " = ")
|
|
pos = _PyiTypeStr(buf, buf_size, pos, aa.value)
|
|
elif aa.value is not None and aa.value.kind() == ast.ASTKind.Constant:
|
|
cv: ast.Constant* = ast.Constant*(aa.value)
|
|
num_buf: bytes = mb.alloc(32)
|
|
if cv.const_kind == ast.CONST_INT and num_buf is not None:
|
|
viperlib.snprintf(num_buf, 32, " = %lld", cv.int_val)
|
|
pos = _PyiEmit(buf, buf_size, pos, num_buf)
|
|
elif cv.const_kind == ast.CONST_FLOAT:
|
|
# Float CDefines (e.g. numpy pi/e) must emit a value or read-back
|
|
# degrades to a global pointer; %.17g round-trips double exactly.
|
|
fbuf_c: bytes = mb.alloc(64)
|
|
if fbuf_c is not None:
|
|
viperlib.snprintf(fbuf_c, 64, " = %.17g", cv.float_val)
|
|
pos = _PyiEmit(buf, buf_size, pos, fbuf_c)
|
|
elif cv.const_kind == ast.CONST_BOOL:
|
|
if cv.int_val != 0: pos = _PyiEmit(buf, buf_size, pos, " = True")
|
|
else: pos = _PyiEmit(buf, buf_size, pos, " = False")
|
|
elif cv.const_kind == ast.CONST_STR:
|
|
# String CDefines (e.g. pyzlib.ZLIB_VERSION) must emit a value so the stub is self-contained
|
|
sbuf_c: bytes = mb.alloc(string.strlen(cv.str_val) + 6)
|
|
if sbuf_c is not None:
|
|
viperlib.snprintf(sbuf_c, string.strlen(cv.str_val) + 6, " = \"%s\"", cv.str_val)
|
|
pos = _PyiEmit(buf, buf_size, pos, sbuf_c)
|
|
elif cv.const_kind == ast.CONST_NONE: pos = _PyiEmit(buf, buf_size, pos, " = None")
|
|
elif aa.value is not None and aa.value.kind() == ast.ASTKind.UnaryOp:
|
|
# Negative constant (e.g. INVALID_HANDLE_VALUE = -1):
|
|
# UnaryOp(USub, Constant(1)) -> -1
|
|
uop_cv: ast.UnaryOp* = ast.UnaryOp*(aa.value)
|
|
num_buf_u: bytes = mb.alloc(32)
|
|
if uop_cv.op == ast.OpKind.USub and uop_cv.operand is not None and uop_cv.operand.kind() == ast.ASTKind.Constant:
|
|
uop_const: ast.Constant* = ast.Constant*(uop_cv.operand)
|
|
if uop_const.const_kind == ast.CONST_INT and num_buf_u is not None:
|
|
neg_val: int64 = -uop_const.int_val
|
|
viperlib.snprintf(num_buf_u, 32, " = %lld", neg_val)
|
|
pos = _PyiEmit(buf, buf_size, pos, num_buf_u)
|
|
elif aa.value is not None and aa.value.kind() == ast.ASTKind.Call:
|
|
# Type-constructor constant (e.g. t.CUnsignedLong(-11)):
|
|
# extract and emit the integer value of the first argument.
|
|
call_cv: ast.Call* = ast.Call*(aa.value)
|
|
if call_cv.args is not None:
|
|
call_args_cv: list[ast.AST*]* = call_cv.args
|
|
if len(call_args_cv) > 0:
|
|
first_arg: ast.AST* = call_args_cv.get(0)
|
|
if first_arg is not None:
|
|
arg_kind: int = first_arg.kind()
|
|
if arg_kind == ast.ASTKind.Constant:
|
|
ca_cv: ast.Constant* = ast.Constant*(first_arg)
|
|
num_buf_c: bytes = mb.alloc(32)
|
|
if ca_cv.const_kind == ast.CONST_INT and num_buf_c is not None:
|
|
viperlib.snprintf(num_buf_c, 32, " = %lld", ca_cv.int_val)
|
|
pos = _PyiEmit(buf, buf_size, pos, num_buf_c)
|
|
elif ca_cv.const_kind == ast.CONST_FLOAT:
|
|
# Call-wrapped float constant (e.g. float(3.14159...)): emit the
|
|
# value or read-back degrades to 0.0; %.17g round-trips double.
|
|
fbuf_cc: bytes = mb.alloc(64)
|
|
if fbuf_cc is not None:
|
|
viperlib.snprintf(fbuf_cc, 64, " = %.17g", ca_cv.float_val)
|
|
pos = _PyiEmit(buf, buf_size, pos, fbuf_cc)
|
|
elif arg_kind == ast.ASTKind.UnaryOp:
|
|
uop_ca: ast.UnaryOp* = ast.UnaryOp*(first_arg)
|
|
num_buf_cu: bytes = mb.alloc(32)
|
|
if uop_ca.op == ast.OpKind.USub and uop_ca.operand is not None and uop_ca.operand.kind() == ast.ASTKind.Constant:
|
|
uoc: ast.Constant* = ast.Constant*(uop_ca.operand)
|
|
if uoc.const_kind == ast.CONST_INT and num_buf_cu is not None:
|
|
neg_c_val: int64 = -uoc.int_val
|
|
viperlib.snprintf(num_buf_cu, 32, " = %lld", neg_c_val)
|
|
pos = _PyiEmit(buf, buf_size, pos, num_buf_cu)
|
|
elif uoc.const_kind == ast.CONST_FLOAT:
|
|
# Negative Call-wrapped float (e.g. float(-1.5)):
|
|
# same as CONST_FLOAT, %.17g round-trips exactly.
|
|
neg_cf: double = -uoc.float_val
|
|
fbuf_cu: bytes = mb.alloc(64)
|
|
if fbuf_cu is not None:
|
|
viperlib.snprintf(fbuf_cu, 64, " = %.17g", neg_cf)
|
|
pos = _PyiEmit(buf, buf_size, pos, fbuf_cu)
|
|
elif arg_kind == ast.ASTKind.BinOp:
|
|
# BinOp inside a Call (e.g. t.CUnsignedLong(A | B)):
|
|
# recursively evaluate the BinOp value.
|
|
binop_val_bo: int64 = _eval_binop_int(first_arg)
|
|
num_buf_bo: bytes = mb.alloc(32)
|
|
if (binop_val_bo != 0 or _eval_binop_valid(first_arg) != 0) and num_buf_bo is not None:
|
|
viperlib.snprintf(num_buf_bo, 32, " = %lld", binop_val_bo)
|
|
pos = _PyiEmit(buf, buf_size, pos, num_buf_bo)
|
|
elif aa.value is not None and aa.value.kind() == ast.ASTKind.BinOp:
|
|
# BinOp constant (e.g. FOREGROUND_COMBO = FOREGROUND_RED | FOREGROUND_GREEN):
|
|
# recursively evaluate and emit it.
|
|
binop_val_av: int64 = _eval_binop_int(aa.value)
|
|
if binop_val_av != 0 or _eval_binop_valid(aa.value) != 0:
|
|
num_buf_b: bytes = mb.alloc(32)
|
|
if num_buf_b is not None:
|
|
viperlib.snprintf(num_buf_b, 32, " = %lld", binop_val_av)
|
|
pos = _PyiEmit(buf, buf_size, pos, num_buf_b)
|
|
elif aa.value is not None and aa.value.kind() == ast.ASTKind.Name:
|
|
# Name reference constant (e.g. ALIAS = OTHER_CONST):
|
|
# emit the referenced name for the consumer to resolve locally.
|
|
name_val: ast.Name* = ast.Name*(aa.value)
|
|
if name_val.id is not None:
|
|
pos = _PyiEmit(buf, buf_size, pos, " = ")
|
|
pos = _PyiEmit(buf, buf_size, pos, name_val.id)
|
|
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
|
|
|
# Global assignment (no annotation) - infer type from the Constant value
|
|
elif k == ast.ASTKind.Assign:
|
|
asg: ast.Assign* = ast.Assign*(stmt)
|
|
if asg.targets is not None and asg.value is not None:
|
|
asg_targets: list[ast.AST*]* = asg.targets
|
|
atn: sizet = len(asg_targets)
|
|
if atn == 1:
|
|
atg: ast.AST* = asg_targets.get(0)
|
|
if atg is not None and atg.kind() == ast.ASTKind.Name:
|
|
agn: ast.Name* = ast.Name*(atg)
|
|
aval: ast.AST* = asg.value
|
|
avk: int = aval.kind()
|
|
inferred: int = 0
|
|
if avk == ast.ASTKind.Constant:
|
|
acv: ast.Constant* = ast.Constant*(aval)
|
|
if acv.const_kind == ast.CONST_INT: inferred = 1
|
|
elif acv.const_kind == ast.CONST_FLOAT: inferred = 2
|
|
elif acv.const_kind == ast.CONST_STR: inferred = 3
|
|
elif acv.const_kind == ast.CONST_BOOL: inferred = 4
|
|
elif acv.const_kind == ast.CONST_NONE: inferred = 5
|
|
if inferred > 0 and agn.id is not None:
|
|
pos = _PyiEmit(buf, buf_size, pos, agn.id)
|
|
pos = _PyiEmit(buf, buf_size, pos, ": ")
|
|
if inferred == 1: pos = _PyiEmit(buf, buf_size, pos, "int")
|
|
elif inferred == 2: pos = _PyiEmit(buf, buf_size, pos, "double")
|
|
elif inferred == 3: pos = _PyiEmit(buf, buf_size, pos, "str")
|
|
elif inferred == 4: pos = _PyiEmit(buf, buf_size, pos, "bool")
|
|
elif inferred == 5: pos = _PyiEmit(buf, buf_size, pos, "None")
|
|
pos = _PyiEmit(buf, buf_size, pos, "\n")
|
|
|
|
# NUL terminator
|
|
if pos < buf_size: buf[pos] = 0
|
|
else: buf[buf_size - 1] = 0
|
|
return pos
|