This commit is contained in:
agra
2026-03-04 09:18:24 +02:00
parent 0336f361c7
commit 67e02a20a5
6 changed files with 153 additions and 23 deletions

View File

@@ -100,6 +100,7 @@ pub const Lowering = struct {
protocol_ast_map: std.StringHashMap(*const ast.ProtocolDecl) = std.StringHashMap(*const ast.ProtocolDecl).init(std.heap.page_allocator), // protocol name → AST node
protocol_thunk_map: std.StringHashMap([]const FuncId) = std.StringHashMap([]const FuncId).init(std.heap.page_allocator), // "Proto\x00Type" → thunk FuncIds
protocol_vtable_type_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // protocol name → vtable struct TypeId
protocol_vtable_global_map: std.StringHashMap(inst_mod.GlobalId) = std.StringHashMap(inst_mod.GlobalId).init(std.heap.page_allocator), // "Proto\x00Type" → vtable GlobalId
struct_const_map: std.StringHashMap(StructConstInfo) = std.StringHashMap(StructConstInfo).init(std.heap.page_allocator), // "Struct.CONST" → value info
module_const_map: std.StringHashMap(ModuleConstInfo) = std.StringHashMap(ModuleConstInfo).init(std.heap.page_allocator), // module-level value constants (e.g. AF_INET :s32: 2)
foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames
@@ -3701,6 +3702,15 @@ pub const Lowering = struct {
}
// Check builtins first (these are handled natively by interpreter and emitter)
if (resolveBuiltin(id.name)) |bid| {
// free(protocol_value) → extract ctx (field 0) and free it
if (bid == .free and args.items.len == 1) {
const arg_ty = self.builder.getRefType(args.items[0]);
if (self.getProtocolInfo(arg_ty) != null) {
const void_ptr_ty = self.module.types.ptrTo(.void);
const ctx_ref = self.builder.emit(.{ .struct_get = .{ .base = args.items[0], .field_index = 0 } }, void_ptr_ty);
return self.builder.emit(.{ .heap_free = .{ .operand = ctx_ref } }, .void);
}
}
const ret_ty: TypeId = switch (bid) {
.malloc => .s64, // pointer
.size_of => .s64,
@@ -7500,18 +7510,38 @@ pub const Lowering = struct {
/// Build a protocol value from a concrete pointer.
/// For inline protocols: struct_init { ctx, thunk1, thunk2, ... }
/// For vtable protocols: struct_init { ctx, vtable_ptr } where vtable is stack-allocated
fn buildProtocolValue(self: *Lowering, concrete_ptr: Ref, proto_name: []const u8, concrete_type_name: []const u8, proto_ty: TypeId) Ref {
/// When `heap_copy` is true, the concrete data is heap-copied so the protocol value
/// outlives the current stack frame (used when source is a value, not an explicit pointer).
/// When false, the pointer is used directly (user manages the pointee's lifetime).
fn buildProtocolValue(self: *Lowering, concrete_ptr: Ref, proto_name: []const u8, concrete_type_name: []const u8, proto_ty: TypeId, concrete_ty: TypeId, heap_copy: bool) Ref {
const pd = self.protocol_decl_map.get(proto_name) orelse return concrete_ptr;
const thunks = self.getOrCreateThunks(proto_name, concrete_type_name);
if (thunks.len != pd.methods.len) return concrete_ptr;
const void_ptr_ty = self.module.types.ptrTo(.void);
// When source is a value (not an explicit pointer), heap-allocate
// so the protocol value outlives the current stack frame.
// When source is an explicit pointer (xx @obj), use it directly —
// the user is responsible for the pointee's lifetime.
var ctx_ptr = concrete_ptr;
if (heap_copy) {
const concrete_size = self.module.types.typeSizeBytes(concrete_ty);
const size_ref = self.builder.constInt(@intCast(concrete_size), .s64);
const heap_ptr = self.builder.emit(.{ .heap_alloc = .{ .operand = size_ref } }, void_ptr_ty);
const memcpy_args = self.alloc.dupe(Ref, &.{ heap_ptr, concrete_ptr, size_ref }) catch unreachable;
_ = self.builder.emit(.{ .call_builtin = .{
.builtin = inst_mod.BuiltinId.memcpy,
.args = memcpy_args,
} }, void_ptr_ty);
ctx_ptr = heap_ptr;
}
if (pd.is_inline) {
// Inline: { ctx, fn1, fn2, ... }
var field_vals = std.ArrayList(Ref).empty;
defer field_vals.deinit(self.alloc);
field_vals.append(self.alloc, concrete_ptr) catch unreachable;
field_vals.append(self.alloc, ctx_ptr) catch unreachable;
for (thunks) |thunk_id| {
const fn_ref = self.builder.emit(.{ .func_ref = thunk_id }, void_ptr_ty);
field_vals.append(self.alloc, fn_ref) catch unreachable;
@@ -7520,24 +7550,37 @@ pub const Lowering = struct {
return self.builder.emit(.{ .struct_init = .{ .fields = owned } }, proto_ty);
} else {
// Vtable: { ctx, vtable_ptr }
// Build vtable struct on stack: alloca + store fn_ptrs
// Vtable is a global constant (same function pointers for every instance
// of the same Protocol+ConcreteType pair). Cached per pair.
const vtable_ty = self.protocol_vtable_type_map.get(proto_name) orelse return concrete_ptr;
var vtable_fields = std.ArrayList(Ref).empty;
defer vtable_fields.deinit(self.alloc);
for (thunks) |thunk_id| {
const fn_ref = self.builder.emit(.{ .func_ref = thunk_id }, void_ptr_ty);
vtable_fields.append(self.alloc, fn_ref) catch unreachable;
}
const vtable_fields_owned = self.alloc.dupe(Ref, vtable_fields.items) catch unreachable;
const vtable_val = self.builder.emit(.{ .struct_init = .{ .fields = vtable_fields_owned } }, vtable_ty);
const vtable_alloca = self.builder.alloca(vtable_ty);
self.builder.store(vtable_alloca, vtable_val);
// Build cache key: "Proto\x00Type"
const key = std.fmt.allocPrint(self.alloc, "{s}\x00{s}", .{ proto_name, concrete_type_name }) catch unreachable;
const vtable_global_id = self.protocol_vtable_global_map.get(key) orelse blk: {
// Create vtable global with function pointer initializer
const global_name = std.fmt.allocPrint(self.alloc, "__{s}__{s}__vtable", .{ proto_name, concrete_type_name }) catch unreachable;
const global_name_id = self.module.types.strings.intern(self.alloc, global_name);
const thunk_ids = self.alloc.dupe(FuncId, thunks) catch unreachable;
const gid = self.module.addGlobal(.{
.name = global_name_id,
.ty = vtable_ty,
.init_val = .{ .vtable = thunk_ids },
.is_const = true,
});
self.protocol_vtable_global_map.put(key, gid) catch {};
break :blk gid;
};
// Reference the vtable global's address
const vtable_ptr_ty = self.module.types.ptrTo(vtable_ty);
const vtable_addr = self.builder.emit(.{ .global_addr = vtable_global_id }, vtable_ptr_ty);
// Build protocol struct: { ctx, &vtable }
var proto_fields = std.ArrayList(Ref).empty;
defer proto_fields.deinit(self.alloc);
proto_fields.append(self.alloc, concrete_ptr) catch unreachable;
proto_fields.append(self.alloc, vtable_alloca) catch unreachable;
proto_fields.append(self.alloc, ctx_ptr) catch unreachable;
proto_fields.append(self.alloc, vtable_addr) catch unreachable;
const proto_owned = self.alloc.dupe(Ref, proto_fields.items) catch unreachable;
return self.builder.emit(.{ .struct_init = .{ .fields = proto_owned } }, proto_ty);
}
@@ -8031,20 +8074,26 @@ pub const Lowering = struct {
if (dst_info != .@"struct") return operand;
const proto_name = self.module.types.getString(dst_info.@"struct".name);
// Determine concrete type name — resolve through pointer if needed
// Determine concrete type name and type — resolve through pointer if needed
var concrete_ptr = operand;
var concrete_type_name: ?[]const u8 = null;
var concrete_ty: TypeId = src_ty;
var heap_copy = false;
if (!src_ty.isBuiltin()) {
const src_info = self.module.types.get(src_ty);
if (src_info == .pointer) {
// xx @acc — operand is already a pointer
// xx @acc — operand is already a pointer (user manages lifetime)
const pointee = src_info.pointer.pointee;
concrete_type_name = self.resolveConcreteTypeName(pointee);
concrete_ty = pointee;
heap_copy = false;
} else if (src_info == .@"struct") {
// xx acc — operand is a value, need to take address
// xx acc — operand is a value, need to take address + heap-copy
concrete_type_name = self.module.types.getString(src_info.@"struct".name);
// Alloca + store to get a pointer
concrete_ty = src_ty;
heap_copy = true;
// Alloca + store to get a pointer (will be heap-copied in buildProtocolValue)
const slot = self.builder.alloca(src_ty);
self.builder.store(slot, operand);
concrete_ptr = slot;
@@ -8054,10 +8103,11 @@ pub const Lowering = struct {
// Also try from the operand node for struct literals: xx Accumulator.{ total = 0 }
if (concrete_type_name == null) {
concrete_type_name = self.inferConcreteTypeName(operand_node);
if (concrete_type_name != null) heap_copy = true;
}
if (concrete_type_name) |ctn| {
return self.buildProtocolValue(concrete_ptr, proto_name, ctn, dst_ty);
return self.buildProtocolValue(concrete_ptr, proto_name, ctn, dst_ty, concrete_ty, heap_copy);
}
return operand;
}
@@ -8343,17 +8393,23 @@ pub const Lowering = struct {
if (dst_info == .@"struct") {
const proto_name = self.module.types.getString(dst_info.@"struct".name);
if (self.resolveConcreteTypeName(src_ty)) |ctn| {
// If src is a pointer, use directly; otherwise alloca+store
// If src is a pointer, use directly; otherwise alloca+store + heap-copy
var concrete_ptr = val;
var concrete_ty = src_ty;
var heap_copy = false;
if (!src_ty.isBuiltin()) {
const si = self.module.types.get(src_ty);
if (si != .pointer) {
if (si == .pointer) {
concrete_ty = si.pointer.pointee;
heap_copy = false;
} else {
const slot = self.builder.alloca(src_ty);
self.builder.store(slot, val);
concrete_ptr = slot;
heap_copy = true;
}
}
return self.buildProtocolValue(concrete_ptr, proto_name, ctn, dst_ty);
return self.buildProtocolValue(concrete_ptr, proto_name, ctn, dst_ty, concrete_ty, heap_copy);
}
}
}