Files
ViperC/App/lib/core/Gen/Expr/ExprGenConst.vp
T

315 lines
19 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 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