These instructions have fast, inline JIT paths for the common cases, and only
call out to host VM functions for the slow paths. This required some changes to
`cranelift-wasm`'s `FuncEnvironment`: instead of taking a `FuncCursor` to insert
an instruction sequence within the current basic block,
`FuncEnvironment::translate_table_{get,set}` now take a `&mut FunctionBuilder`
so that they can create whole new basic blocks. This is necessary for
implementing GC read/write barriers that involve branching (e.g. checking for
null, or whether a store buffer is at capacity).
Furthermore, it required that the `load`, `load_complex`, and `store`
instructions handle loading and storing through an `r{32,64}` rather than just
`i{32,64}` addresses. This involved making `r{32,64}` types acceptable
instantiations of the `iAddr` type variable, plus a few new instruction
encodings.
Part of #929
471 lines
16 KiB
Rust
471 lines
16 KiB
Rust
//! Helper functions to gather information for each of the non-function sections of a
|
|
//! WebAssembly module.
|
|
//!
|
|
//! The code of these helper functions is straightforward since they only read metadata
|
|
//! about linear memories, tables, globals, etc. and store 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, WasmError, WasmResult};
|
|
use crate::state::ModuleTranslationState;
|
|
use crate::translation_utils::{
|
|
tabletype_to_type, type_to_type, DataIndex, ElemIndex, FuncIndex, Global, GlobalIndex,
|
|
GlobalInit, Memory, MemoryIndex, SignatureIndex, Table, TableElementType, TableIndex,
|
|
};
|
|
use crate::{wasm_unsupported, HashMap};
|
|
use core::convert::TryFrom;
|
|
use cranelift_codegen::ir::immediates::V128Imm;
|
|
use cranelift_codegen::ir::{self, AbiParam, Signature};
|
|
use cranelift_entity::packed_option::ReservedValue;
|
|
use cranelift_entity::EntityRef;
|
|
use std::boxed::Box;
|
|
use std::vec::Vec;
|
|
use wasmparser::{
|
|
self, CodeSectionReader, Data, DataKind, DataSectionReader, Element, ElementItem, ElementItems,
|
|
ElementKind, ElementSectionReader, Export, ExportSectionReader, ExternalKind,
|
|
FunctionSectionReader, GlobalSectionReader, GlobalType, ImportSectionEntryType,
|
|
ImportSectionReader, MemorySectionReader, MemoryType, NameSectionReader, Naming, NamingReader,
|
|
Operator, TableSectionReader, Type, TypeDef, TypeSectionReader,
|
|
};
|
|
|
|
/// Parses the Type section of the wasm module.
|
|
pub fn parse_type_section(
|
|
types: TypeSectionReader,
|
|
module_translation_state: &mut ModuleTranslationState,
|
|
environ: &mut dyn ModuleEnvironment,
|
|
) -> WasmResult<()> {
|
|
let count = types.get_count();
|
|
module_translation_state.wasm_types.reserve(count as usize);
|
|
environ.reserve_signatures(count)?;
|
|
|
|
for entry in types {
|
|
if let Ok(TypeDef::Func(wasm_func_ty)) = entry {
|
|
let mut sig =
|
|
Signature::new(ModuleEnvironment::target_config(environ).default_call_conv);
|
|
sig.params.extend(wasm_func_ty.params.iter().map(|ty| {
|
|
let cret_arg: ir::Type = type_to_type(*ty, environ)
|
|
.expect("only numeric types are supported in function signatures");
|
|
AbiParam::new(cret_arg)
|
|
}));
|
|
sig.returns.extend(wasm_func_ty.returns.iter().map(|ty| {
|
|
let cret_arg: ir::Type = type_to_type(*ty, environ)
|
|
.expect("only numeric types are supported in function signatures");
|
|
AbiParam::new(cret_arg)
|
|
}));
|
|
environ.declare_signature(&wasm_func_ty, sig)?;
|
|
module_translation_state
|
|
.wasm_types
|
|
.push((wasm_func_ty.params, wasm_func_ty.returns));
|
|
} else {
|
|
unimplemented!("module linking not implemented yet")
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Parses the Import section of the wasm module.
|
|
pub fn parse_import_section<'data>(
|
|
imports: ImportSectionReader<'data>,
|
|
environ: &mut dyn ModuleEnvironment<'data>,
|
|
) -> WasmResult<()> {
|
|
environ.reserve_imports(imports.get_count())?;
|
|
|
|
for entry in imports {
|
|
let import = entry?;
|
|
let module_name = import.module;
|
|
let field_name = import.field.unwrap(); // TODO Handle error when module linking is implemented.
|
|
|
|
match import.ty {
|
|
ImportSectionEntryType::Function(sig) => {
|
|
environ.declare_func_import(
|
|
SignatureIndex::from_u32(sig),
|
|
module_name,
|
|
field_name,
|
|
)?;
|
|
}
|
|
ImportSectionEntryType::Module(_sig) | ImportSectionEntryType::Instance(_sig) => {
|
|
unimplemented!("module linking not implemented yet")
|
|
}
|
|
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 {
|
|
wasm_ty: ty.content_type,
|
|
ty: type_to_type(ty.content_type, environ).unwrap(),
|
|
mutability: ty.mutable,
|
|
initializer: GlobalInit::Import,
|
|
},
|
|
module_name,
|
|
field_name,
|
|
)?;
|
|
}
|
|
ImportSectionEntryType::Table(ref tab) => {
|
|
environ.declare_table_import(
|
|
Table {
|
|
wasm_ty: tab.element_type,
|
|
ty: match tabletype_to_type(tab.element_type, environ)? {
|
|
Some(t) => TableElementType::Val(t),
|
|
None => 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 dyn ModuleEnvironment,
|
|
) -> WasmResult<()> {
|
|
let num_functions = functions.get_count();
|
|
if num_functions == std::u32::MAX {
|
|
// We reserve `u32::MAX` for our own use in cranelift-entity.
|
|
return Err(WasmError::ImplLimitExceeded);
|
|
}
|
|
|
|
environ.reserve_func_types(num_functions)?;
|
|
|
|
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 dyn ModuleEnvironment,
|
|
) -> WasmResult<()> {
|
|
environ.reserve_tables(tables.get_count())?;
|
|
|
|
for entry in tables {
|
|
let table = entry?;
|
|
environ.declare_table(Table {
|
|
wasm_ty: table.element_type,
|
|
ty: match tabletype_to_type(table.element_type, environ)? {
|
|
Some(t) => TableElementType::Val(t),
|
|
None => 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 dyn 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 dyn 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::V128Const { value } => {
|
|
GlobalInit::V128Const(V128Imm::from(value.bytes().to_vec().as_slice()))
|
|
}
|
|
Operator::RefNull { ty: _ } => GlobalInit::RefNullConst,
|
|
Operator::RefFunc { function_index } => {
|
|
GlobalInit::RefFunc(FuncIndex::from_u32(function_index))
|
|
}
|
|
Operator::GlobalGet { global_index } => {
|
|
GlobalInit::GetGlobal(GlobalIndex::from_u32(global_index))
|
|
}
|
|
ref s => {
|
|
return Err(wasm_unsupported!(
|
|
"unsupported init expr in global section: {:?}",
|
|
s
|
|
));
|
|
}
|
|
};
|
|
let global = Global {
|
|
wasm_ty: content_type,
|
|
ty: type_to_type(content_type, environ).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 dyn 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 index = index as usize;
|
|
match *kind {
|
|
ExternalKind::Function => environ.declare_func_export(FuncIndex::new(index), field)?,
|
|
ExternalKind::Table => environ.declare_table_export(TableIndex::new(index), field)?,
|
|
ExternalKind::Memory => {
|
|
environ.declare_memory_export(MemoryIndex::new(index), field)?
|
|
}
|
|
ExternalKind::Global => {
|
|
environ.declare_global_export(GlobalIndex::new(index), field)?
|
|
}
|
|
ExternalKind::Type | ExternalKind::Module | ExternalKind::Instance => {
|
|
unimplemented!("module linking not implemented yet")
|
|
}
|
|
}
|
|
}
|
|
|
|
environ.finish_exports()?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Parses the Start section of the wasm module.
|
|
pub fn parse_start_section(index: u32, environ: &mut dyn ModuleEnvironment) -> WasmResult<()> {
|
|
environ.declare_start_func(FuncIndex::from_u32(index))?;
|
|
Ok(())
|
|
}
|
|
|
|
fn read_elems(items: &ElementItems) -> WasmResult<Box<[FuncIndex]>> {
|
|
let items_reader = items.get_items_reader()?;
|
|
let mut elems = Vec::with_capacity(usize::try_from(items_reader.get_count()).unwrap());
|
|
for item in items_reader {
|
|
let elem = match item? {
|
|
ElementItem::Null(_ty) => FuncIndex::reserved_value(),
|
|
ElementItem::Func(index) => FuncIndex::from_u32(index),
|
|
};
|
|
elems.push(elem);
|
|
}
|
|
Ok(elems.into_boxed_slice())
|
|
}
|
|
|
|
/// Parses the Element section of the wasm module.
|
|
pub fn parse_element_section<'data>(
|
|
elements: ElementSectionReader<'data>,
|
|
environ: &mut dyn ModuleEnvironment,
|
|
) -> WasmResult<()> {
|
|
environ.reserve_table_elements(elements.get_count())?;
|
|
|
|
for (index, entry) in elements.into_iter().enumerate() {
|
|
let Element { kind, items, ty } = entry?;
|
|
if ty != Type::FuncRef {
|
|
return Err(wasm_unsupported!(
|
|
"unsupported table element type: {:?}",
|
|
ty
|
|
));
|
|
}
|
|
let segments = read_elems(&items)?;
|
|
match kind {
|
|
ElementKind::Active {
|
|
table_index,
|
|
init_expr,
|
|
} => {
|
|
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::GlobalGet { global_index } => {
|
|
(Some(GlobalIndex::from_u32(global_index)), 0)
|
|
}
|
|
ref s => {
|
|
return Err(wasm_unsupported!(
|
|
"unsupported init expr in element section: {:?}",
|
|
s
|
|
));
|
|
}
|
|
};
|
|
environ.declare_table_elements(
|
|
TableIndex::from_u32(table_index),
|
|
base,
|
|
offset,
|
|
segments,
|
|
)?
|
|
}
|
|
ElementKind::Passive => {
|
|
let index = ElemIndex::from_u32(index as u32);
|
|
environ.declare_passive_element(index, segments)?;
|
|
}
|
|
ElementKind::Declared => {
|
|
// Nothing to do here.
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Parses the Code section of the wasm module.
|
|
pub fn parse_code_section<'data>(
|
|
code: CodeSectionReader<'data>,
|
|
module_translation_state: &ModuleTranslationState,
|
|
environ: &mut dyn ModuleEnvironment<'data>,
|
|
) -> WasmResult<()> {
|
|
for body in code {
|
|
let mut reader = body?.get_binary_reader();
|
|
let size = reader.bytes_remaining();
|
|
let offset = reader.original_position();
|
|
environ.define_function_body(module_translation_state, reader.read_bytes(size)?, offset)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Parses the Data section of the wasm module.
|
|
pub fn parse_data_section<'data>(
|
|
data: DataSectionReader<'data>,
|
|
environ: &mut dyn ModuleEnvironment<'data>,
|
|
) -> WasmResult<()> {
|
|
environ.reserve_data_initializers(data.get_count())?;
|
|
|
|
for (index, entry) in data.into_iter().enumerate() {
|
|
let Data { kind, data } = entry?;
|
|
match kind {
|
|
DataKind::Active {
|
|
memory_index,
|
|
init_expr,
|
|
} => {
|
|
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::GlobalGet { global_index } => {
|
|
(Some(GlobalIndex::from_u32(global_index)), 0)
|
|
}
|
|
ref s => {
|
|
return Err(wasm_unsupported!(
|
|
"unsupported init expr in data section: {:?}",
|
|
s
|
|
))
|
|
}
|
|
};
|
|
environ.declare_data_initialization(
|
|
MemoryIndex::from_u32(memory_index),
|
|
base,
|
|
offset,
|
|
data,
|
|
)?;
|
|
}
|
|
DataKind::Passive => {
|
|
let index = DataIndex::from_u32(index as u32);
|
|
environ.declare_passive_data(index, data)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Parses the Name section of the wasm module.
|
|
pub fn parse_name_section<'data>(
|
|
mut names: NameSectionReader<'data>,
|
|
environ: &mut dyn ModuleEnvironment<'data>,
|
|
) -> WasmResult<()> {
|
|
while let Ok(subsection) = names.read() {
|
|
match subsection {
|
|
wasmparser::Name::Function(function_subsection) => {
|
|
if let Some(function_names) = function_subsection
|
|
.get_map()
|
|
.ok()
|
|
.and_then(parse_function_name_subsection)
|
|
{
|
|
for (index, name) in function_names {
|
|
environ.declare_func_name(index, name)?;
|
|
}
|
|
}
|
|
}
|
|
wasmparser::Name::Module(module) => {
|
|
if let Ok(name) = module.get_name() {
|
|
environ.declare_module_name(name)?;
|
|
}
|
|
}
|
|
wasmparser::Name::Local(_) => {}
|
|
};
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn parse_function_name_subsection(
|
|
mut naming_reader: NamingReader<'_>,
|
|
) -> Option<HashMap<FuncIndex, &str>> {
|
|
let mut function_names = HashMap::new();
|
|
for _ in 0..naming_reader.get_count() {
|
|
let Naming { index, name } = naming_reader.read().ok()?;
|
|
if index == std::u32::MAX {
|
|
// We reserve `u32::MAX` for our own use in cranelift-entity.
|
|
return None;
|
|
}
|
|
|
|
if function_names
|
|
.insert(FuncIndex::from_u32(index), name)
|
|
.is_some()
|
|
{
|
|
// If the function index has been previously seen, then we
|
|
// break out of the loop and early return `None`, because these
|
|
// should be unique.
|
|
return None;
|
|
}
|
|
}
|
|
Some(function_names)
|
|
}
|