Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,9 @@ pub fn build(b: *std.Build) void {
}
// Ensure host library is copied before running the test
run_fx_platform_test.step.dependOn(&copy_test_fx_host.step);
// Ensure roc binary is installed before running the test (test uses ./zig-out/bin/roc)
const install_roc_for_fx_test = b.addInstallArtifact(roc_exe, .{});
run_fx_platform_test.step.dependOn(&install_roc_for_fx_test.step);
tests_summary.addRun(&run_fx_platform_test.step);
}

Expand Down
26 changes: 17 additions & 9 deletions src/base/CommonEnv.zig
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,26 @@ pub const Serialized = extern struct {
offset: i64,
source: []const u8,
) *CommonEnv {
// Note: Serialized may be smaller than the runtime struct because:
// - Uses i64 offsets instead of usize pointers (same size on 64-bit, but conceptually different)
// - May have different alignment/padding requirements
// We deserialize by overwriting the Serialized memory with the runtime struct.
// CRITICAL: We must deserialize ALL fields into local variables BEFORE writing to the
// output struct. This is because CommonEnv is a regular struct (not extern), so Zig may
// reorder its fields differently than Serialized (which is extern). If we read from self
// while writing to env (which aliases self), we may read corrupted data in Release mode
// when field orderings differ.

// Step 1: Deserialize all fields into local variables first
const deserialized_idents = self.idents.deserialize(offset).*;
const deserialized_strings = self.strings.deserialize(offset).*;
const deserialized_exposed_items = self.exposed_items.deserialize(offset).*;
const deserialized_line_starts = self.line_starts.deserialize(offset).*;

// Step 2: Overwrite ourself with the deserialized version
const env = @as(*CommonEnv, @ptrFromInt(@intFromPtr(self)));

env.* = CommonEnv{
.idents = self.idents.deserialize(offset).*,
// .ident_ids_for_slicing = self.ident_ids_for_slicing.deserialize(offset).*,
.strings = self.strings.deserialize(offset).*,
.exposed_items = self.exposed_items.deserialize(offset).*,
.line_starts = self.line_starts.deserialize(offset).*,
.idents = deserialized_idents,
.strings = deserialized_strings,
.exposed_items = deserialized_exposed_items,
.line_starts = deserialized_line_starts,
.source = source,
};

Expand Down
26 changes: 16 additions & 10 deletions src/base/Ident.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ const CompactWriter = collections.CompactWriter;

const Ident = @This();

/// Method name for parsing integers from digit lists - used by numeric literal type checking
pub const FROM_INT_DIGITS_METHOD_NAME = "from_int_digits";
/// Method name for parsing decimals from digit lists - used by numeric literal type checking
pub const FROM_DEC_DIGITS_METHOD_NAME = "from_dec_digits";
/// Method name for addition - used by + operator desugaring
pub const PLUS_METHOD_NAME = "plus";
/// Method name for negation - used by unary - operator desugaring
Expand Down Expand Up @@ -124,15 +120,25 @@ pub const Store = struct {
}

/// Deserialize this Serialized struct into a Store
pub fn deserialize(self: *Serialized, offset: i64) *Store {
// Note: Serialized may be smaller than the runtime struct.
// We deserialize by overwriting the Serialized memory with the runtime struct.
pub noinline fn deserialize(self: *Serialized, offset: i64) *Store {
// CRITICAL: We must deserialize ALL fields into local variables BEFORE writing to the
// output struct. This is because Store is a regular struct (not extern), so Zig may
// reorder its fields differently than Serialized (which is extern). If we read from self
// while writing to store (which aliases self), we may read corrupted data in Release mode
// when field orderings differ.

// Step 1: Deserialize all fields into local variables first
const deserialized_interner = self.interner.deserialize(offset).*;
const deserialized_attributes = self.attributes.deserialize(offset).*;
const deserialized_next_unique_name = self.next_unique_name;

// Step 2: Overwrite ourself with the deserialized version
const store = @as(*Store, @ptrFromInt(@intFromPtr(self)));

store.* = Store{
.interner = self.interner.deserialize(offset).*,
.attributes = self.attributes.deserialize(offset).*,
.next_unique_name = self.next_unique_name,
.interner = deserialized_interner,
.attributes = deserialized_attributes,
.next_unique_name = deserialized_next_unique_name,
};

return store;
Expand Down
21 changes: 16 additions & 5 deletions src/base/SmallStringInterner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,25 @@ pub const Serialized = extern struct {
}

/// Deserialize this Serialized struct into a SmallStringInterner
pub fn deserialize(self: *Serialized, offset: i64) *SmallStringInterner {
// Overwrite ourself with the deserialized version, and return our pointer after casting it to Self.
pub noinline fn deserialize(self: *Serialized, offset: i64) *SmallStringInterner {
// CRITICAL: We must deserialize ALL fields into local variables BEFORE writing to the
// output struct. This is because SmallStringInterner is a regular struct (not extern), so Zig may
// reorder its fields differently than Serialized (which is extern). If we read from self
// while writing to interner (which aliases self), we may read corrupted data in Release mode
// when field orderings differ.

// Step 1: Deserialize all fields into local variables first
const deserialized_bytes = self.bytes.deserialize(offset).*;
const deserialized_hash_table = self.hash_table.deserialize(offset).*;
const deserialized_entry_count = self.entry_count;

// Step 2: Overwrite ourself with the deserialized version
const interner = @as(*SmallStringInterner, @ptrCast(self));

interner.* = .{
.bytes = self.bytes.deserialize(offset).*,
.hash_table = self.hash_table.deserialize(offset).*,
.entry_count = self.entry_count,
.bytes = deserialized_bytes,
.hash_table = deserialized_hash_table,
.entry_count = deserialized_entry_count,
};

return interner;
Expand Down
12 changes: 9 additions & 3 deletions src/base/StringLiteral.zig
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,18 @@ pub const Store = struct {
}

/// Deserialize this Serialized struct into a Store
pub fn deserialize(self: *Serialized, offset: i64) *Store {
// Overwrite ourself with the deserialized version, and return our pointer after casting it to Self.
pub noinline fn deserialize(self: *Serialized, offset: i64) *Store {
// CRITICAL: We must deserialize ALL fields into local variables BEFORE writing to the
// output struct to avoid aliasing issues in Release mode.

// Step 1: Deserialize all fields into local variables first
const deserialized_buffer = self.buffer.deserialize(offset).*;

// Step 2: Overwrite ourself with the deserialized version
const store = @as(*Store, @ptrFromInt(@intFromPtr(self)));

store.* = Store{
.buffer = self.buffer.deserialize(offset).*,
.buffer = deserialized_buffer,
};

return store;
Expand Down
25 changes: 1 addition & 24 deletions src/build/builtin_compiler/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -236,29 +236,6 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) {
}
}

// Numeric parsing operations (all numeric types have from_int_digits)
for (numeric_types) |num_type| {
var buf: [256]u8 = undefined;

// from_int_digits
const from_int_digits = try std.fmt.bufPrint(&buf, "Builtin.Num.{s}.from_int_digits", .{num_type});
if (env.common.findIdent(from_int_digits)) |ident| {
try low_level_map.put(ident, .num_from_int_digits);
}
}

// from_dec_digits (Dec, F32, F64 only)
const dec_types = [_][]const u8{ "Dec", "F32", "F64" };
for (dec_types) |num_type| {
var buf: [256]u8 = undefined;

// from_dec_digits
const from_dec_digits = try std.fmt.bufPrint(&buf, "Builtin.Num.{s}.from_dec_digits", .{num_type});
if (env.common.findIdent(from_dec_digits)) |ident| {
try low_level_map.put(ident, .num_from_dec_digits);
}
}

// from_numeral (all numeric types)
for (numeric_types) |num_type| {
var buf: [256]u8 = undefined;
Expand Down Expand Up @@ -341,7 +318,7 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) {
// Create parameter patterns for the lambda
// Binary operations need 2 parameters, unary operations need 1
const num_params: u32 = switch (low_level_op) {
.num_negate, .num_is_zero, .num_is_negative, .num_is_positive, .num_from_numeral, .num_from_int_digits, .u8_to_str, .i8_to_str, .u16_to_str, .i16_to_str, .u32_to_str, .i32_to_str, .u64_to_str, .i64_to_str, .u128_to_str, .i128_to_str, .dec_to_str, .f32_to_str, .f64_to_str => 1,
.num_negate, .num_is_zero, .num_is_negative, .num_is_positive, .num_from_numeral, .u8_to_str, .i8_to_str, .u16_to_str, .i16_to_str, .u32_to_str, .i32_to_str, .u64_to_str, .i64_to_str, .u128_to_str, .i128_to_str, .dec_to_str, .f32_to_str, .f64_to_str => 1,
else => 2, // Most numeric operations are binary
};

Expand Down
18 changes: 1 addition & 17 deletions src/build/roc/Builtin.roc
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Builtin :: [].{

Box(item) :: [ProvidedByCompiler].{}

Try(ok, err) := [Ok(ok), Err(err)].{
Try(ok, err) := [Err(err), Ok(ok)].{
is_ok : Try(_ok, _err) -> Bool
is_ok = |try| match try {
Ok(_) => True
Expand Down Expand Up @@ -154,7 +154,6 @@ Builtin :: [].{
div_trunc_by : U8, U8 -> U8
rem_by : U8, U8 -> U8

from_int_digits : List(U8) -> Try(U8, [OutOfRange])
from_numeral : Numeral -> Try(U8, [InvalidNumeral(Str)])
}

Expand All @@ -177,7 +176,6 @@ Builtin :: [].{
div_trunc_by : I8, I8 -> I8
rem_by : I8, I8 -> I8

from_int_digits : List(U8) -> Try(I8, [OutOfRange])
from_numeral : Numeral -> Try(I8, [InvalidNumeral(Str)])
}

Expand All @@ -197,7 +195,6 @@ Builtin :: [].{
div_trunc_by : U16, U16 -> U16
rem_by : U16, U16 -> U16

from_int_digits : List(U8) -> Try(U16, [OutOfRange])
from_numeral : Numeral -> Try(U16, [InvalidNumeral(Str)])
}

Expand All @@ -220,7 +217,6 @@ Builtin :: [].{
div_trunc_by : I16, I16 -> I16
rem_by : I16, I16 -> I16

from_int_digits : List(U8) -> Try(I16, [OutOfRange])
from_numeral : Numeral -> Try(I16, [InvalidNumeral(Str)])
}

Expand All @@ -240,7 +236,6 @@ Builtin :: [].{
div_trunc_by : U32, U32 -> U32
rem_by : U32, U32 -> U32

from_int_digits : List(U8) -> Try(U32, [OutOfRange])
from_numeral : Numeral -> Try(U32, [InvalidNumeral(Str)])
}

Expand All @@ -263,7 +258,6 @@ Builtin :: [].{
div_trunc_by : I32, I32 -> I32
rem_by : I32, I32 -> I32

from_int_digits : List(U8) -> Try(I32, [OutOfRange])
from_numeral : Numeral -> Try(I32, [InvalidNumeral(Str)])
}

Expand All @@ -283,7 +277,6 @@ Builtin :: [].{
div_trunc_by : U64, U64 -> U64
rem_by : U64, U64 -> U64

from_int_digits : List(U8) -> Try(U64, [OutOfRange])
from_numeral : Numeral -> Try(U64, [InvalidNumeral(Str)])
}

Expand All @@ -306,7 +299,6 @@ Builtin :: [].{
div_trunc_by : I64, I64 -> I64
rem_by : I64, I64 -> I64

from_int_digits : List(U8) -> Try(I64, [OutOfRange])
from_numeral : Numeral -> Try(I64, [InvalidNumeral(Str)])
}

Expand All @@ -326,7 +318,6 @@ Builtin :: [].{
div_trunc_by : U128, U128 -> U128
rem_by : U128, U128 -> U128

from_int_digits : List(U8) -> Try(U128, [OutOfRange])
from_numeral : Numeral -> Try(U128, [InvalidNumeral(Str)])
}

Expand All @@ -349,7 +340,6 @@ Builtin :: [].{
div_trunc_by : I128, I128 -> I128
rem_by : I128, I128 -> I128

from_int_digits : List(U8) -> Try(I128, [OutOfRange])
from_numeral : Numeral -> Try(I128, [InvalidNumeral(Str)])
}

Expand All @@ -373,8 +363,6 @@ Builtin :: [].{
div_trunc_by : Dec, Dec -> Dec
rem_by : Dec, Dec -> Dec

from_int_digits : List(U8) -> Try(Dec, [OutOfRange])
from_dec_digits : (List(U8), List(U8)) -> Try(Dec, [OutOfRange])
from_numeral : Numeral -> Try(Dec, [InvalidNumeral(Str)])
}

Expand All @@ -396,8 +384,6 @@ Builtin :: [].{
div_trunc_by : F32, F32 -> F32
rem_by : F32, F32 -> F32

from_int_digits : List(U8) -> Try(F32, [OutOfRange])
from_dec_digits : (List(U8), List(U8)) -> Try(F32, [OutOfRange])
from_numeral : Numeral -> Try(F32, [InvalidNumeral(Str)])
}

Expand All @@ -419,8 +405,6 @@ Builtin :: [].{
div_trunc_by : F64, F64 -> F64
rem_by : F64, F64 -> F64

from_int_digits : List(U8) -> Try(F64, [OutOfRange])
from_dec_digits : (List(U8), List(U8)) -> Try(F64, [OutOfRange])
from_numeral : Numeral -> Try(F64, [InvalidNumeral(Str)])
}
}
Expand Down
10 changes: 7 additions & 3 deletions src/canonicalize/CIR.zig
Original file line number Diff line number Diff line change
Expand Up @@ -704,13 +704,17 @@ pub const Import = struct {
}

/// Deserialize this Serialized struct into a Store
pub fn deserialize(self: *Serialized, offset: i64, allocator: std.mem.Allocator) std.mem.Allocator.Error!*Store {
// Overwrite ourself with the deserialized version, and return our pointer after casting it to Store.
pub noinline fn deserialize(self: *Serialized, offset: i64, allocator: std.mem.Allocator) std.mem.Allocator.Error!*Store {
// CRITICAL: Deserialize nested struct BEFORE casting and writing to store
// to avoid aliasing issues in Release mode.
const deserialized_imports = self.imports.deserialize(offset).*;

// Now we can cast and write to the destination
const store = @as(*Store, @ptrFromInt(@intFromPtr(self)));

store.* = .{
.map = .{}, // Will be repopulated below
.imports = self.imports.deserialize(offset).*,
.imports = deserialized_imports,
};

// Pre-allocate the exact capacity needed for the map
Expand Down
2 changes: 0 additions & 2 deletions src/canonicalize/Expression.zig
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,6 @@ pub const Expr = union(enum) {
num_rem_by, // All numeric types

// Numeric parsing operations
num_from_int_digits, // Parse List(U8) -> Try(num, [OutOfRange])
num_from_dec_digits, // Parse (List(U8), List(U8)) -> Try(num, [OutOfRange])
num_from_numeral, // Parse Numeral -> Try(num, [InvalidNumeral(Str)])
};

Expand Down
Loading
Loading