Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1d1d31d
Fix LLVM evaluator bitrot: sync with LIR proc_call architecture, wire…
lukewilliamboswell Mar 24, 2026
930af13
Fix str_concat fold bug and remove dead SortComparatorThunk
lukewilliamboswell Mar 24, 2026
440fd0a
Route RocEnv diagnostic output through Io instead of std.debug.print
lukewilliamboswell Mar 24, 2026
b755701
Add focused LLVM backend test suite and document CLI integration TODO
lukewilliamboswell Mar 24, 2026
fa463b4
Use real LLVM in eval and REPL comparisons
rtfeldman Mar 25, 2026
c5abcf9
Wire real LLVM through CLI and comparisons
rtfeldman Mar 25, 2026
d409255
Keep LLVM temp artifacts alive through dylib lifetime
rtfeldman Mar 25, 2026
0ff3adc
Move LLVM artifact cleanup out of eval paths
rtfeldman Mar 25, 2026
6398b44
Implement LLVM numeric shift low-level ops
rtfeldman Mar 25, 2026
9ab2ede
Re-export LLVM artifact cleanup helper
rtfeldman Mar 25, 2026
7603e7a
Fix LLVM recursive tag matching and boxed payload loads
rtfeldman Mar 25, 2026
cf7c1e0
Isolate LLVM function state and coerce proc returns
rtfeldman Mar 25, 2026
f19f311
Fix LLVM control flow after terminating expressions
rtfeldman Mar 25, 2026
45c9ae9
Load inline LLVM tag unions by address
rtfeldman Mar 25, 2026
888d7e1
Make LLVM list_contains compare by layout
rtfeldman Mar 25, 2026
a5484a8
Fix LLVM list rest-pattern slices for refcounted elements
rtfeldman Mar 25, 2026
05eafb8
Fix LLVM tag-union field storage
rtfeldman Mar 25, 2026
fd20de2
Fix LLVM string conversion result layouts
rtfeldman Mar 25, 2026
3be8a35
Fix LLVM transparent unions and aligned Roc realloc
rtfeldman Mar 25, 2026
36481bf
Remove unused LLVM tag materialization arg
rtfeldman Mar 25, 2026
f336e34
Accept prebuilt LLVM closure payload structs
rtfeldman Mar 25, 2026
f2f93a2
Preserve later-live owners through nested consumes
rtfeldman Mar 25, 2026
076bd29
Restore LLVM lexical env across branch merges
rtfeldman Mar 25, 2026
ab9924f
Retain mutable cell snapshots in immutable bindings
rtfeldman Mar 25, 2026
1155191
Add LLVM RC value wrappers and RC-aware list sort
rtfeldman Mar 25, 2026
544f961
Differentiate LLVM RC helper storage kinds
rtfeldman Mar 25, 2026
694e7ba
Retain mutable block result snapshots
rtfeldman Mar 25, 2026
40c07c7
Keep borrow-only loop sources alive across blocks
rtfeldman Mar 25, 2026
87e3be0
Preserve stable RC borrows through loop lowering
rtfeldman Mar 25, 2026
6f45c85
Fix LLVM RC leaks around retained cell snapshots
rtfeldman Mar 25, 2026
1689262
Fix RC borrow normalization for top-level defs
rtfeldman Mar 25, 2026
865beba
Retain closure captures on fallback lowering
rtfeldman Mar 25, 2026
709d3bd
Fix RC tail cleanup for transferred nested owners
rtfeldman Mar 25, 2026
6b17407
Fix RC insert tuple layout tests
rtfeldman Mar 25, 2026
7b098ff
Run eval backends in subprocesses
rtfeldman Mar 25, 2026
499fc52
Preserve alias semantics in borrowed RC materialization
rtfeldman Mar 25, 2026
7c2cf33
Fix brittle RC wildcard layout test
rtfeldman Mar 25, 2026
0cf4f9d
Fix loop-carried later-use RC retention
rtfeldman Mar 25, 2026
64cf522
Refine LLVM mutable cell ownership handling
rtfeldman Mar 25, 2026
5d99aee
Fix rebased CLI target validation merge
rtfeldman Mar 25, 2026
a88b6aa
Fix LLVM lint and tidy blockers
rtfeldman Mar 25, 2026
0f7f87d
Implement LLVM list_drop_at lowering
rtfeldman Mar 25, 2026
567ec22
Implement LLVM literal pattern matching
rtfeldman Mar 25, 2026
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
4,416 changes: 2,997 additions & 1,419 deletions src/backend/llvm/MonoLlvmCodeGen.zig

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions src/base/LowLevel.zig
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,16 @@ pub const LowLevel = enum {
};
}

