diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index c8f5363fd1..ae565b83f4 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -34,6 +34,7 @@ interface List sortWith, drop, dropAt, + dropLast, swap ] imports [] @@ -439,6 +440,9 @@ drop : List elem, Nat -> List elem ## To replace the element at a given index, instead of dropping it, see [List.set]. dropAt : List elem, Nat -> List elem +## Drops the last element in a List. +dropLast : List elem -> List elem + ## Adds a new element to the end of the list. ## ## >>> List.append [ "a", "b" ] "c" diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 4b648c6486..645d485ed6 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -937,6 +937,13 @@ pub fn types() -> MutMap { Box::new(list_type(flex(TVAR1))), ); + // dropLast : List elem -> List elem + add_top_level_function_type!( + Symbol::LIST_DROP_LAST, + vec![list_type(flex(TVAR1))], + Box::new(list_type(flex(TVAR1))), + ); + // swap : List elem, Nat, Nat -> List elem add_top_level_function_type!( Symbol::LIST_SWAP, diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 7fcb56808f..6f77843588 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -89,6 +89,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_MAP3 => list_map3, LIST_DROP => list_drop, LIST_DROP_AT => list_drop_at, + LIST_DROP_LAST => list_drop_last, LIST_SWAP => list_swap, LIST_MAP_WITH_INDEX => list_map_with_index, LIST_KEEP_IF => list_keep_if, @@ -2010,6 +2011,51 @@ fn list_drop_at(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// List.dropLast: List elem -> List elem +fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + let index_var = var_store.fresh(); + let arg_var = var_store.fresh(); + let len_var = Variable::NAT; + let num_var = len_var; + let num_precision_var = Variable::NATURAL; + + let body = RunLowLevel { + op: LowLevel::ListDropAt, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + ( + index_var, + // Num.sub (List.len list) 1 + RunLowLevel { + op: LowLevel::NumSubWrap, + args: vec![ + ( + arg_var, + // List.len list + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: len_var, + }, + ), + (arg_var, int(num_var, num_precision_var, 1)), + ], + ret_var: len_var, + }, + ), + ], + ret_var: list_var, + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1)], + var_store, + body, + list_var, + ) +} /// List.append : List elem, elem -> List elem fn list_append(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 75917fd6ff..44d3adf41d 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::code_builder::{BlockType, CodeBuilder, 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, overwrite_padded_u32, 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) @@ -25,18 +20,17 @@ const UNUSED_DATA_SECTION_BYTES: u32 = 1024; #[derive(Clone, Copy, Debug)] struct LabelId(u32); -// TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43) pub struct WasmBackend<'a> { - // Module level: Wasm AST - pub module_builder: ModuleBuilder, env: &'a Env<'a>, - // Module level: internal state & IR mappings + // Module-level data + pub module_builder: ModuleBuilder, + pub code_section_bytes: std::vec::Vec, _data_offset_map: MutMap, u32>, _data_offset_next: u32, proc_symbol_map: MutMap, - // Function level + // Function-level data code_builder: CodeBuilder<'a>, storage: Storage<'a>, @@ -46,21 +40,29 @@ pub struct WasmBackend<'a> { } impl<'a> WasmBackend<'a> { - pub fn new(env: &'a Env<'a>) -> Self { + pub fn new(env: &'a Env<'a>, num_procs: usize) -> Self { + // Code section is prefixed with the number of Wasm functions + // For now, this is the same as the number of IR procedures (until we start inlining!) + let mut code_section_bytes = std::vec::Vec::with_capacity(4096); + + // Reserve space for code section header: inner byte length and number of functions + // Padded to the maximum 5 bytes each, so we can update later without moving everything + code_section_bytes.resize(10, 0); + overwrite_padded_u32(&mut code_section_bytes[5..10], num_procs as u32); // gets modified in unit tests + WasmBackend { - // Module: Wasm AST - module_builder: builder::module(), env, - // Module: internal state & IR mappings + // Module-level data + module_builder: builder::module(), + code_section_bytes, _data_offset_map: MutMap::default(), _data_offset_next: UNUSED_DATA_SECTION_BYTES, proc_symbol_map: MutMap::default(), + // Function-level data block_depth: 0, joinpoint_label_map: MutMap::default(), - - // Functions code_builder: CodeBuilder::new(env.arena), storage: Storage::new(env.arena), } @@ -82,21 +84,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 +111,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 +122,29 @@ 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) -> Result<(), String> { + // 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); + // 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, + ); - 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 + self.code_builder + .serialize(&mut self.code_section_bytes) + .map_err(|e| format!("{:?}", e))?; + Ok(()) } /********************************************************** @@ -182,17 +156,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 +229,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 +267,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 +349,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 +401,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 +419,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 +446,6 @@ impl<'a> WasmBackend<'a> { return Err(format!("loading literal, {:?}, is not yet implemented", x)); } }; - self.code_builder.push(instruction); Ok(()) } @@ -546,34 +516,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/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index 17c49996db..09aa6a58a3 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -1,16 +1,83 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use core::panic; -use std::collections::BTreeMap; use std::fmt::Debug; -use parity_wasm::elements::{Instruction, Instruction::*}; use roc_module::symbol::Symbol; -use crate::LocalId; +use crate::{ + encode_f32, encode_f64, encode_i32, encode_i64, encode_u32, round_up_to_alignment, LocalId, + FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID, +}; +use crate::{opcodes::*, overwrite_padded_u32}; const DEBUG_LOG: bool = false; +/// Wasm value type. (Rust representation matches Wasm encoding) +#[repr(u8)] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum ValueType { + I32 = 0x7f, + I64 = 0x7e, + F32 = 0x7d, + F64 = 0x7c, +} + +// This is a bit unfortunate. Will go away if we generate our own Types section. +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), +} + +impl BlockType { + pub fn as_byte(&self) -> u8 { + match self { + Self::NoResult => 0x40, + Self::Value(t) => *t as u8, + } + } +} + +/// Wasm memory alignment. (Rust representation matches Wasm encoding) +#[repr(u8)] +#[derive(Clone, Copy, Debug)] +pub enum Align { + Bytes1 = 0, + Bytes2 = 1, + Bytes4 = 2, + Bytes8 = 3, + Bytes16 = 4, + Bytes32 = 5, + Bytes64 = 6, + // ... we can add more if we need them ... +} + +impl From for Align { + fn from(x: u32) -> Align { + match x { + 1 => Align::Bytes1, + 2 => Align::Bytes2, + 4 => Align::Bytes4, + 8 => Align::Bytes8, + 16 => Align::Bytes16, + 32 => Align::Bytes32, + 64 => Align::Bytes64, + _ => panic!("{:?}-byte alignment not supported", x), + } + } +} + #[derive(Debug, Clone, PartialEq, Copy)] pub enum VirtualMachineSymbolState { /// Value doesn't exist yet @@ -26,18 +93,52 @@ pub enum VirtualMachineSymbolState { Popped { pushed_at: usize }, } +// An instruction (local.set or local.tee) to be inserted into the function code +#[derive(Debug)] +struct InsertLocation { + insert_at: usize, + start: usize, + end: usize, +} + +macro_rules! instruction_no_args { + ($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => { + pub fn $method_name(&mut self) { + self.inst($opcode, $pops, $push); + } + }; +} + +macro_rules! instruction_memargs { + ($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => { + pub fn $method_name(&mut self, align: Align, offset: u32) { + self.inst_mem($opcode, $pops, $push, align, offset); + } + }; +} + #[derive(Debug)] pub struct CodeBuilder<'a> { /// The main container for the instructions - code: Vec<'a, Instruction>, + code: Vec<'a, u8>, - /// 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, + /// Instruction bytes to be inserted into the code when finalizing the function + /// (Used for setting locals when we realise they are used multiple times) + insert_bytes: Vec<'a, u8>, + + /// Code locations where the insert_bytes should go + insert_locations: Vec<'a, InsertLocation>, + + /// Bytes for local variable declarations and stack-frame setup code. + /// We can't write this until we've finished the main code. But it goes + /// before it in the final output, so we need a separate vector. + preamble: Vec<'a, u8>, + + /// Encoded bytes for the inner length of the function, locals + code. + /// ("inner" because it doesn't include its own length!) + /// Again, we can't write this until we've finished the code and preamble, + /// but it goes before them in the binary, so it's a separate vector. + inner_length: Vec<'a, u8>, /// Our simulation model of the Wasm stack machine /// Keeps track of where Symbol values are in the VM stack @@ -48,102 +149,33 @@ pub struct CodeBuilder<'a> { impl<'a> CodeBuilder<'a> { pub fn new(arena: &'a Bump) -> Self { CodeBuilder { - vm_stack: Vec::with_capacity_in(32, arena), - insertions: BTreeMap::default(), code: Vec::with_capacity_in(1024, arena), + insert_locations: Vec::with_capacity_in(32, arena), + insert_bytes: Vec::with_capacity_in(64, arena), + preamble: Vec::with_capacity_in(32, arena), + inner_length: Vec::with_capacity_in(5, arena), + vm_stack: Vec::with_capacity_in(32, arena), } } pub fn clear(&mut self) { self.code.clear(); - self.insertions.clear(); + self.insert_locations.clear(); + self.insert_bytes.clear(); + self.preamble.clear(); + self.inner_length.clear(); self.vm_stack.clear(); } - /// Add an 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); - if push { - self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); - } - if DEBUG_LOG { - println!("{:?} {:?}", inst, self.vm_stack); - } - self.code.push(inst); - } + /********************************************************** - /// Add many instructions - 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; - 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.vm_stack.truncate(min_len); - self.vm_stack - .resize(len, Symbol::WASM_ANONYMOUS_STACK_VALUE); - if DEBUG_LOG { - println!("{:?} {:?}", instructions, self.vm_stack); - } - self.code.extend_from_slice(instructions); - } + SYMBOLS - /// 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 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 = - std::vec::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 - ); - } - self.vm_stack.truncate(stack_depth - pops); - if push { - self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); - } - let inst = Call(function_index); - if DEBUG_LOG { - println!("{:?} {:?}", inst, self.vm_stack); - } - self.code.push(inst); - } + The Wasm VM stores temporary values in its stack machine. + We track which stack positions correspond to IR Symbols, + because it helps to generate more efficient code. - /// Finalize a function body by copying all instructions into a vector - pub fn finalize_into(&mut self, final_code: &mut std::vec::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()); - next_insertion = insertions_iter.next(); - } - _ => {} - } - final_code.push(instruction); - } - debug_assert!(next_insertion == None); - } - - /// Total number of instructions in the final output - pub fn len(&self) -> usize { - 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 @@ -180,6 +212,19 @@ impl<'a> CodeBuilder<'a> { true } + fn add_insertion(&mut self, insert_at: usize, opcode: u8, immediate: u32) { + let start = self.insert_bytes.len(); + + self.insert_bytes.push(opcode); + encode_u32(&mut self.insert_bytes, immediate); + + self.insert_locations.push(InsertLocation { + insert_at, + start, + end: self.insert_bytes.len(), + }); + } + /// 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. @@ -208,22 +253,14 @@ impl<'a> CodeBuilder<'a> { } 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)); + // Insert a local.set where the value was created + self.add_insertion(pushed_at, SETLOCAL, next_local_id.0); + + // Take the value out of the stack where local.set was inserted 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); + // Insert a local.get at the current position + self.get_local(next_local_id); self.vm_stack.push(symbol); // This Symbol is no longer stored in the VM stack, but in a local @@ -239,22 +276,11 @@ impl<'a> CodeBuilder<'a> { Popped { pushed_at } => { // This Symbol is being used for a second time + // Insert a local.tee where it was pushed, so we don't interfere with the first usage + self.add_insertion(pushed_at, TEELOCAL, next_local_id.0); - // 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 - 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); + // Insert a local.get at the current position + self.get_local(next_local_id); self.vm_stack.push(symbol); // This symbol has been promoted to a Local @@ -263,197 +289,400 @@ impl<'a> CodeBuilder<'a> { } } } -} -fn get_pops_and_pushes(inst: &Instruction) -> (u8, bool) { - match inst { - Unreachable => (0, false), - Nop => (0, false), - Block(_) => (0, false), - Loop(_) => (0, false), - If(_) => (1, false), - Else => (0, false), - End => (0, false), - Br(_) => (0, false), - BrIf(_) => (1, false), - BrTable(_) => (1, false), - Return => (0, false), + /********************************************************** - Call(_) | CallIndirect(_, _) => { - panic!("Unknown number of pushes and pops. Use add_call()"); + FINALIZE AND SERIALIZE + + ***********************************************************/ + + /// Generate bytes to declare the function's local variables + fn build_local_declarations(&mut self, local_types: &[ValueType]) { + // reserve one byte for num_batches + self.preamble.push(0); + + if local_types.is_empty() { + return; } - Drop => (1, false), - Select => (3, true), + // Write declarations in batches of the same ValueType + let mut num_batches: u32 = 0; + let mut batch_type = local_types[0]; + let mut batch_size = 0; + for t in local_types { + if *t == batch_type { + batch_size += 1; + } else { + encode_u32(&mut self.preamble, batch_size); + self.preamble.push(batch_type as u8); + batch_type = *t; + batch_size = 1; + num_batches += 1; + } + } + encode_u32(&mut self.preamble, batch_size); + self.preamble.push(batch_type as u8); + num_batches += 1; - 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), + // Go back and write the number of batches at the start + if num_batches < 128 { + self.preamble[0] = num_batches as u8; + } else { + // We need more than 1 byte to encode num_batches! + // This is a ridiculous edge case, so just pad to 5 bytes for simplicity + let old_len = self.preamble.len(); + self.preamble.resize(old_len + 4, 0); + self.preamble.copy_within(1..old_len, 5); + overwrite_padded_u32(&mut self.preamble[0..5], num_batches); + } } + + /// Generate instruction bytes to grab a frame of stack memory on entering the function + fn build_stack_frame_push(&mut self, frame_size: i32, frame_pointer: LocalId) { + // Can't use the usual instruction methods because they push to self.code. + // This is the only case where we push instructions somewhere different. + self.preamble.push(GETGLOBAL); + encode_u32(&mut self.preamble, STACK_POINTER_GLOBAL_ID); + self.preamble.push(I32CONST); + encode_i32(&mut self.preamble, frame_size); + self.preamble.push(I32SUB); + self.preamble.push(TEELOCAL); + encode_u32(&mut self.preamble, frame_pointer.0); + self.preamble.push(SETGLOBAL); + encode_u32(&mut self.preamble, STACK_POINTER_GLOBAL_ID); + } + + /// Generate instruction bytes to release a frame of stack memory on leaving the function + fn build_stack_frame_pop(&mut self, frame_size: i32, frame_pointer: LocalId) { + self.get_local(frame_pointer); + self.i32_const(frame_size); + self.i32_add(); + self.set_global(STACK_POINTER_GLOBAL_ID); + } + + /// Finalize the function + /// 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: Option, + ) { + self.build_local_declarations(local_types); + + 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_ptr_id); + self.build_stack_frame_pop(aligned_size, frame_ptr_id); + } + + self.code.push(END); + + let inner_len = self.preamble.len() + self.code.len() + self.insert_bytes.len(); + encode_u32(&mut self.inner_length, inner_len as u32); + } + + /// Write out all the bytes in the right order + pub fn serialize(&mut self, writer: &mut W) -> std::io::Result<()> { + writer.write_all(&self.inner_length)?; + writer.write_all(&self.preamble)?; + + // We created each insertion when a local was used for the _second_ time. + // But we want them in the order they were first assigned, which may not be the same. + self.insert_locations.sort_by_key(|loc| loc.insert_at); + + let mut pos: usize = 0; + for location in self.insert_locations.iter() { + writer.write_all(&self.code[pos..location.insert_at])?; + writer.write_all(&self.insert_bytes[location.start..location.end])?; + pos = location.insert_at; + } + + let len = self.code.len(); + writer.write_all(&self.code[pos..len]) + } + + /********************************************************** + + INSTRUCTION HELPER METHODS + + ***********************************************************/ + + /// Base method for generating instructions + /// Emits the opcode and simulates VM stack push/pop + fn inst(&mut self, opcode: u8, pops: usize, push: bool) { + let new_len = self.vm_stack.len() - pops as usize; + self.vm_stack.truncate(new_len); + if push { + self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); + } + self.code.push(opcode); + } + + fn inst_imm8(&mut self, opcode: u8, pops: usize, push: bool, immediate: u8) { + self.inst(opcode, pops, push); + self.code.push(immediate); + } + + fn inst_imm32(&mut self, opcode: u8, pops: usize, push: bool, immediate: u32) { + self.inst(opcode, pops, push); + encode_u32(&mut self.code, immediate); + } + + fn inst_mem(&mut self, opcode: u8, pops: usize, push: bool, align: Align, offset: u32) { + self.inst(opcode, pops, push); + self.code.push(align as u8); + encode_u32(&mut self.code, offset); + } + + /********************************************************** + + INSTRUCTION METHODS + + One method for each Wasm instruction (in same order as the spec) + macros are for compactness & readability for the most common cases + Patterns that don't repeat very much don't have macros + + ***********************************************************/ + + instruction_no_args!(unreachable_, UNREACHABLE, 0, false); + instruction_no_args!(nop, NOP, 0, false); + + pub fn block(&mut self, ty: BlockType) { + self.inst_imm8(BLOCK, 0, false, ty.as_byte()); + } + pub fn loop_(&mut self, ty: BlockType) { + self.inst_imm8(LOOP, 0, false, ty.as_byte()); + } + pub fn if_(&mut self, ty: BlockType) { + self.inst_imm8(IF, 1, false, ty.as_byte()); + } + + instruction_no_args!(else_, ELSE, 0, false); + instruction_no_args!(end, END, 0, false); + + pub fn br(&mut self, levels: u32) { + self.inst_imm32(BR, 0, false, levels); + } + pub fn br_if(&mut self, levels: u32) { + self.inst_imm32(BRIF, 1, false, levels); + } + fn br_table() { + panic!("TODO"); + } + + instruction_no_args!(return_, RETURN, 0, false); + + pub fn call(&mut self, function_index: u32, n_args: usize, has_return_val: bool) { + let stack_depth = self.vm_stack.len(); + if n_args > stack_depth { + panic!( + "Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\n{:?}", + function_index, n_args, stack_depth, self + ); + } + self.vm_stack.truncate(stack_depth - n_args); + if has_return_val { + self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); + } + self.code.push(CALL); + encode_u32(&mut self.code, function_index); + } + fn call_indirect() { + panic!("Not implemented. Roc doesn't use function pointers"); + } + + instruction_no_args!(drop_, DROP, 1, false); + instruction_no_args!(select, SELECT, 3, true); + + pub fn get_local(&mut self, id: LocalId) { + self.inst_imm32(GETLOCAL, 0, true, id.0); + } + pub fn set_local(&mut self, id: LocalId) { + self.inst_imm32(SETLOCAL, 1, false, id.0); + } + pub fn tee_local(&mut self, id: LocalId) { + self.inst_imm32(TEELOCAL, 0, false, id.0); + } + pub fn get_global(&mut self, id: u32) { + self.inst_imm32(GETGLOBAL, 0, true, id); + } + pub fn set_global(&mut self, id: u32) { + self.inst_imm32(SETGLOBAL, 1, false, id); + } + + instruction_memargs!(i32_load, I32LOAD, 1, true); + instruction_memargs!(i64_load, I64LOAD, 1, true); + instruction_memargs!(f32_load, F32LOAD, 1, true); + instruction_memargs!(f64_load, F64LOAD, 1, true); + instruction_memargs!(i32_load8_s, I32LOAD8S, 1, true); + instruction_memargs!(i32_load8_u, I32LOAD8U, 1, true); + instruction_memargs!(i32_load16_s, I32LOAD16S, 1, true); + instruction_memargs!(i32_load16_u, I32LOAD16U, 1, true); + instruction_memargs!(i64_load8_s, I64LOAD8S, 1, true); + instruction_memargs!(i64_load8_u, I64LOAD8U, 1, true); + instruction_memargs!(i64_load16_s, I64LOAD16S, 1, true); + instruction_memargs!(i64_load16_u, I64LOAD16U, 1, true); + instruction_memargs!(i64_load32_s, I64LOAD32S, 1, true); + instruction_memargs!(i64_load32_u, I64LOAD32U, 1, true); + instruction_memargs!(i32_store, I32STORE, 2, false); + instruction_memargs!(i64_store, I64STORE, 2, false); + instruction_memargs!(f32_store, F32STORE, 2, false); + instruction_memargs!(f64_store, F64STORE, 2, false); + instruction_memargs!(i32_store8, I32STORE8, 2, false); + instruction_memargs!(i32_store16, I32STORE16, 2, false); + instruction_memargs!(i64_store8, I64STORE8, 2, false); + instruction_memargs!(i64_store16, I64STORE16, 2, false); + instruction_memargs!(i64_store32, I64STORE32, 2, false); + + pub fn memory_size(&mut self) { + self.inst_imm8(CURRENTMEMORY, 0, true, 0); + } + pub fn memory_grow(&mut self) { + self.inst_imm8(GROWMEMORY, 1, true, 0); + } + pub fn i32_const(&mut self, x: i32) { + self.inst(I32CONST, 0, true); + encode_i32(&mut self.code, x); + } + pub fn i64_const(&mut self, x: i64) { + self.inst(I64CONST, 0, true); + encode_i64(&mut self.code, x); + } + pub fn f32_const(&mut self, x: f32) { + self.inst(F32CONST, 0, true); + encode_f32(&mut self.code, x); + } + pub fn f64_const(&mut self, x: f64) { + self.inst(F64CONST, 0, true); + encode_f64(&mut self.code, x); + } + + // TODO: Consider creating unified methods for numerical ops like 'eq' and 'add', + // passing the ValueType as an argument. Could simplify lowlevel code gen. + instruction_no_args!(i32_eqz, I32EQZ, 1, true); + instruction_no_args!(i32_eq, I32EQ, 2, true); + instruction_no_args!(i32_ne, I32NE, 2, true); + instruction_no_args!(i32_lt_s, I32LTS, 2, true); + instruction_no_args!(i32_lt_u, I32LTU, 2, true); + instruction_no_args!(i32_gt_s, I32GTS, 2, true); + instruction_no_args!(i32_gt_u, I32GTU, 2, true); + instruction_no_args!(i32_le_s, I32LES, 2, true); + instruction_no_args!(i32_le_u, I32LEU, 2, true); + instruction_no_args!(i32_ge_s, I32GES, 2, true); + instruction_no_args!(i32_ge_u, I32GEU, 2, true); + instruction_no_args!(i64_eqz, I64EQZ, 1, true); + instruction_no_args!(i64_eq, I64EQ, 2, true); + instruction_no_args!(i64_ne, I64NE, 2, true); + instruction_no_args!(i64_lt_s, I64LTS, 2, true); + instruction_no_args!(i64_lt_u, I64LTU, 2, true); + instruction_no_args!(i64_gt_s, I64GTS, 2, true); + instruction_no_args!(i64_gt_u, I64GTU, 2, true); + instruction_no_args!(i64_le_s, I64LES, 2, true); + instruction_no_args!(i64_le_u, I64LEU, 2, true); + instruction_no_args!(i64_ge_s, I64GES, 2, true); + instruction_no_args!(i64_ge_u, I64GEU, 2, true); + instruction_no_args!(f32_eq, F32EQ, 2, true); + instruction_no_args!(f32_ne, F32NE, 2, true); + instruction_no_args!(f32_lt, F32LT, 2, true); + instruction_no_args!(f32_gt, F32GT, 2, true); + instruction_no_args!(f32_le, F32LE, 2, true); + instruction_no_args!(f32_ge, F32GE, 2, true); + instruction_no_args!(f64_eq, F64EQ, 2, true); + instruction_no_args!(f64_ne, F64NE, 2, true); + instruction_no_args!(f64_lt, F64LT, 2, true); + instruction_no_args!(f64_gt, F64GT, 2, true); + instruction_no_args!(f64_le, F64LE, 2, true); + instruction_no_args!(f64_ge, F64GE, 2, true); + instruction_no_args!(i32_clz, I32CLZ, 1, true); + instruction_no_args!(i32_ctz, I32CTZ, 1, true); + instruction_no_args!(i32_popcnt, I32POPCNT, 1, true); + instruction_no_args!(i32_add, I32ADD, 2, true); + instruction_no_args!(i32_sub, I32SUB, 2, true); + instruction_no_args!(i32_mul, I32MUL, 2, true); + instruction_no_args!(i32_div_s, I32DIVS, 2, true); + instruction_no_args!(i32_div_u, I32DIVU, 2, true); + instruction_no_args!(i32_rem_s, I32REMS, 2, true); + instruction_no_args!(i32_rem_u, I32REMU, 2, true); + instruction_no_args!(i32_and, I32AND, 2, true); + instruction_no_args!(i32_or, I32OR, 2, true); + instruction_no_args!(i32_xor, I32XOR, 2, true); + instruction_no_args!(i32_shl, I32SHL, 2, true); + instruction_no_args!(i32_shr_s, I32SHRS, 2, true); + instruction_no_args!(i32_shr_u, I32SHRU, 2, true); + instruction_no_args!(i32_rotl, I32ROTL, 2, true); + instruction_no_args!(i32_rotr, I32ROTR, 2, true); + instruction_no_args!(i64_clz, I64CLZ, 1, true); + instruction_no_args!(i64_ctz, I64CTZ, 1, true); + instruction_no_args!(i64_popcnt, I64POPCNT, 1, true); + instruction_no_args!(i64_add, I64ADD, 2, true); + instruction_no_args!(i64_sub, I64SUB, 2, true); + instruction_no_args!(i64_mul, I64MUL, 2, true); + instruction_no_args!(i64_div_s, I64DIVS, 2, true); + instruction_no_args!(i64_div_u, I64DIVU, 2, true); + instruction_no_args!(i64_rem_s, I64REMS, 2, true); + instruction_no_args!(i64_rem_u, I64REMU, 2, true); + instruction_no_args!(i64_and, I64AND, 2, true); + instruction_no_args!(i64_or, I64OR, 2, true); + instruction_no_args!(i64_xor, I64XOR, 2, true); + instruction_no_args!(i64_shl, I64SHL, 2, true); + instruction_no_args!(i64_shr_s, I64SHRS, 2, true); + instruction_no_args!(i64_shr_u, I64SHRU, 2, true); + instruction_no_args!(i64_rotl, I64ROTL, 2, true); + instruction_no_args!(i64_rotr, I64ROTR, 2, true); + instruction_no_args!(f32_abs, F32ABS, 1, true); + instruction_no_args!(f32_neg, F32NEG, 1, true); + instruction_no_args!(f32_ceil, F32CEIL, 1, true); + instruction_no_args!(f32_floor, F32FLOOR, 1, true); + instruction_no_args!(f32_trunc, F32TRUNC, 1, true); + instruction_no_args!(f32_nearest, F32NEAREST, 1, true); + instruction_no_args!(f32_sqrt, F32SQRT, 1, true); + instruction_no_args!(f32_add, F32ADD, 2, true); + instruction_no_args!(f32_sub, F32SUB, 2, true); + instruction_no_args!(f32_mul, F32MUL, 2, true); + instruction_no_args!(f32_div, F32DIV, 2, true); + instruction_no_args!(f32_min, F32MIN, 2, true); + instruction_no_args!(f32_max, F32MAX, 2, true); + instruction_no_args!(f32_copysign, F32COPYSIGN, 2, true); + instruction_no_args!(f64_abs, F64ABS, 1, true); + instruction_no_args!(f64_neg, F64NEG, 1, true); + instruction_no_args!(f64_ceil, F64CEIL, 1, true); + instruction_no_args!(f64_floor, F64FLOOR, 1, true); + instruction_no_args!(f64_trunc, F64TRUNC, 1, true); + instruction_no_args!(f64_nearest, F64NEAREST, 1, true); + instruction_no_args!(f64_sqrt, F64SQRT, 1, true); + instruction_no_args!(f64_add, F64ADD, 2, true); + instruction_no_args!(f64_sub, F64SUB, 2, true); + instruction_no_args!(f64_mul, F64MUL, 2, true); + instruction_no_args!(f64_div, F64DIV, 2, true); + instruction_no_args!(f64_min, F64MIN, 2, true); + instruction_no_args!(f64_max, F64MAX, 2, true); + instruction_no_args!(f64_copysign, F64COPYSIGN, 2, true); + instruction_no_args!(i32_wrap_i64, I32WRAPI64, 1, true); + instruction_no_args!(i32_trunc_s_f32, I32TRUNCSF32, 1, true); + instruction_no_args!(i32_trunc_u_f32, I32TRUNCUF32, 1, true); + instruction_no_args!(i32_trunc_s_f64, I32TRUNCSF64, 1, true); + instruction_no_args!(i32_trunc_u_f64, I32TRUNCUF64, 1, true); + instruction_no_args!(i64_extend_s_i32, I64EXTENDSI32, 1, true); + instruction_no_args!(i64_extend_u_i32, I64EXTENDUI32, 1, true); + instruction_no_args!(i64_trunc_s_f32, I64TRUNCSF32, 1, true); + instruction_no_args!(i64_trunc_u_f32, I64TRUNCUF32, 1, true); + instruction_no_args!(i64_trunc_s_f64, I64TRUNCSF64, 1, true); + instruction_no_args!(i64_trunc_u_f64, I64TRUNCUF64, 1, true); + instruction_no_args!(f32_convert_s_i32, F32CONVERTSI32, 1, true); + instruction_no_args!(f32_convert_u_i32, F32CONVERTUI32, 1, true); + instruction_no_args!(f32_convert_s_i64, F32CONVERTSI64, 1, true); + instruction_no_args!(f32_convert_u_i64, F32CONVERTUI64, 1, true); + instruction_no_args!(f32_demote_f64, F32DEMOTEF64, 1, true); + instruction_no_args!(f64_convert_s_i32, F64CONVERTSI32, 1, true); + instruction_no_args!(f64_convert_u_i32, F64CONVERTUI32, 1, true); + instruction_no_args!(f64_convert_s_i64, F64CONVERTSI64, 1, true); + instruction_no_args!(f64_convert_u_i64, F64CONVERTUI64, 1, true); + instruction_no_args!(f64_promote_f32, F64PROMOTEF32, 1, true); + instruction_no_args!(i32_reinterpret_f32, I32REINTERPRETF32, 1, true); + instruction_no_args!(i64_reinterpret_f64, I64REINTERPRETF64, 1, true); + instruction_no_args!(f32_reinterpret_i32, F32REINTERPRETI32, 1, true); + instruction_no_args!(f64_reinterpret_i64, F64REINTERPRETI64, 1, true); } diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 5227f7755a..66aba67d04 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::{code_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 a66f7e92f3..d9312645da 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -1,33 +1,36 @@ mod backend; -mod code_builder; pub mod from_wasm32_memory; mod layout; mod storage; +#[allow(dead_code)] +pub mod code_builder; + +#[allow(dead_code)] +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::{Instruction, Internal, Module, Section}; 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::code_builder::{Align, CodeBuilder, 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 STACK_ALIGNMENT_BYTES: i32 = 16; +pub const FRAME_ALIGNMENT_BYTES: i32 = 16; + +/// Code section ID from spec +/// https://webassembly.github.io/spec/core/binary/modules.html#sections +pub const CODE_SECTION_ID: u8 = 10; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct LocalId(pub u32); @@ -42,8 +45,10 @@ pub fn build_module<'a>( env: &'a Env, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> Result, String> { - let (builder, _) = build_module_help(env, procedures)?; - let module = builder.build(); + let (builder, code_section_bytes, _) = build_module_help(env, procedures)?; + let mut module = builder.build(); + replace_code_section(&mut module, code_section_bytes); + module .into_bytes() .map_err(|e| -> String { format!("Error serialising Wasm module {:?}", e) }) @@ -52,8 +57,8 @@ pub fn build_module<'a>( pub fn build_module_help<'a>( env: &'a Env, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, -) -> Result<(builder::ModuleBuilder, u32), String> { - let mut backend = WasmBackend::new(env); +) -> Result<(builder::ModuleBuilder, std::vec::Vec, u32), String> { + let mut backend = WasmBackend::new(env, procedures.len()); let mut layout_ids = LayoutIds::default(); // Sort procedures by occurrence order @@ -86,6 +91,10 @@ pub fn build_module_help<'a>( } } + // Update code section length + let inner_length = (backend.code_section_bytes.len() - 5) as u32; + overwrite_padded_u32(&mut backend.code_section_bytes[0..5], inner_length); + // Because of the sorting above, we know the last function in the `for` is the main function. // Here we grab its index and return it, so that the test_wrapper is able to call it. // This is a workaround until we implement object files with symbols and relocations. @@ -105,23 +114,32 @@ 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)) .build(); backend.module_builder.push_global(stack_pointer_global); - Ok((backend.module_builder, main_function_index)) + Ok(( + backend.module_builder, + backend.code_section_bytes, + main_function_index, + )) } -fn encode_alignment(bytes: u32) -> u32 { - match bytes { - 1 => ALIGN_1, - 2 => ALIGN_2, - 4 => ALIGN_4, - 8 => ALIGN_8, - _ => panic!("{:?}-byte alignment is not supported", bytes), +/// Replace parity-wasm's code section with our own handmade one +pub fn replace_code_section(module: &mut Module, code_section_bytes: std::vec::Vec) { + let sections = module.sections_mut(); + let mut code_section_index = usize::MAX; + for (i, s) in sections.iter().enumerate() { + if let Section::Code(_) = s { + code_section_index = i; + } } + sections[code_section_index] = Section::Unparsed { + id: CODE_SECTION_ID, + payload: code_section_bytes, + }; } pub struct CopyMemoryConfig { @@ -138,33 +156,27 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) { return; } - let alignment_flag = encode_alignment(config.alignment_bytes); + let alignment = Align::from(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; } } @@ -178,31 +190,87 @@ 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, STACK_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 debug_panic(error: E) { + panic!("{:?}", error); } -pub fn pop_stack_frame( - instructions: &mut std::vec::Vec, - size: i32, - local_frame_pointer: LocalId, -) { - let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES); - instructions.extend([ - GetLocal(local_frame_pointer.0), - I32Const(aligned_size), - I32Add, - SetGlobal(STACK_POINTER_GLOBAL_ID), - ]); +/// Write an unsigned value into the provided buffer in LEB-128 format, returning byte length +/// +/// All integers in Wasm are variable-length encoded, which saves space for small values. +/// The most significant bit indicates "more bytes are coming", and the other 7 are payload. +macro_rules! encode_uleb128 { + ($name: ident, $ty: ty) => { + pub fn $name<'a>(buffer: &mut Vec<'a, u8>, value: $ty) -> usize { + let mut x = value; + let start_len = buffer.len(); + while x >= 0x80 { + buffer.push(0x80 | ((x & 0x7f) as u8)); + x >>= 7; + } + buffer.push(x as u8); + buffer.len() - start_len + } + }; +} + +encode_uleb128!(encode_u32, u32); +encode_uleb128!(encode_u64, u64); + +/// Write a *signed* value into the provided buffer in LEB-128 format, returning byte length +macro_rules! encode_sleb128 { + ($name: ident, $ty: ty) => { + pub fn $name<'a>(buffer: &mut Vec<'a, u8>, value: $ty) -> usize { + let mut x = value; + let start_len = buffer.len(); + loop { + let byte = (x & 0x7f) as u8; + x >>= 7; + let byte_is_negative = (byte & 0x40) != 0; + if ((x == 0 && !byte_is_negative) || (x == -1 && byte_is_negative)) { + buffer.push(byte); + break; + } + buffer.push(byte | 0x80); + } + buffer.len() - start_len + } + }; +} + +encode_sleb128!(encode_i32, i32); +encode_sleb128!(encode_i64, i64); + +/// No LEB encoding, and always little-endian regardless of compiler host. +macro_rules! encode_float { + ($name: ident, $ty: ty) => { + pub fn $name<'a>(buffer: &mut Vec<'a, u8>, value: $ty) { + let mut x = value.to_bits(); + let size = std::mem::size_of::<$ty>(); + for _ in 0..size { + buffer.push((x & 0xff) as u8); + x >>= 8; + } + } + }; +} + +encode_float!(encode_f32, f32); +encode_float!(encode_f64, f64); + +/// Overwrite a LEB-128 encoded u32 value, padded to maximum length (5 bytes) +/// +/// We need some fixed-length values so we can overwrite them without moving all following bytes. +/// Many parts of the binary format are prefixed with their length, which we only know at the end. +/// And relocation values get updated during linking. +/// This can help us to avoid copies, which is good for speed, but there's a tradeoff with output size. +/// +/// The value 3 is encoded as 0x83 0x80 0x80 0x80 0x00. +/// https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#relocation-sections +pub fn overwrite_padded_u32(buffer: &mut [u8], value: u32) { + let mut x = value; + for byte in buffer.iter_mut().take(4) { + *byte = 0x80 | ((x & 0x7f) as u8); + x >>= 7; + } + buffer[4] = x as u8; } diff --git a/compiler/gen_wasm/src/opcodes.rs b/compiler/gen_wasm/src/opcodes.rs new file mode 100644 index 0000000000..714889bdae --- /dev/null +++ b/compiler/gen_wasm/src/opcodes.rs @@ -0,0 +1,178 @@ +pub const UNREACHABLE: u8 = 0x00; +pub const NOP: u8 = 0x01; +pub const BLOCK: u8 = 0x02; +pub const LOOP: u8 = 0x03; +pub const IF: u8 = 0x04; +pub const ELSE: u8 = 0x05; +pub const END: u8 = 0x0b; +pub const BR: u8 = 0x0c; +pub const BRIF: u8 = 0x0d; +pub const BRTABLE: u8 = 0x0e; +pub const RETURN: u8 = 0x0f; +pub const CALL: u8 = 0x10; +pub const CALLINDIRECT: u8 = 0x11; +pub const DROP: u8 = 0x1a; +pub const SELECT: u8 = 0x1b; +pub const GETLOCAL: u8 = 0x20; +pub const SETLOCAL: u8 = 0x21; +pub const TEELOCAL: u8 = 0x22; +pub const GETGLOBAL: u8 = 0x23; +pub const SETGLOBAL: u8 = 0x24; +pub const I32LOAD: u8 = 0x28; +pub const I64LOAD: u8 = 0x29; +pub const F32LOAD: u8 = 0x2a; +pub const F64LOAD: u8 = 0x2b; +pub const I32LOAD8S: u8 = 0x2c; +pub const I32LOAD8U: u8 = 0x2d; +pub const I32LOAD16S: u8 = 0x2e; +pub const I32LOAD16U: u8 = 0x2f; +pub const I64LOAD8S: u8 = 0x30; +pub const I64LOAD8U: u8 = 0x31; +pub const I64LOAD16S: u8 = 0x32; +pub const I64LOAD16U: u8 = 0x33; +pub const I64LOAD32S: u8 = 0x34; +pub const I64LOAD32U: u8 = 0x35; +pub const I32STORE: u8 = 0x36; +pub const I64STORE: u8 = 0x37; +pub const F32STORE: u8 = 0x38; +pub const F64STORE: u8 = 0x39; +pub const I32STORE8: u8 = 0x3a; +pub const I32STORE16: u8 = 0x3b; +pub const I64STORE8: u8 = 0x3c; +pub const I64STORE16: u8 = 0x3d; +pub const I64STORE32: u8 = 0x3e; +pub const CURRENTMEMORY: u8 = 0x3f; +pub const GROWMEMORY: u8 = 0x40; +pub const I32CONST: u8 = 0x41; +pub const I64CONST: u8 = 0x42; +pub const F32CONST: u8 = 0x43; +pub const F64CONST: u8 = 0x44; +pub const I32EQZ: u8 = 0x45; +pub const I32EQ: u8 = 0x46; +pub const I32NE: u8 = 0x47; +pub const I32LTS: u8 = 0x48; +pub const I32LTU: u8 = 0x49; +pub const I32GTS: u8 = 0x4a; +pub const I32GTU: u8 = 0x4b; +pub const I32LES: u8 = 0x4c; +pub const I32LEU: u8 = 0x4d; +pub const I32GES: u8 = 0x4e; +pub const I32GEU: u8 = 0x4f; +pub const I64EQZ: u8 = 0x50; +pub const I64EQ: u8 = 0x51; +pub const I64NE: u8 = 0x52; +pub const I64LTS: u8 = 0x53; +pub const I64LTU: u8 = 0x54; +pub const I64GTS: u8 = 0x55; +pub const I64GTU: u8 = 0x56; +pub const I64LES: u8 = 0x57; +pub const I64LEU: u8 = 0x58; +pub const I64GES: u8 = 0x59; +pub const I64GEU: u8 = 0x5a; + +pub const F32EQ: u8 = 0x5b; +pub const F32NE: u8 = 0x5c; +pub const F32LT: u8 = 0x5d; +pub const F32GT: u8 = 0x5e; +pub const F32LE: u8 = 0x5f; +pub const F32GE: u8 = 0x60; + +pub const F64EQ: u8 = 0x61; +pub const F64NE: u8 = 0x62; +pub const F64LT: u8 = 0x63; +pub const F64GT: u8 = 0x64; +pub const F64LE: u8 = 0x65; +pub const F64GE: u8 = 0x66; + +pub const I32CLZ: u8 = 0x67; +pub const I32CTZ: u8 = 0x68; +pub const I32POPCNT: u8 = 0x69; +pub const I32ADD: u8 = 0x6a; +pub const I32SUB: u8 = 0x6b; +pub const I32MUL: u8 = 0x6c; +pub const I32DIVS: u8 = 0x6d; +pub const I32DIVU: u8 = 0x6e; +pub const I32REMS: u8 = 0x6f; +pub const I32REMU: u8 = 0x70; +pub const I32AND: u8 = 0x71; +pub const I32OR: u8 = 0x72; +pub const I32XOR: u8 = 0x73; +pub const I32SHL: u8 = 0x74; +pub const I32SHRS: u8 = 0x75; +pub const I32SHRU: u8 = 0x76; +pub const I32ROTL: u8 = 0x77; +pub const I32ROTR: u8 = 0x78; + +pub const I64CLZ: u8 = 0x79; +pub const I64CTZ: u8 = 0x7a; +pub const I64POPCNT: u8 = 0x7b; +pub const I64ADD: u8 = 0x7c; +pub const I64SUB: u8 = 0x7d; +pub const I64MUL: u8 = 0x7e; +pub const I64DIVS: u8 = 0x7f; +pub const I64DIVU: u8 = 0x80; +pub const I64REMS: u8 = 0x81; +pub const I64REMU: u8 = 0x82; +pub const I64AND: u8 = 0x83; +pub const I64OR: u8 = 0x84; +pub const I64XOR: u8 = 0x85; +pub const I64SHL: u8 = 0x86; +pub const I64SHRS: u8 = 0x87; +pub const I64SHRU: u8 = 0x88; +pub const I64ROTL: u8 = 0x89; +pub const I64ROTR: u8 = 0x8a; +pub const F32ABS: u8 = 0x8b; +pub const F32NEG: u8 = 0x8c; +pub const F32CEIL: u8 = 0x8d; +pub const F32FLOOR: u8 = 0x8e; +pub const F32TRUNC: u8 = 0x8f; +pub const F32NEAREST: u8 = 0x90; +pub const F32SQRT: u8 = 0x91; +pub const F32ADD: u8 = 0x92; +pub const F32SUB: u8 = 0x93; +pub const F32MUL: u8 = 0x94; +pub const F32DIV: u8 = 0x95; +pub const F32MIN: u8 = 0x96; +pub const F32MAX: u8 = 0x97; +pub const F32COPYSIGN: u8 = 0x98; +pub const F64ABS: u8 = 0x99; +pub const F64NEG: u8 = 0x9a; +pub const F64CEIL: u8 = 0x9b; +pub const F64FLOOR: u8 = 0x9c; +pub const F64TRUNC: u8 = 0x9d; +pub const F64NEAREST: u8 = 0x9e; +pub const F64SQRT: u8 = 0x9f; +pub const F64ADD: u8 = 0xa0; +pub const F64SUB: u8 = 0xa1; +pub const F64MUL: u8 = 0xa2; +pub const F64DIV: u8 = 0xa3; +pub const F64MIN: u8 = 0xa4; +pub const F64MAX: u8 = 0xa5; +pub const F64COPYSIGN: u8 = 0xa6; + +pub const I32WRAPI64: u8 = 0xa7; +pub const I32TRUNCSF32: u8 = 0xa8; +pub const I32TRUNCUF32: u8 = 0xa9; +pub const I32TRUNCSF64: u8 = 0xaa; +pub const I32TRUNCUF64: u8 = 0xab; +pub const I64EXTENDSI32: u8 = 0xac; +pub const I64EXTENDUI32: u8 = 0xad; +pub const I64TRUNCSF32: u8 = 0xae; +pub const I64TRUNCUF32: u8 = 0xaf; +pub const I64TRUNCSF64: u8 = 0xb0; +pub const I64TRUNCUF64: u8 = 0xb1; +pub const F32CONVERTSI32: u8 = 0xb2; +pub const F32CONVERTUI32: u8 = 0xb3; +pub const F32CONVERTSI64: u8 = 0xb4; +pub const F32CONVERTUI64: u8 = 0xb5; +pub const F32DEMOTEF64: u8 = 0xb6; +pub const F64CONVERTSI32: u8 = 0xb7; +pub const F64CONVERTUI32: u8 = 0xb8; +pub const F64CONVERTSI64: u8 = 0xb9; +pub const F64CONVERTUI64: u8 = 0xba; +pub const F64PROMOTEF32: u8 = 0xbb; + +pub const I32REINTERPRETF32: u8 = 0xbc; +pub const I64REINTERPRETF64: u8 = 0xbd; +pub const F32REINTERPRETI32: u8 = 0xbe; +pub const F64REINTERPRETI64: u8 = 0xbf; diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 9185a7fb7c..04982846bf 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,16 +1,12 @@ 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::{CodeBuilder, ValueType, 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, -}; +use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, PTR_SIZE, PTR_TYPE}; pub enum StoredValueKind { Parameter, @@ -241,7 +237,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 +245,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); } } @@ -297,20 +291,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::code_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 } } @@ -343,7 +337,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 +355,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); } ( @@ -421,7 +415,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/eval.rs b/compiler/gen_wasm/tests/helpers/eval.rs index 45d21e8962..e4bbbd71da 100644 --- a/compiler/gen_wasm/tests/helpers/eval.rs +++ b/compiler/gen_wasm/tests/helpers/eval.rs @@ -4,6 +4,7 @@ use std::hash::{Hash, Hasher}; use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; +use roc_gen_wasm::replace_code_section; // use roc_std::{RocDec, RocList, RocOrder, RocStr}; use crate::helpers::wasm32_test_result::Wasm32TestResult; use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; @@ -102,11 +103,20 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( exposed_to_host, }; - let (mut builder, main_function_index) = + let (mut builder, mut code_section_bytes, main_function_index) = roc_gen_wasm::build_module_help(&env, procedures).unwrap(); - T::insert_test_wrapper(&mut builder, TEST_WRAPPER_NAME, main_function_index); - let module_bytes = builder.build().into_bytes().unwrap(); + T::insert_test_wrapper( + arena, + &mut builder, + &mut code_section_bytes, + TEST_WRAPPER_NAME, + main_function_index, + ); + + let mut parity_module = builder.build(); + replace_code_section(&mut parity_module, code_section_bytes); + let module_bytes = parity_module.into_bytes().unwrap(); // for debugging (e.g. with wasm2wat) if false { @@ -138,7 +148,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( let store = Store::default(); // let module = Module::from_file(&store, &test_wasm_path).unwrap(); - let module = Module::from_binary(&store, &module_bytes).unwrap(); + let wasmer_module = Module::from_binary(&store, &module_bytes).unwrap(); // First, we create the `WasiEnv` use wasmer_wasi::WasiState; @@ -147,10 +157,10 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( // Then, we get the import object related to our WASI // and attach it to the Wasm instance. let import_object = wasi_env - .import_object(&module) + .import_object(&wasmer_module) .unwrap_or_else(|_| wasmer::imports!()); - Instance::new(&module, &import_object).unwrap() + Instance::new(&wasmer_module, &import_object).unwrap() } #[allow(dead_code)] @@ -188,7 +198,7 @@ where macro_rules! assert_wasm_evals_to { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { match $crate::helpers::eval::assert_wasm_evals_to_help::<$ty>($src, $expected) { - Err(msg) => println!("{:?}", msg), + Err(msg) => panic!("{:?}", msg), Ok(actual) => { assert_eq!($transform(actual), $expected) } diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index 5cf709a597..629eeccac2 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -1,130 +1,118 @@ use parity_wasm::builder; -use parity_wasm::builder::ModuleBuilder; -use parity_wasm::elements::{ - Instruction, Instruction::*, Instructions, Internal, Local, ValueType, -}; +use parity_wasm::elements::Internal; +use roc_gen_wasm::code_builder::{Align, CodeBuilder, ValueType}; use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; -use roc_gen_wasm::*; +use roc_gen_wasm::{overwrite_padded_u32, LocalId}; 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, + fn insert_test_wrapper<'a>( + arena: &'a bumpalo::Bump, + module_builder: &mut builder::ModuleBuilder, + code_section_bytes: &mut std::vec::Vec, wrapper_name: &str, main_function_index: u32, ) { - let instructions = Self::build_wrapper_body(main_function_index); + let signature = builder::signature() + .with_result(parity_wasm::elements::ValueType::I32) + .build_sig(); - 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); + + let mut code_builder = CodeBuilder::new(arena); + Self::build_wrapper_body(&mut code_builder, main_function_index); + + code_builder.serialize(code_section_bytes).unwrap(); + + let mut num_procs = 0; + for (i, byte) in code_section_bytes[5..10].iter().enumerate() { + num_procs += ((byte & 0x7f) as u32) << (i * 7); + } + let inner_length = (code_section_bytes.len() - 5) as u32; + overwrite_padded_u32(&mut code_section_bytes[0..5], inner_length); + overwrite_padded_u32(&mut code_section_bytes[5..10], num_procs + 1); } - fn build_wrapper_body(main_function_index: u32) -> Vec; + fn build_wrapper_body(code_builder: &mut CodeBuilder, 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 CodeBuilder, main_function_index: u32) { + 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 CodeBuilder, + main_function_index: u32, + size: usize, +) { + 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 as i32, 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 CodeBuilder, main_function_index: u32) { + 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); @@ -132,21 +120,21 @@ wasm_test_result_stack_memory!(RocDec); wasm_test_result_stack_memory!(RocStr); impl Wasm32TestResult for RocList { - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory(main_function_index, 12) + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory(code_builder, main_function_index, 12) } } impl Wasm32TestResult for &'_ T { - build_wrapper_body_primitive!(I32Store, ALIGN_4); + build_wrapper_body_primitive!(i32_store, Align::Bytes4); } impl Wasm32TestResult for [T; N] where T: Wasm32TestResult + FromWasm32Memory, { - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory(main_function_index, N * T::ACTUAL_WIDTH) + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory(code_builder, main_function_index, N * T::ACTUAL_WIDTH) } } @@ -155,8 +143,12 @@ where T: Wasm32TestResult + FromWasm32Memory, U: Wasm32TestResult + FromWasm32Memory, { - fn build_wrapper_body(main_function_index: u32) -> Vec { - build_wrapper_body_stack_memory(main_function_index, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH) + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { + build_wrapper_body_stack_memory( + code_builder, + main_function_index, + T::ACTUAL_WIDTH + U::ACTUAL_WIDTH, + ) } } @@ -166,8 +158,9 @@ where U: Wasm32TestResult + FromWasm32Memory, V: Wasm32TestResult + FromWasm32Memory, { - fn build_wrapper_body(main_function_index: u32) -> Vec { + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory( + code_builder, main_function_index, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH, ) @@ -181,8 +174,9 @@ where V: Wasm32TestResult + FromWasm32Memory, W: Wasm32TestResult + FromWasm32Memory, { - fn build_wrapper_body(main_function_index: u32) -> Vec { + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory( + code_builder, main_function_index, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH, ) @@ -197,8 +191,9 @@ where W: Wasm32TestResult + FromWasm32Memory, X: Wasm32TestResult + FromWasm32Memory, { - fn build_wrapper_body(main_function_index: u32) -> Vec { + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory( + code_builder, main_function_index, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH, ) @@ -214,8 +209,9 @@ where X: Wasm32TestResult + FromWasm32Memory, Y: Wasm32TestResult + FromWasm32Memory, { - fn build_wrapper_body(main_function_index: u32) -> Vec { + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory( + code_builder, main_function_index, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH @@ -237,8 +233,9 @@ where Y: Wasm32TestResult + FromWasm32Memory, Z: Wasm32TestResult + FromWasm32Memory, { - fn build_wrapper_body(main_function_index: u32) -> Vec { + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory( + code_builder, main_function_index, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH @@ -262,8 +259,9 @@ where Z: Wasm32TestResult + FromWasm32Memory, A: Wasm32TestResult + FromWasm32Memory, { - fn build_wrapper_body(main_function_index: u32) -> Vec { + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) { build_wrapper_body_stack_memory( + code_builder, main_function_index, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 0ff4bbf105..dde49e4680 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1053,6 +1053,7 @@ define_builtins! { 32 LIST_DROP: "drop" 33 LIST_SWAP: "swap" 34 LIST_DROP_AT: "dropAt" + 35 LIST_DROP_LAST: "dropLast" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index f6b4106379..d7c8a2a1c0 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3742,6 +3742,18 @@ mod solve_expr { "# ), "Str -> Str", + ); + } + + #[test] + fn list_drop_last() { + infer_eq_without_problem( + indoc!( + r#" + List.dropLast + "# + ), + "List a -> List a", ); } diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index d9e72ea8b3..27b0c883eb 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -235,6 +235,38 @@ fn list_drop_at_shared() { ); } +#[test] +fn list_drop_last() { + assert_evals_to!( + "List.dropLast [1, 2, 3]", + RocList::from_slice(&[1, 2]), + RocList + ); + assert_evals_to!("List.dropLast []", RocList::from_slice(&[]), RocList); + assert_evals_to!("List.dropLast [0]", RocList::from_slice(&[]), RocList); +} + +#[test] +fn list_drop_last_mutable() { + assert_evals_to!( + indoc!( + r#" + list : List I64 + list = [ if True then 4 else 4, 5, 6 ] + + { newList: List.dropLast list, original: list } + "# + ), + ( + // new_list + RocList::from_slice(&[4, 5]), + // original + RocList::from_slice(&[4, 5, 6]), + ), + (RocList, RocList,) + ); +} + #[test] fn list_swap() { assert_evals_to!("List.swap [] 0 1", RocList::from_slice(&[]), RocList);