use super::{Input, function::FunctionRegistry, root::RootTable, types::FunctionTypes};
use crate::schema::FieldTag;
use std::collections::BTreeMap;
use wasm_encoder::{
ConstExpr, Function, GlobalType, HeapType, Instruction as I, TableType, ValType,
};
pub(super) struct ExternRef {
enabled: bool,
new: u32,
release: u32,
table: RootTable,
}
impl ExternRef {
pub fn new(
input: &Input,
table: u32,
first_global: u32,
types: &mut FunctionTypes,
functions: &mut FunctionRegistry,
exports: &mut BTreeMap<String, u32>,
) -> Self {
let enabled = input
.imports
.iter()
.filter_map(|(name, _)| name.as_deref())
.any(|name| name.starts_with("externref_"))
|| input
.operations
.values()
.map(|operation| &input.schemas[operation.schema as usize])
.flat_map(|schema| &schema.fields)
.any(|field| field.tag == FieldTag::ExternRef)
|| input
.bridges
.iter()
.flat_map(|bridge| bridge.params.iter().chain(&bridge.result))
.any(|field| field.tag == FieldTag::ExternRef);
let table = RootTable::new(table, first_global, HeapType::EXTERN);
if !enabled {
return Self {
enabled,
new: 0,
release: 0,
table,
};
}
let mut new = Function::new([(1, ValType::I32)]);
table.allocate(&mut new, 1);
for instruction in [
I::LocalGet(1),
I::LocalGet(0),
I::TableSet(table.index),
I::LocalGet(1),
I::End,
] {
new.instruction(&instruction);
}
let new = functions.push(types.intern([ValType::EXTERNREF], [ValType::I32]), new);
exports.insert("wasmgc.externref_new".to_owned(), new);
let mut release = Function::new([]);
table.release(&mut release, 0);
release.instruction(&I::End);
let release = functions.push(types.intern([ValType::I32], []), release);
let mut take = Function::new([(1, ValType::EXTERNREF)]);
take.instruction(&I::LocalGet(0))
.instruction(&I::TableGet(table.index))
.instruction(&I::LocalSet(1))
.instruction(&I::LocalGet(0))
.instruction(&I::Call(release))
.instruction(&I::LocalGet(1))
.instruction(&I::End);
let take = functions.push(types.intern([ValType::I32], [ValType::EXTERNREF]), take);
exports.insert("wasmgc.externref_take".to_owned(), take);
let mut get = Function::new([]);
get.instruction(&I::LocalGet(0))
.instruction(&I::TableGet(table.index))
.instruction(&I::End);
let get = functions.push(types.intern([ValType::I32], [ValType::EXTERNREF]), get);
exports.insert("wasmgc.externref_get".to_owned(), get);
Self {
enabled,
new,
release,
table,
}
}
pub fn append_tables(&self, tables: &mut Vec<TableType>) {
if self.enabled {
self.table.append_table(tables);
}
}
pub fn append_globals(&self, globals: &mut Vec<(GlobalType, ConstExpr)>) {
if self.enabled {
self.table.append_globals(globals);
}
}
pub fn intrinsic(&self, name: &str) -> Option<Function> {
let mut function = Function::new([]);
match name {
"externref_null" => {
function.instruction(&I::RefNull(HeapType::EXTERN));
self.field_output(&mut function);
}
"externref_is_null" => {
self.field_input(&mut function, 0);
function.instruction(&I::RefIsNull);
}
"externref_clone" => {
self.field_input(&mut function, 0);
self.field_output(&mut function);
}
"externref_release" => {
function
.instruction(&I::LocalGet(0))
.instruction(&I::Call(self.release));
}
_ => return None,
}
function.instruction(&I::End);
Some(function)
}
pub fn field_input(&self, function: &mut Function, local: u32) {
function
.instruction(&I::LocalGet(local))
.instruction(&I::TableGet(self.table.index));
}
pub fn field_release(&self, function: &mut Function, local: u32) {
function
.instruction(&I::LocalGet(local))
.instruction(&I::Call(self.release));
}
pub fn field_output(&self, function: &mut Function) {
function.instruction(&I::Call(self.new));
}
}