Files
ViperC/App/lib/core/Gen/Func/FuncGenBase.vp
T

293 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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