Merge branch 'trunk' into str_trim

This commit is contained in:
Folkert de Vries 2021-10-26 22:45:32 +02:00 committed by GitHub
commit 29bd4e3e50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1169 additions and 622 deletions

View file

@ -34,6 +34,7 @@ interface List
sortWith, sortWith,
drop, drop,
dropAt, dropAt,
dropLast,
swap swap
] ]
imports [] 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]. ## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem 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. ## Adds a new element to the end of the list.
## ##
## >>> List.append [ "a", "b" ] "c" ## >>> List.append [ "a", "b" ] "c"

View file

@ -937,6 +937,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(flex(TVAR1))), 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 // swap : List elem, Nat, Nat -> List elem
add_top_level_function_type!( add_top_level_function_type!(
Symbol::LIST_SWAP, Symbol::LIST_SWAP,

View file

@ -89,6 +89,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_MAP3 => list_map3, LIST_MAP3 => list_map3,
LIST_DROP => list_drop, LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at, LIST_DROP_AT => list_drop_at,
LIST_DROP_LAST => list_drop_last,
LIST_SWAP => list_swap, LIST_SWAP => list_swap,
LIST_MAP_WITH_INDEX => list_map_with_index, LIST_MAP_WITH_INDEX => list_map_with_index,
LIST_KEEP_IF => list_keep_if, LIST_KEEP_IF => list_keep_if,
@ -2010,6 +2011,51 @@ fn list_drop_at(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// List.dropLast: List elem -> List elem
fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let index_var = var_store.fresh();
let arg_var = var_store.fresh();
let len_var = Variable::NAT;
let num_var = len_var;
let num_precision_var = Variable::NATURAL;
let body = RunLowLevel {
op: LowLevel::ListDropAt,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(
index_var,
// Num.sub (List.len list) 1
RunLowLevel {
op: LowLevel::NumSubWrap,
args: vec![
(
arg_var,
// List.len list
RunLowLevel {
op: LowLevel::ListLen,
args: vec![(list_var, Var(Symbol::ARG_1))],
ret_var: len_var,
},
),
(arg_var, int(num_var, num_precision_var, 1)),
],
ret_var: len_var,
},
),
],
ret_var: list_var,
};
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
list_var,
)
}
/// List.append : List elem, elem -> List elem /// List.append : List elem, elem -> List elem
fn list_append(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_append(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();

View file

@ -1,9 +1,6 @@
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder, SignatureBuilder}; use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder};
use parity_wasm::elements::{
BlockType, Instruction, Instruction::*, Instructions, Local, ValueType,
};
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel; 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::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
use crate::code_builder::CodeBuilder; use crate::code_builder::{BlockType, CodeBuilder, ValueType};
use crate::layout::WasmLayout; use crate::layout::WasmLayout;
use crate::storage::{Storage, StoredValue, StoredValueKind}; use crate::storage::{Storage, StoredValue, StoredValueKind};
use crate::{ use crate::{copy_memory, overwrite_padded_u32, CopyMemoryConfig, Env, LocalId, PTR_TYPE};
copy_memory, pop_stack_frame, push_stack_frame, CopyMemoryConfig, Env, LocalId, PTR_TYPE,
};
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // 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) // Follow Emscripten's example by using 1kB (4 bytes would probably do)
@ -25,18 +20,17 @@ const UNUSED_DATA_SECTION_BYTES: u32 = 1024;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
struct LabelId(u32); struct LabelId(u32);
// TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43)
pub struct WasmBackend<'a> { pub struct WasmBackend<'a> {
// Module level: Wasm AST
pub module_builder: ModuleBuilder,
env: &'a Env<'a>, env: &'a Env<'a>,
// Module level: internal state & IR mappings // Module-level data
pub module_builder: ModuleBuilder,
pub code_section_bytes: std::vec::Vec<u8>,
_data_offset_map: MutMap<Literal<'a>, u32>, _data_offset_map: MutMap<Literal<'a>, u32>,
_data_offset_next: u32, _data_offset_next: u32,
proc_symbol_map: MutMap<Symbol, CodeLocation>, proc_symbol_map: MutMap<Symbol, CodeLocation>,
// Function level // Function-level data
code_builder: CodeBuilder<'a>, code_builder: CodeBuilder<'a>,
storage: Storage<'a>, storage: Storage<'a>,
@ -46,21 +40,29 @@ pub struct WasmBackend<'a> {
} }
impl<'a> WasmBackend<'a> { impl<'a> WasmBackend<'a> {
pub fn new(env: &'a Env<'a>) -> Self { pub fn new(env: &'a Env<'a>, num_procs: usize) -> Self {
// Code section is prefixed with the number of Wasm functions
// For now, this is the same as the number of IR procedures (until we start inlining!)
let mut code_section_bytes = std::vec::Vec::with_capacity(4096);
// Reserve space for code section header: inner byte length and number of functions
// Padded to the maximum 5 bytes each, so we can update later without moving everything
code_section_bytes.resize(10, 0);
overwrite_padded_u32(&mut code_section_bytes[5..10], num_procs as u32); // gets modified in unit tests
WasmBackend { WasmBackend {
// Module: Wasm AST
module_builder: builder::module(),
env, env,
// Module: internal state & IR mappings // Module-level data
module_builder: builder::module(),
code_section_bytes,
_data_offset_map: MutMap::default(), _data_offset_map: MutMap::default(),
_data_offset_next: UNUSED_DATA_SECTION_BYTES, _data_offset_next: UNUSED_DATA_SECTION_BYTES,
proc_symbol_map: MutMap::default(), proc_symbol_map: MutMap::default(),
// Function-level data
block_depth: 0, block_depth: 0,
joinpoint_label_map: MutMap::default(), joinpoint_label_map: MutMap::default(),
// Functions
code_builder: CodeBuilder::new(env.arena), code_builder: CodeBuilder::new(env.arena),
storage: Storage::new(env.arena), storage: Storage::new(env.arena),
} }
@ -82,21 +84,24 @@ impl<'a> WasmBackend<'a> {
pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> { pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> {
// println!("\ngenerating procedure {:?}\n", sym); // 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)?; self.build_stmt(&proc.body, &proc.ret_layout)?;
let function_def = self.finalize_proc(signature_builder); self.finalize_proc()?;
let location = self.module_builder.push_function(function_def);
let function_index = location.body;
self.proc_symbol_map.insert(sym, location);
self.reset(); self.reset();
// println!("\nfinished generating {:?}\n", sym); // println!("\nfinished generating {:?}\n", sym);
Ok(function_index) 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 ret_layout = WasmLayout::new(&proc.ret_layout);
let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout { let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout {
@ -106,7 +111,7 @@ impl<'a> WasmBackend<'a> {
} else { } else {
let ret_type = ret_layout.value_type(); let ret_type = ret_layout.value_type();
self.start_block(BlockType::Value(ret_type)); // block to ensure all paths pop stack memory (if any) 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 { for (layout, symbol) in proc.args {
@ -117,60 +122,29 @@ impl<'a> WasmBackend<'a> {
); );
} }
signature_builder.with_params(self.storage.arg_types.clone()) let parity_params = self.storage.arg_types.iter().map(|t| t.to_parity_wasm());
let signature = signature_builder.with_params(parity_params).build_sig();
// parity-wasm FunctionDefinition with no instructions
builder::function().with_signature(signature).build()
} }
fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition { fn finalize_proc(&mut self) -> Result<(), String> {
self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any) // 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; // Write local declarations and stack frame push/pop code
let mut final_instructions = self.code_builder.finalize(
std::vec::Vec::with_capacity(self.code_builder.len() + STACK_FRAME_INSTRUCTIONS_LEN); &self.storage.local_types,
self.storage.stack_frame_size,
self.storage.stack_frame_pointer,
);
if self.storage.stack_frame_size > 0 { self.code_builder
push_stack_frame( .serialize(&mut self.code_section_bytes)
&mut final_instructions, .map_err(|e| format!("{:?}", e))?;
self.storage.stack_frame_size, Ok(())
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
} }
/********************************************************** /**********************************************************
@ -182,17 +156,17 @@ impl<'a> WasmBackend<'a> {
/// start a loop that leaves a value on the stack /// start a loop that leaves a value on the stack
fn start_loop_with_return(&mut self, value_type: ValueType) { fn start_loop_with_return(&mut self, value_type: ValueType) {
self.block_depth += 1; 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) { fn start_block(&mut self, block_type: BlockType) {
self.block_depth += 1; self.block_depth += 1;
self.code_builder.push(Block(block_type)); self.code_builder.block(block_type);
} }
fn end_block(&mut self) { fn end_block(&mut self) {
self.block_depth -= 1; 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> { fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
@ -255,7 +229,7 @@ impl<'a> WasmBackend<'a> {
_ => { _ => {
self.storage.load_symbols(&mut self.code_builder, &[*sym]); self.storage.load_symbols(&mut self.code_builder, &[*sym]);
self.code_builder.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) self.code_builder.br(self.block_depth); // jump to end of function (for stack frame pop)
} }
} }
@ -293,13 +267,13 @@ impl<'a> WasmBackend<'a> {
self.storage self.storage
.load_symbols(&mut self.code_builder, &[*cond_symbol]); .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 // compare the 2 topmost values
self.code_builder.push(I32Eq); self.code_builder.i32_eq();
// "break" out of `i` surrounding blocks // "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 // if we never jumped because a value matched, we're in the default case
@ -375,7 +349,7 @@ impl<'a> WasmBackend<'a> {
// jump // jump
let levels = self.block_depth - target; let levels = self.block_depth - target;
self.code_builder.push(Br(levels)); self.code_builder.br(levels);
Ok(()) Ok(())
} }
@ -427,11 +401,8 @@ impl<'a> WasmBackend<'a> {
func_sym, sym func_sym, sym
))?; ))?;
self.code_builder.push_call( self.code_builder
function_location.body, .call(function_location.body, wasm_args.len(), has_return_val);
wasm_args.len(),
has_return_val,
);
Ok(()) Ok(())
} }
@ -448,25 +419,25 @@ impl<'a> WasmBackend<'a> {
} }
fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> { fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> {
let instruction = match lit { match lit {
Literal::Bool(x) => I32Const(*x as i32), Literal::Bool(x) => self.code_builder.i32_const(*x as i32),
Literal::Byte(x) => I32Const(*x as i32), Literal::Byte(x) => self.code_builder.i32_const(*x as i32),
Literal::Int(x) => match layout { 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( Layout::Builtin(
Builtin::Int32 Builtin::Int32
| Builtin::Int16 | Builtin::Int16
| Builtin::Int8 | Builtin::Int8
| Builtin::Int1 | Builtin::Int1
| Builtin::Usize, | Builtin::Usize,
) => I32Const(*x as i32), ) => self.code_builder.i32_const(*x as i32),
x => { x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x)); return Err(format!("loading literal, {:?}, is not yet implemented", x));
} }
}, },
Literal::Float(x) => match layout { Literal::Float(x) => match layout {
Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()), Layout::Builtin(Builtin::Float64) => self.code_builder.f64_const(*x as f64),
Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()), Layout::Builtin(Builtin::Float32) => self.code_builder.f32_const(*x as f32),
x => { x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x)); return Err(format!("loading literal, {:?}, is not yet implemented", x));
} }
@ -475,7 +446,6 @@ impl<'a> WasmBackend<'a> {
return Err(format!("loading literal, {:?}, is not yet implemented", x)); return Err(format!("loading literal, {:?}, is not yet implemented", x));
} }
}; };
self.code_builder.push(instruction);
Ok(()) Ok(())
} }
@ -546,34 +516,33 @@ impl<'a> WasmBackend<'a> {
// Some Roc low-level ops care about wrapping, clipping, sign-extending... // 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, // 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. // 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 { LowLevel::NumAdd => match return_value_type {
ValueType::I32 => &[I32Add], ValueType::I32 => self.code_builder.i32_add(),
ValueType::I64 => &[I64Add], ValueType::I64 => self.code_builder.i64_add(),
ValueType::F32 => &[F32Add], ValueType::F32 => self.code_builder.f32_add(),
ValueType::F64 => &[F64Add], ValueType::F64 => self.code_builder.f64_add(),
}, },
LowLevel::NumSub => match return_value_type { LowLevel::NumSub => match return_value_type {
ValueType::I32 => &[I32Sub], ValueType::I32 => self.code_builder.i32_sub(),
ValueType::I64 => &[I64Sub], ValueType::I64 => self.code_builder.i64_sub(),
ValueType::F32 => &[F32Sub], ValueType::F32 => self.code_builder.f32_sub(),
ValueType::F64 => &[F64Sub], ValueType::F64 => self.code_builder.f64_sub(),
}, },
LowLevel::NumMul => match return_value_type { LowLevel::NumMul => match return_value_type {
ValueType::I32 => &[I32Mul], ValueType::I32 => self.code_builder.i32_mul(),
ValueType::I64 => &[I64Mul], ValueType::I64 => self.code_builder.i64_mul(),
ValueType::F32 => &[F32Mul], ValueType::F32 => self.code_builder.f32_mul(),
ValueType::F64 => &[F64Mul], ValueType::F64 => self.code_builder.f64_mul(),
}, },
LowLevel::NumGt => { LowLevel::NumGt => {
// needs layout of the argument to be implemented fully // needs layout of the argument to be implemented fully
&[I32GtS] self.code_builder.i32_gt_s()
} }
_ => { _ => {
return Err(format!("unsupported low-level op {:?}", lowlevel)); return Err(format!("unsupported low-level op {:?}", lowlevel));
} }
}; };
self.code_builder.extend_from_slice(instructions);
Ok(()) Ok(())
} }
} }

View file

@ -1,16 +1,83 @@
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use core::panic; use core::panic;
use std::collections::BTreeMap;
use std::fmt::Debug; use std::fmt::Debug;
use parity_wasm::elements::{Instruction, Instruction::*};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use crate::LocalId; use crate::{
encode_f32, encode_f64, encode_i32, encode_i64, encode_u32, round_up_to_alignment, LocalId,
FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID,
};
use crate::{opcodes::*, overwrite_padded_u32};
const DEBUG_LOG: bool = false; const DEBUG_LOG: bool = false;
/// Wasm value type. (Rust representation matches Wasm encoding)
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum ValueType {
I32 = 0x7f,
I64 = 0x7e,
F32 = 0x7d,
F64 = 0x7c,
}
// This is a bit unfortunate. Will go away if we generate our own Types section.
impl ValueType {
pub fn to_parity_wasm(&self) -> parity_wasm::elements::ValueType {
match self {
Self::I32 => parity_wasm::elements::ValueType::I32,
Self::I64 => parity_wasm::elements::ValueType::I64,
Self::F32 => parity_wasm::elements::ValueType::F32,
Self::F64 => parity_wasm::elements::ValueType::F64,
}
}
}
pub enum BlockType {
NoResult,
Value(ValueType),
}
impl BlockType {
pub fn as_byte(&self) -> u8 {
match self {
Self::NoResult => 0x40,
Self::Value(t) => *t as u8,
}
}
}
/// Wasm memory alignment. (Rust representation matches Wasm encoding)
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum Align {
Bytes1 = 0,
Bytes2 = 1,
Bytes4 = 2,
Bytes8 = 3,
Bytes16 = 4,
Bytes32 = 5,
Bytes64 = 6,
// ... we can add more if we need them ...
}
impl From<u32> 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)] #[derive(Debug, Clone, PartialEq, Copy)]
pub enum VirtualMachineSymbolState { pub enum VirtualMachineSymbolState {
/// Value doesn't exist yet /// Value doesn't exist yet
@ -26,18 +93,52 @@ pub enum VirtualMachineSymbolState {
Popped { pushed_at: usize }, Popped { pushed_at: usize },
} }
// An instruction (local.set or local.tee) to be inserted into the function code
#[derive(Debug)]
struct InsertLocation {
insert_at: usize,
start: usize,
end: usize,
}
macro_rules! instruction_no_args {
($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => {
pub fn $method_name(&mut self) {
self.inst($opcode, $pops, $push);
}
};
}
macro_rules! instruction_memargs {
($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => {
pub fn $method_name(&mut self, align: Align, offset: u32) {
self.inst_mem($opcode, $pops, $push, align, offset);
}
};
}
#[derive(Debug)] #[derive(Debug)]
pub struct CodeBuilder<'a> { pub struct CodeBuilder<'a> {
/// The main container for the instructions /// The main container for the instructions
code: Vec<'a, Instruction>, code: Vec<'a, u8>,
/// Extra instructions to insert at specific positions during finalisation /// Instruction bytes to be inserted into the code when finalizing the function
/// (Go back and set locals when we realise we need them) /// (Used for setting locals when we realise they are used multiple times)
/// We need BTree rather than Map or Vec, to ensure keys are sorted. insert_bytes: Vec<'a, u8>,
/// 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, /// Code locations where the insert_bytes should go
/// so it's not a simple predictable order. insert_locations: Vec<'a, InsertLocation>,
insertions: BTreeMap<usize, Instruction>,
/// Bytes for local variable declarations and stack-frame setup code.
/// We can't write this until we've finished the main code. But it goes
/// before it in the final output, so we need a separate vector.
preamble: Vec<'a, u8>,
/// Encoded bytes for the inner length of the function, locals + code.
/// ("inner" because it doesn't include its own length!)
/// Again, we can't write this until we've finished the code and preamble,
/// but it goes before them in the binary, so it's a separate vector.
inner_length: Vec<'a, u8>,
/// Our simulation model of the Wasm stack machine /// Our simulation model of the Wasm stack machine
/// Keeps track of where Symbol values are in the VM stack /// Keeps track of where Symbol values are in the VM stack
@ -48,102 +149,33 @@ pub struct CodeBuilder<'a> {
impl<'a> CodeBuilder<'a> { impl<'a> CodeBuilder<'a> {
pub fn new(arena: &'a Bump) -> Self { pub fn new(arena: &'a Bump) -> Self {
CodeBuilder { CodeBuilder {
vm_stack: Vec::with_capacity_in(32, arena),
insertions: BTreeMap::default(),
code: Vec::with_capacity_in(1024, arena), code: Vec::with_capacity_in(1024, arena),
insert_locations: Vec::with_capacity_in(32, arena),
insert_bytes: Vec::with_capacity_in(64, arena),
preamble: Vec::with_capacity_in(32, arena),
inner_length: Vec::with_capacity_in(5, arena),
vm_stack: Vec::with_capacity_in(32, arena),
} }
} }
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.code.clear(); 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(); 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 SYMBOLS
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 The Wasm VM stores temporary values in its stack machine.
/// Specify the number of arguments the function pops from the VM stack, and whether it pushes a return value We track which stack positions correspond to IR Symbols,
pub fn push_call(&mut self, function_index: u32, pops: usize, push: bool) { because it helps to generate more efficient code.
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<Instruction>) {
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 /// 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 /// We will use this later when we need to load the Symbol
@ -180,6 +212,19 @@ impl<'a> CodeBuilder<'a> {
true 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 /// 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. /// 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. /// Otherwise, local.set and local.get instructions will be inserted, using the LocalId provided.
@ -208,22 +253,14 @@ impl<'a> CodeBuilder<'a> {
} else { } else {
// Symbol is not on top of the stack. Find it. // Symbol is not on top of the stack. Find it.
if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) { 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) // Insert a local.set where the value was created
self.insertions.insert(pushed_at, SetLocal(next_local_id.0)); 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); self.vm_stack.remove(found_index);
// Insert a GetLocal at the current position // Insert a local.get at the current position
let inst = GetLocal(next_local_id.0); self.get_local(next_local_id);
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); self.vm_stack.push(symbol);
// This Symbol is no longer stored in the VM stack, but in a local // This Symbol is no longer stored in the VM stack, but in a local
@ -239,22 +276,11 @@ impl<'a> CodeBuilder<'a> {
Popped { pushed_at } => { Popped { pushed_at } => {
// This Symbol is being used for a second time // This Symbol is being used for a second time
// Insert a local.tee where it was pushed, so we don't interfere with the first usage
self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
// Insert a TeeLocal where it was created (must remain on the stack for the first usage) // Insert a local.get at the current position
self.insertions.insert(pushed_at, TeeLocal(next_local_id.0)); self.get_local(next_local_id);
// 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); self.vm_stack.push(symbol);
// This symbol has been promoted to a Local // This symbol has been promoted to a Local
@ -263,197 +289,400 @@ impl<'a> CodeBuilder<'a> {
} }
} }
} }
}
fn get_pops_and_pushes(inst: &Instruction) -> (u8, bool) { /**********************************************************
match inst {
Unreachable => (0, false),
Nop => (0, false),
Block(_) => (0, false),
Loop(_) => (0, false),
If(_) => (1, false),
Else => (0, false),
End => (0, false),
Br(_) => (0, false),
BrIf(_) => (1, false),
BrTable(_) => (1, false),
Return => (0, false),
Call(_) | CallIndirect(_, _) => { FINALIZE AND SERIALIZE
panic!("Unknown number of pushes and pops. Use add_call()");
***********************************************************/
/// Generate bytes to declare the function's local variables
fn build_local_declarations(&mut self, local_types: &[ValueType]) {
// reserve one byte for num_batches
self.preamble.push(0);
if local_types.is_empty() {
return;
} }
Drop => (1, false), // Write declarations in batches of the same ValueType
Select => (3, true), let mut num_batches: u32 = 0;
let mut batch_type = local_types[0];
let mut batch_size = 0;
for t in local_types {
if *t == batch_type {
batch_size += 1;
} else {
encode_u32(&mut self.preamble, batch_size);
self.preamble.push(batch_type as u8);
batch_type = *t;
batch_size = 1;
num_batches += 1;
}
}
encode_u32(&mut self.preamble, batch_size);
self.preamble.push(batch_type as u8);
num_batches += 1;
GetLocal(_) => (0, true), // Go back and write the number of batches at the start
SetLocal(_) => (1, false), if num_batches < 128 {
TeeLocal(_) => (1, true), self.preamble[0] = num_batches as u8;
GetGlobal(_) => (0, true), } else {
SetGlobal(_) => (1, false), // We need more than 1 byte to encode num_batches!
// This is a ridiculous edge case, so just pad to 5 bytes for simplicity
I32Load(_, _) => (1, true), let old_len = self.preamble.len();
I64Load(_, _) => (1, true), self.preamble.resize(old_len + 4, 0);
F32Load(_, _) => (1, true), self.preamble.copy_within(1..old_len, 5);
F64Load(_, _) => (1, true), overwrite_padded_u32(&mut self.preamble[0..5], num_batches);
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),
} }
/// Generate instruction bytes to grab a frame of stack memory on entering the function
fn build_stack_frame_push(&mut self, frame_size: i32, frame_pointer: LocalId) {
// Can't use the usual instruction methods because they push to self.code.
// This is the only case where we push instructions somewhere different.
self.preamble.push(GETGLOBAL);
encode_u32(&mut self.preamble, STACK_POINTER_GLOBAL_ID);
self.preamble.push(I32CONST);
encode_i32(&mut self.preamble, frame_size);
self.preamble.push(I32SUB);
self.preamble.push(TEELOCAL);
encode_u32(&mut self.preamble, frame_pointer.0);
self.preamble.push(SETGLOBAL);
encode_u32(&mut self.preamble, STACK_POINTER_GLOBAL_ID);
}
/// Generate instruction bytes to release a frame of stack memory on leaving the function
fn build_stack_frame_pop(&mut self, frame_size: i32, frame_pointer: LocalId) {
self.get_local(frame_pointer);
self.i32_const(frame_size);
self.i32_add();
self.set_global(STACK_POINTER_GLOBAL_ID);
}
/// Finalize the function
/// Generate all the "extra" bytes: local declarations, stack frame push/pop code, and function length
/// After this, bytes will have been _generated_, but not yet _serialized_ into a single stream.
/// Returns the final number of bytes the function will occupy in the target binary
pub fn finalize(
&mut self,
local_types: &[ValueType],
frame_size: i32,
frame_pointer: Option<LocalId>,
) {
self.build_local_declarations(local_types);
if let Some(frame_ptr_id) = frame_pointer {
let aligned_size = round_up_to_alignment(frame_size, FRAME_ALIGNMENT_BYTES);
self.build_stack_frame_push(aligned_size, frame_ptr_id);
self.build_stack_frame_pop(aligned_size, frame_ptr_id);
}
self.code.push(END);
let inner_len = self.preamble.len() + self.code.len() + self.insert_bytes.len();
encode_u32(&mut self.inner_length, inner_len as u32);
}
/// Write out all the bytes in the right order
pub fn serialize<W: std::io::Write>(&mut self, writer: &mut W) -> std::io::Result<()> {
writer.write_all(&self.inner_length)?;
writer.write_all(&self.preamble)?;
// We created each insertion when a local was used for the _second_ time.
// But we want them in the order they were first assigned, which may not be the same.
self.insert_locations.sort_by_key(|loc| loc.insert_at);
let mut pos: usize = 0;
for location in self.insert_locations.iter() {
writer.write_all(&self.code[pos..location.insert_at])?;
writer.write_all(&self.insert_bytes[location.start..location.end])?;
pos = location.insert_at;
}
let len = self.code.len();
writer.write_all(&self.code[pos..len])
}
/**********************************************************
INSTRUCTION HELPER METHODS
***********************************************************/
/// Base method for generating instructions
/// Emits the opcode and simulates VM stack push/pop
fn inst(&mut self, opcode: u8, pops: usize, push: bool) {
let new_len = self.vm_stack.len() - pops as usize;
self.vm_stack.truncate(new_len);
if push {
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
}
self.code.push(opcode);
}
fn inst_imm8(&mut self, opcode: u8, pops: usize, push: bool, immediate: u8) {
self.inst(opcode, pops, push);
self.code.push(immediate);
}
fn inst_imm32(&mut self, opcode: u8, pops: usize, push: bool, immediate: u32) {
self.inst(opcode, pops, push);
encode_u32(&mut self.code, immediate);
}
fn inst_mem(&mut self, opcode: u8, pops: usize, push: bool, align: Align, offset: u32) {
self.inst(opcode, pops, push);
self.code.push(align as u8);
encode_u32(&mut self.code, offset);
}
/**********************************************************
INSTRUCTION METHODS
One method for each Wasm instruction (in same order as the spec)
macros are for compactness & readability for the most common cases
Patterns that don't repeat very much don't have macros
***********************************************************/
instruction_no_args!(unreachable_, UNREACHABLE, 0, false);
instruction_no_args!(nop, NOP, 0, false);
pub fn block(&mut self, ty: BlockType) {
self.inst_imm8(BLOCK, 0, false, ty.as_byte());
}
pub fn loop_(&mut self, ty: BlockType) {
self.inst_imm8(LOOP, 0, false, ty.as_byte());
}
pub fn if_(&mut self, ty: BlockType) {
self.inst_imm8(IF, 1, false, ty.as_byte());
}
instruction_no_args!(else_, ELSE, 0, false);
instruction_no_args!(end, END, 0, false);
pub fn br(&mut self, levels: u32) {
self.inst_imm32(BR, 0, false, levels);
}
pub fn br_if(&mut self, levels: u32) {
self.inst_imm32(BRIF, 1, false, levels);
}
fn br_table() {
panic!("TODO");
}
instruction_no_args!(return_, RETURN, 0, false);
pub fn call(&mut self, function_index: u32, n_args: usize, has_return_val: bool) {
let stack_depth = self.vm_stack.len();
if n_args > stack_depth {
panic!(
"Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\n{:?}",
function_index, n_args, stack_depth, self
);
}
self.vm_stack.truncate(stack_depth - n_args);
if has_return_val {
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
}
self.code.push(CALL);
encode_u32(&mut self.code, function_index);
}
fn call_indirect() {
panic!("Not implemented. Roc doesn't use function pointers");
}
instruction_no_args!(drop_, DROP, 1, false);
instruction_no_args!(select, SELECT, 3, true);
pub fn get_local(&mut self, id: LocalId) {
self.inst_imm32(GETLOCAL, 0, true, id.0);
}
pub fn set_local(&mut self, id: LocalId) {
self.inst_imm32(SETLOCAL, 1, false, id.0);
}
pub fn tee_local(&mut self, id: LocalId) {
self.inst_imm32(TEELOCAL, 0, false, id.0);
}
pub fn get_global(&mut self, id: u32) {
self.inst_imm32(GETGLOBAL, 0, true, id);
}
pub fn set_global(&mut self, id: u32) {
self.inst_imm32(SETGLOBAL, 1, false, id);
}
instruction_memargs!(i32_load, I32LOAD, 1, true);
instruction_memargs!(i64_load, I64LOAD, 1, true);
instruction_memargs!(f32_load, F32LOAD, 1, true);
instruction_memargs!(f64_load, F64LOAD, 1, true);
instruction_memargs!(i32_load8_s, I32LOAD8S, 1, true);
instruction_memargs!(i32_load8_u, I32LOAD8U, 1, true);
instruction_memargs!(i32_load16_s, I32LOAD16S, 1, true);
instruction_memargs!(i32_load16_u, I32LOAD16U, 1, true);
instruction_memargs!(i64_load8_s, I64LOAD8S, 1, true);
instruction_memargs!(i64_load8_u, I64LOAD8U, 1, true);
instruction_memargs!(i64_load16_s, I64LOAD16S, 1, true);
instruction_memargs!(i64_load16_u, I64LOAD16U, 1, true);
instruction_memargs!(i64_load32_s, I64LOAD32S, 1, true);
instruction_memargs!(i64_load32_u, I64LOAD32U, 1, true);
instruction_memargs!(i32_store, I32STORE, 2, false);
instruction_memargs!(i64_store, I64STORE, 2, false);
instruction_memargs!(f32_store, F32STORE, 2, false);
instruction_memargs!(f64_store, F64STORE, 2, false);
instruction_memargs!(i32_store8, I32STORE8, 2, false);
instruction_memargs!(i32_store16, I32STORE16, 2, false);
instruction_memargs!(i64_store8, I64STORE8, 2, false);
instruction_memargs!(i64_store16, I64STORE16, 2, false);
instruction_memargs!(i64_store32, I64STORE32, 2, false);
pub fn memory_size(&mut self) {
self.inst_imm8(CURRENTMEMORY, 0, true, 0);
}
pub fn memory_grow(&mut self) {
self.inst_imm8(GROWMEMORY, 1, true, 0);
}
pub fn i32_const(&mut self, x: i32) {
self.inst(I32CONST, 0, true);
encode_i32(&mut self.code, x);
}
pub fn i64_const(&mut self, x: i64) {
self.inst(I64CONST, 0, true);
encode_i64(&mut self.code, x);
}
pub fn f32_const(&mut self, x: f32) {
self.inst(F32CONST, 0, true);
encode_f32(&mut self.code, x);
}
pub fn f64_const(&mut self, x: f64) {
self.inst(F64CONST, 0, true);
encode_f64(&mut self.code, x);
}
// TODO: Consider creating unified methods for numerical ops like 'eq' and 'add',
// passing the ValueType as an argument. Could simplify lowlevel code gen.
instruction_no_args!(i32_eqz, I32EQZ, 1, true);
instruction_no_args!(i32_eq, I32EQ, 2, true);
instruction_no_args!(i32_ne, I32NE, 2, true);
instruction_no_args!(i32_lt_s, I32LTS, 2, true);
instruction_no_args!(i32_lt_u, I32LTU, 2, true);
instruction_no_args!(i32_gt_s, I32GTS, 2, true);
instruction_no_args!(i32_gt_u, I32GTU, 2, true);
instruction_no_args!(i32_le_s, I32LES, 2, true);
instruction_no_args!(i32_le_u, I32LEU, 2, true);
instruction_no_args!(i32_ge_s, I32GES, 2, true);
instruction_no_args!(i32_ge_u, I32GEU, 2, true);
instruction_no_args!(i64_eqz, I64EQZ, 1, true);
instruction_no_args!(i64_eq, I64EQ, 2, true);
instruction_no_args!(i64_ne, I64NE, 2, true);
instruction_no_args!(i64_lt_s, I64LTS, 2, true);
instruction_no_args!(i64_lt_u, I64LTU, 2, true);
instruction_no_args!(i64_gt_s, I64GTS, 2, true);
instruction_no_args!(i64_gt_u, I64GTU, 2, true);
instruction_no_args!(i64_le_s, I64LES, 2, true);
instruction_no_args!(i64_le_u, I64LEU, 2, true);
instruction_no_args!(i64_ge_s, I64GES, 2, true);
instruction_no_args!(i64_ge_u, I64GEU, 2, true);
instruction_no_args!(f32_eq, F32EQ, 2, true);
instruction_no_args!(f32_ne, F32NE, 2, true);
instruction_no_args!(f32_lt, F32LT, 2, true);
instruction_no_args!(f32_gt, F32GT, 2, true);
instruction_no_args!(f32_le, F32LE, 2, true);
instruction_no_args!(f32_ge, F32GE, 2, true);
instruction_no_args!(f64_eq, F64EQ, 2, true);
instruction_no_args!(f64_ne, F64NE, 2, true);
instruction_no_args!(f64_lt, F64LT, 2, true);
instruction_no_args!(f64_gt, F64GT, 2, true);
instruction_no_args!(f64_le, F64LE, 2, true);
instruction_no_args!(f64_ge, F64GE, 2, true);
instruction_no_args!(i32_clz, I32CLZ, 1, true);
instruction_no_args!(i32_ctz, I32CTZ, 1, true);
instruction_no_args!(i32_popcnt, I32POPCNT, 1, true);
instruction_no_args!(i32_add, I32ADD, 2, true);
instruction_no_args!(i32_sub, I32SUB, 2, true);
instruction_no_args!(i32_mul, I32MUL, 2, true);
instruction_no_args!(i32_div_s, I32DIVS, 2, true);
instruction_no_args!(i32_div_u, I32DIVU, 2, true);
instruction_no_args!(i32_rem_s, I32REMS, 2, true);
instruction_no_args!(i32_rem_u, I32REMU, 2, true);
instruction_no_args!(i32_and, I32AND, 2, true);
instruction_no_args!(i32_or, I32OR, 2, true);
instruction_no_args!(i32_xor, I32XOR, 2, true);
instruction_no_args!(i32_shl, I32SHL, 2, true);
instruction_no_args!(i32_shr_s, I32SHRS, 2, true);
instruction_no_args!(i32_shr_u, I32SHRU, 2, true);
instruction_no_args!(i32_rotl, I32ROTL, 2, true);
instruction_no_args!(i32_rotr, I32ROTR, 2, true);
instruction_no_args!(i64_clz, I64CLZ, 1, true);
instruction_no_args!(i64_ctz, I64CTZ, 1, true);
instruction_no_args!(i64_popcnt, I64POPCNT, 1, true);
instruction_no_args!(i64_add, I64ADD, 2, true);
instruction_no_args!(i64_sub, I64SUB, 2, true);
instruction_no_args!(i64_mul, I64MUL, 2, true);
instruction_no_args!(i64_div_s, I64DIVS, 2, true);
instruction_no_args!(i64_div_u, I64DIVU, 2, true);
instruction_no_args!(i64_rem_s, I64REMS, 2, true);
instruction_no_args!(i64_rem_u, I64REMU, 2, true);
instruction_no_args!(i64_and, I64AND, 2, true);
instruction_no_args!(i64_or, I64OR, 2, true);
instruction_no_args!(i64_xor, I64XOR, 2, true);
instruction_no_args!(i64_shl, I64SHL, 2, true);
instruction_no_args!(i64_shr_s, I64SHRS, 2, true);
instruction_no_args!(i64_shr_u, I64SHRU, 2, true);
instruction_no_args!(i64_rotl, I64ROTL, 2, true);
instruction_no_args!(i64_rotr, I64ROTR, 2, true);
instruction_no_args!(f32_abs, F32ABS, 1, true);
instruction_no_args!(f32_neg, F32NEG, 1, true);
instruction_no_args!(f32_ceil, F32CEIL, 1, true);
instruction_no_args!(f32_floor, F32FLOOR, 1, true);
instruction_no_args!(f32_trunc, F32TRUNC, 1, true);
instruction_no_args!(f32_nearest, F32NEAREST, 1, true);
instruction_no_args!(f32_sqrt, F32SQRT, 1, true);
instruction_no_args!(f32_add, F32ADD, 2, true);
instruction_no_args!(f32_sub, F32SUB, 2, true);
instruction_no_args!(f32_mul, F32MUL, 2, true);
instruction_no_args!(f32_div, F32DIV, 2, true);
instruction_no_args!(f32_min, F32MIN, 2, true);
instruction_no_args!(f32_max, F32MAX, 2, true);
instruction_no_args!(f32_copysign, F32COPYSIGN, 2, true);
instruction_no_args!(f64_abs, F64ABS, 1, true);
instruction_no_args!(f64_neg, F64NEG, 1, true);
instruction_no_args!(f64_ceil, F64CEIL, 1, true);
instruction_no_args!(f64_floor, F64FLOOR, 1, true);
instruction_no_args!(f64_trunc, F64TRUNC, 1, true);
instruction_no_args!(f64_nearest, F64NEAREST, 1, true);
instruction_no_args!(f64_sqrt, F64SQRT, 1, true);
instruction_no_args!(f64_add, F64ADD, 2, true);
instruction_no_args!(f64_sub, F64SUB, 2, true);
instruction_no_args!(f64_mul, F64MUL, 2, true);
instruction_no_args!(f64_div, F64DIV, 2, true);
instruction_no_args!(f64_min, F64MIN, 2, true);
instruction_no_args!(f64_max, F64MAX, 2, true);
instruction_no_args!(f64_copysign, F64COPYSIGN, 2, true);
instruction_no_args!(i32_wrap_i64, I32WRAPI64, 1, true);
instruction_no_args!(i32_trunc_s_f32, I32TRUNCSF32, 1, true);
instruction_no_args!(i32_trunc_u_f32, I32TRUNCUF32, 1, true);
instruction_no_args!(i32_trunc_s_f64, I32TRUNCSF64, 1, true);
instruction_no_args!(i32_trunc_u_f64, I32TRUNCUF64, 1, true);
instruction_no_args!(i64_extend_s_i32, I64EXTENDSI32, 1, true);
instruction_no_args!(i64_extend_u_i32, I64EXTENDUI32, 1, true);
instruction_no_args!(i64_trunc_s_f32, I64TRUNCSF32, 1, true);
instruction_no_args!(i64_trunc_u_f32, I64TRUNCUF32, 1, true);
instruction_no_args!(i64_trunc_s_f64, I64TRUNCSF64, 1, true);
instruction_no_args!(i64_trunc_u_f64, I64TRUNCUF64, 1, true);
instruction_no_args!(f32_convert_s_i32, F32CONVERTSI32, 1, true);
instruction_no_args!(f32_convert_u_i32, F32CONVERTUI32, 1, true);
instruction_no_args!(f32_convert_s_i64, F32CONVERTSI64, 1, true);
instruction_no_args!(f32_convert_u_i64, F32CONVERTUI64, 1, true);
instruction_no_args!(f32_demote_f64, F32DEMOTEF64, 1, true);
instruction_no_args!(f64_convert_s_i32, F64CONVERTSI32, 1, true);
instruction_no_args!(f64_convert_u_i32, F64CONVERTUI32, 1, true);
instruction_no_args!(f64_convert_s_i64, F64CONVERTSI64, 1, true);
instruction_no_args!(f64_convert_u_i64, F64CONVERTUI64, 1, true);
instruction_no_args!(f64_promote_f32, F64PROMOTEF32, 1, true);
instruction_no_args!(i32_reinterpret_f32, I32REINTERPRETF32, 1, true);
instruction_no_args!(i64_reinterpret_f64, I64REINTERPRETF64, 1, true);
instruction_no_args!(f32_reinterpret_i32, F32REINTERPRETI32, 1, true);
instruction_no_args!(f64_reinterpret_i64, F64REINTERPRETI64, 1, true);
} }

View file

@ -1,7 +1,6 @@
use parity_wasm::elements::ValueType;
use roc_mono::layout::{Layout, UnionLayout}; use roc_mono::layout::{Layout, UnionLayout};
use crate::{PTR_SIZE, PTR_TYPE}; use crate::{code_builder::ValueType, PTR_SIZE, PTR_TYPE};
// See README for background information on Wasm locals, memory and function calls // See README for background information on Wasm locals, memory and function calls
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -1,33 +1,36 @@
mod backend; mod backend;
mod code_builder;
pub mod from_wasm32_memory; pub mod from_wasm32_memory;
mod layout; mod layout;
mod storage; mod storage;
#[allow(dead_code)]
pub mod code_builder;
#[allow(dead_code)]
mod opcodes;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::elements::{Instruction, Instruction::*, Internal, ValueType};
use parity_wasm::elements::{Instruction, Internal, Module, Section};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds; use roc_mono::layout::LayoutIds;
use crate::backend::WasmBackend; use crate::backend::WasmBackend;
use crate::code_builder::CodeBuilder; use crate::code_builder::{Align, CodeBuilder, ValueType};
const PTR_SIZE: u32 = 4; const PTR_SIZE: u32 = 4;
const PTR_TYPE: ValueType = ValueType::I32; const PTR_TYPE: ValueType = ValueType::I32;
// All usages of these alignment constants take u32, so an enum wouldn't add any safety.
pub const ALIGN_1: u32 = 0;
pub const ALIGN_2: u32 = 1;
pub const ALIGN_4: u32 = 2;
pub const ALIGN_8: u32 = 3;
pub const STACK_POINTER_GLOBAL_ID: u32 = 0; pub const STACK_POINTER_GLOBAL_ID: u32 = 0;
pub const STACK_ALIGNMENT_BYTES: i32 = 16; pub const FRAME_ALIGNMENT_BYTES: i32 = 16;
/// Code section ID from spec
/// https://webassembly.github.io/spec/core/binary/modules.html#sections
pub const CODE_SECTION_ID: u8 = 10;
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LocalId(pub u32); pub struct LocalId(pub u32);
@ -42,8 +45,10 @@ pub fn build_module<'a>(
env: &'a Env, env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<std::vec::Vec<u8>, String> { ) -> Result<std::vec::Vec<u8>, String> {
let (builder, _) = build_module_help(env, procedures)?; let (builder, code_section_bytes, _) = build_module_help(env, procedures)?;
let module = builder.build(); let mut module = builder.build();
replace_code_section(&mut module, code_section_bytes);
module module
.into_bytes() .into_bytes()
.map_err(|e| -> String { format!("Error serialising Wasm module {:?}", e) }) .map_err(|e| -> String { format!("Error serialising Wasm module {:?}", e) })
@ -52,8 +57,8 @@ pub fn build_module<'a>(
pub fn build_module_help<'a>( pub fn build_module_help<'a>(
env: &'a Env, env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<(builder::ModuleBuilder, u32), String> { ) -> Result<(builder::ModuleBuilder, std::vec::Vec<u8>, u32), String> {
let mut backend = WasmBackend::new(env); let mut backend = WasmBackend::new(env, procedures.len());
let mut layout_ids = LayoutIds::default(); let mut layout_ids = LayoutIds::default();
// Sort procedures by occurrence order // Sort procedures by occurrence order
@ -86,6 +91,10 @@ pub fn build_module_help<'a>(
} }
} }
// Update code section length
let inner_length = (backend.code_section_bytes.len() - 5) as u32;
overwrite_padded_u32(&mut backend.code_section_bytes[0..5], inner_length);
// Because of the sorting above, we know the last function in the `for` is the main function. // 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. // 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. // This is a workaround until we implement object files with symbols and relocations.
@ -105,23 +114,32 @@ pub fn build_module_help<'a>(
backend.module_builder.push_export(memory_export); backend.module_builder.push_export(memory_export);
let stack_pointer_global = builder::global() let stack_pointer_global = builder::global()
.with_type(PTR_TYPE) .with_type(parity_wasm::elements::ValueType::I32)
.mutable() .mutable()
.init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32)) .init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32))
.build(); .build();
backend.module_builder.push_global(stack_pointer_global); backend.module_builder.push_global(stack_pointer_global);
Ok((backend.module_builder, main_function_index)) Ok((
backend.module_builder,
backend.code_section_bytes,
main_function_index,
))
} }
fn encode_alignment(bytes: u32) -> u32 { /// Replace parity-wasm's code section with our own handmade one
match bytes { pub fn replace_code_section(module: &mut Module, code_section_bytes: std::vec::Vec<u8>) {
1 => ALIGN_1, let sections = module.sections_mut();
2 => ALIGN_2, let mut code_section_index = usize::MAX;
4 => ALIGN_4, for (i, s) in sections.iter().enumerate() {
8 => ALIGN_8, if let Section::Code(_) = s {
_ => panic!("{:?}-byte alignment is not supported", bytes), code_section_index = i;
}
} }
sections[code_section_index] = Section::Unparsed {
id: CODE_SECTION_ID,
payload: code_section_bytes,
};
} }
pub struct CopyMemoryConfig { pub struct CopyMemoryConfig {
@ -138,33 +156,27 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) {
return; return;
} }
let alignment_flag = encode_alignment(config.alignment_bytes); let alignment = Align::from(config.alignment_bytes);
let mut i = 0; let mut i = 0;
while config.size - i >= 8 { while config.size - i >= 8 {
code_builder.extend_from_slice(&[ code_builder.get_local(config.to_ptr);
GetLocal(config.to_ptr.0), code_builder.get_local(config.from_ptr);
GetLocal(config.from_ptr.0), code_builder.i64_load(alignment, i + config.from_offset);
I64Load(alignment_flag, i + config.from_offset), code_builder.i64_store(alignment, i + config.to_offset);
I64Store(alignment_flag, i + config.to_offset),
]);
i += 8; i += 8;
} }
if config.size - i >= 4 { if config.size - i >= 4 {
code_builder.extend_from_slice(&[ code_builder.get_local(config.to_ptr);
GetLocal(config.to_ptr.0), code_builder.get_local(config.from_ptr);
GetLocal(config.from_ptr.0), code_builder.i32_load(alignment, i + config.from_offset);
I32Load(alignment_flag, i + config.from_offset), code_builder.i32_store(alignment, i + config.to_offset);
I32Store(alignment_flag, i + config.to_offset),
]);
i += 4; i += 4;
} }
while config.size - i > 0 { while config.size - i > 0 {
code_builder.extend_from_slice(&[ code_builder.get_local(config.to_ptr);
GetLocal(config.to_ptr.0), code_builder.get_local(config.from_ptr);
GetLocal(config.from_ptr.0), code_builder.i32_load8_u(alignment, i + config.from_offset);
I32Load8U(alignment_flag, i + config.from_offset), code_builder.i32_store8(alignment, i + config.to_offset);
I32Store8(alignment_flag, i + config.to_offset),
]);
i += 1; i += 1;
} }
} }
@ -178,31 +190,87 @@ pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 {
aligned aligned
} }
pub fn push_stack_frame( pub fn debug_panic<E: std::fmt::Debug>(error: E) {
instructions: &mut std::vec::Vec<Instruction>, panic!("{:?}", error);
size: i32,
local_frame_pointer: LocalId,
) {
let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES);
instructions.extend([
GetGlobal(STACK_POINTER_GLOBAL_ID),
I32Const(aligned_size),
I32Sub,
TeeLocal(local_frame_pointer.0),
SetGlobal(STACK_POINTER_GLOBAL_ID),
]);
} }
pub fn pop_stack_frame( /// Write an unsigned value into the provided buffer in LEB-128 format, returning byte length
instructions: &mut std::vec::Vec<Instruction>, ///
size: i32, /// All integers in Wasm are variable-length encoded, which saves space for small values.
local_frame_pointer: LocalId, /// The most significant bit indicates "more bytes are coming", and the other 7 are payload.
) { macro_rules! encode_uleb128 {
let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES); ($name: ident, $ty: ty) => {
instructions.extend([ pub fn $name<'a>(buffer: &mut Vec<'a, u8>, value: $ty) -> usize {
GetLocal(local_frame_pointer.0), let mut x = value;
I32Const(aligned_size), let start_len = buffer.len();
I32Add, while x >= 0x80 {
SetGlobal(STACK_POINTER_GLOBAL_ID), buffer.push(0x80 | ((x & 0x7f) as u8));
]); x >>= 7;
}
buffer.push(x as u8);
buffer.len() - start_len
}
};
}
encode_uleb128!(encode_u32, u32);
encode_uleb128!(encode_u64, u64);
/// Write a *signed* value into the provided buffer in LEB-128 format, returning byte length
macro_rules! encode_sleb128 {
($name: ident, $ty: ty) => {
pub fn $name<'a>(buffer: &mut Vec<'a, u8>, value: $ty) -> usize {
let mut x = value;
let start_len = buffer.len();
loop {
let byte = (x & 0x7f) as u8;
x >>= 7;
let byte_is_negative = (byte & 0x40) != 0;
if ((x == 0 && !byte_is_negative) || (x == -1 && byte_is_negative)) {
buffer.push(byte);
break;
}
buffer.push(byte | 0x80);
}
buffer.len() - start_len
}
};
}
encode_sleb128!(encode_i32, i32);
encode_sleb128!(encode_i64, i64);
/// No LEB encoding, and always little-endian regardless of compiler host.
macro_rules! encode_float {
($name: ident, $ty: ty) => {
pub fn $name<'a>(buffer: &mut Vec<'a, u8>, value: $ty) {
let mut x = value.to_bits();
let size = std::mem::size_of::<$ty>();
for _ in 0..size {
buffer.push((x & 0xff) as u8);
x >>= 8;
}
}
};
}
encode_float!(encode_f32, f32);
encode_float!(encode_f64, f64);
/// Overwrite a LEB-128 encoded u32 value, padded to maximum length (5 bytes)
///
/// We need some fixed-length values so we can overwrite them without moving all following bytes.
/// Many parts of the binary format are prefixed with their length, which we only know at the end.
/// And relocation values get updated during linking.
/// This can help us to avoid copies, which is good for speed, but there's a tradeoff with output size.
///
/// The value 3 is encoded as 0x83 0x80 0x80 0x80 0x00.
/// https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#relocation-sections
pub fn overwrite_padded_u32(buffer: &mut [u8], value: u32) {
let mut x = value;
for byte in buffer.iter_mut().take(4) {
*byte = 0x80 | ((x & 0x7f) as u8);
x >>= 7;
}
buffer[4] = x as u8;
} }

