mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
Merge branch 'trunk' into str_trim
This commit is contained in:
commit
29bd4e3e50
14 changed files with 1169 additions and 622 deletions
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
178
compiler/gen_wasm/src/opcodes.rs
Normal file
178
compiler/gen_wasm/src/opcodes.rs
Normal 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;
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue