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 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::function_builder::{BlockType, FunctionBuilder, 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, 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)
@ -37,7 +32,7 @@ pub struct WasmBackend<'a> {
proc_symbol_map: MutMap<Symbol, CodeLocation>, proc_symbol_map: MutMap<Symbol, CodeLocation>,
// Function level // Function level
code_builder: CodeBuilder<'a>, code_builder: FunctionBuilder<'a>,
storage: Storage<'a>, storage: Storage<'a>,
/// how many blocks deep are we (used for jumps) /// how many blocks deep are we (used for jumps)
@ -61,7 +56,7 @@ impl<'a> WasmBackend<'a> {
joinpoint_label_map: MutMap::default(), joinpoint_label_map: MutMap::default(),
// Functions // Functions
code_builder: CodeBuilder::new(env.arena), code_builder: FunctionBuilder::new(env.arena),
storage: Storage::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> { 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 +104,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 +115,24 @@ impl<'a> WasmBackend<'a> {
); );
} }
signature_builder.with_params(self.storage.arg_types.clone()) let parity_params = self.storage.arg_types.iter().map(|t| t.to_parity_wasm());
let signature = signature_builder.with_params(parity_params).build_sig();
// parity-wasm FunctionDefinition with no instructions
builder::function().with_signature(signature).build()
} }
fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition { fn finalize_proc(&mut self) {
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,
if self.storage.stack_frame_size > 0 { self.storage.stack_frame_pointer,
push_stack_frame( );
&mut final_instructions,
self.storage.stack_frame_size,
self.storage.stack_frame_pointer.unwrap(),
);
}
self.code_builder.finalize_into(&mut final_instructions);
if self.storage.stack_frame_size > 0 {
pop_stack_frame(
&mut final_instructions,
self.storage.stack_frame_size,
self.storage.stack_frame_pointer.unwrap(),
);
}
final_instructions.push(End);
// Declare local variables (in batches of the same type)
let num_locals = self.storage.local_types.len();
let mut locals = Vec::with_capacity_in(num_locals, self.env.arena);
if num_locals > 0 {
let mut batch_type = self.storage.local_types[0];
let mut batch_size = 0;
for t in &self.storage.local_types {
if *t == batch_type {
batch_size += 1;
} else {
locals.push(Local::new(batch_size, batch_type));
batch_type = *t;
batch_size = 1;
}
}
locals.push(Local::new(batch_size, batch_type));
}
builder::function()
.with_signature(signature_builder.build_sig())
.body()
.with_locals(locals)
.with_instructions(Instructions::new(final_instructions))
.build() // body
.build() // function
} }
/********************************************************** /**********************************************************
@ -182,17 +144,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 +217,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 +255,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 +337,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 +389,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 +407,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 +434,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 +504,33 @@ impl<'a> WasmBackend<'a> {
// Some Roc low-level ops care about wrapping, clipping, sign-extending... // Some Roc low-level ops care about wrapping, clipping, sign-extending...
// For those, we'll need to pre-process each argument before the main op, // For those, we'll need to pre-process each argument before the main op,
// so simple arrays of instructions won't work. But there are common patterns. // so simple arrays of instructions won't work. But there are common patterns.
let instructions: &[Instruction] = match lowlevel { match lowlevel {
LowLevel::NumAdd => match return_value_type { LowLevel::NumAdd => match return_value_type {
ValueType::I32 => &[I32Add], ValueType::I32 => self.code_builder.i32_add(),
ValueType::I64 => &[I64Add], ValueType::I64 => self.code_builder.i64_add(),
ValueType::F32 => &[F32Add], ValueType::F32 => self.code_builder.f32_add(),
ValueType::F64 => &[F64Add], ValueType::F64 => self.code_builder.f64_add(),
}, },
LowLevel::NumSub => match return_value_type { LowLevel::NumSub => match return_value_type {
ValueType::I32 => &[I32Sub], ValueType::I32 => self.code_builder.i32_sub(),
ValueType::I64 => &[I64Sub], ValueType::I64 => self.code_builder.i64_sub(),
ValueType::F32 => &[F32Sub], ValueType::F32 => self.code_builder.f32_sub(),
ValueType::F64 => &[F64Sub], ValueType::F64 => self.code_builder.f64_sub(),
}, },
LowLevel::NumMul => match return_value_type { LowLevel::NumMul => match return_value_type {
ValueType::I32 => &[I32Mul], ValueType::I32 => self.code_builder.i32_mul(),
ValueType::I64 => &[I64Mul], ValueType::I64 => self.code_builder.i64_mul(),
ValueType::F32 => &[F32Mul], ValueType::F32 => self.code_builder.f32_mul(),
ValueType::F64 => &[F64Mul], ValueType::F64 => self.code_builder.f64_mul(),
}, },
LowLevel::NumGt => { LowLevel::NumGt => {
// needs layout of the argument to be implemented fully // needs layout of the argument to be implemented fully
&[I32GtS] self.code_builder.i32_gt_s()
} }
_ => { _ => {
return Err(format!("unsupported low-level op {:?}", lowlevel)); return Err(format!("unsupported low-level op {:?}", lowlevel));
} }
}; };
self.code_builder.extend_from_slice(instructions);
Ok(()) Ok(())
} }
} }

View file

@ -15,7 +15,7 @@ use crate::{
const DEBUG_LOG: bool = false; const DEBUG_LOG: bool = false;
#[repr(u8)] #[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy)] #[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum ValueType { pub enum ValueType {
I32 = 0x7f, I32 = 0x7f,
I64 = 0x7e, I64 = 0x7e,
@ -23,6 +23,17 @@ pub enum ValueType {
F64 = 0x7c, 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 { pub enum BlockType {
NoResult, NoResult,
Value(ValueType), Value(ValueType),
@ -38,6 +49,7 @@ impl BlockType {
} }
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum Align { pub enum Align {
Bytes1 = 0, Bytes1 = 0,
Bytes2 = 1, Bytes2 = 1,
@ -199,7 +211,8 @@ impl<'a> FunctionBuilder<'a> {
length: 0, length: 0,
bytes: [SETLOCAL, 0, 0, 0, 0, 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_byte_len += insertion.length;
self.insertions.push(insertion); 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 /// 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. /// 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 /// 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 { pub fn finalize(
self.insertions.sort_by_key(|insertion| insertion.position); &mut self,
local_types: &[ValueType],
frame_size: i32,
frame_pointer: Option<LocalId>,
) -> usize {
self.build_local_declarations(local_types); 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); 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_push(aligned_size, frame_ptr_id);
self.build_stack_frame_pop(aligned_size, frame_pointer); 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. // 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! // 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. // 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 /// 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.inner_length)?;
writer.write(&self.preamble)?; writer.write(&self.preamble)?;
self.insertions.sort_by_key(|insertion| insertion.position);
let mut pos: usize = 0; let mut pos: usize = 0;
for insertion in self.insertions.iter() { for insertion in self.insertions.iter() {
writer.write(&self.code[pos..insertion.position])?; writer.write(&self.code[pos..insertion.position])?;
writer.write(&insertion.bytes[0..insertion.length])?; writer.write(&insertion.bytes[0..insertion.length])?;
pos = insertion.position; pos = insertion.position;
} }
let len = self.code.len(); let len = self.code.len();
writer.write(&self.code[pos..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 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 // See README for background information on Wasm locals, memory and function calls
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -5,7 +5,7 @@ mod layout;
mod storage; mod storage;
#[allow(dead_code)] #[allow(dead_code)]
mod function_builder; pub mod function_builder;
#[allow(dead_code)] #[allow(dead_code)]
mod opcodes; mod opcodes;
@ -13,25 +13,19 @@ 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::Internal;
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::function_builder::{Align, FunctionBuilder, 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 FRAME_ALIGNMENT_BYTES: i32 = 16; pub const FRAME_ALIGNMENT_BYTES: i32 = 16;
@ -111,21 +105,23 @@ 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(parity_wasm::elements::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, main_function_index))
} }
fn encode_alignment(bytes: u32) -> u32 { fn encode_alignment(bytes: u32) -> Align {
match bytes { match bytes {
1 => ALIGN_1, 1 => Align::Bytes1,
2 => ALIGN_2, 2 => Align::Bytes2,
4 => ALIGN_4, 4 => Align::Bytes4,
8 => ALIGN_8, 8 => Align::Bytes8,
_ => panic!("{:?}-byte alignment is not supported", bytes), _ => panic!("{:?}-byte alignment is not supported", bytes),
} }
} }
@ -139,38 +135,32 @@ pub struct CopyMemoryConfig {
alignment_bytes: u32, 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 { if config.from_ptr == config.to_ptr && config.from_offset == config.to_offset {
return; return;
} }
let alignment_flag = encode_alignment(config.alignment_bytes); let alignment = encode_alignment(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;
} }
} }
@ -184,35 +174,6 @@ pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 {
aligned 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) { pub fn debug_panic<E: std::fmt::Debug>(error: E) {
panic!("{:?}", error); panic!("{:?}", error);
} }

View file

@ -1,16 +1,13 @@
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::VirtualMachineSymbolState;
use crate::function_builder::{FunctionBuilder, ValueType};
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,
@ -192,7 +189,7 @@ impl<'a> Storage<'a> {
/// Load symbols to the top of the VM stack /// 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, /// Avoid calling this method in a loop with one symbol at a time! It will work,
/// but it generates very inefficient Wasm code. /// 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) { if code_builder.verify_stack_match(symbols) {
// The symbols were already at the top of the stack, do nothing! // 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 // 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), 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 +246,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);
} }
} }
@ -264,7 +259,7 @@ impl<'a> Storage<'a> {
/// (defined by a pointer and offset). /// (defined by a pointer and offset).
pub fn copy_value_to_memory( pub fn copy_value_to_memory(
&mut self, &mut self,
code_builder: &mut CodeBuilder, code_builder: &mut FunctionBuilder,
to_ptr: LocalId, to_ptr: LocalId,
to_offset: u32, to_offset: u32,
from_symbol: Symbol, from_symbol: Symbol,
@ -297,20 +292,20 @@ impl<'a> Storage<'a> {
| StoredValue::Local { | StoredValue::Local {
value_type, size, .. value_type, size, ..
} => { } => {
let store_instruction = match (value_type, size) { use crate::function_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
} }
} }
@ -320,7 +315,7 @@ impl<'a> Storage<'a> {
/// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory` /// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory`
pub fn clone_value( pub fn clone_value(
&mut self, &mut self,
code_builder: &mut CodeBuilder, code_builder: &mut FunctionBuilder,
to: &StoredValue, to: &StoredValue,
from: &StoredValue, from: &StoredValue,
from_symbol: Symbol, from_symbol: Symbol,
@ -343,7 +338,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 +356,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);
} }
( (
@ -408,7 +403,7 @@ impl<'a> Storage<'a> {
/// (In the case of structs in stack memory, we just use the stack frame pointer local) /// (In the case of structs in stack memory, we just use the stack frame pointer local)
pub fn ensure_value_has_local( pub fn ensure_value_has_local(
&mut self, &mut self,
code_builder: &mut CodeBuilder, code_builder: &mut FunctionBuilder,
symbol: Symbol, symbol: Symbol,
storage: StoredValue, storage: StoredValue,
) -> StoredValue { ) -> StoredValue {
@ -421,7 +416,7 @@ impl<'a> Storage<'a> {
let local_id = self.get_next_local_id(); let local_id = self.get_next_local_id();
if vm_state != VirtualMachineSymbolState::NotYetPushed { if vm_state != VirtualMachineSymbolState::NotYetPushed {
code_builder.load_symbol(symbol, vm_state, local_id); code_builder.load_symbol(symbol, vm_state, local_id);
code_builder.push(SetLocal(local_id.0)); code_builder.set_local(local_id);
} }
self.local_types.push(value_type); self.local_types.push(value_type);

View file

@ -1,130 +1,109 @@
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::builder::ModuleBuilder; 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::from_wasm32_memory::FromWasm32Memory;
use roc_gen_wasm::*; use roc_gen_wasm::function_builder::{Align, FunctionBuilder, ValueType};
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(
module_builder: &mut ModuleBuilder, module_builder: &mut ModuleBuilder,
code_builder: &mut FunctionBuilder,
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(ValueType::I32).build_sig(); let signature = builder::signature().with_result(ValueType::I32).build_sig();
let stack_frame_pointer = Local::new(1, ValueType::I32); // parity-wasm FunctionDefinition with no instructions
let function_def = builder::function() let empty_fn_def = builder::function().with_signature(signature).build();
.with_signature(signature) let location = module_builder.push_function(empty_fn_def);
.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);
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 { 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(
let size: i32 = 8; code_builder: &mut FunctionBuilder,
let mut instructions = Vec::with_capacity(16); main_function_index: u32,
push_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); ) -> Vec<Instruction> {
instructions.extend([ let frame_pointer_id = LocalId(0);
// load result address to prepare for the store instruction later let frame_pointer = Some(frame_pointer_id);
GetLocal(STACK_POINTER_LOCAL_ID), let local_types = &[ValueType::I32];
// let frame_size = 8;
// Call the main function with no arguments. Get primitive back.
Call(main_function_index), code_builder.get_local(frame_pointer_id);
// code_builder.call(main_function_index, 0, true);
// Store the primitive at the allocated address code_builder.$store_instruction($align, 0);
$store_instruction($align, 0), code_builder.get_local(frame_pointer_id);
//
// Return the result pointer code_builder.finalize(local_types, frame_size, frame_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 FunctionBuilder,
push_stack_frame( main_function_index: u32,
&mut instructions, size: usize,
size as i32, ) -> Vec<Instruction> {
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, 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(
build_wrapper_body_stack_memory(main_function_index, $type_name::ACTUAL_WIDTH) 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!(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);
@ -138,7 +117,7 @@ impl<T: Wasm32TestResult> Wasm32TestResult for RocList<T> {
} }
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]