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:
Nick Fitzgerald
2019-10-02 12:40:35 -07:00
committed by GitHub
parent f9d802fb1d
commit 10be3e4ba8
30 changed files with 610 additions and 138 deletions

View File

@@ -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,
});
}
}