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,60 +115,24 @@ impl<'a> WasmBackend<'a> {
);
}
signature_builder.with_params(self.storage.arg_types.clone())
let parity_params = self.storage.arg_types.iter().map(|t| t.to_parity_wasm());
let signature = signature_builder.with_params(parity_params).build_sig();
// parity-wasm FunctionDefinition with no instructions
builder::function().with_signature(signature).build()
}
fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition {
self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any)
fn finalize_proc(&mut self) {
// end the block from start_proc, to ensure all paths pop stack memory (if any)
self.end_block();
const STACK_FRAME_INSTRUCTIONS_LEN: usize = 10;
let mut final_instructions =
std::vec::Vec::with_capacity(self.code_builder.len() + STACK_FRAME_INSTRUCTIONS_LEN);
if self.storage.stack_frame_size > 0 {
push_stack_frame(
&mut final_instructions,
self.storage.stack_frame_size,
self.storage.stack_frame_pointer.unwrap(),
);
}
self.code_builder.finalize_into(&mut final_instructions);
if self.storage.stack_frame_size > 0 {
pop_stack_frame(
&mut final_instructions,
self.storage.stack_frame_size,
self.storage.stack_frame_pointer.unwrap(),
);
}
final_instructions.push(End);
// Declare local variables (in batches of the same type)
let num_locals = self.storage.local_types.len();
let mut locals = Vec::with_capacity_in(num_locals, self.env.arena);
if num_locals > 0 {
let mut batch_type = self.storage.local_types[0];
let mut batch_size = 0;
for t in &self.storage.local_types {
if *t == batch_type {
batch_size += 1;
} else {
locals.push(Local::new(batch_size, batch_type));
batch_type = *t;
batch_size = 1;
}
}
locals.push(Local::new(batch_size, batch_type));
}
builder::function()
.with_signature(signature_builder.build_sig())
.body()
.with_locals(locals)
.with_instructions(Instructions::new(final_instructions))
.build() // body
.build() // function
// Write local declarations and stack frame push/pop code
self.code_builder.finalize(
&self.storage.local_types,
self.storage.stack_frame_size,
self.storage.stack_frame_pointer,
);
}
/**********************************************************
@ -182,17 +144,17 @@ impl<'a> WasmBackend<'a> {
/// start a loop that leaves a value on the stack
fn start_loop_with_return(&mut self, value_type: ValueType) {
self.block_depth += 1;
self.code_builder.push(Loop(BlockType::Value(value_type)));
self.code_builder.loop_(BlockType::Value(value_type));
}
fn start_block(&mut self, block_type: BlockType) {
self.block_depth += 1;
self.code_builder.push(Block(block_type));
self.code_builder.block(block_type);
}
fn end_block(&mut self) {
self.block_depth -= 1;
self.code_builder.push(End);
self.code_builder.end();
}
fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
@ -255,7 +217,7 @@ impl<'a> WasmBackend<'a> {
_ => {
self.storage.load_symbols(&mut self.code_builder, &[*sym]);
self.code_builder.push(Br(self.block_depth)); // jump to end of function (for stack frame pop)
self.code_builder.br(self.block_depth); // jump to end of function (for stack frame pop)
}
}
@ -293,13 +255,13 @@ impl<'a> WasmBackend<'a> {
self.storage
.load_symbols(&mut self.code_builder, &[*cond_symbol]);
self.code_builder.push(I32Const(*value as i32));
self.code_builder.i32_const(*value as i32);
// compare the 2 topmost values
self.code_builder.push(I32Eq);
self.code_builder.i32_eq();
// "break" out of `i` surrounding blocks
self.code_builder.push(BrIf(i as u32));
self.code_builder.br_if(i as u32);
}
// if we never jumped because a value matched, we're in the default case
@ -375,7 +337,7 @@ impl<'a> WasmBackend<'a> {
// jump
let levels = self.block_depth - target;
self.code_builder.push(Br(levels));
self.code_builder.br(levels);
Ok(())
}
@ -427,11 +389,8 @@ impl<'a> WasmBackend<'a> {
func_sym, sym
))?;
self.code_builder.push_call(
function_location.body,
wasm_args.len(),
has_return_val,
);
self.code_builder
.call(function_location.body, wasm_args.len(), has_return_val);
Ok(())
}
@ -448,25 +407,25 @@ impl<'a> WasmBackend<'a> {
}
fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> {
let instruction = match lit {
Literal::Bool(x) => I32Const(*x as i32),
Literal::Byte(x) => I32Const(*x as i32),
match lit {
Literal::Bool(x) => self.code_builder.i32_const(*x as i32),
Literal::Byte(x) => self.code_builder.i32_const(*x as i32),
Literal::Int(x) => match layout {
Layout::Builtin(Builtin::Int64) => I64Const(*x as i64),
Layout::Builtin(Builtin::Int64) => self.code_builder.i64_const(*x as i64),
Layout::Builtin(
Builtin::Int32
| Builtin::Int16
| Builtin::Int8
| Builtin::Int1
| Builtin::Usize,
) => I32Const(*x as i32),
) => self.code_builder.i32_const(*x as i32),
x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
}
},
Literal::Float(x) => match layout {
Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()),
Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()),
Layout::Builtin(Builtin::Float64) => self.code_builder.f64_const(*x as f64),
Layout::Builtin(Builtin::Float32) => self.code_builder.f32_const(*x as f32),
x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
}
@ -475,7 +434,6 @@ impl<'a> WasmBackend<'a> {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
}
};
self.code_builder.push(instruction);
Ok(())
}
@ -546,34 +504,33 @@ impl<'a> WasmBackend<'a> {
// Some Roc low-level ops care about wrapping, clipping, sign-extending...
// For those, we'll need to pre-process each argument before the main op,
// so simple arrays of instructions won't work. But there are common patterns.
let instructions: &[Instruction] = match lowlevel {
match lowlevel {
LowLevel::NumAdd => match return_value_type {
ValueType::I32 => &[I32Add],
ValueType::I64 => &[I64Add],
ValueType::F32 => &[F32Add],
ValueType::F64 => &[F64Add],
ValueType::I32 => self.code_builder.i32_add(),
ValueType::I64 => self.code_builder.i64_add(),
ValueType::F32 => self.code_builder.f32_add(),
ValueType::F64 => self.code_builder.f64_add(),
},
LowLevel::NumSub => match return_value_type {
ValueType::I32 => &[I32Sub],
ValueType::I64 => &[I64Sub],
ValueType::F32 => &[F32Sub],
ValueType::F64 => &[F64Sub],
ValueType::I32 => self.code_builder.i32_sub(),
ValueType::I64 => self.code_builder.i64_sub(),
ValueType::F32 => self.code_builder.f32_sub(),
ValueType::F64 => self.code_builder.f64_sub(),
},
LowLevel::NumMul => match return_value_type {
ValueType::I32 => &[I32Mul],
ValueType::I64 => &[I64Mul],
ValueType::F32 => &[F32Mul],
ValueType::F64 => &[F64Mul],
ValueType::I32 => self.code_builder.i32_mul(),
ValueType::I64 => self.code_builder.i64_mul(),
ValueType::F32 => self.code_builder.f32_mul(),
ValueType::F64 => self.code_builder.f64_mul(),
},
LowLevel::NumGt => {
// needs layout of the argument to be implemented fully
&[I32GtS]
self.code_builder.i32_gt_s()
}
_ => {
return Err(format!("unsupported low-level op {:?}", lowlevel));
}
};
self.code_builder.extend_from_slice(instructions);
Ok(())
}
}