Files
sx/src/ir/compiler_lib.test.zig
agra 40d075ca98 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.
2026-06-17 15:45:23 +03:00

144 lines
6.8 KiB
Zig

// Tests for the comptime `compiler` library's binding registry.
const std = @import("std");
const compiler_lib = @import("compiler_lib.zig");
const types = @import("types.zig");
// Lock: `findType("Field")` resolves to the welded `StructInfo.Field` type, and
// its baked layout EQUALS the real Zig type's `@sizeOf`/`@alignOf`/`@offsetOf`.
// This is the foundation the layout sub-step builds on — the welded record's
// offsets come from the implementation, so they can't drift.
test "compiler_lib: Field welds to StructInfo.Field's real layout" {
const FieldZig = types.TypeInfo.StructInfo.Field;
const bt = compiler_lib.findType("Field") orelse return error.MissingBoundType;
try std.testing.expectEqualStrings("Field", bt.sx_name);
try std.testing.expectEqual(@sizeOf(FieldZig), bt.size);
try std.testing.expectEqual(@alignOf(FieldZig), bt.alignment);
// Two u32 fields, in declaration order.
try std.testing.expectEqual(@as(usize, 2), bt.fields.len);
try std.testing.expectEqualStrings("name", bt.fields[0].name);
try std.testing.expectEqual(@offsetOf(FieldZig, "name"), bt.fields[0].offset);
try std.testing.expectEqual(@as(usize, 4), bt.fields[0].size);
try std.testing.expectEqualStrings("ty", bt.fields[1].name);
try std.testing.expectEqual(@offsetOf(FieldZig, "ty"), bt.fields[1].offset);
try std.testing.expectEqual(@as(usize, 4), bt.fields[1].size);
// Sanity: the concrete shape the design calls out — two u32s, 8 bytes.
try std.testing.expectEqual(@as(usize, 8), bt.size);
try std.testing.expectEqual(@as(usize, 0), bt.fields[0].offset);
try std.testing.expectEqual(@as(usize, 4), bt.fields[1].offset);
}
// Lock: a name NOT on the export list is unreachable — `findType` returns null
// (the safety boundary; the welded-decl path falls through to a clean error,
// never a silent default).
test "compiler_lib: unexported name returns null" {
try std.testing.expect(compiler_lib.findType("NotExported") == null);
try std.testing.expect(compiler_lib.findType("") == null);
}
// Lock: a faithful sx header for `Field` validates clean (the natural two-u32
// layout matches the welded type).
test "compiler_lib: validateStructLayout accepts a faithful Field header" {
const bt = compiler_lib.findType("Field").?;
const sx = [_]compiler_lib.SxField{
.{ .name = "name", .size = 4 },
.{ .name = "ty", .size = 4 },
};
try std.testing.expect(compiler_lib.validateStructLayout(bt, &sx, 8) == null);
}
// Lock: every drift the assertion is meant to catch surfaces as the right
// `LayoutMismatch` variant (field count / name / size / total), and the first
// mismatch wins.
test "compiler_lib: validateStructLayout flags each kind of drift" {
const bt = compiler_lib.findType("Field").?;
// Wrong field count (one field instead of two).
{
const sx = [_]compiler_lib.SxField{.{ .name = "name", .size = 4 }};
const m = compiler_lib.validateStructLayout(bt, &sx, 4).?;
try std.testing.expect(m == .field_count);
try std.testing.expectEqual(@as(usize, 2), m.field_count.expected);
try std.testing.expectEqual(@as(usize, 1), m.field_count.got);
}
// Wrong field name (reorder / rename) at index 1.
{
const sx = [_]compiler_lib.SxField{
.{ .name = "name", .size = 4 },
.{ .name = "kind", .size = 4 },
};
const m = compiler_lib.validateStructLayout(bt, &sx, 8).?;
try std.testing.expect(m == .field_name);
try std.testing.expectEqual(@as(usize, 1), m.field_name.index);
try std.testing.expectEqualStrings("ty", m.field_name.expected);
try std.testing.expectEqualStrings("kind", m.field_name.got);
}
// Wrong field size (retype to an 8-byte field).
{
const sx = [_]compiler_lib.SxField{
.{ .name = "name", .size = 4 },
.{ .name = "ty", .size = 8 },
};
const m = compiler_lib.validateStructLayout(bt, &sx, 12).?;
try std.testing.expect(m == .field_size);
try std.testing.expectEqual(@as(usize, 1), m.field_size.index);
try std.testing.expectEqual(@as(usize, 4), m.field_size.expected);
try std.testing.expectEqual(@as(usize, 8), m.field_size.got);
}
// Right fields, wrong total (padding drift).
{
const sx = [_]compiler_lib.SxField{
.{ .name = "name", .size = 4 },
.{ .name = "ty", .size = 4 },
};
const m = compiler_lib.validateStructLayout(bt, &sx, 16).?;
try std.testing.expect(m == .total_size);
try std.testing.expectEqual(@as(usize, 8), m.total_size.expected);
try std.testing.expectEqual(@as(usize, 16), m.total_size.got);
}
}
// Lock: `StructInfo` is reflected in MEMORY order — Zig reorders it from source
// order (name, fields, is_protocol, nominal_id) to (fields@0, name@16,
// nominal_id@20, is_protocol@24). The registry must present the fields in that
// memory order, since an sx welded header must declare them so to be
// byte-identical.
test "compiler_lib: StructInfo is reflected in Zig memory order" {
const StructInfoZig = types.TypeInfo.StructInfo;
const bt = compiler_lib.findType("StructInfo").?;
try std.testing.expectEqual(@sizeOf(StructInfoZig), bt.size);
try std.testing.expectEqual(@as(usize, 4), bt.fields.len);
// Memory order: fields, name, nominal_id, is_protocol.
try std.testing.expectEqualStrings("fields", bt.fields[0].name);
try std.testing.expectEqual(@offsetOf(StructInfoZig, "fields"), bt.fields[0].offset);
try std.testing.expectEqualStrings("name", bt.fields[1].name);
try std.testing.expectEqual(@offsetOf(StructInfoZig, "name"), bt.fields[1].offset);
try std.testing.expectEqualStrings("nominal_id", bt.fields[2].name);
try std.testing.expectEqual(@offsetOf(StructInfoZig, "nominal_id"), bt.fields[2].offset);
try std.testing.expectEqualStrings("is_protocol", bt.fields[3].name);
try std.testing.expectEqual(@offsetOf(StructInfoZig, "is_protocol"), bt.fields[3].offset);
// Offsets are strictly ascending (memory order).
try std.testing.expect(bt.fields[0].offset < bt.fields[1].offset);
try std.testing.expect(bt.fields[1].offset < bt.fields[2].offset);
try std.testing.expect(bt.fields[2].offset < bt.fields[3].offset);
}
// Lock: the welded-function export list resolves the round-trip readers and
// rejects unexported names (the boundary the interp's dispatch consults).
test "compiler_lib: findFn resolves exported functions, rejects others" {
try std.testing.expect(compiler_lib.findFn("intern") != null);
try std.testing.expect(compiler_lib.findFn("text_of") != null);
try std.testing.expectEqualStrings("intern", compiler_lib.findFn("intern").?.sx_name);
try std.testing.expect(compiler_lib.findFn("not_exported") == null);
try std.testing.expect(compiler_lib.findFn("") == null);
}