cranelift-wasm: support multi-value Wasm (#1049)
This commit introduces initial support for multi-value Wasm. Wasm blocks and calls can now take and return an arbitrary number of values. The encoding for multi-value blocks means that we need to keep the contents of the "Types" section around when translating function bodies. To do this, we introduce a `WasmTypesMap` type that maps the type indices to their parameters and returns, construct it when parsing the "Types" section, and shepherd it through a bunch of functions and methods when translating function bodies.
This commit is contained in:
@@ -9,6 +9,24 @@ use crate::translation_utils::{FuncIndex, GlobalIndex, MemoryIndex, SignatureInd
|
||||
use cranelift_codegen::ir::{self, Ebb, Inst, Value};
|
||||
use std::vec::Vec;
|
||||
|
||||
/// Information about the presence of an associated `else` for an `if`, or the
|
||||
/// lack thereof.
|
||||
#[derive(Debug)]
|
||||
pub enum ElseData {
|
||||
/// The `if` does not already have an `else` block.
|
||||
NoElse {
|
||||
/// If we discover that we need an `else` block, this is the jump
|
||||
/// instruction that needs to be fixed up to point to the new `else`
|
||||
/// block rather than the destination block after the `if...end`.
|
||||
branch_inst: Inst,
|
||||
},
|
||||
/// We have already allocated an `else` block.
|
||||
WithElse {
|
||||
/// This is the `else` block.
|
||||
else_block: Ebb,
|
||||
},
|
||||
}
|
||||
|
||||
/// A control stack frame can be an `if`, a `block` or a `loop`, each one having the following
|
||||
/// fields:
|
||||
///
|
||||
@@ -23,14 +41,17 @@ use std::vec::Vec;
|
||||
pub enum ControlStackFrame {
|
||||
If {
|
||||
destination: Ebb,
|
||||
branch_inst: Inst,
|
||||
else_data: ElseData,
|
||||
num_param_values: usize,
|
||||
num_return_values: usize,
|
||||
original_stack_size: usize,
|
||||
exit_is_branched_to: bool,
|
||||
reachable_from_top: bool,
|
||||
blocktype: wasmparser::TypeOrFuncType,
|
||||
},
|
||||
Block {
|
||||
destination: Ebb,
|
||||
num_param_values: usize,
|
||||
num_return_values: usize,
|
||||
original_stack_size: usize,
|
||||
exit_is_branched_to: bool,
|
||||
@@ -38,6 +59,7 @@ pub enum ControlStackFrame {
|
||||
Loop {
|
||||
destination: Ebb,
|
||||
header: Ebb,
|
||||
num_param_values: usize,
|
||||
num_return_values: usize,
|
||||
original_stack_size: usize,
|
||||
},
|
||||
@@ -58,6 +80,19 @@ impl ControlStackFrame {
|
||||
} => num_return_values,
|
||||
}
|
||||
}
|
||||
pub fn num_param_values(&self) -> usize {
|
||||
match *self {
|
||||
ControlStackFrame::If {
|
||||
num_param_values, ..
|
||||
}
|
||||
| ControlStackFrame::Block {
|
||||
num_param_values, ..
|
||||
}
|
||||
| ControlStackFrame::Loop {
|
||||
num_param_values, ..
|
||||
} => num_param_values,
|
||||
}
|
||||
}
|
||||
pub fn following_code(&self) -> Ebb {
|
||||
match *self {
|
||||
ControlStackFrame::If { destination, .. }
|
||||
@@ -202,6 +237,7 @@ impl TranslationState {
|
||||
self.clear();
|
||||
self.push_block(
|
||||
exit_block,
|
||||
0,
|
||||
sig.returns
|
||||
.iter()
|
||||
.filter(|arg| arg.purpose == ir::ArgumentPurpose::Normal)
|
||||
@@ -221,12 +257,17 @@ impl TranslationState {
|
||||
|
||||
/// Pop one value.
|
||||
pub(crate) fn pop1(&mut self) -> Value {
|
||||
self.stack.pop().unwrap()
|
||||
self.stack
|
||||
.pop()
|
||||
.expect("attempted to pop a value from an empty stack")
|
||||
}
|
||||
|
||||
/// Peek at the top of the stack without popping it.
|
||||
pub(crate) fn peek1(&self) -> Value {
|
||||
*self.stack.last().unwrap()
|
||||
*self
|
||||
.stack
|
||||
.last()
|
||||
.expect("attempted to peek at a value on an empty stack")
|
||||
}
|
||||
|
||||
/// Pop two values. Return them in the order they were pushed.
|
||||
@@ -248,31 +289,58 @@ impl TranslationState {
|
||||
///
|
||||
/// The popped values are not returned. Use `peekn` to look at them before popping.
|
||||
pub(crate) fn popn(&mut self, n: usize) {
|
||||
debug_assert!(
|
||||
n <= self.stack.len(),
|
||||
"popn({}) but stack only has {} values",
|
||||
n,
|
||||
self.stack.len()
|
||||
);
|
||||
let new_len = self.stack.len() - n;
|
||||
self.stack.truncate(new_len);
|
||||
}
|
||||
|
||||
/// Peek at the top `n` values on the stack in the order they were pushed.
|
||||
pub(crate) fn peekn(&self, n: usize) -> &[Value] {
|
||||
debug_assert!(
|
||||
n <= self.stack.len(),
|
||||
"peekn({}) but stack only has {} values",
|
||||
n,
|
||||
self.stack.len()
|
||||
);
|
||||
&self.stack[self.stack.len() - n..]
|
||||
}
|
||||
|
||||
/// Push a block on the control stack.
|
||||
pub(crate) fn push_block(&mut self, following_code: Ebb, num_result_types: usize) {
|
||||
pub(crate) fn push_block(
|
||||
&mut self,
|
||||
following_code: Ebb,
|
||||
num_param_types: usize,
|
||||
num_result_types: usize,
|
||||
) {
|
||||
debug_assert!(num_param_types <= self.stack.len());
|
||||
self.control_stack.push(ControlStackFrame::Block {
|
||||
destination: following_code,
|
||||
original_stack_size: self.stack.len(),
|
||||
original_stack_size: self.stack.len() - num_param_types,
|
||||
num_param_values: num_param_types,
|
||||
num_return_values: num_result_types,
|
||||
exit_is_branched_to: false,
|
||||
});
|
||||
}
|
||||
|
||||
/// Push a loop on the control stack.
|
||||
pub(crate) fn push_loop(&mut self, header: Ebb, following_code: Ebb, num_result_types: usize) {
|
||||
pub(crate) fn push_loop(
|
||||
&mut self,
|
||||
header: Ebb,
|
||||
following_code: Ebb,
|
||||
num_param_types: usize,
|
||||
num_result_types: usize,
|
||||
) {
|
||||
debug_assert!(num_param_types <= self.stack.len());
|
||||
self.control_stack.push(ControlStackFrame::Loop {
|
||||
header,
|
||||
destination: following_code,
|
||||
original_stack_size: self.stack.len(),
|
||||
original_stack_size: self.stack.len() - num_param_types,
|
||||
num_param_values: num_param_types,
|
||||
num_return_values: num_result_types,
|
||||
});
|
||||
}
|
||||
@@ -280,17 +348,39 @@ impl TranslationState {
|
||||
/// Push an if on the control stack.
|
||||
pub(crate) fn push_if(
|
||||
&mut self,
|
||||
branch_inst: Inst,
|
||||
following_code: Ebb,
|
||||
destination: Ebb,
|
||||
else_data: ElseData,
|
||||
num_param_types: usize,
|
||||
num_result_types: usize,
|
||||
blocktype: wasmparser::TypeOrFuncType,
|
||||
) {
|
||||
debug_assert!(num_param_types <= self.stack.len());
|
||||
|
||||
// Push a second copy of our `if`'s parameters on the stack. This lets
|
||||
// us avoid saving them on the side in the `ControlStackFrame` for our
|
||||
// `else` block (if it exists), which would require a second heap
|
||||
// allocation. See also the comment in `translate_operator` for
|
||||
// `Operator::Else`.
|
||||
self.stack.reserve(num_param_types);
|
||||
for i in (self.stack.len() - num_param_types)..self.stack.len() {
|
||||
let val = self.stack[i];
|
||||
self.stack.push(val);
|
||||
}
|
||||
|
||||
let has_else = match else_data {
|
||||
ElseData::NoElse { .. } => false,
|
||||
ElseData::WithElse { .. } => true,
|
||||
};
|
||||
|
||||
self.control_stack.push(ControlStackFrame::If {
|
||||
branch_inst,
|
||||
destination: following_code,
|
||||
original_stack_size: self.stack.len(),
|
||||
destination,
|
||||
else_data,
|
||||
original_stack_size: self.stack.len() - num_param_types,
|
||||
num_param_values: num_param_types,
|
||||
num_return_values: num_result_types,
|
||||
exit_is_branched_to: false,
|
||||
reachable_from_top: self.reachable,
|
||||
reachable_from_top: self.reachable && !has_else,
|
||||
blocktype,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user