use crate::descriptor::VectorKind;
use crate::transforms::externref::Context;
use crate::wit::{AdapterKind, Instruction, NonstandardWitSection};
use crate::wit::{AdapterType, InstructionData, StackChange, WasmBindgenAux};
use anyhow::Result;
use std::collections::HashMap;
use walrus::ElementItems;
use walrus::{ir::Value, ConstExpr, ElementKind, Module};
pub fn process(module: &mut Module) -> Result<()> {
let mut cfg = Context::new(module)?;
let section = module
.customs
.get_typed_mut::<NonstandardWitSection>()
.expect("wit custom section should exist");
let implements = section
.implements
.iter()
.cloned()
.map(|(core, _, adapter)| (adapter, core))
.collect::<HashMap<_, _>>();
for (id, adapter) in &mut section.adapters {
let instructions = match &mut adapter.kind {
AdapterKind::Local { instructions } => instructions,
AdapterKind::Import { .. } => continue,
};
if let Some(id) = implements.get(id) {
import_xform(
&mut cfg,
*id,
instructions,
&mut adapter.params,
&mut adapter.results,
);
} else {
export_xform(&mut cfg, instructions);
}
}
let meta = cfg.run(module)?;
let mut aux = module
.customs
.delete_typed::<WasmBindgenAux>()
.expect("wit custom section should exist");
let section = module
.customs
.get_typed_mut::<NonstandardWitSection>()
.expect("wit custom section should exist");
aux.externref_table = Some(meta.table);
if module_needs_externref_metadata(&aux, section) {
aux.externref_alloc = meta.alloc;
aux.externref_drop = meta.drop;
aux.externref_drop_slice = meta.drop_slice;
}
for adapter in &mut section.adapters.values_mut() {
let instrs = match &mut adapter.kind {
AdapterKind::Local { instructions } => instructions,
AdapterKind::Import { .. } => continue,
};
for instr in instrs {
match instr.instr {
Instruction::I32FromOptionExternref {
ref mut table_and_alloc,
} => {
*table_and_alloc = meta.alloc.map(|id| (meta.table, id));
}
Instruction::ExternrefLoadOwned {
ref mut table_and_drop,
}
| Instruction::UnwrapResult {
ref mut table_and_drop,
}
| Instruction::UnwrapResultString {
ref mut table_and_drop,
} => {
*table_and_drop = meta.drop.map(|id| (meta.table, id));
}
Instruction::CachedStringLoad { ref mut table, .. } => *table = Some(meta.table),
_ => continue,
};
}
}
module.customs.add(*aux);
Ok(())
}
fn import_xform(
cx: &mut Context,
id: walrus::ImportId,
instrs: &mut Vec<InstructionData>,
params: &mut [AdapterType],
results: &mut [AdapterType],
) {
struct Arg {
idx: usize,
externref: Option<bool>,
}
let mut to_delete = Vec::new();
let mut iter = instrs.iter().enumerate();
let mut args = Vec::new();
for (i, instr) in iter.by_ref() {
match instr.instr {
Instruction::CallAdapter(_) => break,
Instruction::ExternrefLoadOwned { .. } | Instruction::TableGet => {
let owned = !matches!(instr.instr, Instruction::TableGet);
let mut arg: Arg = match args.pop().unwrap() {
Some(arg) => arg,
None => panic!("previous instruction must be `arg.get`"),
};
arg.externref = Some(owned);
match params[arg.idx] {
AdapterType::I32 => {}
_ => panic!("must be `i32` type"),
}
params[arg.idx] = AdapterType::Externref;
args.push(Some(arg));
to_delete.push(i);
}
Instruction::ArgGet(n) => {
args.push(Some(Arg {
idx: n as usize,
externref: None,
}));
}
_ => match instr.stack_change {
StackChange::Modified { pushed, popped } => {
for _ in 0..popped {
args.pop();
}
for _ in 0..pushed {
args.push(None);
}
}
StackChange::Unknown => {
panic!("must have stack change data");
}
},
}
}
let mut ret_externref = false;
for (i, instr) in iter {
if matches!(instr.instr, Instruction::I32FromExternrefOwned) {
assert_eq!(results.len(), 1);
assert!(matches!(results[0], AdapterType::I32), "must be `i32` type");
results[0] = AdapterType::Externref;
ret_externref = true;
to_delete.push(i);
}
}
for idx in to_delete.into_iter().rev() {
instrs.remove(idx);
}
let args = args
.iter()
.filter_map(|arg| arg.as_ref())
.filter_map(|arg| arg.externref.map(|owned| (arg.idx, owned)))
.collect::<Vec<_>>();
cx.import_xform(id, &args, ret_externref);
}
fn export_xform(cx: &mut Context, instrs: &mut Vec<InstructionData>) {
let mut to_delete = Vec::new();
let mut iter = instrs.iter().enumerate();
let mut args = Vec::new();
let export = iter
.by_ref()
.find_map(|(i, instr)| {
match instr.instr {
Instruction::CallExport(export) => return Some(export),
Instruction::I32FromExternrefOwned => {
args.pop();
args.push(Some(true));
to_delete.push(i);
}
Instruction::I32FromExternrefBorrow => {
args.pop();
args.push(Some(false));
to_delete.push(i);
}
_ => match instr.stack_change {
StackChange::Modified { pushed, popped } => {
for _ in 0..popped {
args.pop();
}
for _ in 0..pushed {
args.push(None);
}
}
StackChange::Unknown => {
panic!("must have stack change data");
}
},
}
None
})
.expect("must call an export");
let mut uses_retptr = false;
let mut ret_externref = false;
for (i, instr) in iter {
match instr.instr {
Instruction::LoadRetptr { .. } => uses_retptr = true,
Instruction::ExternrefLoadOwned { .. } if !uses_retptr => {
ret_externref = true;
to_delete.push(i);
}
_ => {}
}
}
let args = args
.iter()
.enumerate()
.filter_map(|(i, owned)| owned.map(|owned| (i, owned)))
.collect::<Vec<_>>();
cx.export_xform(export, &args, ret_externref);
for idx in to_delete.into_iter().rev() {
instrs.remove(idx);
}
}
fn module_needs_externref_metadata(aux: &WasmBindgenAux, section: &NonstandardWitSection) -> bool {
use Instruction::*;
if !aux.imports_with_catch.is_empty() {
return true;
}
section.adapters.iter().any(|(_, adapter)| {
let instructions = match &adapter.kind {
AdapterKind::Local { instructions } => instructions,
AdapterKind::Import { .. } => return false,
};
instructions.iter().any(|instr| {
matches!(
instr.instr,
VectorToMemory {
kind: VectorKind::Externref | VectorKind::NamedExternref(_),
..
} | MutableSliceToMemory {
kind: VectorKind::Externref | VectorKind::NamedExternref(_),
..
} | OptionVector {
kind: VectorKind::Externref | VectorKind::NamedExternref(_),
..
} | VectorLoad {
kind: VectorKind::Externref | VectorKind::NamedExternref(_),
..
} | OptionVectorLoad {
kind: VectorKind::Externref | VectorKind::NamedExternref(_),
..
} | View {
kind: VectorKind::Externref | VectorKind::NamedExternref(_),
..
} | OptionView {
kind: VectorKind::Externref | VectorKind::NamedExternref(_),
..
}
)
})
})
}
pub fn force_contiguous_elements(module: &mut Module) -> Result<()> {
let mut new_segments = Vec::new();
for segment in module.elements.iter_mut() {
let (ty, items) = match &mut segment.items {
ElementItems::Expressions(ty, items) => {
if items
.iter()
.all(|item| !matches!(item, ConstExpr::RefNull(_)))
{
continue;
}
(*ty, items)
}
ElementItems::Functions(_) => continue,
};
let (table, offset) = match &segment.kind {
ElementKind::Active {
table,
offset: ConstExpr::Value(Value::I32(n)),
} => (*table, *n),
_ => continue,
};
let mut block = None;
let mut truncate = 0;
let mut commit = |last_idx: usize, block: Vec<_>| {
let new_offset = offset + (last_idx - block.len()) as i32;
let new_offset = ConstExpr::Value(Value::I32(new_offset));
new_segments.push((table, new_offset, ty, block));
};
for (i, expr) in items.iter().enumerate() {
match expr {
ConstExpr::RefNull(_) => {
let block: Vec<_> = match block.take() {
Some(b) => b,
None => continue,
};
if truncate == 0 && block.len() == i {
truncate = i;
} else {
commit(i, block);
}
}
_ => block.get_or_insert(Vec::new()).push(expr.clone()),
}
}
if let Some(block) = block {
commit(items.len(), block);
}
items.truncate(truncate);
}
for (table, offset, ty, members) in new_segments {
let id = module.elements.add(
ElementKind::Active { table, offset },
ElementItems::Expressions(ty, members),
);
module.tables.get_mut(table).elem_segments.insert(id);
}
Ok(())
}