/// Some consume-mode low-levels may return the same managed owner they
/// consumed, rather than always allocating a distinct result. RC insertion
/// must preserve that owner until the returned value has taken over.
pub fn consumedArgMayAliasResult(self: LowLevel, arg_index: usize) bool {
return switch (self) {
.list_append_unsafe, .list_prepend => arg_index == 0,
else => false,
};
}

pub fn getArgOwnership(self: LowLevel) []const ArgOwnership {
return switch (self) {
.str_count_utf8_bytes => &.{.borrow},
Expand Down
284 changes: 268 additions & 16 deletions src/builtins/dev_wrappers.zig
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,17 @@ const fromUtf8Lossy = str.fromUtf8Lossy;
const listConcat = list.listConcat;
const listPrepend = list.listPrepend;
const listSublist = list.listSublist;
const listDropAt = list.listDropAt;
const listReplace = list.listReplace;
const listReserve = list.listReserve;
const listReleaseExcessCapacity = list.listReleaseExcessCapacity;
const listWithCapacity = list.listWithCapacity;
const listAppendUnsafe = list.listAppendUnsafe;
const listAppendSafeC = list.listAppendSafeC;
const listDecref = list.listDecref;
const ElementIncrefFn = *const fn (?*anyopaque, ?[*]u8) callconv(.c) void;
const RcDropFn = *const fn (?[*]u8, *RocOps) callconv(.c) void;
const RcIncrefFn = *const fn (?[*]u8, isize, *RocOps) callconv(.c) void;
const SortCmpFn = *const fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.c) u8;

// ═══════════════════════════════════════════════════════════════════════════
Expand Down Expand Up @@ -338,11 +341,27 @@ const FlatListElementDecrefContext = struct {
roc_ops: *RocOps,
};

const CallbackElementIncrefContext = struct {
callback: RcIncrefFn,
roc_ops: *RocOps,
};

const CallbackElementDecrefContext = struct {
callback: RcDropFn,
roc_ops: *RocOps,
};

fn callbackListElementIncref(context: ?*anyopaque, element: ?[*]u8) callconv(.c) void {
if (element == null) return;
const ctx_ptr = context orelse unreachable;
const ctx: *const CallbackElementIncrefContext = utils.alignedPtrCast(
*const CallbackElementIncrefContext,
@as([*]u8, @ptrCast(ctx_ptr)),
@src(),
);
ctx.callback(element, 1, ctx.roc_ops);
}

fn flatListElementDecref(context: ?*anyopaque, element: ?[*]u8) callconv(.c) void {
if (element == null) return;
const ctx_ptr = context orelse unreachable;
Expand Down Expand Up @@ -378,8 +397,7 @@ pub fn roc_builtins_list_with_capacity(out: *RocList, capacity: u64, alignment:
out.* = listWithCapacity(capacity, alignment, element_width, elements_refcounted, null, @ptrCast(&rcNone), roc_ops);
}

/// Wrapper: listSortWith using a backend-provided comparator trampoline.
pub fn roc_builtins_list_sort_with(
fn listSortWithComparatorImpl(
out: *RocList,
list_bytes: ?[*]u8,
list_len: usize,
Expand All @@ -388,22 +406,89 @@ pub fn roc_builtins_list_sort_with(
cmp_data: ?[*]u8,
alignment: u32,
element_width: usize,
elements_refcounted: bool,
element_incref: ?RcIncrefFn,
element_decref: ?RcDropFn,
roc_ops: *RocOps,
) callconv(.c) void {
) void {
if (list_len < 2 or element_width == 0) {
out.* = RocList{ .bytes = list_bytes, .length = list_len, .capacity_or_alloc_ptr = list_cap };
return;
}

const total_bytes = list_len * element_width;
const sorted_bytes = allocateWithRefcountC(total_bytes, alignment, false, roc_ops);
if (list_bytes) |src| {
@memcpy(sorted_bytes[0..total_bytes], src[0..total_bytes]);
}
const cmp_fn_ptr_non_null = cmp_fn_ptr orelse {
roc_ops.crash("roc_builtins_list_sort_with: missing comparator trampoline");
unreachable;
};

const cmp_fn: SortCmpFn = @ptrFromInt(@intFromPtr(cmp_fn_ptr orelse unreachable));
const temp_ptr: [*]u8 = @ptrCast(roc_ops.alloc(@max(@as(usize, alignment), 1), element_width));
defer roc_ops.dealloc(temp_ptr, @max(@as(usize, alignment), 1));
const input = RocList{ .bytes = list_bytes, .length = list_len, .capacity_or_alloc_ptr = list_cap };

var incref_ctx_storage: CallbackElementIncrefContext = undefined;
var decref_ctx_storage: CallbackElementDecrefContext = undefined;

const incref_ctx: ?*anyopaque = blk: {
if (!elements_refcounted) break :blk null;

const incref_callback = element_incref orelse {
roc_ops.crash("roc_builtins_list_sort_with: refcounted elements require an incref callback");
unreachable;
};

incref_ctx_storage = .{
.callback = incref_callback,
.roc_ops = roc_ops,
};

break :blk @ptrCast(&incref_ctx_storage);
};
const incref_fn: ElementIncrefFn = if (elements_refcounted) &callbackListElementIncref else &rcNone;
const decref_ctx: ?*anyopaque = blk: {
if (!elements_refcounted) break :blk null;

const decref_callback = element_decref orelse {
roc_ops.crash("roc_builtins_list_sort_with: refcounted elements require a decref callback");
unreachable;
};

decref_ctx_storage = .{
.callback = decref_callback,
.roc_ops = roc_ops,
};

break :blk @ptrCast(&decref_ctx_storage);
};
const decref_fn: ElementIncrefFn = if (elements_refcounted) &callbackListElementDecref else &rcNone;

const list_value = input.makeUnique(
alignment,
element_width,
elements_refcounted,
incref_ctx,
incref_fn,
decref_ctx,
decref_fn,
roc_ops,
);

const sorted_bytes = list_value.bytes orelse {
out.* = list_value;
return;
};

const cmp_fn: SortCmpFn = @ptrFromInt(@intFromPtr(cmp_fn_ptr_non_null));

var stack_temp: [256]u8 align(16) = undefined;
var heap_temp: ?[*]u8 = null;
const temp_ptr: [*]u8 = if (element_width <= stack_temp.len) blk: {
break :blk @as([*]u8, @ptrCast(&stack_temp));
} else blk: {
const alloc_ptr: [*]u8 = @ptrCast(roc_ops.alloc(@max(@as(usize, alignment), 1), element_width));
heap_temp = alloc_ptr;
break :blk alloc_ptr;
};
defer if (heap_temp) |heap_bytes| {
roc_ops.dealloc(heap_bytes, @max(@as(usize, alignment), 1));
};

var i: usize = 1;
while (i < list_len) : (i += 1) {
Expand All @@ -425,11 +510,66 @@ pub fn roc_builtins_list_sort_with(
@memcpy(insert_pos[0..element_width], temp_ptr[0..element_width]);
}

out.* = .{
.bytes = sorted_bytes,
.length = list_len,
.capacity_or_alloc_ptr = list_len,
};
out.* = list_value;
}

/// Wrapper: listSortWith using a backend-provided comparator trampoline.
pub fn roc_builtins_list_sort_with(
out: *RocList,
list_bytes: ?[*]u8,
list_len: usize,
list_cap: usize,
cmp_fn_ptr: ?*const anyopaque,
cmp_data: ?[*]u8,
alignment: u32,
element_width: usize,
roc_ops: *RocOps,
) callconv(.c) void {
listSortWithComparatorImpl(
out,
list_bytes,
list_len,
list_cap,
cmp_fn_ptr,
cmp_data,
alignment,
element_width,
false,
null,
null,
roc_ops,
);
}

/// Wrapper: listSortWith with element RC callbacks for refcounted element layouts.
pub fn roc_builtins_list_sort_with_rc(
out: *RocList,
list_bytes: ?[*]u8,
list_len: usize,
list_cap: usize,
cmp_fn_ptr: ?*const anyopaque,
cmp_data: ?[*]u8,
alignment: u32,
element_width: usize,
elements_refcounted: bool,
element_incref: ?RcIncrefFn,
element_decref: ?RcDropFn,
roc_ops: *RocOps,
) callconv(.c) void {
listSortWithComparatorImpl(
out,
list_bytes,
list_len,
list_cap,
cmp_fn_ptr,
cmp_data,
alignment,
element_width,
elements_refcounted,
element_incref,
element_decref,
roc_ops,
);
}

/// Wrapper: listAppendUnsafe
Expand Down Expand Up @@ -457,6 +597,12 @@ pub fn roc_builtins_list_sublist(out: *RocList, list_bytes: ?[*]u8, list_len: us
out.* = listSublist(l, alignment, element_width, elements_refcounted, start, len, null, @ptrCast(&rcNone), roc_ops);
}

/// Wrapper: listDropAt
pub fn roc_builtins_list_drop_at(out: *RocList, list_bytes: ?[*]u8, list_len: usize, list_cap: usize, alignment: u32, element_width: usize, drop_index: u64, elements_refcounted: bool, roc_ops: *RocOps) callconv(.c) void {
const l = RocList{ .bytes = list_bytes, .length = list_len, .capacity_or_alloc_ptr = list_cap };
out.* = listDropAt(l, alignment, element_width, elements_refcounted, drop_index, null, @ptrCast(&rcNone), null, @ptrCast(&rcNone), roc_ops);
}

/// Wrapper: listReplace for list_set
pub fn roc_builtins_list_replace(out: *RocList, list_bytes: ?[*]u8, list_len: usize, list_cap: usize, alignment: u32, index: u64, element: ?[*]u8, element_width: usize, out_element: ?[*]u8, elements_refcounted: bool, roc_ops: *RocOps) callconv(.c) void {
const l = RocList{ .bytes = list_bytes, .length = list_len, .capacity_or_alloc_ptr = list_cap };
Expand Down Expand Up @@ -634,6 +780,112 @@ pub fn roc_builtins_box_free_with(
freeDataPtrC(payload_ptr, payload_alignment, false, roc_ops);
}

/// Value-slot wrapper: incref a RocStr stored at `value_ptr`.
pub fn roc_builtins_str_incref_value(value_ptr: ?[*]u8, amount: isize, roc_ops: *RocOps) callconv(.c) void {
const bytes = value_ptr orelse return;
const str_ptr: *const RocStr = @ptrCast(@alignCast(bytes));
str_ptr.*.incref(@intCast(amount), roc_ops);
}

/// Value-slot wrapper: decref a RocStr stored at `value_ptr`.
pub fn roc_builtins_str_decref_value(value_ptr: ?[*]u8, roc_ops: *RocOps) callconv(.c) void {
const bytes = value_ptr orelse return;
const str_ptr: *const RocStr = @ptrCast(@alignCast(bytes));
@constCast(str_ptr).*.decref(roc_ops);
}

/// Value-slot wrapper: free a RocStr stored at `value_ptr`.
pub fn roc_builtins_str_free_value(value_ptr: ?[*]u8, roc_ops: *RocOps) callconv(.c) void {
const bytes = value_ptr orelse return;
const str_ptr: *const RocStr = @ptrCast(@alignCast(bytes));
const value = str_ptr.*;
if (!value.isSmallStr()) {
freeDataPtrC(value.getAllocationPtr(), RocStr.alignment, false, roc_ops);
}
}

/// Value-slot wrapper: incref a RocList stored at `value_ptr`.
pub fn roc_builtins_list_incref_value(value_ptr: ?[*]u8, elements_refcounted: bool, amount: isize, roc_ops: *RocOps) callconv(.c) void {
const bytes = value_ptr orelse return;
const list_ptr: *const RocList = @ptrCast(@alignCast(bytes));
list_ptr.*.incref(amount, elements_refcounted, roc_ops);
}

/// Value-slot wrapper: decref a RocList stored at `value_ptr`.
pub fn roc_builtins_list_decref_value(
value_ptr: ?[*]u8,
alignment: u32,
element_width: usize,
element_decref: ?RcDropFn,
roc_ops: *RocOps,
) callconv(.c) void {
const bytes = value_ptr orelse return;
const list_ptr: *const RocList = @ptrCast(@alignCast(bytes));
const value = list_ptr.*;
if (element_decref) |callback| {
var ctx = CallbackElementDecrefContext{
.callback = callback,
.roc_ops = roc_ops,
};
value.decref(alignment, element_width, true, @ptrCast(&ctx), &callbackListElementDecref, roc_ops);
} else {
value.decref(alignment, element_width, false, null, &rcNone, roc_ops);
}
}

/// Value-slot wrapper: free a RocList stored at `value_ptr`.
pub fn roc_builtins_list_free_value(
value_ptr: ?[*]u8,
alignment: u32,
element_width: usize,
element_decref: ?RcDropFn,
roc_ops: *RocOps,
) callconv(.c) void {
const bytes = value_ptr orelse return;
const list_ptr: *const RocList = @ptrCast(@alignCast(bytes));
const value = list_ptr.*;
roc_builtins_list_free_with(
value.bytes,
value.length,
value.capacity_or_alloc_ptr,
alignment,
element_width,
element_decref,
roc_ops,
);
}

/// Value-slot wrapper: incref a boxed payload pointer stored at `value_ptr`.
pub fn roc_builtins_box_incref_value(value_ptr: ?[*]u8, amount: isize, roc_ops: *RocOps) callconv(.c) void {
const bytes = value_ptr orelse return;
const payload_slot: *const ?[*]u8 = @ptrCast(@alignCast(bytes));
increfDataPtrC(payload_slot.*, amount, roc_ops);
}

/// Value-slot wrapper: decref a boxed payload pointer stored at `value_ptr`.
pub fn roc_builtins_box_decref_value(
value_ptr: ?[*]u8,
payload_alignment: u32,
payload_decref: ?RcDropFn,
roc_ops: *RocOps,
) callconv(.c) void {
const bytes = value_ptr orelse return;
const payload_slot: *const ?[*]u8 = @ptrCast(@alignCast(bytes));
roc_builtins_box_decref_with(payload_slot.*, payload_alignment, payload_decref, roc_ops);
}

/// Value-slot wrapper: free a boxed payload pointer stored at `value_ptr`.
pub fn roc_builtins_box_free_value(
value_ptr: ?[*]u8,
payload_alignment: u32,
payload_decref: ?RcDropFn,
roc_ops: *RocOps,
) callconv(.c) void {
const bytes = value_ptr orelse return;
const payload_slot: *const ?[*]u8 = @ptrCast(@alignCast(bytes));
roc_builtins_box_free_with(payload_slot.*, payload_alignment, payload_decref, roc_ops);
}

// ═══════════════════════════════════════════════════════════════════════════
// Memory/Refcounting Wrappers (re-export with dev_ prefix for consistency)
// ═══════════════════════════════════════════════════════════════════════════
Expand Down
11 changes: 11 additions & 0 deletions src/builtins/static_lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ comptime {
@export(&dw.roc_builtins_str_escape_and_quote, .{ .name = "roc_builtins_str_escape_and_quote" });
@export(&dw.roc_builtins_list_with_capacity, .{ .name = "roc_builtins_list_with_capacity" });
@export(&dw.roc_builtins_list_sort_with, .{ .name = "roc_builtins_list_sort_with" });
@export(&dw.roc_builtins_list_sort_with_rc, .{ .name = "roc_builtins_list_sort_with_rc" });
@export(&dw.roc_builtins_list_append_unsafe, .{ .name = "roc_builtins_list_append_unsafe" });
@export(&dw.roc_builtins_list_concat, .{ .name = "roc_builtins_list_concat" });
@export(&dw.roc_builtins_list_prepend, .{ .name = "roc_builtins_list_prepend" });
@export(&dw.roc_builtins_list_sublist, .{ .name = "roc_builtins_list_sublist" });
@export(&dw.roc_builtins_list_drop_at, .{ .name = "roc_builtins_list_drop_at" });
@export(&dw.roc_builtins_list_replace, .{ .name = "roc_builtins_list_replace" });
@export(&dw.roc_builtins_list_reserve, .{ .name = "roc_builtins_list_reserve" });
@export(&dw.roc_builtins_list_release_excess_capacity, .{ .name = "roc_builtins_list_release_excess_capacity" });
Expand All @@ -67,6 +69,15 @@ comptime {
@export(&dw.roc_builtins_list_free_with, .{ .name = "roc_builtins_list_free_with" });
@export(&dw.roc_builtins_box_decref_with, .{ .name = "roc_builtins_box_decref_with" });
@export(&dw.roc_builtins_box_free_with, .{ .name = "roc_builtins_box_free_with" });
@export(&dw.roc_builtins_str_incref_value, .{ .name = "roc_builtins_str_incref_value" });
@export(&dw.roc_builtins_str_decref_value, .{ .name = "roc_builtins_str_decref_value" });
@export(&dw.roc_builtins_str_free_value, .{ .name = "roc_builtins_str_free_value" });
@export(&dw.roc_builtins_list_incref_value, .{ .name = "roc_builtins_list_incref_value" });
@export(&dw.roc_builtins_list_decref_value, .{ .name = "roc_builtins_list_decref_value" });
@export(&dw.roc_builtins_list_free_value, .{ .name = "roc_builtins_list_free_value" });
@export(&dw.roc_builtins_box_incref_value, .{ .name = "roc_builtins_box_incref_value" });
@export(&dw.roc_builtins_box_decref_value, .{ .name = "roc_builtins_box_decref_value" });
@export(&dw.roc_builtins_box_free_value, .{ .name = "roc_builtins_box_free_value" });
@export(&dw.roc_builtins_allocate_with_refcount, .{ .name = "roc_builtins_allocate_with_refcount" });
@export(&dw.roc_builtins_incref_data_ptr, .{ .name = "roc_builtins_incref_data_ptr" });
@export(&dw.roc_builtins_decref_data_ptr, .{ .name = "roc_builtins_decref_data_ptr" });
Expand Down
Loading
Loading