diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 75917fd6ff..43416ad95e 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1,9 +1,6 @@ use bumpalo::collections::Vec; use parity_wasm::builder; -use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder, SignatureBuilder}; -use parity_wasm::elements::{ - BlockType, Instruction, Instruction::*, Instructions, Local, ValueType, -}; +use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder}; use roc_collections::all::MutMap; use roc_module::low_level::LowLevel; @@ -11,12 +8,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; +use crate::function_builder::{BlockType, FunctionBuilder, ValueType}; use crate::layout::WasmLayout; use crate::storage::{Storage, StoredValue, StoredValueKind}; -use crate::{ - copy_memory, pop_stack_frame, push_stack_frame, CopyMemoryConfig, Env, LocalId, PTR_TYPE, -}; +use crate::{copy_memory, CopyMemoryConfig, Env, 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) @@ -37,7 +32,7 @@ pub struct WasmBackend<'a> { proc_symbol_map: MutMap, // Function level - code_builder: CodeBuilder<'a>, + code_builder: FunctionBuilder<'a>, storage: Storage<'a>, /// how many blocks deep are we (used for jumps) @@ -61,7 +56,7 @@ impl<'a> WasmBackend<'a> { joinpoint_label_map: MutMap::default(), // Functions - code_builder: CodeBuilder::new(env.arena), + code_builder: FunctionBuilder::new(env.arena), storage: Storage::new(env.arena), } } @@ -82,21 +77,24 @@ 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); + // Use parity-wasm to add the signature in "types" and "functions" sections + // but no instructions, since we are building our own code section + let empty_function_def = self.start_proc(&proc); + let location = self.module_builder.push_function(empty_function_def); + let function_index = location.body; + self.proc_symbol_map.insert(sym, location); self.build_stmt(&proc.body, &proc.ret_layout)?; - let function_def = self.finalize_proc(signature_builder); - let location = self.module_builder.push_function(function_def); - let function_index = location.body; - self.proc_symbol_map.insert(sym, location); + self.finalize_proc(); self.reset(); + // println!("\nfinished generating {:?}\n", sym); Ok(function_index) } - fn start_proc(&mut self, proc: &Proc<'a>) -> SignatureBuilder { + fn start_proc(&mut self, proc: &Proc<'a>) -> FunctionDefinition { let ret_layout = WasmLayout::new(&proc.ret_layout); let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout { @@ -106,7 +104,7 @@ impl<'a> WasmBackend<'a> { } else { let ret_type = ret_layout.value_type(); self.start_block(BlockType::Value(ret_type)); // block to ensure all paths pop stack memory (if any) - builder::signature().with_result(ret_type) + builder::signature().with_result(ret_type.to_parity_wasm()) }; for (layout, symbol) in proc.args { @@ -117,60 +115,24 @@ impl<'a> WasmBackend<'a> { ); } - signature_builder.with_params(self.storage.arg_types.clone()) + let parity_params = self.storage.arg_types.iter().map(|t| t.to_parity_wasm()); + + let signature = signature_builder.with_params(parity_params).build_sig(); + + // parity-wasm FunctionDefinition with no instructions + builder::function().with_signature(signature).build() } - 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) + fn finalize_proc(&mut self) { + // end the block from start_proc, to ensure all paths pop stack memory (if any) + self.end_block(); - const STACK_FRAME_INSTRUCTIONS_LEN: usize = 10; - let mut final_instructions = - std::vec::Vec::with_capacity(self.code_builder.len() + STACK_FRAME_INSTRUCTIONS_LEN); - - if self.storage.stack_frame_size > 0 { - push_stack_frame( - &mut final_instructions, - self.storage.stack_frame_size, - self.storage.stack_frame_pointer.unwrap(), - ); - } - - self.code_builder.finalize_into(&mut final_instructions); - - if self.storage.stack_frame_size > 0 { - pop_stack_frame( - &mut final_instructions, - self.storage.stack_frame_size, - self.storage.stack_frame_pointer.unwrap(), - ); - } - final_instructions.push(End); - - // Declare local variables (in batches of the same type) - let num_locals = self.storage.local_types.len(); - let mut locals = Vec::with_capacity_in(num_locals, self.env.arena); - if num_locals > 0 { - let mut batch_type = self.storage.local_types[0]; - let mut batch_size = 0; - for t in &self.storage.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(locals) - .with_instructions(Instructions::new(final_instructions)) - .build() // body - .build() // function + // Write local declarations and stack frame push/pop code + self.code_builder.finalize( + &self.storage.local_types, + self.storage.stack_frame_size, + self.storage.stack_frame_pointer, + ); } /********************************************************** @@ -182,17 +144,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.push(Loop(BlockType::Value(value_type))); + self.code_builder.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.block(block_type); } fn end_block(&mut self) { self.block_depth -= 1; - self.code_builder.push(End); + self.code_builder.end(); } fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { @@ -255,7 +217,7 @@ impl<'a> WasmBackend<'a> { _ => { self.storage.load_symbols(&mut self.code_builder, &[*sym]); - self.code_builder.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) + self.code_builder.br(self.block_depth); // jump to end of function (for stack frame pop) } } @@ -293,13 +255,13 @@ impl<'a> WasmBackend<'a> { self.storage .load_symbols(&mut self.code_builder, &[*cond_symbol]); - self.code_builder.push(I32Const(*value as i32)); + self.code_builder.i32_const(*value as i32); // compare the 2 topmost values - self.code_builder.push(I32Eq); + self.code_builder.i32_eq(); // "break" out of `i` surrounding blocks - self.code_builder.push(BrIf(i as u32)); + self.code_builder.br_if(i as u32); } // if we never jumped because a value matched, we're in the default case @@ -375,7 +337,7 @@ impl<'a> WasmBackend<'a> { // jump let levels = self.block_depth - target; - self.code_builder.push(Br(levels)); + self.code_builder.br(levels); Ok(()) } @@ -427,11 +389,8 @@ impl<'a> WasmBackend<'a> { func_sym, sym ))?; - self.code_builder.push_call( - function_location.body, - wasm_args.len(), - has_return_val, - ); + self.code_builder + .call(function_location.body, wasm_args.len(), has_return_val); Ok(()) } @@ -448,25 +407,25 @@ impl<'a> WasmBackend<'a> { } fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> { - let instruction = match lit { - Literal::Bool(x) => I32Const(*x as i32), - Literal::Byte(x) => I32Const(*x as i32), + match lit { + Literal::Bool(x) => self.code_builder.i32_const(*x as i32), + Literal::Byte(x) => self.code_builder.i32_const(*x as i32), Literal::Int(x) => match layout { - Layout::Builtin(Builtin::Int64) => I64Const(*x as i64), + Layout::Builtin(Builtin::Int64) => self.code_builder.i64_const(*x as i64), Layout::Builtin( Builtin::Int32 | Builtin::Int16 | Builtin::Int8 | Builtin::Int1 | Builtin::Usize, - ) => I32Const(*x as i32), + ) => self.code_builder.i32_const(*x as i32), x => { return Err(format!("loading literal, {:?}, is not yet implemented", x)); } }, Literal::Float(x) => match layout { - Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()), - Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()), + Layout::Builtin(Builtin::Float64) => self.code_builder.f64_const(*x as f64), + Layout::Builtin(Builtin::Float32) => self.code_builder.f32_const(*x as f32), x => { return Err(format!("loading literal, {:?}, is not yet implemented", x)); } @@ -475,7 +434,6 @@ impl<'a> WasmBackend<'a> { return Err(format!("loading literal, {:?}, is not yet implemented", x)); } }; - self.code_builder.push(instruction); Ok(()) } @@ -546,34 +504,33 @@ impl<'a> WasmBackend<'a> { // Some Roc low-level ops care about wrapping, clipping, sign-extending... // 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 { + match lowlevel { LowLevel::NumAdd => match return_value_type { - ValueType::I32 => &[I32Add], - ValueType::I64 => &[I64Add], - ValueType::F32 => &[F32Add], - ValueType::F64 => &[F64Add], + ValueType::I32 => self.code_builder.i32_add(), + ValueType::I64 => self.code_builder.i64_add(), + ValueType::F32 => self.code_builder.f32_add(), + ValueType::F64 => self.code_builder.f64_add(), }, LowLevel::NumSub => match return_value_type { - ValueType::I32 => &[I32Sub], - ValueType::I64 => &[I64Sub], - ValueType::F32 => &[F32Sub], - ValueType::F64 => &[F64Sub], + ValueType::I32 => self.code_builder.i32_sub(), + ValueType::I64 => self.code_builder.i64_sub(), + ValueType::F32 => self.code_builder.f32_sub(), + ValueType::F64 => self.code_builder.f64_sub(), }, LowLevel::NumMul => match return_value_type { - ValueType::I32 => &[I32Mul], - ValueType::I64 => &[I64Mul], - ValueType::F32 => &[F32Mul], - ValueType::F64 => &[F64Mul], + ValueType::I32 => self.code_builder.i32_mul(), + ValueType::I64 => self.code_builder.i64_mul(), + ValueType::F32 => self.code_builder.f32_mul(), + ValueType::F64 => self.code_builder.f64_mul(), }, LowLevel::NumGt => { // needs layout of the argument to be implemented fully - &[I32GtS] + self.code_builder.i32_gt_s() } _ => { return Err(format!("unsupported low-level op {:?}", lowlevel)); } }; - self.code_builder.extend_from_slice(instructions); Ok(()) } } diff --git a/compiler/gen_wasm/src/function_builder.rs b/compiler/gen_wasm/src/function_builder.rs index c294cf8237..9ab030a68f 100644 --- a/compiler/gen_wasm/src/function_builder.rs +++ b/compiler/gen_wasm/src/function_builder.rs @@ -15,7 +15,7 @@ use crate::{ const DEBUG_LOG: bool = false; #[repr(u8)] -#[derive(PartialEq, Eq, Clone, Copy)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum ValueType { I32 = 0x7f, I64 = 0x7e, @@ -23,6 +23,17 @@ pub enum ValueType { F64 = 0x7c, } +impl ValueType { + pub fn to_parity_wasm(&self) -> parity_wasm::elements::ValueType { + match self { + Self::I32 => parity_wasm::elements::ValueType::I32, + Self::I64 => parity_wasm::elements::ValueType::I64, + Self::F32 => parity_wasm::elements::ValueType::F32, + Self::F64 => parity_wasm::elements::ValueType::F64, + } + } +} + pub enum BlockType { NoResult, Value(ValueType), @@ -38,6 +49,7 @@ impl BlockType { } #[repr(u8)] +#[derive(Clone, Copy, Debug)] pub enum Align { Bytes1 = 0, Bytes2 = 1, @@ -199,7 +211,8 @@ impl<'a> FunctionBuilder<'a> { length: 0, bytes: [SETLOCAL, 0, 0, 0, 0, 0], }; - insertion.length = 1 + encode_u32(&mut insertion.bytes[1..], next_local_id.0); + insertion.length = + 1 + encode_u32(&mut insertion.bytes[1..], next_local_id.0); self.insertions_byte_len += insertion.length; self.insertions.push(insertion); @@ -301,17 +314,22 @@ impl<'a> FunctionBuilder<'a> { /// Generate all the "extra" bytes: local declarations, stack frame push/pop code, and function length /// After this, bytes will have been _generated_, but not yet _serialized_ into a single stream. /// Returns the final number of bytes the function will occupy in the target binary - pub fn finalize(&mut self, local_types: &[ValueType], frame_size: i32, frame_pointer: LocalId) -> usize { - self.insertions.sort_by_key(|insertion| insertion.position); - + pub fn finalize( + &mut self, + local_types: &[ValueType], + frame_size: i32, + frame_pointer: Option, + ) -> usize { self.build_local_declarations(local_types); - if frame_size > 0 { + if let Some(frame_ptr_id) = frame_pointer { let aligned_size = round_up_to_alignment(frame_size, FRAME_ALIGNMENT_BYTES); - self.build_stack_frame_push(aligned_size, frame_pointer); - self.build_stack_frame_pop(aligned_size, frame_pointer); + self.build_stack_frame_push(aligned_size, frame_ptr_id); + self.build_stack_frame_pop(aligned_size, frame_ptr_id); } + self.code.push(END); + // The length of the function is written in front of its body. // But that means the length _itself_ has a byte length, which adds to the total! // We use the terms "inner" and "outer" lengths to distinguish the two. @@ -322,15 +340,19 @@ impl<'a> FunctionBuilder<'a> { } /// Write out all the bytes in the right order - pub fn serialize(&self, writer: &mut W) -> std::io::Result { + pub fn serialize(&mut self, writer: &mut W) -> std::io::Result { writer.write(&self.inner_length)?; writer.write(&self.preamble)?; + + self.insertions.sort_by_key(|insertion| insertion.position); + let mut pos: usize = 0; for insertion in self.insertions.iter() { writer.write(&self.code[pos..insertion.position])?; writer.write(&insertion.bytes[0..insertion.length])?; pos = insertion.position; } + let len = self.code.len(); writer.write(&self.code[pos..len]) } diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 5227f7755a..94f03db619 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -1,7 +1,6 @@ -use parity_wasm::elements::ValueType; use roc_mono::layout::{Layout, UnionLayout}; -use crate::{PTR_SIZE, PTR_TYPE}; +use crate::{function_builder::ValueType, PTR_SIZE, PTR_TYPE}; // See README for background information on Wasm locals, memory and function calls #[derive(Debug, Clone)] diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 3547f7f9d6..f3bf359913 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -5,7 +5,7 @@ mod layout; mod storage; #[allow(dead_code)] -mod function_builder; +pub mod function_builder; #[allow(dead_code)] mod opcodes; @@ -13,25 +13,19 @@ mod opcodes; use bumpalo::collections::Vec; use bumpalo::Bump; use parity_wasm::builder; -use parity_wasm::elements::{Instruction, Instruction::*, Internal, ValueType}; +use parity_wasm::elements::Internal; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::{Interns, Symbol}; use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; use crate::backend::WasmBackend; -use crate::code_builder::CodeBuilder; +use crate::function_builder::{Align, FunctionBuilder, ValueType}; const PTR_SIZE: u32 = 4; const PTR_TYPE: ValueType = ValueType::I32; -// All usages of these alignment constants take u32, so an enum wouldn't add any safety. -pub const ALIGN_1: u32 = 0; -pub const ALIGN_2: u32 = 1; -pub const ALIGN_4: u32 = 2; -pub const ALIGN_8: u32 = 3; - pub const STACK_POINTER_GLOBAL_ID: u32 = 0; pub const FRAME_ALIGNMENT_BYTES: i32 = 16; @@ -111,21 +105,23 @@ pub fn build_module_help<'a>( backend.module_builder.push_export(memory_export); let stack_pointer_global = builder::global() - .with_type(PTR_TYPE) + .with_type(parity_wasm::elements::ValueType::I32) .mutable() - .init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32)) + .init_expr(parity_wasm::elements::Instruction::I32Const( + (MIN_MEMORY_SIZE_KB * 1024) as i32, + )) .build(); backend.module_builder.push_global(stack_pointer_global); Ok((backend.module_builder, main_function_index)) } -fn encode_alignment(bytes: u32) -> u32 { +fn encode_alignment(bytes: u32) -> Align { match bytes { - 1 => ALIGN_1, - 2 => ALIGN_2, - 4 => ALIGN_4, - 8 => ALIGN_8, + 1 => Align::Bytes1, + 2 => Align::Bytes2, + 4 => Align::Bytes4, + 8 => Align::Bytes8, _ => panic!("{:?}-byte alignment is not supported", bytes), } } @@ -139,38 +135,32 @@ pub struct CopyMemoryConfig { alignment_bytes: u32, } -pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) { +pub fn copy_memory(code_builder: &mut FunctionBuilder, config: CopyMemoryConfig) { if config.from_ptr == config.to_ptr && config.from_offset == config.to_offset { return; } - let alignment_flag = encode_alignment(config.alignment_bytes); + let alignment = encode_alignment(config.alignment_bytes); let mut i = 0; while config.size - i >= 8 { - code_builder.extend_from_slice(&[ - GetLocal(config.to_ptr.0), - GetLocal(config.from_ptr.0), - I64Load(alignment_flag, i + config.from_offset), - I64Store(alignment_flag, i + config.to_offset), - ]); + code_builder.get_local(config.to_ptr); + code_builder.get_local(config.from_ptr); + code_builder.i64_load(alignment, i + config.from_offset); + code_builder.i64_store(alignment, i + config.to_offset); i += 8; } if config.size - i >= 4 { - code_builder.extend_from_slice(&[ - GetLocal(config.to_ptr.0), - GetLocal(config.from_ptr.0), - I32Load(alignment_flag, i + config.from_offset), - I32Store(alignment_flag, i + config.to_offset), - ]); + code_builder.get_local(config.to_ptr); + code_builder.get_local(config.from_ptr); + code_builder.i32_load(alignment, i + config.from_offset); + code_builder.i32_store(alignment, i + config.to_offset); i += 4; } while config.size - i > 0 { - code_builder.extend_from_slice(&[ - GetLocal(config.to_ptr.0), - GetLocal(config.from_ptr.0), - I32Load8U(alignment_flag, i + config.from_offset), - I32Store8(alignment_flag, i + config.to_offset), - ]); + code_builder.get_local(config.to_ptr); + code_builder.get_local(config.from_ptr); + code_builder.i32_load8_u(alignment, i + config.from_offset); + code_builder.i32_store8(alignment, i + config.to_offset); i += 1; } } @@ -184,35 +174,6 @@ pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 { aligned } -pub fn push_stack_frame( - instructions: &mut std::vec::Vec, - size: i32, - local_frame_pointer: LocalId, -) { - let aligned_size = round_up_to_alignment(size, FRAME_ALIGNMENT_BYTES); - instructions.extend([ - GetGlobal(STACK_POINTER_GLOBAL_ID), - I32Const(aligned_size), - I32Sub, - TeeLocal(local_frame_pointer.0), - SetGlobal(STACK_POINTER_GLOBAL_ID), - ]); -} - -pub fn pop_stack_frame( - instructions: &mut std::vec::Vec, - size: i32, - local_frame_pointer: LocalId, -) { - let aligned_size = round_up_to_alignment(size, FRAME_ALIGNMENT_BYTES); - instructions.extend([ - GetLocal(local_frame_pointer.0), - I32Const(aligned_size), - I32Add, - SetGlobal(STACK_POINTER_GLOBAL_ID), - ]); -} - pub fn debug_panic(error: E) { panic!("{:?}", error); } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 9185a7fb7c..c52b7b388f 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,16 +1,13 @@ use bumpalo::collections::Vec; use bumpalo::Bump; -use parity_wasm::elements::{Instruction::*, ValueType}; use roc_collections::all::MutMap; use roc_module::symbol::Symbol; -use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState}; +use crate::code_builder::VirtualMachineSymbolState; +use crate::function_builder::{FunctionBuilder, ValueType}; 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, -}; +use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, PTR_SIZE, PTR_TYPE}; pub enum StoredValueKind { Parameter, @@ -192,7 +189,7 @@ impl<'a> Storage<'a> { /// Load symbols to the top of the VM stack /// 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]) { + pub fn load_symbols(&mut self, code_builder: &mut FunctionBuilder, 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 @@ -241,7 +238,7 @@ impl<'a> Storage<'a> { location: StackMemoryLocation::PointerArg(local_id), .. } => { - code_builder.push(GetLocal(local_id.0)); + code_builder.get_local(local_id); code_builder.set_top_symbol(*sym); } @@ -249,11 +246,9 @@ impl<'a> Storage<'a> { location: StackMemoryLocation::FrameOffset(offset), .. } => { - code_builder.extend_from_slice(&[ - GetLocal(self.stack_frame_pointer.unwrap().0), - I32Const(offset as i32), - I32Add, - ]); + code_builder.get_local(self.stack_frame_pointer.unwrap()); + code_builder.i32_const(offset as i32); + code_builder.i32_add(); code_builder.set_top_symbol(*sym); } } @@ -264,7 +259,7 @@ impl<'a> Storage<'a> { /// (defined by a pointer and offset). pub fn copy_value_to_memory( &mut self, - code_builder: &mut CodeBuilder, + code_builder: &mut FunctionBuilder, to_ptr: LocalId, to_offset: u32, from_symbol: Symbol, @@ -297,20 +292,20 @@ impl<'a> Storage<'a> { | 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), + use crate::function_builder::Align::*; + code_builder.get_local(to_ptr); + self.load_symbols(code_builder, &[from_symbol]); + match (value_type, size) { + (ValueType::I64, 8) => code_builder.i64_store(Bytes8, to_offset), + (ValueType::I32, 4) => code_builder.i32_store(Bytes4, to_offset), + (ValueType::I32, 2) => code_builder.i32_store16(Bytes2, to_offset), + (ValueType::I32, 1) => code_builder.i32_store8(Bytes1, to_offset), + (ValueType::F32, 4) => code_builder.f32_store(Bytes4, to_offset), + (ValueType::F64, 8) => code_builder.f64_store(Bytes8, to_offset), _ => { panic!("Cannot store {:?} with alignment of {:?}", value_type, size); } }; - code_builder.push(GetLocal(to_ptr.0)); - self.load_symbols(code_builder, &[from_symbol]); - code_builder.push(store_instruction); size } } @@ -320,7 +315,7 @@ impl<'a> Storage<'a> { /// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory` pub fn clone_value( &mut self, - code_builder: &mut CodeBuilder, + code_builder: &mut FunctionBuilder, to: &StoredValue, from: &StoredValue, from_symbol: Symbol, @@ -343,7 +338,7 @@ impl<'a> Storage<'a> { debug_assert!(to_value_type == from_value_type); debug_assert!(to_size == from_size); self.load_symbols(code_builder, &[from_symbol]); - code_builder.push(SetLocal(to_local_id.0)); + code_builder.set_local(*to_local_id); self.symbol_storage_map.insert(from_symbol, to.clone()); } @@ -361,8 +356,8 @@ impl<'a> Storage<'a> { ) => { debug_assert!(to_value_type == from_value_type); debug_assert!(to_size == from_size); - code_builder - .extend_from_slice(&[GetLocal(from_local_id.0), SetLocal(to_local_id.0)]); + code_builder.get_local(*from_local_id); + code_builder.set_local(*to_local_id); } ( @@ -408,7 +403,7 @@ impl<'a> Storage<'a> { /// (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, + code_builder: &mut FunctionBuilder, symbol: Symbol, storage: StoredValue, ) -> StoredValue { @@ -421,7 +416,7 @@ impl<'a> Storage<'a> { let local_id = self.get_next_local_id(); if vm_state != VirtualMachineSymbolState::NotYetPushed { code_builder.load_symbol(symbol, vm_state, local_id); - code_builder.push(SetLocal(local_id.0)); + code_builder.set_local(local_id); } self.local_types.push(value_type); diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index 5cf709a597..ee8a80313b 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -1,130 +1,109 @@ use parity_wasm::builder; use parity_wasm::builder::ModuleBuilder; -use parity_wasm::elements::{ - Instruction, Instruction::*, Instructions, Internal, Local, ValueType, -}; use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; -use roc_gen_wasm::*; +use roc_gen_wasm::function_builder::{Align, FunctionBuilder, ValueType}; use roc_std::{RocDec, RocList, RocOrder, RocStr}; -const STACK_POINTER_LOCAL_ID: u32 = 0; - pub trait Wasm32TestResult { fn insert_test_wrapper( module_builder: &mut ModuleBuilder, + code_builder: &mut FunctionBuilder, wrapper_name: &str, main_function_index: u32, ) { - let instructions = Self::build_wrapper_body(main_function_index); - let signature = builder::signature().with_result(ValueType::I32).build_sig(); - let stack_frame_pointer = Local::new(1, ValueType::I32); - let function_def = builder::function() - .with_signature(signature) - .body() - .with_locals(vec![stack_frame_pointer]) - .with_instructions(Instructions::new(instructions)) - .build() // body - .build(); // function - - let location = module_builder.push_function(function_def); + // parity-wasm FunctionDefinition with no instructions + let empty_fn_def = builder::function().with_signature(signature).build(); + let location = module_builder.push_function(empty_fn_def); let export = builder::export() .field(wrapper_name) .with_internal(Internal::Function(location.body)) .build(); - module_builder.push_export(export); + + self.build_wrapper_body(code_builder, main_function_index); } - fn build_wrapper_body(main_function_index: u32) -> Vec; + fn build_wrapper_body(code_builder: &mut FunctionBuilder, main_function_index: u32); } macro_rules! build_wrapper_body_primitive { - ($store_instruction: expr, $align: expr) => { - fn build_wrapper_body(main_function_index: u32) -> Vec { - let size: i32 = 8; - let mut instructions = Vec::with_capacity(16); - push_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); - instructions.extend([ - // load result address to prepare for the store instruction later - GetLocal(STACK_POINTER_LOCAL_ID), - // - // Call the main function with no arguments. Get primitive back. - Call(main_function_index), - // - // Store the primitive at the allocated address - $store_instruction($align, 0), - // - // Return the result pointer - GetLocal(STACK_POINTER_LOCAL_ID), - ]); - pop_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); - instructions.push(End); - instructions + ($store_instruction: ident, $align: expr) => { + fn build_wrapper_body( + code_builder: &mut FunctionBuilder, + main_function_index: u32, + ) -> Vec { + let frame_pointer_id = LocalId(0); + let frame_pointer = Some(frame_pointer_id); + let local_types = &[ValueType::I32]; + let frame_size = 8; + + code_builder.get_local(frame_pointer_id); + code_builder.call(main_function_index, 0, true); + code_builder.$store_instruction($align, 0); + code_builder.get_local(frame_pointer_id); + + code_builder.finalize(local_types, frame_size, frame_pointer); } }; } macro_rules! wasm_test_result_primitive { - ($type_name: ident, $store_instruction: expr, $align: expr) => { + ($type_name: ident, $store_instruction: ident, $align: expr) => { impl Wasm32TestResult for $type_name { build_wrapper_body_primitive!($store_instruction, $align); } }; } -fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec { - let mut instructions = Vec::with_capacity(16); - push_stack_frame( - &mut instructions, - size as i32, - LocalId(STACK_POINTER_LOCAL_ID), - ); - instructions.extend([ - // - // Call the main function with the allocated address to write the result. - // No value is returned to the VM stack. This is the same as in compiled C. - GetLocal(STACK_POINTER_LOCAL_ID), - Call(main_function_index), - // - // Return the result address - GetLocal(STACK_POINTER_LOCAL_ID), - ]); - pop_stack_frame( - &mut instructions, - size as i32, - LocalId(STACK_POINTER_LOCAL_ID), - ); - instructions.push(End); - instructions +fn build_wrapper_body_stack_memory( + code_builder: &mut FunctionBuilder, + main_function_index: u32, + size: usize, +) -> Vec { + let local_id = LocalId(0); + let local_types = &[ValueType::I32]; + let frame_pointer = Some(local_id); + + code_builder.get_local(local_id); + code_builder.call(main_function_index, 0, true); + code_builder.get_local(local_id); + code_builder.finalize(local_types, size, frame_pointer); } macro_rules! wasm_test_result_stack_memory { ($type_name: ident) => { impl Wasm32TestResult for $type_name { - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory(main_function_index, $type_name::ACTUAL_WIDTH) + fn build_wrapper_body( + code_builder: &mut FunctionBuilder, + main_function_index: u32, + ) -> Vec { + build_wrapper_body_stack_memory( + code_builder, + main_function_index, + $type_name::ACTUAL_WIDTH, + ) } } }; } -wasm_test_result_primitive!(bool, I32Store8, ALIGN_1); -wasm_test_result_primitive!(RocOrder, I32Store8, ALIGN_1); +wasm_test_result_primitive!(bool, i32_store8, Align::Bytes1); +wasm_test_result_primitive!(RocOrder, i32_store8, Align::Bytes1); -wasm_test_result_primitive!(u8, I32Store8, ALIGN_1); -wasm_test_result_primitive!(i8, I32Store8, ALIGN_1); -wasm_test_result_primitive!(u16, I32Store16, ALIGN_2); -wasm_test_result_primitive!(i16, I32Store16, ALIGN_2); -wasm_test_result_primitive!(u32, I32Store, ALIGN_4); -wasm_test_result_primitive!(i32, I32Store, ALIGN_4); -wasm_test_result_primitive!(u64, I64Store, ALIGN_8); -wasm_test_result_primitive!(i64, I64Store, ALIGN_8); +wasm_test_result_primitive!(u8, i32_store8, Align::Bytes1); +wasm_test_result_primitive!(i8, i32_store8, Align::Bytes1); +wasm_test_result_primitive!(u16, i32_store16, Align::Bytes2); +wasm_test_result_primitive!(i16, i32_store16, Align::Bytes2); +wasm_test_result_primitive!(u32, i32_store, Align::Bytes4); +wasm_test_result_primitive!(i32, i32_store, Align::Bytes4); +wasm_test_result_primitive!(u64, i64_store, Align::Bytes8); +wasm_test_result_primitive!(i64, i64_store, Align::Bytes8); -wasm_test_result_primitive!(f32, F32Store, ALIGN_8); -wasm_test_result_primitive!(f64, F64Store, ALIGN_8); +wasm_test_result_primitive!(f32, f32_store, Align::Bytes8); +wasm_test_result_primitive!(f64, f64_store, Align::Bytes8); wasm_test_result_stack_memory!(u128); wasm_test_result_stack_memory!(i128); @@ -138,7 +117,7 @@ impl Wasm32TestResult for RocList { } impl Wasm32TestResult for &'_ T { - build_wrapper_body_primitive!(I32Store, ALIGN_4); + build_wrapper_body_primitive!(i32_store, Align::Bytes4); } impl Wasm32TestResult for [T; N]