diff --git a/src/build/builtin_compiler/main.zig b/src/build/builtin_compiler/main.zig index ed6209568e..3c3b463a5d 100644 --- a/src/build/builtin_compiler/main.zig +++ b/src/build/builtin_compiler/main.zig @@ -130,6 +130,9 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) { if (env.common.findIdent("Builtin.List.concat")) |list_concat_ident| { try low_level_map.put(list_concat_ident, .list_concat); } + if (env.common.findIdent("Builtin.List.append")) |list_append_ident| { + try low_level_map.put(list_append_ident, .list_append); + } if (env.common.findIdent("list_get_unsafe")) |list_get_unsafe_ident| { try low_level_map.put(list_get_unsafe_ident, .list_get_unsafe); } diff --git a/src/build/roc/Builtin.roc b/src/build/roc/Builtin.roc index ea79054f35..2f6a053c8c 100644 --- a/src/build/roc/Builtin.roc +++ b/src/build/roc/Builtin.roc @@ -44,6 +44,8 @@ Builtin :: [].{ True } + append : List(a), a -> List(a) + first : List(item) -> Try(item, [ListWasEmpty]) first = |list| List.get(list, 0) diff --git a/src/builtins/list.zig b/src/builtins/list.zig index 1082d972f2..9b38475e4f 100644 --- a/src/builtins/list.zig +++ b/src/builtins/list.zig @@ -11,10 +11,10 @@ const RocOps = @import("host_abi.zig").RocOps; const RocStr = @import("str.zig").RocStr; const increfDataPtrC = utils.increfDataPtrC; -const Opaque = ?[*]u8; +pub const Opaque = ?[*]u8; const EqFn = *const fn (Opaque, Opaque) callconv(.c) bool; const CompareFn = *const fn (Opaque, Opaque, Opaque) callconv(.c) u8; -const CopyFn = *const fn (Opaque, Opaque) callconv(.c) void; +pub const CopyFn = *const fn (Opaque, Opaque) callconv(.c) void; const Inc = *const fn (?*anyopaque, ?[*]u8) callconv(.c) void; const IncN = *const fn (?*anyopaque, ?[*]u8, usize) callconv(.c) void; @@ -531,7 +531,7 @@ pub fn listAppendUnsafe( list: RocList, element: Opaque, element_width: usize, - copy: CopyFn, + // copy: CopyFn, ) callconv(.c) RocList { const old_length = list.len(); var output = list; @@ -540,22 +540,23 @@ pub fn listAppendUnsafe( if (output.bytes) |bytes| { if (element) |source| { const target = bytes + old_length * element_width; - copy(target, source); + @memcpy(target[0..element_width], source[0..element_width]); } } return output; } -fn listAppend( +pub fn listAppend( list: RocList, alignment: u32, element: Opaque, element_width: usize, elements_refcounted: bool, + inc_context: ?*anyopaque, inc: Inc, update_mode: UpdateMode, - copy: CopyFn, + // copy: CopyFn, roc_ops: *RocOps, ) callconv(.c) RocList { const with_capacity = listReserve( @@ -564,11 +565,12 @@ fn listAppend( 1, element_width, elements_refcounted, + inc_context, inc, update_mode, roc_ops, ); - return listAppendUnsafe(with_capacity, element, element_width, copy); + return listAppendUnsafe(with_capacity, element, element_width); // copy } /// Directly mutate the given list to push an element onto the end, and then return it. diff --git a/src/canonicalize/Expression.zig b/src/canonicalize/Expression.zig index e24e1311e3..d138652867 100644 --- a/src/canonicalize/Expression.zig +++ b/src/canonicalize/Expression.zig @@ -464,6 +464,7 @@ pub const Expr = union(enum) { list_is_empty, list_get_unsafe, list_concat, + list_append, // Set operations set_is_empty, diff --git a/src/eval/interpreter.zig b/src/eval/interpreter.zig index 45989327c5..1944cb6948 100644 --- a/src/eval/interpreter.zig +++ b/src/eval/interpreter.zig @@ -3423,6 +3423,73 @@ pub const Interpreter = struct { out.is_initialized = true; return out; }, + + // .list_append => { + // // List.append: List(a), a -> List(a) + // std.debug.assert(args.len == 2); // low-level .list_get_unsafe expects 2 arguments + // + // const roc_list_arg = args[0]; + // const elt_arg = args[1]; + // + // std.debug.assert(roc_list_arg.ptr != null); // low-level .list_get_unsafe expects non-null list pointer + // + // // Extract element layout from List(a) + // std.debug.assert(roc_list_arg.layout.tag == .list or roc_list_arg.layout.tag == .list_of_zst); // low-level .list_get_unsafe expects list layout + // + // const roc_list: *const builtins.list.RocList = @ptrCast(@alignCast(roc_list_arg.ptr.?)); + // + // // const append_elt: *const builtins.list.Opaque = @ptrCast(@alignCast(elt_arg.ptr.?)); + // + // // Get element layout + // const elem_layout_idx = roc_list_arg.layout.data.list; + // const elem_layout = self.runtime_layout_store.getLayout(elem_layout_idx); + // const elem_size: u32 = self.runtime_layout_store.layoutSize(elem_layout); + // const elem_alignment = elem_layout.alignment(self.runtime_layout_store.targetUsize()).toByteUnits(); + // const elem_alignment_u32: u32 = @intCast(elem_alignment); + // + // // Determine if elements are refcounted + // const elements_refcounted = elem_layout.isRefcounted(); + // + // var temp_ptr: [32]u8 align(@alignOf(u128)) = undefined; + // + // try elt_arg.copyToPtr(&self.runtime_layout_store, &temp_ptr, roc_ops); + // + // const append_elt: *const builtins.list.Opaque = @ptrCast(@alignCast(&temp_ptr)); + // + // // Determine if list can be mutated in place + // const update_mode = if (roc_list.isUnique()) builtins.utils.UpdateMode.InPlace else builtins.utils.UpdateMode.Immutable; + // + // // Set up context for refcount callbacks + // var refcount_context = RefcountContext{ + // .layout_store = &self.runtime_layout_store, + // .elem_layout = elem_layout, + // .roc_ops = roc_ops, + // }; + // + // // const sized_copy = struct { + // // const item_size = elem_size; + // // fn copy_fn(target: builtins.list.Opaque, src: builtins.list.Opaque) callconv(.c) void { + // // const target_ptr: [*]u8 = target.?; + // // const src_ptr: [*]u8 = src.?; + // // @memcpy(target_ptr, src_ptr[0..item_size]); + // // } + // // }; + // + // const result_list = builtins.list.listAppend(roc_list.*, elem_alignment_u32, append_elt.*, elem_size, elements_refcounted, if (elements_refcounted) @ptrCast(&refcount_context) else null, if (elements_refcounted) &listElementInc else &builtins.list.rcNone, update_mode, roc_ops); + // + // // Allocate space for the result list + // const result_layout = roc_list_arg.layout; // Same layout as input + // var out = try self.pushRaw(result_layout, 0); + // out.is_initialized = false; + // + // // Copy the result list structure to the output + // const result_ptr: *builtins.list.RocList = @ptrCast(@alignCast(out.ptr.?)); + // result_ptr.* = result_list; + // + // out.is_initialized = true; + // return out; + // // return error.Crash; + // }, .set_is_empty => { // TODO: implement Set.is_empty self.triggerCrash("Set.is_empty not yet implemented", false, roc_ops); diff --git a/src/eval/test/low_level_interp_test.zig b/src/eval/test/low_level_interp_test.zig index 0f77f77cbf..d64e5d0355 100644 --- a/src/eval/test/low_level_interp_test.zig +++ b/src/eval/test/low_level_interp_test.zig @@ -666,6 +666,16 @@ test "e_low_level_lambda - List.concat with empty string list" { try testing.expectEqual(@as(i128, 3), len_value); } +test "e_low_level_lambda - List.append on non-empty list" { + const src = + \\x = List.append([0, 1, 2, 3], 4) + \\len = List.len(x) + ; + + const len_value = try evalModuleAndGetInt(src, 1); + try testing.expectEqual(@as(i128, 3), len_value); +} + test "e_low_level_lambda - Dec.to_str returns string representation of decimal" { const src = \\a : Dec