use crate::component::dfg::CoreDef;
use crate::component::{
Adapter, AdapterOptions as AdapterOptionsDfg, ComponentTypesBuilder, FlatType, InterfaceType,
RuntimeComponentInstanceIndex, StringEncoding, Transcode, TypeFuncIndex,
};
use crate::fact::transcode::Transcoder;
use crate::{EntityRef, FuncIndex, GlobalIndex, MemoryIndex, PrimaryMap, Tunables};
use crate::{ModuleInternedTypeIndex, prelude::*};
use std::collections::HashMap;
use wasm_encoder::*;
mod core_types;
mod signature;
mod trampoline;
mod transcode;
pub static PREPARE_CALL_FIXED_PARAMS: &[ValType] = &[
ValType::FUNCREF,
ValType::FUNCREF,
ValType::I32,
ValType::I32,
ValType::I32,
ValType::I32,
ValType::I32,
ValType::I32,
];
pub struct Module<'a> {
tunables: &'a Tunables,
types: &'a ComponentTypesBuilder,
core_types: core_types::CoreTypes,
core_imports: ImportSection,
imports: Vec<Import>,
imported: HashMap<CoreDef, usize>,
imported_transcoders: HashMap<Transcoder, FuncIndex>,
imported_resource_transfer_own: Option<FuncIndex>,
imported_resource_transfer_borrow: Option<FuncIndex>,
imported_resource_enter_call: Option<FuncIndex>,
imported_resource_exit_call: Option<FuncIndex>,
imported_async_start_calls: HashMap<(Option<FuncIndex>, Option<FuncIndex>), FuncIndex>,
imported_future_transfer: Option<FuncIndex>,
imported_stream_transfer: Option<FuncIndex>,
imported_error_context_transfer: Option<FuncIndex>,
imported_enter_sync_call: Option<FuncIndex>,
imported_exit_sync_call: Option<FuncIndex>,
imported_trap: Option<FuncIndex>,
imported_funcs: PrimaryMap<FuncIndex, Option<CoreDef>>,
imported_memories: PrimaryMap<MemoryIndex, CoreDef>,
imported_globals: PrimaryMap<GlobalIndex, CoreDef>,
funcs: PrimaryMap<FunctionId, Function>,
helper_funcs: HashMap<Helper, FunctionId>,
helper_worklist: Vec<(FunctionId, Helper)>,
exports: Vec<(u32, String)>,
task_may_block: Option<GlobalIndex>,
}
struct AdapterData {
name: String,
lift: AdapterOptions,
lower: AdapterOptions,
callee: FuncIndex,
}
struct AdapterOptions {
instance: RuntimeComponentInstanceIndex,
ancestors: Vec<RuntimeComponentInstanceIndex>,
ty: TypeFuncIndex,
flags: GlobalIndex,
post_return: Option<FuncIndex>,
options: Options,
}
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
struct LinearMemoryOptions {
memory64: bool,
memory: Option<MemoryIndex>,
realloc: Option<FuncIndex>,
}
impl LinearMemoryOptions {
fn ptr(&self) -> ValType {
if self.memory64 {
ValType::I64
} else {
ValType::I32
}
}
fn ptr_size(&self) -> u8 {
if self.memory64 { 8 } else { 4 }
}
}
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
enum DataModel {
Gc {},
LinearMemory(LinearMemoryOptions),
}
impl DataModel {
#[track_caller]
fn unwrap_memory(&self) -> &LinearMemoryOptions {
match self {
DataModel::Gc {} => panic!("`unwrap_memory` on GC"),
DataModel::LinearMemory(opts) => opts,
}
}
}
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
struct Options {
string_encoding: StringEncoding,
callback: Option<FuncIndex>,
async_: bool,
core_type: ModuleInternedTypeIndex,
data_model: DataModel,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct Helper {
src: HelperType,
dst: HelperType,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct HelperType {
ty: InterfaceType,
opts: Options,
loc: HelperLocation,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
enum HelperLocation {
Stack,
Memory,
#[expect(dead_code, reason = "CM+GC is still WIP")]
StructField,
#[expect(dead_code, reason = "CM+GC is still WIP")]
ArrayElement,
}
impl<'a> Module<'a> {
pub fn new(types: &'a ComponentTypesBuilder, tunables: &'a Tunables) -> Module<'a> {
Module {
tunables,
types,
core_types: Default::default(),
core_imports: Default::default(),
imported: Default::default(),
imports: Default::default(),
imported_transcoders: Default::default(),
imported_funcs: PrimaryMap::new(),
imported_memories: PrimaryMap::new(),
imported_globals: PrimaryMap::new(),
funcs: PrimaryMap::new(),
helper_funcs: HashMap::new(),
helper_worklist: Vec::new(),
imported_resource_transfer_own: None,
imported_resource_transfer_borrow: None,
imported_resource_enter_call: None,
imported_resource_exit_call: None,
imported_async_start_calls: HashMap::new(),
imported_future_transfer: None,
imported_stream_transfer: None,
imported_error_context_transfer: None,
imported_enter_sync_call: None,
imported_exit_sync_call: None,
imported_trap: None,
exports: Vec::new(),
task_may_block: None,
}
}
pub fn adapt(&mut self, name: &str, adapter: &Adapter) {
let mut lift = self.import_options(adapter.lift_ty, &adapter.lift_options);
let lower = self.import_options(adapter.lower_ty, &adapter.lower_options);
assert!(adapter.lower_options.post_return.is_none());
let signature = self.types.signature(&lift);
let ty = self
.core_types
.function(&signature.params, &signature.results);
let callee = self.import_func("callee", name, ty, adapter.func.clone());
lift.post_return = adapter.lift_options.post_return.as_ref().map(|func| {
let ty = self.core_types.function(&signature.results, &[]);
self.import_func("post_return", name, ty, func.clone())
});
trampoline::compile(
self,
&AdapterData {
name: name.to_string(),
lift,
lower,
callee,
},
);
while let Some((result, helper)) = self.helper_worklist.pop() {
trampoline::compile_helper(self, result, helper);
}
}
fn import_options(&mut self, ty: TypeFuncIndex, options: &AdapterOptionsDfg) -> AdapterOptions {
let AdapterOptionsDfg {
instance,
ancestors,
string_encoding,
post_return: _,
callback,
async_,
core_type,
data_model,
cancellable,
} = options;
assert!(!cancellable);
let flags = self.import_global(
"flags",
&format!("instance{}", instance.as_u32()),
GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
},
CoreDef::InstanceFlags(*instance),
);
let data_model = match data_model {
crate::component::DataModel::Gc {} => DataModel::Gc {},
crate::component::DataModel::LinearMemory {
memory,
memory64,
realloc,
} => {
let memory = memory.as_ref().map(|memory| {
self.import_memory(
"memory",
&format!("m{}", self.imported_memories.len()),
MemoryType {
minimum: 0,
maximum: None,
shared: false,
memory64: *memory64,
page_size_log2: None,
},
memory.clone().into(),
)
});
let realloc = realloc.as_ref().map(|func| {
let ptr = if *memory64 {
ValType::I64
} else {
ValType::I32
};
let ty = self.core_types.function(&[ptr, ptr, ptr, ptr], &[ptr]);
self.import_func(
"realloc",
&format!("f{}", self.imported_funcs.len()),
ty,
func.clone(),
)
});
DataModel::LinearMemory(LinearMemoryOptions {
memory64: *memory64,
memory,
realloc,
})
}
};
let callback = callback.as_ref().map(|func| {
let ty = self
.core_types
.function(&[ValType::I32, ValType::I32, ValType::I32], &[ValType::I32]);
self.import_func(
"callback",
&format!("f{}", self.imported_funcs.len()),
ty,
func.clone(),
)
});
AdapterOptions {
instance: *instance,
ancestors: ancestors.clone(),
ty,
flags,
post_return: None,
options: Options {
string_encoding: *string_encoding,
callback,
async_: *async_,
core_type: *core_type,
data_model,
},
}
}
fn import_func(&mut self, module: &str, name: &str, ty: u32, def: CoreDef) -> FuncIndex {
self.import(module, name, EntityType::Function(ty), def, |m| {
&mut m.imported_funcs
})
}
fn import_global(
&mut self,
module: &str,
name: &str,
ty: GlobalType,
def: CoreDef,
) -> GlobalIndex {
self.import(module, name, EntityType::Global(ty), def, |m| {
&mut m.imported_globals
})
}
fn import_memory(
&mut self,
module: &str,
name: &str,
ty: MemoryType,
def: CoreDef,
) -> MemoryIndex {
self.import(module, name, EntityType::Memory(ty), def, |m| {
&mut m.imported_memories
})
}
fn import<K: EntityRef, V: From<CoreDef>>(
&mut self,
module: &str,
name: &str,
ty: EntityType,
def: CoreDef,
map: impl FnOnce(&mut Self) -> &mut PrimaryMap<K, V>,
) -> K {
if let Some(prev) = self.imported.get(&def) {
return K::new(*prev);
}
let idx = map(self).push(def.clone().into());
self.core_imports.import(module, name, ty);
self.imported.insert(def.clone(), idx.index());
self.imports.push(Import::CoreDef(def));
idx
}
fn import_task_may_block(&mut self) -> GlobalIndex {
if let Some(task_may_block) = self.task_may_block {
task_may_block
} else {
let task_may_block = self.import_global(
"instance",
"task_may_block",
GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
},
CoreDef::TaskMayBlock,
);
self.task_may_block = Some(task_may_block);
task_may_block
}
}
fn import_transcoder(&mut self, transcoder: transcode::Transcoder) -> FuncIndex {
*self
.imported_transcoders
.entry(transcoder)
.or_insert_with(|| {
let name = transcoder.name();
let ty = transcoder.ty(&mut self.core_types);
self.core_imports.import("transcode", &name, ty);
let from = self.imported_memories[transcoder.from_memory].clone();
let to = self.imported_memories[transcoder.to_memory].clone();
self.imports.push(Import::Transcode {
op: transcoder.op,
from,
from64: transcoder.from_memory64,
to,
to64: transcoder.to_memory64,
});
self.imported_funcs.push(None)
})
}
fn import_simple(
&mut self,
module: &str,
name: &str,
params: &[ValType],
results: &[ValType],
import: Import,
get: impl Fn(&mut Self) -> &mut Option<FuncIndex>,
) -> FuncIndex {
self.import_simple_get_and_set(
module,
name,
params,
results,
import,
|me| *get(me),
|me, v| *get(me) = Some(v),
)
}
fn import_simple_get_and_set(
&mut self,
module: &str,
name: &str,
params: &[ValType],
results: &[ValType],
import: Import,
get: impl Fn(&mut Self) -> Option<FuncIndex>,
set: impl Fn(&mut Self, FuncIndex),
) -> FuncIndex {
if let Some(idx) = get(self) {
return idx;
}
let ty = self.core_types.function(params, results);
let ty = EntityType::Function(ty);
self.core_imports.import(module, name, ty);
self.imports.push(import);
let idx = self.imported_funcs.push(None);
set(self, idx);
idx
}
fn import_prepare_call(
&mut self,
suffix: &str,
params: &[ValType],
memory: Option<MemoryIndex>,
) -> FuncIndex {
let ty = self.core_types.function(
&PREPARE_CALL_FIXED_PARAMS
.iter()
.copied()
.chain(params.iter().copied())
.collect::<Vec<_>>(),
&[],
);
self.core_imports.import(
"sync",
&format!("[prepare-call]{suffix}"),
EntityType::Function(ty),
);
let import = Import::PrepareCall {
memory: memory.map(|v| self.imported_memories[v].clone()),
};
self.imports.push(import);
self.imported_funcs.push(None)
}
fn import_sync_start_call(
&mut self,
suffix: &str,
callback: Option<FuncIndex>,
results: &[ValType],
) -> FuncIndex {
let ty = self
.core_types
.function(&[ValType::FUNCREF, ValType::I32], results);
self.core_imports.import(
"sync",
&format!("[start-call]{suffix}"),
EntityType::Function(ty),
);
let import = Import::SyncStartCall {
callback: callback
.map(|callback| self.imported_funcs.get(callback).unwrap().clone().unwrap()),
};
self.imports.push(import);
self.imported_funcs.push(None)
}
fn import_async_start_call(
&mut self,
suffix: &str,
callback: Option<FuncIndex>,
post_return: Option<FuncIndex>,
) -> FuncIndex {
self.import_simple_get_and_set(
"async",
&format!("[start-call]{suffix}"),
&[ValType::FUNCREF, ValType::I32, ValType::I32, ValType::I32],
&[ValType::I32],
Import::AsyncStartCall {
callback: callback
.map(|callback| self.imported_funcs.get(callback).unwrap().clone().unwrap()),
post_return: post_return.map(|post_return| {
self.imported_funcs
.get(post_return)
.unwrap()
.clone()
.unwrap()
}),
},
|me| {
me.imported_async_start_calls
.get(&(callback, post_return))
.copied()
},
|me, v| {
assert!(
me.imported_async_start_calls
.insert((callback, post_return), v)
.is_none()
)
},
)
}
fn import_future_transfer(&mut self) -> FuncIndex {
self.import_simple(
"future",
"transfer",
&[ValType::I32; 3],
&[ValType::I32],
Import::FutureTransfer,
|me| &mut me.imported_future_transfer,
)
}
fn import_stream_transfer(&mut self) -> FuncIndex {
self.import_simple(
"stream",
"transfer",
&[ValType::I32; 3],
&[ValType::I32],
Import::StreamTransfer,
|me| &mut me.imported_stream_transfer,
)
}
fn import_error_context_transfer(&mut self) -> FuncIndex {
self.import_simple(
"error-context",
"transfer",
&[ValType::I32; 3],
&[ValType::I32],
Import::ErrorContextTransfer,
|me| &mut me.imported_error_context_transfer,
)
}
fn import_resource_transfer_own(&mut self) -> FuncIndex {
self.import_simple(
"resource",
"transfer-own",
&[ValType::I32, ValType::I32, ValType::I32],
&[ValType::I32],
Import::ResourceTransferOwn,
|me| &mut me.imported_resource_transfer_own,
)
}
fn import_resource_transfer_borrow(&mut self) -> FuncIndex {
self.import_simple(
"resource",
"transfer-borrow",
&[ValType::I32, ValType::I32, ValType::I32],
&[ValType::I32],
Import::ResourceTransferBorrow,
|me| &mut me.imported_resource_transfer_borrow,
)
}
fn import_resource_enter_call(&mut self) -> FuncIndex {
self.import_simple(
"resource",
"enter-call",
&[],
&[],
Import::ResourceEnterCall,
|me| &mut me.imported_resource_enter_call,
)
}
fn import_resource_exit_call(&mut self) -> FuncIndex {
self.import_simple(
"resource",
"exit-call",
&[],
&[],
Import::ResourceExitCall,
|me| &mut me.imported_resource_exit_call,
)
}
fn import_enter_sync_call(&mut self) -> FuncIndex {
self.import_simple(
"async",
"enter-sync-call",
&[ValType::I32; 3],
&[],
Import::EnterSyncCall,
|me| &mut me.imported_enter_sync_call,
)
}
fn import_exit_sync_call(&mut self) -> FuncIndex {
self.import_simple(
"async",
"exit-sync-call",
&[],
&[],
Import::ExitSyncCall,
|me| &mut me.imported_exit_sync_call,
)
}
fn import_trap(&mut self) -> FuncIndex {
self.import_simple(
"runtime",
"trap",
&[ValType::I32],
&[],
Import::Trap,
|me| &mut me.imported_trap,
)
}
fn translate_helper(&mut self, helper: Helper) -> FunctionId {
*self.helper_funcs.entry(helper).or_insert_with(|| {
let ty = helper.core_type(self.types, &mut self.core_types);
let id = self.funcs.push(Function::new(None, ty));
self.helper_worklist.push((id, helper));
id
})
}
pub fn encode(&mut self) -> Vec<u8> {
let mut funcs = FunctionSection::new();
let mut exports = ExportSection::new();
let mut id_to_index = PrimaryMap::<FunctionId, FuncIndex>::new();
for (id, func) in self.funcs.iter() {
assert!(func.filled_in);
let idx = FuncIndex::from_u32(self.imported_funcs.next_key().as_u32() + id.as_u32());
let id2 = id_to_index.push(idx);
assert_eq!(id2, id);
funcs.function(func.ty);
if let Some(name) = &func.export {
exports.export(name, ExportKind::Func, idx.as_u32());
}
}
for (idx, name) in &self.exports {
exports.export(name, ExportKind::Func, *idx);
}
let mut code = CodeSection::new();
for (_, func) in self.funcs.iter() {
let mut body = Vec::new();
func.locals.len().encode(&mut body);
for (count, ty) in func.locals.iter() {
count.encode(&mut body);
ty.encode(&mut body);
}
for chunk in func.body.iter() {
match chunk {
Body::Raw(code) => {
body.extend_from_slice(code);
}
Body::Call(id) => {
Instruction::Call(id_to_index[*id].as_u32()).encode(&mut body);
}
Body::RefFunc(id) => {
Instruction::RefFunc(id_to_index[*id].as_u32()).encode(&mut body);
}
}
}
code.raw(&body);
}
let mut result = wasm_encoder::Module::new();
result.section(&self.core_types.section);
result.section(&self.core_imports);
result.section(&funcs);
result.section(&exports);
result.section(&code);
result.finish()
}
pub fn imports(&self) -> &[Import] {
&self.imports
}
}
#[derive(Clone)]
pub enum Import {
CoreDef(CoreDef),
Transcode {
op: Transcode,
from: CoreDef,
from64: bool,
to: CoreDef,
to64: bool,
},
ResourceTransferOwn,
ResourceTransferBorrow,
ResourceEnterCall,
ResourceExitCall,
PrepareCall {
memory: Option<CoreDef>,
},
SyncStartCall {
callback: Option<CoreDef>,
},
AsyncStartCall {
callback: Option<CoreDef>,
post_return: Option<CoreDef>,
},
FutureTransfer,
StreamTransfer,
ErrorContextTransfer,
Trap,
EnterSyncCall,
ExitSyncCall,
}
impl Options {
fn flat_types<'a>(
&self,
ty: &InterfaceType,
types: &'a ComponentTypesBuilder,
) -> Option<&'a [FlatType]> {
let flat = types.flat_types(ty)?;
match self.data_model {
DataModel::Gc {} => todo!("CM+GC"),
DataModel::LinearMemory(mem_opts) => Some(if mem_opts.memory64 {
flat.memory64
} else {
flat.memory32
}),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct FunctionId(u32);
cranelift_entity::entity_impl!(FunctionId);
struct Function {
filled_in: bool,
ty: u32,
locals: Vec<(u32, ValType)>,
export: Option<String>,
body: Vec<Body>,
}
enum Body {
Raw(Vec<u8>),
Call(FunctionId),
RefFunc(FunctionId),
}
impl Function {
fn new(export: Option<String>, ty: u32) -> Function {
Function {
filled_in: false,
ty,
locals: Vec::new(),
export,
body: Vec::new(),
}
}
}
impl Helper {
fn core_type(
&self,
types: &ComponentTypesBuilder,
core_types: &mut core_types::CoreTypes,
) -> u32 {
let mut params = Vec::new();
let mut results = Vec::new();
self.src.push_flat(&mut params, types);
match self.dst.loc {
HelperLocation::Stack => self.dst.push_flat(&mut results, types),
HelperLocation::Memory => params.push(self.dst.opts.data_model.unwrap_memory().ptr()),
HelperLocation::StructField | HelperLocation::ArrayElement => todo!("CM+GC"),
}
core_types.function(¶ms, &results)
}
}
impl HelperType {
fn push_flat(&self, dst: &mut Vec<ValType>, types: &ComponentTypesBuilder) {
match self.loc {
HelperLocation::Stack => {
for ty in self.opts.flat_types(&self.ty, types).unwrap() {
dst.push((*ty).into());
}
}
HelperLocation::Memory => {
dst.push(self.opts.data_model.unwrap_memory().ptr());
}
HelperLocation::StructField | HelperLocation::ArrayElement => todo!("CM+GC"),
}
}
}