mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Merge pull request #2071 from rtfeldman/refcount-mono-ir
Start generating refcounting code as mono IR
This commit is contained in:
commit
a3827d6636
21 changed files with 726 additions and 209 deletions
|
@ -491,11 +491,12 @@ fn gen_from_mono_module_dev_wasm32(
|
|||
loaded: MonomorphizedModule,
|
||||
app_o_file: &Path,
|
||||
) -> CodeGenTiming {
|
||||
let mut procedures = MutMap::default();
|
||||
|
||||
for (key, proc) in loaded.procedures {
|
||||
procedures.insert(key, proc);
|
||||
}
|
||||
let MonomorphizedModule {
|
||||
module_id,
|
||||
procedures,
|
||||
mut interns,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
let exposed_to_host = loaded
|
||||
.exposed_to_host
|
||||
|
@ -505,11 +506,11 @@ fn gen_from_mono_module_dev_wasm32(
|
|||
|
||||
let env = roc_gen_wasm::Env {
|
||||
arena,
|
||||
interns: loaded.interns,
|
||||
module_id,
|
||||
exposed_to_host,
|
||||
};
|
||||
|
||||
let bytes = roc_gen_wasm::build_module(&env, procedures).unwrap();
|
||||
let bytes = roc_gen_wasm::build_module(&env, &mut interns, procedures).unwrap();
|
||||
|
||||
std::fs::write(&app_o_file, &bytes).expect("failed to write object to file");
|
||||
|
||||
|
|
|
@ -138,6 +138,7 @@ comptime {
|
|||
|
||||
comptime {
|
||||
exportUtilsFn(utils.test_panic, "test_panic");
|
||||
exportUtilsFn(utils.increfC, "incref");
|
||||
exportUtilsFn(utils.decrefC, "decref");
|
||||
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
|
||||
|
||||
|
|
|
@ -104,6 +104,12 @@ pub const IntWidth = enum(u8) {
|
|||
I128 = 9,
|
||||
};
|
||||
|
||||
pub fn increfC(ptr_to_refcount: *isize, amount: isize) callconv(.C) void {
|
||||
var refcount = ptr_to_refcount.*;
|
||||
var masked_amount = if (refcount == REFCOUNT_MAX_ISIZE) 0 else amount;
|
||||
ptr_to_refcount.* = refcount + masked_amount;
|
||||
}
|
||||
|
||||
pub fn decrefC(
|
||||
bytes_or_null: ?[*]isize,
|
||||
alignment: u32,
|
||||
|
@ -261,3 +267,17 @@ pub const UpdateMode = enum(u8) {
|
|||
Immutable = 0,
|
||||
InPlace = 1,
|
||||
};
|
||||
|
||||
test "increfC, refcounted data" {
|
||||
var mock_rc: isize = REFCOUNT_ONE_ISIZE + 17;
|
||||
var ptr_to_refcount: *isize = &mock_rc;
|
||||
increfC(ptr_to_refcount, 2);
|
||||
try std.testing.expectEqual(mock_rc, REFCOUNT_ONE_ISIZE + 19);
|
||||
}
|
||||
|
||||
test "increfC, static data" {
|
||||
var mock_rc: isize = REFCOUNT_MAX_ISIZE;
|
||||
var ptr_to_refcount: *isize = &mock_rc;
|
||||
increfC(ptr_to_refcount, 2);
|
||||
try std.testing.expectEqual(mock_rc, REFCOUNT_MAX_ISIZE);
|
||||
}
|
||||
|
|
|
@ -309,5 +309,6 @@ pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow";
|
|||
pub const DEC_DIV: &str = "roc_builtins.dec.div";
|
||||
|
||||
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
|
||||
pub const UTILS_INCREF: &str = "roc_builtins.utils.incref";
|
||||
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
|
||||
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
|
||||
|
|
|
@ -6038,6 +6038,10 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
| ListAny | ListAll | ListFindUnsafe | DictWalk => {
|
||||
unreachable!("these are higher order, and are handled elsewhere")
|
||||
}
|
||||
|
||||
RefCountGetPtr | RefCountInc | RefCountDec => {
|
||||
unreachable!("LLVM backend does not use lowlevels for refcounting");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ use roc_module::symbol::Interns;
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
|
||||
|
||||
/// "Infinite" reference count, for static values
|
||||
/// Ref counts are encoded as negative numbers where isize::MIN represents 1
|
||||
pub const REFCOUNT_MAX: usize = 0_usize;
|
||||
|
||||
pub fn refcount_1(ctx: &Context, ptr_bytes: u32) -> IntValue<'_> {
|
||||
|
|
|
@ -3,7 +3,8 @@ use bumpalo::{self, collections::Vec};
|
|||
use code_builder::Align;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_mono::gen_refcount::{RefcountProcGenerator, REFCOUNT_MAX};
|
||||
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds};
|
||||
|
||||
|
@ -20,7 +21,7 @@ use crate::wasm_module::sections::{
|
|||
};
|
||||
use crate::wasm_module::{
|
||||
code_builder, BlockType, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType,
|
||||
LocalId, Signature, SymInfo, ValueType,
|
||||
LinkingSubSection, LocalId, Signature, SymInfo, ValueType,
|
||||
};
|
||||
use crate::{
|
||||
copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_SIZE,
|
||||
|
@ -37,18 +38,21 @@ const CONST_SEGMENT_INDEX: usize = 0;
|
|||
|
||||
pub struct WasmBackend<'a> {
|
||||
env: &'a Env<'a>,
|
||||
interns: &'a mut Interns,
|
||||
|
||||
// Module-level data
|
||||
pub module: WasmModule<'a>,
|
||||
module: WasmModule<'a>,
|
||||
layout_ids: LayoutIds<'a>,
|
||||
constant_sym_index_map: MutMap<&'a str, usize>,
|
||||
builtin_sym_index_map: MutMap<&'a str, usize>,
|
||||
proc_symbols: Vec<'a, Symbol>,
|
||||
pub linker_symbols: Vec<'a, SymInfo>,
|
||||
proc_symbols: Vec<'a, (Symbol, u32)>,
|
||||
linker_symbols: Vec<'a, SymInfo>,
|
||||
refcount_proc_gen: RefcountProcGenerator<'a>,
|
||||
|
||||
// Function-level data
|
||||
code_builder: CodeBuilder<'a>,
|
||||
storage: Storage<'a>,
|
||||
symbol_layouts: MutMap<Symbol, Layout<'a>>,
|
||||
|
||||
/// how many blocks deep are we (used for jumps)
|
||||
block_depth: u32,
|
||||
|
@ -58,10 +62,12 @@ pub struct WasmBackend<'a> {
|
|||
impl<'a> WasmBackend<'a> {
|
||||
pub fn new(
|
||||
env: &'a Env<'a>,
|
||||
interns: &'a mut Interns,
|
||||
layout_ids: LayoutIds<'a>,
|
||||
proc_symbols: Vec<'a, Symbol>,
|
||||
proc_symbols: Vec<'a, (Symbol, u32)>,
|
||||
mut linker_symbols: Vec<'a, SymInfo>,
|
||||
mut exports: Vec<'a, Export>,
|
||||
refcount_proc_gen: RefcountProcGenerator<'a>,
|
||||
) -> Self {
|
||||
const MEMORY_INIT_SIZE: u32 = 1024 * 1024;
|
||||
let arena = env.arena;
|
||||
|
@ -124,6 +130,7 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
WasmBackend {
|
||||
env,
|
||||
interns,
|
||||
|
||||
// Module-level data
|
||||
module,
|
||||
|
@ -133,15 +140,47 @@ impl<'a> WasmBackend<'a> {
|
|||
builtin_sym_index_map: MutMap::default(),
|
||||
proc_symbols,
|
||||
linker_symbols,
|
||||
refcount_proc_gen,
|
||||
|
||||
// Function-level data
|
||||
block_depth: 0,
|
||||
joinpoint_label_map: MutMap::default(),
|
||||
code_builder: CodeBuilder::new(arena),
|
||||
storage: Storage::new(arena),
|
||||
symbol_layouts: MutMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_refcount_procs(&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)
|
||||
}
|
||||
|
||||
pub fn finalize_module(mut self) -> WasmModule<'a> {
|
||||
let symbol_table = LinkingSubSection::SymbolTable(self.linker_symbols);
|
||||
self.module.linking.subsections.push(symbol_table);
|
||||
self.module
|
||||
}
|
||||
|
||||
/// Register the debug names of Symbols in a global lookup table
|
||||
/// so that they have meaningful names when you print them.
|
||||
/// Particularly useful after generating IR for refcount procedures
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn register_symbol_debug_names(&self) {
|
||||
let module_id = self.env.module_id;
|
||||
let ident_ids = self.interns.all_ident_ids.get(&module_id).unwrap();
|
||||
self.env.module_id.register_debug_idents(ident_ids);
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub fn register_symbol_debug_names(&self) {}
|
||||
|
||||
/// Reset function-level data
|
||||
fn reset(&mut self) {
|
||||
// Push the completed CodeBuilder into the module and swap it for a new empty one
|
||||
|
@ -151,6 +190,7 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
self.storage.clear();
|
||||
self.joinpoint_label_map.clear();
|
||||
self.symbol_layouts.clear();
|
||||
assert_eq!(self.block_depth, 0);
|
||||
}
|
||||
|
||||
|
@ -160,17 +200,17 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
***********************************************************/
|
||||
|
||||
pub fn build_proc(&mut self, proc: Proc<'a>, _sym: Symbol) -> Result<(), String> {
|
||||
// println!("\ngenerating procedure {:?}\n", _sym);
|
||||
pub fn build_proc(&mut self, proc: &Proc<'a>) -> Result<(), String> {
|
||||
// println!("\ngenerating procedure {:?}\n", proc.name);
|
||||
|
||||
self.start_proc(&proc);
|
||||
self.start_proc(proc);
|
||||
|
||||
self.build_stmt(&proc.body, &proc.ret_layout)?;
|
||||
|
||||
self.finalize_proc()?;
|
||||
self.reset();
|
||||
|
||||
// println!("\nfinished generating {:?}\n", _sym);
|
||||
// println!("\nfinished generating {:?}\n", proc.name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -243,6 +283,8 @@ impl<'a> WasmBackend<'a> {
|
|||
Stmt::Let(_, _, _, _) => {
|
||||
let mut current_stmt = stmt;
|
||||
while let Stmt::Let(sym, expr, layout, following) = current_stmt {
|
||||
// println!("let {:?} = {}", sym, expr.to_pretty(200)); // ignore `following`! Too confusing otherwise.
|
||||
|
||||
let wasm_layout = WasmLayout::new(layout);
|
||||
|
||||
let kind = match following {
|
||||
|
@ -268,6 +310,8 @@ impl<'a> WasmBackend<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
self.symbol_layouts.insert(*sym, *layout);
|
||||
|
||||
current_stmt = *following;
|
||||
}
|
||||
|
||||
|
@ -458,9 +502,46 @@ impl<'a> WasmBackend<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
Stmt::Refcounting(_modify, following) => {
|
||||
// TODO: actually deal with refcounting. For hello world, we just skipped it.
|
||||
self.build_stmt(following, ret_layout)?;
|
||||
Stmt::Refcounting(modify, following) => {
|
||||
let value = modify.get_symbol();
|
||||
let layout = self.symbol_layouts.get(&value).unwrap();
|
||||
|
||||
let ident_ids = self
|
||||
.interns
|
||||
.all_ident_ids
|
||||
.get_mut(&self.env.module_id)
|
||||
.unwrap();
|
||||
|
||||
let (rc_stmt, new_proc_info) = self
|
||||
.refcount_proc_gen
|
||||
.expand_refcount_stmt(ident_ids, *layout, modify, *following);
|
||||
|
||||
if false {
|
||||
self.register_symbol_debug_names();
|
||||
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,
|
||||
}));
|
||||
}
|
||||
|
||||
self.build_stmt(&rc_stmt, ret_layout)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -504,29 +585,26 @@ impl<'a> WasmBackend<'a> {
|
|||
CallConv::C,
|
||||
);
|
||||
|
||||
// Index of the called function in the code section. Assumes all functions end up in the binary.
|
||||
// (We may decide to keep all procs even if calls are inlined, in case platform calls them)
|
||||
let func_index = match self.proc_symbols.iter().position(|s| s == func_sym) {
|
||||
Some(i) => i as u32,
|
||||
None => {
|
||||
// TODO: actually useful linking! Push a relocation for it.
|
||||
return Err(format!(
|
||||
"Not yet supported: calling foreign function {:?}",
|
||||
func_sym
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Index of the function's name in the symbol table
|
||||
// Same as the function index since those are the first symbols we add
|
||||
let symbol_index = func_index;
|
||||
|
||||
for (func_index, (ir_sym, linker_sym_index)) in
|
||||
self.proc_symbols.iter().enumerate()
|
||||
{
|
||||
if ir_sym == func_sym {
|
||||
let num_wasm_args = param_types.len();
|
||||
let has_return_val = ret_type.is_some();
|
||||
self.code_builder
|
||||
.call(func_index, symbol_index, num_wasm_args, has_return_val);
|
||||
self.code_builder.call(
|
||||
func_index as u32,
|
||||
*linker_sym_index,
|
||||
num_wasm_args,
|
||||
has_return_val,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
unreachable!(
|
||||
"Could not find procedure {:?}\nKnown procedures: {:?}",
|
||||
func_sym, self.proc_symbols
|
||||
);
|
||||
}
|
||||
|
||||
CallType::LowLevel { op: lowlevel, .. } => {
|
||||
|
@ -631,7 +709,7 @@ impl<'a> WasmBackend<'a> {
|
|||
sym: Symbol,
|
||||
layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
let not_supported_error = || Err(format!("Literal value {:?} is not yet implemented", lit));
|
||||
let not_supported_error = || panic!("Literal value {:?} is not yet implemented", lit);
|
||||
|
||||
match storage {
|
||||
StoredValue::VirtualMachineStack { value_type, .. } => {
|
||||
|
@ -675,6 +753,8 @@ impl<'a> WasmBackend<'a> {
|
|||
stack_mem_bytes[7] = 0x80 | (len as u8);
|
||||
let str_as_int = i64::from_le_bytes(stack_mem_bytes);
|
||||
|
||||
// Write all 8 bytes at once using an i64
|
||||
// Str is normally two i32's, but in this special case, we can get away with fewer instructions
|
||||
self.code_builder.get_local(local_id);
|
||||
self.code_builder.i64_const(str_as_int);
|
||||
self.code_builder.i64_store(Align::Bytes4, offset);
|
||||
|
@ -732,10 +812,13 @@ impl<'a> WasmBackend<'a> {
|
|||
None => {
|
||||
let const_segment_bytes = &mut self.module.data.segments[CONST_SEGMENT_INDEX].init;
|
||||
|
||||
// Store the string in the data section, to be loaded on module instantiation
|
||||
// RocStr `elements` field will point to that constant data, not the heap
|
||||
let segment_offset = const_segment_bytes.len() as u32;
|
||||
let elements_addr = segment_offset + CONST_SEGMENT_BASE_ADDR;
|
||||
// Store the string in the data section
|
||||
// Prefix it with a special refcount value (treated as "infinity")
|
||||
// The string's `elements` field points at the data after the refcount
|
||||
let refcount_max_bytes: [u8; 4] = (REFCOUNT_MAX as i32).to_le_bytes();
|
||||
const_segment_bytes.extend_from_slice(&refcount_max_bytes);
|
||||
let elements_offset = const_segment_bytes.len() as u32;
|
||||
let elements_addr = elements_offset + CONST_SEGMENT_BASE_ADDR;
|
||||
const_segment_bytes.extend_from_slice(string.as_bytes());
|
||||
|
||||
// Generate linker info
|
||||
|
@ -743,12 +826,12 @@ impl<'a> WasmBackend<'a> {
|
|||
let name = self
|
||||
.layout_ids
|
||||
.get(sym, layout)
|
||||
.to_symbol_string(sym, &self.env.interns);
|
||||
.to_symbol_string(sym, self.interns);
|
||||
let linker_symbol = SymInfo::Data(DataSymbol::Defined {
|
||||
flags: 0,
|
||||
name,
|
||||
segment_index: CONST_SEGMENT_INDEX as u32,
|
||||
segment_offset,
|
||||
segment_offset: elements_offset,
|
||||
size: string.len() as u32,
|
||||
});
|
||||
|
||||
|
@ -830,10 +913,11 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
None => {
|
||||
// Wasm function signature
|
||||
let signature_index = self.module.types.insert(Signature {
|
||||
let signature = Signature {
|
||||
param_types,
|
||||
ret_type,
|
||||
});
|
||||
};
|
||||
let signature_index = self.module.types.insert(signature);
|
||||
|
||||
// Declare it as an import since it comes from a different .o file
|
||||
let import_index = self.module.import.entries.len() as u32;
|
||||
|
|
|
@ -39,9 +39,6 @@ pub enum WasmLayout {
|
|||
alignment_bytes: u32,
|
||||
format: StackMemoryFormat,
|
||||
},
|
||||
|
||||
// Local pointer to heap memory
|
||||
HeapMemory,
|
||||
}
|
||||
|
||||
impl WasmLayout {
|
||||
|
@ -105,7 +102,7 @@ impl WasmLayout {
|
|||
| NullableWrapped { .. }
|
||||
| NullableUnwrapped { .. },
|
||||
)
|
||||
| Layout::RecursivePointer => Self::HeapMemory,
|
||||
| Layout::RecursivePointer => Self::Primitive(PTR_TYPE, PTR_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,7 +117,6 @@ impl WasmLayout {
|
|||
Self::Primitive(I64, _) => &[I64],
|
||||
Self::Primitive(F32, _) => &[F32],
|
||||
Self::Primitive(F64, _) => &[F64],
|
||||
Self::HeapMemory => &[I32],
|
||||
|
||||
// 1 Roc argument => 0-2 Wasm arguments (depending on size and calling convention)
|
||||
Self::StackMemory { size, format, .. } => conv.stack_memory_arg_types(*size, *format),
|
||||
|
@ -130,7 +126,6 @@ impl WasmLayout {
|
|||
pub fn return_method(&self) -> ReturnMethod {
|
||||
match self {
|
||||
Self::Primitive(ty, _) => ReturnMethod::Primitive(*ty),
|
||||
Self::HeapMemory => ReturnMethod::Primitive(PTR_TYPE),
|
||||
Self::StackMemory { size, .. } => {
|
||||
if *size == 0 {
|
||||
ReturnMethod::NoReturnValue
|
||||
|
@ -145,7 +140,6 @@ impl WasmLayout {
|
|||
match self {
|
||||
Self::Primitive(_, size) => *size,
|
||||
Self::StackMemory { size, .. } => *size,
|
||||
Self::HeapMemory => PTR_SIZE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,16 +6,17 @@ pub mod wasm_module;
|
|||
|
||||
use bumpalo::{self, collections::Vec, Bump};
|
||||
|
||||
use roc_builtins::bitcode::IntWidth;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_mono::gen_refcount::RefcountProcGenerator;
|
||||
use roc_mono::ir::{Proc, ProcLayout};
|
||||
use roc_mono::layout::LayoutIds;
|
||||
|
||||
use crate::backend::WasmBackend;
|
||||
use crate::wasm_module::{
|
||||
Align, CodeBuilder, Export, ExportType, LinkingSubSection, LocalId, SymInfo, ValueType,
|
||||
WasmModule,
|
||||
Align, CodeBuilder, Export, ExportType, LocalId, SymInfo, ValueType, WasmModule,
|
||||
};
|
||||
|
||||
const PTR_SIZE: u32 = 4;
|
||||
|
@ -29,27 +30,29 @@ pub const STACK_POINTER_NAME: &str = "__stack_pointer";
|
|||
|
||||
pub struct Env<'a> {
|
||||
pub arena: &'a Bump,
|
||||
pub interns: Interns,
|
||||
pub module_id: ModuleId,
|
||||
pub exposed_to_host: MutSet<Symbol>,
|
||||
}
|
||||
|
||||
pub fn build_module<'a>(
|
||||
env: &'a Env,
|
||||
env: &'a Env<'a>,
|
||||
interns: &'a mut Interns,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Result<std::vec::Vec<u8>, String> {
|
||||
let (mut wasm_module, _) = build_module_help(env, procedures)?;
|
||||
let (mut wasm_module, _) = build_module_help(env, interns, procedures)?;
|
||||
let mut buffer = std::vec::Vec::with_capacity(4096);
|
||||
wasm_module.serialize_mut(&mut buffer);
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn build_module_help<'a>(
|
||||
env: &'a Env,
|
||||
env: &'a Env<'a>,
|
||||
interns: &'a mut Interns,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Result<(WasmModule<'a>, u32), String> {
|
||||
let mut layout_ids = LayoutIds::default();
|
||||
let mut generated_procs = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
let mut generated_symbols = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
let mut procs = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
let mut proc_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
|
||||
let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
|
||||
let mut exports = Vec::with_capacity_in(4, env.arena);
|
||||
let mut main_fn_index = None;
|
||||
|
@ -61,12 +64,11 @@ pub fn build_module_help<'a>(
|
|||
if LowLevel::from_inlined_wrapper(sym).is_some() {
|
||||
continue;
|
||||
}
|
||||
generated_procs.push(proc);
|
||||
generated_symbols.push(sym);
|
||||
procs.push(proc);
|
||||
|
||||
let fn_name = layout_ids
|
||||
.get_toplevel(sym, &layout)
|
||||
.to_symbol_string(sym, &env.interns);
|
||||
.to_symbol_string(sym, interns);
|
||||
|
||||
if env.exposed_to_host.contains(&sym) {
|
||||
main_fn_index = Some(fn_index);
|
||||
|
@ -78,29 +80,54 @@ pub fn build_module_help<'a>(
|
|||
}
|
||||
|
||||
let linker_sym = SymInfo::for_function(fn_index, fn_name);
|
||||
proc_symbols.push((sym, linker_symbols.len() as u32));
|
||||
linker_symbols.push(linker_sym);
|
||||
|
||||
fn_index += 1;
|
||||
}
|
||||
|
||||
// Build the Wasm module
|
||||
let (mut module, linker_symbols) = {
|
||||
let mut backend = WasmBackend::new(
|
||||
env,
|
||||
interns,
|
||||
layout_ids,
|
||||
generated_symbols.clone(),
|
||||
proc_symbols,
|
||||
linker_symbols,
|
||||
exports,
|
||||
RefcountProcGenerator::new(env.arena, IntWidth::I32, env.module_id),
|
||||
);
|
||||
|
||||
for (proc, sym) in generated_procs.into_iter().zip(generated_symbols) {
|
||||
backend.build_proc(proc, sym)?;
|
||||
if false {
|
||||
println!("## procs");
|
||||
for proc in procs.iter() {
|
||||
println!("{}", proc.to_pretty(200));
|
||||
println!("{:#?}", proc);
|
||||
}
|
||||
}
|
||||
(backend.module, backend.linker_symbols)
|
||||
};
|
||||
|
||||
let symbol_table = LinkingSubSection::SymbolTable(linker_symbols);
|
||||
module.linking.subsections.push(symbol_table);
|
||||
// Generate procs from user code
|
||||
for proc in procs.iter() {
|
||||
backend.build_proc(proc)?;
|
||||
}
|
||||
|
||||
// Generate IR for refcounting procs
|
||||
let refcount_procs = backend.generate_refcount_procs();
|
||||
|
||||
backend.register_symbol_debug_names();
|
||||
|
||||
if false {
|
||||
println!("## refcount_procs");
|
||||
for proc in refcount_procs.iter() {
|
||||
println!("{}", proc.to_pretty(200));
|
||||
println!("{:#?}", proc);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate Wasm for refcounting procs
|
||||
for proc in refcount_procs.iter() {
|
||||
backend.build_proc(proc)?;
|
||||
}
|
||||
|
||||
let module = backend.finalize_module();
|
||||
|
||||
Ok((module, main_fn_index.unwrap()))
|
||||
}
|
||||
|
|
|
@ -76,7 +76,6 @@ pub fn decode_low_level<'a>(
|
|||
StackMemoryFormat::Float128 => return NotImplemented,
|
||||
StackMemoryFormat::Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW),
|
||||
},
|
||||
WasmLayout::HeapMemory { .. } => return NotImplemented,
|
||||
},
|
||||
NumAddWrap => match ret_layout.arg_types(CallConv::Zig)[0] {
|
||||
I32 => {
|
||||
|
@ -371,6 +370,12 @@ pub fn decode_low_level<'a>(
|
|||
Not => code_builder.i32_eqz(),
|
||||
Hash => return NotImplemented,
|
||||
ExpectTrue => return NotImplemented,
|
||||
RefCountGetPtr => {
|
||||
code_builder.i32_const(4);
|
||||
code_builder.i32_sub();
|
||||
}
|
||||
RefCountInc => return BuiltinCall(bitcode::UTILS_INCREF),
|
||||
RefCountDec => return BuiltinCall(bitcode::UTILS_DECREF),
|
||||
}
|
||||
Done
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::layout::{
|
|||
CallConv, ReturnMethod, StackMemoryFormat, WasmLayout, ZigVersion, BUILTINS_ZIG_VERSION,
|
||||
};
|
||||
use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
|
||||
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE};
|
||||
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_TYPE};
|
||||
|
||||
pub enum StoredValueKind {
|
||||
Parameter,
|
||||
|
@ -79,6 +79,7 @@ impl StoredValue {
|
|||
|
||||
/// Helper structure for WasmBackend, to keep track of how values are stored,
|
||||
/// including the VM stack, local variables, and linear memory
|
||||
#[derive(Debug)]
|
||||
pub struct Storage<'a> {
|
||||
pub arg_types: Vec<'a, ValueType>,
|
||||
pub local_types: Vec<'a, ValueType>,
|
||||
|
@ -146,18 +147,6 @@ impl<'a> Storage<'a> {
|
|||
},
|
||||
},
|
||||
|
||||
WasmLayout::HeapMemory => {
|
||||
match kind {
|
||||
StoredValueKind::Parameter => self.arg_types.push(PTR_TYPE),
|
||||
_ => self.local_types.push(PTR_TYPE),
|
||||
}
|
||||
StoredValue::Local {
|
||||
local_id: next_local_id,
|
||||
value_type: PTR_TYPE,
|
||||
size: PTR_SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
WasmLayout::StackMemory {
|
||||
size,
|
||||
alignment_bytes,
|
||||
|
|
|
@ -234,7 +234,9 @@ impl<'a> CodeBuilder<'a> {
|
|||
pub fn set_top_symbol(&mut self, sym: Symbol) -> VmSymbolState {
|
||||
let current_stack = &mut self.vm_block_stack.last_mut().unwrap().value_stack;
|
||||
let pushed_at = self.code.len();
|
||||
let top_symbol: &mut Symbol = current_stack.last_mut().unwrap();
|
||||
let top_symbol: &mut Symbol = current_stack
|
||||
.last_mut()
|
||||
.unwrap_or_else(|| unreachable!("Empty stack when trying to set Symbol {:?}", sym));
|
||||
*top_symbol = sym;
|
||||
|
||||
VmSymbolState::Pushed { pushed_at }
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [[ -z "$1" || -z "$2" ]]
|
||||
then
|
||||
echo "$0 needs 2 arguments: the directories to compare"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
OVERHEAD_BYTES=114 # total file size minus generated code size (test wrapper + module headers)
|
||||
|
||||
|
||||
printf "filename \tLHS\tRHS\tchange\n"
|
||||
printf "======== \t===\t===\t======\n"
|
||||
|
||||
for f in `ls $1/wasm`
|
||||
do
|
||||
if [[ ! -f "$2/wasm/$f" ]]
|
||||
then
|
||||
echo "$f found in $1/wasm but not in $2/wasm"
|
||||
continue
|
||||
fi
|
||||
SIZE1=$(stat --format '%s' "$1/wasm/$f")
|
||||
SIZE2=$(stat --format '%s' "$2/wasm/$f")
|
||||
CHANGE=$(( $SIZE2 - $SIZE1 ))
|
||||
NET_SIZE1=$(( $SIZE1 - $OVERHEAD_BYTES ))
|
||||
NET_SIZE2=$(( $SIZE2 - $OVERHEAD_BYTES ))
|
||||
PERCENT_CHANGE=$(( $CHANGE * 100 / $NET_SIZE1 ))
|
||||
printf "%s\t%d\t%d\t%d\t%d%%\n" $f $NET_SIZE1 $NET_SIZE2 $CHANGE $PERCENT_CHANGE
|
||||
done
|
|
@ -1,24 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
TARGET_DIR=$1
|
||||
|
||||
if [[ -z "$TARGET_DIR" ]]
|
||||
then
|
||||
echo "$0 needs an argument: target directory for output wasm and wat files"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf output $TARGET_DIR
|
||||
mkdir -p output $TARGET_DIR $TARGET_DIR/wasm $TARGET_DIR/wat
|
||||
cargo test -- --test-threads=1 --nocapture
|
||||
|
||||
mv output/* $TARGET_DIR/wasm
|
||||
|
||||
for f in `ls $TARGET_DIR/wasm`
|
||||
do
|
||||
wasm2wat $TARGET_DIR/wasm/$f -o $TARGET_DIR/wat/${f%.wasm}.wat
|
||||
done
|
||||
|
||||
|
||||
SIZE=$(du -b "$TARGET_DIR/wasm")
|
||||
echo "Total bytes *.wasm = $SIZE"
|
|
@ -116,6 +116,9 @@ pub enum LowLevel {
|
|||
Not,
|
||||
Hash,
|
||||
ExpectTrue,
|
||||
RefCountGetPtr,
|
||||
RefCountInc,
|
||||
RefCountDec,
|
||||
}
|
||||
|
||||
macro_rules! higher_order {
|
||||
|
|
|
@ -1008,6 +1008,10 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
|||
SetFromList => arena.alloc_slice_copy(&[owned]),
|
||||
|
||||
ExpectTrue => arena.alloc_slice_copy(&[irrelevant]),
|
||||
|
||||
RefCountGetPtr | RefCountInc | RefCountDec => {
|
||||
unreachable!("Refcounting lowlevel calls are inserted *after* borrow checking");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
410
compiler/mono/src/gen_refcount.rs
Normal file
410
compiler/mono/src/gen_refcount.rs
Normal file
|
@ -0,0 +1,410 @@
|
|||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_builtins::bitcode::IntWidth;
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||
|
||||
use crate::ir::{
|
||||
BranchInfo, Call, CallSpecId, CallType, Expr, HostExposedLayouts, Literal, ModifyRc, Proc,
|
||||
ProcLayout, SelfRecursive, Stmt, UpdateModeId,
|
||||
};
|
||||
use crate::layout::{Builtin, Layout};
|
||||
|
||||
const LAYOUT_BOOL: Layout = Layout::Builtin(Builtin::Bool);
|
||||
const LAYOUT_UNIT: Layout = Layout::Struct(&[]);
|
||||
const LAYOUT_PTR: Layout = Layout::RecursivePointer;
|
||||
const LAYOUT_U32: Layout = Layout::Builtin(Builtin::Int(IntWidth::U32));
|
||||
|
||||
/// "Infinite" reference count, for static values
|
||||
/// Ref counts are encoded as negative numbers where isize::MIN represents 1
|
||||
pub const REFCOUNT_MAX: usize = 0;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum RefcountOp {
|
||||
Inc,
|
||||
Dec,
|
||||
DecRef,
|
||||
}
|
||||
|
||||
/// Generate specialized refcounting code in mono IR format
|
||||
/// -------------------------------------------------------
|
||||
///
|
||||
/// Any backend that wants to use this, needs a field of type `RefcountProcGenerator`.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// Essentially we are expanding the `Refcounting` statement into a more detailed
|
||||
/// form that's more suitable for code generation.
|
||||
///
|
||||
/// But so far, we've only mentioned _calls_ to the refcounting procedures.
|
||||
/// The procedures themselves don't exist yet!
|
||||
///
|
||||
/// 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> {
|
||||
arena: &'a Bump,
|
||||
home: ModuleId,
|
||||
ptr_size: u32,
|
||||
layout_isize: Layout<'a>,
|
||||
/// List of refcounting procs to generate, specialised by Layout and RefCountOp
|
||||
/// Order of insertion is preserved, since it is important for Wasm backend
|
||||
procs_to_generate: Vec<'a, (Layout<'a>, RefcountOp, Symbol)>,
|
||||
}
|
||||
|
||||
impl<'a> RefcountProcGenerator<'a> {
|
||||
pub fn new(arena: &'a Bump, intwidth_isize: IntWidth, home: ModuleId) -> Self {
|
||||
RefcountProcGenerator {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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`
|
||||
pub fn expand_refcount_stmt(
|
||||
&mut self,
|
||||
ident_ids: &mut IdentIds,
|
||||
layout: Layout<'a>,
|
||||
modify: &ModifyRc,
|
||||
following: &'a Stmt<'a>,
|
||||
) -> (Stmt<'a>, Option<(Symbol, ProcLayout<'a>)>) {
|
||||
match modify {
|
||||
ModifyRc::Inc(structure, amount) => {
|
||||
let layout_isize = self.layout_isize;
|
||||
|
||||
let (is_existing, proc_name) =
|
||||
self.get_proc_symbol(ident_ids, layout, RefcountOp::Inc);
|
||||
|
||||
// Define a constant for the amount to increment
|
||||
let amount_sym = self.create_symbol(ident_ids, "amount");
|
||||
let amount_expr = Expr::Literal(Literal::Int(*amount as i128));
|
||||
let amount_stmt = |next| Stmt::Let(amount_sym, amount_expr, layout_isize, next);
|
||||
|
||||
// Call helper proc, passing the Roc structure and constant amount
|
||||
let arg_layouts = self.arena.alloc([layout, layout_isize]);
|
||||
let call_result_empty = self.create_symbol(ident_ids, "call_result_empty");
|
||||
let call_expr = Expr::Call(Call {
|
||||
call_type: CallType::ByName {
|
||||
name: proc_name,
|
||||
ret_layout: &LAYOUT_UNIT,
|
||||
arg_layouts,
|
||||
specialization_id: CallSpecId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: self.arena.alloc([*structure, amount_sym]),
|
||||
});
|
||||
let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following);
|
||||
let rc_stmt = amount_stmt(self.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)
|
||||
}
|
||||
|
||||
ModifyRc::Dec(structure) => {
|
||||
let (is_existing, proc_name) =
|
||||
self.get_proc_symbol(ident_ids, layout, RefcountOp::Dec);
|
||||
|
||||
// Call helper proc, passing the Roc structure
|
||||
let arg_layouts = self.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 {
|
||||
name: proc_name,
|
||||
ret_layout: &LAYOUT_UNIT,
|
||||
arg_layouts: self.arena.alloc([layout]),
|
||||
specialization_id: CallSpecId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: self.arena.alloc([*structure]),
|
||||
});
|
||||
|
||||
let rc_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, 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)
|
||||
}
|
||||
|
||||
ModifyRc::DecRef(structure) => {
|
||||
// No generated procs for DecRef, just lowlevel calls
|
||||
|
||||
// Get a pointer to the refcount itself
|
||||
let rc_ptr_sym = self.create_symbol(ident_ids, "rc_ptr");
|
||||
let rc_ptr_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::RefCountGetPtr,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: self.arena.alloc([*structure]),
|
||||
});
|
||||
let rc_ptr_stmt = |next| Stmt::Let(rc_ptr_sym, rc_ptr_expr, LAYOUT_PTR, next);
|
||||
|
||||
// Pass the refcount pointer to the lowlevel call (see utils.zig)
|
||||
let call_result_empty = self.create_symbol(ident_ids, "call_result_empty");
|
||||
let call_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::RefCountDec,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: self.arena.alloc([rc_ptr_sym]),
|
||||
});
|
||||
let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following);
|
||||
let rc_stmt = rc_ptr_stmt(self.arena.alloc(call_stmt));
|
||||
|
||||
(rc_stmt, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(
|
||||
&mut self,
|
||||
arena: &'a Bump,
|
||||
ident_ids: &mut IdentIds,
|
||||
) -> Vec<'a, Proc<'a>> {
|
||||
// Move the vector so we can loop over it safely
|
||||
let mut procs_to_generate = Vec::with_capacity_in(0, arena);
|
||||
std::mem::swap(&mut self.procs_to_generate, &mut procs_to_generate);
|
||||
|
||||
let mut procs = Vec::with_capacity_in(procs_to_generate.len(), arena);
|
||||
for (layout, op, proc_symbol) in procs_to_generate.drain(0..) {
|
||||
let proc = match layout {
|
||||
Layout::Builtin(Builtin::Str) => self.gen_modify_str(ident_ids, op, proc_symbol),
|
||||
_ => todo!("Refcounting is not yet implemented for Layout {:?}", layout),
|
||||
};
|
||||
procs.push(proc);
|
||||
}
|
||||
|
||||
procs
|
||||
}
|
||||
|
||||
/// Find the Symbol of the procedure for this layout and refcount operation,
|
||||
/// or create one if needed.
|
||||
fn get_proc_symbol(
|
||||
&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);
|
||||
|
||||
if let Some((_, _, existing_symbol)) = found {
|
||||
(true, *existing_symbol)
|
||||
} 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 new_symbol: Symbol = self.create_symbol(ident_ids, &debug_name);
|
||||
self.procs_to_generate.push((layout, op, new_symbol));
|
||||
(false, new_symbol)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_symbol(&mut self, ident_ids: &mut IdentIds, debug_name: &str) -> Symbol {
|
||||
let ident_id = ident_ids.add(Ident::from(debug_name));
|
||||
Symbol::new(self.home, ident_id)
|
||||
}
|
||||
|
||||
fn return_unit(&mut self, ident_ids: &mut IdentIds) -> Stmt<'a> {
|
||||
let unit = self.create_symbol(ident_ids, "unit");
|
||||
let ret_stmt = self.arena.alloc(Stmt::Ret(unit));
|
||||
Stmt::Let(unit, Expr::Struct(&[]), LAYOUT_UNIT, ret_stmt)
|
||||
}
|
||||
|
||||
fn gen_args(&mut self, op: RefcountOp, layout: Layout<'a>) -> &'a [(Layout<'a>, Symbol)] {
|
||||
let roc_value = (layout, Symbol::ARG_1);
|
||||
match op {
|
||||
RefcountOp::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]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a procedure to modify the reference count of a Str
|
||||
fn gen_modify_str(
|
||||
&mut self,
|
||||
ident_ids: &mut IdentIds,
|
||||
op: RefcountOp,
|
||||
proc_name: Symbol,
|
||||
) -> Proc<'a> {
|
||||
let string = Symbol::ARG_1;
|
||||
let layout_isize = self.layout_isize;
|
||||
|
||||
// Get the string length as a signed int
|
||||
let len = self.create_symbol(ident_ids, "len");
|
||||
let len_expr = Expr::StructAtIndex {
|
||||
index: 1,
|
||||
field_layouts: self.arena.alloc([LAYOUT_PTR, layout_isize]),
|
||||
structure: string,
|
||||
};
|
||||
let len_stmt = |next| Stmt::Let(len, len_expr, layout_isize, next);
|
||||
|
||||
// Zero
|
||||
let zero = self.create_symbol(ident_ids, "zero");
|
||||
let zero_expr = Expr::Literal(Literal::Int(0));
|
||||
let zero_stmt = |next| Stmt::Let(zero, zero_expr, layout_isize, next);
|
||||
|
||||
// is_big_str = (len >= 0);
|
||||
// Treat len as isize so that the small string flag is the same as the sign bit
|
||||
let is_big_str = self.create_symbol(ident_ids, "is_big_str");
|
||||
let is_big_str_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::NumGte,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: self.arena.alloc([len, zero]),
|
||||
});
|
||||
let is_big_str_stmt = |next| Stmt::Let(is_big_str, is_big_str_expr, LAYOUT_BOOL, next);
|
||||
|
||||
// Get the pointer to the string elements
|
||||
let elements = self.create_symbol(ident_ids, "elements");
|
||||
let elements_expr = Expr::StructAtIndex {
|
||||
index: 0,
|
||||
field_layouts: self.arena.alloc([LAYOUT_PTR, layout_isize]),
|
||||
structure: string,
|
||||
};
|
||||
let elements_stmt = |next| Stmt::Let(elements, elements_expr, LAYOUT_PTR, next);
|
||||
|
||||
// Get a pointer to the refcount value, just below the elements pointer
|
||||
let rc_ptr = self.create_symbol(ident_ids, "rc_ptr");
|
||||
let rc_ptr_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::RefCountGetPtr,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: self.arena.alloc([elements]),
|
||||
});
|
||||
let rc_ptr_stmt = |next| Stmt::Let(rc_ptr, rc_ptr_expr, LAYOUT_PTR, next);
|
||||
|
||||
// Alignment constant
|
||||
let alignment = self.create_symbol(ident_ids, "alignment");
|
||||
let alignment_expr = Expr::Literal(Literal::Int(self.ptr_size as i128));
|
||||
let alignment_stmt = |next| Stmt::Let(alignment, alignment_expr, LAYOUT_U32, next);
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::RefCountDec,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: self.arena.alloc([rc_ptr, alignment]),
|
||||
}),
|
||||
};
|
||||
let zig_call_stmt = |next| Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, next);
|
||||
|
||||
// Generate an `if` to skip small strings but modify big strings
|
||||
let then_branch = elements_stmt(self.arena.alloc(
|
||||
//
|
||||
rc_ptr_stmt(self.arena.alloc(
|
||||
//
|
||||
alignment_stmt(self.arena.alloc(
|
||||
//
|
||||
zig_call_stmt(self.arena.alloc(
|
||||
//
|
||||
Stmt::Ret(zig_call_result),
|
||||
)),
|
||||
)),
|
||||
)),
|
||||
));
|
||||
let if_stmt = Stmt::Switch {
|
||||
cond_symbol: is_big_str,
|
||||
cond_layout: LAYOUT_BOOL,
|
||||
branches: self.arena.alloc([(1, BranchInfo::None, then_branch)]),
|
||||
default_branch: (
|
||||
BranchInfo::None,
|
||||
self.arena.alloc(self.return_unit(ident_ids)),
|
||||
),
|
||||
ret_layout: LAYOUT_UNIT,
|
||||
};
|
||||
|
||||
// Combine the statements in sequence
|
||||
let body = len_stmt(self.arena.alloc(
|
||||
//
|
||||
zero_stmt(self.arena.alloc(
|
||||
//
|
||||
is_big_str_stmt(self.arena.alloc(
|
||||
//
|
||||
if_stmt,
|
||||
)),
|
||||
)),
|
||||
));
|
||||
|
||||
let args = self.gen_args(op, Layout::Builtin(Builtin::Str));
|
||||
|
||||
Proc {
|
||||
name: proc_name,
|
||||
args,
|
||||
body,
|
||||
closure_data_layout: None,
|
||||
ret_layout: LAYOUT_UNIT,
|
||||
is_self_recursive: SelfRecursive::NotSelfRecursive,
|
||||
must_own_arguments: false,
|
||||
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to derive a debug function name from a layout
|
||||
fn layout_debug_name<'a>(layout: &Layout<'a>) -> &'static str {
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::List(_)) => "list",
|
||||
Layout::Builtin(Builtin::Set(_)) => "set",
|
||||
Layout::Builtin(Builtin::Dict(_, _)) => "dict",
|
||||
Layout::Builtin(Builtin::Str) => "str",
|
||||
Layout::Builtin(builtin) => {
|
||||
debug_assert!(!builtin.is_refcounted());
|
||||
unreachable!("Builtin {:?} is not refcounted", builtin);
|
||||
}
|
||||
Layout::Struct(_) => "struct",
|
||||
Layout::Union(_) => "union",
|
||||
Layout::LambdaSet(_) => "lambdaset",
|
||||
Layout::RecursivePointer => "recursive_pointer",
|
||||
}
|
||||
}
|
|
@ -1289,6 +1289,10 @@ impl CallSpecId {
|
|||
pub fn to_bytes(self) -> [u8; 4] {
|
||||
self.id.to_ne_bytes()
|
||||
}
|
||||
|
||||
/// Dummy value for generating refcount helper procs in the backends
|
||||
/// This happens *after* specialization so it's safe
|
||||
pub const BACKEND_DUMMY: Self = Self { id: 0 };
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
@ -1300,6 +1304,10 @@ impl UpdateModeId {
|
|||
pub fn to_bytes(self) -> [u8; 4] {
|
||||
self.id.to_ne_bytes()
|
||||
}
|
||||
|
||||
/// Dummy value for generating refcount helper procs in the backends
|
||||
/// This happens *after* alias analysis so it's safe
|
||||
pub const BACKEND_DUMMY: Self = Self { id: 0 };
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
|
@ -1587,6 +1595,17 @@ impl<'a> Expr<'a> {
|
|||
.append(symbol_to_doc(alloc, *structure)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_pretty(&self, width: usize) -> String {
|
||||
let allocator = BoxAllocator;
|
||||
let mut w = std::vec::Vec::new();
|
||||
self.to_doc::<_, ()>(&allocator)
|
||||
.1
|
||||
.render(width, &mut w)
|
||||
.unwrap();
|
||||
w.push(b'\n');
|
||||
String::from_utf8(w).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Stmt<'a> {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
pub mod alias_analysis;
|
||||
pub mod borrow;
|
||||
pub mod gen_refcount;
|
||||
pub mod inc_dec;
|
||||
pub mod ir;
|
||||
pub mod layout;
|
||||
|
|
|
@ -79,8 +79,9 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
|||
|
||||
use roc_load::file::MonomorphizedModule;
|
||||
let MonomorphizedModule {
|
||||
module_id,
|
||||
procedures,
|
||||
interns,
|
||||
mut interns,
|
||||
exposed_to_host,
|
||||
..
|
||||
} = loaded;
|
||||
|
@ -114,12 +115,12 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
|||
|
||||
let env = roc_gen_wasm::Env {
|
||||
arena,
|
||||
interns,
|
||||
module_id,
|
||||
exposed_to_host,
|
||||
};
|
||||
|
||||
let (mut wasm_module, main_fn_index) =
|
||||
roc_gen_wasm::build_module_help(&env, procedures).unwrap();
|
||||
roc_gen_wasm::build_module_help(&env, &mut interns, procedures).unwrap();
|
||||
|
||||
T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index);
|
||||
|
||||
|
@ -136,7 +137,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
|||
let store = Store::default();
|
||||
|
||||
// Keep the final .wasm file for debugging with wasm-objdump or wasm2wat
|
||||
const DEBUG_WASM_FILE: bool = true;
|
||||
const DEBUG_WASM_FILE: bool = false;
|
||||
|
||||
let wasmer_module = {
|
||||
let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped
|
||||
|
@ -166,8 +167,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
|||
// write the module to a file so the linker can access it
|
||||
std::fs::write(&app_o_file, &module_bytes).unwrap();
|
||||
|
||||
let _linker_output = std::process::Command::new("zig")
|
||||
.args(&[
|
||||
let args = &[
|
||||
"wasm-ld",
|
||||
// input files
|
||||
app_o_file.to_str().unwrap(),
|
||||
|
@ -188,11 +188,21 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
|||
"test_wrapper",
|
||||
"--export",
|
||||
"#UserApp_main_1",
|
||||
])
|
||||
];
|
||||
|
||||
let linker_output = std::process::Command::new("zig")
|
||||
.args(args)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// dbg!(_linker_output);
|
||||
if !linker_output.status.success() {
|
||||
print!("\nLINKER FAILED\n");
|
||||
for arg in args {
|
||||
print!("{} ", arg);
|
||||
}
|
||||
println!("\n{}", std::str::from_utf8(&linker_output.stdout).unwrap());
|
||||
println!("{}", std::str::from_utf8(&linker_output.stderr).unwrap());
|
||||
}
|
||||
|
||||
Module::from_file(&store, &final_wasm_file).unwrap()
|
||||
};
|
||||
|
|
|
@ -865,8 +865,8 @@ fn str_starts_with_false_small_str() {
|
|||
#[test]
|
||||
fn str_repeat_small() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.repeat "Roc" 3"#),
|
||||
RocStr::from("RocRocRoc"),
|
||||
indoc!(r#"Str.repeat "Roc" 2"#),
|
||||
RocStr::from("RocRoc"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
@ -903,8 +903,8 @@ fn str_trim_small_blank_string() {
|
|||
#[test]
|
||||
fn str_trim_small_to_small() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trim " hello world ""#),
|
||||
RocStr::from("hello world"),
|
||||
indoc!(r#"Str.trim " hello ""#),
|
||||
RocStr::from("hello"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
@ -921,8 +921,8 @@ fn str_trim_large_to_large_unique() {
|
|||
#[test]
|
||||
fn str_trim_large_to_small_unique() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trim (Str.concat " " "hello world ")"#),
|
||||
RocStr::from("hello world"),
|
||||
indoc!(r#"Str.trim (Str.concat " " "hello ")"#),
|
||||
RocStr::from("hello"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
@ -952,15 +952,12 @@ fn str_trim_large_to_small_shared() {
|
|||
indoc!(
|
||||
r#"
|
||||
original : Str
|
||||
original = " hello world "
|
||||
original = " hello "
|
||||
|
||||
{ trimmed: Str.trim original, original: original }
|
||||
"#
|
||||
),
|
||||
(
|
||||
RocStr::from(" hello world "),
|
||||
RocStr::from("hello world"),
|
||||
),
|
||||
(RocStr::from(" hello "), RocStr::from("hello"),),
|
||||
(RocStr, RocStr)
|
||||
);
|
||||
}
|
||||
|
@ -971,12 +968,12 @@ fn str_trim_small_to_small_shared() {
|
|||
indoc!(
|
||||
r#"
|
||||
original : Str
|
||||
original = " hello world "
|
||||
original = " hello "
|
||||
|
||||
{ trimmed: Str.trim original, original: original }
|
||||
"#
|
||||
),
|
||||
(RocStr::from(" hello world "), RocStr::from("hello world"),),
|
||||
(RocStr::from(" hello "), RocStr::from("hello"),),
|
||||
(RocStr, RocStr)
|
||||
);
|
||||
}
|
||||
|
@ -989,8 +986,8 @@ fn str_trim_left_small_blank_string() {
|
|||
#[test]
|
||||
fn str_trim_left_small_to_small() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trimLeft " hello world ""#),
|
||||
RocStr::from("hello world "),
|
||||
indoc!(r#"Str.trimLeft " hello ""#),
|
||||
RocStr::from("hello "),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
@ -1007,8 +1004,8 @@ fn str_trim_left_large_to_large_unique() {
|
|||
#[test]
|
||||
fn str_trim_left_large_to_small_unique() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trimLeft (Str.concat " " "hello world ")"#),
|
||||
RocStr::from("hello world "),
|
||||
indoc!(r#"Str.trimLeft (Str.concat " " "hello ")"#),
|
||||
RocStr::from("hello "),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
@ -1021,8 +1018,8 @@ fn str_trim_right_small_blank_string() {
|
|||
#[test]
|
||||
fn str_trim_right_small_to_small() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trimRight " hello world ""#),
|
||||
RocStr::from(" hello world"),
|
||||
indoc!(r#"Str.trimRight " hello ""#),
|
||||
RocStr::from(" hello"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
@ -1039,8 +1036,8 @@ fn str_trim_right_large_to_large_unique() {
|
|||
#[test]
|
||||
fn str_trim_right_large_to_small_unique() {
|
||||
assert_evals_to!(
|
||||
indoc!(r#"Str.trimRight (Str.concat " hello world" " ")"#),
|
||||
RocStr::from(" hello world"),
|
||||
indoc!(r#"Str.trimRight (Str.concat " hello" " ")"#),
|
||||
RocStr::from(" hello"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
@ -1070,15 +1067,12 @@ fn str_trim_right_large_to_small_shared() {
|
|||
indoc!(
|
||||
r#"
|
||||
original : Str
|
||||
original = " hello world "
|
||||
original = " hello "
|
||||
|
||||
{ trimmed: Str.trimRight original, original: original }
|
||||
"#
|
||||
),
|
||||
(
|
||||
RocStr::from(" hello world "),
|
||||
RocStr::from(" hello world"),
|
||||
),
|
||||
(RocStr::from(" hello "), RocStr::from(" hello"),),
|
||||
(RocStr, RocStr)
|
||||
);
|
||||
}
|
||||
|
@ -1089,12 +1083,12 @@ fn str_trim_right_small_to_small_shared() {
|
|||
indoc!(
|
||||
r#"
|
||||
original : Str
|
||||
original = " hello world "
|
||||
original = " hello "
|
||||
|
||||
{ trimmed: Str.trimRight original, original: original }
|
||||
"#
|
||||
),
|
||||
(RocStr::from(" hello world "), RocStr::from(" hello world"),),
|
||||
(RocStr::from(" hello "), RocStr::from(" hello"),),
|
||||
(RocStr, RocStr)
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue