use self::bitvec::BitVec;
use anyhow::{bail, Result};
use indexmap::{IndexMap, IndexSet};
use std::{
borrow::Cow,
collections::{HashMap, HashSet},
mem,
ops::Deref,
};
use wasm_encoder::{Encode, EntityType, Instruction, RawCustomSection};
use wasmparser::*;
const PAGE_SIZE: i32 = 64 * 1024;
pub fn run<T>(
wasm: &[u8],
required: &IndexMap<String, T>,
main_module_realloc: Option<&str>,
) -> Result<Vec<u8>> {
assert!(!required.is_empty());
let mut module = Module::default();
module.parse(wasm)?;
for (name, _ty) in required {
if !module.exports.contains_key(name.as_str()) {
bail!("adapter module does not have export `{name}`")
}
}
let mut not_required = IndexSet::new();
for name in module.exports.keys().copied() {
let name = if let Some(suffix) = name.strip_prefix("cabi_post_") {
suffix
} else {
name
};
if !required.contains_key(name) && !always_keep(name) {
not_required.insert(name);
}
}
for name in not_required {
module.exports.remove(name);
}
assert!(!module.exports.is_empty());
module.liveness()?;
module.encode(main_module_realloc)
}
fn always_keep(name: &str) -> bool {
match name {
"cabi_realloc" | "cabi_import_realloc" | "cabi_export_realloc" => true,
_ => false,
}
}
fn realloc_via_memory_grow() -> wasm_encoder::Function {
use wasm_encoder::Instruction::*;
let mut func = wasm_encoder::Function::new([(1, wasm_encoder::ValType::I32)]);
func.instruction(&I32Const(0));
func.instruction(&LocalGet(0));
func.instruction(&I32Ne);
func.instruction(&If(wasm_encoder::BlockType::Empty));
func.instruction(&Unreachable);
func.instruction(&End);
func.instruction(&I32Const(0));
func.instruction(&LocalGet(1));
func.instruction(&I32Ne);
func.instruction(&If(wasm_encoder::BlockType::Empty));
func.instruction(&Unreachable);
func.instruction(&End);
func.instruction(&I32Const(PAGE_SIZE));
func.instruction(&LocalGet(3));
func.instruction(&I32Ne);
func.instruction(&If(wasm_encoder::BlockType::Empty));
func.instruction(&Unreachable);
func.instruction(&End);
func.instruction(&I32Const(1));
func.instruction(&MemoryGrow(0));
func.instruction(&LocalTee(4));
func.instruction(&I32Const(-1));
func.instruction(&I32Eq);
func.instruction(&If(wasm_encoder::BlockType::Empty));
func.instruction(&Unreachable);
func.instruction(&End);
func.instruction(&LocalGet(4));
func.instruction(&I32Const(16));
func.instruction(&I32Shl);
func.instruction(&End);
func
}
#[repr(i32)]
#[non_exhaustive]
enum StackAllocationState {
Unallocated,
Allocating,
Allocated,
}
fn allocate_stack_via_realloc(
realloc_index: u32,
sp: u32,
allocation_state: Option<u32>,
) -> wasm_encoder::Function {
use wasm_encoder::Instruction::*;
let mut func = wasm_encoder::Function::new([]);
if let Some(allocation_state) = allocation_state {
func.instruction(&GlobalGet(allocation_state));
func.instruction(&I32Const(StackAllocationState::Unallocated as _));
func.instruction(&I32Eq);
func.instruction(&If(wasm_encoder::BlockType::Empty));
func.instruction(&I32Const(StackAllocationState::Allocating as _));
func.instruction(&GlobalSet(allocation_state));
}
func.instruction(&I32Const(0));
func.instruction(&I32Const(0));
func.instruction(&I32Const(8));
func.instruction(&I32Const(PAGE_SIZE));
func.instruction(&Call(realloc_index));
func.instruction(&I32Const(PAGE_SIZE));
func.instruction(&I32Add);
func.instruction(&GlobalSet(sp));
if let Some(allocation_state) = allocation_state {
func.instruction(&I32Const(StackAllocationState::Allocated as _));
func.instruction(&GlobalSet(allocation_state));
func.instruction(&End);
}
func.instruction(&End);
func
}
type WorklistFunc<'a> = fn(&mut Module<'a>, u32) -> Result<()>;
#[derive(Default)]
struct Module<'a> {
types: Vec<FuncType>,
tables: Vec<Table<'a>>,
globals: Vec<Global<'a>>,
memories: Vec<Memory<'a>>,
funcs: Vec<Func<'a>>,
exports: IndexMap<&'a str, Export<'a>>,
func_names: HashMap<u32, &'a str>,
global_names: HashMap<u32, &'a str>,
producers: Option<wasm_metadata::Producers>,
live_types: BitVec,
live_tables: BitVec,
live_globals: BitVec,
live_memories: BitVec,
live_funcs: BitVec,
worklist: Vec<(u32, WorklistFunc<'a>)>,
}
struct Table<'a> {
def: Definition<'a, ()>,
ty: TableType,
}
struct Memory<'a> {
def: Definition<'a, ()>,
ty: MemoryType,
}
struct Global<'a> {
def: Definition<'a, ConstExpr<'a>>,
ty: GlobalType,
}
#[derive(Clone)]
struct Func<'a> {
def: Definition<'a, FunctionBody<'a>>,
ty: u32,
}
#[derive(Clone)]
enum Definition<'a, T> {
Import(&'a str, &'a str),
Local(T),
}
impl<'a> Module<'a> {
fn parse(&mut self, wasm: &'a [u8]) -> Result<()> {
let mut next_code_index = 0;
let mut validator = Validator::new();
for payload in Parser::new(0).parse_all(wasm) {
let payload = payload?;
validator.payload(&payload)?;
match payload {
Payload::Version { encoding, .. } => {
if encoding != Encoding::Module {
bail!("adapter must be a core wasm module, not a component");
}
}
Payload::End(_) => {}
Payload::TypeSection(s) => {
for ty in s.into_iter_err_on_gc_types() {
self.types.push(ty?);
}
}
Payload::ImportSection(s) => {
for i in s {
let i = i?;
match i.ty {
TypeRef::Func(ty) => self.funcs.push(Func {
def: Definition::Import(i.module, i.name),
ty,
}),
TypeRef::Table(ty) => self.tables.push(Table {
def: Definition::Import(i.module, i.name),
ty,
}),
TypeRef::Global(ty) => self.globals.push(Global {
def: Definition::Import(i.module, i.name),
ty,
}),
TypeRef::Memory(ty) => self.memories.push(Memory {
def: Definition::Import(i.module, i.name),
ty,
}),
TypeRef::Tag(_) => bail!("unsupported `tag` type"),
}
}
}
Payload::TableSection(s) => {
for table in s {
let table = table?;
self.tables.push(Table {
def: Definition::Local(()),
ty: table.ty,
});
}
}
Payload::MemorySection(s) => {
for ty in s {
let ty = ty?;
self.memories.push(Memory {
def: Definition::Local(()),
ty,
});
}
}
Payload::GlobalSection(s) => {
for g in s {
let g = g?;
self.globals.push(Global {
def: Definition::Local(g.init_expr),
ty: g.ty,
});
}
}
Payload::ExportSection(s) => {
for e in s {
let e = e?;
self.exports.insert(e.name, e);
}
}
Payload::FunctionSection(s) => {
next_code_index = self.funcs.len();
for ty in s {
let ty = ty?;
self.funcs.push(Func {
def: Definition::Local(FunctionBody::new(0, &[])),
ty,
});
}
}
Payload::CodeSectionStart { .. } => {}
Payload::CodeSectionEntry(body) => {
self.funcs[next_code_index].def = Definition::Local(body);
next_code_index += 1;
}
Payload::CustomSection(s) => {
if s.name() == "name" {
drop(self.parse_name_section(&s));
}
if s.name() == "producers" {
drop(self.parse_producers_section(&s));
}
}
Payload::DataCountSection { .. }
| Payload::ElementSection(_)
| Payload::DataSection(_)
| Payload::StartSection { .. }
| Payload::TagSection(_)
| Payload::UnknownSection { .. } => {
bail!("unsupported section found in adapter module")
}
Payload::ModuleSection { .. }
| Payload::ComponentSection { .. }
| Payload::InstanceSection(_)
| Payload::ComponentInstanceSection(_)
| Payload::ComponentAliasSection(_)
| Payload::ComponentCanonicalSection(_)
| Payload::ComponentStartSection { .. }
| Payload::ComponentImportSection(_)
| Payload::CoreTypeSection(_)
| Payload::ComponentExportSection(_)
| Payload::ComponentTypeSection(_) => {
bail!("component section found in adapter module")
}
}
}
Ok(())
}
fn parse_name_section(&mut self, section: &CustomSectionReader<'a>) -> Result<()> {
let section = NameSectionReader::new(section.data(), section.data_offset());
for s in section {
match s? {
Name::Function(map) => {
for naming in map {
let naming = naming?;
self.func_names.insert(naming.index, naming.name);
}
}
Name::Global(map) => {
for naming in map {
let naming = naming?;
self.global_names.insert(naming.index, naming.name);
}
}
_ => {}
}
}
Ok(())
}
fn parse_producers_section(&mut self, section: &CustomSectionReader<'a>) -> Result<()> {
let producers =
wasm_metadata::Producers::from_bytes(section.data(), section.data_offset())?;
self.producers = Some(producers);
Ok(())
}
fn liveness(&mut self) -> Result<()> {
let exports = mem::take(&mut self.exports);
for (_, e) in exports.iter() {
match e.kind {
ExternalKind::Func => self.func(e.index),
ExternalKind::Global => self.global(e.index),
ExternalKind::Table => self.table(e.index),
ExternalKind::Memory => self.memory(e.index),
ExternalKind::Tag => bail!("unsupported exported tag"),
}
}
self.exports = exports;
while let Some((idx, func)) = self.worklist.pop() {
func(self, idx)?;
}
Ok(())
}
fn func(&mut self, func: u32) {
if !self.live_funcs.insert(func) {
return;
}
self.worklist.push((func, |me, func| {
let func = me.funcs[func as usize].clone();
me.ty(func.ty);
let mut body = match &func.def {
Definition::Import(..) => return Ok(()),
Definition::Local(e) => e.get_binary_reader(),
};
let local_count = body.read_var_u32()?;
for _ in 0..local_count {
body.read_var_u32()?;
body.read::<ValType>()?;
}
me.operators(body)
}));
}
fn global(&mut self, global: u32) {
if !self.live_globals.insert(global) {
return;
}
self.worklist.push((global, |me, global| {
let init = match &me.globals[global as usize].def {
Definition::Import(..) => return Ok(()),
Definition::Local(e) => e,
};
me.operators(init.get_binary_reader())
}));
}
fn table(&mut self, table: u32) {
if !self.live_tables.insert(table) {
return;
}
self.worklist.push((table, |me, table| {
let ty = me.tables[table as usize].ty.element_type;
me.valty(ty.into());
Ok(())
}));
}
fn memory(&mut self, memory: u32) {
self.live_memories.insert(memory);
}
fn blockty(&mut self, ty: BlockType) {
if let BlockType::FuncType(ty) = ty {
self.ty(ty);
}
}
fn valty(&mut self, ty: ValType) {
match ty {
ValType::Ref(r) => self.heapty(r.heap_type()),
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => {}
}
}
fn heapty(&mut self, ty: HeapType) {
match ty {
HeapType::Func
| HeapType::Extern
| HeapType::Any
| HeapType::None
| HeapType::NoExtern
| HeapType::NoFunc
| HeapType::Eq
| HeapType::Struct
| HeapType::Array
| HeapType::I31 => {}
HeapType::Indexed(i) => self.ty(i),
}
}
fn ty(&mut self, ty: u32) {
if !self.live_types.insert(ty) {
return;
}
self.worklist.push((ty, |me, ty| {
let ty = me.types[ty as usize].clone();
for param in ty.params().iter().chain(ty.results()) {
me.valty(*param);
}
Ok(())
}));
}
fn operators(&mut self, mut reader: BinaryReader<'a>) -> Result<()> {
while !reader.eof() {
reader.visit_operator(self)?;
}
Ok(())
}
fn live_types(&self) -> impl Iterator<Item = (u32, &FuncType)> + '_ {
live_iter(&self.live_types, self.types.iter())
}
fn live_funcs(&self) -> impl Iterator<Item = (u32, &Func<'a>)> + '_ {
live_iter(&self.live_funcs, self.funcs.iter())
}
fn live_memories(&self) -> impl Iterator<Item = (u32, &Memory<'a>)> + '_ {
live_iter(&self.live_memories, self.memories.iter())
}
fn live_globals(&self) -> impl Iterator<Item = (u32, &Global<'a>)> + '_ {
live_iter(&self.live_globals, self.globals.iter())
}
fn live_tables(&self) -> impl Iterator<Item = (u32, &Table<'a>)> + '_ {
live_iter(&self.live_tables, self.tables.iter())
}
fn encode(&mut self, main_module_realloc: Option<&str>) -> Result<Vec<u8>> {
let mut map = Encoder::default();
let mut types = wasm_encoder::TypeSection::new();
let mut imports = wasm_encoder::ImportSection::new();
let mut funcs = wasm_encoder::FunctionSection::new();
let mut tables = wasm_encoder::TableSection::new();
let mut memories = wasm_encoder::MemorySection::new();
let mut globals = wasm_encoder::GlobalSection::new();
let mut code = wasm_encoder::CodeSection::new();
let mut empty_type = None;
for (i, ty) in self.live_types() {
map.types.push(i);
types.function(
ty.params().iter().map(|t| map.valty(*t)),
ty.results().iter().map(|t| map.valty(*t)),
);
if ty.params().is_empty() && ty.results().is_empty() {
empty_type = Some(map.types.remap(i));
}
}
let mut num_memories = 0;
for (i, mem) in self.live_memories() {
map.memories.push(i);
let ty = wasm_encoder::MemoryType {
minimum: mem.ty.initial,
maximum: mem.ty.maximum,
shared: mem.ty.shared,
memory64: mem.ty.memory64,
};
match &mem.def {
Definition::Import(m, n) => {
imports.import(m, n, ty);
}
Definition::Local(()) => {
memories.memory(ty);
}
}
num_memories += 1;
}
for (i, table) in self.live_tables() {
map.tables.push(i);
let ty = wasm_encoder::TableType {
minimum: table.ty.initial,
maximum: table.ty.maximum,
element_type: map.refty(table.ty.element_type),
};
match &table.def {
Definition::Import(m, n) => {
imports.import(m, n, ty);
}
Definition::Local(()) => {
tables.table(ty);
}
}
}
for (i, global) in self.live_globals() {
map.globals.push(i);
let ty = wasm_encoder::GlobalType {
mutable: global.ty.mutable,
val_type: map.valty(global.ty.content_type),
};
match &global.def {
Definition::Import(m, n) => {
imports.import(m, n, ty);
}
Definition::Local(init) => {
let mut bytes = map.operators(init.get_binary_reader())?;
assert_eq!(bytes.pop(), Some(0xb));
globals.global(ty, &wasm_encoder::ConstExpr::raw(bytes));
}
}
}
let mut realloc_index = None;
let mut num_func_imports = 0;
let is_realloc =
|m, n| m == "__main_module__" && matches!(n, "canonical_abi_realloc" | "cabi_realloc");
let (imported, local) =
self.live_funcs()
.partition::<Vec<_>, _>(|(_, func)| match &func.def {
Definition::Import(m, n) => {
!is_realloc(*m, *n) || main_module_realloc.is_some()
}
Definition::Local(_) => false,
});
for (i, func) in imported {
map.funcs.push(i);
let ty = map.types.remap(func.ty);
match &func.def {
Definition::Import(m, n) => {
let name = if is_realloc(*m, *n) {
realloc_index = Some(num_func_imports);
main_module_realloc.unwrap_or(n)
} else {
n
};
imports.import(m, name, EntityType::Function(ty));
num_func_imports += 1;
}
Definition::Local(_) => unreachable!(),
}
}
let add_realloc_type = |types: &mut wasm_encoder::TypeSection| {
let type_index = types.len();
types.function(
[
wasm_encoder::ValType::I32,
wasm_encoder::ValType::I32,
wasm_encoder::ValType::I32,
wasm_encoder::ValType::I32,
],
[wasm_encoder::ValType::I32],
);
type_index
};
let add_empty_type = |types: &mut wasm_encoder::TypeSection| {
let type_index = types.len();
types.function([], []);
type_index
};
let sp = self.find_mut_i32_global("__stack_pointer")?;
let allocation_state = self.find_mut_i32_global("allocation_state")?;
let mut func_names = Vec::new();
if let (Some(realloc), Some(_), None) = (main_module_realloc, sp, realloc_index) {
map.funcs.next += 1;
realloc_index = Some(num_func_imports);
imports.import(
"__main_module__",
realloc,
EntityType::Function(add_realloc_type(&mut types)),
);
func_names.push((num_func_imports, realloc));
num_func_imports += 1;
}
for (i, func) in local {
map.funcs.push(i);
let ty = map.types.remap(func.ty);
match &func.def {
Definition::Import(_, _) => {
realloc_index = Some(num_func_imports + funcs.len());
funcs.function(ty);
code.function(&realloc_via_memory_grow());
}
Definition::Local(_) => {
funcs.function(ty);
}
}
}
let lazy_stack_init_index =
if sp.is_some() && allocation_state.is_some() && main_module_realloc.is_some() {
let index = num_func_imports + funcs.len();
map.funcs.next += 1;
funcs.function(add_empty_type(&mut types));
Some(index)
} else {
None
};
let exported_funcs = self
.exports
.values()
.filter_map(|export| match export.kind {
ExternalKind::Func => Some(export.index),
_ => None,
})
.collect::<HashSet<_>>();
for (i, func) in self.live_funcs() {
let mut body = match &func.def {
Definition::Import(..) => continue,
Definition::Local(body) => body.get_binary_reader(),
};
let mut locals = Vec::new();
for _ in 0..body.read_var_u32()? {
let cnt = body.read_var_u32()?;
let ty = body.read()?;
locals.push((cnt, map.valty(ty)));
}
if let (Some(lazy_stack_init_index), true) =
(lazy_stack_init_index, exported_funcs.contains(&i))
{
Instruction::Call(lazy_stack_init_index).encode(&mut map.buf);
}
let bytes = map.operators(body)?;
let mut func = wasm_encoder::Function::new(locals);
func.raw(bytes);
code.function(&func);
}
if lazy_stack_init_index.is_some() {
code.function(&allocate_stack_via_realloc(
realloc_index.unwrap(),
sp.unwrap(),
allocation_state,
));
}
if sp.is_some() && (realloc_index.is_none() || allocation_state.is_none()) {
realloc_index = Some(num_func_imports + funcs.len());
funcs.function(add_realloc_type(&mut types));
code.function(&realloc_via_memory_grow());
}
let mut start = None;
if let (Some(sp), None) = (sp, lazy_stack_init_index) {
if num_memories > 0 {
if num_memories != 1 {
bail!("adapter modules don't support multi-memory");
}
let sp = map.globals.remap(sp);
let function_index = num_func_imports + funcs.len();
let empty_type = empty_type.unwrap_or_else(|| {
types.function([], []);
types.len() - 1
});
funcs.function(empty_type);
func_names.push((function_index, "allocate_stack"));
code.function(&allocate_stack_via_realloc(
realloc_index.unwrap(),
sp,
allocation_state,
));
start = Some(wasm_encoder::StartSection { function_index });
}
}
if self.live_tables().count() != 0 {
bail!("tables should not be present in the final adapter module");
}
if self.live_memories().count() > 1 {
bail!("the adapter module should not use multi-memory");
}
if !memories.is_empty() {
bail!("locally-defined memories are not allowed define a local memory");
}
let mut ret = wasm_encoder::Module::default();
if !types.is_empty() {
ret.section(&types);
}
if !imports.is_empty() {
ret.section(&imports);
}
if !funcs.is_empty() {
ret.section(&funcs);
}
if !tables.is_empty() {
ret.section(&tables);
}
if !memories.is_empty() {
ret.section(&memories);
}
if !globals.is_empty() {
ret.section(&globals);
}
if !self.exports.is_empty() {
let mut exports = wasm_encoder::ExportSection::new();
for (_, export) in self.exports.iter() {
let (kind, index) = match export.kind {
ExternalKind::Func => (
wasm_encoder::ExportKind::Func,
map.funcs.remap(export.index),
),
ExternalKind::Table => (
wasm_encoder::ExportKind::Table,
map.tables.remap(export.index),
),
ExternalKind::Memory => (
wasm_encoder::ExportKind::Memory,
map.memories.remap(export.index),
),
ExternalKind::Global => (
wasm_encoder::ExportKind::Global,
map.globals.remap(export.index),
),
kind => bail!("unsupported export kind {kind:?}"),
};
exports.export(export.name, kind, index);
}
ret.section(&exports);
}
if let Some(start) = &start {
ret.section(start);
}
if !code.is_empty() {
ret.section(&code);
}
let mut global_names = Vec::new();
for (i, _func) in self.live_funcs() {
let name = match self.func_names.get(&i) {
Some(name) => name,
None => continue,
};
func_names.push((map.funcs.remap(i), *name));
}
for (i, _global) in self.live_globals() {
let name = match self.global_names.get(&i) {
Some(name) => name,
None => continue,
};
global_names.push((map.globals.remap(i), *name));
}
let mut section = Vec::new();
let mut encode_subsection = |code: u8, names: &[(u32, &str)]| {
if names.is_empty() {
return;
}
let mut subsection = Vec::new();
names.len().encode(&mut subsection);
for (i, name) in names {
i.encode(&mut subsection);
name.encode(&mut subsection);
}
section.push(code);
subsection.encode(&mut section);
};
if let (Some(realloc_index), true) = (
realloc_index,
main_module_realloc.is_none() || allocation_state.is_none(),
) {
func_names.push((realloc_index, "realloc_via_memory_grow"));
}
if let Some(lazy_stack_init_index) = lazy_stack_init_index {
func_names.push((lazy_stack_init_index, "allocate_stack"));
}
encode_subsection(0x01, &func_names);
encode_subsection(0x07, &global_names);
if !section.is_empty() {
ret.section(&wasm_encoder::CustomSection {
name: "name".into(),
data: Cow::Borrowed(§ion),
});
}
if let Some(producers) = &self.producers {
ret.section(&RawCustomSection(&producers.raw_custom_section()));
}
Ok(ret.finish())
}
fn find_mut_i32_global(&self, name: &str) -> Result<Option<u32>> {
let matches = &self
.live_globals()
.filter_map(|(i, g)| {
if g.ty.mutable
&& g.ty.content_type == ValType::I32
&& *self.global_names.get(&i)? == name
{
Some(i)
} else {
None
}
})
.collect::<Vec<_>>();
match matches.deref() {
[] => Ok(None),
[i] => Ok(Some(*i)),
_ => bail!(
"found {} mutable i32 globals with name {name}",
matches.len()
),
}
}
}
macro_rules! define_visit {
($(@$p:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => {
$(
fn $visit(&mut self $(, $($arg: $argty),*)?) {
$(
$(
define_visit!(mark_live self $arg $arg);
)*
)?
}
)*
};
(mark_live $self:ident $arg:ident type_index) => {$self.ty($arg);};
(mark_live $self:ident $arg:ident src_table) => {$self.table($arg);};
(mark_live $self:ident $arg:ident dst_table) => {$self.table($arg);};
(mark_live $self:ident $arg:ident table_index) => {$self.table($arg);};
(mark_live $self:ident $arg:ident table) => {$self.table($arg);};
(mark_live $self:ident $arg:ident table_index) => {$self.table($arg);};
(mark_live $self:ident $arg:ident global_index) => {$self.global($arg);};
(mark_live $self:ident $arg:ident function_index) => {$self.func($arg);};
(mark_live $self:ident $arg:ident mem) => {$self.memory($arg);};
(mark_live $self:ident $arg:ident src_mem) => {$self.memory($arg);};
(mark_live $self:ident $arg:ident dst_mem) => {$self.memory($arg);};
(mark_live $self:ident $arg:ident memarg) => {$self.memory($arg.memory);};
(mark_live $self:ident $arg:ident blockty) => {$self.blockty($arg);};
(mark_live $self:ident $arg:ident ty) => {$self.valty($arg)};
(mark_live $self:ident $arg:ident hty) => {$self.heapty($arg)};
(mark_live $self:ident $arg:ident lane) => {};
(mark_live $self:ident $arg:ident lanes) => {};
(mark_live $self:ident $arg:ident flags) => {};
(mark_live $self:ident $arg:ident value) => {};
(mark_live $self:ident $arg:ident mem_byte) => {};
(mark_live $self:ident $arg:ident table_byte) => {};
(mark_live $self:ident $arg:ident local_index) => {};
(mark_live $self:ident $arg:ident relative_depth) => {};
(mark_live $self:ident $arg:ident tag_index) => {};
(mark_live $self:ident $arg:ident targets) => {};
(mark_live $self:ident $arg:ident data_index) => {};
(mark_live $self:ident $arg:ident elem_index) => {};
}
impl<'a> VisitOperator<'a> for Module<'a> {
type Output = ();
wasmparser::for_each_operator!(define_visit);
}
fn live_iter<'a, T>(
live: &'a BitVec,
iter: impl Iterator<Item = T> + 'a,
) -> impl Iterator<Item = (u32, T)> + 'a {
iter.enumerate().filter_map(|(i, t)| {
let i = i as u32;
if live.contains(i) {
Some((i, t))
} else {
None
}
})
}
#[derive(Default)]
struct Encoder {
types: Remap,
funcs: Remap,
memories: Remap,
globals: Remap,
tables: Remap,
buf: Vec<u8>,
}
impl Encoder {
fn operators(&mut self, mut reader: BinaryReader<'_>) -> Result<Vec<u8>> {
while !reader.eof() {
reader.visit_operator(self)?;
}
Ok(mem::take(&mut self.buf))
}
fn memarg(&self, ty: MemArg) -> wasm_encoder::MemArg {
wasm_encoder::MemArg {
offset: ty.offset,
align: ty.align.into(),
memory_index: self.memories.remap(ty.memory),
}
}
fn blockty(&self, ty: BlockType) -> wasm_encoder::BlockType {
match ty {
BlockType::Empty => wasm_encoder::BlockType::Empty,
BlockType::Type(ty) => wasm_encoder::BlockType::Result(self.valty(ty)),
BlockType::FuncType(ty) => wasm_encoder::BlockType::FunctionType(self.types.remap(ty)),
}
}
fn valty(&self, ty: wasmparser::ValType) -> wasm_encoder::ValType {
match ty {
wasmparser::ValType::I32 => wasm_encoder::ValType::I32,
wasmparser::ValType::I64 => wasm_encoder::ValType::I64,
wasmparser::ValType::F32 => wasm_encoder::ValType::F32,
wasmparser::ValType::F64 => wasm_encoder::ValType::F64,
wasmparser::ValType::V128 => wasm_encoder::ValType::V128,
wasmparser::ValType::Ref(rt) => wasm_encoder::ValType::Ref(self.refty(rt)),
}
}
fn refty(&self, rt: wasmparser::RefType) -> wasm_encoder::RefType {
wasm_encoder::RefType {
nullable: rt.is_nullable(),
heap_type: self.heapty(rt.heap_type()),
}
}
fn heapty(&self, ht: wasmparser::HeapType) -> wasm_encoder::HeapType {
match ht {
HeapType::Func => wasm_encoder::HeapType::Func,
HeapType::Extern => wasm_encoder::HeapType::Extern,
HeapType::Any => wasm_encoder::HeapType::Any,
HeapType::None => wasm_encoder::HeapType::None,
HeapType::NoExtern => wasm_encoder::HeapType::NoExtern,
HeapType::NoFunc => wasm_encoder::HeapType::NoFunc,
HeapType::Eq => wasm_encoder::HeapType::Eq,
HeapType::Struct => wasm_encoder::HeapType::Struct,
HeapType::Array => wasm_encoder::HeapType::Array,
HeapType::I31 => wasm_encoder::HeapType::I31,
HeapType::Indexed(idx) => wasm_encoder::HeapType::Indexed(self.types.remap(idx)),
}
}
}
macro_rules! define_encode {
($(@$p:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => {
$(
#[allow(clippy::drop_copy)]
fn $visit(&mut self $(, $($arg: $argty),*)?) {
#[allow(unused_imports)]
use wasm_encoder::Instruction::*;
$(
$(
let $arg = define_encode!(map self $arg $arg);
)*
)?
let insn = define_encode!(mk $op $($($arg)*)?);
insn.encode(&mut self.buf);
}
)*
};
(mk $op:ident) => ($op);
(mk BrTable $arg:ident) => ({
BrTable($arg.0, $arg.1)
});
(mk CallIndirect $ty:ident $table:ident $table_byte:ident) => ({
let _ = $table_byte;
CallIndirect { ty: $ty, table: $table }
});
(mk ReturnCallIndirect $ty:ident $table:ident) => (
ReturnCallIndirect { ty: $ty, table: $table }
);
(mk MemorySize $mem:ident $mem_byte:ident) => ({
let _ = $mem_byte;
MemorySize($mem)
});
(mk MemoryGrow $mem:ident $mem_byte:ident) => ({
let _ = $mem_byte;
MemoryGrow($mem)
});
(mk I32Const $v:ident) => (I32Const($v));
(mk I64Const $v:ident) => (I64Const($v));
(mk F32Const $v:ident) => (F32Const(f32::from_bits($v.bits())));
(mk F64Const $v:ident) => (F64Const(f64::from_bits($v.bits())));
(mk V128Const $v:ident) => (V128Const($v.i128()));
(mk $op:ident $arg:ident) => ($op($arg));
(mk $op:ident $($arg:ident)*) => ($op { $($arg),* });
(map $self:ident $arg:ident memarg) => {$self.memarg($arg)};
(map $self:ident $arg:ident blockty) => {$self.blockty($arg)};
(map $self:ident $arg:ident hty) => {$self.heapty($arg)};
(map $self:ident $arg:ident tag_index) => {$arg};
(map $self:ident $arg:ident relative_depth) => {$arg};
(map $self:ident $arg:ident function_index) => {$self.funcs.remap($arg)};
(map $self:ident $arg:ident global_index) => {$self.globals.remap($arg)};
(map $self:ident $arg:ident mem) => {$self.memories.remap($arg)};
(map $self:ident $arg:ident src_mem) => {$self.memories.remap($arg)};
(map $self:ident $arg:ident dst_mem) => {$self.memories.remap($arg)};
(map $self:ident $arg:ident table) => {$self.tables.remap($arg)};
(map $self:ident $arg:ident table_index) => {$self.tables.remap($arg)};
(map $self:ident $arg:ident src_table) => {$self.tables.remap($arg)};
(map $self:ident $arg:ident dst_table) => {$self.tables.remap($arg)};
(map $self:ident $arg:ident type_index) => {$self.types.remap($arg)};
(map $self:ident $arg:ident ty) => {$self.valty($arg)};
(map $self:ident $arg:ident local_index) => {$arg};
(map $self:ident $arg:ident lane) => {$arg};
(map $self:ident $arg:ident lanes) => {$arg};
(map $self:ident $arg:ident elem_index) => {$arg};
(map $self:ident $arg:ident data_index) => {$arg};
(map $self:ident $arg:ident table_byte) => {$arg};
(map $self:ident $arg:ident mem_byte) => {$arg};
(map $self:ident $arg:ident value) => {$arg};
(map $self:ident $arg:ident targets) => ((
$arg.targets().map(|i| i.unwrap()).collect::<Vec<_>>().into(),
$arg.default(),
));
}
impl<'a> VisitOperator<'a> for Encoder {
type Output = ();
wasmparser::for_each_operator!(define_encode);
}
mod bitvec {
use std::mem;
type T = u64;
#[derive(Default)]
pub struct BitVec {
bits: Vec<T>,
}
impl BitVec {
pub fn insert(&mut self, idx: u32) -> bool {
let (idx, bit) = idx_bit(idx);
match self.bits.get_mut(idx) {
Some(bits) => {
if *bits & bit != 0 {
return false;
}
*bits |= bit;
}
None => {
self.bits.resize(idx + 1, 0);
self.bits[idx] = bit;
}
}
true
}
pub fn contains(&self, idx: u32) -> bool {
let (idx, bit) = idx_bit(idx);
match self.bits.get(idx) {
Some(bits) => (*bits & bit) != 0,
None => false,
}
}
}
fn idx_bit(idx: u32) -> (usize, T) {
let idx = idx as usize;
let size = mem::size_of::<T>() * 8;
let index = idx / size;
let bit = 1 << (idx % size);
(index, bit)
}
}
#[derive(Default)]
struct Remap {
map: HashMap<u32, u32>,
next: u32,
}
impl Remap {
fn push(&mut self, old: u32) {
self.map.insert(old, self.next);
self.next += 1;
}
fn remap(&self, old: u32) -> u32 {
*self
.map
.get(&old)
.unwrap_or_else(|| panic!("can't map {old} to a new index"))
}
}