From 32f79b5ee26d173701f977f1f554cb51ee2413b6 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 3 Oct 2021 13:25:17 +0100 Subject: [PATCH 01/51] Create CodeBuilder to track Wasm VM stack as we accumulate instructions --- compiler/gen_wasm/src/backend.rs | 11 +- compiler/gen_wasm/src/code_builder.rs | 261 ++++++++++++++++++++++++++ compiler/gen_wasm/src/lib.rs | 4 +- compiler/gen_wasm/src/storage.rs | 7 +- 4 files changed, 274 insertions(+), 9 deletions(-) create mode 100644 compiler/gen_wasm/src/code_builder.rs diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 4512203625..d4f64580c5 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -12,6 +12,7 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::{StackMemoryLocation, SymbolStorage}; +use crate::code_builder::CodeBuilder; use crate::{ copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, CopyMemoryConfig, LocalId, PTR_SIZE, PTR_TYPE, @@ -40,7 +41,7 @@ pub struct WasmBackend<'a> { proc_symbol_map: MutMap, // Functions: Wasm AST - instructions: std::vec::Vec, + instructions: CodeBuilder, arg_types: std::vec::Vec, locals: std::vec::Vec, @@ -65,7 +66,7 @@ impl<'a> WasmBackend<'a> { proc_symbol_map: MutMap::default(), // Functions: Wasm AST - instructions: std::vec::Vec::with_capacity(256), + instructions: CodeBuilder::new(), arg_types: std::vec::Vec::with_capacity(8), locals: std::vec::Vec::with_capacity(32), @@ -139,7 +140,7 @@ impl<'a> WasmBackend<'a> { ); } - final_instructions.extend(self.instructions.drain(0..)); + final_instructions.extend(self.instructions.drain()); if self.stack_memory > 0 { pop_stack_frame( @@ -270,7 +271,7 @@ impl<'a> WasmBackend<'a> { location: StackMemoryLocation::FrameOffset(offset), .. } => { - self.instructions.extend([ + self.instructions.extend(&[ GetLocal(self.stack_frame_pointer.unwrap().0), I32Const(offset as i32), I32Add, @@ -658,7 +659,7 @@ impl<'a> WasmBackend<'a> { return Err(format!("unsupported low-level op {:?}", lowlevel)); } }; - self.instructions.extend_from_slice(instructions); + self.instructions.extend(instructions); Ok(()) } } diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs new file mode 100644 index 0000000000..fd4c2ef872 --- /dev/null +++ b/compiler/gen_wasm/src/code_builder.rs @@ -0,0 +1,261 @@ +use parity_wasm::elements::{Instruction, Instruction::*}; +use roc_module::symbol::Symbol; + +pub struct CodeBuilder { + stack: Vec>, + code: Vec, +} + +impl CodeBuilder { + pub fn new() -> Self { + CodeBuilder { + stack: Vec::with_capacity(32), + code: Vec::with_capacity(1024), + } + } + + pub fn clear(&mut self) { + self.stack.clear(); + self.code.clear(); + } + + pub fn push(&mut self, inst: Instruction) { + let (pops, push) = get_pops_and_pushes(&inst); + let new_len = self.stack.len() - pops as usize; + self.stack.truncate(new_len); + if push { + self.stack.push(None); + } + self.code.push(inst); + } + + pub fn extend(&mut self, instructions: &[Instruction]) { + let old_len = self.stack.len(); + let mut len = old_len; + let mut min_len = len; + for inst in instructions { + let (pops, push) = get_pops_and_pushes(&inst); + len -= pops as usize; + if len < min_len { + min_len = len; + } + if push { + len += 1; + } + } + self.stack.truncate(min_len); + self.stack.resize(len, None); + self.code.extend_from_slice(instructions); + } + + pub fn drain(&mut self) -> std::vec::Drain { + self.code.drain(0..) + } + + pub fn len(&self) -> usize { + self.code.len() + } + + pub fn set_top_symbol(&mut self, sym: Symbol) { + let len = self.stack.len(); + let code_index = self.code.len(); + self.stack[len - 1] = Some((sym, code_index)); + } + + pub fn get_top_symbol(&self) -> Option<(Symbol, usize)> { + let len = self.stack.len(); + self.stack[len - 1] + } +} + +fn get_pops_and_pushes(inst: &Instruction) -> (u8, bool) { + match inst { + Unreachable => (0, false), + Nop => (0, false), + Block(_) => (0, false), + Loop(_) => (0, false), + If(_) => (0, false), + Else => (0, false), + End => (0, false), + Br(_) => (0, false), + BrIf(_) => (0, false), + BrTable(_) => (0, false), + Return => (0, false), + + Call(_) => (0, false), // depends on the function! handle this elsewhere + CallIndirect(_, _) => (0, false), + + Drop => (1, false), + Select => (3, true), + + GetLocal(_) => (0, true), + SetLocal(_) => (1, false), + TeeLocal(_) => (1, true), + GetGlobal(_) => (0, true), + SetGlobal(_) => (1, false), + + I32Load(_, _) => (1, true), + I64Load(_, _) => (1, true), + F32Load(_, _) => (1, true), + F64Load(_, _) => (1, true), + I32Load8S(_, _) => (1, true), + I32Load8U(_, _) => (1, true), + I32Load16S(_, _) => (1, true), + I32Load16U(_, _) => (1, true), + I64Load8S(_, _) => (1, true), + I64Load8U(_, _) => (1, true), + I64Load16S(_, _) => (1, true), + I64Load16U(_, _) => (1, true), + I64Load32S(_, _) => (1, true), + I64Load32U(_, _) => (1, true), + I32Store(_, _) => (2, false), + I64Store(_, _) => (2, false), + F32Store(_, _) => (2, false), + F64Store(_, _) => (2, false), + I32Store8(_, _) => (2, false), + I32Store16(_, _) => (2, false), + I64Store8(_, _) => (2, false), + I64Store16(_, _) => (2, false), + I64Store32(_, _) => (2, false), + + CurrentMemory(_) => (0, true), + GrowMemory(_) => (1, true), + I32Const(_) => (0, true), + I64Const(_) => (0, true), + F32Const(_) => (0, true), + F64Const(_) => (0, true), + + I32Eqz => (1, true), + I32Eq => (2, true), + I32Ne => (2, true), + I32LtS => (2, true), + I32LtU => (2, true), + I32GtS => (2, true), + I32GtU => (2, true), + I32LeS => (2, true), + I32LeU => (2, true), + I32GeS => (2, true), + I32GeU => (2, true), + + I64Eqz => (1, true), + I64Eq => (2, true), + I64Ne => (2, true), + I64LtS => (2, true), + I64LtU => (2, true), + I64GtS => (2, true), + I64GtU => (2, true), + I64LeS => (2, true), + I64LeU => (2, true), + I64GeS => (2, true), + I64GeU => (2, true), + + F32Eq => (2, true), + F32Ne => (2, true), + F32Lt => (2, true), + F32Gt => (2, true), + F32Le => (2, true), + F32Ge => (2, true), + + F64Eq => (2, true), + F64Ne => (2, true), + F64Lt => (2, true), + F64Gt => (2, true), + F64Le => (2, true), + F64Ge => (2, true), + + I32Clz => (1, true), + I32Ctz => (1, true), + I32Popcnt => (1, true), + I32Add => (2, true), + I32Sub => (2, true), + I32Mul => (2, true), + I32DivS => (2, true), + I32DivU => (2, true), + I32RemS => (2, true), + I32RemU => (2, true), + I32And => (2, true), + I32Or => (2, true), + I32Xor => (2, true), + I32Shl => (2, true), + I32ShrS => (2, true), + I32ShrU => (2, true), + I32Rotl => (2, true), + I32Rotr => (2, true), + + I64Clz => (1, true), + I64Ctz => (1, true), + I64Popcnt => (1, true), + I64Add => (2, true), + I64Sub => (2, true), + I64Mul => (2, true), + I64DivS => (2, true), + I64DivU => (2, true), + I64RemS => (2, true), + I64RemU => (2, true), + I64And => (2, true), + I64Or => (2, true), + I64Xor => (2, true), + I64Shl => (2, true), + I64ShrS => (2, true), + I64ShrU => (2, true), + I64Rotl => (2, true), + I64Rotr => (2, true), + + F32Abs => (1, true), + F32Neg => (1, true), + F32Ceil => (1, true), + F32Floor => (1, true), + F32Trunc => (1, true), + F32Nearest => (1, true), + F32Sqrt => (1, true), + F32Add => (2, true), + F32Sub => (2, true), + F32Mul => (2, true), + F32Div => (2, true), + F32Min => (2, true), + F32Max => (2, true), + F32Copysign => (2, true), + + F64Abs => (1, true), + F64Neg => (1, true), + F64Ceil => (1, true), + F64Floor => (1, true), + F64Trunc => (1, true), + F64Nearest => (1, true), + F64Sqrt => (1, true), + F64Add => (2, true), + F64Sub => (2, true), + F64Mul => (2, true), + F64Div => (2, true), + F64Min => (2, true), + F64Max => (2, true), + F64Copysign => (2, true), + + I32WrapI64 => (1, true), + I32TruncSF32 => (1, true), + I32TruncUF32 => (1, true), + I32TruncSF64 => (1, true), + I32TruncUF64 => (1, true), + I64ExtendSI32 => (1, true), + I64ExtendUI32 => (1, true), + I64TruncSF32 => (1, true), + I64TruncUF32 => (1, true), + I64TruncSF64 => (1, true), + I64TruncUF64 => (1, true), + F32ConvertSI32 => (1, true), + F32ConvertUI32 => (1, true), + F32ConvertSI64 => (1, true), + F32ConvertUI64 => (1, true), + F32DemoteF64 => (1, true), + F64ConvertSI32 => (1, true), + F64ConvertUI32 => (1, true), + F64ConvertSI64 => (1, true), + F64ConvertUI64 => (1, true), + F64PromoteF32 => (1, true), + + I32ReinterpretF32 => (1, true), + I64ReinterpretF64 => (1, true), + F32ReinterpretI32 => (1, true), + F64ReinterpretI64 => (1, true), + } +} diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index a5517cb4e4..ab4f110179 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -2,6 +2,7 @@ mod backend; pub mod from_wasm32_memory; mod layout; mod storage; +mod code_builder; use bumpalo::Bump; use parity_wasm::builder; @@ -13,6 +14,7 @@ use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; use crate::backend::WasmBackend; +use crate::code_builder::CodeBuilder; const PTR_SIZE: u32 = 4; const PTR_TYPE: ValueType = ValueType::I32; @@ -130,7 +132,7 @@ pub struct CopyMemoryConfig { alignment_bytes: u32, } -pub fn copy_memory(instructions: &mut Vec, config: CopyMemoryConfig) { +pub fn copy_memory(instructions: &mut CodeBuilder, config: CopyMemoryConfig) { let alignment_flag = encode_alignment(config.alignment_bytes); let mut i = 0; while config.size - i >= 8 { diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 80c0583234..0402bbc2f6 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,5 +1,6 @@ +use crate::code_builder::CodeBuilder; use crate::{copy_memory, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; -use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; +use parity_wasm::elements::{Instruction::*, ValueType}; #[derive(Debug, Clone)] pub enum StackMemoryLocation { @@ -37,7 +38,7 @@ impl SymbolStorage { pub fn copy_from( &self, from: &Self, - instructions: &mut Vec, + instructions: &mut CodeBuilder, stack_frame_pointer: Option, ) { match (self, from) { @@ -98,7 +99,7 @@ impl SymbolStorage { /// Generate code to copy to a memory address (such as a struct index) pub fn copy_to_memory( &self, - instructions: &mut Vec, + instructions: &mut CodeBuilder, to_ptr: LocalId, to_offset: u32, stack_frame_pointer: Option, From d796bbcc6865c408407071551cec72763ad3ae48 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 3 Oct 2021 13:34:18 +0100 Subject: [PATCH 02/51] add a couple of assertions --- compiler/gen_wasm/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index ab4f110179..0cc74eddc3 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -133,6 +133,7 @@ pub struct CopyMemoryConfig { } pub fn copy_memory(instructions: &mut CodeBuilder, config: CopyMemoryConfig) { + debug_assert!(config.from_ptr != config.to_ptr || config.from_offset != config.to_offset); let alignment_flag = encode_alignment(config.alignment_bytes); let mut i = 0; while config.size - i >= 8 { @@ -158,8 +159,9 @@ pub fn copy_memory(instructions: &mut CodeBuilder, config: CopyMemoryConfig) { } } -/// Round up to alignment_bytes (assumed to be a power of 2) +/// Round up to alignment_bytes (which must be a power of 2) pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 { + debug_assert!(alignment_bytes.count_ones() == 1); let mut aligned = unaligned; aligned += alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary aligned &= -alignment_bytes; // mask with a flag that has upper bits 1, lower bits 0 From d6bba482eeae53c90673263fab0b417520899064 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 5 Oct 2021 21:19:53 +0100 Subject: [PATCH 03/51] Treat the Virtual Machine stack as a form of SymbolStorage Simulate the behaviour of the stack machine, mark where Symbol values are created, and track where they are in the stack as we emit instructions. This will allow us to be smarter with setting and getting locals. However that's not actually implemented yet! At the moment, we're still generating the same code as before but in a fancier way. What's happening: Let's say we have a function call that takes two arguments [A,B] and that we are lucky and just happen to have a VM stack of [A,B] That's great, we _should_ not have to do anything, just emit 'Call' BUT what we are doing is "load A to the top" and then "load B to the top". This sounds good but it's actually stupid We are saying we want A at the top of the stack when we don't!! We want it right where it is, just beneath the top! So we are emitting code to bring it from 2nd position to the top. How do we do that? By inserting a local.set instruction to save it, and a local.get instruction to load it to the top! What should be happening: Check the entire VM stack at once for all the symbols we are trying to load. If they happen to be there (which is likely given the IR structure) do nothing. If it's not quite perfect... then emit lots of local.set and local.get. Whatever. What would be the way to solve this properly? Build the Wasm instructions as a tree, then serialise it depth-first. The tree encodes all of the information about what needs to be on the stack at what point in the program. If a tree node has two children, it consumes two items from the stack. If you do it this way, all of the instructions get executed just at the right time. --- compiler/gen_wasm/src/backend.rs | 223 ++++++++++++++++++-------- compiler/gen_wasm/src/code_builder.rs | 168 ++++++++++++++++--- compiler/gen_wasm/src/storage.rs | 90 ++++------- 3 files changed, 336 insertions(+), 145 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index d4f64580c5..95dd4ac768 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -10,12 +10,12 @@ use roc_module::symbol::Symbol; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; +use crate::code_builder::CodeBuilder; use crate::layout::WasmLayout; use crate::storage::{StackMemoryLocation, SymbolStorage}; -use crate::code_builder::CodeBuilder; use crate::{ copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, CopyMemoryConfig, - LocalId, PTR_SIZE, PTR_TYPE, + LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8, PTR_SIZE, PTR_TYPE, }; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. @@ -94,6 +94,8 @@ impl<'a> WasmBackend<'a> { } pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { + // println!("\ngenerating procedure {:?}\n", sym); + let signature_builder = self.start_proc(&proc); self.build_stmt(&proc.body, &proc.ret_layout)?; @@ -103,6 +105,7 @@ impl<'a> WasmBackend<'a> { let function_index = location.body; self.proc_symbol_map.insert(sym, location); self.reset(); + // println!("\nfinished generating {:?}\n", sym); Ok(function_index) } @@ -121,7 +124,7 @@ impl<'a> WasmBackend<'a> { }; for (layout, symbol) in proc.args { - self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter); + self.create_storage(WasmLayout::new(layout), *symbol, LocalKind::Parameter); } signature_builder.with_params(self.arg_types.clone()) @@ -140,7 +143,7 @@ impl<'a> WasmBackend<'a> { ); } - final_instructions.extend(self.instructions.drain()); + self.instructions.finalize_into(&mut final_instructions); if self.stack_memory > 0 { pop_stack_frame( @@ -160,41 +163,37 @@ impl<'a> WasmBackend<'a> { .build() // function } - fn insert_local( + fn get_next_local_id(&self) -> LocalId { + LocalId((self.arg_types.len() + self.locals.len()) as u32) + } + + fn create_storage( &mut self, wasm_layout: WasmLayout, symbol: Symbol, kind: LocalKind, ) -> Option { - let next_local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32); + let next_local_id = self.get_next_local_id(); - match kind { - LocalKind::Parameter => { - self.arg_types.push(wasm_layout.value_type()); - } - LocalKind::Variable => { - self.locals.push(Local::new(1, wasm_layout.value_type())); - } - } - - let (maybe_local_id, storage) = match wasm_layout { - WasmLayout::LocalOnly(value_type, size) => ( - Some(next_local_id), - SymbolStorage::Local { + let storage = match wasm_layout { + WasmLayout::LocalOnly(value_type, size) => match kind { + LocalKind::Parameter => SymbolStorage::Local { local_id: next_local_id, value_type, size, }, - ), - - WasmLayout::HeapMemory => ( - Some(next_local_id), - SymbolStorage::Local { - local_id: next_local_id, - value_type: PTR_TYPE, - size: PTR_SIZE, + LocalKind::Variable => SymbolStorage::VirtualMachineStack { + vm_state: self.instructions.set_top_symbol(symbol), + value_type, + size, }, - ), + }, + + WasmLayout::HeapMemory => SymbolStorage::Local { + local_id: next_local_id, + value_type: PTR_TYPE, + size: PTR_SIZE, + }, WasmLayout::StackMemory { size, @@ -220,17 +219,28 @@ impl<'a> WasmBackend<'a> { } }; - ( - None, - SymbolStorage::StackMemory { - location, - size, - alignment_bytes, - }, - ) + SymbolStorage::StackMemory { + location, + size, + alignment_bytes, + } } }; + let maybe_local_id = storage.local_id(); + + match kind { + LocalKind::Parameter => { + self.arg_types.push(wasm_layout.value_type()); + } + LocalKind::Variable => match maybe_local_id { + Some(_) => { + self.locals.push(Local::new(1, wasm_layout.value_type())); + } + None => {} + }, + } + self.symbol_storage_map.insert(symbol, storage); maybe_local_id @@ -259,6 +269,41 @@ impl<'a> WasmBackend<'a> { fn load_symbol(&mut self, sym: &Symbol) { let storage = self.get_symbol_storage(sym).to_owned(); match storage { + SymbolStorage::VirtualMachineStack { + vm_state, + value_type, + size, + } => { + let next_local_id = self.get_next_local_id(); + let maybe_next_vm_state = + self.instructions.load_symbol(*sym, vm_state, next_local_id); + match maybe_next_vm_state { + // The act of loading the value changed the VM state, so update it + Some(next_vm_state) => { + self.symbol_storage_map.insert( + *sym, + SymbolStorage::VirtualMachineStack { + vm_state: next_vm_state, + value_type, + size, + }, + ); + } + None => { + // Loading the value required creating a new local, because + // it was not in a convenient position in the VM stack. + self.locals.push(Local::new(1, value_type)); + self.symbol_storage_map.insert( + *sym, + SymbolStorage::Local { + local_id: next_local_id, + value_type, + size, + }, + ); + } + } + } SymbolStorage::Local { local_id, .. } | SymbolStorage::StackMemory { location: StackMemoryLocation::PointerArg(local_id), @@ -322,12 +367,21 @@ impl<'a> WasmBackend<'a> { Stmt::Let(sym, expr, layout, following) => { let wasm_layout = WasmLayout::new(layout); - let maybe_local_id = self.insert_local(wasm_layout, *sym, LocalKind::Variable); - self.build_expr(sym, expr, layout)?; - - if let Some(local_id) = maybe_local_id { - self.instructions.push(SetLocal(local_id.0)); + match wasm_layout { + WasmLayout::StackMemory { .. } => { + // If the expression writes to stack memory, allocate *before* generating + // so that know where to write it + self.create_storage(wasm_layout, *sym, LocalKind::Variable); + self.build_expr(sym, expr, layout)?; + } + _ => { + // If the expression produces a primitive, create storage *after* generating, + // because we don't know if we need a local until afterwards + // TODO: should we make this uniform by having a "not yet pushed" state in VirtualMachineSymbolState? + self.build_expr(sym, expr, layout)?; + self.create_storage(wasm_layout, *sym, LocalKind::Variable); + } } self.build_stmt(following, ret_layout)?; @@ -345,13 +399,8 @@ impl<'a> WasmBackend<'a> { size, alignment_bytes, } => { - let (from_ptr, from_offset) = match location { - StackMemoryLocation::PointerArg(local_id) => (*local_id, 0), - StackMemoryLocation::FrameOffset(offset) => { - (self.stack_frame_pointer.unwrap(), *offset) - } - }; - + let (from_ptr, from_offset) = + location.local_and_offset(self.stack_frame_pointer); copy_memory( &mut self.instructions, CopyMemoryConfig { @@ -365,8 +414,8 @@ impl<'a> WasmBackend<'a> { ); } - Local { local_id, .. } => { - self.instructions.push(GetLocal(local_id.0)); + _ => { + self.load_symbol(sym); self.instructions.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) } } @@ -432,7 +481,7 @@ impl<'a> WasmBackend<'a> { for parameter in parameters.iter() { let wasm_layout = WasmLayout::new(¶meter.layout); let maybe_local_id = - self.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable); + self.create_storage(wasm_layout, parameter.symbol, LocalKind::Variable); let jp_param_id = maybe_local_id.unwrap(); jp_parameter_local_ids.push(jp_param_id); } @@ -499,7 +548,12 @@ impl<'a> WasmBackend<'a> { "Cannot find function {:?} called from {:?}", func_sym, sym ))?; - self.instructions.push(Call(function_location.body)); + + // TODO: Recreating the same WasmLayout as in the Let, for Backend compatibility + let wasm_layout = WasmLayout::new(layout); + let push = wasm_layout.stack_memory() == 0; + let pops = arguments.len(); + self.instructions.call(function_location.body, pops, push); Ok(()) } @@ -553,6 +607,8 @@ impl<'a> WasmBackend<'a> { layout: &Layout<'a>, fields: &'a [Symbol], ) -> Result<(), String> { + // TODO: we just calculated storage and now we're getting it out of a map + // Not passing it as an argument because I'm trying to match Backend method signatures let storage = self.get_symbol_storage(sym).to_owned(); if let Layout::Struct(field_layouts) = layout { @@ -563,11 +619,8 @@ impl<'a> WasmBackend<'a> { location.local_and_offset(self.stack_frame_pointer); let mut field_offset = struct_offset; for (field, _) in fields.iter().zip(field_layouts.iter()) { - field_offset += self.copy_symbol_to_pointer_at_offset( - local_id, - field_offset, - field, - ); + field_offset += + self.copy_symbol_to_memory(local_id, field_offset, *field); } } else { return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); @@ -592,19 +645,57 @@ impl<'a> WasmBackend<'a> { Ok(()) } - fn copy_symbol_to_pointer_at_offset( + fn copy_symbol_to_memory( &mut self, to_ptr: LocalId, to_offset: u32, - from_symbol: &Symbol, + from_symbol: Symbol, ) -> u32 { - let from_storage = self.get_symbol_storage(from_symbol).to_owned(); - from_storage.copy_to_memory( - &mut self.instructions, - to_ptr, - to_offset, - self.stack_frame_pointer, - ) + let from_storage = self.get_symbol_storage(&from_symbol).to_owned(); + match from_storage { + SymbolStorage::StackMemory { + location, + size, + alignment_bytes, + } => { + let (from_ptr, from_offset) = location.local_and_offset(self.stack_frame_pointer); + copy_memory( + &mut self.instructions, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr, + to_offset, + size, + alignment_bytes, + }, + ); + size + } + + SymbolStorage::VirtualMachineStack { + value_type, size, .. + } + | SymbolStorage::Local { + value_type, size, .. + } => { + let store_instruction = match (value_type, size) { + (ValueType::I64, 8) => I64Store(ALIGN_8, to_offset), + (ValueType::I32, 4) => I32Store(ALIGN_4, to_offset), + (ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset), + (ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset), + (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), + (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), + _ => { + panic!("Cannot store {:?} with alignment of {:?}", value_type, size); + } + }; + self.instructions.push(GetLocal(to_ptr.0)); + self.load_symbol(&from_symbol); + self.instructions.push(store_instruction); + size + } + } } fn build_call_low_level( diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index fd4c2ef872..d2f41922b6 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -1,36 +1,63 @@ +use core::panic; +use std::collections::BTreeMap; + use parity_wasm::elements::{Instruction, Instruction::*}; use roc_module::symbol::Symbol; +use crate::LocalId; + +#[derive(Debug, Clone, PartialEq)] +pub enum VirtualMachineSymbolState { + /// Value has been pushed onto the VM stack but not yet popped + /// Remember which instruction pushed it, in case we need it + Pushed { pushed_at: usize }, + + /// Value has been pushed and popped. If we want to use it again, we will + /// have to go back and insert an instruction where it was pushed + Popped { pushed_at: usize }, +} + pub struct CodeBuilder { - stack: Vec>, + /// The main container for the instructions code: Vec, + + /// Extra instructions to insert at specific positions during finalisation + /// (Mainly to go back and set locals when we realise we need them) + insertions: BTreeMap, + + /// Our simulation model of the Wasm stack machine + /// Keeps track of where Symbol values are in the VM stack + vm_stack: Vec>, } impl CodeBuilder { pub fn new() -> Self { CodeBuilder { - stack: Vec::with_capacity(32), + vm_stack: Vec::with_capacity(32), + insertions: BTreeMap::default(), code: Vec::with_capacity(1024), } } pub fn clear(&mut self) { - self.stack.clear(); self.code.clear(); + self.insertions.clear(); + self.vm_stack.clear(); } pub fn push(&mut self, inst: Instruction) { let (pops, push) = get_pops_and_pushes(&inst); - let new_len = self.stack.len() - pops as usize; - self.stack.truncate(new_len); + let new_len = self.vm_stack.len() - pops as usize; + self.vm_stack.truncate(new_len); if push { - self.stack.push(None); + self.vm_stack.push(None); } + // println!("{:?} {:?}", inst, self.vm_stack); self.code.push(inst); } pub fn extend(&mut self, instructions: &[Instruction]) { - let old_len = self.stack.len(); + let old_len = self.vm_stack.len(); let mut len = old_len; let mut min_len = len; for inst in instructions { @@ -43,28 +70,126 @@ impl CodeBuilder { len += 1; } } - self.stack.truncate(min_len); - self.stack.resize(len, None); + self.vm_stack.truncate(min_len); + self.vm_stack.resize(len, None); + // println!("{:?} {:?}", instructions, self.vm_stack); self.code.extend_from_slice(instructions); } - pub fn drain(&mut self) -> std::vec::Drain { - self.code.drain(0..) + pub fn call(&mut self, function_index: u32, pops: usize, push: bool) { + let stack_depth = self.vm_stack.len(); + if pops > stack_depth { + let mut final_code = Vec::with_capacity(self.code.len() + self.insertions.len()); + self.finalize_into(&mut final_code); + panic!( + "Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\nfinal_code={:?}\nvm_stack={:?}", + function_index, pops, stack_depth, final_code, self.vm_stack + ); + } + let new_stack_depth = stack_depth - pops as usize; + self.vm_stack.truncate(new_stack_depth); + if push { + self.vm_stack.push(None); + } + self.code.push(Call(function_index)); } + pub fn finalize_into(&mut self, final_code: &mut Vec) { + let mut insertions_iter = self.insertions.iter(); + let mut next_insertion = insertions_iter.next(); + + for (pos, instruction) in self.code.drain(0..).enumerate() { + match next_insertion { + Some((&insert_pos, insert_inst)) if insert_pos == pos => { + final_code.push(insert_inst.to_owned()); + final_code.push(instruction); + next_insertion = insertions_iter.next(); + } + _ => { + final_code.push(instruction); + } + } + } + } + + /// Total number of instructions in the final output pub fn len(&self) -> usize { - self.code.len() + self.code.len() + self.insertions.len() } - pub fn set_top_symbol(&mut self, sym: Symbol) { - let len = self.stack.len(); - let code_index = self.code.len(); - self.stack[len - 1] = Some((sym, code_index)); + pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState { + let len = self.vm_stack.len(); + let pushed_at = self.code.len(); + + if len == 0 { + panic!( + "trying to set symbol with nothing on stack, code = {:?}", + self.code + ); + } + + self.vm_stack[len - 1] = Some(sym); + + VirtualMachineSymbolState::Pushed { pushed_at } } - pub fn get_top_symbol(&self) -> Option<(Symbol, usize)> { - let len = self.stack.len(); - self.stack[len - 1] + pub fn load_symbol( + &mut self, + symbol: Symbol, + vm_state: VirtualMachineSymbolState, + next_local_id: LocalId, + ) -> Option { + use VirtualMachineSymbolState::*; + + match vm_state { + Pushed { pushed_at } => { + let &top = self.vm_stack.last().unwrap(); + match top { + Some(top_symbol) if top_symbol == symbol => { + // We're lucky, the symbol is already on top of the VM stack + // No code to generate, just let the caller know what happened + Some(Popped { pushed_at }) + } + _ => { + // Symbol is not on top of the stack. Find it. + if let Some(found_index) = + self.vm_stack.iter().rposition(|&s| s == Some(symbol)) + { + // Insert a SetLocal where the value was created (this removes it from the VM stack) + self.insertions.insert(pushed_at, SetLocal(next_local_id.0)); + self.vm_stack.remove(found_index); + + // Insert a GetLocal at the current position + self.code.push(GetLocal(next_local_id.0)); + self.vm_stack.push(Some(symbol)); + + // This Symbol is no longer stored in the VM stack, but in a local + None + } else { + panic!( + "{:?} has state {:?} but not found in VM stack", + symbol, vm_state + ); + } + } + } + } + + Popped { pushed_at } => { + // This Symbol is being used for a second time + + // Insert a TeeLocal where it was created (must remain on the stack for the first usage) + self.insertions.insert(pushed_at, TeeLocal(next_local_id.0)); + + // Insert a GetLocal at the current position + self.code.push(GetLocal(next_local_id.0)); + self.vm_stack.push(Some(symbol)); + + // This symbol has been promoted to a Local + // Tell the caller it no longer has a VirtualMachineSymbolState + None + } + } } } @@ -82,8 +207,9 @@ fn get_pops_and_pushes(inst: &Instruction) -> (u8, bool) { BrTable(_) => (0, false), Return => (0, false), - Call(_) => (0, false), // depends on the function! handle this elsewhere - CallIndirect(_, _) => (0, false), + Call(_) | CallIndirect(_, _) => { + panic!("Unknown number of pushes and pops. Use Codebuilder::call() instead."); + } Drop => (1, false), Select => (3, true), diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 0402bbc2f6..d9b1060c43 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,5 +1,5 @@ -use crate::code_builder::CodeBuilder; -use crate::{copy_memory, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; +use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState}; +use crate::{copy_memory, CopyMemoryConfig, LocalId}; use parity_wasm::elements::{Instruction::*, ValueType}; #[derive(Debug, Clone)] @@ -19,21 +19,49 @@ impl StackMemoryLocation { #[derive(Debug, Clone)] pub enum SymbolStorage { - // TODO: implicit storage in the VM stack - // TODO: const data storage + /// Value is stored implicitly in the VM stack + VirtualMachineStack { + vm_state: VirtualMachineSymbolState, + value_type: ValueType, + size: u32, + }, + + /// A local variable in the Wasm function Local { local_id: LocalId, value_type: ValueType, size: u32, }, + + /// Value is stored in stack memory StackMemory { location: StackMemoryLocation, size: u32, alignment_bytes: u32, }, + // TODO: const data storage (fixed address) } impl SymbolStorage { + pub fn local_id(&self) -> Option { + use StackMemoryLocation::*; + match self { + Self::VirtualMachineStack { .. } => None, + + Self::Local { local_id, .. } => Some(*local_id), + + Self::StackMemory { + location: FrameOffset(_), + .. + } => None, + + Self::StackMemory { + location: PointerArg(local_id), + .. + } => Some(*local_id), + } + } + /// generate code to copy from another storage of the same type pub fn copy_from( &self, @@ -95,58 +123,4 @@ impl SymbolStorage { } } } - - /// Generate code to copy to a memory address (such as a struct index) - pub fn copy_to_memory( - &self, - instructions: &mut CodeBuilder, - to_ptr: LocalId, - to_offset: u32, - stack_frame_pointer: Option, - ) -> u32 { - match self { - Self::Local { - local_id, - value_type, - size, - .. - } => { - let store_instruction = match (value_type, size) { - (ValueType::I64, 8) => I64Store(ALIGN_8, to_offset), - (ValueType::I32, 4) => I32Store(ALIGN_4, to_offset), - (ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset), - (ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset), - (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), - (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), - _ => { - panic!("Cannot store {:?} with alignment of {:?}", value_type, size); - } - }; - instructions.push(GetLocal(to_ptr.0)); - instructions.push(GetLocal(local_id.0)); - instructions.push(store_instruction); - *size - } - - Self::StackMemory { - location, - size, - alignment_bytes, - } => { - let (from_ptr, from_offset) = location.local_and_offset(stack_frame_pointer); - copy_memory( - instructions, - CopyMemoryConfig { - from_ptr, - from_offset, - to_ptr, - to_offset, - size: *size, - alignment_bytes: *alignment_bytes, - }, - ); - *size - } - } - } } From af823fe5a8dcbdea7abf57f984b84fa8b372ee65 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 6 Oct 2021 20:07:20 +0100 Subject: [PATCH 04/51] Get rid of unneeded local.set/get in common cases --- compiler/gen_wasm/src/backend.rs | 128 +++++++++++++------------- compiler/gen_wasm/src/code_builder.rs | 20 ++++ 2 files changed, 86 insertions(+), 62 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 95dd4ac768..91463034be 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -265,62 +265,71 @@ impl<'a> WasmBackend<'a> { } } - /// Load a symbol, e.g. for passing to a function call - fn load_symbol(&mut self, sym: &Symbol) { - let storage = self.get_symbol_storage(sym).to_owned(); - match storage { - SymbolStorage::VirtualMachineStack { - vm_state, - value_type, - size, - } => { - let next_local_id = self.get_next_local_id(); - let maybe_next_vm_state = - self.instructions.load_symbol(*sym, vm_state, next_local_id); - match maybe_next_vm_state { - // The act of loading the value changed the VM state, so update it - Some(next_vm_state) => { - self.symbol_storage_map.insert( - *sym, - SymbolStorage::VirtualMachineStack { - vm_state: next_vm_state, - value_type, - size, - }, - ); - } - None => { - // Loading the value required creating a new local, because - // it was not in a convenient position in the VM stack. - self.locals.push(Local::new(1, value_type)); - self.symbol_storage_map.insert( - *sym, - SymbolStorage::Local { - local_id: next_local_id, - value_type, - size, - }, - ); + /// Load symbols to the top of the VM stack + fn load_symbols(&mut self, symbols: &[Symbol]) { + if self.instructions.verify_stack_match(symbols) { + // The symbols were already at the top of the stack, do nothing! + // This should be quite common due to the structure of the Mono IR + return; + } + for sym in symbols.iter() { + let storage = self.get_symbol_storage(sym).to_owned(); + match storage { + SymbolStorage::VirtualMachineStack { + vm_state, + value_type, + size, + } => { + let next_local_id = self.get_next_local_id(); + let maybe_next_vm_state = + self.instructions.load_symbol(*sym, vm_state, next_local_id); + match maybe_next_vm_state { + // The act of loading the value changed the VM state, so update it + Some(next_vm_state) => { + self.symbol_storage_map.insert( + *sym, + SymbolStorage::VirtualMachineStack { + vm_state: next_vm_state, + value_type, + size, + }, + ); + } + None => { + // Loading the value required creating a new local, because + // it was not in a convenient position in the VM stack. + self.locals.push(Local::new(1, value_type)); + self.symbol_storage_map.insert( + *sym, + SymbolStorage::Local { + local_id: next_local_id, + value_type, + size, + }, + ); + } } } - } - SymbolStorage::Local { local_id, .. } - | SymbolStorage::StackMemory { - location: StackMemoryLocation::PointerArg(local_id), - .. - } => { - self.instructions.push(GetLocal(local_id.0)); - } + SymbolStorage::Local { local_id, .. } + | SymbolStorage::StackMemory { + location: StackMemoryLocation::PointerArg(local_id), + .. + } => { + self.instructions.push(GetLocal(local_id.0)); + self.instructions.set_top_symbol(*sym); + } - SymbolStorage::StackMemory { - location: StackMemoryLocation::FrameOffset(offset), - .. - } => { - self.instructions.extend(&[ - GetLocal(self.stack_frame_pointer.unwrap().0), - I32Const(offset as i32), - I32Add, - ]); + SymbolStorage::StackMemory { + location: StackMemoryLocation::FrameOffset(offset), + .. + } => { + self.instructions.extend(&[ + GetLocal(self.stack_frame_pointer.unwrap().0), + I32Const(offset as i32), + I32Add, + ]); + self.instructions.set_top_symbol(*sym); + } } } } @@ -415,7 +424,7 @@ impl<'a> WasmBackend<'a> { } _ => { - self.load_symbol(sym); + self.load_symbols(&[*sym]); self.instructions.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) } } @@ -541,9 +550,7 @@ impl<'a> WasmBackend<'a> { arguments, }) => match call_type { CallType::ByName { name: func_sym, .. } => { - for arg in *arguments { - self.load_symbol(arg); - } + self.load_symbols(*arguments); let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!( "Cannot find function {:?} called from {:?}", func_sym, sym @@ -691,7 +698,7 @@ impl<'a> WasmBackend<'a> { } }; self.instructions.push(GetLocal(to_ptr.0)); - self.load_symbol(&from_symbol); + self.load_symbols(&[from_symbol]); self.instructions.push(store_instruction); size } @@ -704,9 +711,7 @@ impl<'a> WasmBackend<'a> { args: &'a [Symbol], return_layout: &Layout<'a>, ) -> Result<(), String> { - for arg in args { - self.load_symbol(arg); - } + self.load_symbols(args); let wasm_layout = WasmLayout::new(return_layout); self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?; Ok(()) @@ -723,7 +728,6 @@ impl<'a> WasmBackend<'a> { // For those, we'll need to pre-process each argument before the main op, // so simple arrays of instructions won't work. But there are common patterns. let instructions: &[Instruction] = match lowlevel { - // Wasm type might not be enough, may need to sign-extend i8 etc. Maybe in load_symbol? LowLevel::NumAdd => match return_value_type { ValueType::I32 => &[I32Add], ValueType::I64 => &[I64Add], diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index d2f41922b6..e4a117211a 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -133,6 +133,26 @@ impl CodeBuilder { VirtualMachineSymbolState::Pushed { pushed_at } } + /// Verify if a sequence of symbols is at the top of the stack + pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool { + let n_symbols = symbols.len(); + let stack_depth = self.vm_stack.len(); + if n_symbols > stack_depth { + return false; + } + let offset = stack_depth - n_symbols; + + for (i, sym) in symbols.iter().enumerate() { + match self.vm_stack[offset + i] { + Some(stack_symbol) if stack_symbol == *sym => {} + _ => { + return false; + } + } + } + true + } + pub fn load_symbol( &mut self, symbol: Symbol, From d81999045abe9f0f9eb0c4e3aafe3e6560f2ac0b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 7 Oct 2021 09:14:35 +0100 Subject: [PATCH 05/51] Get Switch statements working with VM storage --- compiler/gen_wasm/src/backend.rs | 25 +++++++++++++++++++++---- compiler/gen_wasm/src/storage.rs | 2 ++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 91463034be..26de28faad 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -443,18 +443,35 @@ impl<'a> WasmBackend<'a> { // We may be able to improve this in the future with `Select` // or `BrTable` + // Ensure the condition value is not stored only in the VM stack + // Otherwise we can't reach it from inside the block + let cond_storage = self.get_symbol_storage(cond_symbol).to_owned(); + if let SymbolStorage::VirtualMachineStack { + value_type, size, .. + } = cond_storage + { + let local_id = self.get_next_local_id(); + self.locals.push(Local::new(1, value_type)); + self.instructions.push(SetLocal(local_id.0)); + self.symbol_storage_map.insert( + *cond_symbol, + SymbolStorage::Local { + local_id, + value_type, + size, + }, + ); + } + // create (number_of_branches - 1) new blocks. for _ in 0..branches.len() { self.start_block(BlockType::NoResult) } - // the LocalId of the symbol that we match on - let matched_on = self.local_id_from_symbol(cond_symbol); - // then, we jump whenever the value under scrutiny is equal to the value of a branch for (i, (value, _, _)) in branches.iter().enumerate() { // put the cond_symbol on the top of the stack - self.instructions.push(GetLocal(matched_on.0)); + self.load_symbols(&[*cond_symbol]); self.instructions.push(I32Const(*value as i32)); diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index d9b1060c43..e15b793e4d 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -43,6 +43,8 @@ pub enum SymbolStorage { } impl SymbolStorage { + /// Mostly-deprecated. If you are calling this method, it is likely a bug! + /// Gets the local, if any, associated with this symbol. Hopefully there is none. pub fn local_id(&self) -> Option { use StackMemoryLocation::*; match self { From 3aaafdefe1d94f3e1f19173403b56dc49c8b065d Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 9 Oct 2021 09:54:13 +0100 Subject: [PATCH 06/51] Get Join/Jump working with VM storage --- compiler/gen_wasm/src/backend.rs | 406 +++++++++++++++++--------- compiler/gen_wasm/src/code_builder.rs | 8 +- compiler/gen_wasm/src/layout.rs | 16 +- compiler/gen_wasm/src/storage.rs | 92 +----- 4 files changed, 279 insertions(+), 243 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 26de28faad..0ffca5bf11 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -10,7 +10,7 @@ use roc_module::symbol::Symbol; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; -use crate::code_builder::CodeBuilder; +use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState}; use crate::layout::WasmLayout; use crate::storage::{StackMemoryLocation, SymbolStorage}; use crate::{ @@ -51,7 +51,7 @@ pub struct WasmBackend<'a> { symbol_storage_map: MutMap, /// how many blocks deep are we (used for jumps) block_depth: u32, - joinpoint_label_map: MutMap)>, + joinpoint_label_map: MutMap)>, } impl<'a> WasmBackend<'a> { @@ -93,6 +93,12 @@ impl<'a> WasmBackend<'a> { assert_eq!(self.block_depth, 0); } + /********************************************************** + + PROCEDURE + + ***********************************************************/ + pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { // println!("\ngenerating procedure {:?}\n", sym); @@ -124,7 +130,7 @@ impl<'a> WasmBackend<'a> { }; for (layout, symbol) in proc.args { - self.create_storage(WasmLayout::new(layout), *symbol, LocalKind::Parameter); + self.create_storage(&WasmLayout::new(layout), *symbol, LocalKind::Parameter); } signature_builder.with_params(self.arg_types.clone()) @@ -163,29 +169,35 @@ impl<'a> WasmBackend<'a> { .build() // function } + /********************************************************** + + SYMBOL STORAGE, LOCALS, AND COPYING + + ***********************************************************/ + fn get_next_local_id(&self) -> LocalId { LocalId((self.arg_types.len() + self.locals.len()) as u32) } fn create_storage( &mut self, - wasm_layout: WasmLayout, + wasm_layout: &WasmLayout, symbol: Symbol, kind: LocalKind, - ) -> Option { + ) -> SymbolStorage { let next_local_id = self.get_next_local_id(); let storage = match wasm_layout { - WasmLayout::LocalOnly(value_type, size) => match kind { + WasmLayout::Primitive(value_type, size) => match kind { LocalKind::Parameter => SymbolStorage::Local { local_id: next_local_id, - value_type, - size, + value_type: *value_type, + size: *size, }, LocalKind::Variable => SymbolStorage::VirtualMachineStack { - vm_state: self.instructions.set_top_symbol(symbol), - value_type, - size, + vm_state: VirtualMachineSymbolState::NotYetPushed, + value_type: *value_type, + size: *size, }, }, @@ -203,17 +215,14 @@ impl<'a> WasmBackend<'a> { LocalKind::Parameter => StackMemoryLocation::PointerArg(next_local_id), LocalKind::Variable => { - match self.stack_frame_pointer { - Some(_) => {} - None => { - self.stack_frame_pointer = Some(next_local_id); - } - }; + if self.stack_frame_pointer.is_none() { + self.stack_frame_pointer = Some(next_local_id); + } let offset = - round_up_to_alignment(self.stack_memory, alignment_bytes as i32); + round_up_to_alignment(self.stack_memory, *alignment_bytes as i32); - self.stack_memory = offset + size as i32; + self.stack_memory = offset + (*size as i32); StackMemoryLocation::FrameOffset(offset as u32) } @@ -221,29 +230,22 @@ impl<'a> WasmBackend<'a> { SymbolStorage::StackMemory { location, - size, - alignment_bytes, + size: *size, + alignment_bytes: *alignment_bytes, } } }; - let maybe_local_id = storage.local_id(); - - match kind { - LocalKind::Parameter => { - self.arg_types.push(wasm_layout.value_type()); - } - LocalKind::Variable => match maybe_local_id { - Some(_) => { - self.locals.push(Local::new(1, wasm_layout.value_type())); - } - None => {} - }, + let value_type = wasm_layout.value_type(); + match (kind, wasm_layout) { + (LocalKind::Parameter, _) => self.arg_types.push(value_type), + (LocalKind::Variable, WasmLayout::StackMemory { .. }) => (), + (LocalKind::Variable, _) => self.locals.push(Local::new(1, value_type)), } - self.symbol_storage_map.insert(symbol, storage); + self.symbol_storage_map.insert(symbol, storage.clone()); - maybe_local_id + storage } fn get_symbol_storage(&self, sym: &Symbol) -> &SymbolStorage { @@ -255,16 +257,6 @@ impl<'a> WasmBackend<'a> { }) } - fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId { - let storage = self.get_symbol_storage(sym); - match storage { - SymbolStorage::Local { local_id, .. } => *local_id, - _ => { - panic!("{:?} does not have a local_id", sym); - } - } - } - /// Load symbols to the top of the VM stack fn load_symbols(&mut self, symbols: &[Symbol]) { if self.instructions.verify_stack_match(symbols) { @@ -334,6 +326,181 @@ impl<'a> WasmBackend<'a> { } } + fn copy_symbol_to_memory( + &mut self, + to_ptr: LocalId, + to_offset: u32, + from_symbol: Symbol, + ) -> u32 { + let from_storage = self.get_symbol_storage(&from_symbol).to_owned(); + match from_storage { + SymbolStorage::StackMemory { + location, + size, + alignment_bytes, + } => { + let (from_ptr, from_offset) = location.local_and_offset(self.stack_frame_pointer); + copy_memory( + &mut self.instructions, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr, + to_offset, + size, + alignment_bytes, + }, + ); + size + } + + SymbolStorage::VirtualMachineStack { + value_type, size, .. + } + | SymbolStorage::Local { + value_type, size, .. + } => { + let store_instruction = match (value_type, size) { + (ValueType::I64, 8) => I64Store(ALIGN_8, to_offset), + (ValueType::I32, 4) => I32Store(ALIGN_4, to_offset), + (ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset), + (ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset), + (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), + (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), + _ => { + panic!("Cannot store {:?} with alignment of {:?}", value_type, size); + } + }; + self.instructions.push(GetLocal(to_ptr.0)); + self.load_symbols(&[from_symbol]); + self.instructions.push(store_instruction); + size + } + } + } + + /// generate code to copy a value from one SymbolStorage to another + pub fn copy_value_by_storage( + &mut self, + to: &SymbolStorage, + from: &SymbolStorage, + from_symbol: Symbol, + ) { + use SymbolStorage::*; + + match (to, from) { + ( + Local { + local_id: to_local_id, + value_type: to_value_type, + size: to_size, + }, + VirtualMachineStack { + value_type: from_value_type, + size: from_size, + .. + }, + ) => { + debug_assert!(to_value_type == from_value_type); + debug_assert!(to_size == from_size); + self.load_symbols(&[from_symbol]); + self.instructions.push(SetLocal(to_local_id.0)); + self.symbol_storage_map.insert(from_symbol, to.clone()); + } + + ( + Local { + local_id: to_local_id, + value_type: to_value_type, + size: to_size, + }, + Local { + local_id: from_local_id, + value_type: from_value_type, + size: from_size, + }, + ) => { + debug_assert!(to_value_type == from_value_type); + debug_assert!(to_size == from_size); + self.instructions.push(GetLocal(from_local_id.0)); + self.instructions.push(SetLocal(to_local_id.0)); + } + + ( + StackMemory { + location: to_location, + size: to_size, + alignment_bytes: to_alignment_bytes, + }, + StackMemory { + location: from_location, + size: from_size, + alignment_bytes: from_alignment_bytes, + }, + ) => { + let (from_ptr, from_offset) = + from_location.local_and_offset(self.stack_frame_pointer); + let (to_ptr, to_offset) = to_location.local_and_offset(self.stack_frame_pointer); + debug_assert!(*to_size == *from_size); + debug_assert!(*to_alignment_bytes == *from_alignment_bytes); + copy_memory( + &mut self.instructions, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr, + to_offset, + size: *from_size, + alignment_bytes: *from_alignment_bytes, + }, + ); + } + + _ => { + panic!("Cannot copy storage from {:?} to {:?}", from, to); + } + } + } + + /// Ensure SymbolStorage has an associated local. + /// (Blocks can't access the VM stack of their parent scope, but they can access locals.) + fn ensure_symbol_storage_has_local( + &mut self, + symbol: Symbol, + storage: SymbolStorage, + ) -> SymbolStorage { + if let SymbolStorage::VirtualMachineStack { + vm_state, + value_type, + size, + } = storage + { + let local_id = self.get_next_local_id(); + if vm_state != VirtualMachineSymbolState::NotYetPushed { + self.instructions.load_symbol(symbol, vm_state, local_id); + self.instructions.push(SetLocal(local_id.0)); + } + + self.locals.push(Local::new(1, value_type)); + let new_storage = SymbolStorage::Local { + local_id, + value_type, + size, + }; + + self.symbol_storage_map.insert(symbol, new_storage.clone()); + return new_storage; + } else { + storage + } + } + + /********************************************************** + + STATEMENTS + + ***********************************************************/ + /// start a loop that leaves a value on the stack fn start_loop_with_return(&mut self, value_type: ValueType) { self.block_depth += 1; @@ -353,8 +520,9 @@ impl<'a> WasmBackend<'a> { fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { match stmt { // Simple optimisation: if we are just returning the expression, we don't need a local - Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if let_sym == ret_sym => { + Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if *let_sym == *ret_sym => { let wasm_layout = WasmLayout::new(layout); + if let WasmLayout::StackMemory { size, alignment_bytes, @@ -369,7 +537,21 @@ impl<'a> WasmBackend<'a> { }; self.symbol_storage_map.insert(*let_sym, storage); } + self.build_expr(let_sym, expr, layout)?; + + if let WasmLayout::Primitive(value_type, size) = wasm_layout { + let vm_state = self.instructions.set_top_symbol(*let_sym); + self.symbol_storage_map.insert( + *let_sym, + SymbolStorage::VirtualMachineStack { + vm_state, + value_type, + size, + }, + ); + } + self.instructions.push(Br(self.block_depth)); // jump to end of function (stack frame pop) Ok(()) } @@ -377,20 +559,19 @@ impl<'a> WasmBackend<'a> { Stmt::Let(sym, expr, layout, following) => { let wasm_layout = WasmLayout::new(layout); - match wasm_layout { - WasmLayout::StackMemory { .. } => { - // If the expression writes to stack memory, allocate *before* generating - // so that know where to write it - self.create_storage(wasm_layout, *sym, LocalKind::Variable); - self.build_expr(sym, expr, layout)?; - } - _ => { - // If the expression produces a primitive, create storage *after* generating, - // because we don't know if we need a local until afterwards - // TODO: should we make this uniform by having a "not yet pushed" state in VirtualMachineSymbolState? - self.build_expr(sym, expr, layout)?; - self.create_storage(wasm_layout, *sym, LocalKind::Variable); - } + self.create_storage(&wasm_layout, *sym, LocalKind::Variable); + self.build_expr(sym, expr, layout)?; + + if let WasmLayout::Primitive(value_type, size) = wasm_layout { + let vm_state = self.instructions.set_top_symbol(*sym); + self.symbol_storage_map.insert( + *sym, + SymbolStorage::VirtualMachineStack { + vm_state, + value_type, + size, + }, + ); } self.build_stmt(following, ret_layout)?; @@ -446,22 +627,7 @@ impl<'a> WasmBackend<'a> { // Ensure the condition value is not stored only in the VM stack // Otherwise we can't reach it from inside the block let cond_storage = self.get_symbol_storage(cond_symbol).to_owned(); - if let SymbolStorage::VirtualMachineStack { - value_type, size, .. - } = cond_storage - { - let local_id = self.get_next_local_id(); - self.locals.push(Local::new(1, value_type)); - self.instructions.push(SetLocal(local_id.0)); - self.symbol_storage_map.insert( - *cond_symbol, - SymbolStorage::Local { - local_id, - value_type, - size, - }, - ); - } + self.ensure_symbol_storage_has_local(*cond_symbol, cond_storage); // create (number_of_branches - 1) new blocks. for _ in 0..branches.len() { @@ -503,19 +669,20 @@ impl<'a> WasmBackend<'a> { remainder, } => { // make locals for join pointer parameters - let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len()); + let mut jp_param_storages = std::vec::Vec::with_capacity(parameters.len()); for parameter in parameters.iter() { let wasm_layout = WasmLayout::new(¶meter.layout); - let maybe_local_id = - self.create_storage(wasm_layout, parameter.symbol, LocalKind::Variable); - let jp_param_id = maybe_local_id.unwrap(); - jp_parameter_local_ids.push(jp_param_id); + let mut param_storage = + self.create_storage(&wasm_layout, parameter.symbol, LocalKind::Variable); + param_storage = + self.ensure_symbol_storage_has_local(parameter.symbol, param_storage); + jp_param_storages.push(param_storage); } self.start_block(BlockType::NoResult); self.joinpoint_label_map - .insert(*id, (self.block_depth, jp_parameter_local_ids)); + .insert(*id, (self.block_depth, jp_param_storages)); self.build_stmt(remainder, ret_layout)?; @@ -534,13 +701,11 @@ impl<'a> WasmBackend<'a> { Ok(()) } Stmt::Jump(id, arguments) => { - let (target, locals) = &self.joinpoint_label_map[id]; + let (target, param_storages) = self.joinpoint_label_map[id].clone(); - // put the arguments on the stack - for (symbol, local_id) in arguments.iter().zip(locals.iter()) { - let argument = self.local_id_from_symbol(symbol); - self.instructions.push(GetLocal(argument.0)); - self.instructions.push(SetLocal(local_id.0)); + for (arg_symbol, param_storage) in arguments.iter().zip(param_storages.iter()) { + let arg_storage = self.get_symbol_storage(arg_symbol).clone(); + self.copy_value_by_storage(¶m_storage, &arg_storage, *arg_symbol); } // jump @@ -553,6 +718,12 @@ impl<'a> WasmBackend<'a> { } } + /********************************************************** + + EXPRESSIONS + + ***********************************************************/ + fn build_expr( &mut self, sym: &Symbol, @@ -660,68 +831,11 @@ impl<'a> WasmBackend<'a> { } else { // Struct expression but not Struct layout => single element. Copy it. let field_storage = self.get_symbol_storage(&fields[0]).to_owned(); - storage.copy_from( - &field_storage, - &mut self.instructions, - self.stack_frame_pointer, - ); + self.copy_value_by_storage(&storage, &field_storage, fields[0]); } Ok(()) } - fn copy_symbol_to_memory( - &mut self, - to_ptr: LocalId, - to_offset: u32, - from_symbol: Symbol, - ) -> u32 { - let from_storage = self.get_symbol_storage(&from_symbol).to_owned(); - match from_storage { - SymbolStorage::StackMemory { - location, - size, - alignment_bytes, - } => { - let (from_ptr, from_offset) = location.local_and_offset(self.stack_frame_pointer); - copy_memory( - &mut self.instructions, - CopyMemoryConfig { - from_ptr, - from_offset, - to_ptr, - to_offset, - size, - alignment_bytes, - }, - ); - size - } - - SymbolStorage::VirtualMachineStack { - value_type, size, .. - } - | SymbolStorage::Local { - value_type, size, .. - } => { - let store_instruction = match (value_type, size) { - (ValueType::I64, 8) => I64Store(ALIGN_8, to_offset), - (ValueType::I32, 4) => I32Store(ALIGN_4, to_offset), - (ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset), - (ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset), - (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), - (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), - _ => { - panic!("Cannot store {:?} with alignment of {:?}", value_type, size); - } - }; - self.instructions.push(GetLocal(to_ptr.0)); - self.load_symbols(&[from_symbol]); - self.instructions.push(store_instruction); - size - } - } - } - fn build_call_low_level( &mut self, lowlevel: &LowLevel, diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index e4a117211a..09d7fe535a 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -6,8 +6,11 @@ use roc_module::symbol::Symbol; use crate::LocalId; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] pub enum VirtualMachineSymbolState { + /// Value doesn't exist yet + NotYetPushed, + /// Value has been pushed onto the VM stack but not yet popped /// Remember which instruction pushed it, in case we need it Pushed { pushed_at: usize }, @@ -17,6 +20,7 @@ pub enum VirtualMachineSymbolState { Popped { pushed_at: usize }, } +#[derive(Debug)] pub struct CodeBuilder { /// The main container for the instructions code: Vec, @@ -162,6 +166,8 @@ impl CodeBuilder { use VirtualMachineSymbolState::*; match vm_state { + NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol), + Pushed { pushed_at } => { let &top = self.vm_stack.last().unwrap(); match top { diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index df59b80eb1..5227f7755a 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -6,9 +6,9 @@ use crate::{PTR_SIZE, PTR_TYPE}; // See README for background information on Wasm locals, memory and function calls #[derive(Debug, Clone)] pub enum WasmLayout { - // Primitive number value. Just a Wasm local, without any stack memory. - // For example, Roc i8 is represented as Wasm i32. Store the type and the original size. - LocalOnly(ValueType, u32), + // Primitive number value, without any stack memory. + // For example, Roc i8 is represented as Primitive(ValueType::I32, 1) + Primitive(ValueType, u32), // Local pointer to stack memory StackMemory { size: u32, alignment_bytes: u32 }, @@ -27,13 +27,13 @@ impl WasmLayout { let alignment_bytes = layout.alignment_bytes(PTR_SIZE); match layout { - Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size), + Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::Primitive(I32, size), - Layout::Builtin(Int64) => Self::LocalOnly(I64, size), + Layout::Builtin(Int64) => Self::Primitive(I64, size), - Layout::Builtin(Float32) => Self::LocalOnly(F32, size), + Layout::Builtin(Float32) => Self::Primitive(F32, size), - Layout::Builtin(Float64) => Self::LocalOnly(F64, size), + Layout::Builtin(Float64) => Self::Primitive(F64, size), Layout::Builtin( Int128 @@ -67,7 +67,7 @@ impl WasmLayout { pub fn value_type(&self) -> ValueType { match self { - Self::LocalOnly(type_, _) => *type_, + Self::Primitive(type_, _) => *type_, _ => PTR_TYPE, } } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index e15b793e4d..44753c22c7 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,6 +1,6 @@ -use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState}; -use crate::{copy_memory, CopyMemoryConfig, LocalId}; -use parity_wasm::elements::{Instruction::*, ValueType}; +use parity_wasm::elements::ValueType; + +use crate::{LocalId, code_builder::VirtualMachineSymbolState}; #[derive(Debug, Clone)] pub enum StackMemoryLocation { @@ -39,90 +39,6 @@ pub enum SymbolStorage { size: u32, alignment_bytes: u32, }, + // TODO: const data storage (fixed address) } - -impl SymbolStorage { - /// Mostly-deprecated. If you are calling this method, it is likely a bug! - /// Gets the local, if any, associated with this symbol. Hopefully there is none. - pub fn local_id(&self) -> Option { - use StackMemoryLocation::*; - match self { - Self::VirtualMachineStack { .. } => None, - - Self::Local { local_id, .. } => Some(*local_id), - - Self::StackMemory { - location: FrameOffset(_), - .. - } => None, - - Self::StackMemory { - location: PointerArg(local_id), - .. - } => Some(*local_id), - } - } - - /// generate code to copy from another storage of the same type - pub fn copy_from( - &self, - from: &Self, - instructions: &mut CodeBuilder, - stack_frame_pointer: Option, - ) { - match (self, from) { - ( - Self::Local { - local_id: to_local_id, - value_type: to_value_type, - size: to_size, - }, - Self::Local { - local_id: from_local_id, - value_type: from_value_type, - size: from_size, - }, - ) => { - debug_assert!(to_value_type == from_value_type); - debug_assert!(to_size == from_size); - instructions.push(GetLocal(from_local_id.0)); - instructions.push(SetLocal(to_local_id.0)); - } - ( - Self::StackMemory { - location: to_location, - size: to_size, - alignment_bytes: to_alignment_bytes, - }, - Self::StackMemory { - location: from_location, - size: from_size, - alignment_bytes: from_alignment_bytes, - }, - ) => { - let (from_ptr, from_offset) = from_location.local_and_offset(stack_frame_pointer); - let (to_ptr, to_offset) = to_location.local_and_offset(stack_frame_pointer); - debug_assert!(*to_size == *from_size); - debug_assert!(*to_alignment_bytes == *from_alignment_bytes); - copy_memory( - instructions, - CopyMemoryConfig { - from_ptr, - from_offset, - to_ptr, - to_offset, - size: *from_size, - alignment_bytes: *from_alignment_bytes, - }, - ); - } - _ => { - panic!( - "Cannot copy different storage types {:?} to {:?}", - from, self - ); - } - } - } -} From 476c1664ec0a49d47c2d76472625d9143e3ea11b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 9 Oct 2021 17:24:37 +0100 Subject: [PATCH 07/51] Add debug logging to CodeBuilder and fix a minor bug --- compiler/gen_wasm/src/backend.rs | 2 ++ compiler/gen_wasm/src/code_builder.rs | 46 ++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 0ffca5bf11..38090c725a 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -258,6 +258,8 @@ impl<'a> WasmBackend<'a> { } /// Load symbols to the top of the VM stack + /// (There is no method for one symbol. This is deliberate, since + /// if anyone ever called it in a loop, it would generate inefficient code) fn load_symbols(&mut self, symbols: &[Symbol]) { if self.instructions.verify_stack_match(symbols) { // The symbols were already at the top of the stack, do nothing! diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index 09d7fe535a..d2dc6e7962 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -6,6 +6,8 @@ use roc_module::symbol::Symbol; use crate::LocalId; +const DEBUG_LOG: bool = false; + #[derive(Debug, Clone, PartialEq, Copy)] pub enum VirtualMachineSymbolState { /// Value doesn't exist yet @@ -56,7 +58,9 @@ impl CodeBuilder { if push { self.vm_stack.push(None); } - // println!("{:?} {:?}", inst, self.vm_stack); + if DEBUG_LOG { + println!("{:?} {:?}", inst, self.vm_stack); + } self.code.push(inst); } @@ -76,7 +80,9 @@ impl CodeBuilder { } self.vm_stack.truncate(min_len); self.vm_stack.resize(len, None); - // println!("{:?} {:?}", instructions, self.vm_stack); + if DEBUG_LOG { + println!("{:?} {:?}", instructions, self.vm_stack); + } self.code.extend_from_slice(instructions); } @@ -95,7 +101,11 @@ impl CodeBuilder { if push { self.vm_stack.push(None); } - self.code.push(Call(function_index)); + let inst = Call(function_index); + if DEBUG_LOG { + println!("{:?} {:?}", inst, self.vm_stack); + } + self.code.push(inst); } pub fn finalize_into(&mut self, final_code: &mut Vec) { @@ -186,7 +196,17 @@ impl CodeBuilder { self.vm_stack.remove(found_index); // Insert a GetLocal at the current position - self.code.push(GetLocal(next_local_id.0)); + let inst = GetLocal(next_local_id.0); + if DEBUG_LOG { + println!( + "{:?} {:?} (& insert {:?} at {:?})", + inst, + self.vm_stack, + SetLocal(next_local_id.0), + pushed_at + ); + } + self.code.push(inst); self.vm_stack.push(Some(symbol)); // This Symbol is no longer stored in the VM stack, but in a local @@ -208,7 +228,17 @@ impl CodeBuilder { self.insertions.insert(pushed_at, TeeLocal(next_local_id.0)); // Insert a GetLocal at the current position - self.code.push(GetLocal(next_local_id.0)); + let inst = GetLocal(next_local_id.0); + if DEBUG_LOG { + println!( + "{:?} {:?} (& insert {:?} at {:?})", + inst, + self.vm_stack, + TeeLocal(next_local_id.0), + pushed_at + ); + } + self.code.push(inst); self.vm_stack.push(Some(symbol)); // This symbol has been promoted to a Local @@ -225,12 +255,12 @@ fn get_pops_and_pushes(inst: &Instruction) -> (u8, bool) { Nop => (0, false), Block(_) => (0, false), Loop(_) => (0, false), - If(_) => (0, false), + If(_) => (1, false), Else => (0, false), End => (0, false), Br(_) => (0, false), - BrIf(_) => (0, false), - BrTable(_) => (0, false), + BrIf(_) => (1, false), + BrTable(_) => (1, false), Return => (0, false), Call(_) | CallIndirect(_, _) => { From d166f65a319ee3c13047ee06dad8f337b11f90e1 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 9 Oct 2021 18:14:52 +0100 Subject: [PATCH 08/51] Fix bug: Forgot to generate a local for the stack frame pointer --- compiler/gen_wasm/src/backend.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 38090c725a..8e7da8c0c7 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -217,6 +217,7 @@ impl<'a> WasmBackend<'a> { LocalKind::Variable => { if self.stack_frame_pointer.is_none() { self.stack_frame_pointer = Some(next_local_id); + self.locals.push(Local::new(1, PTR_TYPE)); } let offset = From 32b9f4fb079529d79b87af2f23bae1d281c4f6ac Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 9 Oct 2021 18:47:37 +0100 Subject: [PATCH 09/51] Generate a .wasm file for every test, for size benchmarking --- compiler/gen_wasm/tests/helpers/eval.rs | 31 +++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/compiler/gen_wasm/tests/helpers/eval.rs b/compiler/gen_wasm/tests/helpers/eval.rs index f29a360094..4287ded78a 100644 --- a/compiler/gen_wasm/tests/helpers/eval.rs +++ b/compiler/gen_wasm/tests/helpers/eval.rs @@ -1,3 +1,5 @@ +use std::cell::Cell; + use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; // use roc_std::{RocDec, RocList, RocOrder, RocStr}; @@ -6,6 +8,10 @@ use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; const TEST_WRAPPER_NAME: &str = "test_wrapper"; +std::thread_local! { + static TEST_COUNTER: Cell = Cell::new(0); +} + fn promote_expr_to_module(src: &str) -> String { let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); @@ -101,16 +107,27 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( let module_bytes = builder.build().to_bytes().unwrap(); // for debugging (e.g. with wasm2wat) - if false { + if true { use std::io::Write; - let path = "/home/brian/Documents/roc/compiler/gen_wasm/debug.wasm"; - match std::fs::File::create(path) { - Err(e) => eprintln!("Problem creating wasm debug file: {:?}", e), - Ok(mut file) => { - file.write_all(&module_bytes).unwrap(); + TEST_COUNTER.with(|test_count| -> () { + let thread_id = std::thread::current().id(); + let thread_num_string: String = format!("{:?}", thread_id).chars().filter(|c| c.is_digit(10)).collect(); + let thread_num: i64 = thread_num_string.parse().unwrap(); + let path = format!( + "/home/brian/Documents/roc/compiler/gen_wasm/output/test-{:?}-{:?}.wasm", + thread_num, + test_count.get() + ); + test_count.set(test_count.get() + 1); + + match std::fs::File::create(path) { + Err(e) => eprintln!("Problem creating wasm debug file: {:?}", e), + Ok(mut file) => { + file.write_all(&module_bytes).unwrap(); + } } - } + }); } // now, do wasmer stuff From 47f93bddeae2ce7ea653c5119f29ff3461fd29c3 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 10 Oct 2021 10:36:23 +0100 Subject: [PATCH 10/51] Fix & refactor create_storage --- compiler/gen_wasm/src/backend.rs | 41 ++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 8e7da8c0c7..21b62ea76e 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -189,11 +189,14 @@ impl<'a> WasmBackend<'a> { let storage = match wasm_layout { WasmLayout::Primitive(value_type, size) => match kind { - LocalKind::Parameter => SymbolStorage::Local { - local_id: next_local_id, - value_type: *value_type, - size: *size, - }, + LocalKind::Parameter => { + self.arg_types.push(*value_type); + SymbolStorage::Local { + local_id: next_local_id, + value_type: *value_type, + size: *size, + } + } LocalKind::Variable => SymbolStorage::VirtualMachineStack { vm_state: VirtualMachineSymbolState::NotYetPushed, value_type: *value_type, @@ -201,18 +204,27 @@ impl<'a> WasmBackend<'a> { }, }, - WasmLayout::HeapMemory => SymbolStorage::Local { - local_id: next_local_id, - value_type: PTR_TYPE, - size: PTR_SIZE, - }, + WasmLayout::HeapMemory => { + match kind { + LocalKind::Parameter => self.arg_types.push(PTR_TYPE), + LocalKind::Variable => self.locals.push(Local::new(1, PTR_TYPE)), + } + SymbolStorage::Local { + local_id: next_local_id, + value_type: PTR_TYPE, + size: PTR_SIZE, + } + } WasmLayout::StackMemory { size, alignment_bytes, } => { let location = match kind { - LocalKind::Parameter => StackMemoryLocation::PointerArg(next_local_id), + LocalKind::Parameter => { + self.arg_types.push(PTR_TYPE); + StackMemoryLocation::PointerArg(next_local_id) + } LocalKind::Variable => { if self.stack_frame_pointer.is_none() { @@ -237,13 +249,6 @@ impl<'a> WasmBackend<'a> { } }; - let value_type = wasm_layout.value_type(); - match (kind, wasm_layout) { - (LocalKind::Parameter, _) => self.arg_types.push(value_type), - (LocalKind::Variable, WasmLayout::StackMemory { .. }) => (), - (LocalKind::Variable, _) => self.locals.push(Local::new(1, value_type)), - } - self.symbol_storage_map.insert(symbol, storage.clone()); storage From 14f7f0f3b474af8df72633fa46bb60034fe95cbd Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 10 Oct 2021 10:42:02 +0100 Subject: [PATCH 11/51] Improved test setup for size comparison --- compiler/gen_wasm/test-compare.sh | 29 +++++++++++++++++++++++ compiler/gen_wasm/test-run.sh | 24 +++++++++++++++++++ compiler/gen_wasm/tests/helpers/eval.rs | 31 ++++++++++++------------- 3 files changed, 68 insertions(+), 16 deletions(-) create mode 100755 compiler/gen_wasm/test-compare.sh create mode 100755 compiler/gen_wasm/test-run.sh diff --git a/compiler/gen_wasm/test-compare.sh b/compiler/gen_wasm/test-compare.sh new file mode 100755 index 0000000000..fcc1fdbe7a --- /dev/null +++ b/compiler/gen_wasm/test-compare.sh @@ -0,0 +1,29 @@ +#!/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 )) + PERCENT_CHANGE=$(( $CHANGE * 100 / $NET_SIZE1 )) + printf "%s\t%d\t%d\t%d\t%d%%\n" $f $SIZE1 $SIZE2 $CHANGE $PERCENT_CHANGE +done diff --git a/compiler/gen_wasm/test-run.sh b/compiler/gen_wasm/test-run.sh new file mode 100755 index 0000000000..df7b015e05 --- /dev/null +++ b/compiler/gen_wasm/test-run.sh @@ -0,0 +1,24 @@ +#!/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" diff --git a/compiler/gen_wasm/tests/helpers/eval.rs b/compiler/gen_wasm/tests/helpers/eval.rs index 4287ded78a..044a8f5774 100644 --- a/compiler/gen_wasm/tests/helpers/eval.rs +++ b/compiler/gen_wasm/tests/helpers/eval.rs @@ -1,4 +1,6 @@ use std::cell::Cell; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; @@ -110,24 +112,21 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( if true { use std::io::Write; - TEST_COUNTER.with(|test_count| -> () { - let thread_id = std::thread::current().id(); - let thread_num_string: String = format!("{:?}", thread_id).chars().filter(|c| c.is_digit(10)).collect(); - let thread_num: i64 = thread_num_string.parse().unwrap(); - let path = format!( - "/home/brian/Documents/roc/compiler/gen_wasm/output/test-{:?}-{:?}.wasm", - thread_num, - test_count.get() - ); - test_count.set(test_count.get() + 1); + let mut hash_state = DefaultHasher::new(); + src.hash(&mut hash_state); + let src_hash = hash_state.finish(); - match std::fs::File::create(path) { - Err(e) => eprintln!("Problem creating wasm debug file: {:?}", e), - Ok(mut file) => { - file.write_all(&module_bytes).unwrap(); - } + let path = format!( + "/home/brian/Documents/roc/compiler/gen_wasm/output/test-{:016x}.wasm", + src_hash + ); + + match std::fs::File::create(path) { + Err(e) => eprintln!("Problem creating wasm debug file: {:?}", e), + Ok(mut file) => { + file.write_all(&module_bytes).unwrap(); } - }); + } } // now, do wasmer stuff From 9dcc6f2bc51375fee72b5d99f33184b8c0e81ac4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 10 Oct 2021 12:51:35 +0100 Subject: [PATCH 12/51] size comparison tweaks --- compiler/gen_wasm/.gitignore | 1 + compiler/gen_wasm/test-compare.sh | 4 ++-- compiler/gen_wasm/tests/helpers/eval.rs | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/gen_wasm/.gitignore b/compiler/gen_wasm/.gitignore index 64bbb919fa..bd517f6c96 100644 --- a/compiler/gen_wasm/.gitignore +++ b/compiler/gen_wasm/.gitignore @@ -1,3 +1,4 @@ *.wasm *.wat /notes.md +/tmp diff --git a/compiler/gen_wasm/test-compare.sh b/compiler/gen_wasm/test-compare.sh index fcc1fdbe7a..37286cd3e3 100755 --- a/compiler/gen_wasm/test-compare.sh +++ b/compiler/gen_wasm/test-compare.sh @@ -10,8 +10,8 @@ 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" +printf "filename \tLHS\tRHS\tchange\n" +printf "======== \t===\t===\t======\n" for f in `ls $1/wasm` do diff --git a/compiler/gen_wasm/tests/helpers/eval.rs b/compiler/gen_wasm/tests/helpers/eval.rs index 044a8f5774..a3d011eb18 100644 --- a/compiler/gen_wasm/tests/helpers/eval.rs +++ b/compiler/gen_wasm/tests/helpers/eval.rs @@ -121,6 +121,8 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( src_hash ); + println!("dumping file {:?}", path); + match std::fs::File::create(path) { Err(e) => eprintln!("Problem creating wasm debug file: {:?}", e), Ok(mut file) => { From c9997f211511df5d17541b9827aedf448ec8eb1b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 10 Oct 2021 15:47:50 +0100 Subject: [PATCH 13/51] Turn off output file generation & delete duplicate test --- compiler/gen_wasm/tests/helpers/eval.rs | 4 +++- compiler/gen_wasm/tests/wasm_records.rs | 12 ------------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/compiler/gen_wasm/tests/helpers/eval.rs b/compiler/gen_wasm/tests/helpers/eval.rs index a3d011eb18..af368e59aa 100644 --- a/compiler/gen_wasm/tests/helpers/eval.rs +++ b/compiler/gen_wasm/tests/helpers/eval.rs @@ -109,18 +109,20 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( let module_bytes = builder.build().to_bytes().unwrap(); // for debugging (e.g. with wasm2wat) - if true { + if false { use std::io::Write; let mut hash_state = DefaultHasher::new(); src.hash(&mut hash_state); let src_hash = hash_state.finish(); + // Filename contains a hash of the Roc test source code. Helpful when comparing across commits. let path = format!( "/home/brian/Documents/roc/compiler/gen_wasm/output/test-{:016x}.wasm", src_hash ); + // Print out filename (appears just after test name) println!("dumping file {:?}", path); match std::fs::File::create(path) { diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs index 884e92c7db..334809ac37 100644 --- a/compiler/gen_wasm/tests/wasm_records.rs +++ b/compiler/gen_wasm/tests/wasm_records.rs @@ -641,18 +641,6 @@ mod wasm_records { // ); // } - #[test] - fn return_record_2() { - assert_evals_to!( - indoc!( - r#" - { x: 3, y: 5 } - "# - ), - [3, 5], - [i64; 2] - ); - } #[test] fn return_record_3() { From 8164a14dfaa28ecc6845af4c8e8b4b774488579b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 10 Oct 2021 16:04:06 +0100 Subject: [PATCH 14/51] rename module_builder --- compiler/gen_wasm/src/backend.rs | 6 +++--- compiler/gen_wasm/src/lib.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 21b62ea76e..340d35a8b0 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -33,7 +33,7 @@ enum LocalKind { // TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43) pub struct WasmBackend<'a> { // Module: Wasm AST - pub builder: ModuleBuilder, + pub module_builder: ModuleBuilder, // Module: internal state & IR mappings _data_offset_map: MutMap, u32>, @@ -58,7 +58,7 @@ impl<'a> WasmBackend<'a> { pub fn new() -> Self { WasmBackend { // Module: Wasm AST - builder: builder::module(), + module_builder: builder::module(), // Module: internal state & IR mappings _data_offset_map: MutMap::default(), @@ -107,7 +107,7 @@ impl<'a> WasmBackend<'a> { self.build_stmt(&proc.body, &proc.ret_layout)?; let function_def = self.finalize_proc(signature_builder); - let location = self.builder.push_function(function_def); + let location = self.module_builder.push_function(function_def); let function_index = location.body; self.proc_symbol_map.insert(sym, location); self.reset(); diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 0cc74eddc3..a2fd1bd370 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -1,8 +1,8 @@ mod backend; +mod code_builder; pub mod from_wasm32_memory; mod layout; mod storage; -mod code_builder; use bumpalo::Bump; use parity_wasm::builder; @@ -81,7 +81,7 @@ pub fn build_module_help<'a>( .with_internal(Internal::Function(function_index)) .build(); - backend.builder.push_export(export); + backend.module_builder.push_export(export); } } @@ -96,21 +96,21 @@ pub fn build_module_help<'a>( let memory = builder::MemoryBuilder::new() .with_min(MIN_MEMORY_SIZE_KB / PAGE_SIZE_KB) .build(); - backend.builder.push_memory(memory); + backend.module_builder.push_memory(memory); let memory_export = builder::export() .field("memory") .with_internal(Internal::Memory(0)) .build(); - backend.builder.push_export(memory_export); + backend.module_builder.push_export(memory_export); let stack_pointer_global = builder::global() .with_type(PTR_TYPE) .mutable() .init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32)) .build(); - backend.builder.push_global(stack_pointer_global); + backend.module_builder.push_global(stack_pointer_global); - Ok((backend.builder, main_function_index)) + Ok((backend.module_builder, main_function_index)) } fn encode_alignment(bytes: u32) -> u32 { From c5ee41af25a19c8f2aefb3e07f5cbd2e1da6b81b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 10 Oct 2021 16:07:48 +0100 Subject: [PATCH 15/51] rename code_builder --- compiler/gen_wasm/src/backend.rs | 70 ++++++++++++++++---------------- compiler/gen_wasm/src/lib.rs | 26 ++++++------ 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 340d35a8b0..1b65b48380 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -41,7 +41,7 @@ pub struct WasmBackend<'a> { proc_symbol_map: MutMap, // Functions: Wasm AST - instructions: CodeBuilder, + code_builder: CodeBuilder, arg_types: std::vec::Vec, locals: std::vec::Vec, @@ -66,7 +66,7 @@ impl<'a> WasmBackend<'a> { proc_symbol_map: MutMap::default(), // Functions: Wasm AST - instructions: CodeBuilder::new(), + code_builder: CodeBuilder::new(), arg_types: std::vec::Vec::with_capacity(8), locals: std::vec::Vec::with_capacity(32), @@ -81,7 +81,7 @@ impl<'a> WasmBackend<'a> { fn reset(&mut self) { // Functions: Wasm AST - self.instructions.clear(); + self.code_builder.clear(); self.arg_types.clear(); self.locals.clear(); @@ -139,7 +139,7 @@ impl<'a> WasmBackend<'a> { fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition { self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any) - let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); + let mut final_instructions = Vec::with_capacity(self.code_builder.len() + 10); if self.stack_memory > 0 { push_stack_frame( @@ -149,7 +149,7 @@ impl<'a> WasmBackend<'a> { ); } - self.instructions.finalize_into(&mut final_instructions); + self.code_builder.finalize_into(&mut final_instructions); if self.stack_memory > 0 { pop_stack_frame( @@ -267,7 +267,7 @@ impl<'a> WasmBackend<'a> { /// (There is no method for one symbol. This is deliberate, since /// if anyone ever called it in a loop, it would generate inefficient code) fn load_symbols(&mut self, symbols: &[Symbol]) { - if self.instructions.verify_stack_match(symbols) { + if self.code_builder.verify_stack_match(symbols) { // The symbols were already at the top of the stack, do nothing! // This should be quite common due to the structure of the Mono IR return; @@ -282,7 +282,7 @@ impl<'a> WasmBackend<'a> { } => { let next_local_id = self.get_next_local_id(); let maybe_next_vm_state = - self.instructions.load_symbol(*sym, vm_state, next_local_id); + self.code_builder.load_symbol(*sym, vm_state, next_local_id); match maybe_next_vm_state { // The act of loading the value changed the VM state, so update it Some(next_vm_state) => { @@ -315,20 +315,20 @@ impl<'a> WasmBackend<'a> { location: StackMemoryLocation::PointerArg(local_id), .. } => { - self.instructions.push(GetLocal(local_id.0)); - self.instructions.set_top_symbol(*sym); + self.code_builder.push(GetLocal(local_id.0)); + self.code_builder.set_top_symbol(*sym); } SymbolStorage::StackMemory { location: StackMemoryLocation::FrameOffset(offset), .. } => { - self.instructions.extend(&[ + self.code_builder.extend(&[ GetLocal(self.stack_frame_pointer.unwrap().0), I32Const(offset as i32), I32Add, ]); - self.instructions.set_top_symbol(*sym); + self.code_builder.set_top_symbol(*sym); } } } @@ -349,7 +349,7 @@ impl<'a> WasmBackend<'a> { } => { let (from_ptr, from_offset) = location.local_and_offset(self.stack_frame_pointer); copy_memory( - &mut self.instructions, + &mut self.code_builder, CopyMemoryConfig { from_ptr, from_offset, @@ -379,9 +379,9 @@ impl<'a> WasmBackend<'a> { panic!("Cannot store {:?} with alignment of {:?}", value_type, size); } }; - self.instructions.push(GetLocal(to_ptr.0)); + self.code_builder.push(GetLocal(to_ptr.0)); self.load_symbols(&[from_symbol]); - self.instructions.push(store_instruction); + self.code_builder.push(store_instruction); size } } @@ -412,7 +412,7 @@ impl<'a> WasmBackend<'a> { debug_assert!(to_value_type == from_value_type); debug_assert!(to_size == from_size); self.load_symbols(&[from_symbol]); - self.instructions.push(SetLocal(to_local_id.0)); + self.code_builder.push(SetLocal(to_local_id.0)); self.symbol_storage_map.insert(from_symbol, to.clone()); } @@ -430,8 +430,8 @@ impl<'a> WasmBackend<'a> { ) => { debug_assert!(to_value_type == from_value_type); debug_assert!(to_size == from_size); - self.instructions.push(GetLocal(from_local_id.0)); - self.instructions.push(SetLocal(to_local_id.0)); + self.code_builder.push(GetLocal(from_local_id.0)); + self.code_builder.push(SetLocal(to_local_id.0)); } ( @@ -452,7 +452,7 @@ impl<'a> WasmBackend<'a> { debug_assert!(*to_size == *from_size); debug_assert!(*to_alignment_bytes == *from_alignment_bytes); copy_memory( - &mut self.instructions, + &mut self.code_builder, CopyMemoryConfig { from_ptr, from_offset, @@ -485,8 +485,8 @@ impl<'a> WasmBackend<'a> { { let local_id = self.get_next_local_id(); if vm_state != VirtualMachineSymbolState::NotYetPushed { - self.instructions.load_symbol(symbol, vm_state, local_id); - self.instructions.push(SetLocal(local_id.0)); + self.code_builder.load_symbol(symbol, vm_state, local_id); + self.code_builder.push(SetLocal(local_id.0)); } self.locals.push(Local::new(1, value_type)); @@ -512,17 +512,17 @@ impl<'a> WasmBackend<'a> { /// start a loop that leaves a value on the stack fn start_loop_with_return(&mut self, value_type: ValueType) { self.block_depth += 1; - self.instructions.push(Loop(BlockType::Value(value_type))); + self.code_builder.push(Loop(BlockType::Value(value_type))); } fn start_block(&mut self, block_type: BlockType) { self.block_depth += 1; - self.instructions.push(Block(block_type)); + self.code_builder.push(Block(block_type)); } fn end_block(&mut self) { self.block_depth -= 1; - self.instructions.push(End); + self.code_builder.push(End); } fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { @@ -549,7 +549,7 @@ impl<'a> WasmBackend<'a> { self.build_expr(let_sym, expr, layout)?; if let WasmLayout::Primitive(value_type, size) = wasm_layout { - let vm_state = self.instructions.set_top_symbol(*let_sym); + let vm_state = self.code_builder.set_top_symbol(*let_sym); self.symbol_storage_map.insert( *let_sym, SymbolStorage::VirtualMachineStack { @@ -560,7 +560,7 @@ impl<'a> WasmBackend<'a> { ); } - self.instructions.push(Br(self.block_depth)); // jump to end of function (stack frame pop) + self.code_builder.push(Br(self.block_depth)); // jump to end of function (stack frame pop) Ok(()) } @@ -571,7 +571,7 @@ impl<'a> WasmBackend<'a> { self.build_expr(sym, expr, layout)?; if let WasmLayout::Primitive(value_type, size) = wasm_layout { - let vm_state = self.instructions.set_top_symbol(*sym); + let vm_state = self.code_builder.set_top_symbol(*sym); self.symbol_storage_map.insert( *sym, SymbolStorage::VirtualMachineStack { @@ -600,7 +600,7 @@ impl<'a> WasmBackend<'a> { let (from_ptr, from_offset) = location.local_and_offset(self.stack_frame_pointer); copy_memory( - &mut self.instructions, + &mut self.code_builder, CopyMemoryConfig { from_ptr, from_offset, @@ -614,7 +614,7 @@ impl<'a> WasmBackend<'a> { _ => { self.load_symbols(&[*sym]); - self.instructions.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) + self.code_builder.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) } } @@ -647,13 +647,13 @@ impl<'a> WasmBackend<'a> { // put the cond_symbol on the top of the stack self.load_symbols(&[*cond_symbol]); - self.instructions.push(I32Const(*value as i32)); + self.code_builder.push(I32Const(*value as i32)); // compare the 2 topmost values - self.instructions.push(I32Eq); + self.code_builder.push(I32Eq); // "break" out of `i` surrounding blocks - self.instructions.push(BrIf(i as u32)); + self.code_builder.push(BrIf(i as u32)); } // if we never jumped because a value matched, we're in the default case @@ -718,7 +718,7 @@ impl<'a> WasmBackend<'a> { // jump let levels = self.block_depth - target; - self.instructions.push(Br(levels)); + self.code_builder.push(Br(levels)); Ok(()) } @@ -756,7 +756,7 @@ impl<'a> WasmBackend<'a> { let wasm_layout = WasmLayout::new(layout); let push = wasm_layout.stack_memory() == 0; let pops = arguments.len(); - self.instructions.call(function_location.body, pops, push); + self.code_builder.call(function_location.body, pops, push); Ok(()) } @@ -800,7 +800,7 @@ impl<'a> WasmBackend<'a> { return Err(format!("loading literal, {:?}, is not yet implemented", x)); } }; - self.instructions.push(instruction); + self.code_builder.push(instruction); Ok(()) } @@ -893,7 +893,7 @@ impl<'a> WasmBackend<'a> { return Err(format!("unsupported low-level op {:?}", lowlevel)); } }; - self.instructions.extend(instructions); + self.code_builder.extend(instructions); Ok(()) } } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index a2fd1bd370..1e01e3c762 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -132,29 +132,29 @@ pub struct CopyMemoryConfig { alignment_bytes: u32, } -pub fn copy_memory(instructions: &mut CodeBuilder, config: CopyMemoryConfig) { +pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) { debug_assert!(config.from_ptr != config.to_ptr || config.from_offset != config.to_offset); let alignment_flag = encode_alignment(config.alignment_bytes); let mut i = 0; while config.size - i >= 8 { - instructions.push(GetLocal(config.to_ptr.0)); - instructions.push(GetLocal(config.from_ptr.0)); - instructions.push(I64Load(alignment_flag, i + config.from_offset)); - instructions.push(I64Store(alignment_flag, i + config.to_offset)); + code_builder.push(GetLocal(config.to_ptr.0)); + code_builder.push(GetLocal(config.from_ptr.0)); + code_builder.push(I64Load(alignment_flag, i + config.from_offset)); + code_builder.push(I64Store(alignment_flag, i + config.to_offset)); i += 8; } if config.size - i >= 4 { - instructions.push(GetLocal(config.to_ptr.0)); - instructions.push(GetLocal(config.from_ptr.0)); - instructions.push(I32Load(alignment_flag, i + config.from_offset)); - instructions.push(I32Store(alignment_flag, i + config.to_offset)); + code_builder.push(GetLocal(config.to_ptr.0)); + code_builder.push(GetLocal(config.from_ptr.0)); + code_builder.push(I32Load(alignment_flag, i + config.from_offset)); + code_builder.push(I32Store(alignment_flag, i + config.to_offset)); i += 4; } while config.size - i > 0 { - instructions.push(GetLocal(config.to_ptr.0)); - instructions.push(GetLocal(config.from_ptr.0)); - instructions.push(I32Load8U(alignment_flag, i + config.from_offset)); - instructions.push(I32Store8(alignment_flag, i + config.to_offset)); + code_builder.push(GetLocal(config.to_ptr.0)); + code_builder.push(GetLocal(config.from_ptr.0)); + code_builder.push(I32Load8U(alignment_flag, i + config.from_offset)); + code_builder.push(I32Store8(alignment_flag, i + config.to_offset)); i += 1; } } From 041e26e8071edaf4659145509b2e702e6ae47df2 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 10 Oct 2021 16:21:53 +0100 Subject: [PATCH 16/51] rename CodeBuilder methods --- compiler/gen_wasm/src/backend.rs | 41 ++++++++++++++------------- compiler/gen_wasm/src/code_builder.rs | 11 ++++--- compiler/gen_wasm/src/lib.rs | 30 ++++++++++++-------- 3 files changed, 44 insertions(+), 38 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 1b65b48380..9f44396bf4 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -315,7 +315,7 @@ impl<'a> WasmBackend<'a> { location: StackMemoryLocation::PointerArg(local_id), .. } => { - self.code_builder.push(GetLocal(local_id.0)); + self.code_builder.add_one(GetLocal(local_id.0)); self.code_builder.set_top_symbol(*sym); } @@ -323,7 +323,7 @@ impl<'a> WasmBackend<'a> { location: StackMemoryLocation::FrameOffset(offset), .. } => { - self.code_builder.extend(&[ + self.code_builder.add_many(&[ GetLocal(self.stack_frame_pointer.unwrap().0), I32Const(offset as i32), I32Add, @@ -379,9 +379,9 @@ impl<'a> WasmBackend<'a> { panic!("Cannot store {:?} with alignment of {:?}", value_type, size); } }; - self.code_builder.push(GetLocal(to_ptr.0)); + self.code_builder.add_one(GetLocal(to_ptr.0)); self.load_symbols(&[from_symbol]); - self.code_builder.push(store_instruction); + self.code_builder.add_one(store_instruction); size } } @@ -412,7 +412,7 @@ impl<'a> WasmBackend<'a> { debug_assert!(to_value_type == from_value_type); debug_assert!(to_size == from_size); self.load_symbols(&[from_symbol]); - self.code_builder.push(SetLocal(to_local_id.0)); + self.code_builder.add_one(SetLocal(to_local_id.0)); self.symbol_storage_map.insert(from_symbol, to.clone()); } @@ -430,8 +430,8 @@ impl<'a> WasmBackend<'a> { ) => { debug_assert!(to_value_type == from_value_type); debug_assert!(to_size == from_size); - self.code_builder.push(GetLocal(from_local_id.0)); - self.code_builder.push(SetLocal(to_local_id.0)); + self.code_builder + .add_many(&[GetLocal(from_local_id.0), SetLocal(to_local_id.0)]); } ( @@ -486,7 +486,7 @@ impl<'a> WasmBackend<'a> { let local_id = self.get_next_local_id(); if vm_state != VirtualMachineSymbolState::NotYetPushed { self.code_builder.load_symbol(symbol, vm_state, local_id); - self.code_builder.push(SetLocal(local_id.0)); + self.code_builder.add_one(SetLocal(local_id.0)); } self.locals.push(Local::new(1, value_type)); @@ -512,17 +512,18 @@ impl<'a> WasmBackend<'a> { /// start a loop that leaves a value on the stack fn start_loop_with_return(&mut self, value_type: ValueType) { self.block_depth += 1; - self.code_builder.push(Loop(BlockType::Value(value_type))); + self.code_builder + .add_one(Loop(BlockType::Value(value_type))); } fn start_block(&mut self, block_type: BlockType) { self.block_depth += 1; - self.code_builder.push(Block(block_type)); + self.code_builder.add_one(Block(block_type)); } fn end_block(&mut self) { self.block_depth -= 1; - self.code_builder.push(End); + self.code_builder.add_one(End); } fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { @@ -560,7 +561,7 @@ impl<'a> WasmBackend<'a> { ); } - self.code_builder.push(Br(self.block_depth)); // jump to end of function (stack frame pop) + self.code_builder.add_one(Br(self.block_depth)); // jump to end of function (stack frame pop) Ok(()) } @@ -614,7 +615,7 @@ impl<'a> WasmBackend<'a> { _ => { self.load_symbols(&[*sym]); - self.code_builder.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) + self.code_builder.add_one(Br(self.block_depth)); // jump to end of function (for stack frame pop) } } @@ -647,13 +648,13 @@ impl<'a> WasmBackend<'a> { // put the cond_symbol on the top of the stack self.load_symbols(&[*cond_symbol]); - self.code_builder.push(I32Const(*value as i32)); + self.code_builder.add_one(I32Const(*value as i32)); // compare the 2 topmost values - self.code_builder.push(I32Eq); + self.code_builder.add_one(I32Eq); // "break" out of `i` surrounding blocks - self.code_builder.push(BrIf(i as u32)); + self.code_builder.add_one(BrIf(i as u32)); } // if we never jumped because a value matched, we're in the default case @@ -718,7 +719,7 @@ impl<'a> WasmBackend<'a> { // jump let levels = self.block_depth - target; - self.code_builder.push(Br(levels)); + self.code_builder.add_one(Br(levels)); Ok(()) } @@ -756,7 +757,7 @@ impl<'a> WasmBackend<'a> { let wasm_layout = WasmLayout::new(layout); let push = wasm_layout.stack_memory() == 0; let pops = arguments.len(); - self.code_builder.call(function_location.body, pops, push); + self.code_builder.add_call(function_location.body, pops, push); Ok(()) } @@ -800,7 +801,7 @@ impl<'a> WasmBackend<'a> { return Err(format!("loading literal, {:?}, is not yet implemented", x)); } }; - self.code_builder.push(instruction); + self.code_builder.add_one(instruction); Ok(()) } @@ -893,7 +894,7 @@ impl<'a> WasmBackend<'a> { return Err(format!("unsupported low-level op {:?}", lowlevel)); } }; - self.code_builder.extend(instructions); + self.code_builder.add_many(instructions); Ok(()) } } diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index d2dc6e7962..e06ada8e21 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -51,7 +51,7 @@ impl CodeBuilder { self.vm_stack.clear(); } - pub fn push(&mut self, inst: Instruction) { + pub fn add_one(&mut self, inst: Instruction) { let (pops, push) = get_pops_and_pushes(&inst); let new_len = self.vm_stack.len() - pops as usize; self.vm_stack.truncate(new_len); @@ -64,7 +64,7 @@ impl CodeBuilder { self.code.push(inst); } - pub fn extend(&mut self, instructions: &[Instruction]) { + pub fn add_many(&mut self, instructions: &[Instruction]) { let old_len = self.vm_stack.len(); let mut len = old_len; let mut min_len = len; @@ -86,7 +86,7 @@ impl CodeBuilder { self.code.extend_from_slice(instructions); } - pub fn call(&mut self, function_index: u32, pops: usize, push: bool) { + pub fn add_call(&mut self, function_index: u32, pops: usize, push: bool) { let stack_depth = self.vm_stack.len(); if pops > stack_depth { let mut final_code = Vec::with_capacity(self.code.len() + self.insertions.len()); @@ -96,8 +96,7 @@ impl CodeBuilder { function_index, pops, stack_depth, final_code, self.vm_stack ); } - let new_stack_depth = stack_depth - pops as usize; - self.vm_stack.truncate(new_stack_depth); + self.vm_stack.truncate(stack_depth - pops); if push { self.vm_stack.push(None); } @@ -264,7 +263,7 @@ fn get_pops_and_pushes(inst: &Instruction) -> (u8, bool) { Return => (0, false), Call(_) | CallIndirect(_, _) => { - panic!("Unknown number of pushes and pops. Use Codebuilder::call() instead."); + panic!("Unknown number of pushes and pops. Use add_call()"); } Drop => (1, false), diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 1e01e3c762..ec245d355a 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -137,24 +137,30 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) { let alignment_flag = encode_alignment(config.alignment_bytes); let mut i = 0; while config.size - i >= 8 { - code_builder.push(GetLocal(config.to_ptr.0)); - code_builder.push(GetLocal(config.from_ptr.0)); - code_builder.push(I64Load(alignment_flag, i + config.from_offset)); - code_builder.push(I64Store(alignment_flag, i + config.to_offset)); + code_builder.add_many(&[ + GetLocal(config.to_ptr.0), + GetLocal(config.from_ptr.0), + I64Load(alignment_flag, i + config.from_offset), + I64Store(alignment_flag, i + config.to_offset), + ]); i += 8; } if config.size - i >= 4 { - code_builder.push(GetLocal(config.to_ptr.0)); - code_builder.push(GetLocal(config.from_ptr.0)); - code_builder.push(I32Load(alignment_flag, i + config.from_offset)); - code_builder.push(I32Store(alignment_flag, i + config.to_offset)); + code_builder.add_many(&[ + GetLocal(config.to_ptr.0), + GetLocal(config.from_ptr.0), + I32Load(alignment_flag, i + config.from_offset), + I32Store(alignment_flag, i + config.to_offset), + ]); i += 4; } while config.size - i > 0 { - code_builder.push(GetLocal(config.to_ptr.0)); - code_builder.push(GetLocal(config.from_ptr.0)); - code_builder.push(I32Load8U(alignment_flag, i + config.from_offset)); - code_builder.push(I32Store8(alignment_flag, i + config.to_offset)); + code_builder.add_many(&[ + GetLocal(config.to_ptr.0), + GetLocal(config.from_ptr.0), + I32Load8U(alignment_flag, i + config.from_offset), + I32Store8(alignment_flag, i + config.to_offset), + ]); i += 1; } } From 58549bdf0769e0704cd2f6ba8758b2433b7ae161 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 10 Oct 2021 17:23:46 +0100 Subject: [PATCH 17/51] CodeBuilder doc comments --- compiler/gen_wasm/src/code_builder.rs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index e06ada8e21..aa21dbe3b1 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -14,11 +14,12 @@ pub enum VirtualMachineSymbolState { NotYetPushed, /// Value has been pushed onto the VM stack but not yet popped - /// Remember which instruction pushed it, in case we need it + /// Remember where it was pushed, in case we need to insert another instruction there later Pushed { pushed_at: usize }, - /// Value has been pushed and popped. If we want to use it again, we will - /// have to go back and insert an instruction where it was pushed + /// Value has been pushed and popped, so it's not on the VM stack any more. + /// If we want to use it again later, we will have to create a local for it, + /// by going back to insert a local.tee instruction at pushed_at Popped { pushed_at: usize }, } @@ -28,7 +29,7 @@ pub struct CodeBuilder { code: Vec, /// Extra instructions to insert at specific positions during finalisation - /// (Mainly to go back and set locals when we realise we need them) + /// (Go back and set locals when we realise we need them) insertions: BTreeMap, /// Our simulation model of the Wasm stack machine @@ -51,6 +52,7 @@ impl CodeBuilder { self.vm_stack.clear(); } + /// Add an instruction pub fn add_one(&mut self, inst: Instruction) { let (pops, push) = get_pops_and_pushes(&inst); let new_len = self.vm_stack.len() - pops as usize; @@ -64,6 +66,7 @@ impl CodeBuilder { self.code.push(inst); } + /// Add many instructions pub fn add_many(&mut self, instructions: &[Instruction]) { let old_len = self.vm_stack.len(); let mut len = old_len; @@ -86,6 +89,8 @@ impl CodeBuilder { self.code.extend_from_slice(instructions); } + /// Special-case method to add a Call instruction + /// Specify the number of arguments the function pops from the VM stack, and whether it pushes a return value pub fn add_call(&mut self, function_index: u32, pops: usize, push: bool) { let stack_depth = self.vm_stack.len(); if pops > stack_depth { @@ -107,6 +112,7 @@ impl CodeBuilder { self.code.push(inst); } + /// Finalize a function body by copying all instructions into a vector pub fn finalize_into(&mut self, final_code: &mut Vec) { let mut insertions_iter = self.insertions.iter(); let mut next_insertion = insertions_iter.next(); @@ -130,6 +136,8 @@ impl CodeBuilder { self.code.len() + self.insertions.len() } + /// Set the Symbol that is at the top of the VM stack right now + /// We will use this later when we need to load the Symbol pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState { let len = self.vm_stack.len(); let pushed_at = self.code.len(); @@ -166,6 +174,13 @@ impl CodeBuilder { true } + /// Load a Symbol that is stored in the VM stack + /// If it's already at the top of the stack, no code will be generated. + /// Otherwise, local.set and local.get instructions will be inserted, using the LocalId provided. + /// + /// If the return value is `Some(s)`, `s` should be stored by the caller, and provided in the next call. + /// If the return value is `None`, the Symbol is no longer stored in the VM stack, but in a local. + /// (In this case, the caller must remember to declare the local in the function header.) pub fn load_symbol( &mut self, symbol: Symbol, @@ -182,7 +197,8 @@ impl CodeBuilder { match top { Some(top_symbol) if top_symbol == symbol => { // We're lucky, the symbol is already on top of the VM stack - // No code to generate, just let the caller know what happened + // No code to generate! (This reduces code size by up to 25% in tests.) + // Just let the caller know what happened Some(Popped { pushed_at }) } _ => { From f6685349b383e3f3f5813335d7340be42742b097 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 10 Oct 2021 21:53:50 +0100 Subject: [PATCH 18/51] Batch local declarations to save a few bytes and make some code a little nicer --- compiler/gen_wasm/src/backend.rs | 39 ++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 9f44396bf4..a83066526c 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -43,7 +43,7 @@ pub struct WasmBackend<'a> { // Functions: Wasm AST code_builder: CodeBuilder, arg_types: std::vec::Vec, - locals: std::vec::Vec, + local_types: std::vec::Vec, // Functions: internal state & IR mappings stack_memory: i32, @@ -68,7 +68,7 @@ impl<'a> WasmBackend<'a> { // Functions: Wasm AST code_builder: CodeBuilder::new(), arg_types: std::vec::Vec::with_capacity(8), - locals: std::vec::Vec::with_capacity(32), + local_types: std::vec::Vec::with_capacity(32), // Functions: internal state & IR mappings stack_memory: 0, @@ -83,7 +83,7 @@ impl<'a> WasmBackend<'a> { // Functions: Wasm AST self.code_builder.clear(); self.arg_types.clear(); - self.locals.clear(); + self.local_types.clear(); // Functions: internal state & IR mappings self.stack_memory = 0; @@ -160,10 +160,28 @@ impl<'a> WasmBackend<'a> { } final_instructions.push(End); + // Declare local variables (in batches of the same type) + let num_locals = self.local_types.len(); + let mut locals = Vec::with_capacity(num_locals); + if num_locals > 0 { + let mut batch_type = self.local_types[0]; + let mut batch_size = 0; + for t in &self.local_types { + if *t == batch_type { + batch_size += 1; + } else { + locals.push(Local::new(batch_size, batch_type)); + batch_type = *t; + batch_size = 1; + } + } + locals.push(Local::new(batch_size, batch_type)); + } + builder::function() .with_signature(signature_builder.build_sig()) .body() - .with_locals(self.locals.clone()) + .with_locals(locals) .with_instructions(Instructions::new(final_instructions)) .build() // body .build() // function @@ -176,7 +194,7 @@ impl<'a> WasmBackend<'a> { ***********************************************************/ fn get_next_local_id(&self) -> LocalId { - LocalId((self.arg_types.len() + self.locals.len()) as u32) + LocalId((self.arg_types.len() + self.local_types.len()) as u32) } fn create_storage( @@ -207,7 +225,7 @@ impl<'a> WasmBackend<'a> { WasmLayout::HeapMemory => { match kind { LocalKind::Parameter => self.arg_types.push(PTR_TYPE), - LocalKind::Variable => self.locals.push(Local::new(1, PTR_TYPE)), + LocalKind::Variable => self.local_types.push(PTR_TYPE), } SymbolStorage::Local { local_id: next_local_id, @@ -229,7 +247,7 @@ impl<'a> WasmBackend<'a> { LocalKind::Variable => { if self.stack_frame_pointer.is_none() { self.stack_frame_pointer = Some(next_local_id); - self.locals.push(Local::new(1, PTR_TYPE)); + self.local_types.push(PTR_TYPE); } let offset = @@ -298,7 +316,7 @@ impl<'a> WasmBackend<'a> { None => { // Loading the value required creating a new local, because // it was not in a convenient position in the VM stack. - self.locals.push(Local::new(1, value_type)); + self.local_types.push(value_type); self.symbol_storage_map.insert( *sym, SymbolStorage::Local { @@ -489,7 +507,7 @@ impl<'a> WasmBackend<'a> { self.code_builder.add_one(SetLocal(local_id.0)); } - self.locals.push(Local::new(1, value_type)); + self.local_types.push(value_type); let new_storage = SymbolStorage::Local { local_id, value_type, @@ -757,7 +775,8 @@ impl<'a> WasmBackend<'a> { let wasm_layout = WasmLayout::new(layout); let push = wasm_layout.stack_memory() == 0; let pops = arguments.len(); - self.code_builder.add_call(function_location.body, pops, push); + self.code_builder + .add_call(function_location.body, pops, push); Ok(()) } From 85ca33ddd6b51716672d4008c35624ae7e6b3293 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 11 Oct 2021 08:20:33 +0100 Subject: [PATCH 19/51] tweak test script --- compiler/gen_wasm/test-compare.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/gen_wasm/test-compare.sh b/compiler/gen_wasm/test-compare.sh index 37286cd3e3..31529968b9 100755 --- a/compiler/gen_wasm/test-compare.sh +++ b/compiler/gen_wasm/test-compare.sh @@ -24,6 +24,7 @@ do 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 $SIZE1 $SIZE2 $CHANGE $PERCENT_CHANGE + printf "%s\t%d\t%d\t%d\t%d%%\n" $f $NET_SIZE1 $NET_SIZE2 $CHANGE $PERCENT_CHANGE done From d2e01bd10a40e28f8d352356cd5b490f917ff8ac Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 12 Oct 2021 09:22:07 +0100 Subject: [PATCH 20/51] rename SymbolStorage -> StoredValue --- compiler/gen_wasm/src/backend.rs | 69 ++++++++++++++++---------------- compiler/gen_wasm/src/storage.rs | 2 +- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index a83066526c..b2d171946b 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -12,7 +12,7 @@ use roc_mono::layout::{Builtin, Layout}; use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState}; use crate::layout::WasmLayout; -use crate::storage::{StackMemoryLocation, SymbolStorage}; +use crate::storage::{StackMemoryLocation, StoredValue}; use crate::{ copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8, PTR_SIZE, PTR_TYPE, @@ -48,10 +48,10 @@ pub struct WasmBackend<'a> { // Functions: internal state & IR mappings stack_memory: i32, stack_frame_pointer: Option, - symbol_storage_map: MutMap, + symbol_storage_map: MutMap, /// how many blocks deep are we (used for jumps) block_depth: u32, - joinpoint_label_map: MutMap)>, + joinpoint_label_map: MutMap)>, } impl<'a> WasmBackend<'a> { @@ -65,17 +65,16 @@ impl<'a> WasmBackend<'a> { _data_offset_next: UNUSED_DATA_SECTION_BYTES, proc_symbol_map: MutMap::default(), + block_depth: 0, + joinpoint_label_map: MutMap::default(), + stack_memory: 0, + // Functions: Wasm AST code_builder: CodeBuilder::new(), arg_types: std::vec::Vec::with_capacity(8), local_types: std::vec::Vec::with_capacity(32), - - // Functions: internal state & IR mappings - stack_memory: 0, stack_frame_pointer: None, symbol_storage_map: MutMap::default(), - block_depth: 0, - joinpoint_label_map: MutMap::default(), } } @@ -202,20 +201,20 @@ impl<'a> WasmBackend<'a> { wasm_layout: &WasmLayout, symbol: Symbol, kind: LocalKind, - ) -> SymbolStorage { + ) -> StoredValue { let next_local_id = self.get_next_local_id(); let storage = match wasm_layout { WasmLayout::Primitive(value_type, size) => match kind { LocalKind::Parameter => { self.arg_types.push(*value_type); - SymbolStorage::Local { + StoredValue::Local { local_id: next_local_id, value_type: *value_type, size: *size, } } - LocalKind::Variable => SymbolStorage::VirtualMachineStack { + LocalKind::Variable => StoredValue::VirtualMachineStack { vm_state: VirtualMachineSymbolState::NotYetPushed, value_type: *value_type, size: *size, @@ -227,7 +226,7 @@ impl<'a> WasmBackend<'a> { LocalKind::Parameter => self.arg_types.push(PTR_TYPE), LocalKind::Variable => self.local_types.push(PTR_TYPE), } - SymbolStorage::Local { + StoredValue::Local { local_id: next_local_id, value_type: PTR_TYPE, size: PTR_SIZE, @@ -259,7 +258,7 @@ impl<'a> WasmBackend<'a> { } }; - SymbolStorage::StackMemory { + StoredValue::StackMemory { location, size: *size, alignment_bytes: *alignment_bytes, @@ -272,7 +271,7 @@ impl<'a> WasmBackend<'a> { storage } - fn get_symbol_storage(&self, sym: &Symbol) -> &SymbolStorage { + fn get_symbol_storage(&self, sym: &Symbol) -> &StoredValue { self.symbol_storage_map.get(sym).unwrap_or_else(|| { panic!( "Symbol {:?} not found in function scope:\n{:?}", @@ -293,7 +292,7 @@ impl<'a> WasmBackend<'a> { for sym in symbols.iter() { let storage = self.get_symbol_storage(sym).to_owned(); match storage { - SymbolStorage::VirtualMachineStack { + StoredValue::VirtualMachineStack { vm_state, value_type, size, @@ -306,7 +305,7 @@ impl<'a> WasmBackend<'a> { Some(next_vm_state) => { self.symbol_storage_map.insert( *sym, - SymbolStorage::VirtualMachineStack { + StoredValue::VirtualMachineStack { vm_state: next_vm_state, value_type, size, @@ -319,7 +318,7 @@ impl<'a> WasmBackend<'a> { self.local_types.push(value_type); self.symbol_storage_map.insert( *sym, - SymbolStorage::Local { + StoredValue::Local { local_id: next_local_id, value_type, size, @@ -328,8 +327,8 @@ impl<'a> WasmBackend<'a> { } } } - SymbolStorage::Local { local_id, .. } - | SymbolStorage::StackMemory { + StoredValue::Local { local_id, .. } + | StoredValue::StackMemory { location: StackMemoryLocation::PointerArg(local_id), .. } => { @@ -337,7 +336,7 @@ impl<'a> WasmBackend<'a> { self.code_builder.set_top_symbol(*sym); } - SymbolStorage::StackMemory { + StoredValue::StackMemory { location: StackMemoryLocation::FrameOffset(offset), .. } => { @@ -360,7 +359,7 @@ impl<'a> WasmBackend<'a> { ) -> u32 { let from_storage = self.get_symbol_storage(&from_symbol).to_owned(); match from_storage { - SymbolStorage::StackMemory { + StoredValue::StackMemory { location, size, alignment_bytes, @@ -380,10 +379,10 @@ impl<'a> WasmBackend<'a> { size } - SymbolStorage::VirtualMachineStack { + StoredValue::VirtualMachineStack { value_type, size, .. } - | SymbolStorage::Local { + | StoredValue::Local { value_type, size, .. } => { let store_instruction = match (value_type, size) { @@ -408,11 +407,11 @@ impl<'a> WasmBackend<'a> { /// generate code to copy a value from one SymbolStorage to another pub fn copy_value_by_storage( &mut self, - to: &SymbolStorage, - from: &SymbolStorage, + to: &StoredValue, + from: &StoredValue, from_symbol: Symbol, ) { - use SymbolStorage::*; + use StoredValue::*; match (to, from) { ( @@ -493,9 +492,9 @@ impl<'a> WasmBackend<'a> { fn ensure_symbol_storage_has_local( &mut self, symbol: Symbol, - storage: SymbolStorage, - ) -> SymbolStorage { - if let SymbolStorage::VirtualMachineStack { + storage: StoredValue, + ) -> StoredValue { + if let StoredValue::VirtualMachineStack { vm_state, value_type, size, @@ -508,7 +507,7 @@ impl<'a> WasmBackend<'a> { } self.local_types.push(value_type); - let new_storage = SymbolStorage::Local { + let new_storage = StoredValue::Local { local_id, value_type, size, @@ -557,7 +556,7 @@ impl<'a> WasmBackend<'a> { { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later - let storage = SymbolStorage::StackMemory { + let storage = StoredValue::StackMemory { location: StackMemoryLocation::PointerArg(LocalId(0)), size, alignment_bytes, @@ -571,7 +570,7 @@ impl<'a> WasmBackend<'a> { let vm_state = self.code_builder.set_top_symbol(*let_sym); self.symbol_storage_map.insert( *let_sym, - SymbolStorage::VirtualMachineStack { + StoredValue::VirtualMachineStack { vm_state, value_type, size, @@ -593,7 +592,7 @@ impl<'a> WasmBackend<'a> { let vm_state = self.code_builder.set_top_symbol(*sym); self.symbol_storage_map.insert( *sym, - SymbolStorage::VirtualMachineStack { + StoredValue::VirtualMachineStack { vm_state, value_type, size, @@ -606,7 +605,7 @@ impl<'a> WasmBackend<'a> { } Stmt::Ret(sym) => { - use crate::storage::SymbolStorage::*; + use crate::storage::StoredValue::*; let storage = self.symbol_storage_map.get(sym).unwrap(); @@ -836,7 +835,7 @@ impl<'a> WasmBackend<'a> { if let Layout::Struct(field_layouts) = layout { match storage { - SymbolStorage::StackMemory { location, size, .. } => { + StoredValue::StackMemory { location, size, .. } => { if size > 0 { let (local_id, struct_offset) = location.local_and_offset(self.stack_frame_pointer); diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 44753c22c7..459b0749e7 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -18,7 +18,7 @@ impl StackMemoryLocation { } #[derive(Debug, Clone)] -pub enum SymbolStorage { +pub enum StoredValue { /// Value is stored implicitly in the VM stack VirtualMachineStack { vm_state: VirtualMachineSymbolState, From 472943a0683bced79830670521d04b962f965492 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 12 Oct 2021 10:17:55 +0100 Subject: [PATCH 21/51] Refactoring: extract Storage out of WasmBackend --- compiler/gen_wasm/src/backend.rs | 474 +++++-------------------------- compiler/gen_wasm/src/storage.rs | 376 +++++++++++++++++++++++- 2 files changed, 444 insertions(+), 406 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index b2d171946b..2da86ebc46 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -10,13 +10,10 @@ use roc_module::symbol::Symbol; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; -use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState}; +use crate::code_builder::CodeBuilder; use crate::layout::WasmLayout; -use crate::storage::{StackMemoryLocation, StoredValue}; -use crate::{ - copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, CopyMemoryConfig, - LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8, PTR_SIZE, PTR_TYPE, -}; +use crate::storage::{LocalKind, StackMemoryLocation, Storage, StoredValue}; +use crate::{copy_memory, pop_stack_frame, push_stack_frame, CopyMemoryConfig, LocalId, PTR_TYPE}; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) @@ -25,30 +22,20 @@ const UNUSED_DATA_SECTION_BYTES: u32 = 1024; #[derive(Clone, Copy, Debug)] struct LabelId(u32); -enum LocalKind { - Parameter, - Variable, -} - // TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43) pub struct WasmBackend<'a> { - // Module: Wasm AST + // Module level: Wasm AST pub module_builder: ModuleBuilder, - // Module: internal state & IR mappings + // Module level: internal state & IR mappings _data_offset_map: MutMap, u32>, _data_offset_next: u32, proc_symbol_map: MutMap, - // Functions: Wasm AST + // Function level code_builder: CodeBuilder, - arg_types: std::vec::Vec, - local_types: std::vec::Vec, + storage: Storage, - // Functions: internal state & IR mappings - stack_memory: i32, - stack_frame_pointer: Option, - symbol_storage_map: MutMap, /// how many blocks deep are we (used for jumps) block_depth: u32, joinpoint_label_map: MutMap)>, @@ -67,27 +54,16 @@ impl<'a> WasmBackend<'a> { block_depth: 0, joinpoint_label_map: MutMap::default(), - stack_memory: 0, - // Functions: Wasm AST + // Functions code_builder: CodeBuilder::new(), - arg_types: std::vec::Vec::with_capacity(8), - local_types: std::vec::Vec::with_capacity(32), - stack_frame_pointer: None, - symbol_storage_map: MutMap::default(), + storage: Storage::new(), } } fn reset(&mut self) { - // Functions: Wasm AST self.code_builder.clear(); - self.arg_types.clear(); - self.local_types.clear(); - - // Functions: internal state & IR mappings - self.stack_memory = 0; - self.stack_frame_pointer = None; - self.symbol_storage_map.clear(); + self.storage.clear(); self.joinpoint_label_map.clear(); assert_eq!(self.block_depth, 0); } @@ -119,7 +95,7 @@ impl<'a> WasmBackend<'a> { let ret_layout = WasmLayout::new(&proc.ret_layout); let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout { - self.arg_types.push(PTR_TYPE); + self.storage.arg_types.push(PTR_TYPE); self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any) builder::signature() } else { @@ -129,10 +105,11 @@ impl<'a> WasmBackend<'a> { }; for (layout, symbol) in proc.args { - self.create_storage(&WasmLayout::new(layout), *symbol, LocalKind::Parameter); + self.storage + .allocate(&WasmLayout::new(layout), *symbol, LocalKind::Parameter); } - signature_builder.with_params(self.arg_types.clone()) + signature_builder.with_params(self.storage.arg_types.clone()) } fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition { @@ -140,32 +117,32 @@ impl<'a> WasmBackend<'a> { let mut final_instructions = Vec::with_capacity(self.code_builder.len() + 10); - if self.stack_memory > 0 { + if self.storage.stack_memory > 0 { push_stack_frame( &mut final_instructions, - self.stack_memory, - self.stack_frame_pointer.unwrap(), + self.storage.stack_memory, + self.storage.stack_frame_pointer.unwrap(), ); } self.code_builder.finalize_into(&mut final_instructions); - if self.stack_memory > 0 { + if self.storage.stack_memory > 0 { pop_stack_frame( &mut final_instructions, - self.stack_memory, - self.stack_frame_pointer.unwrap(), + self.storage.stack_memory, + self.storage.stack_frame_pointer.unwrap(), ); } final_instructions.push(End); // Declare local variables (in batches of the same type) - let num_locals = self.local_types.len(); + let num_locals = self.storage.local_types.len(); let mut locals = Vec::with_capacity(num_locals); if num_locals > 0 { - let mut batch_type = self.local_types[0]; + let mut batch_type = self.storage.local_types[0]; let mut batch_size = 0; - for t in &self.local_types { + for t in &self.storage.local_types { if *t == batch_type { batch_size += 1; } else { @@ -186,340 +163,6 @@ impl<'a> WasmBackend<'a> { .build() // function } - /********************************************************** - - SYMBOL STORAGE, LOCALS, AND COPYING - - ***********************************************************/ - - fn get_next_local_id(&self) -> LocalId { - LocalId((self.arg_types.len() + self.local_types.len()) as u32) - } - - fn create_storage( - &mut self, - wasm_layout: &WasmLayout, - symbol: Symbol, - kind: LocalKind, - ) -> StoredValue { - let next_local_id = self.get_next_local_id(); - - let storage = match wasm_layout { - WasmLayout::Primitive(value_type, size) => match kind { - LocalKind::Parameter => { - self.arg_types.push(*value_type); - StoredValue::Local { - local_id: next_local_id, - value_type: *value_type, - size: *size, - } - } - LocalKind::Variable => StoredValue::VirtualMachineStack { - vm_state: VirtualMachineSymbolState::NotYetPushed, - value_type: *value_type, - size: *size, - }, - }, - - WasmLayout::HeapMemory => { - match kind { - LocalKind::Parameter => self.arg_types.push(PTR_TYPE), - LocalKind::Variable => 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, - } => { - let location = match kind { - LocalKind::Parameter => { - self.arg_types.push(PTR_TYPE); - StackMemoryLocation::PointerArg(next_local_id) - } - - LocalKind::Variable => { - if self.stack_frame_pointer.is_none() { - self.stack_frame_pointer = Some(next_local_id); - self.local_types.push(PTR_TYPE); - } - - let offset = - round_up_to_alignment(self.stack_memory, *alignment_bytes as i32); - - self.stack_memory = offset + (*size as i32); - - StackMemoryLocation::FrameOffset(offset as u32) - } - }; - - StoredValue::StackMemory { - location, - size: *size, - alignment_bytes: *alignment_bytes, - } - } - }; - - self.symbol_storage_map.insert(symbol, storage.clone()); - - storage - } - - fn get_symbol_storage(&self, sym: &Symbol) -> &StoredValue { - self.symbol_storage_map.get(sym).unwrap_or_else(|| { - panic!( - "Symbol {:?} not found in function scope:\n{:?}", - sym, self.symbol_storage_map - ) - }) - } - - /// Load symbols to the top of the VM stack - /// (There is no method for one symbol. This is deliberate, since - /// if anyone ever called it in a loop, it would generate inefficient code) - fn load_symbols(&mut self, symbols: &[Symbol]) { - if self.code_builder.verify_stack_match(symbols) { - // The symbols were already at the top of the stack, do nothing! - // This should be quite common due to the structure of the Mono IR - return; - } - for sym in symbols.iter() { - let storage = self.get_symbol_storage(sym).to_owned(); - match storage { - StoredValue::VirtualMachineStack { - vm_state, - value_type, - size, - } => { - let next_local_id = self.get_next_local_id(); - let maybe_next_vm_state = - self.code_builder.load_symbol(*sym, vm_state, next_local_id); - match maybe_next_vm_state { - // The act of loading the value changed the VM state, so update it - Some(next_vm_state) => { - self.symbol_storage_map.insert( - *sym, - StoredValue::VirtualMachineStack { - vm_state: next_vm_state, - value_type, - size, - }, - ); - } - None => { - // Loading the value required creating a new local, because - // it was not in a convenient position in the VM stack. - self.local_types.push(value_type); - self.symbol_storage_map.insert( - *sym, - StoredValue::Local { - local_id: next_local_id, - value_type, - size, - }, - ); - } - } - } - StoredValue::Local { local_id, .. } - | StoredValue::StackMemory { - location: StackMemoryLocation::PointerArg(local_id), - .. - } => { - self.code_builder.add_one(GetLocal(local_id.0)); - self.code_builder.set_top_symbol(*sym); - } - - StoredValue::StackMemory { - location: StackMemoryLocation::FrameOffset(offset), - .. - } => { - self.code_builder.add_many(&[ - GetLocal(self.stack_frame_pointer.unwrap().0), - I32Const(offset as i32), - I32Add, - ]); - self.code_builder.set_top_symbol(*sym); - } - } - } - } - - fn copy_symbol_to_memory( - &mut self, - to_ptr: LocalId, - to_offset: u32, - from_symbol: Symbol, - ) -> u32 { - let from_storage = self.get_symbol_storage(&from_symbol).to_owned(); - match from_storage { - StoredValue::StackMemory { - location, - size, - alignment_bytes, - } => { - let (from_ptr, from_offset) = location.local_and_offset(self.stack_frame_pointer); - copy_memory( - &mut self.code_builder, - CopyMemoryConfig { - from_ptr, - from_offset, - to_ptr, - to_offset, - size, - alignment_bytes, - }, - ); - size - } - - StoredValue::VirtualMachineStack { - value_type, size, .. - } - | StoredValue::Local { - value_type, size, .. - } => { - let store_instruction = match (value_type, size) { - (ValueType::I64, 8) => I64Store(ALIGN_8, to_offset), - (ValueType::I32, 4) => I32Store(ALIGN_4, to_offset), - (ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset), - (ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset), - (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), - (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), - _ => { - panic!("Cannot store {:?} with alignment of {:?}", value_type, size); - } - }; - self.code_builder.add_one(GetLocal(to_ptr.0)); - self.load_symbols(&[from_symbol]); - self.code_builder.add_one(store_instruction); - size - } - } - } - - /// generate code to copy a value from one SymbolStorage to another - pub fn copy_value_by_storage( - &mut self, - to: &StoredValue, - from: &StoredValue, - from_symbol: Symbol, - ) { - use StoredValue::*; - - match (to, from) { - ( - Local { - local_id: to_local_id, - value_type: to_value_type, - size: to_size, - }, - VirtualMachineStack { - value_type: from_value_type, - size: from_size, - .. - }, - ) => { - debug_assert!(to_value_type == from_value_type); - debug_assert!(to_size == from_size); - self.load_symbols(&[from_symbol]); - self.code_builder.add_one(SetLocal(to_local_id.0)); - self.symbol_storage_map.insert(from_symbol, to.clone()); - } - - ( - Local { - local_id: to_local_id, - value_type: to_value_type, - size: to_size, - }, - Local { - local_id: from_local_id, - value_type: from_value_type, - size: from_size, - }, - ) => { - debug_assert!(to_value_type == from_value_type); - debug_assert!(to_size == from_size); - self.code_builder - .add_many(&[GetLocal(from_local_id.0), SetLocal(to_local_id.0)]); - } - - ( - StackMemory { - location: to_location, - size: to_size, - alignment_bytes: to_alignment_bytes, - }, - StackMemory { - location: from_location, - size: from_size, - alignment_bytes: from_alignment_bytes, - }, - ) => { - let (from_ptr, from_offset) = - from_location.local_and_offset(self.stack_frame_pointer); - let (to_ptr, to_offset) = to_location.local_and_offset(self.stack_frame_pointer); - debug_assert!(*to_size == *from_size); - debug_assert!(*to_alignment_bytes == *from_alignment_bytes); - copy_memory( - &mut self.code_builder, - CopyMemoryConfig { - from_ptr, - from_offset, - to_ptr, - to_offset, - size: *from_size, - alignment_bytes: *from_alignment_bytes, - }, - ); - } - - _ => { - panic!("Cannot copy storage from {:?} to {:?}", from, to); - } - } - } - - /// Ensure SymbolStorage has an associated local. - /// (Blocks can't access the VM stack of their parent scope, but they can access locals.) - fn ensure_symbol_storage_has_local( - &mut self, - symbol: Symbol, - storage: StoredValue, - ) -> StoredValue { - if let StoredValue::VirtualMachineStack { - vm_state, - value_type, - size, - } = storage - { - let local_id = self.get_next_local_id(); - if vm_state != VirtualMachineSymbolState::NotYetPushed { - self.code_builder.load_symbol(symbol, vm_state, local_id); - self.code_builder.add_one(SetLocal(local_id.0)); - } - - self.local_types.push(value_type); - let new_storage = StoredValue::Local { - local_id, - value_type, - size, - }; - - self.symbol_storage_map.insert(symbol, new_storage.clone()); - return new_storage; - } else { - storage - } - } - /********************************************************** STATEMENTS @@ -561,14 +204,14 @@ impl<'a> WasmBackend<'a> { size, alignment_bytes, }; - self.symbol_storage_map.insert(*let_sym, storage); + self.storage.symbol_storage_map.insert(*let_sym, storage); } self.build_expr(let_sym, expr, layout)?; if let WasmLayout::Primitive(value_type, size) = wasm_layout { let vm_state = self.code_builder.set_top_symbol(*let_sym); - self.symbol_storage_map.insert( + self.storage.symbol_storage_map.insert( *let_sym, StoredValue::VirtualMachineStack { vm_state, @@ -585,12 +228,13 @@ impl<'a> WasmBackend<'a> { Stmt::Let(sym, expr, layout, following) => { let wasm_layout = WasmLayout::new(layout); - self.create_storage(&wasm_layout, *sym, LocalKind::Variable); + self.storage + .allocate(&wasm_layout, *sym, LocalKind::Variable); self.build_expr(sym, expr, layout)?; if let WasmLayout::Primitive(value_type, size) = wasm_layout { let vm_state = self.code_builder.set_top_symbol(*sym); - self.symbol_storage_map.insert( + self.storage.symbol_storage_map.insert( *sym, StoredValue::VirtualMachineStack { vm_state, @@ -607,7 +251,7 @@ impl<'a> WasmBackend<'a> { Stmt::Ret(sym) => { use crate::storage::StoredValue::*; - let storage = self.symbol_storage_map.get(sym).unwrap(); + let storage = self.storage.symbol_storage_map.get(sym).unwrap(); match storage { StackMemory { @@ -616,7 +260,7 @@ impl<'a> WasmBackend<'a> { alignment_bytes, } => { let (from_ptr, from_offset) = - location.local_and_offset(self.stack_frame_pointer); + location.local_and_offset(self.storage.stack_frame_pointer); copy_memory( &mut self.code_builder, CopyMemoryConfig { @@ -631,7 +275,7 @@ impl<'a> WasmBackend<'a> { } _ => { - self.load_symbols(&[*sym]); + self.storage.load_symbols(&mut self.code_builder, &[*sym]); self.code_builder.add_one(Br(self.block_depth)); // jump to end of function (for stack frame pop) } } @@ -652,8 +296,12 @@ impl<'a> WasmBackend<'a> { // Ensure the condition value is not stored only in the VM stack // Otherwise we can't reach it from inside the block - let cond_storage = self.get_symbol_storage(cond_symbol).to_owned(); - self.ensure_symbol_storage_has_local(*cond_symbol, cond_storage); + let cond_storage = self.storage.get(cond_symbol).to_owned(); + self.storage.ensure_symbol_storage_has_local( + &mut self.code_builder, + *cond_symbol, + cond_storage, + ); // create (number_of_branches - 1) new blocks. for _ in 0..branches.len() { @@ -663,7 +311,8 @@ impl<'a> WasmBackend<'a> { // then, we jump whenever the value under scrutiny is equal to the value of a branch for (i, (value, _, _)) in branches.iter().enumerate() { // put the cond_symbol on the top of the stack - self.load_symbols(&[*cond_symbol]); + self.storage + .load_symbols(&mut self.code_builder, &[*cond_symbol]); self.code_builder.add_one(I32Const(*value as i32)); @@ -699,9 +348,13 @@ impl<'a> WasmBackend<'a> { for parameter in parameters.iter() { let wasm_layout = WasmLayout::new(¶meter.layout); let mut param_storage = - self.create_storage(&wasm_layout, parameter.symbol, LocalKind::Variable); - param_storage = - self.ensure_symbol_storage_has_local(parameter.symbol, param_storage); + self.storage + .allocate(&wasm_layout, parameter.symbol, LocalKind::Variable); + param_storage = self.storage.ensure_symbol_storage_has_local( + &mut self.code_builder, + parameter.symbol, + param_storage, + ); jp_param_storages.push(param_storage); } @@ -730,8 +383,13 @@ impl<'a> WasmBackend<'a> { let (target, param_storages) = self.joinpoint_label_map[id].clone(); for (arg_symbol, param_storage) in arguments.iter().zip(param_storages.iter()) { - let arg_storage = self.get_symbol_storage(arg_symbol).clone(); - self.copy_value_by_storage(¶m_storage, &arg_storage, *arg_symbol); + let arg_storage = self.storage.get(arg_symbol).clone(); + self.storage.copy_value_by_storage( + &mut self.code_builder, + ¶m_storage, + &arg_storage, + *arg_symbol, + ); } // jump @@ -764,7 +422,8 @@ impl<'a> WasmBackend<'a> { arguments, }) => match call_type { CallType::ByName { name: func_sym, .. } => { - self.load_symbols(*arguments); + self.storage + .load_symbols(&mut self.code_builder, *arguments); let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!( "Cannot find function {:?} called from {:?}", func_sym, sym @@ -831,18 +490,22 @@ impl<'a> WasmBackend<'a> { ) -> Result<(), String> { // TODO: we just calculated storage and now we're getting it out of a map // Not passing it as an argument because I'm trying to match Backend method signatures - let storage = self.get_symbol_storage(sym).to_owned(); + let storage = self.storage.get(sym).to_owned(); if let Layout::Struct(field_layouts) = layout { match storage { StoredValue::StackMemory { location, size, .. } => { if size > 0 { let (local_id, struct_offset) = - location.local_and_offset(self.stack_frame_pointer); + location.local_and_offset(self.storage.stack_frame_pointer); let mut field_offset = struct_offset; for (field, _) in fields.iter().zip(field_layouts.iter()) { - field_offset += - self.copy_symbol_to_memory(local_id, field_offset, *field); + field_offset += self.storage.copy_symbol_to_memory( + &mut self.code_builder, + local_id, + field_offset, + *field, + ); } } else { return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); @@ -857,8 +520,13 @@ impl<'a> WasmBackend<'a> { }; } else { // Struct expression but not Struct layout => single element. Copy it. - let field_storage = self.get_symbol_storage(&fields[0]).to_owned(); - self.copy_value_by_storage(&storage, &field_storage, fields[0]); + let field_storage = self.storage.get(&fields[0]).to_owned(); + self.storage.copy_value_by_storage( + &mut self.code_builder, + &storage, + &field_storage, + fields[0], + ); } Ok(()) } @@ -869,7 +537,7 @@ impl<'a> WasmBackend<'a> { args: &'a [Symbol], return_layout: &Layout<'a>, ) -> Result<(), String> { - self.load_symbols(args); + self.storage.load_symbols(&mut self.code_builder, args); let wasm_layout = WasmLayout::new(return_layout); self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?; Ok(()) diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 459b0749e7..9e792eeac0 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,6 +1,19 @@ -use parity_wasm::elements::ValueType; +use parity_wasm::elements::{Instruction::*, ValueType}; -use crate::{LocalId, code_builder::VirtualMachineSymbolState}; +use roc_collections::all::MutMap; +use roc_module::symbol::Symbol; + +use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState}; +use crate::layout::WasmLayout; +use crate::{ + copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, + ALIGN_8, PTR_SIZE, PTR_TYPE, +}; + +pub enum LocalKind { + Parameter, + Variable, +} #[derive(Debug, Clone)] pub enum StackMemoryLocation { @@ -39,6 +52,363 @@ pub enum StoredValue { size: u32, alignment_bytes: u32, }, - // TODO: const data storage (fixed address) } + +pub struct Storage { + pub arg_types: std::vec::Vec, + pub local_types: std::vec::Vec, + pub symbol_storage_map: MutMap, + pub stack_memory: i32, + pub stack_frame_pointer: Option, +} + +impl Storage { + pub fn new() -> Self { + Storage { + stack_memory: 0, + arg_types: std::vec::Vec::with_capacity(8), + local_types: std::vec::Vec::with_capacity(32), + stack_frame_pointer: None, + symbol_storage_map: MutMap::default(), + } + } + + pub fn clear(&mut self) { + self.arg_types.clear(); + self.local_types.clear(); + self.stack_memory = 0; + self.stack_frame_pointer = None; + self.symbol_storage_map.clear(); + } + + fn get_next_local_id(&self) -> LocalId { + LocalId((self.arg_types.len() + self.local_types.len()) as u32) + } + + pub fn allocate( + &mut self, + wasm_layout: &WasmLayout, + symbol: Symbol, + kind: LocalKind, + ) -> StoredValue { + let next_local_id = self.get_next_local_id(); + + let storage = match wasm_layout { + WasmLayout::Primitive(value_type, size) => match kind { + LocalKind::Parameter => { + self.arg_types.push(*value_type); + StoredValue::Local { + local_id: next_local_id, + value_type: *value_type, + size: *size, + } + } + LocalKind::Variable => StoredValue::VirtualMachineStack { + vm_state: VirtualMachineSymbolState::NotYetPushed, + value_type: *value_type, + size: *size, + }, + }, + + WasmLayout::HeapMemory => { + match kind { + LocalKind::Parameter => self.arg_types.push(PTR_TYPE), + LocalKind::Variable => 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, + } => { + let location = match kind { + LocalKind::Parameter => { + self.arg_types.push(PTR_TYPE); + StackMemoryLocation::PointerArg(next_local_id) + } + + LocalKind::Variable => { + if self.stack_frame_pointer.is_none() { + self.stack_frame_pointer = Some(next_local_id); + self.local_types.push(PTR_TYPE); + } + + let offset = + round_up_to_alignment(self.stack_memory, *alignment_bytes as i32); + + self.stack_memory = offset + (*size as i32); + + StackMemoryLocation::FrameOffset(offset as u32) + } + }; + + StoredValue::StackMemory { + location, + size: *size, + alignment_bytes: *alignment_bytes, + } + } + }; + + self.symbol_storage_map.insert(symbol, storage.clone()); + + storage + } + + pub fn get(&self, sym: &Symbol) -> &StoredValue { + self.symbol_storage_map.get(sym).unwrap_or_else(|| { + panic!( + "Symbol {:?} not found in function scope:\n{:?}", + sym, self.symbol_storage_map + ) + }) + } + + /// Load symbols to the top of the VM stack + /// (There is no method for one symbol. This is deliberate, since + /// if anyone ever called it in a loop, it would generate inefficient code) + pub fn load_symbols(&mut self, code_builder: &mut CodeBuilder, symbols: &[Symbol]) { + if code_builder.verify_stack_match(symbols) { + // The symbols were already at the top of the stack, do nothing! + // This should be quite common due to the structure of the Mono IR + return; + } + for sym in symbols.iter() { + let storage = self.get(sym).to_owned(); + match storage { + StoredValue::VirtualMachineStack { + vm_state, + value_type, + size, + } => { + let next_local_id = self.get_next_local_id(); + let maybe_next_vm_state = + code_builder.load_symbol(*sym, vm_state, next_local_id); + match maybe_next_vm_state { + // The act of loading the value changed the VM state, so update it + Some(next_vm_state) => { + self.symbol_storage_map.insert( + *sym, + StoredValue::VirtualMachineStack { + vm_state: next_vm_state, + value_type, + size, + }, + ); + } + None => { + // Loading the value required creating a new local, because + // it was not in a convenient position in the VM stack. + self.local_types.push(value_type); + self.symbol_storage_map.insert( + *sym, + StoredValue::Local { + local_id: next_local_id, + value_type, + size, + }, + ); + } + } + } + StoredValue::Local { local_id, .. } + | StoredValue::StackMemory { + location: StackMemoryLocation::PointerArg(local_id), + .. + } => { + code_builder.add_one(GetLocal(local_id.0)); + code_builder.set_top_symbol(*sym); + } + + StoredValue::StackMemory { + location: StackMemoryLocation::FrameOffset(offset), + .. + } => { + code_builder.add_many(&[ + GetLocal(self.stack_frame_pointer.unwrap().0), + I32Const(offset as i32), + I32Add, + ]); + code_builder.set_top_symbol(*sym); + } + } + } + } + + pub fn copy_symbol_to_memory( + &mut self, + code_builder: &mut CodeBuilder, + to_ptr: LocalId, + to_offset: u32, + from_symbol: Symbol, + ) -> u32 { + let from_storage = self.get(&from_symbol).to_owned(); + match from_storage { + StoredValue::StackMemory { + location, + size, + alignment_bytes, + } => { + let (from_ptr, from_offset) = location.local_and_offset(self.stack_frame_pointer); + copy_memory( + code_builder, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr, + to_offset, + size, + alignment_bytes, + }, + ); + size + } + + StoredValue::VirtualMachineStack { + value_type, size, .. + } + | StoredValue::Local { + value_type, size, .. + } => { + let store_instruction = match (value_type, size) { + (ValueType::I64, 8) => I64Store(ALIGN_8, to_offset), + (ValueType::I32, 4) => I32Store(ALIGN_4, to_offset), + (ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset), + (ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset), + (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), + (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), + _ => { + panic!("Cannot store {:?} with alignment of {:?}", value_type, size); + } + }; + code_builder.add_one(GetLocal(to_ptr.0)); + self.load_symbols(code_builder, &[from_symbol]); + code_builder.add_one(store_instruction); + size + } + } + } + + /// generate code to copy a value from one SymbolStorage to another + pub fn copy_value_by_storage( + &mut self, + code_builder: &mut CodeBuilder, + to: &StoredValue, + from: &StoredValue, + from_symbol: Symbol, + ) { + use StoredValue::*; + + match (to, from) { + ( + Local { + local_id: to_local_id, + value_type: to_value_type, + size: to_size, + }, + VirtualMachineStack { + value_type: from_value_type, + size: from_size, + .. + }, + ) => { + debug_assert!(to_value_type == from_value_type); + debug_assert!(to_size == from_size); + self.load_symbols(code_builder, &[from_symbol]); + code_builder.add_one(SetLocal(to_local_id.0)); + self.symbol_storage_map.insert(from_symbol, to.clone()); + } + + ( + Local { + local_id: to_local_id, + value_type: to_value_type, + size: to_size, + }, + Local { + local_id: from_local_id, + value_type: from_value_type, + size: from_size, + }, + ) => { + debug_assert!(to_value_type == from_value_type); + debug_assert!(to_size == from_size); + code_builder.add_many(&[GetLocal(from_local_id.0), SetLocal(to_local_id.0)]); + } + + ( + StackMemory { + location: to_location, + size: to_size, + alignment_bytes: to_alignment_bytes, + }, + StackMemory { + location: from_location, + size: from_size, + alignment_bytes: from_alignment_bytes, + }, + ) => { + let (from_ptr, from_offset) = + from_location.local_and_offset(self.stack_frame_pointer); + let (to_ptr, to_offset) = to_location.local_and_offset(self.stack_frame_pointer); + debug_assert!(*to_size == *from_size); + debug_assert!(*to_alignment_bytes == *from_alignment_bytes); + copy_memory( + code_builder, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr, + to_offset, + size: *from_size, + alignment_bytes: *from_alignment_bytes, + }, + ); + } + + _ => { + panic!("Cannot copy storage from {:?} to {:?}", from, to); + } + } + } + + /// Ensure SymbolStorage has an associated local. + /// (Blocks can't access the VM stack of their parent scope, but they can access locals.) + pub fn ensure_symbol_storage_has_local( + &mut self, + code_builder: &mut CodeBuilder, + symbol: Symbol, + storage: StoredValue, + ) -> StoredValue { + if let StoredValue::VirtualMachineStack { + vm_state, + value_type, + size, + } = storage + { + let local_id = self.get_next_local_id(); + if vm_state != VirtualMachineSymbolState::NotYetPushed { + code_builder.load_symbol(symbol, vm_state, local_id); + code_builder.add_one(SetLocal(local_id.0)); + } + + self.local_types.push(value_type); + let new_storage = StoredValue::Local { + local_id, + value_type, + size, + }; + + self.symbol_storage_map.insert(symbol, new_storage.clone()); + return new_storage; + } else { + storage + } + } +} From 5c42455ca75f2e919368ffd382822da00828260a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 13 Oct 2021 10:52:51 +0200 Subject: [PATCH 22/51] Rename some values & improve comments --- compiler/gen_wasm/src/backend.rs | 24 ++++++-------- compiler/gen_wasm/src/storage.rs | 56 ++++++++++++++++++++++---------- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 2da86ebc46..e8ba82b27f 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -117,20 +117,20 @@ impl<'a> WasmBackend<'a> { let mut final_instructions = Vec::with_capacity(self.code_builder.len() + 10); - if self.storage.stack_memory > 0 { + if self.storage.stack_frame_size > 0 { push_stack_frame( &mut final_instructions, - self.storage.stack_memory, + self.storage.stack_frame_size, self.storage.stack_frame_pointer.unwrap(), ); } self.code_builder.finalize_into(&mut final_instructions); - if self.storage.stack_memory > 0 { + if self.storage.stack_frame_size > 0 { pop_stack_frame( &mut final_instructions, - self.storage.stack_memory, + self.storage.stack_frame_size, self.storage.stack_frame_pointer.unwrap(), ); } @@ -297,7 +297,7 @@ impl<'a> WasmBackend<'a> { // Ensure the condition value is not stored only in the VM stack // Otherwise we can't reach it from inside the block let cond_storage = self.storage.get(cond_symbol).to_owned(); - self.storage.ensure_symbol_storage_has_local( + self.storage.ensure_value_has_local( &mut self.code_builder, *cond_symbol, cond_storage, @@ -350,7 +350,7 @@ impl<'a> WasmBackend<'a> { let mut param_storage = self.storage .allocate(&wasm_layout, parameter.symbol, LocalKind::Variable); - param_storage = self.storage.ensure_symbol_storage_has_local( + param_storage = self.storage.ensure_value_has_local( &mut self.code_builder, parameter.symbol, param_storage, @@ -384,7 +384,7 @@ impl<'a> WasmBackend<'a> { for (arg_symbol, param_storage) in arguments.iter().zip(param_storages.iter()) { let arg_storage = self.storage.get(arg_symbol).clone(); - self.storage.copy_value_by_storage( + self.storage.clone_value( &mut self.code_builder, ¶m_storage, &arg_storage, @@ -500,7 +500,7 @@ impl<'a> WasmBackend<'a> { location.local_and_offset(self.storage.stack_frame_pointer); let mut field_offset = struct_offset; for (field, _) in fields.iter().zip(field_layouts.iter()) { - field_offset += self.storage.copy_symbol_to_memory( + field_offset += self.storage.copy_value_to_memory( &mut self.code_builder, local_id, field_offset, @@ -521,12 +521,8 @@ impl<'a> WasmBackend<'a> { } else { // Struct expression but not Struct layout => single element. Copy it. let field_storage = self.storage.get(&fields[0]).to_owned(); - self.storage.copy_value_by_storage( - &mut self.code_builder, - &storage, - &field_storage, - fields[0], - ); + self.storage + .clone_value(&mut self.code_builder, &storage, &field_storage, fields[0]); } Ok(()) } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 9e792eeac0..09d63cbae2 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -32,21 +32,21 @@ impl StackMemoryLocation { #[derive(Debug, Clone)] pub enum StoredValue { - /// Value is stored implicitly in the VM stack + /// A value stored implicitly in the VM stack (primitives only) VirtualMachineStack { vm_state: VirtualMachineSymbolState, value_type: ValueType, size: u32, }, - /// A local variable in the Wasm function + /// A local variable in the Wasm function (primitives only) Local { local_id: LocalId, value_type: ValueType, size: u32, }, - /// Value is stored in stack memory + /// A Struct, or other non-primitive value, stored in stack memory StackMemory { location: StackMemoryLocation, size: u32, @@ -55,37 +55,50 @@ pub enum StoredValue { // TODO: const data storage (fixed address) } +/// Helper structure for WasmBackend, to keep track of how values are stored, +/// including the VM stack, local variables, and linear memory pub struct Storage { pub arg_types: std::vec::Vec, pub local_types: std::vec::Vec, pub symbol_storage_map: MutMap, - pub stack_memory: i32, pub stack_frame_pointer: Option, + pub stack_frame_size: i32, } impl Storage { pub fn new() -> Self { Storage { - stack_memory: 0, arg_types: std::vec::Vec::with_capacity(8), local_types: std::vec::Vec::with_capacity(32), - stack_frame_pointer: None, symbol_storage_map: MutMap::default(), + stack_frame_pointer: None, + stack_frame_size: 0, } } pub fn clear(&mut self) { self.arg_types.clear(); self.local_types.clear(); - self.stack_memory = 0; - self.stack_frame_pointer = None; self.symbol_storage_map.clear(); + self.stack_frame_pointer = None; + self.stack_frame_size = 0; } + /// Internal use only. If you think you want it externally, you really want `allocate` fn get_next_local_id(&self) -> LocalId { LocalId((self.arg_types.len() + self.local_types.len()) as u32) } + /// Allocate storage for a Roc value + /// + /// Wasm primitives (i32, i64, f32, f64) are allocated "storage" on the VM stack. + /// This is really just a way to model how the stack machine works as a sort of + /// temporary storage. It doesn't result in any code generation. + /// For some values, this initial storage allocation may need to be upgraded later + /// to a Local. See `load_symbols`. + /// + /// Structs and Tags are stored in memory rather than in Wasm primitives. + /// They are allocated a certain offset and size in the stack frame. pub fn allocate( &mut self, wasm_layout: &WasmLayout, @@ -140,9 +153,9 @@ impl Storage { } let offset = - round_up_to_alignment(self.stack_memory, *alignment_bytes as i32); + round_up_to_alignment(self.stack_frame_size, *alignment_bytes as i32); - self.stack_memory = offset + (*size as i32); + self.stack_frame_size = offset + (*size as i32); StackMemoryLocation::FrameOffset(offset as u32) } @@ -161,6 +174,7 @@ impl Storage { storage } + /// Get storage info for a given symbol pub fn get(&self, sym: &Symbol) -> &StoredValue { self.symbol_storage_map.get(sym).unwrap_or_else(|| { panic!( @@ -171,8 +185,8 @@ impl Storage { } /// Load symbols to the top of the VM stack - /// (There is no method for one symbol. This is deliberate, since - /// if anyone ever called it in a loop, it would generate inefficient code) + /// Avoid calling this method in a loop with one symbol at a time! It will work, + /// but it generates very inefficient Wasm code. pub fn load_symbols(&mut self, code_builder: &mut CodeBuilder, symbols: &[Symbol]) { if code_builder.verify_stack_match(symbols) { // The symbols were already at the top of the stack, do nothing! @@ -241,7 +255,9 @@ impl Storage { } } - pub fn copy_symbol_to_memory( + /// Generate code to copy a StoredValue to an arbitrary memory location + /// (defined by a pointer and offset). + pub fn copy_value_to_memory( &mut self, code_builder: &mut CodeBuilder, to_ptr: LocalId, @@ -295,8 +311,9 @@ impl Storage { } } - /// generate code to copy a value from one SymbolStorage to another - pub fn copy_value_by_storage( + /// Generate code to copy from one StoredValue to another + /// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory` + pub fn clone_value( &mut self, code_builder: &mut CodeBuilder, to: &StoredValue, @@ -378,9 +395,12 @@ impl Storage { } } - /// Ensure SymbolStorage has an associated local. - /// (Blocks can't access the VM stack of their parent scope, but they can access locals.) - pub fn ensure_symbol_storage_has_local( + /// Ensure a StoredValue has an associated local + /// This is useful when a value needs to be accessed from a more deeply-nested block. + /// In that case we want to make sure it's not just stored in the VM stack, because + /// blocks can't access the VM stack from outer blocks, but they can access locals. + /// (In the case of structs in stack memory, we just use the stack frame pointer local) + pub fn ensure_value_has_local( &mut self, code_builder: &mut CodeBuilder, symbol: Symbol, From e6245c41f5ded3df88fe5591bac35b2f5fddc3d2 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 14 Oct 2021 00:04:17 +0200 Subject: [PATCH 23/51] README updates --- compiler/gen_wasm/README.md | 51 ++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/compiler/gen_wasm/README.md b/compiler/gen_wasm/README.md index 9681e350c3..46c7e12a15 100644 --- a/compiler/gen_wasm/README.md +++ b/compiler/gen_wasm/README.md @@ -14,14 +14,14 @@ - [x] Push and pop stack frames - [x] Deal with returning structs - [x] Distinguish which variables go in locals, own stack frame, caller stack frame, etc. - - [ ] Ensure early Return statements don't skip stack cleanup + - [x] Ensure early Return statements don't skip stack cleanup + - [x] Model the stack machine as a storage mechanism, to make generated code "less bad" - [ ] Vendor-in parity_wasm library so that we can use `bumpalo::Vec` - [ ] Implement relocations - Requires knowing the _byte_ offset of each call site. This is awkward as the backend builds a `Vec` rather than a `Vec`. It may be worth serialising each instruction as it is inserted. - Refactor for code sharing with CPU backends - - [ ] Implement a `scan_ast` pre-pass like `Backend` does, but for reusing Wasm locals rather than CPU registers - [ ] Extract a trait from `WasmBackend` that looks as similar as possible to `Backend`, to prepare for code sharing - [ ] Refactor to actually share code between `WasmBackend` and `Backend` if it seems feasible @@ -88,7 +88,14 @@ main = 1 + 2 + 4 ``` + +### Direct translation of Mono IR + The Mono IR contains two functions, `Num.add` and `main`, so we generate two corresponding WebAssembly functions. +Since it has a Symbol for every expression, the simplest thing is to create a local for each one. +The code ends up being quite bloated, with lots of `local.set` and `local.get` instructions. + +I've added comments on each line to show what is on the stack and in the locals at each point in the program. ``` (func (;0;) (param i64 i64) (result i64) ; declare function index 0 (Num.add) with two i64 parameters and an i64 result @@ -115,13 +122,10 @@ The Mono IR contains two functions, `Num.add` and `main`, so we generate two cor return) ; return the value at the top of the stack ``` -If we run this code through the `wasm-opt` tool from the [binaryen toolkit](https://github.com/WebAssembly/binaryen#tools), the unnecessary locals get optimised away (which is all of them in this example!). The command line below runs the minimum number of passes to achieve this (`--simplify-locals` must come first). +### Handwritten equivalent -``` -$ wasm-opt --simplify-locals --reorder-locals --vacuum example.wasm > opt.wasm -``` - -The optimised functions have no local variables at all for this example. (Of course, this is an oversimplified toy example! It might not be so extreme in a real program.) +This code doesn't actually require any locals at all. +(It also doesn't need the `return` instructions, but that's less of a problem.) ``` (func (;0;) (param i64 i64) (result i64) @@ -138,13 +142,36 @@ The optimised functions have no local variables at all for this example. (Of cou ### Reducing sets and gets -It would be nice to find some cheap optimisation to reduce the number of `local.set` and `local.get` instructions. +For our example code, we don't need any locals because the WebAssembly virtual machine effectively _stores_ intermediate results in a stack. Since it's already storing those values, there is no need for us to create locals. If you compare the two versions, you'll see that the `local.set` and `local.get` instructions have simply been deleted and the other instructions are in the same order. -We don't need a `local` if the value we want is already at the top of the VM stack. In fact, for our example above, it just so happens that if we simply skip generating the `local.set` instructions, everything _does_ appear on the VM stack in the right order, which means we can skip the `local.get` too. It ends up being very close to the fully optimised version! I assume this is because the Mono IR within the function is in dependency order, but I'm not sure... +But sometimes we really do need locals! We may need to use the same symbol twice, or values could end up on the stack in the wrong order and need to be swapped around by setting a local and getting it again. -Of course the trick is to do this reliably for more complex dependency graphs. I am investigating whether we can do it by optimistically assuming it's OK not to create a local, and then keeping track of which symbols are at which positions in the VM stack after every instruction. Then when we need to use a symbol we can first check if it's on the VM stack and only create a local if it's not. In cases where we _do_ need to create a local, we need to go back and insert a `local.set` instruction at an earlier point in the program. We can make this fast by waiting to do all of the insertions in one batch when we're finalising the procedure. +The hard part is knowing when we need a local, and when we don't. For that, the `WasmBackend` needs to have some understanding of the stack machine. -For a while we thought it would be very helpful to reuse the same local for multiple symbols at different points in the program. And we already have similar code in the CPU backends for register allocation. But on further examination, it doesn't actually buy us much! In our example above, we would still have the same number of `local.set` and `local.get` instructions - they'd just be operating on two locals instead of four! That doesn't shrink much code. Only the declaration at the top of the function would shrink from `(local i64 i64 i64 i64)` to `(local i64 i64)`... and in fact that's only smaller in the text format, it's the same size in the binary format! So the `scan_ast` pass doesn't seem worthwhile for Wasm. +To help with this, the `CodeBuilder` maintains a vector that represents the stack. For every instruction the backend generates, `CodeBuilder` simulates the right number of pushes and pops for that instruction, so that we always know the state of the VM stack at every point in the program. + +When the `WasmBackend` generates code for a `Let` statement, it can "label" the top of the stack with the relevant `Symbol`. Then at any later point in the program, when we need to retrieve a list of symbols in a certain order, we can check whether they already happen to be at the top of the stack in that order (as they were in our example above.) + +In practice it should be very common for values to appear on the VM stack in the right order, because in the Mono IR, statements occur in dependency order! We should only generate locals when the dependency graph is a little more complicated, and we actually need them. + +``` + ┌─────────────────┐ ┌─────────────┐ + │ │ │ │ + │ ├─────► Storage ├──────┐ + │ │ │ │ │ + │ │ └─────────────┘ │ + │ │ Manage state about │ + │ │ how/where symbol │ Delegate part of + │ WasmBackend │ values are stored │ state management + │ │ │ for values on + │ │ │ the VM stack + │ │ │ + │ │ Generate ┌────────▼──────┐ + │ │ instructions │ │ + │ ├─────────────────► CodeBuilder │ + │ │ │ │ + └─────────────────┘ └───────────────┘ +``` ## Memory From 6206418b67b0471e846dbb4924a97b9703d19865 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 14 Oct 2021 00:13:47 +0200 Subject: [PATCH 24/51] Fix clippy warnings --- compiler/gen_wasm/src/backend.rs | 2 +- compiler/gen_wasm/src/code_builder.rs | 3 ++- compiler/gen_wasm/src/storage.rs | 2 +- compiler/gen_wasm/tests/wasm_records.rs | 1 - 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index e8ba82b27f..ff661a3ff7 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -386,7 +386,7 @@ impl<'a> WasmBackend<'a> { let arg_storage = self.storage.get(arg_symbol).clone(); self.storage.clone_value( &mut self.code_builder, - ¶m_storage, + param_storage, &arg_storage, *arg_symbol, ); diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index aa21dbe3b1..2fcede8f5c 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -37,6 +37,7 @@ pub struct CodeBuilder { vm_stack: Vec>, } +#[allow(clippy::new_without_default)] impl CodeBuilder { pub fn new() -> Self { CodeBuilder { @@ -72,7 +73,7 @@ impl CodeBuilder { let mut len = old_len; let mut min_len = len; for inst in instructions { - let (pops, push) = get_pops_and_pushes(&inst); + let (pops, push) = get_pops_and_pushes(inst); len -= pops as usize; if len < min_len { min_len = len; diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 09d63cbae2..a6d70601bc 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -426,7 +426,7 @@ impl Storage { }; self.symbol_storage_map.insert(symbol, new_storage.clone()); - return new_storage; + new_storage } else { storage } diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs index 334809ac37..0e3d7d11c1 100644 --- a/compiler/gen_wasm/tests/wasm_records.rs +++ b/compiler/gen_wasm/tests/wasm_records.rs @@ -641,7 +641,6 @@ mod wasm_records { // ); // } - #[test] fn return_record_3() { assert_evals_to!( From 19eadbfe707f3e7c0a8b10f079e4232542516da3 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 14 Oct 2021 19:57:23 +0200 Subject: [PATCH 25/51] make list inc/dec non-recursive (except when freeing the list) --- compiler/gen_llvm/src/llvm/refcounting.rs | 76 +++++++++++++++++------ examples/benchmarks/platform/host.zig | 4 +- 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index 8a89c924d7..d1d0cc344e 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -741,27 +741,67 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>( let (len, ptr) = load_list(env.builder, original_wrapper, ptr_type); - let loop_fn = |_index, element| { - modify_refcount_layout_help( - env, - parent, - layout_ids, - mode.to_call_mode(fn_val), - when_recursive, - element, - element_layout, - ); - }; + let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, ptr); + let call_mode = mode_to_call_mode(fn_val, mode); - incrementing_elem_loop(env, parent, ptr, len, "modify_rc_index", loop_fn); + match mode { + Mode::Inc => { + // inc is cheap; we never recurse + refcount_ptr.modify(call_mode, layout, env); + builder.build_unconditional_branch(cont_block); + } + + Mode::Dec => { + let do_recurse_block = env.context.append_basic_block(parent, "do_recurse"); + let no_recurse_block = env.context.append_basic_block(parent, "no_recurse"); + + builder.build_conditional_branch( + refcount_ptr.is_1(env), + do_recurse_block, + no_recurse_block, + ); + + { + env.builder.position_at_end(no_recurse_block); + + refcount_ptr.modify(call_mode, layout, env); + builder.build_unconditional_branch(cont_block); + } + + { + env.builder.position_at_end(do_recurse_block); + + let loop_fn = |_index, element| { + modify_refcount_layout_help( + env, + parent, + layout_ids, + mode.to_call_mode(fn_val), + when_recursive, + element, + element_layout, + ); + }; + + incrementing_elem_loop(env, parent, ptr, len, "modify_rc_index", loop_fn); + builder.build_unconditional_branch(cont_block); + } + } + } + } else { + // just increment/decrement the list itself, don't touch the elements + let ptr_type = basic_type_from_layout(env, element_layout).ptr_type(AddressSpace::Generic); + + let (_, ptr) = load_list(env.builder, original_wrapper, ptr_type); + + let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, ptr); + let call_mode = mode_to_call_mode(fn_val, mode); + + refcount_ptr.modify(call_mode, layout, env); + + builder.build_unconditional_branch(cont_block); } - let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper); - let call_mode = mode_to_call_mode(fn_val, mode); - refcount_ptr.modify(call_mode, layout, env); - - builder.build_unconditional_branch(cont_block); - builder.position_at_end(cont_block); // this function returns void diff --git a/examples/benchmarks/platform/host.zig b/examples/benchmarks/platform/host.zig index aa339ec306..deed8ce10f 100644 --- a/examples/benchmarks/platform/host.zig +++ b/examples/benchmarks/platform/host.zig @@ -77,11 +77,11 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { std.process.exit(0); } -export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{ +export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void { return memcpy(dst, src, size); } -export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{ +export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void { return memset(dst, value, size); } From 7773cf9b4d6d908345c21f9d0053e0647f65fb21 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 14 Oct 2021 20:12:43 +0200 Subject: [PATCH 26/51] clippy --- compiler/gen_llvm/src/llvm/refcounting.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/refcounting.rs b/compiler/gen_llvm/src/llvm/refcounting.rs index d1d0cc344e..a640119d4b 100644 --- a/compiler/gen_llvm/src/llvm/refcounting.rs +++ b/compiler/gen_llvm/src/llvm/refcounting.rs @@ -736,9 +736,8 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>( builder.position_at_end(modification_block); + let ptr_type = basic_type_from_layout(env, element_layout).ptr_type(AddressSpace::Generic); if element_layout.contains_refcounted() { - let ptr_type = basic_type_from_layout(env, element_layout).ptr_type(AddressSpace::Generic); - let (len, ptr) = load_list(env.builder, original_wrapper, ptr_type); let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, ptr); @@ -790,8 +789,6 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>( } } else { // just increment/decrement the list itself, don't touch the elements - let ptr_type = basic_type_from_layout(env, element_layout).ptr_type(AddressSpace::Generic); - let (_, ptr) = load_list(env.builder, original_wrapper, ptr_type); let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, ptr); From 2ca9dad1569e34197608c8310fcfc26e9b4927ce Mon Sep 17 00:00:00 2001 From: Chadtech Date: Sat, 16 Oct 2021 13:30:32 -0400 Subject: [PATCH 27/51] Do not add parens around exprs if they are not needed in formatting --- compiler/fmt/src/annotation.rs | 2 +- compiler/fmt/src/expr.rs | 10 +++++++--- compiler/fmt/tests/test_fmt.rs | 25 +++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 7fb5b13043..0d9e7d8ff4 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -18,7 +18,7 @@ use roc_region::all::Located; /// Just (Just a) /// List (List a) /// reverse (reverse l) -#[derive(PartialEq, Eq, Clone, Copy)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum Parens { NotNeeded, InFunctionType, diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 44501f09e4..9160188699 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -133,9 +133,13 @@ impl<'a> Formattable<'a> for Expr<'a> { } } ParensAround(sub_expr) => { - buf.push('('); - sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); - buf.push(')'); + if parens == Parens::NotNeeded { + sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + } else { + buf.push('('); + sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + buf.push(')'); + } } Str(literal) => { use roc_parse::ast::StrLiteral::*; diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index edf60c1696..10dbb6b90a 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -362,6 +362,31 @@ mod test_fmt { ); } + #[test] + fn excess_parens() { + expr_formats_to( + indoc!( + r#" + x = (5) + + + y = ((10)) + + 42 + "# + ), + indoc!( + r#" + x = 5 + + y = 10 + + 42 + "# + ), + ); + } + // #[test] // fn defs_with_defs() { // expr_formats_to( From 56c36c51d75da83c39f76e9d1f8352a82aba387e Mon Sep 17 00:00:00 2001 From: Chadtech Date: Sat, 16 Oct 2021 13:44:14 -0400 Subject: [PATCH 28/51] Commented in many tests which apparently did not work in the past --- compiler/fmt/tests/test_fmt.rs | 230 ++++++++++++++++++--------------- 1 file changed, 125 insertions(+), 105 deletions(-) diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 10dbb6b90a..73a044c090 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -387,32 +387,32 @@ mod test_fmt { ); } - // #[test] - // fn defs_with_defs() { - // expr_formats_to( - // indoc!( - // r#" - // x = - // y = 4 - // z = 8 - // w + #[test] + fn defs_with_defs() { + expr_formats_to( + indoc!( + r#" + x = + y = 4 + z = 8 + w - // x - // "# - // ), - // indoc!( - // r#" - // x = - // y = 4 - // z = 8 + x + "# + ), + indoc!( + r#" + x = + y = 4 + z = 8 - // w + w - // x - // "# - // ), - // ); - // } + x + "# + ), + ); + } #[test] fn comment_between_two_defs() { @@ -573,15 +573,16 @@ mod test_fmt { )); } - // #[test] - // fn record_field_destructuring() { - // expr_formats_same(indoc!( - // r#" - // when foo is - // { x: 5 } -> 42 - // "# - // )); - // } + #[test] + fn record_field_destructuring() { + expr_formats_same(indoc!( + r#" + when foo is + { x: 5 } -> + 42 + "# + )); + } #[test] fn record_updating() { @@ -942,24 +943,43 @@ mod test_fmt { "# )); - // expr_formats_to( - // indoc!( - // r#" - // identity = \a - // -> a + expr_formats_to( + indoc!( + r#" + identity = \a + -> a - // identity 41 - // "# - // ), - // indoc!( - // r#" - // identity = \a -> - // a + identity 41 + "# + ), + indoc!( + r#" + identity = \a -> a - // identity 41 - // "# - // ), - // ); + identity 41 + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + identity = \a + -> + a + b + + identity 4010 + "# + ), + indoc!( + r#" + identity = \a -> + a + b + + identity 4010 + "# + ), + ); expr_formats_same(indoc!( r#" @@ -969,19 +989,19 @@ mod test_fmt { "# )); - // expr_formats_same(indoc!( - // r#" - // identity = - // \{ - // x, - // y - // } - // -> a - // - // identity 43 - // "# - // )); + // expr_formats_same(indoc!( + // r#" + // identity = + // \{ + // x, + // y + // } + // -> a // + // identity 43 + // "# + // )); + expr_formats_same(indoc!( r#" identity = \a, @@ -1237,17 +1257,17 @@ mod test_fmt { } #[test] fn multi_line_list_def() { - // expr_formats_same(indoc!( - // r#" - // l = - // [ - // 1, - // 2 - // ] + expr_formats_same(indoc!( + r#" + l = + [ + 1, + 2, + ] - // l - // "# - // )); + l + "# + )); expr_formats_to( indoc!( @@ -1273,32 +1293,32 @@ mod test_fmt { ), ); - // expr_formats_to( - // indoc!( - // r#" - // results = - // # Let's count past 6 - // [ - // Ok 6, - // Err CountError - // ] + expr_formats_to( + indoc!( + r#" + results = + # Let's count past 6 + [ + Ok 6, + Err CountError + ] - // allOks results - // "# - // ), - // indoc!( - // r#" - // results = - // # Let's count past 6 - // [ - // Ok 6, - // Err CountError - // ] + allOks results + "# + ), + indoc!( + r#" + results = + # Let's count past 6 + [ + Ok 6, + Err CountError, + ] - // allOks results - // "# - // ), - // ); + allOks results + "# + ), + ); } // RECORD LITERALS @@ -1355,18 +1375,18 @@ mod test_fmt { #[test] fn multi_line_record_def() { - // expr_formats_same(indoc!( - // r#" - // pos = - // { - // x: 4, - // y: 11, - // z: 16 - // } + expr_formats_same(indoc!( + r#" + pos = + { + x: 4, + y: 11, + z: 16, + } - // pos - // "# - // )); + pos + "# + )); expr_formats_to( indoc!( From 5e2decf89a98c1eafe9f0b54eb8fccd820d6aeec Mon Sep 17 00:00:00 2001 From: Chadtech Date: Sat, 16 Oct 2021 18:01:13 -0400 Subject: [PATCH 29/51] Binops with parentheses keep their parentheses --- compiler/fmt/src/expr.rs | 37 +++++++++++++++++++++++++++++++--- compiler/fmt/tests/test_fmt.rs | 35 +++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 9160188699..b2a58df133 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -133,7 +133,7 @@ impl<'a> Formattable<'a> for Expr<'a> { } } ParensAround(sub_expr) => { - if parens == Parens::NotNeeded { + if parens == Parens::NotNeeded && !sub_expr_requests_parens(&sub_expr) { sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); } else { buf.push('('); @@ -319,7 +319,7 @@ impl<'a> Formattable<'a> for Expr<'a> { buf.push_str(key); } Access(expr, key) => { - expr.format_with_options(buf, parens, Newlines::Yes, indent); + expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent); buf.push('.'); buf.push_str(key); } @@ -398,6 +398,8 @@ fn fmt_bin_ops<'a>( || lefts.iter().any(|(expr, _)| expr.value.is_multiline()); for (loc_left_side, loc_bin_op) in lefts { + let bin_op = loc_bin_op.value; + loc_left_side.format_with_options(buf, apply_needs_parens, Newlines::No, indent); if is_multiline { @@ -406,7 +408,7 @@ fn fmt_bin_ops<'a>( buf.push(' '); } - push_op(buf, loc_bin_op.value); + push_op(buf, bin_op); buf.push(' '); } @@ -1050,3 +1052,32 @@ fn format_field_multiline<'a, T>( } } } + +fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool { + match expr { + Expr::BinOps(left_side, _) => { + left_side + .iter() + .any(|(_, loc_bin_op)| match loc_bin_op.value { + BinOp::Caret + | BinOp::Star + | BinOp::Slash + | BinOp::DoubleSlash + | BinOp::Percent + | BinOp::DoublePercent + | BinOp::Plus + | BinOp::Minus + | BinOp::Equals + | BinOp::NotEquals + | BinOp::LessThan + | BinOp::GreaterThan + | BinOp::LessThanOrEq + | BinOp::GreaterThanOrEq + | BinOp::And + | BinOp::Or => true, + BinOp::Pizza | BinOp::Assignment | BinOp::HasType | BinOp::Backpassing => false, + }) + } + _ => false, + } +} diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 73a044c090..db0a632508 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -2249,7 +2249,7 @@ mod test_fmt { } #[test] - fn precedence_conflict_exponents() { + fn binop_parens() { expr_formats_same(indoc!( r#" if 4 == (6 ^ 6 ^ 7 ^ 8) then @@ -2258,6 +2258,39 @@ mod test_fmt { "Naturally" "# )); + + expr_formats_same(indoc!( + r#" + if 5 == 1 ^ 1 ^ 1 ^ 1 then + "Not buying it" + else + "True" + "# + )); + + expr_formats_to( + indoc!( + r#" + if (1 == 1) + && (2 == 1) && (3 == 2) then + "true" + else + "false" + "# + ), + indoc!( + r#" + if + (1 == 1) + && (2 == 1) + && (3 == 2) + then + "true" + else + "false" + "# + ), + ); } #[test] From 8e0fd1b4cd03026192a1007b6c940aa611511a24 Mon Sep 17 00:00:00 2001 From: Chadtech Date: Sat, 16 Oct 2021 18:08:18 -0400 Subject: [PATCH 30/51] Dont borrow sub_expr --- compiler/fmt/src/expr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index b2a58df133..49a0f5f937 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -133,7 +133,7 @@ impl<'a> Formattable<'a> for Expr<'a> { } } ParensAround(sub_expr) => { - if parens == Parens::NotNeeded && !sub_expr_requests_parens(&sub_expr) { + if parens == Parens::NotNeeded && !sub_expr_requests_parens(sub_expr) { sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); } else { buf.push('('); From 99468b0aac8db978e9a695d0d26ba11bc2edfd5c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 17 Oct 2021 11:19:10 +0200 Subject: [PATCH 31/51] Clarify a comment --- compiler/gen_wasm/src/code_builder.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index 2fcede8f5c..ce2bc1e3e8 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -30,6 +30,10 @@ pub struct CodeBuilder { /// Extra instructions to insert at specific positions during finalisation /// (Go back and set locals when we realise we need them) + /// We need BTree rather than Map or Vec, to ensure keys are sorted. + /// Entries may not be added in order. They are created when a Symbol + /// is used for the second time, or is in an inconvenient VM stack position, + /// so it's not a simple predictable order. insertions: BTreeMap, /// Our simulation model of the Wasm stack machine From 9763f9b51be86309f690a9233fc66532b6bd8036 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 17 Oct 2021 16:08:41 +0200 Subject: [PATCH 32/51] WIP --- compiler/gen_dev/src/lib.rs | 1 + compiler/gen_llvm/src/llvm/build.rs | 442 ++++++++++++++++++++++++++++ compiler/mono/src/alias_analysis.rs | 195 ++++++++++++ compiler/mono/src/borrow.rs | 88 +++++- compiler/mono/src/inc_dec.rs | 157 ++++++++++ compiler/mono/src/ir.rs | 29 ++ compiler/mono/src/lib.rs | 1 + 7 files changed, 912 insertions(+), 1 deletion(-) diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 5ef03a3a42..d11fb50dc4 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -786,6 +786,7 @@ where CallType::ByName { .. } => {} CallType::LowLevel { .. } => {} CallType::HigherOrderLowLevel { .. } => {} + CallType::NewHigherOrderLowLevel { .. } => {} CallType::Foreign { .. } => {} } } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index eac79e9978..b6b03f0c36 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -977,6 +977,36 @@ pub fn build_exp_call<'a, 'ctx, 'env>( ) } + CallType::NewHigherOrderLowLevel { + op, + function_owns_closure_data, + specialization_id, + function_name, + function_env, + arg_layouts, + ret_layout, + .. + } => { + let bytes = specialization_id.to_bytes(); + let callee_var = CalleeSpecVar(&bytes); + let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap(); + + run_new_higher_order_low_level( + env, + layout_ids, + scope, + layout, + *op, + func_spec, + arg_layouts, + ret_layout, + *function_owns_closure_data, + *function_name, + function_env, + arguments, + ) + } + CallType::Foreign { foreign_symbol, ret_layout, @@ -4456,6 +4486,418 @@ fn roc_function_call<'a, 'ctx, 'env>( } } +#[allow(clippy::too_many_arguments)] +fn run_new_higher_order_low_level<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + scope: &Scope<'a, 'ctx>, + return_layout: &Layout<'a>, + op: roc_mono::low_level::HigherOrder, + func_spec: FuncSpec, + argument_layouts: &[Layout<'a>], + result_layout: &Layout<'a>, + function_owns_closure_data: bool, + function_name: Symbol, + function_env: &Symbol, + args: &[Symbol], +) -> BasicValueEnum<'ctx> { + use roc_mono::low_level::HigherOrder::*; + + // macros because functions cause lifetime issues related to the `env` or `layout_ids` + macro_rules! function_details { + () => {{ + let function = function_value_by_func_spec( + env, + func_spec, + function_name, + argument_layouts, + return_layout, + ); + + let (closure, closure_layout) = load_symbol_and_lambda_set(scope, function_env); + + (function, closure, closure_layout) + }}; + } + + macro_rules! list_walk { + ($variant:expr, $xs:expr, $state:expr) => {{ + let (list, list_layout) = load_symbol_and_layout(scope, &$xs); + let (default, default_layout) = load_symbol_and_layout(scope, &$state); + + let (function, closure, closure_layout) = function_details!(); + + match list_layout { + Layout::Builtin(Builtin::EmptyList) => default, + Layout::Builtin(Builtin::List(element_layout)) => { + let argument_layouts = &[*default_layout, **element_layout]; + + let roc_function_call = roc_function_call( + env, + layout_ids, + function, + closure, + closure_layout, + function_owns_closure_data, + argument_layouts, + ); + + crate::llvm::build_list::list_walk_generic( + env, + layout_ids, + roc_function_call, + result_layout, + list, + element_layout, + default, + default_layout, + $variant, + ) + } + _ => unreachable!("invalid list layout"), + } + }}; + } + match op { + ListMap { xs } => { + // List.map : List before, (before -> after) -> List after + let (list, list_layout) = load_symbol_and_layout(scope, &xs); + + let (function, closure, closure_layout) = function_details!(); + + match (list_layout, return_layout) { + (Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), + ( + Layout::Builtin(Builtin::List(element_layout)), + Layout::Builtin(Builtin::List(result_layout)), + ) => { + let argument_layouts = &[**element_layout]; + + let roc_function_call = roc_function_call( + env, + layout_ids, + function, + closure, + closure_layout, + function_owns_closure_data, + argument_layouts, + ); + + list_map(env, roc_function_call, list, element_layout, result_layout) + } + _ => unreachable!("invalid list layout"), + } + } + ListMap2 { xs, ys } => { + let (list1, list1_layout) = load_symbol_and_layout(scope, &xs); + let (list2, list2_layout) = load_symbol_and_layout(scope, &ys); + + let (function, closure, closure_layout) = function_details!(); + + match (list1_layout, list2_layout, return_layout) { + ( + Layout::Builtin(Builtin::List(element1_layout)), + Layout::Builtin(Builtin::List(element2_layout)), + Layout::Builtin(Builtin::List(result_layout)), + ) => { + let argument_layouts = &[**element1_layout, **element2_layout]; + + let roc_function_call = roc_function_call( + env, + layout_ids, + function, + closure, + closure_layout, + function_owns_closure_data, + argument_layouts, + ); + + list_map2( + env, + layout_ids, + roc_function_call, + list1, + list2, + element1_layout, + element2_layout, + result_layout, + ) + } + (Layout::Builtin(Builtin::EmptyList), _, _) + | (_, Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), + _ => unreachable!("invalid list layout"), + } + } + ListMap3 { xs, ys, zs } => { + let (list1, list1_layout) = load_symbol_and_layout(scope, &xs); + let (list2, list2_layout) = load_symbol_and_layout(scope, &ys); + let (list3, list3_layout) = load_symbol_and_layout(scope, &zs); + + let (function, closure, closure_layout) = function_details!(); + + match (list1_layout, list2_layout, list3_layout, return_layout) { + ( + Layout::Builtin(Builtin::List(element1_layout)), + Layout::Builtin(Builtin::List(element2_layout)), + Layout::Builtin(Builtin::List(element3_layout)), + Layout::Builtin(Builtin::List(result_layout)), + ) => { + let argument_layouts = + &[**element1_layout, **element2_layout, **element3_layout]; + + let roc_function_call = roc_function_call( + env, + layout_ids, + function, + closure, + closure_layout, + function_owns_closure_data, + argument_layouts, + ); + + list_map3( + env, + layout_ids, + roc_function_call, + list1, + list2, + list3, + element1_layout, + element2_layout, + element3_layout, + result_layout, + ) + } + (Layout::Builtin(Builtin::EmptyList), _, _, _) + | (_, Layout::Builtin(Builtin::EmptyList), _, _) + | (_, _, Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), + _ => unreachable!("invalid list layout"), + } + } + ListMapWithIndex { xs } => { + // List.mapWithIndex : List before, (Nat, before -> after) -> List after + let (list, list_layout) = load_symbol_and_layout(scope, &xs); + + let (function, closure, closure_layout) = function_details!(); + + match (list_layout, return_layout) { + (Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), + ( + Layout::Builtin(Builtin::List(element_layout)), + Layout::Builtin(Builtin::List(result_layout)), + ) => { + let argument_layouts = &[Layout::Builtin(Builtin::Usize), **element_layout]; + + let roc_function_call = roc_function_call( + env, + layout_ids, + function, + closure, + closure_layout, + function_owns_closure_data, + argument_layouts, + ); + + list_map_with_index(env, roc_function_call, list, element_layout, result_layout) + } + _ => unreachable!("invalid list layout"), + } + } + ListKeepIf { xs } => { + // List.keepIf : List elem, (elem -> Bool) -> List elem + let (list, list_layout) = load_symbol_and_layout(scope, &xs); + + let (function, closure, closure_layout) = function_details!(); + + match list_layout { + Layout::Builtin(Builtin::EmptyList) => empty_list(env), + Layout::Builtin(Builtin::List(element_layout)) => { + let argument_layouts = &[**element_layout]; + + let roc_function_call = roc_function_call( + env, + layout_ids, + function, + closure, + closure_layout, + function_owns_closure_data, + argument_layouts, + ); + + list_keep_if(env, layout_ids, roc_function_call, list, element_layout) + } + _ => unreachable!("invalid list layout"), + } + } + ListKeepOks { xs } => { + // List.keepOks : List before, (before -> Result after *) -> List after + let (list, list_layout) = load_symbol_and_layout(scope, &xs); + + let (function, closure, closure_layout) = function_details!(); + + match (list_layout, return_layout) { + (_, Layout::Builtin(Builtin::EmptyList)) + | (Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), + ( + Layout::Builtin(Builtin::List(before_layout)), + Layout::Builtin(Builtin::List(after_layout)), + ) => { + let argument_layouts = &[**before_layout]; + + let roc_function_call = roc_function_call( + env, + layout_ids, + function, + closure, + closure_layout, + function_owns_closure_data, + argument_layouts, + ); + + list_keep_oks( + env, + layout_ids, + roc_function_call, + result_layout, + list, + before_layout, + after_layout, + ) + } + (other1, other2) => { + unreachable!("invalid list layouts:\n{:?}\n{:?}", other1, other2) + } + } + } + ListKeepErrs { xs } => { + // List.keepErrs : List before, (before -> Result * after) -> List after + let (list, list_layout) = load_symbol_and_layout(scope, &xs); + + let (function, closure, closure_layout) = function_details!(); + + match (list_layout, return_layout) { + (_, Layout::Builtin(Builtin::EmptyList)) + | (Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), + ( + Layout::Builtin(Builtin::List(before_layout)), + Layout::Builtin(Builtin::List(after_layout)), + ) => { + let argument_layouts = &[**before_layout]; + + let roc_function_call = roc_function_call( + env, + layout_ids, + function, + closure, + closure_layout, + function_owns_closure_data, + argument_layouts, + ); + + list_keep_errs( + env, + layout_ids, + roc_function_call, + result_layout, + list, + before_layout, + after_layout, + ) + } + (other1, other2) => { + unreachable!("invalid list layouts:\n{:?}\n{:?}", other1, other2) + } + } + } + ListWalk { xs, state } => { + list_walk!(crate::llvm::build_list::ListWalk::Walk, xs, state) + } + ListWalkUntil { xs, state } => { + list_walk!(crate::llvm::build_list::ListWalk::WalkUntil, xs, state) + } + ListWalkBackwards { xs, state } => { + list_walk!(crate::llvm::build_list::ListWalk::WalkBackwards, xs, state) + } + ListSortWith { xs } => { + // List.sortWith : List a, (a, a -> Ordering) -> List a + let (list, list_layout) = load_symbol_and_layout(scope, &xs); + + let (function, closure, closure_layout) = function_details!(); + + match list_layout { + Layout::Builtin(Builtin::EmptyList) => empty_list(env), + Layout::Builtin(Builtin::List(element_layout)) => { + use crate::llvm::bitcode::build_compare_wrapper; + + let argument_layouts = &[**element_layout, **element_layout]; + + let compare_wrapper = + build_compare_wrapper(env, function, closure_layout, element_layout) + .as_global_value() + .as_pointer_value(); + + let roc_function_call = roc_function_call( + env, + layout_ids, + function, + closure, + closure_layout, + function_owns_closure_data, + argument_layouts, + ); + + list_sort_with( + env, + roc_function_call, + compare_wrapper, + list, + element_layout, + ) + } + _ => unreachable!("invalid list layout"), + } + } + DictWalk { xs, state } => { + let (dict, dict_layout) = load_symbol_and_layout(scope, &xs); + let (default, default_layout) = load_symbol_and_layout(scope, &state); + + let (function, closure, closure_layout) = function_details!(); + + match dict_layout { + Layout::Builtin(Builtin::EmptyDict) => { + // no elements, so `key` is not in here + panic!("key type unknown") + } + Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => { + let argument_layouts = &[*default_layout, **key_layout, **value_layout]; + + let roc_function_call = roc_function_call( + env, + layout_ids, + function, + closure, + closure_layout, + function_owns_closure_data, + argument_layouts, + ); + + dict_walk( + env, + roc_function_call, + dict, + default, + key_layout, + value_layout, + default_layout, + ) + } + _ => unreachable!("invalid dict layout"), + } + } + _ => unreachable!(), + } +} + #[allow(clippy::too_many_arguments)] fn run_higher_order_low_level<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 3cf3ab797e..43abd3ed11 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -605,6 +605,201 @@ fn call_spec( *update_mode, call.arguments, ), + NewHigherOrderLowLevel { + specialization_id, + closure_env_layout, + op, + arg_layouts, + ret_layout, + function_name, + function_env, + .. + } => { + let array = specialization_id.to_bytes(); + let spec_var = CalleeSpecVar(&array); + + let it = arg_layouts.iter().copied(); + let bytes = func_name_bytes_help(*function_name, it, *ret_layout); + let name = FuncName(&bytes); + let module = MOD_APP; + + use crate::low_level::HigherOrder::*; + + match op { + DictWalk { xs, state } => { + let dict = env.symbols[xs]; + let state = env.symbols[state]; + let closure_env = env.symbols[function_env]; + + let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?; + let _cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?; + + let first = builder.add_bag_get(block, bag)?; + + let key = builder.add_get_tuple_field(block, first, 0)?; + let val = builder.add_get_tuple_field(block, first, 1)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[state, key, val])? + } else { + builder.add_make_tuple(block, &[state, key, val, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListWalk { xs, state } + | ListWalkBackwards { xs, state } + | ListWalkUntil { xs, state } => { + let list = env.symbols[xs]; + let state = env.symbols[state]; + let closure_env = env.symbols[function_env]; + + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + + let first = builder.add_bag_get(block, bag)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[state, first])? + } else { + builder.add_make_tuple(block, &[state, first, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListMapWithIndex { xs } => { + let list = env.symbols[xs]; + let closure_env = env.symbols[function_env]; + + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + + let first = builder.add_bag_get(block, bag)?; + let index = builder.add_make_tuple(block, &[])?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[index, first])? + } else { + builder.add_make_tuple(block, &[index, first, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListMap { xs } => { + let list = env.symbols[xs]; + let closure_env = env.symbols[function_env]; + + let bag1 = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let _cell1 = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + + let elem1 = builder.add_bag_get(block, bag1)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[elem1])? + } else { + builder.add_make_tuple(block, &[elem1, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListSortWith { xs } => { + let list = env.symbols[xs]; + let closure_env = env.symbols[function_env]; + + let bag1 = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let _cell1 = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + + let elem1 = builder.add_bag_get(block, bag1)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[elem1, elem1])? + } else { + builder.add_make_tuple(block, &[elem1, elem1, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListMap2 { xs, ys } => { + let list1 = env.symbols[xs]; + let list2 = env.symbols[ys]; + let closure_env = env.symbols[function_env]; + + let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; + let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; + let elem1 = builder.add_bag_get(block, bag1)?; + + let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; + let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?; + let elem2 = builder.add_bag_get(block, bag2)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[elem1, elem2])? + } else { + builder.add_make_tuple(block, &[elem1, elem2, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListMap3 { xs, ys, zs } => { + let list1 = env.symbols[xs]; + let list2 = env.symbols[ys]; + let list3 = env.symbols[zs]; + let closure_env = env.symbols[function_env]; + + let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; + let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; + let elem1 = builder.add_bag_get(block, bag1)?; + + let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; + let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?; + let elem2 = builder.add_bag_get(block, bag2)?; + + let bag3 = builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?; + let _cell3 = builder.add_get_tuple_field(block, list3, LIST_CELL_INDEX)?; + let elem3 = builder.add_bag_get(block, bag3)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[elem1, elem2, elem3])? + } else { + builder.add_make_tuple(block, &[elem1, elem2, elem3, closure_env])? + }; + builder.add_call(block, spec_var, module, name, argument)?; + } + + ListKeepIf { xs } | ListKeepOks { xs } | ListKeepErrs { xs } => { + let list = env.symbols[xs]; + let closure_env = env.symbols[function_env]; + + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + // let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + + let first = builder.add_bag_get(block, bag)?; + + let argument = if closure_env_layout.is_none() { + builder.add_make_tuple(block, &[first])? + } else { + builder.add_make_tuple(block, &[first, closure_env])? + }; + let result = builder.add_call(block, spec_var, module, name, argument)?; + let unit = builder.add_tuple_type(&[])?; + builder.add_unknown_with(block, &[result], unit)?; + } + } + + // TODO overly pessimstic + // filter_map because one of the arguments is a function name, which + // is not defined in the env + let arguments: Vec<_> = call + .arguments + .iter() + .filter_map(|symbol| env.symbols.get(symbol)) + .copied() + .collect(); + + let result_type = layout_spec(builder, layout)?; + + builder.add_unknown_with(block, &arguments, result_type) + } HigherOrderLowLevel { specialization_id, closure_env_layout, diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index cd89f63ef1..9097ca8040 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -593,6 +593,91 @@ impl<'a> BorrowInfState<'a> { self.own_args_using_bools(arguments, ps); } + NewHigherOrderLowLevel { + op, + arg_layouts, + ret_layout, + function_name, + function_env, + .. + } => { + use crate::low_level::HigherOrder::*; + + let closure_layout = ProcLayout { + arguments: arg_layouts, + result: *ret_layout, + }; + + let function_ps = match param_map.get_symbol(*function_name, closure_layout) { + Some(function_ps) => function_ps, + None => unreachable!(), + }; + + match op { + ListMap { xs } + | ListKeepIf { xs } + | ListKeepOks { xs } + | ListKeepErrs { xs } => { + // own the list if the function wants to own the element + if !function_ps[0].borrow { + self.own_var(*xs); + } + } + ListMapWithIndex { xs } => { + // own the list if the function wants to own the element + if !function_ps[1].borrow { + self.own_var(*xs); + } + } + ListMap2 { xs, ys } => { + // own the lists if the function wants to own the element + if !function_ps[0].borrow { + self.own_var(*xs); + } + + if !function_ps[1].borrow { + self.own_var(*ys); + } + } + ListMap3 { xs, ys, zs } => { + // own the lists if the function wants to own the element + if !function_ps[0].borrow { + self.own_var(*xs); + } + if !function_ps[1].borrow { + self.own_var(*ys); + } + if !function_ps[2].borrow { + self.own_var(*zs); + } + } + ListSortWith { xs } => { + // always own the input list + self.own_var(*xs); + } + ListWalk { xs, state } + | ListWalkUntil { xs, state } + | ListWalkBackwards { xs, state } + | DictWalk { xs, state } => { + // own the default value if the function wants to own it + if !function_ps[0].borrow { + self.own_var(*state); + } + + // own the data structure if the function wants to own the element + if !function_ps[1].borrow { + self.own_var(*xs); + } + } + } + + // own the closure environment if the function needs to own it + let function_env_position = op.function_arity(); + if let Some(false) = function_ps.get(function_env_position).map(|p| p.borrow) { + self.own_var(*function_env); + } + } + HigherOrderLowLevel { op, arg_layouts, @@ -952,7 +1037,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { use LowLevel::*; // TODO is true or false more efficient for non-refcounted layouts? - let irrelevant = OWNED; + let irrelevant = BORROWED; let function = irrelevant; let closure_data = irrelevant; let owned = OWNED; @@ -1071,6 +1156,7 @@ fn call_info_call<'a>(call: &crate::ir::Call<'a>, info: &mut CallInfo<'a>) { Foreign { .. } => {} LowLevel { .. } => {} HigherOrderLowLevel { .. } => {} + NewHigherOrderLowLevel { .. } => {} } } diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index 8aebc86fdc..195ad255c0 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -463,6 +463,163 @@ impl<'a> Context<'a> { &*self.arena.alloc(Stmt::Let(z, v, l, b)) } + NewHigherOrderLowLevel { + op, + closure_env_layout, + specialization_id, + arg_layouts, + ret_layout, + function_name, + function_env, + .. + } => { + // setup + use crate::low_level::HigherOrder::*; + + macro_rules! create_call { + ($borrows:expr) => { + Expr::Call(crate::ir::Call { + call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) { + NewHigherOrderLowLevel { + op: *op, + closure_env_layout: *closure_env_layout, + function_owns_closure_data: true, + specialization_id: *specialization_id, + function_name: *function_name, + function_env: *function_env, + arg_layouts, + ret_layout: *ret_layout, + } + } else { + call_type + }, + arguments, + }) + }; + } + + macro_rules! decref_if_owned { + ($borrows:expr, $argument:expr, $stmt:expr) => { + if !$borrows { + self.arena.alloc(Stmt::Refcounting( + ModifyRc::DecRef($argument), + self.arena.alloc($stmt), + )) + } else { + $stmt + } + }; + } + + const FUNCTION: bool = BORROWED; + const CLOSURE_DATA: bool = BORROWED; + + let function_layout = ProcLayout { + arguments: arg_layouts, + result: *ret_layout, + }; + + let function_ps = match self.param_map.get_symbol(*function_name, function_layout) { + Some(function_ps) => function_ps, + None => unreachable!(), + }; + + match op { + ListMap { xs } + | ListKeepIf { xs } + | ListKeepOks { xs } + | ListKeepErrs { xs } => { + let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA]; + + let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); + + // if the list is owned, then all elements have been consumed, but not the list itself + let b = decref_if_owned!(function_ps[0].borrow, *xs, b); + + let v = create_call!(function_ps.get(1)); + + &*self.arena.alloc(Stmt::Let(z, v, l, b)) + } + ListMap2 { xs, ys } => { + let borrows = [ + function_ps[0].borrow, + function_ps[1].borrow, + FUNCTION, + CLOSURE_DATA, + ]; + + let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); + + let b = decref_if_owned!(function_ps[0].borrow, *xs, b); + let b = decref_if_owned!(function_ps[1].borrow, *ys, b); + + let v = create_call!(function_ps.get(2)); + + &*self.arena.alloc(Stmt::Let(z, v, l, b)) + } + ListMap3 { xs, ys, zs } => { + let borrows = [ + function_ps[0].borrow, + function_ps[1].borrow, + function_ps[2].borrow, + FUNCTION, + CLOSURE_DATA, + ]; + + let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); + + let b = decref_if_owned!(function_ps[0].borrow, *xs, b); + let b = decref_if_owned!(function_ps[1].borrow, *ys, b); + let b = decref_if_owned!(function_ps[2].borrow, *zs, b); + + let v = create_call!(function_ps.get(3)); + + &*self.arena.alloc(Stmt::Let(z, v, l, b)) + } + ListMapWithIndex { xs } => { + let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA]; + + let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); + + let b = decref_if_owned!(function_ps[1].borrow, *xs, b); + + let v = create_call!(function_ps.get(2)); + + &*self.arena.alloc(Stmt::Let(z, v, l, b)) + } + ListSortWith { xs: _ } => { + let borrows = [OWNED, FUNCTION, CLOSURE_DATA]; + + let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); + + let v = create_call!(function_ps.get(2)); + + &*self.arena.alloc(Stmt::Let(z, v, l, b)) + } + ListWalk { xs, state: _ } + | ListWalkUntil { xs, state: _ } + | ListWalkBackwards { xs, state: _ } + | DictWalk { xs, state: _ } => { + // borrow data structure based on first argument of the folded function + // borrow the default based on second argument of the folded function + let borrows = [ + function_ps[1].borrow, + function_ps[0].borrow, + FUNCTION, + CLOSURE_DATA, + ]; + + let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); + + let b = decref_if_owned!(function_ps[1].borrow, *xs, b); + + let v = create_call!(function_ps.get(2)); + + &*self.arena.alloc(Stmt::Let(z, v, l, b)) + } + } + } + HigherOrderLowLevel { op, closure_env_layout, diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index cc21209828..b19bf7ac3d 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1050,6 +1050,13 @@ impl<'a> Call<'a> { .text(format!("lowlevel {:?} ", lowlevel)) .append(alloc.intersperse(it, " ")) } + NewHigherOrderLowLevel { op: lowlevel, .. } => { + let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s)); + + alloc + .text(format!("lowlevel {:?} ", lowlevel)) + .append(alloc.intersperse(it, " ")) + } Foreign { ref foreign_symbol, .. } => { @@ -1113,6 +1120,27 @@ pub enum CallType<'a> { arg_layouts: &'a [Layout<'a>], ret_layout: Layout<'a>, }, + NewHigherOrderLowLevel { + op: crate::low_level::HigherOrder, + /// the layout of the closure argument, if any + closure_env_layout: Option>, + + /// name of the top-level function that is passed as an argument + /// e.g. in `List.map xs Num.abs` this would be `Num.abs` + function_name: Symbol, + + /// Symbol of the environment captured by the function argument + function_env: Symbol, + + /// does the function argument need to own the closure data + function_owns_closure_data: bool, + + /// specialization id of the function argument, used for name generation + specialization_id: CallSpecId, + /// function layout, used for name generation + arg_layouts: &'a [Layout<'a>], + ret_layout: Layout<'a>, + }, } #[derive(Clone, Debug, PartialEq)] @@ -5447,6 +5475,7 @@ fn substitute_in_call<'a>( CallType::Foreign { .. } => None, CallType::LowLevel { .. } => None, CallType::HigherOrderLowLevel { .. } => None, + CallType::NewHigherOrderLowLevel { .. } => None, }; let mut did_change = false; diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index d377342bed..659b3f7cc8 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -8,6 +8,7 @@ pub mod expand_rc; pub mod inc_dec; pub mod ir; pub mod layout; +pub mod low_level; pub mod reset_reuse; pub mod tail_recursion; From 03295d4369549a2c32fa4b5567519ea3dd731e8e Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sun, 17 Oct 2021 09:29:11 -0500 Subject: [PATCH 33/51] So far, add type for divCeil to the language --- cli/tests/repl_eval.rs | 8 ++++++++ compiler/builtins/src/std.rs | 13 +++++++++++++ compiler/module/src/symbol.rs | 1 + compiler/solve/tests/solve_expr.rs | 12 ++++++++++++ 4 files changed, 34 insertions(+) diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 7da596ff03..41567297dd 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -116,6 +116,14 @@ mod repl_eval { ); } + #[test] + fn num_ceil_division_success() { + expect_success( + "Num.divCeil 4 3", + "Ok 2 : Result (Int *) [ DivByZero ]*" + ) + } + #[test] fn bool_in_record() { expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }"); diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 9c03042f41..bb79d7fc50 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -305,6 +305,19 @@ pub fn types() -> MutMap { Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), ); + //divCeil: Int a, Int a -> Result (Int a) [ DivByZero ]* + let div_by_zero = SolvedType::TagUnion( + vec![(TagName::Global("DivByZero".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + + add_top_level_function_type!( + Symbol::NUM_DIV_CEIL, + vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], + Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), + ); + + // bitwiseAnd : Int a, Int a -> Int a add_top_level_function_type!( Symbol::NUM_BITWISE_AND, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 897fcdbca3..fe208a3313 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -943,6 +943,7 @@ define_builtins! { 103 NUM_BYTES_TO_U16: "bytesToU16" 104 NUM_BYTES_TO_U32: "bytesToU32" 105 NUM_CAST_TO_NAT: "#castToNat" + 106 NUM_DIV_CEIL: "divCeil" } 2 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index fadd1d5a0b..ea17643abc 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3305,6 +3305,18 @@ mod solve_expr { ); } + #[test] + fn divCeil() { + infer_eq_without_problem( + indoc!( + r#" + Num.divCeil + "# + ), + "Float * -> Int *", + ); + } + #[test] fn pow_int() { infer_eq_without_problem( From 4b65430beff1e95749de8d5bca43fbe421c4e736 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 17 Oct 2021 16:32:45 +0200 Subject: [PATCH 34/51] fix test --- compiler/solve/tests/solve_expr.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index ea17643abc..cd05ede55d 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3306,14 +3306,14 @@ mod solve_expr { } #[test] - fn divCeil() { + fn div_ceil() { infer_eq_without_problem( indoc!( r#" Num.divCeil "# ), - "Float * -> Int *", + "Int a, Int a -> Result (Int a) [ DivByZero ]*", ); } From 0cc6e44d355be9468747b2ce7bb3fbc42cfe7ba3 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sun, 17 Oct 2021 10:46:16 -0500 Subject: [PATCH 35/51] Thread through the divCeil implementation from Zig: + OPEN QUESTION: Evidently the Zig implementation can throw an overflow error. Do we want to do something in Roc to fix this? --- compiler/builtins/bitcode/src/main.zig | 1 + compiler/builtins/bitcode/src/num.zig | 4 ++ compiler/builtins/src/bitcode.rs | 1 + compiler/can/src/builtins.rs | 68 ++++++++++++++++++++++++++ compiler/gen_llvm/src/llvm/build.rs | 3 +- compiler/module/src/low_level.rs | 3 +- compiler/mono/src/borrow.rs | 2 +- 7 files changed, 79 insertions(+), 3 deletions(-) diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index d82b8542a8..6d56e3b318 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -76,6 +76,7 @@ comptime { exportNumFn(num.atan, "atan"); exportNumFn(num.isFinite, "is_finite"); exportNumFn(num.powInt, "pow_int"); + exportNumFn(num.divCeil, "div_ceil"); exportNumFn(num.acos, "acos"); exportNumFn(num.asin, "asin"); exportNumFn(num.bytesToU16C, "bytes_to_u16"); diff --git a/compiler/builtins/bitcode/src/num.zig b/compiler/builtins/bitcode/src/num.zig index 94873f2fe6..396ac4fcfa 100644 --- a/compiler/builtins/bitcode/src/num.zig +++ b/compiler/builtins/bitcode/src/num.zig @@ -15,6 +15,10 @@ pub fn powInt(base: i64, exp: i64) callconv(.C) i64 { return @call(.{ .modifier = always_inline }, math.pow, .{ i64, base, exp }); } +pub fn divCeil(numerator: i64, denominator: i64) callconv(.C) i64 { + return @call(.{ .modifier = always_inline }, math.divCeil, .{ i64, numerator, denominator }); +} + pub fn acos(num: f64) callconv(.C) f64 { return @call(.{ .modifier = always_inline }, math.acos, .{num}); } diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index e451a1e68d..976c4cb9a1 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -8,6 +8,7 @@ pub const NUM_ACOS: &str = "roc_builtins.num.acos"; pub const NUM_ATAN: &str = "roc_builtins.num.atan"; pub const NUM_IS_FINITE: &str = "roc_builtins.num.is_finite"; pub const NUM_POW_INT: &str = "roc_builtins.num.pow_int"; +pub const NUM_DIV_CEIL: &str = "roc_builtins.num.div_ceil"; pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16"; pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32"; pub const NUM_ROUND: &str = "roc_builtins.num.round"; diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 05dcb00746..f7f3d30c37 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -143,6 +143,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_TAN => num_tan, NUM_DIV_FLOAT => num_div_float, NUM_DIV_INT => num_div_int, + NUM_DIV_CEIL => num_div_ceil, NUM_ABS => num_abs, NUM_NEG => num_neg, NUM_REM => num_rem, @@ -2844,6 +2845,73 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// Num.divCeil : Int a , Int a -> Result (Int a) [ DivByZero ]* +fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let num_var = var_store.fresh(); + let unbound_zero_var = var_store.fresh(); + let unbound_zero_precision_var = var_store.fresh(); + let ret_var = var_store.fresh(); + + let body = If { + branch_var: ret_var, + cond_var: bool_var, + branches: vec![( + // if-condition + no_region( + // Num.neq denominator 0 + RunLowLevel { + op: LowLevel::NotEq, + args: vec![ + (num_var, Var(Symbol::ARG_2)), + ( + num_var, + int(unbound_zero_var, unbound_zero_precision_var, 0), + ), + ], + ret_var: bool_var, + }, + ), + // denominator was not zero + no_region( + // Ok (Int.#divUnchecked numerator denominator) + tag( + "Ok", + vec![ + // Num.#divUnchecked numerator denominator + RunLowLevel { + op: LowLevel::NumDivCeil, + args: vec![ + (num_var, Var(Symbol::ARG_1)), + (num_var, Var(Symbol::ARG_2)), + ], + ret_var: num_var, + }, + ], + var_store, + ), + ), + )], + final_else: Box::new( + // denominator was zero + no_region(tag( + "Err", + vec![tag("DivByZero", Vec::new(), var_store)], + var_store, + )), + ), + }; + + defn( + symbol, + vec![(num_var, Symbol::ARG_1), (num_var, Symbol::ARG_2)], + var_store, + body, + ret_var, + ) +} + + /// List.first : List elem -> Result elem [ ListWasEmpty ]* /// /// List.first : diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index eac79e9978..ed0e7790c7 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -5327,7 +5327,7 @@ fn run_low_level<'a, 'ctx, 'env>( } NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked - | NumIsMultipleOf | NumAddWrap | NumAddChecked | NumDivUnchecked | NumPow | NumPowInt + | NumIsMultipleOf | NumAddWrap | NumAddChecked | NumDivUnchecked | NumDivCeilUnchecked | NumPow | NumPowInt | NumSubWrap | NumSubChecked | NumMulWrap | NumMulChecked => { debug_assert_eq!(args.len(), 2); @@ -6073,6 +6073,7 @@ fn build_int_binop<'a, 'ctx, 'env>( } NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(), NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], bitcode::NUM_POW_INT), + NumDivCeilUnchecked => call_bitcode_fn(env, &[lhs.into(), rhs.into()], bitcode::NUM_DIV_CEIL), NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(), NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(), NumBitwiseOr => bd.build_or(lhs, rhs, "int_bitwise_or").into(), diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 8753d21af0..975ed59463 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -71,6 +71,7 @@ pub enum LowLevel { NumLte, NumCompare, NumDivUnchecked, + NumDivCeilUnchecked, NumRemUnchecked, NumIsMultipleOf, NumAbs, @@ -123,7 +124,7 @@ impl LowLevel { | DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte - | NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf + | NumLt | NumLte | NumCompare | NumDivUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan | NumAcos | NumAsin | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index cd89f63ef1..98753306d7 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -1000,7 +1000,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare - | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt + | NumDivUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), From 4152519bfbd738b19878b627ae085bc5163aa1cd Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 17 Oct 2021 18:03:40 +0200 Subject: [PATCH 36/51] formatting & catch zig overflow error --- cli/tests/repl_eval.rs | 5 +---- compiler/builtins/bitcode/src/num.zig | 2 +- compiler/builtins/src/std.rs | 8 +------- compiler/can/src/builtins.rs | 3 +-- compiler/gen_llvm/src/llvm/build.rs | 8 +++++--- compiler/module/src/low_level.rs | 13 +++++++------ compiler/mono/src/borrow.rs | 6 +++--- 7 files changed, 19 insertions(+), 26 deletions(-) diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 41567297dd..6633a74f8b 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -118,10 +118,7 @@ mod repl_eval { #[test] fn num_ceil_division_success() { - expect_success( - "Num.divCeil 4 3", - "Ok 2 : Result (Int *) [ DivByZero ]*" - ) + expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*") } #[test] diff --git a/compiler/builtins/bitcode/src/num.zig b/compiler/builtins/bitcode/src/num.zig index 396ac4fcfa..0901722fd4 100644 --- a/compiler/builtins/bitcode/src/num.zig +++ b/compiler/builtins/bitcode/src/num.zig @@ -16,7 +16,7 @@ pub fn powInt(base: i64, exp: i64) callconv(.C) i64 { } pub fn divCeil(numerator: i64, denominator: i64) callconv(.C) i64 { - return @call(.{ .modifier = always_inline }, math.divCeil, .{ i64, numerator, denominator }); + return @call(.{ .modifier = always_inline }, math.divCeil, .{ i64, numerator, denominator }) catch unreachable; } pub fn acos(num: f64) callconv(.C) f64 { diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index bb79d7fc50..f17cc22098 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -293,12 +293,12 @@ pub fn types() -> MutMap { // minInt : Int range add_type!(Symbol::NUM_MIN_INT, int_type(flex(TVAR1))); - // divInt : Int a, Int a -> Result (Int a) [ DivByZero ]* let div_by_zero = SolvedType::TagUnion( vec![(TagName::Global("DivByZero".into()), vec![])], Box::new(SolvedType::Wildcard), ); + // divInt : Int a, Int a -> Result (Int a) [ DivByZero ]* add_top_level_function_type!( Symbol::NUM_DIV_INT, vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], @@ -306,18 +306,12 @@ pub fn types() -> MutMap { ); //divCeil: Int a, Int a -> Result (Int a) [ DivByZero ]* - let div_by_zero = SolvedType::TagUnion( - vec![(TagName::Global("DivByZero".into()), vec![])], - Box::new(SolvedType::Wildcard), - ); - add_top_level_function_type!( Symbol::NUM_DIV_CEIL, vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), ); - // bitwiseAnd : Int a, Int a -> Int a add_top_level_function_type!( Symbol::NUM_BITWISE_AND, diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index f7f3d30c37..3546fa6549 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -2880,7 +2880,7 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def { vec![ // Num.#divUnchecked numerator denominator RunLowLevel { - op: LowLevel::NumDivCeil, + op: LowLevel::NumDivCeilUnchecked, args: vec![ (num_var, Var(Symbol::ARG_1)), (num_var, Var(Symbol::ARG_2)), @@ -2911,7 +2911,6 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } - /// List.first : List elem -> Result elem [ ListWasEmpty ]* /// /// List.first : diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index ed0e7790c7..018d05d1a2 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -5327,8 +5327,8 @@ fn run_low_level<'a, 'ctx, 'env>( } NumAdd | NumSub | NumMul | NumLt | NumLte | NumGt | NumGte | NumRemUnchecked - | NumIsMultipleOf | NumAddWrap | NumAddChecked | NumDivUnchecked | NumDivCeilUnchecked | NumPow | NumPowInt - | NumSubWrap | NumSubChecked | NumMulWrap | NumMulChecked => { + | NumIsMultipleOf | NumAddWrap | NumAddChecked | NumDivUnchecked | NumDivCeilUnchecked + | NumPow | NumPowInt | NumSubWrap | NumSubChecked | NumMulWrap | NumMulChecked => { debug_assert_eq!(args.len(), 2); let (lhs_arg, lhs_layout) = load_symbol_and_layout(scope, &args[0]); @@ -6073,7 +6073,9 @@ fn build_int_binop<'a, 'ctx, 'env>( } NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(), NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], bitcode::NUM_POW_INT), - NumDivCeilUnchecked => call_bitcode_fn(env, &[lhs.into(), rhs.into()], bitcode::NUM_DIV_CEIL), + NumDivCeilUnchecked => { + call_bitcode_fn(env, &[lhs.into(), rhs.into()], bitcode::NUM_DIV_CEIL) + } NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(), NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(), NumBitwiseOr => bd.build_or(lhs, rhs, "int_bitwise_or").into(), diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 975ed59463..560f959e22 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -124,12 +124,13 @@ impl LowLevel { | DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte - | NumLt | NumLte | NumCompare | NumDivUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf - | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound - | NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan - | NumAcos | NumAsin | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy - | NumShiftRightBy | NumBytesToU16 | NumBytesToU32 | NumShiftRightZfBy | NumIntCast - | Eq | NotEq | And | Or | Not | Hash | ExpectTrue => false, + | NumLt | NumLte | NumCompare | NumDivUnchecked | NumDivCeilUnchecked + | NumRemUnchecked | NumIsMultipleOf | NumAbs | NumNeg | NumSin | NumCos + | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumToFloat | NumPow | NumCeiling + | NumPowInt | NumFloor | NumIsFinite | NumAtan | NumAcos | NumAsin | NumBitwiseAnd + | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumBytesToU16 + | NumBytesToU32 | NumShiftRightZfBy | NumIntCast | Eq | NotEq | And | Or | Not + | Hash | ExpectTrue => false, ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 98753306d7..6c4dd106bd 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -1000,9 +1000,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare - | NumDivUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow | NumPowInt - | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy - | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), + | NumDivUnchecked | NumDivCeilUnchecked | NumRemUnchecked | NumIsMultipleOf | NumPow + | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy + | NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin From 338bef90053852aad487449d52b3960502db3a16 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sun, 17 Oct 2021 13:00:15 -0400 Subject: [PATCH 37/51] Update AUTHORS --- AUTHORS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/AUTHORS b/AUTHORS index edc78155ab..85ddade782 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,3 +28,11 @@ Ju Liu Peter Fields Brian J. Cardiff Basile Henry +Tarjei Skjærset +Brian Hicks +Dan Gieschen Knutson +Joshua Hoeflich +Brian Carroll +Kofi Gumbs +Luiz de Oliveira +Chelsea Troy From f236ae0a6ac7198a3bca3977cbbf873e11a78503 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 17 Oct 2021 19:38:12 +0200 Subject: [PATCH 38/51] cleanup --- compiler/gen_llvm/src/llvm/build.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index b6b03f0c36..48c4c71eea 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -1003,7 +1003,6 @@ pub fn build_exp_call<'a, 'ctx, 'env>( *function_owns_closure_data, *function_name, function_env, - arguments, ) } @@ -4499,7 +4498,6 @@ fn run_new_higher_order_low_level<'a, 'ctx, 'env>( function_owns_closure_data: bool, function_name: Symbol, function_env: &Symbol, - args: &[Symbol], ) -> BasicValueEnum<'ctx> { use roc_mono::low_level::HigherOrder::*; @@ -4894,7 +4892,6 @@ fn run_new_higher_order_low_level<'a, 'ctx, 'env>( _ => unreachable!("invalid dict layout"), } } - _ => unreachable!(), } } From 2c702cc893571f10a664608f4c37b580d25f989c Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 17 Oct 2021 19:39:05 +0200 Subject: [PATCH 39/51] code that makes new NewHigherOrderLowLevels, use this later --- compiler/mono/src/ir.rs | 112 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index b19bf7ac3d..d0c8d8c479 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -4041,24 +4041,128 @@ pub fn with_hole<'a>( let layout = return_on_layout_error!(env, layout_cache.from_var(env.arena, ret_var, env.subs)); + let closure_data_symbol = arg_symbols[2]; + + macro_rules! new_match_on_closure_argument { + ($env:expr, $procs:expr, $layout_cache:expr, $closure_data_symbol:expr, $closure_data_var:expr, $op:expr, $ho:ident, [$($x:ident),* $(,)?], $layout: expr, $assigned:expr, $hole:expr) => {{ + let closure_data_layout = return_on_layout_error!( + $env, + $layout_cache.raw_from_var($env.arena, $closure_data_var, $env.subs) + ); + + let top_level = ProcLayout::from_raw($env.arena, closure_data_layout); + + let arena = $env.arena; + + let arg_layouts = top_level.arguments; + let ret_layout = top_level.result; + + match closure_data_layout { + RawFunctionLayout::Function(_, lambda_set, _) => { + lowlevel_match_on_lambda_set( + $env, + lambda_set, + $op, + $closure_data_symbol, + |top_level_function, closure_data, closure_env_layout, specialization_id| self::Call { + call_type: CallType::NewHigherOrderLowLevel { + op: $ho { $($x,)* }, + closure_env_layout, + specialization_id, + function_owns_closure_data: false, + function_env: closure_data_symbol, + function_name: top_level_function, + arg_layouts, + ret_layout, + }, + arguments: arena.alloc([$($x,)* top_level_function, closure_data]), + }, + $layout, + $assigned, + $hole, + ) + } + RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!("match_on_closure_argument received a zero-argument thunk"), + } + }}; +} + use LowLevel::*; match op { - ListMap | ListMapWithIndex | ListKeepIf | ListKeepOks | ListKeepErrs - | ListSortWith => { + ListMap => { + debug_assert_eq!(arg_symbols.len(), 2); + + let closure_index = 1; + let closure_data_symbol = arg_symbols[closure_index]; + let closure_data_var = args[closure_index].0; + let xs = arg_symbols[0]; + + let closure_data_layout = return_on_layout_error!( + env, + layout_cache.raw_from_var(env.arena, closure_data_var, env.subs) + ); + + let top_level = ProcLayout::from_raw(env.arena, closure_data_layout); + + let arena = env.arena; + + let arg_layouts = top_level.arguments; + let ret_layout = top_level.result; + + match closure_data_layout { + RawFunctionLayout::Function(_, lambda_set, _) => { + lowlevel_match_on_lambda_set( + env, + lambda_set, + op, + closure_data_symbol, + |top_level_function, + closure_data, + closure_env_layout, + specialization_id| self::Call { + call_type: CallType::NewHigherOrderLowLevel { + op: crate::low_level::HigherOrder::ListMap { xs }, + closure_env_layout, + specialization_id, + function_owns_closure_data: false, + function_env: closure_data_symbol, + function_name: top_level_function, + arg_layouts, + ret_layout, + }, + arguments: arena.alloc([xs, top_level_function, closure_data]), + }, + layout, + assigned, + hole, + ) + } + RawFunctionLayout::ZeroArgumentThunk(_) => { + unreachable!("match_on_closure_argument received a zero-argument thunk") + } + } + } + + ListMapWithIndex | ListKeepIf | ListKeepOks | ListKeepErrs | ListSortWith => { debug_assert_eq!(arg_symbols.len(), 2); let closure_index = 1; let closure_data_symbol = arg_symbols[closure_index]; let closure_data_var = args[closure_index].0; - match_on_closure_argument!( + let xs = arg_symbols[0]; + + use crate::low_level::HigherOrder::ListMapWithIndex; + + new_match_on_closure_argument!( env, procs, layout_cache, closure_data_symbol, closure_data_var, op, - [arg_symbols[0]], + ListMapWithIndex, + [xs], layout, assigned, hole From 5052fe68a433114e0bddd0e82d5a91205778ac5c Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 17 Oct 2021 19:40:34 +0200 Subject: [PATCH 40/51] Revert "code that makes new NewHigherOrderLowLevels, use this later" This reverts commit 2c702cc893571f10a664608f4c37b580d25f989c. --- compiler/mono/src/ir.rs | 112 ++-------------------------------------- 1 file changed, 4 insertions(+), 108 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index d0c8d8c479..b19bf7ac3d 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -4041,128 +4041,24 @@ pub fn with_hole<'a>( let layout = return_on_layout_error!(env, layout_cache.from_var(env.arena, ret_var, env.subs)); - let closure_data_symbol = arg_symbols[2]; - - macro_rules! new_match_on_closure_argument { - ($env:expr, $procs:expr, $layout_cache:expr, $closure_data_symbol:expr, $closure_data_var:expr, $op:expr, $ho:ident, [$($x:ident),* $(,)?], $layout: expr, $assigned:expr, $hole:expr) => {{ - let closure_data_layout = return_on_layout_error!( - $env, - $layout_cache.raw_from_var($env.arena, $closure_data_var, $env.subs) - ); - - let top_level = ProcLayout::from_raw($env.arena, closure_data_layout); - - let arena = $env.arena; - - let arg_layouts = top_level.arguments; - let ret_layout = top_level.result; - - match closure_data_layout { - RawFunctionLayout::Function(_, lambda_set, _) => { - lowlevel_match_on_lambda_set( - $env, - lambda_set, - $op, - $closure_data_symbol, - |top_level_function, closure_data, closure_env_layout, specialization_id| self::Call { - call_type: CallType::NewHigherOrderLowLevel { - op: $ho { $($x,)* }, - closure_env_layout, - specialization_id, - function_owns_closure_data: false, - function_env: closure_data_symbol, - function_name: top_level_function, - arg_layouts, - ret_layout, - }, - arguments: arena.alloc([$($x,)* top_level_function, closure_data]), - }, - $layout, - $assigned, - $hole, - ) - } - RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!("match_on_closure_argument received a zero-argument thunk"), - } - }}; -} - use LowLevel::*; match op { - ListMap => { - debug_assert_eq!(arg_symbols.len(), 2); - - let closure_index = 1; - let closure_data_symbol = arg_symbols[closure_index]; - let closure_data_var = args[closure_index].0; - let xs = arg_symbols[0]; - - let closure_data_layout = return_on_layout_error!( - env, - layout_cache.raw_from_var(env.arena, closure_data_var, env.subs) - ); - - let top_level = ProcLayout::from_raw(env.arena, closure_data_layout); - - let arena = env.arena; - - let arg_layouts = top_level.arguments; - let ret_layout = top_level.result; - - match closure_data_layout { - RawFunctionLayout::Function(_, lambda_set, _) => { - lowlevel_match_on_lambda_set( - env, - lambda_set, - op, - closure_data_symbol, - |top_level_function, - closure_data, - closure_env_layout, - specialization_id| self::Call { - call_type: CallType::NewHigherOrderLowLevel { - op: crate::low_level::HigherOrder::ListMap { xs }, - closure_env_layout, - specialization_id, - function_owns_closure_data: false, - function_env: closure_data_symbol, - function_name: top_level_function, - arg_layouts, - ret_layout, - }, - arguments: arena.alloc([xs, top_level_function, closure_data]), - }, - layout, - assigned, - hole, - ) - } - RawFunctionLayout::ZeroArgumentThunk(_) => { - unreachable!("match_on_closure_argument received a zero-argument thunk") - } - } - } - - ListMapWithIndex | ListKeepIf | ListKeepOks | ListKeepErrs | ListSortWith => { + ListMap | ListMapWithIndex | ListKeepIf | ListKeepOks | ListKeepErrs + | ListSortWith => { debug_assert_eq!(arg_symbols.len(), 2); let closure_index = 1; let closure_data_symbol = arg_symbols[closure_index]; let closure_data_var = args[closure_index].0; - let xs = arg_symbols[0]; - - use crate::low_level::HigherOrder::ListMapWithIndex; - - new_match_on_closure_argument!( + match_on_closure_argument!( env, procs, layout_cache, closure_data_symbol, closure_data_var, op, - ListMapWithIndex, - [xs], + [arg_symbols[0]], layout, assigned, hole From b8a2db793f3b16ee601202410299a0dc15848bf3 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 17 Oct 2021 19:44:24 +0200 Subject: [PATCH 41/51] Revert "Revert "code that makes new NewHigherOrderLowLevels, use this later"" This reverts commit 5052fe68a433114e0bddd0e82d5a91205778ac5c. --- compiler/mono/src/ir.rs | 112 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index b19bf7ac3d..d0c8d8c479 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -4041,24 +4041,128 @@ pub fn with_hole<'a>( let layout = return_on_layout_error!(env, layout_cache.from_var(env.arena, ret_var, env.subs)); + let closure_data_symbol = arg_symbols[2]; + + macro_rules! new_match_on_closure_argument { + ($env:expr, $procs:expr, $layout_cache:expr, $closure_data_symbol:expr, $closure_data_var:expr, $op:expr, $ho:ident, [$($x:ident),* $(,)?], $layout: expr, $assigned:expr, $hole:expr) => {{ + let closure_data_layout = return_on_layout_error!( + $env, + $layout_cache.raw_from_var($env.arena, $closure_data_var, $env.subs) + ); + + let top_level = ProcLayout::from_raw($env.arena, closure_data_layout); + + let arena = $env.arena; + + let arg_layouts = top_level.arguments; + let ret_layout = top_level.result; + + match closure_data_layout { + RawFunctionLayout::Function(_, lambda_set, _) => { + lowlevel_match_on_lambda_set( + $env, + lambda_set, + $op, + $closure_data_symbol, + |top_level_function, closure_data, closure_env_layout, specialization_id| self::Call { + call_type: CallType::NewHigherOrderLowLevel { + op: $ho { $($x,)* }, + closure_env_layout, + specialization_id, + function_owns_closure_data: false, + function_env: closure_data_symbol, + function_name: top_level_function, + arg_layouts, + ret_layout, + }, + arguments: arena.alloc([$($x,)* top_level_function, closure_data]), + }, + $layout, + $assigned, + $hole, + ) + } + RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!("match_on_closure_argument received a zero-argument thunk"), + } + }}; +} + use LowLevel::*; match op { - ListMap | ListMapWithIndex | ListKeepIf | ListKeepOks | ListKeepErrs - | ListSortWith => { + ListMap => { + debug_assert_eq!(arg_symbols.len(), 2); + + let closure_index = 1; + let closure_data_symbol = arg_symbols[closure_index]; + let closure_data_var = args[closure_index].0; + let xs = arg_symbols[0]; + + let closure_data_layout = return_on_layout_error!( + env, + layout_cache.raw_from_var(env.arena, closure_data_var, env.subs) + ); + + let top_level = ProcLayout::from_raw(env.arena, closure_data_layout); + + let arena = env.arena; + + let arg_layouts = top_level.arguments; + let ret_layout = top_level.result; + + match closure_data_layout { + RawFunctionLayout::Function(_, lambda_set, _) => { + lowlevel_match_on_lambda_set( + env, + lambda_set, + op, + closure_data_symbol, + |top_level_function, + closure_data, + closure_env_layout, + specialization_id| self::Call { + call_type: CallType::NewHigherOrderLowLevel { + op: crate::low_level::HigherOrder::ListMap { xs }, + closure_env_layout, + specialization_id, + function_owns_closure_data: false, + function_env: closure_data_symbol, + function_name: top_level_function, + arg_layouts, + ret_layout, + }, + arguments: arena.alloc([xs, top_level_function, closure_data]), + }, + layout, + assigned, + hole, + ) + } + RawFunctionLayout::ZeroArgumentThunk(_) => { + unreachable!("match_on_closure_argument received a zero-argument thunk") + } + } + } + + ListMapWithIndex | ListKeepIf | ListKeepOks | ListKeepErrs | ListSortWith => { debug_assert_eq!(arg_symbols.len(), 2); let closure_index = 1; let closure_data_symbol = arg_symbols[closure_index]; let closure_data_var = args[closure_index].0; - match_on_closure_argument!( + let xs = arg_symbols[0]; + + use crate::low_level::HigherOrder::ListMapWithIndex; + + new_match_on_closure_argument!( env, procs, layout_cache, closure_data_symbol, closure_data_var, op, - [arg_symbols[0]], + ListMapWithIndex, + [xs], layout, assigned, hole From 434ccbbf68733bc3913c9826d0846d6b0d1cdbb2 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 17 Oct 2021 20:42:29 +0200 Subject: [PATCH 42/51] working parallel implementation --- compiler/module/src/low_level.rs | 151 +++++++++++++++++--- compiler/mono/src/ir.rs | 237 ++++++++----------------------- 2 files changed, 194 insertions(+), 194 deletions(-) diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 8753d21af0..3b3e102367 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -107,6 +107,118 @@ pub enum LowLevel { ExpectTrue, } +macro_rules! first_order { + () => { + StrConcat + | StrJoinWith + | StrIsEmpty + | StrStartsWith + | StrStartsWithCodePt + | StrEndsWith + | StrSplit + | StrCountGraphemes + | StrFromInt + | StrFromUtf8 + | StrFromUtf8Range + | StrToUtf8 + | StrRepeat + | StrFromFloat + | ListLen + | ListGetUnsafe + | ListSet + | ListDrop + | ListDropAt + | ListSingle + | ListRepeat + | ListReverse + | ListConcat + | ListContains + | ListAppend + | ListPrepend + | ListJoin + | ListRange + | ListSwap + | DictSize + | DictEmpty + | DictInsert + | DictRemove + | DictContains + | DictGetUnsafe + | DictKeys + | DictValues + | DictUnion + | DictIntersection + | DictDifference + | SetFromList + | NumAdd + | NumAddWrap + | NumAddChecked + | NumSub + | NumSubWrap + | NumSubChecked + | NumMul + | NumMulWrap + | NumMulChecked + | NumGt + | NumGte + | NumLt + | NumLte + | NumCompare + | NumDivUnchecked + | NumRemUnchecked + | NumIsMultipleOf + | NumAbs + | NumNeg + | NumSin + | NumCos + | NumSqrtUnchecked + | NumLogUnchecked + | NumRound + | NumToFloat + | NumPow + | NumCeiling + | NumPowInt + | NumFloor + | NumIsFinite + | NumAtan + | NumAcos + | NumAsin + | NumBitwiseAnd + | NumBitwiseXor + | NumBitwiseOr + | NumShiftLeftBy + | NumShiftRightBy + | NumBytesToU16 + | NumBytesToU32 + | NumShiftRightZfBy + | NumIntCast + | Eq + | NotEq + | And + | Or + | Not + | Hash + | ExpectTrue + }; +} + +macro_rules! higher_order { + () => { + ListMap + | ListMap2 + | ListMap3 + | ListMapWithIndex + | ListKeepIf + | ListWalk + | ListWalkUntil + | ListWalkBackwards + | ListKeepOks + | ListKeepErrs + | ListSortWith + | DictWalk + }; +} + impl LowLevel { /// is one of the arguments always a function? /// An example is List.map. @@ -114,25 +226,28 @@ impl LowLevel { use LowLevel::*; match self { - StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt - | StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 - | StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | ListLen | ListGetUnsafe - | ListSet | ListDrop | ListDropAt | ListSingle | ListRepeat | ListReverse - | ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange - | ListSwap | DictSize | DictEmpty | DictInsert | DictRemove | DictContains - | DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection - | DictDifference | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub - | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte - | NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf - | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound - | NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan - | NumAcos | NumAsin | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy - | NumShiftRightBy | NumBytesToU16 | NumBytesToU32 | NumShiftRightZfBy | NumIntCast - | Eq | NotEq | And | Or | Not | Hash | ExpectTrue => false, + first_order!() => false, + higher_order!() => true, + } + } - ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk - | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith - | DictWalk => true, + pub fn function_argument_position(&self) -> usize { + use LowLevel::*; + + match self { + first_order!() => unreachable!(), + ListMap => 1, + ListMap2 => 2, + ListMap3 => 3, + ListMapWithIndex => 1, + ListKeepIf => 1, + ListWalk => 2, + ListWalkUntil => 2, + ListWalkBackwards => 2, + ListKeepOks => 1, + ListKeepErrs => 1, + ListSortWith => 1, + DictWalk => 2, } } } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index d0c8d8c479..9f3a61738b 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -2707,48 +2707,6 @@ fn specialize_naked_symbol<'a>( ) } -macro_rules! match_on_closure_argument { - ($env:expr, $procs:expr, $layout_cache:expr, $closure_data_symbol:expr, $closure_data_var:expr, $op:expr, [$($x:expr),* $(,)?], $layout: expr, $assigned:expr, $hole:expr) => {{ - let closure_data_layout = return_on_layout_error!( - $env, - $layout_cache.raw_from_var($env.arena, $closure_data_var, $env.subs) - ); - - let top_level = ProcLayout::from_raw($env.arena, closure_data_layout); - - let arena = $env.arena; - - let arg_layouts = top_level.arguments; - let ret_layout = top_level.result; - - match closure_data_layout { - RawFunctionLayout::Function(_, lambda_set, _) => { - lowlevel_match_on_lambda_set( - $env, - lambda_set, - $op, - $closure_data_symbol, - |top_level_function, closure_data, closure_env_layout, specialization_id| self::Call { - call_type: CallType::HigherOrderLowLevel { - op: $op, - closure_env_layout, - specialization_id, - function_owns_closure_data: false, - arg_layouts, - ret_layout, - }, - arguments: arena.alloc([$($x,)* top_level_function, closure_data]), - }, - $layout, - $assigned, - $hole, - ) - } - RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!("match_on_closure_argument received a zero-argument thunk"), - } - }}; -} - fn try_make_literal<'a>( env: &mut Env<'a, '_>, can_expr: &roc_can::expr::Expr, @@ -4041,61 +3999,11 @@ pub fn with_hole<'a>( let layout = return_on_layout_error!(env, layout_cache.from_var(env.arena, ret_var, env.subs)); - let closure_data_symbol = arg_symbols[2]; - - macro_rules! new_match_on_closure_argument { - ($env:expr, $procs:expr, $layout_cache:expr, $closure_data_symbol:expr, $closure_data_var:expr, $op:expr, $ho:ident, [$($x:ident),* $(,)?], $layout: expr, $assigned:expr, $hole:expr) => {{ - let closure_data_layout = return_on_layout_error!( - $env, - $layout_cache.raw_from_var($env.arena, $closure_data_var, $env.subs) - ); - - let top_level = ProcLayout::from_raw($env.arena, closure_data_layout); - - let arena = $env.arena; - - let arg_layouts = top_level.arguments; - let ret_layout = top_level.result; - - match closure_data_layout { - RawFunctionLayout::Function(_, lambda_set, _) => { - lowlevel_match_on_lambda_set( - $env, - lambda_set, - $op, - $closure_data_symbol, - |top_level_function, closure_data, closure_env_layout, specialization_id| self::Call { - call_type: CallType::NewHigherOrderLowLevel { - op: $ho { $($x,)* }, - closure_env_layout, - specialization_id, - function_owns_closure_data: false, - function_env: closure_data_symbol, - function_name: top_level_function, - arg_layouts, - ret_layout, - }, - arguments: arena.alloc([$($x,)* top_level_function, closure_data]), - }, - $layout, - $assigned, - $hole, - ) - } - RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!("match_on_closure_argument received a zero-argument thunk"), - } - }}; -} - - use LowLevel::*; - match op { - ListMap => { - debug_assert_eq!(arg_symbols.len(), 2); - - let closure_index = 1; + macro_rules! match_on_closure_argument { + ( $ho:ident, [$($x:ident),* $(,)?]) => {{ + let closure_index = op.function_argument_position(); let closure_data_symbol = arg_symbols[closure_index]; let closure_data_var = args[closure_index].0; - let xs = arg_symbols[0]; let closure_data_layout = return_on_layout_error!( env, @@ -4110,18 +4018,15 @@ pub fn with_hole<'a>( let ret_layout = top_level.result; match closure_data_layout { - RawFunctionLayout::Function(_, lambda_set, _) => { + RawFunctionLayout::Function(_, lambda_set, _) => { lowlevel_match_on_lambda_set( env, lambda_set, op, closure_data_symbol, - |top_level_function, - closure_data, - closure_env_layout, - specialization_id| self::Call { + |top_level_function, closure_data, closure_env_layout, specialization_id| self::Call { call_type: CallType::NewHigherOrderLowLevel { - op: crate::low_level::HigherOrder::ListMap { xs }, + op: crate::low_level::HigherOrder::$ho { $($x,)* }, closure_env_layout, specialization_id, function_owns_closure_data: false, @@ -4130,66 +4035,30 @@ pub fn with_hole<'a>( arg_layouts, ret_layout, }, - arguments: arena.alloc([xs, top_level_function, closure_data]), + arguments: arena.alloc([$($x,)* top_level_function, closure_data]), }, layout, assigned, hole, ) } - RawFunctionLayout::ZeroArgumentThunk(_) => { - unreachable!("match_on_closure_argument received a zero-argument thunk") - } + RawFunctionLayout::ZeroArgumentThunk(_) => unreachable!("match_on_closure_argument received a zero-argument thunk"), } - } + }}; + } - ListMapWithIndex | ListKeepIf | ListKeepOks | ListKeepErrs | ListSortWith => { - debug_assert_eq!(arg_symbols.len(), 2); - - let closure_index = 1; - let closure_data_symbol = arg_symbols[closure_index]; - let closure_data_var = args[closure_index].0; - - let xs = arg_symbols[0]; - - use crate::low_level::HigherOrder::ListMapWithIndex; - - new_match_on_closure_argument!( - env, - procs, - layout_cache, - closure_data_symbol, - closure_data_var, - op, - ListMapWithIndex, - [xs], - layout, - assigned, - hole - ) - } - ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => { + macro_rules! walk { + ($oh:ident) => {{ debug_assert_eq!(arg_symbols.len(), 3); const LIST_INDEX: usize = 0; const DEFAULT_INDEX: usize = 1; const CLOSURE_INDEX: usize = 2; - let closure_data_symbol = arg_symbols[CLOSURE_INDEX]; - let closure_data_var = args[CLOSURE_INDEX].0; + let xs = arg_symbols[LIST_INDEX]; + let state = arg_symbols[DEFAULT_INDEX]; - let stmt = match_on_closure_argument!( - env, - procs, - layout_cache, - closure_data_symbol, - closure_data_var, - op, - [arg_symbols[LIST_INDEX], arg_symbols[DEFAULT_INDEX]], - layout, - assigned, - hole - ); + let stmt = match_on_closure_argument!($oh, [xs, state]); // because of a hack to implement List.product and List.sum, we need to also // assign to symbols here. Normally the arguments to a lowlevel function are @@ -4226,46 +4095,62 @@ pub fn with_hole<'a>( arg_symbols[CLOSURE_INDEX], stmt, ) + }}; + } + + use LowLevel::*; + match op { + ListMap => { + debug_assert_eq!(arg_symbols.len(), 2); + let xs = arg_symbols[0]; + match_on_closure_argument!(ListMap, [xs]) } + + ListMapWithIndex => { + debug_assert_eq!(arg_symbols.len(), 2); + let xs = arg_symbols[0]; + match_on_closure_argument!(ListMapWithIndex, [xs]) + } + ListKeepIf => { + debug_assert_eq!(arg_symbols.len(), 2); + let xs = arg_symbols[0]; + match_on_closure_argument!(ListKeepIf, [xs]) + } + ListKeepOks => { + debug_assert_eq!(arg_symbols.len(), 2); + let xs = arg_symbols[0]; + match_on_closure_argument!(ListKeepOks, [xs]) + } + ListKeepErrs => { + debug_assert_eq!(arg_symbols.len(), 2); + let xs = arg_symbols[0]; + match_on_closure_argument!(ListKeepErrs, [xs]) + } + ListSortWith => { + debug_assert_eq!(arg_symbols.len(), 2); + let xs = arg_symbols[0]; + match_on_closure_argument!(ListSortWith, [xs]) + } + ListWalk => walk!(ListWalk), + ListWalkUntil => walk!(ListWalkUntil), + ListWalkBackwards => walk!(ListWalkBackwards), + DictWalk => walk!(DictWalk), ListMap2 => { debug_assert_eq!(arg_symbols.len(), 3); - let closure_index = 2; - let closure_data_symbol = arg_symbols[closure_index]; - let closure_data_var = args[closure_index].0; + let xs = arg_symbols[0]; + let ys = arg_symbols[1]; - match_on_closure_argument!( - env, - procs, - layout_cache, - closure_data_symbol, - closure_data_var, - op, - [arg_symbols[0], arg_symbols[1]], - layout, - assigned, - hole - ) + match_on_closure_argument!(ListMap2, [xs, ys]) } ListMap3 => { debug_assert_eq!(arg_symbols.len(), 4); - let closure_index = 3; - let closure_data_symbol = arg_symbols[closure_index]; - let closure_data_var = args[closure_index].0; + let xs = arg_symbols[0]; + let ys = arg_symbols[1]; + let zs = arg_symbols[2]; - match_on_closure_argument!( - env, - procs, - layout_cache, - closure_data_symbol, - closure_data_var, - op, - [arg_symbols[0], arg_symbols[1], arg_symbols[2]], - layout, - assigned, - hole - ) + match_on_closure_argument!(ListMap3, [xs, ys, zs]) } _ => { let call = self::Call { From a4f7ddb1f5989a7c7cc73f6446d1361ced24bbaa Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 17 Oct 2021 20:45:49 +0200 Subject: [PATCH 43/51] rename --- compiler/gen_dev/src/lib.rs | 1 - compiler/gen_llvm/src/llvm/build.rs | 471 ---------------------------- compiler/mono/src/alias_analysis.rs | 219 +------------ compiler/mono/src/borrow.rs | 135 +------- compiler/mono/src/inc_dec.rs | 218 +------------ compiler/mono/src/ir.rs | 20 -- 6 files changed, 3 insertions(+), 1061 deletions(-) diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index d11fb50dc4..5ef03a3a42 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -786,7 +786,6 @@ where CallType::ByName { .. } => {} CallType::LowLevel { .. } => {} CallType::HigherOrderLowLevel { .. } => {} - CallType::NewHigherOrderLowLevel { .. } => {} CallType::Foreign { .. } => {} } } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 48c4c71eea..eada14eeb7 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -952,32 +952,6 @@ pub fn build_exp_call<'a, 'ctx, 'env>( } CallType::HigherOrderLowLevel { - op, - function_owns_closure_data, - specialization_id, - arg_layouts, - ret_layout, - .. - } => { - let bytes = specialization_id.to_bytes(); - let callee_var = CalleeSpecVar(&bytes); - let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap(); - - run_higher_order_low_level( - env, - layout_ids, - scope, - layout, - *op, - func_spec, - arg_layouts, - ret_layout, - *function_owns_closure_data, - arguments, - ) - } - - CallType::NewHigherOrderLowLevel { op, function_owns_closure_data, specialization_id, @@ -4895,451 +4869,6 @@ fn run_new_higher_order_low_level<'a, 'ctx, 'env>( } } -#[allow(clippy::too_many_arguments)] -fn run_higher_order_low_level<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - layout_ids: &mut LayoutIds<'a>, - scope: &Scope<'a, 'ctx>, - return_layout: &Layout<'a>, - op: LowLevel, - func_spec: FuncSpec, - argument_layouts: &[Layout<'a>], - result_layout: &Layout<'a>, - function_owns_closure_data: bool, - args: &[Symbol], -) -> BasicValueEnum<'ctx> { - use LowLevel::*; - - debug_assert!(op.is_higher_order()); - - // macros because functions cause lifetime issues related to the `env` or `layout_ids` - macro_rules! passed_function_at_index { - ($index:expr) => {{ - let function_symbol = args[$index]; - - function_value_by_func_spec( - env, - func_spec, - function_symbol, - argument_layouts, - return_layout, - ) - }}; - } - - macro_rules! list_walk { - ($variant:expr) => {{ - let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); - - let (default, default_layout) = load_symbol_and_layout(scope, &args[1]); - - let function = passed_function_at_index!(2); - - let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[3]); - - match list_layout { - Layout::Builtin(Builtin::EmptyList) => default, - Layout::Builtin(Builtin::List(element_layout)) => { - let argument_layouts = &[*default_layout, **element_layout]; - - let roc_function_call = roc_function_call( - env, - layout_ids, - function, - closure, - closure_layout, - function_owns_closure_data, - argument_layouts, - ); - - crate::llvm::build_list::list_walk_generic( - env, - layout_ids, - roc_function_call, - result_layout, - list, - element_layout, - default, - default_layout, - $variant, - ) - } - _ => unreachable!("invalid list layout"), - } - }}; - } - match op { - ListMap => { - // List.map : List before, (before -> after) -> List after - debug_assert_eq!(args.len(), 3); - - let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); - - let function = passed_function_at_index!(1); - - let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]); - - match (list_layout, return_layout) { - (Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), - ( - Layout::Builtin(Builtin::List(element_layout)), - Layout::Builtin(Builtin::List(result_layout)), - ) => { - let argument_layouts = &[**element_layout]; - - let roc_function_call = roc_function_call( - env, - layout_ids, - function, - closure, - closure_layout, - function_owns_closure_data, - argument_layouts, - ); - - list_map(env, roc_function_call, list, element_layout, result_layout) - } - _ => unreachable!("invalid list layout"), - } - } - ListMap2 => { - debug_assert_eq!(args.len(), 4); - - let (list1, list1_layout) = load_symbol_and_layout(scope, &args[0]); - let (list2, list2_layout) = load_symbol_and_layout(scope, &args[1]); - - let function = passed_function_at_index!(2); - let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[3]); - - match (list1_layout, list2_layout, return_layout) { - ( - Layout::Builtin(Builtin::List(element1_layout)), - Layout::Builtin(Builtin::List(element2_layout)), - Layout::Builtin(Builtin::List(result_layout)), - ) => { - let argument_layouts = &[**element1_layout, **element2_layout]; - - let roc_function_call = roc_function_call( - env, - layout_ids, - function, - closure, - closure_layout, - function_owns_closure_data, - argument_layouts, - ); - - list_map2( - env, - layout_ids, - roc_function_call, - list1, - list2, - element1_layout, - element2_layout, - result_layout, - ) - } - (Layout::Builtin(Builtin::EmptyList), _, _) - | (_, Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), - _ => unreachable!("invalid list layout"), - } - } - ListMap3 => { - debug_assert_eq!(args.len(), 5); - - let (list1, list1_layout) = load_symbol_and_layout(scope, &args[0]); - let (list2, list2_layout) = load_symbol_and_layout(scope, &args[1]); - let (list3, list3_layout) = load_symbol_and_layout(scope, &args[2]); - - let function = passed_function_at_index!(3); - let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[4]); - - match (list1_layout, list2_layout, list3_layout, return_layout) { - ( - Layout::Builtin(Builtin::List(element1_layout)), - Layout::Builtin(Builtin::List(element2_layout)), - Layout::Builtin(Builtin::List(element3_layout)), - Layout::Builtin(Builtin::List(result_layout)), - ) => { - let argument_layouts = - &[**element1_layout, **element2_layout, **element3_layout]; - - let roc_function_call = roc_function_call( - env, - layout_ids, - function, - closure, - closure_layout, - function_owns_closure_data, - argument_layouts, - ); - - list_map3( - env, - layout_ids, - roc_function_call, - list1, - list2, - list3, - element1_layout, - element2_layout, - element3_layout, - result_layout, - ) - } - (Layout::Builtin(Builtin::EmptyList), _, _, _) - | (_, Layout::Builtin(Builtin::EmptyList), _, _) - | (_, _, Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), - _ => unreachable!("invalid list layout"), - } - } - ListMapWithIndex => { - // List.mapWithIndex : List before, (Nat, before -> after) -> List after - debug_assert_eq!(args.len(), 3); - - let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); - - let function = passed_function_at_index!(1); - - let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]); - - match (list_layout, return_layout) { - (Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), - ( - Layout::Builtin(Builtin::List(element_layout)), - Layout::Builtin(Builtin::List(result_layout)), - ) => { - let argument_layouts = &[Layout::Builtin(Builtin::Usize), **element_layout]; - - let roc_function_call = roc_function_call( - env, - layout_ids, - function, - closure, - closure_layout, - function_owns_closure_data, - argument_layouts, - ); - - list_map_with_index(env, roc_function_call, list, element_layout, result_layout) - } - _ => unreachable!("invalid list layout"), - } - } - ListKeepIf => { - // List.keepIf : List elem, (elem -> Bool) -> List elem - debug_assert_eq!(args.len(), 3); - - let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); - - let function = passed_function_at_index!(1); - - let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]); - - match list_layout { - Layout::Builtin(Builtin::EmptyList) => empty_list(env), - Layout::Builtin(Builtin::List(element_layout)) => { - let argument_layouts = &[**element_layout]; - - let roc_function_call = roc_function_call( - env, - layout_ids, - function, - closure, - closure_layout, - function_owns_closure_data, - argument_layouts, - ); - - list_keep_if(env, layout_ids, roc_function_call, list, element_layout) - } - _ => unreachable!("invalid list layout"), - } - } - ListKeepOks => { - // List.keepOks : List before, (before -> Result after *) -> List after - debug_assert_eq!(args.len(), 3); - - let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); - - let function = passed_function_at_index!(1); - - let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]); - - match (list_layout, return_layout) { - (_, Layout::Builtin(Builtin::EmptyList)) - | (Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), - ( - Layout::Builtin(Builtin::List(before_layout)), - Layout::Builtin(Builtin::List(after_layout)), - ) => { - let argument_layouts = &[**before_layout]; - - let roc_function_call = roc_function_call( - env, - layout_ids, - function, - closure, - closure_layout, - function_owns_closure_data, - argument_layouts, - ); - - list_keep_oks( - env, - layout_ids, - roc_function_call, - result_layout, - list, - before_layout, - after_layout, - ) - } - (other1, other2) => { - unreachable!("invalid list layouts:\n{:?}\n{:?}", other1, other2) - } - } - } - ListKeepErrs => { - // List.keepErrs : List before, (before -> Result * after) -> List after - debug_assert_eq!(args.len(), 3); - - let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); - - let function = passed_function_at_index!(1); - - let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]); - - match (list_layout, return_layout) { - (_, Layout::Builtin(Builtin::EmptyList)) - | (Layout::Builtin(Builtin::EmptyList), _) => empty_list(env), - ( - Layout::Builtin(Builtin::List(before_layout)), - Layout::Builtin(Builtin::List(after_layout)), - ) => { - let argument_layouts = &[**before_layout]; - - let roc_function_call = roc_function_call( - env, - layout_ids, - function, - closure, - closure_layout, - function_owns_closure_data, - argument_layouts, - ); - - list_keep_errs( - env, - layout_ids, - roc_function_call, - result_layout, - list, - before_layout, - after_layout, - ) - } - (other1, other2) => { - unreachable!("invalid list layouts:\n{:?}\n{:?}", other1, other2) - } - } - } - ListWalk => { - list_walk!(crate::llvm::build_list::ListWalk::Walk) - } - ListWalkUntil => { - list_walk!(crate::llvm::build_list::ListWalk::WalkUntil) - } - ListWalkBackwards => { - list_walk!(crate::llvm::build_list::ListWalk::WalkBackwards) - } - ListSortWith => { - // List.sortWith : List a, (a, a -> Ordering) -> List a - debug_assert_eq!(args.len(), 3); - - let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); - - let function = passed_function_at_index!(1); - - let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[2]); - - match list_layout { - Layout::Builtin(Builtin::EmptyList) => empty_list(env), - Layout::Builtin(Builtin::List(element_layout)) => { - use crate::llvm::bitcode::build_compare_wrapper; - - let argument_layouts = &[**element_layout, **element_layout]; - - let compare_wrapper = - build_compare_wrapper(env, function, closure_layout, element_layout) - .as_global_value() - .as_pointer_value(); - - let roc_function_call = roc_function_call( - env, - layout_ids, - function, - closure, - closure_layout, - function_owns_closure_data, - argument_layouts, - ); - - list_sort_with( - env, - roc_function_call, - compare_wrapper, - list, - element_layout, - ) - } - _ => unreachable!("invalid list layout"), - } - } - DictWalk => { - debug_assert_eq!(args.len(), 4); - - let (dict, dict_layout) = load_symbol_and_layout(scope, &args[0]); - let (default, default_layout) = load_symbol_and_layout(scope, &args[1]); - let function = passed_function_at_index!(2); - let (closure, closure_layout) = load_symbol_and_lambda_set(scope, &args[3]); - - match dict_layout { - Layout::Builtin(Builtin::EmptyDict) => { - // no elements, so `key` is not in here - panic!("key type unknown") - } - Layout::Builtin(Builtin::Dict(key_layout, value_layout)) => { - let argument_layouts = &[*default_layout, **key_layout, **value_layout]; - - let roc_function_call = roc_function_call( - env, - layout_ids, - function, - closure, - closure_layout, - function_owns_closure_data, - argument_layouts, - ); - - dict_walk( - env, - roc_function_call, - dict, - default, - key_layout, - value_layout, - default_layout, - ) - } - _ => unreachable!("invalid dict layout"), - } - } - _ => unreachable!(), - } -} - // TODO: Fix me! I should be different in tests vs. user code! fn expect_failed() { panic!("An expectation failed!"); diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 43abd3ed11..13485868d1 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -605,7 +605,7 @@ fn call_spec( *update_mode, call.arguments, ), - NewHigherOrderLowLevel { + HigherOrderLowLevel { specialization_id, closure_env_layout, op, @@ -798,223 +798,6 @@ fn call_spec( let result_type = layout_spec(builder, layout)?; - builder.add_unknown_with(block, &arguments, result_type) - } - HigherOrderLowLevel { - specialization_id, - closure_env_layout, - op, - arg_layouts, - ret_layout, - .. - } => { - let array = specialization_id.to_bytes(); - let spec_var = CalleeSpecVar(&array); - - let symbol = { - use roc_module::low_level::LowLevel::*; - - match op { - ListMap | ListMapWithIndex => call.arguments[1], - ListMap2 => call.arguments[2], - ListMap3 => call.arguments[3], - ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => call.arguments[2], - ListKeepIf | ListKeepOks | ListKeepErrs => call.arguments[1], - ListSortWith => call.arguments[1], - _ => unreachable!(), - } - }; - - let it = arg_layouts.iter().copied(); - let bytes = func_name_bytes_help(symbol, it, *ret_layout); - let name = FuncName(&bytes); - let module = MOD_APP; - - { - use roc_module::low_level::LowLevel::*; - - match op { - DictWalk => { - let dict = env.symbols[&call.arguments[0]]; - let state = env.symbols[&call.arguments[1]]; - let closure_env = env.symbols[&call.arguments[3]]; - - let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?; - let _cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?; - - let first = builder.add_bag_get(block, bag)?; - - let key = builder.add_get_tuple_field(block, first, 0)?; - let val = builder.add_get_tuple_field(block, first, 1)?; - - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[state, key, val])? - } else { - builder.add_make_tuple(block, &[state, key, val, closure_env])? - }; - builder.add_call(block, spec_var, module, name, argument)?; - } - - ListWalk | ListWalkBackwards | ListWalkUntil => { - let list = env.symbols[&call.arguments[0]]; - let state = env.symbols[&call.arguments[1]]; - let closure_env = env.symbols[&call.arguments[3]]; - - let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; - - let first = builder.add_bag_get(block, bag)?; - - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[state, first])? - } else { - builder.add_make_tuple(block, &[state, first, closure_env])? - }; - builder.add_call(block, spec_var, module, name, argument)?; - } - - ListMapWithIndex => { - let list = env.symbols[&call.arguments[0]]; - let closure_env = env.symbols[&call.arguments[2]]; - - let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; - - let first = builder.add_bag_get(block, bag)?; - let index = builder.add_make_tuple(block, &[])?; - - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[index, first])? - } else { - builder.add_make_tuple(block, &[index, first, closure_env])? - }; - builder.add_call(block, spec_var, module, name, argument)?; - } - - ListMap => { - let list1 = env.symbols[&call.arguments[0]]; - let closure_env = env.symbols[&call.arguments[2]]; - - let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; - let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; - - let elem1 = builder.add_bag_get(block, bag1)?; - - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[elem1])? - } else { - builder.add_make_tuple(block, &[elem1, closure_env])? - }; - builder.add_call(block, spec_var, module, name, argument)?; - } - - ListSortWith => { - let list1 = env.symbols[&call.arguments[0]]; - let closure_env = env.symbols[&call.arguments[2]]; - - let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; - let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; - - let elem1 = builder.add_bag_get(block, bag1)?; - - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[elem1, elem1])? - } else { - builder.add_make_tuple(block, &[elem1, elem1, closure_env])? - }; - builder.add_call(block, spec_var, module, name, argument)?; - } - - ListMap2 => { - let list1 = env.symbols[&call.arguments[0]]; - let list2 = env.symbols[&call.arguments[1]]; - let closure_env = env.symbols[&call.arguments[3]]; - - let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; - let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; - let elem1 = builder.add_bag_get(block, bag1)?; - - let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; - let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?; - let elem2 = builder.add_bag_get(block, bag2)?; - - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[elem1, elem2])? - } else { - builder.add_make_tuple(block, &[elem1, elem2, closure_env])? - }; - builder.add_call(block, spec_var, module, name, argument)?; - } - - ListMap3 => { - let list1 = env.symbols[&call.arguments[0]]; - let list2 = env.symbols[&call.arguments[1]]; - let list3 = env.symbols[&call.arguments[2]]; - let closure_env = env.symbols[&call.arguments[4]]; - - let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?; - let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?; - let elem1 = builder.add_bag_get(block, bag1)?; - - let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?; - let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?; - let elem2 = builder.add_bag_get(block, bag2)?; - - let bag3 = builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?; - let _cell3 = builder.add_get_tuple_field(block, list3, LIST_CELL_INDEX)?; - let elem3 = builder.add_bag_get(block, bag3)?; - - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[elem1, elem2, elem3])? - } else { - builder.add_make_tuple(block, &[elem1, elem2, elem3, closure_env])? - }; - builder.add_call(block, spec_var, module, name, argument)?; - } - - ListKeepIf | ListKeepOks | ListKeepErrs => { - let list = env.symbols[&call.arguments[0]]; - let closure_env = env.symbols[&call.arguments[2]]; - - let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; - // let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; - - let first = builder.add_bag_get(block, bag)?; - - let argument = if closure_env_layout.is_none() { - builder.add_make_tuple(block, &[first])? - } else { - builder.add_make_tuple(block, &[first, closure_env])? - }; - let result = builder.add_call(block, spec_var, module, name, argument)?; - let unit = builder.add_tuple_type(&[])?; - builder.add_unknown_with(block, &[result], unit)?; - } - - _ => { - // fake a call to the function argument - // to make sure the function is specialized - - // very invalid - let arg_value_id = build_tuple_value(builder, env, block, &[])?; - - builder.add_call(block, spec_var, module, name, arg_value_id)?; - } - } - } - - // TODO overly pessimstic - // filter_map because one of the arguments is a function name, which - // is not defined in the env - let arguments: Vec<_> = call - .arguments - .iter() - .filter_map(|symbol| env.symbols.get(symbol)) - .copied() - .collect(); - - let result_type = layout_spec(builder, layout)?; - builder.add_unknown_with(block, &arguments, result_type) } } diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 9097ca8040..b337c5f87f 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -593,7 +593,7 @@ impl<'a> BorrowInfState<'a> { self.own_args_using_bools(arguments, ps); } - NewHigherOrderLowLevel { + HigherOrderLowLevel { op, arg_layouts, ret_layout, @@ -678,138 +678,6 @@ impl<'a> BorrowInfState<'a> { } } - HigherOrderLowLevel { - op, - arg_layouts, - ret_layout, - .. - } => { - use roc_module::low_level::LowLevel::*; - - debug_assert!(op.is_higher_order()); - - let closure_layout = ProcLayout { - arguments: arg_layouts, - result: *ret_layout, - }; - - match op { - ListMap | ListKeepIf | ListKeepOks | ListKeepErrs => { - match param_map.get_symbol(arguments[1], closure_layout) { - Some(function_ps) => { - // own the list if the function wants to own the element - if !function_ps[0].borrow { - self.own_var(arguments[0]); - } - - // own the closure environment if the function needs to own it - if let Some(false) = function_ps.get(1).map(|p| p.borrow) { - self.own_var(arguments[2]); - } - } - None => unreachable!(), - } - } - ListMapWithIndex => { - match param_map.get_symbol(arguments[1], closure_layout) { - Some(function_ps) => { - // own the list if the function wants to own the element - if !function_ps[1].borrow { - self.own_var(arguments[0]); - } - - // own the closure environment if the function needs to own it - if let Some(false) = function_ps.get(2).map(|p| p.borrow) { - self.own_var(arguments[2]); - } - } - None => unreachable!(), - } - } - ListMap2 => match param_map.get_symbol(arguments[2], closure_layout) { - Some(function_ps) => { - // own the lists if the function wants to own the element - if !function_ps[0].borrow { - self.own_var(arguments[0]); - } - - if !function_ps[1].borrow { - self.own_var(arguments[1]); - } - - // own the closure environment if the function needs to own it - if let Some(false) = function_ps.get(2).map(|p| p.borrow) { - self.own_var(arguments[3]); - } - } - None => unreachable!(), - }, - ListMap3 => match param_map.get_symbol(arguments[3], closure_layout) { - Some(function_ps) => { - // own the lists if the function wants to own the element - if !function_ps[0].borrow { - self.own_var(arguments[0]); - } - if !function_ps[1].borrow { - self.own_var(arguments[1]); - } - if !function_ps[2].borrow { - self.own_var(arguments[2]); - } - - // own the closure environment if the function needs to own it - if let Some(false) = function_ps.get(3).map(|p| p.borrow) { - self.own_var(arguments[4]); - } - } - None => unreachable!(), - }, - ListSortWith => { - match param_map.get_symbol(arguments[1], closure_layout) { - Some(function_ps) => { - // always own the input list - self.own_var(arguments[0]); - - // own the closure environment if the function needs to own it - if let Some(false) = function_ps.get(2).map(|p| p.borrow) { - self.own_var(arguments[2]); - } - } - None => unreachable!(), - } - } - ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => { - match param_map.get_symbol(arguments[2], closure_layout) { - Some(function_ps) => { - // own the default value if the function wants to own it - if !function_ps[0].borrow { - self.own_var(arguments[1]); - } - - // own the data structure if the function wants to own the element - if !function_ps[1].borrow { - self.own_var(arguments[0]); - } - - // own the closure environment if the function needs to own it - if let Some(false) = function_ps.get(2).map(|p| p.borrow) { - self.own_var(arguments[3]); - } - } - None => unreachable!(), - } - } - _ => { - // very unsure what demand RunLowLevel should place upon its arguments - self.own_var(z); - - let ps = lowlevel_borrow_signature(self.arena, *op); - - self.own_args_using_bools(arguments, ps); - } - } - } - Foreign { .. } => { // very unsure what demand ForeignCall should place upon its arguments self.own_var(z); @@ -1156,7 +1024,6 @@ fn call_info_call<'a>(call: &crate::ir::Call<'a>, info: &mut CallInfo<'a>) { Foreign { .. } => {} LowLevel { .. } => {} HigherOrderLowLevel { .. } => {} - NewHigherOrderLowLevel { .. } => {} } } diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index 195ad255c0..577dd3790c 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -463,7 +463,7 @@ impl<'a> Context<'a> { &*self.arena.alloc(Stmt::Let(z, v, l, b)) } - NewHigherOrderLowLevel { + HigherOrderLowLevel { op, closure_env_layout, specialization_id, @@ -620,222 +620,6 @@ impl<'a> Context<'a> { } } - HigherOrderLowLevel { - op, - closure_env_layout, - specialization_id, - arg_layouts, - ret_layout, - .. - } => { - macro_rules! create_call { - ($borrows:expr) => { - Expr::Call(crate::ir::Call { - call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) { - HigherOrderLowLevel { - op: *op, - closure_env_layout: *closure_env_layout, - function_owns_closure_data: true, - specialization_id: *specialization_id, - arg_layouts, - ret_layout: *ret_layout, - } - } else { - call_type - }, - arguments, - }) - }; - } - - macro_rules! decref_if_owned { - ($borrows:expr, $argument:expr, $stmt:expr) => { - if !$borrows { - self.arena.alloc(Stmt::Refcounting( - ModifyRc::DecRef($argument), - self.arena.alloc($stmt), - )) - } else { - $stmt - } - }; - } - - const FUNCTION: bool = BORROWED; - const CLOSURE_DATA: bool = BORROWED; - - let function_layout = ProcLayout { - arguments: arg_layouts, - result: *ret_layout, - }; - - match op { - roc_module::low_level::LowLevel::ListMap - | roc_module::low_level::LowLevel::ListKeepIf - | roc_module::low_level::LowLevel::ListKeepOks - | roc_module::low_level::LowLevel::ListKeepErrs => { - match self.param_map.get_symbol(arguments[1], function_layout) { - Some(function_ps) => { - let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA]; - - let b = self.add_dec_after_lowlevel( - arguments, - &borrows, - b, - b_live_vars, - ); - - // if the list is owned, then all elements have been consumed, but not the list itself - let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b); - - let v = create_call!(function_ps.get(1)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - None => unreachable!(), - } - } - roc_module::low_level::LowLevel::ListMapWithIndex => { - match self.param_map.get_symbol(arguments[1], function_layout) { - Some(function_ps) => { - let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA]; - - let b = self.add_dec_after_lowlevel( - arguments, - &borrows, - b, - b_live_vars, - ); - - let b = decref_if_owned!(function_ps[1].borrow, arguments[0], b); - - let v = create_call!(function_ps.get(2)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - None => unreachable!(), - } - } - roc_module::low_level::LowLevel::ListMap2 => { - match self.param_map.get_symbol(arguments[2], function_layout) { - Some(function_ps) => { - let borrows = [ - function_ps[0].borrow, - function_ps[1].borrow, - FUNCTION, - CLOSURE_DATA, - ]; - - let b = self.add_dec_after_lowlevel( - arguments, - &borrows, - b, - b_live_vars, - ); - - let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b); - let b = decref_if_owned!(function_ps[1].borrow, arguments[1], b); - - let v = create_call!(function_ps.get(2)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - None => unreachable!(), - } - } - roc_module::low_level::LowLevel::ListMap3 => { - match self.param_map.get_symbol(arguments[3], function_layout) { - Some(function_ps) => { - let borrows = [ - function_ps[0].borrow, - function_ps[1].borrow, - function_ps[2].borrow, - FUNCTION, - CLOSURE_DATA, - ]; - - let b = self.add_dec_after_lowlevel( - arguments, - &borrows, - b, - b_live_vars, - ); - - let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b); - let b = decref_if_owned!(function_ps[1].borrow, arguments[1], b); - let b = decref_if_owned!(function_ps[2].borrow, arguments[2], b); - - let v = create_call!(function_ps.get(3)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - None => unreachable!(), - } - } - roc_module::low_level::LowLevel::ListSortWith => { - match self.param_map.get_symbol(arguments[1], function_layout) { - Some(function_ps) => { - let borrows = [OWNED, FUNCTION, CLOSURE_DATA]; - - let b = self.add_dec_after_lowlevel( - arguments, - &borrows, - b, - b_live_vars, - ); - - let v = create_call!(function_ps.get(2)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - None => unreachable!(), - } - } - roc_module::low_level::LowLevel::ListWalk - | roc_module::low_level::LowLevel::ListWalkUntil - | roc_module::low_level::LowLevel::ListWalkBackwards - | roc_module::low_level::LowLevel::DictWalk => { - match self.param_map.get_symbol(arguments[2], function_layout) { - Some(function_ps) => { - // borrow data structure based on first argument of the folded function - // borrow the default based on second argument of the folded function - let borrows = [ - function_ps[1].borrow, - function_ps[0].borrow, - FUNCTION, - CLOSURE_DATA, - ]; - - let b = self.add_dec_after_lowlevel( - arguments, - &borrows, - b, - b_live_vars, - ); - - let b = decref_if_owned!(function_ps[1].borrow, arguments[0], b); - - let v = create_call!(function_ps.get(2)); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - None => unreachable!(), - } - } - _ => { - let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op); - let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars); - - let v = Expr::Call(crate::ir::Call { - call_type, - arguments, - }); - - &*self.arena.alloc(Stmt::Let(z, v, l, b)) - } - } - } - Foreign { .. } => { let ps = crate::borrow::foreign_borrow_signature(self.arena, arguments.len()); let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars); diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 9f3a61738b..684a843c38 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -1050,13 +1050,6 @@ impl<'a> Call<'a> { .text(format!("lowlevel {:?} ", lowlevel)) .append(alloc.intersperse(it, " ")) } - NewHigherOrderLowLevel { op: lowlevel, .. } => { - let it = arguments.iter().map(|s| symbol_to_doc(alloc, *s)); - - alloc - .text(format!("lowlevel {:?} ", lowlevel)) - .append(alloc.intersperse(it, " ")) - } Foreign { ref foreign_symbol, .. } => { @@ -1109,18 +1102,6 @@ pub enum CallType<'a> { update_mode: UpdateModeId, }, HigherOrderLowLevel { - op: LowLevel, - /// the layout of the closure argument, if any - closure_env_layout: Option>, - /// specialization id of the function argument - specialization_id: CallSpecId, - /// does the function need to own the closure data - function_owns_closure_data: bool, - /// function layout - arg_layouts: &'a [Layout<'a>], - ret_layout: Layout<'a>, - }, - NewHigherOrderLowLevel { op: crate::low_level::HigherOrder, /// the layout of the closure argument, if any closure_env_layout: Option>, @@ -5464,7 +5445,6 @@ fn substitute_in_call<'a>( CallType::Foreign { .. } => None, CallType::LowLevel { .. } => None, CallType::HigherOrderLowLevel { .. } => None, - CallType::NewHigherOrderLowLevel { .. } => None, }; let mut did_change = false; From dbf650f7abb3ba21be5f71e878449188f7ea8d8b Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 17 Oct 2021 21:14:30 +0200 Subject: [PATCH 44/51] fixes --- compiler/gen_llvm/src/llvm/build.rs | 4 +- compiler/mono/src/inc_dec.rs | 2 +- compiler/mono/src/ir.rs | 2 +- compiler/mono/src/low_level.rs | 329 ++++++++++++++++++++++++++++ 4 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 compiler/mono/src/low_level.rs diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index eada14eeb7..69cc764c98 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -965,7 +965,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>( let callee_var = CalleeSpecVar(&bytes); let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap(); - run_new_higher_order_low_level( + run_higher_order_low_level( env, layout_ids, scope, @@ -4460,7 +4460,7 @@ fn roc_function_call<'a, 'ctx, 'env>( } #[allow(clippy::too_many_arguments)] -fn run_new_higher_order_low_level<'a, 'ctx, 'env>( +fn run_higher_order_low_level<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, scope: &Scope<'a, 'ctx>, diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index 577dd3790c..d7363a9195 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -480,7 +480,7 @@ impl<'a> Context<'a> { ($borrows:expr) => { Expr::Call(crate::ir::Call { call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) { - NewHigherOrderLowLevel { + HigherOrderLowLevel { op: *op, closure_env_layout: *closure_env_layout, function_owns_closure_data: true, diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 684a843c38..61a5869d46 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -4006,7 +4006,7 @@ pub fn with_hole<'a>( op, closure_data_symbol, |top_level_function, closure_data, closure_env_layout, specialization_id| self::Call { - call_type: CallType::NewHigherOrderLowLevel { + call_type: CallType::HigherOrderLowLevel { op: crate::low_level::HigherOrder::$ho { $($x,)* }, closure_env_layout, specialization_id, diff --git a/compiler/mono/src/low_level.rs b/compiler/mono/src/low_level.rs new file mode 100644 index 0000000000..e95719c7aa --- /dev/null +++ b/compiler/mono/src/low_level.rs @@ -0,0 +1,329 @@ +use roc_module::symbol::Symbol; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum HigherOrder { + ListMap { xs: Symbol }, + ListMap2 { xs: Symbol, ys: Symbol }, + ListMap3 { xs: Symbol, ys: Symbol, zs: Symbol }, + ListMapWithIndex { xs: Symbol }, + ListKeepIf { xs: Symbol }, + ListWalk { xs: Symbol, state: Symbol }, + ListWalkUntil { xs: Symbol, state: Symbol }, + ListWalkBackwards { xs: Symbol, state: Symbol }, + ListKeepOks { xs: Symbol }, + ListKeepErrs { xs: Symbol }, + ListSortWith { xs: Symbol }, + DictWalk { xs: Symbol, state: Symbol }, +} + +impl HigherOrder { + pub fn function_arity(&self) -> usize { + match self { + HigherOrder::ListMap { .. } => 1, + HigherOrder::ListMap2 { .. } => 2, + HigherOrder::ListMap3 { .. } => 3, + HigherOrder::ListMapWithIndex { .. } => 2, + HigherOrder::ListKeepIf { .. } => 1, + HigherOrder::ListWalk { .. } => 2, + HigherOrder::ListWalkUntil { .. } => 2, + HigherOrder::ListWalkBackwards { .. } => 2, + HigherOrder::ListKeepOks { .. } => 1, + HigherOrder::ListKeepErrs { .. } => 1, + HigherOrder::ListSortWith { .. } => 2, + HigherOrder::DictWalk { .. } => 2, + } + } +} + +#[allow(dead_code)] +enum FirstOrder { + StrConcat, + StrJoinWith, + StrIsEmpty, + StrStartsWith, + StrStartsWithCodePt, + StrEndsWith, + StrSplit, + StrCountGraphemes, + StrFromInt, + StrFromUtf8, + StrFromUtf8Range, + StrToUtf8, + StrRepeat, + StrFromFloat, + ListLen, + ListGetUnsafe, + ListSet, + ListDrop, + ListDropAt, + ListSingle, + ListRepeat, + ListReverse, + ListConcat, + ListContains, + ListAppend, + ListPrepend, + ListJoin, + ListRange, + ListSwap, + DictSize, + DictEmpty, + DictInsert, + DictRemove, + DictContains, + DictGetUnsafe, + DictKeys, + DictValues, + DictUnion, + DictIntersection, + DictDifference, + SetFromList, + NumAdd, + NumAddWrap, + NumAddChecked, + NumSub, + NumSubWrap, + NumSubChecked, + NumMul, + NumMulWrap, + NumMulChecked, + NumGt, + NumGte, + NumLt, + NumLte, + NumCompare, + NumDivUnchecked, + NumRemUnchecked, + NumIsMultipleOf, + NumAbs, + NumNeg, + NumSin, + NumCos, + NumSqrtUnchecked, + NumLogUnchecked, + NumRound, + NumToFloat, + NumPow, + NumCeiling, + NumPowInt, + NumFloor, + NumIsFinite, + NumAtan, + NumAcos, + NumAsin, + NumBitwiseAnd, + NumBitwiseXor, + NumBitwiseOr, + NumShiftLeftBy, + NumShiftRightBy, + NumBytesToU16, + NumBytesToU32, + NumShiftRightZfBy, + NumIntCast, + Eq, + NotEq, + And, + Or, + Not, + Hash, + ExpectTrue, +} + +/* +enum FirstOrHigher { + First(FirstOrder), + Higher(HigherOrder), +} + +fn from_low_level(low_level: &LowLevel, arguments: &[Symbol]) -> FirstOrHigher { + use FirstOrHigher::*; + use FirstOrder::*; + use HigherOrder::*; + + match low_level { + LowLevel::StrConcat => First(StrConcat), + LowLevel::StrJoinWith => First(StrJoinWith), + LowLevel::StrIsEmpty => First(StrIsEmpty), + LowLevel::StrStartsWith => First(StrStartsWith), + LowLevel::StrStartsWithCodePt => First(StrStartsWithCodePt), + LowLevel::StrEndsWith => First(StrEndsWith), + LowLevel::StrSplit => First(StrSplit), + LowLevel::StrCountGraphemes => First(StrCountGraphemes), + LowLevel::StrFromInt => First(StrFromInt), + LowLevel::StrFromUtf8 => First(StrFromUtf8), + LowLevel::StrFromUtf8Range => First(StrFromUtf8Range), + LowLevel::StrToUtf8 => First(StrToUtf8), + LowLevel::StrRepeat => First(StrRepeat), + LowLevel::StrFromFloat => First(StrFromFloat), + LowLevel::ListLen => First(ListLen), + LowLevel::ListGetUnsafe => First(ListGetUnsafe), + LowLevel::ListSet => First(ListSet), + LowLevel::ListDrop => First(ListDrop), + LowLevel::ListDropAt => First(ListDropAt), + LowLevel::ListSingle => First(ListSingle), + LowLevel::ListRepeat => First(ListRepeat), + LowLevel::ListReverse => First(ListReverse), + LowLevel::ListConcat => First(ListConcat), + LowLevel::ListContains => First(ListContains), + LowLevel::ListAppend => First(ListAppend), + LowLevel::ListPrepend => First(ListPrepend), + LowLevel::ListJoin => First(ListJoin), + LowLevel::ListRange => First(ListRange), + LowLevel::ListSwap => First(ListSwap), + LowLevel::DictSize => First(DictSize), + LowLevel::DictEmpty => First(DictEmpty), + LowLevel::DictInsert => First(DictInsert), + LowLevel::DictRemove => First(DictRemove), + LowLevel::DictContains => First(DictContains), + LowLevel::DictGetUnsafe => First(DictGetUnsafe), + LowLevel::DictKeys => First(DictKeys), + LowLevel::DictValues => First(DictValues), + LowLevel::DictUnion => First(DictUnion), + LowLevel::DictIntersection => First(DictIntersection), + LowLevel::DictDifference => First(DictDifference), + LowLevel::SetFromList => First(SetFromList), + LowLevel::NumAdd => First(NumAdd), + LowLevel::NumAddWrap => First(NumAddWrap), + LowLevel::NumAddChecked => First(NumAddChecked), + LowLevel::NumSub => First(NumSub), + LowLevel::NumSubWrap => First(NumSubWrap), + LowLevel::NumSubChecked => First(NumSubChecked), + LowLevel::NumMul => First(NumMul), + LowLevel::NumMulWrap => First(NumMulWrap), + LowLevel::NumMulChecked => First(NumMulChecked), + LowLevel::NumGt => First(NumGt), + LowLevel::NumGte => First(NumGte), + LowLevel::NumLt => First(NumLt), + LowLevel::NumLte => First(NumLte), + LowLevel::NumCompare => First(NumCompare), + LowLevel::NumDivUnchecked => First(NumDivUnchecked), + LowLevel::NumRemUnchecked => First(NumRemUnchecked), + LowLevel::NumIsMultipleOf => First(NumIsMultipleOf), + LowLevel::NumAbs => First(NumAbs), + LowLevel::NumNeg => First(NumNeg), + LowLevel::NumSin => First(NumSin), + LowLevel::NumCos => First(NumCos), + LowLevel::NumSqrtUnchecked => First(NumSqrtUnchecked), + LowLevel::NumLogUnchecked => First(NumLogUnchecked), + LowLevel::NumRound => First(NumRound), + LowLevel::NumToFloat => First(NumToFloat), + LowLevel::NumPow => First(NumPow), + LowLevel::NumCeiling => First(NumCeiling), + LowLevel::NumPowInt => First(NumPowInt), + LowLevel::NumFloor => First(NumFloor), + LowLevel::NumIsFinite => First(NumIsFinite), + LowLevel::NumAtan => First(NumAtan), + LowLevel::NumAcos => First(NumAcos), + LowLevel::NumAsin => First(NumAsin), + LowLevel::NumBitwiseAnd => First(NumBitwiseAnd), + LowLevel::NumBitwiseXor => First(NumBitwiseXor), + LowLevel::NumBitwiseOr => First(NumBitwiseOr), + LowLevel::NumShiftLeftBy => First(NumShiftLeftBy), + LowLevel::NumShiftRightBy => First(NumShiftRightBy), + LowLevel::NumBytesToU16 => First(NumBytesToU16), + LowLevel::NumBytesToU32 => First(NumBytesToU32), + LowLevel::NumShiftRightZfBy => First(NumShiftRightZfBy), + LowLevel::NumIntCast => First(NumIntCast), + LowLevel::Eq => First(Eq), + LowLevel::NotEq => First(NotEq), + LowLevel::And => First(And), + LowLevel::Or => First(Or), + LowLevel::Not => First(Not), + LowLevel::Hash => First(Hash), + LowLevel::ExpectTrue => First(ExpectTrue), + LowLevel::ListMap => { + debug_assert_eq!(arguments.len(), 3); + Higher(ListMap { + xs: arguments[0], + function_name: arguments[1], + function_env: arguments[2], + }) + } + LowLevel::ListMap2 => { + debug_assert_eq!(arguments.len(), 4); + Higher(ListMap2 { + xs: arguments[0], + ys: arguments[1], + function_name: arguments[2], + function_env: arguments[3], + }) + } + LowLevel::ListMap3 => { + debug_assert_eq!(arguments.len(), 5); + Higher(ListMap3 { + xs: arguments[0], + ys: arguments[1], + zs: arguments[2], + function_name: arguments[3], + function_env: arguments[4], + }) + } + LowLevel::ListMapWithIndex => { + debug_assert_eq!(arguments.len(), 3); + Higher(ListMapWithIndex { + xs: arguments[0], + function_name: arguments[1], + function_env: arguments[2], + }) + } + LowLevel::ListKeepIf => { + debug_assert_eq!(arguments.len(), 3); + Higher(ListKeepIf { + xs: arguments[0], + function_name: arguments[1], + function_env: arguments[2], + }) + } + LowLevel::ListWalk => { + debug_assert_eq!(arguments.len(), 3); + Higher(ListWalk { + xs: arguments[0], + function_name: arguments[1], + function_env: arguments[2], + }) + } + LowLevel::ListWalkUntil => { + debug_assert_eq!(arguments.len(), 3); + Higher(ListWalkUntil { + function_name: arguments[1], + function_env: arguments[2], + }) + } + LowLevel::ListWalkBackwards => { + debug_assert_eq!(arguments.len(), 3); + Higher(ListWalkBackwards { + function_name: arguments[1], + function_env: arguments[2], + }) + } + LowLevel::ListKeepOks => { + debug_assert_eq!(arguments.len(), 3); + Higher(ListKeepOks { + function_name: arguments[1], + function_env: arguments[2], + }) + } + LowLevel::ListKeepErrs => { + debug_assert_eq!(arguments.len(), 3); + Higher(ListKeepErrs { + function_name: arguments[1], + function_env: arguments[2], + }) + } + LowLevel::ListSortWith => { + debug_assert_eq!(arguments.len(), 3); + Higher(ListSortWith { + function_name: arguments[1], + function_env: arguments[2], + }) + } + LowLevel::DictWalk => { + debug_assert_eq!(arguments.len(), 3); + Higher(DictWalk { + function_name: arguments[1], + function_env: arguments[2], + }) + } + } +} +*/ From 2daefc8b3212b5e46b05e69c130a1eabb7e83295 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 18 Oct 2021 13:11:05 +0200 Subject: [PATCH 45/51] Name a constant --- compiler/gen_wasm/src/backend.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index ff661a3ff7..041987e049 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -115,7 +115,9 @@ impl<'a> WasmBackend<'a> { fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition { self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any) - let mut final_instructions = Vec::with_capacity(self.code_builder.len() + 10); + const STACK_FRAME_INSTRUCTIONS_LEN: usize = 10; + let mut final_instructions = + Vec::with_capacity(self.code_builder.len() + STACK_FRAME_INSTRUCTIONS_LEN); if self.storage.stack_frame_size > 0 { push_stack_frame( From 1b97675f1f2ebd5ea096be7c51066e59d3680eae Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 18 Oct 2021 13:18:10 +0200 Subject: [PATCH 46/51] Refactor the optimisation for immediately-returned Let statements The original intention was to avoid creating a local when we define and immediately return a primitive value. But now our default path does avoids unnecessary locals anyway! For StackMemory values we do need an optimised path but it's nicer to just pass a flag to Storage::allocate. --- compiler/gen_wasm/src/backend.rs | 65 ++++++++++---------------------- compiler/gen_wasm/src/lib.rs | 5 ++- compiler/gen_wasm/src/storage.rs | 21 +++++++---- 3 files changed, 37 insertions(+), 54 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 041987e049..2fd0832197 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -12,7 +12,7 @@ use roc_mono::layout::{Builtin, Layout}; use crate::code_builder::CodeBuilder; use crate::layout::WasmLayout; -use crate::storage::{LocalKind, StackMemoryLocation, Storage, StoredValue}; +use crate::storage::{Storage, StoredValue, StoredValueKind}; use crate::{copy_memory, pop_stack_frame, push_stack_frame, CopyMemoryConfig, LocalId, PTR_TYPE}; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. @@ -105,8 +105,11 @@ impl<'a> WasmBackend<'a> { }; for (layout, symbol) in proc.args { - self.storage - .allocate(&WasmLayout::new(layout), *symbol, LocalKind::Parameter); + self.storage.allocate( + &WasmLayout::new(layout), + *symbol, + StoredValueKind::Parameter, + ); } signature_builder.with_params(self.storage.arg_types.clone()) @@ -190,50 +193,20 @@ impl<'a> WasmBackend<'a> { fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { match stmt { - // Simple optimisation: if we are just returning the expression, we don't need a local - Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if *let_sym == *ret_sym => { - let wasm_layout = WasmLayout::new(layout); - - if let WasmLayout::StackMemory { - size, - alignment_bytes, - } = wasm_layout - { - // Map this symbol to the first argument (pointer into caller's stack) - // Saves us from having to copy it later - let storage = StoredValue::StackMemory { - location: StackMemoryLocation::PointerArg(LocalId(0)), - size, - alignment_bytes, - }; - self.storage.symbol_storage_map.insert(*let_sym, storage); - } - - self.build_expr(let_sym, expr, layout)?; - - if let WasmLayout::Primitive(value_type, size) = wasm_layout { - let vm_state = self.code_builder.set_top_symbol(*let_sym); - self.storage.symbol_storage_map.insert( - *let_sym, - StoredValue::VirtualMachineStack { - vm_state, - value_type, - size, - }, - ); - } - - self.code_builder.add_one(Br(self.block_depth)); // jump to end of function (stack frame pop) - Ok(()) - } - Stmt::Let(sym, expr, layout, following) => { let wasm_layout = WasmLayout::new(layout); - self.storage - .allocate(&wasm_layout, *sym, LocalKind::Variable); + let kind = match following { + Stmt::Ret(ret_sym) if *sym == *ret_sym => StoredValueKind::ReturnValue, + _ => StoredValueKind::Variable, + }; + + self.storage.allocate(&wasm_layout, *sym, kind); + self.build_expr(sym, expr, layout)?; + // For primitives, we record that this symbol is at the top of the VM stack + // (For other values, we wrote to memory and there's nothing on the VM stack) if let WasmLayout::Primitive(value_type, size) = wasm_layout { let vm_state = self.code_builder.set_top_symbol(*sym); self.storage.symbol_storage_map.insert( @@ -349,9 +322,11 @@ impl<'a> WasmBackend<'a> { let mut jp_param_storages = std::vec::Vec::with_capacity(parameters.len()); for parameter in parameters.iter() { let wasm_layout = WasmLayout::new(¶meter.layout); - let mut param_storage = - self.storage - .allocate(&wasm_layout, parameter.symbol, LocalKind::Variable); + let mut param_storage = self.storage.allocate( + &wasm_layout, + parameter.symbol, + StoredValueKind::Variable, + ); param_storage = self.storage.ensure_value_has_local( &mut self.code_builder, parameter.symbol, diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index ec245d355a..05b53507ad 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -133,7 +133,10 @@ pub struct CopyMemoryConfig { } pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) { - debug_assert!(config.from_ptr != config.to_ptr || config.from_offset != config.to_offset); + if config.from_ptr == config.to_ptr && config.from_offset == config.to_offset { + return; + } + let alignment_flag = encode_alignment(config.alignment_bytes); let mut i = 0; while config.size - i >= 8 { diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index a6d70601bc..ae3c0eaa31 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -10,9 +10,10 @@ use crate::{ ALIGN_8, PTR_SIZE, PTR_TYPE, }; -pub enum LocalKind { +pub enum StoredValueKind { Parameter, Variable, + ReturnValue, } #[derive(Debug, Clone)] @@ -103,13 +104,13 @@ impl Storage { &mut self, wasm_layout: &WasmLayout, symbol: Symbol, - kind: LocalKind, + kind: StoredValueKind, ) -> StoredValue { let next_local_id = self.get_next_local_id(); let storage = match wasm_layout { WasmLayout::Primitive(value_type, size) => match kind { - LocalKind::Parameter => { + StoredValueKind::Parameter => { self.arg_types.push(*value_type); StoredValue::Local { local_id: next_local_id, @@ -117,7 +118,7 @@ impl Storage { size: *size, } } - LocalKind::Variable => StoredValue::VirtualMachineStack { + _ => StoredValue::VirtualMachineStack { vm_state: VirtualMachineSymbolState::NotYetPushed, value_type: *value_type, size: *size, @@ -126,8 +127,8 @@ impl Storage { WasmLayout::HeapMemory => { match kind { - LocalKind::Parameter => self.arg_types.push(PTR_TYPE), - LocalKind::Variable => self.local_types.push(PTR_TYPE), + StoredValueKind::Parameter => self.arg_types.push(PTR_TYPE), + _ => self.local_types.push(PTR_TYPE), } StoredValue::Local { local_id: next_local_id, @@ -141,12 +142,12 @@ impl Storage { alignment_bytes, } => { let location = match kind { - LocalKind::Parameter => { + StoredValueKind::Parameter => { self.arg_types.push(PTR_TYPE); StackMemoryLocation::PointerArg(next_local_id) } - LocalKind::Variable => { + StoredValueKind::Variable => { if self.stack_frame_pointer.is_none() { self.stack_frame_pointer = Some(next_local_id); self.local_types.push(PTR_TYPE); @@ -159,6 +160,10 @@ impl Storage { StackMemoryLocation::FrameOffset(offset as u32) } + + StoredValueKind::ReturnValue => { + StackMemoryLocation::PointerArg(LocalId(0)) + } }; StoredValue::StackMemory { From f242aade8608a3fe48911be96002f0b84ebad397 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 18 Oct 2021 18:35:48 +0200 Subject: [PATCH 47/51] Rename a variable & fix a bug calling functions that return via stack memory --- compiler/gen_wasm/src/backend.rs | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 2fd0832197..15165f0255 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -399,19 +399,35 @@ impl<'a> WasmBackend<'a> { arguments, }) => match call_type { CallType::ByName { name: func_sym, .. } => { + // TODO: See if we can make this more efficient + // Recreating the same WasmLayout again, rather than passing it down, + // to match signature of Backend::build_expr + let wasm_layout = WasmLayout::new(layout); + + let mut wasm_args_tmp: Vec; + let (wasm_args, has_return_val) = match wasm_layout { + WasmLayout::StackMemory { .. } => { + wasm_args_tmp = Vec::with_capacity(arguments.len() + 1); // TODO: bumpalo + wasm_args_tmp.push(*sym); + wasm_args_tmp.extend_from_slice(*arguments); + (wasm_args_tmp.as_slice(), false) + } + _ => (*arguments, true), + }; + self.storage - .load_symbols(&mut self.code_builder, *arguments); + .load_symbols(&mut self.code_builder, wasm_args); + let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!( "Cannot find function {:?} called from {:?}", func_sym, sym ))?; - // TODO: Recreating the same WasmLayout as in the Let, for Backend compatibility - let wasm_layout = WasmLayout::new(layout); - let push = wasm_layout.stack_memory() == 0; - let pops = arguments.len(); - self.code_builder - .add_call(function_location.body, pops, push); + self.code_builder.add_call( + function_location.body, + wasm_args.len(), + has_return_val, + ); Ok(()) } From b86849347a923654f2baf2a2c29fa0841e34a65d Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 18 Oct 2021 20:40:39 +0200 Subject: [PATCH 48/51] Write test debug output to a path in /tmp --- compiler/gen_wasm/tests/helpers/eval.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/compiler/gen_wasm/tests/helpers/eval.rs b/compiler/gen_wasm/tests/helpers/eval.rs index af368e59aa..dd712df350 100644 --- a/compiler/gen_wasm/tests/helpers/eval.rs +++ b/compiler/gen_wasm/tests/helpers/eval.rs @@ -117,10 +117,9 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( let src_hash = hash_state.finish(); // Filename contains a hash of the Roc test source code. Helpful when comparing across commits. - let path = format!( - "/home/brian/Documents/roc/compiler/gen_wasm/output/test-{:016x}.wasm", - src_hash - ); + let dir = "/tmp/roc/compiler/gen_wasm/output"; + std::fs::create_dir_all(dir).unwrap(); + let path = format!("{}/test-{:016x}.wasm", dir, src_hash); // Print out filename (appears just after test name) println!("dumping file {:?}", path); From 64603a480c0368311ed1d8743fdcf8d5696739df Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 18 Oct 2021 21:03:54 +0200 Subject: [PATCH 49/51] Get rid of Option in VM stack model --- compiler/gen_wasm/src/code_builder.rs | 85 ++++++++++++--------------- compiler/module/src/symbol.rs | 3 + 2 files changed, 42 insertions(+), 46 deletions(-) diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index ce2bc1e3e8..443ca79f17 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -38,7 +38,7 @@ pub struct CodeBuilder { /// Our simulation model of the Wasm stack machine /// Keeps track of where Symbol values are in the VM stack - vm_stack: Vec>, + vm_stack: Vec, } #[allow(clippy::new_without_default)] @@ -63,7 +63,7 @@ impl CodeBuilder { let new_len = self.vm_stack.len() - pops as usize; self.vm_stack.truncate(new_len); if push { - self.vm_stack.push(None); + self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); } if DEBUG_LOG { println!("{:?} {:?}", inst, self.vm_stack); @@ -87,7 +87,8 @@ impl CodeBuilder { } } self.vm_stack.truncate(min_len); - self.vm_stack.resize(len, None); + self.vm_stack + .resize(len, Symbol::WASM_ANONYMOUS_STACK_VALUE); if DEBUG_LOG { println!("{:?} {:?}", instructions, self.vm_stack); } @@ -108,7 +109,7 @@ impl CodeBuilder { } self.vm_stack.truncate(stack_depth - pops); if push { - self.vm_stack.push(None); + self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); } let inst = Call(function_index); if DEBUG_LOG { @@ -154,7 +155,7 @@ impl CodeBuilder { ); } - self.vm_stack[len - 1] = Some(sym); + self.vm_stack[len - 1] = sym; VirtualMachineSymbolState::Pushed { pushed_at } } @@ -169,11 +170,8 @@ impl CodeBuilder { let offset = stack_depth - n_symbols; for (i, sym) in symbols.iter().enumerate() { - match self.vm_stack[offset + i] { - Some(stack_symbol) if stack_symbol == *sym => {} - _ => { - return false; - } + if self.vm_stack[offset + i] != *sym { + return false; } } true @@ -199,44 +197,39 @@ impl CodeBuilder { Pushed { pushed_at } => { let &top = self.vm_stack.last().unwrap(); - match top { - Some(top_symbol) if top_symbol == symbol => { - // We're lucky, the symbol is already on top of the VM stack - // No code to generate! (This reduces code size by up to 25% in tests.) - // Just let the caller know what happened - Some(Popped { pushed_at }) - } - _ => { - // Symbol is not on top of the stack. Find it. - if let Some(found_index) = - self.vm_stack.iter().rposition(|&s| s == Some(symbol)) - { - // Insert a SetLocal where the value was created (this removes it from the VM stack) - self.insertions.insert(pushed_at, SetLocal(next_local_id.0)); - self.vm_stack.remove(found_index); + if top == symbol { + // We're lucky, the symbol is already on top of the VM stack + // No code to generate! (This reduces code size by up to 25% in tests.) + // Just let the caller know what happened + Some(Popped { pushed_at }) + } else { + // Symbol is not on top of the stack. Find it. + if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) { + // Insert a SetLocal where the value was created (this removes it from the VM stack) + self.insertions.insert(pushed_at, SetLocal(next_local_id.0)); + self.vm_stack.remove(found_index); - // Insert a GetLocal at the current position - let inst = GetLocal(next_local_id.0); - if DEBUG_LOG { - println!( - "{:?} {:?} (& insert {:?} at {:?})", - inst, - self.vm_stack, - SetLocal(next_local_id.0), - pushed_at - ); - } - self.code.push(inst); - self.vm_stack.push(Some(symbol)); - - // This Symbol is no longer stored in the VM stack, but in a local - None - } else { - panic!( - "{:?} has state {:?} but not found in VM stack", - symbol, vm_state + // Insert a GetLocal at the current position + let inst = GetLocal(next_local_id.0); + if DEBUG_LOG { + println!( + "{:?} {:?} (& insert {:?} at {:?})", + inst, + self.vm_stack, + SetLocal(next_local_id.0), + pushed_at ); } + self.code.push(inst); + self.vm_stack.push(symbol); + + // This Symbol is no longer stored in the VM stack, but in a local + None + } else { + panic!( + "{:?} has state {:?} but not found in VM stack", + symbol, vm_state + ); } } } @@ -259,7 +252,7 @@ impl CodeBuilder { ); } self.code.push(inst); - self.vm_stack.push(Some(symbol)); + self.vm_stack.push(symbol); // This symbol has been promoted to a Local // Tell the caller it no longer has a VirtualMachineSymbolState diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 897fcdbca3..7e070cab0a 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -835,6 +835,9 @@ define_builtins! { // used by the dev backend to store the pointer to where to store large return types 23 RET_POINTER: "#ret_pointer" + + // used in wasm dev backend to mark values in the VM stack that have no other Symbol + 24 WASM_ANONYMOUS_STACK_VALUE: "#wasm_anonymous_stack_value" } 1 NUM: "Num" => { 0 NUM_NUM: "Num" imported // the Num.Num type alias From e4db06cbdd436e2833bf072fa08a981dd3896c05 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 18 Oct 2021 21:16:54 +0200 Subject: [PATCH 50/51] Add a debug assertion in code builder --- compiler/gen_wasm/src/code_builder.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index 443ca79f17..74259b4677 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -127,14 +127,13 @@ impl CodeBuilder { match next_insertion { Some((&insert_pos, insert_inst)) if insert_pos == pos => { final_code.push(insert_inst.to_owned()); - final_code.push(instruction); next_insertion = insertions_iter.next(); } - _ => { - final_code.push(instruction); - } + _ => {} } + final_code.push(instruction); } + debug_assert!(next_insertion == None); } /// Total number of instructions in the final output From 401f2ececdbc3dde3f540e9e8139aca887fcfba4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 18 Oct 2021 21:35:16 +0200 Subject: [PATCH 51/51] rename some methods --- compiler/gen_wasm/src/backend.rs | 26 ++++++++++++-------------- compiler/gen_wasm/src/code_builder.rs | 7 ++++--- compiler/gen_wasm/src/lib.rs | 6 +++--- compiler/gen_wasm/src/storage.rs | 19 +++++++++---------- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 15165f0255..85f22635f4 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -177,18 +177,17 @@ impl<'a> WasmBackend<'a> { /// start a loop that leaves a value on the stack fn start_loop_with_return(&mut self, value_type: ValueType) { self.block_depth += 1; - self.code_builder - .add_one(Loop(BlockType::Value(value_type))); + self.code_builder.push(Loop(BlockType::Value(value_type))); } fn start_block(&mut self, block_type: BlockType) { self.block_depth += 1; - self.code_builder.add_one(Block(block_type)); + self.code_builder.push(Block(block_type)); } fn end_block(&mut self) { self.block_depth -= 1; - self.code_builder.add_one(End); + self.code_builder.push(End); } fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { @@ -251,7 +250,7 @@ impl<'a> WasmBackend<'a> { _ => { self.storage.load_symbols(&mut self.code_builder, &[*sym]); - self.code_builder.add_one(Br(self.block_depth)); // jump to end of function (for stack frame pop) + self.code_builder.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) } } @@ -289,13 +288,13 @@ impl<'a> WasmBackend<'a> { self.storage .load_symbols(&mut self.code_builder, &[*cond_symbol]); - self.code_builder.add_one(I32Const(*value as i32)); + self.code_builder.push(I32Const(*value as i32)); // compare the 2 topmost values - self.code_builder.add_one(I32Eq); + self.code_builder.push(I32Eq); // "break" out of `i` surrounding blocks - self.code_builder.add_one(BrIf(i as u32)); + self.code_builder.push(BrIf(i as u32)); } // if we never jumped because a value matched, we're in the default case @@ -371,7 +370,7 @@ impl<'a> WasmBackend<'a> { // jump let levels = self.block_depth - target; - self.code_builder.add_one(Br(levels)); + self.code_builder.push(Br(levels)); Ok(()) } @@ -415,15 +414,14 @@ impl<'a> WasmBackend<'a> { _ => (*arguments, true), }; - self.storage - .load_symbols(&mut self.code_builder, wasm_args); + self.storage.load_symbols(&mut self.code_builder, wasm_args); let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!( "Cannot find function {:?} called from {:?}", func_sym, sym ))?; - self.code_builder.add_call( + self.code_builder.push_call( function_location.body, wasm_args.len(), has_return_val, @@ -471,7 +469,7 @@ impl<'a> WasmBackend<'a> { return Err(format!("loading literal, {:?}, is not yet implemented", x)); } }; - self.code_builder.add_one(instruction); + self.code_builder.push(instruction); Ok(()) } @@ -569,7 +567,7 @@ impl<'a> WasmBackend<'a> { return Err(format!("unsupported low-level op {:?}", lowlevel)); } }; - self.code_builder.add_many(instructions); + self.code_builder.extend_from_slice(instructions); Ok(()) } } diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index 74259b4677..f82e1132bc 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -1,5 +1,6 @@ use core::panic; use std::collections::BTreeMap; +use std::fmt::Debug; use parity_wasm::elements::{Instruction, Instruction::*}; use roc_module::symbol::Symbol; @@ -58,7 +59,7 @@ impl CodeBuilder { } /// Add an instruction - pub fn add_one(&mut self, inst: Instruction) { + pub fn push(&mut self, inst: Instruction) { let (pops, push) = get_pops_and_pushes(&inst); let new_len = self.vm_stack.len() - pops as usize; self.vm_stack.truncate(new_len); @@ -72,7 +73,7 @@ impl CodeBuilder { } /// Add many instructions - pub fn add_many(&mut self, instructions: &[Instruction]) { + pub fn extend_from_slice(&mut self, instructions: &[Instruction]) { let old_len = self.vm_stack.len(); let mut len = old_len; let mut min_len = len; @@ -97,7 +98,7 @@ impl CodeBuilder { /// Special-case method to add a Call instruction /// Specify the number of arguments the function pops from the VM stack, and whether it pushes a return value - pub fn add_call(&mut self, function_index: u32, pops: usize, push: bool) { + pub fn push_call(&mut self, function_index: u32, pops: usize, push: bool) { let stack_depth = self.vm_stack.len(); if pops > stack_depth { let mut final_code = Vec::with_capacity(self.code.len() + self.insertions.len()); diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 05b53507ad..6af623acf1 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -140,7 +140,7 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) { let alignment_flag = encode_alignment(config.alignment_bytes); let mut i = 0; while config.size - i >= 8 { - code_builder.add_many(&[ + code_builder.extend_from_slice(&[ GetLocal(config.to_ptr.0), GetLocal(config.from_ptr.0), I64Load(alignment_flag, i + config.from_offset), @@ -149,7 +149,7 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) { i += 8; } if config.size - i >= 4 { - code_builder.add_many(&[ + code_builder.extend_from_slice(&[ GetLocal(config.to_ptr.0), GetLocal(config.from_ptr.0), I32Load(alignment_flag, i + config.from_offset), @@ -158,7 +158,7 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) { i += 4; } while config.size - i > 0 { - code_builder.add_many(&[ + code_builder.extend_from_slice(&[ GetLocal(config.to_ptr.0), GetLocal(config.from_ptr.0), I32Load8U(alignment_flag, i + config.from_offset), diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index ae3c0eaa31..f0a5453c24 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -161,9 +161,7 @@ impl Storage { StackMemoryLocation::FrameOffset(offset as u32) } - StoredValueKind::ReturnValue => { - StackMemoryLocation::PointerArg(LocalId(0)) - } + StoredValueKind::ReturnValue => StackMemoryLocation::PointerArg(LocalId(0)), }; StoredValue::StackMemory { @@ -241,7 +239,7 @@ impl Storage { location: StackMemoryLocation::PointerArg(local_id), .. } => { - code_builder.add_one(GetLocal(local_id.0)); + code_builder.push(GetLocal(local_id.0)); code_builder.set_top_symbol(*sym); } @@ -249,7 +247,7 @@ impl Storage { location: StackMemoryLocation::FrameOffset(offset), .. } => { - code_builder.add_many(&[ + code_builder.extend_from_slice(&[ GetLocal(self.stack_frame_pointer.unwrap().0), I32Const(offset as i32), I32Add, @@ -308,9 +306,9 @@ impl Storage { panic!("Cannot store {:?} with alignment of {:?}", value_type, size); } }; - code_builder.add_one(GetLocal(to_ptr.0)); + code_builder.push(GetLocal(to_ptr.0)); self.load_symbols(code_builder, &[from_symbol]); - code_builder.add_one(store_instruction); + code_builder.push(store_instruction); size } } @@ -343,7 +341,7 @@ impl Storage { debug_assert!(to_value_type == from_value_type); debug_assert!(to_size == from_size); self.load_symbols(code_builder, &[from_symbol]); - code_builder.add_one(SetLocal(to_local_id.0)); + code_builder.push(SetLocal(to_local_id.0)); self.symbol_storage_map.insert(from_symbol, to.clone()); } @@ -361,7 +359,8 @@ impl Storage { ) => { debug_assert!(to_value_type == from_value_type); debug_assert!(to_size == from_size); - code_builder.add_many(&[GetLocal(from_local_id.0), SetLocal(to_local_id.0)]); + code_builder + .extend_from_slice(&[GetLocal(from_local_id.0), SetLocal(to_local_id.0)]); } ( @@ -420,7 +419,7 @@ impl Storage { let local_id = self.get_next_local_id(); if vm_state != VirtualMachineSymbolState::NotYetPushed { code_builder.load_symbol(symbol, vm_state, local_id); - code_builder.add_one(SetLocal(local_id.0)); + code_builder.push(SetLocal(local_id.0)); } self.local_types.push(value_type);