#![warn(missing_docs)]
use super::ref_list::{EntryRef, RefList};
use crate::std::{borrow::ToOwned, collections::BTreeMap, string::String, vec::Vec};
use parity_wasm::elements;
#[derive(Debug)]
pub enum ImportedOrDeclared<T = ()> {
Imported(String, String),
Declared(T),
}
impl<T> From<&elements::ImportEntry> for ImportedOrDeclared<T> {
fn from(v: &elements::ImportEntry) -> Self {
ImportedOrDeclared::Imported(v.module().to_owned(), v.field().to_owned())
}
}
#[derive(Debug)]
pub enum Error {
InconsistentSource,
Format(elements::Error),
DetachedEntry,
}
pub type FuncOrigin = ImportedOrDeclared<FuncBody>;
pub type GlobalOrigin = ImportedOrDeclared<Vec<Instruction>>;
pub type MemoryOrigin = ImportedOrDeclared;
pub type TableOrigin = ImportedOrDeclared;
#[derive(Debug)]
pub struct FuncBody {
pub locals: Vec<elements::Local>,
pub code: Vec<Instruction>,
}
#[derive(Debug)]
pub struct Func {
pub type_ref: EntryRef<elements::Type>,
pub origin: FuncOrigin,
}
#[derive(Debug)]
pub struct Global {
pub content: elements::ValueType,
pub is_mut: bool,
pub origin: GlobalOrigin,
}
#[derive(Debug)]
pub enum Instruction {
Plain(elements::Instruction),
Call(EntryRef<Func>),
CallIndirect(EntryRef<elements::Type>, u8),
GetGlobal(EntryRef<Global>),
SetGlobal(EntryRef<Global>),
}
#[derive(Debug)]
pub struct Memory {
pub limits: elements::ResizableLimits,
pub origin: MemoryOrigin,
}
#[derive(Debug)]
pub struct Table {
pub limits: elements::ResizableLimits,
pub origin: TableOrigin,
}
#[derive(Debug)]
pub enum SegmentLocation {
Passive,
Default(Vec<Instruction>),
WithIndex(u32, Vec<Instruction>),
}
#[derive(Debug)]
pub struct DataSegment {
pub location: SegmentLocation,
pub value: Vec<u8>,
}
#[derive(Debug)]
pub struct ElementSegment {
pub location: SegmentLocation,
pub value: Vec<EntryRef<Func>>,
}
#[derive(Debug)]
pub enum ExportLocal {
Func(EntryRef<Func>),
Global(EntryRef<Global>),
Table(EntryRef<Table>),
Memory(EntryRef<Memory>),
}
#[derive(Debug)]
pub struct Export {
pub name: String,
pub local: ExportLocal,
}
#[derive(Debug, Default)]
pub struct Module {
pub types: RefList<elements::Type>,
pub funcs: RefList<Func>,
pub memory: RefList<Memory>,
pub tables: RefList<Table>,
pub globals: RefList<Global>,
pub start: Option<EntryRef<Func>>,
pub exports: Vec<Export>,
pub elements: Vec<ElementSegment>,
pub data: Vec<DataSegment>,
pub other: BTreeMap<usize, elements::Section>,
}
impl Module {
fn map_instructions(&self, instructions: &[elements::Instruction]) -> Vec<Instruction> {
use parity_wasm::elements::Instruction::*;
instructions
.iter()
.map(|instruction| match instruction {
Call(func_idx) => Instruction::Call(self.funcs.clone_ref(*func_idx as usize)),
CallIndirect(type_idx, arg2) =>
Instruction::CallIndirect(self.types.clone_ref(*type_idx as usize), *arg2),
SetGlobal(global_idx) =>
Instruction::SetGlobal(self.globals.clone_ref(*global_idx as usize)),
GetGlobal(global_idx) =>
Instruction::GetGlobal(self.globals.clone_ref(*global_idx as usize)),
other_instruction => Instruction::Plain(other_instruction.clone()),
})
.collect()
}
fn generate_instructions(&self, instructions: &[Instruction]) -> Vec<elements::Instruction> {
use parity_wasm::elements::Instruction::*;
instructions
.iter()
.map(|instruction| match instruction {
Instruction::Call(func_ref) =>
Call(func_ref.order().expect("detached instruction!") as u32),
Instruction::CallIndirect(type_ref, arg2) =>
CallIndirect(type_ref.order().expect("detached instruction!") as u32, *arg2),
Instruction::SetGlobal(global_ref) =>
SetGlobal(global_ref.order().expect("detached instruction!") as u32),
Instruction::GetGlobal(global_ref) =>
GetGlobal(global_ref.order().expect("detached instruction!") as u32),
Instruction::Plain(plain) => plain.clone(),
})
.collect()
}
pub fn from_elements(module: &elements::Module) -> Result<Self, Error> {
let mut res = Module::default();
let mut imported_functions = 0;
for (idx, section) in module.sections().iter().enumerate() {
match section {
elements::Section::Type(type_section) => {
res.types = RefList::from_slice(type_section.types());
},
elements::Section::Import(import_section) =>
for entry in import_section.entries() {
match *entry.external() {
elements::External::Function(f) => {
res.funcs.push(Func {
type_ref: res
.types
.get(f as usize)
.ok_or(Error::InconsistentSource)?
.clone(),
origin: entry.into(),
});
imported_functions += 1;
},
elements::External::Memory(m) => {
res.memory
.push(Memory { limits: *m.limits(), origin: entry.into() });
},
elements::External::Global(g) => {
res.globals.push(Global {
content: g.content_type(),
is_mut: g.is_mutable(),
origin: entry.into(),
});
},
elements::External::Table(t) => {
res.tables
.push(Table { limits: *t.limits(), origin: entry.into() });
},
};
},
elements::Section::Function(function_section) => {
for f in function_section.entries() {
res.funcs.push(Func {
type_ref: res
.types
.get(f.type_ref() as usize)
.ok_or(Error::InconsistentSource)?
.clone(),
origin: ImportedOrDeclared::Declared(FuncBody {
locals: Vec::new(),
code: Vec::new(),
}),
});
}
},
elements::Section::Table(table_section) =>
for t in table_section.entries() {
res.tables.push(Table {
limits: *t.limits(),
origin: ImportedOrDeclared::Declared(()),
});
},
elements::Section::Memory(table_section) =>
for t in table_section.entries() {
res.memory.push(Memory {
limits: *t.limits(),
origin: ImportedOrDeclared::Declared(()),
});
},
elements::Section::Global(global_section) =>
for g in global_section.entries() {
let init_code = res.map_instructions(g.init_expr().code());
res.globals.push(Global {
content: g.global_type().content_type(),
is_mut: g.global_type().is_mutable(),
origin: ImportedOrDeclared::Declared(init_code),
});
},
elements::Section::Export(export_section) =>
for e in export_section.entries() {
let local = match e.internal() {
elements::Internal::Function(func_idx) =>
ExportLocal::Func(res.funcs.clone_ref(*func_idx as usize)),
elements::Internal::Global(global_idx) =>
ExportLocal::Global(res.globals.clone_ref(*global_idx as usize)),
elements::Internal::Memory(mem_idx) =>
ExportLocal::Memory(res.memory.clone_ref(*mem_idx as usize)),
elements::Internal::Table(table_idx) =>
ExportLocal::Table(res.tables.clone_ref(*table_idx as usize)),
};
res.exports.push(Export { local, name: e.field().to_owned() })
},
elements::Section::Start(start_func) => {
res.start = Some(res.funcs.clone_ref(*start_func as usize));
},
elements::Section::Element(element_section) => {
for element_segment in element_section.entries() {
let init_expr = element_segment
.offset()
.as_ref()
.expect("parity-wasm is compiled without bulk-memory operations")
.code();
let location = SegmentLocation::Default(res.map_instructions(init_expr));
let funcs_map = element_segment
.members()
.iter()
.map(|idx| res.funcs.clone_ref(*idx as usize))
.collect::<Vec<EntryRef<Func>>>();
res.elements.push(ElementSegment { value: funcs_map, location });
}
},
elements::Section::Code(code_section) => {
for (idx, func_body) in code_section.bodies().iter().enumerate() {
let code = res.map_instructions(func_body.code().elements());
let mut func = res.funcs.get_ref(imported_functions + idx).write();
match &mut func.origin {
ImportedOrDeclared::Declared(body) => {
body.code = code;
body.locals = func_body.locals().to_vec();
},
_ => return Err(Error::InconsistentSource),
}
}
},
elements::Section::Data(data_section) => {
for data_segment in data_section.entries() {
let init_expr = data_segment
.offset()
.as_ref()
.expect("parity-wasm is compiled without bulk-memory operations")
.code();
let location = SegmentLocation::Default(res.map_instructions(init_expr));
res.data
.push(DataSegment { value: data_segment.value().to_vec(), location });
}
},
_ => {
res.other.insert(idx, section.clone());
},
}
}
Ok(res)
}
pub fn generate(&self) -> Result<elements::Module, Error> {
use self::ImportedOrDeclared::*;
let mut idx = 0;
let mut sections = Vec::new();
custom_round(&self.other, &mut idx, &mut sections);
if !self.types.is_empty() {
let mut type_section = elements::TypeSection::default();
{
let types = type_section.types_mut();
for type_entry in self.types.iter() {
types.push(type_entry.read().clone())
}
}
sections.push(elements::Section::Type(type_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
let mut import_section = elements::ImportSection::default();
let add = {
let imports = import_section.entries_mut();
for func in self.funcs.iter() {
match &func.read().origin {
Imported(module, field) => imports.push(elements::ImportEntry::new(
module.to_owned(),
field.to_owned(),
elements::External::Function(
func.read().type_ref.order().ok_or(Error::DetachedEntry)? as u32,
),
)),
_ => continue,
}
}
for global in self.globals.iter() {
match &global.read().origin {
Imported(module, field) => imports.push(elements::ImportEntry::new(
module.to_owned(),
field.to_owned(),
elements::External::Global(elements::GlobalType::new(
global.read().content,
global.read().is_mut,
)),
)),
_ => continue,
}
}
for memory in self.memory.iter() {
match &memory.read().origin {
Imported(module, field) => imports.push(elements::ImportEntry::new(
module.to_owned(),
field.to_owned(),
elements::External::Memory(elements::MemoryType::new(
memory.read().limits.initial(),
memory.read().limits.maximum(),
)),
)),
_ => continue,
}
}
for table in self.tables.iter() {
match &table.read().origin {
Imported(module, field) => imports.push(elements::ImportEntry::new(
module.to_owned(),
field.to_owned(),
elements::External::Table(elements::TableType::new(
table.read().limits.initial(),
table.read().limits.maximum(),
)),
)),
_ => continue,
}
}
!imports.is_empty()
};
if add {
sections.push(elements::Section::Import(import_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if !self.funcs.is_empty() {
let mut func_section = elements::FunctionSection::default();
{
let funcs = func_section.entries_mut();
for func in self.funcs.iter() {
match func.read().origin {
Declared(_) => {
funcs.push(elements::Func::new(
func.read().type_ref.order().ok_or(Error::DetachedEntry)? as u32,
));
},
_ => continue,
}
}
}
sections.push(elements::Section::Function(func_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if !self.tables.is_empty() {
let mut table_section = elements::TableSection::default();
{
let tables = table_section.entries_mut();
for table in self.tables.iter() {
match table.read().origin {
Declared(_) => {
tables.push(elements::TableType::new(
table.read().limits.initial(),
table.read().limits.maximum(),
));
},
_ => continue,
}
}
}
sections.push(elements::Section::Table(table_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if !self.memory.is_empty() {
let mut memory_section = elements::MemorySection::default();
{
let memories = memory_section.entries_mut();
for memory in self.memory.iter() {
match memory.read().origin {
Declared(_) => {
memories.push(elements::MemoryType::new(
memory.read().limits.initial(),
memory.read().limits.maximum(),
));
},
_ => continue,
}
}
}
sections.push(elements::Section::Memory(memory_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if !self.globals.is_empty() {
let mut global_section = elements::GlobalSection::default();
{
let globals = global_section.entries_mut();
for global in self.globals.iter() {
match &global.read().origin {
Declared(init_code) => {
globals.push(elements::GlobalEntry::new(
elements::GlobalType::new(
global.read().content,
global.read().is_mut,
),
elements::InitExpr::new(self.generate_instructions(&init_code[..])),
));
},
_ => continue,
}
}
}
sections.push(elements::Section::Global(global_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if !self.exports.is_empty() {
let mut export_section = elements::ExportSection::default();
{
let exports = export_section.entries_mut();
for export in self.exports.iter() {
let internal = match &export.local {
ExportLocal::Func(func_ref) => elements::Internal::Function(
func_ref.order().ok_or(Error::DetachedEntry)? as u32,
),
ExportLocal::Global(global_ref) => elements::Internal::Global(
global_ref.order().ok_or(Error::DetachedEntry)? as u32,
),
ExportLocal::Table(table_ref) => elements::Internal::Table(
table_ref.order().ok_or(Error::DetachedEntry)? as u32,
),
ExportLocal::Memory(memory_ref) => elements::Internal::Memory(
memory_ref.order().ok_or(Error::DetachedEntry)? as u32,
),
};
exports.push(elements::ExportEntry::new(export.name.to_owned(), internal));
}
}
sections.push(elements::Section::Export(export_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if let Some(func_ref) = &self.start {
sections.push(elements::Section::Start(
func_ref.order().ok_or(Error::DetachedEntry)? as u32
));
}
if !self.elements.is_empty() {
let mut element_section = elements::ElementSection::default();
{
let element_segments = element_section.entries_mut();
for element in self.elements.iter() {
match &element.location {
SegmentLocation::Default(offset_expr) => {
let mut elements_map = Vec::new();
for f in element.value.iter() {
elements_map.push(f.order().ok_or(Error::DetachedEntry)? as u32);
}
element_segments.push(elements::ElementSegment::new(
0,
Some(elements::InitExpr::new(
self.generate_instructions(&offset_expr[..]),
)),
elements_map,
));
},
_ => unreachable!("Other segment location types are never added"),
}
}
}
sections.push(elements::Section::Element(element_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if !self.funcs.is_empty() {
let mut code_section = elements::CodeSection::default();
{
let funcs = code_section.bodies_mut();
for func in self.funcs.iter() {
match &func.read().origin {
Declared(body) => {
funcs.push(elements::FuncBody::new(
body.locals.clone(),
elements::Instructions::new(
self.generate_instructions(&body.code[..]),
),
));
},
_ => continue,
}
}
}
sections.push(elements::Section::Code(code_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
if !self.data.is_empty() {
let mut data_section = elements::DataSection::default();
{
let data_segments = data_section.entries_mut();
for data_entry in self.data.iter() {
match &data_entry.location {
SegmentLocation::Default(offset_expr) => {
data_segments.push(elements::DataSegment::new(
0,
Some(elements::InitExpr::new(
self.generate_instructions(&offset_expr[..]),
)),
data_entry.value.clone(),
));
},
_ => unreachable!("Other segment location types are never added"),
}
}
}
sections.push(elements::Section::Data(data_section));
idx += 1;
custom_round(&self.other, &mut idx, &mut sections);
}
Ok(elements::Module::new(sections))
}
}
fn custom_round(
map: &BTreeMap<usize, elements::Section>,
idx: &mut usize,
sections: &mut Vec<elements::Section>,
) {
while let Some(other_section) = map.get(idx) {
sections.push(other_section.clone());
*idx += 1;
}
}
pub fn parse(wasm: &[u8]) -> Result<Module, Error> {
Module::from_elements(&::parity_wasm::deserialize_buffer(wasm).map_err(Error::Format)?)
}
pub fn generate(f: &Module) -> Result<Vec<u8>, Error> {
let pm = f.generate()?;
::parity_wasm::serialize(pm).map_err(Error::Format)
}
#[cfg(test)]
mod tests {
use indoc::indoc;
use parity_wasm::elements;
fn load_sample(wat: &'static str) -> super::Module {
super::parse(&wabt::wat2wasm(wat).expect("faled to parse wat!")[..])
.expect("error making representation")
}
fn validate_sample(module: &super::Module) {
let binary = super::generate(module).expect("Failed to generate binary");
wabt::Module::read_binary(&binary, &Default::default())
.expect("Wabt failed to read final binary")
.validate()
.expect("Invalid module");
}
#[test]
fn smoky() {
let sample = load_sample(indoc!(
r#"
(module
(type (func))
(func (type 0))
(memory 0 1)
(export "simple" (func 0)))"#
));
assert_eq!(sample.types.len(), 1);
assert_eq!(sample.funcs.len(), 1);
assert_eq!(sample.tables.len(), 0);
assert_eq!(sample.memory.len(), 1);
assert_eq!(sample.exports.len(), 1);
assert_eq!(sample.types.get_ref(0).link_count(), 1);
assert_eq!(sample.funcs.get_ref(0).link_count(), 1);
}
#[test]
fn table() {
let mut sample = load_sample(indoc!(
r#"
(module
(import "env" "foo" (func $foo))
(func (param i32)
get_local 0
i32.const 0
call $i32.add
drop
)
(func $i32.add (export "i32.add") (param i32 i32) (result i32)
get_local 0
get_local 1
i32.add
)
(table 10 anyfunc)
;; Refer all types of functions: imported, defined not exported and defined exported.
(elem (i32.const 0) 0 1 2)
)"#
));
{
let element_func = &sample.elements[0].value[1];
let rfunc = element_func.read();
let rtype = &**rfunc.type_ref.read();
let elements::Type::Function(ftype) = rtype;
assert_eq!(rfunc.order(), Some(1));
assert_eq!(ftype.params().len(), 1);
}
sample.funcs.begin_delete().push(0).done();
{
let element_func = &sample.elements[0].value[1];
let rfunc = element_func.read();
let rtype = &**rfunc.type_ref.read();
let elements::Type::Function(ftype) = rtype;
assert_eq!(rfunc.order(), Some(0));
assert_eq!(ftype.params().len(), 1);
}
}
#[test]
fn new_import() {
let mut sample = load_sample(indoc!(
r#"
(module
(type (;0;) (func))
(type (;1;) (func (param i32 i32) (result i32)))
(import "env" "foo" (func (type 1)))
(func (param i32)
get_local 0
i32.const 0
call 0
drop
)
(func (type 0)
i32.const 0
call 1
)
)"#
));
{
let type_ref_0 = sample.types.clone_ref(0);
let declared_func_2 = sample.funcs.clone_ref(2);
let mut tx = sample.funcs.begin_insert_not_until(|f| {
matches!(f.origin, super::ImportedOrDeclared::Imported(_, _))
});
let new_import_func = tx.push(super::Func {
type_ref: type_ref_0,
origin: super::ImportedOrDeclared::Imported("env".to_owned(), "bar".to_owned()),
});
tx.done();
assert_eq!(new_import_func.order(), Some(1));
assert_eq!(declared_func_2.order(), Some(3));
assert_eq!(
match &declared_func_2.read().origin {
super::ImportedOrDeclared::Declared(body) => {
match &body.code[1] {
super::Instruction::Call(called_func) => called_func.order(),
_ => panic!("instruction #2 should be a call!"),
}
},
_ => panic!("func #3 should be declared!"),
},
Some(2),
"Call should be recalculated to 2"
);
}
validate_sample(&sample);
}
#[test]
fn simple_opt() {
let mut sample = load_sample(indoc!(
r#"
(module
(type (;0;) (func))
(type (;1;) (func (param i32 i32) (result i32)))
(type (;2;) (func (param i32 i32) (result i32)))
(type (;3;) (func (param i32 i32) (result i32)))
(import "env" "foo" (func (type 1)))
(import "env" "foo2" (func (type 2)))
(import "env" "foo3" (func (type 3)))
(func (type 0)
i32.const 1
i32.const 1
call 0
drop
)
(func (type 0)
i32.const 2
i32.const 2
call 1
drop
)
(func (type 0)
i32.const 3
i32.const 3
call 2
drop
)
(func (type 0)
call 3
)
)"#
));
validate_sample(&sample);
sample.funcs.begin_delete().push(4).push(5).done();
validate_sample(&sample);
sample.funcs.begin_delete().push(1).push(2).done();
validate_sample(&sample);
let declared_func_2 = sample.funcs.clone_ref(2);
assert_eq!(
match &declared_func_2.read().origin {
super::ImportedOrDeclared::Declared(body) => {
match &body.code[0] {
super::Instruction::Call(called_func) => called_func.order(),
wrong_instruction => panic!(
"instruction #2 should be a call but got {:?}!",
wrong_instruction
),
}
},
_ => panic!("func #0 should be declared!"),
},
Some(1),
"Call should be recalculated to 1"
);
}
}