315 lines
19 KiB
Plaintext
315 lines
19 KiB
Plaintext
from stdint import *
|
||
import ast
|
||
import mlirlite
|
||
import memhub
|
||
import string
|
||
import stdio
|
||
import stdlib
|
||
import viperlib
|
||
import lib.core.Gen.Translator as HT
|
||
|
||
import lib.core.Gen.GenTypes as GenTypes
|
||
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
|
||
|
||
|
||
# ExprGenConst - split from ExprGenMixin (stage D): constants / global strings / list literals
|
||
novirtual class ExprGenConst(ExprGenBase.ExprGenBase):
|
||
"""Translation of constants, global strings and list literals."""
|
||
|
||
# Create global string constant
|
||
def create_global_string(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, str_val: str, trans: HT.Translator*) -> mlirlite.Value*:
|
||
"""Create a global string constant and return its i8* bitcast."""
|
||
escaped: str = self.escape_llvm_string(pool, str_val)
|
||
if escaped is None: return None
|
||
|
||
slen: sizet = string.strlen(str_val)
|
||
count: int = int(slen + 1)
|
||
|
||
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
arr_ty: mlirlite.LLVMType* = mlirlite.Array(pool, i8_ty, count)
|
||
|
||
# Use the module-level counter (trans._label_counter) to avoid cross-function naming conflicts
|
||
str_idx: int = trans._label_counter
|
||
trans._label_counter += 1
|
||
|
||
# Prefix string names with the SHA1 (same rule as function export) to avoid cross-module collisions
|
||
gv_name: str = pool.alloc(48)
|
||
if gv_name is None: return None
|
||
if trans.ModuleSha1 is not None: viperlib.snprintf(gv_name, 48, ".str.%s.%d", trans.ModuleSha1, str_idx)
|
||
else: viperlib.snprintf(gv_name, 48, ".str.%d", str_idx)
|
||
|
||
gv: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, gv_name, arr_ty)
|
||
if gv is None: return None
|
||
mlirlite.module_add_global(mod, gv)
|
||
|
||
# Drop private linkage, same as funcs: declare in stub, define in text
|
||
mlirlite.global_set_unnamed_addr(gv, 1)
|
||
mlirlite.global_set_constant(gv, 1)
|
||
# LLVM 22+ forbids initializers on external-linkage globals, so set internal
|
||
gv.Linkage = "internal"
|
||
gv.Initializer = escaped
|
||
|
||
arr_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, arr_ty)
|
||
gv_ref_name: str = pool.alloc(64)
|
||
if gv_ref_name is None: return None
|
||
viperlib.snprintf(gv_ref_name, 64, "@%s", gv.Name)
|
||
gv_ref: mlirlite.Value* = mlirlite.SSAValue(pool, arr_ptr_ty, gv_ref_name)
|
||
|
||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
|
||
|
||
bc: mlirlite.Value* = mlirlite.build_bitcast(builder, gv_ref, i8_ptr_ty)
|
||
return bc
|
||
# _explicit_width_int_const - 显式宽度字面量(`123_int32` / `0xFFFFFFFF_uint32`):
|
||
# 按声明宽度出常量;值必须落在该宽度的可表示范围(有符号 [-2^(w-1), 2^(w-1)-1];无符号 [0, 2^w-1]),
|
||
# 越界编译期 fail-fast(不允许隐式截断)。w==64 时 int64 全域都合法。
|
||
def _explicit_width_int_const(self, pool: memhub.MemBuddy*, node: ast.AST*, w: int, is_unsigned: int, iv: int64) -> mlirlite.Value*:
|
||
"""Explicit-width integer literal: strict range check, then a constant of exactly w bits."""
|
||
ok: int = 1
|
||
if is_unsigned != 0:
|
||
if w < 64 and iv < 0: ok = 0
|
||
elif w <= 62 and iv > (int64(1) << w) - 1: ok = 0
|
||
else:
|
||
if w < 64:
|
||
lo: int64 = -(int64(1) << (w - 1))
|
||
hi: int64 = (int64(1) << (w - 1)) - 1
|
||
if iv < lo or iv > hi: ok = 0
|
||
if ok == 0:
|
||
wname: str = "int"
|
||
if is_unsigned != 0: wname = "uint"
|
||
self.Gen.TypeGenErr.fatal_error(node, f"integer literal {iv} does not fit the explicit width {wname}{w} (it would truncate); use a wider suffix or an explicit cast", "OverflowError")
|
||
return mlirlite.const_int(pool, w, iv)
|
||
# Translate constant expressions
|
||
def translate_constant(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None, target_ty: mlirlite.LLVMType* = None) -> mlirlite.Value*:
|
||
"""Translate a constant node (int/str/bool)."""
|
||
cn: ast.Constant* = ast.Constant*(node)
|
||
if cn is None: return None
|
||
ck: int = cn.const_kind
|
||
if cn.const_kind == ast.CONST_INT:
|
||
# Bit width: (1) if the target context is >32-bit int, use i64 directly - required
|
||
# since atoll wraps 0xFFFF... to -1 as signed int64; (2) else i64 if out of i32 range, otherwise i32.
|
||
iv: int64 = cn.int_val
|
||
# 显式宽度后缀(_int<N>/_uint<N>/_i<N>/_u<N>):按声明宽度出常量并严格校验不截断
|
||
if cn.int_width > 0: return self._explicit_width_int_const(pool, node, cn.int_width, cn.int_unsigned, iv)
|
||
if target_ty is not None and self.is_ptr_type(target_ty) == 0 and self.get_llvm_type_bits(target_ty) > 32: return mlirlite.const_int64(pool, iv)
|
||
# 位宽按值推导:i32 放不下、但 u32 放得下的字面量归为 32 位(u32)常量,而不是一律落 64 位
|
||
# (否则 `crc ^ 0xFFFFFFFF` 的异或会变成 64 位,赋值回 32 位 uint 时报“隐式窄化禁止”)。
|
||
# 文本用有符号等价(LLVM/MLIR 惯例,避免越界字面量);IntVal 保留无符号真值,
|
||
# 使窄化检查按真实数值判定,不静默截断。
|
||
if iv > 2147483647 and iv <= 4294967295:
|
||
ty32: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
nm32: str = pool.alloc(32)
|
||
if nm32 is None: return mlirlite.const_int32(pool, iv)
|
||
viperlib.snprintf(nm32, 32, "%lld", iv - 4294967296)
|
||
return mlirlite.ConstInt(pool, ty32, iv, nm32)
|
||
if iv > 2147483647 or iv < -2147483648: return mlirlite.const_int64(pool, iv)
|
||
return mlirlite.const_int32(pool, iv)
|
||
elif cn.const_kind == ast.CONST_FLOAT:
|
||
# 目标类型是 32 位浮点(float(1.5) / x: float = 1.5 / f32 形参)→ 直接出 f32 常量。
|
||
# 旧实现无视 target_ty 一律先出 double 常量、再由 coerce_to_type fptrunc,
|
||
# 现场:float(2.0) / float(1.0) 生成 f64 常量 + fptrunc,与双精度常量混算时又多出 fpext。
|
||
# 其余情况(无目标类型 / double / 非浮点)保持 double 常量,由 coerce_to_type 负责转换。
|
||
if target_ty is not None and self.get_llvm_float_bits(target_ty) == 32:
|
||
return mlirlite.ConstFloat(pool, target_ty, cn.float_val)
|
||
double_ty: mlirlite.LLVMType* = mlirlite.Double(pool)
|
||
return mlirlite.ConstFloat(pool, double_ty, cn.float_val)
|
||
elif cn.const_kind == ast.CONST_CHAR:
|
||
# Single-char literal (CONST_CHAR): default as char value (i32, coerce truncates to i8); if the target type is a pointer (str/bytes/t.CPtr) materialize a 1-char string, else inttoptr treats the char code as a pointer -> strlen crash. In 1a stage (declare_only=1) found_func=None so a misaligned target_ty is treated as garbage and skipped
|
||
if target_ty is not None and (uint64(target_ty) & 7) == 0 and self.is_ptr_type(target_ty) != 0:
|
||
cbuf: bytes = stdlib.malloc(2)
|
||
if cbuf is None: return mlirlite.const_int32(pool, 0)
|
||
cbuf[0] = char(cn.int_val)
|
||
cbuf[1] = '\0'
|
||
r_cs: mlirlite.Value* = self.create_global_string(builder, pool, mod, cbuf, trans)
|
||
stdlib.free(cbuf)
|
||
return r_cs
|
||
cv: int64 = cn.int_val
|
||
return mlirlite.const_int32(pool, cv)
|
||
elif cn.const_kind == ast.CONST_STR:
|
||
sv: str = cn.str_val
|
||
if sv is None: return None
|
||
r: mlirlite.Value* = self.create_global_string(builder, pool, mod, sv, trans)
|
||
return r
|
||
elif cn.const_kind == ast.CONST_BOOL:
|
||
if cn.int_val != 0: return mlirlite.const_int32(pool, 1)
|
||
return mlirlite.const_int32(pool, 0)
|
||
elif cn.const_kind == ast.CONST_NONE:
|
||
# None -> i8* null (null pointer constant) for `p is None` / `p is not None` comparisons
|
||
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
if i8_ty is None: return None
|
||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
|
||
if i8_ptr_ty is None: return None
|
||
rv_none: mlirlite.Value* = mlirlite.ConstNull(pool, i8_ptr_ty, "null")
|
||
return rv_none
|
||
return None
|
||
# _infer_elem_type_from_value - infer the list element type name from an LLVM Value.
|
||
# Used for element type inference of list literals [a, b, c].
|
||
def _infer_elem_type_from_value(self, pool: memhub.MemBuddy*, value: mlirlite.Value*) -> str:
|
||
"""Infer the list element type name from an LLVM Value; None if unmappable."""
|
||
if value is None or value.Ty is None: return None
|
||
ty: mlirlite.LLVMType* = value.Ty
|
||
bits: int = self.get_llvm_type_bits(ty)
|
||
if bits != 0:
|
||
if bits == 32: return "int"
|
||
if bits == 64: return "CSizeT"
|
||
if bits == 8: return "CInt8T"
|
||
if bits == 16: return "CInt16T"
|
||
return None
|
||
if self.is_ptr_type(ty) != 0: return "str"
|
||
fbits: int = self.get_llvm_float_bits(ty)
|
||
if fbits != 0:
|
||
if fbits == 64: return "CDouble"
|
||
if fbits == 32: return "CFloat"
|
||
return None
|
||
# _find_pool_var - find the pool variable in the context.
|
||
# Tries "pool", "_mbuddy", "mbuddy", "mb" in order.
|
||
def _find_pool_var(self, trans: HT.Translator*) -> mlirlite.Value*:
|
||
"""Find the pool variable in context (pool/_mbuddy/mbuddy/mb), else fall back."""
|
||
if trans is None: return None
|
||
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, "pool")
|
||
if alloca is not None: return alloca
|
||
alloca = self.Gen.VarGen.lookup_var(trans.SymTab, "_mbuddy")
|
||
if alloca is not None: return alloca
|
||
alloca = self.Gen.VarGen.lookup_var(trans.SymTab, "mbuddy")
|
||
if alloca is not None: return alloca
|
||
alloca = self.Gen.VarGen.lookup_var(trans.SymTab, "mb")
|
||
if alloca is not None: return alloca
|
||
# Fallback: create a module-mangled @_mbuddy definition (linkonce_odr + zeroinitializer),
|
||
# matching the SHA1-prefixed global naming; a real crash exposes an uninitialized pool (fail-fast).
|
||
pool: memhub.MemBuddy* = trans.Pool
|
||
if pool is None or trans.Module is None or trans.SymTab is None: return None
|
||
# First check whether it is already registered in the module scope (avoid duplicate globals)
|
||
existing: mlirlite.Value* = self.Gen.VarGen.lookup_module_var(trans.SymTab, "_mbuddy")
|
||
if existing is not None: return existing
|
||
# Create the i8* type (t.CPtr = i8*)
|
||
i8_ty_fb: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
i8_ptr_ty_fb: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty_fb)
|
||
# Module-mangled name (@<module-sha1>._mbuddy)
|
||
mb_name_fb: str = self.Gen.FuncGenBase._mangle_name(trans, "_mbuddy")
|
||
# Create a linkonce_odr definition (linker-mergeable, self-contained for linking)
|
||
gv_ext: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, mb_name_fb, i8_ptr_ty_fb)
|
||
if gv_ext is not None:
|
||
gv_ext.Linkage = "linkonce_odr"
|
||
gv_ext.Initializer = "zeroinitializer"
|
||
mlirlite.module_add_global(trans.Module, gv_ext)
|
||
# Create the Value reference (@<module-sha1>._mbuddy, type i8**)
|
||
i8_ptr_ptr_ty_fb: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty_fb)
|
||
ref_name_fb: str = pool.alloc(96)
|
||
if ref_name_fb is not None:
|
||
# Mangled names start with a digit (SHA1 prefix), so the reference must be quoted (see make_global_ref)
|
||
if self.Gen.ClassGenVTable._vtable_name_needs_quote(mb_name_fb) != 0: viperlib.snprintf(ref_name_fb, 96, "@\"%s\"", mb_name_fb)
|
||
else: viperlib.snprintf(ref_name_fb, 96, "@%s", mb_name_fb)
|
||
gv_ref_fb: mlirlite.Value* = mlirlite.SSAValue(pool, i8_ptr_ptr_ty_fb, ref_name_fb)
|
||
# Register in the module scope to avoid duplicate creation
|
||
self.Gen.VarGen.define_module_var(trans.SymTab, "_mbuddy", gv_ref_fb)
|
||
return gv_ref_fb
|
||
# translate_list_literal - lower [a, b, c] to list[T](pool) construction + append calls; elem_type_name None means infer from elements; returns Ptr(list[T] struct) or None
|
||
def translate_list_literal(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, elem_type_name: str, trans: HT.Translator*) -> mlirlite.Value*:
|
||
"""Lower a list literal to list[T](pool) construction plus append calls."""
|
||
if node is None or trans is None: return None
|
||
|
||
list_node: ast.List* = ast.List*(node)
|
||
if list_node is None: return None
|
||
|
||
elts: list[ast.AST*]* = list_node.elts
|
||
elts_count: sizet = 0
|
||
if elts is not None: elts_count = len(elts)
|
||
|
||
# Infer element type
|
||
inferred_type_name: str = elem_type_name
|
||
first_elem_val: mlirlite.Value* = None
|
||
if inferred_type_name is None:
|
||
if elts_count > 0:
|
||
first_elem_node: ast.AST* = elts.get(0)
|
||
first_elem_val = self.Gen.ExprGen.translate_value(builder, pool, mod, first_elem_node, None, 0, trans)
|
||
if first_elem_val is None or first_elem_val.Ty is None:
|
||
# fail-fast: first-element translation failed/Value type missing -> abort now (surface root cause)
|
||
self.Gen.TypeGenErr.fatal_error(first_elem_node, "list literal element type inference failed (Value type missing)", "TypeError")
|
||
inferred_type_name = self._infer_elem_type_from_value(pool, first_elem_val)
|
||
if inferred_type_name is None:
|
||
# fail-fast: first-element Value type cannot map to a list element type -> abort now
|
||
self.Gen.TypeGenErr.fatal_error(first_elem_node, "list literal element type inference failed (Value type missing)", "TypeError")
|
||
else:
|
||
# Empty list has no element to infer, keep default str (list[str])
|
||
inferred_type_name = "str"
|
||
|
||
# Find the pool variable
|
||
pool_alloca: mlirlite.Value* = self._find_pool_var(trans)
|
||
if pool_alloca is None:
|
||
self.Gen.TypeGenErr.fatal_error(node, "list literal requires pool variable in context")
|
||
return None
|
||
|
||
# Load the pool variable value (i8*)
|
||
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
||
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
|
||
pool_val: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, pool_alloca)
|
||
if pool_val is None: return None
|
||
|
||
# Specialize the list[T] class (elem_size=8: str is a pointer; 4 would overlap elements)
|
||
type_args: list[str]* = list[str](pool, 8)
|
||
type_args.append(inferred_type_name)
|
||
spec_name: str = self.Gen.ClassGenGeneric._specialize_generic_class(trans, "list", type_args)
|
||
if spec_name is None:
|
||
self.Gen.TypeGenErr.fatal_error(node, "list literal generic specialization failed")
|
||
return None
|
||
|
||
# Find the struct entry
|
||
struct_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(spec_name)
|
||
if struct_entry is None:
|
||
self.Gen.TypeGenErr.fatal_error(node, "list literal specialized class not registered")
|
||
return None
|
||
struct_ty: mlirlite.LLVMType* = struct_entry.Ty
|
||
if struct_ty is None: return None
|
||
|
||
# alloca temporary list storage; ensure the cross-module struct definition is
|
||
# fully available in the current module (so alloca can size it).
|
||
self.Gen.StructGen.ensure_struct_def_in_module(pool, mod, struct_ty)
|
||
tmp: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, struct_ty)
|
||
if tmp is None: return None
|
||
|
||
# Storage pointer: alloca by default, or the pointer returned by __new__ if present.
|
||
# __new__ is optional and only called when the generic template defines it (consistent with ctor lowering).
|
||
storage_ptr: mlirlite.Value* = tmp
|
||
has_new_flag: int = 0
|
||
if struct_entry is not None: has_new_flag = struct_entry.HasNew
|
||
if has_new_flag != 0:
|
||
new_args: sizet* = pool.alloc(8 * 32)
|
||
if new_args is None: return None
|
||
string.memset(new_args, 0, 8 * 32)
|
||
new_args[0] = sizet(pool_val)
|
||
new_ptr: mlirlite.Value* = self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__new__", tmp, new_args, 1, trans)
|
||
if new_ptr is not None: storage_ptr = new_ptr
|
||
|
||
# Call __before_init__(storage_ptr)
|
||
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__before_init__", storage_ptr, None, 0, trans)
|
||
|
||
# Call __init__(storage_ptr, pool_val, elem_size=0)
|
||
init_args: sizet* = pool.alloc(8 * 32)
|
||
if init_args is None: return None
|
||
string.memset(init_args, 0, 8 * 32)
|
||
init_args[0] = sizet(pool_val)
|
||
elem_size_val: mlirlite.Value* = mlirlite.const_int64(pool, 0)
|
||
init_args[1] = sizet(elem_size_val)
|
||
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "__init__", storage_ptr, init_args, 2, trans)
|
||
|
||
# Call append(storage_ptr, elem_val) for each element
|
||
if elts_count > 0:
|
||
# The first element may already be translated
|
||
if first_elem_val is not None:
|
||
append_args: sizet* = pool.alloc(8 * 32)
|
||
if append_args is not None:
|
||
string.memset(append_args, 0, 8 * 32)
|
||
append_args[0] = sizet(first_elem_val)
|
||
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "append", storage_ptr, append_args, 1, trans)
|
||
|
||
# Translate the remaining elements
|
||
for i in range(1, int(elts_count)):
|
||
elem_node_i: ast.AST* = elts.get(i)
|
||
elem_val_i: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, elem_node_i, None, 0, trans)
|
||
if elem_val_i is not None:
|
||
ap_args: sizet* = pool.alloc(8 * 32)
|
||
if ap_args is not None:
|
||
string.memset(ap_args, 0, 8 * 32)
|
||
ap_args[0] = sizet(elem_val_i)
|
||
self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, spec_name, "append", storage_ptr, ap_args, 1, trans)
|
||
|
||
return storage_ptr |