diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 26972282dc..06de496ae0 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -16,7 +16,7 @@ const InPlace = enum(u8) { }; const SMALL_STR_MAX_LENGTH = small_string_size - 1; -const small_string_size = 2 * @sizeOf(usize); +const small_string_size = @sizeOf(RocStr); const blank_small_string: [@sizeOf(RocStr)]u8 = init_blank_small_string(small_string_size); fn init_blank_small_string(comptime n: usize) [n]u8 { @@ -37,8 +37,9 @@ pub const RocStr = extern struct { pub const alignment = @alignOf(usize); pub inline fn empty() RocStr { + const small_str_flag: isize = std.math.minInt(isize); return RocStr{ - .str_len = 0, + .str_len = @bitCast(usize, small_str_flag), .str_bytes = null, }; } @@ -80,7 +81,7 @@ pub const RocStr = extern struct { } pub fn deinit(self: RocStr) void { - if (!self.isSmallStr() and !self.isEmpty()) { + if (!self.isSmallStr()) { utils.decref(self.str_bytes, self.str_len, RocStr.alignment); } } @@ -142,7 +143,7 @@ pub const RocStr = extern struct { } pub fn clone(in_place: InPlace, str: RocStr) RocStr { - if (str.isSmallStr() or str.isEmpty()) { + if (str.isSmallStr()) { // just return the bytes return str; } else { @@ -214,7 +215,8 @@ pub const RocStr = extern struct { } pub fn isEmpty(self: RocStr) bool { - return self.len() == 0; + comptime const empty_len = RocStr.empty().str_len; + return self.str_len == empty_len; } // If a string happens to be null-terminated already, then we can pass its @@ -294,11 +296,6 @@ pub const RocStr = extern struct { } pub fn isUnique(self: RocStr) bool { - // the empty string is unique (in the sense that copying it will not leak memory) - if (self.isEmpty()) { - return true; - } - // small strings can be copied if (self.isSmallStr()) { return true; @@ -321,8 +318,8 @@ pub const RocStr = extern struct { // Since this conditional would be prone to branch misprediction, // make sure it will compile to a cmov. - // return if (self.isSmallStr() or self.isEmpty()) (&@bitCast([@sizeOf(RocStr)]u8, self)) else (@ptrCast([*]u8, self.str_bytes)); - if (self.isSmallStr() or self.isEmpty()) { + // return if (self.isSmallStr()) (&@bitCast([@sizeOf(RocStr)]u8, self)) else (@ptrCast([*]u8, self.str_bytes)); + if (self.isSmallStr()) { const as_int = @ptrToInt(&self); const as_ptr = @intToPtr([*]u8, as_int); return as_ptr; diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 507809b0dd..f978de93fa 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -15,7 +15,7 @@ mod test_fmt { use roc_test_utils::assert_multiline_str_eq; // Not intended to be used directly in tests; please use expr_formats_to or expr_formats_same - fn expect_format_helper(input: &str, expected: &str) { + fn expect_format_expr_helper(input: &str, expected: &str) { let arena = Bump::new(); match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) { Ok(actual) => { @@ -34,21 +34,20 @@ mod test_fmt { let expected = expected.trim_end(); // First check that input formats to the expected version - expect_format_helper(input, expected); + expect_format_expr_helper(input, expected); // Parse the expected result format it, asserting that it doesn't change // It's important that formatting be stable / idempotent - expect_format_helper(expected, expected); + expect_format_expr_helper(expected, expected); } fn expr_formats_same(input: &str) { expr_formats_to(input, input); } - fn module_formats_to(src: &str, expected: &str) { + // Not intended to be used directly in tests; please use module_formats_to or module_formats_same + fn expect_format_module_helper(src: &str, expected: &str) { let arena = Bump::new(); - let src = src.trim_end(); - match module::parse_header(&arena, State::new(src.as_bytes())) { Ok((actual, state)) => { let mut buf = Buf::new_in(&arena); @@ -70,6 +69,17 @@ mod test_fmt { }; } + fn module_formats_to(input: &str, expected: &str) { + let input = input.trim_end(); + + // First check that input formats to the expected version + expect_format_module_helper(input, expected); + + // Parse the expected result format it, asserting that it doesn't change + // It's important that formatting be stable / idempotent + expect_format_module_helper(expected, expected); + } + fn module_formats_same(input: &str) { module_formats_to(input, input); } diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 28d1e9eb6a..e4d6e99a70 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -3,7 +3,7 @@ use bumpalo::collections::Vec; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::{Interns, Symbol}; -use roc_mono::gen_refcount::RefcountProcGenerator; +use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt}; use roc_mono::layout::{Builtin, Layout}; use roc_reporting::internal_error; @@ -256,8 +256,8 @@ pub struct Backend64Bit< phantom_cc: PhantomData, env: &'a Env<'a>, interns: &'a mut Interns, - refcount_proc_gen: RefcountProcGenerator<'a>, - refcount_proc_symbols: Vec<'a, (Symbol, ProcLayout<'a>)>, + helper_proc_gen: CodeGenHelp<'a>, + helper_proc_symbols: Vec<'a, (Symbol, ProcLayout<'a>)>, buf: Vec<'a, u8>, relocs: Vec<'a, Relocation>, proc_name: Option, @@ -308,8 +308,8 @@ pub fn new_backend_64bit< phantom_cc: PhantomData, env, interns, - refcount_proc_gen: RefcountProcGenerator::new(env.arena, IntWidth::I64, env.module_id), - refcount_proc_symbols: bumpalo::vec![in env.arena], + helper_proc_gen: CodeGenHelp::new(env.arena, IntWidth::I64, env.module_id), + helper_proc_symbols: bumpalo::vec![in env.arena], proc_name: None, is_self_recursive: None, buf: bumpalo::vec![in env.arena], @@ -346,19 +346,17 @@ impl< fn interns(&self) -> &Interns { self.interns } - fn env_interns_refcount_mut( - &mut self, - ) -> (&Env<'a>, &mut Interns, &mut RefcountProcGenerator<'a>) { - (self.env, self.interns, &mut self.refcount_proc_gen) + fn env_interns_helpers_mut(&mut self) -> (&Env<'a>, &mut Interns, &mut CodeGenHelp<'a>) { + (self.env, self.interns, &mut self.helper_proc_gen) } - fn refcount_proc_gen_mut(&mut self) -> &mut RefcountProcGenerator<'a> { - &mut self.refcount_proc_gen + fn helper_proc_gen_mut(&mut self) -> &mut CodeGenHelp<'a> { + &mut self.helper_proc_gen } - fn refcount_proc_symbols_mut(&mut self) -> &mut Vec<'a, (Symbol, ProcLayout<'a>)> { - &mut self.refcount_proc_symbols + fn helper_proc_symbols_mut(&mut self) -> &mut Vec<'a, (Symbol, ProcLayout<'a>)> { + &mut self.helper_proc_symbols } - fn refcount_proc_symbols(&self) -> &Vec<'a, (Symbol, ProcLayout<'a>)> { - &self.refcount_proc_symbols + fn helper_proc_symbols(&self) -> &Vec<'a, (Symbol, ProcLayout<'a>)> { + &self.helper_proc_symbols } fn reset(&mut self, name: String, is_self_recursive: SelfRecursive) { @@ -383,7 +381,7 @@ impl< self.float_used_regs.clear(); self.float_free_regs .extend_from_slice(CC::FLOAT_DEFAULT_FREE_REGS); - self.refcount_proc_symbols.clear(); + self.helper_proc_symbols.clear(); } fn literal_map(&mut self) -> &mut MutMap, *const Layout<'a>)> { diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index cbb808df84..cda45410f6 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -8,7 +8,7 @@ use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{ModuleName, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; -use roc_mono::gen_refcount::RefcountProcGenerator; +use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{ BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout, SelfRecursive, Stmt, @@ -62,9 +62,7 @@ trait Backend<'a> { // This method is suboptimal, but it seems to be the only way to make rust understand // that all of these values can be mutable at the same time. By returning them together, // rust understands that they are part of a single use of mutable self. - fn env_interns_refcount_mut( - &mut self, - ) -> (&Env<'a>, &mut Interns, &mut RefcountProcGenerator<'a>); + fn env_interns_helpers_mut(&mut self) -> (&Env<'a>, &mut Interns, &mut CodeGenHelp<'a>); fn symbol_to_string(&self, symbol: Symbol, layout_id: LayoutId) -> String { layout_id.to_symbol_string(symbol, self.interns()) @@ -76,11 +74,11 @@ trait Backend<'a> { .starts_with(ModuleName::APP) } - fn refcount_proc_gen_mut(&mut self) -> &mut RefcountProcGenerator<'a>; + fn helper_proc_gen_mut(&mut self) -> &mut CodeGenHelp<'a>; - fn refcount_proc_symbols_mut(&mut self) -> &mut Vec<'a, (Symbol, ProcLayout<'a>)>; + fn helper_proc_symbols_mut(&mut self) -> &mut Vec<'a, (Symbol, ProcLayout<'a>)>; - fn refcount_proc_symbols(&self) -> &Vec<'a, (Symbol, ProcLayout<'a>)>; + fn helper_proc_symbols(&self) -> &Vec<'a, (Symbol, ProcLayout<'a>)>; /// reset resets any registers or other values that may be occupied at the end of a procedure. /// It also passes basic procedure information to the builder for setup of the next function. @@ -116,17 +114,17 @@ trait Backend<'a> { self.scan_ast(&proc.body); self.create_free_map(); self.build_stmt(&proc.body, &proc.ret_layout); - let mut rc_proc_names = bumpalo::vec![in self.env().arena]; - rc_proc_names.reserve(self.refcount_proc_symbols().len()); - for (rc_proc_sym, rc_proc_layout) in self.refcount_proc_symbols() { + let mut helper_proc_names = bumpalo::vec![in self.env().arena]; + helper_proc_names.reserve(self.helper_proc_symbols().len()); + for (rc_proc_sym, rc_proc_layout) in self.helper_proc_symbols() { let name = layout_ids .get_toplevel(*rc_proc_sym, rc_proc_layout) .to_symbol_string(*rc_proc_sym, self.interns()); - rc_proc_names.push((*rc_proc_sym, name)); + helper_proc_names.push((*rc_proc_sym, name)); } let (bytes, relocs) = self.finalize(); - (bytes, relocs, rc_proc_names) + (bytes, relocs, helper_proc_names) } /// build_stmt builds a statement and outputs at the end of the buffer. @@ -150,17 +148,16 @@ trait Backend<'a> { // Expand the Refcounting statement into more detailed IR with a function call // If this layout requires a new RC proc, we get enough info to create a linker symbol // for it. Here we don't create linker symbols at this time, but in Wasm backend, we do. - let (rc_stmt, new_proc_info) = { - let (env, interns, rc_proc_gen) = self.env_interns_refcount_mut(); + let (rc_stmt, new_specializations) = { + let (env, interns, rc_proc_gen) = self.env_interns_helpers_mut(); let module_id = env.module_id; let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap(); rc_proc_gen.expand_refcount_stmt(ident_ids, layout, modify, *following) }; - if let Some((rc_proc_symbol, rc_proc_layout)) = new_proc_info { - self.refcount_proc_symbols_mut() - .push((rc_proc_symbol, rc_proc_layout)); + for spec in new_specializations.into_iter() { + self.helper_proc_symbols_mut().push(spec); } self.build_stmt(rc_stmt, ret_layout) @@ -538,7 +535,7 @@ trait Backend<'a> { debug_assert_eq!( 1, args.len(), - "RefCountGetPtr: expected to have exactly two argument" + "RefCountGetPtr: expected to have exactly one argument" ); self.build_refcount_getptr(sym, &args[0]) } diff --git a/compiler/gen_dev/src/object_builder.rs b/compiler/gen_dev/src/object_builder.rs index 40160d1e1e..318a5b0f38 100644 --- a/compiler/gen_dev/src/object_builder.rs +++ b/compiler/gen_dev/src/object_builder.rs @@ -240,38 +240,38 @@ fn build_object<'a, B: Backend<'a>>( ) } - let rc_procs = { + // Generate IR for specialized helper procs (refcounting & equality) + let helper_procs = { let module_id = backend.env().module_id; - let (env, interns, rc_proc_gen) = backend.env_interns_refcount_mut(); + let (env, interns, helper_proc_gen) = backend.env_interns_helpers_mut(); - // Generate IR for refcounting procedures let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap(); - let rc_procs = rc_proc_gen.generate_refcount_procs(arena, ident_ids); + let helper_procs = helper_proc_gen.generate_procs(arena, ident_ids); env.module_id.register_debug_idents(ident_ids); - rc_procs + helper_procs }; let empty = bumpalo::collections::Vec::new_in(arena); - let rc_symbols_and_layouts = std::mem::replace(backend.refcount_proc_symbols_mut(), empty); - let mut rc_names_symbols_procs = Vec::with_capacity_in(rc_procs.len(), arena); + let helper_symbols_and_layouts = std::mem::replace(backend.helper_proc_symbols_mut(), empty); + let mut helper_names_symbols_procs = Vec::with_capacity_in(helper_procs.len(), arena); - // Names and linker data for refcounting procedures - for ((sym, layout), proc) in rc_symbols_and_layouts.into_iter().zip(rc_procs) { + // Names and linker data for helpers + for ((sym, layout), proc) in helper_symbols_and_layouts.into_iter().zip(helper_procs) { let layout_id = layout_ids.get_toplevel(sym, &layout); let fn_name = backend.symbol_to_string(sym, layout_id); if let Some(proc_id) = output.symbol_id(fn_name.as_bytes()) { if let SymbolSection::Section(section_id) = output.symbol(proc_id).section { - rc_names_symbols_procs.push((fn_name, section_id, proc_id, proc)); + helper_names_symbols_procs.push((fn_name, section_id, proc_id, proc)); continue; } } internal_error!("failed to create rc fn for symbol {:?}", sym); } - // Build refcounting procedures - for (fn_name, section_id, proc_id, proc) in rc_names_symbols_procs { + // Build helpers + for (fn_name, section_id, proc_id, proc) in helper_names_symbols_procs { build_proc( &mut output, &mut backend, @@ -285,7 +285,7 @@ fn build_object<'a, B: Backend<'a>>( ) } - // Relocations for all procedures (user code & refcounting) + // Relocations for all procedures (user code & helpers) for (section_id, reloc) in relocations { match output.add_relocation(section_id, reloc) { Ok(obj) => obj, diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 685d517742..66c9715c93 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -15,8 +15,8 @@ use crate::llvm::build_list::{ list_single, list_sort_with, list_sublist, list_swap, }; use crate::llvm::build_str::{ - empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, - str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split, + str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8, + str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split, str_starts_with, str_starts_with_code_point, str_to_utf8, str_trim, str_trim_left, str_trim_right, }; @@ -779,118 +779,109 @@ pub fn build_exp_literal<'a, 'ctx, 'env>( Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(), Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(), Str(str_literal) => { - if str_literal.is_empty() { - empty_str(env) - } else { - let ctx = env.context; - let builder = env.builder; - let number_of_chars = str_literal.len() as u64; + let ctx = env.context; + let builder = env.builder; + let number_of_chars = str_literal.len() as u64; - let str_type = super::convert::zig_str_type(env); + let str_type = super::convert::zig_str_type(env); - if str_literal.len() < env.small_str_bytes() as usize { - // TODO support big endian systems + if str_literal.len() < env.small_str_bytes() as usize { + // TODO support big endian systems - let array_alloca = builder.build_array_alloca( - ctx.i8_type(), - ctx.i8_type().const_int(env.small_str_bytes() as u64, false), - "alloca_small_str", - ); + let array_alloca = builder.build_array_alloca( + ctx.i8_type(), + ctx.i8_type().const_int(env.small_str_bytes() as u64, false), + "alloca_small_str", + ); - // Zero out all the bytes. If we don't do this, then - // small strings would have uninitialized bytes, which could - // cause string equality checks to fail randomly. - // - // We're running memset over *all* the bytes, even though - // the final one is about to be manually overridden, on - // the theory that LLVM will optimize the memset call - // into two instructions to move appropriately-sized - // zero integers into the appropriate locations instead - // of doing any iteration. - // - // TODO: look at the compiled output to verify this theory! - env.call_memset( + // Zero out all the bytes. If we don't do this, then + // small strings would have uninitialized bytes, which could + // cause string equality checks to fail randomly. + // + // We're running memset over *all* the bytes, even though + // the final one is about to be manually overridden, on + // the theory that LLVM will optimize the memset call + // into two instructions to move appropriately-sized + // zero integers into the appropriate locations instead + // of doing any iteration. + // + // TODO: look at the compiled output to verify this theory! + env.call_memset( + array_alloca, + ctx.i8_type().const_zero(), + env.ptr_int().const_int(env.small_str_bytes() as u64, false), + ); + + let final_byte = (str_literal.len() as u8) | 0b1000_0000; + + let final_byte_ptr = unsafe { + builder.build_in_bounds_gep( array_alloca, - ctx.i8_type().const_zero(), - env.ptr_int().const_int(env.small_str_bytes() as u64, false), - ); - - let final_byte = (str_literal.len() as u8) | 0b1000_0000; - - let final_byte_ptr = unsafe { - builder.build_in_bounds_gep( - array_alloca, - &[ctx - .i8_type() - .const_int(env.small_str_bytes() as u64 - 1, false)], - "str_literal_final_byte", - ) - }; - - builder.build_store( - final_byte_ptr, - ctx.i8_type().const_int(final_byte as u64, false), - ); - - // Copy the elements from the list literal into the array - for (index, character) in str_literal.as_bytes().iter().enumerate() { - let val = env - .context + &[ctx .i8_type() - .const_int(*character as u64, false) - .as_basic_value_enum(); - let index_val = ctx.i64_type().const_int(index as u64, false); - let elem_ptr = unsafe { - builder.build_in_bounds_gep(array_alloca, &[index_val], "index") - }; - - builder.build_store(elem_ptr, val); - } - - builder.build_load( - builder - .build_bitcast( - array_alloca, - str_type.ptr_type(AddressSpace::Generic), - "cast_collection", - ) - .into_pointer_value(), - "small_str_array", + .const_int(env.small_str_bytes() as u64 - 1, false)], + "str_literal_final_byte", ) - } else { - let ptr = define_global_str_literal_ptr(env, *str_literal); - let number_of_elements = env.ptr_int().const_int(number_of_chars, false); + }; - let struct_type = str_type; + builder.build_store( + final_byte_ptr, + ctx.i8_type().const_int(final_byte as u64, false), + ); - let mut struct_val; + // Copy the elements from the list literal into the array + for (index, character) in str_literal.as_bytes().iter().enumerate() { + let val = env + .context + .i8_type() + .const_int(*character as u64, false) + .as_basic_value_enum(); + let index_val = ctx.i64_type().const_int(index as u64, false); + let elem_ptr = + unsafe { builder.build_in_bounds_gep(array_alloca, &[index_val], "index") }; - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr, - Builtin::WRAPPER_PTR, - "insert_ptr_str_literal", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value( - struct_val, - number_of_elements, - Builtin::WRAPPER_LEN, - "insert_len", - ) - .unwrap(); - - builder.build_bitcast( - struct_val.into_struct_value(), - str_type, - "cast_collection", - ) + builder.build_store(elem_ptr, val); } + + builder.build_load( + builder + .build_bitcast( + array_alloca, + str_type.ptr_type(AddressSpace::Generic), + "cast_collection", + ) + .into_pointer_value(), + "small_str_array", + ) + } else { + let ptr = define_global_str_literal_ptr(env, *str_literal); + let number_of_elements = env.ptr_int().const_int(number_of_chars, false); + + let struct_type = str_type; + + let mut struct_val; + + // Store the pointer + struct_val = builder + .build_insert_value( + struct_type.get_undef(), + ptr, + Builtin::WRAPPER_PTR, + "insert_ptr_str_literal", + ) + .unwrap(); + + // Store the length + struct_val = builder + .build_insert_value( + struct_val, + number_of_elements, + Builtin::WRAPPER_LEN, + "insert_len", + ) + .unwrap(); + + builder.build_bitcast(struct_val.into_struct_value(), str_type, "cast_collection") } } } diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index ce186340aa..3c92e39cdb 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -432,12 +432,3 @@ pub fn str_equal<'a, 'ctx, 'env>( bitcode::STR_EQUAL, ) } - -// TODO investigate: does this cause problems when the layout is known? this value is now not refcounted! -pub fn empty_str<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> { - let struct_type = super::convert::zig_str_type(env); - - // The pointer should be null (aka zero) and the length should be zero, - // so the whole struct should be a const_zero - BasicValueEnum::StructValue(struct_type.const_zero()) -} diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 881943121b..1fec3347d1 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1,13 +1,16 @@ use bumpalo::{self, collections::Vec}; use code_builder::Align; -use roc_builtins::bitcode::IntWidth; +use roc_builtins::bitcode::{self, IntWidth}; use roc_collections::all::MutMap; use roc_module::ident::Ident; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, Symbol}; -use roc_mono::gen_refcount::{RefcountProcGenerator, REFCOUNT_MAX}; -use roc_mono::ir::{CallType, Expr, JoinPointId, ListLiteralElement, Literal, Proc, Stmt}; +use roc_mono::code_gen_help::{CodeGenHelp, REFCOUNT_MAX}; +use roc_mono::ir::{ + CallType, Expr, JoinPointId, ListLiteralElement, Literal, Proc, ProcLayout, Stmt, +}; + use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout}; use roc_reporting::internal_error; @@ -50,7 +53,7 @@ pub struct WasmBackend<'a> { builtin_sym_index_map: MutMap<&'a str, usize>, proc_symbols: Vec<'a, (Symbol, u32)>, linker_symbols: Vec<'a, SymInfo>, - refcount_proc_gen: RefcountProcGenerator<'a>, + helper_proc_gen: CodeGenHelp<'a>, // Function-level data code_builder: CodeBuilder<'a>, @@ -72,7 +75,7 @@ impl<'a> WasmBackend<'a> { proc_symbols: Vec<'a, (Symbol, u32)>, mut linker_symbols: Vec<'a, SymInfo>, mut exports: Vec<'a, Export>, - refcount_proc_gen: RefcountProcGenerator<'a>, + helper_proc_gen: CodeGenHelp<'a>, ) -> Self { const MEMORY_INIT_SIZE: u32 = 1024 * 1024; let arena = env.arena; @@ -145,7 +148,7 @@ impl<'a> WasmBackend<'a> { builtin_sym_index_map: MutMap::default(), proc_symbols, linker_symbols, - refcount_proc_gen, + helper_proc_gen, // Function-level data block_depth: 0, @@ -158,15 +161,34 @@ impl<'a> WasmBackend<'a> { } } - pub fn generate_refcount_procs(&mut self) -> Vec<'a, Proc<'a>> { + pub fn generate_helpers(&mut self) -> Vec<'a, Proc<'a>> { let ident_ids = self .interns .all_ident_ids .get_mut(&self.env.module_id) .unwrap(); - self.refcount_proc_gen - .generate_refcount_procs(self.env.arena, ident_ids) + self.helper_proc_gen + .generate_procs(self.env.arena, ident_ids) + } + + fn register_helper_proc(&mut self, new_proc_info: (Symbol, ProcLayout<'a>)) { + let (new_proc_sym, new_proc_layout) = new_proc_info; + let wasm_fn_index = self.proc_symbols.len() as u32; + let linker_sym_index = self.linker_symbols.len() as u32; + + let name = self + .layout_ids + .get_toplevel(new_proc_sym, &new_proc_layout) + .to_symbol_string(new_proc_sym, self.interns); + + self.proc_symbols.push((new_proc_sym, linker_sym_index)); + self.linker_symbols + .push(SymInfo::Function(WasmObjectSymbol::Defined { + flags: 0, + index: wasm_fn_index, + name, + })); } pub fn finalize_module(mut self) -> WasmModule<'a> { @@ -523,8 +545,8 @@ impl<'a> WasmBackend<'a> { .get_mut(&self.env.module_id) .unwrap(); - let (rc_stmt, new_proc_info) = self - .refcount_proc_gen + let (rc_stmt, new_specializations) = self + .helper_proc_gen .expand_refcount_stmt(ident_ids, *layout, modify, *following); if false { @@ -532,24 +554,9 @@ impl<'a> WasmBackend<'a> { println!("## rc_stmt:\n{}\n{:?}", rc_stmt.to_pretty(200), rc_stmt); } - // If we're creating a new RC procedure, we need to store its symbol data, - // so that we can correctly generate calls to it. - if let Some((rc_proc_sym, rc_proc_layout)) = new_proc_info { - let wasm_fn_index = self.proc_symbols.len() as u32; - let linker_sym_index = self.linker_symbols.len() as u32; - - let name = self - .layout_ids - .get_toplevel(rc_proc_sym, &rc_proc_layout) - .to_symbol_string(rc_proc_sym, self.interns); - - self.proc_symbols.push((rc_proc_sym, linker_sym_index)); - self.linker_symbols - .push(SymInfo::Function(WasmObjectSymbol::Defined { - flags: 0, - index: wasm_fn_index, - name, - })); + // If any new specializations were created, register their symbol data + for spec in new_specializations.into_iter() { + self.register_helper_proc(spec); } self.build_stmt(rc_stmt, ret_layout); @@ -583,7 +590,13 @@ impl<'a> WasmBackend<'a> { CallType::ByName { name: func_sym, .. } => { // If this function is just a lowlevel wrapper, then inline it if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) { - return self.build_low_level(lowlevel, arguments, *sym, wasm_layout); + return self.build_low_level( + lowlevel, + arguments, + *sym, + wasm_layout, + storage, + ); } let (param_types, ret_type) = self.storage.load_symbols_for_call( @@ -619,7 +632,7 @@ impl<'a> WasmBackend<'a> { } CallType::LowLevel { op: lowlevel, .. } => { - self.build_low_level(*lowlevel, arguments, *sym, wasm_layout) + self.build_low_level(*lowlevel, arguments, *sym, wasm_layout, storage) } x => todo!("call type {:?}", x), @@ -1009,6 +1022,7 @@ impl<'a> WasmBackend<'a> { arguments: &'a [Symbol], return_sym: Symbol, return_layout: WasmLayout, + storage: &StoredValue, ) { let (param_types, ret_type) = self.storage.load_symbols_for_call( self.env.arena, @@ -1033,6 +1047,37 @@ impl<'a> WasmBackend<'a> { BuiltinCall(name) => { self.call_zig_builtin(name, param_types, ret_type); } + SpecializedEq | SpecializedNotEq => { + let layout = self.symbol_layouts[&arguments[0]]; + + if layout == Layout::Builtin(Builtin::Str) { + self.call_zig_builtin(bitcode::STR_EQUAL, param_types, ret_type); + } else { + let ident_ids = self + .interns + .all_ident_ids + .get_mut(&self.env.module_id) + .unwrap(); + + let (replacement_expr, new_specializations) = self + .helper_proc_gen + .specialize_equals(ident_ids, &layout, arguments); + + // If any new specializations were created, register their symbol data + for spec in new_specializations.into_iter() { + self.register_helper_proc(spec); + } + + self.build_expr(&return_sym, replacement_expr, &layout, storage); + } + + if matches!(build_result, SpecializedNotEq) { + self.code_builder.i32_eqz(); + } + } + SpecializedHash => { + todo!("Specialized hash functions") + } NotImplemented => { todo!("Low level operation {:?}", lowlevel) } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 19c499c32b..1586dc90d6 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -10,7 +10,7 @@ use roc_builtins::bitcode::IntWidth; use roc_collections::all::{MutMap, MutSet}; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; -use roc_mono::gen_refcount::RefcountProcGenerator; +use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; use roc_reporting::internal_error; @@ -94,7 +94,7 @@ pub fn build_module_help<'a>( proc_symbols, linker_symbols, exports, - RefcountProcGenerator::new(env.arena, IntWidth::I32, env.module_id), + CodeGenHelp::new(env.arena, IntWidth::I32, env.module_id), ); if false { @@ -110,21 +110,21 @@ pub fn build_module_help<'a>( backend.build_proc(proc); } - // Generate IR for refcounting procs - let refcount_procs = backend.generate_refcount_procs(); + // Generate specialized helpers for refcounting & equality + let helper_procs = backend.generate_helpers(); backend.register_symbol_debug_names(); if false { - println!("## refcount_procs"); - for proc in refcount_procs.iter() { + println!("## helper_procs"); + for proc in helper_procs.iter() { println!("{}", proc.to_pretty(200)); println!("{:#?}", proc); } } // Generate Wasm for refcounting procs - for proc in refcount_procs.iter() { + for proc in helper_procs.iter() { backend.build_proc(proc); } diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 86af29a11c..0ccd8d96af 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -10,6 +10,9 @@ use crate::wasm_module::{Align, CodeBuilder, ValueType::*}; pub enum LowlevelBuildResult { Done, BuiltinCall(&'static str), + SpecializedEq, + SpecializedNotEq, + SpecializedHash, NotImplemented, } @@ -360,8 +363,7 @@ pub fn decode_low_level<'a>( match storage.get(&args[0]) { VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { match value_type { - I32 => code_builder.i32_const(1), - I64 => code_builder.i32_const(1), + I32 | I64 => code_builder.i32_const(1), // always true for integers F32 => { code_builder.i32_reinterpret_f32(); code_builder.i32_const(0x7f80_0000); @@ -573,7 +575,8 @@ pub fn decode_low_level<'a>( code_builder.i32_and(); } Int128 => compare_bytes(code_builder), - Float128 | DataStructure => return NotImplemented, + Float128 => return NotImplemented, + DataStructure => return SpecializedEq, } } } @@ -587,15 +590,19 @@ pub fn decode_low_level<'a>( F32 => code_builder.f32_ne(), F64 => code_builder.f64_ne(), }, - StoredValue::StackMemory { .. } => { - decode_low_level(code_builder, storage, LowLevel::Eq, args, ret_layout); - code_builder.i32_eqz(); + StoredValue::StackMemory { format, .. } => { + if matches!(format, DataStructure) { + return SpecializedNotEq; + } else { + decode_low_level(code_builder, storage, LowLevel::Eq, args, ret_layout); + code_builder.i32_eqz(); + } } }, And => code_builder.i32_and(), Or => code_builder.i32_or(), Not => code_builder.i32_eqz(), - Hash => return NotImplemented, + Hash => return SpecializedHash, ExpectTrue => return NotImplemented, RefCountGetPtr => { code_builder.i32_const(4); diff --git a/compiler/mono/src/gen_refcount.rs b/compiler/mono/src/code_gen_help.rs similarity index 58% rename from compiler/mono/src/gen_refcount.rs rename to compiler/mono/src/code_gen_help.rs index 99e7be6057..2493f3da74 100644 --- a/compiler/mono/src/gen_refcount.rs +++ b/compiler/mono/src/code_gen_help.rs @@ -9,7 +9,7 @@ use crate::ir::{ BranchInfo, Call, CallSpecId, CallType, Expr, HostExposedLayouts, Literal, ModifyRc, Proc, ProcLayout, SelfRecursive, Stmt, UpdateModeId, }; -use crate::layout::{Builtin, Layout}; +use crate::layout::{Builtin, Layout, UnionLayout}; const LAYOUT_BOOL: Layout = Layout::Builtin(Builtin::Bool); const LAYOUT_UNIT: Layout = Layout::Struct(&[]); @@ -21,71 +21,79 @@ const LAYOUT_U32: Layout = Layout::Builtin(Builtin::Int(IntWidth::U32)); pub const REFCOUNT_MAX: usize = 0; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum RefcountOp { +pub enum HelperOp { Inc, Dec, DecRef, + Eq, } -/// Generate specialized refcounting code in mono IR format -/// ------------------------------------------------------- +impl From<&ModifyRc> for HelperOp { + fn from(modify: &ModifyRc) -> Self { + match modify { + ModifyRc::Inc(..) => Self::Inc, + ModifyRc::Dec(_) => Self::Dec, + ModifyRc::DecRef(_) => Self::DecRef, + } + } +} + +/// Generate specialized helper procs for code gen +/// ---------------------------------------------- /// -/// Any backend that wants to use this, needs a field of type `RefcountProcGenerator`. +/// Some low level operations need specialized helper procs to traverse data structures at runtime. +/// This includes refcounting, hashing, and equality checks. /// -/// Whenever the backend sees a `Stmt::Refcounting`, it calls -/// `RefcountProcGenerator::expand_refcount_stmt()`, which returns IR statements -/// to call a refcounting procedure. The backend can then generate target code -/// for those IR statements instead of the original `Refcounting` statement. +/// For example, when checking List equality, we need to visit each element and compare them. +/// Depending on the type of the list elements, we may need to recurse deeper into each element. +/// For tag unions, we may need branches for different tag IDs, etc. /// -/// Essentially we are expanding the `Refcounting` statement into a more detailed -/// form that's more suitable for code generation. +/// This module creates specialized helper procs for all such operations and types used in the program. /// -/// But so far, we've only mentioned _calls_ to the refcounting procedures. -/// The procedures themselves don't exist yet! +/// The backend drives the process, in two steps: +/// 1) When it sees the relevant node, it calls CodeGenHelp to get the replacement IR. +/// CodeGenHelp returns IR for a call to the helper proc, and remembers the specialization. +/// 2) After the backend has generated code for all user procs, it takes the IR for all of the +/// specialized helpers procs, and generates target code for them too. /// -/// So when the backend has finished with all the `Proc`s from user code, -/// it's time to call `RefcountProcGenerator::generate_refcount_procs()`, -/// which generates the `Procs` for refcounting helpers. The backend can -/// simply generate target code for these `Proc`s just like any other Proc. -/// -pub struct RefcountProcGenerator<'a> { +pub struct CodeGenHelp<'a> { arena: &'a Bump, home: ModuleId, ptr_size: u32, layout_isize: Layout<'a>, - /// List of refcounting procs to generate, specialised by Layout and RefCountOp + /// Specializations to generate /// Order of insertion is preserved, since it is important for Wasm backend - procs_to_generate: Vec<'a, (Layout<'a>, RefcountOp, Symbol)>, + specs: Vec<'a, (Layout<'a>, HelperOp, Symbol)>, } -impl<'a> RefcountProcGenerator<'a> { +impl<'a> CodeGenHelp<'a> { pub fn new(arena: &'a Bump, intwidth_isize: IntWidth, home: ModuleId) -> Self { - RefcountProcGenerator { + CodeGenHelp { arena, home, ptr_size: intwidth_isize.stack_size(), layout_isize: Layout::Builtin(Builtin::Int(intwidth_isize)), - procs_to_generate: Vec::with_capacity_in(16, arena), + specs: Vec::with_capacity_in(16, arena), } } - /// Expands the IR node Stmt::Refcounting to a more detailed IR Stmt that calls a helper proc. - /// The helper procs themselves can be generated later by calling `generate_refcount_procs` + /// Expand a `Refcounting` node to a `Let` node that calls a specialized helper proc. + /// The helper procs themselves are to be generated later with `generate_procs` pub fn expand_refcount_stmt( &mut self, ident_ids: &mut IdentIds, layout: Layout<'a>, modify: &ModifyRc, following: &'a Stmt<'a>, - ) -> (&'a Stmt<'a>, Option<(Symbol, ProcLayout<'a>)>) { - if !Self::layout_is_supported(&layout) { + ) -> (&'a Stmt<'a>, Vec<'a, (Symbol, ProcLayout<'a>)>) { + if !Self::is_rc_implemented_yet(&layout) { // Just a warning, so we can decouple backend development from refcounting development. // When we are closer to completion, we can change it to a panic. println!( "WARNING! MEMORY LEAK! Refcounting not yet implemented for Layout {:?}", layout ); - return (following, None); + return (following, Vec::new_in(self.arena)); } let arena = self.arena; @@ -94,8 +102,8 @@ impl<'a> RefcountProcGenerator<'a> { ModifyRc::Inc(structure, amount) => { let layout_isize = self.layout_isize; - let (is_existing, proc_name) = - self.get_proc_symbol(ident_ids, layout, RefcountOp::Inc); + let (proc_name, new_procs_info) = + self.get_or_create_proc_symbols_recursive(ident_ids, &layout, HelperOp::Inc); // Define a constant for the amount to increment let amount_sym = self.create_symbol(ident_ids, "amount"); @@ -117,28 +125,14 @@ impl<'a> RefcountProcGenerator<'a> { let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); let rc_stmt = arena.alloc(amount_stmt(arena.alloc(call_stmt))); - // Create a linker symbol for the helper proc if this is the first usage - let new_proc_info = if is_existing { - None - } else { - Some(( - proc_name, - ProcLayout { - arguments: arg_layouts, - result: LAYOUT_UNIT, - }, - )) - }; - - (rc_stmt, new_proc_info) + (rc_stmt, new_procs_info) } ModifyRc::Dec(structure) => { - let (is_existing, proc_name) = - self.get_proc_symbol(ident_ids, layout, RefcountOp::Dec); + let (proc_name, new_procs_info) = + self.get_or_create_proc_symbols_recursive(ident_ids, &layout, HelperOp::Dec); // Call helper proc, passing the Roc structure - let arg_layouts = arena.alloc([layout, self.layout_isize]); let call_result_empty = self.create_symbol(ident_ids, "call_result_empty"); let call_expr = Expr::Call(Call { call_type: CallType::ByName { @@ -157,20 +151,7 @@ impl<'a> RefcountProcGenerator<'a> { following, )); - // Create a linker symbol for the helper proc if this is the first usage - let new_proc_info = if is_existing { - None - } else { - Some(( - proc_name, - ProcLayout { - arguments: arg_layouts, - result: LAYOUT_UNIT, - }, - )) - }; - - (rc_stmt, new_proc_info) + (rc_stmt, new_procs_info) } ModifyRc::DecRef(structure) => { @@ -199,67 +180,202 @@ impl<'a> RefcountProcGenerator<'a> { let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following); let rc_stmt = arena.alloc(rc_ptr_stmt(arena.alloc(call_stmt))); - (rc_stmt, None) + (rc_stmt, Vec::new_in(self.arena)) } } } - // TODO: consider refactoring so that we have just one place to define what's supported - // (Probably by generating procs on the fly instead of all at the end) - fn layout_is_supported(layout: &Layout) -> bool { + /// Replace a generic `Lowlevel::Eq` call with a specialized helper proc. + /// The helper procs themselves are to be generated later with `generate_procs` + pub fn specialize_equals( + &mut self, + ident_ids: &mut IdentIds, + layout: &Layout<'a>, + arguments: &'a [Symbol], + ) -> (&'a Expr<'a>, Vec<'a, (Symbol, ProcLayout<'a>)>) { + // Record a specialization and get its name + let (proc_name, new_procs_info) = + self.get_or_create_proc_symbols_recursive(ident_ids, layout, HelperOp::Eq); + + // Call the specialized helper + let arg_layouts = self.arena.alloc([*layout, *layout]); + let expr = self.arena.alloc(Expr::Call(Call { + call_type: CallType::ByName { + name: proc_name, + ret_layout: &LAYOUT_BOOL, + arg_layouts, + specialization_id: CallSpecId::BACKEND_DUMMY, + }, + arguments, + })); + + (expr, new_procs_info) + } + + // Check if refcounting is implemented yet. In the long term, this will be deleted. + // In the short term, it helps us to skip refcounting and let it leak, so we can make + // progress incrementally. Kept in sync with generate_procs using assertions. + fn is_rc_implemented_yet(layout: &Layout) -> bool { matches!(layout, Layout::Builtin(Builtin::Str)) } /// Generate refcounting helper procs, each specialized to a particular Layout. /// For example `List (Result { a: Str, b: Int } Str)` would get its own helper /// to update the refcounts on the List, the Result and the strings. - pub fn generate_refcount_procs( + pub fn generate_procs( &mut self, arena: &'a Bump, ident_ids: &mut IdentIds, ) -> Vec<'a, Proc<'a>> { - // Move the vector out of self, so we can loop over it safely - let mut procs_to_generate = - std::mem::replace(&mut self.procs_to_generate, Vec::with_capacity_in(0, arena)); + use HelperOp::*; - let procs_iter = procs_to_generate - .drain(0..) - .map(|(layout, op, proc_symbol)| { - debug_assert!(Self::layout_is_supported(&layout)); + // Move the vector out of self, so we can loop over it safely + let mut specs = std::mem::replace(&mut self.specs, Vec::with_capacity_in(0, arena)); + + let procs_iter = specs.drain(0..).map(|(layout, op, proc_symbol)| match op { + Inc | Dec | DecRef => { + debug_assert!(Self::is_rc_implemented_yet(&layout)); match layout { Layout::Builtin(Builtin::Str) => { self.gen_modify_str(ident_ids, op, proc_symbol) } - _ => todo!("Please update layout_is_supported for {:?}", layout), + _ => todo!("Please update is_rc_implemented_yet for `{:?}`", layout), } - }); + } + Eq => match layout { + Layout::Builtin( + Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal, + ) => panic!( + "No generated helper proc. Use direct code gen for {:?}", + layout + ), + + Layout::Builtin(Builtin::Str) => { + panic!("No generated helper proc. Use Zig builtin for Str.") + } + + _ => todo!("Specialized equality check for `{:?}`", layout), + }, + }); Vec::from_iter_in(procs_iter, arena) } - /// Find the Symbol of the procedure for this layout and refcount operation, - /// or create one if needed. - fn get_proc_symbol( + /// Find the Symbol of the procedure for this layout and operation + /// If any new helper procs are needed for this layout or its children, + /// return their details in a vector. + fn get_or_create_proc_symbols_recursive( &mut self, ident_ids: &mut IdentIds, - layout: Layout<'a>, - op: RefcountOp, - ) -> (bool, Symbol) { - let found = self - .procs_to_generate - .iter() - .find(|(l, o, _)| *l == layout && *o == op); + layout: &Layout<'a>, + op: HelperOp, + ) -> (Symbol, Vec<'a, (Symbol, ProcLayout<'a>)>) { + let mut new_procs_info = Vec::new_in(self.arena); + + let proc_symbol = + self.get_or_create_proc_symbols_visit(ident_ids, &mut new_procs_info, op, layout); + + (proc_symbol, new_procs_info) + } + + fn get_or_create_proc_symbols_visit( + &mut self, + ident_ids: &mut IdentIds, + new_procs_info: &mut Vec<'a, (Symbol, ProcLayout<'a>)>, + op: HelperOp, + layout: &Layout<'a>, + ) -> Symbol { + if let Layout::LambdaSet(lambda_set) = layout { + return self.get_or_create_proc_symbols_visit( + ident_ids, + new_procs_info, + op, + &lambda_set.runtime_representation(), + ); + } + + let (symbol, new_proc_layout) = self.get_or_create_proc_symbol(ident_ids, layout, op); + + if let Some(proc_layout) = new_proc_layout { + new_procs_info.push((symbol, proc_layout)); + + let mut visit_child = |child| { + self.get_or_create_proc_symbols_visit(ident_ids, new_procs_info, op, child); + }; + + let mut visit_children = |children: &'a [Layout]| { + for child in children { + visit_child(child); + } + }; + + let mut visit_tags = |tags: &'a [&'a [Layout]]| { + for tag in tags { + visit_children(tag); + } + }; + + match layout { + Layout::Builtin(builtin) => match builtin { + Builtin::Dict(key, value) => { + visit_child(key); + visit_child(value); + } + Builtin::Set(element) | Builtin::List(element) => visit_child(element), + _ => {} + }, + Layout::Struct(fields) => visit_children(fields), + Layout::Union(union_layout) => match union_layout { + UnionLayout::NonRecursive(tags) => visit_tags(tags), + UnionLayout::Recursive(tags) => visit_tags(tags), + UnionLayout::NonNullableUnwrapped(fields) => visit_children(fields), + UnionLayout::NullableWrapped { other_tags, .. } => visit_tags(other_tags), + UnionLayout::NullableUnwrapped { other_fields, .. } => { + visit_children(other_fields) + } + }, + Layout::LambdaSet(_) => unreachable!(), + Layout::RecursivePointer => {} + } + } + + symbol + } + + fn get_or_create_proc_symbol( + &mut self, + ident_ids: &mut IdentIds, + layout: &Layout<'a>, + op: HelperOp, + ) -> (Symbol, Option>) { + let found = self.specs.iter().find(|(l, o, _)| l == layout && *o == op); if let Some((_, _, existing_symbol)) = found { - (true, *existing_symbol) + (*existing_symbol, None) } else { - let layout_name = layout_debug_name(&layout); - let unique_idx = self.procs_to_generate.len(); - let debug_name = format!("#rc{:?}_{}_{}", op, layout_name, unique_idx); + let layout_name = layout_debug_name(layout); + let debug_name = format!("#help{:?}_{}", op, layout_name); let new_symbol: Symbol = self.create_symbol(ident_ids, &debug_name); - self.procs_to_generate.push((layout, op, new_symbol)); - (false, new_symbol) + self.specs.push((*layout, op, new_symbol)); + + let new_proc_layout = match op { + HelperOp::Inc => Some(ProcLayout { + arguments: self.arena.alloc([*layout, self.layout_isize]), + result: LAYOUT_UNIT, + }), + HelperOp::Dec => Some(ProcLayout { + arguments: self.arena.alloc([*layout]), + result: LAYOUT_UNIT, + }), + HelperOp::DecRef => None, + HelperOp::Eq => Some(ProcLayout { + arguments: self.arena.alloc([*layout, *layout]), + result: LAYOUT_BOOL, + }), + }; + + (new_symbol, new_proc_layout) } } @@ -274,14 +390,15 @@ impl<'a> RefcountProcGenerator<'a> { Stmt::Let(unit, Expr::Struct(&[]), LAYOUT_UNIT, ret_stmt) } - fn gen_args(&self, op: RefcountOp, layout: Layout<'a>) -> &'a [(Layout<'a>, Symbol)] { + fn gen_args(&self, op: HelperOp, layout: Layout<'a>) -> &'a [(Layout<'a>, Symbol)] { let roc_value = (layout, Symbol::ARG_1); match op { - RefcountOp::Inc => { + HelperOp::Inc => { let inc_amount = (self.layout_isize, Symbol::ARG_2); self.arena.alloc([roc_value, inc_amount]) } - RefcountOp::Dec | RefcountOp::DecRef => self.arena.alloc([roc_value]), + HelperOp::Dec | HelperOp::DecRef => self.arena.alloc([roc_value]), + HelperOp::Eq => self.arena.alloc([roc_value, (layout, Symbol::ARG_2)]), } } @@ -289,7 +406,7 @@ impl<'a> RefcountProcGenerator<'a> { fn gen_modify_str( &mut self, ident_ids: &mut IdentIds, - op: RefcountOp, + op: HelperOp, proc_name: Symbol, ) -> Proc<'a> { let string = Symbol::ARG_1; @@ -349,20 +466,21 @@ impl<'a> RefcountProcGenerator<'a> { // Call the relevant Zig lowlevel to actually modify the refcount let zig_call_result = self.create_symbol(ident_ids, "zig_call_result"); let zig_call_expr = match op { - RefcountOp::Inc => Expr::Call(Call { + HelperOp::Inc => Expr::Call(Call { call_type: CallType::LowLevel { op: LowLevel::RefCountInc, update_mode: UpdateModeId::BACKEND_DUMMY, }, arguments: self.arena.alloc([rc_ptr, Symbol::ARG_2]), }), - RefcountOp::Dec | RefcountOp::DecRef => Expr::Call(Call { + HelperOp::Dec | HelperOp::DecRef => Expr::Call(Call { call_type: CallType::LowLevel { op: LowLevel::RefCountDec, update_mode: UpdateModeId::BACKEND_DUMMY, }, arguments: self.arena.alloc([rc_ptr, alignment]), }), + _ => unreachable!(), }; let zig_call_stmt = |next| Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, next); diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index 9dca478015..b48c0e6fd6 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -4,7 +4,7 @@ pub mod alias_analysis; pub mod borrow; -pub mod gen_refcount; +pub mod code_gen_help; pub mod inc_dec; pub mod ir; pub mod layout; diff --git a/compiler/test_gen/src/wasm_str.rs b/compiler/test_gen/src/wasm_str.rs index 04013a41ca..09bd0b44b7 100644 --- a/compiler/test_gen/src/wasm_str.rs +++ b/compiler/test_gen/src/wasm_str.rs @@ -665,17 +665,17 @@ fn str_starts_with_false_small_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); -// } +#[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); +} // #[test] // fn nested_recursive_literal() { diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 9002ef766d..5c1f8a58f4 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -507,7 +507,7 @@ impl RocStr { pub fn storage(&self) -> Option { use core::cmp::Ordering::*; - if self.is_small_str() || self.length == 0 { + if self.is_small_str() { return None; } @@ -660,7 +660,7 @@ impl RocStr { impl Default for RocStr { fn default() -> Self { Self { - length: 0, + length: isize::MIN as usize, elements: core::ptr::null_mut(), } } @@ -693,7 +693,7 @@ impl Eq for RocStr {} impl Clone for RocStr { fn clone(&self) -> Self { - if self.is_small_str() || self.is_empty() { + if self.is_small_str() { Self { elements: self.elements, length: self.length, @@ -730,7 +730,7 @@ impl Clone for RocStr { impl Drop for RocStr { fn drop(&mut self) { - if !self.is_small_str() && !self.is_empty() { + if !self.is_small_str() { let storage_ptr = self.get_storage_ptr_mut(); unsafe {