ir done'ish

This commit is contained in:
agra
2026-03-01 22:38:41 +02:00
parent 6a920dbd2c
commit f763765ea2
17 changed files with 1443 additions and 15017 deletions

View File

@@ -56,6 +56,7 @@ pub const TypeInfo = union(enum) {
@"struct": StructInfo,
@"enum": EnumInfo,
@"union": UnionInfo,
tagged_union: TaggedUnionInfo,
array: ArrayInfo,
slice: SliceInfo,
pointer: PointerInfo,
@@ -84,12 +85,20 @@ pub const TypeInfo = union(enum) {
variants: []const StringId,
is_flags: bool = false,
explicit_values: ?[]const i64 = null, // for flags (power-of-2) or custom values
backing_type: ?TypeId = null, // e.g. u32 for `enum u32 { ... }`
};
pub const UnionInfo = struct {
name: StringId,
fields: []const StructInfo.Field,
tag_type: ?TypeId, // tagged union enum type, null if untagged
};
pub const TaggedUnionInfo = struct {
name: StringId,
fields: []const StructInfo.Field,
tag_type: TypeId, // tag integer type (e.g. .u32, .s64)
backing_type: ?TypeId = null, // enum struct backing (e.g. { tag: u32; _: u32; payload: [30]u32; })
explicit_tag_values: ?[]const i64 = null, // explicit variant values (e.g., quit :: 0x100)
};
pub const ArrayInfo = struct {
@@ -290,6 +299,7 @@ pub const TypeTable = struct {
const n: ?StringId = switch (info) {
.@"struct" => |s| s.name,
.@"union" => |u| u.name,
.tagged_union => |u| u.name,
.@"enum" => |e| e.name,
else => null,
};
@@ -358,15 +368,27 @@ pub const TypeTable = struct {
return if (total == 0) 8 else total;
},
.@"union" => |u| {
// Size of union = tag + max(field sizes)
var max_field: u32 = 0;
for (u.fields) |f| {
const sz = self.sizeOf(f.ty);
if (sz > max_field) max_field = sz;
}
return 8 + @max(max_field, 8);
return @max(max_field, 8);
},
.tagged_union => |u| {
if (u.backing_type) |bt| return self.sizeOf(bt);
var max_field: u32 = 0;
for (u.fields) |f| {
const sz = self.sizeOf(f.ty);
if (sz > max_field) max_field = sz;
}
const tag_sz = @as(u32, @intCast(self.typeSizeBytes(u.tag_type)));
return tag_sz + @max(max_field, 8);
},
.@"enum" => |e| {
if (e.backing_type) |bt| return self.sizeOf(bt);
return 8;
},
.@"enum" => 8, // plain enums are just integer tags
.tuple => |t| {
var total: u32 = 0;
for (t.fields) |f| total += @max(self.sizeOf(f), 8);
@@ -376,6 +398,145 @@ pub const TypeTable = struct {
};
}
/// Compute the ABI size in bytes for a type, matching LLVM's struct layout rules.
/// This is the authoritative size computation used for closure env sizing and
/// verified against LLVMABISizeOfType.
pub fn typeSizeBytes(self: *const TypeTable, ty: TypeId) usize {
if (ty == .void) return 0;
if (ty == .bool) return 1;
if (ty == .u8 or ty == .s8) return 1;
if (ty == .u16 or ty == .s16) return 2;
if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
if (ty == .string) return 16; // {ptr, i64}
if (ty.isBuiltin()) return 8; // default for unknown builtins
const info = self.get(ty);
return switch (info) {
.pointer, .many_pointer, .function => 8,
.slice => 16, // {ptr, i64}
.closure => 16, // {fn_ptr, env_ptr}
.optional => |o| blk: {
const child_info = self.get(o.child);
if (child_info == .pointer or child_info == .many_pointer or child_info == .function)
break :blk 8;
if (child_info == .closure)
break :blk 16; // {fn_ptr, env_ptr}
const cs = self.typeSizeBytes(o.child);
const ca = self.typeAlignBytes(o.child);
// { T, i1 } — i1 goes right after T, then pad to struct alignment
const unpadded = cs + 1;
break :blk (unpadded + ca - 1) & ~(ca - 1);
},
.@"struct" => |s| blk: {
var offset: usize = 0;
var max_a: usize = 1;
for (s.fields) |f| {
const fs = self.typeSizeBytes(f.ty);
const fa = self.typeAlignBytes(f.ty);
if (fa > max_a) max_a = fa;
offset = (offset + fa - 1) & ~(fa - 1);
offset += fs;
}
break :blk if (offset == 0) 0 else (offset + max_a - 1) & ~(max_a - 1);
},
.@"union" => |u| blk: {
var max_payload: usize = 0;
for (u.fields) |f| {
const fs = self.typeSizeBytes(f.ty);
if (fs > max_payload) max_payload = fs;
}
break :blk if (max_payload == 0) 8 else max_payload;
},
.tagged_union => |u| blk: {
if (u.backing_type) |bt| break :blk self.typeSizeBytes(bt);
var max_payload: usize = 0;
for (u.fields) |f| {
const fs = self.typeSizeBytes(f.ty);
if (fs > max_payload) max_payload = fs;
}
const tag_size = self.typeSizeBytes(u.tag_type);
const raw = max_payload + tag_size;
break :blk (raw + 7) & ~@as(usize, 7);
},
.array => |a| blk: {
const elem_size = self.typeSizeBytes(a.element);
break :blk elem_size * @as(usize, @intCast(a.length));
},
.vector => |v| blk: {
const elem_size = self.typeSizeBytes(v.element);
const raw = elem_size * @as(usize, @intCast(v.length));
// LLVM vectors round ABI size up to next power of 2
break :blk std.math.ceilPowerOfTwo(usize, raw) catch raw;
},
.tuple => |t| blk: {
var offset: usize = 0;
var max_a: usize = 1;
for (t.fields) |f| {
const fs = self.typeSizeBytes(f);
const fa = self.typeAlignBytes(f);
if (fa > max_a) max_a = fa;
offset = (offset + fa - 1) & ~(fa - 1);
offset += fs;
}
break :blk if (offset == 0) 0 else (offset + max_a - 1) & ~(max_a - 1);
},
.any => 16,
.protocol => 16,
.@"enum" => |e| {
if (e.backing_type) |bt| return self.typeSizeBytes(bt);
return 8;
},
else => 8,
};
}
/// Compute the ABI alignment in bytes for a type, matching LLVM's rules.
pub fn typeAlignBytes(self: *const TypeTable, ty: TypeId) usize {
if (ty == .void) return 1;
if (ty == .bool) return 1;
if (ty == .u8 or ty == .s8) return 1;
if (ty == .u16 or ty == .s16) return 2;
if (ty == .s32 or ty == .u32 or ty == .f32) return 4;
if (ty == .s64 or ty == .u64 or ty == .f64) return 8;
if (ty == .string) return 8;
if (ty.isBuiltin()) return 8;
const info = self.get(ty);
return switch (info) {
.pointer, .many_pointer, .function => 8,
.slice, .closure => 8,
.optional => |o| blk: {
const child_info = self.get(o.child);
if (child_info == .pointer or child_info == .many_pointer or child_info == .function or child_info == .closure)
break :blk 8;
break :blk self.typeAlignBytes(o.child);
},
.@"struct" => |s| blk: {
var max_a: usize = 1;
for (s.fields) |f| {
const fa = self.typeAlignBytes(f.ty);
if (fa > max_a) max_a = fa;
}
break :blk max_a;
},
.@"union", .tagged_union => 8,
.@"enum" => |e| {
if (e.backing_type) |bt| return self.typeAlignBytes(bt);
return 8;
},
.array => |a| self.typeAlignBytes(a.element),
.vector => |v| self.typeAlignBytes(v.element),
.tuple => |t| blk: {
var max_a: usize = 1;
for (t.fields) |f| {
const fa = self.typeAlignBytes(f);
if (fa > max_a) max_a = fa;
}
break :blk max_a;
},
else => 8,
};
}
/// Intern a string into the pool.
pub fn internString(self: *TypeTable, str: []const u8) StringId {
return self.strings.intern(self.alloc, str);
@@ -412,6 +573,7 @@ pub const TypeTable = struct {
.@"struct" => |s| self.getString(s.name),
.@"enum" => |e| self.getString(e.name),
.@"union" => |u| self.getString(u.name),
.tagged_union => |u| self.getString(u.name),
.protocol => |p| self.getString(p.name),
else => "?",
};
@@ -471,6 +633,7 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void {
.@"struct" => |s| h.update(std.mem.asBytes(&s.name)),
.@"enum" => |e| h.update(std.mem.asBytes(&e.name)),
.@"union" => |u| h.update(std.mem.asBytes(&u.name)),
.tagged_union => |u| h.update(std.mem.asBytes(&u.name)),
.protocol => |p| h.update(std.mem.asBytes(&p.name)),
.tuple => |t| {
for (t.fields) |f| h.update(std.mem.asBytes(&f));
@@ -513,6 +676,7 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
.@"struct" => |s| s.name == b.@"struct".name,
.@"enum" => |e| e.name == b.@"enum".name,
.@"union" => |u| u.name == b.@"union".name,
.tagged_union => |u| u.name == b.tagged_union.name,
.protocol => |p| p.name == b.protocol.name,
.tuple => |t| {
const u = b.tuple;