use std::borrow::Cow;
use wasm_encoder::{
BlockType, CodeSection, ConstExpr, DataSection, ElementSection, Elements, EntityType,
ExportKind, ExportSection, Function, FunctionSection, GlobalSection, GlobalType, ImportSection,
MemArg, MemorySection, MemoryType, Module, NameMap, NameSection, RefType, TableSection,
TableType, TypeSection, ValType,
};
pub const BUMP_ALLOCATOR_ALIGN: u32 = 8;
pub const BUMP_ALLOCATOR_NAME: &str = "__alloc";
pub const STR_EQ_NAME: &str = "__str_eq";
pub const STR_CONCAT_NAME: &str = "__str_concat";
pub const STR_LEN_NAME: &str = "__str_len";
pub const STR_IS_EMPTY_NAME: &str = "__str_is_empty";
pub const STR_BYTE_AT_NAME: &str = "__str_byte_at";
pub const STR_SLICE_NAME: &str = "__str_slice";
pub const STR_STARTS_WITH_NAME: &str = "__str_starts_with";
pub const STR_CONTAINS_NAME: &str = "__str_contains";
pub const ARRAY_LEN_NAME: &str = "__array_len";
pub const ARRAY_IS_EMPTY_NAME: &str = "__array_is_empty";
pub const OPTIONAL_IS_SOME_NAME: &str = "__optional_is_some";
pub const OPTIONAL_IS_NONE_NAME: &str = "__optional_is_none";
pub const RANGE_LEN_NAME: &str = "__range_len";
pub const RANGE_IS_EMPTY_NAME: &str = "__range_is_empty";
pub const DICT_LEN_NAME: &str = "__dict_len";
pub const DICT_IS_EMPTY_NAME: &str = "__dict_is_empty";
pub const CABI_REALLOC_NAME: &str = "cabi_realloc";
pub const MEMORY_EXPORT_NAME: &str = "memory";
pub const IMPORT_MODULE_NAME: &str = "cm32p2";
const BUMP_ALIGN_ROUND_UP_ADDEND: i32 = 7;
const BUMP_ALIGN_KEEP_MASK: i32 = -8;
const _: () = {
assert!(
BUMP_ALLOCATOR_ALIGN == 8,
"BUMP_ALIGN_ROUND_UP_ADDEND / BUMP_ALIGN_KEEP_MASK assume 8-byte alignment",
);
};
pub const MEMORY_INDEX: u32 = 0;
pub const HEAP_PTR_GLOBAL_INDEX: u32 = 0;
pub const HEAP_BASE: i32 = 0;
pub const INITIAL_MEMORY_PAGES: u64 = 1;
#[derive(Debug)]
#[non_exhaustive]
pub struct ModuleBuilder {
types: TypeSection,
imports: ImportSection,
import_function_count: u32,
functions: FunctionSection,
tables: TableSection,
memories: MemorySection,
exports: ExportSection,
elements: ElementSection,
code: CodeSection,
bump_allocator: Option<u32>,
str_eq: Option<u32>,
str_concat: Option<u32>,
str_len: Option<u32>,
str_is_empty: Option<u32>,
str_byte_at: Option<u32>,
str_slice: Option<u32>,
str_starts_with: Option<u32>,
str_contains: Option<u32>,
array_len: Option<u32>,
array_is_empty: Option<u32>,
optional_is_some: Option<u32>,
optional_is_none: Option<u32>,
range_len: Option<u32>,
range_is_empty: Option<u32>,
dict_len: Option<u32>,
dict_is_empty: Option<u32>,
closure_table_idx: Option<u32>,
method_table_idx: Option<u32>,
static_data: Vec<u8>,
function_names: Vec<Option<String>>,
}
impl Default for ModuleBuilder {
fn default() -> Self {
Self::new()
}
}
impl ModuleBuilder {
#[must_use]
pub fn new() -> Self {
let mut memories = MemorySection::new();
memories.memory(MemoryType {
minimum: INITIAL_MEMORY_PAGES,
maximum: None,
memory64: false,
shared: false,
page_size_log2: None,
});
let mut exports = ExportSection::new();
exports.export(MEMORY_EXPORT_NAME, ExportKind::Memory, MEMORY_INDEX);
Self {
types: TypeSection::new(),
imports: ImportSection::new(),
import_function_count: 0,
functions: FunctionSection::new(),
tables: TableSection::new(),
memories,
exports,
elements: ElementSection::new(),
code: CodeSection::new(),
bump_allocator: None,
str_eq: None,
str_concat: None,
str_len: None,
str_is_empty: None,
str_byte_at: None,
str_slice: None,
str_starts_with: None,
str_contains: None,
array_len: None,
array_is_empty: None,
optional_is_some: None,
optional_is_none: None,
range_len: None,
range_is_empty: None,
dict_len: None,
dict_is_empty: None,
closure_table_idx: None,
method_table_idx: None,
static_data: Vec::new(),
function_names: Vec::new(),
}
}
pub fn set_function_name(&mut self, function_index: u32, name: &str) {
let idx = function_index as usize;
if self.function_names.len() <= idx {
self.function_names.resize(idx.saturating_add(1), None);
}
if let Some(slot) = self.function_names.get_mut(idx) {
*slot = Some(name.to_owned());
}
}
pub fn set_static_data(&mut self, bytes: Vec<u8>) {
self.static_data = bytes;
}
pub fn declare_type(&mut self, params: &[ValType], results: &[ValType]) -> u32 {
let idx = self.types.len();
self.types
.ty()
.function(params.iter().copied(), results.iter().copied());
idx
}
pub fn declare_closure_table(&mut self, num_closures: u32) -> u32 {
if let Some(idx) = self.closure_table_idx {
return idx;
}
let idx = self.tables.len();
self.tables.table(TableType {
element_type: RefType::FUNCREF,
minimum: u64::from(num_closures),
maximum: Some(u64::from(num_closures)),
table64: false,
shared: false,
});
self.closure_table_idx = Some(idx);
idx
}
pub fn populate_closure_table(&mut self, wasm_func_indices: &[u32]) {
let Some(table_idx) = self.closure_table_idx else {
return;
};
self.elements.active(
Some(table_idx),
&ConstExpr::i32_const(0),
Elements::Functions(Cow::Borrowed(wasm_func_indices)),
);
}
#[must_use]
pub const fn closure_table_index(&self) -> Option<u32> {
self.closure_table_idx
}
pub fn declare_method_table(&mut self, num_methods: u32) -> u32 {
if let Some(idx) = self.method_table_idx {
return idx;
}
let idx = self.tables.len();
self.tables.table(TableType {
element_type: RefType::FUNCREF,
minimum: u64::from(num_methods),
maximum: Some(u64::from(num_methods)),
table64: false,
shared: false,
});
self.method_table_idx = Some(idx);
idx
}
pub fn populate_method_table(&mut self, wasm_func_indices: &[u32]) {
let Some(table_idx) = self.method_table_idx else {
return;
};
self.elements.active(
Some(table_idx),
&ConstExpr::i32_const(0),
Elements::Functions(Cow::Borrowed(wasm_func_indices)),
);
}
#[must_use]
pub const fn method_table_index(&self) -> Option<u32> {
self.method_table_idx
}
pub fn export_function(&mut self, name: &str, function_index: u32) {
self.exports.export(name, ExportKind::Func, function_index);
}
pub fn declare_function_with_body(
&mut self,
param_types: &[ValType],
result_types: &[ValType],
body: &Function,
) -> u32 {
let type_index = self.types.len();
let local_index = self.functions.len();
let func_index = self.import_function_count.saturating_add(local_index);
self.types
.ty()
.function(param_types.iter().copied(), result_types.iter().copied());
self.functions.function(type_index);
self.code.function(body);
func_index
}
pub fn declare_function_import(
&mut self,
module_name: &str,
fn_name: &str,
param_types: &[ValType],
result_types: &[ValType],
) -> u32 {
let type_index = self.types.len();
self.types
.ty()
.function(param_types.iter().copied(), result_types.iter().copied());
self.imports
.import(module_name, fn_name, EntityType::Function(type_index));
let func_index = self.import_function_count;
self.import_function_count = self.import_function_count.saturating_add(1);
self.set_function_name(func_index, fn_name);
func_index
}
pub fn declare_function(&mut self, param_types: &[ValType], result_types: &[ValType]) -> u32 {
let mut body = Function::new(core::iter::empty());
body.instructions().unreachable().end();
self.declare_function_with_body(param_types, result_types, &body)
}
pub fn declare_bump_allocator(&mut self) -> u32 {
if let Some(idx) = self.bump_allocator {
return idx;
}
let mut body = Function::new(core::iter::once((1, ValType::I32)));
let mut i = body.instructions();
i.global_get(HEAP_PTR_GLOBAL_INDEX)
.i32_const(BUMP_ALIGN_ROUND_UP_ADDEND)
.i32_add()
.i32_const(BUMP_ALIGN_KEEP_MASK)
.i32_and()
.local_tee(1)
.local_get(0)
.i32_add()
.global_set(HEAP_PTR_GLOBAL_INDEX)
.local_get(1)
.end();
let idx = self.declare_function_with_body(&[ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, BUMP_ALLOCATOR_NAME);
self.bump_allocator = Some(idx);
idx
}
#[must_use]
pub const fn bump_allocator_index(&self) -> Option<u32> {
self.bump_allocator
}
pub fn declare_str_eq(&mut self) -> u32 {
if let Some(idx) = self.str_eq {
return idx;
}
let mut body = Function::new(core::iter::once((2, ValType::I32)));
let mut i = body.instructions();
i.local_get(0)
.i32_load(MemArg {
offset: u64::from(crate::layout::STRING_LEN_OFFSET),
align: 2, memory_index: MEMORY_INDEX,
})
.local_tee(2) .local_get(1)
.i32_load(MemArg {
offset: u64::from(crate::layout::STRING_LEN_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
})
.i32_ne()
.if_(BlockType::Empty)
.i32_const(0)
.return_()
.end();
i.i32_const(0).local_set(3);
i.local_get(0)
.i32_load(MemArg {
offset: u64::from(crate::layout::STRING_PTR_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
})
.local_set(0);
i.local_get(1)
.i32_load(MemArg {
offset: u64::from(crate::layout::STRING_PTR_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
})
.local_set(1);
i.block(BlockType::Empty)
.loop_(BlockType::Empty)
.local_get(3)
.local_get(2)
.i32_ge_u()
.br_if(1)
.local_get(0)
.local_get(3)
.i32_add()
.i32_load8_u(MemArg {
offset: 0,
align: 0,
memory_index: MEMORY_INDEX,
})
.local_get(1)
.local_get(3)
.i32_add()
.i32_load8_u(MemArg {
offset: 0,
align: 0,
memory_index: MEMORY_INDEX,
})
.i32_ne()
.if_(BlockType::Empty)
.i32_const(0)
.return_()
.end()
.local_get(3)
.i32_const(1)
.i32_add()
.local_set(3)
.br(0)
.end() .end();
i.i32_const(1).end();
let idx =
self.declare_function_with_body(&[ValType::I32, ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, STR_EQ_NAME);
self.str_eq = Some(idx);
idx
}
#[must_use]
pub const fn str_eq_index(&self) -> Option<u32> {
self.str_eq
}
pub fn declare_str_concat(&mut self) -> u32 {
if let Some(idx) = self.str_concat {
return idx;
}
let alloc_idx = self.declare_bump_allocator();
let mut body = Function::new(core::iter::once((5, ValType::I32)));
let len_mem_arg = MemArg {
offset: u64::from(crate::layout::STRING_LEN_OFFSET),
align: 2, memory_index: MEMORY_INDEX,
};
let ptr_mem_arg = MemArg {
offset: u64::from(crate::layout::STRING_PTR_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
};
let header_mem_arg = |offset: u64| MemArg {
offset,
align: 2,
memory_index: MEMORY_INDEX,
};
let mut i = body.instructions();
i.local_get(0).i32_load(len_mem_arg).local_set(2);
i.local_get(1).i32_load(len_mem_arg).local_set(3);
i.local_get(2).local_get(3).i32_add().local_set(4);
i.local_get(4).call(alloc_idx).local_set(5);
i.local_get(5)
.local_get(0)
.i32_load(ptr_mem_arg)
.local_get(2)
.memory_copy(MEMORY_INDEX, MEMORY_INDEX);
i.local_get(5)
.local_get(2)
.i32_add()
.local_get(1)
.i32_load(ptr_mem_arg)
.local_get(3)
.memory_copy(MEMORY_INDEX, MEMORY_INDEX);
i.i32_const(i32::try_from(crate::layout::STRING_HEADER_SIZE).unwrap_or(8))
.call(alloc_idx)
.local_set(6);
i.local_get(6)
.local_get(5)
.i32_store(header_mem_arg(u64::from(crate::layout::STRING_PTR_OFFSET)));
i.local_get(6)
.local_get(4)
.i32_store(header_mem_arg(u64::from(crate::layout::STRING_LEN_OFFSET)));
i.local_get(6).end();
let idx =
self.declare_function_with_body(&[ValType::I32, ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, STR_CONCAT_NAME);
self.str_concat = Some(idx);
idx
}
#[must_use]
pub const fn str_concat_index(&self) -> Option<u32> {
self.str_concat
}
pub fn declare_str_len(&mut self) -> u32 {
if let Some(idx) = self.str_len {
return idx;
}
let mut body = Function::new(core::iter::empty());
body.instructions()
.local_get(0)
.i32_load(MemArg {
offset: u64::from(crate::layout::STRING_LEN_OFFSET),
align: 2, memory_index: MEMORY_INDEX,
})
.end();
let idx = self.declare_function_with_body(&[ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, STR_LEN_NAME);
self.str_len = Some(idx);
idx
}
#[must_use]
pub const fn str_len_index(&self) -> Option<u32> {
self.str_len
}
pub fn declare_str_is_empty(&mut self) -> u32 {
if let Some(idx) = self.str_is_empty {
return idx;
}
let mut body = Function::new(core::iter::empty());
body.instructions()
.local_get(0)
.i32_load(MemArg {
offset: u64::from(crate::layout::STRING_LEN_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
})
.i32_eqz()
.end();
let idx = self.declare_function_with_body(&[ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, STR_IS_EMPTY_NAME);
self.str_is_empty = Some(idx);
idx
}
#[must_use]
pub const fn str_is_empty_index(&self) -> Option<u32> {
self.str_is_empty
}
pub fn declare_str_byte_at(&mut self) -> u32 {
if let Some(idx) = self.str_byte_at {
return idx;
}
let len_mem_arg = MemArg {
offset: u64::from(crate::layout::STRING_LEN_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
};
let ptr_mem_arg = MemArg {
offset: u64::from(crate::layout::STRING_PTR_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
};
let mut body = Function::new(core::iter::empty());
body.instructions()
.local_get(1)
.local_get(0)
.i32_load(len_mem_arg)
.i32_ge_u()
.if_(BlockType::Empty)
.unreachable()
.end()
.local_get(0)
.i32_load(ptr_mem_arg)
.local_get(1)
.i32_add()
.i32_load8_u(MemArg {
offset: 0,
align: 0,
memory_index: MEMORY_INDEX,
})
.end();
let idx =
self.declare_function_with_body(&[ValType::I32, ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, STR_BYTE_AT_NAME);
self.str_byte_at = Some(idx);
idx
}
#[must_use]
pub const fn str_byte_at_index(&self) -> Option<u32> {
self.str_byte_at
}
pub fn declare_str_slice(&mut self) -> u32 {
if let Some(idx) = self.str_slice {
return idx;
}
let alloc_idx = self.declare_bump_allocator();
let len_mem_arg = MemArg {
offset: u64::from(crate::layout::STRING_LEN_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
};
let ptr_mem_arg = MemArg {
offset: u64::from(crate::layout::STRING_PTR_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
};
let header_size = i32::try_from(crate::layout::STRING_HEADER_SIZE).unwrap_or(8);
let mut body = Function::new(core::iter::once((1, ValType::I32)));
let mut i = body.instructions();
i.local_get(1)
.local_get(2)
.i32_gt_u()
.if_(BlockType::Empty)
.unreachable()
.end();
i.local_get(2)
.local_get(0)
.i32_load(len_mem_arg)
.i32_gt_u()
.if_(BlockType::Empty)
.unreachable()
.end();
i.i32_const(header_size).call(alloc_idx).local_set(3);
i.local_get(3)
.local_get(0)
.i32_load(ptr_mem_arg)
.local_get(1)
.i32_add()
.i32_store(ptr_mem_arg);
i.local_get(3)
.local_get(2)
.local_get(1)
.i32_sub()
.i32_store(len_mem_arg);
i.local_get(3).end();
let idx = self.declare_function_with_body(
&[ValType::I32, ValType::I32, ValType::I32],
&[ValType::I32],
&body,
);
self.set_function_name(idx, STR_SLICE_NAME);
self.str_slice = Some(idx);
idx
}
#[must_use]
pub const fn str_slice_index(&self) -> Option<u32> {
self.str_slice
}
pub fn declare_str_starts_with(&mut self) -> u32 {
if let Some(idx) = self.str_starts_with {
return idx;
}
let len_mem_arg = MemArg {
offset: u64::from(crate::layout::STRING_LEN_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
};
let ptr_mem_arg = MemArg {
offset: u64::from(crate::layout::STRING_PTR_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
};
let mut body = Function::new(core::iter::once((2, ValType::I32)));
let mut i = body.instructions();
i.local_get(1)
.i32_load(len_mem_arg)
.local_tee(2)
.local_get(0)
.i32_load(len_mem_arg)
.i32_gt_u()
.if_(BlockType::Empty)
.i32_const(0)
.return_()
.end();
i.local_get(0).i32_load(ptr_mem_arg).local_set(0);
i.local_get(1).i32_load(ptr_mem_arg).local_set(1);
i.i32_const(0).local_set(3);
i.block(BlockType::Empty)
.loop_(BlockType::Empty)
.local_get(3)
.local_get(2)
.i32_ge_u()
.br_if(1)
.local_get(0)
.local_get(3)
.i32_add()
.i32_load8_u(MemArg {
offset: 0,
align: 0,
memory_index: MEMORY_INDEX,
})
.local_get(1)
.local_get(3)
.i32_add()
.i32_load8_u(MemArg {
offset: 0,
align: 0,
memory_index: MEMORY_INDEX,
})
.i32_ne()
.if_(BlockType::Empty)
.i32_const(0)
.return_()
.end()
.local_get(3)
.i32_const(1)
.i32_add()
.local_set(3)
.br(0)
.end()
.end();
i.i32_const(1).end();
let idx =
self.declare_function_with_body(&[ValType::I32, ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, STR_STARTS_WITH_NAME);
self.str_starts_with = Some(idx);
idx
}
#[must_use]
pub const fn str_starts_with_index(&self) -> Option<u32> {
self.str_starts_with
}
pub fn declare_str_contains(&mut self) -> u32 {
if let Some(idx) = self.str_contains {
return idx;
}
let len_mem_arg = MemArg {
offset: u64::from(crate::layout::STRING_LEN_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
};
let ptr_mem_arg = MemArg {
offset: u64::from(crate::layout::STRING_PTR_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
};
let byte_load = MemArg {
offset: 0,
align: 0,
memory_index: MEMORY_INDEX,
};
let mut body = Function::new(core::iter::once((4, ValType::I32)));
let mut i = body.instructions();
i.local_get(1)
.i32_load(len_mem_arg)
.local_tee(2)
.i32_eqz()
.if_(BlockType::Empty)
.i32_const(1)
.return_()
.end();
i.local_get(2)
.local_get(0)
.i32_load(len_mem_arg)
.i32_gt_u()
.if_(BlockType::Empty)
.i32_const(0)
.return_()
.end();
i.local_get(0)
.i32_load(len_mem_arg)
.local_get(2)
.i32_sub()
.local_set(3);
i.local_get(0).i32_load(ptr_mem_arg).local_set(0);
i.local_get(1).i32_load(ptr_mem_arg).local_set(1);
i.i32_const(0).local_set(4);
i.block(BlockType::Empty)
.loop_(BlockType::Empty)
.local_get(4)
.local_get(3)
.i32_gt_u()
.br_if(1) .i32_const(0)
.local_set(5) .block(BlockType::Empty)
.loop_(BlockType::Empty)
.local_get(5)
.local_get(2)
.i32_ge_u()
.if_(BlockType::Empty)
.i32_const(1)
.return_()
.end()
.local_get(0)
.local_get(4)
.i32_add()
.local_get(5)
.i32_add()
.i32_load8_u(byte_load)
.local_get(1)
.local_get(5)
.i32_add()
.i32_load8_u(byte_load)
.i32_ne()
.br_if(1) .local_get(5)
.i32_const(1)
.i32_add()
.local_set(5)
.br(0) .end() .end() .local_get(4)
.i32_const(1)
.i32_add()
.local_set(4)
.br(0) .end() .end();
i.i32_const(0).end();
let idx =
self.declare_function_with_body(&[ValType::I32, ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, STR_CONTAINS_NAME);
self.str_contains = Some(idx);
idx
}
#[must_use]
pub const fn str_contains_index(&self) -> Option<u32> {
self.str_contains
}
pub fn declare_array_len(&mut self) -> u32 {
if let Some(idx) = self.array_len {
return idx;
}
let mut body = Function::new(core::iter::empty());
body.instructions()
.local_get(0)
.i32_load(MemArg {
offset: u64::from(crate::layout::ARRAY_HEADER_LEN_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
})
.end();
let idx = self.declare_function_with_body(&[ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, ARRAY_LEN_NAME);
self.array_len = Some(idx);
idx
}
pub fn declare_array_is_empty(&mut self) -> u32 {
if let Some(idx) = self.array_is_empty {
return idx;
}
let mut body = Function::new(core::iter::empty());
body.instructions()
.local_get(0)
.i32_load(MemArg {
offset: u64::from(crate::layout::ARRAY_HEADER_LEN_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
})
.i32_eqz()
.end();
let idx = self.declare_function_with_body(&[ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, ARRAY_IS_EMPTY_NAME);
self.array_is_empty = Some(idx);
idx
}
pub fn declare_optional_is_some(&mut self) -> u32 {
if let Some(idx) = self.optional_is_some {
return idx;
}
let mut body = Function::new(core::iter::empty());
body.instructions()
.local_get(0)
.i32_load(MemArg {
offset: 0,
align: 2,
memory_index: MEMORY_INDEX,
})
.i32_const(0)
.i32_ne()
.end();
let idx = self.declare_function_with_body(&[ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, OPTIONAL_IS_SOME_NAME);
self.optional_is_some = Some(idx);
idx
}
pub fn declare_optional_is_none(&mut self) -> u32 {
if let Some(idx) = self.optional_is_none {
return idx;
}
let mut body = Function::new(core::iter::empty());
body.instructions()
.local_get(0)
.i32_load(MemArg {
offset: 0,
align: 2,
memory_index: MEMORY_INDEX,
})
.i32_eqz()
.end();
let idx = self.declare_function_with_body(&[ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, OPTIONAL_IS_NONE_NAME);
self.optional_is_none = Some(idx);
idx
}
pub fn declare_range_len(&mut self) -> u32 {
if let Some(idx) = self.range_len {
return idx;
}
let mut body = Function::new(core::iter::empty());
body.instructions()
.local_get(0)
.i32_load(MemArg {
offset: 4, align: 2,
memory_index: MEMORY_INDEX,
})
.local_get(0)
.i32_load(MemArg {
offset: 0,
align: 2,
memory_index: MEMORY_INDEX,
})
.i32_sub()
.end();
let idx = self.declare_function_with_body(&[ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, RANGE_LEN_NAME);
self.range_len = Some(idx);
idx
}
pub fn declare_range_is_empty(&mut self) -> u32 {
if let Some(idx) = self.range_is_empty {
return idx;
}
let mut body = Function::new(core::iter::empty());
body.instructions()
.local_get(0)
.i32_load(MemArg {
offset: 4,
align: 2,
memory_index: MEMORY_INDEX,
})
.local_get(0)
.i32_load(MemArg {
offset: 0,
align: 2,
memory_index: MEMORY_INDEX,
})
.i32_le_s()
.end();
let idx = self.declare_function_with_body(&[ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, RANGE_IS_EMPTY_NAME);
self.range_is_empty = Some(idx);
idx
}
pub fn declare_dict_len(&mut self) -> u32 {
if let Some(idx) = self.dict_len {
return idx;
}
let mut body = Function::new(core::iter::empty());
body.instructions()
.local_get(0)
.i32_load(MemArg {
offset: u64::from(crate::layout::ARRAY_HEADER_LEN_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
})
.end();
let idx = self.declare_function_with_body(&[ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, DICT_LEN_NAME);
self.dict_len = Some(idx);
idx
}
pub fn declare_dict_is_empty(&mut self) -> u32 {
if let Some(idx) = self.dict_is_empty {
return idx;
}
let mut body = Function::new(core::iter::empty());
body.instructions()
.local_get(0)
.i32_load(MemArg {
offset: u64::from(crate::layout::ARRAY_HEADER_LEN_OFFSET),
align: 2,
memory_index: MEMORY_INDEX,
})
.i32_eqz()
.end();
let idx = self.declare_function_with_body(&[ValType::I32], &[ValType::I32], &body);
self.set_function_name(idx, DICT_IS_EMPTY_NAME);
self.dict_is_empty = Some(idx);
idx
}
pub fn declare_cabi_realloc(&mut self) -> u32 {
let alloc_idx = self.declare_bump_allocator();
let mut body = Function::new(core::iter::empty());
body.instructions().local_get(3).call(alloc_idx).end();
let idx = self.declare_function_with_body(
&[ValType::I32, ValType::I32, ValType::I32, ValType::I32],
&[ValType::I32],
&body,
);
self.set_function_name(idx, CABI_REALLOC_NAME);
self.export_function(CABI_REALLOC_NAME, idx);
idx
}
#[must_use]
pub fn function_count(&self) -> u32 {
self.import_function_count
.saturating_add(self.functions.len())
}
#[must_use]
pub fn finish(self) -> Vec<u8> {
let heap_base = compute_heap_base(self.static_data.len());
let mut globals = GlobalSection::new();
globals.global(
GlobalType {
val_type: ValType::I32,
mutable: true,
shared: false,
},
&ConstExpr::i32_const(heap_base),
);
let mut data = DataSection::new();
if !self.static_data.is_empty() {
data.active(
MEMORY_INDEX,
&ConstExpr::i32_const(HEAP_BASE),
self.static_data.iter().copied(),
);
}
let mut module = Module::new();
if !self.types.is_empty() {
module.section(&self.types);
}
if !self.imports.is_empty() {
module.section(&self.imports);
}
if !self.functions.is_empty() {
module.section(&self.functions);
}
if !self.tables.is_empty() {
module.section(&self.tables);
}
module.section(&self.memories);
module.section(&globals);
if !self.exports.is_empty() {
module.section(&self.exports);
}
if !self.elements.is_empty() {
module.section(&self.elements);
}
if !self.code.is_empty() {
module.section(&self.code);
}
if !self.static_data.is_empty() {
module.section(&data);
}
if let Some(name_section) = build_name_section(&self.function_names) {
module.section(&name_section);
}
module.finish()
}
}
fn build_name_section(function_names: &[Option<String>]) -> Option<NameSection> {
if function_names.iter().all(Option::is_none) {
return None;
}
let mut names = NameMap::new();
for (idx, slot) in function_names.iter().enumerate() {
if let Some(name) = slot {
let idx_u32 = u32::try_from(idx).unwrap_or(u32::MAX);
names.append(idx_u32, name);
}
}
let mut section = NameSection::new();
section.functions(&names);
Some(section)
}
fn compute_heap_base(data_len: usize) -> i32 {
if data_len == 0 {
return HEAP_BASE;
}
let raw = u32::try_from(data_len).unwrap_or(u32::MAX);
let mask = BUMP_ALLOCATOR_ALIGN.saturating_sub(1);
let aligned = raw.saturating_add(mask) & !mask;
i32::try_from(aligned).unwrap_or(i32::MAX)
}