Skip to content

Commit 7bed6e5

Browse files
authored
Merge pull request #8302 from roc-lang/import-builtins
Import builtins from .roc files
2 parents a9e869f + 204f1d9 commit 7bed6e5

File tree

187 files changed

+7080
-3777
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

187 files changed

+7080
-3777
lines changed

.github/workflows/ci_zig.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ jobs:
6464
run: |
6565
zig build test-playground -Doptimize=ReleaseSmall -- --verbose
6666
67+
- name: Verify Serialized Type Sizes
68+
run: |
69+
zig build test-serialization-sizes
70+
6771
zig-tests:
6872
needs: check-once
6973
runs-on: ${{ matrix.os }}

build.zig

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@ pub fn build(b: *std.Build) void {
237237
}
238238
}
239239

240+
// Also copy builtin_indices.bin
241+
_ = write_compiled_builtins.addCopyFile(
242+
.{ .cwd_relative = "zig-out/builtins/builtin_indices.bin" },
243+
"builtin_indices.bin",
244+
);
245+
240246
// Generate compiled_builtins.zig dynamically based on discovered .roc files
241247
const builtins_source_str = generateCompiledBuiltinsSource(b, roc_files) catch |err| {
242248
std.debug.print("Failed to generate compiled_builtins.zig: {}\n", .{err});
@@ -252,6 +258,9 @@ pub fn build(b: *std.Build) void {
252258
.root_source_file = compiled_builtins_source,
253259
});
254260

261+
roc_modules.repl.addImport("compiled_builtins", compiled_builtins_module);
262+
roc_modules.compile.addImport("compiled_builtins", compiled_builtins_module);
263+
255264
// Manual rebuild command: zig build rebuild-builtins
256265
// Use this after making compiler changes to ensure those changes are reflected in builtins
257266
const rebuild_builtins_step = b.step(
@@ -334,7 +343,11 @@ pub fn build(b: *std.Build) void {
334343
}));
335344
playground_exe.entry = .disabled;
336345
playground_exe.rdynamic = true;
346+
playground_exe.link_function_sections = true;
347+
playground_exe.import_memory = false;
337348
roc_modules.addAll(playground_exe);
349+
playground_exe.root_module.addImport("compiled_builtins", compiled_builtins_module);
350+
playground_exe.step.dependOn(&write_compiled_builtins.step);
338351

