Initial support for all Wasm MVP opcodes in frontend. Still have a few fuzz failures.

This commit is contained in:
Chris Fallin 2021-11-13 16:31:11 -08:00
parent 0966867317
commit 45a66fa3b8
3 changed files with 456 additions and 38 deletions

View file

@ -39,10 +39,31 @@ fn handle_payload<'a>(
}
Payload::ImportSection(mut reader) => {
for _ in 0..reader.get_count() {
if let ImportSectionEntryType::Function(sig_idx) = reader.read()?.ty {
match reader.read()?.ty {
ImportSectionEntryType::Function(sig_idx) => {
module.funcs.push(FuncDecl::Import(sig_idx as SignatureId));
*next_func += 1;
}
ImportSectionEntryType::Global(ty) => {
module.globals.push(ty.content_type);
}
ImportSectionEntryType::Table(ty) => {
module.tables.push(ty.element_type);
}
_ => {}
}
}
}
Payload::GlobalSection(mut reader) => {
for _ in 0..reader.get_count() {
let global = reader.read()?;
module.globals.push(global.ty.content_type);
}
}
Payload::TableSection(mut reader) => {
for _ in 0..reader.get_count() {
let table = reader.read()?;
module.tables.push(table.element_type);
}
}
Payload::FunctionSection(mut reader) => {
@ -104,7 +125,7 @@ struct FunctionBodyBuilder<'a, 'b> {
body: &'b mut FunctionBody<'a>,
cur_block: Option<BlockId>,
ctrl_stack: Vec<Frame>,
op_stack: Vec<ValueId>,
op_stack: Vec<(Type, ValueId)>,
}
#[derive(Clone, Debug)]
@ -126,7 +147,7 @@ enum Frame {
start_depth: usize,
out: BlockId,
el: BlockId,
param_values: Vec<ValueId>,
param_values: Vec<(Type, ValueId)>,
params: Vec<Type>,
results: Vec<Type>,
},
@ -183,6 +204,18 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
self.body.blocks[block].params.extend_from_slice(tys);
}
fn pop_n(&mut self, n: usize) -> Vec<ValueId> {
self.op_stack
.split_off(n)
.into_iter()
.map(|(_ty, value)| value)
.collect()
}
fn pop_1(&mut self) -> ValueId {
self.op_stack.pop().unwrap().1
}
fn handle_op(&mut self, op: Operator<'a>) -> Result<()> {
match &op {
Operator::Unreachable
@ -364,7 +397,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
Operator::Nop => {}
Operator::Drop => {
self.op_stack.pop().unwrap();
let _ = self.pop_1();
}
Operator::End => match self.ctrl_stack.pop() {
@ -385,7 +418,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
}) => {
// Generate a branch to the out-block with
// blockparams for the results.
let result_values = self.op_stack.split_off(results.len());
let result_values = self.pop_n(results.len());
self.emit_branch(out, &result_values[..]);
assert_eq!(self.op_stack.len(), start_depth);
self.cur_block = Some(out);
@ -401,13 +434,17 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
}) => {
// Generate a branch to the out-block with
// blockparams for the results.
let result_values = self.op_stack.split_off(results.len());
let result_values = self.pop_n(results.len());
self.emit_branch(out, &result_values[..]);
// No `else`, so we need to generate a trivial
// branch in the else-block. If the if-block-type
// has results, they must be exactly the params.
let else_result_values = param_values;
assert_eq!(else_result_values.len(), results.len());
let else_result_values = else_result_values
.iter()
.map(|(_ty, value)| *value)
.collect::<Vec<_>>();
self.emit_branch(el, &else_result_values[..]);
assert_eq!(self.op_stack.len(), start_depth);
self.cur_block = Some(out);
@ -421,7 +458,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
}) => {
// Generate a branch to the out-block with
// blockparams for the results.
let result_values = self.op_stack.split_off(results.len());
let result_values = self.pop_n(results.len());
assert_eq!(self.op_stack.len(), start_depth);
self.emit_branch(out, &result_values[..]);
self.cur_block = Some(out);
@ -446,7 +483,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
let (params, results) = self.block_params_and_results(*ty);
let header = self.create_block();
self.add_block_params(header, &params[..]);
let initial_args = self.op_stack.split_off(params.len());
let initial_args = self.pop_n(params.len());
let start_depth = self.op_stack.len();
self.emit_branch(header, &initial_args[..]);
self.cur_block = Some(header);
@ -467,7 +504,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
let if_false = self.create_block();
let join = self.create_block();
self.add_block_params(join, &results[..]);
let cond = self.op_stack.pop().unwrap();
let cond = self.pop_1();
let param_values = self.op_stack[self.op_stack.len() - params.len()..].to_vec();
let start_depth = self.op_stack.len();
self.ctrl_stack.push(Frame::If {
@ -492,7 +529,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
results,
} = self.ctrl_stack.pop().unwrap()
{
let if_results = self.op_stack.split_off(results.len());
let if_results = self.pop_n(results.len());
self.emit_branch(out, &if_results[..]);
self.op_stack.extend(param_values);
self.ctrl_stack.push(Frame::Else {
@ -510,13 +547,13 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
Operator::Br { relative_depth } | Operator::BrIf { relative_depth } => {
let cond = match &op {
Operator::Br { .. } => None,
Operator::BrIf { .. } => Some(self.op_stack.pop().unwrap()),
Operator::BrIf { .. } => Some(self.pop_1()),
_ => unreachable!(),
};
// Get the frame we're branching to.
let frame = self.relative_frame(*relative_depth).clone();
// Get the args off the stack.
let args = self.op_stack.split_off(frame.br_args().len());
let args = self.pop_n(frame.br_args().len());
// Finally, generate the branch itself.
match cond {
None => {
@ -533,14 +570,14 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
Operator::BrTable { table } => {
// Get the selector index.
let index = self.op_stack.pop().unwrap();
let index = self.pop_1();
// Get the signature of the default frame; this tells
// us the signature of all frames (since wasmparser
// validates the input for us). Pop that many args.
let default_frame = self.relative_frame(table.default());
let default_term_target = default_frame.br_target();
let arg_len = default_frame.br_args().len();
let args = self.op_stack.split_off(arg_len);
let args = self.pop_n(arg_len);
// Generate a branch terminator with the same args for
// every branch target.
let mut term_targets = vec![];
@ -556,9 +593,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
}
Operator::Return => {
let retvals = self
.op_stack
.split_off(self.module.signatures[self.my_sig].returns.len());
let retvals = self.pop_n(self.module.signatures[self.my_sig].returns.len());
self.emit_ret(&retvals[..]);
self.cur_block = None;
}
@ -677,7 +712,7 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
kind: ValueKind::BlockParam(self.cur_block.unwrap(), i),
ty,
});
self.op_stack.push(value_id);
self.op_stack.push((ty, value_id));
}
}
@ -686,25 +721,33 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
let inst = self.body.blocks[block].insts.len() as InstId;
let mut inputs = vec![];
for input in op_inputs(self.module, self.my_sig, &self.body.locals[..], &op)?
for input in op_inputs(
self.module,
self.my_sig,
&self.body.locals[..],
&self.op_stack[..],
&op,
)?
.into_iter()
.rev()
{
let stack_top = self.op_stack.pop().unwrap();
assert_eq!(self.body.values[stack_top].ty, input);
let (stack_top_ty, stack_top) = self.op_stack.pop().unwrap();
assert_eq!(stack_top_ty, input);
inputs.push(Operand::value(stack_top));
}
inputs.reverse();
let mut outputs = vec![];
for output in op_outputs(self.module, &self.body.locals[..], &op)?.into_iter() {
for output_ty in
op_outputs(self.module, &self.body.locals[..], &self.op_stack[..], &op)?.into_iter()
{
let val = self.body.values.len() as ValueId;
outputs.push(val);
self.body.values.push(ValueDef {
kind: ValueKind::Inst(block, inst),
ty: output,
ty: output_ty,
});
self.op_stack.push(val);
self.op_stack.push((output_ty, val));
}
self.body.blocks[block].insts.push(Inst {
@ -713,11 +756,18 @@ impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
inputs,
});
} else {
let _ = self
.op_stack
.split_off(op_inputs(self.module, self.my_sig, &self.body.locals[..], &op)?.len());
for _ in 0..op_outputs(self.module, &self.body.locals[..], &op)?.len() {
self.op_stack.push(NO_VALUE);
let _ = self.pop_n(
op_inputs(
self.module,
self.my_sig,
&self.body.locals[..],
&self.op_stack[..],
&op,
)?
.len(),
);
for ty in op_outputs(self.module, &self.body.locals[..], &self.op_stack[..], &op)? {
self.op_stack.push((ty, NO_VALUE));
}
}

View file

@ -14,6 +14,8 @@ pub const NO_VALUE: ValueId = usize::MAX;
pub struct Module<'a> {
pub funcs: Vec<FuncDecl<'a>>,
pub signatures: Vec<FuncType>,
pub globals: Vec<Type>,
pub tables: Vec<Type>,
}
#[derive(Clone, Debug)]

View file

@ -1,6 +1,6 @@
//! Metadata on operators.
use crate::ir::{Module, SignatureId};
use crate::ir::{Module, SignatureId, ValueId};
use anyhow::{bail, Result};
use wasmparser::{Operator, Type};
@ -8,6 +8,7 @@ pub fn op_inputs(
module: &Module,
my_sig: SignatureId,
my_locals: &[Type],
op_stack: &[(Type, ValueId)],
op: &Operator<'_>,
) -> Result<Vec<Type>> {
match op {
@ -29,14 +30,201 @@ pub fn op_inputs(
}
&Operator::LocalGet { .. } => Ok(vec![]),
&Operator::I32Eqz => Ok(vec![Type::I32]),
&Operator::I32Eq => Ok(vec![Type::I32, Type::I32]),
&Operator::Select => {
let val_ty = op_stack[op_stack.len() - 2].0;
Ok(vec![val_ty, val_ty, Type::I32])
}
&Operator::TypedSelect { ty } => Ok(vec![ty, ty, Type::I32]),
&Operator::GlobalGet { .. } => Ok(vec![]),
&Operator::GlobalSet { global_index } => Ok(vec![module.globals[global_index as usize]]),
Operator::I32Load { .. }
| Operator::I64Load { .. }
| Operator::F32Load { .. }
| Operator::F64Load { .. }
| Operator::I32Load8S { .. }
| Operator::I32Load8U { .. }
| Operator::I32Load16S { .. }
| Operator::I32Load16U { .. }
| Operator::I64Load8S { .. }
| Operator::I64Load8U { .. }
| Operator::I64Load16S { .. }
| Operator::I64Load16U { .. }
| Operator::I64Load32S { .. }
| Operator::I64Load32U { .. } => Ok(vec![Type::I32]),
Operator::I32Store { .. } => Ok(vec![Type::I32, Type::I32]),
Operator::I64Store { .. } => Ok(vec![Type::I32, Type::I64]),
Operator::F32Store { .. } => Ok(vec![Type::I32, Type::F32]),
Operator::F64Store { .. } => Ok(vec![Type::I32, Type::F64]),
Operator::I32Store8 { .. } => Ok(vec![Type::I32, Type::I32]),
Operator::I32Store16 { .. } => Ok(vec![Type::I32, Type::I32]),
Operator::I64Store8 { .. } => Ok(vec![Type::I32, Type::I64]),
Operator::I64Store16 { .. } => Ok(vec![Type::I32, Type::I64]),
Operator::I64Store32 { .. } => Ok(vec![Type::I32, Type::I64]),
Operator::I32Const { .. }
| Operator::I64Const { .. }
| Operator::F32Const { .. }
| Operator::F64Const { .. } => Ok(vec![]),
Operator::I32Eqz => Ok(vec![Type::I32]),
Operator::I32Eq
| Operator::I32Ne
| Operator::I32LtS
| Operator::I32LtU
| Operator::I32GtS
| Operator::I32GtU
| Operator::I32LeS
| Operator::I32LeU
| Operator::I32GeS
| Operator::I32GeU => Ok(vec![Type::I32, Type::I32]),
Operator::I64Eqz => Ok(vec![Type::I64]),
Operator::I64Eq
| Operator::I64Ne
| Operator::I64LtS
| Operator::I64LtU
| Operator::I64GtU
| Operator::I64GtS
| Operator::I64LeS
| Operator::I64LeU
| Operator::I64GeS
| Operator::I64GeU => Ok(vec![Type::I64, Type::I64]),
Operator::F32Eq
| Operator::F32Ne
| Operator::F32Lt
| Operator::F32Gt
| Operator::F32Le
| Operator::F32Ge => Ok(vec![Type::F32, Type::F32]),
Operator::F64Eq
| Operator::F64Ne
| Operator::F64Lt
| Operator::F64Gt
| Operator::F64Le
| Operator::F64Ge => Ok(vec![Type::F64, Type::F64]),
Operator::I32Clz | Operator::I32Ctz | Operator::I32Popcnt => Ok(vec![Type::I32]),
Operator::I32Add
| Operator::I32Sub
| Operator::I32Mul
| Operator::I32DivS
| Operator::I32DivU
| Operator::I32RemS
| Operator::I32RemU
| Operator::I32And
| Operator::I32Or
| Operator::I32Xor
| Operator::I32Shl
| Operator::I32ShrS
| Operator::I32ShrU
| Operator::I32Rotl
| Operator::I32Rotr => Ok(vec![Type::I32, Type::I32]),
Operator::I64Clz | Operator::I64Ctz | Operator::I64Popcnt => Ok(vec![Type::I64]),
Operator::I64Add
| Operator::I64Sub
| Operator::I64Mul
| Operator::I64DivS
| Operator::I64DivU
| Operator::I64RemS
| Operator::I64RemU
| Operator::I64And
| Operator::I64Or
| Operator::I64Xor
| Operator::I64Shl
| Operator::I64ShrS
| Operator::I64ShrU
| Operator::I64Rotl
| Operator::I64Rotr => Ok(vec![Type::I64, Type::I64]),
Operator::F32Abs
| Operator::F32Neg
| Operator::F32Ceil
| Operator::F32Floor
| Operator::F32Trunc
| Operator::F32Nearest
| Operator::F32Sqrt => Ok(vec![Type::F32]),
Operator::F32Add
| Operator::F32Sub
| Operator::F32Mul
| Operator::F32Div
| Operator::F32Min
| Operator::F32Max
| Operator::F32Copysign => Ok(vec![Type::F32, Type::F32]),
Operator::F64Abs
| Operator::F64Neg
| Operator::F64Ceil
| Operator::F64Floor
| Operator::F64Trunc
| Operator::F64Nearest
| Operator::F64Sqrt => Ok(vec![Type::F64]),
Operator::F64Add
| Operator::F64Sub
| Operator::F64Mul
| Operator::F64Div
| Operator::F64Min
| Operator::F64Max
| Operator::F64Copysign => Ok(vec![Type::F64, Type::F64]),
Operator::I32WrapI64 => Ok(vec![Type::I64]),
Operator::I32TruncF32S => Ok(vec![Type::F32]),
Operator::I32TruncF32U => Ok(vec![Type::F32]),
Operator::I32TruncF64S => Ok(vec![Type::F64]),
Operator::I32TruncF64U => Ok(vec![Type::F64]),
Operator::I64ExtendI32S => Ok(vec![Type::I32]),
Operator::I64ExtendI32U => Ok(vec![Type::I32]),
Operator::I64TruncF32S => Ok(vec![Type::F32]),
Operator::I64TruncF32U => Ok(vec![Type::F32]),
Operator::I64TruncF64S => Ok(vec![Type::F64]),
Operator::I64TruncF64U => Ok(vec![Type::F64]),
Operator::F32ConvertI32S => Ok(vec![Type::I32]),
Operator::F32ConvertI32U => Ok(vec![Type::I32]),
Operator::F32ConvertI64S => Ok(vec![Type::I64]),
Operator::F32ConvertI64U => Ok(vec![Type::I64]),
Operator::F32DemoteF64 => Ok(vec![Type::F64]),
Operator::F64ConvertI32S => Ok(vec![Type::I32]),
Operator::F64ConvertI32U => Ok(vec![Type::I32]),
Operator::F64ConvertI64S => Ok(vec![Type::I64]),
Operator::F64ConvertI64U => Ok(vec![Type::I64]),
Operator::F64PromoteF32 => Ok(vec![Type::F32]),
Operator::I32Extend8S => Ok(vec![Type::I32]),
Operator::I32Extend16S => Ok(vec![Type::I32]),
Operator::I64Extend8S => Ok(vec![Type::I64]),
Operator::I64Extend16S => Ok(vec![Type::I64]),
Operator::I64Extend32S => Ok(vec![Type::I64]),
Operator::I32TruncSatF32S => Ok(vec![Type::F32]),
Operator::I32TruncSatF32U => Ok(vec![Type::F32]),
Operator::I32TruncSatF64S => Ok(vec![Type::F64]),
Operator::I32TruncSatF64U => Ok(vec![Type::F64]),
Operator::I64TruncSatF32S => Ok(vec![Type::F32]),
Operator::I64TruncSatF32U => Ok(vec![Type::F32]),
Operator::I64TruncSatF64S => Ok(vec![Type::F64]),
Operator::I64TruncSatF64U => Ok(vec![Type::F64]),
Operator::TableGet { .. } => Ok(vec![Type::I32]),
Operator::TableSet { table } => Ok(vec![Type::I32, module.tables[*table as usize]]),
Operator::TableGrow { .. } => Ok(vec![Type::I32]),
Operator::TableSize { .. } => Ok(vec![]),
_ => bail!("Unknown operator in op_inputs(): {:?}", op),
}
}
pub fn op_outputs(module: &Module, my_locals: &[Type], op: &Operator<'_>) -> Result<Vec<Type>> {
pub fn op_outputs(
module: &Module,
my_locals: &[Type],
op_stack: &[(Type, ValueId)],
op: &Operator<'_>,
) -> Result<Vec<Type>> {
match op {
&Operator::Unreachable | &Operator::Nop => Ok(vec![]),
@ -51,7 +239,185 @@ pub fn op_outputs(module: &Module, my_locals: &[Type], op: &Operator<'_>) -> Res
&Operator::LocalSet { .. } | &Operator::LocalTee { .. } => Ok(vec![]),
&Operator::LocalGet { local_index } => Ok(vec![my_locals[local_index as usize]]),
&Operator::I32Eqz | &Operator::I32Eq => Ok(vec![Type::I32]),
&Operator::Select => {
let val_ty = op_stack[op_stack.len() - 2].0;
Ok(vec![val_ty])
}
&Operator::TypedSelect { ty } => Ok(vec![ty]),
&Operator::GlobalGet { global_index } => Ok(vec![module.globals[global_index as usize]]),
&Operator::GlobalSet { .. } => Ok(vec![]),
Operator::I32Load { .. }
| Operator::I64Load { .. }
| Operator::F32Load { .. }
| Operator::F64Load { .. }
| Operator::I32Load8S { .. }
| Operator::I32Load8U { .. }
| Operator::I32Load16S { .. }
| Operator::I32Load16U { .. } => Ok(vec![Type::I32]),
Operator::I64Load8S { .. }
| Operator::I64Load8U { .. }
| Operator::I64Load16S { .. }
| Operator::I64Load16U { .. }
| Operator::I64Load32S { .. }
| Operator::I64Load32U { .. } => Ok(vec![Type::I64]),
Operator::I32Store { .. } => Ok(vec![]),
Operator::I64Store { .. } => Ok(vec![]),
Operator::F32Store { .. } => Ok(vec![]),
Operator::F64Store { .. } => Ok(vec![]),
Operator::I32Store8 { .. } => Ok(vec![]),
Operator::I32Store16 { .. } => Ok(vec![]),
Operator::I64Store8 { .. } => Ok(vec![]),
Operator::I64Store16 { .. } => Ok(vec![]),
Operator::I64Store32 { .. } => Ok(vec![]),
Operator::I32Const { .. } => Ok(vec![Type::I32]),
Operator::I64Const { .. } => Ok(vec![Type::I64]),
Operator::F32Const { .. } => Ok(vec![Type::F32]),
Operator::F64Const { .. } => Ok(vec![Type::F64]),
Operator::I32Eqz
| Operator::I32Eq
| Operator::I32Ne
| Operator::I32LtS
| Operator::I32LtU
| Operator::I32GtS
| Operator::I32GtU
| Operator::I32LeS
| Operator::I32LeU
| Operator::I32GeS
| Operator::I32GeU
| Operator::I64Eqz
| Operator::I64Eq
| Operator::I64Ne
| Operator::I64LtS
| Operator::I64LtU
| Operator::I64GtU
| Operator::I64GtS
| Operator::I64LeS
| Operator::I64LeU
| Operator::I64GeS
| Operator::I64GeU
| Operator::F32Eq
| Operator::F32Ne
| Operator::F32Lt
| Operator::F32Gt
| Operator::F32Le
| Operator::F32Ge
| Operator::F64Eq
| Operator::F64Ne
| Operator::F64Lt
| Operator::F64Gt
| Operator::F64Le
| Operator::F64Ge => Ok(vec![Type::I32]),
Operator::I32Clz
| Operator::I32Ctz
| Operator::I32Popcnt
| Operator::I32Add
| Operator::I32Sub
| Operator::I32Mul
| Operator::I32DivS
| Operator::I32DivU
| Operator::I32RemS
| Operator::I32RemU
| Operator::I32And
| Operator::I32Or
| Operator::I32Xor
| Operator::I32Shl
| Operator::I32ShrS
| Operator::I32ShrU
| Operator::I32Rotl
| Operator::I32Rotr => Ok(vec![Type::I32]),
Operator::I64Clz
| Operator::I64Ctz
| Operator::I64Popcnt
| Operator::I64Add
| Operator::I64Sub
| Operator::I64Mul
| Operator::I64DivS
| Operator::I64DivU
| Operator::I64RemS
| Operator::I64RemU
| Operator::I64And
| Operator::I64Or
| Operator::I64Xor
| Operator::I64Shl
| Operator::I64ShrS
| Operator::I64ShrU
| Operator::I64Rotl
| Operator::I64Rotr => Ok(vec![Type::I64]),
Operator::F32Abs
| Operator::F32Neg
| Operator::F32Ceil
| Operator::F32Floor
| Operator::F32Trunc
| Operator::F32Nearest
| Operator::F32Sqrt
| Operator::F32Add
| Operator::F32Sub
| Operator::F32Mul
| Operator::F32Div
| Operator::F32Min
| Operator::F32Max
| Operator::F32Copysign => Ok(vec![Type::F32]),
Operator::F64Abs
| Operator::F64Neg
| Operator::F64Ceil
| Operator::F64Floor
| Operator::F64Trunc
| Operator::F64Nearest
| Operator::F64Sqrt
| Operator::F64Add
| Operator::F64Sub
| Operator::F64Mul
| Operator::F64Div
| Operator::F64Min
| Operator::F64Max
| Operator::F64Copysign => Ok(vec![Type::F64]),
Operator::I32WrapI64 => Ok(vec![Type::I32]),
Operator::I32TruncF32S => Ok(vec![Type::I32]),
Operator::I32TruncF32U => Ok(vec![Type::I32]),
Operator::I32TruncF64S => Ok(vec![Type::I32]),
Operator::I32TruncF64U => Ok(vec![Type::I32]),
Operator::I64ExtendI32S => Ok(vec![Type::I64]),
Operator::I64ExtendI32U => Ok(vec![Type::I64]),
Operator::I64TruncF32S => Ok(vec![Type::I64]),
Operator::I64TruncF32U => Ok(vec![Type::I64]),
Operator::I64TruncF64S => Ok(vec![Type::I64]),
Operator::I64TruncF64U => Ok(vec![Type::I64]),
Operator::F32ConvertI32S => Ok(vec![Type::F32]),
Operator::F32ConvertI32U => Ok(vec![Type::F32]),
Operator::F32ConvertI64S => Ok(vec![Type::F32]),
Operator::F32ConvertI64U => Ok(vec![Type::F32]),
Operator::F32DemoteF64 => Ok(vec![Type::F32]),
Operator::F64ConvertI32S => Ok(vec![Type::F64]),
Operator::F64ConvertI32U => Ok(vec![Type::F64]),
Operator::F64ConvertI64S => Ok(vec![Type::F64]),
Operator::F64ConvertI64U => Ok(vec![Type::F64]),
Operator::F64PromoteF32 => Ok(vec![Type::F64]),
Operator::I32Extend8S => Ok(vec![Type::I32]),
Operator::I32Extend16S => Ok(vec![Type::I32]),
Operator::I64Extend8S => Ok(vec![Type::I64]),
Operator::I64Extend16S => Ok(vec![Type::I64]),
Operator::I64Extend32S => Ok(vec![Type::I64]),
Operator::I32TruncSatF32S => Ok(vec![Type::I32]),
Operator::I32TruncSatF32U => Ok(vec![Type::I32]),
Operator::I32TruncSatF64S => Ok(vec![Type::I32]),
Operator::I32TruncSatF64U => Ok(vec![Type::I32]),
Operator::I64TruncSatF32S => Ok(vec![Type::I64]),
Operator::I64TruncSatF32U => Ok(vec![Type::I64]),
Operator::I64TruncSatF64S => Ok(vec![Type::I64]),
Operator::I64TruncSatF64U => Ok(vec![Type::I64]),
Operator::TableGet { table } => Ok(vec![module.tables[*table as usize]]),
Operator::TableSet { .. } => Ok(vec![]),
Operator::TableGrow { .. } => Ok(vec![]),
Operator::TableSize { .. } => Ok(vec![Type::I32]),
_ => bail!("Unknown operator in op_outputs(): {:?}", op),
}