mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
Move wasm file format code into a submodule
This commit is contained in:
parent
7fa6436ee6
commit
ad9b761fce
10 changed files with 45 additions and 50 deletions
729
compiler/gen_wasm/src/wasm_module/code_builder.rs
Normal file
729
compiler/gen_wasm/src/wasm_module/code_builder.rs
Normal file
|
@ -0,0 +1,729 @@
|
|||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
use core::panic;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use super::opcodes::*;
|
||||
use super::sections::{IndexRelocType, RelocationEntry};
|
||||
use super::serialize::{SerialBuffer, Serialize};
|
||||
use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct LocalId(pub u32);
|
||||
|
||||
/// Wasm value type. (Rust representation matches Wasm encoding)
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum ValueType {
|
||||
I32 = 0x7f,
|
||||
I64 = 0x7e,
|
||||
F32 = 0x7d,
|
||||
F64 = 0x7c,
|
||||
}
|
||||
|
||||
impl Serialize for ValueType {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.append_u8(*self as u8);
|
||||
}
|
||||
}
|
||||
|
||||
pub enum BlockType {
|
||||
NoResult,
|
||||
Value(ValueType),
|
||||
}
|
||||
|
||||
impl BlockType {
|
||||
pub fn as_byte(&self) -> u8 {
|
||||
match self {
|
||||
Self::NoResult => 0x40,
|
||||
Self::Value(t) => *t as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wasm memory alignment. (Rust representation matches Wasm encoding)
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Align {
|
||||
Bytes1 = 0,
|
||||
Bytes2 = 1,
|
||||
Bytes4 = 2,
|
||||
Bytes8 = 3,
|
||||
Bytes16 = 4,
|
||||
Bytes32 = 5,
|
||||
Bytes64 = 6,
|
||||
// ... we can add more if we need them ...
|
||||
}
|
||||
|
||||
impl From<u32> for Align {
|
||||
fn from(x: u32) -> Align {
|
||||
match x {
|
||||
1 => Align::Bytes1,
|
||||
2 => Align::Bytes2,
|
||||
4 => Align::Bytes4,
|
||||
8 => Align::Bytes8,
|
||||
16 => Align::Bytes16,
|
||||
32 => Align::Bytes32,
|
||||
64 => Align::Bytes64,
|
||||
_ => panic!("{:?}-byte alignment not supported", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
pub enum VirtualMachineSymbolState {
|
||||
/// Value doesn't exist yet
|
||||
NotYetPushed,
|
||||
|
||||
/// Value has been pushed onto the VM stack but not yet popped
|
||||
/// Remember where it was pushed, in case we need to insert another instruction there later
|
||||
Pushed { pushed_at: usize },
|
||||
|
||||
/// Value has been pushed and popped, so it's not on the VM stack any more.
|
||||
/// If we want to use it again later, we will have to create a local for it,
|
||||
/// by going back to insert a local.tee instruction at pushed_at
|
||||
Popped { pushed_at: usize },
|
||||
}
|
||||
|
||||
// An instruction (local.set or local.tee) to be inserted into the function code
|
||||
#[derive(Debug)]
|
||||
struct Insertion {
|
||||
at: usize,
|
||||
start: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
macro_rules! instruction_no_args {
|
||||
($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => {
|
||||
pub fn $method_name(&mut self) {
|
||||
self.inst($opcode, $pops, $push);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! instruction_memargs {
|
||||
($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => {
|
||||
pub fn $method_name(&mut self, align: Align, offset: u32) {
|
||||
self.inst_mem($opcode, $pops, $push, align, offset);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CodeBuilder<'a> {
|
||||
/// The main container for the instructions
|
||||
code: Vec<'a, u8>,
|
||||
|
||||
/// Instruction bytes to be inserted into the code when finalizing the function
|
||||
/// (Used for setting locals when we realise they are used multiple times)
|
||||
insert_bytes: Vec<'a, u8>,
|
||||
|
||||
/// Code locations where the insert_bytes should go
|
||||
insertions: Vec<'a, Insertion>,
|
||||
|
||||
/// Bytes for local variable declarations and stack-frame setup code.
|
||||
/// We can't write this until we've finished the main code. But it goes
|
||||
/// before it in the final output, so we need a separate vector.
|
||||
preamble: Vec<'a, u8>,
|
||||
|
||||
/// Encoded bytes for the inner length of the function, locals + code.
|
||||
/// ("inner" because it doesn't include its own length!)
|
||||
/// Again, we can't write this until we've finished the code and preamble,
|
||||
/// but it goes before them in the binary, so it's a separate vector.
|
||||
inner_length: Vec<'a, u8>,
|
||||
|
||||
/// Our simulation model of the Wasm stack machine
|
||||
/// Keeps track of where Symbol values are in the VM stack
|
||||
vm_stack: Vec<'a, Symbol>,
|
||||
|
||||
/// Linker info to help combine the Roc module with builtin & platform modules,
|
||||
/// e.g. to modify call instructions when function indices change
|
||||
relocations: Vec<'a, RelocationEntry>,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl<'a> CodeBuilder<'a> {
|
||||
pub fn new(arena: &'a Bump) -> Self {
|
||||
CodeBuilder {
|
||||
code: Vec::with_capacity_in(1024, arena),
|
||||
insertions: Vec::with_capacity_in(32, arena),
|
||||
insert_bytes: Vec::with_capacity_in(64, arena),
|
||||
preamble: Vec::with_capacity_in(32, arena),
|
||||
inner_length: Vec::with_capacity_in(5, arena),
|
||||
vm_stack: Vec::with_capacity_in(32, arena),
|
||||
relocations: Vec::with_capacity_in(32, arena),
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
|
||||
SYMBOLS
|
||||
|
||||
The Wasm VM stores temporary values in its stack machine.
|
||||
We track which stack positions correspond to IR Symbols,
|
||||
because it helps to generate more efficient code.
|
||||
|
||||
***********************************************************/
|
||||
|
||||
/// Set the Symbol that is at the top of the VM stack right now
|
||||
/// We will use this later when we need to load the Symbol
|
||||
pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState {
|
||||
let len = self.vm_stack.len();
|
||||
let pushed_at = self.code.len();
|
||||
|
||||
if len == 0 {
|
||||
panic!(
|
||||
"trying to set symbol with nothing on stack, code = {:?}",
|
||||
self.code
|
||||
);
|
||||
}
|
||||
|
||||
self.vm_stack[len - 1] = sym;
|
||||
|
||||
VirtualMachineSymbolState::Pushed { pushed_at }
|
||||
}
|
||||
|
||||
/// Verify if a sequence of symbols is at the top of the stack
|
||||
pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool {
|
||||
let n_symbols = symbols.len();
|
||||
let stack_depth = self.vm_stack.len();
|
||||
if n_symbols > stack_depth {
|
||||
return false;
|
||||
}
|
||||
let offset = stack_depth - n_symbols;
|
||||
|
||||
for (i, sym) in symbols.iter().enumerate() {
|
||||
if self.vm_stack[offset + i] != *sym {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn add_insertion(&mut self, insert_at: usize, opcode: u8, immediate: u32) {
|
||||
let start = self.insert_bytes.len();
|
||||
|
||||
self.insert_bytes.push(opcode);
|
||||
self.insert_bytes.encode_u32(immediate);
|
||||
|
||||
self.insertions.push(Insertion {
|
||||
at: insert_at,
|
||||
start,
|
||||
end: self.insert_bytes.len(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Load a Symbol that is stored in the VM stack
|
||||
/// If it's already at the top of the stack, no code will be generated.
|
||||
/// Otherwise, local.set and local.get instructions will be inserted, using the LocalId provided.
|
||||
///
|
||||
/// If the return value is `Some(s)`, `s` should be stored by the caller, and provided in the next call.
|
||||
/// If the return value is `None`, the Symbol is no longer stored in the VM stack, but in a local.
|
||||
/// (In this case, the caller must remember to declare the local in the function header.)
|
||||
pub fn load_symbol(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
vm_state: VirtualMachineSymbolState,
|
||||
next_local_id: LocalId,
|
||||
) -> Option<VirtualMachineSymbolState> {
|
||||
use VirtualMachineSymbolState::*;
|
||||
|
||||
match vm_state {
|
||||
NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol),
|
||||
|
||||
Pushed { pushed_at } => {
|
||||
let &top = self.vm_stack.last().unwrap();
|
||||
if top == symbol {
|
||||
// We're lucky, the symbol is already on top of the VM stack
|
||||
// No code to generate! (This reduces code size by up to 25% in tests.)
|
||||
// Just let the caller know what happened
|
||||
Some(Popped { pushed_at })
|
||||
} else {
|
||||
// Symbol is not on top of the stack. Find it.
|
||||
if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) {
|
||||
// Insert a local.set where the value was created
|
||||
self.add_insertion(pushed_at, SETLOCAL, next_local_id.0);
|
||||
|
||||
// Take the value out of the stack where local.set was inserted
|
||||
self.vm_stack.remove(found_index);
|
||||
|
||||
// Insert a local.get at the current position
|
||||
self.get_local(next_local_id);
|
||||
self.vm_stack.push(symbol);
|
||||
|
||||
// This Symbol is no longer stored in the VM stack, but in a local
|
||||
None
|
||||
} else {
|
||||
panic!(
|
||||
"{:?} has state {:?} but not found in VM stack",
|
||||
symbol, vm_state
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Popped { pushed_at } => {
|
||||
// This Symbol is being used for a second time
|
||||
// Insert a local.tee where it was pushed, so we don't interfere with the first usage
|
||||
self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
|
||||
|
||||
// Insert a local.get at the current position
|
||||
self.get_local(next_local_id);
|
||||
self.vm_stack.push(symbol);
|
||||
|
||||
// This symbol has been promoted to a Local
|
||||
// Tell the caller it no longer has a VirtualMachineSymbolState
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
|
||||
FINALIZE AND SERIALIZE
|
||||
|
||||
***********************************************************/
|
||||
|
||||
/// Generate bytes to declare the function's local variables
|
||||
fn build_local_declarations(&mut self, local_types: &[ValueType]) {
|
||||
// reserve one byte for num_batches
|
||||
self.preamble.push(0);
|
||||
|
||||
if local_types.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write declarations in batches of the same ValueType
|
||||
let mut num_batches: u32 = 0;
|
||||
let mut batch_type = local_types[0];
|
||||
let mut batch_size = 0;
|
||||
for t in local_types {
|
||||
if *t == batch_type {
|
||||
batch_size += 1;
|
||||
} else {
|
||||
self.preamble.encode_u32(batch_size);
|
||||
self.preamble.push(batch_type as u8);
|
||||
batch_type = *t;
|
||||
batch_size = 1;
|
||||
num_batches += 1;
|
||||
}
|
||||
}
|
||||
self.preamble.encode_u32(batch_size);
|
||||
self.preamble.push(batch_type as u8);
|
||||
num_batches += 1;
|
||||
|
||||
// Go back and write the number of batches at the start
|
||||
if num_batches < 128 {
|
||||
self.preamble[0] = num_batches as u8;
|
||||
} else {
|
||||
// We need more than 1 byte to encode num_batches!
|
||||
// This is a ridiculous edge case, so just pad to 5 bytes for simplicity
|
||||
let old_len = self.preamble.len();
|
||||
self.preamble.resize(old_len + 4, 0);
|
||||
self.preamble.copy_within(1..old_len, 5);
|
||||
self.preamble.overwrite_padded_u32(0, num_batches);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate instruction bytes to grab a frame of stack memory on entering the function
|
||||
fn build_stack_frame_push(&mut self, frame_size: i32, frame_pointer: LocalId) {
|
||||
// Can't use the usual instruction methods because they push to self.code.
|
||||
// This is the only case where we push instructions somewhere different.
|
||||
self.preamble.push(GETGLOBAL);
|
||||
self.preamble.encode_u32(STACK_POINTER_GLOBAL_ID);
|
||||
self.preamble.push(I32CONST);
|
||||
self.preamble.encode_i32(frame_size);
|
||||
self.preamble.push(I32SUB);
|
||||
self.preamble.push(TEELOCAL);
|
||||
self.preamble.encode_u32(frame_pointer.0);
|
||||
self.preamble.push(SETGLOBAL);
|
||||
self.preamble.encode_u32(STACK_POINTER_GLOBAL_ID);
|
||||
}
|
||||
|
||||
/// Generate instruction bytes to release a frame of stack memory on leaving the function
|
||||
fn build_stack_frame_pop(&mut self, frame_size: i32, frame_pointer: LocalId) {
|
||||
self.get_local(frame_pointer);
|
||||
self.i32_const(frame_size);
|
||||
self.i32_add();
|
||||
self.set_global(STACK_POINTER_GLOBAL_ID);
|
||||
}
|
||||
|
||||
/// Finalize the function
|
||||
/// Generate all the "extra" bytes: local declarations, stack frame push/pop code, and function length
|
||||
/// After this, bytes will have been _generated_, but not yet _serialized_ into a single stream.
|
||||
/// Returns the final number of bytes the function will occupy in the target binary
|
||||
pub fn finalize(
|
||||
&mut self,
|
||||
local_types: &[ValueType],
|
||||
frame_size: i32,
|
||||
frame_pointer: Option<LocalId>,
|
||||
) {
|
||||
self.build_local_declarations(local_types);
|
||||
|
||||
if let Some(frame_ptr_id) = frame_pointer {
|
||||
let aligned_size = round_up_to_alignment(frame_size, FRAME_ALIGNMENT_BYTES);
|
||||
self.build_stack_frame_push(aligned_size, frame_ptr_id);
|
||||
self.build_stack_frame_pop(aligned_size, frame_ptr_id);
|
||||
}
|
||||
|
||||
self.code.push(END);
|
||||
|
||||
let inner_len = self.preamble.len() + self.code.len() + self.insert_bytes.len();
|
||||
self.inner_length.encode_u32(inner_len as u32);
|
||||
|
||||
// Sort insertions. They are not created in order of assignment, but in order of *second* usage.
|
||||
self.insertions.sort_by_key(|ins| ins.at);
|
||||
}
|
||||
|
||||
/// Serialize all byte vectors in the right order
|
||||
/// Also update relocation offsets relative to the provided base offset in the buffer
|
||||
pub fn serialize_with_relocs<T: SerialBuffer>(
|
||||
&self,
|
||||
buffer: &mut T,
|
||||
final_relocs: &mut Vec<'a, RelocationEntry>,
|
||||
reloc_base_offset: usize,
|
||||
) {
|
||||
buffer.append_slice(&self.inner_length);
|
||||
buffer.append_slice(&self.preamble);
|
||||
|
||||
// Do the insertions & update relocation offsets
|
||||
let mut reloc_index = 0;
|
||||
let mut code_pos = 0;
|
||||
let mut insert_iter = self.insertions.iter();
|
||||
|
||||
loop {
|
||||
let next_insert = insert_iter.next();
|
||||
let next_pos = next_insert.map(|i| i.at).unwrap_or(self.code.len());
|
||||
|
||||
// Relocation offset needs to be an index into the body of the code section, but
|
||||
// at this point it is an index into self.code. Need to adjust for all previous functions
|
||||
// in the code section, and for insertions in the current function.
|
||||
let section_body_pos = buffer.size() - reloc_base_offset;
|
||||
while reloc_index < self.relocations.len()
|
||||
&& self.relocations[reloc_index].offset() < next_pos as u32
|
||||
{
|
||||
let mut reloc_clone = self.relocations[reloc_index].clone();
|
||||
*reloc_clone.offset_mut() += (section_body_pos - code_pos) as u32;
|
||||
final_relocs.push(reloc_clone);
|
||||
reloc_index += 1;
|
||||
}
|
||||
|
||||
buffer.append_slice(&self.code[code_pos..next_pos]);
|
||||
|
||||
match next_insert {
|
||||
Some(Insertion { at, start, end }) => {
|
||||
buffer.append_slice(&self.insert_bytes[*start..*end]);
|
||||
code_pos = *at;
|
||||
}
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
|
||||
INSTRUCTION HELPER METHODS
|
||||
|
||||
***********************************************************/
|
||||
|
||||
/// Base method for generating instructions
|
||||
/// Emits the opcode and simulates VM stack push/pop
|
||||
fn inst(&mut self, opcode: u8, pops: usize, push: bool) {
|
||||
let new_len = self.vm_stack.len() - pops as usize;
|
||||
self.vm_stack.truncate(new_len);
|
||||
if push {
|
||||
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
|
||||
}
|
||||
self.code.push(opcode);
|
||||
}
|
||||
|
||||
fn inst_imm8(&mut self, opcode: u8, pops: usize, push: bool, immediate: u8) {
|
||||
self.inst(opcode, pops, push);
|
||||
self.code.push(immediate);
|
||||
}
|
||||
|
||||
// public for use in test code
|
||||
pub fn inst_imm32(&mut self, opcode: u8, pops: usize, push: bool, immediate: u32) {
|
||||
self.inst(opcode, pops, push);
|
||||
self.code.encode_u32(immediate);
|
||||
}
|
||||
|
||||
fn inst_mem(&mut self, opcode: u8, pops: usize, push: bool, align: Align, offset: u32) {
|
||||
self.inst(opcode, pops, push);
|
||||
self.code.push(align as u8);
|
||||
self.code.encode_u32(offset);
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
|
||||
INSTRUCTION METHODS
|
||||
|
||||
One method for each Wasm instruction (in same order as the spec)
|
||||
macros are for compactness & readability for the most common cases
|
||||
Patterns that don't repeat very much don't have macros
|
||||
|
||||
***********************************************************/
|
||||
|
||||
instruction_no_args!(unreachable_, UNREACHABLE, 0, false);
|
||||
instruction_no_args!(nop, NOP, 0, false);
|
||||
|
||||
pub fn block(&mut self, ty: BlockType) {
|
||||
self.inst_imm8(BLOCK, 0, false, ty.as_byte());
|
||||
}
|
||||
pub fn loop_(&mut self, ty: BlockType) {
|
||||
self.inst_imm8(LOOP, 0, false, ty.as_byte());
|
||||
}
|
||||
pub fn if_(&mut self, ty: BlockType) {
|
||||
self.inst_imm8(IF, 1, false, ty.as_byte());
|
||||
}
|
||||
|
||||
instruction_no_args!(else_, ELSE, 0, false);
|
||||
instruction_no_args!(end, END, 0, false);
|
||||
|
||||
pub fn br(&mut self, levels: u32) {
|
||||
self.inst_imm32(BR, 0, false, levels);
|
||||
}
|
||||
pub fn br_if(&mut self, levels: u32) {
|
||||
self.inst_imm32(BRIF, 1, false, levels);
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
fn br_table() {
|
||||
panic!("TODO");
|
||||
}
|
||||
|
||||
instruction_no_args!(return_, RETURN, 0, false);
|
||||
|
||||
pub fn call(
|
||||
&mut self,
|
||||
function_index: u32,
|
||||
symbol_index: u32,
|
||||
n_args: usize,
|
||||
has_return_val: bool,
|
||||
) {
|
||||
let stack_depth = self.vm_stack.len();
|
||||
if n_args > stack_depth {
|
||||
panic!(
|
||||
"Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\n{:?}",
|
||||
function_index, n_args, stack_depth, self
|
||||
);
|
||||
}
|
||||
self.vm_stack.truncate(stack_depth - n_args);
|
||||
if has_return_val {
|
||||
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
|
||||
}
|
||||
self.code.push(CALL);
|
||||
|
||||
// Write the index of the function to be called.
|
||||
// Also make a RelocationEntry so the linker can see that this byte offset relates to a function by name.
|
||||
// Here we initialise the offset to an index of self.code. After completing the function, we'll add
|
||||
// other factors to make it relative to the code section. (All insertions will be known then.)
|
||||
let offset = self.code.len() as u32;
|
||||
self.code.encode_padded_u32(function_index);
|
||||
self.relocations.push(RelocationEntry::Index {
|
||||
type_id: IndexRelocType::FunctionIndexLeb,
|
||||
offset,
|
||||
symbol_index,
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn call_indirect() {
|
||||
panic!("Not implemented. Roc doesn't use function pointers");
|
||||
}
|
||||
|
||||
instruction_no_args!(drop_, DROP, 1, false);
|
||||
instruction_no_args!(select, SELECT, 3, true);
|
||||
|
||||
pub fn get_local(&mut self, id: LocalId) {
|
||||
self.inst_imm32(GETLOCAL, 0, true, id.0);
|
||||
}
|
||||
pub fn set_local(&mut self, id: LocalId) {
|
||||
self.inst_imm32(SETLOCAL, 1, false, id.0);
|
||||
}
|
||||
pub fn tee_local(&mut self, id: LocalId) {
|
||||
self.inst_imm32(TEELOCAL, 0, false, id.0);
|
||||
}
|
||||
pub fn get_global(&mut self, id: u32) {
|
||||
self.inst_imm32(GETGLOBAL, 0, true, id);
|
||||
}
|
||||
pub fn set_global(&mut self, id: u32) {
|
||||
self.inst_imm32(SETGLOBAL, 1, false, id);
|
||||
}
|
||||
|
||||
instruction_memargs!(i32_load, I32LOAD, 1, true);
|
||||
instruction_memargs!(i64_load, I64LOAD, 1, true);
|
||||
instruction_memargs!(f32_load, F32LOAD, 1, true);
|
||||
instruction_memargs!(f64_load, F64LOAD, 1, true);
|
||||
instruction_memargs!(i32_load8_s, I32LOAD8S, 1, true);
|
||||
instruction_memargs!(i32_load8_u, I32LOAD8U, 1, true);
|
||||
instruction_memargs!(i32_load16_s, I32LOAD16S, 1, true);
|
||||
instruction_memargs!(i32_load16_u, I32LOAD16U, 1, true);
|
||||
instruction_memargs!(i64_load8_s, I64LOAD8S, 1, true);
|
||||
instruction_memargs!(i64_load8_u, I64LOAD8U, 1, true);
|
||||
instruction_memargs!(i64_load16_s, I64LOAD16S, 1, true);
|
||||
instruction_memargs!(i64_load16_u, I64LOAD16U, 1, true);
|
||||
instruction_memargs!(i64_load32_s, I64LOAD32S, 1, true);
|
||||
instruction_memargs!(i64_load32_u, I64LOAD32U, 1, true);
|
||||
instruction_memargs!(i32_store, I32STORE, 2, false);
|
||||
instruction_memargs!(i64_store, I64STORE, 2, false);
|
||||
instruction_memargs!(f32_store, F32STORE, 2, false);
|
||||
instruction_memargs!(f64_store, F64STORE, 2, false);
|
||||
instruction_memargs!(i32_store8, I32STORE8, 2, false);
|
||||
instruction_memargs!(i32_store16, I32STORE16, 2, false);
|
||||
instruction_memargs!(i64_store8, I64STORE8, 2, false);
|
||||
instruction_memargs!(i64_store16, I64STORE16, 2, false);
|
||||
instruction_memargs!(i64_store32, I64STORE32, 2, false);
|
||||
|
||||
pub fn memory_size(&mut self) {
|
||||
self.inst_imm8(CURRENTMEMORY, 0, true, 0);
|
||||
}
|
||||
pub fn memory_grow(&mut self) {
|
||||
self.inst_imm8(GROWMEMORY, 1, true, 0);
|
||||
}
|
||||
pub fn i32_const(&mut self, x: i32) {
|
||||
self.inst(I32CONST, 0, true);
|
||||
self.code.encode_i32(x);
|
||||
}
|
||||
pub fn i64_const(&mut self, x: i64) {
|
||||
self.inst(I64CONST, 0, true);
|
||||
self.code.encode_i64(x);
|
||||
}
|
||||
pub fn f32_const(&mut self, x: f32) {
|
||||
self.inst(F32CONST, 0, true);
|
||||
self.code.encode_f32(x);
|
||||
}
|
||||
pub fn f64_const(&mut self, x: f64) {
|
||||
self.inst(F64CONST, 0, true);
|
||||
self.code.encode_f64(x);
|
||||
}
|
||||
|
||||
// TODO: Consider creating unified methods for numerical ops like 'eq' and 'add',
|
||||
// passing the ValueType as an argument. Could simplify lowlevel code gen.
|
||||
instruction_no_args!(i32_eqz, I32EQZ, 1, true);
|
||||
instruction_no_args!(i32_eq, I32EQ, 2, true);
|
||||
instruction_no_args!(i32_ne, I32NE, 2, true);
|
||||
instruction_no_args!(i32_lt_s, I32LTS, 2, true);
|
||||
instruction_no_args!(i32_lt_u, I32LTU, 2, true);
|
||||
instruction_no_args!(i32_gt_s, I32GTS, 2, true);
|
||||
instruction_no_args!(i32_gt_u, I32GTU, 2, true);
|
||||
instruction_no_args!(i32_le_s, I32LES, 2, true);
|
||||
instruction_no_args!(i32_le_u, I32LEU, 2, true);
|
||||
instruction_no_args!(i32_ge_s, I32GES, 2, true);
|
||||
instruction_no_args!(i32_ge_u, I32GEU, 2, true);
|
||||
instruction_no_args!(i64_eqz, I64EQZ, 1, true);
|
||||
instruction_no_args!(i64_eq, I64EQ, 2, true);
|
||||
instruction_no_args!(i64_ne, I64NE, 2, true);
|
||||
instruction_no_args!(i64_lt_s, I64LTS, 2, true);
|
||||
instruction_no_args!(i64_lt_u, I64LTU, 2, true);
|
||||
instruction_no_args!(i64_gt_s, I64GTS, 2, true);
|
||||
instruction_no_args!(i64_gt_u, I64GTU, 2, true);
|
||||
instruction_no_args!(i64_le_s, I64LES, 2, true);
|
||||
instruction_no_args!(i64_le_u, I64LEU, 2, true);
|
||||
instruction_no_args!(i64_ge_s, I64GES, 2, true);
|
||||
instruction_no_args!(i64_ge_u, I64GEU, 2, true);
|
||||
instruction_no_args!(f32_eq, F32EQ, 2, true);
|
||||
instruction_no_args!(f32_ne, F32NE, 2, true);
|
||||
instruction_no_args!(f32_lt, F32LT, 2, true);
|
||||
instruction_no_args!(f32_gt, F32GT, 2, true);
|
||||
instruction_no_args!(f32_le, F32LE, 2, true);
|
||||
instruction_no_args!(f32_ge, F32GE, 2, true);
|
||||
instruction_no_args!(f64_eq, F64EQ, 2, true);
|
||||
instruction_no_args!(f64_ne, F64NE, 2, true);
|
||||
instruction_no_args!(f64_lt, F64LT, 2, true);
|
||||
instruction_no_args!(f64_gt, F64GT, 2, true);
|
||||
instruction_no_args!(f64_le, F64LE, 2, true);
|
||||
instruction_no_args!(f64_ge, F64GE, 2, true);
|
||||
instruction_no_args!(i32_clz, I32CLZ, 1, true);
|
||||
instruction_no_args!(i32_ctz, I32CTZ, 1, true);
|
||||
instruction_no_args!(i32_popcnt, I32POPCNT, 1, true);
|
||||
instruction_no_args!(i32_add, I32ADD, 2, true);
|
||||
instruction_no_args!(i32_sub, I32SUB, 2, true);
|
||||
instruction_no_args!(i32_mul, I32MUL, 2, true);
|
||||
instruction_no_args!(i32_div_s, I32DIVS, 2, true);
|
||||
instruction_no_args!(i32_div_u, I32DIVU, 2, true);
|
||||
instruction_no_args!(i32_rem_s, I32REMS, 2, true);
|
||||
instruction_no_args!(i32_rem_u, I32REMU, 2, true);
|
||||
instruction_no_args!(i32_and, I32AND, 2, true);
|
||||
instruction_no_args!(i32_or, I32OR, 2, true);
|
||||
instruction_no_args!(i32_xor, I32XOR, 2, true);
|
||||
instruction_no_args!(i32_shl, I32SHL, 2, true);
|
||||
instruction_no_args!(i32_shr_s, I32SHRS, 2, true);
|
||||
instruction_no_args!(i32_shr_u, I32SHRU, 2, true);
|
||||
instruction_no_args!(i32_rotl, I32ROTL, 2, true);
|
||||
instruction_no_args!(i32_rotr, I32ROTR, 2, true);
|
||||
instruction_no_args!(i64_clz, I64CLZ, 1, true);
|
||||
instruction_no_args!(i64_ctz, I64CTZ, 1, true);
|
||||
instruction_no_args!(i64_popcnt, I64POPCNT, 1, true);
|
||||
instruction_no_args!(i64_add, I64ADD, 2, true);
|
||||
instruction_no_args!(i64_sub, I64SUB, 2, true);
|
||||
instruction_no_args!(i64_mul, I64MUL, 2, true);
|
||||
instruction_no_args!(i64_div_s, I64DIVS, 2, true);
|
||||
instruction_no_args!(i64_div_u, I64DIVU, 2, true);
|
||||
instruction_no_args!(i64_rem_s, I64REMS, 2, true);
|
||||
instruction_no_args!(i64_rem_u, I64REMU, 2, true);
|
||||
instruction_no_args!(i64_and, I64AND, 2, true);
|
||||
instruction_no_args!(i64_or, I64OR, 2, true);
|
||||
instruction_no_args!(i64_xor, I64XOR, 2, true);
|
||||
instruction_no_args!(i64_shl, I64SHL, 2, true);
|
||||
instruction_no_args!(i64_shr_s, I64SHRS, 2, true);
|
||||
instruction_no_args!(i64_shr_u, I64SHRU, 2, true);
|
||||
instruction_no_args!(i64_rotl, I64ROTL, 2, true);
|
||||
instruction_no_args!(i64_rotr, I64ROTR, 2, true);
|
||||
instruction_no_args!(f32_abs, F32ABS, 1, true);
|
||||
instruction_no_args!(f32_neg, F32NEG, 1, true);
|
||||
instruction_no_args!(f32_ceil, F32CEIL, 1, true);
|
||||
instruction_no_args!(f32_floor, F32FLOOR, 1, true);
|
||||
instruction_no_args!(f32_trunc, F32TRUNC, 1, true);
|
||||
instruction_no_args!(f32_nearest, F32NEAREST, 1, true);
|
||||
instruction_no_args!(f32_sqrt, F32SQRT, 1, true);
|
||||
instruction_no_args!(f32_add, F32ADD, 2, true);
|
||||
instruction_no_args!(f32_sub, F32SUB, 2, true);
|
||||
instruction_no_args!(f32_mul, F32MUL, 2, true);
|
||||
instruction_no_args!(f32_div, F32DIV, 2, true);
|
||||
instruction_no_args!(f32_min, F32MIN, 2, true);
|
||||
instruction_no_args!(f32_max, F32MAX, 2, true);
|
||||
instruction_no_args!(f32_copysign, F32COPYSIGN, 2, true);
|
||||
instruction_no_args!(f64_abs, F64ABS, 1, true);
|
||||
instruction_no_args!(f64_neg, F64NEG, 1, true);
|
||||
instruction_no_args!(f64_ceil, F64CEIL, 1, true);
|
||||
instruction_no_args!(f64_floor, F64FLOOR, 1, true);
|
||||
instruction_no_args!(f64_trunc, F64TRUNC, 1, true);
|
||||
instruction_no_args!(f64_nearest, F64NEAREST, 1, true);
|
||||
instruction_no_args!(f64_sqrt, F64SQRT, 1, true);
|
||||
instruction_no_args!(f64_add, F64ADD, 2, true);
|
||||
instruction_no_args!(f64_sub, F64SUB, 2, true);
|
||||
instruction_no_args!(f64_mul, F64MUL, 2, true);
|
||||
instruction_no_args!(f64_div, F64DIV, 2, true);
|
||||
instruction_no_args!(f64_min, F64MIN, 2, true);
|
||||
instruction_no_args!(f64_max, F64MAX, 2, true);
|
||||
instruction_no_args!(f64_copysign, F64COPYSIGN, 2, true);
|
||||
instruction_no_args!(i32_wrap_i64, I32WRAPI64, 1, true);
|
||||
instruction_no_args!(i32_trunc_s_f32, I32TRUNCSF32, 1, true);
|
||||
instruction_no_args!(i32_trunc_u_f32, I32TRUNCUF32, 1, true);
|
||||
instruction_no_args!(i32_trunc_s_f64, I32TRUNCSF64, 1, true);
|
||||
instruction_no_args!(i32_trunc_u_f64, I32TRUNCUF64, 1, true);
|
||||
instruction_no_args!(i64_extend_s_i32, I64EXTENDSI32, 1, true);
|
||||
instruction_no_args!(i64_extend_u_i32, I64EXTENDUI32, 1, true);
|
||||
instruction_no_args!(i64_trunc_s_f32, I64TRUNCSF32, 1, true);
|
||||
instruction_no_args!(i64_trunc_u_f32, I64TRUNCUF32, 1, true);
|
||||
instruction_no_args!(i64_trunc_s_f64, I64TRUNCSF64, 1, true);
|
||||
instruction_no_args!(i64_trunc_u_f64, I64TRUNCUF64, 1, true);
|
||||
instruction_no_args!(f32_convert_s_i32, F32CONVERTSI32, 1, true);
|
||||
instruction_no_args!(f32_convert_u_i32, F32CONVERTUI32, 1, true);
|
||||
instruction_no_args!(f32_convert_s_i64, F32CONVERTSI64, 1, true);
|
||||
instruction_no_args!(f32_convert_u_i64, F32CONVERTUI64, 1, true);
|
||||
instruction_no_args!(f32_demote_f64, F32DEMOTEF64, 1, true);
|
||||
instruction_no_args!(f64_convert_s_i32, F64CONVERTSI32, 1, true);
|
||||
instruction_no_args!(f64_convert_u_i32, F64CONVERTUI32, 1, true);
|
||||
instruction_no_args!(f64_convert_s_i64, F64CONVERTSI64, 1, true);
|
||||
instruction_no_args!(f64_convert_u_i64, F64CONVERTUI64, 1, true);
|
||||
instruction_no_args!(f64_promote_f32, F64PROMOTEF32, 1, true);
|
||||
instruction_no_args!(i32_reinterpret_f32, I32REINTERPRETF32, 1, true);
|
||||
instruction_no_args!(i64_reinterpret_f64, I64REINTERPRETF64, 1, true);
|
||||
instruction_no_args!(f32_reinterpret_i32, F32REINTERPRETI32, 1, true);
|
||||
instruction_no_args!(f64_reinterpret_i64, F64REINTERPRETI64, 1, true);
|
||||
}
|
12
compiler/gen_wasm/src/wasm_module/mod.rs
Normal file
12
compiler/gen_wasm/src/wasm_module/mod.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
pub mod code_builder;
|
||||
pub mod opcodes;
|
||||
pub mod sections;
|
||||
pub mod serialize;
|
||||
|
||||
pub use code_builder::{
|
||||
Align, BlockType, CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState,
|
||||
};
|
||||
pub use sections::{
|
||||
Export, ExportType, Global, GlobalInitValue, GlobalType, LinkingSubSection, Signature, SymInfo,
|
||||
WasmModule,
|
||||
};
|
178
compiler/gen_wasm/src/wasm_module/opcodes.rs
Normal file
178
compiler/gen_wasm/src/wasm_module/opcodes.rs
Normal file
|
@ -0,0 +1,178 @@
|
|||
pub const UNREACHABLE: u8 = 0x00;
|
||||
pub const NOP: u8 = 0x01;
|
||||
pub const BLOCK: u8 = 0x02;
|
||||
pub const LOOP: u8 = 0x03;
|
||||
pub const IF: u8 = 0x04;
|
||||
pub const ELSE: u8 = 0x05;
|
||||
pub const END: u8 = 0x0b;
|
||||
pub const BR: u8 = 0x0c;
|
||||
pub const BRIF: u8 = 0x0d;
|
||||
pub const BRTABLE: u8 = 0x0e;
|
||||
pub const RETURN: u8 = 0x0f;
|
||||
pub const CALL: u8 = 0x10;
|
||||
pub const CALLINDIRECT: u8 = 0x11;
|
||||
pub const DROP: u8 = 0x1a;
|
||||
pub const SELECT: u8 = 0x1b;
|
||||
pub const GETLOCAL: u8 = 0x20;
|
||||
pub const SETLOCAL: u8 = 0x21;
|
||||
pub const TEELOCAL: u8 = 0x22;
|
||||
pub const GETGLOBAL: u8 = 0x23;
|
||||
pub const SETGLOBAL: u8 = 0x24;
|
||||
pub const I32LOAD: u8 = 0x28;
|
||||
pub const I64LOAD: u8 = 0x29;
|
||||
pub const F32LOAD: u8 = 0x2a;
|
||||
pub const F64LOAD: u8 = 0x2b;
|
||||
pub const I32LOAD8S: u8 = 0x2c;
|
||||
pub const I32LOAD8U: u8 = 0x2d;
|
||||
pub const I32LOAD16S: u8 = 0x2e;
|
||||
pub const I32LOAD16U: u8 = 0x2f;
|
||||
pub const I64LOAD8S: u8 = 0x30;
|
||||
pub const I64LOAD8U: u8 = 0x31;
|
||||
pub const I64LOAD16S: u8 = 0x32;
|
||||
pub const I64LOAD16U: u8 = 0x33;
|
||||
pub const I64LOAD32S: u8 = 0x34;
|
||||
pub const I64LOAD32U: u8 = 0x35;
|
||||
pub const I32STORE: u8 = 0x36;
|
||||
pub const I64STORE: u8 = 0x37;
|
||||
pub const F32STORE: u8 = 0x38;
|
||||
pub const F64STORE: u8 = 0x39;
|
||||
pub const I32STORE8: u8 = 0x3a;
|
||||
pub const I32STORE16: u8 = 0x3b;
|
||||
pub const I64STORE8: u8 = 0x3c;
|
||||
pub const I64STORE16: u8 = 0x3d;
|
||||
pub const I64STORE32: u8 = 0x3e;
|
||||
pub const CURRENTMEMORY: u8 = 0x3f;
|
||||
pub const GROWMEMORY: u8 = 0x40;
|
||||
pub const I32CONST: u8 = 0x41;
|
||||
pub const I64CONST: u8 = 0x42;
|
||||
pub const F32CONST: u8 = 0x43;
|
||||
pub const F64CONST: u8 = 0x44;
|
||||
pub const I32EQZ: u8 = 0x45;
|
||||
pub const I32EQ: u8 = 0x46;
|
||||
pub const I32NE: u8 = 0x47;
|
||||
pub const I32LTS: u8 = 0x48;
|
||||
pub const I32LTU: u8 = 0x49;
|
||||
pub const I32GTS: u8 = 0x4a;
|
||||
pub const I32GTU: u8 = 0x4b;
|
||||
pub const I32LES: u8 = 0x4c;
|
||||
pub const I32LEU: u8 = 0x4d;
|
||||
pub const I32GES: u8 = 0x4e;
|
||||
pub const I32GEU: u8 = 0x4f;
|
||||
pub const I64EQZ: u8 = 0x50;
|
||||
pub const I64EQ: u8 = 0x51;
|
||||
pub const I64NE: u8 = 0x52;
|
||||
pub const I64LTS: u8 = 0x53;
|
||||
pub const I64LTU: u8 = 0x54;
|
||||
pub const I64GTS: u8 = 0x55;
|
||||
pub const I64GTU: u8 = 0x56;
|
||||
pub const I64LES: u8 = 0x57;
|
||||
pub const I64LEU: u8 = 0x58;
|
||||
pub const I64GES: u8 = 0x59;
|
||||
pub const I64GEU: u8 = 0x5a;
|
||||
|
||||
pub const F32EQ: u8 = 0x5b;
|
||||
pub const F32NE: u8 = 0x5c;
|
||||
pub const F32LT: u8 = 0x5d;
|
||||
pub const F32GT: u8 = 0x5e;
|
||||
pub const F32LE: u8 = 0x5f;
|
||||
pub const F32GE: u8 = 0x60;
|
||||
|
||||
pub const F64EQ: u8 = 0x61;
|
||||
pub const F64NE: u8 = 0x62;
|
||||
pub const F64LT: u8 = 0x63;
|
||||
pub const F64GT: u8 = 0x64;
|
||||
pub const F64LE: u8 = 0x65;
|
||||
pub const F64GE: u8 = 0x66;
|
||||
|
||||
pub const I32CLZ: u8 = 0x67;
|
||||
pub const I32CTZ: u8 = 0x68;
|
||||
pub const I32POPCNT: u8 = 0x69;
|
||||
pub const I32ADD: u8 = 0x6a;
|
||||
pub const I32SUB: u8 = 0x6b;
|
||||
pub const I32MUL: u8 = 0x6c;
|
||||
pub const I32DIVS: u8 = 0x6d;
|
||||
pub const I32DIVU: u8 = 0x6e;
|
||||
pub const I32REMS: u8 = 0x6f;
|
||||
pub const I32REMU: u8 = 0x70;
|
||||
pub const I32AND: u8 = 0x71;
|
||||
pub const I32OR: u8 = 0x72;
|
||||
pub const I32XOR: u8 = 0x73;
|
||||
pub const I32SHL: u8 = 0x74;
|
||||
pub const I32SHRS: u8 = 0x75;
|
||||
pub const I32SHRU: u8 = 0x76;
|
||||
pub const I32ROTL: u8 = 0x77;
|
||||
pub const I32ROTR: u8 = 0x78;
|
||||
|
||||
pub const I64CLZ: u8 = 0x79;
|
||||
pub const I64CTZ: u8 = 0x7a;
|
||||
pub const I64POPCNT: u8 = 0x7b;
|
||||
pub const I64ADD: u8 = 0x7c;
|
||||
pub const I64SUB: u8 = 0x7d;
|
||||
pub const I64MUL: u8 = 0x7e;
|
||||
pub const I64DIVS: u8 = 0x7f;
|
||||
pub const I64DIVU: u8 = 0x80;
|
||||
pub const I64REMS: u8 = 0x81;
|
||||
pub const I64REMU: u8 = 0x82;
|
||||
pub const I64AND: u8 = 0x83;
|
||||
pub const I64OR: u8 = 0x84;
|
||||
pub const I64XOR: u8 = 0x85;
|
||||
pub const I64SHL: u8 = 0x86;
|
||||
pub const I64SHRS: u8 = 0x87;
|
||||
pub const I64SHRU: u8 = 0x88;
|
||||
pub const I64ROTL: u8 = 0x89;
|
||||
pub const I64ROTR: u8 = 0x8a;
|
||||
pub const F32ABS: u8 = 0x8b;
|
||||
pub const F32NEG: u8 = 0x8c;
|
||||
pub const F32CEIL: u8 = 0x8d;
|
||||
pub const F32FLOOR: u8 = 0x8e;
|
||||
pub const F32TRUNC: u8 = 0x8f;
|
||||
pub const F32NEAREST: u8 = 0x90;
|
||||
pub const F32SQRT: u8 = 0x91;
|
||||
pub const F32ADD: u8 = 0x92;
|
||||
pub const F32SUB: u8 = 0x93;
|
||||
pub const F32MUL: u8 = 0x94;
|
||||
pub const F32DIV: u8 = 0x95;
|
||||
pub const F32MIN: u8 = 0x96;
|
||||
pub const F32MAX: u8 = 0x97;
|
||||
pub const F32COPYSIGN: u8 = 0x98;
|
||||
pub const F64ABS: u8 = 0x99;
|
||||
pub const F64NEG: u8 = 0x9a;
|
||||
pub const F64CEIL: u8 = 0x9b;
|
||||
pub const F64FLOOR: u8 = 0x9c;
|
||||
pub const F64TRUNC: u8 = 0x9d;
|
||||
pub const F64NEAREST: u8 = 0x9e;
|
||||
pub const F64SQRT: u8 = 0x9f;
|
||||
pub const F64ADD: u8 = 0xa0;
|
||||
pub const F64SUB: u8 = 0xa1;
|
||||
pub const F64MUL: u8 = 0xa2;
|
||||
pub const F64DIV: u8 = 0xa3;
|
||||
pub const F64MIN: u8 = 0xa4;
|
||||
pub const F64MAX: u8 = 0xa5;
|
||||
pub const F64COPYSIGN: u8 = 0xa6;
|
||||
|
||||
pub const I32WRAPI64: u8 = 0xa7;
|
||||
pub const I32TRUNCSF32: u8 = 0xa8;
|
||||
pub const I32TRUNCUF32: u8 = 0xa9;
|
||||
pub const I32TRUNCSF64: u8 = 0xaa;
|
||||
pub const I32TRUNCUF64: u8 = 0xab;
|
||||
pub const I64EXTENDSI32: u8 = 0xac;
|
||||
pub const I64EXTENDUI32: u8 = 0xad;
|
||||
pub const I64TRUNCSF32: u8 = 0xae;
|
||||
pub const I64TRUNCUF32: u8 = 0xaf;
|
||||
pub const I64TRUNCSF64: u8 = 0xb0;
|
||||
pub const I64TRUNCUF64: u8 = 0xb1;
|
||||
pub const F32CONVERTSI32: u8 = 0xb2;
|
||||
pub const F32CONVERTUI32: u8 = 0xb3;
|
||||
pub const F32CONVERTSI64: u8 = 0xb4;
|
||||
pub const F32CONVERTUI64: u8 = 0xb5;
|
||||
pub const F32DEMOTEF64: u8 = 0xb6;
|
||||
pub const F64CONVERTSI32: u8 = 0xb7;
|
||||
pub const F64CONVERTUI32: u8 = 0xb8;
|
||||
pub const F64CONVERTSI64: u8 = 0xb9;
|
||||
pub const F64CONVERTUI64: u8 = 0xba;
|
||||
pub const F64PROMOTEF32: u8 = 0xbb;
|
||||
|
||||
pub const I32REINTERPRETF32: u8 = 0xbc;
|
||||
pub const I64REINTERPRETF64: u8 = 0xbd;
|
||||
pub const F32REINTERPRETI32: u8 = 0xbe;
|
||||
pub const F64REINTERPRETI64: u8 = 0xbf;
|
1032
compiler/gen_wasm/src/wasm_module/sections.rs
Normal file
1032
compiler/gen_wasm/src/wasm_module/sections.rs
Normal file
File diff suppressed because it is too large
Load diff
395
compiler/gen_wasm/src/wasm_module/serialize.rs
Normal file
395
compiler/gen_wasm/src/wasm_module/serialize.rs
Normal file
|
@ -0,0 +1,395 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use bumpalo::collections::vec::Vec;
|
||||
|
||||
pub trait Serialize {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T);
|
||||
}
|
||||
|
||||
impl Serialize for str {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.encode_u32(self.len() as u32);
|
||||
buffer.append_slice(self.as_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for u32 {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.encode_u32(*self);
|
||||
}
|
||||
}
|
||||
|
||||
// Unit is used as a placeholder in parts of the Wasm spec we don't use yet
|
||||
impl Serialize for () {
|
||||
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {}
|
||||
}
|
||||
|
||||
impl<S: Serialize> Serialize for [S] {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
buffer.encode_u32(self.len() as u32);
|
||||
for item in self.iter() {
|
||||
item.serialize(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Serialize> Serialize for Option<S> {
|
||||
/// serialize Option as a vector of length 1 or 0
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
match self {
|
||||
Some(x) => {
|
||||
buffer.append_u8(1);
|
||||
x.serialize(buffer);
|
||||
}
|
||||
None => {
|
||||
buffer.append_u8(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write an unsigned integer into the provided buffer in LEB-128 format, returning byte length
|
||||
///
|
||||
/// All integers in Wasm are variable-length encoded, which saves space for small values.
|
||||
/// The most significant bit indicates "more bytes are coming", and the other 7 are payload.
|
||||
macro_rules! encode_uleb128 {
|
||||
($name: ident, $ty: ty) => {
|
||||
fn $name(&mut self, value: $ty) -> usize {
|
||||
let mut x = value;
|
||||
let start_len = self.size();
|
||||
while x >= 0x80 {
|
||||
self.append_u8(0x80 | ((x & 0x7f) as u8));
|
||||
x >>= 7;
|
||||
}
|
||||
self.append_u8(x as u8);
|
||||
self.size() - start_len
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Write a signed integer into the provided buffer in LEB-128 format, returning byte length
|
||||
macro_rules! encode_sleb128 {
|
||||
($name: ident, $ty: ty) => {
|
||||
fn $name(&mut self, value: $ty) -> usize {
|
||||
let mut x = value;
|
||||
let start_len = self.size();
|
||||
loop {
|
||||
let byte = (x & 0x7f) as u8;
|
||||
x >>= 7;
|
||||
let byte_is_negative = (byte & 0x40) != 0;
|
||||
if ((x == 0 && !byte_is_negative) || (x == -1 && byte_is_negative)) {
|
||||
self.append_u8(byte);
|
||||
break;
|
||||
}
|
||||
self.append_u8(byte | 0x80);
|
||||
}
|
||||
self.size() - start_len
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! write_unencoded {
|
||||
($name: ident, $ty: ty) => {
|
||||
/// write an unencoded little-endian integer (only used in relocations)
|
||||
fn $name(&mut self, value: $ty) {
|
||||
let mut x = value;
|
||||
let size = std::mem::size_of::<$ty>();
|
||||
for _ in 0..size {
|
||||
self.append_u8((x & 0xff) as u8);
|
||||
x >>= 8;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! encode_padded_sleb128 {
|
||||
($name: ident, $ty: ty) => {
|
||||
/// write a maximally-padded SLEB128 integer (only used in relocations)
|
||||
fn $name(&mut self, value: $ty) {
|
||||
let mut x = value;
|
||||
let size = (std::mem::size_of::<$ty>() / 4) * 5;
|
||||
for _ in 0..(size - 1) {
|
||||
self.append_u8(0x80 | (x & 0x7f) as u8);
|
||||
x >>= 7;
|
||||
}
|
||||
self.append_u8((x & 0x7f) as u8);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait SerialBuffer: Debug {
|
||||
fn append_u8(&mut self, b: u8);
|
||||
fn overwrite_u8(&mut self, index: usize, b: u8);
|
||||
fn append_slice(&mut self, b: &[u8]);
|
||||
|
||||
fn size(&self) -> usize;
|
||||
|
||||
encode_uleb128!(encode_u32, u32);
|
||||
encode_uleb128!(encode_u64, u64);
|
||||
encode_sleb128!(encode_i32, i32);
|
||||
encode_sleb128!(encode_i64, i64);
|
||||
|
||||
fn reserve_padded_u32(&mut self) -> usize;
|
||||
fn encode_padded_u32(&mut self, value: u32) -> usize;
|
||||
fn overwrite_padded_u32(&mut self, index: usize, value: u32);
|
||||
|
||||
fn encode_f32(&mut self, value: f32) {
|
||||
self.write_unencoded_u32(value.to_bits());
|
||||
}
|
||||
|
||||
fn encode_f64(&mut self, value: f64) {
|
||||
self.write_unencoded_u64(value.to_bits());
|
||||
}
|
||||
|
||||
// methods for relocations
|
||||
write_unencoded!(write_unencoded_u32, u32);
|
||||
write_unencoded!(write_unencoded_u64, u64);
|
||||
encode_padded_sleb128!(encode_padded_i32, i32);
|
||||
encode_padded_sleb128!(encode_padded_i64, i64);
|
||||
}
|
||||
|
||||
fn overwrite_padded_u32_help(buffer: &mut [u8], value: u32) {
|
||||
let mut x = value;
|
||||
for byte in buffer.iter_mut().take(4) {
|
||||
*byte = 0x80 | ((x & 0x7f) as u8);
|
||||
x >>= 7;
|
||||
}
|
||||
buffer[4] = x as u8;
|
||||
}
|
||||
|
||||
impl SerialBuffer for std::vec::Vec<u8> {
|
||||
fn append_u8(&mut self, b: u8) {
|
||||
self.push(b);
|
||||
}
|
||||
fn overwrite_u8(&mut self, index: usize, b: u8) {
|
||||
self[index] = b;
|
||||
}
|
||||
fn append_slice(&mut self, b: &[u8]) {
|
||||
self.extend_from_slice(b);
|
||||
}
|
||||
fn size(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
fn reserve_padded_u32(&mut self) -> usize {
|
||||
let index = self.len();
|
||||
self.resize(index + 5, 0xff);
|
||||
index
|
||||
}
|
||||
fn encode_padded_u32(&mut self, value: u32) -> usize {
|
||||
let index = self.len();
|
||||
let new_len = index + 5;
|
||||
self.resize(new_len, 0);
|
||||
overwrite_padded_u32_help(&mut self[index..new_len], value);
|
||||
index
|
||||
}
|
||||
fn overwrite_padded_u32(&mut self, index: usize, value: u32) {
|
||||
overwrite_padded_u32_help(&mut self[index..(index + 5)], value);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SerialBuffer for Vec<'a, u8> {
|
||||
fn append_u8(&mut self, b: u8) {
|
||||
self.push(b);
|
||||
}
|
||||
fn overwrite_u8(&mut self, index: usize, b: u8) {
|
||||
self[index] = b;
|
||||
}
|
||||
fn append_slice(&mut self, b: &[u8]) {
|
||||
self.extend_from_slice(b);
|
||||
}
|
||||
fn size(&self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
fn reserve_padded_u32(&mut self) -> usize {
|
||||
let index = self.len();
|
||||
self.resize(index + 5, 0xff);
|
||||
index
|
||||
}
|
||||
fn encode_padded_u32(&mut self, value: u32) -> usize {
|
||||
let index = self.len();
|
||||
let new_len = index + 5;
|
||||
self.resize(new_len, 0);
|
||||
overwrite_padded_u32_help(&mut self[index..new_len], value);
|
||||
index
|
||||
}
|
||||
fn overwrite_padded_u32(&mut self, index: usize, value: u32) {
|
||||
overwrite_padded_u32_help(&mut self[index..(index + 5)], value);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bumpalo::{self, collections::Vec, Bump};
|
||||
|
||||
fn help_u32<'a>(arena: &'a Bump, value: u32) -> Vec<'a, u8> {
|
||||
let mut buffer = Vec::with_capacity_in(5, arena);
|
||||
buffer.encode_u32(value);
|
||||
buffer
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_u32() {
|
||||
let a = &Bump::new();
|
||||
assert_eq!(help_u32(a, 0), &[0]);
|
||||
assert_eq!(help_u32(a, 64), &[64]);
|
||||
assert_eq!(help_u32(a, 0x7f), &[0x7f]);
|
||||
assert_eq!(help_u32(a, 0x80), &[0x80, 0x01]);
|
||||
assert_eq!(help_u32(a, 0x3fff), &[0xff, 0x7f]);
|
||||
assert_eq!(help_u32(a, 0x4000), &[0x80, 0x80, 0x01]);
|
||||
assert_eq!(help_u32(a, u32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x0f]);
|
||||
}
|
||||
|
||||
fn help_u64<'a>(arena: &'a Bump, value: u64) -> Vec<'a, u8> {
|
||||
let mut buffer = Vec::with_capacity_in(10, arena);
|
||||
buffer.encode_u64(value);
|
||||
buffer
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_u64() {
|
||||
let a = &Bump::new();
|
||||
assert_eq!(help_u64(a, 0), &[0]);
|
||||
assert_eq!(help_u64(a, 64), &[64]);
|
||||
assert_eq!(help_u64(a, 0x7f), &[0x7f]);
|
||||
assert_eq!(help_u64(a, 0x80), &[0x80, 0x01]);
|
||||
assert_eq!(help_u64(a, 0x3fff), &[0xff, 0x7f]);
|
||||
assert_eq!(help_u64(a, 0x4000), &[0x80, 0x80, 0x01]);
|
||||
assert_eq!(
|
||||
help_u64(a, u64::MAX),
|
||||
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01],
|
||||
);
|
||||
}
|
||||
|
||||
fn help_i32<'a>(arena: &'a Bump, value: i32) -> Vec<'a, u8> {
|
||||
let mut buffer = Vec::with_capacity_in(5, arena);
|
||||
buffer.encode_i32(value);
|
||||
buffer
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_i32() {
|
||||
let a = &Bump::new();
|
||||
assert_eq!(help_i32(a, 0), &[0]);
|
||||
assert_eq!(help_i32(a, 1), &[1]);
|
||||
assert_eq!(help_i32(a, -1), &[0x7f]);
|
||||
assert_eq!(help_i32(a, 63), &[63]);
|
||||
assert_eq!(help_i32(a, 64), &[0xc0, 0x0]);
|
||||
assert_eq!(help_i32(a, -64), &[0x40]);
|
||||
assert_eq!(help_i32(a, -65), &[0xbf, 0x7f]);
|
||||
assert_eq!(help_i32(a, i32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x07]);
|
||||
assert_eq!(help_i32(a, i32::MIN), &[0x80, 0x80, 0x80, 0x80, 0x78]);
|
||||
}
|
||||
|
||||
fn help_i64<'a>(arena: &'a Bump, value: i64) -> Vec<'a, u8> {
|
||||
let mut buffer = Vec::with_capacity_in(10, arena);
|
||||
buffer.encode_i64(value);
|
||||
buffer
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_i64() {
|
||||
let a = &Bump::new();
|
||||
assert_eq!(help_i64(a, 0), &[0]);
|
||||
assert_eq!(help_i64(a, 1), &[1]);
|
||||
assert_eq!(help_i64(a, -1), &[0x7f]);
|
||||
assert_eq!(help_i64(a, 63), &[63]);
|
||||
assert_eq!(help_i64(a, 64), &[0xc0, 0x0]);
|
||||
assert_eq!(help_i64(a, -64), &[0x40]);
|
||||
assert_eq!(help_i64(a, -65), &[0xbf, 0x7f]);
|
||||
assert_eq!(
|
||||
help_i64(a, i64::MAX),
|
||||
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00],
|
||||
);
|
||||
assert_eq!(
|
||||
help_i64(a, i64::MIN),
|
||||
&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_overwrite_u32_padded() {
|
||||
let mut buffer = [0, 0, 0, 0, 0];
|
||||
|
||||
overwrite_padded_u32_help(&mut buffer, u32::MAX);
|
||||
assert_eq!(buffer, [0xff, 0xff, 0xff, 0xff, 0x0f]);
|
||||
|
||||
overwrite_padded_u32_help(&mut buffer, 0);
|
||||
assert_eq!(buffer, [0x80, 0x80, 0x80, 0x80, 0x00]);
|
||||
|
||||
overwrite_padded_u32_help(&mut buffer, 127);
|
||||
assert_eq!(buffer, [0xff, 0x80, 0x80, 0x80, 0x00]);
|
||||
|
||||
overwrite_padded_u32_help(&mut buffer, 128);
|
||||
assert_eq!(buffer, [0x80, 0x81, 0x80, 0x80, 0x00]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_unencoded_u32() {
|
||||
let mut buffer = std::vec::Vec::with_capacity(4);
|
||||
|
||||
buffer.write_unencoded_u32(0);
|
||||
assert_eq!(buffer, &[0, 0, 0, 0]);
|
||||
|
||||
buffer.clear();
|
||||
buffer.write_unencoded_u32(u32::MAX);
|
||||
assert_eq!(buffer, &[0xff, 0xff, 0xff, 0xff]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_unencoded_u64() {
|
||||
let mut buffer = std::vec::Vec::with_capacity(8);
|
||||
|
||||
buffer.write_unencoded_u64(0);
|
||||
assert_eq!(buffer, &[0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
|
||||
buffer.clear();
|
||||
buffer.write_unencoded_u64(u64::MAX);
|
||||
assert_eq!(buffer, &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
|
||||
}
|
||||
|
||||
fn help_pad_i32(val: i32) -> std::vec::Vec<u8> {
|
||||
let mut buffer = std::vec::Vec::with_capacity(5);
|
||||
buffer.encode_padded_i32(val);
|
||||
buffer
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_padded_i32() {
|
||||
assert_eq!(help_pad_i32(0), &[0x80, 0x80, 0x80, 0x80, 0x00]);
|
||||
assert_eq!(help_pad_i32(1), &[0x81, 0x80, 0x80, 0x80, 0x00]);
|
||||
assert_eq!(help_pad_i32(-1), &[0xff, 0xff, 0xff, 0xff, 0x7f]);
|
||||
assert_eq!(help_pad_i32(i32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x07]);
|
||||
assert_eq!(help_pad_i32(i32::MIN), &[0x80, 0x80, 0x80, 0x80, 0x78]);
|
||||
}
|
||||
|
||||
fn help_pad_i64(val: i64) -> std::vec::Vec<u8> {
|
||||
let mut buffer = std::vec::Vec::with_capacity(10);
|
||||
buffer.encode_padded_i64(val);
|
||||
buffer
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_padded_i64() {
|
||||
assert_eq!(
|
||||
help_pad_i64(0),
|
||||
&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00]
|
||||
);
|
||||
assert_eq!(
|
||||
help_pad_i64(1),
|
||||
&[0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00]
|
||||
);
|
||||
assert_eq!(
|
||||
help_pad_i64(-1),
|
||||
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]
|
||||
);
|
||||
assert_eq!(
|
||||
help_pad_i64(i64::MAX),
|
||||
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00],
|
||||
);
|
||||
assert_eq!(
|
||||
help_pad_i64(i64::MIN),
|
||||
&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f],
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue