1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
use crate::core::reader::span::Span;
use crate::core::reader::types::global::GlobalType;
use crate::core::reader::WasmReader;
use crate::{Error, NumType, Result, ValType};
use super::validation_stack::ValidationStack;
/// Read and validate constant expressions.
///
/// This function is used to validate that a constant expression produces the expected result. The main use case for
/// this is to validate that an initialization expression for a global returns the correct value.
///
/// Note: to be valid, constant expressions may not leave garbage data on the stack. It may leave only what is expected
/// and nothing more.
///
/// Valid constant instructions are:
/// - Core: <https://webassembly.github.io/spec/core/valid/instructions.html#valid-constant>
/// - Extended Proposal: <https://webassembly.github.io/extended-const/core/valid/instructions.html#valid-constant>
///
/// # The Wonders of `global.get`
/// The `global.get` instruction is quite picky by nature. To make a long story short, there are two rules to follow to
/// be able to use this expression.
///
/// ## 1. The referenced global must be imported
/// Take the example code:
/// ```wat
/// (module
/// (global (export "g") (mut i32) (
/// i32.add (i32.const 1) (i32.const 2)
/// ))
///
/// (global (export "h1") i32 (
/// i32.const 1
/// ))
///
/// (global (export "h2") i32 (
/// global.get 1
/// ))
///
/// (func (export "f")
/// i32.const 100
/// global.set 0))
/// ```
///
/// When compiling with wat2wasm, the following error is thrown:
/// ```wat
/// Error: validate failed:
/// test.wast:11:24: error: initializer expression can only reference an imported global
/// global.get 1
/// ^
/// ```
///
/// When compiling the code with the latest dev build of wasmtime, the following error is thrown:
/// ```wat
/// failed to parse WebAssembly module
///
/// Caused by:
/// constant expression required: global.get of locally defined global (at offset 0x24)
/// ```
///
/// ## 2. The referenced global must be immutable
///
///```wat
/// (module
/// (import "env" "g" (global (mut i32)))
/// (global (export "h") (mut i32) (
/// i32.add (i32.const 1) (global.get 0)
/// ))
/// )
/// ```
///
/// When compiling with wat2wasm, the following error is thrown:
/// ```wat
/// Error: validate failed:
/// test.wast:4:27: error: initializer expression cannot reference a mutable global
/// i32.add (i32.const 1) (global.get 0)
/// ```
///
/// # Note
/// The following instructions are not yet supported:
/// - `ref.null`
/// - `ref.func`
/// - `global.get`
pub fn read_constant_instructions(
wasm: &mut WasmReader,
this_global_valtype: Option<ValType>,
_globals_ty: Option<&[GlobalType]>,
) -> Result<Span> {
let start_pc = wasm.pc;
// Compared to the code validation, we create the validation stack here as opposed to taking it as an argument.
let mut stack = ValidationStack::new();
loop {
let Ok(first_instr_byte) = wasm.read_u8() else {
return Err(Error::ExprMissingEnd);
};
trace!("Read constant instruction byte {first_instr_byte:#X?} ({first_instr_byte})");
use crate::core::reader::types::opcode::*;
match first_instr_byte {
// Missing: ref.null, ref.func, global.get
END => {
// The stack must only contain the global's valtype
if this_global_valtype.is_some() {
stack.assert_val_types(&[this_global_valtype.unwrap()])?;
}
return Ok(Span::new(start_pc, wasm.pc - start_pc + 1));
}
I32_CONST => {
let _num = wasm.read_var_i32()?;
stack.push_valtype(ValType::NumType(NumType::I32));
}
I64_CONST => {
let _num = wasm.read_var_i64()?;
stack.push_valtype(ValType::NumType(NumType::I64));
}
I32_ADD | I32_SUB | I32_MUL => {
stack.assert_pop_val_type(ValType::NumType(NumType::I32))?;
stack.assert_pop_val_type(ValType::NumType(NumType::I32))?;
stack.push_valtype(ValType::NumType(NumType::I32));
}
I64_ADD | I64_SUB | I64_MUL => {
stack.assert_pop_val_type(ValType::NumType(NumType::I64))?;
stack.assert_pop_val_type(ValType::NumType(NumType::I64))?;
stack.push_valtype(ValType::NumType(NumType::I64));
}
REF_NULL => {
todo!();
}
REF_FUNC => {
wasm.read_var_u32().unwrap();
}
_ => return Err(Error::InvalidInstr(first_instr_byte)),
}
}
}