Files

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)