Skip to content

Commit b395677

Browse files
rtfeldmanclaude
andcommitted
Simplify comptime_value.zig and make DevEvaluator panic on unsupported
- Remove legacy ComptimeHeap/ComptimeValue/ComptimeEnv from comptime_value.zig - Move these types into dev_evaluator.zig as private implementation details - comptime_value.zig now only has TopLevelBindings - Change devEvaluatorStr to panic instead of returning null on errors - Failing tests now reveal features that need to be implemented Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 394dab0 commit b395677

3 files changed

Lines changed: 121 additions & 172 deletions

File tree

src/eval/comptime_value.zig

Lines changed: 0 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -35,120 +35,6 @@ pub const TopLevelBindings = struct {
3535
}
3636
};
3737

38-
// Legacy types for backward compatibility during migration.
39-
// These will be removed once the full refactor is complete.
40-
41-
/// Simple heap for allocating memory during evaluation.
42-
/// Uses an arena so everything can be freed at once.
43-
pub const ComptimeHeap = struct {
44-
arena: std.heap.ArenaAllocator,
45-
46-
pub fn init(backing_allocator: std.mem.Allocator) ComptimeHeap {
47-
return .{
48-
.arena = std.heap.ArenaAllocator.init(backing_allocator),
49-
};
50-
}
51-
52-
pub fn alloc(self: *ComptimeHeap, size: usize, comptime log2_alignment: u8) ![]u8 {
53-
const alignment: std.mem.Alignment = @enumFromInt(log2_alignment);
54-
return self.arena.allocator().alignedAlloc(u8, alignment, size);
55-
}
56-
57-
pub fn allocator(self: *ComptimeHeap) std.mem.Allocator {
58-
return self.arena.allocator();
59-
}
60-
61-
pub fn deinit(self: *ComptimeHeap) void {
62-
self.arena.deinit();
63-
}
64-
};
65-
66-
/// Simple wrapper for a pointer to bytes.
67-
/// Used for storing intermediate values during evaluation.
68-
pub const ComptimeValue = struct {
69-
bytes: [*]u8,
70-
size: usize,
71-
72-
pub fn fromBytes(bytes_slice: []u8, _: anytype) ComptimeValue {
73-
return .{
74-
.bytes = bytes_slice.ptr,
75-
.size = bytes_slice.len,
76-
};
77-
}
78-
79-
pub fn as(self: ComptimeValue, comptime T: type) T {
80-
std.debug.assert(self.size >= @sizeOf(T));
81-
return @as(*const T, @ptrCast(@alignCast(self.bytes))).*;
82-
}
83-
84-
pub fn set(self: ComptimeValue, comptime T: type, value: T) void {
85-
std.debug.assert(self.size >= @sizeOf(T));
86-
@as(*T, @ptrCast(@alignCast(self.bytes))).* = value;
87-
}
88-
89-
pub fn toSlice(self: ComptimeValue) []u8 {
90-
return self.bytes[0..self.size];
91-
}
92-
93-
pub fn toConstSlice(self: ComptimeValue) []const u8 {
94-
return self.bytes[0..self.size];
95-
}
96-
};
97-
98-
/// Simple environment mapping pattern indices to values.
99-
/// Flat map, no child scopes.
100-
pub const ComptimeEnv = struct {
101-
const can = @import("can");
102-
const CIR = can.CIR;
103-
104-
heap: *ComptimeHeap,
105-
bindings: std.AutoHashMap(u32, ComptimeValue),
106-
closure_refs: std.AutoHashMap(u32, CIR.Expr.Idx),
107-
108-
pub fn init(heap: *ComptimeHeap, _: anytype) ComptimeEnv {
109-
return .{
110-
.heap = heap,
111-
.bindings = std.AutoHashMap(u32, ComptimeValue).init(heap.allocator()),
112-
.closure_refs = std.AutoHashMap(u32, CIR.Expr.Idx).init(heap.allocator()),
113-
};
114-
}
115-
116-
pub fn bind(self: *ComptimeEnv, pattern_idx: u32, value: ComptimeValue) !void {
117-
try self.bindings.put(pattern_idx, value);
118-
}
119-
120-
pub fn bindClosure(self: *ComptimeEnv, pattern_idx: u32, expr_idx: CIR.Expr.Idx) !void {
121-
try self.closure_refs.put(pattern_idx, expr_idx);
122-
}
123-
124-
pub fn lookup(self: *const ComptimeEnv, pattern_idx: u32) ?ComptimeValue {
125-
return self.bindings.get(pattern_idx);
126-
}
127-
128-
pub fn lookupClosure(self: *const ComptimeEnv, pattern_idx: u32) ?CIR.Expr.Idx {
129-
return self.closure_refs.get(pattern_idx);
130-
}
131-
132-
/// Create a child environment - for compatibility, just creates a copy
133-
pub fn child(self: *const ComptimeEnv) !ComptimeEnv {
134-
var new_env = ComptimeEnv.init(self.heap, null);
135-
var iter = self.bindings.iterator();
136-
while (iter.next()) |entry| {
137-
try new_env.bindings.put(entry.key_ptr.*, entry.value_ptr.*);
138-
}
139-
var closure_iter = self.closure_refs.iterator();
140-
while (closure_iter.next()) |entry| {
141-
try new_env.closure_refs.put(entry.key_ptr.*, entry.value_ptr.*);
142-
}
143-
return new_env;
144-
}
145-
146-
pub fn deinit(self: *ComptimeEnv) void {
147-
self.bindings.deinit();
148-
self.closure_refs.deinit();
149-
}
150-
};
151-
15238
test "TopLevelBindings bind and lookup" {
15339
var bindings = TopLevelBindings.init(std.testing.allocator);
15440
defer bindings.deinit();
@@ -169,40 +55,3 @@ test "TopLevelBindings lookup missing returns null" {
16955

17056
try std.testing.expectEqual(@as(?[*]u8, null), bindings.lookup(999));
17157
}
172-
173-
test "ComptimeHeap basic allocation" {
174-
var heap = ComptimeHeap.init(std.testing.allocator);
175-
defer heap.deinit();
176-
177-
const bytes = try heap.alloc(8, 3);
178-
try std.testing.expectEqual(@as(usize, 8), bytes.len);
179-
}
180-
181-
test "ComptimeValue read/write i64" {
182-
var heap = ComptimeHeap.init(std.testing.allocator);
183-
defer heap.deinit();
184-
185-
const bytes = try heap.alloc(8, 3);
186-
const value = ComptimeValue.fromBytes(bytes, undefined);
187-
188-
value.set(i64, 42);
189-
try std.testing.expectEqual(@as(i64, 42), value.as(i64));
190-
}
191-
192-
test "ComptimeEnv bind and lookup" {
193-
var heap = ComptimeHeap.init(std.testing.allocator);
194-
defer heap.deinit();
195-
196-
var env = ComptimeEnv.init(&heap, null);
197-
defer env.deinit();
198-
199-
const bytes = try heap.alloc(8, 3);
200-
const value = ComptimeValue.fromBytes(bytes, undefined);
201-
value.set(i64, 123);
202-
203-
try env.bind(42, value);
204-
205-
const looked_up = env.lookup(42);
206-
try std.testing.expect(looked_up != null);
207-
try std.testing.expectEqual(@as(i64, 123), looked_up.?.as(i64));
208-
}

src/eval/dev_evaluator.zig

Lines changed: 101 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,107 @@ const Can = can.Can;
2828
const Check = check.Check;
2929
const builtin_loading = eval_mod.builtin_loading;
3030
const comptime_value = eval_mod.comptime_value;
31-
const ComptimeHeap = comptime_value.ComptimeHeap;
32-
const ComptimeValue = comptime_value.ComptimeValue;
33-
const ComptimeEnv = comptime_value.ComptimeEnv;
3431
const TopLevelBindings = comptime_value.TopLevelBindings;
3532

33+
/// Simple heap for allocating memory during evaluation.
34+
/// Uses an arena so everything can be freed at once.
35+
const ComptimeHeap = struct {
36+
arena: std.heap.ArenaAllocator,
37+
38+
fn init(backing_allocator: Allocator) ComptimeHeap {
39+
return .{
40+
.arena = std.heap.ArenaAllocator.init(backing_allocator),
41+
};
42+
}
43+
44+
fn alloc(self: *ComptimeHeap, size: usize, comptime log2_alignment: u8) ![]u8 {
45+
const alignment: std.mem.Alignment = @enumFromInt(log2_alignment);
46+
return self.arena.allocator().alignedAlloc(u8, alignment, size);
47+
}
48+
49+
fn allocator(self: *ComptimeHeap) Allocator {
50+
return self.arena.allocator();
51+
}
52+
53+
fn deinit(self: *ComptimeHeap) void {
54+
self.arena.deinit();
55+
}
56+
};
57+
58+
/// Simple wrapper for a pointer to bytes.
59+
/// Used for storing intermediate values during evaluation.
60+
const ComptimeValue = struct {
61+
bytes: [*]u8,
62+
size: usize,
63+
64+
fn fromBytes(bytes_slice: []u8) ComptimeValue {
65+
return .{
66+
.bytes = bytes_slice.ptr,
67+
.size = bytes_slice.len,
68+
};
69+
}
70+
71+
fn as(self: ComptimeValue, comptime T: type) T {
72+
std.debug.assert(self.size >= @sizeOf(T));
73+
return @as(*const T, @ptrCast(@alignCast(self.bytes))).*;
74+
}
75+
76+
fn set(self: ComptimeValue, comptime T: type, value: T) void {
77+
std.debug.assert(self.size >= @sizeOf(T));
78+
@as(*T, @ptrCast(@alignCast(self.bytes))).* = value;
79+
}
80+
};
81+
82+
/// Environment mapping pattern indices to values during evaluation.
83+
const ComptimeEnv = struct {
84+
heap: *ComptimeHeap,
85+
bindings: std.AutoHashMap(u32, ComptimeValue),
86+
closure_refs: std.AutoHashMap(u32, CIR.Expr.Idx),
87+
88+
fn init(heap: *ComptimeHeap) ComptimeEnv {
89+
return .{
90+
.heap = heap,
91+
.bindings = std.AutoHashMap(u32, ComptimeValue).init(heap.allocator()),
92+
.closure_refs = std.AutoHashMap(u32, CIR.Expr.Idx).init(heap.allocator()),
93+
};
94+
}
95+
96+
fn bind(self: *ComptimeEnv, pattern_idx: u32, value: ComptimeValue) !void {
97+
try self.bindings.put(pattern_idx, value);
98+
}
99+
100+
fn bindClosure(self: *ComptimeEnv, pattern_idx: u32, expr_idx: CIR.Expr.Idx) !void {
101+
try self.closure_refs.put(pattern_idx, expr_idx);
102+
}
103+
104+
fn lookup(self: *const ComptimeEnv, pattern_idx: u32) ?ComptimeValue {
105+
return self.bindings.get(pattern_idx);
106+
}
107+
108+
fn lookupClosure(self: *const ComptimeEnv, pattern_idx: u32) ?CIR.Expr.Idx {
109+
return self.closure_refs.get(pattern_idx);
110+
}
111+
112+
/// Create a child environment that inherits bindings from parent.
113+
fn child(self: *const ComptimeEnv) !ComptimeEnv {
114+
var new_env = ComptimeEnv.init(self.heap);
115+
var iter = self.bindings.iterator();
116+
while (iter.next()) |entry| {
117+
try new_env.bindings.put(entry.key_ptr.*, entry.value_ptr.*);
118+
}
119+
var closure_iter = self.closure_refs.iterator();
120+
while (closure_iter.next()) |entry| {
121+
try new_env.closure_refs.put(entry.key_ptr.*, entry.value_ptr.*);
122+
}
123+
return new_env;
124+
}
125+
126+
fn deinit(self: *ComptimeEnv) void {
127+
self.bindings.deinit();
128+
self.closure_refs.deinit();
129+
}
130+
};
131+
36132
/// Dev backend-based evaluator for Roc expressions
37133
pub const DevEvaluator = struct {
38134
allocator: Allocator,
@@ -195,14 +291,13 @@ pub const DevEvaluator = struct {
195291

196292
/// Create an empty ComptimeEnv using the evaluator's heap
197293
fn createEnv(self: *DevEvaluator) ComptimeEnv {
198-
return ComptimeEnv.init(&self.comptime_heap, null);
294+
return ComptimeEnv.init(&self.comptime_heap);
199295
}
200296

201297
/// Create a ComptimeValue holding an i64
202298
fn createI64Value(self: *DevEvaluator, value: i64) Error!ComptimeValue {
203299
const bytes = self.comptime_heap.alloc(8, 3) catch return error.OutOfMemory;
204-
// layout_idx is undefined - will be set properly when we have LayoutStore integration
205-
const cv = ComptimeValue.fromBytes(bytes, undefined);
300+
const cv = ComptimeValue.fromBytes(bytes);
206301
cv.set(i64, value);
207302
return cv;
208303
}

src/eval/test/helpers.zig

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,39 +43,44 @@ const TraceWriter = struct {
4343
}
4444
};
4545

46-
/// Try to evaluate an expression using the DevEvaluator and return the result as a string.
47-
/// Returns null if DevEvaluator can't handle this expression (unsupported, JIT unavailable, etc.)
48-
fn tryDevEvaluatorStr(allocator: std.mem.Allocator, module_env: *ModuleEnv, expr_idx: CIR.Expr.Idx) ?[]const u8 {
46+
/// Evaluate an expression using the DevEvaluator and return the result as a string.
47+
fn devEvaluatorStr(allocator: std.mem.Allocator, module_env: *ModuleEnv, expr_idx: CIR.Expr.Idx) []const u8 {
4948
// Initialize DevEvaluator
50-
var dev_eval = DevEvaluator.init(allocator) catch return null;
49+
var dev_eval = DevEvaluator.init(allocator) catch |err| {
50+
std.debug.panic("DevEvaluator.init failed: {}", .{err});
51+
};
5152
defer dev_eval.deinit();
5253

5354
// Get the expression from the index
5455
const expr = module_env.store.getExpr(expr_idx);
5556

56-
// Try to generate code
57-
var code_result = dev_eval.generateCode(module_env, expr) catch return null;
57+
// Generate code
58+
var code_result = dev_eval.generateCode(module_env, expr) catch |err| {
59+
std.debug.panic("DevEvaluator.generateCode failed: {}", .{err});
60+
};
5861
defer code_result.deinit();
5962

60-
// Try to JIT execute the code
61-
var jit = backend.JitCode.init(code_result.code) catch return null;
63+
// JIT execute the code
64+
var jit = backend.JitCode.init(code_result.code) catch |err| {
65+
std.debug.panic("JitCode.init failed: {}", .{err});
66+
};
6267
defer jit.deinit();
6368

6469
// Execute and format result as string based on layout
6570
const layout_mod = @import("layout");
6671
return switch (code_result.result_layout) {
67-
layout_mod.Idx.i64, layout_mod.Idx.i8, layout_mod.Idx.i16, layout_mod.Idx.i32 => std.fmt.allocPrint(allocator, "{}", .{jit.callReturnI64()}) catch null,
68-
layout_mod.Idx.u64, layout_mod.Idx.u8, layout_mod.Idx.u16, layout_mod.Idx.u32, layout_mod.Idx.bool => std.fmt.allocPrint(allocator, "{}", .{jit.callReturnU64()}) catch null,
69-
layout_mod.Idx.f64, layout_mod.Idx.f32 => std.fmt.allocPrint(allocator, "{d}", .{jit.callReturnF64()}) catch null,
70-
layout_mod.Idx.i128, layout_mod.Idx.u128, layout_mod.Idx.dec => std.fmt.allocPrint(allocator, "{}", .{jit.callReturnI64()}) catch null,
71-
else => null, // Unsupported layout for now
72+
layout_mod.Idx.i64, layout_mod.Idx.i8, layout_mod.Idx.i16, layout_mod.Idx.i32 => std.fmt.allocPrint(allocator, "{}", .{jit.callReturnI64()}) catch @panic("allocPrint failed"),
73+
layout_mod.Idx.u64, layout_mod.Idx.u8, layout_mod.Idx.u16, layout_mod.Idx.u32, layout_mod.Idx.bool => std.fmt.allocPrint(allocator, "{}", .{jit.callReturnU64()}) catch @panic("allocPrint failed"),
74+
layout_mod.Idx.f64, layout_mod.Idx.f32 => std.fmt.allocPrint(allocator, "{d}", .{jit.callReturnF64()}) catch @panic("allocPrint failed"),
75+
layout_mod.Idx.i128, layout_mod.Idx.u128, layout_mod.Idx.dec => std.fmt.allocPrint(allocator, "{}", .{jit.callReturnI64()}) catch @panic("allocPrint failed"),
76+
else => std.debug.panic("Unsupported layout: {}", .{code_result.result_layout}),
7277
};
7378
}
7479

7580
/// Compare Interpreter result string with DevEvaluator result string.
76-
/// Fails the test if both succeed but produce different strings.
81+
/// Fails the test if they produce different strings.
7782
fn compareWithDevEvaluator(allocator: std.mem.Allocator, interpreter_str: []const u8, module_env: *ModuleEnv, expr_idx: CIR.Expr.Idx) !void {
78-
const dev_str = tryDevEvaluatorStr(allocator, module_env, expr_idx) orelse return; // Skip if unsupported
83+
const dev_str = devEvaluatorStr(allocator, module_env, expr_idx);
7984
defer allocator.free(dev_str);
8085

8186
if (!std.mem.eql(u8, interpreter_str, dev_str)) {

0 commit comments

Comments
 (0)