diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 14ad92d698..2df42ae9e5 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -602,3 +602,89 @@ pub fn listAppend(list: RocList, alignment: usize, element: Opaque, element_widt return output; } + +pub fn listRange(width: utils.IntWidth, low: Opaque, high: Opaque) callconv(.C) RocList { + const allocator = std.heap.c_allocator; + const IntWidth = utils.IntWidth; + + switch (width) { + IntWidth.U8 => { + return helper1(allocator, u8, low, high); + }, + IntWidth.U16 => { + return helper1(allocator, u16, low, high); + }, + IntWidth.U32 => { + return helper1(allocator, u32, low, high); + }, + IntWidth.U64 => { + return helper1(allocator, u64, low, high); + }, + IntWidth.U128 => { + return helper1(allocator, u128, low, high); + }, + IntWidth.I8 => { + return helper1(allocator, i8, low, high); + }, + IntWidth.I16 => { + return helper1(allocator, i16, low, high); + }, + IntWidth.I32 => { + return helper1(allocator, i32, low, high); + }, + IntWidth.I64 => { + return helper1(allocator, i64, low, high); + }, + IntWidth.I128 => { + return helper1(allocator, i128, low, high); + }, + IntWidth.Usize => { + return helper1(allocator, usize, low, high); + }, + } +} + +fn helper1(allocator: *Allocator, comptime T: type, low: Opaque, high: Opaque) RocList { + const ptr1 = @ptrCast(*T, @alignCast(@alignOf(T), low)); + const ptr2 = @ptrCast(*T, @alignCast(@alignOf(T), high)); + + return listRangeHelp(allocator, T, ptr1.*, ptr2.*); +} + +fn listRangeHelp(allocator: *Allocator, comptime T: type, low: T, high: T) RocList { + const Order = std.math.Order; + + switch (std.math.order(low, high)) { + Order.gt => { + return RocList.empty(); + }, + + Order.eq => { + const list = RocList.allocate(allocator, @alignOf(usize), 1, @sizeOf(T)); + const buffer = @ptrCast([*]T, @alignCast(@alignOf(T), list.bytes orelse unreachable)); + + buffer[0] = low; + + return list; + }, + + Order.lt => { + const length: usize = @intCast(usize, high - low); + const list = RocList.allocate(allocator, @alignOf(usize), length, @sizeOf(T)); + + const buffer = @ptrCast([*]T, @alignCast(@alignOf(T), list.bytes orelse unreachable)); + + var i: usize = 0; + var current = low; + + while (i < length) { + buffer[i] = current; + + i += 1; + current += 1; + } + + return list; + }, + } +} diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index be9d3bb832..2661c7c1b0 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -19,6 +19,7 @@ comptime { exportListFn(list.listContains, "contains"); exportListFn(list.listRepeat, "repeat"); exportListFn(list.listAppend, "append"); + exportListFn(list.listRange, "range"); } // Dict Module diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 76d8908206..6628c46ba6 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -4,6 +4,58 @@ const Allocator = std.mem.Allocator; const REFCOUNT_ONE_ISIZE: comptime isize = std.math.minInt(isize); pub const REFCOUNT_ONE: usize = @bitCast(usize, REFCOUNT_ONE_ISIZE); +pub const IntWidth = enum(u8) { + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + Usize, +}; + +pub fn intWidth(width: IntWidth) anytype { + switch (width) { + IntWidth.U8 => { + return u8; + }, + IntWidth.U16 => { + return u16; + }, + IntWidth.U32 => { + return u32; + }, + IntWidth.U64 => { + return u64; + }, + IntWidth.U128 => { + return u128; + }, + IntWidth.I8 => { + return i8; + }, + IntWidth.I16 => { + return i16; + }, + IntWidth.I32 => { + return i32; + }, + IntWidth.I64 => { + return i64; + }, + IntWidth.I128 => { + return i128; + }, + IntWidth.Usize => { + return usize; + }, + } +} + pub fn decref( allocator: *Allocator, alignment: usize, diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index eeef60f61e..cebd34dbf8 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -75,3 +75,4 @@ pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards"; pub const LIST_CONTAINS: &str = "roc_builtins.list.contains"; pub const LIST_REPEAT: &str = "roc_builtins.list.repeat"; pub const LIST_APPEND: &str = "roc_builtins.list.append"; +pub const LIST_RANGE: &str = "roc_builtins.list.range"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index a98fcd4dc9..1fb11107ed 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -827,7 +827,7 @@ pub fn types() -> MutMap { ) }); - // keepOks : List before, (before -> Result * after) -> List after + // keepErrs: List before, (before -> Result * after) -> List after add_type(Symbol::LIST_KEEP_ERRS, { let_tvars! { star, cvar, before, after}; top_level_function( @@ -843,6 +843,14 @@ pub fn types() -> MutMap { ) }); + // range : Int a, Int a -> List (Int a) + add_type(Symbol::LIST_RANGE, { + top_level_function( + vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], + Box::new(list_type(int_type(flex(TVAR1)))), + ) + }); + // map : List before, (before -> after) -> List after add_type( Symbol::LIST_MAP, diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 9667b5a8a7..286c24554b 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -87,6 +87,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_KEEP_IF => list_keep_if, LIST_KEEP_OKS => list_keep_oks, LIST_KEEP_ERRS=> list_keep_errs, + LIST_RANGE => list_range, LIST_WALK => list_walk, LIST_WALK_BACKWARDS => list_walk_backwards, LIST_WALK_UNTIL => list_walk_until, @@ -2092,6 +2093,11 @@ fn list_keep_errs(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::ListKeepErrs, var_store) } +/// List.keepErrs: List before, (before -> Result * after) -> List after +fn list_range(symbol: Symbol, var_store: &mut VarStore) -> Def { + lowlevel_2(symbol, LowLevel::ListRange, var_store) +} + /// List.map : List before, (before -> after) -> List after fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def { lowlevel_2(symbol, LowLevel::ListMap, var_store) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 1fa860c7bf..8daf7dc5d4 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -7,8 +7,8 @@ use crate::llvm::build_hash::generic_hash; use crate::llvm::build_list::{ allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map, - list_map2, list_map3, list_map_with_index, list_prepend, list_repeat, list_reverse, list_set, - list_single, list_walk_help, + list_map2, list_map3, list_map_with_index, list_prepend, list_range, list_repeat, list_reverse, + list_set, list_single, list_walk_help, }; use crate::llvm::build_str::{ str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8, @@ -3879,6 +3879,20 @@ fn run_low_level<'a, 'ctx, 'env>( list_contains(env, layout_ids, elem, elem_layout, list) } + ListRange => { + // List.contains : List elem, elem -> Bool + debug_assert_eq!(args.len(), 2); + + let (low, low_layout) = load_symbol_and_layout(scope, &args[0]); + let high = load_symbol(scope, &args[1]); + + let builtin = match low_layout { + Layout::Builtin(builtin) => builtin, + _ => unreachable!(), + }; + + list_range(env, *builtin, low.into_int_value(), high.into_int_value()) + } ListWalk => list_walk_help( env, layout_ids, diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index 1efccc8e22..70d80c4c78 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -851,6 +851,79 @@ fn list_walk_generic<'a, 'ctx, 'env>( env.builder.build_load(result_ptr, "load_result") } +#[allow(dead_code)] +#[repr(u8)] +enum IntWidth { + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + Usize, +} + +impl From> for IntWidth { + fn from(builtin: Builtin) -> Self { + use IntWidth::*; + + match builtin { + Builtin::Int128 => I128, + Builtin::Int64 => I64, + Builtin::Int32 => I32, + Builtin::Int16 => I16, + Builtin::Int8 => I8, + Builtin::Usize => Usize, + _ => unreachable!(), + } + } +} + +/// List.range : Int a, Int a -> List (Int a) +pub fn list_range<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + builtin: Builtin<'a>, + low: IntValue<'ctx>, + high: IntValue<'ctx>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + + let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic); + + let low_ptr = builder.build_alloca(low.get_type(), "low_ptr"); + env.builder.build_store(low_ptr, low); + + let high_ptr = builder.build_alloca(high.get_type(), "high_ptr"); + env.builder.build_store(high_ptr, high); + + let int_width = env + .context + .i8_type() + .const_int(IntWidth::from(builtin) as u64, false) + .into(); + + let output = call_bitcode_fn( + env, + &[ + int_width, + env.builder.build_bitcast(low_ptr, u8_ptr, "to_u8_ptr"), + env.builder.build_bitcast(high_ptr, u8_ptr, "to_u8_ptr"), + ], + &bitcode::LIST_RANGE, + ); + + complex_bitcast( + env.builder, + output, + collection(env.context, env.ptr_bytes).into(), + "from_i128", + ) +} + /// List.contains : List elem, elem -> Bool pub fn list_contains<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 297b4f459f..df8ae22fc3 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -26,6 +26,7 @@ pub enum LowLevel { ListAppend, ListPrepend, ListJoin, + ListRange, ListMap, ListMap2, ListMap3, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 4d97e103a5..68894d9fe3 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -918,6 +918,7 @@ define_builtins! { 27 LIST_SUM_ADD: "#sumadd" 28 LIST_PRODUCT_MUL: "#productmul" 29 LIST_WALK_UNTIL: "walkUntil" + 30 LIST_RANGE: "range" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 3fdbb24308..483a1e63de 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -655,6 +655,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, irrelevant]), ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]), ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]), + ListRange => arena.alloc_slice_copy(&[irrelevant, irrelevant]), ListWalk | ListWalkUntil | ListWalkBackwards => { arena.alloc_slice_copy(&[owned, irrelevant, owned]) } diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 7c1efa8dcf..5b65a05efb 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -1836,3 +1836,22 @@ fn cleanup_because_exception() { RocList ); } + +#[test] +fn list_range() { + assert_evals_to!( + indoc!("List.range 0 -1"), + RocList::from_slice(&[]), + RocList + ); + assert_evals_to!( + indoc!("List.range 0 0"), + RocList::from_slice(&[0]), + RocList + ); + assert_evals_to!( + indoc!("List.range 0 5"), + RocList::from_slice(&[0, 1, 2, 3, 4]), + RocList + ); +}