This commit is contained in:
Richard Feldman 2025-11-20 00:12:02 -05:00
parent 9d134e42c3
commit 8750552f3d
No known key found for this signature in database
5 changed files with 68 additions and 24 deletions

View file

@ -2061,3 +2061,11 @@ fn checkTypesExpr(
return test_env.assertLastDefType(expected);
}
test "check type - List.first returns Try" {
const source =
\\x = [10, 20, 30, 40, 50]
\\first = List.first(x)
;
try checkTypesModule(source, .{ .pass = .last_def }, "Try(Num(_size), [ListWasEmpty])");
}

View file

@ -33,8 +33,8 @@ fn comptimeRocAlloc(alloc_args: *RocAlloc, env: *anyopaque) callconv(.c) void {
const evaluator: *ComptimeEvaluator = @ptrCast(@alignCast(env));
const align_enum = std.mem.Alignment.fromByteUnits(@as(usize, @intCast(alloc_args.alignment)));
// Use C allocator for Roc's allocations to bypass GPA canary checks
const c_alloc = std.heap.c_allocator;
// Use page_allocator for Roc's allocations to isolate them from GPA
const c_alloc = std.heap.page_allocator;
// Add padding in debug builds to detect buffer overflows
const debug_padding = if (std.debug.runtime_safety) 16 else 0;
@ -59,11 +59,6 @@ fn comptimeRocAlloc(alloc_args: *RocAlloc, env: *anyopaque) callconv(.c) void {
if (std.debug.runtime_safety) {
const padding_start = base_ptr + alloc_args.length;
@memset(padding_start[0..debug_padding], 0xAA);
// Log allocations to help debug
if (alloc_args.length == 48) {
std.debug.print("[ALLOC 48 bytes] ptr=0x{x}\n", .{ptr_addr});
}
}
// Return the allocation start
@ -121,8 +116,8 @@ fn comptimeRocDealloc(dealloc_args: *RocDealloc, env: *anyopaque) callconv(.c) v
// Remove from tracking map
_ = evaluator.roc_allocations.remove(ptr_addr);
// Free the memory using c_allocator (including padding in debug builds)
const c_alloc = std.heap.c_allocator;
// Free the memory using page_allocator (including padding in debug builds)
const c_alloc = std.heap.page_allocator;
const align_enum = std.mem.Alignment.fromByteUnits(@as(usize, @intCast(dealloc_args.alignment)));
const ptr: [*]u8 = @ptrFromInt(ptr_addr);
const debug_padding = if (std.debug.runtime_safety) 16 else 0;
@ -134,7 +129,7 @@ fn comptimeRocRealloc(realloc_args: *RocRealloc, env: *anyopaque) callconv(.c) v
const evaluator: *ComptimeEvaluator = @ptrCast(@alignCast(env));
const old_ptr_addr = @intFromPtr(realloc_args.answer);
const c_alloc = std.heap.c_allocator;
const c_alloc = std.heap.page_allocator;
const debug_padding = if (std.debug.runtime_safety) 16 else 0;
// Look up the old allocation size
@ -178,10 +173,9 @@ fn comptimeRocRealloc(realloc_args: *RocRealloc, env: *anyopaque) callconv(.c) v
}
}
// Realloc using c_allocator (include padding in both old and new sizes)
const old_ptr: [*]u8 = @ptrFromInt(old_ptr_addr);
const old_slice = old_ptr[0 .. old_size + debug_padding];
const new_slice = c_alloc.realloc(old_slice, realloc_args.new_length + debug_padding) catch {
// Manually realloc using rawAlloc + copy + rawFree to preserve alignment
const align_enum = std.mem.Alignment.fromByteUnits(@as(usize, @intCast(realloc_args.alignment)));
const new_ptr = c_alloc.rawAlloc(realloc_args.new_length + debug_padding, align_enum, @returnAddress()) orelse {
const msg = "Out of memory during compile-time evaluation (realloc)";
const crashed = RocCrashed{
.utf8_bytes = @ptrCast(@constCast(msg.ptr)),
@ -192,14 +186,22 @@ fn comptimeRocRealloc(realloc_args: *RocRealloc, env: *anyopaque) callconv(.c) v
return;
};
// Copy old data to new location (NOT including padding - that's for canaries)
const old_ptr: [*]u8 = @ptrFromInt(old_ptr_addr);
const copy_size = @min(old_size, realloc_args.new_length);
@memcpy(new_ptr[0..copy_size], old_ptr[0..copy_size]);
// Free old memory
const old_slice = old_ptr[0 .. old_size + debug_padding];
c_alloc.rawFree(old_slice, align_enum, @returnAddress());
// Update tracking map with new pointer and size
_ = evaluator.roc_allocations.remove(old_ptr_addr);
const new_ptr_addr = @intFromPtr(new_slice.ptr);
const new_ptr_addr = @intFromPtr(new_ptr);
evaluator.roc_allocations.put(new_ptr_addr, realloc_args.new_length) catch {};
// Set canary in the new allocation's padding
if (std.debug.runtime_safety) {
const new_ptr = new_slice.ptr;
const padding_start = new_ptr + realloc_args.new_length;
@memset(padding_start[0..debug_padding], 0xAA);
}

View file

@ -1871,9 +1871,17 @@ pub const Interpreter = struct {
const low_level = lambda_expr.e_low_level_lambda;
const result = try self.callLowLevelBuiltin(low_level.op, arg_values, roc_ops);
// Decref all args
for (arg_values) |arg| {
arg.decref(&self.runtime_layout_store, roc_ops);
// Decref all args UNLESS the operation takes ownership of them.
// List.concat takes ownership of its arguments and manages their refcounts internally.
const takes_ownership = switch (low_level.op) {
.list_concat => true,
else => false,
};
if (!takes_ownership) {
for (arg_values) |arg| {
arg.decref(&self.runtime_layout_store, roc_ops);
}
}
return result;
@ -2115,9 +2123,17 @@ pub const Interpreter = struct {
// Dispatch to actual low-level builtin implementation
const result = try self.callLowLevelBuiltin(low_level.op, all_args, roc_ops);
// Decref all args
for (all_args) |arg| {
arg.decref(&self.runtime_layout_store, roc_ops);
// Decref all args UNLESS the operation takes ownership of them.
// List.concat takes ownership of its arguments and manages their refcounts internally.
const takes_ownership = switch (low_level.op) {
.list_concat => true,
else => false,
};
if (!takes_ownership) {
for (all_args) |arg| {
arg.decref(&self.runtime_layout_store, roc_ops);
}
}
return result;

View file

@ -140,24 +140,33 @@ pub fn renderValueRocWithType(ctx: *RenderCtx, value: StackValue, rt_var: types.
var out = std.array_list.AlignedManaged(u8, null).init(gpa);
errdefer out.deinit();
try out.appendSlice(tag_name);
std.debug.print("[BUG] Appended tag name: {s}\n", .{tag_name});
if (acc.findFieldIndex(ctx.env, "payload")) |pidx| {
const payload = try acc.getFieldByIndex(pidx);
const args_range = tags.items(.args)[tag_index];
const arg_vars = ctx.runtime_types.sliceVars(toVarRange(args_range));
std.debug.print("[BUG] Tag {s}: arg_vars.len = {}\n", .{tag_name, arg_vars.len});
if (arg_vars.len > 0) {
std.debug.print("[BUG] Entering arg_vars > 0 block\n", .{});
try out.append('(');
if (arg_vars.len == 1) {
std.debug.print("[BUG] arg_vars.len == 1, rendering single arg\n", .{});
const arg_var = arg_vars[0];
const layout_idx = try ctx.layout_store.addTypeVar(arg_var, ctx.type_scope);
std.debug.print("[BUG] addTypeVar succeeded, getting layout\n", .{});
const arg_layout = ctx.layout_store.getLayout(layout_idx);
std.debug.print("[BUG] Got arg_layout, creating payload_value\n", .{});
const payload_value = StackValue{
.layout = arg_layout,
.ptr = payload.ptr,
.is_initialized = payload.is_initialized,
};
std.debug.print("[BUG] About to recursively render payload\n", .{});
const rendered = try renderValueRocWithType(ctx, payload_value, arg_var);
defer gpa.free(rendered);
std.debug.print("[BUG] Payload rendered as: {s}, appending\n", .{rendered});
try out.appendSlice(rendered);
std.debug.print("[BUG] Appended payload\n", .{});
} else {
var elem_layouts = try ctx.allocator.alloc(layout.Layout, arg_vars.len);
defer ctx.allocator.free(elem_layouts);
@ -203,10 +212,17 @@ pub fn renderValueRocWithType(ctx: *RenderCtx, value: StackValue, rt_var: types.
}
}
}
std.debug.print("[BUG] About to append closing paren\n", .{});
try out.append(')');
std.debug.print("[BUG] Appended closing paren\n", .{});
}
} else {
std.debug.print("[BUG] No payload field found\n", .{});
}
return out.toOwnedSlice();
std.debug.print("[BUG] Returning: {s}\n", .{out.items});
const result = try out.toOwnedSlice();
std.debug.print("[BUG] toOwnedSlice returned: {s} (len={})\n", .{result, result.len});
return result;
}
}
},

View file

@ -537,7 +537,9 @@ pub const Store = struct {
const resolved_ext = self.types_store.resolveVar(current_ext);
switch (resolved_ext.desc.content) {
.structure => |ext_flat_type| switch (ext_flat_type) {
.empty_tag_union => break,
.empty_tag_union => {
break;
},
.tag_union => |ext_tag_union| {
if (ext_tag_union.tags.len() > 0) {
num_tags += ext_tag_union.tags.len();