From e168d175c4c562901d0d0e83fc9544cfe1f884e2 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 21 Oct 2021 21:28:50 +0200 Subject: [PATCH 01/28] Start writing a byte-level function_builder --- compiler/gen_wasm/src/function_builder.rs | 162 ++++++++++++++++++++++ compiler/gen_wasm/src/lib.rs | 1 + 2 files changed, 163 insertions(+) create mode 100644 compiler/gen_wasm/src/function_builder.rs diff --git a/compiler/gen_wasm/src/function_builder.rs b/compiler/gen_wasm/src/function_builder.rs new file mode 100644 index 0000000000..8aecbb8d57 --- /dev/null +++ b/compiler/gen_wasm/src/function_builder.rs @@ -0,0 +1,162 @@ +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::code_builder::VirtualMachineSymbolState; +use crate::LocalId; + +const DEBUG_LOG: bool = false; + +#[derive(Debug)] +pub struct FunctionBuilder<'a> { + /// The main container for the instructions + code: Vec<'a, u8>, + + /// Extra instructions to insert at specific positions during finalisation + /// (Go back and set locals when we realise we need them) + insertions: Vec<'a, (usize, Vec<'a, u8>)>, + + /// Instruction bytes for locals and stack frame setup code + locals_and_frame_setup: Vec<'a, u8>, + + /// Encoded bytes for the inner length of the function + /// This is the total size of locals + code, as encoded into the module + inner_length: Vec<'a, u8>, + + /// Our simulation model of the Wasm stack machine + /// Keeps track of where Symbol values are in the VM stack + vm_stack: Vec<'a, Symbol>, +} + +#[allow(clippy::new_without_default)] +impl<'a> FunctionBuilder<'a> { + pub fn new(arena: &'a Bump) -> Self { + FunctionBuilder { + code: Vec::with_capacity_in(1024, arena), + insertions: Vec::with_capacity_in(1024, arena), + locals_and_frame_setup: 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.vm_stack.clear(); + } + + /// Set the Symbol that is at the top of the VM stack right now + /// We will use this later when we need to load the Symbol + pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState { + let len = self.vm_stack.len(); + let pushed_at = self.code.len(); + + if len == 0 { + panic!( + "trying to set symbol with nothing on stack, code = {:?}", + self.code + ); + } + + self.vm_stack[len - 1] = sym; + + VirtualMachineSymbolState::Pushed { pushed_at } + } + + /// Verify if a sequence of symbols is at the top of the stack + pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool { + let n_symbols = symbols.len(); + let stack_depth = self.vm_stack.len(); + if n_symbols > stack_depth { + return false; + } + let offset = stack_depth - n_symbols; + + for (i, sym) in symbols.iter().enumerate() { + if self.vm_stack[offset + i] != *sym { + return false; + } + } + true + } + + fn push_stack_frame(frame_size: i32, local_frame_pointer: LocalId) -> [Instruction; 5] { + return [ + GetGlobal(STACK_POINTER_GLOBAL_ID), + I32Const(frame_size), + I32Sub, + TeeLocal(local_frame_pointer.0), + SetGlobal(STACK_POINTER_GLOBAL_ID), + ]; + } + + fn pop_stack_frame(frame_size: i32, local_frame_pointer: LocalId) -> [Instruction; 4] { + return [ + GetLocal(local_frame_pointer.0), + I32Const(frame_size), + I32Add, + SetGlobal(STACK_POINTER_GLOBAL_ID), + ]; + } + + fn serialize_locals(writer: &mut W, local_types: &[ValueType]) + where + W: std::io::Write, + { + let num_locals = local_types.len(); + VarUint32::from(num_locals) + .serialize(writer) + .unwrap_or_else(debug_panic); + + // write bytes for locals, in batches of the same ValueType + if num_locals > 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 { + let local = Local::new(batch_size, batch_type); + local.serialize(writer).unwrap_or_else(debug_panic); + batch_type = *t; + batch_size = 1; + } + } + let local = Local::new(batch_size, batch_type); + local.serialize(writer).unwrap_or_else(debug_panic); + } + } + + /// Finalize a function body + pub fn finalize(&mut self, final_code: &mut std::vec::Vec) { + // sort insertions + // encode local declarations + // encode stack frame push + // encode stack frame pop + // calculate inner length + // encode inner length + } + + pub fn serialize(&self, writer: W) { + // write inner length + // write locals + // write stack frame push + // write code+insertions + // write stack frame pop + } + + /// Total bytes, including inner length + /// (to help calculate overall code section length) + pub fn outer_len(&self) -> usize { + self.code.len() + + self.insertions.len() + + self.locals_and_frame_setup.len() + + self.inner_length.len() + } +} diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index a66f7e92f3..9147f4f37b 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -1,6 +1,7 @@ mod backend; mod code_builder; pub mod from_wasm32_memory; +mod function_builder; mod layout; mod storage; From dc80623d45de839d7751720afb52eb46f62c2917 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 21 Oct 2021 23:12:27 +0200 Subject: [PATCH 02/28] Sketch out methods for byte-level instructions. No macros yet. --- compiler/gen_wasm/src/function_builder.rs | 243 +++++++++++++++++++++- 1 file changed, 241 insertions(+), 2 deletions(-) diff --git a/compiler/gen_wasm/src/function_builder.rs b/compiler/gen_wasm/src/function_builder.rs index 8aecbb8d57..56293fbaf2 100644 --- a/compiler/gen_wasm/src/function_builder.rs +++ b/compiler/gen_wasm/src/function_builder.rs @@ -5,6 +5,7 @@ use std::collections::BTreeMap; use std::fmt::Debug; use parity_wasm::elements::{Instruction, Instruction::*}; +use parity_wasm::elements::ops::opcodes::*; use roc_module::symbol::Symbol; use crate::code_builder::VirtualMachineSymbolState; @@ -12,6 +13,36 @@ use crate::LocalId; const DEBUG_LOG: bool = false; +#[repr(u8)] +enum ValueType { + I32 = -0x01, + I64 = -0x02, + F32 = -0x03, + F64 = -0x04, +} + +enum BlockType { + NoResult, + Value(ValueType), +} + +impl BlockType { + pub fn as_byte(&self) -> u8 { + match self { + NoResult => -0x40, + Value(t) => t as u8 + } + } +} + +#[repr(u8)] +enum Align { + Bytes1 = 0, + Bytes2 = 1, + Bytes4 = 2, + Bytes8 = 3, +} + #[derive(Debug)] pub struct FunctionBuilder<'a> { /// The main container for the instructions @@ -70,7 +101,7 @@ impl<'a> FunctionBuilder<'a> { } /// Verify if a sequence of symbols is at the top of the stack - pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool { + pub fn verify_stack_match(&self, symbols: Symbol) -> bool { let n_symbols = symbols.len(); let stack_depth = self.vm_stack.len(); if n_symbols > stack_depth { @@ -105,7 +136,7 @@ impl<'a> FunctionBuilder<'a> { ]; } - fn serialize_locals(writer: &mut W, local_types: &[ValueType]) + fn serialize_locals(writer: &mut W, local_types: ValueType) where W: std::io::Write, { @@ -159,4 +190,212 @@ impl<'a> FunctionBuilder<'a> { + self.locals_and_frame_setup.len() + self.inner_length.len() } + + pub fn call(&mut self, function_index: u32, pops: usize, push: bool) { + let stack_depth = self.vm_stack.len(); + if pops > stack_depth { + panic!( + "Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\n{:?}", + function_index, pops, stack_depth, self + ); + } + self.vm_stack.truncate(stack_depth - pops); + if push { + self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); + } + self.code.push(CALL); + } + + fn inst(pops: usize, push: bool, opcode: u8) { + panic!("TODO"); + } + + fn inst_imm8(pops: usize, push: bool, opcode: u8, immediate: u8) { + panic!("TODO"); + } + + fn inst_imm32(pops: usize, push: bool, opcode: u8, immediate: u32) { + panic!("TODO"); + } + + fn inst_imm64(pops: usize, push: bool, opcode: u8, immediate: u64) { + panic!("TODO"); + } + + fn inst_mem(pops: usize, push: bool, opcode: u8, align: Align, offset: u32) { + panic!("TODO"); + } + + pub fn unreachable(&mut self) { self.inst(0, false, UNREACHABLE); } + pub fn nop(&mut self) { self.inst(0, false, NOP); } + pub fn block(ty: BlockType) { self.inst_imm8(0, false, BLOCK, ty as u8); } + pub fn loop_(ty: BlockType) { self.inst_imm8(0, false, LOOP, ty as u8); } + pub fn if_(ty: BlockType) { self.inst_imm8(1, false, IF, ty as u8); } + pub fn else_(&mut self) { self.inst(0, false, ELSE); } + pub fn end(&mut self) { self.inst(0, false, END); } + pub fn br(levels: u32) { self.inst_imm32(0, false, BR, levels); } + pub fn br_if(levels: u32) { self.inst_imm32(1, false, BRIF, levels); } + // br_table: not implemented + pub fn return_(&mut self) { self.inst(0, false, RETURN); } + // call: see above + // call_indirect: not implemented + pub fn drop(&mut self) { self.inst(1, false, DROP); } + pub fn select(&mut self) { self.inst(3, true, SELECT); } + pub fn get_local(&mut self, id: LocalId) { self.inst_imm32(0, true, GETLOCAL, id.0); } + pub fn set_local(&mut self, id: LocalId) { self.inst_imm32(1, false, SETLOCAL, id.0); } + pub fn tee_local(&mut self, id: LocalId) { self.inst_imm32(1, true, TEELOCAL, id.0); } + pub fn get_global(&mut self, id: u32) { self.inst_imm32(0, true, GETGLOBAL, id); } + pub fn set_global(&mut self, id: u32) { self.inst_imm32(1, false, SETGLOBAL, id); } + pub fn i32_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD, align, offset); } + pub fn i64_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD, align, offset); } + pub fn f32_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, F32LOAD, align, offset); } + pub fn f64_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, F64LOAD, align, offset); } + pub fn i32_load8_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD8S, align, offset); } + pub fn i32_load8_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD8U, align, offset); } + pub fn i32_load16_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD16S, align, offset); } + pub fn i32_load16_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD16U, align, offset); } + pub fn i64_load8_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD8S, align, offset); } + pub fn i64_load8_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD8U, align, offset); } + pub fn i64_load16_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD16S, align, offset); } + pub fn i64_load16_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD16U, align, offset); } + pub fn i64_load32_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD32S, align, offset); } + pub fn i64_load32_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD32U, align, offset); } + pub fn i32_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I32STORE, align, offset); } + pub fn i64_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE, align, offset); } + pub fn f32_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, F32STORE, align, offset); } + pub fn f64_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, F64STORE, align, offset); } + pub fn i32_store8(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I32STORE8, align, offset); } + pub fn i32_store16(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I32STORE16, align, offset); } + pub fn i64_store8(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE8, align, offset); } + pub fn i64_store16(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE16, align, offset); } + pub fn i64_store32(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE32, align, offset); } + pub fn memory_size(&mut self) { self.inst_imm8(0, true, CURRENTMEMORY, 0); } + pub fn memory_grow(&mut self) { self.inst_imm8(1, true, GROWMEMORY, 0); } + pub fn i32_const(&mut self, x: i32) { self.inst_imm32(0, true, I32CONST, x as u32); } + pub fn i64_const(&mut self, x: i64) { self.inst_imm64(0, true, I64CONST, x as u64); } + pub fn f32_const(&mut self, x: f32) { self.inst_imm32(0, true, F32CONST, x.to_bits()); } + pub fn f64_const(&mut self, x: f64) { self.inst_imm64(0, true, F64CONST, x.to_bits()); } + pub fn i32_eqz(&mut self) { self.inst(1, true, I32EQZ); } + pub fn i32_eq(&mut self) { self.inst(2, true, I32EQ); } + pub fn i32_ne(&mut self) { self.inst(2, true, I32NE); } + pub fn i32_lt_s(&mut self) { self.inst(2, true, I32LTS); } + pub fn i32_lt_u(&mut self) { self.inst(2, true, I32LTU); } + pub fn i32_gt_s(&mut self) { self.inst(2, true, I32GTS); } + pub fn i32_gt_u(&mut self) { self.inst(2, true, I32GTU); } + pub fn i32_le_s(&mut self) { self.inst(2, true, I32LES); } + pub fn i32_le_u(&mut self) { self.inst(2, true, I32LEU); } + pub fn i32_ge_s(&mut self) { self.inst(2, true, I32GES); } + pub fn i32_ge_u(&mut self) { self.inst(2, true, I32GEU); } + pub fn i64_eqz(&mut self) { self.inst(1, true, I64EQZ); } + pub fn i64_eq(&mut self) { self.inst(2, true, I64EQ); } + pub fn i64_ne(&mut self) { self.inst(2, true, I64NE); } + pub fn i64_lt_s(&mut self) { self.inst(2, true, I64LTS); } + pub fn i64_lt_u(&mut self) { self.inst(2, true, I64LTU); } + pub fn i64_gt_s(&mut self) { self.inst(2, true, I64GTS); } + pub fn i64_gt_u(&mut self) { self.inst(2, true, I64GTU); } + pub fn i64_le_s(&mut self) { self.inst(2, true, I64LES); } + pub fn i64_le_u(&mut self) { self.inst(2, true, I64LEU); } + pub fn i64_ge_s(&mut self) { self.inst(2, true, I64GES); } + pub fn i64_ge_u(&mut self) { self.inst(2, true, I64GEU); } + pub fn f32_eq(&mut self) { self.inst(2, true, F32EQ); } + pub fn f32_ne(&mut self) { self.inst(2, true, F32NE); } + pub fn f32_lt(&mut self) { self.inst(2, true, F32LT); } + pub fn f32_gt(&mut self) { self.inst(2, true, F32GT); } + pub fn f32_le(&mut self) { self.inst(2, true, F32LE); } + pub fn f32_ge(&mut self) { self.inst(2, true, F32GE); } + pub fn f64_eq(&mut self) { self.inst(2, true, F64EQ); } + pub fn f64_ne(&mut self) { self.inst(2, true, F64NE); } + pub fn f64_lt(&mut self) { self.inst(2, true, F64LT); } + pub fn f64_gt(&mut self) { self.inst(2, true, F64GT); } + pub fn f64_le(&mut self) { self.inst(2, true, F64LE); } + pub fn f64_ge(&mut self) { self.inst(2, true, F64GE); } + pub fn i32_clz(&mut self) { self.inst(1, true, I32CLZ); } + pub fn i32_ctz(&mut self) { self.inst(1, true, I32CTZ); } + pub fn i32_popcnt(&mut self) { self.inst(1, true, I32POPCNT); } + pub fn i32_add(&mut self) { self.inst(2, true, I32ADD); } + pub fn i32_sub(&mut self) { self.inst(2, true, I32SUB); } + pub fn i32_mul(&mut self) { self.inst(2, true, I32MUL); } + pub fn i32_div_s(&mut self) { self.inst(2, true, I32DIVS); } + pub fn i32_div_u(&mut self) { self.inst(2, true, I32DIVU); } + pub fn i32_rem_s(&mut self) { self.inst(2, true, I32REMS); } + pub fn i32_rem_u(&mut self) { self.inst(2, true, I32REMU); } + pub fn i32_and(&mut self) { self.inst(2, true, I32AND); } + pub fn i32_or(&mut self) { self.inst(2, true, I32OR); } + pub fn i32_xor(&mut self) { self.inst(2, true, I32XOR); } + pub fn i32_shl(&mut self) { self.inst(2, true, I32SHL); } + pub fn i32_shr_s(&mut self) { self.inst(2, true, I32SHRS); } + pub fn i32_shr_u(&mut self) { self.inst(2, true, I32SHRU); } + pub fn i32_rotl(&mut self) { self.inst(2, true, I32ROTL); } + pub fn i32_rotr(&mut self) { self.inst(2, true, I32ROTR); } + pub fn i64_clz(&mut self) { self.inst(1, true, I64CLZ); } + pub fn i64_ctz(&mut self) { self.inst(1, true, I64CTZ); } + pub fn i64_popcnt(&mut self) { self.inst(1, true, I64POPCNT); } + pub fn i64_add(&mut self) { self.inst(2, true, I64ADD); } + pub fn i64_sub(&mut self) { self.inst(2, true, I64SUB); } + pub fn i64_mul(&mut self) { self.inst(2, true, I64MUL); } + pub fn i64_div_s(&mut self) { self.inst(2, true, I64DIVS); } + pub fn i64_div_u(&mut self) { self.inst(2, true, I64DIVU); } + pub fn i64_rem_s(&mut self) { self.inst(2, true, I64REMS); } + pub fn i64_rem_u(&mut self) { self.inst(2, true, I64REMU); } + pub fn i64_and(&mut self) { self.inst(2, true, I64AND); } + pub fn i64_or(&mut self) { self.inst(2, true, I64OR); } + pub fn i64_xor(&mut self) { self.inst(2, true, I64XOR); } + pub fn i64_shl(&mut self) { self.inst(2, true, I64SHL); } + pub fn i64_shr_s(&mut self) { self.inst(2, true, I64SHRS); } + pub fn i64_shr_u(&mut self) { self.inst(2, true, I64SHRU); } + pub fn i64_rotl(&mut self) { self.inst(2, true, I64ROTL); } + pub fn i64_rotr(&mut self) { self.inst(2, true, I64ROTR); } + pub fn f32_abs(&mut self) { self.inst(1, true, F32ABS); } + pub fn f32_neg(&mut self) { self.inst(1, true, F32NEG); } + pub fn f32_ceil(&mut self) { self.inst(1, true, F32CEIL); } + pub fn f32_floor(&mut self) { self.inst(1, true, F32FLOOR); } + pub fn f32_trunc(&mut self) { self.inst(1, true, F32TRUNC); } + pub fn f32_nearest(&mut self) { self.inst(1, true, F32NEAREST); } + pub fn f32_sqrt(&mut self) { self.inst(1, true, F32SQRT); } + pub fn f32_add(&mut self) { self.inst(2, true, F32ADD); } + pub fn f32_sub(&mut self) { self.inst(2, true, F32SUB); } + pub fn f32_mul(&mut self) { self.inst(2, true, F32MUL); } + pub fn f32_div(&mut self) { self.inst(2, true, F32DIV); } + pub fn f32_min(&mut self) { self.inst(2, true, F32MIN); } + pub fn f32_max(&mut self) { self.inst(2, true, F32MAX); } + pub fn f32_copysign(&mut self) { self.inst(2, true, F32COPYSIGN); } + pub fn f64_abs(&mut self) { self.inst(1, true, F64ABS); } + pub fn f64_neg(&mut self) { self.inst(1, true, F64NEG); } + pub fn f64_ceil(&mut self) { self.inst(1, true, F64CEIL); } + pub fn f64_floor(&mut self) { self.inst(1, true, F64FLOOR); } + pub fn f64_trunc(&mut self) { self.inst(1, true, F64TRUNC); } + pub fn f64_nearest(&mut self) { self.inst(1, true, F64NEAREST); } + pub fn f64_sqrt(&mut self) { self.inst(1, true, F64SQRT); } + pub fn f64_add(&mut self) { self.inst(2, true, F64ADD); } + pub fn f64_sub(&mut self) { self.inst(2, true, F64SUB); } + pub fn f64_mul(&mut self) { self.inst(2, true, F64MUL); } + pub fn f64_div(&mut self) { self.inst(2, true, F64DIV); } + pub fn f64_min(&mut self) { self.inst(2, true, F64MIN); } + pub fn f64_max(&mut self) { self.inst(2, true, F64MAX); } + pub fn f64_copysign(&mut self) { self.inst(2, true, F64COPYSIGN); } + pub fn i32_wrap_i64(&mut self) { self.inst(1, true, I32WRAPI64); } + pub fn i32_trunc_s_f32(&mut self) { self.inst(1, true, I32TRUNCSF32); } + pub fn i32_trunc_u_f32(&mut self) { self.inst(1, true, I32TRUNCUF32); } + pub fn i32_trunc_s_f64(&mut self) { self.inst(1, true, I32TRUNCSF64); } + pub fn i32_trunc_u_f64(&mut self) { self.inst(1, true, I32TRUNCUF64); } + pub fn i64_extend_s_i32(&mut self) { self.inst(1, true, I64EXTENDSI32); } + pub fn i64_extend_u_i32(&mut self) { self.inst(1, true, I64EXTENDUI32); } + pub fn i64_trunc_s_f32(&mut self) { self.inst(1, true, I64TRUNCSF32); } + pub fn i64_trunc_u_f32(&mut self) { self.inst(1, true, I64TRUNCUF32); } + pub fn i64_trunc_s_f64(&mut self) { self.inst(1, true, I64TRUNCSF64); } + pub fn i64_trunc_u_f64(&mut self) { self.inst(1, true, I64TRUNCUF64); } + pub fn f32_convert_s_i32(&mut self) { self.inst(1, true, F32CONVERTSI32); } + pub fn f32_convert_u_i32(&mut self) { self.inst(1, true, F32CONVERTUI32); } + pub fn f32_convert_s_i64(&mut self) { self.inst(1, true, F32CONVERTSI64); } + pub fn f32_convert_u_i64(&mut self) { self.inst(1, true, F32CONVERTUI64); } + pub fn f32_demote_f64(&mut self) { self.inst(1, true, F32DEMOTEF64); } + pub fn f64_convert_s_i32(&mut self) { self.inst(1, true, F64CONVERTSI32); } + pub fn f64_convert_u_i32(&mut self) { self.inst(1, true, F64CONVERTUI32); } + pub fn f64_convert_s_i64(&mut self) { self.inst(1, true, F64CONVERTSI64); } + pub fn f64_convert_u_i64(&mut self) { self.inst(1, true, F64CONVERTUI64); } + pub fn f64_promote_f32(&mut self) { self.inst(1, true, F64PROMOTEF32); } + pub fn i32_reinterpret_f32(&mut self) { self.inst(1, true, I32REINTERPRETF32); } + pub fn i64_reinterpret_f64(&mut self) { self.inst(1, true, I64REINTERPRETF64); } + pub fn f32_reinterpret_i32(&mut self) { self.inst(1, true, F32REINTERPRETI32); } + pub fn f64_reinterpret_i64(&mut self) { self.inst(1, true, F64REINTERPRETI64); } } From 49a832d757a0f7f47e30182ead09c59e872f9178 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Thu, 21 Oct 2021 23:02:26 -0500 Subject: [PATCH 03/28] Add dropLast to tests and parser --- compiler/builtins/src/std.rs | 7 ++++++ compiler/gen_llvm/src/llvm/build_list.rs | 22 ++++++++++++++++ compiler/module/src/low_level.rs | 2 ++ compiler/module/src/symbol.rs | 1 + compiler/mono/src/borrow.rs | 1 + compiler/solve/tests/solve_expr.rs | 12 +++++++++ compiler/test_gen/src/gen_list.rs | 32 ++++++++++++++++++++++++ 7 files changed, 77 insertions(+) diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index f17cc22098..38230bf0e3 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -934,6 +934,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/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 3d80912689..1e17e261e8 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -341,6 +341,28 @@ pub fn list_drop_at<'a, 'ctx, 'env>( ) } +/// List.dropLast : List elem -> List elem +pub fn list_drop_last<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + original_wrapper: StructValue<'ctx>, + count: IntValue<'ctx>, + element_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout); + call_bitcode_fn_returns_list( + env, + &[ + pass_list_cc(env, original_wrapper.into()), + env.alignment_intvalue(element_layout), + layout_width(env, element_layout), + count.into(), + dec_element_fn.as_global_value().as_pointer_value().into(), + ], + bitcode::LIST_DROP_LAST, + ) +} + /// List.set : List elem, Nat, elem -> List elem pub fn list_set<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 9be88c2945..e949213884 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -42,6 +42,7 @@ pub enum LowLevel { ListSortWith, ListDrop, ListDropAt, + ListDropLast, ListSwap, DictSize, DictEmpty, @@ -129,6 +130,7 @@ macro_rules! first_order { | ListSet | ListDrop | ListDropAt + | ListDropLast | ListSingle | ListRepeat | ListReverse diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 55aeb7e06b..a1031f2f41 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1015,6 +1015,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/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 55fd53f18b..f09ac825c4 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -947,6 +947,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListAppend => arena.alloc_slice_copy(&[owned, owned]), ListDrop => arena.alloc_slice_copy(&[owned, irrelevant]), ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]), + ListDropLast => arena.alloc_slice_copy(&[owned]), ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]), diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index cd05ede55d..f05eb2b9df 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3733,6 +3733,18 @@ mod solve_expr { ); } + #[test] + fn list_drop_at() { + infer_eq_without_problem( + indoc!( + r#" + List.dropLast + "# + ), + "List a -> List a", + ); + } + #[test] fn function_that_captures_nothing_is_not_captured() { // we should make sure that a function that doesn't capture anything it not itself captured diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 719e61da44..6ba248d673 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_mutable() { ); } +#[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); From 4563e2af49afda3561044a368007683e2be42e86 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 22 Oct 2021 10:38:53 +0200 Subject: [PATCH 04/28] Get function_builder compiling --- compiler/gen_wasm/src/backend.rs | 2 +- compiler/gen_wasm/src/function_builder.rs | 420 ++++++++++++---------- compiler/gen_wasm/src/lib.rs | 5 + compiler/gen_wasm/src/opcodes.rs | 178 +++++++++ 4 files changed, 415 insertions(+), 190 deletions(-) create mode 100644 compiler/gen_wasm/src/opcodes.rs diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 30064d5ff4..75917fd6ff 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -13,7 +13,7 @@ use roc_mono::layout::{Builtin, Layout}; use crate::code_builder::CodeBuilder; use crate::layout::WasmLayout; -use crate::storage::{StackMemoryLocation, Storage, StoredValue, StoredValueKind}; +use crate::storage::{Storage, StoredValue, StoredValueKind}; use crate::{ copy_memory, pop_stack_frame, push_stack_frame, CopyMemoryConfig, Env, LocalId, PTR_TYPE, }; diff --git a/compiler/gen_wasm/src/function_builder.rs b/compiler/gen_wasm/src/function_builder.rs index 56293fbaf2..4efcde3c04 100644 --- a/compiler/gen_wasm/src/function_builder.rs +++ b/compiler/gen_wasm/src/function_builder.rs @@ -1,24 +1,24 @@ 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 parity_wasm::elements::ops::opcodes::*; +use parity_wasm::elements::{Instruction, Instruction::*, Local, Serialize, VarUint32}; use roc_module::symbol::Symbol; use crate::code_builder::VirtualMachineSymbolState; -use crate::LocalId; +use crate::opcodes::*; +use crate::{debug_panic, LocalId, STACK_POINTER_GLOBAL_ID}; const DEBUG_LOG: bool = false; #[repr(u8)] +#[derive(PartialEq, Eq, Clone, Copy)] enum ValueType { - I32 = -0x01, - I64 = -0x02, - F32 = -0x03, - F64 = -0x04, + I32 = 0x7f, + I64 = 0x7e, + F32 = 0x7d, + F64 = 0x7c, } enum BlockType { @@ -29,8 +29,8 @@ enum BlockType { impl BlockType { pub fn as_byte(&self) -> u8 { match self { - NoResult => -0x40, - Value(t) => t as u8 + Self::NoResult => 0x40, + Self::Value(t) => *t as u8, } } } @@ -43,6 +43,46 @@ enum Align { Bytes8 = 3, } +// macro_rules! instruction_method { +// ($method_name: ident, $pops: expr, $push: expr, $opcode: expr) => { +// pub fn $name(&mut self) { +// self.inst($pops, $push, $opcode); +// } +// }; +// } + +// macro_rules! binop_method { +// ($method_name: ident, $opcode: expr) => { +// pub fn $name(&mut self) { +// self.inst(2, true, $opcode); +// } +// }; +// } + +// macro_rules! unop_method { +// ($method_name: ident, $opcode: expr) => { +// pub fn $name(&mut self) { +// self.inst(1, true, $opcode); +// } +// }; +// } + +// macro_rules! mem_method { +// ($method_name: ident, $pops: expr, $push: expr, $opcode: expr) => { +// pub fn $name(&mut self) { +// self.inst($pops, $push, $opcode); +// } +// }; +// } + +// macro_rules! instruction_method { +// ($method_name: ident, $pops: expr, $push: expr, $opcode: expr) => { +// pub fn $name(&mut self) { +// self.inst($pops, $push, $opcode); +// } +// }; +// } + #[derive(Debug)] pub struct FunctionBuilder<'a> { /// The main container for the instructions @@ -101,7 +141,7 @@ impl<'a> FunctionBuilder<'a> { } /// Verify if a sequence of symbols is at the top of the stack - pub fn verify_stack_match(&self, symbols: Symbol) -> bool { + pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool { let n_symbols = symbols.len(); let stack_depth = self.vm_stack.len(); if n_symbols > stack_depth { @@ -136,7 +176,7 @@ impl<'a> FunctionBuilder<'a> { ]; } - fn serialize_locals(writer: &mut W, local_types: ValueType) + fn serialize_locals(writer: &mut W, local_types: &[parity_wasm::elements::ValueType]) where W: std::io::Write, { @@ -206,196 +246,198 @@ impl<'a> FunctionBuilder<'a> { self.code.push(CALL); } - fn inst(pops: usize, push: bool, opcode: u8) { + fn inst(&mut self, pops: usize, push: bool, opcode: u8) { panic!("TODO"); } - fn inst_imm8(pops: usize, push: bool, opcode: u8, immediate: u8) { + fn inst_imm8(&mut self, pops: usize, push: bool, opcode: u8, immediate: u8) { panic!("TODO"); } - fn inst_imm32(pops: usize, push: bool, opcode: u8, immediate: u32) { + fn inst_imm32(&mut self, pops: usize, push: bool, opcode: u8, immediate: u32) { panic!("TODO"); } - fn inst_imm64(pops: usize, push: bool, opcode: u8, immediate: u64) { + fn inst_imm64(&mut self, pops: usize, push: bool, opcode: u8, immediate: u64) { panic!("TODO"); } - fn inst_mem(pops: usize, push: bool, opcode: u8, align: Align, offset: u32) { + fn inst_mem(&mut self, pops: usize, push: bool, opcode: u8, align: Align, offset: u32) { panic!("TODO"); } - pub fn unreachable(&mut self) { self.inst(0, false, UNREACHABLE); } - pub fn nop(&mut self) { self.inst(0, false, NOP); } - pub fn block(ty: BlockType) { self.inst_imm8(0, false, BLOCK, ty as u8); } - pub fn loop_(ty: BlockType) { self.inst_imm8(0, false, LOOP, ty as u8); } - pub fn if_(ty: BlockType) { self.inst_imm8(1, false, IF, ty as u8); } - pub fn else_(&mut self) { self.inst(0, false, ELSE); } - pub fn end(&mut self) { self.inst(0, false, END); } - pub fn br(levels: u32) { self.inst_imm32(0, false, BR, levels); } - pub fn br_if(levels: u32) { self.inst_imm32(1, false, BRIF, levels); } - // br_table: not implemented - pub fn return_(&mut self) { self.inst(0, false, RETURN); } - // call: see above - // call_indirect: not implemented - pub fn drop(&mut self) { self.inst(1, false, DROP); } - pub fn select(&mut self) { self.inst(3, true, SELECT); } - pub fn get_local(&mut self, id: LocalId) { self.inst_imm32(0, true, GETLOCAL, id.0); } - pub fn set_local(&mut self, id: LocalId) { self.inst_imm32(1, false, SETLOCAL, id.0); } - pub fn tee_local(&mut self, id: LocalId) { self.inst_imm32(1, true, TEELOCAL, id.0); } - pub fn get_global(&mut self, id: u32) { self.inst_imm32(0, true, GETGLOBAL, id); } - pub fn set_global(&mut self, id: u32) { self.inst_imm32(1, false, SETGLOBAL, id); } - pub fn i32_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD, align, offset); } - pub fn i64_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD, align, offset); } - pub fn f32_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, F32LOAD, align, offset); } - pub fn f64_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, F64LOAD, align, offset); } - pub fn i32_load8_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD8S, align, offset); } - pub fn i32_load8_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD8U, align, offset); } - pub fn i32_load16_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD16S, align, offset); } - pub fn i32_load16_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD16U, align, offset); } - pub fn i64_load8_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD8S, align, offset); } - pub fn i64_load8_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD8U, align, offset); } - pub fn i64_load16_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD16S, align, offset); } - pub fn i64_load16_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD16U, align, offset); } - pub fn i64_load32_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD32S, align, offset); } - pub fn i64_load32_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD32U, align, offset); } - pub fn i32_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I32STORE, align, offset); } - pub fn i64_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE, align, offset); } - pub fn f32_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, F32STORE, align, offset); } - pub fn f64_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, F64STORE, align, offset); } - pub fn i32_store8(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I32STORE8, align, offset); } - pub fn i32_store16(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I32STORE16, align, offset); } - pub fn i64_store8(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE8, align, offset); } - pub fn i64_store16(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE16, align, offset); } - pub fn i64_store32(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE32, align, offset); } - pub fn memory_size(&mut self) { self.inst_imm8(0, true, CURRENTMEMORY, 0); } - pub fn memory_grow(&mut self) { self.inst_imm8(1, true, GROWMEMORY, 0); } - pub fn i32_const(&mut self, x: i32) { self.inst_imm32(0, true, I32CONST, x as u32); } - pub fn i64_const(&mut self, x: i64) { self.inst_imm64(0, true, I64CONST, x as u64); } - pub fn f32_const(&mut self, x: f32) { self.inst_imm32(0, true, F32CONST, x.to_bits()); } - pub fn f64_const(&mut self, x: f64) { self.inst_imm64(0, true, F64CONST, x.to_bits()); } - pub fn i32_eqz(&mut self) { self.inst(1, true, I32EQZ); } - pub fn i32_eq(&mut self) { self.inst(2, true, I32EQ); } - pub fn i32_ne(&mut self) { self.inst(2, true, I32NE); } - pub fn i32_lt_s(&mut self) { self.inst(2, true, I32LTS); } - pub fn i32_lt_u(&mut self) { self.inst(2, true, I32LTU); } - pub fn i32_gt_s(&mut self) { self.inst(2, true, I32GTS); } - pub fn i32_gt_u(&mut self) { self.inst(2, true, I32GTU); } - pub fn i32_le_s(&mut self) { self.inst(2, true, I32LES); } - pub fn i32_le_u(&mut self) { self.inst(2, true, I32LEU); } - pub fn i32_ge_s(&mut self) { self.inst(2, true, I32GES); } - pub fn i32_ge_u(&mut self) { self.inst(2, true, I32GEU); } - pub fn i64_eqz(&mut self) { self.inst(1, true, I64EQZ); } - pub fn i64_eq(&mut self) { self.inst(2, true, I64EQ); } - pub fn i64_ne(&mut self) { self.inst(2, true, I64NE); } - pub fn i64_lt_s(&mut self) { self.inst(2, true, I64LTS); } - pub fn i64_lt_u(&mut self) { self.inst(2, true, I64LTU); } - pub fn i64_gt_s(&mut self) { self.inst(2, true, I64GTS); } - pub fn i64_gt_u(&mut self) { self.inst(2, true, I64GTU); } - pub fn i64_le_s(&mut self) { self.inst(2, true, I64LES); } - pub fn i64_le_u(&mut self) { self.inst(2, true, I64LEU); } - pub fn i64_ge_s(&mut self) { self.inst(2, true, I64GES); } - pub fn i64_ge_u(&mut self) { self.inst(2, true, I64GEU); } - pub fn f32_eq(&mut self) { self.inst(2, true, F32EQ); } - pub fn f32_ne(&mut self) { self.inst(2, true, F32NE); } - pub fn f32_lt(&mut self) { self.inst(2, true, F32LT); } - pub fn f32_gt(&mut self) { self.inst(2, true, F32GT); } - pub fn f32_le(&mut self) { self.inst(2, true, F32LE); } - pub fn f32_ge(&mut self) { self.inst(2, true, F32GE); } - pub fn f64_eq(&mut self) { self.inst(2, true, F64EQ); } - pub fn f64_ne(&mut self) { self.inst(2, true, F64NE); } - pub fn f64_lt(&mut self) { self.inst(2, true, F64LT); } - pub fn f64_gt(&mut self) { self.inst(2, true, F64GT); } - pub fn f64_le(&mut self) { self.inst(2, true, F64LE); } - pub fn f64_ge(&mut self) { self.inst(2, true, F64GE); } - pub fn i32_clz(&mut self) { self.inst(1, true, I32CLZ); } - pub fn i32_ctz(&mut self) { self.inst(1, true, I32CTZ); } - pub fn i32_popcnt(&mut self) { self.inst(1, true, I32POPCNT); } - pub fn i32_add(&mut self) { self.inst(2, true, I32ADD); } - pub fn i32_sub(&mut self) { self.inst(2, true, I32SUB); } - pub fn i32_mul(&mut self) { self.inst(2, true, I32MUL); } - pub fn i32_div_s(&mut self) { self.inst(2, true, I32DIVS); } - pub fn i32_div_u(&mut self) { self.inst(2, true, I32DIVU); } - pub fn i32_rem_s(&mut self) { self.inst(2, true, I32REMS); } - pub fn i32_rem_u(&mut self) { self.inst(2, true, I32REMU); } - pub fn i32_and(&mut self) { self.inst(2, true, I32AND); } - pub fn i32_or(&mut self) { self.inst(2, true, I32OR); } - pub fn i32_xor(&mut self) { self.inst(2, true, I32XOR); } - pub fn i32_shl(&mut self) { self.inst(2, true, I32SHL); } - pub fn i32_shr_s(&mut self) { self.inst(2, true, I32SHRS); } - pub fn i32_shr_u(&mut self) { self.inst(2, true, I32SHRU); } - pub fn i32_rotl(&mut self) { self.inst(2, true, I32ROTL); } - pub fn i32_rotr(&mut self) { self.inst(2, true, I32ROTR); } - pub fn i64_clz(&mut self) { self.inst(1, true, I64CLZ); } - pub fn i64_ctz(&mut self) { self.inst(1, true, I64CTZ); } - pub fn i64_popcnt(&mut self) { self.inst(1, true, I64POPCNT); } - pub fn i64_add(&mut self) { self.inst(2, true, I64ADD); } - pub fn i64_sub(&mut self) { self.inst(2, true, I64SUB); } - pub fn i64_mul(&mut self) { self.inst(2, true, I64MUL); } - pub fn i64_div_s(&mut self) { self.inst(2, true, I64DIVS); } - pub fn i64_div_u(&mut self) { self.inst(2, true, I64DIVU); } - pub fn i64_rem_s(&mut self) { self.inst(2, true, I64REMS); } - pub fn i64_rem_u(&mut self) { self.inst(2, true, I64REMU); } - pub fn i64_and(&mut self) { self.inst(2, true, I64AND); } - pub fn i64_or(&mut self) { self.inst(2, true, I64OR); } - pub fn i64_xor(&mut self) { self.inst(2, true, I64XOR); } - pub fn i64_shl(&mut self) { self.inst(2, true, I64SHL); } - pub fn i64_shr_s(&mut self) { self.inst(2, true, I64SHRS); } - pub fn i64_shr_u(&mut self) { self.inst(2, true, I64SHRU); } - pub fn i64_rotl(&mut self) { self.inst(2, true, I64ROTL); } - pub fn i64_rotr(&mut self) { self.inst(2, true, I64ROTR); } - pub fn f32_abs(&mut self) { self.inst(1, true, F32ABS); } - pub fn f32_neg(&mut self) { self.inst(1, true, F32NEG); } - pub fn f32_ceil(&mut self) { self.inst(1, true, F32CEIL); } - pub fn f32_floor(&mut self) { self.inst(1, true, F32FLOOR); } - pub fn f32_trunc(&mut self) { self.inst(1, true, F32TRUNC); } - pub fn f32_nearest(&mut self) { self.inst(1, true, F32NEAREST); } - pub fn f32_sqrt(&mut self) { self.inst(1, true, F32SQRT); } - pub fn f32_add(&mut self) { self.inst(2, true, F32ADD); } - pub fn f32_sub(&mut self) { self.inst(2, true, F32SUB); } - pub fn f32_mul(&mut self) { self.inst(2, true, F32MUL); } - pub fn f32_div(&mut self) { self.inst(2, true, F32DIV); } - pub fn f32_min(&mut self) { self.inst(2, true, F32MIN); } - pub fn f32_max(&mut self) { self.inst(2, true, F32MAX); } - pub fn f32_copysign(&mut self) { self.inst(2, true, F32COPYSIGN); } - pub fn f64_abs(&mut self) { self.inst(1, true, F64ABS); } - pub fn f64_neg(&mut self) { self.inst(1, true, F64NEG); } - pub fn f64_ceil(&mut self) { self.inst(1, true, F64CEIL); } - pub fn f64_floor(&mut self) { self.inst(1, true, F64FLOOR); } - pub fn f64_trunc(&mut self) { self.inst(1, true, F64TRUNC); } - pub fn f64_nearest(&mut self) { self.inst(1, true, F64NEAREST); } - pub fn f64_sqrt(&mut self) { self.inst(1, true, F64SQRT); } - pub fn f64_add(&mut self) { self.inst(2, true, F64ADD); } - pub fn f64_sub(&mut self) { self.inst(2, true, F64SUB); } - pub fn f64_mul(&mut self) { self.inst(2, true, F64MUL); } - pub fn f64_div(&mut self) { self.inst(2, true, F64DIV); } - pub fn f64_min(&mut self) { self.inst(2, true, F64MIN); } - pub fn f64_max(&mut self) { self.inst(2, true, F64MAX); } - pub fn f64_copysign(&mut self) { self.inst(2, true, F64COPYSIGN); } - pub fn i32_wrap_i64(&mut self) { self.inst(1, true, I32WRAPI64); } - pub fn i32_trunc_s_f32(&mut self) { self.inst(1, true, I32TRUNCSF32); } - pub fn i32_trunc_u_f32(&mut self) { self.inst(1, true, I32TRUNCUF32); } - pub fn i32_trunc_s_f64(&mut self) { self.inst(1, true, I32TRUNCSF64); } - pub fn i32_trunc_u_f64(&mut self) { self.inst(1, true, I32TRUNCUF64); } - pub fn i64_extend_s_i32(&mut self) { self.inst(1, true, I64EXTENDSI32); } - pub fn i64_extend_u_i32(&mut self) { self.inst(1, true, I64EXTENDUI32); } - pub fn i64_trunc_s_f32(&mut self) { self.inst(1, true, I64TRUNCSF32); } - pub fn i64_trunc_u_f32(&mut self) { self.inst(1, true, I64TRUNCUF32); } - pub fn i64_trunc_s_f64(&mut self) { self.inst(1, true, I64TRUNCSF64); } - pub fn i64_trunc_u_f64(&mut self) { self.inst(1, true, I64TRUNCUF64); } - pub fn f32_convert_s_i32(&mut self) { self.inst(1, true, F32CONVERTSI32); } - pub fn f32_convert_u_i32(&mut self) { self.inst(1, true, F32CONVERTUI32); } - pub fn f32_convert_s_i64(&mut self) { self.inst(1, true, F32CONVERTSI64); } - pub fn f32_convert_u_i64(&mut self) { self.inst(1, true, F32CONVERTUI64); } - pub fn f32_demote_f64(&mut self) { self.inst(1, true, F32DEMOTEF64); } - pub fn f64_convert_s_i32(&mut self) { self.inst(1, true, F64CONVERTSI32); } - pub fn f64_convert_u_i32(&mut self) { self.inst(1, true, F64CONVERTUI32); } - pub fn f64_convert_s_i64(&mut self) { self.inst(1, true, F64CONVERTSI64); } - pub fn f64_convert_u_i64(&mut self) { self.inst(1, true, F64CONVERTUI64); } - pub fn f64_promote_f32(&mut self) { self.inst(1, true, F64PROMOTEF32); } - pub fn i32_reinterpret_f32(&mut self) { self.inst(1, true, I32REINTERPRETF32); } - pub fn i64_reinterpret_f64(&mut self) { self.inst(1, true, I64REINTERPRETF64); } - pub fn f32_reinterpret_i32(&mut self) { self.inst(1, true, F32REINTERPRETI32); } - pub fn f64_reinterpret_i64(&mut self) { self.inst(1, true, F64REINTERPRETI64); } + pub fn unreachable(&mut self) { + self.inst(0, false, UNREACHABLE); + } + // pub fn nop(&mut self) { self.inst(0, false, NOP); } + // pub fn block(ty: BlockType) { self.inst_imm8(0, false, BLOCK, ty as u8); } + // pub fn loop_(ty: BlockType) { self.inst_imm8(0, false, LOOP, ty as u8); } + // pub fn if_(ty: BlockType) { self.inst_imm8(1, false, IF, ty as u8); } + // pub fn else_(&mut self) { self.inst(0, false, ELSE); } + // pub fn end(&mut self) { self.inst(0, false, END); } + // pub fn br(levels: u32) { self.inst_imm32(0, false, BR, levels); } + // pub fn br_if(levels: u32) { self.inst_imm32(1, false, BRIF, levels); } + // // br_table: not implemented + // pub fn return_(&mut self) { self.inst(0, false, RETURN); } + // // call: see above + // // call_indirect: not implemented + // pub fn drop(&mut self) { self.inst(1, false, DROP); } + // pub fn select(&mut self) { self.inst(3, true, SELECT); } + // pub fn get_local(&mut self, id: LocalId) { self.inst_imm32(0, true, GETLOCAL, id.0); } + // pub fn set_local(&mut self, id: LocalId) { self.inst_imm32(1, false, SETLOCAL, id.0); } + // pub fn tee_local(&mut self, id: LocalId) { self.inst_imm32(1, true, TEELOCAL, id.0); } + // pub fn get_global(&mut self, id: u32) { self.inst_imm32(0, true, GETGLOBAL, id); } + // pub fn set_global(&mut self, id: u32) { self.inst_imm32(1, false, SETGLOBAL, id); } + // pub fn i32_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD, align, offset); } + // pub fn i64_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD, align, offset); } + // pub fn f32_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, F32LOAD, align, offset); } + // pub fn f64_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, F64LOAD, align, offset); } + // pub fn i32_load8_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD8S, align, offset); } + // pub fn i32_load8_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD8U, align, offset); } + // pub fn i32_load16_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD16S, align, offset); } + // pub fn i32_load16_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD16U, align, offset); } + // pub fn i64_load8_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD8S, align, offset); } + // pub fn i64_load8_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD8U, align, offset); } + // pub fn i64_load16_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD16S, align, offset); } + // pub fn i64_load16_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD16U, align, offset); } + // pub fn i64_load32_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD32S, align, offset); } + // pub fn i64_load32_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD32U, align, offset); } + // pub fn i32_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I32STORE, align, offset); } + // pub fn i64_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE, align, offset); } + // pub fn f32_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, F32STORE, align, offset); } + // pub fn f64_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, F64STORE, align, offset); } + // pub fn i32_store8(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I32STORE8, align, offset); } + // pub fn i32_store16(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I32STORE16, align, offset); } + // pub fn i64_store8(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE8, align, offset); } + // pub fn i64_store16(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE16, align, offset); } + // pub fn i64_store32(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE32, align, offset); } + // pub fn memory_size(&mut self) { self.inst_imm8(0, true, CURRENTMEMORY, 0); } + // pub fn memory_grow(&mut self) { self.inst_imm8(1, true, GROWMEMORY, 0); } + // pub fn i32_const(&mut self, x: i32) { self.inst_imm32(0, true, I32CONST, x as u32); } + // pub fn i64_const(&mut self, x: i64) { self.inst_imm64(0, true, I64CONST, x as u64); } + // pub fn f32_const(&mut self, x: f32) { self.inst_imm32(0, true, F32CONST, x.to_bits()); } + // pub fn f64_const(&mut self, x: f64) { self.inst_imm64(0, true, F64CONST, x.to_bits()); } + // pub fn i32_eqz(&mut self) { self.inst(1, true, I32EQZ); } + // pub fn i32_eq(&mut self) { self.inst(2, true, I32EQ); } + // pub fn i32_ne(&mut self) { self.inst(2, true, I32NE); } + // pub fn i32_lt_s(&mut self) { self.inst(2, true, I32LTS); } + // pub fn i32_lt_u(&mut self) { self.inst(2, true, I32LTU); } + // pub fn i32_gt_s(&mut self) { self.inst(2, true, I32GTS); } + // pub fn i32_gt_u(&mut self) { self.inst(2, true, I32GTU); } + // pub fn i32_le_s(&mut self) { self.inst(2, true, I32LES); } + // pub fn i32_le_u(&mut self) { self.inst(2, true, I32LEU); } + // pub fn i32_ge_s(&mut self) { self.inst(2, true, I32GES); } + // pub fn i32_ge_u(&mut self) { self.inst(2, true, I32GEU); } + // pub fn i64_eqz(&mut self) { self.inst(1, true, I64EQZ); } + // pub fn i64_eq(&mut self) { self.inst(2, true, I64EQ); } + // pub fn i64_ne(&mut self) { self.inst(2, true, I64NE); } + // pub fn i64_lt_s(&mut self) { self.inst(2, true, I64LTS); } + // pub fn i64_lt_u(&mut self) { self.inst(2, true, I64LTU); } + // pub fn i64_gt_s(&mut self) { self.inst(2, true, I64GTS); } + // pub fn i64_gt_u(&mut self) { self.inst(2, true, I64GTU); } + // pub fn i64_le_s(&mut self) { self.inst(2, true, I64LES); } + // pub fn i64_le_u(&mut self) { self.inst(2, true, I64LEU); } + // pub fn i64_ge_s(&mut self) { self.inst(2, true, I64GES); } + // pub fn i64_ge_u(&mut self) { self.inst(2, true, I64GEU); } + // pub fn f32_eq(&mut self) { self.inst(2, true, F32EQ); } + // pub fn f32_ne(&mut self) { self.inst(2, true, F32NE); } + // pub fn f32_lt(&mut self) { self.inst(2, true, F32LT); } + // pub fn f32_gt(&mut self) { self.inst(2, true, F32GT); } + // pub fn f32_le(&mut self) { self.inst(2, true, F32LE); } + // pub fn f32_ge(&mut self) { self.inst(2, true, F32GE); } + // pub fn f64_eq(&mut self) { self.inst(2, true, F64EQ); } + // pub fn f64_ne(&mut self) { self.inst(2, true, F64NE); } + // pub fn f64_lt(&mut self) { self.inst(2, true, F64LT); } + // pub fn f64_gt(&mut self) { self.inst(2, true, F64GT); } + // pub fn f64_le(&mut self) { self.inst(2, true, F64LE); } + // pub fn f64_ge(&mut self) { self.inst(2, true, F64GE); } + // pub fn i32_clz(&mut self) { self.inst(1, true, I32CLZ); } + // pub fn i32_ctz(&mut self) { self.inst(1, true, I32CTZ); } + // pub fn i32_popcnt(&mut self) { self.inst(1, true, I32POPCNT); } + // pub fn i32_add(&mut self) { self.inst(2, true, I32ADD); } + // pub fn i32_sub(&mut self) { self.inst(2, true, I32SUB); } + // pub fn i32_mul(&mut self) { self.inst(2, true, I32MUL); } + // pub fn i32_div_s(&mut self) { self.inst(2, true, I32DIVS); } + // pub fn i32_div_u(&mut self) { self.inst(2, true, I32DIVU); } + // pub fn i32_rem_s(&mut self) { self.inst(2, true, I32REMS); } + // pub fn i32_rem_u(&mut self) { self.inst(2, true, I32REMU); } + // pub fn i32_and(&mut self) { self.inst(2, true, I32AND); } + // pub fn i32_or(&mut self) { self.inst(2, true, I32OR); } + // pub fn i32_xor(&mut self) { self.inst(2, true, I32XOR); } + // pub fn i32_shl(&mut self) { self.inst(2, true, I32SHL); } + // pub fn i32_shr_s(&mut self) { self.inst(2, true, I32SHRS); } + // pub fn i32_shr_u(&mut self) { self.inst(2, true, I32SHRU); } + // pub fn i32_rotl(&mut self) { self.inst(2, true, I32ROTL); } + // pub fn i32_rotr(&mut self) { self.inst(2, true, I32ROTR); } + // pub fn i64_clz(&mut self) { self.inst(1, true, I64CLZ); } + // pub fn i64_ctz(&mut self) { self.inst(1, true, I64CTZ); } + // pub fn i64_popcnt(&mut self) { self.inst(1, true, I64POPCNT); } + // pub fn i64_add(&mut self) { self.inst(2, true, I64ADD); } + // pub fn i64_sub(&mut self) { self.inst(2, true, I64SUB); } + // pub fn i64_mul(&mut self) { self.inst(2, true, I64MUL); } + // pub fn i64_div_s(&mut self) { self.inst(2, true, I64DIVS); } + // pub fn i64_div_u(&mut self) { self.inst(2, true, I64DIVU); } + // pub fn i64_rem_s(&mut self) { self.inst(2, true, I64REMS); } + // pub fn i64_rem_u(&mut self) { self.inst(2, true, I64REMU); } + // pub fn i64_and(&mut self) { self.inst(2, true, I64AND); } + // pub fn i64_or(&mut self) { self.inst(2, true, I64OR); } + // pub fn i64_xor(&mut self) { self.inst(2, true, I64XOR); } + // pub fn i64_shl(&mut self) { self.inst(2, true, I64SHL); } + // pub fn i64_shr_s(&mut self) { self.inst(2, true, I64SHRS); } + // pub fn i64_shr_u(&mut self) { self.inst(2, true, I64SHRU); } + // pub fn i64_rotl(&mut self) { self.inst(2, true, I64ROTL); } + // pub fn i64_rotr(&mut self) { self.inst(2, true, I64ROTR); } + // pub fn f32_abs(&mut self) { self.inst(1, true, F32ABS); } + // pub fn f32_neg(&mut self) { self.inst(1, true, F32NEG); } + // pub fn f32_ceil(&mut self) { self.inst(1, true, F32CEIL); } + // pub fn f32_floor(&mut self) { self.inst(1, true, F32FLOOR); } + // pub fn f32_trunc(&mut self) { self.inst(1, true, F32TRUNC); } + // pub fn f32_nearest(&mut self) { self.inst(1, true, F32NEAREST); } + // pub fn f32_sqrt(&mut self) { self.inst(1, true, F32SQRT); } + // pub fn f32_add(&mut self) { self.inst(2, true, F32ADD); } + // pub fn f32_sub(&mut self) { self.inst(2, true, F32SUB); } + // pub fn f32_mul(&mut self) { self.inst(2, true, F32MUL); } + // pub fn f32_div(&mut self) { self.inst(2, true, F32DIV); } + // pub fn f32_min(&mut self) { self.inst(2, true, F32MIN); } + // pub fn f32_max(&mut self) { self.inst(2, true, F32MAX); } + // pub fn f32_copysign(&mut self) { self.inst(2, true, F32COPYSIGN); } + // pub fn f64_abs(&mut self) { self.inst(1, true, F64ABS); } + // pub fn f64_neg(&mut self) { self.inst(1, true, F64NEG); } + // pub fn f64_ceil(&mut self) { self.inst(1, true, F64CEIL); } + // pub fn f64_floor(&mut self) { self.inst(1, true, F64FLOOR); } + // pub fn f64_trunc(&mut self) { self.inst(1, true, F64TRUNC); } + // pub fn f64_nearest(&mut self) { self.inst(1, true, F64NEAREST); } + // pub fn f64_sqrt(&mut self) { self.inst(1, true, F64SQRT); } + // pub fn f64_add(&mut self) { self.inst(2, true, F64ADD); } + // pub fn f64_sub(&mut self) { self.inst(2, true, F64SUB); } + // pub fn f64_mul(&mut self) { self.inst(2, true, F64MUL); } + // pub fn f64_div(&mut self) { self.inst(2, true, F64DIV); } + // pub fn f64_min(&mut self) { self.inst(2, true, F64MIN); } + // pub fn f64_max(&mut self) { self.inst(2, true, F64MAX); } + // pub fn f64_copysign(&mut self) { self.inst(2, true, F64COPYSIGN); } + // pub fn i32_wrap_i64(&mut self) { self.inst(1, true, I32WRAPI64); } + // pub fn i32_trunc_s_f32(&mut self) { self.inst(1, true, I32TRUNCSF32); } + // pub fn i32_trunc_u_f32(&mut self) { self.inst(1, true, I32TRUNCUF32); } + // pub fn i32_trunc_s_f64(&mut self) { self.inst(1, true, I32TRUNCSF64); } + // pub fn i32_trunc_u_f64(&mut self) { self.inst(1, true, I32TRUNCUF64); } + // pub fn i64_extend_s_i32(&mut self) { self.inst(1, true, I64EXTENDSI32); } + // pub fn i64_extend_u_i32(&mut self) { self.inst(1, true, I64EXTENDUI32); } + // pub fn i64_trunc_s_f32(&mut self) { self.inst(1, true, I64TRUNCSF32); } + // pub fn i64_trunc_u_f32(&mut self) { self.inst(1, true, I64TRUNCUF32); } + // pub fn i64_trunc_s_f64(&mut self) { self.inst(1, true, I64TRUNCSF64); } + // pub fn i64_trunc_u_f64(&mut self) { self.inst(1, true, I64TRUNCUF64); } + // pub fn f32_convert_s_i32(&mut self) { self.inst(1, true, F32CONVERTSI32); } + // pub fn f32_convert_u_i32(&mut self) { self.inst(1, true, F32CONVERTUI32); } + // pub fn f32_convert_s_i64(&mut self) { self.inst(1, true, F32CONVERTSI64); } + // pub fn f32_convert_u_i64(&mut self) { self.inst(1, true, F32CONVERTUI64); } + // pub fn f32_demote_f64(&mut self) { self.inst(1, true, F32DEMOTEF64); } + // pub fn f64_convert_s_i32(&mut self) { self.inst(1, true, F64CONVERTSI32); } + // pub fn f64_convert_u_i32(&mut self) { self.inst(1, true, F64CONVERTUI32); } + // pub fn f64_convert_s_i64(&mut self) { self.inst(1, true, F64CONVERTSI64); } + // pub fn f64_convert_u_i64(&mut self) { self.inst(1, true, F64CONVERTUI64); } + // pub fn f64_promote_f32(&mut self) { self.inst(1, true, F64PROMOTEF32); } + // pub fn i32_reinterpret_f32(&mut self) { self.inst(1, true, I32REINTERPRETF32); } + // pub fn i64_reinterpret_f64(&mut self) { self.inst(1, true, I64REINTERPRETF64); } + // pub fn f32_reinterpret_i32(&mut self) { self.inst(1, true, F32REINTERPRETI32); } + // pub fn f64_reinterpret_i64(&mut self) { self.inst(1, true, F64REINTERPRETI64); } } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 9147f4f37b..e11d2f4b30 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -3,6 +3,7 @@ mod code_builder; pub mod from_wasm32_memory; mod function_builder; mod layout; +mod opcodes; mod storage; use bumpalo::collections::Vec; @@ -207,3 +208,7 @@ pub fn pop_stack_frame( SetGlobal(STACK_POINTER_GLOBAL_ID), ]); } + +pub fn debug_panic(error: E) { + panic!("{:?}", error); +} 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; From 4f958e8a80f41eb2ba2328ac4e5e34f86bfdee66 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 22 Oct 2021 12:01:22 +0200 Subject: [PATCH 05/28] Get most common instruction patterns to compile --- compiler/gen_wasm/src/function_builder.rs | 445 +++++++++++----------- compiler/gen_wasm/src/lib.rs | 8 +- 2 files changed, 232 insertions(+), 221 deletions(-) diff --git a/compiler/gen_wasm/src/function_builder.rs b/compiler/gen_wasm/src/function_builder.rs index 4efcde3c04..2219ce8585 100644 --- a/compiler/gen_wasm/src/function_builder.rs +++ b/compiler/gen_wasm/src/function_builder.rs @@ -14,14 +14,14 @@ const DEBUG_LOG: bool = false; #[repr(u8)] #[derive(PartialEq, Eq, Clone, Copy)] -enum ValueType { +pub enum ValueType { I32 = 0x7f, I64 = 0x7e, F32 = 0x7d, F64 = 0x7c, } -enum BlockType { +pub enum BlockType { NoResult, Value(ValueType), } @@ -36,52 +36,32 @@ impl BlockType { } #[repr(u8)] -enum Align { +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 ... } -// macro_rules! instruction_method { -// ($method_name: ident, $pops: expr, $push: expr, $opcode: expr) => { -// pub fn $name(&mut self) { -// self.inst($pops, $push, $opcode); -// } -// }; -// } +macro_rules! instruction_method { + ($method_name: ident, $pops: expr, $push: expr, $opcode: expr) => { + pub fn $method_name(&mut self) { + self.inst($pops, $push, $opcode); + } + }; +} -// macro_rules! binop_method { -// ($method_name: ident, $opcode: expr) => { -// pub fn $name(&mut self) { -// self.inst(2, true, $opcode); -// } -// }; -// } - -// macro_rules! unop_method { -// ($method_name: ident, $opcode: expr) => { -// pub fn $name(&mut self) { -// self.inst(1, true, $opcode); -// } -// }; -// } - -// macro_rules! mem_method { -// ($method_name: ident, $pops: expr, $push: expr, $opcode: expr) => { -// pub fn $name(&mut self) { -// self.inst($pops, $push, $opcode); -// } -// }; -// } - -// macro_rules! instruction_method { -// ($method_name: ident, $pops: expr, $push: expr, $opcode: expr) => { -// pub fn $name(&mut self) { -// self.inst($pops, $push, $opcode); -// } -// }; -// } +macro_rules! instruction_method_mem { + ($method_name: ident, $pops: expr, $push: expr, $opcode: expr) => { + pub fn $method_name(&mut self, align: Align, offset: u32) { + self.inst_mem($pops, $push, $opcode, align, offset); + } + }; +} #[derive(Debug)] pub struct FunctionBuilder<'a> { @@ -205,7 +185,7 @@ impl<'a> FunctionBuilder<'a> { } /// Finalize a function body - pub fn finalize(&mut self, final_code: &mut std::vec::Vec) { + pub fn finalize(&mut self, _final_code: &mut std::vec::Vec) { // sort insertions // encode local declarations // encode stack frame push @@ -214,7 +194,7 @@ impl<'a> FunctionBuilder<'a> { // encode inner length } - pub fn serialize(&self, writer: W) { + pub fn serialize(&self, _writer: W) { // write inner length // write locals // write stack frame push @@ -247,197 +227,224 @@ impl<'a> FunctionBuilder<'a> { } fn inst(&mut self, pops: usize, push: bool, opcode: u8) { - panic!("TODO"); + 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, pops: usize, push: bool, opcode: u8, immediate: u8) { - panic!("TODO"); + self.inst(pops, push, opcode); + self.code.push(immediate); } fn inst_imm32(&mut self, pops: usize, push: bool, opcode: u8, immediate: u32) { - panic!("TODO"); + self.inst(pops, push, opcode); + let mut value = immediate; + while value >= 0x80 { + self.code.push(0x80 | ((value & 0x7f) as u8)); + value >>= 7; + } + self.code.push(value as u8); } fn inst_imm64(&mut self, pops: usize, push: bool, opcode: u8, immediate: u64) { - panic!("TODO"); + self.inst(pops, push, opcode); + let mut value = immediate; + while value >= 0x80 { + self.code.push(0x80 | ((value & 0x7f) as u8)); + value >>= 7; + } + self.code.push(value as u8); } fn inst_mem(&mut self, pops: usize, push: bool, opcode: u8, align: Align, offset: u32) { - panic!("TODO"); + self.inst(pops, push, opcode); + self.code.push(align as u8); + let mut value = offset; + while value >= 0x80 { + self.code.push(0x80 | ((value & 0x7f) as u8)); + value >>= 7; + } + self.code.push(value as u8); } - pub fn unreachable(&mut self) { - self.inst(0, false, UNREACHABLE); - } - // pub fn nop(&mut self) { self.inst(0, false, NOP); } + // Instruction methods + // One method for each Wasm instruction, ordered as in the spec and in parity-wasm + // macros are just to improve readability for the most common cases + // Patterns that don't repeat very much don't have macros + instruction_method!(unreachable_, 0, false, UNREACHABLE); + instruction_method!(nop, 0, false, NOP); // pub fn block(ty: BlockType) { self.inst_imm8(0, false, BLOCK, ty as u8); } // pub fn loop_(ty: BlockType) { self.inst_imm8(0, false, LOOP, ty as u8); } // pub fn if_(ty: BlockType) { self.inst_imm8(1, false, IF, ty as u8); } - // pub fn else_(&mut self) { self.inst(0, false, ELSE); } - // pub fn end(&mut self) { self.inst(0, false, END); } - // pub fn br(levels: u32) { self.inst_imm32(0, false, BR, levels); } - // pub fn br_if(levels: u32) { self.inst_imm32(1, false, BRIF, levels); } - // // br_table: not implemented - // pub fn return_(&mut self) { self.inst(0, false, RETURN); } - // // call: see above - // // call_indirect: not implemented - // pub fn drop(&mut self) { self.inst(1, false, DROP); } - // pub fn select(&mut self) { self.inst(3, true, SELECT); } - // pub fn get_local(&mut self, id: LocalId) { self.inst_imm32(0, true, GETLOCAL, id.0); } - // pub fn set_local(&mut self, id: LocalId) { self.inst_imm32(1, false, SETLOCAL, id.0); } - // pub fn tee_local(&mut self, id: LocalId) { self.inst_imm32(1, true, TEELOCAL, id.0); } - // pub fn get_global(&mut self, id: u32) { self.inst_imm32(0, true, GETGLOBAL, id); } - // pub fn set_global(&mut self, id: u32) { self.inst_imm32(1, false, SETGLOBAL, id); } - // pub fn i32_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD, align, offset); } - // pub fn i64_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD, align, offset); } - // pub fn f32_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, F32LOAD, align, offset); } - // pub fn f64_load(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, F64LOAD, align, offset); } - // pub fn i32_load8_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD8S, align, offset); } - // pub fn i32_load8_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD8U, align, offset); } - // pub fn i32_load16_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD16S, align, offset); } - // pub fn i32_load16_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I32LOAD16U, align, offset); } - // pub fn i64_load8_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD8S, align, offset); } - // pub fn i64_load8_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD8U, align, offset); } - // pub fn i64_load16_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD16S, align, offset); } - // pub fn i64_load16_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD16U, align, offset); } - // pub fn i64_load32_s(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD32S, align, offset); } - // pub fn i64_load32_u(&mut self, align: Align, offset: u32) { self.inst_mem(1, true, I64LOAD32U, align, offset); } - // pub fn i32_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I32STORE, align, offset); } - // pub fn i64_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE, align, offset); } - // pub fn f32_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, F32STORE, align, offset); } - // pub fn f64_store(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, F64STORE, align, offset); } - // pub fn i32_store8(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I32STORE8, align, offset); } - // pub fn i32_store16(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I32STORE16, align, offset); } - // pub fn i64_store8(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE8, align, offset); } - // pub fn i64_store16(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE16, align, offset); } - // pub fn i64_store32(&mut self, align: Align, offset: u32) { self.inst_mem(2, false, I64STORE32, align, offset); } - // pub fn memory_size(&mut self) { self.inst_imm8(0, true, CURRENTMEMORY, 0); } - // pub fn memory_grow(&mut self) { self.inst_imm8(1, true, GROWMEMORY, 0); } - // pub fn i32_const(&mut self, x: i32) { self.inst_imm32(0, true, I32CONST, x as u32); } - // pub fn i64_const(&mut self, x: i64) { self.inst_imm64(0, true, I64CONST, x as u64); } - // pub fn f32_const(&mut self, x: f32) { self.inst_imm32(0, true, F32CONST, x.to_bits()); } - // pub fn f64_const(&mut self, x: f64) { self.inst_imm64(0, true, F64CONST, x.to_bits()); } - // pub fn i32_eqz(&mut self) { self.inst(1, true, I32EQZ); } - // pub fn i32_eq(&mut self) { self.inst(2, true, I32EQ); } - // pub fn i32_ne(&mut self) { self.inst(2, true, I32NE); } - // pub fn i32_lt_s(&mut self) { self.inst(2, true, I32LTS); } - // pub fn i32_lt_u(&mut self) { self.inst(2, true, I32LTU); } - // pub fn i32_gt_s(&mut self) { self.inst(2, true, I32GTS); } - // pub fn i32_gt_u(&mut self) { self.inst(2, true, I32GTU); } - // pub fn i32_le_s(&mut self) { self.inst(2, true, I32LES); } - // pub fn i32_le_u(&mut self) { self.inst(2, true, I32LEU); } - // pub fn i32_ge_s(&mut self) { self.inst(2, true, I32GES); } - // pub fn i32_ge_u(&mut self) { self.inst(2, true, I32GEU); } - // pub fn i64_eqz(&mut self) { self.inst(1, true, I64EQZ); } - // pub fn i64_eq(&mut self) { self.inst(2, true, I64EQ); } - // pub fn i64_ne(&mut self) { self.inst(2, true, I64NE); } - // pub fn i64_lt_s(&mut self) { self.inst(2, true, I64LTS); } - // pub fn i64_lt_u(&mut self) { self.inst(2, true, I64LTU); } - // pub fn i64_gt_s(&mut self) { self.inst(2, true, I64GTS); } - // pub fn i64_gt_u(&mut self) { self.inst(2, true, I64GTU); } - // pub fn i64_le_s(&mut self) { self.inst(2, true, I64LES); } - // pub fn i64_le_u(&mut self) { self.inst(2, true, I64LEU); } - // pub fn i64_ge_s(&mut self) { self.inst(2, true, I64GES); } - // pub fn i64_ge_u(&mut self) { self.inst(2, true, I64GEU); } - // pub fn f32_eq(&mut self) { self.inst(2, true, F32EQ); } - // pub fn f32_ne(&mut self) { self.inst(2, true, F32NE); } - // pub fn f32_lt(&mut self) { self.inst(2, true, F32LT); } - // pub fn f32_gt(&mut self) { self.inst(2, true, F32GT); } - // pub fn f32_le(&mut self) { self.inst(2, true, F32LE); } - // pub fn f32_ge(&mut self) { self.inst(2, true, F32GE); } - // pub fn f64_eq(&mut self) { self.inst(2, true, F64EQ); } - // pub fn f64_ne(&mut self) { self.inst(2, true, F64NE); } - // pub fn f64_lt(&mut self) { self.inst(2, true, F64LT); } - // pub fn f64_gt(&mut self) { self.inst(2, true, F64GT); } - // pub fn f64_le(&mut self) { self.inst(2, true, F64LE); } - // pub fn f64_ge(&mut self) { self.inst(2, true, F64GE); } - // pub fn i32_clz(&mut self) { self.inst(1, true, I32CLZ); } - // pub fn i32_ctz(&mut self) { self.inst(1, true, I32CTZ); } - // pub fn i32_popcnt(&mut self) { self.inst(1, true, I32POPCNT); } - // pub fn i32_add(&mut self) { self.inst(2, true, I32ADD); } - // pub fn i32_sub(&mut self) { self.inst(2, true, I32SUB); } - // pub fn i32_mul(&mut self) { self.inst(2, true, I32MUL); } - // pub fn i32_div_s(&mut self) { self.inst(2, true, I32DIVS); } - // pub fn i32_div_u(&mut self) { self.inst(2, true, I32DIVU); } - // pub fn i32_rem_s(&mut self) { self.inst(2, true, I32REMS); } - // pub fn i32_rem_u(&mut self) { self.inst(2, true, I32REMU); } - // pub fn i32_and(&mut self) { self.inst(2, true, I32AND); } - // pub fn i32_or(&mut self) { self.inst(2, true, I32OR); } - // pub fn i32_xor(&mut self) { self.inst(2, true, I32XOR); } - // pub fn i32_shl(&mut self) { self.inst(2, true, I32SHL); } - // pub fn i32_shr_s(&mut self) { self.inst(2, true, I32SHRS); } - // pub fn i32_shr_u(&mut self) { self.inst(2, true, I32SHRU); } - // pub fn i32_rotl(&mut self) { self.inst(2, true, I32ROTL); } - // pub fn i32_rotr(&mut self) { self.inst(2, true, I32ROTR); } - // pub fn i64_clz(&mut self) { self.inst(1, true, I64CLZ); } - // pub fn i64_ctz(&mut self) { self.inst(1, true, I64CTZ); } - // pub fn i64_popcnt(&mut self) { self.inst(1, true, I64POPCNT); } - // pub fn i64_add(&mut self) { self.inst(2, true, I64ADD); } - // pub fn i64_sub(&mut self) { self.inst(2, true, I64SUB); } - // pub fn i64_mul(&mut self) { self.inst(2, true, I64MUL); } - // pub fn i64_div_s(&mut self) { self.inst(2, true, I64DIVS); } - // pub fn i64_div_u(&mut self) { self.inst(2, true, I64DIVU); } - // pub fn i64_rem_s(&mut self) { self.inst(2, true, I64REMS); } - // pub fn i64_rem_u(&mut self) { self.inst(2, true, I64REMU); } - // pub fn i64_and(&mut self) { self.inst(2, true, I64AND); } - // pub fn i64_or(&mut self) { self.inst(2, true, I64OR); } - // pub fn i64_xor(&mut self) { self.inst(2, true, I64XOR); } - // pub fn i64_shl(&mut self) { self.inst(2, true, I64SHL); } - // pub fn i64_shr_s(&mut self) { self.inst(2, true, I64SHRS); } - // pub fn i64_shr_u(&mut self) { self.inst(2, true, I64SHRU); } - // pub fn i64_rotl(&mut self) { self.inst(2, true, I64ROTL); } - // pub fn i64_rotr(&mut self) { self.inst(2, true, I64ROTR); } - // pub fn f32_abs(&mut self) { self.inst(1, true, F32ABS); } - // pub fn f32_neg(&mut self) { self.inst(1, true, F32NEG); } - // pub fn f32_ceil(&mut self) { self.inst(1, true, F32CEIL); } - // pub fn f32_floor(&mut self) { self.inst(1, true, F32FLOOR); } - // pub fn f32_trunc(&mut self) { self.inst(1, true, F32TRUNC); } - // pub fn f32_nearest(&mut self) { self.inst(1, true, F32NEAREST); } - // pub fn f32_sqrt(&mut self) { self.inst(1, true, F32SQRT); } - // pub fn f32_add(&mut self) { self.inst(2, true, F32ADD); } - // pub fn f32_sub(&mut self) { self.inst(2, true, F32SUB); } - // pub fn f32_mul(&mut self) { self.inst(2, true, F32MUL); } - // pub fn f32_div(&mut self) { self.inst(2, true, F32DIV); } - // pub fn f32_min(&mut self) { self.inst(2, true, F32MIN); } - // pub fn f32_max(&mut self) { self.inst(2, true, F32MAX); } - // pub fn f32_copysign(&mut self) { self.inst(2, true, F32COPYSIGN); } - // pub fn f64_abs(&mut self) { self.inst(1, true, F64ABS); } - // pub fn f64_neg(&mut self) { self.inst(1, true, F64NEG); } - // pub fn f64_ceil(&mut self) { self.inst(1, true, F64CEIL); } - // pub fn f64_floor(&mut self) { self.inst(1, true, F64FLOOR); } - // pub fn f64_trunc(&mut self) { self.inst(1, true, F64TRUNC); } - // pub fn f64_nearest(&mut self) { self.inst(1, true, F64NEAREST); } - // pub fn f64_sqrt(&mut self) { self.inst(1, true, F64SQRT); } - // pub fn f64_add(&mut self) { self.inst(2, true, F64ADD); } - // pub fn f64_sub(&mut self) { self.inst(2, true, F64SUB); } - // pub fn f64_mul(&mut self) { self.inst(2, true, F64MUL); } - // pub fn f64_div(&mut self) { self.inst(2, true, F64DIV); } - // pub fn f64_min(&mut self) { self.inst(2, true, F64MIN); } - // pub fn f64_max(&mut self) { self.inst(2, true, F64MAX); } - // pub fn f64_copysign(&mut self) { self.inst(2, true, F64COPYSIGN); } - // pub fn i32_wrap_i64(&mut self) { self.inst(1, true, I32WRAPI64); } - // pub fn i32_trunc_s_f32(&mut self) { self.inst(1, true, I32TRUNCSF32); } - // pub fn i32_trunc_u_f32(&mut self) { self.inst(1, true, I32TRUNCUF32); } - // pub fn i32_trunc_s_f64(&mut self) { self.inst(1, true, I32TRUNCSF64); } - // pub fn i32_trunc_u_f64(&mut self) { self.inst(1, true, I32TRUNCUF64); } - // pub fn i64_extend_s_i32(&mut self) { self.inst(1, true, I64EXTENDSI32); } - // pub fn i64_extend_u_i32(&mut self) { self.inst(1, true, I64EXTENDUI32); } - // pub fn i64_trunc_s_f32(&mut self) { self.inst(1, true, I64TRUNCSF32); } - // pub fn i64_trunc_u_f32(&mut self) { self.inst(1, true, I64TRUNCUF32); } - // pub fn i64_trunc_s_f64(&mut self) { self.inst(1, true, I64TRUNCSF64); } - // pub fn i64_trunc_u_f64(&mut self) { self.inst(1, true, I64TRUNCUF64); } - // pub fn f32_convert_s_i32(&mut self) { self.inst(1, true, F32CONVERTSI32); } - // pub fn f32_convert_u_i32(&mut self) { self.inst(1, true, F32CONVERTUI32); } - // pub fn f32_convert_s_i64(&mut self) { self.inst(1, true, F32CONVERTSI64); } - // pub fn f32_convert_u_i64(&mut self) { self.inst(1, true, F32CONVERTUI64); } - // pub fn f32_demote_f64(&mut self) { self.inst(1, true, F32DEMOTEF64); } - // pub fn f64_convert_s_i32(&mut self) { self.inst(1, true, F64CONVERTSI32); } - // pub fn f64_convert_u_i32(&mut self) { self.inst(1, true, F64CONVERTUI32); } - // pub fn f64_convert_s_i64(&mut self) { self.inst(1, true, F64CONVERTSI64); } - // pub fn f64_convert_u_i64(&mut self) { self.inst(1, true, F64CONVERTUI64); } - // pub fn f64_promote_f32(&mut self) { self.inst(1, true, F64PROMOTEF32); } - // pub fn i32_reinterpret_f32(&mut self) { self.inst(1, true, I32REINTERPRETF32); } - // pub fn i64_reinterpret_f64(&mut self) { self.inst(1, true, I64REINTERPRETF64); } - // pub fn f32_reinterpret_i32(&mut self) { self.inst(1, true, F32REINTERPRETI32); } - // pub fn f64_reinterpret_i64(&mut self) { self.inst(1, true, F64REINTERPRETI64); } + instruction_method!(else_, 0, false, ELSE); + instruction_method!(end, 0, false, END); + // instruction_method!(br(levels: u32) { self.inst_imm32(0, false, BR, levels); + // instruction_method!(br_if(levels: u32) { self.inst_imm32(1, false, BRIF, levels); + // br_table: not implemented + instruction_method!(return_, 0, false, RETURN); + // call: see above + // call_indirect: not implemented + instruction_method!(drop, 1, false, DROP); + instruction_method!(select, 3, true, SELECT); + // instruction_method!(get_local(&mut self, id: LocalId) { self.inst_imm32(0, true, GETLOCAL, id.0); + // instruction_method!(set_local(&mut self, id: LocalId) { self.inst_imm32(1, false, SETLOCAL, id.0); + // instruction_method!(tee_local(&mut self, id: LocalId) { self.inst_imm32(1, true, TEELOCAL, id.0); + // instruction_method!(get_global(&mut self, id: u32) { self.inst_imm32(0, true, GETGLOBAL, id); + // instruction_method!(set_global(&mut self, id: u32) { self.inst_imm32(1, false, SETGLOBAL, id); + instruction_method_mem!(i32_load, 1, true, I32LOAD); + instruction_method_mem!(i64_load, 1, true, I64LOAD); + instruction_method_mem!(f32_load, 1, true, F32LOAD); + instruction_method_mem!(f64_load, 1, true, F64LOAD); + instruction_method_mem!(i32_load8_s, 1, true, I32LOAD8S); + instruction_method_mem!(i32_load8_u, 1, true, I32LOAD8U); + instruction_method_mem!(i32_load16_s, 1, true, I32LOAD16S); + instruction_method_mem!(i32_load16_u, 1, true, I32LOAD16U); + instruction_method_mem!(i64_load8_s, 1, true, I64LOAD8S); + instruction_method_mem!(i64_load8_u, 1, true, I64LOAD8U); + instruction_method_mem!(i64_load16_s, 1, true, I64LOAD16S); + instruction_method_mem!(i64_load16_u, 1, true, I64LOAD16U); + instruction_method_mem!(i64_load32_s, 1, true, I64LOAD32S); + instruction_method_mem!(i64_load32_u, 1, true, I64LOAD32U); + instruction_method_mem!(i32_store, 2, false, I32STORE); + instruction_method_mem!(i64_store, 2, false, I64STORE); + instruction_method_mem!(f32_store, 2, false, F32STORE); + instruction_method_mem!(f64_store, 2, false, F64STORE); + instruction_method_mem!(i32_store8, 2, false, I32STORE8); + instruction_method_mem!(i32_store16, 2, false, I32STORE16); + instruction_method_mem!(i64_store8, 2, false, I64STORE8); + instruction_method_mem!(i64_store16, 2, false, I64STORE16); + instruction_method_mem!(i64_store32, 2, false, I64STORE32); + // instruction_method!(memory_size(&mut self) { self.inst_imm8(0, true, CURRENTMEMORY, 0); + // instruction_method!(memory_grow(&mut self) { self.inst_imm8(1, true, GROWMEMORY, 0); + // instruction_method!(i32_const(&mut self, x: i32) { self.inst_imm32(0, true, I32CONST, x as u32); + // instruction_method!(i64_const(&mut self, x: i64) { self.inst_imm64(0, true, I64CONST, x as u64); + // instruction_method!(f32_const(&mut self, x: f32) { self.inst_imm32(0, true, F32CONST, x.to_bits()); + // instruction_method!(f64_const(&mut self, x: f64) { self.inst_imm64(0, true, F64CONST, x.to_bits()); + instruction_method!(i32_eqz, 1, true, I32EQZ); + instruction_method!(i32_eq, 2, true, I32EQ); + instruction_method!(i32_ne, 2, true, I32NE); + instruction_method!(i32_lt_s, 2, true, I32LTS); + instruction_method!(i32_lt_u, 2, true, I32LTU); + instruction_method!(i32_gt_s, 2, true, I32GTS); + instruction_method!(i32_gt_u, 2, true, I32GTU); + instruction_method!(i32_le_s, 2, true, I32LES); + instruction_method!(i32_le_u, 2, true, I32LEU); + instruction_method!(i32_ge_s, 2, true, I32GES); + instruction_method!(i32_ge_u, 2, true, I32GEU); + instruction_method!(i64_eqz, 1, true, I64EQZ); + instruction_method!(i64_eq, 2, true, I64EQ); + instruction_method!(i64_ne, 2, true, I64NE); + instruction_method!(i64_lt_s, 2, true, I64LTS); + instruction_method!(i64_lt_u, 2, true, I64LTU); + instruction_method!(i64_gt_s, 2, true, I64GTS); + instruction_method!(i64_gt_u, 2, true, I64GTU); + instruction_method!(i64_le_s, 2, true, I64LES); + instruction_method!(i64_le_u, 2, true, I64LEU); + instruction_method!(i64_ge_s, 2, true, I64GES); + instruction_method!(i64_ge_u, 2, true, I64GEU); + instruction_method!(f32_eq, 2, true, F32EQ); + instruction_method!(f32_ne, 2, true, F32NE); + instruction_method!(f32_lt, 2, true, F32LT); + instruction_method!(f32_gt, 2, true, F32GT); + instruction_method!(f32_le, 2, true, F32LE); + instruction_method!(f32_ge, 2, true, F32GE); + instruction_method!(f64_eq, 2, true, F64EQ); + instruction_method!(f64_ne, 2, true, F64NE); + instruction_method!(f64_lt, 2, true, F64LT); + instruction_method!(f64_gt, 2, true, F64GT); + instruction_method!(f64_le, 2, true, F64LE); + instruction_method!(f64_ge, 2, true, F64GE); + instruction_method!(i32_clz, 1, true, I32CLZ); + instruction_method!(i32_ctz, 1, true, I32CTZ); + instruction_method!(i32_popcnt, 1, true, I32POPCNT); + instruction_method!(i32_add, 2, true, I32ADD); + instruction_method!(i32_sub, 2, true, I32SUB); + instruction_method!(i32_mul, 2, true, I32MUL); + instruction_method!(i32_div_s, 2, true, I32DIVS); + instruction_method!(i32_div_u, 2, true, I32DIVU); + instruction_method!(i32_rem_s, 2, true, I32REMS); + instruction_method!(i32_rem_u, 2, true, I32REMU); + instruction_method!(i32_and, 2, true, I32AND); + instruction_method!(i32_or, 2, true, I32OR); + instruction_method!(i32_xor, 2, true, I32XOR); + instruction_method!(i32_shl, 2, true, I32SHL); + instruction_method!(i32_shr_s, 2, true, I32SHRS); + instruction_method!(i32_shr_u, 2, true, I32SHRU); + instruction_method!(i32_rotl, 2, true, I32ROTL); + instruction_method!(i32_rotr, 2, true, I32ROTR); + instruction_method!(i64_clz, 1, true, I64CLZ); + instruction_method!(i64_ctz, 1, true, I64CTZ); + instruction_method!(i64_popcnt, 1, true, I64POPCNT); + instruction_method!(i64_add, 2, true, I64ADD); + instruction_method!(i64_sub, 2, true, I64SUB); + instruction_method!(i64_mul, 2, true, I64MUL); + instruction_method!(i64_div_s, 2, true, I64DIVS); + instruction_method!(i64_div_u, 2, true, I64DIVU); + instruction_method!(i64_rem_s, 2, true, I64REMS); + instruction_method!(i64_rem_u, 2, true, I64REMU); + instruction_method!(i64_and, 2, true, I64AND); + instruction_method!(i64_or, 2, true, I64OR); + instruction_method!(i64_xor, 2, true, I64XOR); + instruction_method!(i64_shl, 2, true, I64SHL); + instruction_method!(i64_shr_s, 2, true, I64SHRS); + instruction_method!(i64_shr_u, 2, true, I64SHRU); + instruction_method!(i64_rotl, 2, true, I64ROTL); + instruction_method!(i64_rotr, 2, true, I64ROTR); + instruction_method!(f32_abs, 1, true, F32ABS); + instruction_method!(f32_neg, 1, true, F32NEG); + instruction_method!(f32_ceil, 1, true, F32CEIL); + instruction_method!(f32_floor, 1, true, F32FLOOR); + instruction_method!(f32_trunc, 1, true, F32TRUNC); + instruction_method!(f32_nearest, 1, true, F32NEAREST); + instruction_method!(f32_sqrt, 1, true, F32SQRT); + instruction_method!(f32_add, 2, true, F32ADD); + instruction_method!(f32_sub, 2, true, F32SUB); + instruction_method!(f32_mul, 2, true, F32MUL); + instruction_method!(f32_div, 2, true, F32DIV); + instruction_method!(f32_min, 2, true, F32MIN); + instruction_method!(f32_max, 2, true, F32MAX); + instruction_method!(f32_copysign, 2, true, F32COPYSIGN); + instruction_method!(f64_abs, 1, true, F64ABS); + instruction_method!(f64_neg, 1, true, F64NEG); + instruction_method!(f64_ceil, 1, true, F64CEIL); + instruction_method!(f64_floor, 1, true, F64FLOOR); + instruction_method!(f64_trunc, 1, true, F64TRUNC); + instruction_method!(f64_nearest, 1, true, F64NEAREST); + instruction_method!(f64_sqrt, 1, true, F64SQRT); + instruction_method!(f64_add, 2, true, F64ADD); + instruction_method!(f64_sub, 2, true, F64SUB); + instruction_method!(f64_mul, 2, true, F64MUL); + instruction_method!(f64_div, 2, true, F64DIV); + instruction_method!(f64_min, 2, true, F64MIN); + instruction_method!(f64_max, 2, true, F64MAX); + instruction_method!(f64_copysign, 2, true, F64COPYSIGN); + instruction_method!(i32_wrap_i64, 1, true, I32WRAPI64); + instruction_method!(i32_trunc_s_f32, 1, true, I32TRUNCSF32); + instruction_method!(i32_trunc_u_f32, 1, true, I32TRUNCUF32); + instruction_method!(i32_trunc_s_f64, 1, true, I32TRUNCSF64); + instruction_method!(i32_trunc_u_f64, 1, true, I32TRUNCUF64); + instruction_method!(i64_extend_s_i32, 1, true, I64EXTENDSI32); + instruction_method!(i64_extend_u_i32, 1, true, I64EXTENDUI32); + instruction_method!(i64_trunc_s_f32, 1, true, I64TRUNCSF32); + instruction_method!(i64_trunc_u_f32, 1, true, I64TRUNCUF32); + instruction_method!(i64_trunc_s_f64, 1, true, I64TRUNCSF64); + instruction_method!(i64_trunc_u_f64, 1, true, I64TRUNCUF64); + instruction_method!(f32_convert_s_i32, 1, true, F32CONVERTSI32); + instruction_method!(f32_convert_u_i32, 1, true, F32CONVERTUI32); + instruction_method!(f32_convert_s_i64, 1, true, F32CONVERTSI64); + instruction_method!(f32_convert_u_i64, 1, true, F32CONVERTUI64); + instruction_method!(f32_demote_f64, 1, true, F32DEMOTEF64); + instruction_method!(f64_convert_s_i32, 1, true, F64CONVERTSI32); + instruction_method!(f64_convert_u_i32, 1, true, F64CONVERTUI32); + instruction_method!(f64_convert_s_i64, 1, true, F64CONVERTSI64); + instruction_method!(f64_convert_u_i64, 1, true, F64CONVERTUI64); + instruction_method!(f64_promote_f32, 1, true, F64PROMOTEF32); + instruction_method!(i32_reinterpret_f32, 1, true, I32REINTERPRETF32); + instruction_method!(i64_reinterpret_f64, 1, true, I64REINTERPRETF64); + instruction_method!(f32_reinterpret_i32, 1, true, F32REINTERPRETI32); + instruction_method!(f64_reinterpret_i64, 1, true, F64REINTERPRETI64); } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index e11d2f4b30..72b23d0565 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -1,11 +1,15 @@ mod backend; mod code_builder; pub mod from_wasm32_memory; -mod function_builder; mod layout; -mod opcodes; mod storage; +#[allow(dead_code)] +mod function_builder; + +#[allow(dead_code)] +mod opcodes; + use bumpalo::collections::Vec; use bumpalo::Bump; use parity_wasm::builder; From fbceafa8dc24fe2614df0dfd9459684e3e846338 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 22 Oct 2021 12:59:19 +0200 Subject: [PATCH 06/28] Get all instructon methods compiling --- compiler/gen_wasm/src/function_builder.rs | 412 ++++++++++++---------- 1 file changed, 229 insertions(+), 183 deletions(-) diff --git a/compiler/gen_wasm/src/function_builder.rs b/compiler/gen_wasm/src/function_builder.rs index 2219ce8585..9f6cbacff4 100644 --- a/compiler/gen_wasm/src/function_builder.rs +++ b/compiler/gen_wasm/src/function_builder.rs @@ -47,7 +47,7 @@ pub enum Align { // ... we can add more if we need them ... } -macro_rules! instruction_method { +macro_rules! instruction_no_args { ($method_name: ident, $pops: expr, $push: expr, $opcode: expr) => { pub fn $method_name(&mut self) { self.inst($pops, $push, $opcode); @@ -55,7 +55,7 @@ macro_rules! instruction_method { }; } -macro_rules! instruction_method_mem { +macro_rules! instruction_memargs { ($method_name: ident, $pops: expr, $push: expr, $opcode: expr) => { pub fn $method_name(&mut self, align: Align, offset: u32) { self.inst_mem($pops, $push, $opcode, align, offset); @@ -226,6 +226,8 @@ impl<'a> FunctionBuilder<'a> { self.code.push(CALL); } + /// Base method for generating instructions + /// Emits the opcode and simulates VM stack push/pop fn inst(&mut self, pops: usize, push: bool, opcode: u8) { let new_len = self.vm_stack.len() - pops as usize; self.vm_stack.truncate(new_len); @@ -250,16 +252,6 @@ impl<'a> FunctionBuilder<'a> { self.code.push(value as u8); } - fn inst_imm64(&mut self, pops: usize, push: bool, opcode: u8, immediate: u64) { - self.inst(pops, push, opcode); - let mut value = immediate; - while value >= 0x80 { - self.code.push(0x80 | ((value & 0x7f) as u8)); - value >>= 7; - } - self.code.push(value as u8); - } - fn inst_mem(&mut self, pops: usize, push: bool, opcode: u8, align: Align, offset: u32) { self.inst(pops, push, opcode); self.code.push(align as u8); @@ -272,179 +264,233 @@ impl<'a> FunctionBuilder<'a> { } // Instruction methods - // One method for each Wasm instruction, ordered as in the spec and in parity-wasm - // macros are just to improve readability for the most common cases + // + // One method for each Wasm instruction (in same order as the spec) + // macros are just for compactness and readability for the most common cases // Patterns that don't repeat very much don't have macros - instruction_method!(unreachable_, 0, false, UNREACHABLE); - instruction_method!(nop, 0, false, NOP); - // pub fn block(ty: BlockType) { self.inst_imm8(0, false, BLOCK, ty as u8); } - // pub fn loop_(ty: BlockType) { self.inst_imm8(0, false, LOOP, ty as u8); } - // pub fn if_(ty: BlockType) { self.inst_imm8(1, false, IF, ty as u8); } - instruction_method!(else_, 0, false, ELSE); - instruction_method!(end, 0, false, END); - // instruction_method!(br(levels: u32) { self.inst_imm32(0, false, BR, levels); - // instruction_method!(br_if(levels: u32) { self.inst_imm32(1, false, BRIF, levels); + // instruction_no_args! creates a method that takes no arguments + // instruction_memargs! creates a method that takes alignment and offset arguments + + instruction_no_args!(unreachable_, 0, false, UNREACHABLE); + instruction_no_args!(nop, 0, false, NOP); + pub fn block(&mut self, ty: BlockType) { + self.inst_imm8(0, false, BLOCK, ty.as_byte()); + } + pub fn loop_(&mut self, ty: BlockType) { + self.inst_imm8(0, false, LOOP, ty.as_byte()); + } + pub fn if_(&mut self, ty: BlockType) { + self.inst_imm8(1, false, IF, ty.as_byte()); + } + instruction_no_args!(else_, 0, false, ELSE); + instruction_no_args!(end, 0, false, END); + pub fn br(&mut self, levels: u32) { + self.inst_imm32(0, false, BR, levels); + } + pub fn br_if(&mut self, levels: u32) { + self.inst_imm32(1, false, BRIF, levels); + } // br_table: not implemented - instruction_method!(return_, 0, false, RETURN); + instruction_no_args!(return_, 0, false, RETURN); // call: see above // call_indirect: not implemented - instruction_method!(drop, 1, false, DROP); - instruction_method!(select, 3, true, SELECT); - // instruction_method!(get_local(&mut self, id: LocalId) { self.inst_imm32(0, true, GETLOCAL, id.0); - // instruction_method!(set_local(&mut self, id: LocalId) { self.inst_imm32(1, false, SETLOCAL, id.0); - // instruction_method!(tee_local(&mut self, id: LocalId) { self.inst_imm32(1, true, TEELOCAL, id.0); - // instruction_method!(get_global(&mut self, id: u32) { self.inst_imm32(0, true, GETGLOBAL, id); - // instruction_method!(set_global(&mut self, id: u32) { self.inst_imm32(1, false, SETGLOBAL, id); - instruction_method_mem!(i32_load, 1, true, I32LOAD); - instruction_method_mem!(i64_load, 1, true, I64LOAD); - instruction_method_mem!(f32_load, 1, true, F32LOAD); - instruction_method_mem!(f64_load, 1, true, F64LOAD); - instruction_method_mem!(i32_load8_s, 1, true, I32LOAD8S); - instruction_method_mem!(i32_load8_u, 1, true, I32LOAD8U); - instruction_method_mem!(i32_load16_s, 1, true, I32LOAD16S); - instruction_method_mem!(i32_load16_u, 1, true, I32LOAD16U); - instruction_method_mem!(i64_load8_s, 1, true, I64LOAD8S); - instruction_method_mem!(i64_load8_u, 1, true, I64LOAD8U); - instruction_method_mem!(i64_load16_s, 1, true, I64LOAD16S); - instruction_method_mem!(i64_load16_u, 1, true, I64LOAD16U); - instruction_method_mem!(i64_load32_s, 1, true, I64LOAD32S); - instruction_method_mem!(i64_load32_u, 1, true, I64LOAD32U); - instruction_method_mem!(i32_store, 2, false, I32STORE); - instruction_method_mem!(i64_store, 2, false, I64STORE); - instruction_method_mem!(f32_store, 2, false, F32STORE); - instruction_method_mem!(f64_store, 2, false, F64STORE); - instruction_method_mem!(i32_store8, 2, false, I32STORE8); - instruction_method_mem!(i32_store16, 2, false, I32STORE16); - instruction_method_mem!(i64_store8, 2, false, I64STORE8); - instruction_method_mem!(i64_store16, 2, false, I64STORE16); - instruction_method_mem!(i64_store32, 2, false, I64STORE32); - // instruction_method!(memory_size(&mut self) { self.inst_imm8(0, true, CURRENTMEMORY, 0); - // instruction_method!(memory_grow(&mut self) { self.inst_imm8(1, true, GROWMEMORY, 0); - // instruction_method!(i32_const(&mut self, x: i32) { self.inst_imm32(0, true, I32CONST, x as u32); - // instruction_method!(i64_const(&mut self, x: i64) { self.inst_imm64(0, true, I64CONST, x as u64); - // instruction_method!(f32_const(&mut self, x: f32) { self.inst_imm32(0, true, F32CONST, x.to_bits()); - // instruction_method!(f64_const(&mut self, x: f64) { self.inst_imm64(0, true, F64CONST, x.to_bits()); - instruction_method!(i32_eqz, 1, true, I32EQZ); - instruction_method!(i32_eq, 2, true, I32EQ); - instruction_method!(i32_ne, 2, true, I32NE); - instruction_method!(i32_lt_s, 2, true, I32LTS); - instruction_method!(i32_lt_u, 2, true, I32LTU); - instruction_method!(i32_gt_s, 2, true, I32GTS); - instruction_method!(i32_gt_u, 2, true, I32GTU); - instruction_method!(i32_le_s, 2, true, I32LES); - instruction_method!(i32_le_u, 2, true, I32LEU); - instruction_method!(i32_ge_s, 2, true, I32GES); - instruction_method!(i32_ge_u, 2, true, I32GEU); - instruction_method!(i64_eqz, 1, true, I64EQZ); - instruction_method!(i64_eq, 2, true, I64EQ); - instruction_method!(i64_ne, 2, true, I64NE); - instruction_method!(i64_lt_s, 2, true, I64LTS); - instruction_method!(i64_lt_u, 2, true, I64LTU); - instruction_method!(i64_gt_s, 2, true, I64GTS); - instruction_method!(i64_gt_u, 2, true, I64GTU); - instruction_method!(i64_le_s, 2, true, I64LES); - instruction_method!(i64_le_u, 2, true, I64LEU); - instruction_method!(i64_ge_s, 2, true, I64GES); - instruction_method!(i64_ge_u, 2, true, I64GEU); - instruction_method!(f32_eq, 2, true, F32EQ); - instruction_method!(f32_ne, 2, true, F32NE); - instruction_method!(f32_lt, 2, true, F32LT); - instruction_method!(f32_gt, 2, true, F32GT); - instruction_method!(f32_le, 2, true, F32LE); - instruction_method!(f32_ge, 2, true, F32GE); - instruction_method!(f64_eq, 2, true, F64EQ); - instruction_method!(f64_ne, 2, true, F64NE); - instruction_method!(f64_lt, 2, true, F64LT); - instruction_method!(f64_gt, 2, true, F64GT); - instruction_method!(f64_le, 2, true, F64LE); - instruction_method!(f64_ge, 2, true, F64GE); - instruction_method!(i32_clz, 1, true, I32CLZ); - instruction_method!(i32_ctz, 1, true, I32CTZ); - instruction_method!(i32_popcnt, 1, true, I32POPCNT); - instruction_method!(i32_add, 2, true, I32ADD); - instruction_method!(i32_sub, 2, true, I32SUB); - instruction_method!(i32_mul, 2, true, I32MUL); - instruction_method!(i32_div_s, 2, true, I32DIVS); - instruction_method!(i32_div_u, 2, true, I32DIVU); - instruction_method!(i32_rem_s, 2, true, I32REMS); - instruction_method!(i32_rem_u, 2, true, I32REMU); - instruction_method!(i32_and, 2, true, I32AND); - instruction_method!(i32_or, 2, true, I32OR); - instruction_method!(i32_xor, 2, true, I32XOR); - instruction_method!(i32_shl, 2, true, I32SHL); - instruction_method!(i32_shr_s, 2, true, I32SHRS); - instruction_method!(i32_shr_u, 2, true, I32SHRU); - instruction_method!(i32_rotl, 2, true, I32ROTL); - instruction_method!(i32_rotr, 2, true, I32ROTR); - instruction_method!(i64_clz, 1, true, I64CLZ); - instruction_method!(i64_ctz, 1, true, I64CTZ); - instruction_method!(i64_popcnt, 1, true, I64POPCNT); - instruction_method!(i64_add, 2, true, I64ADD); - instruction_method!(i64_sub, 2, true, I64SUB); - instruction_method!(i64_mul, 2, true, I64MUL); - instruction_method!(i64_div_s, 2, true, I64DIVS); - instruction_method!(i64_div_u, 2, true, I64DIVU); - instruction_method!(i64_rem_s, 2, true, I64REMS); - instruction_method!(i64_rem_u, 2, true, I64REMU); - instruction_method!(i64_and, 2, true, I64AND); - instruction_method!(i64_or, 2, true, I64OR); - instruction_method!(i64_xor, 2, true, I64XOR); - instruction_method!(i64_shl, 2, true, I64SHL); - instruction_method!(i64_shr_s, 2, true, I64SHRS); - instruction_method!(i64_shr_u, 2, true, I64SHRU); - instruction_method!(i64_rotl, 2, true, I64ROTL); - instruction_method!(i64_rotr, 2, true, I64ROTR); - instruction_method!(f32_abs, 1, true, F32ABS); - instruction_method!(f32_neg, 1, true, F32NEG); - instruction_method!(f32_ceil, 1, true, F32CEIL); - instruction_method!(f32_floor, 1, true, F32FLOOR); - instruction_method!(f32_trunc, 1, true, F32TRUNC); - instruction_method!(f32_nearest, 1, true, F32NEAREST); - instruction_method!(f32_sqrt, 1, true, F32SQRT); - instruction_method!(f32_add, 2, true, F32ADD); - instruction_method!(f32_sub, 2, true, F32SUB); - instruction_method!(f32_mul, 2, true, F32MUL); - instruction_method!(f32_div, 2, true, F32DIV); - instruction_method!(f32_min, 2, true, F32MIN); - instruction_method!(f32_max, 2, true, F32MAX); - instruction_method!(f32_copysign, 2, true, F32COPYSIGN); - instruction_method!(f64_abs, 1, true, F64ABS); - instruction_method!(f64_neg, 1, true, F64NEG); - instruction_method!(f64_ceil, 1, true, F64CEIL); - instruction_method!(f64_floor, 1, true, F64FLOOR); - instruction_method!(f64_trunc, 1, true, F64TRUNC); - instruction_method!(f64_nearest, 1, true, F64NEAREST); - instruction_method!(f64_sqrt, 1, true, F64SQRT); - instruction_method!(f64_add, 2, true, F64ADD); - instruction_method!(f64_sub, 2, true, F64SUB); - instruction_method!(f64_mul, 2, true, F64MUL); - instruction_method!(f64_div, 2, true, F64DIV); - instruction_method!(f64_min, 2, true, F64MIN); - instruction_method!(f64_max, 2, true, F64MAX); - instruction_method!(f64_copysign, 2, true, F64COPYSIGN); - instruction_method!(i32_wrap_i64, 1, true, I32WRAPI64); - instruction_method!(i32_trunc_s_f32, 1, true, I32TRUNCSF32); - instruction_method!(i32_trunc_u_f32, 1, true, I32TRUNCUF32); - instruction_method!(i32_trunc_s_f64, 1, true, I32TRUNCSF64); - instruction_method!(i32_trunc_u_f64, 1, true, I32TRUNCUF64); - instruction_method!(i64_extend_s_i32, 1, true, I64EXTENDSI32); - instruction_method!(i64_extend_u_i32, 1, true, I64EXTENDUI32); - instruction_method!(i64_trunc_s_f32, 1, true, I64TRUNCSF32); - instruction_method!(i64_trunc_u_f32, 1, true, I64TRUNCUF32); - instruction_method!(i64_trunc_s_f64, 1, true, I64TRUNCSF64); - instruction_method!(i64_trunc_u_f64, 1, true, I64TRUNCUF64); - instruction_method!(f32_convert_s_i32, 1, true, F32CONVERTSI32); - instruction_method!(f32_convert_u_i32, 1, true, F32CONVERTUI32); - instruction_method!(f32_convert_s_i64, 1, true, F32CONVERTSI64); - instruction_method!(f32_convert_u_i64, 1, true, F32CONVERTUI64); - instruction_method!(f32_demote_f64, 1, true, F32DEMOTEF64); - instruction_method!(f64_convert_s_i32, 1, true, F64CONVERTSI32); - instruction_method!(f64_convert_u_i32, 1, true, F64CONVERTUI32); - instruction_method!(f64_convert_s_i64, 1, true, F64CONVERTSI64); - instruction_method!(f64_convert_u_i64, 1, true, F64CONVERTUI64); - instruction_method!(f64_promote_f32, 1, true, F64PROMOTEF32); - instruction_method!(i32_reinterpret_f32, 1, true, I32REINTERPRETF32); - instruction_method!(i64_reinterpret_f64, 1, true, I64REINTERPRETF64); - instruction_method!(f32_reinterpret_i32, 1, true, F32REINTERPRETI32); - instruction_method!(f64_reinterpret_i64, 1, true, F64REINTERPRETI64); + instruction_no_args!(drop, 1, false, DROP); + instruction_no_args!(select, 3, true, SELECT); + pub fn get_local(&mut self, id: LocalId) { + self.inst_imm32(0, true, GETLOCAL, id.0); + } + pub fn set_local(&mut self, id: LocalId) { + self.inst_imm32(1, false, SETLOCAL, id.0); + } + pub fn tee_local(&mut self, id: LocalId) { + self.inst_imm32(1, true, TEELOCAL, id.0); + } + pub fn get_global(&mut self, id: u32) { + self.inst_imm32(0, true, GETGLOBAL, id); + } + pub fn set_global(&mut self, id: u32) { + self.inst_imm32(1, false, SETGLOBAL, id); + } + instruction_memargs!(i32_load, 1, true, I32LOAD); + instruction_memargs!(i64_load, 1, true, I64LOAD); + instruction_memargs!(f32_load, 1, true, F32LOAD); + instruction_memargs!(f64_load, 1, true, F64LOAD); + instruction_memargs!(i32_load8_s, 1, true, I32LOAD8S); + instruction_memargs!(i32_load8_u, 1, true, I32LOAD8U); + instruction_memargs!(i32_load16_s, 1, true, I32LOAD16S); + instruction_memargs!(i32_load16_u, 1, true, I32LOAD16U); + instruction_memargs!(i64_load8_s, 1, true, I64LOAD8S); + instruction_memargs!(i64_load8_u, 1, true, I64LOAD8U); + instruction_memargs!(i64_load16_s, 1, true, I64LOAD16S); + instruction_memargs!(i64_load16_u, 1, true, I64LOAD16U); + instruction_memargs!(i64_load32_s, 1, true, I64LOAD32S); + instruction_memargs!(i64_load32_u, 1, true, I64LOAD32U); + instruction_memargs!(i32_store, 2, false, I32STORE); + instruction_memargs!(i64_store, 2, false, I64STORE); + instruction_memargs!(f32_store, 2, false, F32STORE); + instruction_memargs!(f64_store, 2, false, F64STORE); + instruction_memargs!(i32_store8, 2, false, I32STORE8); + instruction_memargs!(i32_store16, 2, false, I32STORE16); + instruction_memargs!(i64_store8, 2, false, I64STORE8); + instruction_memargs!(i64_store16, 2, false, I64STORE16); + instruction_memargs!(i64_store32, 2, false, I64STORE32); + pub fn memory_size(&mut self) { + self.inst_imm8(0, true, CURRENTMEMORY, 0); + } + pub fn memory_grow(&mut self) { + self.inst_imm8(1, true, GROWMEMORY, 0); + } + pub fn i32_const(&mut self, x: i32) { + self.inst_imm32(0, true, I32CONST, x as u32); + } + pub fn i64_const(&mut self, x: i64) { + self.inst(0, true, I64CONST); + let mut value = x as u64; + while value >= 0x80 { + self.code.push(0x80 | ((value & 0x7f) as u8)); + value >>= 7; + } + self.code.push(value as u8); + } + pub fn f32_const(&mut self, x: f32) { + self.inst(0, true, F32CONST); + // No LEB encoding, and always little-endian regardless of compiler host. + let mut value: u32 = x.to_bits(); + for _ in 0..4 { + self.code.push((value & 0xff) as u8); + value >>= 8; + } + } + pub fn f64_const(&mut self, x: f64) { + self.inst(0, true, F64CONST); + // No LEB encoding, and always little-endian regardless of compiler host. + let mut value: u64 = x.to_bits(); + for _ in 0..8 { + self.code.push((value & 0xff) as u8); + value >>= 8; + } + } + instruction_no_args!(i32_eqz, 1, true, I32EQZ); + instruction_no_args!(i32_eq, 2, true, I32EQ); + instruction_no_args!(i32_ne, 2, true, I32NE); + instruction_no_args!(i32_lt_s, 2, true, I32LTS); + instruction_no_args!(i32_lt_u, 2, true, I32LTU); + instruction_no_args!(i32_gt_s, 2, true, I32GTS); + instruction_no_args!(i32_gt_u, 2, true, I32GTU); + instruction_no_args!(i32_le_s, 2, true, I32LES); + instruction_no_args!(i32_le_u, 2, true, I32LEU); + instruction_no_args!(i32_ge_s, 2, true, I32GES); + instruction_no_args!(i32_ge_u, 2, true, I32GEU); + instruction_no_args!(i64_eqz, 1, true, I64EQZ); + instruction_no_args!(i64_eq, 2, true, I64EQ); + instruction_no_args!(i64_ne, 2, true, I64NE); + instruction_no_args!(i64_lt_s, 2, true, I64LTS); + instruction_no_args!(i64_lt_u, 2, true, I64LTU); + instruction_no_args!(i64_gt_s, 2, true, I64GTS); + instruction_no_args!(i64_gt_u, 2, true, I64GTU); + instruction_no_args!(i64_le_s, 2, true, I64LES); + instruction_no_args!(i64_le_u, 2, true, I64LEU); + instruction_no_args!(i64_ge_s, 2, true, I64GES); + instruction_no_args!(i64_ge_u, 2, true, I64GEU); + instruction_no_args!(f32_eq, 2, true, F32EQ); + instruction_no_args!(f32_ne, 2, true, F32NE); + instruction_no_args!(f32_lt, 2, true, F32LT); + instruction_no_args!(f32_gt, 2, true, F32GT); + instruction_no_args!(f32_le, 2, true, F32LE); + instruction_no_args!(f32_ge, 2, true, F32GE); + instruction_no_args!(f64_eq, 2, true, F64EQ); + instruction_no_args!(f64_ne, 2, true, F64NE); + instruction_no_args!(f64_lt, 2, true, F64LT); + instruction_no_args!(f64_gt, 2, true, F64GT); + instruction_no_args!(f64_le, 2, true, F64LE); + instruction_no_args!(f64_ge, 2, true, F64GE); + instruction_no_args!(i32_clz, 1, true, I32CLZ); + instruction_no_args!(i32_ctz, 1, true, I32CTZ); + instruction_no_args!(i32_popcnt, 1, true, I32POPCNT); + instruction_no_args!(i32_add, 2, true, I32ADD); + instruction_no_args!(i32_sub, 2, true, I32SUB); + instruction_no_args!(i32_mul, 2, true, I32MUL); + instruction_no_args!(i32_div_s, 2, true, I32DIVS); + instruction_no_args!(i32_div_u, 2, true, I32DIVU); + instruction_no_args!(i32_rem_s, 2, true, I32REMS); + instruction_no_args!(i32_rem_u, 2, true, I32REMU); + instruction_no_args!(i32_and, 2, true, I32AND); + instruction_no_args!(i32_or, 2, true, I32OR); + instruction_no_args!(i32_xor, 2, true, I32XOR); + instruction_no_args!(i32_shl, 2, true, I32SHL); + instruction_no_args!(i32_shr_s, 2, true, I32SHRS); + instruction_no_args!(i32_shr_u, 2, true, I32SHRU); + instruction_no_args!(i32_rotl, 2, true, I32ROTL); + instruction_no_args!(i32_rotr, 2, true, I32ROTR); + instruction_no_args!(i64_clz, 1, true, I64CLZ); + instruction_no_args!(i64_ctz, 1, true, I64CTZ); + instruction_no_args!(i64_popcnt, 1, true, I64POPCNT); + instruction_no_args!(i64_add, 2, true, I64ADD); + instruction_no_args!(i64_sub, 2, true, I64SUB); + instruction_no_args!(i64_mul, 2, true, I64MUL); + instruction_no_args!(i64_div_s, 2, true, I64DIVS); + instruction_no_args!(i64_div_u, 2, true, I64DIVU); + instruction_no_args!(i64_rem_s, 2, true, I64REMS); + instruction_no_args!(i64_rem_u, 2, true, I64REMU); + instruction_no_args!(i64_and, 2, true, I64AND); + instruction_no_args!(i64_or, 2, true, I64OR); + instruction_no_args!(i64_xor, 2, true, I64XOR); + instruction_no_args!(i64_shl, 2, true, I64SHL); + instruction_no_args!(i64_shr_s, 2, true, I64SHRS); + instruction_no_args!(i64_shr_u, 2, true, I64SHRU); + instruction_no_args!(i64_rotl, 2, true, I64ROTL); + instruction_no_args!(i64_rotr, 2, true, I64ROTR); + instruction_no_args!(f32_abs, 1, true, F32ABS); + instruction_no_args!(f32_neg, 1, true, F32NEG); + instruction_no_args!(f32_ceil, 1, true, F32CEIL); + instruction_no_args!(f32_floor, 1, true, F32FLOOR); + instruction_no_args!(f32_trunc, 1, true, F32TRUNC); + instruction_no_args!(f32_nearest, 1, true, F32NEAREST); + instruction_no_args!(f32_sqrt, 1, true, F32SQRT); + instruction_no_args!(f32_add, 2, true, F32ADD); + instruction_no_args!(f32_sub, 2, true, F32SUB); + instruction_no_args!(f32_mul, 2, true, F32MUL); + instruction_no_args!(f32_div, 2, true, F32DIV); + instruction_no_args!(f32_min, 2, true, F32MIN); + instruction_no_args!(f32_max, 2, true, F32MAX); + instruction_no_args!(f32_copysign, 2, true, F32COPYSIGN); + instruction_no_args!(f64_abs, 1, true, F64ABS); + instruction_no_args!(f64_neg, 1, true, F64NEG); + instruction_no_args!(f64_ceil, 1, true, F64CEIL); + instruction_no_args!(f64_floor, 1, true, F64FLOOR); + instruction_no_args!(f64_trunc, 1, true, F64TRUNC); + instruction_no_args!(f64_nearest, 1, true, F64NEAREST); + instruction_no_args!(f64_sqrt, 1, true, F64SQRT); + instruction_no_args!(f64_add, 2, true, F64ADD); + instruction_no_args!(f64_sub, 2, true, F64SUB); + instruction_no_args!(f64_mul, 2, true, F64MUL); + instruction_no_args!(f64_div, 2, true, F64DIV); + instruction_no_args!(f64_min, 2, true, F64MIN); + instruction_no_args!(f64_max, 2, true, F64MAX); + instruction_no_args!(f64_copysign, 2, true, F64COPYSIGN); + instruction_no_args!(i32_wrap_i64, 1, true, I32WRAPI64); + instruction_no_args!(i32_trunc_s_f32, 1, true, I32TRUNCSF32); + instruction_no_args!(i32_trunc_u_f32, 1, true, I32TRUNCUF32); + instruction_no_args!(i32_trunc_s_f64, 1, true, I32TRUNCSF64); + instruction_no_args!(i32_trunc_u_f64, 1, true, I32TRUNCUF64); + instruction_no_args!(i64_extend_s_i32, 1, true, I64EXTENDSI32); + instruction_no_args!(i64_extend_u_i32, 1, true, I64EXTENDUI32); + instruction_no_args!(i64_trunc_s_f32, 1, true, I64TRUNCSF32); + instruction_no_args!(i64_trunc_u_f32, 1, true, I64TRUNCUF32); + instruction_no_args!(i64_trunc_s_f64, 1, true, I64TRUNCSF64); + instruction_no_args!(i64_trunc_u_f64, 1, true, I64TRUNCUF64); + instruction_no_args!(f32_convert_s_i32, 1, true, F32CONVERTSI32); + instruction_no_args!(f32_convert_u_i32, 1, true, F32CONVERTUI32); + instruction_no_args!(f32_convert_s_i64, 1, true, F32CONVERTSI64); + instruction_no_args!(f32_convert_u_i64, 1, true, F32CONVERTUI64); + instruction_no_args!(f32_demote_f64, 1, true, F32DEMOTEF64); + instruction_no_args!(f64_convert_s_i32, 1, true, F64CONVERTSI32); + instruction_no_args!(f64_convert_u_i32, 1, true, F64CONVERTUI32); + instruction_no_args!(f64_convert_s_i64, 1, true, F64CONVERTSI64); + instruction_no_args!(f64_convert_u_i64, 1, true, F64CONVERTUI64); + instruction_no_args!(f64_promote_f32, 1, true, F64PROMOTEF32); + instruction_no_args!(i32_reinterpret_f32, 1, true, I32REINTERPRETF32); + instruction_no_args!(i64_reinterpret_f64, 1, true, I64REINTERPRETF64); + instruction_no_args!(f32_reinterpret_i32, 1, true, F32REINTERPRETI32); + instruction_no_args!(f64_reinterpret_i64, 1, true, F64REINTERPRETI64); } From 0586f91b04817afd01b92e47f84c1951a4f7d3eb Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 22 Oct 2021 13:17:18 +0200 Subject: [PATCH 07/28] Reorder arguments of instruction methods Having the opcode first makes it easier to see if it matches the method name --- compiler/gen_wasm/src/function_builder.rs | 362 +++++++++++----------- 1 file changed, 182 insertions(+), 180 deletions(-) diff --git a/compiler/gen_wasm/src/function_builder.rs b/compiler/gen_wasm/src/function_builder.rs index 9f6cbacff4..183f384106 100644 --- a/compiler/gen_wasm/src/function_builder.rs +++ b/compiler/gen_wasm/src/function_builder.rs @@ -48,17 +48,17 @@ pub enum Align { } macro_rules! instruction_no_args { - ($method_name: ident, $pops: expr, $push: expr, $opcode: expr) => { + ($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => { pub fn $method_name(&mut self) { - self.inst($pops, $push, $opcode); + self.inst($opcode, $pops, $push); } }; } macro_rules! instruction_memargs { - ($method_name: ident, $pops: expr, $push: expr, $opcode: expr) => { + ($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => { pub fn $method_name(&mut self, align: Align, offset: u32) { - self.inst_mem($pops, $push, $opcode, align, offset); + self.inst_mem($opcode, $pops, $push, align, offset); } }; } @@ -228,7 +228,7 @@ impl<'a> FunctionBuilder<'a> { /// Base method for generating instructions /// Emits the opcode and simulates VM stack push/pop - fn inst(&mut self, pops: usize, push: bool, opcode: u8) { + 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 { @@ -237,13 +237,13 @@ impl<'a> FunctionBuilder<'a> { self.code.push(opcode); } - fn inst_imm8(&mut self, pops: usize, push: bool, opcode: u8, immediate: u8) { - self.inst(pops, 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, pops: usize, push: bool, opcode: u8, immediate: u32) { - self.inst(pops, push, opcode); + fn inst_imm32(&mut self, opcode: u8, pops: usize, push: bool, immediate: u32) { + self.inst(opcode, pops, push); let mut value = immediate; while value >= 0x80 { self.code.push(0x80 | ((value & 0x7f) as u8)); @@ -252,8 +252,8 @@ impl<'a> FunctionBuilder<'a> { self.code.push(value as u8); } - fn inst_mem(&mut self, pops: usize, push: bool, opcode: u8, align: Align, offset: u32) { - self.inst(pops, push, opcode); + 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); let mut value = offset; while value >= 0x80 { @@ -271,80 +271,80 @@ impl<'a> FunctionBuilder<'a> { // instruction_no_args! creates a method that takes no arguments // instruction_memargs! creates a method that takes alignment and offset arguments - instruction_no_args!(unreachable_, 0, false, UNREACHABLE); - instruction_no_args!(nop, 0, false, NOP); + instruction_no_args!(unreachable_, UNREACHABLE, 0, false); + instruction_no_args!(nop, NOP, 0, false); pub fn block(&mut self, ty: BlockType) { - self.inst_imm8(0, false, BLOCK, ty.as_byte()); + self.inst_imm8(BLOCK, 0, false, ty.as_byte()); } pub fn loop_(&mut self, ty: BlockType) { - self.inst_imm8(0, false, LOOP, ty.as_byte()); + self.inst_imm8(LOOP, 0, false, ty.as_byte()); } pub fn if_(&mut self, ty: BlockType) { - self.inst_imm8(1, false, IF, ty.as_byte()); + self.inst_imm8(IF, 1, false, ty.as_byte()); } - instruction_no_args!(else_, 0, false, ELSE); - instruction_no_args!(end, 0, false, END); + instruction_no_args!(else_, ELSE, 0, false); + instruction_no_args!(end, END, 0, false); pub fn br(&mut self, levels: u32) { - self.inst_imm32(0, false, BR, levels); + self.inst_imm32(BR, 0, false, levels); } pub fn br_if(&mut self, levels: u32) { - self.inst_imm32(1, false, BRIF, levels); + self.inst_imm32(BRIF, 1, false, levels); } // br_table: not implemented - instruction_no_args!(return_, 0, false, RETURN); + instruction_no_args!(return_, RETURN, 0, false); // call: see above // call_indirect: not implemented - instruction_no_args!(drop, 1, false, DROP); - instruction_no_args!(select, 3, true, SELECT); + 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(0, true, GETLOCAL, id.0); + self.inst_imm32(GETLOCAL, 0, true, id.0); } pub fn set_local(&mut self, id: LocalId) { - self.inst_imm32(1, false, SETLOCAL, id.0); + self.inst_imm32(SETLOCAL, 1, false, id.0); } pub fn tee_local(&mut self, id: LocalId) { - self.inst_imm32(1, true, TEELOCAL, id.0); + self.inst_imm32(TEELOCAL, 1, true, id.0); } pub fn get_global(&mut self, id: u32) { - self.inst_imm32(0, true, GETGLOBAL, id); + self.inst_imm32(GETGLOBAL, 0, true, id); } pub fn set_global(&mut self, id: u32) { - self.inst_imm32(1, false, SETGLOBAL, id); + self.inst_imm32(SETGLOBAL, 1, false, id); } - instruction_memargs!(i32_load, 1, true, I32LOAD); - instruction_memargs!(i64_load, 1, true, I64LOAD); - instruction_memargs!(f32_load, 1, true, F32LOAD); - instruction_memargs!(f64_load, 1, true, F64LOAD); - instruction_memargs!(i32_load8_s, 1, true, I32LOAD8S); - instruction_memargs!(i32_load8_u, 1, true, I32LOAD8U); - instruction_memargs!(i32_load16_s, 1, true, I32LOAD16S); - instruction_memargs!(i32_load16_u, 1, true, I32LOAD16U); - instruction_memargs!(i64_load8_s, 1, true, I64LOAD8S); - instruction_memargs!(i64_load8_u, 1, true, I64LOAD8U); - instruction_memargs!(i64_load16_s, 1, true, I64LOAD16S); - instruction_memargs!(i64_load16_u, 1, true, I64LOAD16U); - instruction_memargs!(i64_load32_s, 1, true, I64LOAD32S); - instruction_memargs!(i64_load32_u, 1, true, I64LOAD32U); - instruction_memargs!(i32_store, 2, false, I32STORE); - instruction_memargs!(i64_store, 2, false, I64STORE); - instruction_memargs!(f32_store, 2, false, F32STORE); - instruction_memargs!(f64_store, 2, false, F64STORE); - instruction_memargs!(i32_store8, 2, false, I32STORE8); - instruction_memargs!(i32_store16, 2, false, I32STORE16); - instruction_memargs!(i64_store8, 2, false, I64STORE8); - instruction_memargs!(i64_store16, 2, false, I64STORE16); - instruction_memargs!(i64_store32, 2, false, I64STORE32); + 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(0, true, CURRENTMEMORY, 0); + self.inst_imm8(CURRENTMEMORY, 0, true, 0); } pub fn memory_grow(&mut self) { - self.inst_imm8(1, true, GROWMEMORY, 0); + self.inst_imm8(GROWMEMORY, 1, true, 0); } pub fn i32_const(&mut self, x: i32) { - self.inst_imm32(0, true, I32CONST, x as u32); + self.inst_imm32(I32CONST, 0, true, x as u32); } pub fn i64_const(&mut self, x: i64) { - self.inst(0, true, I64CONST); + self.inst(I64CONST, 0, true); let mut value = x as u64; while value >= 0x80 { self.code.push(0x80 | ((value & 0x7f) as u8)); @@ -353,7 +353,7 @@ impl<'a> FunctionBuilder<'a> { self.code.push(value as u8); } pub fn f32_const(&mut self, x: f32) { - self.inst(0, true, F32CONST); + self.inst(F32CONST, 0, true); // No LEB encoding, and always little-endian regardless of compiler host. let mut value: u32 = x.to_bits(); for _ in 0..4 { @@ -362,7 +362,7 @@ impl<'a> FunctionBuilder<'a> { } } pub fn f64_const(&mut self, x: f64) { - self.inst(0, true, F64CONST); + self.inst(F64CONST, 0, true); // No LEB encoding, and always little-endian regardless of compiler host. let mut value: u64 = x.to_bits(); for _ in 0..8 { @@ -370,127 +370,129 @@ impl<'a> FunctionBuilder<'a> { value >>= 8; } } - instruction_no_args!(i32_eqz, 1, true, I32EQZ); - instruction_no_args!(i32_eq, 2, true, I32EQ); - instruction_no_args!(i32_ne, 2, true, I32NE); - instruction_no_args!(i32_lt_s, 2, true, I32LTS); - instruction_no_args!(i32_lt_u, 2, true, I32LTU); - instruction_no_args!(i32_gt_s, 2, true, I32GTS); - instruction_no_args!(i32_gt_u, 2, true, I32GTU); - instruction_no_args!(i32_le_s, 2, true, I32LES); - instruction_no_args!(i32_le_u, 2, true, I32LEU); - instruction_no_args!(i32_ge_s, 2, true, I32GES); - instruction_no_args!(i32_ge_u, 2, true, I32GEU); - instruction_no_args!(i64_eqz, 1, true, I64EQZ); - instruction_no_args!(i64_eq, 2, true, I64EQ); - instruction_no_args!(i64_ne, 2, true, I64NE); - instruction_no_args!(i64_lt_s, 2, true, I64LTS); - instruction_no_args!(i64_lt_u, 2, true, I64LTU); - instruction_no_args!(i64_gt_s, 2, true, I64GTS); - instruction_no_args!(i64_gt_u, 2, true, I64GTU); - instruction_no_args!(i64_le_s, 2, true, I64LES); - instruction_no_args!(i64_le_u, 2, true, I64LEU); - instruction_no_args!(i64_ge_s, 2, true, I64GES); - instruction_no_args!(i64_ge_u, 2, true, I64GEU); - instruction_no_args!(f32_eq, 2, true, F32EQ); - instruction_no_args!(f32_ne, 2, true, F32NE); - instruction_no_args!(f32_lt, 2, true, F32LT); - instruction_no_args!(f32_gt, 2, true, F32GT); - instruction_no_args!(f32_le, 2, true, F32LE); - instruction_no_args!(f32_ge, 2, true, F32GE); - instruction_no_args!(f64_eq, 2, true, F64EQ); - instruction_no_args!(f64_ne, 2, true, F64NE); - instruction_no_args!(f64_lt, 2, true, F64LT); - instruction_no_args!(f64_gt, 2, true, F64GT); - instruction_no_args!(f64_le, 2, true, F64LE); - instruction_no_args!(f64_ge, 2, true, F64GE); - instruction_no_args!(i32_clz, 1, true, I32CLZ); - instruction_no_args!(i32_ctz, 1, true, I32CTZ); - instruction_no_args!(i32_popcnt, 1, true, I32POPCNT); - instruction_no_args!(i32_add, 2, true, I32ADD); - instruction_no_args!(i32_sub, 2, true, I32SUB); - instruction_no_args!(i32_mul, 2, true, I32MUL); - instruction_no_args!(i32_div_s, 2, true, I32DIVS); - instruction_no_args!(i32_div_u, 2, true, I32DIVU); - instruction_no_args!(i32_rem_s, 2, true, I32REMS); - instruction_no_args!(i32_rem_u, 2, true, I32REMU); - instruction_no_args!(i32_and, 2, true, I32AND); - instruction_no_args!(i32_or, 2, true, I32OR); - instruction_no_args!(i32_xor, 2, true, I32XOR); - instruction_no_args!(i32_shl, 2, true, I32SHL); - instruction_no_args!(i32_shr_s, 2, true, I32SHRS); - instruction_no_args!(i32_shr_u, 2, true, I32SHRU); - instruction_no_args!(i32_rotl, 2, true, I32ROTL); - instruction_no_args!(i32_rotr, 2, true, I32ROTR); - instruction_no_args!(i64_clz, 1, true, I64CLZ); - instruction_no_args!(i64_ctz, 1, true, I64CTZ); - instruction_no_args!(i64_popcnt, 1, true, I64POPCNT); - instruction_no_args!(i64_add, 2, true, I64ADD); - instruction_no_args!(i64_sub, 2, true, I64SUB); - instruction_no_args!(i64_mul, 2, true, I64MUL); - instruction_no_args!(i64_div_s, 2, true, I64DIVS); - instruction_no_args!(i64_div_u, 2, true, I64DIVU); - instruction_no_args!(i64_rem_s, 2, true, I64REMS); - instruction_no_args!(i64_rem_u, 2, true, I64REMU); - instruction_no_args!(i64_and, 2, true, I64AND); - instruction_no_args!(i64_or, 2, true, I64OR); - instruction_no_args!(i64_xor, 2, true, I64XOR); - instruction_no_args!(i64_shl, 2, true, I64SHL); - instruction_no_args!(i64_shr_s, 2, true, I64SHRS); - instruction_no_args!(i64_shr_u, 2, true, I64SHRU); - instruction_no_args!(i64_rotl, 2, true, I64ROTL); - instruction_no_args!(i64_rotr, 2, true, I64ROTR); - instruction_no_args!(f32_abs, 1, true, F32ABS); - instruction_no_args!(f32_neg, 1, true, F32NEG); - instruction_no_args!(f32_ceil, 1, true, F32CEIL); - instruction_no_args!(f32_floor, 1, true, F32FLOOR); - instruction_no_args!(f32_trunc, 1, true, F32TRUNC); - instruction_no_args!(f32_nearest, 1, true, F32NEAREST); - instruction_no_args!(f32_sqrt, 1, true, F32SQRT); - instruction_no_args!(f32_add, 2, true, F32ADD); - instruction_no_args!(f32_sub, 2, true, F32SUB); - instruction_no_args!(f32_mul, 2, true, F32MUL); - instruction_no_args!(f32_div, 2, true, F32DIV); - instruction_no_args!(f32_min, 2, true, F32MIN); - instruction_no_args!(f32_max, 2, true, F32MAX); - instruction_no_args!(f32_copysign, 2, true, F32COPYSIGN); - instruction_no_args!(f64_abs, 1, true, F64ABS); - instruction_no_args!(f64_neg, 1, true, F64NEG); - instruction_no_args!(f64_ceil, 1, true, F64CEIL); - instruction_no_args!(f64_floor, 1, true, F64FLOOR); - instruction_no_args!(f64_trunc, 1, true, F64TRUNC); - instruction_no_args!(f64_nearest, 1, true, F64NEAREST); - instruction_no_args!(f64_sqrt, 1, true, F64SQRT); - instruction_no_args!(f64_add, 2, true, F64ADD); - instruction_no_args!(f64_sub, 2, true, F64SUB); - instruction_no_args!(f64_mul, 2, true, F64MUL); - instruction_no_args!(f64_div, 2, true, F64DIV); - instruction_no_args!(f64_min, 2, true, F64MIN); - instruction_no_args!(f64_max, 2, true, F64MAX); - instruction_no_args!(f64_copysign, 2, true, F64COPYSIGN); - instruction_no_args!(i32_wrap_i64, 1, true, I32WRAPI64); - instruction_no_args!(i32_trunc_s_f32, 1, true, I32TRUNCSF32); - instruction_no_args!(i32_trunc_u_f32, 1, true, I32TRUNCUF32); - instruction_no_args!(i32_trunc_s_f64, 1, true, I32TRUNCSF64); - instruction_no_args!(i32_trunc_u_f64, 1, true, I32TRUNCUF64); - instruction_no_args!(i64_extend_s_i32, 1, true, I64EXTENDSI32); - instruction_no_args!(i64_extend_u_i32, 1, true, I64EXTENDUI32); - instruction_no_args!(i64_trunc_s_f32, 1, true, I64TRUNCSF32); - instruction_no_args!(i64_trunc_u_f32, 1, true, I64TRUNCUF32); - instruction_no_args!(i64_trunc_s_f64, 1, true, I64TRUNCSF64); - instruction_no_args!(i64_trunc_u_f64, 1, true, I64TRUNCUF64); - instruction_no_args!(f32_convert_s_i32, 1, true, F32CONVERTSI32); - instruction_no_args!(f32_convert_u_i32, 1, true, F32CONVERTUI32); - instruction_no_args!(f32_convert_s_i64, 1, true, F32CONVERTSI64); - instruction_no_args!(f32_convert_u_i64, 1, true, F32CONVERTUI64); - instruction_no_args!(f32_demote_f64, 1, true, F32DEMOTEF64); - instruction_no_args!(f64_convert_s_i32, 1, true, F64CONVERTSI32); - instruction_no_args!(f64_convert_u_i32, 1, true, F64CONVERTUI32); - instruction_no_args!(f64_convert_s_i64, 1, true, F64CONVERTSI64); - instruction_no_args!(f64_convert_u_i64, 1, true, F64CONVERTUI64); - instruction_no_args!(f64_promote_f32, 1, true, F64PROMOTEF32); - instruction_no_args!(i32_reinterpret_f32, 1, true, I32REINTERPRETF32); - instruction_no_args!(i64_reinterpret_f64, 1, true, I64REINTERPRETF64); - instruction_no_args!(f32_reinterpret_i32, 1, true, F32REINTERPRETI32); - instruction_no_args!(f64_reinterpret_i64, 1, true, F64REINTERPRETI64); + // TODO: code gen might for "lowlevel" calls be simplified if the numerical op methods + // took a ValueType as an argument and picked the opcode. Unify all 'eq', all 'add', etc. + 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); } From dc1779d41dffaf7bcd4ac92ed85d7c79cd6726a8 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 22 Oct 2021 19:02:03 +0200 Subject: [PATCH 08/28] Finalisation and serialisation for byte-level function builder --- compiler/gen_wasm/src/function_builder.rs | 206 ++++++++++++---------- compiler/gen_wasm/src/lib.rs | 30 +++- 2 files changed, 137 insertions(+), 99 deletions(-) diff --git a/compiler/gen_wasm/src/function_builder.rs b/compiler/gen_wasm/src/function_builder.rs index 183f384106..6e66651c03 100644 --- a/compiler/gen_wasm/src/function_builder.rs +++ b/compiler/gen_wasm/src/function_builder.rs @@ -3,12 +3,14 @@ use bumpalo::Bump; use core::panic; use std::fmt::Debug; -use parity_wasm::elements::{Instruction, Instruction::*, Local, Serialize, VarUint32}; use roc_module::symbol::Symbol; use crate::code_builder::VirtualMachineSymbolState; use crate::opcodes::*; -use crate::{debug_panic, LocalId, STACK_POINTER_GLOBAL_ID}; +use crate::{ + encode_u32, encode_u64, round_up_to_alignment, LocalId, FRAME_ALIGNMENT_BYTES, + STACK_POINTER_GLOBAL_ID, +}; const DEBUG_LOG: bool = false; @@ -72,11 +74,15 @@ pub struct FunctionBuilder<'a> { /// (Go back and set locals when we realise we need them) insertions: Vec<'a, (usize, Vec<'a, u8>)>, - /// Instruction bytes for locals and stack frame setup code - locals_and_frame_setup: Vec<'a, u8>, + /// Bytes for locals and stack frame setup code + /// We can't write this until we've finished the main code, + /// but it goes before the main code in the final output. + preamble: Vec<'a, u8>, - /// Encoded bytes for the inner length of the function - /// This is the total size of locals + code, as encoded into the module + /// Encoded bytes for the inner length of the function, locals + code. + /// ("inner" because it doesn't include the bytes of the length itself!) + /// We can't write this until we've finished the code and preamble, + /// but it goes before them in the final output. inner_length: Vec<'a, u8>, /// Our simulation model of the Wasm stack machine @@ -90,7 +96,7 @@ impl<'a> FunctionBuilder<'a> { FunctionBuilder { code: Vec::with_capacity_in(1024, arena), insertions: Vec::with_capacity_in(1024, arena), - locals_and_frame_setup: Vec::with_capacity_in(32, arena), + preamble: Vec::with_capacity_in(32, arena), inner_length: Vec::with_capacity_in(5, arena), vm_stack: Vec::with_capacity_in(32, arena), } @@ -137,35 +143,12 @@ impl<'a> FunctionBuilder<'a> { true } - fn push_stack_frame(frame_size: i32, local_frame_pointer: LocalId) -> [Instruction; 5] { - return [ - GetGlobal(STACK_POINTER_GLOBAL_ID), - I32Const(frame_size), - I32Sub, - TeeLocal(local_frame_pointer.0), - SetGlobal(STACK_POINTER_GLOBAL_ID), - ]; - } - - fn pop_stack_frame(frame_size: i32, local_frame_pointer: LocalId) -> [Instruction; 4] { - return [ - GetLocal(local_frame_pointer.0), - I32Const(frame_size), - I32Add, - SetGlobal(STACK_POINTER_GLOBAL_ID), - ]; - } - - fn serialize_locals(writer: &mut W, local_types: &[parity_wasm::elements::ValueType]) - where - W: std::io::Write, - { + /// Generate bytes to declare a function's local variables + fn build_local_declarations(&mut self, local_types: &[ValueType]) { let num_locals = local_types.len(); - VarUint32::from(num_locals) - .serialize(writer) - .unwrap_or_else(debug_panic); + encode_u32(&mut self.preamble, num_locals as u32); - // write bytes for locals, in batches of the same ValueType + // write declarations in batches of the same ValueType if num_locals > 0 { let mut batch_type = local_types[0]; let mut batch_size = 0; @@ -173,57 +156,76 @@ impl<'a> FunctionBuilder<'a> { if *t == batch_type { batch_size += 1; } else { - let local = Local::new(batch_size, batch_type); - local.serialize(writer).unwrap_or_else(debug_panic); + encode_u32(&mut self.preamble, batch_size); + self.preamble.push(batch_type as u8); batch_type = *t; batch_size = 1; } } - let local = Local::new(batch_size, batch_type); - local.serialize(writer).unwrap_or_else(debug_panic); + encode_u32(&mut self.preamble, batch_size); + self.preamble.push(batch_type as u8); } } + /// 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_u32(&mut self.preamble, frame_size as u32); + 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 a function body - pub fn finalize(&mut self, _final_code: &mut std::vec::Vec) { - // sort insertions - // encode local declarations - // encode stack frame push - // encode stack frame pop - // calculate inner length - // encode inner length + /// Generate all the "extra" bytes: local declarations, stack frame push/pop code, and function length + /// After running this, we will know the final byte length of the function. All bytes will have been + /// _generated_, but not yet _serialized_, as they are still stored in multiple vectors. + pub fn finalize(&mut self, local_types: &[ValueType], frame_size: i32, frame_pointer: LocalId) { + self.insertions.sort_by_key(|pair| pair.0); + + self.build_local_declarations(local_types); + + if frame_size > 0 { + 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); + } + + let inner_length_val = self.preamble.len() + self.code.len() + self.insertions.len(); + encode_u32(&mut self.inner_length, inner_length_val as u32); } - pub fn serialize(&self, _writer: W) { - // write inner length - // write locals - // write stack frame push - // write code+insertions - // write stack frame pop + /// Write out the function bytes serially + pub fn serialize(&self, writer: &mut W) -> std::io::Result { + writer.write(&self.inner_length)?; + writer.write(&self.preamble)?; + let mut pos: usize = 0; + for (next_insert_pos, insert_bytes) in self.insertions.iter() { + writer.write(&self.code[pos..*next_insert_pos])?; + writer.write(insert_bytes)?; + pos = *next_insert_pos; + } + let code_len = self.code.len(); + writer.write(&self.code[pos..code_len]) } - /// Total bytes, including inner length - /// (to help calculate overall code section length) + /// Total bytes this function will occupy in the code section, including its own inner length pub fn outer_len(&self) -> usize { - self.code.len() - + self.insertions.len() - + self.locals_and_frame_setup.len() - + self.inner_length.len() - } - - pub fn call(&mut self, function_index: u32, pops: usize, push: bool) { - let stack_depth = self.vm_stack.len(); - if pops > stack_depth { - panic!( - "Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\n{:?}", - function_index, pops, stack_depth, self - ); - } - self.vm_stack.truncate(stack_depth - pops); - if push { - self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE); - } - self.code.push(CALL); + self.inner_length.len() + self.preamble.len() + self.code.len() + self.insertions.len() } /// Base method for generating instructions @@ -244,23 +246,13 @@ impl<'a> FunctionBuilder<'a> { fn inst_imm32(&mut self, opcode: u8, pops: usize, push: bool, immediate: u32) { self.inst(opcode, pops, push); - let mut value = immediate; - while value >= 0x80 { - self.code.push(0x80 | ((value & 0x7f) as u8)); - value >>= 7; - } - self.code.push(value as u8); + 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); - let mut value = offset; - while value >= 0x80 { - self.code.push(0x80 | ((value & 0x7f) as u8)); - value >>= 7; - } - self.code.push(value as u8); + encode_u32(&mut self.code, offset); } // Instruction methods @@ -273,6 +265,7 @@ impl<'a> FunctionBuilder<'a> { 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()); } @@ -282,20 +275,43 @@ impl<'a> FunctionBuilder<'a> { 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); } - // br_table: not implemented + fn br_table() { + panic!("TODO"); + } + instruction_no_args!(return_, RETURN, 0, false); - // call: see above - // call_indirect: not implemented + + 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); + } + 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); } @@ -303,7 +319,7 @@ impl<'a> FunctionBuilder<'a> { self.inst_imm32(SETLOCAL, 1, false, id.0); } pub fn tee_local(&mut self, id: LocalId) { - self.inst_imm32(TEELOCAL, 1, true, id.0); + self.inst_imm32(TEELOCAL, 0, false, id.0); } pub fn get_global(&mut self, id: u32) { self.inst_imm32(GETGLOBAL, 0, true, id); @@ -311,6 +327,7 @@ impl<'a> FunctionBuilder<'a> { 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); @@ -334,6 +351,7 @@ impl<'a> FunctionBuilder<'a> { 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); } @@ -345,12 +363,7 @@ impl<'a> FunctionBuilder<'a> { } pub fn i64_const(&mut self, x: i64) { self.inst(I64CONST, 0, true); - let mut value = x as u64; - while value >= 0x80 { - self.code.push(0x80 | ((value & 0x7f) as u8)); - value >>= 7; - } - self.code.push(value as u8); + encode_u64(&mut self.code, x as u64); } pub fn f32_const(&mut self, x: f32) { self.inst(F32CONST, 0, true); @@ -370,8 +383,9 @@ impl<'a> FunctionBuilder<'a> { value >>= 8; } } - // TODO: code gen might for "lowlevel" calls be simplified if the numerical op methods - // took a ValueType as an argument and picked the opcode. Unify all 'eq', all 'add', etc. + + // 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); diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 72b23d0565..aca629fb36 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -33,7 +33,7 @@ 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; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct LocalId(pub u32); @@ -189,7 +189,7 @@ pub fn push_stack_frame( size: i32, local_frame_pointer: LocalId, ) { - let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES); + let aligned_size = round_up_to_alignment(size, FRAME_ALIGNMENT_BYTES); instructions.extend([ GetGlobal(STACK_POINTER_GLOBAL_ID), I32Const(aligned_size), @@ -204,7 +204,7 @@ pub fn pop_stack_frame( size: i32, local_frame_pointer: LocalId, ) { - let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES); + let aligned_size = round_up_to_alignment(size, FRAME_ALIGNMENT_BYTES); instructions.extend([ GetLocal(local_frame_pointer.0), I32Const(aligned_size), @@ -216,3 +216,27 @@ pub fn pop_stack_frame( pub fn debug_panic(error: E) { panic!("{:?}", error); } + +/// Write a u32 value as LEB-128 encoded bytes, into the provided buffer +/// +/// 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. +pub fn encode_u32<'a>(buffer: &mut Vec<'a, u8>, mut value: u32) { + while value >= 0x80 { + buffer.push(0x80 | ((value & 0x7f) as u8)); + value >>= 7; + } + buffer.push(value as u8); +} + +/// Write a u64 value as LEB-128 encoded bytes, into the provided buffer +/// +/// 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. +pub fn encode_u64<'a>(buffer: &mut Vec<'a, u8>, mut value: u64) { + while value >= 0x80 { + buffer.push(0x80 | ((value & 0x7f) as u8)); + value >>= 7; + } + buffer.push(value as u8); +} From bca9f31c5868173704ae917e8d4badcaa2d0a916 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 22 Oct 2021 21:54:47 +0200 Subject: [PATCH 09/28] Implement insertions for byte-level code gen --- compiler/gen_wasm/src/function_builder.rs | 183 ++++++++++++++++++---- compiler/gen_wasm/src/lib.rs | 22 ++- 2 files changed, 163 insertions(+), 42 deletions(-) diff --git a/compiler/gen_wasm/src/function_builder.rs b/compiler/gen_wasm/src/function_builder.rs index 6e66651c03..c294cf8237 100644 --- a/compiler/gen_wasm/src/function_builder.rs +++ b/compiler/gen_wasm/src/function_builder.rs @@ -49,6 +49,14 @@ pub enum Align { // ... we can add more if we need them ... } +// An instruction (local.set or local.tee) to be inserted into the function code +#[derive(Debug)] +struct Insertion { + position: usize, + length: usize, + bytes: [u8; 6], +} + macro_rules! instruction_no_args { ($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => { pub fn $method_name(&mut self) { @@ -72,17 +80,20 @@ pub struct FunctionBuilder<'a> { /// Extra instructions to insert at specific positions during finalisation /// (Go back and set locals when we realise we need them) - insertions: Vec<'a, (usize, Vec<'a, u8>)>, + insertions: Vec<'a, Insertion>, - /// Bytes for locals and stack frame setup code - /// We can't write this until we've finished the main code, - /// but it goes before the main code in the final output. + /// Total number of bytes to be written as insertions + insertions_byte_len: usize, + + /// 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 the bytes of the length itself!) - /// We can't write this until we've finished the code and preamble, - /// but it goes before them in the final output. + /// ("inner" because it doesn't include its own length!) + /// We can't write this until we've finished the code and preamble. But + /// it goes before them in the final output, so it's a separate vector. inner_length: Vec<'a, u8>, /// Our simulation model of the Wasm stack machine @@ -95,7 +106,8 @@ impl<'a> FunctionBuilder<'a> { pub fn new(arena: &'a Bump) -> Self { FunctionBuilder { code: Vec::with_capacity_in(1024, arena), - insertions: Vec::with_capacity_in(1024, arena), + insertions: Vec::with_capacity_in(32, arena), + insertions_byte_len: 0, preamble: Vec::with_capacity_in(32, arena), inner_length: Vec::with_capacity_in(5, arena), vm_stack: Vec::with_capacity_in(32, arena), @@ -108,6 +120,16 @@ impl<'a> FunctionBuilder<'a> { self.vm_stack.clear(); } + /********************************************************** + + SYMBOLS + + 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. + + ***********************************************************/ + /// Set the Symbol that is at the top of the VM stack right now /// We will use this later when we need to load the Symbol pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState { @@ -143,7 +165,92 @@ impl<'a> FunctionBuilder<'a> { true } - /// Generate bytes to declare a function's local variables + /// Load a Symbol that is stored in the VM stack + /// If it's already at the top of the stack, no code will be generated. + /// Otherwise, local.set and local.get instructions will be inserted, using the LocalId provided. + /// + /// If the return value is `Some(s)`, `s` should be stored by the caller, and provided in the next call. + /// If the return value is `None`, the Symbol is no longer stored in the VM stack, but in a local. + /// (In this case, the caller must remember to declare the local in the function header.) + pub fn load_symbol( + &mut self, + symbol: Symbol, + vm_state: VirtualMachineSymbolState, + next_local_id: LocalId, + ) -> Option { + use VirtualMachineSymbolState::*; + + match vm_state { + NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol), + + Pushed { pushed_at } => { + let &top = self.vm_stack.last().unwrap(); + if top == symbol { + // We're lucky, the symbol is already on top of the VM stack + // No code to generate! (This reduces code size by up to 25% in tests.) + // Just let the caller know what happened + Some(Popped { pushed_at }) + } else { + // Symbol is not on top of the stack. Find it. + if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) { + // Insert a local.set where the value was created + let mut insertion = Insertion { + position: pushed_at, + length: 0, + bytes: [SETLOCAL, 0, 0, 0, 0, 0], + }; + insertion.length = 1 + encode_u32(&mut insertion.bytes[1..], next_local_id.0); + self.insertions_byte_len += insertion.length; + self.insertions.push(insertion); + + // Take the value out of the stack where local.set was inserted + self.vm_stack.remove(found_index); + + // 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 + None + } else { + panic!( + "{:?} has state {:?} but not found in VM stack", + symbol, vm_state + ); + } + } + } + + Popped { pushed_at } => { + // This Symbol is being used for a second time + // Insert a local.tee where it was pushed, so we don't interfere with the first usage + let mut insertion = Insertion { + position: pushed_at, + length: 0, + bytes: [TEELOCAL, 0, 0, 0, 0, 0], + }; + insertion.length = 1 + encode_u32(&mut insertion.bytes[1..], next_local_id.0); + self.insertions_byte_len += insertion.length; + self.insertions.push(insertion); + + // 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 + // Tell the caller it no longer has a VirtualMachineSymbolState + None + } + } + } + + /********************************************************** + + FINALIZE AND SERIALIZE + + ***********************************************************/ + + /// Generate bytes to declare the function's local variables fn build_local_declarations(&mut self, local_types: &[ValueType]) { let num_locals = local_types.len(); encode_u32(&mut self.preamble, num_locals as u32); @@ -190,12 +297,12 @@ impl<'a> FunctionBuilder<'a> { self.set_global(STACK_POINTER_GLOBAL_ID); } - /// Finalize a function body + /// Finalize the function /// Generate all the "extra" bytes: local declarations, stack frame push/pop code, and function length - /// After running this, we will know the final byte length of the function. All bytes will have been - /// _generated_, but not yet _serialized_, as they are still stored in multiple vectors. - pub fn finalize(&mut self, local_types: &[ValueType], frame_size: i32, frame_pointer: LocalId) { - self.insertions.sort_by_key(|pair| pair.0); + /// 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); self.build_local_declarations(local_types); @@ -205,28 +312,34 @@ impl<'a> FunctionBuilder<'a> { self.build_stack_frame_pop(aligned_size, frame_pointer); } - let inner_length_val = self.preamble.len() + self.code.len() + self.insertions.len(); - encode_u32(&mut self.inner_length, inner_length_val as u32); + // 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. + let inner_length_val = self.preamble.len() + self.code.len() + self.insertions_byte_len; + let inner_length_len = encode_u32(&mut self.inner_length, inner_length_val as u32); + let outer_length = inner_length_len + inner_length_val; + outer_length } - /// Write out the function bytes serially + /// Write out all the bytes in the right order pub fn serialize(&self, writer: &mut W) -> std::io::Result { writer.write(&self.inner_length)?; writer.write(&self.preamble)?; let mut pos: usize = 0; - for (next_insert_pos, insert_bytes) in self.insertions.iter() { - writer.write(&self.code[pos..*next_insert_pos])?; - writer.write(insert_bytes)?; - pos = *next_insert_pos; + for insertion in self.insertions.iter() { + writer.write(&self.code[pos..insertion.position])?; + writer.write(&insertion.bytes[0..insertion.length])?; + pos = insertion.position; } - let code_len = self.code.len(); - writer.write(&self.code[pos..code_len]) + let len = self.code.len(); + writer.write(&self.code[pos..len]) } - /// Total bytes this function will occupy in the code section, including its own inner length - pub fn outer_len(&self) -> usize { - self.inner_length.len() + self.preamble.len() + self.code.len() + self.insertions.len() - } + /********************************************************** + + INSTRUCTION HELPER METHODS + + ***********************************************************/ /// Base method for generating instructions /// Emits the opcode and simulates VM stack push/pop @@ -255,13 +368,15 @@ impl<'a> FunctionBuilder<'a> { encode_u32(&mut self.code, offset); } - // Instruction methods - // - // One method for each Wasm instruction (in same order as the spec) - // macros are just for compactness and readability for the most common cases - // Patterns that don't repeat very much don't have macros - // instruction_no_args! creates a method that takes no arguments - // instruction_memargs! creates a method that takes alignment and offset arguments + /********************************************************** + + 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); diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index aca629fb36..3547f7f9d6 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -217,26 +217,32 @@ pub fn debug_panic(error: E) { panic!("{:?}", error); } -/// Write a u32 value as LEB-128 encoded bytes, into the provided buffer +/// Write a u32 value as LEB-128 encoded bytes into the provided buffer, 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. -pub fn encode_u32<'a>(buffer: &mut Vec<'a, u8>, mut value: u32) { +pub fn encode_u32<'a>(buffer: &mut [u8], mut value: u32) -> usize { + let mut count = 0; while value >= 0x80 { - buffer.push(0x80 | ((value & 0x7f) as u8)); + buffer[count] = 0x80 | ((value & 0x7f) as u8); value >>= 7; + count += 1; } - buffer.push(value as u8); + buffer[count] = value as u8; + count + 1 } -/// Write a u64 value as LEB-128 encoded bytes, into the provided buffer +/// Write a u64 value as LEB-128 encoded bytes, into the provided buffer, 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. -pub fn encode_u64<'a>(buffer: &mut Vec<'a, u8>, mut value: u64) { +pub fn encode_u64<'a>(buffer: &mut Vec<'a, u8>, mut value: u64) -> usize { + let mut count = 0; while value >= 0x80 { - buffer.push(0x80 | ((value & 0x7f) as u8)); + buffer[count] = 0x80 | ((value & 0x7f) as u8); value >>= 7; + count += 1; } - buffer.push(value as u8); + buffer[count] = value as u8; + count + 1 } From fd83c3b7498b0d191a8aeed8b688980f1a89459e Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sat, 23 Oct 2021 00:17:08 -0500 Subject: [PATCH 10/28] Branch the dropLast functionality from dropAt, inserting an index gathered by subtracting 1 from the list length --- compiler/builtins/docs/List.roc | 4 ++++ compiler/builtins/src/bitcode.rs | 1 + compiler/can/src/builtins.rs | 39 ++++++++++++++++++++++++++++++++ compiler/module/src/low_level.rs | 2 -- 4 files changed, 44 insertions(+), 2 deletions(-) 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/bitcode.rs b/compiler/builtins/src/bitcode.rs index 2623b2ad3b..29ff50b103 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -177,6 +177,7 @@ pub const LIST_APPEND: &str = "roc_builtins.list.append"; pub const LIST_PREPEND: &str = "roc_builtins.list.prepend"; pub const LIST_DROP: &str = "roc_builtins.list.drop"; pub const LIST_DROP_AT: &str = "roc_builtins.list.drop_at"; +pub const LIST_DROP_LAST: &str = "roc_builtins.list.drop_last"; pub const LIST_SWAP: &str = "roc_builtins.list.swap"; pub const LIST_SINGLE: &str = "roc_builtins.list.single"; pub const LIST_JOIN: &str = "roc_builtins.list.join"; diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 3546fa6549..6e617bc60c 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -2004,6 +2004,45 @@ 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 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), (index_var, Symbol::ARG_2)], + 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/module/src/low_level.rs b/compiler/module/src/low_level.rs index e949213884..9be88c2945 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -42,7 +42,6 @@ pub enum LowLevel { ListSortWith, ListDrop, ListDropAt, - ListDropLast, ListSwap, DictSize, DictEmpty, @@ -130,7 +129,6 @@ macro_rules! first_order { | ListSet | ListDrop | ListDropAt - | ListDropLast | ListSingle | ListRepeat | ListReverse From 9633a5adaa34c2b0593055dde37bc3e2735b0958 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sat, 23 Oct 2021 00:29:13 -0500 Subject: [PATCH 11/28] Fix the build, but List.dropLast itself still doesn't work. --- compiler/can/src/builtins.rs | 6 ++++++ compiler/mono/src/borrow.rs | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 6e617bc60c..f719233c69 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -88,6 +88,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, @@ -2007,6 +2008,11 @@ 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, diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index f09ac825c4..55fd53f18b 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -947,7 +947,6 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListAppend => arena.alloc_slice_copy(&[owned, owned]), ListDrop => arena.alloc_slice_copy(&[owned, irrelevant]), ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]), - ListDropLast => arena.alloc_slice_copy(&[owned]), ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]), From 74e3239a1c1d4eb859b20a84aedea3df9df566ed Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 23 Oct 2021 01:31:21 +0200 Subject: [PATCH 12/28] Switch over to function_builder --- compiler/gen_wasm/src/backend.rs | 165 +++++++----------- compiler/gen_wasm/src/function_builder.rs | 40 ++++- compiler/gen_wasm/src/layout.rs | 3 +- compiler/gen_wasm/src/lib.rs | 91 +++------- compiler/gen_wasm/src/storage.rs | 55 +++--- .../tests/helpers/wasm32_test_result.rs | 141 +++++++-------- 6 files changed, 204 insertions(+), 291 deletions(-) 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] From dbe6d195f7a0f9fe4cc838225384366b3b9ebb0b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 23 Oct 2021 01:33:17 +0200 Subject: [PATCH 13/28] Delete CodeBuilder --- compiler/gen_wasm/src/code_builder.rs | 459 ---------------------- compiler/gen_wasm/src/function_builder.rs | 16 +- compiler/gen_wasm/src/lib.rs | 1 - compiler/gen_wasm/src/storage.rs | 3 +- 4 files changed, 16 insertions(+), 463 deletions(-) delete mode 100644 compiler/gen_wasm/src/code_builder.rs diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs deleted file mode 100644 index 17c49996db..0000000000 --- a/compiler/gen_wasm/src/code_builder.rs +++ /dev/null @@ -1,459 +0,0 @@ -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; - -const DEBUG_LOG: bool = false; - -#[derive(Debug, Clone, PartialEq, Copy)] -pub enum VirtualMachineSymbolState { - /// Value doesn't exist yet - NotYetPushed, - - /// Value has been pushed onto the VM stack but not yet popped - /// Remember where it was pushed, in case we need to insert another instruction there later - Pushed { pushed_at: usize }, - - /// Value has been pushed and popped, so it's not on the VM stack any more. - /// If we want to use it again later, we will have to create a local for it, - /// by going back to insert a local.tee instruction at pushed_at - Popped { pushed_at: usize }, -} - -#[derive(Debug)] -pub struct CodeBuilder<'a> { - /// The main container for the instructions - code: Vec<'a, Instruction>, - - /// Extra instructions to insert at specific positions during finalisation - /// (Go back and set locals when we realise we need them) - /// We need BTree rather than Map or Vec, to ensure keys are sorted. - /// Entries may not be added in order. They are created when a Symbol - /// is used for the second time, or is in an inconvenient VM stack position, - /// so it's not a simple predictable order. - insertions: BTreeMap, - - /// Our simulation model of the Wasm stack machine - /// Keeps track of where Symbol values are in the VM stack - vm_stack: Vec<'a, Symbol>, -} - -#[allow(clippy::new_without_default)] -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), - } - } - - pub fn clear(&mut self) { - self.code.clear(); - self.insertions.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); - } - - /// 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); - } - - /// 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 - pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState { - let len = self.vm_stack.len(); - let pushed_at = self.code.len(); - - if len == 0 { - panic!( - "trying to set symbol with nothing on stack, code = {:?}", - self.code - ); - } - - self.vm_stack[len - 1] = sym; - - VirtualMachineSymbolState::Pushed { pushed_at } - } - - /// Verify if a sequence of symbols is at the top of the stack - pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool { - let n_symbols = symbols.len(); - let stack_depth = self.vm_stack.len(); - if n_symbols > stack_depth { - return false; - } - let offset = stack_depth - n_symbols; - - for (i, sym) in symbols.iter().enumerate() { - if self.vm_stack[offset + i] != *sym { - return false; - } - } - true - } - - /// Load a Symbol that is stored in the VM stack - /// If it's already at the top of the stack, no code will be generated. - /// Otherwise, local.set and local.get instructions will be inserted, using the LocalId provided. - /// - /// If the return value is `Some(s)`, `s` should be stored by the caller, and provided in the next call. - /// If the return value is `None`, the Symbol is no longer stored in the VM stack, but in a local. - /// (In this case, the caller must remember to declare the local in the function header.) - pub fn load_symbol( - &mut self, - symbol: Symbol, - vm_state: VirtualMachineSymbolState, - next_local_id: LocalId, - ) -> Option { - use VirtualMachineSymbolState::*; - - match vm_state { - NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol), - - Pushed { pushed_at } => { - let &top = self.vm_stack.last().unwrap(); - if top == symbol { - // We're lucky, the symbol is already on top of the VM stack - // No code to generate! (This reduces code size by up to 25% in tests.) - // Just let the caller know what happened - Some(Popped { pushed_at }) - } else { - // Symbol is not on top of the stack. Find it. - if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) { - // Insert a SetLocal where the value was created (this removes it from the VM stack) - self.insertions.insert(pushed_at, SetLocal(next_local_id.0)); - self.vm_stack.remove(found_index); - - // Insert a GetLocal at the current position - let inst = GetLocal(next_local_id.0); - if DEBUG_LOG { - println!( - "{:?} {:?} (& insert {:?} at {:?})", - inst, - self.vm_stack, - SetLocal(next_local_id.0), - pushed_at - ); - } - self.code.push(inst); - self.vm_stack.push(symbol); - - // This Symbol is no longer stored in the VM stack, but in a local - None - } else { - panic!( - "{:?} has state {:?} but not found in VM stack", - symbol, vm_state - ); - } - } - } - - Popped { pushed_at } => { - // This Symbol is being used for a second time - - // Insert a TeeLocal where it was created (must remain on the stack for the first usage) - self.insertions.insert(pushed_at, TeeLocal(next_local_id.0)); - - // Insert a GetLocal at the current position - let inst = GetLocal(next_local_id.0); - if DEBUG_LOG { - println!( - "{:?} {:?} (& insert {:?} at {:?})", - inst, - self.vm_stack, - TeeLocal(next_local_id.0), - pushed_at - ); - } - self.code.push(inst); - self.vm_stack.push(symbol); - - // This symbol has been promoted to a Local - // Tell the caller it no longer has a VirtualMachineSymbolState - None - } - } - } -} - -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()"); - } - - Drop => (1, false), - Select => (3, true), - - GetLocal(_) => (0, true), - SetLocal(_) => (1, false), - TeeLocal(_) => (1, true), - GetGlobal(_) => (0, true), - SetGlobal(_) => (1, false), - - I32Load(_, _) => (1, true), - I64Load(_, _) => (1, true), - F32Load(_, _) => (1, true), - F64Load(_, _) => (1, true), - I32Load8S(_, _) => (1, true), - I32Load8U(_, _) => (1, true), - I32Load16S(_, _) => (1, true), - I32Load16U(_, _) => (1, true), - I64Load8S(_, _) => (1, true), - I64Load8U(_, _) => (1, true), - I64Load16S(_, _) => (1, true), - I64Load16U(_, _) => (1, true), - I64Load32S(_, _) => (1, true), - I64Load32U(_, _) => (1, true), - I32Store(_, _) => (2, false), - I64Store(_, _) => (2, false), - F32Store(_, _) => (2, false), - F64Store(_, _) => (2, false), - I32Store8(_, _) => (2, false), - I32Store16(_, _) => (2, false), - I64Store8(_, _) => (2, false), - I64Store16(_, _) => (2, false), - I64Store32(_, _) => (2, false), - - CurrentMemory(_) => (0, true), - GrowMemory(_) => (1, true), - I32Const(_) => (0, true), - I64Const(_) => (0, true), - F32Const(_) => (0, true), - F64Const(_) => (0, true), - - I32Eqz => (1, true), - I32Eq => (2, true), - I32Ne => (2, true), - I32LtS => (2, true), - I32LtU => (2, true), - I32GtS => (2, true), - I32GtU => (2, true), - I32LeS => (2, true), - I32LeU => (2, true), - I32GeS => (2, true), - I32GeU => (2, true), - - I64Eqz => (1, true), - I64Eq => (2, true), - I64Ne => (2, true), - I64LtS => (2, true), - I64LtU => (2, true), - I64GtS => (2, true), - I64GtU => (2, true), - I64LeS => (2, true), - I64LeU => (2, true), - I64GeS => (2, true), - I64GeU => (2, true), - - F32Eq => (2, true), - F32Ne => (2, true), - F32Lt => (2, true), - F32Gt => (2, true), - F32Le => (2, true), - F32Ge => (2, true), - - F64Eq => (2, true), - F64Ne => (2, true), - F64Lt => (2, true), - F64Gt => (2, true), - F64Le => (2, true), - F64Ge => (2, true), - - I32Clz => (1, true), - I32Ctz => (1, true), - I32Popcnt => (1, true), - I32Add => (2, true), - I32Sub => (2, true), - I32Mul => (2, true), - I32DivS => (2, true), - I32DivU => (2, true), - I32RemS => (2, true), - I32RemU => (2, true), - I32And => (2, true), - I32Or => (2, true), - I32Xor => (2, true), - I32Shl => (2, true), - I32ShrS => (2, true), - I32ShrU => (2, true), - I32Rotl => (2, true), - I32Rotr => (2, true), - - I64Clz => (1, true), - I64Ctz => (1, true), - I64Popcnt => (1, true), - I64Add => (2, true), - I64Sub => (2, true), - I64Mul => (2, true), - I64DivS => (2, true), - I64DivU => (2, true), - I64RemS => (2, true), - I64RemU => (2, true), - I64And => (2, true), - I64Or => (2, true), - I64Xor => (2, true), - I64Shl => (2, true), - I64ShrS => (2, true), - I64ShrU => (2, true), - I64Rotl => (2, true), - I64Rotr => (2, true), - - F32Abs => (1, true), - F32Neg => (1, true), - F32Ceil => (1, true), - F32Floor => (1, true), - F32Trunc => (1, true), - F32Nearest => (1, true), - F32Sqrt => (1, true), - F32Add => (2, true), - F32Sub => (2, true), - F32Mul => (2, true), - F32Div => (2, true), - F32Min => (2, true), - F32Max => (2, true), - F32Copysign => (2, true), - - F64Abs => (1, true), - F64Neg => (1, true), - F64Ceil => (1, true), - F64Floor => (1, true), - F64Trunc => (1, true), - F64Nearest => (1, true), - F64Sqrt => (1, true), - F64Add => (2, true), - F64Sub => (2, true), - F64Mul => (2, true), - F64Div => (2, true), - F64Min => (2, true), - F64Max => (2, true), - F64Copysign => (2, true), - - I32WrapI64 => (1, true), - I32TruncSF32 => (1, true), - I32TruncUF32 => (1, true), - I32TruncSF64 => (1, true), - I32TruncUF64 => (1, true), - I64ExtendSI32 => (1, true), - I64ExtendUI32 => (1, true), - I64TruncSF32 => (1, true), - I64TruncUF32 => (1, true), - I64TruncSF64 => (1, true), - I64TruncUF64 => (1, true), - F32ConvertSI32 => (1, true), - F32ConvertUI32 => (1, true), - F32ConvertSI64 => (1, true), - F32ConvertUI64 => (1, true), - F32DemoteF64 => (1, true), - F64ConvertSI32 => (1, true), - F64ConvertUI32 => (1, true), - F64ConvertSI64 => (1, true), - F64ConvertUI64 => (1, true), - F64PromoteF32 => (1, true), - - I32ReinterpretF32 => (1, true), - I64ReinterpretF64 => (1, true), - F32ReinterpretI32 => (1, true), - F64ReinterpretI64 => (1, true), - } -} diff --git a/compiler/gen_wasm/src/function_builder.rs b/compiler/gen_wasm/src/function_builder.rs index 9ab030a68f..dec689a338 100644 --- a/compiler/gen_wasm/src/function_builder.rs +++ b/compiler/gen_wasm/src/function_builder.rs @@ -5,7 +5,6 @@ use std::fmt::Debug; use roc_module::symbol::Symbol; -use crate::code_builder::VirtualMachineSymbolState; use crate::opcodes::*; use crate::{ encode_u32, encode_u64, round_up_to_alignment, LocalId, FRAME_ALIGNMENT_BYTES, @@ -61,6 +60,21 @@ pub enum Align { // ... we can add more if we need them ... } +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum VirtualMachineSymbolState { + /// Value doesn't exist yet + NotYetPushed, + + /// Value has been pushed onto the VM stack but not yet popped + /// Remember where it was pushed, in case we need to insert another instruction there later + Pushed { pushed_at: usize }, + + /// Value has been pushed and popped, so it's not on the VM stack any more. + /// If we want to use it again later, we will have to create a local for it, + /// by going back to insert a local.tee instruction at pushed_at + Popped { pushed_at: usize }, +} + // An instruction (local.set or local.tee) to be inserted into the function code #[derive(Debug)] struct Insertion { diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index f3bf359913..1d220efc26 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -1,5 +1,4 @@ mod backend; -mod code_builder; pub mod from_wasm32_memory; mod layout; mod storage; diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index c52b7b388f..a9e56d09c9 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -4,8 +4,7 @@ use bumpalo::Bump; use roc_collections::all::MutMap; use roc_module::symbol::Symbol; -use crate::code_builder::VirtualMachineSymbolState; -use crate::function_builder::{FunctionBuilder, ValueType}; +use crate::function_builder::{FunctionBuilder, ValueType, VirtualMachineSymbolState}; use crate::layout::WasmLayout; use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, PTR_SIZE, PTR_TYPE}; From 973626fe2d55d77d7d03cd512f863a265f963a0d Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 23 Oct 2021 13:48:20 +0200 Subject: [PATCH 14/28] Rename FunctionBuilder back to CodeBuilder --- compiler/gen_wasm/src/backend.rs | 6 +++--- .../src/{function_builder.rs => code_builder.rs} | 6 +++--- compiler/gen_wasm/src/layout.rs | 2 +- compiler/gen_wasm/src/lib.rs | 6 +++--- compiler/gen_wasm/src/storage.rs | 12 ++++++------ .../gen_wasm/tests/helpers/wasm32_test_result.rs | 8 ++++---- 6 files changed, 20 insertions(+), 20 deletions(-) rename compiler/gen_wasm/src/{function_builder.rs => code_builder.rs} (99%) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 43416ad95e..ffce729bc8 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -8,7 +8,7 @@ use roc_module::symbol::Symbol; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; -use crate::function_builder::{BlockType, FunctionBuilder, ValueType}; +use crate::code_builder::{BlockType, CodeBuilder, ValueType}; use crate::layout::WasmLayout; use crate::storage::{Storage, StoredValue, StoredValueKind}; use crate::{copy_memory, CopyMemoryConfig, Env, LocalId, PTR_TYPE}; @@ -32,7 +32,7 @@ pub struct WasmBackend<'a> { proc_symbol_map: MutMap, // Function level - code_builder: FunctionBuilder<'a>, + code_builder: CodeBuilder<'a>, storage: Storage<'a>, /// how many blocks deep are we (used for jumps) @@ -56,7 +56,7 @@ impl<'a> WasmBackend<'a> { joinpoint_label_map: MutMap::default(), // Functions - code_builder: FunctionBuilder::new(env.arena), + code_builder: CodeBuilder::new(env.arena), storage: Storage::new(env.arena), } } diff --git a/compiler/gen_wasm/src/function_builder.rs b/compiler/gen_wasm/src/code_builder.rs similarity index 99% rename from compiler/gen_wasm/src/function_builder.rs rename to compiler/gen_wasm/src/code_builder.rs index dec689a338..36406171a3 100644 --- a/compiler/gen_wasm/src/function_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -100,7 +100,7 @@ macro_rules! instruction_memargs { } #[derive(Debug)] -pub struct FunctionBuilder<'a> { +pub struct CodeBuilder<'a> { /// The main container for the instructions code: Vec<'a, u8>, @@ -128,9 +128,9 @@ pub struct FunctionBuilder<'a> { } #[allow(clippy::new_without_default)] -impl<'a> FunctionBuilder<'a> { +impl<'a> CodeBuilder<'a> { pub fn new(arena: &'a Bump) -> Self { - FunctionBuilder { + CodeBuilder { code: Vec::with_capacity_in(1024, arena), insertions: Vec::with_capacity_in(32, arena), insertions_byte_len: 0, diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 94f03db619..66aba67d04 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -1,6 +1,6 @@ use roc_mono::layout::{Layout, UnionLayout}; -use crate::{function_builder::ValueType, 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 1d220efc26..656eb70868 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -4,7 +4,7 @@ mod layout; mod storage; #[allow(dead_code)] -pub mod function_builder; +pub mod code_builder; #[allow(dead_code)] mod opcodes; @@ -20,7 +20,7 @@ use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; use crate::backend::WasmBackend; -use crate::function_builder::{Align, FunctionBuilder, ValueType}; +use crate::code_builder::{Align, CodeBuilder, ValueType}; const PTR_SIZE: u32 = 4; const PTR_TYPE: ValueType = ValueType::I32; @@ -134,7 +134,7 @@ pub struct CopyMemoryConfig { alignment_bytes: u32, } -pub fn copy_memory(code_builder: &mut FunctionBuilder, config: CopyMemoryConfig) { +pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) { if config.from_ptr == config.to_ptr && config.from_offset == config.to_offset { return; } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index a9e56d09c9..04982846bf 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -4,7 +4,7 @@ use bumpalo::Bump; use roc_collections::all::MutMap; use roc_module::symbol::Symbol; -use crate::function_builder::{FunctionBuilder, ValueType, VirtualMachineSymbolState}; +use crate::code_builder::{CodeBuilder, ValueType, VirtualMachineSymbolState}; use crate::layout::WasmLayout; use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, PTR_SIZE, PTR_TYPE}; @@ -188,7 +188,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 FunctionBuilder, symbols: &[Symbol]) { + pub fn load_symbols(&mut self, code_builder: &mut CodeBuilder, symbols: &[Symbol]) { if code_builder.verify_stack_match(symbols) { // The symbols were already at the top of the stack, do nothing! // This should be quite common due to the structure of the Mono IR @@ -258,7 +258,7 @@ impl<'a> Storage<'a> { /// (defined by a pointer and offset). pub fn copy_value_to_memory( &mut self, - code_builder: &mut FunctionBuilder, + code_builder: &mut CodeBuilder, to_ptr: LocalId, to_offset: u32, from_symbol: Symbol, @@ -291,7 +291,7 @@ impl<'a> Storage<'a> { | StoredValue::Local { value_type, size, .. } => { - use crate::function_builder::Align::*; + use crate::code_builder::Align::*; code_builder.get_local(to_ptr); self.load_symbols(code_builder, &[from_symbol]); match (value_type, size) { @@ -314,7 +314,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 FunctionBuilder, + code_builder: &mut CodeBuilder, to: &StoredValue, from: &StoredValue, from_symbol: Symbol, @@ -402,7 +402,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 FunctionBuilder, + code_builder: &mut CodeBuilder, symbol: Symbol, storage: StoredValue, ) -> StoredValue { diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index ee8a80313b..81a1d62827 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -2,13 +2,13 @@ use parity_wasm::builder; use parity_wasm::builder::ModuleBuilder; use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; -use roc_gen_wasm::function_builder::{Align, FunctionBuilder, ValueType}; +use roc_gen_wasm::code_builder::{Align, CodeBuilder, ValueType}; use roc_std::{RocDec, RocList, RocOrder, RocStr}; pub trait Wasm32TestResult { fn insert_test_wrapper( module_builder: &mut ModuleBuilder, - code_builder: &mut FunctionBuilder, + code_builder: &mut CodeBuilder, wrapper_name: &str, main_function_index: u32, ) { @@ -26,7 +26,7 @@ pub trait Wasm32TestResult { self.build_wrapper_body(code_builder, main_function_index); } - fn build_wrapper_body(code_builder: &mut FunctionBuilder, main_function_index: u32); + fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32); } macro_rules! build_wrapper_body_primitive { @@ -59,7 +59,7 @@ macro_rules! wasm_test_result_primitive { } fn build_wrapper_body_stack_memory( - code_builder: &mut FunctionBuilder, + code_builder: &mut CodeBuilder, main_function_index: u32, size: usize, ) -> Vec { From 7c398ba2384076130fdd8dd4ae87cddc55fcd7de Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 23 Oct 2021 13:52:45 +0200 Subject: [PATCH 15/28] tweak comment --- compiler/gen_wasm/src/code_builder.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index 36406171a3..6aae708807 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -111,15 +111,15 @@ pub struct CodeBuilder<'a> { /// Total number of bytes to be written as insertions insertions_byte_len: usize, - /// Bytes for local variable declarations, and stack frame setup code. + /// 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!) - /// We can't write this until we've finished the code and preamble. But - /// it goes before them in the final output, so it's a separate vector. + /// 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 From 13577aa9ecd4f9fc1cb44dbece16e7379a02fcda Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 23 Oct 2021 17:10:00 +0200 Subject: [PATCH 16/28] Combine our handmade Code section with other sections from parity_wasm (tests compile but fail) --- compiler/gen_wasm/src/backend.rs | 40 +++++++---- compiler/gen_wasm/src/code_builder.rs | 22 ++++++ compiler/gen_wasm/src/lib.rs | 69 +++++++++++++----- compiler/gen_wasm/tests/helpers/eval.rs | 13 +++- .../tests/helpers/wasm32_test_result.rs | 70 +++++++++++-------- 5 files changed, 149 insertions(+), 65 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index ffce729bc8..8c617f040a 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -11,7 +11,7 @@ use roc_mono::layout::{Builtin, Layout}; use crate::code_builder::{BlockType, CodeBuilder, ValueType}; use crate::layout::WasmLayout; use crate::storage::{Storage, StoredValue, StoredValueKind}; -use crate::{copy_memory, CopyMemoryConfig, Env, LocalId, PTR_TYPE}; +use crate::{copy_memory, encode_u32_padded, 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) @@ -20,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>, @@ -41,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); + encode_u32_padded(&mut code_section_bytes[5..10], num_procs as u32); + 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), } @@ -86,7 +93,7 @@ impl<'a> WasmBackend<'a> { self.build_stmt(&proc.body, &proc.ret_layout)?; - self.finalize_proc(); + self.finalize_proc()?; self.reset(); // println!("\nfinished generating {:?}\n", sym); @@ -123,7 +130,7 @@ impl<'a> WasmBackend<'a> { builder::function().with_signature(signature).build() } - fn finalize_proc(&mut self) { + 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(); @@ -133,6 +140,11 @@ impl<'a> WasmBackend<'a> { self.storage.stack_frame_size, self.storage.stack_frame_pointer, ); + + self.code_builder + .serialize(&mut self.code_section_bytes) + .map_err(|e| format!("{:?}", e))?; + Ok(()) } /********************************************************** diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index 6aae708807..eed1f43e56 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -60,6 +60,21 @@ pub enum Align { // ... 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 @@ -99,6 +114,13 @@ macro_rules! instruction_memargs { }; } +/// Finalize the code section bytes by writing its inner length at the start. +/// Assumes 5 bytes have been reserved for it (maximally-padded LEB-128) +pub fn finalize_code_section(code_section_bytes: &mut std::vec::Vec) { + let inner_len = (code_section_bytes.len() - 5) as u32; + encode_u32_padded(code_section_bytes[0..5], inner_len); +} + #[derive(Debug)] pub struct CodeBuilder<'a> { /// The main container for the instructions diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 656eb70868..c85d4266f6 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -13,14 +13,14 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use parity_wasm::builder; -use parity_wasm::elements::Internal; +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::{Align, CodeBuilder, ValueType}; +use crate::code_builder::{finalize_code_section, Align, CodeBuilder, ValueType}; const PTR_SIZE: u32 = 4; const PTR_TYPE: ValueType = ValueType::I32; @@ -28,6 +28,10 @@ const PTR_TYPE: ValueType = ValueType::I32; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; 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); @@ -41,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) }) @@ -51,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 @@ -85,6 +91,8 @@ pub fn build_module_help<'a>( } } + finalize_code_section(&mut backend.code_section_bytes); + // 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. @@ -106,23 +114,30 @@ pub fn build_module_help<'a>( let stack_pointer_global = builder::global() .with_type(parity_wasm::elements::ValueType::I32) .mutable() - .init_expr(parity_wasm::elements::Instruction::I32Const( - (MIN_MEMORY_SIZE_KB * 1024) as i32, - )) + .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) -> Align { - match bytes { - 1 => Align::Bytes1, - 2 => Align::Bytes2, - 4 => Align::Bytes4, - 8 => Align::Bytes8, - _ => 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 { @@ -139,7 +154,7 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) { return; } - let alignment = encode_alignment(config.alignment_bytes); + let alignment = Align::from(config.alignment_bytes); let mut i = 0; while config.size - i >= 8 { code_builder.get_local(config.to_ptr); @@ -196,7 +211,7 @@ pub fn encode_u32<'a>(buffer: &mut [u8], mut value: u32) -> usize { /// /// 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. -pub fn encode_u64<'a>(buffer: &mut Vec<'a, u8>, mut value: u64) -> usize { +pub fn encode_u64<'a>(buffer: &mut [u8], mut value: u64) -> usize { let mut count = 0; while value >= 0x80 { buffer[count] = 0x80 | ((value & 0x7f) as u8); @@ -206,3 +221,19 @@ pub fn encode_u64<'a>(buffer: &mut Vec<'a, u8>, mut value: u64) -> usize { buffer[count] = value as u8; count + 1 } + +/// Write a u32 value as LEB-128 encoded bytes, but padded to maximum byte length (5) +/// +/// Sometimes we want a number to have fixed length, so we can update it later (e.g. relocations) +/// without moving all the following bytes. For those cases we pad it to maximum length. +/// For example, 3 is encoded as 0x83 0x80 0x80 0x80 0x00. +/// +/// https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#relocation-sections +pub fn encode_u32_padded<'a>(buffer: &mut [u8], mut value: u32) -> usize { + for i in 0..4 { + buffer[i] = 0x80 | ((value & 0x7f) as u8); + value >>= 7; + } + buffer[4] = value as u8; + 5 +} diff --git a/compiler/gen_wasm/tests/helpers/eval.rs b/compiler/gen_wasm/tests/helpers/eval.rs index 45d21e8962..615aa4034b 100644 --- a/compiler/gen_wasm/tests/helpers/eval.rs +++ b/compiler/gen_wasm/tests/helpers/eval.rs @@ -102,14 +102,21 @@ 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); + + T::insert_test_wrapper( + arena, + &mut builder, + &mut code_section_bytes, + TEST_WRAPPER_NAME, + main_function_index, + ); let module_bytes = builder.build().into_bytes().unwrap(); // for debugging (e.g. with wasm2wat) - if false { + if true { use std::io::Write; let mut hash_state = DefaultHasher::new(); diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index 81a1d62827..af8176cdbd 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -1,18 +1,22 @@ use parity_wasm::builder; -use parity_wasm::builder::ModuleBuilder; +use parity_wasm::elements::Internal; -use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; use roc_gen_wasm::code_builder::{Align, CodeBuilder, ValueType}; +use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; +use roc_gen_wasm::{encode_u32_padded, LocalId}; use roc_std::{RocDec, RocList, RocOrder, RocStr}; pub trait Wasm32TestResult { - fn insert_test_wrapper( - module_builder: &mut ModuleBuilder, - code_builder: &mut CodeBuilder, + 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 signature = builder::signature().with_result(ValueType::I32).build_sig(); + let signature = builder::signature() + .with_result(parity_wasm::elements::ValueType::I32) + .build_sig(); // parity-wasm FunctionDefinition with no instructions let empty_fn_def = builder::function().with_signature(signature).build(); @@ -23,7 +27,11 @@ pub trait Wasm32TestResult { .build(); module_builder.push_export(export); - self.build_wrapper_body(code_builder, main_function_index); + let mut code_builder = CodeBuilder::new(arena); + Self::build_wrapper_body(&mut code_builder, main_function_index); + + code_builder.serialize(code_section_bytes).unwrap(); + finalize_code_section(&mut code_section_bytes); } fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32); @@ -31,10 +39,7 @@ pub trait Wasm32TestResult { macro_rules! build_wrapper_body_primitive { ($store_instruction: ident, $align: expr) => { - fn build_wrapper_body( - code_builder: &mut FunctionBuilder, - main_function_index: u32, - ) -> Vec { + 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]; @@ -62,7 +67,7 @@ fn build_wrapper_body_stack_memory( code_builder: &mut CodeBuilder, main_function_index: u32, size: usize, -) -> Vec { +) { let local_id = LocalId(0); let local_types = &[ValueType::I32]; let frame_pointer = Some(local_id); @@ -70,16 +75,13 @@ fn build_wrapper_body_stack_memory( 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); + 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( - code_builder: &mut FunctionBuilder, - 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, @@ -111,8 +113,8 @@ 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) } } @@ -124,8 +126,8 @@ 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) } } @@ -134,8 +136,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, + ) } } @@ -145,8 +151,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, ) @@ -160,8 +167,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, ) @@ -176,8 +184,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, ) @@ -193,8 +202,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 @@ -216,8 +226,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 @@ -241,8 +252,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 From 6534da5055f0cdb180a3d5dbc43028e7be9244fe Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 24 Oct 2021 11:54:21 +0200 Subject: [PATCH 17/28] Fix LEB encoding and refactor insertions --- compiler/gen_wasm/src/backend.rs | 4 +- compiler/gen_wasm/src/code_builder.rs | 80 +++++++++---------- compiler/gen_wasm/src/lib.rs | 57 ++++++------- .../tests/helpers/wasm32_test_result.rs | 11 ++- 4 files changed, 79 insertions(+), 73 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 8c617f040a..44d3adf41d 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -11,7 +11,7 @@ use roc_mono::layout::{Builtin, Layout}; use crate::code_builder::{BlockType, CodeBuilder, ValueType}; use crate::layout::WasmLayout; use crate::storage::{Storage, StoredValue, StoredValueKind}; -use crate::{copy_memory, encode_u32_padded, 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) @@ -48,7 +48,7 @@ impl<'a> WasmBackend<'a> { // 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); - encode_u32_padded(&mut code_section_bytes[5..10], num_procs as u32); + overwrite_padded_u32(&mut code_section_bytes[5..10], num_procs as u32); // gets modified in unit tests WasmBackend { env, diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index eed1f43e56..32d9f9c6f8 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -92,10 +92,10 @@ pub enum VirtualMachineSymbolState { // An instruction (local.set or local.tee) to be inserted into the function code #[derive(Debug)] -struct Insertion { - position: usize, - length: usize, - bytes: [u8; 6], +struct InsertLocation { + insert_at: usize, + start: usize, + end: usize, } macro_rules! instruction_no_args { @@ -114,24 +114,17 @@ macro_rules! instruction_memargs { }; } -/// Finalize the code section bytes by writing its inner length at the start. -/// Assumes 5 bytes have been reserved for it (maximally-padded LEB-128) -pub fn finalize_code_section(code_section_bytes: &mut std::vec::Vec) { - let inner_len = (code_section_bytes.len() - 5) as u32; - encode_u32_padded(code_section_bytes[0..5], inner_len); -} - #[derive(Debug)] pub struct CodeBuilder<'a> { /// The main container for the instructions code: Vec<'a, u8>, - /// Extra instructions to insert at specific positions during finalisation - /// (Go back and set locals when we realise we need them) - insertions: Vec<'a, Insertion>, + /// 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>, - /// Total number of bytes to be written as insertions - insertions_byte_len: usize, + /// 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 @@ -154,8 +147,8 @@ impl<'a> CodeBuilder<'a> { pub fn new(arena: &'a Bump) -> Self { CodeBuilder { code: Vec::with_capacity_in(1024, arena), - insertions: Vec::with_capacity_in(32, arena), - insertions_byte_len: 0, + 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), @@ -164,7 +157,10 @@ impl<'a> CodeBuilder<'a> { 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(); } @@ -213,6 +209,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. @@ -242,15 +251,7 @@ impl<'a> CodeBuilder<'a> { // 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 local.set where the value was created - let mut insertion = Insertion { - position: pushed_at, - length: 0, - bytes: [SETLOCAL, 0, 0, 0, 0, 0], - }; - insertion.length = - 1 + encode_u32(&mut insertion.bytes[1..], next_local_id.0); - self.insertions_byte_len += insertion.length; - self.insertions.push(insertion); + 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); @@ -273,14 +274,7 @@ 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 - let mut insertion = Insertion { - position: pushed_at, - length: 0, - bytes: [TEELOCAL, 0, 0, 0, 0, 0], - }; - insertion.length = 1 + encode_u32(&mut insertion.bytes[1..], next_local_id.0); - self.insertions_byte_len += insertion.length; - self.insertions.push(insertion); + self.add_insertion(pushed_at, TEELOCAL, next_local_id.0); // Insert a local.get at the current position self.get_local(next_local_id); @@ -369,7 +363,7 @@ impl<'a> CodeBuilder<'a> { // 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. - let inner_length_val = self.preamble.len() + self.code.len() + self.insertions_byte_len; + let inner_length_val = self.preamble.len() + self.code.len() + self.insert_bytes.len(); let inner_length_len = encode_u32(&mut self.inner_length, inner_length_val as u32); let outer_length = inner_length_len + inner_length_val; outer_length @@ -380,13 +374,15 @@ impl<'a> CodeBuilder<'a> { writer.write(&self.inner_length)?; writer.write(&self.preamble)?; - self.insertions.sort_by_key(|insertion| insertion.position); + // 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(|location| location.insert_at); 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; + for location in self.insert_locations.iter() { + writer.write(&self.code[pos..location.insert_at])?; + writer.write(&self.insert_bytes[location.start..location.end])?; + pos = location.insert_at; } let len = self.code.len(); diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index c85d4266f6..493d9ecde7 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -20,7 +20,7 @@ use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::layout::LayoutIds; use crate::backend::WasmBackend; -use crate::code_builder::{finalize_code_section, Align, CodeBuilder, ValueType}; +use crate::code_builder::{Align, CodeBuilder, ValueType}; const PTR_SIZE: u32 = 4; const PTR_TYPE: ValueType = ValueType::I32; @@ -91,7 +91,9 @@ pub fn build_module_help<'a>( } } - finalize_code_section(&mut backend.code_section_bytes); + // 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. @@ -196,44 +198,45 @@ pub fn debug_panic(error: E) { /// /// 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. -pub fn encode_u32<'a>(buffer: &mut [u8], mut value: u32) -> usize { - let mut count = 0; - while value >= 0x80 { - buffer[count] = 0x80 | ((value & 0x7f) as u8); - value >>= 7; - count += 1; +pub fn encode_u32<'a>(buffer: &mut Vec<'a, u8>, value: u32) -> usize { + let mut x = value; + let start_len = buffer.len(); + while x >= 0x80 { + buffer.push(0x80 | ((x & 0x7f) as u8)); + x >>= 7; } - buffer[count] = value as u8; - count + 1 + buffer.push(x as u8); + buffer.len() - start_len } /// Write a u64 value as LEB-128 encoded bytes, into the provided buffer, 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. -pub fn encode_u64<'a>(buffer: &mut [u8], mut value: u64) -> usize { - let mut count = 0; - while value >= 0x80 { - buffer[count] = 0x80 | ((value & 0x7f) as u8); - value >>= 7; - count += 1; +pub fn encode_u64<'a>(buffer: &mut Vec<'a, u8>, value: u64) -> usize { + let mut x = value; + let start_len = buffer.len(); + while x >= 0x80 { + buffer.push(0x80 | ((x & 0x7f) as u8)); + x >>= 7; } - buffer[count] = value as u8; - count + 1 + buffer.push(x as u8); + buffer.len() - start_len } -/// Write a u32 value as LEB-128 encoded bytes, but padded to maximum byte length (5) +/// Overwrite a LEB-128 encoded u32 value that has been padded to maximum length (5 bytes) /// -/// Sometimes we want a number to have fixed length, so we can update it later (e.g. relocations) -/// without moving all the following bytes. For those cases we pad it to maximum length. -/// For example, 3 is encoded as 0x83 0x80 0x80 0x80 0x00. +/// We need some fixed length values so we can overwrite them without moving all following bytes. +/// For example, the code section is prefixed with its length, which we only know at the end. +/// And relocation values get updated during linking. /// +/// 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 encode_u32_padded<'a>(buffer: &mut [u8], mut value: u32) -> usize { +pub fn overwrite_padded_u32<'a>(buffer: &mut [u8], value: u32) { + let mut x = value; for i in 0..4 { - buffer[i] = 0x80 | ((value & 0x7f) as u8); - value >>= 7; + buffer[i] = 0x80 | ((x & 0x7f) as u8); + x >>= 7; } - buffer[4] = value as u8; - 5 + buffer[4] = x as u8; } diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index af8176cdbd..629eeccac2 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -3,7 +3,7 @@ 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::{encode_u32_padded, LocalId}; +use roc_gen_wasm::{overwrite_padded_u32, LocalId}; use roc_std::{RocDec, RocList, RocOrder, RocStr}; pub trait Wasm32TestResult { @@ -31,7 +31,14 @@ pub trait Wasm32TestResult { Self::build_wrapper_body(&mut code_builder, main_function_index); code_builder.serialize(code_section_bytes).unwrap(); - finalize_code_section(&mut code_section_bytes); + + 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(code_builder: &mut CodeBuilder, main_function_index: u32); From c46e73bd64fb4f07a7af879cbde9581c03dbfef8 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 24 Oct 2021 23:34:14 +0200 Subject: [PATCH 18/28] Fix test code gen --- compiler/gen_wasm/tests/helpers/eval.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/gen_wasm/tests/helpers/eval.rs b/compiler/gen_wasm/tests/helpers/eval.rs index 615aa4034b..045f2404be 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; @@ -113,7 +114,9 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( main_function_index, ); - let module_bytes = builder.build().into_bytes().unwrap(); + 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 true { @@ -145,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; @@ -154,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)] From 11f327f722a1d1b05142c1c75288d0e5aa4de120 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 24 Oct 2021 23:35:25 +0200 Subject: [PATCH 19/28] Fix function call --- compiler/gen_wasm/src/code_builder.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index 32d9f9c6f8..76b0161b4d 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -473,6 +473,7 @@ impl<'a> CodeBuilder<'a> { 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"); From b6e442c9ee3a464d464ef43c015dad007e073c4b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 24 Oct 2021 23:36:00 +0200 Subject: [PATCH 20/28] Fix locals code gen --- compiler/gen_wasm/src/code_builder.rs | 63 ++++++++++++++++----------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index 76b0161b4d..bb4b259045 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -295,25 +295,41 @@ impl<'a> CodeBuilder<'a> { /// Generate bytes to declare the function's local variables fn build_local_declarations(&mut self, local_types: &[ValueType]) { - let num_locals = local_types.len(); - encode_u32(&mut self.preamble, num_locals as u32); + // reserve one byte for num_batches + self.preamble.push(0); - // write declarations in batches of the same ValueType - if num_locals > 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; - } + if local_types.len() == 0 { + return; + } + + // 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); + } + encode_u32(&mut self.preamble, batch_size); + self.preamble.push(batch_type as u8); + num_batches += 1; + + // Go back and write the number of batches at the start + if num_batches < 128 { + self.preamble[0] = num_batches as u8; + } else { + // We have 128+ batches of locals (extremely unlikely!) + let tmp = self.preamble.clone(); + self.preamble.clear(); + encode_u32(&mut self.preamble, num_batches); + self.preamble.extend_from_slice(&tmp); } } @@ -349,7 +365,7 @@ impl<'a> CodeBuilder<'a> { local_types: &[ValueType], frame_size: i32, frame_pointer: Option, - ) -> usize { + ) { self.build_local_declarations(local_types); if let Some(frame_ptr_id) = frame_pointer { @@ -360,13 +376,8 @@ impl<'a> CodeBuilder<'a> { 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. - let inner_length_val = self.preamble.len() + self.code.len() + self.insert_bytes.len(); - let inner_length_len = encode_u32(&mut self.inner_length, inner_length_val as u32); - let outer_length = inner_length_len + inner_length_val; - outer_length + 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 @@ -376,7 +387,7 @@ impl<'a> CodeBuilder<'a> { // 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(|location| location.insert_at); + self.insert_locations.sort_by_key(|loc| loc.insert_at); let mut pos: usize = 0; for location in self.insert_locations.iter() { From 063d7b178bec8a947589feb7a10f8f0cc7ca2b75 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Mon, 25 Oct 2021 00:06:37 -0500 Subject: [PATCH 21/28] Figured out why dropLast call was producing an argument mismatch: - Although list_drop_last's LowLevel operation modeling (body) was correct, the defn() CALLED that body with an extra argument for index (a copy-paste error from dropAt). - List.dropAt works now :) --- compiler/can/src/builtins.rs | 2 +- compiler/gen_llvm/src/llvm/build_list.rs | 28 +++++++++++++++--------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index f719233c69..1d3c1d29d5 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -2043,7 +2043,7 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def { defn( symbol, - vec![(list_var, Symbol::ARG_1), (index_var, Symbol::ARG_2)], + vec![(list_var, Symbol::ARG_1)], var_store, body, list_var, diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 1e17e261e8..0c06bb1ca5 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -344,20 +344,28 @@ pub fn list_drop_at<'a, 'ctx, 'env>( /// List.dropLast : List elem -> List elem pub fn list_drop_last<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, - layout_ids: &mut LayoutIds<'a>, - original_wrapper: StructValue<'ctx>, - count: IntValue<'ctx>, - element_layout: &Layout<'a>, + list: BasicValueEnum<'ctx>, + list_layout: &Layout<'a>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { - let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout); + let element_layout = match *list_layout { + Layout::Builtin(Builtin::EmptyList) => { + // this pointer will never actually be dereferenced + Layout::Builtin(Builtin::Int64) + } + + Layout::Builtin(Builtin::List(elem_layout)) => *elem_layout, + + _ => unreachable!("Invalid layout {:?} in List.dropLast", list_layout), + }; + call_bitcode_fn_returns_list( env, &[ - pass_list_cc(env, original_wrapper.into()), - env.alignment_intvalue(element_layout), - layout_width(env, element_layout), - count.into(), - dec_element_fn.as_global_value().as_pointer_value().into(), + pass_list_cc(env, list), + env.alignment_intvalue(&element_layout), + layout_width(env, &element_layout), + pass_update_mode(env, update_mode), ], bitcode::LIST_DROP_LAST, ) From 789cc5acc0f0c99fca8a37f1d6cea9b5a68fada8 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Mon, 25 Oct 2021 00:28:38 -0500 Subject: [PATCH 22/28] cargo fmt --- compiler/can/src/builtins.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 1d3c1d29d5..6403c2d25e 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -2018,7 +2018,8 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListDropAt, args: vec![ (list_var, Var(Symbol::ARG_1)), - (index_var, + ( + index_var, // Num.sub (List.len list) 1 RunLowLevel { op: LowLevel::NumSubWrap, From ddf66293e9670ef704bfc263f4393ba7a4a67d00 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 25 Oct 2021 12:15:04 +0200 Subject: [PATCH 23/28] Fix and refactor number encodings --- compiler/gen_wasm/src/code_builder.rs | 26 +++----- compiler/gen_wasm/src/lib.rs | 86 +++++++++++++++++-------- compiler/gen_wasm/tests/helpers/eval.rs | 2 +- 3 files changed, 71 insertions(+), 43 deletions(-) diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index bb4b259045..e0f90e2296 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -7,12 +7,13 @@ use roc_module::symbol::Symbol; use crate::opcodes::*; use crate::{ - encode_u32, encode_u64, round_up_to_alignment, LocalId, FRAME_ALIGNMENT_BYTES, - STACK_POINTER_GLOBAL_ID, + encode_f32, encode_f64, encode_i32, encode_i64, encode_u32, round_up_to_alignment, LocalId, + FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID, }; const DEBUG_LOG: bool = false; +/// Wasm value type. (Rust representation matches Wasm encoding) #[repr(u8)] #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum ValueType { @@ -22,6 +23,7 @@ pub enum ValueType { 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 { @@ -47,6 +49,7 @@ impl BlockType { } } +/// Wasm memory alignment. (Rust representation matches Wasm encoding) #[repr(u8)] #[derive(Clone, Copy, Debug)] pub enum Align { @@ -540,29 +543,20 @@ impl<'a> CodeBuilder<'a> { self.inst_imm8(GROWMEMORY, 1, true, 0); } pub fn i32_const(&mut self, x: i32) { - self.inst_imm32(I32CONST, 0, true, x as u32); + 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_u64(&mut self.code, x as u64); + encode_i64(&mut self.code, x); } pub fn f32_const(&mut self, x: f32) { self.inst(F32CONST, 0, true); - // No LEB encoding, and always little-endian regardless of compiler host. - let mut value: u32 = x.to_bits(); - for _ in 0..4 { - self.code.push((value & 0xff) as u8); - value >>= 8; - } + encode_f32(&mut self.code, x); } pub fn f64_const(&mut self, x: f64) { self.inst(F64CONST, 0, true); - // No LEB encoding, and always little-endian regardless of compiler host. - let mut value: u64 = x.to_bits(); - for _ in 0..8 { - self.code.push((value & 0xff) as u8); - value >>= 8; - } + encode_f64(&mut self.code, x); } // TODO: Consider creating unified methods for numerical ops like 'eq' and 'add', diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 493d9ecde7..f7676a8ac1 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -194,41 +194,75 @@ pub fn debug_panic(error: E) { panic!("{:?}", error); } -/// Write a u32 value as LEB-128 encoded bytes into the provided buffer, returning byte length +/// 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. -pub fn encode_u32<'a>(buffer: &mut Vec<'a, u8>, value: u32) -> 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 +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 + } + }; } -/// Write a u64 value as LEB-128 encoded bytes, into the provided buffer, 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. -pub fn encode_u64<'a>(buffer: &mut Vec<'a, u8>, value: u64) -> 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 + } + }; } -/// Overwrite a LEB-128 encoded u32 value that has been padded to maximum length (5 bytes) +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. -/// For example, the code section is prefixed with its length, which we only know at the end. +/// 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 diff --git a/compiler/gen_wasm/tests/helpers/eval.rs b/compiler/gen_wasm/tests/helpers/eval.rs index 045f2404be..e0d298b820 100644 --- a/compiler/gen_wasm/tests/helpers/eval.rs +++ b/compiler/gen_wasm/tests/helpers/eval.rs @@ -119,7 +119,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>( let module_bytes = parity_module.into_bytes().unwrap(); // for debugging (e.g. with wasm2wat) - if true { + if false { use std::io::Write; let mut hash_state = DefaultHasher::new(); From 3f404dd1143f2d7d4123d3e014ceaa72515af3a9 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 25 Oct 2021 12:23:24 +0200 Subject: [PATCH 24/28] clippy --- compiler/gen_wasm/src/code_builder.rs | 16 ++++++++-------- compiler/gen_wasm/src/lib.rs | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index e0f90e2296..77ae959bb0 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -301,7 +301,7 @@ impl<'a> CodeBuilder<'a> { // reserve one byte for num_batches self.preamble.push(0); - if local_types.len() == 0 { + if local_types.is_empty() { return; } @@ -384,9 +384,9 @@ impl<'a> CodeBuilder<'a> { } /// Write out all the bytes in the right order - pub fn serialize(&mut self, writer: &mut W) -> std::io::Result { - writer.write(&self.inner_length)?; - writer.write(&self.preamble)?; + 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. @@ -394,13 +394,13 @@ impl<'a> CodeBuilder<'a> { let mut pos: usize = 0; for location in self.insert_locations.iter() { - writer.write(&self.code[pos..location.insert_at])?; - writer.write(&self.insert_bytes[location.start..location.end])?; + 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(&self.code[pos..len]) + writer.write_all(&self.code[pos..len]) } /********************************************************** @@ -493,7 +493,7 @@ impl<'a> CodeBuilder<'a> { panic!("Not implemented. Roc doesn't use function pointers"); } - instruction_no_args!(drop, DROP, 1, false); + instruction_no_args!(drop_, DROP, 1, false); instruction_no_args!(select, SELECT, 3, true); pub fn get_local(&mut self, id: LocalId) { diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index f7676a8ac1..d9312645da 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -266,10 +266,10 @@ encode_float!(encode_f64, f64); /// /// 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<'a>(buffer: &mut [u8], value: u32) { +pub fn overwrite_padded_u32(buffer: &mut [u8], value: u32) { let mut x = value; - for i in 0..4 { - buffer[i] = 0x80 | ((x & 0x7f) as u8); + for byte in buffer.iter_mut().take(4) { + *byte = 0x80 | ((x & 0x7f) as u8); x >>= 7; } buffer[4] = x as u8; From 4e098be7fecc98041f8d4d830d1e8832e6c90cb1 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 25 Oct 2021 13:04:30 +0200 Subject: [PATCH 25/28] Fix a test throwing a Wasm runtime error --- compiler/gen_wasm/src/code_builder.rs | 2 +- compiler/gen_wasm/tests/helpers/eval.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index 77ae959bb0..9c383342ac 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -343,7 +343,7 @@ impl<'a> CodeBuilder<'a> { self.preamble.push(GETGLOBAL); encode_u32(&mut self.preamble, STACK_POINTER_GLOBAL_ID); self.preamble.push(I32CONST); - encode_u32(&mut self.preamble, frame_size as u32); + encode_i32(&mut self.preamble, frame_size); self.preamble.push(I32SUB); self.preamble.push(TEELOCAL); encode_u32(&mut self.preamble, frame_pointer.0); diff --git a/compiler/gen_wasm/tests/helpers/eval.rs b/compiler/gen_wasm/tests/helpers/eval.rs index e0d298b820..e4bbbd71da 100644 --- a/compiler/gen_wasm/tests/helpers/eval.rs +++ b/compiler/gen_wasm/tests/helpers/eval.rs @@ -198,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) } From d996eee72eec206d58233a32390711fbdcfc839d Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 25 Oct 2021 13:26:50 +0200 Subject: [PATCH 26/28] Pointlessly improve how we handle a crazy edge case, because my brain wouldn't let it go --- compiler/gen_wasm/src/code_builder.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/gen_wasm/src/code_builder.rs b/compiler/gen_wasm/src/code_builder.rs index 9c383342ac..09aa6a58a3 100644 --- a/compiler/gen_wasm/src/code_builder.rs +++ b/compiler/gen_wasm/src/code_builder.rs @@ -5,11 +5,11 @@ use std::fmt::Debug; use roc_module::symbol::Symbol; -use crate::opcodes::*; 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; @@ -328,11 +328,12 @@ impl<'a> CodeBuilder<'a> { if num_batches < 128 { self.preamble[0] = num_batches as u8; } else { - // We have 128+ batches of locals (extremely unlikely!) - let tmp = self.preamble.clone(); - self.preamble.clear(); - encode_u32(&mut self.preamble, num_batches); - self.preamble.extend_from_slice(&tmp); + // 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); } } From fc55172219f7d7a3f5c49bf779a4daba4e72ceb0 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Mon, 25 Oct 2021 10:45:18 -0500 Subject: [PATCH 27/28] Fix test typo --- compiler/solve/tests/solve_expr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index f05eb2b9df..43b97738a4 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3734,7 +3734,7 @@ mod solve_expr { } #[test] - fn list_drop_at() { + fn list_drop_last() { infer_eq_without_problem( indoc!( r#" From 7680a1d17a9dcd93776840ee63d12976d4f1385a Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Mon, 25 Oct 2021 21:55:34 -0500 Subject: [PATCH 28/28] Remove unused code --- compiler/builtins/src/bitcode.rs | 1 - compiler/gen_llvm/src/llvm/build_list.rs | 30 ------------------------ 2 files changed, 31 deletions(-) diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 29ff50b103..2623b2ad3b 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -177,7 +177,6 @@ pub const LIST_APPEND: &str = "roc_builtins.list.append"; pub const LIST_PREPEND: &str = "roc_builtins.list.prepend"; pub const LIST_DROP: &str = "roc_builtins.list.drop"; pub const LIST_DROP_AT: &str = "roc_builtins.list.drop_at"; -pub const LIST_DROP_LAST: &str = "roc_builtins.list.drop_last"; pub const LIST_SWAP: &str = "roc_builtins.list.swap"; pub const LIST_SINGLE: &str = "roc_builtins.list.single"; pub const LIST_JOIN: &str = "roc_builtins.list.join"; diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 0c06bb1ca5..3d80912689 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -341,36 +341,6 @@ pub fn list_drop_at<'a, 'ctx, 'env>( ) } -/// List.dropLast : List elem -> List elem -pub fn list_drop_last<'a, 'ctx, 'env>( - env: &Env<'a, 'ctx, 'env>, - list: BasicValueEnum<'ctx>, - list_layout: &Layout<'a>, - update_mode: UpdateMode, -) -> BasicValueEnum<'ctx> { - let element_layout = match *list_layout { - Layout::Builtin(Builtin::EmptyList) => { - // this pointer will never actually be dereferenced - Layout::Builtin(Builtin::Int64) - } - - Layout::Builtin(Builtin::List(elem_layout)) => *elem_layout, - - _ => unreachable!("Invalid layout {:?} in List.dropLast", list_layout), - }; - - call_bitcode_fn_returns_list( - env, - &[ - pass_list_cc(env, list), - env.alignment_intvalue(&element_layout), - layout_width(env, &element_layout), - pass_update_mode(env, update_mode), - ], - bitcode::LIST_DROP_LAST, - ) -} - /// List.set : List elem, Nat, elem -> List elem pub fn list_set<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>,