339352
add_tracy(b, roc_modules.build_options, playground_exe, b.resolveTargetQuery(.{
340353
.cpu_arch = .wasm32,
@@ -429,8 +442,8 @@ pub fn build(b: *std.Build) void {
429442
const tests_summary = TestsSummaryStep.create(b);
430443
const module_tests = roc_modules.createModuleTests(b, target, optimize, zstd, test_filters);
431444
for (module_tests) |module_test| {
432-
// Add compiled builtins to check module tests
433-
if (std.mem.eql(u8, module_test.test_step.name, "check")) {
445+
// Add compiled builtins to check, repl, and eval module tests
446+
if (std.mem.eql(u8, module_test.test_step.name, "check") or std.mem.eql(u8, module_test.test_step.name, "repl") or std.mem.eql(u8, module_test.test_step.name, "eval")) {
434447
module_test.test_step.root_module.addImport("compiled_builtins", compiled_builtins_module);
435448
module_test.test_step.step.dependOn(&write_compiled_builtins.step);
436449
}
@@ -496,6 +509,8 @@ pub fn build(b: *std.Build) void {
496509
roc_modules.addAll(cli_test);
497510
cli_test.linkLibrary(zstd.artifact("zstd"));
498511
add_tracy(b, roc_modules.build_options, cli_test, target, false, flag_enable_tracy);
512+
cli_test.root_module.addImport("compiled_builtins", compiled_builtins_module);
513+
cli_test.step.dependOn(&write_compiled_builtins.step);
499514

500515
const run_cli_test = b.addRunArtifact(cli_test);
501516
if (run_args.len != 0) {
@@ -633,6 +648,9 @@ fn generateCompiledBuiltinsSource(b: *std.Build, roc_files: []const []const u8)
633648
});
634649
}
635650

651+
// Also embed builtin_indices.bin
652+
try writer.writeAll("pub const builtin_indices_bin = @embedFile(\"builtin_indices.bin\");\n");
653+
636654
return builtins_source.toOwnedSlice();
637655
}
638656

src/base/CommonEnv.zig

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,6 @@ pub fn serialize(
8484
return @constCast(offset_self);
8585
}
8686

87-
/// Freezes the identifier and string interners, preventing further modifications.
88-
/// This is used to ensure thread safety when sharing the environment across threads.
89-
pub fn freezeInterners(self: *CommonEnv) void {
90-
self.idents.freeze();
91-
self.strings.freeze();
92-
}
93-
9487
/// Serialized representation of ModuleEnv
9588
pub const Serialized = struct {
9689
idents: Ident.Store.Serialized,
@@ -123,10 +116,10 @@ pub const Serialized = struct {
123116
offset: i64,
124117
source: []const u8,
125118
) *CommonEnv {
126-
// CommonEnv.Serialized should be at least as big as CommonEnv
127-
std.debug.assert(@sizeOf(Serialized) >= @sizeOf(CommonEnv));
128-
129-
// Overwrite ourself with the deserialized version, and return our pointer after casting it to CommonEnv.
119+
// Note: Serialized may be smaller than the runtime struct because:
120+
// - Uses i64 offsets instead of usize pointers (same size on 64-bit, but conceptually different)
121+
// - May have different alignment/padding requirements
122+
// We deserialize by overwriting the Serialized memory with the runtime struct.
130123
const env = @as(*CommonEnv, @ptrFromInt(@intFromPtr(self)));
131124

132125
env.* = CommonEnv{

src/base/Ident.zig

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,8 @@ pub const Store = struct {
115115

116116
/// Deserialize this Serialized struct into a Store
117117
pub fn deserialize(self: *Serialized, offset: i64) *Store {
118-
// Ident.Store.Serialized should be at least as big as Ident.Store
119-
std.debug.assert(@sizeOf(Serialized) >= @sizeOf(Store));
120-
121-
// Overwrite ourself with the deserialized version, and return our pointer after casting it to Self.
118+
// Note: Serialized may be smaller than the runtime struct.
119+
// We deserialize by overwriting the Serialized memory with the runtime struct.
122120
const store = @as(*Store, @ptrFromInt(@intFromPtr(self)));
123121

124122
store.* = Store{
@@ -226,10 +224,6 @@ pub const Store = struct {
226224
};
227225
}
228226

229-
/// Freeze the identifier store, preventing any new entries from being added.
230-
pub fn freeze(self: *Store) void {
231-
self.interner.freeze();
232-
}
233227
/// Calculate the size needed to serialize this Ident.Store
234228
pub fn serializedSize(self: *const Store) usize {
235229
var size: usize = 0;
@@ -535,7 +529,7 @@ test "Ident.Store with genUnique CompactWriter roundtrip" {
535529
try std.testing.expectEqual(@as(u32, 3), deserialized.next_unique_name);
536530
}
537531

538-
test "Ident.Store frozen state CompactWriter roundtrip" {
532+
test "Ident.Store CompactWriter roundtrip" {
539533
const gpa = std.testing.allocator;
540534

541535
// Create and populate store
@@ -545,14 +539,6 @@ test "Ident.Store frozen state CompactWriter roundtrip" {
545539
_ = try original.insert(gpa, Ident.for_text("test1"));
546540
_ = try original.insert(gpa, Ident.for_text("test2"));
547541

548-
// Freeze the store
549-
original.freeze();
550-
551-
// Verify interner is frozen
552-
if (std.debug.runtime_safety) {
553-
try std.testing.expect(original.interner.frozen);
554-
}
555-
556542
// Create a temp file
557543
var tmp_dir = std.testing.tmpDir(.{});
558544
defer tmp_dir.cleanup();
@@ -591,11 +577,6 @@ test "Ident.Store frozen state CompactWriter roundtrip" {
591577
const deserialized = @as(*Ident.Store, @ptrCast(@alignCast(buffer.ptr)));
592578

593579
deserialized.relocate(@as(isize, @intCast(@intFromPtr(buffer.ptr))));
594-
595-
// Verify frozen state is preserved
596-
if (std.debug.runtime_safety) {
597-
try std.testing.expect(deserialized.interner.frozen);
598-
}
599580
}
600581

601582
test "Ident.Store comprehensive CompactWriter roundtrip" {

src/base/SmallStringInterner.zig

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ bytes: collections.SafeList(u8) = .{},
2525
hash_table: collections.SafeList(Idx) = .{},
2626
/// The current number of entries in the hash table.
2727
entry_count: u32 = 0,
28-
/// When true, no new entries can be added to the interner.
29-
/// This is set after parsing is complete.
30-
frozen: if (std.debug.runtime_safety) bool else void = if (std.debug.runtime_safety) false else {},
3128

3229
/// A unique index for a deduped string in this interner.
3330
pub const Idx = enum(u32) {
@@ -134,10 +131,6 @@ fn resizeHashTable(self: *SmallStringInterner, gpa: std.mem.Allocator) std.mem.A
134131

135132
/// Add a string to this interner, returning a unique, serial index.
136133
pub fn insert(self: *SmallStringInterner, gpa: std.mem.Allocator, string: []const u8) std.mem.Allocator.Error!Idx {
137-
if (std.debug.runtime_safety) {
138-
std.debug.assert(!self.frozen); // Should not insert into a frozen interner
139-
}
140-
141134
// Check if we need to resize the hash table (when 80% full = entry_count * 5 >= hash_table.len() * 4)
142135
if (self.entry_count * 5 >= self.hash_table.len() * 4) {
143136
try self.resizeHashTable(gpa);
@@ -178,13 +171,6 @@ pub fn getText(self: *const SmallStringInterner, idx: Idx) []u8 {
178171
return std.mem.sliceTo(bytes_slice[start..], 0);
179172
}
180173

181-
/// Freeze the interner, preventing any new entries from being added.
182-
pub fn freeze(self: *SmallStringInterner) void {
183-
if (std.debug.runtime_safety) {
184-
self.frozen = true;
185-
}
186-
}
187-
188174
/// Serialize this interner to the given CompactWriter. The resulting interner
189175
/// in the writer's buffer will have offsets instead of pointers. Calling any
190176
/// methods on it or dereferencing its internal "pointers" (which are now
@@ -205,7 +191,6 @@ pub fn serialize(
205191
.bytes = serialized_bytes.*,
206192
.hash_table = serialized_hash_table.*,
207193
.entry_count = self.entry_count,
208-
.frozen = self.frozen,
209194
};
210195

211196
// Return the version of Self that's in the writer's buffer
@@ -223,7 +208,6 @@ pub const Serialized = struct {
223208
bytes: collections.SafeList(u8).Serialized,
224209
hash_table: collections.SafeList(Idx).Serialized,
225210
entry_count: u32,
226-
frozen: if (std.debug.runtime_safety) bool else void,
227211

228212
/// Serialize a SmallStringInterner into this Serialized struct, appending data to the writer
229213
pub fn serialize(
@@ -238,22 +222,17 @@ pub const Serialized = struct {
238222
try self.hash_table.serialize(&interner.hash_table, allocator, writer);
239223
// Copy simple values directly
240224
self.entry_count = interner.entry_count;
241-
self.frozen = interner.frozen;
242225
}
243226

244227
/// Deserialize this Serialized struct into a SmallStringInterner
245228
pub fn deserialize(self: *Serialized, offset: i64) *SmallStringInterner {
246-
// Self.Serialized should be at least as big as Self
247-
std.debug.assert(@sizeOf(Serialized) >= @sizeOf(SmallStringInterner));
248-
249229
// Overwrite ourself with the deserialized version, and return our pointer after casting it to Self.
250230
const interner = @as(*SmallStringInterner, @ptrCast(self));
251231

252232
interner.* = .{
253233
.bytes = self.bytes.deserialize(offset).*,
254234
.hash_table = self.hash_table.deserialize(offset).*,
255235
.entry_count = self.entry_count,
256-
.frozen = self.frozen,
257236
};
258237

259238
return interner;
@@ -460,7 +439,7 @@ test "SmallStringInterner with populated hashmap CompactWriter roundtrip" {
460439
try std.testing.expect(original_entry_count > 0);
461440
}
462441

463-
test "SmallStringInterner frozen state CompactWriter roundtrip" {
442+
test "SmallStringInterner CompactWriter roundtrip" {
464443
const gpa = std.testing.allocator;
465444

466445
// Create and populate interner
@@ -470,14 +449,6 @@ test "SmallStringInterner frozen state CompactWriter roundtrip" {
470449
_ = try original.insert(gpa, "test1");
471450
_ = try original.insert(gpa, "test2");
472451

473-
// Freeze the interner
474-
original.freeze();
475-
476-
// Verify it's frozen
477-
if (std.debug.runtime_safety) {
478-
try std.testing.expect(original.frozen);
479-
}
480-
481452
// Create a temp file
482453
var tmp_dir = std.testing.tmpDir(.{});
483454
defer tmp_dir.cleanup();
@@ -510,11 +481,6 @@ test "SmallStringInterner frozen state CompactWriter roundtrip" {
510481
const deserialized = @as(*SmallStringInterner, @ptrCast(@alignCast(buffer.ptr)));
511482
deserialized.relocate(@as(isize, @intCast(@intFromPtr(buffer.ptr))));
512483

513-
// Verify frozen state is preserved
514-
if (std.debug.runtime_safety) {
515-
try std.testing.expect(deserialized.frozen);
516-
}
517-
518484
// Verify strings are still accessible
519485
// Note: Index 0 is reserved for the unused marker, so strings start at index 1
520486
try std.testing.expectEqualStrings("test1", deserialized.getText(@enumFromInt(1)));
@@ -682,14 +648,11 @@ test "SmallStringInterner edge cases CompactWriter roundtrip" {
682648
// try std.testing.expectEqualStrings("interner1_string2", deserialized1.getText(idx1_2));
683649
// try std.testing.expectEqual(@as(u32, 2), deserialized1.entry_count);
684650

685-
// // Verify interner 2 (frozen)
651+
// // Verify interner 2
686652
// try std.testing.expectEqualStrings("interner2_string1", deserialized2.getText(idx2_1));
687653
// try std.testing.expectEqualStrings("interner2_string2", deserialized2.getText(idx2_2));
688654
// try std.testing.expectEqualStrings("interner2_string3", deserialized2.getText(idx2_3));
689655
// try std.testing.expectEqual(@as(u32, 3), deserialized2.entry_count);
690-
// if (std.debug.runtime_safety) {
691-
// try std.testing.expect(deserialized2.frozen);
692-
// }
693656

694657
// // Verify interner 3
695658
// try std.testing.expectEqualStrings("interner3_string1", deserialized3.getText(idx3_1));

0 commit comments

Comments
 (0)