diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 1106c2a5f6..3f41946455 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -23,6 +23,7 @@ comptime { exportStrFn(str.strConcatC, "concat"); exportStrFn(str.strNumberOfBytes, "number_of_bytes"); exportStrFn(str.strFromIntC, "from_int"); + exportStrFn(str.strEqual, "equal"); } // Export helpers - Must be run inside a comptime diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 163c58a123..9a9f07db1d 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -260,6 +260,11 @@ pub const RocStr = extern struct { } }; +// Str.equal +pub fn strEqual(self: RocStr, other: RocStr) callconv(.C) bool { + return self.eq(other); +} + // Str.numberOfBytes pub fn strNumberOfBytes(string: RocStr) callconv(.C) usize { return string.len(); diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 5ccf0c89aa..606c03b3f5 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -31,3 +31,4 @@ pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with"; pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with"; pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes"; pub const STR_FROM_INT: &str = "roc_builtins.str.from_int"; +pub const STR_EQUAL: &str = "roc_builtins.str.equal"; diff --git a/compiler/gen/src/llvm/build_str.rs b/compiler/gen/src/llvm/build_str.rs index 7409a153ef..443b8512fb 100644 --- a/compiler/gen/src/llvm/build_str.rs +++ b/compiler/gen/src/llvm/build_str.rs @@ -77,6 +77,28 @@ fn str_symbol_to_i128<'a, 'ctx, 'env>( .into_int_value() } +fn str_to_i128<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + value: BasicValueEnum<'ctx>, +) -> IntValue<'ctx> { + let cell = env.builder.build_alloca(value.get_type(), "cell"); + + env.builder.build_store(cell, value); + + let i128_ptr = env + .builder + .build_bitcast( + cell, + env.context.i128_type().ptr_type(AddressSpace::Generic), + "cast", + ) + .into_pointer_value(); + + env.builder + .build_load(i128_ptr, "load_as_i128") + .into_int_value() +} + fn zig_str_to_struct<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, zig_str: StructValue<'ctx>, @@ -222,3 +244,19 @@ pub fn str_from_int<'a, 'ctx, 'env>( zig_str_to_struct(env, zig_result).into() } + +/// Str.equal : Str, Str -> Bool +pub fn str_equal<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + value1: BasicValueEnum<'ctx>, + value2: BasicValueEnum<'ctx>, +) -> BasicValueEnum<'ctx> { + let str1_i128 = str_to_i128(env, value1); + let str2_i128 = str_to_i128(env, value2); + + call_bitcode_fn( + env, + &[str1_i128.into(), str2_i128.into()], + &bitcode::STR_EQUAL, + ) +} diff --git a/compiler/gen/src/llvm/compare.rs b/compiler/gen/src/llvm/compare.rs index 2b46b68f37..ea5e86753c 100644 --- a/compiler/gen/src/llvm/compare.rs +++ b/compiler/gen/src/llvm/compare.rs @@ -1,5 +1,6 @@ use crate::llvm::build::Env; -use inkwell::values::BasicValueEnum; +use crate::llvm::build_str::str_equal; +use inkwell::values::{BasicValueEnum, IntValue}; use inkwell::{FloatPredicate, IntPredicate}; use roc_mono::layout::{Builtin, Layout}; @@ -43,6 +44,7 @@ pub fn build_eq<'a, 'ctx, 'env>( (Builtin::Int1, Builtin::Int1) => int_cmp(IntPredicate::EQ, "eq_i1"), (Builtin::Float64, Builtin::Float64) => float_cmp(FloatPredicate::OEQ, "eq_f64"), (Builtin::Float32, Builtin::Float32) => float_cmp(FloatPredicate::OEQ, "eq_f32"), + (Builtin::Str, Builtin::Str) => str_equal(env, lhs_val, rhs_val), (b1, b2) => { todo!("Handle equals for builtin layouts {:?} == {:?}", b1, b2); } @@ -95,6 +97,12 @@ pub fn build_neq<'a, 'ctx, 'env>( (Builtin::Int1, Builtin::Int1) => int_cmp(IntPredicate::NE, "neq_i1"), (Builtin::Float64, Builtin::Float64) => float_cmp(FloatPredicate::ONE, "neq_f64"), (Builtin::Float32, Builtin::Float32) => float_cmp(FloatPredicate::ONE, "neq_f32"), + (Builtin::Str, Builtin::Str) => { + let is_equal = str_equal(env, lhs_val, rhs_val).into_int_value(); + let result: IntValue = env.builder.build_not(is_equal, "negate"); + + result.into() + } (b1, b2) => { todo!("Handle not equals for builtin layouts {:?} == {:?}", b1, b2); } diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index 9f2b2393cc..e7c823e001 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -415,25 +415,24 @@ mod gen_list { ); } - // - // "panicked at 'not yet implemented: Handle equals for builtin layouts Str == Str'" - // - // #[test] - // fn list_keep_if_str_is_hello() { - // assert_evals_to!( - // indoc!( - // r#" - // strIsHello : Str -> Bool - // strIsHello = \str -> - // str == "Hello" - // - // List.keepIf ["Hello", "Hello", "Goodbye"] strIsHello - // "# - // ), - // RocList::from_slice(&["Hello", "Hello"]), - // RocList<&'static str> - // ); - // } + #[test] + #[ignore] + fn list_keep_if_str_is_hello() { + // keepIf causes a segfault with this function + assert_evals_to!( + indoc!( + r#" + strIsHello : Str -> Bool + strIsHello = \str -> + str == "Hello" + + List.keepIf ["Hello", "Hello", "Goodbye"] strIsHello + "# + ), + RocList::from_slice(&["Hello", "Hello"]), + RocList<&'static str> + ); + } #[test] fn list_map_on_empty_list_with_int_layout() { diff --git a/compiler/gen/tests/gen_str.rs b/compiler/gen/tests/gen_str.rs index 5a8e5143a5..3f43c4c48f 100644 --- a/compiler/gen/tests/gen_str.rs +++ b/compiler/gen/tests/gen_str.rs @@ -514,4 +514,16 @@ mod gen_str { let min = format!("{}", i64::MIN); assert_evals_to!(r#"Str.fromInt Num.minInt"#, &min, &'static str); } + + #[test] + fn str_equality() { + assert_evals_to!(r#""a" == "a""#, true, bool); + assert_evals_to!( + r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#, + true, + bool + ); + assert_evals_to!(r#""a" != "b""#, true, bool); + assert_evals_to!(r#""a" == "b""#, false, bool); + } }