use super::root::Roots;
use crate::schema::{FieldTag, OperationKind};
use wasm_encoder::{
BlockType, Function, HeapType, Instruction as I, MemArg, MemArgTarget, RefType, ValType,
};
const ABI_VALUE_LOCAL: u32 = 6;
fn memory_address(function: &mut Function, pointer: u32, cursor: u32, memory64: bool, stride: u32) {
function
.instruction(&I::LocalGet(pointer))
.instruction(&I::LocalGet(cursor));
if memory64 {
function.instruction(&I::I64ExtendI32U);
if stride != 1 {
function
.instruction(&I::I64Const(i64::from(stride)))
.instruction(&I::I64Mul);
}
function.instruction(&I::I64Add);
} else {
if stride != 1 {
function
.instruction(&I::I32Const(stride as i32))
.instruction(&I::I32Mul);
}
function.instruction(&I::I32Add);
}
}
struct MemoryCopy<'a> {
ty: u32,
memory64: bool,
tag: FieldTag,
abi: bool,
field_input: &'a dyn Fn(&mut Function, u32),
field_output: &'a dyn Fn(&mut Function),
}
impl MemoryCopy<'_> {
fn memory_tag(&self) -> FieldTag {
if self.abi {
match self.tag {
FieldTag::I64 | FieldTag::F32 | FieldTag::F64 => self.tag,
FieldTag::I8
| FieldTag::I16
| FieldTag::I32
| FieldTag::Bool
| FieldTag::ExternRef
| FieldTag::Ref
| FieldTag::NullableRef
| FieldTag::Array
| FieldTag::NullableArray => FieldTag::I32,
}
} else {
self.tag
}
}
fn emit(
&self,
function: &mut Function,
array: u32,
cursor: u32,
memory_range: (u32, u32),
offset: Option<u32>,
read_to_memory: bool,
) {
let (pointer, length) = memory_range;
let memory = MemArg {
offset: 0,
align: 0,
target: MemArgTarget::Memory(0),
ordering: None,
};
let tag = self.memory_tag();
let stride = match tag {
FieldTag::I8 | FieldTag::Bool => 1,
FieldTag::I16 => 2,
FieldTag::I32 | FieldTag::F32 => 4,
FieldTag::I64 | FieldTag::F64 => 8,
_ => unreachable!("scalar memory copy storage"),
};
function
.instruction(&I::Block(BlockType::Empty))
.instruction(&I::Loop(BlockType::Empty))
.instruction(&I::LocalGet(cursor))
.instruction(&I::LocalGet(length))
.instruction(&I::I32GeU)
.instruction(&I::BrIf(1));
if read_to_memory {
memory_address(function, pointer, cursor, self.memory64, stride);
}
function.instruction(&I::LocalGet(array));
if let Some(offset) = offset {
function
.instruction(&I::LocalGet(offset))
.instruction(&I::LocalGet(cursor))
.instruction(&I::I32Add);
} else {
function.instruction(&I::LocalGet(cursor));
}
if read_to_memory {
if matches!(self.tag, FieldTag::I8 | FieldTag::I16) {
function.instruction(&I::ArrayGetU(self.ty));
} else {
function.instruction(&I::ArrayGet(self.ty));
}
if self.abi {
(self.field_output)(function);
} else if tag == FieldTag::Bool {
function.instruction(&I::I32Eqz).instruction(&I::I32Eqz);
}
function.instruction(&match tag {
FieldTag::I8 | FieldTag::Bool => I::I32Store8(memory),
FieldTag::I16 => I::I32Store16(memory),
FieldTag::I32 => I::I32Store(memory),
FieldTag::I64 => I::I64Store(memory),
FieldTag::F32 => I::F32Store(memory),
FieldTag::F64 => I::F64Store(memory),
_ => unreachable!("scalar memory copy storage"),
});
} else {
memory_address(function, pointer, cursor, self.memory64, stride);
function.instruction(&match tag {
FieldTag::I8 | FieldTag::Bool => I::I32Load8U(memory),
FieldTag::I16 => I::I32Load16U(memory),
FieldTag::I32 => I::I32Load(memory),
FieldTag::I64 => I::I64Load(memory),
FieldTag::F32 => I::F32Load(memory),
FieldTag::F64 => I::F64Load(memory),
_ => unreachable!("scalar memory copy storage"),
});
if self.abi {
function.instruction(&I::LocalSet(ABI_VALUE_LOCAL));
(self.field_input)(function, ABI_VALUE_LOCAL);
}
function.instruction(&I::ArraySet(self.ty));
}
function
.instruction(&I::LocalGet(cursor))
.instruction(&I::I32Const(1))
.instruction(&I::I32Add)
.instruction(&I::LocalSet(cursor))
.instruction(&I::Br(0))
.instruction(&I::End)
.instruction(&I::End);
}
}
pub(super) fn memory_operation(
kind: OperationKind,
ty: u32,
memory64: bool,
roots: &Roots,
tag: FieldTag,
field_input: impl Fn(&mut Function, u32),
field_output: impl Fn(&mut Function),
) -> Function {
let abi = matches!(
kind,
OperationKind::ArrayReadAbi | OperationKind::ArrayWriteAbi
);
let copy = MemoryCopy {
ty,
memory64,
tag,
abi,
field_input: &field_input,
field_output: &field_output,
};
let array_ref = ValType::Ref(RefType {
nullable: true,
heap_type: HeapType::Concrete(ty),
});
let mut locals = vec![(1, array_ref), (1, ValType::I32)];
if kind == OperationKind::ArrayWriteAbi {
let value_type = match copy.memory_tag() {
FieldTag::I64 => ValType::I64,
FieldTag::F32 => ValType::F32,
FieldTag::F64 => ValType::F64,
FieldTag::I8
| FieldTag::I16
| FieldTag::I32
| FieldTag::Bool
| FieldTag::ExternRef
| FieldTag::Ref
| FieldTag::NullableRef
| FieldTag::Array
| FieldTag::NullableArray => ValType::I32,
};
locals.push((1, value_type));
}
let from_slice = kind == OperationKind::ArrayFromSlice;
let (pointer, length, array, cursor, offset) = if from_slice {
(0, 1, 2, 3, None)
} else {
(2, 3, 4, 5, Some(1))
};
let read_to_memory = matches!(kind, OperationKind::ArrayRead | OperationKind::ArrayReadAbi);
let mut function = Function::new(locals);
if from_slice {
function
.instruction(&I::LocalGet(length))
.instruction(&I::ArrayNewDefault(ty))
.instruction(&I::LocalSet(array));
} else {
function
.instruction(&I::LocalGet(0))
.instruction(&I::TableGet(roots.table.index))
.instruction(&I::RefCastNonNull(HeapType::Concrete(ty)))
.instruction(&I::LocalSet(array));
}
copy.emit(
&mut function,
array,
cursor,
(pointer, length),
offset,
read_to_memory,
);
if from_slice {
function
.instruction(&I::LocalGet(array))
.instruction(&I::Call(roots.root));
}
function.instruction(&I::End);
function
}