143 lines
9.0 KiB
Plaintext
143 lines
9.0 KiB
Plaintext
from stdint import *
|
|
import ast
|
|
import mlirlite
|
|
import memhub
|
|
import string
|
|
import viperlib
|
|
import lib.core.Gen.Translator as HT
|
|
import lib.core.Gen.GenTypes as GenTypes
|
|
|
|
from . import CallGenBase
|
|
|
|
|
|
# CallGenVt - CallGenMixin split (phase B): vtable slot writing
|
|
# ============================================================
|
|
novirtual class CallGenVt(CallGenBase.CallGenBase):
|
|
"""vtable slot writing."""
|
|
|
|
# _write_vtable_slot / _write_vtable_attr: write a module-level function pointer into an object vtable slot via copy-on-write (vtable slots are not data fields, so get_attribute_ptr cannot find them).
|
|
|
|
# ============================================================
|
|
def _write_vtable_slot(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, obj_ptr: mlirlite.Value*, class_name: str, method_index: int, func_val: mlirlite.Value*, trans: HT.Translator*) -> int:
|
|
"""Emit a copy-on-write vtable slot write."""
|
|
if pool is None or builder is None or mod is None or trans is None: return 0
|
|
if obj_ptr is None or class_name is None or func_val is None: return 0
|
|
if method_index < 0 or trans._cur_func is None: return 0
|
|
i8_ty: mlirlite.LLVMType* = mlirlite.Int8(pool)
|
|
if i8_ty is None: return 0
|
|
i8_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ty)
|
|
if i8_ptr_ty is None: return 0
|
|
i8_ptr_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, i8_ptr_ty)
|
|
if i8_ptr_ptr_ty is None: return 0
|
|
method_count: int = self.Gen.StructGen.get_vtable_method_count(class_name)
|
|
if method_count <= 0: return 0
|
|
# Build the vtable type { i8*, i8*, ... }; its field list uses mlirlite.ParamNode, not Function*.
|
|
vt_first: mlirlite.ParamNode* = None
|
|
vt_prev: mlirlite.ParamNode* = None
|
|
for vi in range(int(method_count)):
|
|
pn: mlirlite.ParamNode* = mlirlite.new_param_node(pool, i8_ptr_ty)
|
|
if pn is None: continue
|
|
if vt_first is None: vt_first = pn
|
|
if vt_prev is not None: mlirlite.paramnode_set_next(vt_prev, pn)
|
|
vt_prev = pn
|
|
vtable_ty: mlirlite.LLVMType* = mlirlite.Struct(pool, vt_first, method_count, None)
|
|
if vtable_ty is None: return 0
|
|
# Object vtable slot (object header -> i8**; the vtable pointer is always the first field).
|
|
vtable_slot: mlirlite.Value* = mlirlite.build_bitcast(builder, obj_ptr, i8_ptr_ptr_ty)
|
|
if vtable_slot is None: return 0
|
|
vtable_ptr: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, vtable_slot)
|
|
if vtable_ptr is None: return 0
|
|
# Address of the class default vtable (for comparison).
|
|
class_vt: mlirlite.Value* = self.Gen.CallGenLeafKit._make_vtable_ref_i8(pool, builder, mod, class_name, trans)
|
|
if class_vt is None: return 0
|
|
cmp_vt: mlirlite.Value* = mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, vtable_ptr, class_vt)
|
|
if cmp_vt is None: return 0
|
|
# Unique block names: create_block does not auto-number, so repeated vtable writes in one function would collide.
|
|
vtc3: int = trans._label_counter
|
|
trans._label_counter = vtc3 + 1
|
|
vt_bb_buf3: str = pool.alloc(64)
|
|
if vt_bb_buf3 is None: return 0
|
|
viperlib.snprintf(vt_bb_buf3, 64, "vt.copy.%d", vtc3)
|
|
vt_copy_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, trans._cur_func, vt_bb_buf3)
|
|
viperlib.snprintf(vt_bb_buf3, 64, "vt.skip.%d", vtc3)
|
|
vt_skip_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, trans._cur_func, vt_bb_buf3)
|
|
if vt_copy_bb is None or vt_skip_bb is None: return 0
|
|
mlirlite.build_cond_br(builder, cmp_vt, vt_copy_bb, vt_skip_bb)
|
|
# copy block: create internal copy global (zeroinit) + memcpy + repoint object at it.
|
|
mlirlite.position_at_end(builder, vt_copy_bb)
|
|
vtc: int = trans._label_counter
|
|
trans._label_counter = vtc + 1
|
|
vcopy_buf: str = pool.alloc(256)
|
|
if vcopy_buf is not None:
|
|
viperlib.snprintf(vcopy_buf, 256, "%s.vtable.copy.%d", class_name, vtc)
|
|
vcopy_gv: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, vcopy_buf, vtable_ty)
|
|
if vcopy_gv is not None:
|
|
mlirlite.module_add_global(mod, vcopy_gv)
|
|
vcopy_gv.Linkage = None
|
|
vcopy_gv.Initializer = "zeroinitializer"
|
|
# Reference names need the @ prefix: SSAValue emits the name verbatim (unlike new_global_variable, it adds no @).
|
|
vcopy_ref_nm: str = pool.alloc(272)
|
|
if vcopy_ref_nm is None: vcopy_ref_nm = vcopy_buf
|
|
else: viperlib.snprintf(vcopy_ref_nm, 272, "@%s", vcopy_buf)
|
|
vcopy_ref: mlirlite.Value* = mlirlite.SSAValue(pool, mlirlite.Ptr(pool, vtable_ty), vcopy_ref_nm)
|
|
if vcopy_ref is not None:
|
|
vcopy_i8: mlirlite.Value* = mlirlite.build_bitcast(builder, vcopy_ref, i8_ptr_ty)
|
|
if vcopy_i8 is not None:
|
|
# ConstInt's name must be numeric text (e.g. "56"); an identifier would emit verbatim without % and break llc.
|
|
vtsz_buf: str = pool.alloc(32)
|
|
if vtsz_buf is not None:
|
|
viperlib.snprintf(vtsz_buf, 32, "%lld", method_count * 8)
|
|
vt_size: mlirlite.Value* = mlirlite.ConstInt(pool, mlirlite.Int64(pool), method_count * 8, vtsz_buf)
|
|
if vt_size is not None: self.Gen.CallGenLib._emit_llvm_memcpy_intrinsic(pool, builder, mod, vcopy_i8, class_vt, vt_size)
|
|
mlirlite.build_store(builder, vcopy_i8, vtable_slot)
|
|
mlirlite.build_br(builder, vt_skip_bb)
|
|
# skip block: reload vtable -> GEP the slot -> store the function pointer (i8*).
|
|
mlirlite.position_at_end(builder, vt_skip_bb)
|
|
vt_ptr2: mlirlite.Value* = mlirlite.build_load(builder, i8_ptr_ty, vtable_slot)
|
|
if vt_ptr2 is None: return 0
|
|
vt_typed: mlirlite.Value* = mlirlite.build_bitcast(builder, vt_ptr2, mlirlite.Ptr(pool, vtable_ty))
|
|
if vt_typed is None: return 0
|
|
vt_slotp: mlirlite.Value* = mlirlite.build_gep_struct(builder, vtable_ty, i8_ptr_ty, vt_typed, method_index)
|
|
if vt_slotp is None: return 0
|
|
fptr_i8: mlirlite.Value* = mlirlite.build_bitcast(builder, func_val, i8_ptr_ty)
|
|
if fptr_i8 is not None: mlirlite.build_store(builder, fptr_i8, vt_slotp)
|
|
return 1
|
|
def _write_vtable_attr(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, rhs_val: mlirlite.Value*, trans: HT.Translator*) -> int:
|
|
"""Handle obj.method = func writes that target a vtable slot."""
|
|
if node is None or builder is None or pool is None or mod is None: return 0
|
|
if rhs_val is None or trans is None: return 0
|
|
at: ast.Attribute* = ast.Attribute*(node)
|
|
if at is None or at.value is None or at.attr is None: return 0
|
|
# Resolve the class name (AnnotClassName; strip the module prefix).
|
|
class_nm_v: str = None
|
|
if at.value.kind() == ast.ASTKind.Name:
|
|
nm_v: ast.Name* = ast.Name*(at.value)
|
|
if nm_v.id is not None:
|
|
ve_v: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_v.id)
|
|
if ve_v is not None: class_nm_v = self.Gen.VarGen.var_annot_class_name(ve_v)
|
|
elif at.value.kind() == ast.ASTKind.Attribute:
|
|
# Cross-mixin: _resolve_attr_field_class lives in ExprGenAttr, called via self.Gen.
|
|
class_nm_v = self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, at.value)
|
|
if class_nm_v is None: return 0
|
|
fd_dot_v: str = string.strrchr(class_nm_v, '.')
|
|
if fd_dot_v is not None: class_nm_v = fd_dot_v + 1
|
|
if self.Gen.StructGen.get_struct_type(class_nm_v) is None: return 0
|
|
vm_idx_v: int = self.Gen.StructGen.get_vtable_method_index(class_nm_v, at.attr)
|
|
if vm_idx_v < 0: return 0
|
|
# Resolve obj_ptr (same as the first half of get_attribute_ptr: Name looks up the alloca directly).
|
|
obj_ptr_v: mlirlite.Value* = None
|
|
if at.value.kind() == ast.ASTKind.Name: obj_ptr_v = self.Gen.VarGen.lookup_var(trans.SymTab, (ast.Name*(at.value)).id)
|
|
else: obj_ptr_v = self.Gen.ExprGen.translate_value(builder, pool, mod, at.value, None, 0, trans)
|
|
if obj_ptr_v is None or obj_ptr_v.Ty is None: return 0
|
|
# X* variable (Ptr(Ptr)): load-dereference to get the object pointer.
|
|
match obj_ptr_v.Ty:
|
|
case mlirlite.LLVMType.Ptr(inner_v):
|
|
match inner_v:
|
|
case mlirlite.LLVMType.Ptr(_):
|
|
obj_ptr_v = mlirlite.build_load(builder, inner_v, obj_ptr_v)
|
|
case _:
|
|
pass
|
|
case _:
|
|
pass
|
|
if obj_ptr_v is None: return 0
|
|
return self._write_vtable_slot(pool, builder, mod, obj_ptr_v, class_nm_v, vm_idx_v, rhs_val, trans) |