mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 14:24:45 +00:00
Switch over to function_builder
This commit is contained in:
parent
bca9f31c58
commit
74e3239a1c
6 changed files with 204 additions and 291 deletions
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue