Initial skeleton of some component model processing (#4005)
* Initial skeleton of some component model processing This commit is the first of what will likely be many to implement the component model proposal in Wasmtime. This will be structured as a series of incremental commits, most of which haven't been written yet. My hope is to make this incremental and over time to make this easier to review and easier to test each step in isolation. Here much of the skeleton of how components are going to work in Wasmtime is sketched out. This is not a complete implementation of the component model so it's not all that useful yet, but some things you can do are: * Process the type section into a representation amenable for working with in Wasmtime. * Process the module section and register core wasm modules. * Process the instance section for core wasm modules. * Process core wasm module imports. * Process core wasm instance aliasing. * Ability to compile a component with core wasm embedded. * Ability to instantiate a component with no imports. * Ability to get functions from this component. This is already starting to diverge from the previous module linking representation where a `Component` will try to avoid unnecessary metadata about the component and instead internally only have the bare minimum necessary to instantiate the module. My hope is we can avoid constructing most of the index spaces during instantiation only for it to all ge thrown away. Additionally I'm predicting that we'll need to see through processing where possible to know how to generate adapters and where they are fused. At this time you can't actually call a component's functions, and that's the next PR that I would like to make. * Add tests for the component model support This commit uses the recently updated wasm-tools crates to add tests for the component model added in the previous commit. This involved updating the `wasmtime-wast` crate for component-model changes. Currently the component support there is quite primitive, but enough to at least instantiate components and verify the internals of Wasmtime are all working correctly. Additionally some simple tests for the embedding API have also been added.
This commit is contained in:
174
crates/environ/src/component/info.rs
Normal file
174
crates/environ/src/component/info.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
// General runtime type-information about a component.
|
||||
//
|
||||
// ## Optimizing instantiation
|
||||
//
|
||||
// One major consideration for the structure of the types in this module is to
|
||||
// make instantiation as fast as possible. To facilitate this the representation
|
||||
// here avoids the need to create a `PrimaryMap` during instantiation of a
|
||||
// component for each index space like the func, global, table, etc, index
|
||||
// spaces. Instead a component is simply defined by a list of instantiation
|
||||
// instructions, and arguments to the instantiation of each instance are a list
|
||||
// of "pointers" into previously created instances. This means that we only need
|
||||
// to build up one list of instances during instantiation.
|
||||
//
|
||||
// Additionally we also try to avoid string lookups wherever possible. In the
|
||||
// component model instantiation and aliasing theoretically deals with lots of
|
||||
// string lookups here and there. This is slower than indexing lookup, though,
|
||||
// and not actually necessary when the structure of a module is statically
|
||||
// known. This means that `ExportItem` below has two variants where we try to
|
||||
// use the indexing variant as much as possible, which can be done for
|
||||
// everything except imported core wasm modules.
|
||||
|
||||
use crate::component::*;
|
||||
use crate::{EntityIndex, PrimaryMap};
|
||||
use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Run-time-type-information about a `Component`, its structure, and how to
|
||||
/// instantiate it.
|
||||
///
|
||||
/// This type is intended to mirror the `Module` type in this crate which
|
||||
/// provides all the runtime information about the structure of a module and
|
||||
/// how it works.
|
||||
///
|
||||
/// NB: Lots of the component model is not yet implemented in the runtime so
|
||||
/// this is going to undergo a lot of churn.
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct Component {
|
||||
/// A list of typed values that this component imports, indexed by either
|
||||
/// the import's position or the name of the import.
|
||||
pub imports: IndexMap<String, TypeDef>,
|
||||
|
||||
/// A list of this component's exports, indexed by either position or name.
|
||||
pub exports: IndexMap<String, Export>,
|
||||
|
||||
/// The list of instances that this component creates during instantiation.
|
||||
///
|
||||
/// Note that this is flattened/resolved from the original component to
|
||||
/// the point where alias annotations and such are not required. Instead
|
||||
/// the list of arguments to instantiate each module is provided as exports
|
||||
/// of prior instantiations.
|
||||
pub instances: PrimaryMap<RuntimeInstanceIndex, Instantiation>,
|
||||
}
|
||||
|
||||
/// Different ways to instantiate a module at runtime.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum Instantiation {
|
||||
/// A module "upvar" is being instantiated which is a closed-over module
|
||||
/// that is known at runtime by index.
|
||||
ModuleUpvar {
|
||||
/// The module index which is being instantiated.
|
||||
module: ModuleUpvarIndex,
|
||||
/// The flat list of arguments to the module's instantiation.
|
||||
args: Box<[CoreExport<EntityIndex>]>,
|
||||
},
|
||||
|
||||
/// A module import is being instantiated.
|
||||
///
|
||||
/// NB: this is not implemented in the runtime yet so this is a little less
|
||||
/// fleshed out than the above case. For example it's not entirely clear how
|
||||
/// the import will be referred to here (here a `usize` is used but unsure
|
||||
/// if that will work out).
|
||||
ModuleImport {
|
||||
/// Which module import is being instantiated.
|
||||
import_index: usize,
|
||||
/// The flat list of arguments to the module's instantiation.
|
||||
args: Box<[CoreExport<EntityIndex>]>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Identifier of an exported item from a core WebAssembly module instance.
|
||||
///
|
||||
/// Note that the `T` here is the index type for exports which can be
|
||||
/// identified by index. The `T` is monomorphized with types like
|
||||
/// [`EntityIndex`] or [`FuncIndex`].
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CoreExport<T> {
|
||||
/// The instance that this item is located within.
|
||||
///
|
||||
/// Note that this is intended to index the `instances` map within a
|
||||
/// component. It's validated ahead of time that all instance pointers
|
||||
/// refer only to previously-created instances.
|
||||
pub instance: RuntimeInstanceIndex,
|
||||
|
||||
/// The item that this export is referencing, either by name or by index.
|
||||
pub item: ExportItem<T>,
|
||||
}
|
||||
|
||||
/// An index at which to find an item within a runtime instance.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ExportItem<T> {
|
||||
/// An exact index that the target can be found at.
|
||||
///
|
||||
/// This is used where possible to avoid name lookups at runtime during the
|
||||
/// instantiation process. This can only be used on instances where the
|
||||
/// module was statically known at compile time, however.
|
||||
Index(T),
|
||||
|
||||
/// An item which is identified by a name, so at runtime we need to
|
||||
/// perform a name lookup to determine the index that the item is located
|
||||
/// at.
|
||||
///
|
||||
/// This is used for instantiations of imported modules, for example, since
|
||||
/// the precise shape of the module is not known.
|
||||
Name(String),
|
||||
}
|
||||
|
||||
/// Possible exports from a component.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Export {
|
||||
/// A lifted function being exported which is an adaptation of a core wasm
|
||||
/// function.
|
||||
LiftedFunction(LiftedFunction),
|
||||
}
|
||||
|
||||
/// Description of a lifted function.
|
||||
///
|
||||
/// This represents how a function was lifted, what options were used to lift
|
||||
/// it, and how it's all processed at runtime.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LiftedFunction {
|
||||
/// The component function type of the function being created.
|
||||
pub ty: FuncTypeIndex,
|
||||
/// Which core WebAssembly export is being lifted.
|
||||
pub func: CoreExport<FuncIndex>,
|
||||
/// Any options, if present, associated with this lifting.
|
||||
pub options: CanonicalOptions,
|
||||
}
|
||||
|
||||
/// Canonical ABI options associated with a lifted function.
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CanonicalOptions {
|
||||
/// The optionally-specified encoding used for strings.
|
||||
pub string_encoding: Option<StringEncoding>,
|
||||
/// Representation of the `into` option where intrinsics are peeled out and
|
||||
/// identified from an instance.
|
||||
pub intrinsics: Option<Intrinsics>,
|
||||
}
|
||||
|
||||
/// Possible encodings of strings within the component model.
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum StringEncoding {
|
||||
Utf8,
|
||||
Utf16,
|
||||
CompactUtf16,
|
||||
}
|
||||
|
||||
/// Intrinsics required with the `(into $instance)` option specified in
|
||||
/// `canon.lift`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Intrinsics {
|
||||
/// The linear memory that the module exports which we're reading/writing
|
||||
/// from.
|
||||
pub memory: CoreExport<MemoryIndex>,
|
||||
|
||||
/// A memory allocation, and reallocation, function.
|
||||
pub canonical_abi_realloc: CoreExport<FuncIndex>,
|
||||
|
||||
/// A memory deallocation function.
|
||||
///
|
||||
/// NB: this will probably be replaced with a per-export-destructor rather
|
||||
/// than a general memory deallocation function.
|
||||
pub canonical_abi_free: CoreExport<FuncIndex>,
|
||||
}
|
||||
709
crates/environ/src/component/translate.rs
Normal file
709
crates/environ/src/component/translate.rs
Normal file
@@ -0,0 +1,709 @@
|
||||
use crate::component::*;
|
||||
use crate::{EntityIndex, ModuleEnvironment, ModuleTranslation, PrimaryMap, Tunables};
|
||||
use anyhow::{bail, Result};
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use wasmparser::{Chunk, Encoding, Parser, Payload, Validator};
|
||||
|
||||
/// Structure used to translate a component and parse it.
|
||||
pub struct Translator<'a, 'data> {
|
||||
result: Translation<'data>,
|
||||
validator: &'a mut Validator,
|
||||
types: &'a mut ComponentTypesBuilder,
|
||||
tunables: &'a Tunables,
|
||||
parsers: Vec<Parser>,
|
||||
parser: Parser,
|
||||
}
|
||||
|
||||
/// Result of translation of a component to contain all type information and
|
||||
/// metadata about how to run the component.
|
||||
#[derive(Default)]
|
||||
pub struct Translation<'data> {
|
||||
/// Final type of the component, intended to be persisted all the way to
|
||||
/// runtime.
|
||||
pub component: Component,
|
||||
|
||||
/// List of "upvars" or closed over modules that `Component` would refer
|
||||
/// to. This contains the core wasm results of translation and the indices
|
||||
/// are referred to within types in `Component`.
|
||||
pub upvars: PrimaryMap<ModuleUpvarIndex, ModuleTranslation<'data>>,
|
||||
|
||||
// Index spaces which are built-up during translation but do not persist to
|
||||
// runtime. These are used to understand the structure of the component and
|
||||
// where items come from but at this time these index spaces and their
|
||||
// definitions are not required at runtime as they're effectively "erased"
|
||||
// at the moment. This will probably change as more of the component model
|
||||
// is implemented.
|
||||
//
|
||||
/// Modules and how they're defined (either closed-over or imported)
|
||||
modules: PrimaryMap<ModuleIndex, ModuleDef>,
|
||||
|
||||
/// Instances and how they're defined, either as instantiations of modules
|
||||
/// or "synthetically created" as a bag of named items from our other index
|
||||
/// spaces.
|
||||
instances: PrimaryMap<InstanceIndex, InstanceDef<'data>>,
|
||||
|
||||
/// Both core wasm and component functions, and how they're defined.
|
||||
funcs: PrimaryMap<FuncIndex, Func<'data>>,
|
||||
|
||||
/// Core wasm globals, always sourced from a previously module instance.
|
||||
globals: PrimaryMap<GlobalIndex, CoreSource<'data>>,
|
||||
|
||||
/// Core wasm memories, always sourced from a previously module instance.
|
||||
memories: PrimaryMap<MemoryIndex, CoreSource<'data>>,
|
||||
|
||||
/// Core wasm tables, always sourced from a previously module instance.
|
||||
tables: PrimaryMap<TableIndex, CoreSource<'data>>,
|
||||
}
|
||||
|
||||
/// How a module is defined within a component.
|
||||
#[derive(Debug)]
|
||||
enum ModuleDef {
|
||||
/// This module is defined as an "upvar" or a closed over variable
|
||||
/// implicitly available for the component.
|
||||
///
|
||||
/// This means that the module was either defined within the component or a
|
||||
/// module was aliased into this component which was known defined in the
|
||||
/// parent component.
|
||||
Upvar(ModuleUpvarIndex),
|
||||
|
||||
/// This module is defined as an import to the current component, so
|
||||
/// nothing is known about it except for its type. The `import_index`
|
||||
/// provided here indexes into the `Component`'s import list.
|
||||
Import {
|
||||
type_idx: ModuleTypeIndex,
|
||||
import_index: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InstanceDef<'data> {
|
||||
Module {
|
||||
instance: RuntimeInstanceIndex,
|
||||
module: ModuleIndex,
|
||||
},
|
||||
ModuleSynthetic(HashMap<&'data str, EntityIndex>),
|
||||
}
|
||||
|
||||
/// Source of truth for where a core wasm item comes from.
|
||||
#[derive(Clone)]
|
||||
enum Func<'data> {
|
||||
Lifted {
|
||||
ty: FuncTypeIndex,
|
||||
func: CoreSource<'data>,
|
||||
options: CanonicalOptions,
|
||||
},
|
||||
Core(CoreSource<'data>),
|
||||
}
|
||||
|
||||
/// Source of truth for where a core wasm item comes from.
|
||||
#[derive(Clone)]
|
||||
enum CoreSource<'data> {
|
||||
/// This item comes from an indexed entity within an instance.
|
||||
///
|
||||
/// This is only available when the instance is statically known to be
|
||||
/// defined within the original component itself so we know the exact
|
||||
/// index.
|
||||
Index(RuntimeInstanceIndex, EntityIndex),
|
||||
|
||||
/// This item comes from an named entity within an instance.
|
||||
///
|
||||
/// This must be used for instances of imported modules because we
|
||||
/// otherwise don't know the internal structure of the module and which
|
||||
/// index is being exported.
|
||||
Export(RuntimeInstanceIndex, &'data str),
|
||||
}
|
||||
|
||||
enum Action {
|
||||
KeepGoing,
|
||||
Skip(usize),
|
||||
Done,
|
||||
}
|
||||
|
||||
impl<'a, 'data> Translator<'a, 'data> {
|
||||
/// Creates a new translation state ready to translate a component.
|
||||
pub fn new(
|
||||
tunables: &'a Tunables,
|
||||
validator: &'a mut Validator,
|
||||
types: &'a mut ComponentTypesBuilder,
|
||||
) -> Self {
|
||||
Self {
|
||||
result: Translation::default(),
|
||||
tunables,
|
||||
validator,
|
||||
types,
|
||||
parser: Parser::new(0),
|
||||
parsers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Translates the binary `component`.
|
||||
///
|
||||
/// This is the workhorse of compilation which will parse all of
|
||||
/// `component` and create type information for Wasmtime and such. The
|
||||
/// `component` does not have to be valid and it will be validated during
|
||||
/// compilation.
|
||||
pub fn translate(mut self, component: &'data [u8]) -> Result<Translation<'data>> {
|
||||
let mut remaining = component;
|
||||
loop {
|
||||
let payload = match self.parser.parse(remaining, true)? {
|
||||
Chunk::Parsed { payload, consumed } => {
|
||||
remaining = &remaining[consumed..];
|
||||
payload
|
||||
}
|
||||
Chunk::NeedMoreData(_) => unreachable!(),
|
||||
};
|
||||
|
||||
match self.translate_payload(payload, component)? {
|
||||
Action::KeepGoing => {}
|
||||
Action::Skip(n) => remaining = &remaining[n..],
|
||||
Action::Done => break,
|
||||
}
|
||||
}
|
||||
Ok(self.result)
|
||||
}
|
||||
|
||||
fn translate_payload(
|
||||
&mut self,
|
||||
payload: Payload<'data>,
|
||||
component: &'data [u8],
|
||||
) -> Result<Action> {
|
||||
match payload {
|
||||
Payload::Version {
|
||||
num,
|
||||
encoding,
|
||||
range,
|
||||
} => {
|
||||
self.validator.version(num, encoding, &range)?;
|
||||
|
||||
match encoding {
|
||||
Encoding::Component => {}
|
||||
Encoding::Module => {
|
||||
bail!("attempted to parse a wasm module with a component parser");
|
||||
}
|
||||
}
|
||||
|
||||
// Push a new scope for component types so outer aliases know
|
||||
// that the 0th level is this new component.
|
||||
self.types.push_component_types_scope();
|
||||
}
|
||||
|
||||
Payload::End(offset) => {
|
||||
self.validator.end(offset)?;
|
||||
|
||||
// When leaving a module be sure to pop the types scope to
|
||||
// ensure that when we go back to the previous module outer
|
||||
// type alias indices work correctly again.
|
||||
self.types.pop_component_types_scope();
|
||||
|
||||
match self.parsers.pop() {
|
||||
Some(p) => self.parser = p,
|
||||
None => return Ok(Action::Done),
|
||||
}
|
||||
}
|
||||
|
||||
// When we see a type section the types are validated and then
|
||||
// translated into Wasmtime's representation. Each active type
|
||||
// definition is recorded in the `ComponentTypesBuilder` tables, or
|
||||
// this component's active scope.
|
||||
//
|
||||
// Note that the push/pop of the component types scope happens above
|
||||
// in `Version` and `End` since multiple type sections can appear
|
||||
// within a component.
|
||||
Payload::ComponentTypeSection(s) => {
|
||||
self.validator.component_type_section(&s)?;
|
||||
for ty in s {
|
||||
let ty = self.types.component_type_def(&ty?)?;
|
||||
self.types.push_component_typedef(ty);
|
||||
}
|
||||
}
|
||||
|
||||
Payload::ComponentImportSection(s) => {
|
||||
self.validator.component_import_section(&s)?;
|
||||
for import in s {
|
||||
let import = import?;
|
||||
let ty = TypeIndex::from_u32(import.ty);
|
||||
let ty = self.types.component_outer_type(0, ty);
|
||||
let (import_index, prev) = self
|
||||
.result
|
||||
.component
|
||||
.imports
|
||||
.insert_full(import.name.to_string(), ty);
|
||||
assert!(prev.is_none());
|
||||
match ty {
|
||||
TypeDef::Module(type_idx) => {
|
||||
self.result.modules.push(ModuleDef::Import {
|
||||
type_idx,
|
||||
import_index,
|
||||
});
|
||||
}
|
||||
TypeDef::Component(_) => {
|
||||
unimplemented!("component imports");
|
||||
}
|
||||
TypeDef::ComponentInstance(_) => {
|
||||
unimplemented!("component instance imports");
|
||||
}
|
||||
TypeDef::Func(_) => {
|
||||
unimplemented!("function imports");
|
||||
}
|
||||
TypeDef::Interface(_) => {
|
||||
unimplemented!("interface type imports");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Payload::ComponentFunctionSection(s) => {
|
||||
self.validator.component_function_section(&s)?;
|
||||
for func in s {
|
||||
let func = match func? {
|
||||
wasmparser::ComponentFunction::Lift {
|
||||
type_index,
|
||||
func_index,
|
||||
options,
|
||||
} => {
|
||||
let ty = TypeIndex::from_u32(type_index);
|
||||
let func = FuncIndex::from_u32(func_index);
|
||||
self.lift_function(ty, func, &options)
|
||||
}
|
||||
wasmparser::ComponentFunction::Lower {
|
||||
func_index,
|
||||
options,
|
||||
} => {
|
||||
drop((func_index, options));
|
||||
unimplemented!("lowered functions");
|
||||
}
|
||||
};
|
||||
self.result.funcs.push(func);
|
||||
}
|
||||
}
|
||||
|
||||
// Core wasm modules are translated inline directly here with the
|
||||
// `ModuleEnvironment` from core wasm compilation. This will return
|
||||
// to the caller the size of the module so it knows how many bytes
|
||||
// of the input are skipped.
|
||||
Payload::ModuleSection { parser, range } => {
|
||||
self.validator.module_section(&range)?;
|
||||
let translation = ModuleEnvironment::new(
|
||||
self.tunables,
|
||||
self.validator,
|
||||
self.types.module_types_builder(),
|
||||
)
|
||||
.translate(parser, &component[range.start..range.end])?;
|
||||
let upvar_idx = self.result.upvars.push(translation);
|
||||
self.result.modules.push(ModuleDef::Upvar(upvar_idx));
|
||||
return Ok(Action::Skip(range.end - range.start));
|
||||
}
|
||||
|
||||
Payload::ComponentSection { parser, range } => {
|
||||
self.validator.component_section(&range)?;
|
||||
let old_parser = mem::replace(&mut self.parser, parser);
|
||||
self.parsers.push(old_parser);
|
||||
unimplemented!("component section");
|
||||
}
|
||||
|
||||
Payload::InstanceSection(s) => {
|
||||
self.validator.instance_section(&s)?;
|
||||
for instance in s {
|
||||
let instance = match instance? {
|
||||
wasmparser::Instance::Module { index, args } => {
|
||||
self.module_instance(ModuleIndex::from_u32(index), &args)
|
||||
}
|
||||
wasmparser::Instance::ModuleFromExports(exports) => {
|
||||
self.module_instance_from_exports(&exports)
|
||||
}
|
||||
wasmparser::Instance::Component { index, args } => {
|
||||
drop((index, args));
|
||||
unimplemented!("instantiating a component");
|
||||
}
|
||||
wasmparser::Instance::ComponentFromExports(exports) => {
|
||||
drop(exports);
|
||||
unimplemented!("instantiating a component");
|
||||
}
|
||||
};
|
||||
self.result.instances.push(instance);
|
||||
}
|
||||
}
|
||||
|
||||
Payload::ComponentExportSection(s) => {
|
||||
self.validator.component_export_section(&s)?;
|
||||
for export in s {
|
||||
self.export(&export?);
|
||||
}
|
||||
}
|
||||
|
||||
Payload::ComponentStartSection(s) => {
|
||||
self.validator.component_start_section(&s)?;
|
||||
unimplemented!("component start section");
|
||||
}
|
||||
|
||||
Payload::AliasSection(s) => {
|
||||
self.validator.alias_section(&s)?;
|
||||
for alias in s {
|
||||
self.alias(&alias?);
|
||||
}
|
||||
}
|
||||
|
||||
// All custom sections are ignored by Wasmtime at this time.
|
||||
//
|
||||
// FIXME(WebAssembly/component-model#14): probably want to specify
|
||||
// and parse a `name` section here.
|
||||
Payload::CustomSection { .. } => {}
|
||||
|
||||
// Anything else is either not reachable since we never enable the
|
||||
// feature in Wasmtime or we do enable it and it's a bug we don't
|
||||
// implement it, so let validation take care of most errors here and
|
||||
// if it gets past validation provide a helpful error message to
|
||||
// debug.
|
||||
other => {
|
||||
self.validator.payload(&other)?;
|
||||
panic!("unimplemented section {other:?}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Action::KeepGoing)
|
||||
}
|
||||
|
||||
fn module_instance(
|
||||
&mut self,
|
||||
module: ModuleIndex,
|
||||
args: &[wasmparser::ModuleArg<'data>],
|
||||
) -> InstanceDef<'data> {
|
||||
// Map the flat list of `args` to instead a name-to-instance index.
|
||||
let mut instance_by_name = HashMap::new();
|
||||
for arg in args {
|
||||
match arg.kind {
|
||||
wasmparser::ModuleArgKind::Instance(idx) => {
|
||||
instance_by_name.insert(arg.name, InstanceIndex::from_u32(idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let instantiation = match self.result.modules[module] {
|
||||
// For modules which we are statically aware of we can look at the
|
||||
// exact order of imports required and build up a list of arguemnts
|
||||
// in that order. This will be fast at runtime because all we have
|
||||
// to do is build up the import lists for instantiation, no name
|
||||
// lookups necessary.
|
||||
ModuleDef::Upvar(upvar_idx) => {
|
||||
let trans = &self.result.upvars[upvar_idx];
|
||||
Instantiation::ModuleUpvar {
|
||||
module: upvar_idx,
|
||||
args: self.module_instance_args(
|
||||
&instance_by_name,
|
||||
trans.module.imports().map(|(m, n, _)| (m, n)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// For imported modules the list of arguments is built to match the
|
||||
// order of the imports listed. Note that this will need to be
|
||||
// reshuffled at runtime since the actual module being instantiated
|
||||
// may originally have required imports in a different order.
|
||||
ModuleDef::Import {
|
||||
type_idx,
|
||||
import_index,
|
||||
} => {
|
||||
let ty = &self.types[type_idx];
|
||||
Instantiation::ModuleImport {
|
||||
import_index,
|
||||
args: self.module_instance_args(
|
||||
&instance_by_name,
|
||||
ty.imports.keys().map(|(a, b)| (a.as_str(), b.as_str())),
|
||||
),
|
||||
}
|
||||
}
|
||||
};
|
||||
let instance = self.result.component.instances.push(instantiation);
|
||||
InstanceDef::Module { instance, module }
|
||||
}
|
||||
|
||||
/// Translates the named arguments required by a core wasm module specified
|
||||
/// by `iter` into a list of where the arguments come from at runtime.
|
||||
///
|
||||
/// The `instance_by_name` map is used go go from the module name of an
|
||||
/// import to the instance that's satisfying the import. The `name` field
|
||||
/// of the import is then looked up within the instance's own exports.
|
||||
fn module_instance_args<'b>(
|
||||
&self,
|
||||
instance_by_name: &HashMap<&'data str, InstanceIndex>,
|
||||
iter: impl Iterator<Item = (&'b str, &'b str)>,
|
||||
) -> Box<[CoreExport<EntityIndex>]> {
|
||||
iter.map(|(module, name)| {
|
||||
self.lookup_core_source(instance_by_name[module], name)
|
||||
.to_core_export(|i| i)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Looks up the `CoreSource` corresponding to the export `name` of the
|
||||
/// `module` specified.
|
||||
///
|
||||
/// This classifies the export of the module as either one which we
|
||||
/// statically know by index within the module itself (because we know the
|
||||
/// module), or one that must be referred to by name.
|
||||
fn lookup_core_source(&self, instance: InstanceIndex, name: &'data str) -> CoreSource<'data> {
|
||||
match &self.result.instances[instance] {
|
||||
// The `instance` points to an instantiated module...
|
||||
InstanceDef::Module { module, instance } => match self.result.modules[*module] {
|
||||
// ... and the module instantiated is one that we statically
|
||||
// know the structure of. This means that `name` points to an
|
||||
// exact index of an item within the module which we lookup here
|
||||
// and record.
|
||||
ModuleDef::Upvar(upvar_idx) => {
|
||||
let trans = &self.result.upvars[upvar_idx];
|
||||
CoreSource::Index(*instance, trans.module.exports[name])
|
||||
}
|
||||
|
||||
// ... and the module instantiated is imported so we don't
|
||||
// statically know its structure. This means taht the export
|
||||
// must be identified by name.
|
||||
ModuleDef::Import { .. } => CoreSource::Export(*instance, name),
|
||||
},
|
||||
|
||||
// The `instance `points to a "synthetic" instance created in the
|
||||
// component as a collection of named items from other instances.
|
||||
// This means that we're simply copying over the original source of
|
||||
// the item in the first place.
|
||||
InstanceDef::ModuleSynthetic(defs) => match defs[&name] {
|
||||
EntityIndex::Function(f) => match &self.result.funcs[f] {
|
||||
Func::Core(c) => c.clone(),
|
||||
// should not be possible to hit with a valid component
|
||||
Func::Lifted { .. } => unreachable!(),
|
||||
},
|
||||
EntityIndex::Global(g) => self.result.globals[g].clone(),
|
||||
EntityIndex::Table(t) => self.result.tables[t].clone(),
|
||||
EntityIndex::Memory(m) => self.result.memories[m].clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a synthetic module from the list of items currently in the
|
||||
/// module and their given names.
|
||||
fn module_instance_from_exports(
|
||||
&mut self,
|
||||
exports: &[wasmparser::Export<'data>],
|
||||
) -> InstanceDef<'data> {
|
||||
let mut map = HashMap::with_capacity(exports.len());
|
||||
for export in exports {
|
||||
let idx = match export.kind {
|
||||
wasmparser::ExternalKind::Func => {
|
||||
let index = FuncIndex::from_u32(export.index);
|
||||
EntityIndex::Function(index)
|
||||
}
|
||||
wasmparser::ExternalKind::Table => {
|
||||
let index = TableIndex::from_u32(export.index);
|
||||
EntityIndex::Table(index)
|
||||
}
|
||||
wasmparser::ExternalKind::Memory => {
|
||||
let index = MemoryIndex::from_u32(export.index);
|
||||
EntityIndex::Memory(index)
|
||||
}
|
||||
wasmparser::ExternalKind::Global => {
|
||||
let index = GlobalIndex::from_u32(export.index);
|
||||
EntityIndex::Global(index)
|
||||
}
|
||||
|
||||
// doesn't get past validation
|
||||
wasmparser::ExternalKind::Tag => unimplemented!(),
|
||||
};
|
||||
map.insert(export.name, idx);
|
||||
}
|
||||
InstanceDef::ModuleSynthetic(map)
|
||||
}
|
||||
|
||||
fn export(&mut self, export: &wasmparser::ComponentExport<'data>) {
|
||||
let name = export.name;
|
||||
let export = match export.kind {
|
||||
wasmparser::ComponentExportKind::Module(i) => {
|
||||
let idx = ModuleIndex::from_u32(i);
|
||||
drop(idx);
|
||||
unimplemented!("unimplemented module export");
|
||||
}
|
||||
wasmparser::ComponentExportKind::Component(i) => {
|
||||
let idx = ComponentIndex::from_u32(i);
|
||||
drop(idx);
|
||||
unimplemented!("unimplemented component export");
|
||||
}
|
||||
wasmparser::ComponentExportKind::Instance(i) => {
|
||||
let idx = InstanceIndex::from_u32(i);
|
||||
drop(idx);
|
||||
unimplemented!("unimplemented instance export");
|
||||
}
|
||||
wasmparser::ComponentExportKind::Function(i) => {
|
||||
let idx = FuncIndex::from_u32(i);
|
||||
match &self.result.funcs[idx] {
|
||||
Func::Lifted { ty, func, options } => Export::LiftedFunction(LiftedFunction {
|
||||
ty: *ty,
|
||||
func: func.to_core_export(|i| match i {
|
||||
EntityIndex::Function(i) => i,
|
||||
_ => unreachable!(),
|
||||
}),
|
||||
options: options.clone(),
|
||||
}),
|
||||
// should not be possible to hit with a valid module.
|
||||
Func::Core(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
wasmparser::ComponentExportKind::Value(_) => {
|
||||
unimplemented!("unimplemented value export");
|
||||
}
|
||||
wasmparser::ComponentExportKind::Type(i) => {
|
||||
let idx = TypeIndex::from_u32(i);
|
||||
drop(idx);
|
||||
unimplemented!("unimplemented value export");
|
||||
}
|
||||
};
|
||||
self.result
|
||||
.component
|
||||
.exports
|
||||
.insert(name.to_string(), export);
|
||||
}
|
||||
|
||||
fn alias(&mut self, alias: &wasmparser::Alias<'data>) {
|
||||
match alias {
|
||||
wasmparser::Alias::InstanceExport {
|
||||
kind,
|
||||
instance,
|
||||
name,
|
||||
} => {
|
||||
let instance = InstanceIndex::from_u32(*instance);
|
||||
match &self.result.instances[instance] {
|
||||
InstanceDef::Module { .. } | InstanceDef::ModuleSynthetic(_) => {
|
||||
self.alias_module_instance_export(*kind, instance, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
wasmparser::Alias::OuterModule { .. } => {
|
||||
unimplemented!("alias outer module");
|
||||
}
|
||||
wasmparser::Alias::OuterComponent { .. } => {
|
||||
unimplemented!("alias outer component");
|
||||
}
|
||||
|
||||
// When aliasing a type the `ComponentTypesBuilder` is used to
|
||||
// resolve the outer `count` plus the index, and then once it's
|
||||
// resolved we push the type information into our local index
|
||||
// space.
|
||||
//
|
||||
// Note that this is just copying indices around as all type
|
||||
// information is basically a pointer back into the `TypesBuilder`
|
||||
// structure (and the eventual `TypeTables` that it produces).
|
||||
wasmparser::Alias::OuterType { count, index } => {
|
||||
let index = TypeIndex::from_u32(*index);
|
||||
let ty = self.types.component_outer_type(*count, index);
|
||||
self.types.push_component_typedef(ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts an item in to the relevant namespace aliasing the `name`'d
|
||||
/// export of the `instance` provided.
|
||||
fn alias_module_instance_export(
|
||||
&mut self,
|
||||
kind: wasmparser::AliasKind,
|
||||
instance: InstanceIndex,
|
||||
name: &'data str,
|
||||
) {
|
||||
let src = self.lookup_core_source(instance, name);
|
||||
match kind {
|
||||
wasmparser::AliasKind::Func => {
|
||||
self.result.funcs.push(Func::Core(src));
|
||||
}
|
||||
wasmparser::AliasKind::Global => {
|
||||
self.result.globals.push(src);
|
||||
}
|
||||
wasmparser::AliasKind::Memory => {
|
||||
self.result.memories.push(src);
|
||||
}
|
||||
wasmparser::AliasKind::Table => {
|
||||
self.result.tables.push(src);
|
||||
}
|
||||
other => {
|
||||
panic!("unknown/unimplemented alias kind {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lift_function(
|
||||
&self,
|
||||
ty: TypeIndex,
|
||||
func: FuncIndex,
|
||||
options: &[wasmparser::CanonicalOption],
|
||||
) -> Func<'data> {
|
||||
let ty = match self.types.component_outer_type(0, ty) {
|
||||
TypeDef::Func(ty) => ty,
|
||||
// should not be possible after validation
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let func = match &self.result.funcs[func] {
|
||||
Func::Core(core) => core.clone(),
|
||||
// should not be possible after validation
|
||||
Func::Lifted { .. } => unreachable!(),
|
||||
};
|
||||
let options = self.canonical_options(options);
|
||||
Func::Lifted { ty, func, options }
|
||||
}
|
||||
|
||||
fn canonical_options(&self, opts: &[wasmparser::CanonicalOption]) -> CanonicalOptions {
|
||||
let mut ret = CanonicalOptions::default();
|
||||
for opt in opts {
|
||||
match opt {
|
||||
wasmparser::CanonicalOption::UTF8 => {
|
||||
ret.string_encoding = Some(StringEncoding::Utf8);
|
||||
}
|
||||
wasmparser::CanonicalOption::UTF16 => {
|
||||
ret.string_encoding = Some(StringEncoding::Utf16);
|
||||
}
|
||||
wasmparser::CanonicalOption::CompactUTF16 => {
|
||||
ret.string_encoding = Some(StringEncoding::CompactUtf16);
|
||||
}
|
||||
wasmparser::CanonicalOption::Into(instance) => {
|
||||
let instance = InstanceIndex::from_u32(*instance);
|
||||
|
||||
// Note that the `unreachable!()` should not happen for
|
||||
// components which have passed validation.
|
||||
let memory = self
|
||||
.lookup_core_source(instance, "memory")
|
||||
.to_core_export(|i| match i {
|
||||
EntityIndex::Memory(i) => i,
|
||||
_ => unreachable!(),
|
||||
});
|
||||
let canonical_abi_free = self
|
||||
.lookup_core_source(instance, "canonical_abi_free")
|
||||
.to_core_export(|i| match i {
|
||||
EntityIndex::Function(i) => i,
|
||||
_ => unreachable!(),
|
||||
});
|
||||
let canonical_abi_realloc = self
|
||||
.lookup_core_source(instance, "canonical_abi_realloc")
|
||||
.to_core_export(|i| match i {
|
||||
EntityIndex::Function(i) => i,
|
||||
_ => unreachable!(),
|
||||
});
|
||||
ret.intrinsics = Some(Intrinsics {
|
||||
memory,
|
||||
canonical_abi_free,
|
||||
canonical_abi_realloc,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
impl CoreSource<'_> {
|
||||
fn to_core_export<T>(&self, get_index: impl FnOnce(EntityIndex) -> T) -> CoreExport<T> {
|
||||
match self {
|
||||
CoreSource::Index(instance, index) => CoreExport {
|
||||
instance: *instance,
|
||||
item: ExportItem::Index(get_index(*index)),
|
||||
},
|
||||
CoreSource::Export(instance, name) => CoreExport {
|
||||
instance: *instance,
|
||||
item: ExportItem::Name(name.to_string()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
752
crates/environ/src/component/types.rs
Normal file
752
crates/environ/src/component/types.rs
Normal file
@@ -0,0 +1,752 @@
|
||||
use crate::{
|
||||
EntityType, Global, GlobalInit, ModuleTypes, ModuleTypesBuilder, PrimaryMap, SignatureIndex,
|
||||
};
|
||||
use anyhow::{bail, Result};
|
||||
use cranelift_entity::EntityRef;
|
||||
use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Index;
|
||||
|
||||
macro_rules! indices {
|
||||
($(
|
||||
$(#[$a:meta])*
|
||||
pub struct $name:ident(u32);
|
||||
)*) => ($(
|
||||
$(#[$a])*
|
||||
#[derive(
|
||||
Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug,
|
||||
Serialize, Deserialize,
|
||||
)]
|
||||
pub struct $name(u32);
|
||||
cranelift_entity::entity_impl!($name);
|
||||
)*);
|
||||
}
|
||||
|
||||
indices! {
|
||||
// ========================================================================
|
||||
// These indices are used during compile time only when we're translating a
|
||||
// component at this time. The actual indices are not persisted beyond the
|
||||
// compile phase to when we're actually working with the component at
|
||||
// runtime.
|
||||
|
||||
/// Index within a component's module index space.
|
||||
pub struct ModuleIndex(u32);
|
||||
|
||||
/// Index within a component's component index space.
|
||||
pub struct ComponentIndex(u32);
|
||||
|
||||
/// Index within a component's instance index space.
|
||||
pub struct InstanceIndex(u32);
|
||||
|
||||
// ========================================================================
|
||||
// These indices are used to lookup type information within a `TypeTables`
|
||||
// structure. These represent generally deduplicated type information across
|
||||
// an entire component and are a form of RTTI in a sense.
|
||||
|
||||
/// Index pointing to a component's type (exports/imports with
|
||||
/// component-model types)
|
||||
pub struct ComponentTypeIndex(u32);
|
||||
|
||||
/// Index pointing to a component instance's type (exports with
|
||||
/// component-model types, no imports)
|
||||
pub struct ComponentInstanceTypeIndex(u32);
|
||||
|
||||
/// Index pointing to a core wasm module's type (exports/imports with
|
||||
/// core wasm types)
|
||||
pub struct ModuleTypeIndex(u32);
|
||||
|
||||
/// Index pointing to a component model function type with arguments/result
|
||||
/// as interface types.
|
||||
pub struct FuncTypeIndex(u32);
|
||||
|
||||
/// Index pointing to an interface type, used for recursive types such as
|
||||
/// `List<T>`.
|
||||
pub struct InterfaceTypeIndex(u32);
|
||||
|
||||
/// Index pointing to a record type in the component model (aka a struct).
|
||||
pub struct RecordTypeIndex(u32);
|
||||
/// Index pointing to a variant type in the component model (aka an enum).
|
||||
pub struct VariantTypeIndex(u32);
|
||||
/// Index pointing to a tuple type in the component model.
|
||||
pub struct TupleTypeIndex(u32);
|
||||
/// Index pointing to a flags type in the component model.
|
||||
pub struct FlagsTypeIndex(u32);
|
||||
/// Index pointing to an enum type in the component model.
|
||||
pub struct EnumTypeIndex(u32);
|
||||
/// Index pointing to a union type in the component model.
|
||||
pub struct UnionTypeIndex(u32);
|
||||
/// Index pointing to an expected type in the component model (aka a
|
||||
/// `Result<T, E>`)
|
||||
pub struct ExpectedTypeIndex(u32);
|
||||
|
||||
// ========================================================================
|
||||
// These indices are actually used at runtime when managing a component at
|
||||
// this time.
|
||||
|
||||
/// Index that represents a core wasm instance created at runtime.
|
||||
///
|
||||
/// This is used to keep track of when instances are created and is able to
|
||||
/// refer back to previously created instances for exports and such.
|
||||
pub struct RuntimeInstanceIndex(u32);
|
||||
|
||||
/// Index that represents a closed-over-module for a component.
|
||||
///
|
||||
/// Components which embed modules or otherwise refer to module (such as
|
||||
/// through `alias` annotations) pull in items in to the list of closed over
|
||||
/// modules, and this index indexes, at runtime, which of the upvars is
|
||||
/// referenced.
|
||||
pub struct ModuleUpvarIndex(u32);
|
||||
}
|
||||
|
||||
// Reexport for convenience some core-wasm indices which are also used in the
|
||||
// component model, typically for when aliasing exports of core wasm modules.
|
||||
pub use crate::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex, TypeIndex};
|
||||
|
||||
/// Runtime information about the type information contained within a component.
|
||||
///
|
||||
/// One of these is created per top-level component which describes all of the
|
||||
/// types contained within the top-level component itself. Each sub-component
|
||||
/// will have a pointer to this value as well.
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct ComponentTypes {
|
||||
modules: PrimaryMap<ModuleTypeIndex, ModuleType>,
|
||||
components: PrimaryMap<ComponentTypeIndex, ComponentType>,
|
||||
component_instances: PrimaryMap<ComponentInstanceTypeIndex, ComponentInstanceType>,
|
||||
functions: PrimaryMap<FuncTypeIndex, FuncType>,
|
||||
interface_types: PrimaryMap<InterfaceTypeIndex, InterfaceType>,
|
||||
records: PrimaryMap<RecordTypeIndex, RecordType>,
|
||||
variants: PrimaryMap<VariantTypeIndex, VariantType>,
|
||||
tuples: PrimaryMap<TupleTypeIndex, TupleType>,
|
||||
enums: PrimaryMap<EnumTypeIndex, EnumType>,
|
||||
flags: PrimaryMap<FlagsTypeIndex, FlagsType>,
|
||||
unions: PrimaryMap<UnionTypeIndex, UnionType>,
|
||||
expecteds: PrimaryMap<ExpectedTypeIndex, ExpectedType>,
|
||||
|
||||
module_types: ModuleTypes,
|
||||
}
|
||||
|
||||
impl ComponentTypes {
|
||||
/// Returns the core wasm module types known within this component.
|
||||
pub fn module_types(&self) -> &ModuleTypes {
|
||||
&self.module_types
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_index {
|
||||
($(impl Index<$ty:ident> for ComponentTypes { $output:ident => $field:ident })*) => ($(
|
||||
impl std::ops::Index<$ty> for ComponentTypes {
|
||||
type Output = $output;
|
||||
fn index(&self, idx: $ty) -> &$output {
|
||||
&self.$field[idx]
|
||||
}
|
||||
}
|
||||
)*)
|
||||
}
|
||||
|
||||
impl_index! {
|
||||
impl Index<ModuleTypeIndex> for ComponentTypes { ModuleType => modules }
|
||||
impl Index<ComponentTypeIndex> for ComponentTypes { ComponentType => components }
|
||||
impl Index<ComponentInstanceTypeIndex> for ComponentTypes { ComponentInstanceType => component_instances }
|
||||
impl Index<FuncTypeIndex> for ComponentTypes { FuncType => functions }
|
||||
impl Index<InterfaceTypeIndex> for ComponentTypes { InterfaceType => interface_types }
|
||||
impl Index<RecordTypeIndex> for ComponentTypes { RecordType => records }
|
||||
impl Index<VariantTypeIndex> for ComponentTypes { VariantType => variants }
|
||||
impl Index<TupleTypeIndex> for ComponentTypes { TupleType => tuples }
|
||||
impl Index<EnumTypeIndex> for ComponentTypes { EnumType => enums }
|
||||
impl Index<FlagsTypeIndex> for ComponentTypes { FlagsType => flags }
|
||||
impl Index<UnionTypeIndex> for ComponentTypes { UnionType => unions }
|
||||
impl Index<ExpectedTypeIndex> for ComponentTypes { ExpectedType => expecteds }
|
||||
}
|
||||
|
||||
/// Structured used to build a [`ComponentTypes`] during translation.
|
||||
///
|
||||
/// This contains tables to intern any component types found as well as
|
||||
/// managing building up core wasm [`ModuleTypes`] as well.
|
||||
#[derive(Default)]
|
||||
pub struct ComponentTypesBuilder {
|
||||
type_scopes: Vec<PrimaryMap<TypeIndex, TypeDef>>,
|
||||
functions: HashMap<FuncType, FuncTypeIndex>,
|
||||
interface_types: HashMap<InterfaceType, InterfaceTypeIndex>,
|
||||
records: HashMap<RecordType, RecordTypeIndex>,
|
||||
variants: HashMap<VariantType, VariantTypeIndex>,
|
||||
tuples: HashMap<TupleType, TupleTypeIndex>,
|
||||
enums: HashMap<EnumType, EnumTypeIndex>,
|
||||
flags: HashMap<FlagsType, FlagsTypeIndex>,
|
||||
unions: HashMap<UnionType, UnionTypeIndex>,
|
||||
expecteds: HashMap<ExpectedType, ExpectedTypeIndex>,
|
||||
|
||||
component_types: ComponentTypes,
|
||||
module_types: ModuleTypesBuilder,
|
||||
}
|
||||
|
||||
impl ComponentTypesBuilder {
|
||||
/// Finishes this list of component types and returns the finished
|
||||
/// structure.
|
||||
pub fn finish(mut self) -> ComponentTypes {
|
||||
self.component_types.module_types = self.module_types.finish();
|
||||
self.component_types
|
||||
}
|
||||
|
||||
/// Returns the underlying builder used to build up core wasm module types.
|
||||
///
|
||||
/// Note that this is shared across all modules found within a component to
|
||||
/// improve the wins from deduplicating function signatures.
|
||||
pub fn module_types_builder(&mut self) -> &mut ModuleTypesBuilder {
|
||||
&mut self.module_types
|
||||
}
|
||||
|
||||
/// Pushes a new scope when entering a new index space for types in the
|
||||
/// component model.
|
||||
///
|
||||
/// This happens when a component is recursed into or a module/instance
|
||||
/// type is recursed into.
|
||||
pub fn push_component_types_scope(&mut self) {
|
||||
self.type_scopes.push(PrimaryMap::new());
|
||||
}
|
||||
|
||||
/// Adds a new `TypeDef` definition within the current component types
|
||||
/// scope.
|
||||
///
|
||||
/// Returns the `TypeIndex` associated with the type being pushed..
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Requires that `push_component_types_scope` was called previously.
|
||||
pub fn push_component_typedef(&mut self, ty: TypeDef) -> TypeIndex {
|
||||
self.type_scopes.last_mut().unwrap().push(ty)
|
||||
}
|
||||
|
||||
/// Looks up an "outer" type in this builder to handle outer aliases.
|
||||
///
|
||||
/// The `count` parameter and `ty` are taken from the binary format itself,
|
||||
/// and the `TypeDef` returned is what the outer type refers to.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Assumes that `count` and `ty` are valid.
|
||||
pub fn component_outer_type(&self, count: u32, ty: TypeIndex) -> TypeDef {
|
||||
// Reverse the index and 0 means the "current scope"
|
||||
let idx = self.type_scopes.len() - (count as usize) - 1;
|
||||
self.type_scopes[idx][ty]
|
||||
}
|
||||
|
||||
/// Pops a scope pushed by `push_component_types_scope`.
|
||||
pub fn pop_component_types_scope(&mut self) {
|
||||
self.type_scopes.pop().unwrap();
|
||||
}
|
||||
|
||||
/// Translates a wasmparser `ComponentTypeDef` into a Wasmtime `TypeDef`,
|
||||
/// interning types along the way.
|
||||
pub fn component_type_def(&mut self, ty: &wasmparser::ComponentTypeDef<'_>) -> Result<TypeDef> {
|
||||
Ok(match ty {
|
||||
wasmparser::ComponentTypeDef::Module(ty) => TypeDef::Module(self.module_type(ty)?),
|
||||
wasmparser::ComponentTypeDef::Component(ty) => {
|
||||
TypeDef::Component(self.component_type(ty)?)
|
||||
}
|
||||
wasmparser::ComponentTypeDef::Instance(ty) => {
|
||||
TypeDef::ComponentInstance(self.component_instance_type(ty)?)
|
||||
}
|
||||
wasmparser::ComponentTypeDef::Function(ty) => TypeDef::Func(self.func_type(ty)),
|
||||
wasmparser::ComponentTypeDef::Value(_ty) => unimplemented!("value types"),
|
||||
wasmparser::ComponentTypeDef::Interface(ty) => {
|
||||
TypeDef::Interface(self.interface_type(ty))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn module_type(&mut self, ty: &[wasmparser::ModuleType<'_>]) -> Result<ModuleTypeIndex> {
|
||||
let mut result = ModuleType::default();
|
||||
let mut functypes: PrimaryMap<TypeIndex, SignatureIndex> = PrimaryMap::default();
|
||||
|
||||
for item in ty {
|
||||
match item {
|
||||
wasmparser::ModuleType::Type(wasmparser::TypeDef::Func(f)) => {
|
||||
functypes.push(self.module_types.wasm_func_type(f.clone().try_into()?));
|
||||
}
|
||||
wasmparser::ModuleType::Export { name, ty } => {
|
||||
let prev = result
|
||||
.exports
|
||||
.insert(name.to_string(), type_ref(ty, &functypes)?);
|
||||
assert!(prev.is_none());
|
||||
}
|
||||
wasmparser::ModuleType::Import(import) => {
|
||||
let prev = result.imports.insert(
|
||||
(import.module.to_string(), import.name.to_string()),
|
||||
type_ref(&import.ty, &functypes)?,
|
||||
);
|
||||
assert!(prev.is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(self.component_types.modules.push(result));
|
||||
|
||||
fn type_ref(
|
||||
ty: &wasmparser::TypeRef,
|
||||
functypes: &PrimaryMap<TypeIndex, SignatureIndex>,
|
||||
) -> Result<EntityType> {
|
||||
Ok(match ty {
|
||||
wasmparser::TypeRef::Func(idx) => {
|
||||
EntityType::Function(functypes[TypeIndex::from_u32(*idx)])
|
||||
}
|
||||
wasmparser::TypeRef::Table(ty) => EntityType::Table(ty.clone().try_into()?),
|
||||
wasmparser::TypeRef::Memory(ty) => EntityType::Memory(ty.clone().into()),
|
||||
wasmparser::TypeRef::Global(ty) => {
|
||||
EntityType::Global(Global::new(ty.clone(), GlobalInit::Import)?)
|
||||
}
|
||||
wasmparser::TypeRef::Tag(_) => bail!("exceptions proposal not implemented"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn component_type(
|
||||
&mut self,
|
||||
ty: &[wasmparser::ComponentType<'_>],
|
||||
) -> Result<ComponentTypeIndex> {
|
||||
let mut result = ComponentType::default();
|
||||
self.push_component_types_scope();
|
||||
|
||||
for item in ty {
|
||||
match item {
|
||||
wasmparser::ComponentType::Type(ty) => {
|
||||
let ty = self.component_type_def(ty)?;
|
||||
self.push_component_typedef(ty);
|
||||
}
|
||||
wasmparser::ComponentType::OuterType { count, index } => {
|
||||
let ty = self.component_outer_type(*count, TypeIndex::from_u32(*index));
|
||||
self.push_component_typedef(ty);
|
||||
}
|
||||
wasmparser::ComponentType::Export { name, ty } => {
|
||||
result.exports.insert(
|
||||
name.to_string(),
|
||||
self.component_outer_type(0, TypeIndex::from_u32(*ty)),
|
||||
);
|
||||
}
|
||||
wasmparser::ComponentType::Import(import) => {
|
||||
result.imports.insert(
|
||||
import.name.to_string(),
|
||||
self.component_outer_type(0, TypeIndex::from_u32(import.ty)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.pop_component_types_scope();
|
||||
|
||||
Ok(self.component_types.components.push(result))
|
||||
}
|
||||
|
||||
fn component_instance_type(
|
||||
&mut self,
|
||||
ty: &[wasmparser::InstanceType<'_>],
|
||||
) -> Result<ComponentInstanceTypeIndex> {
|
||||
let mut result = ComponentInstanceType::default();
|
||||
self.push_component_types_scope();
|
||||
|
||||
for item in ty {
|
||||
match item {
|
||||
wasmparser::InstanceType::Type(ty) => {
|
||||
let ty = self.component_type_def(ty)?;
|
||||
self.push_component_typedef(ty);
|
||||
}
|
||||
wasmparser::InstanceType::OuterType { count, index } => {
|
||||
let ty = self.component_outer_type(*count, TypeIndex::from_u32(*index));
|
||||
self.push_component_typedef(ty);
|
||||
}
|
||||
wasmparser::InstanceType::Export { name, ty } => {
|
||||
result.exports.insert(
|
||||
name.to_string(),
|
||||
self.component_outer_type(0, TypeIndex::from_u32(*ty)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.pop_component_types_scope();
|
||||
|
||||
Ok(self.component_types.component_instances.push(result))
|
||||
}
|
||||
|
||||
fn func_type(&mut self, ty: &wasmparser::ComponentFuncType<'_>) -> FuncTypeIndex {
|
||||
let ty = FuncType {
|
||||
params: ty
|
||||
.params
|
||||
.iter()
|
||||
.map(|(name, ty)| (name.map(|s| s.to_string()), self.interface_type_ref(ty)))
|
||||
.collect(),
|
||||
result: self.interface_type_ref(&ty.result),
|
||||
};
|
||||
intern(&mut self.functions, &mut self.component_types.functions, ty)
|
||||
}
|
||||
|
||||
fn interface_type(&mut self, ty: &wasmparser::InterfaceType<'_>) -> InterfaceType {
|
||||
match ty {
|
||||
wasmparser::InterfaceType::Primitive(ty) => ty.into(),
|
||||
wasmparser::InterfaceType::Record(e) => InterfaceType::Record(self.record_type(e)),
|
||||
wasmparser::InterfaceType::Variant(e) => InterfaceType::Variant(self.variant_type(e)),
|
||||
wasmparser::InterfaceType::List(e) => {
|
||||
let ty = self.interface_type_ref(e);
|
||||
InterfaceType::List(self.intern_interface_type(ty))
|
||||
}
|
||||
wasmparser::InterfaceType::Tuple(e) => InterfaceType::Tuple(self.tuple_type(e)),
|
||||
wasmparser::InterfaceType::Flags(e) => InterfaceType::Flags(self.flags_type(e)),
|
||||
wasmparser::InterfaceType::Enum(e) => InterfaceType::Enum(self.enum_type(e)),
|
||||
wasmparser::InterfaceType::Union(e) => InterfaceType::Union(self.union_type(e)),
|
||||
wasmparser::InterfaceType::Option(e) => {
|
||||
let ty = self.interface_type_ref(e);
|
||||
InterfaceType::Option(self.intern_interface_type(ty))
|
||||
}
|
||||
wasmparser::InterfaceType::Expected { ok, error } => {
|
||||
InterfaceType::Expected(self.expected_type(ok, error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn interface_type_ref(&mut self, ty: &wasmparser::InterfaceTypeRef) -> InterfaceType {
|
||||
match ty {
|
||||
wasmparser::InterfaceTypeRef::Primitive(p) => p.into(),
|
||||
wasmparser::InterfaceTypeRef::Type(idx) => {
|
||||
let idx = TypeIndex::from_u32(*idx);
|
||||
match self.component_outer_type(0, idx) {
|
||||
TypeDef::Interface(ty) => ty,
|
||||
// this should not be possible if the module validated
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn intern_interface_type(&mut self, ty: InterfaceType) -> InterfaceTypeIndex {
|
||||
intern(
|
||||
&mut self.interface_types,
|
||||
&mut self.component_types.interface_types,
|
||||
ty,
|
||||
)
|
||||
}
|
||||
|
||||
fn record_type(&mut self, record: &[(&str, wasmparser::InterfaceTypeRef)]) -> RecordTypeIndex {
|
||||
let record = RecordType {
|
||||
fields: record
|
||||
.iter()
|
||||
.map(|(name, ty)| RecordField {
|
||||
name: name.to_string(),
|
||||
ty: self.interface_type_ref(ty),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
intern(&mut self.records, &mut self.component_types.records, record)
|
||||
}
|
||||
|
||||
fn variant_type(&mut self, cases: &[wasmparser::VariantCase<'_>]) -> VariantTypeIndex {
|
||||
let variant = VariantType {
|
||||
cases: cases
|
||||
.iter()
|
||||
.map(|case| {
|
||||
// FIXME: need to implement `default_to`, not sure what that
|
||||
// is at this time.
|
||||
assert!(case.default_to.is_none());
|
||||
VariantCase {
|
||||
name: case.name.to_string(),
|
||||
ty: self.interface_type_ref(&case.ty),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
intern(
|
||||
&mut self.variants,
|
||||
&mut self.component_types.variants,
|
||||
variant,
|
||||
)
|
||||
}
|
||||
|
||||
fn tuple_type(&mut self, types: &[wasmparser::InterfaceTypeRef]) -> TupleTypeIndex {
|
||||
let tuple = TupleType {
|
||||
types: types.iter().map(|ty| self.interface_type_ref(ty)).collect(),
|
||||
};
|
||||
intern(&mut self.tuples, &mut self.component_types.tuples, tuple)
|
||||
}
|
||||
|
||||
fn flags_type(&mut self, flags: &[&str]) -> FlagsTypeIndex {
|
||||
let flags = FlagsType {
|
||||
names: flags.iter().map(|s| s.to_string()).collect(),
|
||||
};
|
||||
intern(&mut self.flags, &mut self.component_types.flags, flags)
|
||||
}
|
||||
|
||||
fn enum_type(&mut self, variants: &[&str]) -> EnumTypeIndex {
|
||||
let e = EnumType {
|
||||
names: variants.iter().map(|s| s.to_string()).collect(),
|
||||
};
|
||||
intern(&mut self.enums, &mut self.component_types.enums, e)
|
||||
}
|
||||
|
||||
fn union_type(&mut self, types: &[wasmparser::InterfaceTypeRef]) -> UnionTypeIndex {
|
||||
let union = UnionType {
|
||||
types: types.iter().map(|ty| self.interface_type_ref(ty)).collect(),
|
||||
};
|
||||
intern(&mut self.unions, &mut self.component_types.unions, union)
|
||||
}
|
||||
|
||||
fn expected_type(
|
||||
&mut self,
|
||||
ok: &wasmparser::InterfaceTypeRef,
|
||||
err: &wasmparser::InterfaceTypeRef,
|
||||
) -> ExpectedTypeIndex {
|
||||
let expected = ExpectedType {
|
||||
ok: self.interface_type_ref(ok),
|
||||
err: self.interface_type_ref(err),
|
||||
};
|
||||
intern(
|
||||
&mut self.expecteds,
|
||||
&mut self.component_types.expecteds,
|
||||
expected,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Forward the indexing impl to the internal `TypeTables`
|
||||
impl<T> Index<T> for ComponentTypesBuilder
|
||||
where
|
||||
ComponentTypes: Index<T>,
|
||||
{
|
||||
type Output = <ComponentTypes as Index<T>>::Output;
|
||||
|
||||
fn index(&self, sig: T) -> &Self::Output {
|
||||
&self.component_types[sig]
|
||||
}
|
||||
}
|
||||
|
||||
fn intern<T, U>(map: &mut HashMap<T, U>, list: &mut PrimaryMap<U, T>, item: T) -> U
|
||||
where
|
||||
T: Hash + Clone + Eq,
|
||||
U: Copy + EntityRef,
|
||||
{
|
||||
if let Some(idx) = map.get(&item) {
|
||||
return *idx;
|
||||
}
|
||||
let idx = list.push(item.clone());
|
||||
map.insert(item, idx);
|
||||
return idx;
|
||||
}
|
||||
|
||||
/// Types of imports and exports in the component model.
|
||||
///
|
||||
/// These types are what's available for import and export in components. Note
|
||||
/// that all indirect indices contained here are intended to be looked up
|
||||
/// through a sibling `ComponentTypes` structure.
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum TypeDef {
|
||||
/// A core wasm module and its type.
|
||||
Module(ModuleTypeIndex),
|
||||
/// A component and its type.
|
||||
Component(ComponentTypeIndex),
|
||||
/// An instance of a component.
|
||||
ComponentInstance(ComponentInstanceTypeIndex),
|
||||
/// A component function, not to be confused with a core wasm function.
|
||||
Func(FuncTypeIndex),
|
||||
/// An interface type.
|
||||
Interface(InterfaceType),
|
||||
}
|
||||
|
||||
// NB: Note that maps below are stored as an `IndexMap` now but the order
|
||||
// typically does not matter. As a minor implementation detail we want the
|
||||
// serialization of this type to always be deterministic and using `IndexMap`
|
||||
// gets us that over using a `HashMap` for example.
|
||||
|
||||
/// The type of a module in the component model.
|
||||
///
|
||||
/// Note that this is not to be confused with `ComponentType` below. This is
|
||||
/// intended only for core wasm modules, not for components.
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct ModuleType {
|
||||
/// The values that this module imports.
|
||||
///
|
||||
/// Note that the value of this map is a core wasm `EntityType`, not a
|
||||
/// component model `TypeRef`. Additionally note that this reflects the
|
||||
/// two-level namespace of core WebAssembly, but unlike core wasm all import
|
||||
/// names are required to be unique to describe a module in the component
|
||||
/// model.
|
||||
pub imports: IndexMap<(String, String), EntityType>,
|
||||
|
||||
/// The values that this module exports.
|
||||
///
|
||||
/// Note that the value of this map is the core wasm `EntityType` to
|
||||
/// represent that core wasm items are being exported.
|
||||
pub exports: IndexMap<String, EntityType>,
|
||||
}
|
||||
|
||||
/// The type of a component in the component model.
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct ComponentType {
|
||||
/// The named values that this component imports.
|
||||
pub imports: IndexMap<String, TypeDef>,
|
||||
/// The named values that this component exports.
|
||||
pub exports: IndexMap<String, TypeDef>,
|
||||
}
|
||||
|
||||
/// The type of a component instance in the component model, or an instantiated
|
||||
/// component.
|
||||
///
|
||||
/// Component instances only have exports of types in the component model.
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct ComponentInstanceType {
|
||||
/// The list of exports that this component has along with their types.
|
||||
pub exports: IndexMap<String, TypeDef>,
|
||||
}
|
||||
|
||||
/// A component function type in the component model.
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct FuncType {
|
||||
/// The list of optionally named parameters for this function, and their
|
||||
/// types.
|
||||
pub params: Box<[(Option<String>, InterfaceType)]>,
|
||||
/// The return value of this function.
|
||||
pub result: InterfaceType,
|
||||
}
|
||||
|
||||
/// All possible interface types that values can have.
|
||||
///
|
||||
/// This list represents an exhaustive listing of interface types and the
|
||||
/// shapes that they can take. Note that this enum is considered an "index" of
|
||||
/// forms where for non-primitive types a `ComponentTypes` structure is used to
|
||||
/// lookup further information based on the index found here.
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum InterfaceType {
|
||||
Unit,
|
||||
Bool,
|
||||
S8,
|
||||
U8,
|
||||
S16,
|
||||
U16,
|
||||
S32,
|
||||
U32,
|
||||
S64,
|
||||
U64,
|
||||
Float32,
|
||||
Float64,
|
||||
Char,
|
||||
String,
|
||||
Record(RecordTypeIndex),
|
||||
Variant(VariantTypeIndex),
|
||||
List(InterfaceTypeIndex),
|
||||
Tuple(TupleTypeIndex),
|
||||
Flags(FlagsTypeIndex),
|
||||
Enum(EnumTypeIndex),
|
||||
Union(UnionTypeIndex),
|
||||
Option(InterfaceTypeIndex),
|
||||
Expected(ExpectedTypeIndex),
|
||||
}
|
||||
|
||||
impl From<&wasmparser::PrimitiveInterfaceType> for InterfaceType {
|
||||
fn from(ty: &wasmparser::PrimitiveInterfaceType) -> InterfaceType {
|
||||
match ty {
|
||||
wasmparser::PrimitiveInterfaceType::Unit => InterfaceType::Unit,
|
||||
wasmparser::PrimitiveInterfaceType::Bool => InterfaceType::Bool,
|
||||
wasmparser::PrimitiveInterfaceType::S8 => InterfaceType::S8,
|
||||
wasmparser::PrimitiveInterfaceType::U8 => InterfaceType::U8,
|
||||
wasmparser::PrimitiveInterfaceType::S16 => InterfaceType::S16,
|
||||
wasmparser::PrimitiveInterfaceType::U16 => InterfaceType::U16,
|
||||
wasmparser::PrimitiveInterfaceType::S32 => InterfaceType::S32,
|
||||
wasmparser::PrimitiveInterfaceType::U32 => InterfaceType::U32,
|
||||
wasmparser::PrimitiveInterfaceType::S64 => InterfaceType::S64,
|
||||
wasmparser::PrimitiveInterfaceType::U64 => InterfaceType::U64,
|
||||
wasmparser::PrimitiveInterfaceType::Float32 => InterfaceType::Float32,
|
||||
wasmparser::PrimitiveInterfaceType::Float64 => InterfaceType::Float64,
|
||||
wasmparser::PrimitiveInterfaceType::Char => InterfaceType::Char,
|
||||
wasmparser::PrimitiveInterfaceType::String => InterfaceType::String,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shape of a "record" type in interface types.
|
||||
///
|
||||
/// This is equivalent to a `struct` in Rust.
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct RecordType {
|
||||
/// The fields that are contained within this struct type.
|
||||
pub fields: Box<[RecordField]>,
|
||||
}
|
||||
|
||||
/// One field within a record.
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct RecordField {
|
||||
/// The name of the field, unique amongst all fields in a record.
|
||||
pub name: String,
|
||||
/// The type that this field contains.
|
||||
pub ty: InterfaceType,
|
||||
}
|
||||
|
||||
/// Shape of a "variant" type in interface types.
|
||||
///
|
||||
/// Variants are close to Rust `enum` declarations where a value is one of many
|
||||
/// cases and each case has a unique name and an optional payload associated
|
||||
/// with it.
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct VariantType {
|
||||
/// The list of cases that this variant can take.
|
||||
pub cases: Box<[VariantCase]>,
|
||||
}
|
||||
|
||||
/// One case of a `variant` type which contains the name of the variant as well
|
||||
/// as the payload.
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct VariantCase {
|
||||
/// Name of the variant, unique amongst all cases in a variant.
|
||||
pub name: String,
|
||||
/// Type associated with this payload, maybe `Unit`.
|
||||
pub ty: InterfaceType,
|
||||
}
|
||||
|
||||
/// Shape of a "tuple" type in interface types.
|
||||
///
|
||||
/// This is largely the same as a tuple in Rust, basically a record with
|
||||
/// unnamed fields.
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct TupleType {
|
||||
/// The types that are contained within this tuple.
|
||||
pub types: Box<[InterfaceType]>,
|
||||
}
|
||||
|
||||
/// Shape of a "flags" type in interface types.
|
||||
///
|
||||
/// This can be thought of as a record-of-bools, although the representation is
|
||||
/// more efficient as bitflags.
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct FlagsType {
|
||||
/// The names of all flags, all of which are unique.
|
||||
pub names: Box<[String]>,
|
||||
}
|
||||
|
||||
/// Shape of an "enum" type in interface types, not to be confused with a Rust
|
||||
/// `enum` type.
|
||||
///
|
||||
/// In interface types enums are simply a bag of names, and can be seen as a
|
||||
/// variant where all payloads are `Unit`.
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct EnumType {
|
||||
/// The names of this enum, all of which are unique.
|
||||
pub names: Box<[String]>,
|
||||
}
|
||||
|
||||
/// Shape of a "union" type in interface types.
|
||||
///
|
||||
/// Note that this can be viewed as a specialization of the `variant` interface
|
||||
/// type where each type here has a name that's numbered. This is still a
|
||||
/// tagged union.
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct UnionType {
|
||||
/// The list of types this is a union over.
|
||||
pub types: Box<[InterfaceType]>,
|
||||
}
|
||||
|
||||
/// Shape of an "expected" interface type.
|
||||
#[derive(Serialize, Deserialize, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct ExpectedType {
|
||||
/// The `T` in `Result<T, E>`
|
||||
pub ok: InterfaceType,
|
||||
/// The `E` in `Result<T, E>`
|
||||
pub err: InterfaceType,
|
||||
}
|
||||
Reference in New Issue
Block a user