Use extern for serialized structs

This commit is contained in:
Richard Feldman 2025-11-22 12:30:32 -05:00
parent fdc1a9832d
commit bedcc21575
No known key found for this signature in database
26 changed files with 110 additions and 68 deletions

View file

@ -84,8 +84,9 @@ pub fn serialize(
return @constCast(offset_self);
}
/// Serialized representation of ModuleEnv
pub const Serialized = struct {
/// Serialized representation of CommonEnv
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
idents: Ident.Store.Serialized,
strings: StringLiteral.Store.Serialized,
exposed_items: ExposedItems.Serialized,

View file

@ -1,22 +1,23 @@
//! Just a small struct to take a span of data in an array
const DataSpan = @This();
/// DataSpan is used for serialization, so it must be extern struct for consistent layout.
pub const DataSpan = extern struct {
start: u32,
len: u32,
start: u32,
len: u32,
/// Creates an empty DataSpan with zero start and zero length.
pub fn empty() DataSpan {
return DataSpan{ .start = 0, .len = 0 };
}
/// Creates an empty DataSpan with zero start and zero length.
pub fn empty() DataSpan {
return DataSpan{ .start = 0, .len = 0 };
}
/// Creates a DataSpan with the specified start position and length.
pub fn init(start: u32, len: u32) DataSpan {
return DataSpan{ .start = start, .len = len };
}
/// Creates a DataSpan with the specified start position and length.
pub fn init(start: u32, len: u32) DataSpan {
return DataSpan{ .start = start, .len = len };
}
/// Converts this DataSpan into a type that contains a span field.
/// This is useful for creating wrapper types around DataSpan.
pub fn as(self: DataSpan, comptime T: type) T {
return @as(T, .{ .span = self });
}
/// Converts this DataSpan into a type that contains a span field.
/// This is useful for creating wrapper types around DataSpan.
pub fn as(self: DataSpan, comptime T: type) T {
return @as(T, .{ .span = self });
}
};

View file

@ -105,7 +105,8 @@ pub const Store = struct {
next_unique_name: u32 = 0,
/// Serialized representation of an Ident.Store
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
interner: SmallStringInterner.Serialized,
attributes: collections.SafeList(Attributes).Serialized,
next_unique_name: u32,

View file

@ -11,7 +11,7 @@
const std = @import("std");
const testing = std.testing;
const DataSpan = @import("DataSpan.zig");
const DataSpan = @import("DataSpan.zig").DataSpan;
/// Configurable packed DataSpan with customizable bit allocation
pub fn PackedDataSpan(comptime start_bits: u6, comptime length_bits: u6) type {

View file

@ -2,7 +2,7 @@
//! when working with recursive operations
const std = @import("std");
const DataSpan = @import("DataSpan.zig");
const DataSpan = @import("DataSpan.zig").DataSpan;
/// A stack for easily adding and removing index types when doing recursive operations
pub fn Scratch(comptime T: type) type {

View file

@ -204,7 +204,8 @@ pub fn relocate(self: *SmallStringInterner, offset: isize) void {
}
/// Serialized representation of a SmallStringInterner
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
bytes: collections.SafeList(u8).Serialized,
hash_table: collections.SafeList(Idx).Serialized,
entry_count: u32,

View file

@ -97,7 +97,8 @@ pub const Store = struct {
}
/// Serialized representation of a Store
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
buffer: collections.SafeList(u8).Serialized,
/// Serialize a Store into this Serialized struct, appending data to the writer

View file

@ -13,7 +13,7 @@ pub const SmallStringInterner = @import("SmallStringInterner.zig");
pub const safe_memory = @import("safe_memory.zig");
pub const target = @import("target.zig");
pub const DataSpan = @import("DataSpan.zig");
pub const DataSpan = @import("DataSpan.zig").DataSpan;
pub const PackedDataSpan = @import("PackedDataSpan.zig").PackedDataSpan;
pub const FunctionArgs = @import("PackedDataSpan.zig").FunctionArgs;
pub const SmallCollections = @import("PackedDataSpan.zig").SmallCollections;

View file

@ -75,7 +75,7 @@ pub const BuiltinIndices = struct {
/// Represents a definition (binding of a pattern to an expression) in the CIR
pub const Def = struct {
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pub const Span = extern struct { span: base.DataSpan };
pattern: Pattern.Idx,
expr: Expr.Idx,
@ -134,7 +134,7 @@ pub const Def = struct {
/// Represents a type header (e.g., 'Maybe a' or 'Try err ok') in type annotations
pub const TypeHeader = struct {
pub const Idx = enum(u32) { _ };
pub const Span = struct { start: u32, len: u32 };
pub const Span = extern struct { start: u32, len: u32 };
name: base.Ident.Idx,
args: TypeAnno.Span,
@ -170,7 +170,7 @@ pub const TypeHeader = struct {
/// Represents a where clause constraint in type definitions
pub const WhereClause = union(enum) {
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pub const Span = extern struct { span: base.DataSpan };
w_method: struct {
var_: TypeAnno.Idx,
@ -293,7 +293,7 @@ pub const Annotation = struct {
/// Represents an item exposed by a module's interface
pub const ExposedItem = struct {
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pub const Span = extern struct { span: base.DataSpan };
name: base.Ident.Idx,
alias: ?base.Ident.Idx,
@ -321,7 +321,7 @@ pub const ExposedItem = struct {
/// Represents a field in a record pattern for pattern matching
pub const PatternRecordField = struct {
pub const Idx = enum(u32) { _ };
pub const Span = struct { start: u32, len: u32 };
pub const Span = extern struct { start: u32, len: u32 };
};
/// Represents an arbitrary precision smallish decimal value
@ -678,10 +678,11 @@ pub const Import = struct {
self.imports.relocate(offset);
}
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
// Placeholder to match Store size - not serialized
// Reserve space for hashmap (3 pointers for unmanaged hashmap internals)
map: [3]u64 = undefined,
map: [3]u64,
imports: collections.SafeList(base.StringLiteral.Idx).Serialized,
/// Serialize a Store into this Serialized struct, appending data to the writer
@ -730,7 +731,7 @@ pub const Import = struct {
/// Represents a field in a record expression
pub const RecordField = struct {
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pub const Span = extern struct { span: base.DataSpan };
name: base.Ident.Idx,
value: Expr.Idx,
@ -761,7 +762,7 @@ pub const ExternalDecl = struct {
region: Region,
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pub const Span = extern struct { span: base.DataSpan };
/// A safe list of external declarations
pub const SafeList = collections.SafeList(ExternalDecl);

View file

@ -252,7 +252,7 @@ pub const Diagnostic = union(enum) {
},
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pub const Span = extern struct { span: base.DataSpan };
/// Helper to extract the region from any diagnostic variant
pub fn toRegion(self: Diagnostic) Region {

View file

@ -429,7 +429,7 @@ pub const Expr = union(enum) {
};
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: DataSpan };
pub const Span = extern struct { span: DataSpan };
/// A single branch of an if expression.
/// Contains a condition expression and the body to execute if the condition is true.
@ -446,7 +446,7 @@ pub const Expr = union(enum) {
body: Expr.Idx,
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pub const Span = extern struct { span: base.DataSpan };
};
/// A closure, which is a lambda expression that captures variables
@ -1151,7 +1151,7 @@ pub const Expr = union(enum) {
exhaustive: TypeVar,
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pub const Span = extern struct { span: base.DataSpan };
/// A single branch within a match expression.
/// Contains patterns to match against, an optional guard condition,
@ -1210,7 +1210,7 @@ pub const Expr = union(enum) {
}
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: DataSpan };
pub const Span = extern struct { span: DataSpan };
};
/// A pattern within a match branch, which may be part of an OR pattern.
@ -1233,7 +1233,7 @@ pub const Expr = union(enum) {
degenerate: bool,
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pub const Span = extern struct { span: base.DataSpan };
};
pub fn pushToSExprTree(self: *const @This(), ir: *const ModuleEnv, tree: *SExprTree, region: Region) std.mem.Allocator.Error!void {
@ -1267,6 +1267,6 @@ pub const Expr = union(enum) {
scope_depth: u32,
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pub const Span = extern struct { span: base.DataSpan };
};
};

View file

@ -19,7 +19,7 @@ type_idx: ?u32,
/// Index type for referencing external declarations in storage.
pub const Idx = enum(u32) { _ };
/// A span of external declarations stored contiguously in memory.
pub const Span = struct { span: DataSpan };
pub const Span = extern struct { span: DataSpan };
/// Converts this external declaration to an S-expression tree representation for debugging
pub fn pushToSExprTree(self: *const ExternalDecl, cir: anytype, tree: anytype) !void {

View file

@ -1655,8 +1655,8 @@ pub fn getSourceLine(self: *const Self, region: Region) ![]const u8 {
}
/// Serialized representation of ModuleEnv.
/// NOTE: Field order matters for cross-platform compatibility! Keep `module_kind` at the end.
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
gpa: [2]u64, // Reserve space for allocator (vtable ptr + context ptr), provided during deserialization
common: CommonEnv.Serialized,
types: TypeStore.Serialized,
@ -1670,7 +1670,7 @@ pub const Serialized = struct {
module_name_idx_reserved: u32, // Reserved space for module_name_idx field (interned during deserialization)
diagnostics: CIR.Diagnostic.Span,
store: NodeStore.Serialized,
module_kind: ModuleKind,
module_kind: [2]u32, // Serialized ModuleKind (tag + optional payload), decoded via decodeModuleKind
evaluation_order_reserved: u64, // Reserved space for evaluation_order field (required for in-place deserialization cast)
from_int_digits_ident_reserved: u32, // Reserved space for from_int_digits_ident field (interned during deserialization)
from_dec_digits_ident_reserved: u32, // Reserved space for from_dec_digits_ident field (interned during deserialization)
@ -1680,7 +1680,7 @@ pub const Serialized = struct {
plus_ident_reserved: u32, // Reserved space for plus_ident field (interned during deserialization)
minus_ident_reserved: u32, // Reserved space for minus_ident field (interned during deserialization)
times_ident_reserved: u32, // Reserved space for times_ident field (interned during deserialization)
div_by_ident_reserved: u32, // Reserved space for div_by_ident field (interned during deserialization)
div_by_ident_reserved: u32, // Reserved space for div_by_ field (interned during deserialization)
div_trunc_by_ident_reserved: u32, // Reserved space for div_trunc_by_ident field (interned during deserialization)
rem_by_ident_reserved: u32, // Reserved space for rem_by_ident field (interned during deserialization)
negate_ident_reserved: u32, // Reserved space for negate_ident field (interned during deserialization)
@ -1704,7 +1704,7 @@ pub const Serialized = struct {
try self.types.serialize(&env.types, allocator, writer);
// Copy simple values directly
self.module_kind = env.module_kind;
self.module_kind = encodeModuleKind(env.module_kind);
self.all_defs = env.all_defs;
self.all_statements = env.all_statements;
self.exports = env.exports;
@ -1714,7 +1714,6 @@ pub const Serialized = struct {
try self.imports.serialize(&env.imports, allocator, writer);
self.diagnostics = env.diagnostics;
self.module_kind = env.module_kind;
// Serialize NodeStore
try self.store.serialize(&env.store, allocator, writer);
@ -1772,7 +1771,7 @@ pub const Serialized = struct {
.gpa = gpa,
.common = common,
.types = self.types.deserialize(offset, gpa).*,
.module_kind = self.module_kind,
.module_kind = decodeModuleKind(self.module_kind),
.all_defs = self.all_defs,
.all_statements = self.all_statements,
.exports = self.exports,
@ -1809,6 +1808,35 @@ pub const Serialized = struct {
return env;
}
/// Encode a ModuleKind to a serializable [2]u32 representation
pub fn encodeModuleKind(kind: ModuleKind) [2]u32 {
return switch (kind) {
.type_module => |idx| .{ 0, @bitCast(idx) },
.default_app => .{ 1, 0 },
.app => .{ 2, 0 },
.package => .{ 3, 0 },
.platform => .{ 4, 0 },
.hosted => .{ 5, 0 },
.deprecated_module => .{ 6, 0 },
.malformed => .{ 7, 0 },
};
}
/// Decode a [2]u32 representation back to a ModuleKind
pub fn decodeModuleKind(encoded: [2]u32) ModuleKind {
return switch (encoded[0]) {
0 => .{ .type_module = @bitCast(encoded[1]) },
1 => .default_app,
2 => .app,
3 => .package,
4 => .platform,
5 => .hosted,
6 => .deprecated_module,
7 => .malformed,
else => unreachable,
};
}
};
/// Convert a type into a node index

View file

@ -3399,7 +3399,8 @@ pub fn matchBranchPatternSpanFrom(store: *NodeStore, start: u32) Allocator.Error
}
/// Serialized representation of NodeStore
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
gpa: [2]u64, // Reserve enough space for 2 64-bit pointers
nodes: Node.List.Serialized,
regions: Region.List.Serialized,

View file

@ -231,7 +231,7 @@ pub const Pattern = union(enum) {
},
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pub const Span = extern struct { span: base.DataSpan };
/// Represents the destructuring of a single field within a record pattern.
/// Each record destructure specifies how to extract a field from a record.
@ -247,7 +247,7 @@ pub const Pattern = union(enum) {
kind: Kind,
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pub const Span = extern struct { span: base.DataSpan };
/// The kind of record field destructuring pattern.
pub const Kind = union(enum) {

View file

@ -178,7 +178,7 @@ pub const Statement = union(enum) {
},
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: DataSpan };
pub const Span = extern struct { span: DataSpan };
pub fn pushToSExprTree(self: *const @This(), env: *const ModuleEnv, tree: *SExprTree, stmt_idx: Statement.Idx) std.mem.Allocator.Error!void {
switch (self.*) {

View file

@ -97,7 +97,7 @@ pub const TypeAnno = union(enum) {
},
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: DataSpan };
pub const Span = extern struct { span: DataSpan };
pub fn pushToSExprTree(self: *const @This(), ir: *const ModuleEnv, tree: *SExprTree, type_anno_idx: TypeAnno.Idx) std.mem.Allocator.Error!void {
switch (self.*) {
@ -325,7 +325,7 @@ pub const TypeAnno = union(enum) {
ty: TypeAnno.Idx,
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: DataSpan };
pub const Span = extern struct { span: DataSpan };
};
/// Either a locally declare type, or an external type

View file

@ -73,7 +73,7 @@ fn loadCompiledModule(gpa: std.mem.Allocator, bin_data: []const u8, module_name:
.gpa = gpa,
.common = common,
.types = serialized_ptr.types.deserialize(@as(i64, @intCast(base_ptr)), gpa).*, // Pass gpa to types deserialize
.module_kind = serialized_ptr.module_kind,
.module_kind = ModuleEnv.Serialized.decodeModuleKind(serialized_ptr.module_kind),
.all_defs = serialized_ptr.all_defs,
.all_statements = serialized_ptr.all_statements,
.exports = serialized_ptr.exports,

View file

@ -106,7 +106,8 @@ pub const ExposedItems = struct {
}
/// Serialized representation of ExposedItems
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
items: SortedArrayBuilder(IdentIdx, u16).Serialized,
/// Serialize an ExposedItems into this Serialized struct, appending data to the writer

View file

@ -272,7 +272,8 @@ pub fn SortedArrayBuilder(comptime K: type, comptime V: type) type {
}
/// Serialized representation of SortedArrayBuilder
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
entries_offset: i64,
entries_len: u64,
entries_capacity: u64,

View file

@ -112,7 +112,8 @@ pub fn SafeList(comptime T: type) type {
};
/// Serialized representation of a SafeList
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
offset: i64,
len: u64,
capacity: u64,
@ -592,7 +593,8 @@ pub fn SafeMultiList(comptime T: type) type {
}
/// Serialized representation of a SafeMultiList
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
offset: i64,
len: u64,
capacity: u64,
@ -905,7 +907,7 @@ test "SafeList edge cases serialization" {
const Self = @This();
pub const Serialized = struct {
pub const Serialized = extern struct {
list_u32: SafeList(u32).Serialized,
list_u8: SafeList(u8).Serialized,

View file

@ -90,7 +90,7 @@ test "ModuleEnv.Serialized roundtrip" {
.gpa = gpa,
.common = common,
.types = deserialized_ptr.types.deserialize(@as(i64, @intCast(@intFromPtr(buffer.ptr))), gpa).*,
.module_kind = deserialized_ptr.module_kind,
.module_kind = ModuleEnv.Serialized.decodeModuleKind(deserialized_ptr.module_kind),
.all_defs = deserialized_ptr.all_defs,
.all_statements = deserialized_ptr.all_statements,
.exports = deserialized_ptr.exports,

View file

@ -65,7 +65,7 @@ pub fn loadCompiledModule(gpa: std.mem.Allocator, bin_data: []const u8, module_n
.gpa = gpa,
.common = common,
.types = serialized_ptr.types.deserialize(@as(i64, @intCast(base_ptr)), gpa).*, // Pass gpa to types deserialize
.module_kind = serialized_ptr.module_kind,
.module_kind = ModuleEnv.Serialized.decodeModuleKind(serialized_ptr.module_kind),
.all_defs = serialized_ptr.all_defs,
.all_statements = serialized_ptr.all_statements,
.exports = serialized_ptr.exports,

View file

@ -949,7 +949,7 @@ fn compileSource(source: []const u8) !CompilerStageData {
.gpa = gpa,
.common = common,
.types = serialized_ptr.types.deserialize(@as(i64, @intCast(base_ptr)), gpa).*,
.module_kind = serialized_ptr.module_kind,
.module_kind = ModuleEnv.Serialized.decodeModuleKind(serialized_ptr.module_kind),
.all_defs = serialized_ptr.all_defs,
.all_statements = serialized_ptr.all_statements,
.exports = serialized_ptr.exports,

View file

@ -76,7 +76,7 @@ fn loadCompiledModule(gpa: std.mem.Allocator, bin_data: []const u8, module_name:
.gpa = gpa,
.common = common,
.types = serialized_ptr.types.deserialize(@as(i64, @intCast(base_ptr)), gpa).*, // Pass gpa to types deserialize
.module_kind = serialized_ptr.module_kind,
.module_kind = ModuleEnv.Serialized.decodeModuleKind(serialized_ptr.module_kind),
.all_defs = serialized_ptr.all_defs,
.all_statements = serialized_ptr.all_statements,
.exports = serialized_ptr.exports,

View file

@ -720,7 +720,8 @@ pub const Store = struct {
// serialization //
/// Serialized representation of types store
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
gpa: [2]u64, // Reserve space for allocator (vtable ptr + context ptr), provided during deserialization
slots: SlotStore.Serialized,
descs: DescStore.Serialized,
@ -922,7 +923,8 @@ const SlotStore = struct {
}
/// Serialized representation of SlotStore
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
backing: collections.SafeList(Slot).Serialized,
/// Serialize a SlotStore into this Serialized struct, appending data to the writer
@ -1024,7 +1026,8 @@ const DescStore = struct {
}
/// Serialized representation of DescStore
pub const Serialized = struct {
/// Uses extern struct to guarantee consistent field layout across optimization levels.
pub const Serialized = extern struct {
backing: DescSafeMultiList.Serialized,
/// Serialize a DescStore into this Serialized struct, appending data to the writer