Hi! I'm stuck on implementing list_replace_unsafe in the new dev backend. It's done in the interpreter, but I can't really get a grip. I've been looking at other low level builtions, but just realized they aren't wired up to the builtins, so I cannot trust them being the right implementation. Do you have advice on what are the right lowlevel functions to look at as inspiration? Think I only have to do work ing the LirCodeGen's generateLowLevel 's big switch. The unique things of this builtin is that it returns a struct of {list, element}. I've based my work on list_set (uses wrapListReplace almost exactly how I'd need it) and list_split_first (returns a struct). I'm also interested how the refcounting should be handled in LirCodeGen. I see that in wrapListReplace, it is skipped compleatly, so I figured it's already accounted for by the ownership rules specified in LowLevel.zig?
I'm getting Evaluation error: error.RecordIndexOutOfBounds
Hi Norbert,
I have a couple of CI issues I need to look at first but I will come back to this after that's done.
I'm getting
Evaluation error: error.RecordIndexOutOfBounds
Can you share your branch @Norbert Hajagos?
Here it is:
https://github.com/roc-lang/roc/tree/list-builtins
There is test/cli/list_replace.roc and test/snapshots/repl/list_replace.md you can use for the testing. I'm on NixOs, x86-64bit. I'm finishing the day, thank you for looking into it.
This is the core fix for the RecordIndexOutOfBounds:
+++ b/src/eval/interpreter.zig
@@ -2788,7 +2788,7 @@ pub const Interpreter = struct {
var dest = try self.pushRaw(record_layout, 0, result_rt_var);
var acc = try dest.asRecord(&self.runtime_layout_store);
- const list_field_stack_val = try acc.getFieldByIndex(list_field.idx, list_arg.rt_var);
+ const list_field_stack_val = try acc.getFieldByName("list", list_arg.rt_var);
const list_ptr: *builtins.list.RocList = @ptrCast(@alignCast(list_field_stack_val.ptr.?));
if (self.runtime_layout_store.isZeroSized(list_info.elem_layout)) {
@@ -2802,7 +2802,7 @@ pub const Interpreter = struct {
// Get pointer space for the replaced item
const elem_rt_var = elt_arg.rt_var;
- const prev_field_stack_val = try acc.getFieldByIndex(prev_field.idx, elem_rt_var);
+ const prev_field_stack_val = try acc.getFieldByName("prev", elem_rt_var);
Make sure to rebase on latest main too, that fixes problems that will cause zig build minici to fail otherwise.
I've based my work on list_set (uses wrapListReplace almost exactly how I'd need it) and list_split_first (returns a struct)
Yes, that is the way to go :)
In LirCodeGen.zig, everything in .list_replace_unsafe => { ... looks correct.
I'm also interested how the refcounting should be handled in LirCodeGen
Refcounting is handled by the ownership rules (see getArgOwnership in LowLevel.zig), not inside the wrapper.
The interpreter version does handle refcounting explicitly.
Thank you Anton! There's so much work being done by others that by the time I'm finished with my small changes, the ground has shifted from under me. But it's fun just reading others' code and figuring things out. Hope I can look at it this weekend, maybe even land it. I'll stick with implementing builtins for a while, so that Roc could collect the dividends from my initial learning investments. Would be a waste to stop here now :smile:
Last updated: Apr 10 2026 at 12:38 UTC