#[cfg(feature = "wasm-compile")]
use wasm_encoder::{
BlockType, ConstExpr, Function, GlobalSection, GlobalType, Instruction, MemorySection,
MemoryType, Module, ValType,
};
#[cfg(feature = "wasm-compile")]
pub const DEFAULT_MEMORY_PAGES: u32 = 1;
#[cfg(feature = "wasm-compile")]
pub const MAX_MEMORY_PAGES: u32 = 256;
#[cfg(feature = "wasm-compile")]
pub const PAGE_SIZE: u32 = 65536;
#[cfg(feature = "wasm-compile")]
pub const DEFAULT_HEAP_START: u32 = 1024;
#[cfg(feature = "wasm-compile")]
#[derive(Debug, Clone, Copy)]
pub struct BumpAllocator {
heap_base_global: u32,
heap_end_global: u32,
}
#[cfg(feature = "wasm-compile")]
impl BumpAllocator {
pub fn new(heap_base_global: u32, heap_end_global: u32) -> Self {
Self {
heap_base_global,
heap_end_global,
}
}
pub fn heap_base_global(&self) -> u32 {
self.heap_base_global
}
pub fn heap_end_global(&self) -> u32 {
self.heap_end_global
}
pub fn emit_globals(module: &mut Module, initial_heap: u32) {
let mut globals = GlobalSection::new();
globals.global(
GlobalType {
val_type: ValType::I32,
mutable: true,
},
&ConstExpr::i32_const(initial_heap as i32),
);
globals.global(
GlobalType {
val_type: ValType::I32,
mutable: false,
},
&ConstExpr::i32_const(PAGE_SIZE as i32),
);
module.section(&globals);
}
pub fn emit_globals_with_end(module: &mut Module, initial_heap: u32, heap_end: u32) {
let mut globals = GlobalSection::new();
globals.global(
GlobalType {
val_type: ValType::I32,
mutable: true,
},
&ConstExpr::i32_const(initial_heap as i32),
);
globals.global(
GlobalType {
val_type: ValType::I32,
mutable: false,
},
&ConstExpr::i32_const(heap_end as i32),
);
module.section(&globals);
}
pub fn emit_memory_section(module: &mut Module, initial_pages: u32) {
let mut memories = MemorySection::new();
memories.memory(MemoryType {
minimum: initial_pages as u64,
maximum: Some(MAX_MEMORY_PAGES as u64),
memory64: false,
shared: false,
});
module.section(&memories);
}
pub fn emit_alloc_function() -> Vec<Instruction<'static>> {
vec![
Instruction::GlobalGet(0), Instruction::LocalGet(1), Instruction::I32Add,
Instruction::I32Const(1),
Instruction::I32Sub,
Instruction::LocalGet(1), Instruction::I32Const(1),
Instruction::I32Sub,
Instruction::I32Const(-1),
Instruction::I32Xor,
Instruction::I32And,
Instruction::LocalTee(2), Instruction::LocalGet(0), Instruction::I32Add,
Instruction::LocalTee(3), Instruction::GlobalGet(1), Instruction::I32GtU,
Instruction::If(BlockType::Empty),
Instruction::I32Const(0),
Instruction::Return,
Instruction::End,
Instruction::LocalGet(3), Instruction::GlobalSet(0), Instruction::LocalGet(2), Instruction::End,
]
}
pub fn build_alloc_function() -> Function {
let locals = vec![(2, ValType::I32)];
let mut function = Function::new(locals);
for instr in Self::emit_alloc_function() {
function.instruction(&instr);
}
function
}
pub fn alloc_type_signature() -> (Vec<ValType>, Vec<ValType>) {
(vec![ValType::I32, ValType::I32], vec![ValType::I32])
}
}
#[cfg(feature = "wasm-compile")]
impl Default for BumpAllocator {
fn default() -> Self {
Self::new(0, 1)
}
}
#[cfg(feature = "wasm-compile")]
pub fn align_up(offset: u32, alignment: u32) -> u32 {
(offset + alignment - 1) & !(alignment - 1)
}
#[cfg(test)]
#[cfg(feature = "wasm-compile")]
mod tests {
use super::*;
#[test]
fn test_align_up() {
assert_eq!(align_up(0, 4), 0);
assert_eq!(align_up(1, 4), 4);
assert_eq!(align_up(3, 4), 4);
assert_eq!(align_up(4, 4), 4);
assert_eq!(align_up(5, 4), 8);
assert_eq!(align_up(0, 8), 0);
assert_eq!(align_up(1, 8), 8);
assert_eq!(align_up(7, 8), 8);
assert_eq!(align_up(8, 8), 8);
assert_eq!(align_up(9, 8), 16);
}
#[test]
fn test_bump_allocator_new() {
let allocator = BumpAllocator::new(0, 1);
assert_eq!(allocator.heap_base_global(), 0);
assert_eq!(allocator.heap_end_global(), 1);
}
#[test]
fn test_bump_allocator_default() {
let allocator = BumpAllocator::default();
assert_eq!(allocator.heap_base_global(), 0);
assert_eq!(allocator.heap_end_global(), 1);
}
#[test]
fn test_alloc_type_signature() {
let (params, results) = BumpAllocator::alloc_type_signature();
assert_eq!(params, vec![ValType::I32, ValType::I32]);
assert_eq!(results, vec![ValType::I32]);
}
#[test]
fn test_emit_alloc_function_not_empty() {
let instructions = BumpAllocator::emit_alloc_function();
assert!(!instructions.is_empty());
assert!(matches!(instructions.last(), Some(Instruction::End)));
}
#[test]
fn test_build_alloc_function() {
let _function = BumpAllocator::build_alloc_function();
}
#[test]
fn test_emit_memory_section() {
let mut module = Module::new();
BumpAllocator::emit_memory_section(&mut module, 1);
let _bytes = module.finish();
}
#[test]
fn test_emit_globals() {
let mut module = Module::new();
BumpAllocator::emit_globals(&mut module, 1024);
let _bytes = module.finish();
}
#[test]
fn test_emit_globals_with_end() {
let mut module = Module::new();
BumpAllocator::emit_globals_with_end(&mut module, 1024, 4 * PAGE_SIZE);
let _bytes = module.finish();
}
#[test]
fn test_constants() {
assert_eq!(PAGE_SIZE, 65536);
assert_eq!(DEFAULT_MEMORY_PAGES, 1);
assert_eq!(MAX_MEMORY_PAGES, 256);
assert_eq!(DEFAULT_HEAP_START, 1024);
}
}