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_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;

View file

@ -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);
}

View file

@ -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>)> {

View file

@ -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])
}

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 (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,

View file

@ -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")
}
}
}

View file

@ -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())
}

View file

@ -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)
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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() {

View file

@ -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 {