View file

@ -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;

View file

@ -1,16 +1,12 @@
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use parity_wasm::elements::{Instruction::*, ValueType};
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState}; use crate::code_builder::{CodeBuilder, ValueType, VirtualMachineSymbolState};
use crate::layout::WasmLayout; use crate::layout::WasmLayout;
use crate::{ use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, PTR_SIZE, PTR_TYPE};
copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4,
ALIGN_8, PTR_SIZE, PTR_TYPE,
};
pub enum StoredValueKind { pub enum StoredValueKind {
Parameter, Parameter,
@ -241,7 +237,7 @@ impl<'a> Storage<'a> {
location: StackMemoryLocation::PointerArg(local_id), location: StackMemoryLocation::PointerArg(local_id),
.. ..
} => { } => {
code_builder.push(GetLocal(local_id.0)); code_builder.get_local(local_id);
code_builder.set_top_symbol(*sym); code_builder.set_top_symbol(*sym);
} }
@ -249,11 +245,9 @@ impl<'a> Storage<'a> {
location: StackMemoryLocation::FrameOffset(offset), location: StackMemoryLocation::FrameOffset(offset),
.. ..
} => { } => {
code_builder.extend_from_slice(&[ code_builder.get_local(self.stack_frame_pointer.unwrap());
GetLocal(self.stack_frame_pointer.unwrap().0), code_builder.i32_const(offset as i32);
I32Const(offset as i32), code_builder.i32_add();
I32Add,
]);
code_builder.set_top_symbol(*sym); code_builder.set_top_symbol(*sym);
} }
} }
@ -297,20 +291,20 @@ impl<'a> Storage<'a> {
| StoredValue::Local { | StoredValue::Local {
value_type, size, .. value_type, size, ..
} => { } => {
let store_instruction = match (value_type, size) { use crate::code_builder::Align::*;
(ValueType::I64, 8) => I64Store(ALIGN_8, to_offset), code_builder.get_local(to_ptr);
(ValueType::I32, 4) => I32Store(ALIGN_4, to_offset), self.load_symbols(code_builder, &[from_symbol]);
(ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset), match (value_type, size) {
(ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset), (ValueType::I64, 8) => code_builder.i64_store(Bytes8, to_offset),
(ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), (ValueType::I32, 4) => code_builder.i32_store(Bytes4, to_offset),
(ValueType::F64, 8) => F64Store(ALIGN_8, 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); 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 size
} }
} }
@ -343,7 +337,7 @@ impl<'a> Storage<'a> {
debug_assert!(to_value_type == from_value_type); debug_assert!(to_value_type == from_value_type);
debug_assert!(to_size == from_size); debug_assert!(to_size == from_size);
self.load_symbols(code_builder, &[from_symbol]); 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()); self.symbol_storage_map.insert(from_symbol, to.clone());
} }
@ -361,8 +355,8 @@ impl<'a> Storage<'a> {
) => { ) => {
debug_assert!(to_value_type == from_value_type); debug_assert!(to_value_type == from_value_type);
debug_assert!(to_size == from_size); debug_assert!(to_size == from_size);
code_builder code_builder.get_local(*from_local_id);
.extend_from_slice(&[GetLocal(from_local_id.0), SetLocal(to_local_id.0)]); code_builder.set_local(*to_local_id);
} }
( (
@ -421,7 +415,7 @@ impl<'a> Storage<'a> {
let local_id = self.get_next_local_id(); let local_id = self.get_next_local_id();
if vm_state != VirtualMachineSymbolState::NotYetPushed { if vm_state != VirtualMachineSymbolState::NotYetPushed {
code_builder.load_symbol(symbol, vm_state, local_id); 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); self.local_types.push(value_type);

View file

@ -4,6 +4,7 @@ use std::hash::{Hash, Hasher};
use roc_can::builtins::builtin_defs_map; use roc_can::builtins::builtin_defs_map;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_gen_wasm::replace_code_section;
// use roc_std::{RocDec, RocList, RocOrder, RocStr}; // use roc_std::{RocDec, RocList, RocOrder, RocStr};
use crate::helpers::wasm32_test_result::Wasm32TestResult; use crate::helpers::wasm32_test_result::Wasm32TestResult;
use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory;
@ -102,11 +103,20 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
exposed_to_host, 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(); roc_gen_wasm::build_module_help(&env, procedures).unwrap();
T::insert_test_wrapper(&mut builder, TEST_WRAPPER_NAME, main_function_index);
let module_bytes = builder.build().into_bytes().unwrap(); T::insert_test_wrapper(
arena,
&mut builder,
&mut code_section_bytes,
TEST_WRAPPER_NAME,
main_function_index,
);
let mut parity_module = builder.build();
replace_code_section(&mut parity_module, code_section_bytes);
let module_bytes = parity_module.into_bytes().unwrap();
// for debugging (e.g. with wasm2wat) // for debugging (e.g. with wasm2wat)
if false { if false {
@ -138,7 +148,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
let store = Store::default(); let store = Store::default();
// let module = Module::from_file(&store, &test_wasm_path).unwrap(); // 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` // First, we create the `WasiEnv`
use wasmer_wasi::WasiState; use wasmer_wasi::WasiState;
@ -147,10 +157,10 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
// Then, we get the import object related to our WASI // Then, we get the import object related to our WASI
// and attach it to the Wasm instance. // and attach it to the Wasm instance.
let import_object = wasi_env let import_object = wasi_env
.import_object(&module) .import_object(&wasmer_module)
.unwrap_or_else(|_| wasmer::imports!()); .unwrap_or_else(|_| wasmer::imports!());
Instance::new(&module, &import_object).unwrap() Instance::new(&wasmer_module, &import_object).unwrap()
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -188,7 +198,7 @@ where
macro_rules! assert_wasm_evals_to { macro_rules! assert_wasm_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
match $crate::helpers::eval::assert_wasm_evals_to_help::<$ty>($src, $expected) { match $crate::helpers::eval::assert_wasm_evals_to_help::<$ty>($src, $expected) {
Err(msg) => println!("{:?}", msg), Err(msg) => panic!("{:?}", msg),
Ok(actual) => { Ok(actual) => {
assert_eq!($transform(actual), $expected) assert_eq!($transform(actual), $expected)
} }

View file

@ -1,130 +1,118 @@
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::builder::ModuleBuilder; use parity_wasm::elements::Internal;
use parity_wasm::elements::{
Instruction, Instruction::*, Instructions, Internal, Local, ValueType,
};
use roc_gen_wasm::code_builder::{Align, CodeBuilder, ValueType};
use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory;
use roc_gen_wasm::*; use roc_gen_wasm::{overwrite_padded_u32, LocalId};
use roc_std::{RocDec, RocList, RocOrder, RocStr}; use roc_std::{RocDec, RocList, RocOrder, RocStr};
const STACK_POINTER_LOCAL_ID: u32 = 0;
pub trait Wasm32TestResult { pub trait Wasm32TestResult {
fn insert_test_wrapper( fn insert_test_wrapper<'a>(
module_builder: &mut ModuleBuilder, arena: &'a bumpalo::Bump,
module_builder: &mut builder::ModuleBuilder,
code_section_bytes: &mut std::vec::Vec<u8>,
wrapper_name: &str, wrapper_name: &str,
main_function_index: u32, main_function_index: u32,
) { ) {
let instructions = Self::build_wrapper_body(main_function_index); let signature = builder::signature()
.with_result(parity_wasm::elements::ValueType::I32)
.build_sig();
let signature = builder::signature().with_result(ValueType::I32).build_sig(); // parity-wasm FunctionDefinition with no instructions
let empty_fn_def = builder::function().with_signature(signature).build();
let stack_frame_pointer = Local::new(1, ValueType::I32); let location = module_builder.push_function(empty_fn_def);
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);
let export = builder::export() let export = builder::export()
.field(wrapper_name) .field(wrapper_name)
.with_internal(Internal::Function(location.body)) .with_internal(Internal::Function(location.body))
.build(); .build();
module_builder.push_export(export); module_builder.push_export(export);
let mut code_builder = CodeBuilder::new(arena);
Self::build_wrapper_body(&mut code_builder, main_function_index);
code_builder.serialize(code_section_bytes).unwrap();
let mut num_procs = 0;
for (i, byte) in code_section_bytes[5..10].iter().enumerate() {
num_procs += ((byte & 0x7f) as u32) << (i * 7);
}
let inner_length = (code_section_bytes.len() - 5) as u32;
overwrite_padded_u32(&mut code_section_bytes[0..5], inner_length);
overwrite_padded_u32(&mut code_section_bytes[5..10], num_procs + 1);
} }
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction>; fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32);
} }
macro_rules! build_wrapper_body_primitive { macro_rules! build_wrapper_body_primitive {
($store_instruction: expr, $align: expr) => { ($store_instruction: ident, $align: expr) => {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
let size: i32 = 8; let frame_pointer_id = LocalId(0);
let mut instructions = Vec::with_capacity(16); let frame_pointer = Some(frame_pointer_id);
push_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); let local_types = &[ValueType::I32];
instructions.extend([ let frame_size = 8;
// load result address to prepare for the store instruction later
GetLocal(STACK_POINTER_LOCAL_ID), code_builder.get_local(frame_pointer_id);
// code_builder.call(main_function_index, 0, true);
// Call the main function with no arguments. Get primitive back. code_builder.$store_instruction($align, 0);
Call(main_function_index), code_builder.get_local(frame_pointer_id);
//
// Store the primitive at the allocated address code_builder.finalize(local_types, frame_size, frame_pointer);
$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
} }
}; };
} }
macro_rules! wasm_test_result_primitive { 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 { impl Wasm32TestResult for $type_name {
build_wrapper_body_primitive!($store_instruction, $align); build_wrapper_body_primitive!($store_instruction, $align);
} }
}; };
} }
fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec<Instruction> { fn build_wrapper_body_stack_memory(
let mut instructions = Vec::with_capacity(16); code_builder: &mut CodeBuilder,
push_stack_frame( main_function_index: u32,
&mut instructions, size: usize,
size as i32, ) {
LocalId(STACK_POINTER_LOCAL_ID), let local_id = LocalId(0);
); let local_types = &[ValueType::I32];
instructions.extend([ let frame_pointer = Some(local_id);
//
// Call the main function with the allocated address to write the result. code_builder.get_local(local_id);
// No value is returned to the VM stack. This is the same as in compiled C. code_builder.call(main_function_index, 0, true);
GetLocal(STACK_POINTER_LOCAL_ID), code_builder.get_local(local_id);
Call(main_function_index), code_builder.finalize(local_types, size as i32, frame_pointer);
//
// 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
} }
macro_rules! wasm_test_result_stack_memory { macro_rules! wasm_test_result_stack_memory {
($type_name: ident) => { ($type_name: ident) => {
impl Wasm32TestResult for $type_name { impl Wasm32TestResult for $type_name {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(main_function_index, $type_name::ACTUAL_WIDTH) 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!(bool, i32_store8, Align::Bytes1);
wasm_test_result_primitive!(RocOrder, I32Store8, ALIGN_1); wasm_test_result_primitive!(RocOrder, i32_store8, Align::Bytes1);
wasm_test_result_primitive!(u8, I32Store8, ALIGN_1); wasm_test_result_primitive!(u8, i32_store8, Align::Bytes1);
wasm_test_result_primitive!(i8, I32Store8, ALIGN_1); wasm_test_result_primitive!(i8, i32_store8, Align::Bytes1);
wasm_test_result_primitive!(u16, I32Store16, ALIGN_2); wasm_test_result_primitive!(u16, i32_store16, Align::Bytes2);
wasm_test_result_primitive!(i16, I32Store16, ALIGN_2); wasm_test_result_primitive!(i16, i32_store16, Align::Bytes2);
wasm_test_result_primitive!(u32, I32Store, ALIGN_4); wasm_test_result_primitive!(u32, i32_store, Align::Bytes4);
wasm_test_result_primitive!(i32, I32Store, ALIGN_4); wasm_test_result_primitive!(i32, i32_store, Align::Bytes4);
wasm_test_result_primitive!(u64, I64Store, ALIGN_8); wasm_test_result_primitive!(u64, i64_store, Align::Bytes8);
wasm_test_result_primitive!(i64, I64Store, ALIGN_8); wasm_test_result_primitive!(i64, i64_store, Align::Bytes8);
wasm_test_result_primitive!(f32, F32Store, ALIGN_8); wasm_test_result_primitive!(f32, f32_store, Align::Bytes8);
wasm_test_result_primitive!(f64, F64Store, ALIGN_8); wasm_test_result_primitive!(f64, f64_store, Align::Bytes8);
wasm_test_result_stack_memory!(u128); wasm_test_result_stack_memory!(u128);
wasm_test_result_stack_memory!(i128); wasm_test_result_stack_memory!(i128);
@ -132,21 +120,21 @@ wasm_test_result_stack_memory!(RocDec);
wasm_test_result_stack_memory!(RocStr); wasm_test_result_stack_memory!(RocStr);
impl<T: Wasm32TestResult> Wasm32TestResult for RocList<T> { impl<T: Wasm32TestResult> Wasm32TestResult for RocList<T> {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(main_function_index, 12) build_wrapper_body_stack_memory(code_builder, main_function_index, 12)
} }
} }
impl<T: Wasm32TestResult> Wasm32TestResult for &'_ T { impl<T: Wasm32TestResult> Wasm32TestResult for &'_ T {
build_wrapper_body_primitive!(I32Store, ALIGN_4); build_wrapper_body_primitive!(i32_store, Align::Bytes4);
} }
impl<T, const N: usize> Wasm32TestResult for [T; N] impl<T, const N: usize> Wasm32TestResult for [T; N]
where where
T: Wasm32TestResult + FromWasm32Memory, T: Wasm32TestResult + FromWasm32Memory,
{ {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(main_function_index, N * T::ACTUAL_WIDTH) build_wrapper_body_stack_memory(code_builder, main_function_index, N * T::ACTUAL_WIDTH)
} }
} }
@ -155,8 +143,12 @@ where
T: Wasm32TestResult + FromWasm32Memory, T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory, U: Wasm32TestResult + FromWasm32Memory,
{ {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(main_function_index, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH) build_wrapper_body_stack_memory(
code_builder,
main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH,
)
} }
} }
@ -166,8 +158,9 @@ where
U: Wasm32TestResult + FromWasm32Memory, U: Wasm32TestResult + FromWasm32Memory,
V: Wasm32TestResult + FromWasm32Memory, V: Wasm32TestResult + FromWasm32Memory,
{ {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory( build_wrapper_body_stack_memory(
code_builder,
main_function_index, main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH,
) )
@ -181,8 +174,9 @@ where
V: Wasm32TestResult + FromWasm32Memory, V: Wasm32TestResult + FromWasm32Memory,
W: Wasm32TestResult + FromWasm32Memory, W: Wasm32TestResult + FromWasm32Memory,
{ {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory( build_wrapper_body_stack_memory(
code_builder,
main_function_index, main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH,
) )
@ -197,8 +191,9 @@ where
W: Wasm32TestResult + FromWasm32Memory, W: Wasm32TestResult + FromWasm32Memory,
X: Wasm32TestResult + FromWasm32Memory, X: Wasm32TestResult + FromWasm32Memory,
{ {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory( build_wrapper_body_stack_memory(
code_builder,
main_function_index, main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH,
) )
@ -214,8 +209,9 @@ where
X: Wasm32TestResult + FromWasm32Memory, X: Wasm32TestResult + FromWasm32Memory,
Y: Wasm32TestResult + FromWasm32Memory, Y: Wasm32TestResult + FromWasm32Memory,
{ {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory( build_wrapper_body_stack_memory(
code_builder,
main_function_index, main_function_index,
T::ACTUAL_WIDTH T::ACTUAL_WIDTH
+ U::ACTUAL_WIDTH + U::ACTUAL_WIDTH
@ -237,8 +233,9 @@ where
Y: Wasm32TestResult + FromWasm32Memory, Y: Wasm32TestResult + FromWasm32Memory,
Z: Wasm32TestResult + FromWasm32Memory, Z: Wasm32TestResult + FromWasm32Memory,
{ {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory( build_wrapper_body_stack_memory(
code_builder,
main_function_index, main_function_index,
T::ACTUAL_WIDTH T::ACTUAL_WIDTH
+ U::ACTUAL_WIDTH + U::ACTUAL_WIDTH
@ -262,8 +259,9 @@ where
Z: Wasm32TestResult + FromWasm32Memory, Z: Wasm32TestResult + FromWasm32Memory,
A: Wasm32TestResult + FromWasm32Memory, A: Wasm32TestResult + FromWasm32Memory,
{ {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory( build_wrapper_body_stack_memory(
code_builder,
main_function_index, main_function_index,
T::ACTUAL_WIDTH T::ACTUAL_WIDTH
+ U::ACTUAL_WIDTH + U::ACTUAL_WIDTH

View file

@ -1053,6 +1053,7 @@ define_builtins! {
32 LIST_DROP: "drop" 32 LIST_DROP: "drop"
33 LIST_SWAP: "swap" 33 LIST_SWAP: "swap"
34 LIST_DROP_AT: "dropAt" 34 LIST_DROP_AT: "dropAt"
35 LIST_DROP_LAST: "dropLast"
} }
5 RESULT: "Result" => { 5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -3742,6 +3742,18 @@ mod solve_expr {
"# "#
), ),
"Str -> Str", "Str -> Str",
);
}
#[test]
fn list_drop_last() {
infer_eq_without_problem(
indoc!(
r#"
List.dropLast
"#
),
"List a -> List a",
); );
} }

View file

@ -235,6 +235,38 @@ fn list_drop_at_shared() {
); );
} }
#[test]
fn list_drop_last() {
assert_evals_to!(
"List.dropLast [1, 2, 3]",
RocList::from_slice(&[1, 2]),
RocList<i64>
);
assert_evals_to!("List.dropLast []", RocList::from_slice(&[]), RocList<i64>);
assert_evals_to!("List.dropLast [0]", RocList::from_slice(&[]), RocList<i64>);
}
#[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<i64>, RocList<i64>,)
);
}
#[test] #[test]
fn list_swap() { fn list_swap() {
assert_evals_to!("List.swap [] 0 1", RocList::from_slice(&[]), RocList<i64>); assert_evals_to!("List.swap [] 0 1", RocList::from_slice(&[]), RocList<i64>);