use crate::decode::{ImportModule, LocalModule};
use crate::descriptor::{Descriptor, Function};
use crate::descriptors::WasmBindgenDescriptorsSection;
use crate::intrinsic::Intrinsic;
use crate::transforms::threads::ThreadCount;
use crate::{decode, wasm_conventions, Bindgen, PLACEHOLDER_MODULE};
use anyhow::{anyhow, bail, ensure, Error};
use std::collections::{BTreeSet, HashMap};
use std::str;
use walrus::ir::VisitorMut;
use walrus::{ConstExpr, ElementItems, ExportId, FunctionId, ImportId, MemoryId, Module};
use wasm_bindgen_shared::struct_function_export_name;
mod incoming;
mod nonstandard;
mod outgoing;
mod standard;
pub use self::nonstandard::*;
pub use self::standard::*;
struct Context<'a> {
start_found: bool,
module: &'a mut Module,
adapters: NonstandardWitSection,
aux: WasmBindgenAux,
function_exports: HashMap<String, (ExportId, FunctionId)>,
function_imports: HashMap<String, (ImportId, FunctionId)>,
memory: Option<MemoryId>,
vendor_prefixes: HashMap<String, Vec<String>>,
unique_crate_identifier: &'a str,
descriptors: HashMap<String, Descriptor>,
externref_enabled: bool,
thread_count: Option<ThreadCount>,
support_start: bool,
linked_modules: bool,
export_adapter_sigs: HashMap<AdapterId, (Vec<Descriptor>, Descriptor, Option<Descriptor>)>,
}
struct InstructionBuilder<'a, 'b> {
input: Vec<AdapterType>,
output: Vec<AdapterType>,
instructions: Vec<InstructionData>,
cx: &'a mut Context<'b>,
return_position: bool,
}
impl InstructionBuilder<'_, '_> {
fn ptr_ty(&self) -> AdapterType {
if self.cx.memory64() {
AdapterType::I64
} else {
AdapterType::I32
}
}
}
pub fn process(
bindgen: &mut Bindgen,
module: &mut Module,
programs: Vec<decode::Program>,
thread_count: Option<ThreadCount>,
) -> Result<(NonstandardWitSectionId, WasmBindgenAuxId), Error> {
let mut cx = Context {
adapters: Default::default(),
aux: Default::default(),
function_exports: Default::default(),
function_imports: Default::default(),
vendor_prefixes: Default::default(),
descriptors: Default::default(),
unique_crate_identifier: "",
memory: wasm_conventions::get_memory(module).ok(),
module,
start_found: false,
externref_enabled: bindgen.externref,
thread_count,
support_start: bindgen.emit_start,
linked_modules: bindgen.split_linked_modules,
export_adapter_sigs: Default::default(),
};
cx.init()?;
for program in programs {
cx.program(program)?;
}
if !cx.start_found {
cx.discover_main()?;
}
cx.find_exn_store();
cx.find_destroy_closure();
cx.verify()?;
cx.unexport_intrinsics();
let implements_set: std::collections::HashSet<_> = cx
.adapters
.implements
.iter()
.map(|(_, _, id)| *id)
.collect();
let mut adapter_exports: Vec<_> = cx
.adapters
.exports
.iter()
.filter(|(_, adapter_id)| {
!cx.aux.export_map.contains_key(adapter_id) && !implements_set.contains(adapter_id)
})
.map(|(export_id, _)| {
let export = cx.module.exports.get(*export_id);
let sort_key = match export.item {
walrus::ExportItem::Function(func_id) => {
let ty_id = cx.module.funcs.get(func_id).ty();
let ty = cx.module.types.get(ty_id);
format!("{:?}-{:?}", ty.params(), ty.results())
}
_ => String::new(),
};
(*export_id, sort_key, export.name.clone(), export.item)
})
.collect();
adapter_exports.sort_by(|a, b| a.1.cmp(&b.1));
let mut old_to_new: std::collections::HashMap<walrus::ExportId, walrus::ExportId> =
std::collections::HashMap::new();
for (old_export_id, _, name, item) in adapter_exports.iter() {
cx.module.exports.delete(*old_export_id);
let new_export_id = cx.module.exports.add(name, *item);
old_to_new.insert(*old_export_id, new_export_id);
if let Some(pos) = cx
.adapters
.exports
.iter()
.position(|(eid, _)| eid == old_export_id)
{
cx.adapters.exports[pos].0 = new_export_id;
}
}
for adapter in cx.adapters.adapters.values_mut() {
let instructions = match &mut adapter.kind {
AdapterKind::Local { instructions } => instructions,
AdapterKind::Import { .. } => continue,
};
for instr in instructions {
if let Instruction::CallExport(export_id) = &mut instr.instr {
if let Some(&new_id) = old_to_new.get(export_id) {
*export_id = new_id;
}
}
}
}
let adapters = cx.module.customs.add(cx.adapters);
let aux = cx.module.customs.add(cx.aux);
Ok((adapters, aux))
}
impl<'a> Context<'a> {
fn init(&mut self) -> Result<(), Error> {
self.aux.stack_pointer = wasm_conventions::get_stack_pointer(self.module);
for export in self.module.exports.iter() {
if let walrus::ExportItem::Function(f) = export.item {
self.function_exports
.insert(export.name.clone(), (export.id(), f));
}
}
let mut duplicate_import_map = HashMap::new();
let mut imports_to_delete = BTreeSet::new();
for import in self.module.imports.iter() {
if import.module != PLACEHOLDER_MODULE {
continue;
}
let f = match import.kind {
walrus::ImportKind::Function(f) => f,
_ => continue,
};
match self.function_imports.get(&import.name) {
Some((_, prev_func)) => {
imports_to_delete.insert(import.id());
duplicate_import_map.insert(f, *prev_func);
}
None => {
self.function_imports
.insert(import.name.clone(), (import.id(), f));
}
}
}
self.add_aux_import_to_import_map(
"__wbindgen_object_clone_ref",
vec![Descriptor::Ref(Box::new(Descriptor::Externref))],
Descriptor::Externref,
AuxImport::Intrinsic(Intrinsic::ObjectCloneRef),
)?;
self.add_aux_import_to_import_map(
"__wbindgen_object_drop_ref",
vec![Descriptor::Externref],
Descriptor::Unit,
AuxImport::Intrinsic(Intrinsic::ObjectDropRef),
)?;
for import in imports_to_delete {
self.module.imports.delete(import);
}
self.inject_externref_initialization()?;
if let Some(custom) = self
.module
.customs
.delete_typed::<WasmBindgenDescriptorsSection>()
{
let WasmBindgenDescriptorsSection {
descriptors,
cast_imports,
} = *custom;
self.descriptors.extend(descriptors);
let mut sorted_casts: Vec<_> = cast_imports
.into_iter()
.map(|(descriptor, orig_func_ids)| {
let signature = descriptor.unwrap_function();
let [arg] = &signature.arguments[..] else {
unreachable!("Cast function must take exactly one argument");
};
let sig_comment = format!("{arg:?} -> {:?}", &signature.ret);
(sig_comment, signature, orig_func_ids)
})
.collect();
sorted_casts.sort_by(|a, b| a.0.cmp(&b.0));
for (idx, (sig_comment, signature, orig_func_ids)) in
sorted_casts.into_iter().enumerate()
{
let import_name = format!("__wbindgen_cast_{:016x}", idx + 1);
let ty = self.module.funcs.get(orig_func_ids[0]).ty();
let (import_func_id, import_id) =
self.module
.add_import_func(PLACEHOLDER_MODULE, &import_name, ty);
self.module.funcs.get_mut(import_func_id).name = Some(sig_comment.clone());
let adapter_id =
self.import_adapter(import_id, signature, AdapterJsImportKind::Normal)?;
self.aux
.import_map
.insert(adapter_id, AuxImport::Cast { sig_comment });
duplicate_import_map
.extend(orig_func_ids.into_iter().map(|id| (id, import_func_id)));
}
}
self.handle_duplicate_imports(&duplicate_import_map);
self.aux.thread_destroy = self.thread_destroy();
Ok(())
}
fn handle_duplicate_imports(&mut self, map: &HashMap<FunctionId, FunctionId>) {
struct Replace<'a> {
map: &'a HashMap<FunctionId, FunctionId>,
}
impl VisitorMut for Replace<'_> {
fn visit_function_id_mut(&mut self, function: &mut FunctionId) {
if let Some(replacement) = self.map.get(function) {
*function = *replacement;
}
}
}
let mut replace = Replace { map };
for (_id, func) in self.module.funcs.iter_local_mut() {
let entry = func.entry_block();
walrus::ir::dfs_pre_order_mut(&mut replace, func, entry);
}
for elems in self.module.elements.iter_mut() {
match &mut elems.items {
ElementItems::Functions(funcs) => {
for func in funcs.iter_mut() {
replace.visit_function_id_mut(func);
}
}
ElementItems::Expressions(_, exprs) => {
for expr in exprs {
if let ConstExpr::RefFunc(func) = expr {
replace.visit_function_id_mut(func);
}
}
}
}
}
}
fn discover_main(&mut self) -> Result<(), Error> {
let main_id = self
.module
.exports
.iter()
.filter_map(|export| match export.item {
walrus::ExportItem::Function(id) => Some((export, self.module.funcs.get(id))),
_ => None,
})
.find(|(export, func)| {
use walrus::ValType::I32;
let name_matches = export.name == "main";
let ty = self.module.types.get(func.ty());
let type_matches = ty.params() == [I32, I32] && ty.results() == [I32];
let unknown = !self
.adapters
.exports
.iter()
.any(|(export_id, _)| *export_id == export.id());
name_matches && type_matches && unknown
})
.map(|(_, func)| func.id());
let main_id = match main_id {
Some(x) => x,
None => return Ok(()),
};
let mut wrapper = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]);
wrapper
.func_body()
.i32_const(0)
.i32_const(0)
.call(main_id)
.drop()
.return_();
let wrapper = wrapper.finish(vec![], &mut self.module.funcs);
self.add_start_function(wrapper)?;
Ok(())
}
fn inject_externref_initialization(&mut self) -> Result<(), Error> {
if !self.externref_enabled {
return Ok(());
}
let ty = self.module.types.add(&[], &[]);
let (import, import_id) =
self.module
.add_import_func(PLACEHOLDER_MODULE, "__wbindgen_init_externref_table", ty);
if self.module.start.is_some() {
let builder = wasm_conventions::get_or_insert_start_builder(self.module);
builder.func_body().call_at(0, import);
} else {
self.module.start = Some(import);
}
let adapter_id = self.import_adapter(
import_id,
Function {
shim_idx: 0,
arguments: vec![],
ret: Descriptor::Unit,
inner_ret: None,
},
AdapterJsImportKind::Normal,
)?;
self.aux.import_map.insert(
adapter_id,
AuxImport::Intrinsic(Intrinsic::InitExternrefTable),
);
Ok(())
}
fn link_module(
&mut self,
id: ImportId,
module: &decode::ImportModule,
offset: usize,
local_modules: &[LocalModule],
inline_js: &[&str],
) -> Result<(), Error> {
let descriptor = Function {
shim_idx: 0,
arguments: Vec::new(),
ret: Descriptor::String,
inner_ret: None,
};
let id = self.import_adapter(id, descriptor, AdapterJsImportKind::Normal)?;
let (path, content) = match module {
decode::ImportModule::Named(n) => (
format!("snippets/{n}"),
local_modules
.iter()
.find(|m| m.identifier == *n)
.map(|m| m.contents),
),
decode::ImportModule::RawNamed(n) => (n.to_string(), None),
decode::ImportModule::Inline(idx) => (
format!(
"snippets/{}/inline{}.js",
self.unique_crate_identifier,
*idx as usize + offset
),
Some(inline_js[*idx as usize]),
),
};
self.aux
.import_map
.insert(id, AuxImport::LinkTo(path, content.map(str::to_string)));
Ok(())
}
fn program(&mut self, program: decode::Program<'a>) -> Result<(), Error> {
self.unique_crate_identifier = program.unique_crate_identifier;
let decode::Program {
exports,
enums,
imports,
structs,
typescript_custom_sections,
local_modules,
inline_js,
unique_crate_identifier,
package_json,
linked_modules,
} = program;
for module in local_modules
.iter()
.filter(|module| self.linked_modules || !module.linked_module)
{
if let Some(prev) = self
.aux
.local_modules
.insert(module.identifier.to_string(), module.contents.to_string())
{
assert_eq!(prev, module.contents);
}
}
if let Some(s) = package_json {
self.aux.package_jsons.insert(s.into());
}
for export in exports {
self.export(export)?;
}
let offset = self
.aux
.snippets
.get(unique_crate_identifier)
.map(|s| s.len())
.unwrap_or(0);
for module in linked_modules {
if let Some((id, _)) = self.function_imports.remove(module.link_function_name) {
self.link_module(
id,
&module.module,
offset,
&local_modules[..],
&inline_js[..],
)?;
}
}
for import in imports.iter() {
if let decode::ImportKind::Type(ty) = &import.kind {
if ty.vendor_prefixes.is_empty() {
continue;
}
self.vendor_prefixes
.entry(ty.name.to_string())
.or_default()
.extend(ty.vendor_prefixes.iter().map(|s| s.to_string()));
}
}
for import in imports {
self.import(import)?;
}
for enum_ in enums {
self.enum_(enum_)?;
}
for struct_ in structs {
self.struct_(struct_)?;
}
self.aux
.extra_typescript
.extend(typescript_custom_sections.iter().map(|s| s.to_string()));
self.aux
.snippets
.entry(unique_crate_identifier.to_string())
.or_default()
.extend(inline_js.iter().map(|s| s.to_string()));
Ok(())
}
fn export(&mut self, export: decode::Export<'_>) -> Result<(), Error> {
let wasm_name = match &export.class {
Some(class) => struct_function_export_name(class, export.function.name),
None => {
let base_name = export.function.name.to_string();
if let Some(ref ns) = export.js_namespace {
format!("{}__{base_name}", ns.join("__"))
} else {
base_name
}
}
};
let mut descriptor = match self.descriptors.remove(&wasm_name) {
None => return Ok(()),
Some(d) => d.unwrap_function(),
};
let Some((export_id, id)) = self.function_exports.get(&wasm_name).copied() else {
bail!("{wasm_name} symbol is missing, \
may be because there are multiple exports with the same name but different signatures, \
and discarded by wasm-ld.");
};
match export.start {
decode::StartKind::Public => {
self.add_start_function(id)?;
}
decode::StartKind::Private => {
self.add_start_function(id)?;
return Ok(());
}
decode::StartKind::None => {}
}
let classless_this = matches!(
&export.method_kind,
decode::MethodKind::Operation(op) if matches!(op.kind, decode::OperationKind::RegularThis)
);
let kind = match export.class {
Some(class) => {
let class = class.to_string();
match export.method_kind {
decode::MethodKind::Constructor => {
verify_constructor_return(&class, &descriptor.ret)?;
AuxExportKind::Constructor(class)
}
decode::MethodKind::Operation(op) => {
if !op.is_static {
descriptor.arguments.insert(0, Descriptor::I32);
}
let (name, kind) = match op.kind {
decode::OperationKind::Getter(f) => (f, AuxExportedMethodKind::Getter),
decode::OperationKind::Setter(f) => (f, AuxExportedMethodKind::Setter),
_ => (export.function.name, AuxExportedMethodKind::Method),
};
AuxExportKind::Method {
class,
name: name.to_owned(),
receiver: if op.is_static {
AuxReceiverKind::None
} else if export.consumed {
AuxReceiverKind::Owned
} else {
AuxReceiverKind::Borrowed
},
kind,
}
}
}
}
_ => {
if classless_this {
AuxExportKind::FunctionThis(export.function.name.to_string())
} else {
AuxExportKind::Function(export.function.name.to_string())
}
}
};
let args = Some(
export
.function
.args
.into_iter()
.map(|v| AuxFunctionArgumentData {
name: v.name,
ty_override: v.ty_override.map(String::from),
optional: v.optional,
desc: v.desc.map(String::from),
})
.collect::<Vec<_>>(),
);
let id = self.export_adapter(export_id, descriptor)?;
self.aux.export_map.insert(
id,
AuxExport {
debug_name: wasm_name,
comments: concatenate_comments(&export.comments),
args,
asyncness: export.function.asyncness,
kind,
js_namespace: export
.js_namespace
.map(|ns| ns.iter().map(|s| s.to_string()).collect()),
generate_typescript: export.function.generate_typescript,
generate_jsdoc: export.function.generate_jsdoc,
variadic: export.function.variadic,
fn_ret_ty_override: export.function.ret_ty_override.map(String::from),
fn_ret_desc: export.function.ret_desc.map(String::from),
},
);
Ok(())
}
fn add_start_function(&mut self, id: FunctionId) -> Result<(), Error> {
self.start_found = true;
if !self.support_start {
return Ok(());
}
if let Some(thread_count) = self.thread_count {
let builder = wasm_conventions::get_or_insert_start_builder(self.module);
thread_count.wrap_start(builder, id);
} else if self.module.start.is_some() {
let builder = wasm_conventions::get_or_insert_start_builder(self.module);
builder.func_body().call(id);
} else {
self.module.start = Some(id);
}
Ok(())
}
fn import(&mut self, import: decode::Import<'_>) -> Result<(), Error> {
match &import.kind {
decode::ImportKind::Function(_) => self.import_function(import),
decode::ImportKind::Static(_) => self.import_static(import),
decode::ImportKind::String(s) => self.import_string(s),
decode::ImportKind::Type(_) => self.import_type(import),
decode::ImportKind::Enum(e) => self.string_enum(e),
}
}
fn import_function(&mut self, import: decode::Import<'_>) -> Result<(), Error> {
let decode::ImportKind::Function(function) = import.kind else {
unreachable!();
};
let decode::ImportFunction {
shim,
catch,
variadic,
method,
structural,
function,
assert_no_shim,
} = function;
let generate_typescript = import.generate_typescript;
let (import_id, _id) = match self.function_imports.get(shim) {
Some(pair) => *pair,
None => {
if let Some(reexport_name) = import.reexport {
self.aux.reexports.insert(
reexport_name,
(
self.determine_import(
&import.module,
&import.js_namespace,
function.name,
)?,
generate_typescript,
),
);
}
return Ok(());
}
};
let descriptor = match self.descriptors.remove(shim) {
None => {
return Ok(());
}
Some(d) => d.unwrap_function(),
};
let (id, aux_import) = match method {
Some(data) => {
let class =
self.determine_import(&import.module, &import.js_namespace, data.class)?;
match data.kind {
decode::MethodKind::Constructor => {
let id = self.import_adapter(
import_id,
descriptor,
AdapterJsImportKind::Constructor,
)?;
(id, AuxImport::Value(AuxValue::Bare(class)))
}
decode::MethodKind::Operation(op) => {
let (import, method) =
self.determine_import_op(class, &function, structural, op)?;
let kind = if method {
AdapterJsImportKind::Method
} else {
AdapterJsImportKind::Normal
};
(self.import_adapter(import_id, descriptor, kind)?, import)
}
}
}
None => {
let id = self.import_adapter(import_id, descriptor, AdapterJsImportKind::Normal)?;
let aux_import = match import.module {
Some(ImportModule::RawNamed(PLACEHOLDER_MODULE)) => {
let intrinsic = function.name.parse()?;
if let Intrinsic::FunctionTable = intrinsic {
self.aux.function_table = self.module.tables.main_function_table()?;
}
if let Intrinsic::Reinit = intrinsic {
self.aux.uses_reinit = true;
}
AuxImport::Intrinsic(intrinsic)
}
_ => {
let js_import = self.determine_import(
&import.module,
&import.js_namespace,
function.name,
)?;
if let Some(reexport_name) = import.reexport {
self.aux
.reexports
.insert(reexport_name, (js_import.clone(), generate_typescript));
}
AuxImport::Value(AuxValue::Bare(js_import))
}
};
(id, aux_import)
}
};
if variadic {
self.aux.imports_with_variadic.insert(id);
}
let adapter = self.adapters.implements.last().unwrap().2;
if catch {
self.aux.imports_with_catch.insert(adapter);
if self.aux.exn_store.is_none() {
self.find_exn_store();
}
}
if assert_no_shim {
self.aux.imports_with_assert_no_shim.insert(adapter);
}
self.aux.import_map.insert(id, aux_import);
Ok(())
}
fn determine_import_op(
&mut self,
mut class: JsImport,
function: &decode::Function<'_>,
structural: bool,
op: decode::Operation<'_>,
) -> Result<(AuxImport, bool), Error> {
match op.kind {
decode::OperationKind::Regular => {
if op.is_static {
Ok((
AuxImport::ValueWithThis(class, function.name.to_string()),
false,
))
} else if structural {
Ok((
AuxImport::StructuralMethod(function.name.to_string()),
false,
))
} else {
class.fields.push("prototype".to_string());
class.fields.push(function.name.to_string());
Ok((AuxImport::Value(AuxValue::Bare(class)), true))
}
}
decode::OperationKind::RegularThis => {
bail!("RegularThis operation kind should only appear on exports, not imports")
}
decode::OperationKind::Getter(field) => {
if structural {
if op.is_static {
Ok((
AuxImport::StructuralClassGetter(class, field.to_string()),
false,
))
} else {
Ok((AuxImport::StructuralGetter(field.to_string()), false))
}
} else {
let val = if op.is_static {
AuxValue::ClassGetter(class, field.to_string())
} else {
AuxValue::Getter(class, field.to_string())
};
Ok((AuxImport::Value(val), true))
}
}
decode::OperationKind::Setter(field) => {
if structural {
if op.is_static {
Ok((
AuxImport::StructuralClassSetter(class, field.to_string()),
false,
))
} else {
Ok((AuxImport::StructuralSetter(field.to_string()), false))
}
} else {
let val = if op.is_static {
AuxValue::ClassSetter(class, field.to_string())
} else {
AuxValue::Setter(class, field.to_string())
};
Ok((AuxImport::Value(val), true))
}
}
decode::OperationKind::IndexingGetter => {
if !structural {
bail!("indexing getters must always be structural");
}
if op.is_static {
Ok((AuxImport::IndexingGetterOfClass(class), false))
} else {
Ok((AuxImport::IndexingGetterOfObject, false))
}
}
decode::OperationKind::IndexingSetter => {
if !structural {
bail!("indexing setters must always be structural");
}
if op.is_static {
Ok((AuxImport::IndexingSetterOfClass(class), false))
} else {
Ok((AuxImport::IndexingSetterOfObject, false))
}
}
decode::OperationKind::IndexingDeleter => {
if !structural {
bail!("indexing deleters must always be structural");
}
if op.is_static {
Ok((AuxImport::IndexingDeleterOfClass(class), false))
} else {
Ok((AuxImport::IndexingDeleterOfObject, false))
}
}
}
}
fn import_static(&mut self, import: decode::Import<'_>) -> Result<(), Error> {
let decode::ImportKind::Static(static_) = import.kind else {
unreachable!();
};
let generate_typescript = import.generate_typescript;
let (import_id, _id) = match self.function_imports.get(static_.shim) {
Some(pair) => *pair,
None => {
if let Some(reexport_name) = import.reexport {
self.aux.reexports.insert(
reexport_name,
(
self.determine_import(
&import.module,
&import.js_namespace,
static_.name,
)?,
generate_typescript,
),
);
}
return Ok(());
}
};
let descriptor = match self.descriptors.remove(static_.shim) {
None => return Ok(()),
Some(d) => d,
};
let optional = matches!(descriptor, Descriptor::Option(_));
let id = self.import_adapter(
import_id,
Function {
arguments: Vec::new(),
shim_idx: 0,
ret: descriptor,
inner_ret: None,
},
AdapterJsImportKind::Normal,
)?;
let js = self.determine_import(&import.module, &import.js_namespace, static_.name)?;
if let Some(reexport_name) = import.reexport {
self.aux
.reexports
.insert(reexport_name, (js.clone(), generate_typescript));
}
self.aux
.import_map
.insert(id, AuxImport::Static { js, optional });
Ok(())
}
fn import_string(&mut self, string: &decode::ImportString<'_>) -> Result<(), Error> {
let (import_id, _id) = match self.function_imports.get(string.shim) {
Some(pair) => *pair,
None => return Ok(()),
};
let id = self.import_adapter(
import_id,
Function {
arguments: Vec::new(),
shim_idx: 0,
ret: Descriptor::Externref,
inner_ret: None,
},
AdapterJsImportKind::Normal,
)?;
self.aux
.import_map
.insert(id, AuxImport::String(string.string.to_owned()));
Ok(())
}
fn import_type(&mut self, import: decode::Import<'_>) -> Result<(), Error> {
let decode::ImportKind::Type(type_) = import.kind else {
unreachable!();
};
let generate_typescript = import.generate_typescript;
let (import_id, _id) = match self.function_imports.get(type_.instanceof_shim) {
Some(pair) => *pair,
None => {
if let Some(reexport_name) = import.reexport {
self.aux.reexports.insert(
reexport_name,
(
self.determine_import(
&import.module,
&import.js_namespace,
type_.name,
)?,
generate_typescript,
),
);
}
return Ok(());
}
};
let id = self.import_adapter(
import_id,
Function {
arguments: vec![Descriptor::Ref(Box::new(Descriptor::Externref))],
shim_idx: 0,
ret: Descriptor::Boolean,
inner_ret: None,
},
AdapterJsImportKind::Normal,
)?;
let js_import = self.determine_import(&import.module, &import.js_namespace, type_.name)?;
if let Some(reexport_name) = import.reexport {
self.aux
.reexports
.insert(reexport_name, (js_import.clone(), generate_typescript));
}
self.aux
.import_map
.insert(id, AuxImport::Instanceof(js_import));
Ok(())
}
fn string_enum(&mut self, string_enum: &decode::StringEnum<'_>) -> Result<(), Error> {
let aux = AuxStringEnum {
name: string_enum.name.to_string(),
comments: concatenate_comments(&string_enum.comments),
variant_values: string_enum
.variant_values
.iter()
.map(|v| v.to_string())
.collect(),
generate_typescript: string_enum.generate_typescript,
js_namespace: string_enum
.js_namespace
.as_ref()
.map(|ns| ns.iter().map(|s| s.to_string()).collect()),
};
let mut result = Ok(());
self.aux
.string_enums
.entry(aux.name.clone())
.and_modify(|existing| {
result = Err(anyhow!("duplicate string enums:\n{existing:?}\n{aux:?}"));
})
.or_insert(aux);
result
}
fn enum_(&mut self, enum_: decode::Enum<'_>) -> Result<(), Error> {
let signed = enum_.signed;
let qualified_name =
wasm_bindgen_shared::qualified_name(enum_.js_namespace.as_deref(), enum_.name);
let aux = AuxEnum {
name: enum_.name.to_string(),
qualified_name: qualified_name.clone(),
comments: concatenate_comments(&enum_.comments),
variants: enum_
.variants
.iter()
.map(|v| {
let value = if signed {
v.value as i32 as i64
} else {
v.value as i64
};
(v.name.to_string(), value, concatenate_comments(&v.comments))
})
.collect(),
generate_typescript: enum_.generate_typescript,
js_namespace: enum_
.js_namespace
.as_ref()
.map(|ns| ns.iter().map(|s| s.to_string()).collect()),
private: enum_.private,
};
let mut result = Ok(());
self.aux
.enums
.entry(qualified_name)
.and_modify(|existing| {
result = Err(anyhow!("duplicate enums:\n{existing:?}\n{aux:?}"));
})
.or_insert(aux);
result
}
fn struct_(&mut self, struct_: decode::Struct<'_>) -> Result<(), Error> {
let qualified_name =
wasm_bindgen_shared::qualified_name(struct_.js_namespace.as_deref(), struct_.name);
let rust_name = struct_.rust_name;
for field in struct_.fields {
let getter = wasm_bindgen_shared::struct_field_get(&qualified_name, field.name);
let setter = wasm_bindgen_shared::struct_field_set(&qualified_name, field.name);
let descriptor = match self.descriptors.remove(&getter) {
None => continue,
Some(d) => d,
};
let (getter_id, _) = self.function_exports[&getter];
let getter_descriptor = Function {
arguments: vec![Descriptor::I32],
shim_idx: 0,
ret: descriptor.clone(),
inner_ret: Some(descriptor.clone()),
};
let getter_id = self.export_adapter(getter_id, getter_descriptor)?;
self.aux.export_map.insert(
getter_id,
AuxExport {
debug_name: format!("getter for `{}::{}`", struct_.name, field.name),
args: None,
asyncness: false,
comments: concatenate_comments(&field.comments),
kind: AuxExportKind::Method {
class: rust_name.to_string(),
name: field.name.to_string(),
receiver: AuxReceiverKind::Borrowed,
kind: AuxExportedMethodKind::Getter,
},
js_namespace: None,
generate_typescript: field.generate_typescript,
generate_jsdoc: field.generate_jsdoc,
variadic: false,
fn_ret_ty_override: None,
fn_ret_desc: None,
},
);
if field.readonly {
continue;
}
let (setter_id, _) = self.function_exports[&setter];
let setter_descriptor = Function {
arguments: vec![Descriptor::I32, descriptor],
shim_idx: 0,
ret: Descriptor::Unit,
inner_ret: None,
};
let setter_id = self.export_adapter(setter_id, setter_descriptor)?;
self.aux.export_map.insert(
setter_id,
AuxExport {
debug_name: format!("setter for `{}::{}`", struct_.name, field.name),
args: None,
asyncness: false,
comments: concatenate_comments(&field.comments),
kind: AuxExportKind::Method {
class: rust_name.to_string(),
name: field.name.to_string(),
receiver: AuxReceiverKind::Borrowed,
kind: AuxExportedMethodKind::Setter,
},
js_namespace: None,
generate_typescript: field.generate_typescript,
generate_jsdoc: field.generate_jsdoc,
variadic: false,
fn_ret_ty_override: None,
fn_ret_desc: None,
},
);
}
let aux = AuxStruct {
name: struct_.name.to_string(),
rust_name: rust_name.to_string(),
qualified_name: qualified_name.clone(),
comments: concatenate_comments(&struct_.comments),
is_inspectable: struct_.is_inspectable,
generate_typescript: struct_.generate_typescript,
js_namespace: struct_
.js_namespace
.as_ref()
.map(|ns| ns.iter().map(|s| s.to_string()).collect()),
private: struct_.private,
};
self.aux.structs.push(aux);
let ptr_desc = if self.memory64() {
Descriptor::I64AsF64
} else {
Descriptor::I32
};
let wrap_constructor = wasm_bindgen_shared::new_function(&qualified_name);
self.add_aux_import_to_import_map(
&wrap_constructor,
vec![ptr_desc.clone()],
Descriptor::Externref,
AuxImport::WrapInExportedClass(rust_name.to_string()),
)?;
let unwrap_fn = wasm_bindgen_shared::unwrap_function(&qualified_name);
self.add_aux_import_to_import_map(
&unwrap_fn,
vec![Descriptor::Ref(Box::new(Descriptor::Externref))],
ptr_desc,
AuxImport::UnwrapExportedClass(rust_name.to_string()),
)?;
Ok(())
}
fn add_aux_import_to_import_map(
&mut self,
fn_name: &str,
arguments: Vec<Descriptor>,
ret: Descriptor,
aux_import: AuxImport,
) -> Result<(), Error> {
if let Some((import_id, _id)) = self.function_imports.get(fn_name).cloned() {
let signature = Function {
shim_idx: 0,
arguments,
ret,
inner_ret: None,
};
let id = self.import_adapter(import_id, signature, AdapterJsImportKind::Normal)?;
self.aux.import_map.insert(id, aux_import);
}
Ok(())
}
fn determine_import(
&self,
module: &Option<ImportModule<'_>>,
js_namespace: &Option<Vec<String>>,
item: &str,
) -> Result<JsImport, Error> {
let vendor_prefixes = self.vendor_prefixes.get(item);
if let Some(vendor_prefixes) = vendor_prefixes {
assert!(!vendor_prefixes.is_empty());
if let Some(decode::ImportModule::Inline(_) | decode::ImportModule::Named(_)) = module {
bail!(
"local JS snippets do not support vendor prefixes for \
the import of `{item}` with a polyfill of `{}`",
&vendor_prefixes[0]
);
}
if let Some(decode::ImportModule::RawNamed(module)) = module {
bail!(
"import of `{item}` from `{module}` has a polyfill of `{}` listed, but
vendor prefixes aren't supported when importing from modules",
&vendor_prefixes[0],
);
}
if let Some(ns) = js_namespace {
bail!(
"import of `{item}` through js namespace `{}` isn't supported \
right now when it lists a polyfill",
ns.join(".")
);
}
return Ok(JsImport {
name: JsImportName::VendorPrefixed {
name: item.to_string(),
prefixes: vendor_prefixes.clone(),
},
fields: Vec::new(),
});
}
let (name, fields) = match js_namespace {
Some(ref ns) => {
let mut tail = ns[1..].to_owned();
tail.push(item.to_string());
(ns[0].to_owned(), tail)
}
None => (item.to_owned(), Vec::new()),
};
let name = match module {
Some(decode::ImportModule::Named(module)) => JsImportName::LocalModule {
module: module.to_string(),
name,
},
Some(decode::ImportModule::RawNamed(module)) => JsImportName::Module {
module: module.to_string(),
name,
},
Some(decode::ImportModule::Inline(idx)) => {
let offset = self
.aux
.snippets
.get(self.unique_crate_identifier)
.map(|s| s.len())
.unwrap_or(0);
JsImportName::InlineJs {
unique_crate_identifier: self.unique_crate_identifier.to_string(),
snippet_idx_in_crate: *idx as usize + offset,
name,
}
}
None => JsImportName::Global { name },
};
Ok(JsImport { name, fields })
}
fn verify(&self) -> Result<(), Error> {
let mut implemented = HashMap::new();
for (core, _, adapter) in self.adapters.implements.iter() {
implemented.insert(core, adapter);
}
for import in self.module.imports.iter() {
if import.module != PLACEHOLDER_MODULE {
continue;
}
match import.kind {
walrus::ImportKind::Function(_) => {}
_ => bail!("import from `{PLACEHOLDER_MODULE}` was not a function"),
}
if import.name == "__wbindgen_describe" || import.name == "__wbindgen_describe_cast" {
continue;
}
if implemented.remove(&import.id()).is_none() {
bail!("import of `{}` doesn't have an adapter listed", import.name);
}
}
if !implemented.is_empty() {
bail!("more implementations listed than imports");
}
let mut imports_counted = 0;
for (id, adapter) in self.adapters.adapters.iter() {
let name = match &adapter.kind {
AdapterKind::Import { name, .. } => name,
AdapterKind::Local { .. } => continue,
};
if !self.aux.import_map.contains_key(id) {
bail!("import of `{name}` doesn't have an import map item listed");
}
imports_counted += 1;
}
if self.aux.import_map.len() != imports_counted {
bail!("import map is larger than the number of imports");
}
for id in self.aux.export_map.keys() {
ensure!(
self.adapters.adapters.contains_key(id),
"export map has an entry that the adapters map does not"
);
}
Ok(())
}
fn import_adapter(
&mut self,
import: ImportId,
mut signature: Function,
kind: AdapterJsImportKind,
) -> Result<AdapterId, Error> {
let import = self.module.imports.get(import);
let import_name = import.name.clone();
let import_id = import.id();
let core_id = match import.kind {
walrus::ImportKind::Function(f) => f,
_ => bail!("bound import must be assigned to function"),
};
let memory64 = self.memory64();
self.normalize_memory64_signature(&mut signature, core_id);
let mut ret = self.instruction_builder(true);
ret.incoming(&signature.ret)?;
let uses_retptr = ret.output.len() > 1;
let mut args = ret.cx.instruction_builder(false);
if uses_retptr {
args.input.push(if memory64 {
AdapterType::F64
} else {
AdapterType::I32
});
}
for arg in signature.arguments.iter() {
args.outgoing(arg)?;
}
let mut instructions = args.instructions;
let f = args.cx.adapters.append(
args.output,
ret.input,
vec![],
AdapterKind::Import {
name: import_name,
kind,
},
);
instructions.push(InstructionData {
instr: Instruction::CallAdapter(f),
stack_change: StackChange::Unknown,
});
instructions.extend(ret.instructions);
let results = if uses_retptr {
let mem = args.cx.memory()?;
for (i, ty) in ret.output.into_iter().enumerate().rev() {
instructions.push(InstructionData {
instr: Instruction::StoreRetptr { offset: i, ty, mem },
stack_change: StackChange::Modified {
pushed: 0,
popped: 1,
},
});
}
Vec::new()
} else {
ret.output
};
let id = args.cx.adapters.append(
args.input,
results,
vec![],
AdapterKind::Local { instructions },
);
args.cx.adapters.implements.push((import_id, core_id, id));
Ok(f)
}
fn export_adapter(
&mut self,
mut export: ExportId,
mut signature: Function,
) -> Result<AdapterId, Error> {
let sig_key = (
signature.arguments.clone(),
signature.ret.clone(),
signature.inner_ret.clone(),
);
if let Some((_, adapter_id)) = self
.adapters
.exports
.iter()
.find(|(export_id, _)| *export_id == export)
{
if self.export_adapter_sigs.get(adapter_id) == Some(&sig_key) {
return Ok(*adapter_id);
} else {
let old_export = self.module.exports.get(export);
let name = format!("{}_{}", old_export.name, self.adapters.exports.len());
let func_id = match old_export.item {
walrus::ExportItem::Function(f) => f,
_ => unreachable!(),
};
export = self.module.exports.add(&name, func_id);
}
}
let core_id = match self.module.exports.get(export).item {
walrus::ExportItem::Function(f) => f,
_ => bail!("bound export must be assigned to function"),
};
self.normalize_memory64_signature(&mut signature, core_id);
let mut args = self.instruction_builder(false);
for arg in signature.arguments.iter() {
args.incoming(arg)?;
}
let inner_ret_output = if let Some(sig_inner_ret) = &signature.inner_ret {
let mut inner_ret = args.cx.instruction_builder(true);
inner_ret.outgoing(sig_inner_ret)?;
inner_ret.output
} else {
vec![]
};
let mut ret = args.cx.instruction_builder(true);
ret.outgoing(&signature.ret)?;
let uses_retptr = ret.input.len() > 1;
let mut instructions = Vec::new();
if uses_retptr {
let size = ret.input.iter().fold(0, |sum, ty| {
let size = match ty {
AdapterType::I32 => 4,
AdapterType::I64 => 8,
AdapterType::F32 => 4,
AdapterType::F64 => 8,
_ => panic!("unsupported type in retptr {ty:?}"),
};
let sum_rounded_up = (sum + (size - 1)) & (!(size - 1));
sum_rounded_up + size
});
let size = (size + 15) & (!15);
instructions.push(InstructionData {
instr: Instruction::Retptr { size },
stack_change: StackChange::Modified {
pushed: 1,
popped: 0,
},
});
}
instructions.extend(args.instructions);
instructions.push(InstructionData {
instr: Instruction::CallExport(export),
stack_change: StackChange::Unknown,
});
if uses_retptr {
let mem = ret.cx.memory()?;
let mut unpacker = StructUnpacker::new();
for ty in ret.input.into_iter() {
let offset = unpacker.read_ty(&ty)?;
instructions.push(InstructionData {
instr: Instruction::LoadRetptr { offset, ty, mem },
stack_change: StackChange::Modified {
pushed: 1,
popped: 0,
},
});
}
}
instructions.extend(ret.instructions);
let id = ret.cx.adapters.append(
args.input,
ret.output,
inner_ret_output,
AdapterKind::Local { instructions },
);
self.adapters.exports.push((export, id));
self.export_adapter_sigs.insert(id, sig_key);
Ok(id)
}
fn normalize_memory64_signature(&self, signature: &mut Function, core_id: FunctionId) {
if !self.memory64() {
return;
}
let ty = self.module.funcs.get(core_id).ty();
let (params, results) = self.module.types.params_results(ty);
for (descriptor, wasm) in signature.arguments.iter_mut().zip(params.iter().copied()) {
Self::normalize_memory64_descriptor(descriptor, wasm);
}
if let [result] = results {
Self::normalize_memory64_descriptor(&mut signature.ret, *result);
}
}
fn normalize_memory64_descriptor(descriptor: &mut Descriptor, wasm: walrus::ValType) {
if wasm != walrus::ValType::F64 {
return;
}
if let Descriptor::Option(inner) = descriptor {
match inner.as_mut() {
Descriptor::I64 => **inner = Descriptor::I64AsF64,
Descriptor::U64 => **inner = Descriptor::U64AsF64,
_ => {}
}
}
}
fn instruction_builder<'b>(&'b mut self, return_position: bool) -> InstructionBuilder<'b, 'a> {
InstructionBuilder {
cx: self,
input: Vec::new(),
output: Vec::new(),
instructions: Vec::new(),
return_position,
}
}
fn malloc(&self) -> Result<FunctionId, Error> {
self.function_exports
.get("__wbindgen_malloc")
.cloned()
.map(|p| p.1)
.ok_or_else(|| anyhow!("failed to find declaration of `__wbindgen_malloc` in module"))
}
fn realloc(&self) -> Option<FunctionId> {
self.function_exports
.get("__wbindgen_realloc")
.cloned()
.map(|p| p.1)
}
fn free(&self) -> Result<FunctionId, Error> {
self.function_exports
.get("__wbindgen_free")
.cloned()
.map(|p| p.1)
.ok_or_else(|| anyhow!("failed to find declaration of `__wbindgen_free` in module"))
}
fn thread_destroy(&self) -> Option<FunctionId> {
self.function_exports
.get("__wbindgen_thread_destroy")
.cloned()
.map(|p| p.1)
}
fn memory(&self) -> Result<MemoryId, Error> {
self.memory
.ok_or_else(|| anyhow!("failed to find memory declaration in module"))
}
fn memory64(&self) -> bool {
self.memory
.map(|id| self.module.memories.get(id).memory64)
.unwrap_or(false)
}
fn unexport_intrinsics(&mut self) {
let mut to_remove = Vec::new();
for export in self.module.exports.iter() {
match export.name.as_str() {
n if n.starts_with("__wbindgen") => {
to_remove.push(export.id());
}
_ => {}
}
}
for id in to_remove {
self.module.exports.delete(id);
}
}
fn find_exn_store(&mut self) {
self.aux.exn_store = self
.module
.exports
.iter()
.find(|e| e.name == "__wbindgen_exn_store")
.and_then(|e| match e.item {
walrus::ExportItem::Function(f) => Some(f),
_ => None,
});
}
fn find_destroy_closure(&mut self) {
self.aux.destroy_closure = self
.module
.exports
.iter()
.find(|e| e.name == "__wbindgen_destroy_closure")
.and_then(|e| match e.item {
walrus::ExportItem::Function(f) => Some(f),
_ => None,
});
}
}
fn verify_constructor_return(class: &str, ret: &Descriptor) -> Result<(), Error> {
match ret {
Descriptor::I8
| Descriptor::U8
| Descriptor::ClampedU8
| Descriptor::I16
| Descriptor::U16
| Descriptor::I32
| Descriptor::U32
| Descriptor::F32
| Descriptor::F64
| Descriptor::I64
| Descriptor::U64
| Descriptor::Boolean
| Descriptor::Char
| Descriptor::CachedString
| Descriptor::String
| Descriptor::Option(_)
| Descriptor::Enum { .. }
| Descriptor::Unit
| Descriptor::RawPointer => {
bail!("The constructor for class `{class}` tries to return a JS primitive type, which would cause the return value to be ignored. Use a builder instead (remove the `constructor` attribute).");
}
Descriptor::Result(ref d) | Descriptor::Ref(ref d) | Descriptor::RefMut(ref d) => {
verify_constructor_return(class, d)
}
_ => Ok(()),
}
}
pub fn extract_programs<'a>(
module: &mut Module,
program_storage: &'a mut Vec<Vec<u8>>,
) -> Result<Vec<decode::Program<'a>>, Error> {
let my_version = wasm_bindgen_shared::version();
assert!(program_storage.is_empty());
while let Some(raw) = module.customs.remove_raw("__wasm_bindgen_unstable") {
log::debug!(
"custom section '{}' looks like a Wasm bindgen section",
raw.name
);
program_storage.push(raw.data);
}
let mut ret = Vec::new();
for program in program_storage.iter() {
let mut payload = &program[..];
while let Some(data) = get_remaining(&mut payload) {
if let Some(their_version) = verify_schema_matches(data)? {
bail!(
"
it looks like the Rust project used to create this Wasm file was linked against
version of wasm-bindgen that uses a different bindgen format than this binary:
rust Wasm file schema version: {their_version}
this binary schema version: {my_version}
Currently the bindgen format is unstable enough that these two schema versions
must exactly match. You can accomplish this by either updating this binary or
the wasm-bindgen dependency in the Rust project.
You should be able to update the wasm-bindgen dependency with:
cargo update -p wasm-bindgen --precise {my_version}
don't forget to recompile your Wasm file! Alternatively, you can update the
binary with:
cargo install -f wasm-bindgen-cli --version {their_version}
if this warning fails to go away though and you're not sure what to do feel free
to open an issue at https://github.com/wasm-bindgen/wasm-bindgen/issues!
"
);
}
let next = get_remaining(&mut payload).unwrap();
log::debug!("found a program of length {}", next.len());
ret.push(<decode::Program as decode::Decode>::decode_all(next));
}
}
Ok(ret)
}
fn get_remaining<'a>(data: &mut &'a [u8]) -> Option<&'a [u8]> {
if data.is_empty() {
return None;
}
let len = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
let (a, b) = data[4..].split_at(len);
*data = b;
Some(a)
}
fn verify_schema_matches(data: &[u8]) -> Result<Option<&str>, Error> {
macro_rules! bad {
() => {
bail!("failed to decode what looked like wasm-bindgen data")
};
}
let data = match str::from_utf8(data) {
Ok(s) => s,
Err(_) => bad!(),
};
log::debug!("found version specifier {data}");
if !data.starts_with('{') || !data.ends_with('}') {
bad!()
}
let needle = "\"schema_version\":\"";
let rest = match data.find(needle) {
Some(i) => &data[i + needle.len()..],
None => bad!(),
};
let their_schema_version = match rest.find('"') {
Some(i) => &rest[..i],
None => bad!(),
};
if their_schema_version == wasm_bindgen_shared::SCHEMA_VERSION {
return Ok(None);
}
let needle = "\"version\":\"";
let rest = match data.find(needle) {
Some(i) => &data[i + needle.len()..],
None => bad!(),
};
let their_version = match rest.find('"') {
Some(i) => &rest[..i],
None => bad!(),
};
Ok(Some(their_version))
}
fn concatenate_comments(comments: &[&str]) -> String {
comments.join("\n")
}
struct StructUnpacker {
next_offset: usize,
}
impl StructUnpacker {
fn new() -> Self {
Self { next_offset: 0 }
}
fn align_up(&mut self, alignment_pow2: usize) -> usize {
let mask = alignment_pow2 - 1;
self.next_offset = (self.next_offset + mask) & (!mask);
self.next_offset
}
fn append(&mut self, quads: usize, alignment_pow2: usize) -> usize {
let ret = self.align_up(alignment_pow2);
self.next_offset += quads;
ret
}
fn read_ty(&mut self, ty: &AdapterType) -> Result<usize, Error> {
let (quads, alignment) = match ty {
AdapterType::I32 | AdapterType::U32 | AdapterType::F32 => (1, 1),
AdapterType::I64 | AdapterType::U64 | AdapterType::F64 => (2, 2),
other => bail!("invalid aggregate return type {other:?}"),
};
Ok(self.append(quads, alignment))
}
}
#[test]
fn test_struct_packer() {
let mut unpacker = StructUnpacker::new();
let i32___ = &AdapterType::I32;
let double = &AdapterType::F64;
let mut read_ty = |ty| unpacker.read_ty(ty).unwrap();
assert_eq!(read_ty(i32___), 0); assert_eq!(read_ty(i32___), 1); assert_eq!(read_ty(double), 2); assert_eq!(read_ty(i32___), 4); assert_eq!(read_ty(double), 6); }
#[test]
fn normalize_memory64_descriptor_only_rewrites_explicit_number_abi_options() {
let mut number_option = Descriptor::Option(Box::new(Descriptor::I64));
Context::normalize_memory64_descriptor(&mut number_option, walrus::ValType::F64);
assert_eq!(
number_option,
Descriptor::Option(Box::new(Descriptor::I64AsF64))
);
let mut exact_i64 = Descriptor::I64;
Context::normalize_memory64_descriptor(&mut exact_i64, walrus::ValType::I64);
assert_eq!(exact_i64, Descriptor::I64);
let mut exact_option = Descriptor::Option(Box::new(Descriptor::I64));
Context::normalize_memory64_descriptor(&mut exact_option, walrus::ValType::I64);
assert_eq!(exact_option, Descriptor::Option(Box::new(Descriptor::I64)));
}