cif 三件套:StmtGenCifMixin 负责常量求值(含 defined(模块宏) 的 Attribute 形式),链式错配与含运行时变量一律 fail-fast;删掉 c.CIf 家族。值引用:TypeGenResolve 让 CRef/CTempRef 与 t.CPtr 走同一解析路径(&void 折叠为 i8*,删除 t.CNeedPtr),CallGenBase 在调用点按形参模式自动取址(ref 只接受左值,右值 fail-fast;temp 物化临时栈),跨模块经 VpiReader/StubMergerBuild 传递模式。assert:StmtGenMixin 合成 testcheck.check(cond, None, msg),Translator 检测到 assert 时自动注入 testcheck 导入(带去重与防环),深递归守卫改用 stdio.printf + stdlib.exit(Translator 无 Gen 成员)。
1039 lines
61 KiB
Plaintext
1039 lines
61 KiB
Plaintext
import lib.core.Gen.GenTypes as GenTypes
|
|
import t, c
|
|
from stdint import *
|
|
import ast
|
|
import mlirlite
|
|
import memhub
|
|
import string
|
|
import viperlib
|
|
import lib.core.VLogger as VLogger
|
|
import lib.core.Gen.Translator as HT
|
|
import lib.core.CompilerFactory as CompilerFactory
|
|
|
|
from . import ClassGenBase
|
|
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
|
|
import lib.core.Gen.Type.TypeSpec as TSpec
|
|
|
|
|
|
# Generic specialization recursion depth limit (guards against generic loops):
|
|
# normal nesting is <= 3 (e.g. list[list[str]]); 64 is far beyond sane, so hitting it means a loop.
|
|
define GENERIC_SPEC_MAX_DEPTH = 64
|
|
# Generic template table capacity (aligned with the template count the GenericClassNames pool tables can hold)
|
|
define GENERIC_TEMPLATE_MAX = 512
|
|
# Generic specialization cache table capacity
|
|
define SPEC_CACHE_MAX = 512
|
|
# Signedness sentinel meaning "take from Spec.IsSigned" (distinct from the three TSpec.TS_SIGN_* values)
|
|
define SIGN_FROM_SPEC = -2
|
|
|
|
# ClassGenGeneric - generic template registration / specialization / cloning
|
|
novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
|
|
"""Generic template registration, specialization and cloning."""
|
|
|
|
# Cluster C: generic template registration/specialization (P5-2 phase 3 migration; bodies moved
|
|
# from HandlesClassDef.vp unchanged; _generic_class_* tables now live in self.GenericClassNames etc.).
|
|
|
|
def _is_generic_class(self, tp: list[str]*) -> int:
|
|
"""Return 1 when the class type_params mark a generic class; 0 otherwise."""
|
|
if tp is None: return 0
|
|
return 1
|
|
def _dup_perm_str(self, s: str) -> str:
|
|
"""Deep-copy a string into the permanent pool; return the input when unavailable."""
|
|
if s is None: return None
|
|
perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
|
if perm is None: return s
|
|
n: sizet = string.strlen(s)
|
|
b: str = perm.alloc(n + 1)
|
|
if b is None: return s
|
|
string.strcpy(b, s)
|
|
return b
|
|
def _register_generic_template(self, pool: memhub.MemBuddy*, cd: ast.ClassDef*, module_sha1: str) -> None:
|
|
"""Register a generic class template (name, ClassDef node, source module SHA1)."""
|
|
if cd is None or cd.name is None: return
|
|
perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
|
table_pool: memhub.MemBuddy* = perm
|
|
if table_pool is None: table_pool = pool
|
|
if self.GenericClassNames is None:
|
|
self.GenericClassNames = list[str](table_pool, 8)
|
|
self.GenericClassNodes = list[ast.ClassDef*](table_pool, 8)
|
|
self.GenericClassSha1s = list[str](table_pool, 8)
|
|
self.GenericClassFiles = list[str](table_pool, 8)
|
|
if self.GenericClassTpParams is None:
|
|
if perm is None: return
|
|
self.GenericClassTpParams = perm.alloc(GENERIC_TEMPLATE_MAX * 8)
|
|
if self.GenericClassTpParams is None: return
|
|
string.memset(self.GenericClassTpParams, 0, GENERIC_TEMPLATE_MAX * 8)
|
|
# Assign the list to a local to trigger type inference (avoids field __len__ failure)
|
|
names_local: list[str]* = self.GenericClassNames
|
|
cn: sizet = len(names_local)
|
|
for i in range(cn):
|
|
nm: str = names_local.get(i)
|
|
if nm is not None and string.strcmp(nm, cd.name) == 0:
|
|
# A duplicate name must not simply return: the first-registered node may lack
|
|
# type_params, so backfill the empty tp slot and replace the node with the richer one.
|
|
if self.GenericClassTpParams is not None and i < GENERIC_TEMPLATE_MAX and self.GenericClassTpParams[i] == 0:
|
|
back_buf: str = self._copy_type_params_stdlib(cd.type_params)
|
|
if back_buf is not None:
|
|
self.GenericClassTpParams[i] = sizet(back_buf)
|
|
if self.GenericClassNodes is not None: self.GenericClassNodes.set(i, cd)
|
|
return
|
|
# Deep-copy type_params to stdlib (the template node's list is pool-allocated and may be recycled)
|
|
tp_names_buf: str = self._copy_type_params_stdlib(cd.type_params)
|
|
self.GenericClassNames.append(self._dup_perm_str(cd.name))
|
|
self.GenericClassNodes.append(cd)
|
|
self.GenericClassSha1s.append(self._dup_perm_str(module_sha1))
|
|
# Also store the template source file path (for error reporting; see the field comment)
|
|
self.GenericClassFiles.append(self._dup_perm_str(self.Gen.TypeGenResolve.get_current_file()))
|
|
if tp_names_buf is not None and cn < GENERIC_TEMPLATE_MAX: self.GenericClassTpParams[cn] = sizet(tp_names_buf)
|
|
def _copy_type_params_stdlib(self, tp: list[str]*) -> str:
|
|
"""Deep-copy template type_params into a space-separated stdlib buffer; None on failure."""
|
|
if tp is None: return None
|
|
tp_n: sizet = len(tp)
|
|
if tp_n == 0: return None
|
|
# Compute total length: each name length + (n-1) spaces + NUL
|
|
total: sizet = 1
|
|
ti: sizet
|
|
for ti in range(tp_n):
|
|
tp_name: str = tp.get(ti)
|
|
if tp_name is not None: total += string.strlen(tp_name) + 1
|
|
buf: str = CompilerFactory.factory.PermPool.alloc(total)
|
|
if buf is None: return None
|
|
buf[0] = '\0'
|
|
for ti in range(tp_n):
|
|
tp_name = tp.get(ti)
|
|
if tp_name is None: continue
|
|
if ti > 0 and buf[0] != '\0': string.strcat(buf, " ")
|
|
string.strcat(buf, tp_name)
|
|
return buf
|
|
|
|
# Generic function templates (PEP 695 def f[T](...)): mirrors the generic class path; keys include
|
|
# the defining module SHA1, call sites infer args, and specialization emits a define (or a declare).
|
|
def _is_generic_function(self, tp: list[str]*) -> int:
|
|
"""Return 1 when the function type_params mark a generic function; 0 otherwise."""
|
|
if tp is None: return 0
|
|
return 1
|
|
|
|
def _register_generic_func_template(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, module_sha1: str) -> None:
|
|
"""Register a generic function template keyed by "{module_sha1}.{name}"."""
|
|
if fd is None or fd.name is None or module_sha1 is None: return
|
|
perm: memhub.MemBuddy* = CompilerFactory.factory.PermPool
|
|
table_pool: memhub.MemBuddy* = perm
|
|
if table_pool is None: table_pool = pool
|
|
if self.GenericFuncKeys is None:
|
|
self.GenericFuncKeys = list[str](table_pool, 8)
|
|
self.GenericFuncNodes = list[ast.FunctionDef*](table_pool, 8)
|
|
self.GenericFuncSha1s = list[str](table_pool, 8)
|
|
self.GenericFuncFiles = list[str](table_pool, 8)
|
|
if self.GenericFuncTpParams is None:
|
|
if perm is None: return
|
|
self.GenericFuncTpParams = perm.alloc(GENERIC_TEMPLATE_MAX * 8)
|
|
if self.GenericFuncTpParams is None: return
|
|
string.memset(self.GenericFuncTpParams, 0, GENERIC_TEMPLATE_MAX * 8)
|
|
# Key = "{sha1}.{name}" (built in a new buffer to avoid in-place strcat overflow)
|
|
key_len: sizet = string.strlen(module_sha1) + string.strlen(fd.name) + 2
|
|
key_buf: str = table_pool.alloc(key_len)
|
|
if key_buf is None: return
|
|
viperlib.snprintf(key_buf, key_len, "%s.%s", module_sha1, fd.name)
|
|
keys_local: list[str]* = self.GenericFuncKeys
|
|
kn: sizet = len(keys_local)
|
|
ki: sizet
|
|
for ki in range(kn):
|
|
knm: str = keys_local.get(ki)
|
|
if knm is not None and string.strcmp(knm, key_buf) == 0: return
|
|
tp_names_buf: str = self._copy_type_params_stdlib(fd.type_params)
|
|
self.GenericFuncKeys.append(self._dup_perm_str(key_buf))
|
|
self.GenericFuncNodes.append(fd)
|
|
self.GenericFuncSha1s.append(self._dup_perm_str(module_sha1))
|
|
self.GenericFuncFiles.append(self._dup_perm_str(self.Gen.TypeGenResolve.get_current_file()))
|
|
if tp_names_buf is not None and kn < GENERIC_TEMPLATE_MAX: self.GenericFuncTpParams[kn] = sizet(tp_names_buf)
|
|
|
|
def _find_generic_func_template(self, key: str) -> ast.FunctionDef*:
|
|
"""Find a generic function template node by "{sha1}.{fname}" key; None when absent."""
|
|
if self.GenericFuncKeys is None or key is None: return None
|
|
keys_local: list[str]* = self.GenericFuncKeys
|
|
nodes_local: list[ast.FunctionDef*]* = self.GenericFuncNodes
|
|
kn: sizet = len(keys_local)
|
|
i: sizet
|
|
for i in range(kn):
|
|
knm: str = keys_local.get(i)
|
|
if knm is not None and string.strcmp(knm, key) == 0: return nodes_local.get(i)
|
|
return None
|
|
|
|
def _find_generic_func_template_sha1(self, key: str) -> str:
|
|
"""Find a generic function template's source module SHA1 by key; None when absent."""
|
|
if self.GenericFuncKeys is None or key is None: return None
|
|
keys_local: list[str]* = self.GenericFuncKeys
|
|
sha1s_local: list[str]* = self.GenericFuncSha1s
|
|
kn: sizet = len(keys_local)
|
|
i: sizet
|
|
for i in range(kn):
|
|
knm: str = keys_local.get(i)
|
|
if knm is not None and string.strcmp(knm, key) == 0: return sha1s_local.get(i)
|
|
return None
|
|
|
|
def _find_generic_func_template_file(self, key: str) -> str:
|
|
"""Find a generic function template's source file path by key; None when absent."""
|
|
if self.GenericFuncKeys is None or key is None: return None
|
|
keys_local: list[str]* = self.GenericFuncKeys
|
|
files_local: list[str]* = self.GenericFuncFiles
|
|
kn: sizet = len(keys_local)
|
|
i: sizet
|
|
for i in range(kn):
|
|
knm: str = keys_local.get(i)
|
|
if knm is not None and string.strcmp(knm, key) == 0: return files_local.get(i)
|
|
return None
|
|
|
|
def _find_generic_func_tp_params(self, pool: memhub.MemBuddy*, key: str) -> list[str]*:
|
|
"""Rebuild a generic function template's type-param name list; None when absent."""
|
|
if self.GenericFuncKeys is None or key is None or self.GenericFuncTpParams is None: return None
|
|
keys_local: list[str]* = self.GenericFuncKeys
|
|
kn: sizet = len(keys_local)
|
|
i: sizet
|
|
for i in range(kn):
|
|
knm: str = keys_local.get(i)
|
|
if knm is not None and string.strcmp(knm, key) == 0:
|
|
if i >= GENERIC_TEMPLATE_MAX: return None
|
|
buf_addr: sizet = self.GenericFuncTpParams[i]
|
|
if buf_addr == 0: return None
|
|
buf_tp: str = rawptr(buf_addr)
|
|
tp_list: list[str]* = list[str](pool, 8)
|
|
if tp_list is None: return None
|
|
seg_start: sizet = 0
|
|
blen: sizet = string.strlen(buf_tp)
|
|
for pos in range(blen + 1):
|
|
if buf_tp[pos] == ' ' or buf_tp[pos] == '\0':
|
|
seg_len: sizet = pos - seg_start
|
|
if seg_len > 0:
|
|
seg: str = pool.alloc(seg_len + 1)
|
|
if seg is not None:
|
|
string.strncpy(seg, buf_tp + seg_start, seg_len)
|
|
seg[seg_len] = '\0'
|
|
tp_list.append(seg)
|
|
seg_start = pos + 1
|
|
return tp_list
|
|
return None
|
|
|
|
def _find_generic_func_param_slot(self, fd: ast.FunctionDef*, tp_name: str) -> int:
|
|
"""Return the 0-based slot of the parameter annotated exactly as tp_name; -1 when absent."""
|
|
if fd is None or tp_name is None or fd.args is None: return -1
|
|
if fd.args.kind() != ast.ASTKind.Arguments: return -1
|
|
ags: ast.Arguments* = ast.Arguments*(fd.args)
|
|
if ags.args is None: return -1
|
|
alist: list[ast.AST*]* = ags.args
|
|
an: sizet = len(alist)
|
|
ai: sizet
|
|
for ai in range(an):
|
|
pa: ast.AST* = alist.get(ai)
|
|
if pa is None or pa.kind() != ast.ASTKind.Arg: continue
|
|
ag: ast.Arg* = ast.Arg*(pa)
|
|
if self.Gen.CallGenCtor._annotation_refers_type_param(ag.annotation, tp_name) != 0: return int(ai)
|
|
return -1
|
|
|
|
def _infer_generic_func_type_arg(self, pool: memhub.MemBuddy*, arg_node: ast.AST*, func_name: str, trans: HT.Translator*) -> str:
|
|
"""Infer a generic function's type argument name from a call argument AST; None when unknown."""
|
|
if arg_node is None: return None
|
|
k: int = arg_node.kind()
|
|
if k == ast.ASTKind.Constant:
|
|
cn: ast.Constant* = ast.Constant*(arg_node)
|
|
if cn.const_kind == ast.CONST_FLOAT: return "double"
|
|
if cn.const_kind == ast.CONST_CHAR: return "char"
|
|
if cn.const_kind == ast.CONST_STR: return "str"
|
|
return "int"
|
|
# Unary +/- constant (clamp(-3, ...)): the type is determined by the operand
|
|
if k == ast.ASTKind.UnaryOp:
|
|
uo: ast.UnaryOp* = ast.UnaryOp*(arg_node)
|
|
return self._infer_generic_func_type_arg(pool, uo.operand, func_name, trans)
|
|
if k == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(arg_node)
|
|
if nm.id is not None and trans is not None:
|
|
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
|
|
if ve is not None:
|
|
cn_nm: str = self.Gen.VarGen.var_annot_class_name(ve)
|
|
pl: int = 0
|
|
if ve.Spec is not None: pl = ve.Spec.PtrLevel
|
|
if cn_nm is not None: return self.Gen.CallGenCtor._ptr_layer_type_arg(pool, cn_nm, pl, trans._from_imports)
|
|
# Builtin scalars (skipped by extract_class_name_from_annotation, SourceName None):
|
|
# reverse-map the Spec's LLVM form to a parseable type name.
|
|
return self._infer_scalar_type_name_from_spec(pool, ve.Spec)
|
|
return None
|
|
if k == ast.ASTKind.Attribute:
|
|
cn_at: str = self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, arg_node)
|
|
pl_at: int = 0
|
|
fe: GenTypes.FieldEntry* = self.Gen.ExprGenAttr._resolve_attr_field_entry(pool, trans, arg_node)
|
|
if fe is not None and fe.Spec is not None: pl_at = fe.Spec.PtrLevel
|
|
fi: str = None
|
|
if trans is not None: fi = trans._from_imports
|
|
if cn_at is not None: return self.Gen.CallGenCtor._ptr_layer_type_arg(pool, cn_at, pl_at, fi)
|
|
# Builtin scalar field: reverse-map from Spec, same as the Name branch
|
|
if fe is not None: return self._infer_scalar_type_name_from_spec(pool, fe.Spec)
|
|
return None
|
|
if k == ast.ASTKind.Call:
|
|
cl_f: ast.Call* = ast.Call*(arg_node)
|
|
if cl_f.func is not None:
|
|
fk_f: int = cl_f.func.kind()
|
|
# Only accept conversion calls whose callee resolves to a scalar type, mapping by exact
|
|
# bit width; non-scalar/unresolvable yields None. No name-substring guessing (never i64->i32).
|
|
if (fk_f == ast.ASTKind.Name or fk_f == ast.ASTKind.Attribute) and trans is not None:
|
|
cty_f: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, cl_f.func, trans._imported_modules, trans._from_imports, trans, 0)
|
|
if cty_f is not None:
|
|
# Signedness can only come from the callee's source text (LLVM iN/uN look alike,
|
|
# SpecFromLLVMType always says SIGNED); reuse spec_sign_of_annot to pick the exact type.
|
|
callee_txt: str = None
|
|
if fk_f == ast.ASTKind.Name: callee_txt = ast.Name*(cl_f.func).id
|
|
else: callee_txt = ast.Attribute*(cl_f.func).attr
|
|
sign_f: int = TSpec.TS_SIGN_UNKNOWN
|
|
if callee_txt is not None: sign_f = self.Gen.TypeGenCType.spec_sign_of_annot(callee_txt, 0)
|
|
return self.infer_scalar_type_arg_from_llvm_ty(pool, cty_f, sign_f)
|
|
return None
|
|
return None
|
|
|
|
def _infer_scalar_type_name_from_spec(self, pool: memhub.MemBuddy*, sp: TSpec.TypeSpec*, sign_override: int = SIGN_FROM_SPEC) -> str:
|
|
"""Reverse-map a variable's TypeSpec to a resolvable builtin scalar type name; None otherwise."""
|
|
if pool is None or sp is None: return None
|
|
buf: str = pool.alloc(32)
|
|
if buf is None: return None
|
|
buf[0] = '\0'
|
|
# SpecToLLVMText returns 0=success / 1=failure; treating 0 as failure made this always return
|
|
# None for builtin scalars, so scalar-typed params could never be inferred.
|
|
if TSpec.SpecToLLVMText(sp, buf, 32) != 0: return None
|
|
sign: int = sign_override
|
|
if sign == SIGN_FROM_SPEC:
|
|
# SpecFromLLVMType always reports TS_SIGN_SIGNED (iN/uN are indistinguishable), so only an
|
|
# annotation-side Spec (via SpecWithSign) makes this field trustworthy.
|
|
sign = sp.IsSigned
|
|
# ---- Integers: exact bit width + consistent signedness (table in docstring) ----
|
|
if string.strcmp(buf, "i1") == 0: return "bool"
|
|
if string.strcmp(buf, "i8") == 0:
|
|
# Unsigned i8 (CUInt8T/CUnsignedChar) via the bare name char would silently change sign
|
|
if sign == TSpec.TS_SIGN_UNSIGNED: return "uint8"
|
|
return "char"
|
|
if string.strcmp(buf, "i16") == 0:
|
|
if sign == TSpec.TS_SIGN_UNSIGNED: return "uint16"
|
|
if sign == TSpec.TS_SIGN_SIGNED: return "int16"
|
|
return None
|
|
if string.strcmp(buf, "i32") == 0:
|
|
# Unsigned i32 (CUInt32T/CSizeT/CUIntPtrT) via the bare name int would silently change sign
|
|
if sign == TSpec.TS_SIGN_UNSIGNED: return "uint32"
|
|
return "int"
|
|
if string.strcmp(buf, "i64") == 0:
|
|
if sign == TSpec.TS_SIGN_UNSIGNED: return "uint64"
|
|
if sign == TSpec.TS_SIGN_SIGNED: return "int64"
|
|
return None
|
|
# ---- Floats: exact bit width (no signedness concept, unambiguous) ----
|
|
if string.strcmp(buf, "half") == 0: return "t.CFloat16T"
|
|
if string.strcmp(buf, "float") == 0: return "float"
|
|
if string.strcmp(buf, "double") == 0: return "double"
|
|
if string.strcmp(buf, "fp128") == 0: return "t.CFloat128T"
|
|
return None
|
|
|
|
def infer_scalar_type_arg_from_llvm_ty(self, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*, sign: int) -> str:
|
|
"""Map an LLVM scalar type to a generic type-argument name by exact bit width; None for non-scalars."""
|
|
if pool is None or ty is None: return None
|
|
arena: TSpec.TypeSpecArena* = self.Gen.TypeGenBase.ensure_spec_arena()
|
|
if arena is None: return None
|
|
return self._infer_scalar_type_name_from_spec(pool, TSpec.SpecFromLLVMType(arena, ty), sign)
|
|
|
|
def _clone_funcdef_for_spec(self, pool: memhub.MemBuddy*, fd: ast.FunctionDef*, spec_base: str, tp_names: list[str]*, type_args: list[str]*) -> ast.FunctionDef*:
|
|
"""Clone a FunctionDef for generic function specialization, substituting concrete types."""
|
|
if fd is None: return None
|
|
new_args: ast.AST* = self._clone_arguments(pool, fd.args, tp_names, type_args)
|
|
new_returns: ast.AST* = self._clone_annotation(pool, fd.returns, tp_names, type_args)
|
|
new_fd: ast.FunctionDef* = ast.FunctionDef(pool, spec_base, new_args, fd.decorator_list, new_returns, 0)
|
|
if fd.children is not None:
|
|
body: list[ast.AST*]* = fd.children
|
|
bn: sizet = len(body)
|
|
bi: sizet
|
|
for bi in range(bn):
|
|
stmt: ast.AST* = body.get(bi)
|
|
if stmt is not None: new_fd.append(stmt)
|
|
return new_fd
|
|
|
|
def _ensure_generic_func_declare(self, trans: HT.Translator*, pool: memhub.MemBuddy*, spec_name: str, template_key: str, tp_names: list[str]*, type_args: list[str]*) -> mlirlite.Function*:
|
|
"""Ensure the current module declares a specialized function; None on failure."""
|
|
if trans is None or pool is None or spec_name is None: return None
|
|
mod: mlirlite.LLVMModule* = trans.Module
|
|
if mod is None: return None
|
|
existing: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, spec_name)
|
|
if existing is not None: return existing
|
|
template_fd: ast.FunctionDef* = self._find_generic_func_template(template_key)
|
|
if template_fd is None: return None
|
|
template_sha1: str = self._find_generic_func_template_sha1(template_key)
|
|
if template_sha1 is None: return None
|
|
spec_base: str = self._mangle_generic_class_name(pool, template_fd.name, type_args, trans._from_imports)
|
|
if spec_base is None: return None
|
|
spec_fd: ast.FunctionDef* = self._clone_funcdef_for_spec(pool, template_fd, spec_base, tp_names, type_args)
|
|
if spec_fd is None: return None
|
|
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
|
saved_ms: str = trans.ModuleSha1
|
|
saved_imp: str = trans._imported_modules
|
|
saved_gtpn: list[str]* = trans.GenericTypeParamNames
|
|
saved_gta: list[str]* = trans.GenericTypeArgs
|
|
trans.ModuleSha1 = template_sha1
|
|
self._merge_template_imports(trans, template_sha1)
|
|
trans.GenericTypeParamNames = tp_names
|
|
trans.GenericTypeArgs = type_args
|
|
self.Gen.TypeGenBase.set_generic_context(tp_names, type_args)
|
|
saved_cur: str = self.Gen.TypeGenResolve.get_current_file()
|
|
tpl_file: str = self._find_generic_func_template_file(template_key)
|
|
if tpl_file is not None: self.Gen.TypeGenResolve.set_current_file(tpl_file)
|
|
ret_ty: mlirlite.LLVMType* = None
|
|
if spec_fd.returns is not None:
|
|
# Value-reference markers are parameter-only: `-> &T` fails fast on a generic too.
|
|
self.Gen.TypeGenResolve.reject_ref_marker_position(spec_fd.returns, trans._imported_modules, "a function return type")
|
|
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, spec_fd.returns, trans._imported_modules, trans._from_imports, trans, 0)
|
|
if ret_ty is None: ret_ty = i32_ty
|
|
func: mlirlite.Function* = mlirlite.create_declare(pool, mod, spec_name, ret_ty)
|
|
if func is not None and spec_fd.args is not None and spec_fd.args.kind() == ast.ASTKind.Arguments:
|
|
ags: ast.Arguments* = ast.Arguments*(spec_fd.args)
|
|
if ags.args is not None:
|
|
alist: list[ast.AST*]* = ags.args
|
|
an: sizet = len(alist)
|
|
ai: sizet
|
|
for ai in range(an):
|
|
pa: ast.AST* = alist.get(ai)
|
|
if pa is None or pa.kind() != ast.ASTKind.Arg: continue
|
|
arg: ast.Arg* = ast.Arg*(pa)
|
|
if arg.arg is None: continue
|
|
param_ty: mlirlite.LLVMType* = i32_ty
|
|
if arg.annotation is not None:
|
|
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, trans._imported_modules, trans._from_imports, trans, 0)
|
|
if resolved is not None:
|
|
match resolved:
|
|
case mlirlite.LLVMType.Struct(_, _, _):
|
|
param_ty = mlirlite.Ptr(pool, resolved)
|
|
case mlirlite.LLVMType.Array(aelem_ty, _):
|
|
param_ty = mlirlite.Ptr(pool, aelem_ty)
|
|
case _:
|
|
param_ty = resolved
|
|
pname: str = pool.alloc(32)
|
|
if pname is not None: viperlib.snprintf(pname, 32, "%%%s", arg.arg)
|
|
mlirlite.add_param(pool, func, param_ty, pname)
|
|
# Register value-reference (&T / &temp T) parameter modes for the specialization.
|
|
mode_buf_g: str = pool.alloc(an + 1)
|
|
if mode_buf_g is not None:
|
|
mode_cnt_g: int = 0
|
|
has_ref_g: int = 0
|
|
for ai_g in range(an):
|
|
pa_g: ast.AST* = alist.get(ai_g)
|
|
if pa_g is None or pa_g.kind() != ast.ASTKind.Arg: continue
|
|
arg_g: ast.Arg* = ast.Arg*(pa_g)
|
|
if arg_g.arg is None: continue
|
|
mm_g: int = 0
|
|
if arg_g.annotation is not None: mm_g = self.Gen.TypeGenResolve.ref_param_mode(arg_g.annotation, trans._imported_modules)
|
|
if mm_g == 1: mode_buf_g[mode_cnt_g] = char(49)
|
|
elif mm_g == 2: mode_buf_g[mode_cnt_g] = char(50)
|
|
else: mode_buf_g[mode_cnt_g] = char(48)
|
|
if mm_g != 0: has_ref_g = 1
|
|
mode_cnt_g += 1
|
|
mode_buf_g[mode_cnt_g] = char(0)
|
|
if has_ref_g != 0: self.Gen.CallGenBase._register_auto_addr_modes(spec_name, mode_buf_g, mode_cnt_g)
|
|
if tpl_file is not None: self.Gen.TypeGenResolve.set_current_file(saved_cur)
|
|
trans.ModuleSha1 = saved_ms
|
|
trans._imported_modules = saved_imp
|
|
trans.GenericTypeParamNames = saved_gtpn
|
|
trans.GenericTypeArgs = saved_gta
|
|
self.Gen.TypeGenBase.clear_generic_context()
|
|
return func
|
|
|
|
def _specialize_generic_function(self, trans: HT.Translator*, template_key: str, type_args: list[str]*) -> str:
|
|
"""Specialize a generic function and return its mangled name."""
|
|
if trans is None or template_key is None or type_args is None: return None
|
|
self.GenericSpecDepth += 1
|
|
if self.GenericSpecDepth > GENERIC_SPEC_MAX_DEPTH: self.Gen.TypeGenErr.fatal_error(None, "generic function specialization recursion too deep (suspected generic loop)", "InternalCompilerError")
|
|
pool: memhub.MemBuddy* = trans.Pool
|
|
template_fd: ast.FunctionDef* = self._find_generic_func_template(template_key)
|
|
if template_fd is None:
|
|
self.GenericSpecDepth -= 1
|
|
return None
|
|
template_sha1: str = self._find_generic_func_template_sha1(template_key)
|
|
if template_sha1 is None:
|
|
self.GenericSpecDepth -= 1
|
|
return None
|
|
# Defensive: any unresolved type-parameter name (e.g. "T") left in the collected type_args
|
|
# is replaced with the concrete type name (nested specialization: generic fn calling generic fn).
|
|
ta_cnt_g: sizet = len(type_args)
|
|
ta_i_g: sizet
|
|
for ta_i_g in range(ta_cnt_g):
|
|
ta_raw_g: str = type_args.get(ta_i_g)
|
|
if ta_raw_g is not None:
|
|
ta_rsv_g: str = self.Gen.TypeGenBase.resolve_generic_type_name(ta_raw_g)
|
|
if ta_rsv_g is not None and string.strcmp(ta_raw_g, ta_rsv_g) != 0: type_args.set(ta_i_g, ta_rsv_g)
|
|
tp_names: list[str]* = self._find_generic_func_tp_params(pool, template_key)
|
|
if tp_names is None: tp_names = template_fd.type_params
|
|
if tp_names is None:
|
|
self.GenericSpecDepth -= 1
|
|
return None
|
|
if len(tp_names) != len(type_args):
|
|
err_tp: str = pool.alloc(256)
|
|
if err_tp is not None:
|
|
viperlib.snprintf(err_tp, 256, "generic function '%s' type argument count mismatch (expected %d, given %d)", template_fd.name, len(tp_names), len(type_args))
|
|
self.Gen.TypeGenErr.fatal_error(None, err_tp, "TypeError")
|
|
self.GenericSpecDepth -= 1
|
|
return None
|
|
# Generic functions with CExport/CExtern/State markers are unsupported (specialized names bypass bare-name export)
|
|
if template_fd.returns is not None:
|
|
if self.Gen.TypeGenErr.has_decorator_marker(template_fd.returns, "CExport", 0) != 0 or self.Gen.TypeGenErr.has_decorator_marker(template_fd.returns, "CExtern", 0) != 0 or self.Gen.TypeGenErr.has_decorator_marker(template_fd.returns, "State", 0) != 0:
|
|
self.Gen.TypeGenErr.fatal_error(template_fd, "generic functions do not yet support t.CExport/t.CExtern/t.State storage markers", "TypeError")
|
|
# Generate the specialized name (the single generator normalizes each arg so bare/prefixed forms converge)
|
|
spec_base: str = self._mangle_generic_class_name(pool, template_fd.name, type_args, trans._from_imports)
|
|
if spec_base is None:
|
|
self.GenericSpecDepth -= 1
|
|
return None
|
|
spec_name: str = self.Gen.FuncGenBase._mangle_name_with_sha1(template_sha1, spec_base)
|
|
# spec_key = template key + normalized spec base, matching spec_name one-to-one so bare/prefixed
|
|
# spellings cannot yield two cache keys; the template key (SHA1) keeps it distinct from class keys.
|
|
spec_key: str = pool.alloc(string.strlen(template_key) + string.strlen(spec_base) + 2)
|
|
if spec_key is None:
|
|
self.GenericSpecDepth -= 1
|
|
return None
|
|
spec_key[0] = '\0'
|
|
string.strcat(spec_key, template_key)
|
|
string.strcat(spec_key, "#")
|
|
string.strcat(spec_key, spec_base)
|
|
cached: str = self._find_cached_spec(spec_key)
|
|
if cached is not None:
|
|
self._ensure_generic_func_declare(trans, pool, spec_name, template_key, tp_names, type_args)
|
|
self.GenericSpecDepth -= 1
|
|
return cached
|
|
# An existing same-name define in this module (multi-pass/repeat) is reused, not re-cloned;
|
|
# a declare only continues to emit (translate_function_def reuses it and fills the body).
|
|
existing_fn: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(trans.Module, spec_name)
|
|
if existing_fn is not None and existing_fn.IsDeclared == 0:
|
|
self._cache_spec(pool, spec_key, spec_name)
|
|
self.GenericSpecDepth -= 1
|
|
return spec_name
|
|
# Clone + translate with context to emit the define
|
|
spec_fd: ast.FunctionDef* = self._clone_funcdef_for_spec(pool, template_fd, spec_base, tp_names, type_args)
|
|
if spec_fd is None:
|
|
self.GenericSpecDepth -= 1
|
|
return None
|
|
# Pre-register in the cache to prevent recursive specialization
|
|
self._cache_spec(pool, spec_key, spec_name)
|
|
saved_ms: str = trans.ModuleSha1
|
|
saved_imp: str = trans._imported_modules
|
|
saved_gtpn: list[str]* = trans.GenericTypeParamNames
|
|
saved_gta: list[str]* = trans.GenericTypeArgs
|
|
# The class-definition window must be saved/restored together with ModuleSha1 (specialization
|
|
# translation refreshes it per template module sha1 + spec name; see _specialize_generic_class).
|
|
saved_def_mod: str = self.Gen.StructGen.CurDefModuleSha1
|
|
saved_def_cls: str = self.Gen.StructGen.CurDefClassName
|
|
# Switch ModuleSha1 to the template-defining module: specialized symbol names are {templateSHA1}.f[args],
|
|
# consistent cross-module; also merge the template module's imports so its cross-module calls resolve.
|
|
trans.ModuleSha1 = template_sha1
|
|
self._merge_template_imports(trans, template_sha1)
|
|
trans.GenericTypeParamNames = tp_names
|
|
trans.GenericTypeArgs = type_args
|
|
self.Gen.TypeGenBase.set_generic_context(tp_names, type_args)
|
|
# Error reporting: the specialized body's node.lineno is a template-file line, so temporarily
|
|
# switch CurrentFile to the template file and push a trace frame (restored after translation).
|
|
saved_cur: str = self.Gen.TypeGenResolve.get_current_file()
|
|
tpl_file: str = self._find_generic_func_template_file(template_key)
|
|
if tpl_file is not None:
|
|
self.Gen.TypeGenResolve.set_current_file(tpl_file)
|
|
self.Gen.TypeGenBase.push_trace(tpl_file, template_fd.lineno)
|
|
self.Gen.FuncGenDef.translate_function_def(trans, spec_fd)
|
|
if tpl_file is not None: self.Gen.TypeGenBase.pop_trace()
|
|
self.Gen.TypeGenResolve.set_current_file(saved_cur)
|
|
trans.ModuleSha1 = saved_ms
|
|
trans._imported_modules = saved_imp
|
|
self.Gen.StructGen.CurDefModuleSha1 = saved_def_mod
|
|
self.Gen.StructGen.CurDefClassName = saved_def_cls
|
|
trans.GenericTypeParamNames = saved_gtpn
|
|
trans.GenericTypeArgs = saved_gta
|
|
self.Gen.TypeGenBase.clear_generic_context()
|
|
self.GenericSpecDepth -= 1
|
|
return spec_name
|
|
|
|
def _find_generic_template_tp_params(self, pool: memhub.MemBuddy*, class_name: str) -> list[str]*:
|
|
"""Rebuild a generic class template's type-param name list; None when absent."""
|
|
if self.GenericClassNames is None or class_name is None or self.GenericClassTpParams is None: return None
|
|
names_local: list[str]* = self.GenericClassNames
|
|
cn: sizet = len(names_local)
|
|
i: sizet
|
|
for i in range(cn):
|
|
nm: str = names_local.get(i)
|
|
if nm is not None and string.strcmp(nm, class_name) == 0:
|
|
if i >= GENERIC_TEMPLATE_MAX: return None
|
|
buf_addr: sizet = self.GenericClassTpParams[i]
|
|
if buf_addr == 0: return None
|
|
buf_tp: str = rawptr(buf_addr)
|
|
# Split by spaces into list[str]. list[str](pool, 8)'s second arg is elem_size (pointer
|
|
# 8 bytes), not capacity; passing 4 corrupts get(i) offsets and append overwrites.
|
|
tp_list: list[str]* = list[str](pool, 8)
|
|
if tp_list is None: return None
|
|
seg_start: sizet = 0
|
|
blen: sizet = string.strlen(buf_tp)
|
|
for pos in range(blen + 1):
|
|
if buf_tp[pos] == ' ' or buf_tp[pos] == '\0':
|
|
seg_len: sizet = pos - seg_start
|
|
if seg_len > 0:
|
|
seg: str = pool.alloc(seg_len + 1)
|
|
if seg is not None:
|
|
string.strncpy(seg, buf_tp + seg_start, seg_len)
|
|
seg[seg_len] = '\0'
|
|
tp_list.append(seg)
|
|
seg_start = pos + 1
|
|
return tp_list
|
|
return None
|
|
def _find_generic_template(self, class_name: str) -> ast.ClassDef*:
|
|
"""Find a generic class template; return the ClassDef node or None."""
|
|
if self.GenericClassNames is None or class_name is None: return None
|
|
# Assign the list to a local to trigger type inference (avoids field __len__ failure)
|
|
names_local: list[str]* = self.GenericClassNames
|
|
nodes_local: list[ast.ClassDef*] | t.CPtr = self.GenericClassNodes
|
|
cn: sizet = len(names_local)
|
|
i: sizet
|
|
for i in range(cn):
|
|
nm: str = names_local.get(i)
|
|
if nm is not None and string.strcmp(nm, class_name) == 0:
|
|
node: ast.ClassDef* = nodes_local.get(i)
|
|
return node
|
|
return None
|
|
def _find_generic_template_sha1(self, class_name: str) -> str:
|
|
"""Find a generic class template's source module SHA1; None when absent."""
|
|
if self.GenericClassNames is None or class_name is None: return None
|
|
# Assign the list to a local to trigger type inference
|
|
names_local: list[str]* = self.GenericClassNames
|
|
sha1s_local: list[str]* = self.GenericClassSha1s
|
|
cn: sizet = len(names_local)
|
|
i: sizet
|
|
for i in range(cn):
|
|
nm: str = names_local.get(i)
|
|
if nm is not None and string.strcmp(nm, class_name) == 0:
|
|
sha1_val: str = sha1s_local.get(i)
|
|
return sha1_val
|
|
return None
|
|
def _find_generic_template_file(self, class_name: str) -> str:
|
|
"""Find a generic class template's source file path; None when absent."""
|
|
if self.GenericClassNames is None or class_name is None: return None
|
|
names_local: list[str]* = self.GenericClassNames
|
|
files_local: list[str]* = self.GenericClassFiles
|
|
cn: sizet = len(names_local)
|
|
i: sizet
|
|
for i in range(cn):
|
|
nm: str = names_local.get(i)
|
|
if nm is not None and string.strcmp(nm, class_name) == 0:
|
|
f_val: str = files_local.get(i)
|
|
return f_val
|
|
return None
|
|
def _mangle_generic_class_name(self, pool: memhub.MemBuddy*, class_name: str, type_args: list[str]*, from_imports: str = None) -> str:
|
|
"""Build the specialized class name (e.g. list + [int] -> list[int]) in a new buffer."""
|
|
if class_name is None or type_args is None or pool is None: return class_name
|
|
n: sizet = len(type_args)
|
|
if n == 0: return class_name
|
|
# Normalize each arg (idempotent): normalize into a new list first, then use it for length and concatenation
|
|
norm_args: list[str]* = list[str](pool, 8)
|
|
if norm_args is None: return class_name
|
|
i: sizet
|
|
for i in range(n):
|
|
ta_raw: str = type_args.get(i)
|
|
if ta_raw is None: continue
|
|
ta_norm: str = self.Gen.TypeGenResolve._norm_type_arg_name(pool, ta_raw, from_imports)
|
|
if ta_norm is None: ta_norm = ta_raw
|
|
norm_args.append(ta_norm)
|
|
n = len(norm_args)
|
|
if n == 0: return class_name
|
|
# Compute total length: class_name + "[" + arg0 + "," + arg1 + ... + "]" + NUL
|
|
total_len: sizet = string.strlen(class_name) + 2 # "[" and "]"
|
|
for i in range(n):
|
|
if i > 0:
|
|
total_len += 1 # ","
|
|
ta: str = norm_args.get(i)
|
|
if ta is not None: total_len += string.strlen(ta)
|
|
total_len += 1 # NUL
|
|
# Allocate a new buffer
|
|
mangled: str = pool.alloc(total_len)
|
|
if mangled is None: return class_name
|
|
mangled[0] = '\0'
|
|
string.strcat(mangled, class_name)
|
|
string.strcat(mangled, "[")
|
|
for i in range(n):
|
|
if i > 0: string.strcat(mangled, ",")
|
|
ta: str = norm_args.get(i)
|
|
if ta is not None: string.strcat(mangled, ta)
|
|
string.strcat(mangled, "]")
|
|
return mangled
|
|
def _clone_annotation(self, pool: memhub.MemBuddy*, node: ast.AST*, tp_names: list[str]*, type_args: list[str]*) -> ast.AST*:
|
|
"""Clone a type annotation expression, replacing type parameter names with concrete types."""
|
|
if node is None: return None
|
|
k: int = node.kind()
|
|
|
|
# Name: may be the type parameter T
|
|
if k == ast.ASTKind.Name:
|
|
nm: ast.Name* = ast.Name*(node)
|
|
if nm.id is not None:
|
|
n: sizet = len(tp_names) if tp_names is not None else 0
|
|
i: sizet
|
|
for i in range(n):
|
|
tpn: str = tp_names.get(i)
|
|
if tpn is not None and string.strcmp(nm.id, tpn) == 0:
|
|
ta: str = type_args.get(i)
|
|
# A type arg may be a composite type (e.g. "AST*"); parse it with _parse_type_string
|
|
parsed_node: ast.AST* = self.Gen.TypeGenResolve._parse_type_string(pool, ta, nm.ctx)
|
|
return parsed_node
|
|
return ast.Name(pool, nm.id, nm.ctx)
|
|
return None
|
|
|
|
# Attribute: t.CPtr etc.
|
|
if k == ast.ASTKind.Attribute:
|
|
at: ast.Attribute* = ast.Attribute*(node)
|
|
new_value: ast.AST* = self._clone_annotation(pool, at.value, tp_names, type_args)
|
|
return ast.Attribute(pool, new_value, at.attr, at.ctx)
|
|
|
|
# BinOp: T* etc.
|
|
if k == ast.ASTKind.BinOp:
|
|
bop: ast.BinOp* = ast.BinOp*(node)
|
|
new_left: ast.AST* = self._clone_annotation(pool, bop.left, tp_names, type_args)
|
|
new_right: ast.AST* = self._clone_annotation(pool, bop.right, tp_names, type_args)
|
|
return ast.BinOp(pool, new_left, bop.op, new_right)
|
|
|
|
# Subscript: list[int] etc.
|
|
if k == ast.ASTKind.Subscript:
|
|
sub: ast.Subscript* = ast.Subscript*(node)
|
|
new_value2: ast.AST* = self._clone_annotation(pool, sub.value, tp_names, type_args)
|
|
new_slice: ast.AST* = self._clone_annotation(pool, sub.slice, tp_names, type_args)
|
|
return ast.Subscript(pool, new_value2, new_slice, sub.ctx)
|
|
|
|
# Tuple: (T, int) etc.
|
|
if k == ast.ASTKind.Tuple:
|
|
tup: ast.Tuple* = ast.Tuple*(node)
|
|
new_elts: list[ast.AST*]* = list[ast.AST*](pool, 8)
|
|
if tup.elts is not None:
|
|
tup_elts: list[ast.AST*]* = tup.elts
|
|
en: sizet = len(tup_elts)
|
|
ei: sizet
|
|
for ei in range(en):
|
|
el: ast.AST* = tup_elts.get(ei)
|
|
new_el: ast.AST* = self._clone_annotation(pool, el, tp_names, type_args)
|
|
if new_el is not None: new_elts.append(new_el)
|
|
return ast.Tuple(pool, new_elts, tup.ctx)
|
|
|
|
# Constant: default values etc.
|
|
if k == ast.ASTKind.Constant:
|
|
cnst: ast.Constant* = ast.Constant*(node)
|
|
return ast.Constant(pool, cnst.const_kind, cnst.int_val, cnst.float_val, cnst.str_val, cnst.lineno, cnst.col_offset)
|
|
|
|
# Other: reference directly (no copy)
|
|
return node
|
|
def _clone_arguments(self, pool: memhub.MemBuddy*, args: ast.AST*, tp_names: list[str]*, type_args: list[str]*) -> ast.AST*:
|
|
"""Clone an Arguments node, replacing type parameters inside arg annotations."""
|
|
if args is None: return None
|
|
if args.kind() != ast.ASTKind.Arguments: return args
|
|
old_args: ast.Arguments* = ast.Arguments*(args)
|
|
# Copy the args list and replace each Arg's annotation
|
|
new_arg_list: list[ast.AST*]* = list[ast.AST*](pool, 8)
|
|
if old_args.args is not None:
|
|
old_arg_list: list[ast.AST*]* = old_args.args
|
|
an: sizet = len(old_arg_list)
|
|
ai: sizet
|
|
for ai in range(an):
|
|
arg_node: ast.AST* = old_arg_list.get(ai)
|
|
if arg_node is None: continue
|
|
if arg_node.kind() == ast.ASTKind.Arg:
|
|
ag: ast.Arg* = ast.Arg*(arg_node)
|
|
new_anno: ast.AST* = self._clone_annotation(pool, ag.annotation, tp_names, type_args)
|
|
new_arg: ast.Arg* = ast.Arg(pool, ag.arg, new_anno)
|
|
new_arg_list.append(new_arg)
|
|
else: new_arg_list.append(arg_node)
|
|
return ast.Arguments(pool, new_arg_list, old_args.vararg, old_args.kwarg, old_args.defaults, old_args.kw_defaults)
|
|
def _clone_classdef_for_spec(self, pool: memhub.MemBuddy*, cd: ast.ClassDef*, spec_name: str, tp_names: list[str]*, type_args: list[str]*) -> ast.ClassDef*:
|
|
"""Clone a ClassDef node for generic specialization, substituting concrete types."""
|
|
# Create the new ClassDef (type_params=None means non-generic)
|
|
new_cd: ast.ClassDef* = ast.ClassDef(pool, spec_name, cd.bases, cd.keywords, cd.decorator_list, None)
|
|
# Copy children
|
|
if cd.children is not None:
|
|
cd_children: list[ast.AST*]* = cd.children
|
|
cn: sizet = len(cd_children)
|
|
ci: sizet
|
|
for ci in range(cn):
|
|
child: ast.AST* = cd_children.get(ci)
|
|
if child is None: continue
|
|
ck: int = child.kind()
|
|
|
|
if ck == ast.ASTKind.AnnAssign:
|
|
aa: ast.AnnAssign* = ast.AnnAssign*(child)
|
|
new_target: ast.AST* = aa.target
|
|
new_anno2: ast.AST* = self._clone_annotation(pool, aa.annotation, tp_names, type_args)
|
|
new_value3: ast.AST* = aa.value
|
|
new_aa: ast.AnnAssign* = ast.AnnAssign(pool, new_target, new_anno2, new_value3, aa.simple)
|
|
new_cd.append(new_aa)
|
|
|
|
elif ck == ast.ASTKind.FunctionDef:
|
|
fd: ast.FunctionDef* = ast.FunctionDef*(child)
|
|
new_args2: ast.AST* = self._clone_arguments(pool, fd.args, tp_names, type_args)
|
|
new_returns2: ast.AST* = self._clone_annotation(pool, fd.returns, tp_names, type_args)
|
|
new_fd: ast.FunctionDef* = ast.FunctionDef(pool, fd.name, new_args2, fd.decorator_list, new_returns2, 0)
|
|
# The method body directly references the original nodes
|
|
if fd.children is not None:
|
|
fd_children: list[ast.AST*]* = fd.children
|
|
bcn: sizet = len(fd_children)
|
|
bci: sizet
|
|
for bci in range(bcn):
|
|
body_stmt: ast.AST* = fd_children.get(bci)
|
|
if body_stmt is not None: new_fd.append(body_stmt)
|
|
new_cd.append(new_fd)
|
|
|
|
else: new_cd.append(child)
|
|
|
|
return new_cd
|
|
def _find_cached_spec(self, spec_key: str) -> str:
|
|
"""Find a cached specialization name; None when absent."""
|
|
if self.SpecKeys is None or spec_key is None: return None
|
|
i: int
|
|
for i in range(self.SpecCount):
|
|
key_addr: sizet = self.SpecKeys[i]
|
|
if key_addr != 0:
|
|
k: str = rawptr(key_addr)
|
|
if k is not None and string.strcmp(k, spec_key) == 0:
|
|
name_addr: sizet = self.SpecNames[i]
|
|
if name_addr != 0: return rawptr(name_addr)
|
|
return None
|
|
def _cache_spec(self, pool: memhub.MemBuddy*, spec_key: str, spec_name: str):
|
|
"""Cache a specialization result as a cross-file-safe stdlib deep copy."""
|
|
if spec_key is None or spec_name is None: return
|
|
if self.SpecKeys is None:
|
|
self.SpecKeys = CompilerFactory.factory.PermPool.alloc(SPEC_CACHE_MAX * 8)
|
|
self.SpecNames = CompilerFactory.factory.PermPool.alloc(SPEC_CACHE_MAX * 8)
|
|
if self.SpecKeys is None or self.SpecNames is None: return
|
|
string.memset(self.SpecKeys, 0, SPEC_CACHE_MAX * 8)
|
|
string.memset(self.SpecNames, 0, SPEC_CACHE_MAX * 8)
|
|
self.SpecCount = 0
|
|
if self.SpecCount >= SPEC_CACHE_MAX: return
|
|
# Deep-copy strings into the stdlib region
|
|
key_len: sizet = string.strlen(spec_key)
|
|
name_len: sizet = string.strlen(spec_name)
|
|
key_copy: str = CompilerFactory.factory.PermPool.alloc(key_len + 1)
|
|
name_copy: str = CompilerFactory.factory.PermPool.alloc(name_len + 1)
|
|
if key_copy is None or name_copy is None: return
|
|
string.strcpy(key_copy, spec_key)
|
|
string.strcpy(name_copy, spec_name)
|
|
self.SpecKeys[self.SpecCount] = sizet(key_copy)
|
|
self.SpecNames[self.SpecCount] = sizet(name_copy)
|
|
self.SpecCount += 1
|
|
def _merge_template_imports(self, trans: HT.Translator*, template_sha1: str) -> None:
|
|
"""Merge the template module's imports into trans._imported_modules."""
|
|
if trans is None or template_sha1 is None: return
|
|
tdeps: str = StubMergerStore.LookupIncludesDeps(template_sha1)
|
|
if tdeps is None or tdeps[0] == '\0': return
|
|
cur: str = trans._imported_modules
|
|
if cur is None or cur[0] == '\0':
|
|
trans._imported_modules = tdeps
|
|
return
|
|
cur_len: sizet = string.strlen(cur)
|
|
td_len: sizet = string.strlen(tdeps)
|
|
merged: str = trans.Pool.alloc(cur_len + td_len + 2)
|
|
if merged is None: return
|
|
string.strcpy(merged, cur)
|
|
merged[cur_len] = ' '
|
|
string.strcpy(merged + cur_len + 1, tdeps)
|
|
trans._imported_modules = merged
|
|
def _record_spec_trigger(self, pool: memhub.MemBuddy*, spec_name: str, trigger_sha1: str) -> int:
|
|
"""Record the triggering file SHA1 of a specialized generic class for later reset."""
|
|
if spec_name is None or trigger_sha1 is None: return 0
|
|
entry_rt: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(spec_name)
|
|
if entry_rt is None: return 0
|
|
if string.strcmp(entry_rt.Name, spec_name) != 0: return 0
|
|
ts_len_r: sizet = string.strlen(trigger_sha1)
|
|
ts_buf_r: str = CompilerFactory.factory.PermPool.alloc(ts_len_r + 1)
|
|
if ts_buf_r is None: return 0
|
|
string.strcpy(ts_buf_r, trigger_sha1)
|
|
entry_rt.SpecTriggerSha1 = ts_buf_r
|
|
return 1
|
|
def _tp_names_for_spec(self, pool: memhub.MemBuddy*, class_name: str, template_cd: ast.ClassDef*) -> list[str]*:
|
|
"""Return type-param names for specialization (stdlib table first, else template node)."""
|
|
tp: list[str]* = self._find_generic_template_tp_params(pool, class_name)
|
|
if tp is not None and len(tp) > 0: return tp
|
|
if template_cd is not None and template_cd.type_params is not None and len(template_cd.type_params) > 0: return template_cd.type_params
|
|
self.Gen.TypeGenErr.fatal_error(None, f"generic class template '{class_name}' is missing type param names (GenericClassTpParams slot is empty, " f"and the template node has no type_params) -- refusing to specialize with an empty generic context", "InternalCompilerError")
|
|
return None
|
|
|
|
def _specialize_generic_class(self, trans: HT.Translator*, class_name: str, type_args: list[str]*) -> str:
|
|
"""Specialize a generic class and return the specialized class name."""
|
|
if trans is None or class_name is None or type_args is None: return None
|
|
# Generic-loop guard: increment recursion depth and abort once the limit is exceeded (prevents
|
|
# F-bounded etc. from looping forever). Every return path must decrement the depth first.
|
|
self.GenericSpecDepth += 1
|
|
# Specialization trigger sha1: trans.ModuleSha1 is the triggering file on entry but is switched
|
|
# to the template sha1 below; used to reset the spec class's MethodsEmitted on invalidation.
|
|
spec_trigger_sha1: str = trans.ModuleSha1
|
|
if self.GenericSpecDepth > GENERIC_SPEC_MAX_DEPTH:
|
|
err_spec: str = trans.Pool.alloc(256)
|
|
if err_spec is not None:
|
|
viperlib.snprintf(err_spec, 256, "generic specialization recursion too deep (suspected generic loop), class: %s depth=%d", class_name, self.GenericSpecDepth)
|
|
self.Gen.TypeGenErr.fatal_error(None, err_spec, "InternalCompilerError")
|
|
self.Gen.TypeGenErr.fatal_error(None, "generic specialization recursion too deep (suspected generic loop)", "InternalCompilerError")
|
|
# Defensive: unresolved type-parameter names (e.g. "T") left in type_args are replaced with the
|
|
# concrete type name to avoid ndarray[T] double-recursion crashes; in-place edits are side-effect free.
|
|
ta_cnt_g: sizet = len(type_args)
|
|
ta_i_g: sizet
|
|
for ta_i_g in range(ta_cnt_g):
|
|
ta_raw_g: str = type_args.get(ta_i_g)
|
|
if ta_raw_g is not None:
|
|
ta_rsv_g: str = self.Gen.TypeGenBase.resolve_generic_type_name(ta_raw_g)
|
|
if ta_rsv_g is not None and string.strcmp(ta_raw_g, ta_rsv_g) != 0: type_args.set(ta_i_g, ta_rsv_g)
|
|
|
|
pool: memhub.MemBuddy* = trans.Pool
|
|
|
|
# 1. Find the generic template
|
|
template_cd: ast.ClassDef* = self._find_generic_template(class_name)
|
|
if template_cd is None:
|
|
self.GenericSpecDepth -= 1
|
|
return None
|
|
|
|
# 2. Generate the specialized name (the single generator normalizes args: bare/prefixed converge)
|
|
_from_imports: str = None
|
|
if trans is not None: _from_imports = trans._from_imports
|
|
spec_name: str = self._mangle_generic_class_name(pool, class_name, type_args, _from_imports)
|
|
if spec_name is None:
|
|
self.GenericSpecDepth -= 1
|
|
return None
|
|
|
|
# Phase B back-fill of the specialized class's method bodies (unified entry, before cache/existing
|
|
# checks): Phase 1a only registered the struct, so Phase B re-entering emits the defines.
|
|
if trans._declare_only == 0:
|
|
pre_existing: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(spec_name)
|
|
if pre_existing is not None and pre_existing.MethodsEmitted == 0:
|
|
# Re-entrancy guard: set MethodsEmitted=1 first so method/return-type annotations cannot
|
|
# re-trigger specialization (infinite recursion -> OOM); _translate_oop_methods must get reemit=1.
|
|
pre_existing.MethodsEmitted = 1
|
|
# Take type_params from the stdlib deep-copy table (the template node's pool list may be invalid across phases)
|
|
tp_names_pre: list[str]* = self._tp_names_for_spec(pool, class_name, template_cd)
|
|
spec_cd_pre: ast.ClassDef* = self._clone_classdef_for_spec(pool, template_cd, spec_name, tp_names_pre, type_args)
|
|
if spec_cd_pre is not None:
|
|
trans.GenericTypeParamNames = tp_names_pre
|
|
trans.GenericTypeArgs = type_args
|
|
self.Gen.TypeGenBase.set_generic_context(tp_names_pre, type_args)
|
|
saved_ms_pre: str = trans.ModuleSha1
|
|
saved_imp_pre: str = trans._imported_modules
|
|
template_sha1_pre: str = self._find_generic_template_sha1(class_name)
|
|
if template_sha1_pre is not None:
|
|
trans.ModuleSha1 = template_sha1_pre
|
|
self._merge_template_imports(trans, template_sha1_pre)
|
|
# Error reporting: the back-filled body is a cloned template, so switch CurrentFile to
|
|
# the template file and push a trace frame (restored after translation).
|
|
saved_cur_pre: str = self.Gen.TypeGenResolve.get_current_file()
|
|
tpl_file_pre: str = self._find_generic_template_file(class_name)
|
|
if tpl_file_pre is not None:
|
|
self.Gen.TypeGenResolve.set_current_file(tpl_file_pre)
|
|
self.Gen.TypeGenBase.push_trace(tpl_file_pre, template_cd.lineno)
|
|
self.Gen.ClassGenVTable._translate_oop_methods(trans, spec_cd_pre, pre_existing.Ty, spec_name, 0, 1)
|
|
if tpl_file_pre is not None: self.Gen.TypeGenBase.pop_trace()
|
|
self.Gen.TypeGenResolve.set_current_file(saved_cur_pre)
|
|
trans.ModuleSha1 = saved_ms_pre
|
|
trans._imported_modules = saved_imp_pre
|
|
trans.GenericTypeParamNames = None
|
|
trans.GenericTypeArgs = None
|
|
self.Gen.TypeGenBase.clear_generic_context()
|
|
|
|
# 3. Check the cache (spec_key includes all type args, avoiding multi-param cache collisions)
|
|
# Allocate a new buffer to avoid in-place strcat overflow
|
|
sk_n: sizet = len(type_args)
|
|
sk_total: sizet = string.strlen(class_name) + 2 # "<" and ">"
|
|
sk_i: sizet
|
|
for sk_i in range(sk_n):
|
|
if sk_i > 0:
|
|
sk_total += 1 # ","
|
|
sk_ta: str = type_args.get(sk_i)
|
|
if sk_ta is not None: sk_total += string.strlen(sk_ta)
|
|
sk_total += 1 # NUL
|
|
spec_key: str = pool.alloc(sk_total)
|
|
if spec_key is None:
|
|
self.GenericSpecDepth -= 1
|
|
return None
|
|
spec_key[0] = '\0'
|
|
string.strcat(spec_key, class_name)
|
|
string.strcat(spec_key, "<")
|
|
for sk_i in range(sk_n):
|
|
if sk_i > 0: string.strcat(spec_key, ",")
|
|
sk_ta: str = type_args.get(sk_i)
|
|
if sk_ta is not None: string.strcat(spec_key, sk_ta)
|
|
string.strcat(spec_key, ">")
|
|
cached: str = self._find_cached_spec(spec_key)
|
|
if cached is not None:
|
|
self._record_spec_trigger(pool, spec_name, spec_trigger_sha1)
|
|
self.GenericSpecDepth -= 1
|
|
return cached
|
|
|
|
# 4. Check whether it is already registered (Phase B duplicate specialization)
|
|
existing: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(spec_name)
|
|
if existing is not None:
|
|
self._cache_spec(pool, spec_key, spec_name)
|
|
self._record_spec_trigger(pool, spec_name, spec_trigger_sha1)
|
|
self.GenericSpecDepth -= 1
|
|
return spec_name
|
|
|
|
# Pre-register in the cache (prevents recursion)
|
|
self._cache_spec(pool, spec_key, spec_name)
|
|
|
|
# 5. Clone the ClassDef node
|
|
# Take type_params from the stdlib deep-copy table (the template node's pool list may be invalid across phases)
|
|
tp_names: list[str]* = self._tp_names_for_spec(pool, class_name, template_cd)
|
|
spec_cd: ast.ClassDef* = self._clone_classdef_for_spec(pool, template_cd, spec_name, tp_names, type_args)
|
|
if spec_cd is None:
|
|
self.GenericSpecDepth -= 1
|
|
return None
|
|
|
|
# 6. Set the type_map context (used for T replacement in method bodies)
|
|
trans.GenericTypeParamNames = tp_names
|
|
trans.GenericTypeArgs = type_args
|
|
self.Gen.TypeGenBase.set_generic_context(tp_names, type_args)
|
|
|
|
# 6.5 Context self-check (fail-fast, right after setting): the type-param name list and type args
|
|
# must correspond one-to-one, each resolving to a concrete type; otherwise T fails with NameError.
|
|
tp_n_c: sizet = len(tp_names) if tp_names is not None else 0
|
|
ta_n_c: sizet = len(type_args) if type_args is not None else 0
|
|
if tp_n_c == 0 or ta_n_c != tp_n_c:
|
|
self.Gen.TypeGenErr.fatal_error(None, f"generic class '{class_name}' type argument count ({ta_n_c}) does not match the template type param count ({tp_n_c}), " f"cannot build the specialization context (spec key {spec_key})", "InternalCompilerError")
|
|
probe_tp: str = tp_names.get(0)
|
|
if probe_tp is not None:
|
|
if string.strcmp(self.Gen.TypeGenBase.resolve_generic_type_name(probe_tp), probe_tp) == 0:
|
|
self.Gen.TypeGenErr.fatal_error(None, f"cannot install the specialization context of generic class '{class_name}': type param '{probe_tp}' does not resolve to a concrete type" f"(spec key {spec_key}) -- type args missing/empty", "InternalCompilerError")
|
|
|
|
# 7. Translate the specialized class: switch trans.ModuleSha1 to the template-defining module so
|
|
# struct type names and method symbols use its SHA1 prefix; also merge its imports.
|
|
saved_module_sha1: str = trans.ModuleSha1
|
|
saved_imported: str = trans._imported_modules
|
|
# Save/restore the class-definition window (CurDefModuleSha1/CurDefClassName) alongside ModuleSha1:
|
|
# translate_class_def overwrites it, and restoring only ModuleSha1 splits one spec into two names.
|
|
saved_def_mod: str = self.Gen.StructGen.CurDefModuleSha1
|
|
saved_def_cls: str = self.Gen.StructGen.CurDefClassName
|
|
template_sha1: str = self._find_generic_template_sha1(class_name)
|
|
if template_sha1 is not None:
|
|
trans.ModuleSha1 = template_sha1
|
|
self._merge_template_imports(trans, template_sha1)
|
|
# Error reporting: spec_cd is cloned from the template, so method node.lineno lines belong to the
|
|
# template file; switch CurrentFile to it and push a trace frame (restored after translation).
|
|
saved_cur_full: str = self.Gen.TypeGenResolve.get_current_file()
|
|
tpl_file_full: str = self._find_generic_template_file(class_name)
|
|
if tpl_file_full is not None:
|
|
self.Gen.TypeGenResolve.set_current_file(tpl_file_full)
|
|
self.Gen.TypeGenBase.push_trace(tpl_file_full, template_cd.lineno)
|
|
self.Gen.ClassGenDef.translate_class_def(trans, spec_cd)
|
|
if tpl_file_full is not None: self.Gen.TypeGenBase.pop_trace()
|
|
self.Gen.TypeGenResolve.set_current_file(saved_cur_full)
|
|
# Restore the original ModuleSha1 / imports / class-definition window (the window does not shift; see above)
|
|
trans.ModuleSha1 = saved_module_sha1
|
|
trans._imported_modules = saved_imported
|
|
self.Gen.StructGen.CurDefModuleSha1 = saved_def_mod
|
|
self.Gen.StructGen.CurDefClassName = saved_def_cls
|
|
|
|
# 8. Clear the type_map context
|
|
trans.GenericTypeParamNames = None
|
|
trans.GenericTypeArgs = None
|
|
self.Gen.TypeGenBase.clear_generic_context()
|
|
|
|
# Specialization field-name integrity check (fail-fast: a missing field name means a corrupted
|
|
# field table, and silently continuing would make later GEPs use wrong indices).
|
|
if string.strcmp(class_name, "GSListNode") == 0:
|
|
result_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(spec_name)
|
|
if result_entry is not None:
|
|
for ri in range(result_entry.FieldCount):
|
|
rfe: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(result_entry, ri)
|
|
if rfe is not None:
|
|
rfn: str = self.Gen.StructGen.get_field_name_ptr(rfe)
|
|
if rfn is None:
|
|
VLogger.error("SPEC field name missing (field table corrupted)", "SPEC")
|
|
self.Gen.TypeGenErr.fatal_error(None, "SPEC field name missing (field table corrupted)", "TypeError")
|
|
|
|
self._record_spec_trigger(pool, spec_name, spec_trigger_sha1)
|
|
self.GenericSpecDepth -= 1
|
|
return spec_name |