Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
cranelift-wasm: support multi-value Wasm
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
fitzgen committed Sep 23, 2019
1 parent cdf29c7 commit 26bd651
Show file tree
Hide file tree
Showing 18 changed files with 288 additions and 80 deletions.
42 changes: 20 additions & 22 deletions cranelift-wasm/src/code_translator.rs
Expand Up @@ -23,11 +23,9 @@
//! That is why `translate_function_body` takes an object having the `WasmRuntime` trait as
//! argument.
use super::{hash_map, HashMap};
use crate::environ::{FuncEnvironment, GlobalVariable, ReturnMode, WasmResult};
use crate::environ::{FuncEnvironment, GlobalVariable, ReturnMode, WasmResult, WasmTypesMap};
use crate::state::{ControlStackFrame, TranslationState};
use crate::translation_utils::{
blocktype_to_type, f32_translation, f64_translation, num_return_values,
};
use crate::translation_utils::{blocktype_to_ebb, f32_translation, f64_translation};
use crate::translation_utils::{FuncIndex, MemoryIndex, SignatureIndex, TableIndex};
use crate::wasm_unsupported;
use core::{i32, u32};
Expand All @@ -43,6 +41,7 @@ use wasmparser::{MemoryImmediate, Operator};
/// Translates wasm operators into Cranelift IR instructions. Returns `true` if it inserted
/// a return.
pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
wasm_types: &WasmTypesMap,
op: &Operator,
builder: &mut FunctionBuilder,
state: &mut TranslationState,
Expand Down Expand Up @@ -132,26 +131,21 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
* possible `Ebb`'s arguments values.
***********************************************************************************/
Operator::Block { ty } => {
let next = builder.create_ebb();
if let Some(ty_cre) = blocktype_to_type(*ty)? {
builder.append_ebb_param(next, ty_cre);
}
state.push_block(next, num_return_values(*ty)?);
let (next, num_param_tys, num_result_tys) = blocktype_to_ebb(wasm_types, builder, *ty)?;
state.push_block(next, num_param_tys, num_result_tys);
}
Operator::Loop { ty } => {
let loop_body = builder.create_ebb();
let next = builder.create_ebb();
if let Some(ty_cre) = blocktype_to_type(*ty)? {
builder.append_ebb_param(next, ty_cre);
}
let (next, num_param_tys, num_result_tys) = blocktype_to_ebb(wasm_types, builder, *ty)?;
builder.ins().jump(loop_body, &[]);
state.push_loop(loop_body, next, num_return_values(*ty)?);
state.push_loop(loop_body, next, num_param_tys, num_result_tys);
builder.switch_to_block(loop_body);
environ.translate_loop_header(builder.cursor())?;
}
Operator::If { ty } => {
let val = state.pop1();
let if_not = builder.create_ebb();
let (if_not, num_param_tys, num_result_tys) =
blocktype_to_ebb(wasm_types, builder, *ty)?;
let jump_inst = builder.ins().brz(val, if_not, &[]);

#[cfg(feature = "basic-blocks")]
Expand All @@ -168,26 +162,25 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
// and we add nothing;
// - either the If have an Else clause, in that case the destination of this jump
// instruction will be changed later when we translate the Else operator.
if let Some(ty_cre) = blocktype_to_type(*ty)? {
builder.append_ebb_param(if_not, ty_cre);
}
state.push_if(jump_inst, if_not, num_return_values(*ty)?);
state.push_if(jump_inst, if_not, num_param_tys, num_result_tys);
}
Operator::Else => {
// We take the control frame pushed by the if, use its ebb as the else body
// and push a new control frame with a new ebb for the code after the if/then/else
// At the end of the then clause we jump to the destination
let i = state.control_stack.len() - 1;
let (destination, return_count, branch_inst, ref mut reachable_from_top) =
let (destination, ref params, return_count, branch_inst, ref mut reachable_from_top) =
match state.control_stack[i] {
ControlStackFrame::If {
destination,
ref params,
num_return_values,
branch_inst,
reachable_from_top,
..
} => (
destination,
params.clone(),
num_return_values,
branch_inst,
reachable_from_top,
Expand All @@ -198,6 +191,8 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
*reachable_from_top = false;
builder.ins().jump(destination, state.peekn(return_count));
state.popn(return_count);
state.pushn(&params);

// We change the target of the branch instruction
let else_ebb = builder.create_ebb();
builder.change_jump_destination(branch_inst, else_ebb);
Expand Down Expand Up @@ -1129,15 +1124,16 @@ fn translate_unreachable_operator(
Operator::If { ty: _ } => {
// Push a placeholder control stack entry. The if isn't reachable,
// so we don't have any branches anywhere.
state.push_if(ir::Inst::reserved_value(), ir::Ebb::reserved_value(), 0);
state.push_if(ir::Inst::reserved_value(), ir::Ebb::reserved_value(), 0, 0);
}
Operator::Loop { ty: _ } | Operator::Block { ty: _ } => {
state.push_block(ir::Ebb::reserved_value(), 0);
state.push_block(ir::Ebb::reserved_value(), 0, 0);
}
Operator::Else => {
let i = state.control_stack.len() - 1;
if let ControlStackFrame::If {
branch_inst,
ref params,
ref mut reachable_from_top,
..
} = state.control_stack[i]
Expand All @@ -1154,6 +1150,8 @@ fn translate_unreachable_operator(
builder.change_jump_destination(branch_inst, else_ebb);
builder.seal_block(else_ebb);
builder.switch_to_block(else_ebb);
let params = params.clone();
state.pushn(&params);
}
}
}
Expand Down
14 changes: 11 additions & 3 deletions cranelift-wasm/src/environ/dummy.rs
Expand Up @@ -5,7 +5,9 @@
//! [wasmtime-environ]: https://crates.io/crates/wasmtime-environ
//! [Wasmtime]: https://github.com/CraneStation/wasmtime

use crate::environ::{FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode, WasmResult};
use crate::environ::{
FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode, WasmResult, WasmTypesMap,
};
use crate::func_translator::FuncTranslator;
use crate::translation_utils::{
DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, Table,
Expand Down Expand Up @@ -529,6 +531,7 @@ impl<'data> ModuleEnvironment<'data> for DummyEnvironment {

fn define_function_body(
&mut self,
wasm_types: &WasmTypesMap,
body_bytes: &'data [u8],
body_offset: usize,
) -> WasmResult<()> {
Expand All @@ -542,8 +545,13 @@ impl<'data> ModuleEnvironment<'data> for DummyEnvironment {
if self.debug_info {
func.collect_debug_info();
}
self.trans
.translate(body_bytes, body_offset, &mut func, &mut func_environ)?;
self.trans.translate(
wasm_types,
body_bytes,
body_offset,
&mut func,
&mut func_environ,
)?;
func
};
self.func_bytecode_sizes.push(body_bytes.len());
Expand Down
1 change: 1 addition & 0 deletions cranelift-wasm/src/environ/mod.rs
Expand Up @@ -7,4 +7,5 @@ mod spec;
pub use crate::environ::dummy::DummyEnvironment;
pub use crate::environ::spec::{
FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode, WasmError, WasmResult,
WasmTypesMap,
};
16 changes: 16 additions & 0 deletions cranelift-wasm/src/environ/spec.rs
Expand Up @@ -15,6 +15,7 @@ use cranelift_codegen::cursor::FuncCursor;
use cranelift_codegen::ir::immediates::Offset32;
use cranelift_codegen::ir::{self, InstBuilder};
use cranelift_codegen::isa::TargetFrontendConfig;
use cranelift_entity::PrimaryMap;
use cranelift_frontend::FunctionBuilder;
use failure_derive::Fail;
use std::boxed::Box;
Expand Down Expand Up @@ -103,6 +104,20 @@ pub enum ReturnMode {
FallthroughReturn,
}

#[derive(Debug)]
pub struct WasmTypesMap {
pub(crate) inner:
PrimaryMap<SignatureIndex, (Box<[wasmparser::Type]>, Box<[wasmparser::Type]>)>,
}

impl WasmTypesMap {
pub(crate) fn new() -> Self {
WasmTypesMap {
inner: PrimaryMap::new(),
}
}
}

/// Environment affecting the translation of a single WebAssembly function.
///
/// A `FuncEnvironment` trait object is required to translate a WebAssembly function to Cranelift
Expand Down Expand Up @@ -449,6 +464,7 @@ pub trait ModuleEnvironment<'data> {
/// functions is already provided by `reserve_func_types`.
fn define_function_body(
&mut self,
wasm_types: &WasmTypesMap,
body_bytes: &'data [u8],
body_offset: usize,
) -> WasmResult<()>;
Expand Down
41 changes: 34 additions & 7 deletions cranelift-wasm/src/func_translator.rs
Expand Up @@ -5,7 +5,7 @@
//! WebAssembly module and the runtime environment.

use crate::code_translator::translate_operator;
use crate::environ::{FuncEnvironment, ReturnMode, WasmResult};
use crate::environ::{FuncEnvironment, ReturnMode, WasmResult, WasmTypesMap};
use crate::state::{TranslationState, VisibleTranslationState};
use crate::translation_utils::get_vmctx_value_label;
use crate::wasm_unsupported;
Expand Down Expand Up @@ -55,12 +55,14 @@ impl FuncTranslator {
///
pub fn translate<FE: FuncEnvironment + ?Sized>(
&mut self,
wasm_types: &WasmTypesMap,
code: &[u8],
code_offset: usize,
func: &mut ir::Function,
environ: &mut FE,
) -> WasmResult<()> {
self.translate_from_reader(
wasm_types,
BinaryReader::new_with_offset(code, code_offset),
func,
environ,
Expand All @@ -70,6 +72,7 @@ impl FuncTranslator {
/// Translate a binary WebAssembly function from a `BinaryReader`.
pub fn translate_from_reader<FE: FuncEnvironment + ?Sized>(
&mut self,
wasm_types: &WasmTypesMap,
mut reader: BinaryReader,
func: &mut ir::Function,
environ: &mut FE,
Expand Down Expand Up @@ -105,7 +108,7 @@ impl FuncTranslator {
self.state.initialize(&builder.func.signature, exit_block);

parse_local_decls(&mut reader, &mut builder, num_params, environ)?;
parse_function_body(reader, &mut builder, &mut self.state, environ)?;
parse_function_body(wasm_types, reader, &mut builder, &mut self.state, environ)?;

builder.finalize();
Ok(())
Expand Down Expand Up @@ -203,6 +206,7 @@ fn declare_locals<FE: FuncEnvironment + ?Sized>(
/// This assumes that the local variable declarations have already been parsed and function
/// arguments and locals are declared in the builder.
fn parse_function_body<FE: FuncEnvironment + ?Sized>(
wasm_types: &WasmTypesMap,
mut reader: BinaryReader,
builder: &mut FunctionBuilder,
state: &mut TranslationState,
Expand All @@ -216,7 +220,7 @@ fn parse_function_body<FE: FuncEnvironment + ?Sized>(
builder.set_srcloc(cur_srcloc(&reader));
let op = reader.read_operator()?;
environ.before_translate_operator(&op, builder, &VisibleTranslationState::new(state))?;
translate_operator(&op, builder, state, environ)?;
translate_operator(wasm_types, &op, builder, state, environ)?;
environ.after_translate_operator(&op, builder, &VisibleTranslationState::new(state))?;
}

Expand Down Expand Up @@ -254,7 +258,7 @@ fn cur_srcloc(reader: &BinaryReader) -> ir::SourceLoc {
#[cfg(test)]
mod tests {
use super::{FuncTranslator, ReturnMode};
use crate::environ::DummyEnvironment;
use crate::environ::{DummyEnvironment, WasmTypesMap};
use cranelift_codegen::ir::types::I32;
use cranelift_codegen::{ir, isa, settings, Context};
use log::debug;
Expand Down Expand Up @@ -286,14 +290,21 @@ mod tests {
false,
);

let wasm_types = WasmTypesMap::new();
let mut ctx = Context::new();

ctx.func.name = ir::ExternalName::testcase("small1");
ctx.func.signature.params.push(ir::AbiParam::new(I32));
ctx.func.signature.returns.push(ir::AbiParam::new(I32));

trans
.translate(&BODY, 0, &mut ctx.func, &mut runtime.func_env())
.translate(
&wasm_types,
&BODY,
0,
&mut ctx.func,
&mut runtime.func_env(),
)
.unwrap();
debug!("{}", ctx.func.display(None));
ctx.verify(&flags).unwrap();
Expand Down Expand Up @@ -325,14 +336,22 @@ mod tests {
ReturnMode::NormalReturns,
false,
);

let wasm_types = WasmTypesMap::new();
let mut ctx = Context::new();

ctx.func.name = ir::ExternalName::testcase("small2");
ctx.func.signature.params.push(ir::AbiParam::new(I32));
ctx.func.signature.returns.push(ir::AbiParam::new(I32));

trans
.translate(&BODY, 0, &mut ctx.func, &mut runtime.func_env())
.translate(
&wasm_types,
&BODY,
0,
&mut ctx.func,
&mut runtime.func_env(),
)
.unwrap();
debug!("{}", ctx.func.display(None));
ctx.verify(&flags).unwrap();
Expand Down Expand Up @@ -373,13 +392,21 @@ mod tests {
ReturnMode::NormalReturns,
false,
);

let wasm_types = WasmTypesMap::new();
let mut ctx = Context::new();

ctx.func.name = ir::ExternalName::testcase("infloop");
ctx.func.signature.returns.push(ir::AbiParam::new(I32));

trans
.translate(&BODY, 0, &mut ctx.func, &mut runtime.func_env())
.translate(
&wasm_types,
&BODY,
0,
&mut ctx.func,
&mut runtime.func_env(),
)
.unwrap();
debug!("{}", ctx.func.display(None));
ctx.verify(&flags).unwrap();
Expand Down
7 changes: 4 additions & 3 deletions cranelift-wasm/src/module_translator.rs
@@ -1,6 +1,6 @@
//! Translation skeleton that traverses the whole WebAssembly module and call helper functions
//! to deal with each part of it.
use crate::environ::{ModuleEnvironment, WasmError, WasmResult};
use crate::environ::{ModuleEnvironment, WasmError, WasmResult, WasmTypesMap};
use crate::sections_translator::{
parse_code_section, parse_data_section, parse_element_section, parse_export_section,
parse_function_section, parse_global_section, parse_import_section, parse_memory_section,
Expand All @@ -17,13 +17,14 @@ pub fn translate_module<'data>(
) -> WasmResult<()> {
let _tt = timing::wasm_translate_module();
let mut reader = ModuleReader::new(data)?;
let mut wasm_types = WasmTypesMap::new();

while !reader.eof() {
let section = reader.read()?;
match section.code {
SectionCode::Type => {
let types = section.get_type_section_reader()?;
parse_type_section(types, environ)?;
parse_type_section(types, &mut wasm_types, environ)?;
}

SectionCode::Import => {
Expand Down Expand Up @@ -68,7 +69,7 @@ pub fn translate_module<'data>(

SectionCode::Code => {
let code = section.get_code_section_reader()?;
parse_code_section(code, environ)?;
parse_code_section(code, &wasm_types, environ)?;
}

SectionCode::Data => {
Expand Down

0 comments on commit 26bd651

Please sign in to comment.