293 lines
16 KiB
Plaintext
293 lines
16 KiB
Plaintext
from stdint import *
|
||
import ast
|
||
import mlirlite
|
||
import memhub
|
||
import string
|
||
import viperlib
|
||
import stdlib
|
||
import lib.core.VLogger as VLogger
|
||
import lib.core.Gen.Translator as HT
|
||
import lib.core.Gen.GenApi as GenApi
|
||
import lib.core.Gen.Call.CallGenBase as CallGenBase
|
||
|
||
# FuncGenBase - split from FuncGenMixin (stage D): attrs / naming / mangle / create_function helpers.
|
||
novirtual class FuncGenBase(CallGenBase.CallGenBase):
|
||
"""Shared helpers for function attributes, naming, mangling and creation."""
|
||
|
||
# extract_func_attrs - extract c.Attribute attrs from decorator_list.
|
||
# Supports @c.Attribute(t.attr.xxx()/t.attr.xxx/t.attr.llvm.xxx); returns LLVM IR attr string or None.
|
||
def extract_func_attrs(self, pool: memhub.MemBuddy*, decorator_list: list[ast.AST*]*, imported_modules: str) -> str:
|
||
"""Extract c.Attribute attrs from decorators; return the LLVM IR attr string."""
|
||
if decorator_list is None: return None
|
||
dn: sizet = len(decorator_list)
|
||
if dn == 0: return None
|
||
|
||
attrs_buf: str = pool.alloc(256)
|
||
if attrs_buf is None: return None
|
||
attrs_buf[0] = '\0'
|
||
found_any: int = 0
|
||
|
||
for di in range(int(dn)):
|
||
deco: ast.AST* = decorator_list.get(di)
|
||
if deco is None: continue
|
||
dk: int = deco.kind()
|
||
# Direct form `@t.attr.<name>` (same as ClassGenBase._has_decorator): `inline def f`
|
||
# synthesizes it and does NOT require `import c`; unsupported names are ignored.
|
||
if dk == ast.ASTKind.Attribute:
|
||
dan: str = self._get_attr_name_from_node(deco)
|
||
if dan is not None and string.strcmp(dan, "section") != 0:
|
||
if self._append_llvm_attr(attrs_buf, dan) != 0: found_any = 1
|
||
continue
|
||
if dk != ast.ASTKind.Call: continue
|
||
call_node: ast.Call* = ast.Call*(deco)
|
||
if call_node.func is None: continue
|
||
cfk: int = call_node.func.kind()
|
||
if cfk != ast.ASTKind.Attribute: continue
|
||
|
||
# Detect c.Attribute
|
||
func_attr: ast.Attribute* = ast.Attribute*(call_node.func)
|
||
if func_attr.attr is None: continue
|
||
if string.strcmp(func_attr.attr, "Attribute") != 0: continue
|
||
if func_attr.value is None: continue
|
||
fvk: int = func_attr.value.kind()
|
||
if fvk != ast.ASTKind.Name: continue
|
||
c_name: ast.Name* = ast.Name*(func_attr.value)
|
||
if c_name.id is None: continue
|
||
if string.strcmp(c_name.id, "c") != 0: continue
|
||
# 不要求 `import c`:`@__attribute__(...)` 会被 ast/parser.vp(_DesugarMagicAttribute)
|
||
# 脱糖成 `@c.Attribute(...)`,这里的 `c.` 前缀是编译器自己产生的(用户写的是
|
||
# `@__attribute__`,并非 `@c.Attribute`)。若在此要求 `import c`,用户明明没引入 c
|
||
# 就会把整条装饰器静默丢弃(`stackrealign`/`always_inline` 等函数属性全失效)。
|
||
# extract_func_section 本就没有这个守卫,二者保持一致。
|
||
|
||
# Iterate args and extract attrs
|
||
if call_node.args is None: continue
|
||
args_list: list[ast.AST*]* = call_node.args
|
||
an: sizet = len(args_list)
|
||
for ai in range(int(an)):
|
||
arg_node: ast.AST* = args_list.get(ai)
|
||
if arg_node is None: continue
|
||
# Extract attr name (t.attr.xxx / t.attr.xxx() / t.attr.llvm.xxx)
|
||
attr_name: str = self._get_attr_name_from_node(arg_node)
|
||
if attr_name is None: continue
|
||
# section string attr cannot be inlined on the signature line (llc: expected
|
||
# string constant); it must go through attr group #N, handled by FuncGenDef.
|
||
if string.strcmp(attr_name, "section") == 0: continue
|
||
# Map to LLVM attr name and append
|
||
ar: int = self._append_llvm_attr(attrs_buf, attr_name)
|
||
if ar != 0: found_any = 1
|
||
|
||
if found_any == 0: return None
|
||
return attrs_buf
|
||
def extract_func_section(self, pool: memhub.MemBuddy*, decorator_list: list[ast.AST*]*, imported_modules: str) -> str:
|
||
"""Extract @c.Attribute(t.attr.section(...)) as the attr-group text."""
|
||
if pool is None or decorator_list is None: return None
|
||
dn: sizet = len(decorator_list)
|
||
if dn == 0: return None
|
||
for di in range(int(dn)):
|
||
deco: ast.AST* = decorator_list.get(di)
|
||
if deco is None or deco.kind() != ast.ASTKind.Call: continue
|
||
call_node: ast.Call* = ast.Call*(deco)
|
||
if call_node.func is None or call_node.func.kind() != ast.ASTKind.Attribute: continue
|
||
func_attr: ast.Attribute* = ast.Attribute*(call_node.func)
|
||
if func_attr.attr is None or string.strcmp(func_attr.attr, "Attribute") != 0: continue
|
||
if call_node.args is None: continue
|
||
args_list: list[ast.AST*]* = call_node.args
|
||
an: sizet = len(args_list)
|
||
for ai in range(int(an)):
|
||
arg_node: ast.AST* = args_list.get(ai)
|
||
if arg_node is None: continue
|
||
attr_name: str = self._get_attr_name_from_node(arg_node)
|
||
if attr_name is None or string.strcmp(attr_name, "section") != 0: continue
|
||
sec_val: str = self._get_attr_call_arg_str(arg_node)
|
||
if sec_val is None: continue
|
||
sec_buf: str = pool.alloc(128)
|
||
if sec_buf is None: return None
|
||
viperlib.snprintf(sec_buf, 128, "\"section\"=\"%s\"", sec_val)
|
||
return sec_buf
|
||
return None
|
||
def _get_attr_name_from_node(self, node: ast.AST*) -> str:
|
||
"""Extract the attribute name from a Call or Attribute AST node."""
|
||
if node is None: return None
|
||
k: int = node.kind()
|
||
|
||
# Call node: t.attr.always_inline()
|
||
if k == ast.ASTKind.Call:
|
||
call: ast.Call* = ast.Call*(node)
|
||
if call.func is None or call.func.kind() != ast.ASTKind.Attribute: return None
|
||
at: ast.Attribute* = ast.Attribute*(call.func)
|
||
return at.attr
|
||
|
||
# Attribute node: t.attr.packed or t.attr.llvm.nounwind
|
||
if k == ast.ASTKind.Attribute:
|
||
at2: ast.Attribute* = ast.Attribute*(node)
|
||
return at2.attr
|
||
|
||
return None
|
||
def _get_attr_call_arg_str(self, node: ast.AST*) -> str:
|
||
"""Return the first positional string constant of a t.attr.xxx(...) call."""
|
||
if node is None or node.kind() != ast.ASTKind.Call: return None
|
||
cl: ast.Call* = ast.Call*(node)
|
||
if cl.args is None or len(cl.args) == 0: return None
|
||
a0: ast.AST* = cl.args.get(0)
|
||
if a0 is None or a0.kind() != ast.ASTKind.Constant: return None
|
||
cn: ast.Constant* = ast.Constant*(a0)
|
||
if cn.const_kind != ast.CONST_STR: return None
|
||
return cn.str_val
|
||
def _append_llvm_attr(self, attrs_buf: str, attr_name: str) -> int:
|
||
"""Map an attribute name to its LLVM name and append it; 1=appended, 0=unsupported."""
|
||
if attrs_buf is None or attr_name is None: return 0
|
||
|
||
# First determine the mapped LLVM attr name
|
||
llvm_name: str = None
|
||
if string.strcmp(attr_name, "always_inline") == 0: llvm_name = "alwaysinline"
|
||
elif string.strcmp(attr_name, "noinline") == 0: llvm_name = "noinline"
|
||
elif string.strcmp(attr_name, "noreturn") == 0: llvm_name = "noreturn"
|
||
elif string.strcmp(attr_name, "pure") == 0: llvm_name = "readonly"
|
||
elif string.strcmp(attr_name, "const") == 0: llvm_name = "readnone"
|
||
elif string.strcmp(attr_name, "nounwind") == 0: llvm_name = "nounwind"
|
||
elif string.strcmp(attr_name, "noredzone") == 0: llvm_name = "noredzone"
|
||
elif string.strcmp(attr_name, "stackrealign") == 0: llvm_name = '"stackrealign"'
|
||
elif string.strcmp(attr_name, "willreturn") == 0: llvm_name = "willreturn"
|
||
elif string.strcmp(attr_name, "mustprogress") == 0: llvm_name = "mustprogress"
|
||
else:
|
||
# packed/aligned/section/visibility/weak are unsupported as inline function attrs
|
||
return 0
|
||
|
||
# Supported attr: append a space separator if the buffer is non-empty
|
||
if attrs_buf[0] != '\0': self._append_str(attrs_buf, " ", 256)
|
||
self._append_str(attrs_buf, llvm_name, 256)
|
||
return 1
|
||
def _append_str(self, buf: str, s: str, max_len: sizet = 256) -> None:
|
||
"""Append a string to buf with a bounded write to avoid overflow."""
|
||
if buf is None or s is None: return
|
||
# Defensive: VPC default-arg registration is buggy (missing max_len falls to 0);
|
||
# fall back to 256 so the bounded-write check does not drop all attr text.
|
||
if max_len == 0: max_len = 256
|
||
cur_len: sizet = string.strlen(buf)
|
||
s_len: sizet = string.strlen(s)
|
||
if cur_len + s_len + 1 > max_len: return # No remaining capacity: drop this append to avoid a bounds write (uncertain)
|
||
string.strcpy(buf + cur_len, s)
|
||
# _mangle_name - prepend a SHA1 prefix to a name.
|
||
# Returns "sha1.name"; if ModuleSha1 is None or the name already has a SHA1 prefix, returns as-is.
|
||
def _mangle_name(self, trans: HT.Translator*, name: str) -> str:
|
||
"""Prepend the SHA1 prefix to a name (no main/export check)."""
|
||
if trans is None or name is None: return name
|
||
if trans.ModuleSha1 is None: return name
|
||
name_len: sizet = string.strlen(name)
|
||
# Skip if it already has a SHA1 prefix ("{16hex}."). [C10] Use the single source of
|
||
# truth GenApi.sha1_prefix_len (aligned with the ">= 17" check).
|
||
if GenApi.sha1_prefix_len(name) != 0: return name
|
||
sha1: str = trans.ModuleSha1
|
||
sha1_len: sizet = string.strlen(sha1)
|
||
mangled: str = stdlib.malloc(sha1_len + name_len + 2)
|
||
if mangled is None: return name
|
||
string.strcpy(mangled, sha1)
|
||
mangled[sha1_len] = '.'
|
||
string.strcpy(mangled + sha1_len + 1, name)
|
||
return mangled
|
||
# _mangle_name_with_sha1 - prepend the given SHA1 to a name.
|
||
# Used for cross-module vtable inheritance: inherited methods mangle with the parent module's SHA1.
|
||
def _mangle_name_with_sha1(self, sha1: str, name: str) -> str:
|
||
"""Prepend the given SHA1 to a name (no main/export check)."""
|
||
if sha1 is None or name is None: return name
|
||
name_len: sizet = string.strlen(name)
|
||
# Skip if it already has a SHA1 prefix ("{16hex}."). [C10] Use the single source of
|
||
# truth GenApi.sha1_prefix_len (same check as _mangle_name).
|
||
if GenApi.sha1_prefix_len(name) != 0: return name
|
||
sha1_len: sizet = string.strlen(sha1)
|
||
mangled: str = stdlib.malloc(sha1_len + name_len + 2)
|
||
if mangled is None: return name
|
||
string.strcpy(mangled, sha1)
|
||
mangled[sha1_len] = '.'
|
||
string.strcpy(mangled + sha1_len + 1, name)
|
||
return mangled
|
||
# _mangle_func_name - prepend a SHA1 prefix to a function name (with main/export checks).
|
||
# No prefix for main, when has_export != 0 (t.CExport), when already prefixed, or when ModuleSha1 is None.
|
||
def _mangle_func_name(self, trans: HT.Translator*, name: str, has_export: int) -> str:
|
||
"""Prepend the SHA1 prefix to a function name (checks main/export)."""
|
||
if trans is None or name is None: return name
|
||
if has_export != 0: return name
|
||
if string.strcmp(name, "main") == 0: return name
|
||
return self._mangle_name(trans, name)
|
||
# _body_has_raise - whether the body contains raise (non-StopIteration).
|
||
# Matches TPC HandlesFunctions.py _body_contains_raise; v1 only scans top-level raise, not nested ones.
|
||
def _body_has_raise(self, fd: ast.FunctionDef*) -> int:
|
||
"""Whether the body has a top-level non-StopIteration raise; 1=yes, 0=no."""
|
||
if fd is None or fd.children is None: return 0
|
||
body_f: list[ast.AST*]* = fd.children
|
||
bn_f: sizet = len(body_f)
|
||
for bi_f in range(int(bn_f)):
|
||
stmt_f: ast.AST* = body_f.get(bi_f)
|
||
if stmt_f is None: continue
|
||
if stmt_f.kind() != ast.ASTKind.Raise: continue
|
||
rn_f: ast.Raise* = ast.Raise*(stmt_f)
|
||
if rn_f is None or rn_f.exc is None: continue
|
||
ek_f: int = rn_f.exc.kind()
|
||
is_stop_f: int = 0
|
||
if ek_f == ast.ASTKind.Name:
|
||
enm_f: ast.Name* = ast.Name*(rn_f.exc)
|
||
if enm_f.id is not None and string.strcmp(enm_f.id, "StopIteration") == 0: is_stop_f = 1
|
||
elif ek_f == ast.ASTKind.Call:
|
||
ecl_f: ast.Call* = ast.Call*(rn_f.exc)
|
||
if ecl_f.func is not None and ecl_f.func.kind() == ast.ASTKind.Name:
|
||
ecn_f: ast.Name* = ast.Name*(ecl_f.func)
|
||
if ecn_f.id is not None and string.strcmp(ecn_f.id, "StopIteration") == 0: is_stop_f = 1
|
||
if is_stop_f == 0: return 1
|
||
return 0
|
||
# RejectCReturnDecorator - @c.CReturn was removed; fail at compile time if seen.
|
||
# Multi-return goes only through `-> tuple[...]`; any CReturn form is fail-fast, shared with FuncGenDef.
|
||
def RejectCReturnDecorator(self, node: ast.AST*, decorators: list[ast.AST*]*) -> int:
|
||
"""Fail if @c.CReturn appears in the decorators; 0=none found."""
|
||
if decorators is None: return 0
|
||
rcr_dn: sizet = len(decorators)
|
||
for rcr_i in range(int(rcr_dn)):
|
||
rcr_deco: ast.AST* = decorators.get(rcr_i)
|
||
if rcr_deco is None: continue
|
||
# Handle both @c.CReturn(...) (Call(func=Attribute)) and bare @c.CReturn (Attribute)
|
||
rcr_attr: ast.Attribute* = None
|
||
if rcr_deco.kind() == ast.ASTKind.Call:
|
||
rcr_call: ast.Call* = ast.Call*(rcr_deco)
|
||
if rcr_call.func is not None and rcr_call.func.kind() == ast.ASTKind.Attribute: rcr_attr = ast.Attribute*(rcr_call.func)
|
||
elif rcr_deco.kind() == ast.ASTKind.Attribute: rcr_attr = ast.Attribute*(rcr_deco)
|
||
if rcr_attr is None or rcr_attr.attr is None: continue
|
||
if string.strcmp(rcr_attr.attr, "CReturn") == 0:
|
||
self.Gen.TypeGenErr.fatal_error(node, "`@c.CReturn` removed: use a return annotation `-> tuple[ty1, ty2, ...]` instead (multiple return values are packed into a struct return)", "TypeError")
|
||
return 1
|
||
return 0
|
||
# Create LLVM function (simple version, declaration only)
|
||
def create_function(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, name: str, args_node: ast.AST*, ret_ty: mlirlite.LLVMType*) -> mlirlite.Function*:
|
||
"""Create an LLVM function and add its parameters."""
|
||
if name is None or mod is None: return None
|
||
|
||
func: mlirlite.Function* = mlirlite.create_function(pool, mod, name, ret_ty)
|
||
if func is None: return None
|
||
|
||
if args_node is not None:
|
||
ags: ast.Arguments* = ast.Arguments*(args_node)
|
||
if ags.args is not None:
|
||
alist: list[ast.AST*]* = ags.args
|
||
an: sizet = len(alist)
|
||
i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
for ai in range(int(an)):
|
||
arg: ast.Arg* = ast.Arg*(alist.get(ai))
|
||
if arg is not None and arg.arg is not None:
|
||
# t.CVoid means no args: skip
|
||
if arg.annotation is not None:
|
||
if arg.annotation.kind() == ast.ASTKind.Attribute:
|
||
at_cf: ast.Attribute* = ast.Attribute*(arg.annotation)
|
||
if at_cf.attr is not None and string.strcmp(at_cf.attr, "CVoid") == 0: 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, None, None, None, 0)
|
||
if resolved is None:
|
||
# [S4 fail-fast] Annotation present but unresolvable: no silent i32 fallback.
|
||
# This fn lacks trans/imports context, so failure surfaces upstream.
|
||
self.Gen.TypeGenErr.fatal_error(arg, f"function '{name}' param '{arg.arg}' type annotation failed to resolve", "NameError")
|
||
return None
|
||
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)
|
||
|
||
return func |