Switch over to function_builder

This commit is contained in:
Brian Carroll 2021-10-23 01:31:21 +02:00
parent bca9f31c58
commit 74e3239a1c
6 changed files with 204 additions and 291 deletions

View file

@ -1,9 +1,6 @@
use bumpalo::collections::Vec;
use parity_wasm::builder;
use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder, SignatureBuilder};
use parity_wasm::elements::{
BlockType, Instruction, Instruction::*, Instructions, Local, ValueType,
};
use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder};
use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel;
@ -11,12 +8,10 @@ use roc_module::symbol::Symbol;
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Builtin, Layout};
use crate::code_builder::CodeBuilder;
use crate::function_builder::{BlockType, FunctionBuilder, ValueType};
use crate::layout::WasmLayout;
use crate::storage::{Storage, StoredValue, StoredValueKind};
use crate::{
copy_memory, pop_stack_frame, push_stack_frame, CopyMemoryConfig, Env, LocalId, PTR_TYPE,
};
use crate::{copy_memory, CopyMemoryConfig, Env, LocalId, PTR_TYPE};
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
// Follow Emscripten's example by using 1kB (4 bytes would probably do)
@ -37,7 +32,7 @@ pub struct WasmBackend<'a> {
proc_symbol_map: MutMap<Symbol, CodeLocation>,
// Function level
code_builder: CodeBuilder<'a>,
code_builder: FunctionBuilder<'a>,
storage: Storage<'a>,
/// how many blocks deep are we (used for jumps)
@ -61,7 +56,7 @@ impl<'a> WasmBackend<'a> {
joinpoint_label_map: MutMap::default(),
// Functions
code_builder: CodeBuilder::new(env.arena),
code_builder: FunctionBuilder::new(env.arena),
storage: Storage::new(env.arena),
}
}
@ -82,21 +77,24 @@ impl<'a> WasmBackend<'a> {
pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> {
// println!("\ngenerating procedure {:?}\n", sym);
let signature_builder = self.start_proc(&proc);
// Use parity-wasm to add the signature in "types" and "functions" sections
// but no instructions, since we are building our own code section
let empty_function_def = self.start_proc(&proc);
let location = self.module_builder.push_function(empty_function_def);
let function_index = location.body;
self.proc_symbol_map.insert(sym, location);
self.build_stmt(&proc.body, &proc.ret_layout)?;
let function_def = self.finalize_proc(signature_builder);
let location = self.module_builder.push_function(function_def);
let function_index = location.body;
self.proc_symbol_map.insert(sym, location);
self.finalize_proc();
self.reset();
// println!("\nfinished generating {:?}\n", sym);
Ok(function_index)
}
fn start_proc(&mut self, proc: &Proc<'a>) -> SignatureBuilder {
fn start_proc(&mut self, proc: &Proc<'a>) -> FunctionDefinition {
let ret_layout = WasmLayout::new(&proc.ret_layout);
let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout {
@ -106,7 +104,7 @@ impl<'a> WasmBackend<'a> {
} else {
let ret_type = ret_layout.value_type();
self.start_block(BlockType::Value(ret_type)); // block to ensure all paths pop stack memory (if any)
builder::signature().with_result(ret_type)
builder::signature().with_result(ret_type.to_parity_wasm())
};
for (layout, symbol) in proc.args {
@ -117,62 +115,26 @@ impl<'a> WasmBackend<'a> {
);
}
signature_builder.with_params(self.storage.arg_types.clone())
let parity_params = self.storage.arg_types.iter().map(|t| t.to_parity_wasm());
let signature = signature_builder.with_params(parity_params).build_sig();
// parity-wasm FunctionDefinition with no instructions
builder::function().with_signature(signature).build()
}
fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition {
self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any)
fn finalize_proc(&mut self) {
// end the block from start_proc, to ensure all paths pop stack memory (if any)
self.end_block();
const STACK_FRAME_INSTRUCTIONS_LEN: usize = 10;
let mut final_instructions =
std::vec::Vec::with_capacity(self.code_builder.len() + STACK_FRAME_INSTRUCTIONS_LEN);
if self.storage.stack_frame_size > 0 {
push_stack_frame(
&mut final_instructions,
// Write local declarations and stack frame push/pop code
self.code_builder.finalize(
&self.storage.local_types,
self.storage.stack_frame_size,
self.storage.stack_frame_pointer.unwrap(),
self.storage.stack_frame_pointer,
);
}
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
}
/**********************************************************
STATEMENTS
@ -182,17 +144,17 @@ impl<'a> WasmBackend<'a> {
/// start a loop that leaves a value on the stack
fn start_loop_with_return(&mut self, value_type: ValueType) {
self.block_depth += 1;
self.code_builder.push(Loop(BlockType::Value(value_type)));
self.code_builder.loop_(BlockType::Value(value_type));
}
fn start_block(&mut self, block_type: BlockType) {
self.block_depth += 1;
self.code_builder.push(Block(block_type));
self.code_builder.block(block_type);
}
fn end_block(&mut self) {
self.block_depth -= 1;
self.code_builder.push(End);
self.code_builder.end();
}
fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
@ -255,7 +217,7 @@ impl<'a> WasmBackend<'a> {
_ => {
self.storage.load_symbols(&mut self.code_builder, &[*sym]);
self.code_builder.push(Br(self.block_depth)); // jump to end of function (for stack frame pop)
self.code_builder.br(self.block_depth); // jump to end of function (for stack frame pop)
}
}
@ -293,13 +255,13 @@ impl<'a> WasmBackend<'a> {
self.storage
.load_symbols(&mut self.code_builder, &[*cond_symbol]);
self.code_builder.push(I32Const(*value as i32));
self.code_builder.i32_const(*value as i32);
// compare the 2 topmost values
self.code_builder.push(I32Eq);
self.code_builder.i32_eq();
// "break" out of `i` surrounding blocks
self.code_builder.push(BrIf(i as u32));
self.code_builder.br_if(i as u32);
}
// if we never jumped because a value matched, we're in the default case
@ -375,7 +337,7 @@ impl<'a> WasmBackend<'a> {
// jump
let levels = self.block_depth - target;
self.code_builder.push(Br(levels));
self.code_builder.br(levels);
Ok(())
}
@ -427,11 +389,8 @@ impl<'a> WasmBackend<'a> {
func_sym, sym
))?;
self.code_builder.push_call(
function_location.body,
wasm_args.len(),
has_return_val,
);
self.code_builder
.call(function_location.body, wasm_args.len(), has_return_val);
Ok(())
}
@ -448,25 +407,25 @@ impl<'a> WasmBackend<'a> {
}
fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> {
let instruction = match lit {
Literal::Bool(x) => I32Const(*x as i32),
Literal::Byte(x) => I32Const(*x as i32),
match lit {
Literal::Bool(x) => self.code_builder.i32_const(*x as i32),
Literal::Byte(x) => self.code_builder.i32_const(*x as i32),
Literal::Int(x) => match layout {
Layout::Builtin(Builtin::Int64) => I64Const(*x as i64),
Layout::Builtin(Builtin::Int64) => self.code_builder.i64_const(*x as i64),
Layout::Builtin(
Builtin::Int32
| Builtin::Int16
| Builtin::Int8
| Builtin::Int1
| Builtin::Usize,
) => I32Const(*x as i32),
) => self.code_builder.i32_const(*x as i32),
x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
}
},
Literal::Float(x) => match layout {
Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()),
Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()),
Layout::Builtin(Builtin::Float64) => self.code_builder.f64_const(*x as f64),
Layout::Builtin(Builtin::Float32) => self.code_builder.f32_const(*x as f32),
x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
}
@ -475,7 +434,6 @@ impl<'a> WasmBackend<'a> {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
}
};
self.code_builder.push(instruction);
Ok(())
}
@ -546,34 +504,33 @@ impl<'a> WasmBackend<'a> {
// Some Roc low-level ops care about wrapping, clipping, sign-extending...
// For those, we'll need to pre-process each argument before the main op,
// so simple arrays of instructions won't work. But there are common patterns.
let instructions: &[Instruction] = match lowlevel {
match lowlevel {
LowLevel::NumAdd => match return_value_type {
ValueType::I32 => &[I32Add],
ValueType::I64 => &[I64Add],
ValueType::F32 => &[F32Add],
ValueType::F64 => &[F64Add],
ValueType::I32 => self.code_builder.i32_add(),
ValueType::I64 => self.code_builder.i64_add(),
ValueType::F32 => self.code_builder.f32_add(),
ValueType::F64 => self.code_builder.f64_add(),
},
LowLevel::NumSub => match return_value_type {
ValueType::I32 => &[I32Sub],
ValueType::I64 => &[I64Sub],
ValueType::F32 => &[F32Sub],
ValueType::F64 => &[F64Sub],
ValueType::I32 => self.code_builder.i32_sub(),
ValueType::I64 => self.code_builder.i64_sub(),
ValueType::F32 => self.code_builder.f32_sub(),
ValueType::F64 => self.code_builder.f64_sub(),
},
LowLevel::NumMul => match return_value_type {
ValueType::I32 => &[I32Mul],
ValueType::I64 => &[I64Mul],
ValueType::F32 => &[F32Mul],
ValueType::F64 => &[F64Mul],
ValueType::I32 => self.code_builder.i32_mul(),
ValueType::I64 => self.code_builder.i64_mul(),
ValueType::F32 => self.code_builder.f32_mul(),
ValueType::F64 => self.code_builder.f64_mul(),
},
LowLevel::NumGt => {
// needs layout of the argument to be implemented fully
&[I32GtS]
self.code_builder.i32_gt_s()
}
_ => {
return Err(format!("unsupported low-level op {:?}", lowlevel));
}
};
self.code_builder.extend_from_slice(instructions);
Ok(())
}
}

View file

@ -15,7 +15,7 @@ use crate::{
const DEBUG_LOG: bool = false;
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum ValueType {
I32 = 0x7f,
I64 = 0x7e,
@ -23,6 +23,17 @@ pub enum ValueType {
F64 = 0x7c,
}
impl ValueType {
pub fn to_parity_wasm(&self) -> parity_wasm::elements::ValueType {
match self {
Self::I32 => parity_wasm::elements::ValueType::I32,
Self::I64 => parity_wasm::elements::ValueType::I64,
Self::F32 => parity_wasm::elements::ValueType::F32,
Self::F64 => parity_wasm::elements::ValueType::F64,
}
}
}
pub enum BlockType {
NoResult,
Value(ValueType),
@ -38,6 +49,7 @@ impl BlockType {
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum Align {
Bytes1 = 0,
Bytes2 = 1,
@ -199,7 +211,8 @@ impl<'a> FunctionBuilder<'a> {
length: 0,
bytes: [SETLOCAL, 0, 0, 0, 0, 0],
};
insertion.length = 1 + encode_u32(&mut insertion.bytes[1..], next_local_id.0);
insertion.length =
1 + encode_u32(&mut insertion.bytes[1..], next_local_id.0);
self.insertions_byte_len += insertion.length;
self.insertions.push(insertion);
@ -301,17 +314,22 @@ impl<'a> FunctionBuilder<'a> {
/// Generate all the "extra" bytes: local declarations, stack frame push/pop code, and function length
/// After this, bytes will have been _generated_, but not yet _serialized_ into a single stream.
/// Returns the final number of bytes the function will occupy in the target binary
pub fn finalize(&mut self, local_types: &[ValueType], frame_size: i32, frame_pointer: LocalId) -> usize {
self.insertions.sort_by_key(|insertion| insertion.position);
pub fn finalize(
&mut self,
local_types: &[ValueType],
frame_size: i32,
frame_pointer: Option<LocalId>,
) -> usize {
self.build_local_declarations(local_types);
if frame_size > 0 {
if let Some(frame_ptr_id) = frame_pointer {
let aligned_size = round_up_to_alignment(frame_size, FRAME_ALIGNMENT_BYTES);
self.build_stack_frame_push(aligned_size, frame_pointer);
self.build_stack_frame_pop(aligned_size, frame_pointer);
self.build_stack_frame_push(aligned_size, frame_ptr_id);
self.build_stack_frame_pop(aligned_size, frame_ptr_id);
}
self.code.push(END);
// The length of the function is written in front of its body.
// But that means the length _itself_ has a byte length, which adds to the total!
// We use the terms "inner" and "outer" lengths to distinguish the two.
@ -322,15 +340,19 @@ impl<'a> FunctionBuilder<'a> {
}
/// Write out all the bytes in the right order
pub fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<usize> {
pub fn serialize<W: std::io::Write>(&mut self, writer: &mut W) -> std::io::Result<usize> {
writer.write(&self.inner_length)?;
writer.write(&self.preamble)?;
self.insertions.sort_by_key(|insertion| insertion.position);
let mut pos: usize = 0;
for insertion in self.insertions.iter() {
writer.write(&self.code[pos..insertion.position])?;
writer.write(&insertion.bytes[0..insertion.length])?;
pos = insertion.position;
}
let len = self.code.len();
writer.write(&self.code[pos..len])
}

View file

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

View file

@ -5,7 +5,7 @@ mod layout;
mod storage;
#[allow(dead_code)]
mod function_builder;
pub mod function_builder;
#[allow(dead_code)]
mod opcodes;
@ -13,25 +13,19 @@ mod opcodes;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use parity_wasm::builder;
use parity_wasm::elements::{Instruction, Instruction::*, Internal, ValueType};
use parity_wasm::elements::Internal;
use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::{Interns, Symbol};
use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds;
use crate::backend::WasmBackend;
use crate::code_builder::CodeBuilder;
use crate::function_builder::{Align, FunctionBuilder, ValueType};
const PTR_SIZE: u32 = 4;
const PTR_TYPE: ValueType = ValueType::I32;
// All usages of these alignment constants take u32, so an enum wouldn't add any safety.
pub const ALIGN_1: u32 = 0;
pub const ALIGN_2: u32 = 1;
pub const ALIGN_4: u32 = 2;
pub const ALIGN_8: u32 = 3;
pub const STACK_POINTER_GLOBAL_ID: u32 = 0;
pub const FRAME_ALIGNMENT_BYTES: i32 = 16;
@ -111,21 +105,23 @@ pub fn build_module_help<'a>(
backend.module_builder.push_export(memory_export);
let stack_pointer_global = builder::global()
.with_type(PTR_TYPE)
.with_type(parity_wasm::elements::ValueType::I32)
.mutable()
.init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32))
.init_expr(parity_wasm::elements::Instruction::I32Const(
(MIN_MEMORY_SIZE_KB * 1024) as i32,
))
.build();
backend.module_builder.push_global(stack_pointer_global);
Ok((backend.module_builder, main_function_index))
}
fn encode_alignment(bytes: u32) -> u32 {
fn encode_alignment(bytes: u32) -> Align {
match bytes {
1 => ALIGN_1,
2 => ALIGN_2,
4 => ALIGN_4,
8 => ALIGN_8,
1 => Align::Bytes1,
2 => Align::Bytes2,
4 => Align::Bytes4,
8 => Align::Bytes8,
_ => panic!("{:?}-byte alignment is not supported", bytes),
}
}
@ -139,38 +135,32 @@ pub struct CopyMemoryConfig {
alignment_bytes: u32,
}
pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) {
pub fn copy_memory(code_builder: &mut FunctionBuilder, config: CopyMemoryConfig) {
if config.from_ptr == config.to_ptr && config.from_offset == config.to_offset {
return;
}
let alignment_flag = encode_alignment(config.alignment_bytes);
let alignment = encode_alignment(config.alignment_bytes);
let mut i = 0;
while config.size - i >= 8 {
code_builder.extend_from_slice(&[
GetLocal(config.to_ptr.0),
GetLocal(config.from_ptr.0),
I64Load(alignment_flag, i + config.from_offset),
I64Store(alignment_flag, i + config.to_offset),
]);
code_builder.get_local(config.to_ptr);
code_builder.get_local(config.from_ptr);
code_builder.i64_load(alignment, i + config.from_offset);
code_builder.i64_store(alignment, i + config.to_offset);
i += 8;
}
if config.size - i >= 4 {
code_builder.extend_from_slice(&[
GetLocal(config.to_ptr.0),
GetLocal(config.from_ptr.0),
I32Load(alignment_flag, i + config.from_offset),
I32Store(alignment_flag, i + config.to_offset),
]);
code_builder.get_local(config.to_ptr);
code_builder.get_local(config.from_ptr);
code_builder.i32_load(alignment, i + config.from_offset);
code_builder.i32_store(alignment, i + config.to_offset);
i += 4;
}
while config.size - i > 0 {
code_builder.extend_from_slice(&[
GetLocal(config.to_ptr.0),
GetLocal(config.from_ptr.0),
I32Load8U(alignment_flag, i + config.from_offset),
I32Store8(alignment_flag, i + config.to_offset),
]);
code_builder.get_local(config.to_ptr);
code_builder.get_local(config.from_ptr);
code_builder.i32_load8_u(alignment, i + config.from_offset);
code_builder.i32_store8(alignment, i + config.to_offset);
i += 1;
}
}
@ -184,35 +174,6 @@ pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 {
aligned
}
pub fn push_stack_frame(
instructions: &mut std::vec::Vec<Instruction>,
size: i32,
local_frame_pointer: LocalId,
) {
let aligned_size = round_up_to_alignment(size, FRAME_ALIGNMENT_BYTES);
instructions.extend([
GetGlobal(STACK_POINTER_GLOBAL_ID),
I32Const(aligned_size),
I32Sub,
TeeLocal(local_frame_pointer.0),
SetGlobal(STACK_POINTER_GLOBAL_ID),
]);
}
pub fn pop_stack_frame(
instructions: &mut std::vec::Vec<Instruction>,
size: i32,
local_frame_pointer: LocalId,
) {
let aligned_size = round_up_to_alignment(size, FRAME_ALIGNMENT_BYTES);
instructions.extend([
GetLocal(local_frame_pointer.0),
I32Const(aligned_size),
I32Add,
SetGlobal(STACK_POINTER_GLOBAL_ID),
]);
}
pub fn debug_panic<E: std::fmt::Debug>(error: E) {
panic!("{:?}", error);
}

View file

@ -1,16 +1,13 @@
use bumpalo::collections::Vec;
use bumpalo::Bump;
use parity_wasm::elements::{Instruction::*, ValueType};
use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState};
use crate::code_builder::VirtualMachineSymbolState;
use crate::function_builder::{FunctionBuilder, ValueType};
use crate::layout::WasmLayout;
use crate::{
copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4,
ALIGN_8, PTR_SIZE, PTR_TYPE,
};
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, PTR_SIZE, PTR_TYPE};
pub enum StoredValueKind {
Parameter,
@ -192,7 +189,7 @@ impl<'a> Storage<'a> {
/// Load symbols to the top of the VM stack
/// Avoid calling this method in a loop with one symbol at a time! It will work,
/// but it generates very inefficient Wasm code.
pub fn load_symbols(&mut self, code_builder: &mut CodeBuilder, symbols: &[Symbol]) {
pub fn load_symbols(&mut self, code_builder: &mut FunctionBuilder, symbols: &[Symbol]) {
if code_builder.verify_stack_match(symbols) {
// The symbols were already at the top of the stack, do nothing!
// This should be quite common due to the structure of the Mono IR
@ -241,7 +238,7 @@ impl<'a> Storage<'a> {
location: StackMemoryLocation::PointerArg(local_id),
..
} => {
code_builder.push(GetLocal(local_id.0));
code_builder.get_local(local_id);
code_builder.set_top_symbol(*sym);
}
@ -249,11 +246,9 @@ impl<'a> Storage<'a> {
location: StackMemoryLocation::FrameOffset(offset),
..
} => {
code_builder.extend_from_slice(&[
GetLocal(self.stack_frame_pointer.unwrap().0),
I32Const(offset as i32),
I32Add,
]);
code_builder.get_local(self.stack_frame_pointer.unwrap());
code_builder.i32_const(offset as i32);
code_builder.i32_add();
code_builder.set_top_symbol(*sym);
}
}
@ -264,7 +259,7 @@ impl<'a> Storage<'a> {
/// (defined by a pointer and offset).
pub fn copy_value_to_memory(
&mut self,
code_builder: &mut CodeBuilder,
code_builder: &mut FunctionBuilder,
to_ptr: LocalId,
to_offset: u32,
from_symbol: Symbol,
@ -297,20 +292,20 @@ impl<'a> Storage<'a> {
| StoredValue::Local {
value_type, size, ..
} => {
let store_instruction = match (value_type, size) {
(ValueType::I64, 8) => I64Store(ALIGN_8, to_offset),
(ValueType::I32, 4) => I32Store(ALIGN_4, to_offset),
(ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset),
(ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset),
(ValueType::F32, 4) => F32Store(ALIGN_4, to_offset),
(ValueType::F64, 8) => F64Store(ALIGN_8, to_offset),
use crate::function_builder::Align::*;
code_builder.get_local(to_ptr);
self.load_symbols(code_builder, &[from_symbol]);
match (value_type, size) {
(ValueType::I64, 8) => code_builder.i64_store(Bytes8, to_offset),
(ValueType::I32, 4) => code_builder.i32_store(Bytes4, to_offset),
(ValueType::I32, 2) => code_builder.i32_store16(Bytes2, to_offset),
(ValueType::I32, 1) => code_builder.i32_store8(Bytes1, to_offset),
(ValueType::F32, 4) => code_builder.f32_store(Bytes4, to_offset),
(ValueType::F64, 8) => code_builder.f64_store(Bytes8, to_offset),
_ => {
panic!("Cannot store {:?} with alignment of {:?}", value_type, size);
}
};
code_builder.push(GetLocal(to_ptr.0));
self.load_symbols(code_builder, &[from_symbol]);
code_builder.push(store_instruction);
size
}
}
@ -320,7 +315,7 @@ impl<'a> Storage<'a> {
/// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory`
pub fn clone_value(
&mut self,
code_builder: &mut CodeBuilder,
code_builder: &mut FunctionBuilder,
to: &StoredValue,
from: &StoredValue,
from_symbol: Symbol,
@ -343,7 +338,7 @@ impl<'a> Storage<'a> {
debug_assert!(to_value_type == from_value_type);
debug_assert!(to_size == from_size);
self.load_symbols(code_builder, &[from_symbol]);
code_builder.push(SetLocal(to_local_id.0));
code_builder.set_local(*to_local_id);
self.symbol_storage_map.insert(from_symbol, to.clone());
}
@ -361,8 +356,8 @@ impl<'a> Storage<'a> {
) => {
debug_assert!(to_value_type == from_value_type);
debug_assert!(to_size == from_size);
code_builder
.extend_from_slice(&[GetLocal(from_local_id.0), SetLocal(to_local_id.0)]);
code_builder.get_local(*from_local_id);
code_builder.set_local(*to_local_id);
}
(
@ -408,7 +403,7 @@ impl<'a> Storage<'a> {
/// (In the case of structs in stack memory, we just use the stack frame pointer local)
pub fn ensure_value_has_local(
&mut self,
code_builder: &mut CodeBuilder,
code_builder: &mut FunctionBuilder,
symbol: Symbol,
storage: StoredValue,
) -> StoredValue {
@ -421,7 +416,7 @@ impl<'a> Storage<'a> {
let local_id = self.get_next_local_id();
if vm_state != VirtualMachineSymbolState::NotYetPushed {
code_builder.load_symbol(symbol, vm_state, local_id);
code_builder.push(SetLocal(local_id.0));
code_builder.set_local(local_id);
}
self.local_types.push(value_type);

View file

@ -1,130 +1,109 @@
use parity_wasm::builder;
use parity_wasm::builder::ModuleBuilder;
use parity_wasm::elements::{
Instruction, Instruction::*, Instructions, Internal, Local, ValueType,
};
use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory;
use roc_gen_wasm::*;
use roc_gen_wasm::function_builder::{Align, FunctionBuilder, ValueType};
use roc_std::{RocDec, RocList, RocOrder, RocStr};
const STACK_POINTER_LOCAL_ID: u32 = 0;
pub trait Wasm32TestResult {
fn insert_test_wrapper(
module_builder: &mut ModuleBuilder,
code_builder: &mut FunctionBuilder,
wrapper_name: &str,
main_function_index: u32,
) {
let instructions = Self::build_wrapper_body(main_function_index);
let signature = builder::signature().with_result(ValueType::I32).build_sig();
let stack_frame_pointer = Local::new(1, ValueType::I32);
let function_def = builder::function()
.with_signature(signature)
.body()
.with_locals(vec![stack_frame_pointer])
.with_instructions(Instructions::new(instructions))
.build() // body
.build(); // function
let location = module_builder.push_function(function_def);
// parity-wasm FunctionDefinition with no instructions
let empty_fn_def = builder::function().with_signature(signature).build();
let location = module_builder.push_function(empty_fn_def);
let export = builder::export()
.field(wrapper_name)
.with_internal(Internal::Function(location.body))
.build();
module_builder.push_export(export);
self.build_wrapper_body(code_builder, main_function_index);
}
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction>;
fn build_wrapper_body(code_builder: &mut FunctionBuilder, main_function_index: u32);
}
macro_rules! build_wrapper_body_primitive {
($store_instruction: expr, $align: expr) => {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
let size: i32 = 8;
let mut instructions = Vec::with_capacity(16);
push_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID));
instructions.extend([
// load result address to prepare for the store instruction later
GetLocal(STACK_POINTER_LOCAL_ID),
//
// Call the main function with no arguments. Get primitive back.
Call(main_function_index),
//
// Store the primitive at the allocated address
$store_instruction($align, 0),
//
// Return the result pointer
GetLocal(STACK_POINTER_LOCAL_ID),
]);
pop_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID));
instructions.push(End);
instructions
($store_instruction: ident, $align: expr) => {
fn build_wrapper_body(
code_builder: &mut FunctionBuilder,
main_function_index: u32,
) -> Vec<Instruction> {
let frame_pointer_id = LocalId(0);
let frame_pointer = Some(frame_pointer_id);
let local_types = &[ValueType::I32];
let frame_size = 8;
code_builder.get_local(frame_pointer_id);
code_builder.call(main_function_index, 0, true);
code_builder.$store_instruction($align, 0);
code_builder.get_local(frame_pointer_id);
code_builder.finalize(local_types, frame_size, frame_pointer);
}
};
}
macro_rules! wasm_test_result_primitive {
($type_name: ident, $store_instruction: expr, $align: expr) => {
($type_name: ident, $store_instruction: ident, $align: expr) => {
impl Wasm32TestResult for $type_name {
build_wrapper_body_primitive!($store_instruction, $align);
}
};
}
fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec<Instruction> {
let mut instructions = Vec::with_capacity(16);
push_stack_frame(
&mut instructions,
size as i32,
LocalId(STACK_POINTER_LOCAL_ID),
);
instructions.extend([
//
// Call the main function with the allocated address to write the result.
// No value is returned to the VM stack. This is the same as in compiled C.
GetLocal(STACK_POINTER_LOCAL_ID),
Call(main_function_index),
//
// Return the result address
GetLocal(STACK_POINTER_LOCAL_ID),
]);
pop_stack_frame(
&mut instructions,
size as i32,
LocalId(STACK_POINTER_LOCAL_ID),
);
instructions.push(End);
instructions
fn build_wrapper_body_stack_memory(
code_builder: &mut FunctionBuilder,
main_function_index: u32,
size: usize,
) -> Vec<Instruction> {
let local_id = LocalId(0);
let local_types = &[ValueType::I32];
let frame_pointer = Some(local_id);
code_builder.get_local(local_id);
code_builder.call(main_function_index, 0, true);
code_builder.get_local(local_id);
code_builder.finalize(local_types, size, frame_pointer);
}
macro_rules! wasm_test_result_stack_memory {
($type_name: ident) => {
impl Wasm32TestResult for $type_name {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(main_function_index, $type_name::ACTUAL_WIDTH)
fn build_wrapper_body(
code_builder: &mut FunctionBuilder,
main_function_index: u32,
) -> Vec<Instruction> {
build_wrapper_body_stack_memory(
code_builder,
main_function_index,
$type_name::ACTUAL_WIDTH,
)
}
}
};
}
wasm_test_result_primitive!(bool, I32Store8, ALIGN_1);
wasm_test_result_primitive!(RocOrder, I32Store8, ALIGN_1);
wasm_test_result_primitive!(bool, i32_store8, Align::Bytes1);
wasm_test_result_primitive!(RocOrder, i32_store8, Align::Bytes1);
wasm_test_result_primitive!(u8, I32Store8, ALIGN_1);
wasm_test_result_primitive!(i8, I32Store8, ALIGN_1);
wasm_test_result_primitive!(u16, I32Store16, ALIGN_2);
wasm_test_result_primitive!(i16, I32Store16, ALIGN_2);
wasm_test_result_primitive!(u32, I32Store, ALIGN_4);
wasm_test_result_primitive!(i32, I32Store, ALIGN_4);
wasm_test_result_primitive!(u64, I64Store, ALIGN_8);
wasm_test_result_primitive!(i64, I64Store, ALIGN_8);
wasm_test_result_primitive!(u8, i32_store8, Align::Bytes1);
wasm_test_result_primitive!(i8, i32_store8, Align::Bytes1);
wasm_test_result_primitive!(u16, i32_store16, Align::Bytes2);
wasm_test_result_primitive!(i16, i32_store16, Align::Bytes2);
wasm_test_result_primitive!(u32, i32_store, Align::Bytes4);
wasm_test_result_primitive!(i32, i32_store, Align::Bytes4);
wasm_test_result_primitive!(u64, i64_store, Align::Bytes8);
wasm_test_result_primitive!(i64, i64_store, Align::Bytes8);
wasm_test_result_primitive!(f32, F32Store, ALIGN_8);
wasm_test_result_primitive!(f64, F64Store, ALIGN_8);
wasm_test_result_primitive!(f32, f32_store, Align::Bytes8);
wasm_test_result_primitive!(f64, f64_store, Align::Bytes8);
wasm_test_result_stack_memory!(u128);
wasm_test_result_stack_memory!(i128);
@ -138,7 +117,7 @@ impl<T: Wasm32TestResult> Wasm32TestResult for RocList<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]