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)