comptime-API: strip the byte-weld; pivot to a flat-memory comptime VM

The byte-weld (sx structs whose layout was validated to mirror the
compiler's Zig records) plus the serialization/marshaling bridge was the
wrong direction: it bolted a parallel layout regime and hand-built
byte-copies onto a comptime value model that fundamentally isn't bytes.

Strip the struct-weld machinery:
- compiler_lib.zig loses the type registry (weldStruct / bound_types /
  BoundType / FieldLayout / findType / SxField / LayoutMismatch /
  validateStructLayout); it is now just the intern/text_of function
  host-call bridge (kept as the Phase-3 compiler-call seed).
- nominal.zig loses validateWeldedStruct / weldedFieldOrderStr + the
  sd.abi == .zig validation call.
- Remove the struct-weld unit tests and examples 0625/0627 (welded
  structs) + 1183/1186 (weld-layout diagnostics).
- The #library / abi / extern syntax stays.

Record the new direction: a bytecode VM over flat, byte-addressable
memory so comptime values are native bytes (no weld/validation/marshal),
target-aware (preserves cross-compilation) and sandboxed. See
current/PLAN-COMPILER-VM.md (Phase 0 strip -> Phase 1 flat-memory value
model -> Phase 2 bytecode -> Phase 3 compiler-API on flat memory).
design/comptime-compiler-api.md gets a SUPERSEDED banner. Also drop the
"~500 lines / split the step" rule from CLAUDE.md.
This commit is contained in:
agra
2026-06-17 19:29:36 +03:00
parent 40d075ca98
commit 18af8eb845
23 changed files with 505 additions and 498 deletions

View File

@@ -6,7 +6,6 @@ const mod_mod = @import("../module.zig");
const type_bridge = @import("../type_bridge.zig");
const program_index_mod = @import("../program_index.zig");
const resolver_mod = @import("../resolver.zig");
const compiler_lib = @import("../compiler_lib.zig");
const StructTemplate = program_index_mod.StructTemplate;
const TemplateParam = program_index_mod.TemplateParam;
@@ -674,13 +673,7 @@ pub fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl, source_fil
// any forward-reference stub. Same-name structs in DIFFERENT sources get
// distinct TypeIds instead of last-wins clobbering the first.
const info: types.TypeInfo = .{ .@"struct" = .{ .name = name_id, .fields = fields.items } };
const struct_tid = self.internNamedTypeDecl(decl_key, name_id, info, nominal_id);
// Welded `struct abi(.zig) extern compiler { … }`: the sx declaration is a
// header checked against the compiler's real Zig type — validate the layout
// matches the binding registry (a mismatch is a build error). See
// design/comptime-compiler-api.md.
if (sd.abi == .zig) validateWeldedStruct(self, sd, struct_tid, fields.items);
_ = self.internNamedTypeDecl(decl_key, name_id, info, nominal_id);
// Store field defaults for struct literal lowering
if (sd.field_defaults.len > 0) {
@@ -716,77 +709,6 @@ pub fn registerStructDecl(self: *Lowering, sd: *const ast.StructDecl, source_fil
}
}
/// Validate a welded `struct abi(.zig) extern <lib> { … }` against the `compiler`
/// library's binding registry: the bound library must be `compiler`, the name
/// must be on the export list, and the sx-declared layout must match the real Zig
/// type's (the sx side is a *header* checked against the implementation). Any
/// failure is a build-gating `.err` diagnostic — never a silent reinterpretation.
fn validateWeldedStruct(self: *Lowering, sd: *const ast.StructDecl, tid: TypeId, fields: []const types.TypeInfo.StructInfo.Field) void {
const diags = self.diagnostics orelse return;
const table = &self.module.types;
// A span that points into the struct (its first field, else zero) — the decl
// has no name span of its own.
const span: ast.Span = if (sd.field_types.len > 0) sd.field_types[0].span else .{ .start = 0, .end = 0 };
// The bound library must be the sole welded source.
if (sd.extern_lib == null or !std.mem.eql(u8, sd.extern_lib.?, compiler_lib.lib_name)) {
diags.addFmt(.err, span, "abi(.zig) struct '{s}' must bind the compiler library — write `extern {s}`", .{ sd.name, compiler_lib.lib_name });
return;
}
// The name must be on the curated export list (the safety boundary).
const bt = compiler_lib.findType(sd.name) orelse {
diags.addFmt(.err, span, "'{s}' is not a type exported by the '{s}' library", .{ sd.name, compiler_lib.lib_name });
return;
};
// Build the observed sx layout (field name + computed size) and total size.
var sx_fields = std.ArrayList(compiler_lib.SxField).empty;
defer sx_fields.deinit(self.alloc);
for (fields) |f| {
sx_fields.append(self.alloc, .{
.name = table.getString(f.name),
.size = table.typeSizeBytes(f.ty),
}) catch return;
}
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}': 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