use std::collections::HashMap;
use wasm_encoder::{Function, MemArg};
use wit_parser::{Resolve, SizeAlign, Type, TypeId};
use super::super::abi::emit::{I8_STORE_LOG2_ALIGN, SLICE_LEN_OFFSET, SLICE_PTR_OFFSET};
const EXPECTED_CELL_CASES: &[&str] = &[
"bool",
"integer",
"floating",
"text",
"bytes",
"list-of",
"tuple-of",
"option-some",
"option-none",
"result-ok",
"result-err",
"record-of",
"flags-set",
"enum-case",
"variant-case",
"resource-handle",
"stream-handle",
"future-handle",
"error-context-handle",
];
pub(crate) struct CellLayout {
pub size: u32,
pub align: u32,
payload_offset: u64,
discs: HashMap<String, u8>,
}
impl CellLayout {
pub(crate) fn from_resolve(sizes: &SizeAlign, resolve: &Resolve, cell_id: TypeId) -> Self {
use wit_parser::TypeDefKind;
let typedef = &resolve.types[cell_id];
let TypeDefKind::Variant(v) = &typedef.kind else {
panic!("CellLayout::from_resolve: `cell` typedef is not a variant");
};
let size = sizes.size(&Type::Id(cell_id)).size_wasm32() as u32;
let align = sizes.align(&Type::Id(cell_id)).align_wasm32() as u32;
let payload_offset = sizes
.payload_offset(v.tag(), v.cases.iter().map(|c| c.ty.as_ref()))
.size_wasm32() as u64;
let discs: HashMap<String, u8> = v
.cases
.iter()
.enumerate()
.map(|(i, c)| {
assert!(
i < u8::MAX as usize,
"CellLayout::from_resolve: `cell` variant has more than 255 cases"
);
(c.name.clone(), i as u8)
})
.collect();
for expected in EXPECTED_CELL_CASES {
assert!(
discs.contains_key(*expected),
"CellLayout::from_resolve: `cell` variant in WIT is missing case `{expected}` \
(the codegen needs every case in EXPECTED_CELL_CASES — update both together)"
);
}
assert_eq!(
discs.len(),
EXPECTED_CELL_CASES.len(),
"CellLayout::from_resolve: `cell` variant has {} cases, codegen expects {}",
discs.len(),
EXPECTED_CELL_CASES.len()
);
Self {
size,
align,
payload_offset,
discs,
}
}
fn disc_of(&self, name: &str) -> u8 {
*self
.discs
.get(name)
.unwrap_or_else(|| panic!("CellLayout::disc_of: no `cell` case named `{name}`"))
}
}
#[derive(Clone, Copy)]
enum StoreKind {
I8,
I32,
I64,
F64,
}
impl StoreKind {
fn natural_align(self) -> u32 {
match self {
StoreKind::I8 => 0,
StoreKind::I32 => 2,
StoreKind::I64 | StoreKind::F64 => 3,
}
}
}
#[derive(Clone, Copy)]
pub(crate) enum PayloadSource {
Local(u32),
ConstI32(i32),
}
#[derive(Clone, Copy)]
struct PayloadPart {
source: PayloadSource,
kind: StoreKind,
offset: u32,
}
impl CellLayout {
fn emit_cell(&self, f: &mut Function, addr_local: u32, disc: u8, parts: &[PayloadPart]) {
f.instructions().local_get(addr_local);
f.instructions().i32_const(disc as i32);
f.instructions().i32_store8(MemArg {
offset: 0,
align: 0,
memory_index: 0,
});
for part in parts {
f.instructions().local_get(addr_local);
match part.source {
PayloadSource::Local(l) => {
f.instructions().local_get(l);
}
PayloadSource::ConstI32(c) => {
f.instructions().i32_const(c);
}
}
let mem = MemArg {
offset: self.payload_offset + part.offset as u64,
align: part.kind.natural_align(),
memory_index: 0,
};
match part.kind {
StoreKind::I8 => f.instructions().i32_store8(mem),
StoreKind::I32 => f.instructions().i32_store(mem),
StoreKind::I64 => f.instructions().i64_store(mem),
StoreKind::F64 => f.instructions().f64_store(mem),
};
}
}
fn emit_single_payload(
&self,
f: &mut Function,
addr_local: u32,
case: &str,
kind: StoreKind,
payload_local: u32,
) {
self.emit_cell(
f,
addr_local,
self.disc_of(case),
&[PayloadPart {
source: PayloadSource::Local(payload_local),
kind,
offset: 0,
}],
);
}
pub(crate) fn emit_bool(&self, f: &mut Function, addr_local: u32, payload_local: u32) {
self.emit_single_payload(f, addr_local, "bool", StoreKind::I8, payload_local);
}
pub(crate) fn emit_integer(&self, f: &mut Function, addr_local: u32, payload_local: u32) {
self.emit_single_payload(f, addr_local, "integer", StoreKind::I64, payload_local);
}
pub(crate) fn emit_floating(&self, f: &mut Function, addr_local: u32, payload_local: u32) {
self.emit_single_payload(f, addr_local, "floating", StoreKind::F64, payload_local);
}
pub(crate) fn emit_text(
&self,
f: &mut Function,
addr_local: u32,
ptr_local: u32,
len_local: u32,
) {
self.emit_cell(
f,
addr_local,
self.disc_of("text"),
&ptr_len_parts(ptr_local, len_local),
);
}
pub(crate) fn emit_bytes(
&self,
f: &mut Function,
addr_local: u32,
ptr_local: u32,
len_local: u32,
) {
self.emit_cell(
f,
addr_local,
self.disc_of("bytes"),
&ptr_len_parts(ptr_local, len_local),
);
}
pub(crate) fn emit_char(
&self,
f: &mut Function,
addr_local: u32,
code_point_local: u32,
scratch_addr_local: u32,
len_local: u32,
) {
emit_utf8_encode(f, code_point_local, scratch_addr_local, len_local);
self.emit_cell(
f,
addr_local,
self.disc_of("text"),
&[
PayloadPart {
source: PayloadSource::Local(scratch_addr_local),
kind: StoreKind::I32,
offset: SLICE_PTR_OFFSET,
},
PayloadPart {
source: PayloadSource::Local(len_local),
kind: StoreKind::I32,
offset: SLICE_LEN_OFFSET,
},
],
);
}
pub(crate) fn emit_list_of(
&self,
f: &mut Function,
addr_local: u32,
idx_array_ptr: u32,
idx_array_len: u32,
) {
self.emit_cell(
f,
addr_local,
self.disc_of("list-of"),
&ptr_len_parts(idx_array_ptr, idx_array_len),
);
}
pub(crate) fn emit_tuple_of(
&self,
f: &mut Function,
addr_local: u32,
indices_off_source: PayloadSource,
indices_len: u32,
) {
self.emit_cell(
f,
addr_local,
self.disc_of("tuple-of"),
&[
PayloadPart {
source: indices_off_source,
kind: StoreKind::I32,
offset: 0,
},
PayloadPart {
source: PayloadSource::ConstI32(indices_len as i32),
kind: StoreKind::I32,
offset: 4,
},
],
);
}
pub(crate) fn emit_option_some(
&self,
f: &mut Function,
addr_local: u32,
inner_idx_source: PayloadSource,
) {
self.emit_cell(
f,
addr_local,
self.disc_of("option-some"),
&[PayloadPart {
source: inner_idx_source,
kind: StoreKind::I32,
offset: 0,
}],
);
}
pub(crate) fn emit_option_none(&self, f: &mut Function, addr_local: u32) {
self.emit_cell(f, addr_local, self.disc_of("option-none"), &[]);
}
pub(crate) fn emit_result_ok(
&self,
f: &mut Function,
addr_local: u32,
has_payload: bool,
inner_idx_source: PayloadSource,
) {
self.emit_result_arm(f, addr_local, "result-ok", has_payload, inner_idx_source);
}
pub(crate) fn emit_result_err(
&self,
f: &mut Function,
addr_local: u32,
has_payload: bool,
inner_idx_source: PayloadSource,
) {
self.emit_result_arm(f, addr_local, "result-err", has_payload, inner_idx_source);
}
fn emit_result_arm(
&self,
f: &mut Function,
addr_local: u32,
case: &str,
has_payload: bool,
inner_idx_source: PayloadSource,
) {
let mut parts = vec![PayloadPart {
source: PayloadSource::ConstI32(if has_payload { 1 } else { 0 }),
kind: StoreKind::I32,
offset: 0,
}];
if has_payload {
parts.push(PayloadPart {
source: inner_idx_source,
kind: StoreKind::I32,
offset: 4,
});
}
self.emit_cell(f, addr_local, self.disc_of(case), &parts);
}
pub(crate) fn emit_record_of(&self, f: &mut Function, addr_local: u32, payload: PayloadSource) {
self.emit_cell(
f,
addr_local,
self.disc_of("record-of"),
&[PayloadPart {
source: payload,
kind: StoreKind::I32,
offset: 0,
}],
);
}
pub(crate) fn emit_flags_set(&self, f: &mut Function, addr_local: u32, payload: PayloadSource) {
self.emit_cell(
f,
addr_local,
self.disc_of("flags-set"),
&[PayloadPart {
source: payload,
kind: StoreKind::I32,
offset: 0,
}],
);
}
pub(crate) fn emit_enum_case(&self, f: &mut Function, addr_local: u32, side_table_idx: u32) {
self.emit_cell(
f,
addr_local,
self.disc_of("enum-case"),
&[PayloadPart {
source: PayloadSource::Local(side_table_idx),
kind: StoreKind::I32,
offset: 0,
}],
);
}
pub(crate) fn emit_variant_case(
&self,
f: &mut Function,
addr_local: u32,
payload: PayloadSource,
) {
self.emit_cell(
f,
addr_local,
self.disc_of("variant-case"),
&[PayloadPart {
source: payload,
kind: StoreKind::I32,
offset: 0,
}],
);
}
pub(crate) fn emit_handle_cell(
&self,
f: &mut Function,
addr_local: u32,
disc_case: &str,
side_table_idx_source: PayloadSource,
) {
self.emit_cell(
f,
addr_local,
self.disc_of(disc_case),
&[PayloadPart {
source: side_table_idx_source,
kind: StoreKind::I32,
offset: 0,
}],
);
}
}
fn ptr_len_parts(ptr_local: u32, len_local: u32) -> [PayloadPart; 2] {
[
PayloadPart {
source: PayloadSource::Local(ptr_local),
kind: StoreKind::I32,
offset: 0,
},
PayloadPart {
source: PayloadSource::Local(len_local),
kind: StoreKind::I32,
offset: 4,
},
]
}
fn emit_utf8_encode(
f: &mut Function,
code_point_local: u32,
scratch_addr_local: u32,
len_local: u32,
) {
use wasm_encoder::BlockType;
let store_i8 = |off: u32| MemArg {
offset: off as u64,
align: I8_STORE_LOG2_ALIGN,
memory_index: 0,
};
f.instructions().local_get(code_point_local);
f.instructions().i32_const(0x80);
f.instructions().i32_lt_u();
f.instructions().if_(BlockType::Empty);
f.instructions().local_get(scratch_addr_local);
f.instructions().local_get(code_point_local);
f.instructions().i32_store8(store_i8(0));
f.instructions().i32_const(1);
f.instructions().local_set(len_local);
f.instructions().else_();
f.instructions().local_get(code_point_local);
f.instructions().i32_const(0x800);
f.instructions().i32_lt_u();
f.instructions().if_(BlockType::Empty);
f.instructions().local_get(scratch_addr_local);
f.instructions().local_get(code_point_local);
f.instructions().i32_const(6);
f.instructions().i32_shr_u();
f.instructions().i32_const(0xC0);
f.instructions().i32_or();
f.instructions().i32_store8(store_i8(0));
f.instructions().local_get(scratch_addr_local);
f.instructions().local_get(code_point_local);
f.instructions().i32_const(0x3F);
f.instructions().i32_and();
f.instructions().i32_const(0x80);
f.instructions().i32_or();
f.instructions().i32_store8(store_i8(1));
f.instructions().i32_const(2);
f.instructions().local_set(len_local);
f.instructions().else_();
f.instructions().local_get(code_point_local);
f.instructions().i32_const(0x10000);
f.instructions().i32_lt_u();
f.instructions().if_(BlockType::Empty);
f.instructions().local_get(scratch_addr_local);
f.instructions().local_get(code_point_local);
f.instructions().i32_const(12);
f.instructions().i32_shr_u();
f.instructions().i32_const(0xE0);
f.instructions().i32_or();
f.instructions().i32_store8(store_i8(0));
f.instructions().local_get(scratch_addr_local);
f.instructions().local_get(code_point_local);
f.instructions().i32_const(6);
f.instructions().i32_shr_u();
f.instructions().i32_const(0x3F);
f.instructions().i32_and();
f.instructions().i32_const(0x80);
f.instructions().i32_or();
f.instructions().i32_store8(store_i8(1));
f.instructions().local_get(scratch_addr_local);
f.instructions().local_get(code_point_local);
f.instructions().i32_const(0x3F);
f.instructions().i32_and();
f.instructions().i32_const(0x80);
f.instructions().i32_or();
f.instructions().i32_store8(store_i8(2));
f.instructions().i32_const(3);
f.instructions().local_set(len_local);
f.instructions().else_();
f.instructions().local_get(scratch_addr_local);
f.instructions().local_get(code_point_local);
f.instructions().i32_const(18);
f.instructions().i32_shr_u();
f.instructions().i32_const(0xF0);
f.instructions().i32_or();
f.instructions().i32_store8(store_i8(0));
f.instructions().local_get(scratch_addr_local);
f.instructions().local_get(code_point_local);
f.instructions().i32_const(12);
f.instructions().i32_shr_u();
f.instructions().i32_const(0x3F);
f.instructions().i32_and();
f.instructions().i32_const(0x80);
f.instructions().i32_or();
f.instructions().i32_store8(store_i8(1));
f.instructions().local_get(scratch_addr_local);
f.instructions().local_get(code_point_local);
f.instructions().i32_const(6);
f.instructions().i32_shr_u();
f.instructions().i32_const(0x3F);
f.instructions().i32_and();
f.instructions().i32_const(0x80);
f.instructions().i32_or();
f.instructions().i32_store8(store_i8(2));
f.instructions().local_get(scratch_addr_local);
f.instructions().local_get(code_point_local);
f.instructions().i32_const(0x3F);
f.instructions().i32_and();
f.instructions().i32_const(0x80);
f.instructions().i32_or();
f.instructions().i32_store8(store_i8(3));
f.instructions().i32_const(4);
f.instructions().local_set(len_local);
f.instructions().end();
f.instructions().end();
f.instructions().end();
}
#[cfg(test)]
mod tests {
use super::*;
use wasm_encoder::{
CodeSection, EntityType, FunctionSection, ImportSection, MemoryType, Module, TypeSection,
ValType,
};
fn build_and_validate(param_types: &[ValType], emit_body: impl FnOnce(&mut Function)) {
let mut module = Module::new();
let mut types = TypeSection::new();
types.ty().function(param_types.iter().copied(), []);
module.section(&types);
let mut imports = ImportSection::new();
imports.import(
"env",
"memory",
EntityType::Memory(MemoryType {
minimum: 1,
maximum: None,
memory64: false,
shared: false,
page_size_log2: None,
}),
);
module.section(&imports);
let mut funcs = FunctionSection::new();
funcs.function(0);
module.section(&funcs);
let mut code = CodeSection::new();
let mut f = Function::new([]);
emit_body(&mut f);
f.instructions().end();
code.function(&f);
module.section(&code);
let bytes = module.finish();
wasmparser::Validator::new()
.validate_all(&bytes)
.expect("emitted module should validate");
}
fn synth_cell_layout() -> CellLayout {
CellLayout {
size: 16,
align: 8,
payload_offset: 8,
discs: EXPECTED_CELL_CASES
.iter()
.enumerate()
.map(|(i, name)| ((*name).to_string(), i as u8))
.collect(),
}
}
#[test]
fn bool_cell_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32, ValType::I32], |f| cl.emit_bool(f, 0, 1));
}
#[test]
fn integer_cell_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32, ValType::I64], |f| cl.emit_integer(f, 0, 1));
}
#[test]
fn floating_cell_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32, ValType::F64], |f| cl.emit_floating(f, 0, 1));
}
#[test]
fn text_cell_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32, ValType::I32, ValType::I32], |f| {
cl.emit_text(f, 0, 1, 2)
});
}
#[test]
fn bytes_cell_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32, ValType::I32, ValType::I32], |f| {
cl.emit_bytes(f, 0, 1, 2)
});
}
#[test]
fn tuple_of_cell_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32], |f| {
cl.emit_tuple_of(f, 0, PayloadSource::ConstI32(0x100), 3)
});
}
#[test]
fn tuple_of_cell_with_local_off_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32, ValType::I32], |f| {
cl.emit_tuple_of(f, 0, PayloadSource::Local(1), 3)
});
}
#[test]
fn option_some_cell_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32], |f| {
cl.emit_option_some(f, 0, PayloadSource::ConstI32(7))
});
}
#[test]
fn option_some_cell_with_local_idx_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32, ValType::I32], |f| {
cl.emit_option_some(f, 0, PayloadSource::Local(1))
});
}
#[test]
fn option_none_cell_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32], |f| cl.emit_option_none(f, 0));
}
#[test]
fn result_ok_with_payload_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32], |f| {
cl.emit_result_ok(f, 0, true, PayloadSource::ConstI32(5))
});
}
#[test]
fn result_ok_unit_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32], |f| {
cl.emit_result_ok(f, 0, false, PayloadSource::ConstI32(0))
});
}
#[test]
fn result_err_with_payload_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32], |f| {
cl.emit_result_err(f, 0, true, PayloadSource::ConstI32(7))
});
}
#[test]
fn result_err_unit_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32], |f| {
cl.emit_result_err(f, 0, false, PayloadSource::ConstI32(0))
});
}
#[test]
fn flags_set_cell_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32], |f| {
cl.emit_flags_set(f, 0, PayloadSource::ConstI32(11))
});
}
#[test]
fn variant_case_cell_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32], |f| {
cl.emit_variant_case(f, 0, PayloadSource::ConstI32(5))
});
}
#[test]
fn char_cell_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(
&[ValType::I32, ValType::I32, ValType::I32, ValType::I32],
|f| {
f.instructions().i32_const(0x1000);
f.instructions().local_set(3);
cl.emit_char(f, 0, 1, 3, 2)
},
);
}
#[test]
fn handle_cells_emit_valid_wasm() {
let cl = synth_cell_layout();
for disc_case in [
"resource-handle",
"stream-handle",
"future-handle",
"error-context-handle",
] {
build_and_validate(&[ValType::I32], |f| {
cl.emit_handle_cell(f, 0, disc_case, PayloadSource::ConstI32(9))
});
}
}
#[test]
fn handle_cell_with_local_idx_emits_valid_wasm() {
let cl = synth_cell_layout();
build_and_validate(&[ValType::I32, ValType::I32], |f| {
cl.emit_handle_cell(f, 0, "resource-handle", PayloadSource::Local(1))
});
}
#[test]
fn primitive_cells_structural_fuzz() {
let seed: u64 = std::env::var("SPLICER_TIER2_FUZZ_SEED")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0xC0FF_EE00_DEAD_BEEF);
let cl = synth_cell_layout();
for iter in 0..100u64 {
let mixed = seed.wrapping_mul(0x9E37_79B9_7F4A_7C15).wrapping_add(iter);
match mixed % 5 {
0 => build_and_validate(&[ValType::I32, ValType::I32], |f| cl.emit_bool(f, 0, 1)),
1 => {
build_and_validate(&[ValType::I32, ValType::I64], |f| cl.emit_integer(f, 0, 1))
}
2 => {
build_and_validate(&[ValType::I32, ValType::F64], |f| cl.emit_floating(f, 0, 1))
}
3 => build_and_validate(&[ValType::I32, ValType::I32, ValType::I32], |f| {
cl.emit_text(f, 0, 1, 2)
}),
4 => build_and_validate(&[ValType::I32, ValType::I32, ValType::I32], |f| {
cl.emit_bytes(f, 0, 1, 2)
}),
_ => unreachable!(),
}
}
}
#[test]
fn cell_discriminants_match_wit_declaration_order() {
let common_wit = include_str!("../../../wit/common/world.wit");
let mut resolve = Resolve::new();
resolve
.push_str("common.wit", common_wit)
.expect("wit/common/world.wit must parse");
let iface_id =
super::super::test_utils::iface_by_unversioned_qname(&resolve, "splicer:common/types");
let cell_id = resolve.interfaces[iface_id]
.types
.get("cell")
.copied()
.expect("splicer:common/types must export `cell` typedef");
let mut sizes = SizeAlign::default();
sizes.fill(&resolve);
let layout = CellLayout::from_resolve(&sizes, &resolve, cell_id);
for (expected_disc, name) in EXPECTED_CELL_CASES.iter().enumerate() {
assert_eq!(
layout.disc_of(name),
expected_disc as u8,
"WIT case `{name}` no longer has disc {expected_disc} — \
reorder/rename detected"
);
}
}
}