compiler-API: welded structs by reflection + memory-order validation

Replace the explored byte-layout-override engine (offset-ordered LLVM structs /
weld plans / byte-blobs — all unnecessary) with a much simpler design: a welded
`struct abi(.zig) extern compiler { … }` is a bodied header declaring its fields
in the bound compiler type's MEMORY order. The compiler reflects the real Zig
type (field names via @typeInfo, offsets via @offsetOf, size via @sizeOf —
nothing hand-maintained) and validates the header matches, with loud diagnostics.

On pass it is an ordinary struct whose natural layout already equals the Zig
layout — no reorder, no padding, no index/remap tables, no special LLVM path — so
@ptrCast'ing it to the compiler's own type and dereferencing is byte-identical.
When types.zig shifts, the header stops matching and the developer gets a specific
message to fix it.

- compiler_lib.zig: weldStruct reflects field names and bakes bound_types fields
  in ascending-offset (memory) order; deleted computeWeldPlan/WeldPlan/WeldElement.
- nominal.zig validateWeldedStruct: precise diagnostics — field-not-found,
  wrong-field-order (+ expected memory order), type-layout (size) mismatch,
  total-size mismatch.
- Examples: 0627 (StructInfo in memory order, byte-identical, usable),
  1186 (source-order StructInfo -> wrong-field-order diagnostic); 1183 refreshed.
- Design doc + checkpoint updated.
This commit is contained in:
agra
2026-06-17 15:45:23 +03:00
parent 88c4cbcfa5
commit 40d075ca98
14 changed files with 230 additions and 218 deletions

View File

@@ -753,14 +753,40 @@ fn validateWeldedStruct(self: *Lowering, sd: *const ast.StructDecl, tid: TypeId,
const total = table.typeSizeBytes(tid);
const mismatch = compiler_lib.validateStructLayout(bt, sx_fields.items, total) orelse return;
// The compiler type's fields, in the memory order an sx header must mirror —
// included in the order/count diagnostics so the fix is obvious.
const order = weldedFieldOrderStr(self.alloc, bt);
defer if (order.len > 0) self.alloc.free(order);
switch (mismatch) {
.field_count => |m| diags.addFmt(.err, span, "welded type '{s}' has {d} field(s) in the compiler library but the declaration has {d}", .{ sd.name, m.expected, m.got }),
.field_name => |m| diags.addFmt(.err, span, "welded type '{s}' field {d} is named '{s}' in the compiler library, not '{s}'", .{ sd.name, m.index, m.expected, m.got }),
.field_size => |m| diags.addFmt(.err, span, "welded type '{s}' field '{s}' is {d} byte(s) in the compiler library but {d} as declared", .{ sd.name, m.name, m.expected, m.got }),
.total_size => |m| diags.addFmt(.err, span, "welded type '{s}' is {d} byte(s) in the compiler library but {d} as declared (padding/alignment mismatch)", .{ sd.name, m.expected, m.got }),
.field_count => |m| diags.addFmt(.err, span, "welded type '{s}': the compiler type has {d} field(s) but the declaration has {d} — declare them in memory order: {s}", .{ sd.name, m.expected, m.got, order }),
.field_name => |m| {
// Distinguish "this name isn't a field at all" from "right field set,
// wrong order".
const exists = blk: {
for (bt.fields) |bf| if (std.mem.eql(u8, bf.name, m.got)) break :blk true;
break :blk false;
};
if (exists)
diags.addFmt(.err, span, "welded type '{s}': wrong field order at position {d} — found '{s}', the compiler type has '{s}' here (memory order: {s})", .{ sd.name, m.index, m.got, m.expected, order })
else
diags.addFmt(.err, span, "welded type '{s}': field '{s}' is not a field of the compiler type (its fields, in memory order: {s})", .{ sd.name, m.got, order });
},
.field_size => |m| diags.addFmt(.err, span, "welded type '{s}': type layout mismatch — field '{s}' is {d} byte(s) in the compiler type but {d} as declared", .{ sd.name, m.name, m.expected, m.got }),
.total_size => |m| diags.addFmt(.err, span, "welded type '{s}': layout mismatch — the compiler type is {d} byte(s) but the declaration is {d} (alignment/padding)", .{ sd.name, m.expected, m.got }),
}
}
/// The bound type's field names in memory order, `, `-joined, for diagnostics.
/// Returns an owned string; empty (no free needed) on allocation failure.
fn weldedFieldOrderStr(alloc: std.mem.Allocator, bt: *const compiler_lib.BoundType) []const u8 {
var buf = std.ArrayList(u8).empty;
for (bt.fields, 0..) |bf, i| {
if (i > 0) buf.appendSlice(alloc, ", ") catch return "";
buf.appendSlice(alloc, bf.name) catch return "";
}
return buf.toOwnedSlice(alloc) catch "";
}
/// Register a top-level ENUM decl under a per-decl nominal identity (E6a) —
/// the enum twin of `registerStructDecl`. A GENUINE same-name shadow already
/// reserved its DISTINCT slot up-front in `scanDecls` (the first at id 0, the