Merge branch 'trunk' into wasm_arrays

This commit is contained in:
rvcas 2021-12-17 11:54:23 -05:00
commit b9bd75d643
14 changed files with 490 additions and 336 deletions

View file

@ -16,7 +16,7 @@ const InPlace = enum(u8) {
}; };
const SMALL_STR_MAX_LENGTH = small_string_size - 1; 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); const blank_small_string: [@sizeOf(RocStr)]u8 = init_blank_small_string(small_string_size);
fn init_blank_small_string(comptime n: usize) [n]u8 { 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 const alignment = @alignOf(usize);
pub inline fn empty() RocStr { pub inline fn empty() RocStr {
const small_str_flag: isize = std.math.minInt(isize);
return RocStr{ return RocStr{
.str_len = 0, .str_len = @bitCast(usize, small_str_flag),
.str_bytes = null, .str_bytes = null,
}; };
} }
@ -80,7 +81,7 @@ pub const RocStr = extern struct {
} }
pub fn deinit(self: RocStr) void { 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); 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 { pub fn clone(in_place: InPlace, str: RocStr) RocStr {
if (str.isSmallStr() or str.isEmpty()) { if (str.isSmallStr()) {
// just return the bytes // just return the bytes
return str; return str;
} else { } else {
@ -214,7 +215,8 @@ pub const RocStr = extern struct {
} }
pub fn isEmpty(self: RocStr) bool { 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 // 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 { 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 // small strings can be copied
if (self.isSmallStr()) { if (self.isSmallStr()) {
return true; return true;
@ -321,8 +318,8 @@ pub const RocStr = extern struct {
// Since this conditional would be prone to branch misprediction, // Since this conditional would be prone to branch misprediction,
// make sure it will compile to a cmov. // 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)); // return if (self.isSmallStr()) (&@bitCast([@sizeOf(RocStr)]u8, self)) else (@ptrCast([*]u8, self.str_bytes));
if (self.isSmallStr() or self.isEmpty()) { if (self.isSmallStr()) {
const as_int = @ptrToInt(&self); const as_int = @ptrToInt(&self);
const as_ptr = @intToPtr([*]u8, as_int); const as_ptr = @intToPtr([*]u8, as_int);
return as_ptr; return as_ptr;

View file

@ -15,7 +15,7 @@ mod test_fmt {
use roc_test_utils::assert_multiline_str_eq; 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 // 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(); let arena = Bump::new();
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) { match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
Ok(actual) => { Ok(actual) => {
@ -34,21 +34,20 @@ mod test_fmt {
let expected = expected.trim_end(); let expected = expected.trim_end();
// First check that input formats to the expected version // 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 // Parse the expected result format it, asserting that it doesn't change
// It's important that formatting be stable / idempotent // 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) { fn expr_formats_same(input: &str) {
expr_formats_to(input, input); 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 arena = Bump::new();
let src = src.trim_end();
match module::parse_header(&arena, State::new(src.as_bytes())) { match module::parse_header(&arena, State::new(src.as_bytes())) {
Ok((actual, state)) => { Ok((actual, state)) => {
let mut buf = Buf::new_in(&arena); 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) { fn module_formats_same(input: &str) {
module_formats_to(input, input); module_formats_to(input, input);
} }

View file

@ -3,7 +3,7 @@ use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::{Interns, Symbol}; 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::ir::{BranchInfo, JoinPointId, Literal, Param, ProcLayout, SelfRecursive, Stmt};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
use roc_reporting::internal_error; use roc_reporting::internal_error;
@ -256,8 +256,8 @@ pub struct Backend64Bit<
phantom_cc: PhantomData<CC>, phantom_cc: PhantomData<CC>,
env: &'a Env<'a>, env: &'a Env<'a>,
interns: &'a mut Interns, interns: &'a mut Interns,
refcount_proc_gen: RefcountProcGenerator<'a>, helper_proc_gen: CodeGenHelp<'a>,
refcount_proc_symbols: Vec<'a, (Symbol, ProcLayout<'a>)>, helper_proc_symbols: Vec<'a, (Symbol, ProcLayout<'a>)>,
buf: Vec<'a, u8>, buf: Vec<'a, u8>,
relocs: Vec<'a, Relocation>, relocs: Vec<'a, Relocation>,
proc_name: Option<String>, proc_name: Option<String>,
@ -308,8 +308,8 @@ pub fn new_backend_64bit<
phantom_cc: PhantomData, phantom_cc: PhantomData,
env, env,
interns, interns,
refcount_proc_gen: RefcountProcGenerator::new(env.arena, IntWidth::I64, env.module_id), helper_proc_gen: CodeGenHelp::new(env.arena, IntWidth::I64, env.module_id),
refcount_proc_symbols: bumpalo::vec![in env.arena], helper_proc_symbols: bumpalo::vec![in env.arena],
proc_name: None, proc_name: None,
is_self_recursive: None, is_self_recursive: None,
buf: bumpalo::vec![in env.arena], buf: bumpalo::vec![in env.arena],
@ -346,19 +346,17 @@ impl<
fn interns(&self) -> &Interns { fn interns(&self) -> &Interns {
self.interns self.interns
} }
fn env_interns_refcount_mut( fn env_interns_helpers_mut(&mut self) -> (&Env<'a>, &mut Interns, &mut CodeGenHelp<'a>) {
&mut self, (self.env, self.interns, &mut self.helper_proc_gen)
) -> (&Env<'a>, &mut Interns, &mut RefcountProcGenerator<'a>) {
(self.env, self.interns, &mut self.refcount_proc_gen)
} }
fn refcount_proc_gen_mut(&mut self) -> &mut RefcountProcGenerator<'a> { fn helper_proc_gen_mut(&mut self) -> &mut CodeGenHelp<'a> {
&mut self.refcount_proc_gen &mut self.helper_proc_gen
} }
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>)> {
&mut self.refcount_proc_symbols &mut self.helper_proc_symbols
} }
fn refcount_proc_symbols(&self) -> &Vec<'a, (Symbol, ProcLayout<'a>)> { fn helper_proc_symbols(&self) -> &Vec<'a, (Symbol, ProcLayout<'a>)> {
&self.refcount_proc_symbols &self.helper_proc_symbols
} }
fn reset(&mut self, name: String, is_self_recursive: SelfRecursive) { fn reset(&mut self, name: String, is_self_recursive: SelfRecursive) {
@ -383,7 +381,7 @@ impl<
self.float_used_regs.clear(); self.float_used_regs.clear();
self.float_free_regs self.float_free_regs
.extend_from_slice(CC::FLOAT_DEFAULT_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<Symbol, (*const Literal<'a>, *const Layout<'a>)> { fn literal_map(&mut self) -> &mut MutMap<Symbol, (*const Literal<'a>, *const Layout<'a>)> {

View file

@ -8,7 +8,7 @@ use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{ModuleName, TagName}; use roc_module::ident::{ModuleName, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::gen_refcount::RefcountProcGenerator; use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::ir::{ use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout, BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout,
SelfRecursive, Stmt, 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 // 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, // 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. // rust understands that they are part of a single use of mutable self.
fn env_interns_refcount_mut( fn env_interns_helpers_mut(&mut self) -> (&Env<'a>, &mut Interns, &mut CodeGenHelp<'a>);
&mut self,
) -> (&Env<'a>, &mut Interns, &mut RefcountProcGenerator<'a>);
fn symbol_to_string(&self, symbol: Symbol, layout_id: LayoutId) -> String { fn symbol_to_string(&self, symbol: Symbol, layout_id: LayoutId) -> String {
layout_id.to_symbol_string(symbol, self.interns()) layout_id.to_symbol_string(symbol, self.interns())
@ -76,11 +74,11 @@ trait Backend<'a> {
.starts_with(ModuleName::APP) .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. /// 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. /// 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.scan_ast(&proc.body);
self.create_free_map(); self.create_free_map();
self.build_stmt(&proc.body, &proc.ret_layout); self.build_stmt(&proc.body, &proc.ret_layout);
let mut rc_proc_names = bumpalo::vec![in self.env().arena]; let mut helper_proc_names = bumpalo::vec![in self.env().arena];
rc_proc_names.reserve(self.refcount_proc_symbols().len()); helper_proc_names.reserve(self.helper_proc_symbols().len());
for (rc_proc_sym, rc_proc_layout) in self.refcount_proc_symbols() { for (rc_proc_sym, rc_proc_layout) in self.helper_proc_symbols() {
let name = layout_ids let name = layout_ids
.get_toplevel(*rc_proc_sym, rc_proc_layout) .get_toplevel(*rc_proc_sym, rc_proc_layout)
.to_symbol_string(*rc_proc_sym, self.interns()); .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(); 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. /// 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 // 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 // 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. // 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 (rc_stmt, new_specializations) = {
let (env, interns, rc_proc_gen) = self.env_interns_refcount_mut(); let (env, interns, rc_proc_gen) = self.env_interns_helpers_mut();
let module_id = env.module_id; let module_id = env.module_id;
let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap(); let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap();
rc_proc_gen.expand_refcount_stmt(ident_ids, layout, modify, *following) rc_proc_gen.expand_refcount_stmt(ident_ids, layout, modify, *following)
}; };
if let Some((rc_proc_symbol, rc_proc_layout)) = new_proc_info { for spec in new_specializations.into_iter() {
self.refcount_proc_symbols_mut() self.helper_proc_symbols_mut().push(spec);
.push((rc_proc_symbol, rc_proc_layout));
} }
self.build_stmt(rc_stmt, ret_layout) self.build_stmt(rc_stmt, ret_layout)
@ -538,7 +535,7 @@ trait Backend<'a> {
debug_assert_eq!( debug_assert_eq!(
1, 1,
args.len(), args.len(),
"RefCountGetPtr: expected to have exactly two argument" "RefCountGetPtr: expected to have exactly one argument"
); );
self.build_refcount_getptr(sym, &args[0]) self.build_refcount_getptr(sym, &args[0])
} }

View file

@ -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 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 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); env.module_id.register_debug_idents(ident_ids);
rc_procs helper_procs
}; };
let empty = bumpalo::collections::Vec::new_in(arena); let empty = bumpalo::collections::Vec::new_in(arena);
let rc_symbols_and_layouts = std::mem::replace(backend.refcount_proc_symbols_mut(), empty); let helper_symbols_and_layouts = std::mem::replace(backend.helper_proc_symbols_mut(), empty);
let mut rc_names_symbols_procs = Vec::with_capacity_in(rc_procs.len(), arena); let mut helper_names_symbols_procs = Vec::with_capacity_in(helper_procs.len(), arena);
// Names and linker data for refcounting procedures // Names and linker data for helpers
for ((sym, layout), proc) in rc_symbols_and_layouts.into_iter().zip(rc_procs) { for ((sym, layout), proc) in helper_symbols_and_layouts.into_iter().zip(helper_procs) {
let layout_id = layout_ids.get_toplevel(sym, &layout); let layout_id = layout_ids.get_toplevel(sym, &layout);
let fn_name = backend.symbol_to_string(sym, layout_id); let fn_name = backend.symbol_to_string(sym, layout_id);
if let Some(proc_id) = output.symbol_id(fn_name.as_bytes()) { if let Some(proc_id) = output.symbol_id(fn_name.as_bytes()) {
if let SymbolSection::Section(section_id) = output.symbol(proc_id).section { 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; continue;
} }
} }
internal_error!("failed to create rc fn for symbol {:?}", sym); internal_error!("failed to create rc fn for symbol {:?}", sym);
} }
// Build refcounting procedures // Build helpers
for (fn_name, section_id, proc_id, proc) in rc_names_symbols_procs { for (fn_name, section_id, proc_id, proc) in helper_names_symbols_procs {
build_proc( build_proc(
&mut output, &mut output,
&mut backend, &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 { for (section_id, reloc) in relocations {
match output.add_relocation(section_id, reloc) { match output.add_relocation(section_id, reloc) {
Ok(obj) => obj, Ok(obj) => obj,

View file

@ -15,8 +15,8 @@ use crate::llvm::build_list::{
list_single, list_sort_with, list_sublist, list_swap, list_single, list_sort_with, list_sublist, list_swap,
}; };
use crate::llvm::build_str::{ use crate::llvm::build_str::{
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8,
str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split, 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_starts_with, str_starts_with_code_point, str_to_utf8, str_trim, str_trim_left,
str_trim_right, str_trim_right,
}; };
@ -779,9 +779,6 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(), 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(), Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(),
Str(str_literal) => { Str(str_literal) => {
if str_literal.is_empty() {
empty_str(env)
} else {
let ctx = env.context; let ctx = env.context;
let builder = env.builder; let builder = env.builder;
let number_of_chars = str_literal.len() as u64; let number_of_chars = str_literal.len() as u64;
@ -840,9 +837,8 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
.const_int(*character as u64, false) .const_int(*character as u64, false)
.as_basic_value_enum(); .as_basic_value_enum();
let index_val = ctx.i64_type().const_int(index as u64, false); let index_val = ctx.i64_type().const_int(index as u64, false);
let elem_ptr = unsafe { let elem_ptr =
builder.build_in_bounds_gep(array_alloca, &[index_val], "index") unsafe { builder.build_in_bounds_gep(array_alloca, &[index_val], "index") };
};
builder.build_store(elem_ptr, val); builder.build_store(elem_ptr, val);
} }
@ -885,12 +881,7 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
) )
.unwrap(); .unwrap();
builder.build_bitcast( builder.build_bitcast(struct_val.into_struct_value(), str_type, "cast_collection")
struct_val.into_struct_value(),
str_type,
"cast_collection",
)
}
} }
} }
} }

View file

@ -432,12 +432,3 @@ pub fn str_equal<'a, 'ctx, 'env>(
bitcode::STR_EQUAL, 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())
}

View file

@ -1,13 +1,16 @@
use bumpalo::{self, collections::Vec}; use bumpalo::{self, collections::Vec};
use code_builder::Align; use code_builder::Align;
use roc_builtins::bitcode::IntWidth; use roc_builtins::bitcode::{self, IntWidth};
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_module::ident::Ident; use roc_module::ident::Ident;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_mono::gen_refcount::{RefcountProcGenerator, REFCOUNT_MAX}; use roc_mono::code_gen_help::{CodeGenHelp, REFCOUNT_MAX};
use roc_mono::ir::{CallType, Expr, JoinPointId, ListLiteralElement, Literal, Proc, Stmt}; use roc_mono::ir::{
CallType, Expr, JoinPointId, ListLiteralElement, Literal, Proc, ProcLayout, Stmt,
};
use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout}; use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout};
use roc_reporting::internal_error; use roc_reporting::internal_error;
@ -50,7 +53,7 @@ pub struct WasmBackend<'a> {
builtin_sym_index_map: MutMap<&'a str, usize>, builtin_sym_index_map: MutMap<&'a str, usize>,
proc_symbols: Vec<'a, (Symbol, u32)>, proc_symbols: Vec<'a, (Symbol, u32)>,
linker_symbols: Vec<'a, SymInfo>, linker_symbols: Vec<'a, SymInfo>,
refcount_proc_gen: RefcountProcGenerator<'a>, helper_proc_gen: CodeGenHelp<'a>,
// Function-level data // Function-level data
code_builder: CodeBuilder<'a>, code_builder: CodeBuilder<'a>,
@ -72,7 +75,7 @@ impl<'a> WasmBackend<'a> {
proc_symbols: Vec<'a, (Symbol, u32)>, proc_symbols: Vec<'a, (Symbol, u32)>,
mut linker_symbols: Vec<'a, SymInfo>, mut linker_symbols: Vec<'a, SymInfo>,
mut exports: Vec<'a, Export>, mut exports: Vec<'a, Export>,
refcount_proc_gen: RefcountProcGenerator<'a>, helper_proc_gen: CodeGenHelp<'a>,
) -> Self { ) -> Self {
const MEMORY_INIT_SIZE: u32 = 1024 * 1024; const MEMORY_INIT_SIZE: u32 = 1024 * 1024;
let arena = env.arena; let arena = env.arena;
@ -145,7 +148,7 @@ impl<'a> WasmBackend<'a> {
builtin_sym_index_map: MutMap::default(), builtin_sym_index_map: MutMap::default(),
proc_symbols, proc_symbols,
linker_symbols, linker_symbols,
refcount_proc_gen, helper_proc_gen,
// Function-level data // Function-level data
block_depth: 0, 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 let ident_ids = self
.interns .interns
.all_ident_ids .all_ident_ids
.get_mut(&self.env.module_id) .get_mut(&self.env.module_id)
.unwrap(); .unwrap();
self.refcount_proc_gen self.helper_proc_gen
.generate_refcount_procs(self.env.arena, ident_ids) .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> { pub fn finalize_module(mut self) -> WasmModule<'a> {
@ -523,8 +545,8 @@ impl<'a> WasmBackend<'a> {
.get_mut(&self.env.module_id) .get_mut(&self.env.module_id)
.unwrap(); .unwrap();
let (rc_stmt, new_proc_info) = self let (rc_stmt, new_specializations) = self
.refcount_proc_gen .helper_proc_gen
.expand_refcount_stmt(ident_ids, *layout, modify, *following); .expand_refcount_stmt(ident_ids, *layout, modify, *following);
if false { if false {
@ -532,24 +554,9 @@ impl<'a> WasmBackend<'a> {
println!("## rc_stmt:\n{}\n{:?}", rc_stmt.to_pretty(200), rc_stmt); 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, // If any new specializations were created, register their symbol data
// so that we can correctly generate calls to it. for spec in new_specializations.into_iter() {
if let Some((rc_proc_sym, rc_proc_layout)) = new_proc_info { self.register_helper_proc(spec);
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,
}));
} }
self.build_stmt(rc_stmt, ret_layout); self.build_stmt(rc_stmt, ret_layout);
@ -583,7 +590,13 @@ impl<'a> WasmBackend<'a> {
CallType::ByName { name: func_sym, .. } => { CallType::ByName { name: func_sym, .. } => {
// If this function is just a lowlevel wrapper, then inline it // If this function is just a lowlevel wrapper, then inline it
if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) { 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( let (param_types, ret_type) = self.storage.load_symbols_for_call(
@ -619,7 +632,7 @@ impl<'a> WasmBackend<'a> {
} }
CallType::LowLevel { op: lowlevel, .. } => { 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), x => todo!("call type {:?}", x),
@ -1009,6 +1022,7 @@ impl<'a> WasmBackend<'a> {
arguments: &'a [Symbol], arguments: &'a [Symbol],
return_sym: Symbol, return_sym: Symbol,
return_layout: WasmLayout, return_layout: WasmLayout,
storage: &StoredValue,
) { ) {
let (param_types, ret_type) = self.storage.load_symbols_for_call( let (param_types, ret_type) = self.storage.load_symbols_for_call(
self.env.arena, self.env.arena,
@ -1033,6 +1047,37 @@ impl<'a> WasmBackend<'a> {
BuiltinCall(name) => { BuiltinCall(name) => {
self.call_zig_builtin(name, param_types, ret_type); 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 => { NotImplemented => {
todo!("Low level operation {:?}", lowlevel) todo!("Low level operation {:?}", lowlevel)
} }

View file

@ -10,7 +10,7 @@ use roc_builtins::bitcode::IntWidth;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol}; 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::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds; use roc_mono::layout::LayoutIds;
use roc_reporting::internal_error; use roc_reporting::internal_error;
@ -94,7 +94,7 @@ pub fn build_module_help<'a>(
proc_symbols, proc_symbols,
linker_symbols, linker_symbols,
exports, exports,
RefcountProcGenerator::new(env.arena, IntWidth::I32, env.module_id), CodeGenHelp::new(env.arena, IntWidth::I32, env.module_id),
); );
if false { if false {
@ -110,21 +110,21 @@ pub fn build_module_help<'a>(
backend.build_proc(proc); backend.build_proc(proc);
} }
// Generate IR for refcounting procs // Generate specialized helpers for refcounting & equality
let refcount_procs = backend.generate_refcount_procs(); let helper_procs = backend.generate_helpers();
backend.register_symbol_debug_names(); backend.register_symbol_debug_names();
if false { if false {
println!("## refcount_procs"); println!("## helper_procs");
for proc in refcount_procs.iter() { for proc in helper_procs.iter() {
println!("{}", proc.to_pretty(200)); println!("{}", proc.to_pretty(200));
println!("{:#?}", proc); println!("{:#?}", proc);
} }
} }
// Generate Wasm for refcounting procs // Generate Wasm for refcounting procs
for proc in refcount_procs.iter() { for proc in helper_procs.iter() {
backend.build_proc(proc); backend.build_proc(proc);
} }

View file

@ -10,6 +10,9 @@ use crate::wasm_module::{Align, CodeBuilder, ValueType::*};
pub enum LowlevelBuildResult { pub enum LowlevelBuildResult {
Done, Done,
BuiltinCall(&'static str), BuiltinCall(&'static str),
SpecializedEq,
SpecializedNotEq,
SpecializedHash,
NotImplemented, NotImplemented,
} }
@ -360,8 +363,7 @@ pub fn decode_low_level<'a>(
match storage.get(&args[0]) { match storage.get(&args[0]) {
VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
match value_type { match value_type {
I32 => code_builder.i32_const(1), I32 | I64 => code_builder.i32_const(1), // always true for integers
I64 => code_builder.i32_const(1),
F32 => { F32 => {
code_builder.i32_reinterpret_f32(); code_builder.i32_reinterpret_f32();
code_builder.i32_const(0x7f80_0000); code_builder.i32_const(0x7f80_0000);
@ -573,7 +575,8 @@ pub fn decode_low_level<'a>(
code_builder.i32_and(); code_builder.i32_and();
} }
Int128 => compare_bytes(code_builder), 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(), F32 => code_builder.f32_ne(),
F64 => code_builder.f64_ne(), F64 => code_builder.f64_ne(),
}, },
StoredValue::StackMemory { .. } => { StoredValue::StackMemory { format, .. } => {
if matches!(format, DataStructure) {
return SpecializedNotEq;
} else {
decode_low_level(code_builder, storage, LowLevel::Eq, args, ret_layout); decode_low_level(code_builder, storage, LowLevel::Eq, args, ret_layout);
code_builder.i32_eqz(); code_builder.i32_eqz();
} }
}
}, },
And => code_builder.i32_and(), And => code_builder.i32_and(),
Or => code_builder.i32_or(), Or => code_builder.i32_or(),
Not => code_builder.i32_eqz(), Not => code_builder.i32_eqz(),
Hash => return NotImplemented, Hash => return SpecializedHash,
ExpectTrue => return NotImplemented, ExpectTrue => return NotImplemented,
RefCountGetPtr => { RefCountGetPtr => {
code_builder.i32_const(4); code_builder.i32_const(4);

View file

@ -9,7 +9,7 @@ use crate::ir::{
BranchInfo, Call, CallSpecId, CallType, Expr, HostExposedLayouts, Literal, ModifyRc, Proc, BranchInfo, Call, CallSpecId, CallType, Expr, HostExposedLayouts, Literal, ModifyRc, Proc,
ProcLayout, SelfRecursive, Stmt, UpdateModeId, 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_BOOL: Layout = Layout::Builtin(Builtin::Bool);
const LAYOUT_UNIT: Layout = Layout::Struct(&[]); 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; pub const REFCOUNT_MAX: usize = 0;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RefcountOp { pub enum HelperOp {
Inc, Inc,
Dec, Dec,
DecRef, 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 /// For example, when checking List equality, we need to visit each element and compare them.
/// `RefcountProcGenerator::expand_refcount_stmt()`, which returns IR statements /// Depending on the type of the list elements, we may need to recurse deeper into each element.
/// to call a refcounting procedure. The backend can then generate target code /// For tag unions, we may need branches for different tag IDs, etc.
/// for those IR statements instead of the original `Refcounting` statement.
/// ///
/// Essentially we are expanding the `Refcounting` statement into a more detailed /// This module creates specialized helper procs for all such operations and types used in the program.
/// form that's more suitable for code generation.
/// ///
/// But so far, we've only mentioned _calls_ to the refcounting procedures. /// The backend drives the process, in two steps:
/// The procedures themselves don't exist yet! /// 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, pub struct CodeGenHelp<'a> {
/// 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> {
arena: &'a Bump, arena: &'a Bump,
home: ModuleId, home: ModuleId,
ptr_size: u32, ptr_size: u32,
layout_isize: Layout<'a>, 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 /// 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 { pub fn new(arena: &'a Bump, intwidth_isize: IntWidth, home: ModuleId) -> Self {
RefcountProcGenerator { CodeGenHelp {
arena, arena,
home, home,
ptr_size: intwidth_isize.stack_size(), ptr_size: intwidth_isize.stack_size(),
layout_isize: Layout::Builtin(Builtin::Int(intwidth_isize)), 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. /// Expand a `Refcounting` node to a `Let` node that calls a specialized helper proc.
/// The helper procs themselves can be generated later by calling `generate_refcount_procs` /// The helper procs themselves are to be generated later with `generate_procs`
pub fn expand_refcount_stmt( pub fn expand_refcount_stmt(
&mut self, &mut self,
ident_ids: &mut IdentIds, ident_ids: &mut IdentIds,
layout: Layout<'a>, layout: Layout<'a>,
modify: &ModifyRc, modify: &ModifyRc,
following: &'a Stmt<'a>, following: &'a Stmt<'a>,
) -> (&'a Stmt<'a>, Option<(Symbol, ProcLayout<'a>)>) { ) -> (&'a Stmt<'a>, Vec<'a, (Symbol, ProcLayout<'a>)>) {
if !Self::layout_is_supported(&layout) { if !Self::is_rc_implemented_yet(&layout) {
// Just a warning, so we can decouple backend development from refcounting development. // 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. // When we are closer to completion, we can change it to a panic.
println!( println!(
"WARNING! MEMORY LEAK! Refcounting not yet implemented for Layout {:?}", "WARNING! MEMORY LEAK! Refcounting not yet implemented for Layout {:?}",
layout layout
); );
return (following, None); return (following, Vec::new_in(self.arena));
} }
let arena = self.arena; let arena = self.arena;
@ -94,8 +102,8 @@ impl<'a> RefcountProcGenerator<'a> {
ModifyRc::Inc(structure, amount) => { ModifyRc::Inc(structure, amount) => {
let layout_isize = self.layout_isize; let layout_isize = self.layout_isize;
let (is_existing, proc_name) = let (proc_name, new_procs_info) =
self.get_proc_symbol(ident_ids, layout, RefcountOp::Inc); self.get_or_create_proc_symbols_recursive(ident_ids, &layout, HelperOp::Inc);
// Define a constant for the amount to increment // Define a constant for the amount to increment
let amount_sym = self.create_symbol(ident_ids, "amount"); 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 call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following);
let rc_stmt = arena.alloc(amount_stmt(arena.alloc(call_stmt))); 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 (rc_stmt, new_procs_info)
let new_proc_info = if is_existing {
None
} else {
Some((
proc_name,
ProcLayout {
arguments: arg_layouts,
result: LAYOUT_UNIT,
},
))
};
(rc_stmt, new_proc_info)
} }
ModifyRc::Dec(structure) => { ModifyRc::Dec(structure) => {
let (is_existing, proc_name) = let (proc_name, new_procs_info) =
self.get_proc_symbol(ident_ids, layout, RefcountOp::Dec); self.get_or_create_proc_symbols_recursive(ident_ids, &layout, HelperOp::Dec);
// Call helper proc, passing the Roc structure // 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_result_empty = self.create_symbol(ident_ids, "call_result_empty");
let call_expr = Expr::Call(Call { let call_expr = Expr::Call(Call {
call_type: CallType::ByName { call_type: CallType::ByName {
@ -157,20 +151,7 @@ impl<'a> RefcountProcGenerator<'a> {
following, following,
)); ));
// Create a linker symbol for the helper proc if this is the first usage (rc_stmt, new_procs_info)
let new_proc_info = if is_existing {
None
} else {
Some((
proc_name,
ProcLayout {
arguments: arg_layouts,
result: LAYOUT_UNIT,
},
))
};
(rc_stmt, new_proc_info)
} }
ModifyRc::DecRef(structure) => { 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 call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following);
let rc_stmt = arena.alloc(rc_ptr_stmt(arena.alloc(call_stmt))); 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 /// Replace a generic `Lowlevel::Eq` call with a specialized helper proc.
// (Probably by generating procs on the fly instead of all at the end) /// The helper procs themselves are to be generated later with `generate_procs`
fn layout_is_supported(layout: &Layout) -> bool { 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)) matches!(layout, Layout::Builtin(Builtin::Str))
} }
/// Generate refcounting helper procs, each specialized to a particular Layout. /// Generate refcounting helper procs, each specialized to a particular Layout.
/// For example `List (Result { a: Str, b: Int } Str)` would get its own helper /// 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. /// to update the refcounts on the List, the Result and the strings.
pub fn generate_refcount_procs( pub fn generate_procs(
&mut self, &mut self,
arena: &'a Bump, arena: &'a Bump,
ident_ids: &mut IdentIds, ident_ids: &mut IdentIds,
) -> Vec<'a, Proc<'a>> { ) -> Vec<'a, Proc<'a>> {
// Move the vector out of self, so we can loop over it safely use HelperOp::*;
let mut procs_to_generate =
std::mem::replace(&mut self.procs_to_generate, Vec::with_capacity_in(0, arena));
let procs_iter = procs_to_generate // Move the vector out of self, so we can loop over it safely
.drain(0..) let mut specs = std::mem::replace(&mut self.specs, Vec::with_capacity_in(0, arena));
.map(|(layout, op, proc_symbol)| {
debug_assert!(Self::layout_is_supported(&layout)); 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 { match layout {
Layout::Builtin(Builtin::Str) => { Layout::Builtin(Builtin::Str) => {
self.gen_modify_str(ident_ids, op, proc_symbol) 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) Vec::from_iter_in(procs_iter, arena)
} }
/// Find the Symbol of the procedure for this layout and refcount operation, /// Find the Symbol of the procedure for this layout and operation
/// or create one if needed. /// If any new helper procs are needed for this layout or its children,
fn get_proc_symbol( /// return their details in a vector.
fn get_or_create_proc_symbols_recursive(
&mut self, &mut self,
ident_ids: &mut IdentIds, ident_ids: &mut IdentIds,
layout: Layout<'a>, layout: &Layout<'a>,
op: RefcountOp, op: HelperOp,
) -> (bool, Symbol) { ) -> (Symbol, Vec<'a, (Symbol, ProcLayout<'a>)>) {
let found = self let mut new_procs_info = Vec::new_in(self.arena);
.procs_to_generate
.iter() let proc_symbol =
.find(|(l, o, _)| *l == layout && *o == op); 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<ProcLayout<'a>>) {
let found = self.specs.iter().find(|(l, o, _)| l == layout && *o == op);
if let Some((_, _, existing_symbol)) = found { if let Some((_, _, existing_symbol)) = found {
(true, *existing_symbol) (*existing_symbol, None)
} else { } else {
let layout_name = layout_debug_name(&layout); let layout_name = layout_debug_name(layout);
let unique_idx = self.procs_to_generate.len(); let debug_name = format!("#help{:?}_{}", op, layout_name);
let debug_name = format!("#rc{:?}_{}_{}", op, layout_name, unique_idx);
let new_symbol: Symbol = self.create_symbol(ident_ids, &debug_name); let new_symbol: Symbol = self.create_symbol(ident_ids, &debug_name);
self.procs_to_generate.push((layout, op, new_symbol)); self.specs.push((*layout, op, new_symbol));
(false, 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) 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); let roc_value = (layout, Symbol::ARG_1);
match op { match op {
RefcountOp::Inc => { HelperOp::Inc => {
let inc_amount = (self.layout_isize, Symbol::ARG_2); let inc_amount = (self.layout_isize, Symbol::ARG_2);
self.arena.alloc([roc_value, inc_amount]) 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( fn gen_modify_str(
&mut self, &mut self,
ident_ids: &mut IdentIds, ident_ids: &mut IdentIds,
op: RefcountOp, op: HelperOp,
proc_name: Symbol, proc_name: Symbol,
) -> Proc<'a> { ) -> Proc<'a> {
let string = Symbol::ARG_1; let string = Symbol::ARG_1;
@ -349,20 +466,21 @@ impl<'a> RefcountProcGenerator<'a> {
// Call the relevant Zig lowlevel to actually modify the refcount // 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_result = self.create_symbol(ident_ids, "zig_call_result");
let zig_call_expr = match op { let zig_call_expr = match op {
RefcountOp::Inc => Expr::Call(Call { HelperOp::Inc => Expr::Call(Call {
call_type: CallType::LowLevel { call_type: CallType::LowLevel {
op: LowLevel::RefCountInc, op: LowLevel::RefCountInc,
update_mode: UpdateModeId::BACKEND_DUMMY, update_mode: UpdateModeId::BACKEND_DUMMY,
}, },
arguments: self.arena.alloc([rc_ptr, Symbol::ARG_2]), 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 { call_type: CallType::LowLevel {
op: LowLevel::RefCountDec, op: LowLevel::RefCountDec,
update_mode: UpdateModeId::BACKEND_DUMMY, update_mode: UpdateModeId::BACKEND_DUMMY,
}, },
arguments: self.arena.alloc([rc_ptr, alignment]), arguments: self.arena.alloc([rc_ptr, alignment]),
}), }),
_ => unreachable!(),
}; };
let zig_call_stmt = |next| Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, next); let zig_call_stmt = |next| Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, next);

View file

@ -4,7 +4,7 @@
pub mod alias_analysis; pub mod alias_analysis;
pub mod borrow; pub mod borrow;
pub mod gen_refcount; pub mod code_gen_help;
pub mod inc_dec; pub mod inc_dec;
pub mod ir; pub mod ir;
pub mod layout; pub mod layout;

View file

@ -665,17 +665,17 @@ fn str_starts_with_false_small_str() {
// ); // );
// } // }
// #[test] #[test]
// fn str_equality() { fn str_equality() {
// assert_evals_to!(r#""a" == "a""#, true, bool); assert_evals_to!(r#""a" == "a""#, true, bool);
// assert_evals_to!( assert_evals_to!(
// r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#, r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#,
// true, true,
// bool bool
// ); );
// assert_evals_to!(r#""a" != "b""#, true, bool); assert_evals_to!(r#""a" != "b""#, true, bool);
// assert_evals_to!(r#""a" == "b""#, false, bool); assert_evals_to!(r#""a" == "b""#, false, bool);
// } }
// #[test] // #[test]
// fn nested_recursive_literal() { // fn nested_recursive_literal() {

View file

@ -507,7 +507,7 @@ impl RocStr {
pub fn storage(&self) -> Option<Storage> { pub fn storage(&self) -> Option<Storage> {
use core::cmp::Ordering::*; use core::cmp::Ordering::*;
if self.is_small_str() || self.length == 0 { if self.is_small_str() {
return None; return None;
} }
@ -660,7 +660,7 @@ impl RocStr {
impl Default for RocStr { impl Default for RocStr {
fn default() -> Self { fn default() -> Self {
Self { Self {
length: 0, length: isize::MIN as usize,
elements: core::ptr::null_mut(), elements: core::ptr::null_mut(),
} }
} }
@ -693,7 +693,7 @@ impl Eq for RocStr {}
impl Clone for RocStr { impl Clone for RocStr {
fn clone(&self) -> Self { fn clone(&self) -> Self {
if self.is_small_str() || self.is_empty() { if self.is_small_str() {
Self { Self {
elements: self.elements, elements: self.elements,
length: self.length, length: self.length,
@ -730,7 +730,7 @@ impl Clone for RocStr {
impl Drop for RocStr { impl Drop for RocStr {
fn drop(&mut self) { 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(); let storage_ptr = self.get_storage_ptr_mut();
unsafe { unsafe {