moved crates in lib/ to src/, renamed crates, modified some files' text (#660)
moved crates in lib/ to src/, renamed crates, modified some files' text (#660)
This commit is contained in:
1100
cranelift/wasm/src/code_translator.rs
Normal file
1100
cranelift/wasm/src/code_translator.rs
Normal file
File diff suppressed because it is too large
Load Diff
477
cranelift/wasm/src/environ/dummy.rs
Normal file
477
cranelift/wasm/src/environ/dummy.rs
Normal file
@@ -0,0 +1,477 @@
|
||||
//! "Dummy" implementations of `ModuleEnvironment` and `FuncEnvironment` for testing
|
||||
//! wasm translation. For complete implementations of `ModuleEnvironment` and
|
||||
//! `FuncEnvironment`, see [wasmtime-environ] in [Wasmtime].
|
||||
//!
|
||||
//! [wasmtime-environ]: https://crates.io/crates/wasmtime-environ
|
||||
//! [Wasmtime]: https://github.com/CraneStation/wasmtime
|
||||
|
||||
use crate::environ::{FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode, WasmResult};
|
||||
use crate::func_translator::FuncTranslator;
|
||||
use crate::translation_utils::{
|
||||
DefinedFuncIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, Table,
|
||||
TableIndex,
|
||||
};
|
||||
use cast;
|
||||
use cranelift_codegen::cursor::FuncCursor;
|
||||
use cranelift_codegen::ir::immediates::{Offset32, Uimm64};
|
||||
use cranelift_codegen::ir::types::*;
|
||||
use cranelift_codegen::ir::{self, InstBuilder};
|
||||
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||
use cranelift_entity::{EntityRef, PrimaryMap};
|
||||
use std::boxed::Box;
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
|
||||
/// Compute a `ir::ExternalName` for a given wasm function index.
|
||||
fn get_func_name(func_index: FuncIndex) -> ir::ExternalName {
|
||||
ir::ExternalName::user(0, func_index.as_u32())
|
||||
}
|
||||
|
||||
/// A collection of names under which a given entity is exported.
|
||||
pub struct Exportable<T> {
|
||||
/// A wasm entity.
|
||||
pub entity: T,
|
||||
|
||||
/// Names under which the entity is exported.
|
||||
pub export_names: Vec<String>,
|
||||
}
|
||||
|
||||
impl<T> Exportable<T> {
|
||||
pub fn new(entity: T) -> Self {
|
||||
Self {
|
||||
entity,
|
||||
export_names: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The main state belonging to a `DummyEnvironment`. This is split out from
|
||||
/// `DummyEnvironment` to allow it to be borrowed separately from the
|
||||
/// `FuncTranslator` field.
|
||||
pub struct DummyModuleInfo {
|
||||
/// Target description relevant to frontends producing Cranelift IR.
|
||||
config: TargetFrontendConfig,
|
||||
|
||||
/// Signatures as provided by `declare_signature`.
|
||||
pub signatures: PrimaryMap<SignatureIndex, ir::Signature>,
|
||||
|
||||
/// Module and field names of imported functions as provided by `declare_func_import`.
|
||||
pub imported_funcs: Vec<(String, String)>,
|
||||
|
||||
/// Module and field names of imported globals as provided by `declare_global_import`.
|
||||
pub imported_globals: Vec<(String, String)>,
|
||||
|
||||
/// Module and field names of imported tables as provided by `declare_table_import`.
|
||||
pub imported_tables: Vec<(String, String)>,
|
||||
|
||||
/// Module and field names of imported memories as provided by `declare_memory_import`.
|
||||
pub imported_memories: Vec<(String, String)>,
|
||||
|
||||
/// Functions, imported and local.
|
||||
pub functions: PrimaryMap<FuncIndex, Exportable<SignatureIndex>>,
|
||||
|
||||
/// Function bodies.
|
||||
pub function_bodies: PrimaryMap<DefinedFuncIndex, ir::Function>,
|
||||
|
||||
/// Tables as provided by `declare_table`.
|
||||
pub tables: PrimaryMap<TableIndex, Exportable<Table>>,
|
||||
|
||||
/// Memories as provided by `declare_memory`.
|
||||
pub memories: PrimaryMap<MemoryIndex, Exportable<Memory>>,
|
||||
|
||||
/// Globals as provided by `declare_global`.
|
||||
pub globals: PrimaryMap<GlobalIndex, Exportable<Global>>,
|
||||
|
||||
/// The start function.
|
||||
pub start_func: Option<FuncIndex>,
|
||||
}
|
||||
|
||||
impl DummyModuleInfo {
|
||||
/// Creates a new `DummyModuleInfo` instance.
|
||||
pub fn new(config: TargetFrontendConfig) -> Self {
|
||||
Self {
|
||||
config,
|
||||
signatures: PrimaryMap::new(),
|
||||
imported_funcs: Vec::new(),
|
||||
imported_globals: Vec::new(),
|
||||
imported_tables: Vec::new(),
|
||||
imported_memories: Vec::new(),
|
||||
functions: PrimaryMap::new(),
|
||||
function_bodies: PrimaryMap::new(),
|
||||
tables: PrimaryMap::new(),
|
||||
memories: PrimaryMap::new(),
|
||||
globals: PrimaryMap::new(),
|
||||
start_func: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This `ModuleEnvironment` implementation is a "naïve" one, doing essentially nothing and
|
||||
/// emitting placeholders when forced to. Don't try to execute code translated for this
|
||||
/// environment, essentially here for translation debug purposes.
|
||||
pub struct DummyEnvironment {
|
||||
/// Module information.
|
||||
pub info: DummyModuleInfo,
|
||||
|
||||
/// Function translation.
|
||||
trans: FuncTranslator,
|
||||
|
||||
/// Vector of wasm bytecode size for each function.
|
||||
pub func_bytecode_sizes: Vec<usize>,
|
||||
|
||||
/// How to return from functions.
|
||||
return_mode: ReturnMode,
|
||||
}
|
||||
|
||||
impl DummyEnvironment {
|
||||
/// Creates a new `DummyEnvironment` instance.
|
||||
pub fn new(config: TargetFrontendConfig, return_mode: ReturnMode) -> Self {
|
||||
Self {
|
||||
info: DummyModuleInfo::new(config),
|
||||
trans: FuncTranslator::new(),
|
||||
func_bytecode_sizes: Vec::new(),
|
||||
return_mode,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a `DummyFuncEnvironment` for translating functions within this
|
||||
/// `DummyEnvironment`.
|
||||
pub fn func_env(&self) -> DummyFuncEnvironment {
|
||||
DummyFuncEnvironment::new(&self.info, self.return_mode)
|
||||
}
|
||||
|
||||
fn get_func_type(&self, func_index: FuncIndex) -> SignatureIndex {
|
||||
self.info.functions[func_index].entity
|
||||
}
|
||||
|
||||
/// Return the number of imported functions within this `DummyEnvironment`.
|
||||
pub fn get_num_func_imports(&self) -> usize {
|
||||
self.info.imported_funcs.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// The `FuncEnvironment` implementation for use by the `DummyEnvironment`.
|
||||
pub struct DummyFuncEnvironment<'dummy_environment> {
|
||||
pub mod_info: &'dummy_environment DummyModuleInfo,
|
||||
|
||||
return_mode: ReturnMode,
|
||||
}
|
||||
|
||||
impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> {
|
||||
pub fn new(mod_info: &'dummy_environment DummyModuleInfo, return_mode: ReturnMode) -> Self {
|
||||
Self {
|
||||
mod_info,
|
||||
return_mode,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a signature for `sigidx` amended with a `vmctx` argument after the standard wasm
|
||||
// arguments.
|
||||
fn vmctx_sig(&self, sigidx: SignatureIndex) -> ir::Signature {
|
||||
let mut sig = self.mod_info.signatures[sigidx].clone();
|
||||
sig.params.push(ir::AbiParam::special(
|
||||
self.pointer_type(),
|
||||
ir::ArgumentPurpose::VMContext,
|
||||
));
|
||||
sig
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environment> {
|
||||
fn target_config(&self) -> TargetFrontendConfig {
|
||||
self.mod_info.config
|
||||
}
|
||||
|
||||
fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalVariable {
|
||||
// Just create a dummy `vmctx` global.
|
||||
let offset = cast::i32((index.index() * 8) + 8).unwrap().into();
|
||||
let vmctx = func.create_global_value(ir::GlobalValueData::VMContext {});
|
||||
GlobalVariable::Memory {
|
||||
gv: vmctx,
|
||||
offset,
|
||||
ty: self.mod_info.globals[index].entity.ty,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_heap(&mut self, func: &mut ir::Function, _index: MemoryIndex) -> ir::Heap {
|
||||
// Create a static heap whose base address is stored at `vmctx+0`.
|
||||
let addr = func.create_global_value(ir::GlobalValueData::VMContext);
|
||||
let gv = func.create_global_value(ir::GlobalValueData::Load {
|
||||
base: addr,
|
||||
offset: Offset32::new(0),
|
||||
global_type: self.pointer_type(),
|
||||
readonly: true,
|
||||
});
|
||||
|
||||
func.create_heap(ir::HeapData {
|
||||
base: gv,
|
||||
min_size: 0.into(),
|
||||
offset_guard_size: 0x8000_0000.into(),
|
||||
style: ir::HeapStyle::Static {
|
||||
bound: 0x1_0000_0000.into(),
|
||||
},
|
||||
index_type: I32,
|
||||
})
|
||||
}
|
||||
|
||||
fn make_table(&mut self, func: &mut ir::Function, _index: TableIndex) -> ir::Table {
|
||||
// Create a table whose base address is stored at `vmctx+0`.
|
||||
let vmctx = func.create_global_value(ir::GlobalValueData::VMContext);
|
||||
let base_gv = func.create_global_value(ir::GlobalValueData::Load {
|
||||
base: vmctx,
|
||||
offset: Offset32::new(0),
|
||||
global_type: self.pointer_type(),
|
||||
readonly: true, // when tables in wasm become "growable", revisit whether this can be readonly or not.
|
||||
});
|
||||
let bound_gv = func.create_global_value(ir::GlobalValueData::Load {
|
||||
base: vmctx,
|
||||
offset: Offset32::new(0),
|
||||
global_type: I32,
|
||||
readonly: true,
|
||||
});
|
||||
|
||||
func.create_table(ir::TableData {
|
||||
base_gv,
|
||||
min_size: Uimm64::new(0),
|
||||
bound_gv,
|
||||
element_size: Uimm64::from(u64::from(self.pointer_bytes()) * 2),
|
||||
index_type: I32,
|
||||
})
|
||||
}
|
||||
|
||||
fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef {
|
||||
// A real implementation would probably change the calling convention and add `vmctx` and
|
||||
// signature index arguments.
|
||||
func.import_signature(self.vmctx_sig(index))
|
||||
}
|
||||
|
||||
fn make_direct_func(&mut self, func: &mut ir::Function, index: FuncIndex) -> ir::FuncRef {
|
||||
let sigidx = self.mod_info.functions[index].entity;
|
||||
// A real implementation would probably add a `vmctx` argument.
|
||||
// And maybe attempt some signature de-duplication.
|
||||
let signature = func.import_signature(self.vmctx_sig(sigidx));
|
||||
let name = get_func_name(index);
|
||||
func.import_function(ir::ExtFuncData {
|
||||
name,
|
||||
signature,
|
||||
colocated: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn translate_call_indirect(
|
||||
&mut self,
|
||||
mut pos: FuncCursor,
|
||||
_table_index: TableIndex,
|
||||
_table: ir::Table,
|
||||
_sig_index: SignatureIndex,
|
||||
sig_ref: ir::SigRef,
|
||||
callee: ir::Value,
|
||||
call_args: &[ir::Value],
|
||||
) -> WasmResult<ir::Inst> {
|
||||
// Pass the current function's vmctx parameter on to the callee.
|
||||
let vmctx = pos
|
||||
.func
|
||||
.special_param(ir::ArgumentPurpose::VMContext)
|
||||
.expect("Missing vmctx parameter");
|
||||
|
||||
// The `callee` value is an index into a table of function pointers.
|
||||
// Apparently, that table is stored at absolute address 0 in this dummy environment.
|
||||
// TODO: Generate bounds checking code.
|
||||
let ptr = self.pointer_type();
|
||||
let callee_offset = if ptr == I32 {
|
||||
pos.ins().imul_imm(callee, 4)
|
||||
} else {
|
||||
let ext = pos.ins().uextend(I64, callee);
|
||||
pos.ins().imul_imm(ext, 4)
|
||||
};
|
||||
let mflags = ir::MemFlags::trusted();
|
||||
let func_ptr = pos.ins().load(ptr, mflags, callee_offset, 0);
|
||||
|
||||
// Build a value list for the indirect call instruction containing the callee, call_args,
|
||||
// and the vmctx parameter.
|
||||
let mut args = ir::ValueList::default();
|
||||
args.push(func_ptr, &mut pos.func.dfg.value_lists);
|
||||
args.extend(call_args.iter().cloned(), &mut pos.func.dfg.value_lists);
|
||||
args.push(vmctx, &mut pos.func.dfg.value_lists);
|
||||
|
||||
Ok(pos
|
||||
.ins()
|
||||
.CallIndirect(ir::Opcode::CallIndirect, INVALID, sig_ref, args)
|
||||
.0)
|
||||
}
|
||||
|
||||
fn translate_call(
|
||||
&mut self,
|
||||
mut pos: FuncCursor,
|
||||
_callee_index: FuncIndex,
|
||||
callee: ir::FuncRef,
|
||||
call_args: &[ir::Value],
|
||||
) -> WasmResult<ir::Inst> {
|
||||
// Pass the current function's vmctx parameter on to the callee.
|
||||
let vmctx = pos
|
||||
.func
|
||||
.special_param(ir::ArgumentPurpose::VMContext)
|
||||
.expect("Missing vmctx parameter");
|
||||
|
||||
// Build a value list for the call instruction containing the call_args and the vmctx
|
||||
// parameter.
|
||||
let mut args = ir::ValueList::default();
|
||||
args.extend(call_args.iter().cloned(), &mut pos.func.dfg.value_lists);
|
||||
args.push(vmctx, &mut pos.func.dfg.value_lists);
|
||||
|
||||
Ok(pos.ins().Call(ir::Opcode::Call, INVALID, callee, args).0)
|
||||
}
|
||||
|
||||
fn translate_memory_grow(
|
||||
&mut self,
|
||||
mut pos: FuncCursor,
|
||||
_index: MemoryIndex,
|
||||
_heap: ir::Heap,
|
||||
_val: ir::Value,
|
||||
) -> WasmResult<ir::Value> {
|
||||
Ok(pos.ins().iconst(I32, -1))
|
||||
}
|
||||
|
||||
fn translate_memory_size(
|
||||
&mut self,
|
||||
mut pos: FuncCursor,
|
||||
_index: MemoryIndex,
|
||||
_heap: ir::Heap,
|
||||
) -> WasmResult<ir::Value> {
|
||||
Ok(pos.ins().iconst(I32, -1))
|
||||
}
|
||||
|
||||
fn return_mode(&self) -> ReturnMode {
|
||||
self.return_mode
|
||||
}
|
||||
}
|
||||
|
||||
impl<'data> ModuleEnvironment<'data> for DummyEnvironment {
|
||||
fn target_config(&self) -> TargetFrontendConfig {
|
||||
self.info.config
|
||||
}
|
||||
|
||||
fn declare_signature(&mut self, sig: ir::Signature) {
|
||||
self.info.signatures.push(sig);
|
||||
}
|
||||
|
||||
fn declare_func_import(
|
||||
&mut self,
|
||||
sig_index: SignatureIndex,
|
||||
module: &'data str,
|
||||
field: &'data str,
|
||||
) {
|
||||
assert_eq!(
|
||||
self.info.functions.len(),
|
||||
self.info.imported_funcs.len(),
|
||||
"Imported functions must be declared first"
|
||||
);
|
||||
self.info.functions.push(Exportable::new(sig_index));
|
||||
self.info
|
||||
.imported_funcs
|
||||
.push((String::from(module), String::from(field)));
|
||||
}
|
||||
|
||||
fn declare_func_type(&mut self, sig_index: SignatureIndex) {
|
||||
self.info.functions.push(Exportable::new(sig_index));
|
||||
}
|
||||
|
||||
fn declare_global(&mut self, global: Global) {
|
||||
self.info.globals.push(Exportable::new(global));
|
||||
}
|
||||
|
||||
fn declare_global_import(&mut self, global: Global, module: &'data str, field: &'data str) {
|
||||
self.info.globals.push(Exportable::new(global));
|
||||
self.info
|
||||
.imported_globals
|
||||
.push((String::from(module), String::from(field)));
|
||||
}
|
||||
|
||||
fn declare_table(&mut self, table: Table) {
|
||||
self.info.tables.push(Exportable::new(table));
|
||||
}
|
||||
|
||||
fn declare_table_import(&mut self, table: Table, module: &'data str, field: &'data str) {
|
||||
self.info.tables.push(Exportable::new(table));
|
||||
self.info
|
||||
.imported_tables
|
||||
.push((String::from(module), String::from(field)));
|
||||
}
|
||||
|
||||
fn declare_table_elements(
|
||||
&mut self,
|
||||
_table_index: TableIndex,
|
||||
_base: Option<GlobalIndex>,
|
||||
_offset: usize,
|
||||
_elements: Box<[FuncIndex]>,
|
||||
) {
|
||||
// We do nothing
|
||||
}
|
||||
|
||||
fn declare_memory(&mut self, memory: Memory) {
|
||||
self.info.memories.push(Exportable::new(memory));
|
||||
}
|
||||
|
||||
fn declare_memory_import(&mut self, memory: Memory, module: &'data str, field: &'data str) {
|
||||
self.info.memories.push(Exportable::new(memory));
|
||||
self.info
|
||||
.imported_memories
|
||||
.push((String::from(module), String::from(field)));
|
||||
}
|
||||
|
||||
fn declare_data_initialization(
|
||||
&mut self,
|
||||
_memory_index: MemoryIndex,
|
||||
_base: Option<GlobalIndex>,
|
||||
_offset: usize,
|
||||
_data: &'data [u8],
|
||||
) {
|
||||
// We do nothing
|
||||
}
|
||||
|
||||
fn declare_func_export(&mut self, func_index: FuncIndex, name: &'data str) {
|
||||
self.info.functions[func_index]
|
||||
.export_names
|
||||
.push(String::from(name));
|
||||
}
|
||||
|
||||
fn declare_table_export(&mut self, table_index: TableIndex, name: &'data str) {
|
||||
self.info.tables[table_index]
|
||||
.export_names
|
||||
.push(String::from(name));
|
||||
}
|
||||
|
||||
fn declare_memory_export(&mut self, memory_index: MemoryIndex, name: &'data str) {
|
||||
self.info.memories[memory_index]
|
||||
.export_names
|
||||
.push(String::from(name));
|
||||
}
|
||||
|
||||
fn declare_global_export(&mut self, global_index: GlobalIndex, name: &'data str) {
|
||||
self.info.globals[global_index]
|
||||
.export_names
|
||||
.push(String::from(name));
|
||||
}
|
||||
|
||||
fn declare_start_func(&mut self, func_index: FuncIndex) {
|
||||
debug_assert!(self.info.start_func.is_none());
|
||||
self.info.start_func = Some(func_index);
|
||||
}
|
||||
|
||||
fn define_function_body(&mut self, body_bytes: &'data [u8]) -> WasmResult<()> {
|
||||
let func = {
|
||||
let mut func_environ = DummyFuncEnvironment::new(&self.info, self.return_mode);
|
||||
let func_index =
|
||||
FuncIndex::new(self.get_num_func_imports() + self.info.function_bodies.len());
|
||||
let name = get_func_name(func_index);
|
||||
let sig = func_environ.vmctx_sig(self.get_func_type(func_index));
|
||||
let mut func = ir::Function::with_name_signature(name, sig);
|
||||
self.trans
|
||||
.translate(body_bytes, &mut func, &mut func_environ)?;
|
||||
func
|
||||
};
|
||||
self.func_bytecode_sizes.push(body_bytes.len());
|
||||
self.info.function_bodies.push(func);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
9
cranelift/wasm/src/environ/mod.rs
Normal file
9
cranelift/wasm/src/environ/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! Support for configurable wasm translation.
|
||||
|
||||
mod dummy;
|
||||
mod spec;
|
||||
|
||||
pub use crate::environ::dummy::DummyEnvironment;
|
||||
pub use crate::environ::spec::{
|
||||
FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode, WasmError, WasmResult,
|
||||
};
|
||||
360
cranelift/wasm/src/environ/spec.rs
Normal file
360
cranelift/wasm/src/environ/spec.rs
Normal file
@@ -0,0 +1,360 @@
|
||||
//! All the runtime support necessary for the wasm to cranelift translation is formalized by the
|
||||
//! traits `FunctionEnvironment` and `ModuleEnvironment`.
|
||||
//!
|
||||
//! There are skeleton implementations of these traits in the `dummy` module, and complete
|
||||
//! implementations in [Wasmtime].
|
||||
//!
|
||||
//! [Wasmtime]: https://github.com/CraneStation/wasmtime
|
||||
|
||||
use crate::translation_utils::{
|
||||
FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, Table, TableIndex,
|
||||
};
|
||||
use core::convert::From;
|
||||
use cranelift_codegen::cursor::FuncCursor;
|
||||
use cranelift_codegen::ir::immediates::Offset32;
|
||||
use cranelift_codegen::ir::{self, InstBuilder};
|
||||
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||
use failure_derive::Fail;
|
||||
use std::boxed::Box;
|
||||
use wasmparser::BinaryReaderError;
|
||||
|
||||
/// The value of a WebAssembly global variable.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum GlobalVariable {
|
||||
/// This is a constant global with a value known at compile time.
|
||||
Const(ir::Value),
|
||||
|
||||
/// This is a variable in memory that should be referenced through a `GlobalValue`.
|
||||
Memory {
|
||||
/// The address of the global variable storage.
|
||||
gv: ir::GlobalValue,
|
||||
/// An offset to add to the address.
|
||||
offset: Offset32,
|
||||
/// The global variable's type.
|
||||
ty: ir::Type,
|
||||
},
|
||||
}
|
||||
|
||||
/// A WebAssembly translation error.
|
||||
///
|
||||
/// When a WebAssembly function can't be translated, one of these error codes will be returned
|
||||
/// to describe the failure.
|
||||
#[derive(Fail, Debug, PartialEq, Eq)]
|
||||
pub enum WasmError {
|
||||
/// The input WebAssembly code is invalid.
|
||||
///
|
||||
/// This error code is used by a WebAssembly translator when it encounters invalid WebAssembly
|
||||
/// code. This should never happen for validated WebAssembly code.
|
||||
#[fail(display = "Invalid input WebAssembly code at offset {}: {}", _1, _0)]
|
||||
InvalidWebAssembly {
|
||||
/// A string describing the validation error.
|
||||
message: &'static str,
|
||||
/// The bytecode offset where the error occurred.
|
||||
offset: usize,
|
||||
},
|
||||
|
||||
/// A feature used by the WebAssembly code is not supported by the embedding environment.
|
||||
///
|
||||
/// Embedding environments may have their own limitations and feature restrictions.
|
||||
#[fail(display = "Unsupported feature: {}", _0)]
|
||||
Unsupported(&'static str),
|
||||
|
||||
/// An implementation limit was exceeded.
|
||||
///
|
||||
/// Cranelift can compile very large and complicated functions, but the [implementation has
|
||||
/// limits][limits] that cause compilation to fail when they are exceeded.
|
||||
///
|
||||
/// [limits]: https://cranelift.readthedocs.io/en/latest/ir.html#implementation-limits
|
||||
#[fail(display = "Implementation limit exceeded")]
|
||||
ImplLimitExceeded,
|
||||
}
|
||||
|
||||
impl From<BinaryReaderError> for WasmError {
|
||||
/// Convert from a `BinaryReaderError` to a `WasmError`.
|
||||
fn from(e: BinaryReaderError) -> Self {
|
||||
let BinaryReaderError { message, offset } = e;
|
||||
WasmError::InvalidWebAssembly { message, offset }
|
||||
}
|
||||
}
|
||||
|
||||
/// A convenient alias for a `Result` that uses `WasmError` as the error type.
|
||||
pub type WasmResult<T> = Result<T, WasmError>;
|
||||
|
||||
/// How to return from functions.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ReturnMode {
|
||||
/// Use normal return instructions as needed.
|
||||
NormalReturns,
|
||||
/// Use a single fallthrough return at the end of the function.
|
||||
FallthroughReturn,
|
||||
}
|
||||
|
||||
/// Environment affecting the translation of a single WebAssembly function.
|
||||
///
|
||||
/// A `FuncEnvironment` trait object is required to translate a WebAssembly function to Cranelift
|
||||
/// IR. The function environment provides information about the WebAssembly module as well as the
|
||||
/// runtime environment.
|
||||
pub trait FuncEnvironment {
|
||||
/// Get the information needed to produce Cranelift IR for the given target.
|
||||
fn target_config(&self) -> TargetFrontendConfig;
|
||||
|
||||
/// Get the Cranelift integer type to use for native pointers.
|
||||
///
|
||||
/// This returns `I64` for 64-bit architectures and `I32` for 32-bit architectures.
|
||||
fn pointer_type(&self) -> ir::Type {
|
||||
ir::Type::int(u16::from(self.target_config().pointer_bits())).unwrap()
|
||||
}
|
||||
|
||||
/// Get the size of a native pointer, in bytes.
|
||||
fn pointer_bytes(&self) -> u8 {
|
||||
self.target_config().pointer_bytes()
|
||||
}
|
||||
|
||||
/// Set up the necessary preamble definitions in `func` to access the global variable
|
||||
/// identified by `index`.
|
||||
///
|
||||
/// The index space covers both imported globals and globals defined by the module.
|
||||
///
|
||||
/// Return the global variable reference that should be used to access the global and the
|
||||
/// WebAssembly type of the global.
|
||||
fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalVariable;
|
||||
|
||||
/// Set up the necessary preamble definitions in `func` to access the linear memory identified
|
||||
/// by `index`.
|
||||
///
|
||||
/// The index space covers both imported and locally declared memories.
|
||||
fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> ir::Heap;
|
||||
|
||||
/// Set up the necessary preamble definitions in `func` to access the table identified
|
||||
/// by `index`.
|
||||
///
|
||||
/// The index space covers both imported and locally declared tables.
|
||||
fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> ir::Table;
|
||||
|
||||
/// Set up a signature definition in the preamble of `func` that can be used for an indirect
|
||||
/// call with signature `index`.
|
||||
///
|
||||
/// The signature may contain additional arguments needed for an indirect call, but the
|
||||
/// arguments marked as `ArgumentPurpose::Normal` must correspond to the WebAssembly signature
|
||||
/// arguments.
|
||||
///
|
||||
/// The signature will only be used for indirect calls, even if the module has direct function
|
||||
/// calls with the same WebAssembly type.
|
||||
fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef;
|
||||
|
||||
/// Set up an external function definition in the preamble of `func` that can be used to
|
||||
/// directly call the function `index`.
|
||||
///
|
||||
/// The index space covers both imported functions and functions defined in the current module.
|
||||
///
|
||||
/// The function's signature may contain additional arguments needed for a direct call, but the
|
||||
/// arguments marked as `ArgumentPurpose::Normal` must correspond to the WebAssembly signature
|
||||
/// arguments.
|
||||
///
|
||||
/// The function's signature will only be used for direct calls, even if the module has
|
||||
/// indirect calls with the same WebAssembly type.
|
||||
fn make_direct_func(&mut self, func: &mut ir::Function, index: FuncIndex) -> ir::FuncRef;
|
||||
|
||||
/// Translate a `call_indirect` WebAssembly instruction at `pos`.
|
||||
///
|
||||
/// Insert instructions at `pos` for an indirect call to the function `callee` in the table
|
||||
/// `table_index` with WebAssembly signature `sig_index`. The `callee` value will have type
|
||||
/// `i32`.
|
||||
///
|
||||
/// The signature `sig_ref` was previously created by `make_indirect_sig()`.
|
||||
///
|
||||
/// Return the call instruction whose results are the WebAssembly return values.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))]
|
||||
fn translate_call_indirect(
|
||||
&mut self,
|
||||
pos: FuncCursor,
|
||||
table_index: TableIndex,
|
||||
table: ir::Table,
|
||||
sig_index: SignatureIndex,
|
||||
sig_ref: ir::SigRef,
|
||||
callee: ir::Value,
|
||||
call_args: &[ir::Value],
|
||||
) -> WasmResult<ir::Inst>;
|
||||
|
||||
/// Translate a `call` WebAssembly instruction at `pos`.
|
||||
///
|
||||
/// Insert instructions at `pos` for a direct call to the function `callee_index`.
|
||||
///
|
||||
/// The function reference `callee` was previously created by `make_direct_func()`.
|
||||
///
|
||||
/// Return the call instruction whose results are the WebAssembly return values.
|
||||
fn translate_call(
|
||||
&mut self,
|
||||
mut pos: FuncCursor,
|
||||
_callee_index: FuncIndex,
|
||||
callee: ir::FuncRef,
|
||||
call_args: &[ir::Value],
|
||||
) -> WasmResult<ir::Inst> {
|
||||
Ok(pos.ins().call(callee, call_args))
|
||||
}
|
||||
|
||||
/// Translate a `memory.grow` WebAssembly instruction.
|
||||
///
|
||||
/// The `index` provided identifies the linear memory to grow, and `heap` is the heap reference
|
||||
/// returned by `make_heap` for the same index.
|
||||
///
|
||||
/// The `val` value is the requested memory size in pages.
|
||||
///
|
||||
/// Returns the old size (in pages) of the memory.
|
||||
fn translate_memory_grow(
|
||||
&mut self,
|
||||
pos: FuncCursor,
|
||||
index: MemoryIndex,
|
||||
heap: ir::Heap,
|
||||
val: ir::Value,
|
||||
) -> WasmResult<ir::Value>;
|
||||
|
||||
/// Translates a `memory.size` WebAssembly instruction.
|
||||
///
|
||||
/// The `index` provided identifies the linear memory to query, and `heap` is the heap reference
|
||||
/// returned by `make_heap` for the same index.
|
||||
///
|
||||
/// Returns the size in pages of the memory.
|
||||
fn translate_memory_size(
|
||||
&mut self,
|
||||
pos: FuncCursor,
|
||||
index: MemoryIndex,
|
||||
heap: ir::Heap,
|
||||
) -> WasmResult<ir::Value>;
|
||||
|
||||
/// Emit code at the beginning of every wasm loop.
|
||||
///
|
||||
/// This can be used to insert explicit interrupt or safepoint checking at
|
||||
/// the beginnings of loops.
|
||||
fn translate_loop_header(&mut self, _pos: FuncCursor) {
|
||||
// By default, don't emit anything.
|
||||
}
|
||||
|
||||
/// Should the code be structured to use a single `fallthrough_return` instruction at the end
|
||||
/// of the function body, rather than `return` instructions as needed? This is used by VMs
|
||||
/// to append custom epilogues.
|
||||
fn return_mode(&self) -> ReturnMode {
|
||||
ReturnMode::NormalReturns
|
||||
}
|
||||
}
|
||||
|
||||
/// An object satisfying the `ModuleEnvironment` trait can be passed as argument to the
|
||||
/// [`translate_module`](fn.translate_module.html) function. These methods should not be called
|
||||
/// by the user, they are only for `cranelift-wasm` internal use.
|
||||
pub trait ModuleEnvironment<'data> {
|
||||
/// Get the information needed to produce Cranelift IR for the current target.
|
||||
fn target_config(&self) -> TargetFrontendConfig;
|
||||
|
||||
/// Provides the number of signatures up front. By default this does nothing, but
|
||||
/// implementations can use this to preallocate memory if desired.
|
||||
fn reserve_signatures(&mut self, _num: u32) {}
|
||||
|
||||
/// Declares a function signature to the environment.
|
||||
fn declare_signature(&mut self, sig: ir::Signature);
|
||||
|
||||
/// Provides the number of imports up front. By default this does nothing, but
|
||||
/// implementations can use this to preallocate memory if desired.
|
||||
fn reserve_imports(&mut self, _num: u32) {}
|
||||
|
||||
/// Declares a function import to the environment.
|
||||
fn declare_func_import(
|
||||
&mut self,
|
||||
sig_index: SignatureIndex,
|
||||
module: &'data str,
|
||||
field: &'data str,
|
||||
);
|
||||
|
||||
/// Declares a table import to the environment.
|
||||
fn declare_table_import(&mut self, table: Table, module: &'data str, field: &'data str);
|
||||
|
||||
/// Declares a memory import to the environment.
|
||||
fn declare_memory_import(&mut self, memory: Memory, module: &'data str, field: &'data str);
|
||||
|
||||
/// Declares a global import to the environment.
|
||||
fn declare_global_import(&mut self, global: Global, module: &'data str, field: &'data str);
|
||||
|
||||
/// Notifies the implementation that all imports have been declared.
|
||||
fn finish_imports(&mut self) {}
|
||||
|
||||
/// Provides the number of defined functions up front. By default this does nothing, but
|
||||
/// implementations can use this to preallocate memory if desired.
|
||||
fn reserve_func_types(&mut self, _num: u32) {}
|
||||
|
||||
/// Declares the type (signature) of a local function in the module.
|
||||
fn declare_func_type(&mut self, sig_index: SignatureIndex);
|
||||
|
||||
/// Provides the number of defined tables up front. By default this does nothing, but
|
||||
/// implementations can use this to preallocate memory if desired.
|
||||
fn reserve_tables(&mut self, _num: u32) {}
|
||||
|
||||
/// Declares a table to the environment.
|
||||
fn declare_table(&mut self, table: Table);
|
||||
|
||||
/// Provides the number of defined memories up front. By default this does nothing, but
|
||||
/// implementations can use this to preallocate memory if desired.
|
||||
fn reserve_memories(&mut self, _num: u32) {}
|
||||
|
||||
/// Declares a memory to the environment
|
||||
fn declare_memory(&mut self, memory: Memory);
|
||||
|
||||
/// Provides the number of defined globals up front. By default this does nothing, but
|
||||
/// implementations can use this to preallocate memory if desired.
|
||||
fn reserve_globals(&mut self, _num: u32) {}
|
||||
|
||||
/// Declares a global to the environment.
|
||||
fn declare_global(&mut self, global: Global);
|
||||
|
||||
/// Provides the number of exports up front. By default this does nothing, but
|
||||
/// implementations can use this to preallocate memory if desired.
|
||||
fn reserve_exports(&mut self, _num: u32) {}
|
||||
|
||||
/// Declares a function export to the environment.
|
||||
fn declare_func_export(&mut self, func_index: FuncIndex, name: &'data str);
|
||||
|
||||
/// Declares a table export to the environment.
|
||||
fn declare_table_export(&mut self, table_index: TableIndex, name: &'data str);
|
||||
|
||||
/// Declares a memory export to the environment.
|
||||
fn declare_memory_export(&mut self, memory_index: MemoryIndex, name: &'data str);
|
||||
|
||||
/// Declares a global export to the environment.
|
||||
fn declare_global_export(&mut self, global_index: GlobalIndex, name: &'data str);
|
||||
|
||||
/// Notifies the implementation that all exports have been declared.
|
||||
fn finish_exports(&mut self) {}
|
||||
|
||||
/// Declares the optional start function.
|
||||
fn declare_start_func(&mut self, index: FuncIndex);
|
||||
|
||||
/// Provides the number of element initializers up front. By default this does nothing, but
|
||||
/// implementations can use this to preallocate memory if desired.
|
||||
fn reserve_table_elements(&mut self, _num: u32) {}
|
||||
|
||||
/// Fills a declared table with references to functions in the module.
|
||||
fn declare_table_elements(
|
||||
&mut self,
|
||||
table_index: TableIndex,
|
||||
base: Option<GlobalIndex>,
|
||||
offset: usize,
|
||||
elements: Box<[FuncIndex]>,
|
||||
);
|
||||
|
||||
/// Provides the contents of a function body.
|
||||
///
|
||||
/// Note there's no `reserve_function_bodies` function because the number of
|
||||
/// functions is already provided by `reserve_func_types`.
|
||||
fn define_function_body(&mut self, body_bytes: &'data [u8]) -> WasmResult<()>;
|
||||
|
||||
/// Provides the number of data initializers up front. By default this does nothing, but
|
||||
/// implementations can use this to preallocate memory if desired.
|
||||
fn reserve_data_initializers(&mut self, _num: u32) {}
|
||||
|
||||
/// Fills a declared memory with bytes at module instantiation.
|
||||
fn declare_data_initialization(
|
||||
&mut self,
|
||||
memory_index: MemoryIndex,
|
||||
base: Option<GlobalIndex>,
|
||||
offset: usize,
|
||||
data: &'data [u8],
|
||||
);
|
||||
}
|
||||
362
cranelift/wasm/src/func_translator.rs
Normal file
362
cranelift/wasm/src/func_translator.rs
Normal file
@@ -0,0 +1,362 @@
|
||||
//! Stand-alone WebAssembly to Cranelift IR translator.
|
||||
//!
|
||||
//! This module defines the `FuncTranslator` type which can translate a single WebAssembly
|
||||
//! function to Cranelift IR guided by a `FuncEnvironment` which provides information about the
|
||||
//! WebAssembly module and the runtime environment.
|
||||
|
||||
use crate::code_translator::translate_operator;
|
||||
use crate::environ::{FuncEnvironment, ReturnMode, WasmResult};
|
||||
use crate::state::TranslationState;
|
||||
use cranelift_codegen::entity::EntityRef;
|
||||
use cranelift_codegen::ir::{self, Ebb, InstBuilder};
|
||||
use cranelift_codegen::timing;
|
||||
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
|
||||
use log::info;
|
||||
use wasmparser::{self, BinaryReader};
|
||||
|
||||
/// WebAssembly to Cranelift IR function translator.
|
||||
///
|
||||
/// A `FuncTranslator` is used to translate a binary WebAssembly function into Cranelift IR guided
|
||||
/// by a `FuncEnvironment` object. A single translator instance can be reused to translate multiple
|
||||
/// functions which will reduce heap allocation traffic.
|
||||
pub struct FuncTranslator {
|
||||
func_ctx: FunctionBuilderContext,
|
||||
state: TranslationState,
|
||||
}
|
||||
|
||||
impl FuncTranslator {
|
||||
/// Create a new translator.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
func_ctx: FunctionBuilderContext::new(),
|
||||
state: TranslationState::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Translate a binary WebAssembly function.
|
||||
///
|
||||
/// The `code` slice contains the binary WebAssembly *function code* as it appears in the code
|
||||
/// section of a WebAssembly module, not including the initial size of the function code. The
|
||||
/// slice is expected to contain two parts:
|
||||
///
|
||||
/// - The declaration of *locals*, and
|
||||
/// - The function *body* as an expression.
|
||||
///
|
||||
/// See [the WebAssembly specification][wasm].
|
||||
///
|
||||
/// [wasm]: https://webassembly.github.io/spec/binary/modules.html#code-section
|
||||
///
|
||||
/// The Cranelift IR function `func` should be completely empty except for the `func.signature`
|
||||
/// and `func.name` fields. The signature may contain special-purpose arguments which are not
|
||||
/// regarded as WebAssembly local variables. Any signature arguments marked as
|
||||
/// `ArgumentPurpose::Normal` are made accessible as WebAssembly local variables.
|
||||
///
|
||||
pub fn translate<FE: FuncEnvironment + ?Sized>(
|
||||
&mut self,
|
||||
code: &[u8],
|
||||
func: &mut ir::Function,
|
||||
environ: &mut FE,
|
||||
) -> WasmResult<()> {
|
||||
self.translate_from_reader(BinaryReader::new(code), func, environ)
|
||||
}
|
||||
|
||||
/// Translate a binary WebAssembly function from a `BinaryReader`.
|
||||
pub fn translate_from_reader<FE: FuncEnvironment + ?Sized>(
|
||||
&mut self,
|
||||
mut reader: BinaryReader,
|
||||
func: &mut ir::Function,
|
||||
environ: &mut FE,
|
||||
) -> WasmResult<()> {
|
||||
let _tt = timing::wasm_translate_function();
|
||||
info!(
|
||||
"translate({} bytes, {}{})",
|
||||
reader.bytes_remaining(),
|
||||
func.name,
|
||||
func.signature
|
||||
);
|
||||
debug_assert_eq!(func.dfg.num_ebbs(), 0, "Function must be empty");
|
||||
debug_assert_eq!(func.dfg.num_insts(), 0, "Function must be empty");
|
||||
|
||||
// This clears the `FunctionBuilderContext`.
|
||||
let mut builder = FunctionBuilder::new(func, &mut self.func_ctx);
|
||||
let entry_block = builder.create_ebb();
|
||||
builder.append_ebb_params_for_function_params(entry_block);
|
||||
builder.switch_to_block(entry_block); // This also creates values for the arguments.
|
||||
builder.seal_block(entry_block);
|
||||
// Make sure the entry block is inserted in the layout before we make any callbacks to
|
||||
// `environ`. The callback functions may need to insert things in the entry block.
|
||||
builder.ensure_inserted_ebb();
|
||||
|
||||
let num_params = declare_wasm_parameters(&mut builder, entry_block);
|
||||
|
||||
// Set up the translation state with a single pushed control block representing the whole
|
||||
// function and its return values.
|
||||
let exit_block = builder.create_ebb();
|
||||
builder.append_ebb_params_for_function_returns(exit_block);
|
||||
self.state.initialize(&builder.func.signature, exit_block);
|
||||
|
||||
parse_local_decls(&mut reader, &mut builder, num_params)?;
|
||||
parse_function_body(reader, &mut builder, &mut self.state, environ)?;
|
||||
|
||||
builder.finalize();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Declare local variables for the signature parameters that correspond to WebAssembly locals.
|
||||
///
|
||||
/// Return the number of local variables declared.
|
||||
fn declare_wasm_parameters(builder: &mut FunctionBuilder, entry_block: Ebb) -> usize {
|
||||
let sig_len = builder.func.signature.params.len();
|
||||
let mut next_local = 0;
|
||||
for i in 0..sig_len {
|
||||
let param_type = builder.func.signature.params[i];
|
||||
// There may be additional special-purpose parameters following the normal WebAssembly
|
||||
// signature parameters. For example, a `vmctx` pointer.
|
||||
if param_type.purpose == ir::ArgumentPurpose::Normal {
|
||||
// This is a normal WebAssembly signature parameter, so create a local for it.
|
||||
let local = Variable::new(next_local);
|
||||
builder.declare_var(local, param_type.value_type);
|
||||
next_local += 1;
|
||||
|
||||
let param_value = builder.ebb_params(entry_block)[i];
|
||||
builder.def_var(local, param_value);
|
||||
}
|
||||
}
|
||||
|
||||
next_local
|
||||
}
|
||||
|
||||
/// Parse the local variable declarations that precede the function body.
|
||||
///
|
||||
/// Declare local variables, starting from `num_params`.
|
||||
fn parse_local_decls(
|
||||
reader: &mut BinaryReader,
|
||||
builder: &mut FunctionBuilder,
|
||||
num_params: usize,
|
||||
) -> WasmResult<()> {
|
||||
let mut next_local = num_params;
|
||||
let local_count = reader.read_local_count()?;
|
||||
|
||||
let mut locals_total = 0;
|
||||
for _ in 0..local_count {
|
||||
builder.set_srcloc(cur_srcloc(reader));
|
||||
let (count, ty) = reader.read_local_decl(&mut locals_total)?;
|
||||
declare_locals(builder, count, ty, &mut next_local);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Declare `count` local variables of the same type, starting from `next_local`.
|
||||
///
|
||||
/// Fail of too many locals are declared in the function, or if the type is not valid for a local.
|
||||
fn declare_locals(
|
||||
builder: &mut FunctionBuilder,
|
||||
count: u32,
|
||||
wasm_type: wasmparser::Type,
|
||||
next_local: &mut usize,
|
||||
) {
|
||||
// All locals are initialized to 0.
|
||||
use wasmparser::Type::*;
|
||||
let zeroval = match wasm_type {
|
||||
I32 => builder.ins().iconst(ir::types::I32, 0),
|
||||
I64 => builder.ins().iconst(ir::types::I64, 0),
|
||||
F32 => builder.ins().f32const(ir::immediates::Ieee32::with_bits(0)),
|
||||
F64 => builder.ins().f64const(ir::immediates::Ieee64::with_bits(0)),
|
||||
_ => panic!("invalid local type"),
|
||||
};
|
||||
|
||||
let ty = builder.func.dfg.value_type(zeroval);
|
||||
for _ in 0..count {
|
||||
let local = Variable::new(*next_local);
|
||||
builder.declare_var(local, ty);
|
||||
builder.def_var(local, zeroval);
|
||||
*next_local += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the function body in `reader`.
|
||||
///
|
||||
/// 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>(
|
||||
mut reader: BinaryReader,
|
||||
builder: &mut FunctionBuilder,
|
||||
state: &mut TranslationState,
|
||||
environ: &mut FE,
|
||||
) -> WasmResult<()> {
|
||||
// The control stack is initialized with a single block representing the whole function.
|
||||
debug_assert_eq!(state.control_stack.len(), 1, "State not initialized");
|
||||
|
||||
// Keep going until the final `End` operator which pops the outermost block.
|
||||
while !state.control_stack.is_empty() {
|
||||
builder.set_srcloc(cur_srcloc(&reader));
|
||||
let op = reader.read_operator()?;
|
||||
translate_operator(op, builder, state, environ)?;
|
||||
}
|
||||
|
||||
// The final `End` operator left us in the exit block where we need to manually add a return
|
||||
// instruction.
|
||||
//
|
||||
// If the exit block is unreachable, it may not have the correct arguments, so we would
|
||||
// generate a return instruction that doesn't match the signature.
|
||||
if state.reachable {
|
||||
debug_assert!(builder.is_pristine());
|
||||
if !builder.is_unreachable() {
|
||||
match environ.return_mode() {
|
||||
ReturnMode::NormalReturns => builder.ins().return_(&state.stack),
|
||||
ReturnMode::FallthroughReturn => builder.ins().fallthrough_return(&state.stack),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Discard any remaining values on the stack. Either we just returned them,
|
||||
// or the end of the function is unreachable.
|
||||
state.stack.clear();
|
||||
|
||||
debug_assert!(reader.eof());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the current source location from a reader.
|
||||
fn cur_srcloc(reader: &BinaryReader) -> ir::SourceLoc {
|
||||
// We record source locations as byte code offsets relative to the beginning of the function.
|
||||
// This will wrap around of a single function's byte code is larger than 4 GB, but a) the
|
||||
// WebAssembly format doesn't allow for that, and b) that would hit other Cranelift
|
||||
// implementation limits anyway.
|
||||
ir::SourceLoc::new(reader.current_position() as u32)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{FuncTranslator, ReturnMode};
|
||||
use crate::environ::DummyEnvironment;
|
||||
use cranelift_codegen::ir::types::I32;
|
||||
use cranelift_codegen::{ir, isa, settings, Context};
|
||||
use log::debug;
|
||||
use target_lexicon::PointerWidth;
|
||||
|
||||
#[test]
|
||||
fn small1() {
|
||||
// Implicit return.
|
||||
//
|
||||
// (func $small1 (param i32) (result i32)
|
||||
// (i32.add (get_local 0) (i32.const 1))
|
||||
// )
|
||||
const BODY: [u8; 7] = [
|
||||
0x00, // local decl count
|
||||
0x20, 0x00, // get_local 0
|
||||
0x41, 0x01, // i32.const 1
|
||||
0x6a, // i32.add
|
||||
0x0b, // end
|
||||
];
|
||||
|
||||
let mut trans = FuncTranslator::new();
|
||||
let flags = settings::Flags::new(settings::builder());
|
||||
let runtime = DummyEnvironment::new(
|
||||
isa::TargetFrontendConfig {
|
||||
default_call_conv: isa::CallConv::Fast,
|
||||
pointer_width: PointerWidth::U64,
|
||||
},
|
||||
ReturnMode::NormalReturns,
|
||||
);
|
||||
|
||||
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, &mut ctx.func, &mut runtime.func_env())
|
||||
.unwrap();
|
||||
debug!("{}", ctx.func.display(None));
|
||||
ctx.verify(&flags).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small2() {
|
||||
// Same as above, but with an explicit return instruction.
|
||||
//
|
||||
// (func $small2 (param i32) (result i32)
|
||||
// (return (i32.add (get_local 0) (i32.const 1)))
|
||||
// )
|
||||
const BODY: [u8; 8] = [
|
||||
0x00, // local decl count
|
||||
0x20, 0x00, // get_local 0
|
||||
0x41, 0x01, // i32.const 1
|
||||
0x6a, // i32.add
|
||||
0x0f, // return
|
||||
0x0b, // end
|
||||
];
|
||||
|
||||
let mut trans = FuncTranslator::new();
|
||||
let flags = settings::Flags::new(settings::builder());
|
||||
let runtime = DummyEnvironment::new(
|
||||
isa::TargetFrontendConfig {
|
||||
default_call_conv: isa::CallConv::Fast,
|
||||
pointer_width: PointerWidth::U64,
|
||||
},
|
||||
ReturnMode::NormalReturns,
|
||||
);
|
||||
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, &mut ctx.func, &mut runtime.func_env())
|
||||
.unwrap();
|
||||
debug!("{}", ctx.func.display(None));
|
||||
ctx.verify(&flags).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infloop() {
|
||||
// An infinite loop, no return instructions.
|
||||
//
|
||||
// (func $infloop (result i32)
|
||||
// (local i32)
|
||||
// (loop (result i32)
|
||||
// (i32.add (get_local 0) (i32.const 1))
|
||||
// (set_local 0)
|
||||
// (br 0)
|
||||
// )
|
||||
// )
|
||||
const BODY: [u8; 16] = [
|
||||
0x01, // 1 local decl.
|
||||
0x01, 0x7f, // 1 i32 local.
|
||||
0x03, 0x7f, // loop i32
|
||||
0x20, 0x00, // get_local 0
|
||||
0x41, 0x01, // i32.const 0
|
||||
0x6a, // i32.add
|
||||
0x21, 0x00, // set_local 0
|
||||
0x0c, 0x00, // br 0
|
||||
0x0b, // end
|
||||
0x0b, // end
|
||||
];
|
||||
|
||||
let mut trans = FuncTranslator::new();
|
||||
let flags = settings::Flags::new(settings::builder());
|
||||
let runtime = DummyEnvironment::new(
|
||||
isa::TargetFrontendConfig {
|
||||
default_call_conv: isa::CallConv::Fast,
|
||||
pointer_width: PointerWidth::U64,
|
||||
},
|
||||
ReturnMode::NormalReturns,
|
||||
);
|
||||
let mut ctx = Context::new();
|
||||
|
||||
ctx.func.name = ir::ExternalName::testcase("infloop");
|
||||
ctx.func.signature.returns.push(ir::AbiParam::new(I32));
|
||||
|
||||
trans
|
||||
.translate(&BODY, &mut ctx.func, &mut runtime.func_env())
|
||||
.unwrap();
|
||||
debug!("{}", ctx.func.display(None));
|
||||
ctx.verify(&flags).unwrap();
|
||||
}
|
||||
}
|
||||
66
cranelift/wasm/src/lib.rs
Normal file
66
cranelift/wasm/src/lib.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
//! Performs translation from a wasm module in binary format to the in-memory form
|
||||
//! of Cranelift IR. More particularly, it translates the code of all the functions bodies and
|
||||
//! interacts with an environment implementing the
|
||||
//! [`ModuleEnvironment`](trait.ModuleEnvironment.html)
|
||||
//! trait to deal with tables, globals and linear memory.
|
||||
//!
|
||||
//! The crate provides a `DummyEnvironment` struct that will allow to translate the code of the
|
||||
//! functions but will fail at execution.
|
||||
//!
|
||||
//! The main function of this module is [`translate_module`](fn.translate_module.html).
|
||||
|
||||
#![deny(missing_docs, trivial_numeric_casts, unused_extern_crates)]
|
||||
#![warn(unused_import_braces)]
|
||||
#![cfg_attr(feature = "std", deny(unstable_features))]
|
||||
#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../../clippy.toml")))]
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
|
||||
#![cfg_attr(
|
||||
feature = "cargo-clippy",
|
||||
warn(
|
||||
clippy::float_arithmetic,
|
||||
clippy::mut_mut,
|
||||
clippy::nonminimal_bool,
|
||||
clippy::option_map_unwrap_or,
|
||||
clippy::option_map_unwrap_or_else,
|
||||
clippy::print_stdout,
|
||||
clippy::unicode_not_nfc,
|
||||
clippy::use_self
|
||||
)
|
||||
)]
|
||||
#![no_std]
|
||||
#![cfg_attr(not(feature = "std"), feature(alloc))]
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[macro_use]
|
||||
extern crate alloc as std;
|
||||
#[cfg(feature = "std")]
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use hashmap_core::{map as hash_map, HashMap};
|
||||
#[cfg(feature = "std")]
|
||||
use std::collections::{hash_map, HashMap};
|
||||
|
||||
mod code_translator;
|
||||
mod environ;
|
||||
mod func_translator;
|
||||
mod module_translator;
|
||||
mod sections_translator;
|
||||
mod state;
|
||||
mod translation_utils;
|
||||
|
||||
pub use crate::environ::{
|
||||
DummyEnvironment, FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode, WasmError,
|
||||
WasmResult,
|
||||
};
|
||||
pub use crate::func_translator::FuncTranslator;
|
||||
pub use crate::module_translator::translate_module;
|
||||
pub use crate::translation_utils::{
|
||||
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, Global,
|
||||
GlobalIndex, GlobalInit, Memory, MemoryIndex, SignatureIndex, Table, TableElementType,
|
||||
TableIndex,
|
||||
};
|
||||
|
||||
/// Version number of this crate.
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
143
cranelift/wasm/src/module_translator.rs
Normal file
143
cranelift/wasm/src/module_translator.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
//! Translation skeleton that traverses the whole WebAssembly module and call helper functions
|
||||
//! to deal with each part of it.
|
||||
use crate::environ::{ModuleEnvironment, WasmResult};
|
||||
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,
|
||||
parse_start_section, parse_table_section, parse_type_section,
|
||||
};
|
||||
use cranelift_codegen::timing;
|
||||
use wasmparser::{ModuleReader, SectionCode};
|
||||
|
||||
/// Translate a sequence of bytes forming a valid Wasm binary into a list of valid Cranelift IR
|
||||
/// [`Function`](../codegen/ir/function/struct.Function.html).
|
||||
pub fn translate_module<'data>(
|
||||
data: &'data [u8],
|
||||
environ: &mut ModuleEnvironment<'data>,
|
||||
) -> WasmResult<()> {
|
||||
let _tt = timing::wasm_translate_module();
|
||||
let mut reader = ModuleReader::new(data)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut section = reader.read()?;
|
||||
|
||||
if let SectionCode::Type = section.code {
|
||||
let types = section.get_type_section_reader()?;
|
||||
parse_type_section(types, environ)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(());
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Import = section.code {
|
||||
let imports = section.get_import_section_reader()?;
|
||||
parse_import_section(imports, environ)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(());
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Function = section.code {
|
||||
let functions = section.get_function_section_reader()?;
|
||||
parse_function_section(functions, environ)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(());
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Table = section.code {
|
||||
let tables = section.get_table_section_reader()?;
|
||||
parse_table_section(tables, environ)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(());
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Memory = section.code {
|
||||
let memories = section.get_memory_section_reader()?;
|
||||
parse_memory_section(memories, environ)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(());
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Global = section.code {
|
||||
let globals = section.get_global_section_reader()?;
|
||||
parse_global_section(globals, environ)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(());
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Export = section.code {
|
||||
let exports = section.get_export_section_reader()?;
|
||||
parse_export_section(exports, environ)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(());
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Start = section.code {
|
||||
let start = section.get_start_section_content()?;
|
||||
parse_start_section(start, environ)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(());
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Element = section.code {
|
||||
let elements = section.get_element_section_reader()?;
|
||||
parse_element_section(elements, environ)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(());
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Code = section.code {
|
||||
let code = section.get_code_section_reader()?;
|
||||
parse_code_section(code, environ)?;
|
||||
|
||||
reader.skip_custom_sections()?;
|
||||
if reader.eof() {
|
||||
return Ok(());
|
||||
}
|
||||
section = reader.read()?;
|
||||
}
|
||||
|
||||
if let SectionCode::Data = section.code {
|
||||
let data = section.get_data_section_reader()?;
|
||||
parse_data_section(data, environ)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
331
cranelift/wasm/src/sections_translator.rs
Normal file
331
cranelift/wasm/src/sections_translator.rs
Normal file
@@ -0,0 +1,331 @@
|
||||
//! Helper functions to gather information for each of the non-function sections of a
|
||||
//! WebAssembly module.
|
||||
//!
|
||||
//! The code of theses helper function is straightforward since it is only about reading metadata
|
||||
//! about linear memories, tables, globals, etc. and storing them for later use.
|
||||
//!
|
||||
//! The special case of the initialize expressions for table elements offsets or global variables
|
||||
//! is handled, according to the semantics of WebAssembly, to only specific expressions that are
|
||||
//! interpreted on the fly.
|
||||
use crate::environ::{ModuleEnvironment, WasmResult};
|
||||
use crate::translation_utils::{
|
||||
type_to_type, FuncIndex, Global, GlobalIndex, GlobalInit, Memory, MemoryIndex, SignatureIndex,
|
||||
Table, TableElementType, TableIndex,
|
||||
};
|
||||
use core::str::from_utf8;
|
||||
use cranelift_codegen::ir::{self, AbiParam, Signature};
|
||||
use cranelift_entity::EntityRef;
|
||||
use std::vec::Vec;
|
||||
use wasmparser::{
|
||||
self, CodeSectionReader, Data, DataSectionReader, Element, ElementSectionReader, Export,
|
||||
ExportSectionReader, ExternalKind, FuncType, FunctionSectionReader, GlobalSectionReader,
|
||||
GlobalType, ImportSectionEntryType, ImportSectionReader, MemorySectionReader, MemoryType,
|
||||
Operator, TableSectionReader, TypeSectionReader,
|
||||
};
|
||||
|
||||
/// Parses the Type section of the wasm module.
|
||||
pub fn parse_type_section(
|
||||
types: TypeSectionReader,
|
||||
environ: &mut ModuleEnvironment,
|
||||
) -> WasmResult<()> {
|
||||
environ.reserve_signatures(types.get_count());
|
||||
|
||||
for entry in types {
|
||||
match entry? {
|
||||
FuncType {
|
||||
form: wasmparser::Type::Func,
|
||||
ref params,
|
||||
ref returns,
|
||||
} => {
|
||||
let mut sig = Signature::new(environ.target_config().default_call_conv);
|
||||
sig.params.extend(params.iter().map(|ty| {
|
||||
let cret_arg: ir::Type = type_to_type(*ty)
|
||||
.expect("only numeric types are supported in function signatures");
|
||||
AbiParam::new(cret_arg)
|
||||
}));
|
||||
sig.returns.extend(returns.iter().map(|ty| {
|
||||
let cret_arg: ir::Type = type_to_type(*ty)
|
||||
.expect("only numeric types are supported in function signatures");
|
||||
AbiParam::new(cret_arg)
|
||||
}));
|
||||
environ.declare_signature(sig);
|
||||
}
|
||||
ref s => panic!("unsupported type: {:?}", s),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Import section of the wasm module.
|
||||
pub fn parse_import_section<'data>(
|
||||
imports: ImportSectionReader<'data>,
|
||||
environ: &mut ModuleEnvironment<'data>,
|
||||
) -> WasmResult<()> {
|
||||
environ.reserve_imports(imports.get_count());
|
||||
|
||||
for entry in imports {
|
||||
let import = entry?;
|
||||
|
||||
// The input has already been validated, so we should be able to
|
||||
// assume valid UTF-8 and use `from_utf8_unchecked` if performance
|
||||
// becomes a concern here.
|
||||
let module_name = from_utf8(import.module).unwrap();
|
||||
let field_name = from_utf8(import.field).unwrap();
|
||||
|
||||
match import.ty {
|
||||
ImportSectionEntryType::Function(sig) => {
|
||||
environ.declare_func_import(SignatureIndex::from_u32(sig), module_name, field_name);
|
||||
}
|
||||
ImportSectionEntryType::Memory(MemoryType {
|
||||
limits: ref memlimits,
|
||||
shared,
|
||||
}) => {
|
||||
environ.declare_memory_import(
|
||||
Memory {
|
||||
minimum: memlimits.initial,
|
||||
maximum: memlimits.maximum,
|
||||
shared,
|
||||
},
|
||||
module_name,
|
||||
field_name,
|
||||
);
|
||||
}
|
||||
ImportSectionEntryType::Global(ref ty) => {
|
||||
environ.declare_global_import(
|
||||
Global {
|
||||
ty: type_to_type(ty.content_type).unwrap(),
|
||||
mutability: ty.mutable,
|
||||
initializer: GlobalInit::Import,
|
||||
},
|
||||
module_name,
|
||||
field_name,
|
||||
);
|
||||
}
|
||||
ImportSectionEntryType::Table(ref tab) => {
|
||||
environ.declare_table_import(
|
||||
Table {
|
||||
ty: match type_to_type(tab.element_type) {
|
||||
Ok(t) => TableElementType::Val(t),
|
||||
Err(()) => TableElementType::Func,
|
||||
},
|
||||
minimum: tab.limits.initial,
|
||||
maximum: tab.limits.maximum,
|
||||
},
|
||||
module_name,
|
||||
field_name,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
environ.finish_imports();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Function section of the wasm module.
|
||||
pub fn parse_function_section(
|
||||
functions: FunctionSectionReader,
|
||||
environ: &mut ModuleEnvironment,
|
||||
) -> WasmResult<()> {
|
||||
environ.reserve_func_types(functions.get_count());
|
||||
|
||||
for entry in functions {
|
||||
let sigindex = entry?;
|
||||
environ.declare_func_type(SignatureIndex::from_u32(sigindex));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Table section of the wasm module.
|
||||
pub fn parse_table_section(
|
||||
tables: TableSectionReader,
|
||||
environ: &mut ModuleEnvironment,
|
||||
) -> WasmResult<()> {
|
||||
environ.reserve_tables(tables.get_count());
|
||||
|
||||
for entry in tables {
|
||||
let table = entry?;
|
||||
environ.declare_table(Table {
|
||||
ty: match type_to_type(table.element_type) {
|
||||
Ok(t) => TableElementType::Val(t),
|
||||
Err(()) => TableElementType::Func,
|
||||
},
|
||||
minimum: table.limits.initial,
|
||||
maximum: table.limits.maximum,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Memory section of the wasm module.
|
||||
pub fn parse_memory_section(
|
||||
memories: MemorySectionReader,
|
||||
environ: &mut ModuleEnvironment,
|
||||
) -> WasmResult<()> {
|
||||
environ.reserve_memories(memories.get_count());
|
||||
|
||||
for entry in memories {
|
||||
let memory = entry?;
|
||||
environ.declare_memory(Memory {
|
||||
minimum: memory.limits.initial,
|
||||
maximum: memory.limits.maximum,
|
||||
shared: memory.shared,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Global section of the wasm module.
|
||||
pub fn parse_global_section(
|
||||
globals: GlobalSectionReader,
|
||||
environ: &mut ModuleEnvironment,
|
||||
) -> WasmResult<()> {
|
||||
environ.reserve_globals(globals.get_count());
|
||||
|
||||
for entry in globals {
|
||||
let wasmparser::Global {
|
||||
ty: GlobalType {
|
||||
content_type,
|
||||
mutable,
|
||||
},
|
||||
init_expr,
|
||||
} = entry?;
|
||||
let mut init_expr_reader = init_expr.get_binary_reader();
|
||||
let initializer = match init_expr_reader.read_operator()? {
|
||||
Operator::I32Const { value } => GlobalInit::I32Const(value),
|
||||
Operator::I64Const { value } => GlobalInit::I64Const(value),
|
||||
Operator::F32Const { value } => GlobalInit::F32Const(value.bits()),
|
||||
Operator::F64Const { value } => GlobalInit::F64Const(value.bits()),
|
||||
Operator::GetGlobal { global_index } => {
|
||||
GlobalInit::GetGlobal(GlobalIndex::from_u32(global_index))
|
||||
}
|
||||
ref s => panic!("unsupported init expr in global section: {:?}", s),
|
||||
};
|
||||
let global = Global {
|
||||
ty: type_to_type(content_type).unwrap(),
|
||||
mutability: mutable,
|
||||
initializer,
|
||||
};
|
||||
environ.declare_global(global);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Export section of the wasm module.
|
||||
pub fn parse_export_section<'data>(
|
||||
exports: ExportSectionReader<'data>,
|
||||
environ: &mut ModuleEnvironment<'data>,
|
||||
) -> WasmResult<()> {
|
||||
environ.reserve_exports(exports.get_count());
|
||||
|
||||
for entry in exports {
|
||||
let Export {
|
||||
field,
|
||||
ref kind,
|
||||
index,
|
||||
} = entry?;
|
||||
|
||||
// The input has already been validated, so we should be able to
|
||||
// assume valid UTF-8 and use `from_utf8_unchecked` if performance
|
||||
// becomes a concern here.
|
||||
let name = from_utf8(field).unwrap();
|
||||
let index = index as usize;
|
||||
match *kind {
|
||||
ExternalKind::Function => environ.declare_func_export(FuncIndex::new(index), name),
|
||||
ExternalKind::Table => environ.declare_table_export(TableIndex::new(index), name),
|
||||
ExternalKind::Memory => environ.declare_memory_export(MemoryIndex::new(index), name),
|
||||
ExternalKind::Global => environ.declare_global_export(GlobalIndex::new(index), name),
|
||||
}
|
||||
}
|
||||
|
||||
environ.finish_exports();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Start section of the wasm module.
|
||||
pub fn parse_start_section(index: u32, environ: &mut ModuleEnvironment) -> WasmResult<()> {
|
||||
environ.declare_start_func(FuncIndex::from_u32(index));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Element section of the wasm module.
|
||||
pub fn parse_element_section<'data>(
|
||||
elements: ElementSectionReader<'data>,
|
||||
environ: &mut ModuleEnvironment,
|
||||
) -> WasmResult<()> {
|
||||
environ.reserve_table_elements(elements.get_count());
|
||||
|
||||
for entry in elements {
|
||||
let Element {
|
||||
table_index,
|
||||
init_expr,
|
||||
items,
|
||||
} = entry?;
|
||||
let mut init_expr_reader = init_expr.get_binary_reader();
|
||||
let (base, offset) = match init_expr_reader.read_operator()? {
|
||||
Operator::I32Const { value } => (None, value as u32 as usize),
|
||||
Operator::GetGlobal { global_index } => (Some(GlobalIndex::from_u32(global_index)), 0),
|
||||
ref s => panic!("unsupported init expr in element section: {:?}", s),
|
||||
};
|
||||
let items_reader = items.get_items_reader()?;
|
||||
let mut elems = Vec::with_capacity(cast::usize(items_reader.get_count()));
|
||||
for item in items_reader {
|
||||
let x = item?;
|
||||
elems.push(FuncIndex::from_u32(x));
|
||||
}
|
||||
environ.declare_table_elements(
|
||||
TableIndex::from_u32(table_index),
|
||||
base,
|
||||
offset,
|
||||
elems.into_boxed_slice(),
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Code section of the wasm module.
|
||||
pub fn parse_code_section<'data>(
|
||||
code: CodeSectionReader<'data>,
|
||||
environ: &mut ModuleEnvironment<'data>,
|
||||
) -> WasmResult<()> {
|
||||
for body in code {
|
||||
let mut reader = body?.get_binary_reader();
|
||||
let size = reader.bytes_remaining();
|
||||
environ.define_function_body(reader.read_bytes(size)?)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses the Data section of the wasm module.
|
||||
pub fn parse_data_section<'data>(
|
||||
data: DataSectionReader<'data>,
|
||||
environ: &mut ModuleEnvironment<'data>,
|
||||
) -> WasmResult<()> {
|
||||
environ.reserve_data_initializers(data.get_count());
|
||||
|
||||
for entry in data {
|
||||
let Data {
|
||||
memory_index,
|
||||
init_expr,
|
||||
data,
|
||||
} = entry?;
|
||||
let mut init_expr_reader = init_expr.get_binary_reader();
|
||||
let (base, offset) = match init_expr_reader.read_operator()? {
|
||||
Operator::I32Const { value } => (None, value as u32 as usize),
|
||||
Operator::GetGlobal { global_index } => (Some(GlobalIndex::from_u32(global_index)), 0),
|
||||
ref s => panic!("unsupported init expr in data section: {:?}", s),
|
||||
};
|
||||
environ.declare_data_initialization(
|
||||
MemoryIndex::from_u32(memory_index),
|
||||
base,
|
||||
offset,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
369
cranelift/wasm/src/state.rs
Normal file
369
cranelift/wasm/src/state.rs
Normal file
@@ -0,0 +1,369 @@
|
||||
//! WebAssembly function translation state.
|
||||
//!
|
||||
//! The `TranslationState` struct defined in this module is used to keep track of the WebAssembly
|
||||
//! value and control stacks during the translation of a single function.
|
||||
|
||||
use super::HashMap;
|
||||
use crate::environ::{FuncEnvironment, GlobalVariable};
|
||||
use crate::translation_utils::{FuncIndex, GlobalIndex, MemoryIndex, SignatureIndex, TableIndex};
|
||||
use cranelift_codegen::ir::{self, Ebb, Inst, Value};
|
||||
use std::vec::Vec;
|
||||
|
||||
/// A control stack frame can be an `if`, a `block` or a `loop`, each one having the following
|
||||
/// fields:
|
||||
///
|
||||
/// - `destination`: reference to the `Ebb` that will hold the code after the control block;
|
||||
/// - `num_return_values`: number of values returned by the control block;
|
||||
/// - `original_stack_size`: size of the value stack at the beginning of the control block.
|
||||
///
|
||||
/// Moreover, the `if` frame has the `branch_inst` field that points to the `brz` instruction
|
||||
/// separating the `true` and `false` branch. The `loop` frame has a `header` field that references
|
||||
/// the `Ebb` that contains the beginning of the body of the loop.
|
||||
#[derive(Debug)]
|
||||
pub enum ControlStackFrame {
|
||||
If {
|
||||
destination: Ebb,
|
||||
branch_inst: Inst,
|
||||
num_return_values: usize,
|
||||
original_stack_size: usize,
|
||||
exit_is_branched_to: bool,
|
||||
reachable_from_top: bool,
|
||||
},
|
||||
Block {
|
||||
destination: Ebb,
|
||||
num_return_values: usize,
|
||||
original_stack_size: usize,
|
||||
exit_is_branched_to: bool,
|
||||
},
|
||||
Loop {
|
||||
destination: Ebb,
|
||||
header: Ebb,
|
||||
num_return_values: usize,
|
||||
original_stack_size: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// Helper methods for the control stack objects.
|
||||
impl ControlStackFrame {
|
||||
pub fn num_return_values(&self) -> usize {
|
||||
match *self {
|
||||
ControlStackFrame::If {
|
||||
num_return_values, ..
|
||||
}
|
||||
| ControlStackFrame::Block {
|
||||
num_return_values, ..
|
||||
}
|
||||
| ControlStackFrame::Loop {
|
||||
num_return_values, ..
|
||||
} => num_return_values,
|
||||
}
|
||||
}
|
||||
pub fn following_code(&self) -> Ebb {
|
||||
match *self {
|
||||
ControlStackFrame::If { destination, .. }
|
||||
| ControlStackFrame::Block { destination, .. }
|
||||
| ControlStackFrame::Loop { destination, .. } => destination,
|
||||
}
|
||||
}
|
||||
pub fn br_destination(&self) -> Ebb {
|
||||
match *self {
|
||||
ControlStackFrame::If { destination, .. }
|
||||
| ControlStackFrame::Block { destination, .. } => destination,
|
||||
ControlStackFrame::Loop { header, .. } => header,
|
||||
}
|
||||
}
|
||||
pub fn original_stack_size(&self) -> usize {
|
||||
match *self {
|
||||
ControlStackFrame::If {
|
||||
original_stack_size,
|
||||
..
|
||||
}
|
||||
| ControlStackFrame::Block {
|
||||
original_stack_size,
|
||||
..
|
||||
}
|
||||
| ControlStackFrame::Loop {
|
||||
original_stack_size,
|
||||
..
|
||||
} => original_stack_size,
|
||||
}
|
||||
}
|
||||
pub fn is_loop(&self) -> bool {
|
||||
match *self {
|
||||
ControlStackFrame::If { .. } | ControlStackFrame::Block { .. } => false,
|
||||
ControlStackFrame::Loop { .. } => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exit_is_branched_to(&self) -> bool {
|
||||
match *self {
|
||||
ControlStackFrame::If {
|
||||
exit_is_branched_to,
|
||||
..
|
||||
}
|
||||
| ControlStackFrame::Block {
|
||||
exit_is_branched_to,
|
||||
..
|
||||
} => exit_is_branched_to,
|
||||
ControlStackFrame::Loop { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_branched_to_exit(&mut self) {
|
||||
match *self {
|
||||
ControlStackFrame::If {
|
||||
ref mut exit_is_branched_to,
|
||||
..
|
||||
}
|
||||
| ControlStackFrame::Block {
|
||||
ref mut exit_is_branched_to,
|
||||
..
|
||||
} => *exit_is_branched_to = true,
|
||||
ControlStackFrame::Loop { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains information passed along during the translation and that records:
|
||||
///
|
||||
/// - The current value and control stacks.
|
||||
/// - The depth of the two unreachable control blocks stacks, that are manipulated when translating
|
||||
/// unreachable code;
|
||||
pub struct TranslationState {
|
||||
pub stack: Vec<Value>,
|
||||
pub control_stack: Vec<ControlStackFrame>,
|
||||
pub reachable: bool,
|
||||
|
||||
// Map of global variables that have already been created by `FuncEnvironment::make_global`.
|
||||
globals: HashMap<GlobalIndex, GlobalVariable>,
|
||||
|
||||
// Map of heaps that have been created by `FuncEnvironment::make_heap`.
|
||||
heaps: HashMap<MemoryIndex, ir::Heap>,
|
||||
|
||||
// Map of tables that have been created by `FuncEnvironment::make_table`.
|
||||
tables: HashMap<TableIndex, ir::Table>,
|
||||
|
||||
// Map of indirect call signatures that have been created by
|
||||
// `FuncEnvironment::make_indirect_sig()`.
|
||||
// Stores both the signature reference and the number of WebAssembly arguments
|
||||
signatures: HashMap<SignatureIndex, (ir::SigRef, usize)>,
|
||||
|
||||
// Imported and local functions that have been created by
|
||||
// `FuncEnvironment::make_direct_func()`.
|
||||
// Stores both the function reference and the number of WebAssembly arguments
|
||||
functions: HashMap<FuncIndex, (ir::FuncRef, usize)>,
|
||||
}
|
||||
|
||||
impl TranslationState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stack: Vec::new(),
|
||||
control_stack: Vec::new(),
|
||||
reachable: true,
|
||||
globals: HashMap::new(),
|
||||
heaps: HashMap::new(),
|
||||
tables: HashMap::new(),
|
||||
signatures: HashMap::new(),
|
||||
functions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
debug_assert!(self.stack.is_empty());
|
||||
debug_assert!(self.control_stack.is_empty());
|
||||
self.reachable = true;
|
||||
self.globals.clear();
|
||||
self.heaps.clear();
|
||||
self.tables.clear();
|
||||
self.signatures.clear();
|
||||
self.functions.clear();
|
||||
}
|
||||
|
||||
/// Initialize the state for compiling a function with the given signature.
|
||||
///
|
||||
/// This resets the state to containing only a single block representing the whole function.
|
||||
/// The exit block is the last block in the function which will contain the return instruction.
|
||||
pub fn initialize(&mut self, sig: &ir::Signature, exit_block: Ebb) {
|
||||
self.clear();
|
||||
self.push_block(
|
||||
exit_block,
|
||||
sig.returns
|
||||
.iter()
|
||||
.filter(|arg| arg.purpose == ir::ArgumentPurpose::Normal)
|
||||
.count(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Push a value.
|
||||
pub fn push1(&mut self, val: Value) {
|
||||
self.stack.push(val);
|
||||
}
|
||||
|
||||
/// Push multiple values.
|
||||
pub fn pushn(&mut self, vals: &[Value]) {
|
||||
self.stack.extend_from_slice(vals);
|
||||
}
|
||||
|
||||
/// Pop one value.
|
||||
pub fn pop1(&mut self) -> Value {
|
||||
self.stack.pop().unwrap()
|
||||
}
|
||||
|
||||
/// Peek at the top of the stack without popping it.
|
||||
pub fn peek1(&self) -> Value {
|
||||
*self.stack.last().unwrap()
|
||||
}
|
||||
|
||||
/// Pop two values. Return them in the order they were pushed.
|
||||
pub fn pop2(&mut self) -> (Value, Value) {
|
||||
let v2 = self.stack.pop().unwrap();
|
||||
let v1 = self.stack.pop().unwrap();
|
||||
(v1, v2)
|
||||
}
|
||||
|
||||
/// Pop three values. Return them in the order they were pushed.
|
||||
pub fn pop3(&mut self) -> (Value, Value, Value) {
|
||||
let v3 = self.stack.pop().unwrap();
|
||||
let v2 = self.stack.pop().unwrap();
|
||||
let v1 = self.stack.pop().unwrap();
|
||||
(v1, v2, v3)
|
||||
}
|
||||
|
||||
/// Pop the top `n` values on the stack.
|
||||
///
|
||||
/// The popped values are not returned. Use `peekn` to look at them before popping.
|
||||
pub fn popn(&mut self, n: usize) {
|
||||
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 fn peekn(&self, n: usize) -> &[Value] {
|
||||
&self.stack[self.stack.len() - n..]
|
||||
}
|
||||
|
||||
// Push a block on the control stack.
|
||||
pub fn push_block(&mut self, following_code: Ebb, num_result_types: usize) {
|
||||
self.control_stack.push(ControlStackFrame::Block {
|
||||
destination: following_code,
|
||||
original_stack_size: self.stack.len(),
|
||||
num_return_values: num_result_types,
|
||||
exit_is_branched_to: false,
|
||||
});
|
||||
}
|
||||
|
||||
// Push a loop on the control stack.
|
||||
pub fn push_loop(&mut self, header: Ebb, following_code: Ebb, num_result_types: usize) {
|
||||
self.control_stack.push(ControlStackFrame::Loop {
|
||||
header,
|
||||
destination: following_code,
|
||||
original_stack_size: self.stack.len(),
|
||||
num_return_values: num_result_types,
|
||||
});
|
||||
}
|
||||
|
||||
// Push an if on the control stack.
|
||||
pub fn push_if(&mut self, branch_inst: Inst, following_code: Ebb, num_result_types: usize) {
|
||||
self.control_stack.push(ControlStackFrame::If {
|
||||
branch_inst,
|
||||
destination: following_code,
|
||||
original_stack_size: self.stack.len(),
|
||||
num_return_values: num_result_types,
|
||||
exit_is_branched_to: false,
|
||||
reachable_from_top: self.reachable,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Methods for handling entity references.
|
||||
impl TranslationState {
|
||||
/// Get the `GlobalVariable` reference that should be used to access the global variable
|
||||
/// `index`. Create the reference if necessary.
|
||||
/// Also return the WebAssembly type of the global.
|
||||
pub fn get_global<FE: FuncEnvironment + ?Sized>(
|
||||
&mut self,
|
||||
func: &mut ir::Function,
|
||||
index: u32,
|
||||
environ: &mut FE,
|
||||
) -> GlobalVariable {
|
||||
let index = GlobalIndex::from_u32(index);
|
||||
*self
|
||||
.globals
|
||||
.entry(index)
|
||||
.or_insert_with(|| environ.make_global(func, index))
|
||||
}
|
||||
|
||||
/// Get the `Heap` reference that should be used to access linear memory `index`.
|
||||
/// Create the reference if necessary.
|
||||
pub fn get_heap<FE: FuncEnvironment + ?Sized>(
|
||||
&mut self,
|
||||
func: &mut ir::Function,
|
||||
index: u32,
|
||||
environ: &mut FE,
|
||||
) -> ir::Heap {
|
||||
let index = MemoryIndex::from_u32(index);
|
||||
*self
|
||||
.heaps
|
||||
.entry(index)
|
||||
.or_insert_with(|| environ.make_heap(func, index))
|
||||
}
|
||||
|
||||
/// Get the `Table` reference that should be used to access table `index`.
|
||||
/// Create the reference if necessary.
|
||||
pub fn get_table<FE: FuncEnvironment + ?Sized>(
|
||||
&mut self,
|
||||
func: &mut ir::Function,
|
||||
index: u32,
|
||||
environ: &mut FE,
|
||||
) -> ir::Table {
|
||||
let index = TableIndex::from_u32(index);
|
||||
*self
|
||||
.tables
|
||||
.entry(index)
|
||||
.or_insert_with(|| environ.make_table(func, index))
|
||||
}
|
||||
|
||||
/// Get the `SigRef` reference that should be used to make an indirect call with signature
|
||||
/// `index`. Also return the number of WebAssembly arguments in the signature.
|
||||
///
|
||||
/// Create the signature if necessary.
|
||||
pub fn get_indirect_sig<FE: FuncEnvironment + ?Sized>(
|
||||
&mut self,
|
||||
func: &mut ir::Function,
|
||||
index: u32,
|
||||
environ: &mut FE,
|
||||
) -> (ir::SigRef, usize) {
|
||||
let index = SignatureIndex::from_u32(index);
|
||||
*self.signatures.entry(index).or_insert_with(|| {
|
||||
let sig = environ.make_indirect_sig(func, index);
|
||||
(sig, normal_args(&func.dfg.signatures[sig]))
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the `FuncRef` reference that should be used to make a direct call to function
|
||||
/// `index`. Also return the number of WebAssembly arguments in the signature.
|
||||
///
|
||||
/// Create the function reference if necessary.
|
||||
pub fn get_direct_func<FE: FuncEnvironment + ?Sized>(
|
||||
&mut self,
|
||||
func: &mut ir::Function,
|
||||
index: u32,
|
||||
environ: &mut FE,
|
||||
) -> (ir::FuncRef, usize) {
|
||||
let index = FuncIndex::from_u32(index);
|
||||
*self.functions.entry(index).or_insert_with(|| {
|
||||
let fref = environ.make_direct_func(func, index);
|
||||
let sig = func.dfg.ext_funcs[fref].signature;
|
||||
(fref, normal_args(&func.dfg.signatures[sig]))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Count the number of normal parameters in a signature.
|
||||
/// Exclude special-purpose parameters that represent runtime stuff and not WebAssembly arguments.
|
||||
fn normal_args(sig: &ir::Signature) -> usize {
|
||||
sig.params
|
||||
.iter()
|
||||
.filter(|arg| arg.purpose == ir::ArgumentPurpose::Normal)
|
||||
.count()
|
||||
}
|
||||
142
cranelift/wasm/src/translation_utils.rs
Normal file
142
cranelift/wasm/src/translation_utils.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
//! Helper functions and structures for the translation.
|
||||
use core::u32;
|
||||
use cranelift_codegen::entity::entity_impl;
|
||||
use cranelift_codegen::ir;
|
||||
use wasmparser;
|
||||
|
||||
/// Index type of a function (imported or defined) inside the WebAssembly module.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub struct FuncIndex(u32);
|
||||
entity_impl!(FuncIndex);
|
||||
|
||||
/// Index type of a defined function inside the WebAssembly module.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub struct DefinedFuncIndex(u32);
|
||||
entity_impl!(DefinedFuncIndex);
|
||||
|
||||
/// Index type of a defined table inside the WebAssembly module.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub struct DefinedTableIndex(u32);
|
||||
entity_impl!(DefinedTableIndex);
|
||||
|
||||
/// Index type of a defined memory inside the WebAssembly module.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub struct DefinedMemoryIndex(u32);
|
||||
entity_impl!(DefinedMemoryIndex);
|
||||
|
||||
/// Index type of a defined global inside the WebAssembly module.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub struct DefinedGlobalIndex(u32);
|
||||
entity_impl!(DefinedGlobalIndex);
|
||||
|
||||
/// Index type of a table (imported or defined) inside the WebAssembly module.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub struct TableIndex(u32);
|
||||
entity_impl!(TableIndex);
|
||||
|
||||
/// Index type of a global variable (imported or defined) inside the WebAssembly module.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub struct GlobalIndex(u32);
|
||||
entity_impl!(GlobalIndex);
|
||||
|
||||
/// Index type of a linear memory (imported or defined) inside the WebAssembly module.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub struct MemoryIndex(u32);
|
||||
entity_impl!(MemoryIndex);
|
||||
|
||||
/// Index type of a signature (imported or defined) inside the WebAssembly module.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub struct SignatureIndex(u32);
|
||||
entity_impl!(SignatureIndex);
|
||||
|
||||
/// WebAssembly global.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Global {
|
||||
/// The type of the value stored in the global.
|
||||
pub ty: ir::Type,
|
||||
/// A flag indicating whether the value may change at runtime.
|
||||
pub mutability: bool,
|
||||
/// The source of the initial value.
|
||||
pub initializer: GlobalInit,
|
||||
}
|
||||
|
||||
/// Globals are initialized via the four `const` operators or by referring to another import.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum GlobalInit {
|
||||
/// An `i32.const`.
|
||||
I32Const(i32),
|
||||
/// An `i64.const`.
|
||||
I64Const(i64),
|
||||
/// An `f32.const`.
|
||||
F32Const(u32),
|
||||
/// An `f64.const`.
|
||||
F64Const(u64),
|
||||
/// A `get_global` of another global.
|
||||
GetGlobal(GlobalIndex),
|
||||
///< The global is imported from, and thus initialized by, a different module.
|
||||
Import,
|
||||
}
|
||||
|
||||
/// WebAssembly table.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Table {
|
||||
/// The type of data stored in elements of the table.
|
||||
pub ty: TableElementType,
|
||||
/// The minimum number of elements in the table.
|
||||
pub minimum: u32,
|
||||
/// The maximum number of elements in the table.
|
||||
pub maximum: Option<u32>,
|
||||
}
|
||||
|
||||
/// WebAssembly table element. Can be a function or a scalar type.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum TableElementType {
|
||||
/// A scalar type.
|
||||
Val(ir::Type),
|
||||
/// A function.
|
||||
Func,
|
||||
}
|
||||
|
||||
/// WebAssembly linear memory.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Memory {
|
||||
/// The minimum number of pages in the memory.
|
||||
pub minimum: u32,
|
||||
/// The maximum number of pages in the memory.
|
||||
pub maximum: Option<u32>,
|
||||
/// Whether the memory may be shared between multiple threads.
|
||||
pub shared: bool,
|
||||
}
|
||||
|
||||
/// Helper function translating wasmparser types to Cranelift types when possible.
|
||||
pub fn type_to_type(ty: wasmparser::Type) -> Result<ir::Type, ()> {
|
||||
Ok(match ty {
|
||||
wasmparser::Type::I32 => ir::types::I32,
|
||||
wasmparser::Type::I64 => ir::types::I64,
|
||||
wasmparser::Type::F32 => ir::types::F32,
|
||||
wasmparser::Type::F64 => ir::types::F64,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Turns a `wasmparser` `f32` into a `Cranelift` one.
|
||||
pub fn f32_translation(x: wasmparser::Ieee32) -> ir::immediates::Ieee32 {
|
||||
ir::immediates::Ieee32::with_bits(x.bits())
|
||||
}
|
||||
|
||||
/// Turns a `wasmparser` `f64` into a `Cranelift` one.
|
||||
pub fn f64_translation(x: wasmparser::Ieee64) -> ir::immediates::Ieee64 {
|
||||
ir::immediates::Ieee64::with_bits(x.bits())
|
||||
}
|
||||
|
||||
/// Translate a `wasmparser` type into its `Cranelift` equivalent, when possible
|
||||
pub fn num_return_values(ty: wasmparser::Type) -> usize {
|
||||
match ty {
|
||||
wasmparser::Type::EmptyBlockType => 0,
|
||||
wasmparser::Type::I32
|
||||
| wasmparser::Type::F32
|
||||
| wasmparser::Type::I64
|
||||
| wasmparser::Type::F64 => 1,
|
||||
_ => panic!("unsupported return value type"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user