mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 06:44:46 +00:00
Merge branch 'trunk' into wasm_arrays
This commit is contained in:
commit
b9bd75d643
14 changed files with 490 additions and 336 deletions
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<CC>,
|
||||
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<String>,
|
||||
|
@ -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<Symbol, (*const Literal<'a>, *const Layout<'a>)> {
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,9 +779,6 @@ 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;
|
||||
|
@ -840,9 +837,8 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
|
|||
.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")
|
||||
};
|
||||
let elem_ptr =
|
||||
unsafe { builder.build_in_bounds_gep(array_alloca, &[index_val], "index") };
|
||||
|
||||
builder.build_store(elem_ptr, val);
|
||||
}
|
||||
|
@ -885,12 +881,7 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
builder.build_bitcast(
|
||||
struct_val.into_struct_value(),
|
||||
str_type,
|
||||
"cast_collection",
|
||||
)
|
||||
}
|
||||
builder.build_bitcast(struct_val.into_struct_value(), str_type, "cast_collection")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 { .. } => {
|
||||
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);
|
||||
|
|
|
@ -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<ProcLayout<'a>>) {
|
||||
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);
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -507,7 +507,7 @@ impl RocStr {
|
|||
pub fn storage(&self) -> Option<Storage> {
|
||||
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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue