///|
///|
pub enum TcEscape {
NoTcEscape
BranchTcEscape(Int32)
TerminalTcEscape
} derive(Eq, Debug)
///|
pub impl Show for TcEscape with fn output(self, logger) {
match self {
NoTcEscape => logger.write_string("NoTcEscape")
BranchTcEscape(depth) => logger.write_string("BranchTcEscape(\{depth})")
TerminalTcEscape => logger.write_string("TerminalTcEscape")
}
}
///|
pub struct TcState {
env : Env
stack : Array[ValType]
// Execution flow is independent of the current validation frame.
reachable : Bool
polymorphic : Bool
escape : TcEscape
reachable_escape_depths : Array[Int32]
initialized_locals : Array[Bool]
}
///|
pub impl Show for TcState with fn output(self, logger) {
logger.write_string("TcState {env: ")
self.env.output(logger)
logger.write_string(", stack: ")
self.stack.output(logger)
logger.write_string(", reachable: ")
self.reachable.output(logger)
logger.write_string(", polymorphic: ")
self.polymorphic.output(logger)
logger.write_string(", escape: ")
self.escape.output(logger)
logger.write_string(", reachable_escape_depths: ")
self.reachable_escape_depths.output(logger)
logger.write_string(", initialized_locals: ")
self.initialized_locals.output(logger)
logger.write_char('}')
}
///|
pub type TcResult = Result[TcState, String]
///|
pub trait Typecheck {
fn typecheck(Self, TcState) -> Result[TcState, String]
}
///|
fn TcState::set_unreachable(st : TcState) -> TcState {
st.stack.clear()
if !st.reachable {
return TcState::{ ..st, polymorphic: true, }
}
TcState::{
..st,
reachable: false,
polymorphic: true,
escape: TerminalTcEscape,
}
}
///|
fn TcState::set_branch_escape(st : TcState, depth : Int32) -> TcState {
st.stack.clear()
if !st.reachable {
return TcState::{ ..st, polymorphic: true, }
}
TcState::{
..st,
reachable: false,
polymorphic: true,
escape: BranchTcEscape(depth),
}
}
///|
fn tc_escape_depths_has(depths : Array[Int32], depth : Int32) -> Bool {
depths.any(fn(d) { d == depth })
}
///|
fn tc_escape_depths_shift_to_parent(depths : Array[Int32]) -> Array[Int32] {
let out : Array[Int32] = []
for depth in depths {
if depth > 0 {
out.push(depth - 1)
}
}
out
}
///|
fn tc_escape_depths_merge(
left : Array[Int32],
right : Array[Int32],
) -> Array[Int32] {
if left.length() == 0 {
return right
}
if right.length() == 0 {
return left
}
let out = left.copy()
for depth in right {
out.push(depth)
}
out
}
///|
fn tc_escape_depths_merge_children(
left : Array[Int32],
right : Array[Int32],
output_arrays~ : Array[Int32]? = None,
) -> Array[Int32] {
// The child observations are borrowed. Keep their order and duplicates in
// one owned result instead of making and then merging two shifted arrays.
let out : Array[Int32] = []
if output_arrays is Some(count) {
count[0] += 1
}
for depth in left {
if depth > 0 {
out.push(depth - 1)
}
}
for depth in right {
if depth > 0 {
out.push(depth - 1)
}
}
out
}
///|
fn TcState::record_reachable_escape_depth(
st : TcState,
was_reachable : Bool,
depth : Int32,
) -> TcState {
if !was_reachable {
return st
}
let depths = st.reachable_escape_depths.copy()
depths.push(depth)
TcState::{ ..st, reachable_escape_depths: depths, }
}
///|
fn TcState::record_reachable_escape_label(
st : TcState,
was_reachable : Bool,
label : LabelIdx,
) -> TcState {
let LabelIdx(raw) = label
st.record_reachable_escape_depth(was_reachable, raw.reinterpret_as_int())
}
///|
fn TcState::merge_child_reachable_escape_depths(
st : TcState,
child : TcState,
) -> TcState {
let shifted = tc_escape_depths_shift_to_parent(child.reachable_escape_depths)
TcState::{
..st,
reachable_escape_depths: tc_escape_depths_merge(
st.reachable_escape_depths,
shifted,
),
}
}
///|
fn TcState::push1(st : TcState, vt : ValType) -> TcState {
st.stack.push(vt)
st
}
///|
fn tc_initialized_intersection(
left : Array[Bool],
right : Array[Bool],
element_checks~ : Array[Int32]? = None,
) -> Array[Bool] {
if physical_equal(left, right) {
// A read-only control fork can share its mask with both arms. The merged
// state still owns a copy so a later initialization cannot change either arm.
return left.copy()
}
let limit = if left.length() < right.length() {
left.length()
} else {
right.length()
}
match element_checks {
Some(counts) => counts[0] += limit
None => ()
}
Array::makei(limit, fn(idx) { left[idx] && right[idx] })
}
///|
#inline
fn tc_initialized_join(left : Array[Bool], right : Array[Bool]) -> Array[Bool] {
// Expression owners copy inherited masks before changing a false bit.
// Reusing an unchanged join also keeps enclosing joins on this constant-time
// path, instead of making equal-but-distinct masks at every nested if.
if physical_equal(left, right) {
left
} else {
tc_initialized_intersection(left, right)
}
}
///|
priv struct TcInitializationOwner {
mut mask : Array[Bool]?
copy_count : Array[Int32]?
}
///|
fn TcInitializationOwner::new(
copy_count~ : Array[Int32]? = None,
) -> TcInitializationOwner {
{ mask: None, copy_count, }
}
///|
fn TcState::mark_local_initialized(
st : TcState,
idx : LocalIdx,
initialization_owner~ : TcInitializationOwner? = None,
) -> TcState {
let LocalIdx(raw) = idx
let local_id = raw.reinterpret_as_int()
if local_id >= 0 &&
local_id < st.initialized_locals.length() &&
!st.initialized_locals[local_id] {
// Control entry, sibling arms, and prior states may share this mask. A
// semantic change owns its storage before setting a bit; idempotent writes
// leave every shared mask untouched.
if initialization_owner is Some(owner) {
if owner.mask is Some(owned) &&
physical_equal(owned, st.initialized_locals) {
st.initialized_locals[local_id] = true
return st
}
if owner.copy_count is Some(count) {
count[0] += 1
}
}
let initialized_locals = st.initialized_locals.copy()
if initialization_owner is Some(owner) {
owner.mask = Some(initialized_locals)
}
initialized_locals[local_id] = true
return TcState::{ ..st, initialized_locals, }
}
st
}
///|
fn TcState::pop_ref_or_bot(st : TcState) -> Result[(TcState, RefType?), String] {
match st.pop1() {
Err(e) => Err(e)
Ok((st1, v)) =>
match v {
BotValType => Ok((st1, None)) // unknown ref type, but acceptable
RefTypeValType(rt) => Ok((st1, Some(rt)))
_ => Err("expected a reference operand")
}
}
}
///|
fn TcState::check_branch_args_present(
st : TcState,
l : LabelIdx,
) -> Result[Array[ValType], String] {
match st.env.get_label_types(l) {
None => Err("invalid label index")
Some(ts) => Ok(ts)
}
}
///|
fn TcState::pop1(st : TcState) -> Result[(TcState, ValType), String] {
match st.stack.pop() {
None =>
if st.polymorphic {
// In unreachable code, missing values below the current frame are
// polymorphic, but concrete values pushed afterward must still be
// consumed from the real stack first.
Ok((st, ValType::bottom()))
} else {
Err("stack underflow")
}
Some(v) => Ok((st, v))
}
}
///|
fn TcState::pop_expect(
st : TcState,
expected : ValType,
operand_pairs~ : Array[Int32]? = None,
) -> TcResult {
ignore(operand_pairs)
match st.pop_expect_error(expected) {
Some(error) => Err(error)
None => Ok(st)
}
}
///|
// Success always retains this state and mutates only its owned operand stack.
// Internal consumers can keep the state without allocating Ok per operand.
fn TcState::pop_expect_error(st : TcState, expected : ValType) -> String? {
// The stack is already owned and mutated by pop1. Check its popped value
// directly so typed operands do not allocate an intermediate state/value pair.
let actual = match st.stack.pop() {
Some(value) => value
None =>
if st.polymorphic {
return None
} else {
return Some("stack underflow")
}
}
if actual == ValType::bottom() || Match::matches(actual, expected, st.env) {
None
} else {
Some("type mismatch: expected=\{expected} actual=\{actual}")
}
}
///|
fn typecheck_drop(st : TcState) -> TcResult {
match st.pop1() {
Err(e) => Err(e)
Ok((st2, _)) => Ok(st2)
}
}
///|
fn TcState::push_types(st : TcState, ts : Array[ValType]) -> TcState {
for t in ts {
st.stack.push(t)
}
st
}
///|
fn TcState::pop_types(st : TcState, ts : Array[ValType]) -> TcResult {
for i = ts.length() - 1; i >= 0; i = i - 1 {
if st.pop_expect_error(ts[i]) is Some(error) {
return Err(error)
}
}
Ok(st)
}
///|
// Direct callers retain this state and need only an operand error. Keep the
// public Result-returning pop_types contract unchanged.
fn TcState::pop_types_error(st : TcState, ts : Array[ValType]) -> String? {
for i = ts.length() - 1; i >= 0; i = i - 1 {
if st.pop_expect_error(ts[i]) is Some(error) {
return Some(error)
}
}
None
}
///|
fn check_pop_types_from_top(
st : TcState,
ts : Array[ValType],
count : Int32,
) -> Result[Int32, String] {
let mut stack_i = match latest_stack_index(st.stack.length()) {
Some(idx) => idx
None => -1
}
let mut i = count - 1
while i >= 0 {
let expected = ts[i]
if stack_i < 0 {
if !st.polymorphic {
return Err("stack underflow")
}
} else {
let actual = st.stack[stack_i]
if actual != ValType::bottom() &&
!Match::matches(actual, expected, st.env) {
return Err("type mismatch")
}
stack_i -= 1
}
i -= 1
}
Ok(stack_i)
}
///|
fn TcState::check_pop_types(
st : TcState,
ts : Array[ValType],
) -> Result[Unit, String] {
match check_pop_types_from_top(st, ts, ts.length()) {
Ok(_) => Ok(())
Err(e) => Err(e)
}
}
///|
fn TcState::retype_label_prefix(
st : TcState,
ts : Array[ValType],
prefix_len : Int32,
) -> TcResult {
for i = prefix_len - 1; i >= 0; i = i - 1 {
if st.pop_expect_error(ts[i]) is Some(error) {
return Err(error)
}
}
// Conditional branch fallthrough uses the declared label types. This also
// materializes operands supplied by polymorphic underflow in a dead frame.
for i in 0.. Array[ValType] {
ts.copy()
}
///|
fn validate_end_stack(
st : TcState,
results : Array[ValType],
) -> Result[Unit, String] {
let top_after_results = match
check_pop_types_from_top(st, results, results.length()) {
Err(e) => return Err(e)
Ok(i) => i
}
if top_after_results != -1 {
return Err("type mismatch")
}
Ok(())
}
///|
fn normalize_untyped_if_branch_exit(
st : TcState,
results : Array[ValType],
) -> Result[(Bool, TcEscape), String] {
let exit = normalize_untyped_if_branch_exit_value(st, results)
match exit.error {
Some(error) => Err(error)
None => Ok((exit.reachable, exit.escape))
}
}
///|
#valtype
priv struct TcIfBranchExit {
reachable : Bool
escape : TcEscape
error : String?
}
///|
fn TcIfBranchExit::new(
reachable : Bool,
escape : TcEscape,
error~ : String? = None,
) -> TcIfBranchExit {
{ reachable, escape, error, }
}
///|
fn normalize_untyped_if_branch_exit_value(
st : TcState,
results : Array[ValType],
) -> TcIfBranchExit {
match validate_end_stack(st, results) {
Err(e) => return TcIfBranchExit::new(false, NoTcEscape, error=Some(e))
Ok(_) => ()
}
if st.reachable {
return TcIfBranchExit::new(true, NoTcEscape)
}
match st.escape {
NoTcEscape | TerminalTcEscape =>
TcIfBranchExit::new(false, TerminalTcEscape)
BranchTcEscape(0) => TcIfBranchExit::new(true, NoTcEscape)
BranchTcEscape(depth) =>
TcIfBranchExit::new(false, BranchTcEscape(depth - 1))
}
}
///|
fn merge_nonfallthrough_if_exits(left : TcEscape, right : TcEscape) -> TcEscape {
if left == right {
return left
}
TerminalTcEscape
}
///|
fn TcState::mem_at_of(st : TcState, mem : MemIdx) -> Result[ValType, String] {
match st.env.get_memtype(mem) {
None => Err("invalid memory index")
Some(MemType(lim, _)) => Ok(lim.addr_valtype())
}
}
///|
fn pop_types_rev(st : TcState, ts : Array[ValType]) -> TcResult {
for i = ts.length() - 1; i >= 0; i = i - 1 {
if st.pop_expect_error(ts[i]) is Some(error) {
return Err(error)
}
}
Ok(st)
}
///|
fn normalize_untyped_block_exit(
st : TcState,
results : Array[ValType],
) -> Result[(Bool, TcEscape), String] {
match validate_end_stack(st, results) {
Err(e) => return Err(e)
Ok(_) => ()
}
if st.reachable {
return Ok((true, NoTcEscape))
}
match st.escape {
NoTcEscape | TerminalTcEscape => Ok((false, TerminalTcEscape))
BranchTcEscape(0) => Ok((true, NoTcEscape))
BranchTcEscape(depth) => Ok((false, BranchTcEscape(depth - 1)))
}
}
///|
fn normalize_loop_exit(
st : TcState,
results : Array[ValType],
) -> Result[(Bool, TcEscape), String] {
match validate_end_stack(st, results) {
Err(e) => return Err(e)
Ok(_) => ()
}
if st.reachable {
return Ok((true, NoTcEscape))
}
match st.escape {
BranchTcEscape(depth) =>
if depth > 0 {
Ok((false, BranchTcEscape(depth - 1)))
} else {
Ok((false, TerminalTcEscape))
}
_ => Ok((false, TerminalTcEscape))
}
}
///|
pub impl Typecheck for Expr with fn typecheck(self, st0) {
typecheck_expr_with_context(self, st0, true)
}
///|
/// Check a speculative expression with the same checks and state ownership as
/// `Typecheck::typecheck`, omitting only the outer instruction/stack error
/// wrapper. Nested diagnostics and the first underlying failure are retained.
pub fn typecheck_expr_for_probe(expr : Expr, state : TcState) -> TcResult {
typecheck_expr_with_context(expr, state, false)
}
///|
fn typecheck_expr_with_context(
expr : Expr,
st0 : TcState,
include_context : Bool,
) -> TcResult {
tc_iterative_expr(expr, st0, include_context)
}
///|
fn typecheck_select_typed(st : TcState, ts : Array[ValType]) -> TcResult {
if Validate::validate(ts, st.env) is Err(t) {
return Err("invalid select type annotation: " + t)
}
// condition
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match pop_types_rev(st1, ts) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match pop_types_rev(st2, ts) {
Err(e) => return Err(e)
Ok(s) => s
}
// push ts
let mut out = st3
for t in ts {
out = out.push1(t)
}
Ok(out)
}
///|
fn typecheck_select_untyped(st : TcState) -> TcResult {
// pop condition (i32)
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop second operand
let (st2, t2) = match st1.pop1() {
Err(e) => return Err(e)
Ok(pair) => pair
}
// pop first operand
let (st3, t1) = match st2.pop1() {
Err(e) => return Err(e)
Ok(pair) => pair
}
if t1 == ValType::bottom() {
return Ok(st3.push1(t2))
}
if t2 == ValType::bottom() {
return Ok(st3.push1(t1))
}
let same = Match::matches(t1, t2, st.env) && Match::matches(t2, t1, st.env)
if !same {
return Err("select requires both operands to have the same type")
}
Ok(st3.push1(t1))
}
///|
fn typecheck_local_get(st : TcState, idx : LocalIdx) -> TcResult {
match typecheck_local_get_error(st, idx) {
None => Ok(st)
Some(error) => Err(error)
}
}
///|
fn typecheck_local_get_error(st : TcState, idx : LocalIdx) -> String? {
match st.env.get_local_type(idx) {
None => Some("invalid local index")
Some(vt) => {
let LocalIdx(raw) = idx
let local_id = raw.reinterpret_as_int()
if local_id < 0 || local_id >= st.initialized_locals.length() {
return Some("invalid local index")
}
if !st.initialized_locals[local_id] {
return Some("uninitialized local: \{local_id}")
}
st.stack.push(vt)
None
}
}
}
///|
fn typecheck_local_set(
st : TcState,
idx : LocalIdx,
initialization_owner~ : TcInitializationOwner? = None,
) -> TcResult {
match st.env.get_local_type(idx) {
None => Err("invalid local index")
Some(vt) =>
// A successful pop keeps this state and mutates only its owned stack.
// Initialization may still return a new state with an isolated mask.
match st.pop_expect_error(vt) {
Some(e) => Err(e)
None => Ok(st.mark_local_initialized(idx, initialization_owner~))
}
}
}
///|
fn typecheck_local_tee(
st : TcState,
idx : LocalIdx,
initialization_owner~ : TcInitializationOwner? = None,
) -> TcResult {
match st.env.get_local_type(idx) {
None => Err("invalid local index")
Some(vt) =>
// tee pops vt then pushes vt
match st.pop_expect_error(vt) {
Some(e) => Err(e)
None =>
Ok(st.mark_local_initialized(idx, initialization_owner~).push1(vt))
}
}
}
///|
fn typecheck_global_get(st : TcState, idx : GlobalIdx) -> TcResult {
match st.env.get_global_type(idx) {
None => Err("invalid global index")
Some(GlobalType(vt, _mut)) => Ok(st.push1(vt))
}
}
///|
fn typecheck_global_set(st : TcState, idx : GlobalIdx) -> TcResult {
match st.env.get_global_type(idx) {
None => Err("invalid global index")
Some(GlobalType(vt, is_mut)) => {
if !is_mut {
return Err("global.set on immutable global")
}
st.pop_expect(vt)
}
}
}
// table.get x : [at] -> [t]
///|
fn typecheck_table_get(st : TcState, idx : TableIdx) -> TcResult {
let TableType(rt, limits) = match st.env.get_table_type(idx) {
None => return Err("invalid table index")
Some(tt) => tt
}
let st1 = match st.pop_expect(limits.addr_valtype()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st1.push1(ValType::ref_type(rt)))
}
// table.set x : [at, t] -> []
///|
fn typecheck_table_set(st : TcState, idx : TableIdx) -> TcResult {
let TableType(rt, limits) = match st.env.get_table_type(idx) {
None => return Err("invalid table index")
Some(tt) => tt
}
// pop value (t)
let st1 = match st.pop_expect(ValType::ref_type(rt)) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop index (the table's address type)
let st2 = match st1.pop_expect(limits.addr_valtype()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2)
}
// table.size x : [] -> [at]
///|
fn typecheck_table_size(st : TcState, idx : TableIdx) -> TcResult {
match st.env.get_table_type(idx) {
None => Err("invalid table index")
Some(TableType(_, limits)) => Ok(st.push1(limits.addr_valtype()))
}
}
// table.grow x : [t, at] -> [at]
///|
fn typecheck_table_grow(st : TcState, idx : TableIdx) -> TcResult {
let TableType(rt, limits) = match st.env.get_table_type(idx) {
None => return Err("invalid table index")
Some(tt) => tt
}
// pop delta (the table's address type) — top of stack
let st1 = match st.pop_expect(limits.addr_valtype()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop init value (t)
let st2 = match st1.pop_expect(ValType::ref_type(rt)) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push1(limits.addr_valtype()))
}
///|
fn typecheck_br(st : TcState, l : LabelIdx) -> TcResult {
let ts = match st.env.get_label_types(l) {
None => return Err("invalid label index")
Some(x) => x
}
let st1 = match st.pop_types(ts) {
Err(e) => return Err(e)
Ok(s) => s
}
let LabelIdx(depth) = l
Ok(st1.set_branch_escape(depth.reinterpret_as_int()))
}
///|
fn typecheck_block(st : TcState, bt : BlockType, body : Expr) -> TcResult {
let Expr(body_instrs) = body
let (params, results) = match st.env.expand_blocktype(bt) {
Err(e) => return Err(e)
Ok(x) => x
}
// Consume params from the outer stack.
let st1 = match st.pop_types(params) {
Err(e) => return Err(e)
Ok(s) => s
}
// Body instructions only see block params above their label boundary.
let body_stack = tc_stack_from_types(params)
let env2 = st1.env.with_label(results)
let st_body0 = TcState::{
env: env2,
stack: body_stack,
reachable: st1.reachable,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals: tc_fork_initialized_for_body(
st1.initialized_locals,
body_instrs,
),
}
let st_body = match Typecheck::typecheck(Expr::new(body_instrs), st_body0) {
Err(e) => return Err(e)
Ok(s) => s
}
tc_state_finish_block(st, st1, st_body, results)
}
///|
// Complete a prechecked control region without walking its instructions again.
pub fn tc_state_finish_block(
outer : TcState,
entry : TcState,
body : TcState,
results : Array[ValType],
) -> Result[TcState, String] {
let (base_reaches_merge, block_escape) = match
normalize_untyped_block_exit(body, results) {
Err(e) => return Err(e)
Ok(x) => x
}
let reaches_merge = base_reaches_merge ||
tc_escape_depths_has(body.reachable_escape_depths, 0)
if reaches_merge {
let out = TcState::{
env: outer.env,
stack: entry.stack,
reachable: outer.reachable,
polymorphic: outer.polymorphic,
escape: outer.escape,
reachable_escape_depths: outer.reachable_escape_depths,
// Inner writes cannot initialize a local in the parent control frame.
initialized_locals: entry.initialized_locals,
}.merge_child_reachable_escape_depths(body)
return Ok(out.push_types(results))
}
Ok(
TcState::{
env: outer.env,
stack: entry.stack,
reachable: false,
polymorphic: outer.polymorphic,
escape: if outer.reachable {
block_escape
} else {
outer.escape
},
reachable_escape_depths: outer.reachable_escape_depths,
initialized_locals: outer.initialized_locals,
}
.merge_child_reachable_escape_depths(body)
.push_types(results),
)
}
///|
fn typecheck_if(
st : TcState,
bt : BlockType,
then_instrs : Array[Instruction],
else_instrs_opt : Array[Instruction]?,
) -> TcResult {
let (params, results) = match st.env.expand_blocktype(bt) {
Err(e) => return Err(e)
Ok(x) => x
}
// pop condition
let st0 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop params
let st1 = match st0.pop_types(params) {
Err(e) => return Err(e)
Ok(s) => s
}
let else_instrs = match else_instrs_opt {
None => []
Some(xs) => xs
}
// typecheck then branch
let branch_stack = tc_stack_from_types(params)
let env_branch = st1.env.with_label(results)
let st_then0 = TcState::{
env: env_branch,
stack: branch_stack,
reachable: st1.reachable,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals: tc_fork_initialized_for_body(
st1.initialized_locals,
then_instrs,
),
}
let st_then = match Typecheck::typecheck(Expr::new(then_instrs), st_then0) {
Err(e) => return Err(e)
Ok(s) => s
}
// typecheck else branch (same starting state!)
let st_else0 = TcState::{
env: env_branch,
stack: tc_stack_from_types(params),
reachable: st1.reachable,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals: tc_fork_initialized_for_body(
st1.initialized_locals,
else_instrs,
),
}
let st_else = match Typecheck::typecheck(Expr::new(else_instrs), st_else0) {
Err(e) => return Err(e)
Ok(s) => s
}
tc_state_finish_if(st, st1, st_then, st_else, results)
}
///|
// Complete a prechecked control region without walking its instructions again.
pub fn tc_state_finish_if(
st : TcState,
st1 : TcState,
st_then : TcState,
st_else : TcState,
results : Array[ValType],
) -> Result[TcState, String] {
let then_exit = normalize_untyped_if_branch_exit_value(st_then, results)
if then_exit.error is Some(error) {
return Err(error)
}
let else_exit = normalize_untyped_if_branch_exit_value(st_else, results)
if else_exit.error is Some(error) {
return Err(error)
}
let then_reach = then_exit.reachable ||
tc_escape_depths_has(st_then.reachable_escape_depths, 0)
let else_reach = else_exit.reachable ||
tc_escape_depths_has(st_else.reachable_escape_depths, 0)
let merged_child_depths = tc_escape_depths_merge_children(
st_then.reachable_escape_depths,
st_else.reachable_escape_depths,
)
if then_reach || else_reach {
let out = TcState::{
env: st.env,
stack: st1.stack,
reachable: st.reachable,
polymorphic: st.polymorphic,
escape: st.escape,
reachable_escape_depths: tc_escape_depths_merge(
st.reachable_escape_depths,
merged_child_depths,
),
// Each arm's initialization scope ends with the if control frame.
initialized_locals: st1.initialized_locals,
}
return Ok(out.push_types(results))
}
Ok(
TcState::{
env: st.env,
stack: st1.stack,
reachable: false,
polymorphic: st.polymorphic,
escape: if st.reachable {
merge_nonfallthrough_if_exits(then_exit.escape, else_exit.escape)
} else {
st.escape
},
reachable_escape_depths: tc_escape_depths_merge(
st.reachable_escape_depths,
merged_child_depths,
),
initialized_locals: st.initialized_locals,
}.push_types(results),
)
}
///|
fn typecheck_br_if(st : TcState, l : LabelIdx) -> TcResult {
let ts = match st.env.get_label_types(l) {
None => return Err("invalid label index")
Some(x) => x
}
// Pop condition first
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_types(ts) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push_types(ts))
}
///|
fn typecheck_br_table(
st : TcState,
ls : Array[LabelIdx],
ldef : LabelIdx,
) -> TcResult {
// pop selector
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let ts = match st.env.get_label_types(ldef) {
None => return Err("invalid default label index")
Some(x) => x
}
// Every target consumes the same number of operands, but its types need not
// be identical: one actual operand can be a subtype of several reference
// types, and a polymorphic bottom operand matches distinct numeric types.
// Check each target against the same unconsumed operand stack.
for l in ls {
let ts_l = match st.env.get_label_types(l) {
None => return Err("invalid label index in br_table")
Some(x) => x
}
if ts_l.length() != ts.length() {
return Err("br_table target labels have different argument arities")
}
if st1.check_pop_types(ts_l) is Err(error) {
return Err(error)
}
}
// Pop the branch arguments (values that will be passed to the target label)
let st2 = match st1.pop_types(ts) {
Err(e) => return Err(e)
Ok(s) => s
}
// br_table always branches - code after is unreachable
let mut all_same = true
for l in ls {
if l != ldef {
all_same = false
}
}
if all_same {
let LabelIdx(depth) = ldef
Ok(st2.set_branch_escape(depth.reinterpret_as_int()))
} else {
Ok(st2.set_unreachable())
}
}
///|
fn typecheck_return(st : TcState) -> TcResult {
let ts = match st.env.return_type {
None => return Err("return used with no function return type in context")
Some(t) => t
}
let st1 = match st.pop_types(ts) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st1.set_unreachable())
}
///|
fn typecheck_call(st : TcState, fidx : FuncIdx) -> TcResult {
let FuncType(params, results) = match st.env.get_functype_by_funcidx(fidx) {
None => return Err("invalid function index")
Some(ft) => ft
}
if st.pop_types_error(params) is Some(error) {
return Err(error)
}
Ok(st.push_types(results))
}
///|
fn typecheck_call_indirect(
st : TcState,
tidx : TypeIdx,
tid : TableIdx,
) -> TcResult {
// Resolve the function type from the type index
let FuncType(params, results) = match st.env.resolve_functype(tidx) {
None => return Err("call_indirect type index is not a function type")
Some(ft) => ft
}
// Table must exist and have element type compatible with funcref
let TableType(elem_rt, limits) = match st.env.get_table_type(tid) {
None => return Err("invalid table index")
Some(tt) => tt
}
// Require elem_rt <: funcref (spec requires the table contains functions)
let fr = ValType::funcref()
let elem_vt = ValType::ref_type(elem_rt)
if !Match::matches(elem_vt, fr, st.env) {
return Err("call_indirect table element type is not funcref-compatible")
}
// Pop the table's address-width index (i32 for table32, i64 for table64).
let st1 = match st.pop_expect(limits.addr_valtype()) {
Err(e) => return Err(e)
Ok(s) => s
}
// Pop params
let st2 = match st1.pop_types(params) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push_types(results))
}
///|
fn typecheck_call_ref(st : TcState, tidx : TypeIdx) -> TcResult {
let FuncType(params, results) = match st.env.resolve_functype(tidx) {
None => return Err("call_ref type index is not a function type")
Some(ft) => ft
}
// Pop funcref of type (ref null tidx)
// Your representation: HeapTypeRefType(nullable, HeapType(TypeIdx))
let fref = ValType::ref_type(RefType::new(true, HeapType::new(tidx)))
let st1 = match st.pop_expect(fref) {
Err(e) => return Err(e)
Ok(s) => s
}
// Pop params
let st2 = match st1.pop_types(params) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push_types(results))
}
///|
fn require_return_results(
st : TcState,
results : Array[ValType],
) -> Result[Unit, String] {
match st.env.return_type {
None => Err("return_call used with no function return type in context")
Some(rt) =>
if Match::matches(results, rt, st.env) {
Ok(())
} else {
Err("tail call result mismatch")
}
}
}
///|
fn typecheck_return_call(st : TcState, fidx : FuncIdx) -> TcResult {
let FuncType(params, results) = match st.env.get_functype_by_funcidx(fidx) {
None => return Err("invalid function index")
Some(ft) => ft
}
match require_return_results(st, results) {
Err(e) => return Err(e)
Ok(_) => ()
}
let st1 = match st.pop_types(params) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st1.set_unreachable())
}
///|
fn typecheck_return_call_indirect(
st : TcState,
tidx : TypeIdx,
t : TableIdx,
) -> TcResult {
let FuncType(params, results) = match st.env.resolve_functype(tidx) {
None => return Err("return_call_indirect type index is not a function type")
Some(ft) => ft
}
match require_return_results(st, results) {
Err(e) => return Err(e)
Ok(_) => ()
}
let TableType(elem_rt, limits) = match st.env.get_table_type(t) {
None => return Err("invalid table index")
Some(tt) => tt
}
// elem_rt <: funcref
let fr = ValType::ref_type(RefType::abs(AbsHeapType::func()))
let elem_vt = ValType::ref_type(elem_rt)
if !Match::matches(elem_vt, fr, st.env) {
return Err(
"return_call_indirect table element type is not funcref-compatible",
)
}
let st1 = match st.pop_expect(limits.addr_valtype()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_types(params) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.set_unreachable())
}
///|
fn typecheck_return_call_ref(st : TcState, tidx : TypeIdx) -> TcResult {
let FuncType(params, results) = match st.env.resolve_functype(tidx) {
None => return Err("return_call_ref type index is not a function type")
Some(ft) => ft
}
match require_return_results(st, results) {
Err(e) => return Err(e)
Ok(_) => ()
}
let fref = ValType::ref_type(RefType::new(true, HeapType::new(tidx)))
let st1 = match st.pop_expect(fref) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_types(params) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.set_unreachable())
}
///|
fn expect_branch_ref(v : ValType) -> Result[(HeapType, RefType), String] {
match v {
RefTypeValType(rt) => Ok((rt.get_heap_type(), rt))
_ => Err("expected a reference type")
}
}
///|
fn typecheck_br_on_null(st : TcState, l : LabelIdx) -> TcResult {
let ts = match st.env.get_label_types(l) {
None => return Err("invalid label index")
Some(x) => x
}
// Pop operand: a nullable ref refines to non-null on fallthrough; a
// statically non-null ref is also valid and simply cannot take the null edge.
let (st1, v) = match st.pop1() {
Err(e) => return Err(e)
Ok(pair) => pair
}
let refined = match v {
BotValType => ValType::bottom()
RefTypeValType(rt_in) => {
let ht = rt_in.get_heap_type()
if Validate::validate(ht, st.env) is Err(t) {
return Err("Invalid heap type in br_on_null operand: " + t)
}
ValType::ref_type(RefType::new(false, ht, exact=rt_in.is_exact()))
}
_ => return Err("expected a reference type")
}
let st2 = match st1.pop_types(ts) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push_types(ts).push1(refined))
}
///|
fn typecheck_br_on_non_null(st : TcState, l : LabelIdx) -> TcResult {
let label_ts = match st.env.get_label_types(l) {
None => return Err("invalid label index")
Some(x) => x
}
let (st1, v) = match st.pop1() {
Err(e) => return Err(e)
Ok(pair) => pair
}
let end = match latest_stack_index(label_ts.length()) {
Some(end) => end
None => return Err("br_on_non_null label type too short")
}
match v {
BotValType =>
match label_ts[end] {
RefTypeValType(_) => ()
_ =>
return Err("br_on_non_null label must end with non-nullable ref type")
}
_ => {
let (ht, rt_in) = match expect_branch_ref(v) {
Err(e) => return Err(e)
Ok(x) => x
}
if Validate::validate(ht, st.env) is Err(t) {
return Err("Invalid heap type in br_on_non_null operand: " + t)
}
// The taken edge contributes the tested ref as non-null. The actual branch
// payload must match the label's final expected type.
let non_null_rt = RefType::new(false, ht, exact=rt_in.is_exact())
let want_last = ValType::ref_type(non_null_rt)
let last = label_ts[end]
if !Match::matches(want_last, last, st.env) {
return Err("br_on_non_null label must end with non-nullable ref type")
}
}
}
st1.retype_label_prefix(label_ts, end)
}
///|
fn typecheck_br_on_cast(
st : TcState,
l : LabelIdx,
op : CastOp,
ht1 : HeapType,
ht2 : HeapType,
) -> TcResult {
let CastOp(n1, n2, exact1, exact2) = op
let rt1 = RefType::new(n1, ht1, exact=exact1)
let rt2 = RefType::new(n2, ht2, exact=exact2)
if Validate::validate(rt1, st.env) is Err(t) {
return Err("Invalid BrOnCast: " + t)
}
if Validate::validate(rt2, st.env) is Err(t) {
return Err("Invalid BrOnCast: " + t)
}
let label_ts = match st.check_branch_args_present(l) {
Err(e) => return Err(e)
Ok(x) => x
}
let end = match latest_stack_index(label_ts.length()) {
Some(end) => end
None => return Err("br_on_cast label type too short")
}
let rt_label = match label_ts[end] {
RefTypeValType(r) => r
_ => return Err("br_on_cast label must end with a reftype")
}
// rt2 <: rt1 and rt2 <: rt_label
if !Match::matches(rt2, rt1, st.env) {
return Err("rt2 does not match rt1")
}
if !Match::matches(rt2, rt_label, st.env) {
return Err("rt2 does not match label rt")
}
// pop rt1
let st1 = match st.pop_expect(ValType::ref_type(rt1)) {
Err(e) => return Err(e)
Ok(s) => s
}
// Check label args excluding rt_label (because branch keeps rt2 / rt_label)
// Per spec, label is t* rt, so the remaining stack must have t*
let st2 = match st1.retype_label_prefix(label_ts, end) {
Err(e) => return Err(e)
Ok(s) => s
}
// push (rt1 \ rt2)
let rt_out = match diff(rt1, rt2) {
Err(e) => return Err(e)
Ok(x) => x
}
Ok(st2.push1(ValType::ref_type(rt_out)))
}
///|
fn typecheck_br_on_cast_desc_eq(
st : TcState,
l : LabelIdx,
source : RefType,
target : RefType,
fail : Bool,
) -> TcResult {
if Validate::validate(source, st.env) is Err(error) {
return Err("Invalid descriptor cast source: " + error)
}
if Validate::validate(target, st.env) is Err(error) {
return Err("Invalid descriptor cast target: " + error)
}
// Descriptor equality supports casts between compatible GC heaps without
// requiring the target's nullability or exactness to subtype the source.
let nullable_source = RefType::new(
true,
source.get_heap_type(),
exact=source.is_exact(),
)
let nullable_target = RefType::new(
true,
target.get_heap_type(),
exact=target.is_exact(),
)
if !typecheck_ref_test_or_cast_types_compatible(
nullable_target,
nullable_source,
st.env,
) {
return Err("descriptor cast types are incompatible")
}
let descriptor_idx = match target.get_heap_type() {
HeapType(type_idx) =>
match st.env.resolve_subtype(type_idx) {
Some(subtype) =>
match subtype.descriptor_type() {
Some(descriptor_idx) => descriptor_idx
None => return Err("descriptor cast target has no descriptor type")
}
None => return Err("descriptor cast target type does not exist")
}
_ => return Err("descriptor cast target must be a defined type")
}
let descriptor_type = ValType::ref_type(
RefType::new(true, HeapType::new(descriptor_idx), exact=target.is_exact()),
)
let st1 = match st.pop_expect(descriptor_type) {
Ok(next) => next
Err(err) => return Err(err)
}
let label_types = match st1.check_branch_args_present(l) {
Ok(types) => types
Err(error) => return Err(error)
}
let end = match latest_stack_index(label_types.length()) {
Some(end) => end
None => return Err("descriptor cast label type too short")
}
let label_ref = match label_types[end] {
RefTypeValType(ref_type) => ref_type
_ => return Err("descriptor cast label must end with a reftype")
}
let source_diff = match diff(source, target) {
Ok(ref_type) => ref_type
Err(error) => return Err(error)
}
let branch_ref = if fail { source_diff } else { target }
if !Match::matches(branch_ref, label_ref, st.env) {
return Err("descriptor cast result does not match label rt")
}
let st2 = match st1.pop_expect(ValType::ref_type(source)) {
Ok(next) => next
Err(error) => return Err(error)
}
let st3 = match st2.retype_label_prefix(label_types, end) {
Ok(next) => next
Err(error) => return Err(error)
}
let fallthrough_ref = if fail { target } else { source_diff }
Ok(st3.push1(ValType::ref_type(fallthrough_ref)))
}
///|
fn typecheck_br_on_cast_fail(
st : TcState,
l : LabelIdx,
op : CastOp,
ht1 : HeapType,
ht2 : HeapType,
) -> TcResult {
let CastOp(n1, n2, exact1, exact2) = op
let rt1 = RefType::new(n1, ht1, exact=exact1)
let rt2 = RefType::new(n2, ht2, exact=exact2)
if Validate::validate(rt1, st.env) is Err(t) {
return Err("Invalid RefType on BrOnCastFail: " + t)
}
if Validate::validate(rt2, st.env) is Err(t) {
return Err("Invalid RefType on BrOnCastFail: " + t)
}
let label_ts = match st.check_branch_args_present(l) {
Err(e) => return Err(e)
Ok(x) => x
}
let end = match latest_stack_index(label_ts.length()) {
Some(end) => end
None => return Err("br_on_cast_fail label type too short")
}
let rt_label = match label_ts[end] {
RefTypeValType(r) => r
_ => return Err("br_on_cast_fail label must end with a reftype")
}
if !Match::matches(rt2, rt1, st.env) {
return Err("rt2 does not match rt1")
}
let rt_diff = match diff(rt1, rt2) {
Err(e) => return Err(e)
Ok(x) => x
}
if !Match::matches(rt_diff, rt_label, st.env) {
return Err("rt1 \\ rt2 does not match label rt")
}
// pop rt1
let st1 = match st.pop_expect(ValType::ref_type(rt1)) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.retype_label_prefix(label_ts, end) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push1(ValType::ref_type(rt2)))
}
///|
fn typecheck_throw(st : TcState, tag : TagIdx) -> TcResult {
// validate tag index
if Validate::validate(tag, st.env) is Err(e) {
return Err(e)
}
// get payload params
let TagType(tidx) = st.env.tags[tag.0.reinterpret_as_int()]
let params = match st.env.resolve_functype(tidx) {
Some(FuncType(ps, results)) => {
if !results.is_empty() {
return Err("throw: exception tag must have no results")
}
ps
}
None => return Err("throw: invalid tag type")
}
// pop payload
let st1 = match st.pop_types(params) {
Ok(s) => s
Err(e) => return Err(e)
}
// throw always makes execution unreachable
Ok(st1.set_unreachable())
}
///|
fn typecheck_throw_ref(st : TcState) -> TcResult {
let st1 = match st.pop_expect(ValType::ref_null_exn()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st1.set_unreachable())
}
///|
fn typecheck_rethrow(st : TcState, catch_depth : LabelIdx) -> TcResult {
let LabelIdx(raw_depth) = catch_depth
if st.env.legacy_rethrow_catch_ordinal(raw_depth) is None {
return Err("invalid legacy rethrow catch depth")
}
Ok(st.set_unreachable())
}
///|
fn tag_payload_types(env : Env, tag : TagIdx) -> Array[ValType] {
let idx = tag.0
// Guard against overflow - if idx >= 2^31, it would become negative when cast to Int
if idx > 0x7FFFFFFF {
return []
}
match env.tags.get(idx.reinterpret_as_int()) {
Some(TagType(tidx)) =>
match env.resolve_functype(tidx) {
Some(FuncType(params, _)) => params
None => []
}
None => []
}
}
///|
fn typecheck_catch_clause(st : TcState, c : Catch) -> TcResult {
match c {
Catch(tag, lbl) => {
if Validate::validate(tag, st.env) is Err(e) {
return Err(e)
}
if st.env.resolve_tag_functype(tag) is Some(FuncType(_, results)) &&
!results.is_empty() {
return Err("catch: exception tag must have no results")
}
// label must expect tag's payload types
let label_types = match st.env.get_catch_label_types(lbl) {
Some(label_types) => label_types
None => return Err("Invalid catch label index")
}
let payload = tag_payload_types(st.env, tag)
if !Match::matches(payload, label_types, st.env) {
return Err("catch types (payload) do not match label types")
}
Ok(st)
}
CatchRef(tag, lbl) => {
if Validate::validate(tag, st.env) is Err(e) {
return Err(e)
}
// label must expect tag's payload types + exnref
let label_types = match st.env.get_catch_label_types(lbl) {
Some(label_types) => label_types
None => return Err("Invalid catch label index")
}
if st.env.resolve_tag_functype(tag) is Some(FuncType(_, results)) &&
!results.is_empty() {
return Err("catch_ref: exception tag must have no results")
}
let payload = tag_payload_types(st.env, tag)
let expected = payload.copy()
expected.push(
ValType::ref_type(
RefType::new(false, HeapType::abs(AbsHeapType::exn())),
),
)
if !Match::matches(expected, label_types, st.env) {
return Err(
"catch_ref types (payload + exnref) do not match label types",
)
}
Ok(st)
}
CatchAll(lbl) => {
// label must expect empty
let label_types = match st.env.get_catch_label_types(lbl) {
Some(label_types) => label_types
None => return Err("Invalid catch label index")
}
if label_types.length() != 0 {
return Err("catch_all label must expect no values")
}
Ok(st)
}
CatchAllRef(lbl) => {
// label must expect just exnref
let label_types = match st.env.get_catch_label_types(lbl) {
Some(label_types) => label_types
None => return Err("Invalid catch label index")
}
let expected = [
ValType::ref_type(
RefType::new(false, HeapType::abs(AbsHeapType::exn())),
),
]
if !Match::matches(expected, label_types, st.env) {
return Err("catch_all_ref label must expect exnref")
}
Ok(st)
}
}
}
///|
fn typecheck_legacy_try(
st : TcState,
bt : BlockType,
body : Expr,
catches : Array[@lib.LegacyCatch],
delegate : LabelIdx?,
) -> TcResult {
if catches.length() > 0 && delegate is Some(_) {
return Err("legacy try cannot mix catches and delegate")
}
let (params, results) = match st.env.expand_blocktype(bt) {
Err(err) => return Err(err)
Ok(types) => types
}
let st1 = match st.pop_types(params) {
Err(err) => return Err(err)
Ok(next) => next
}
let scoped_env = st1.env.with_label(results)
let body_state = TcState::{
env: scoped_env,
stack: tc_stack_from_types(params),
reachable: st1.reachable,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals: st1.initialized_locals.copy(),
}
let checked_body = match Typecheck::typecheck(body, body_state) {
Err(err) => return Err(err)
Ok(next) => next
}
match validate_end_stack(checked_body, results) {
Err(err) => return Err(err)
Ok(_) => ()
}
let mut any_reachable = checked_body.reachable
for catch_ in catches {
let initial_types = match catch_ {
@lib.LegacyCatch(tag, _) => {
if Validate::validate(tag, st.env) is Err(err) {
return Err(err)
}
if st.env.resolve_tag_functype(tag) is Some(FuncType(_, results)) &&
!results.is_empty() {
return Err("legacy catch: exception tag must have no results")
}
tag_payload_types(st.env, tag)
}
@lib.LegacyCatchAll(_) => []
}
let catch_body = match catch_ {
@lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
catch_body
}
let catch_state = TcState::{
env: scoped_env.with_legacy_catch_depth(scoped_env.legacy_catch_depth + 1),
stack: tc_stack_from_types(initial_types),
reachable: st1.reachable,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals: st1.initialized_locals.copy(),
}
let checked_catch = match Typecheck::typecheck(catch_body, catch_state) {
Err(err) => return Err(err)
Ok(next) => next
}
match validate_end_stack(checked_catch, results) {
Err(err) => return Err(err)
Ok(_) => ()
}
if checked_catch.reachable {
any_reachable = true
}
}
match delegate {
Some(label) =>
match st.env.get_catch_label_types(label) {
Some(_) => ()
None => return Err("invalid legacy try delegate label")
}
None => ()
}
Ok(
TcState::{
env: st.env,
stack: st1.stack,
reachable: any_reachable,
polymorphic: st.polymorphic,
escape: if st.reachable {
checked_body.escape
} else {
st.escape
},
reachable_escape_depths: st.reachable_escape_depths,
initialized_locals: st1.initialized_locals,
}
.merge_child_reachable_escape_depths(checked_body)
.push_types(results),
)
}
///|
fn typecheck_try_table(
st : TcState,
bt : BlockType,
catches : Array[Catch],
body : Expr,
) -> TcResult {
let (params, results) = match st.env.expand_blocktype(bt) { // assuming you made it a method
Err(e) => return Err(e)
Ok(x) => x
}
// consume params
let st1 = match st.pop_types(params) {
Err(e) => return Err(e)
Ok(s) => s
}
// extend context with innermost label = results
let env2 = st1.env.with_label(results)
let st_body0 = TcState::{
env: env2,
stack: tc_stack_from_types(params),
reachable: st1.reachable,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
// The body expression owns its first initialization write. Borrowing here
// preserves the entry mask for catches and the parent control frame.
initialized_locals: st1.initialized_locals,
}
// body must typecheck under extended env
let st_body = match Typecheck::typecheck(body, st_body0) {
Err(e) => return Err(e)
Ok(s) => s
}
// Catch branch targets are resolved in the enclosing label space, not the
// try_table's temporary body label.
let st_catch_ctx = st1
for c in catches {
match typecheck_catch_clause(st_catch_ctx, c) {
Err(e) => return Err(e)
Ok(_) => ()
}
}
match validate_end_stack(st_body, results) {
Err(e) => return Err(e)
Ok(_) => ()
}
tc_state_finish_try_table(st, st1, st_body, results, catches)
}
///|
pub fn tc_state_finish_try_table(
outer : TcState,
entry : TcState,
body : TcState,
results : Array[ValType],
catches : Array[Catch],
) -> TcResult {
let mut out = match tc_state_finish_block(outer, entry, body, results) {
Ok(out) => out
Err(err) => return Err(err)
}
// Handler labels belong to the enclosing label space. A throw can take
// them before the body's normal exit, even when that exit is unreachable.
for catch_ in catches {
let label = match catch_ {
Catch(_, label)
| CatchRef(_, label)
| CatchAll(label)
| CatchAllRef(label) => label
}
out = out.record_reachable_escape_label(entry.reachable, label)
}
Ok(out)
}
///|
fn typecheck_table_copy(st : TcState, x : TableIdx, y : TableIdx) -> TcResult {
let TableType(rt1, l1) = match st.env.get_table_type(x) {
None => return Err("invalid dest table index")
Some(tt) => tt
}
let TableType(rt2, l2) = match st.env.get_table_type(y) {
None => return Err("invalid src table index")
Some(tt) => tt
}
// require rt2 <: rt1
if !Match::matches(rt2, rt1, st.env) {
return Err("table.copy source elem type does not match dest elem type")
}
let at_len = min_addr(l1, l2)
// Pop len, src, dst (LIFO), with types at_len, at2, at1
let st1 = match st.pop_expect(at_len.addr_valtype()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(l2.addr_valtype()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match st2.pop_expect(l1.addr_valtype()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st3)
}
///|
fn typecheck_table_init(st : TcState, y : ElemIdx, x : TableIdx) -> TcResult {
let TableType(rt1, l) = match st.env.get_table_type(x) {
None => return Err("invalid table index")
Some(tt) => tt
}
let elem = match st.env.get_elem(y) {
None => return Err("invalid elem segment index")
Some(e) => e
}
let rt2 = elem.reftype()
if !Match::matches(rt2, rt1, st.env) {
return Err("table.init elem segment type does not match table element type")
}
// Pop: len:i32, src:i32, dst:at (LIFO order)
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match st2.pop_expect(l.addr_valtype()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st3)
}
///|
fn typecheck_table_fill(st : TcState, x : TableIdx) -> TcResult {
let TableType(rt, lim) = match st.env.get_table_type(x) {
None => return Err("invalid table index")
Some(tt) => tt
}
// pop len : i32
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop val : rt
let st2 = match st1.pop_expect(ValType::ref_type(rt)) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop start : at (derived from limits)
let st3 = match st2.pop_expect(lim.addr_valtype()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st3)
}
///|
fn typecheck_elem_drop(st : TcState, y : ElemIdx) -> TcResult {
match st.env.get_elem(y) {
None => Err("invalid elem segment index")
Some(_) => Ok(st)
}
}
// Check memory selection, alignment and offset before any operand-stack pop.
///|
#valtype
priv struct TcMemargCheck {
error : String?
address64 : Bool
}
///|
fn TcMemargCheck::success(address64 : Bool) -> TcMemargCheck {
{ error: None, address64, }
}
///|
fn TcMemargCheck::failure(error : String) -> TcMemargCheck {
{ error: Some(error), address64: false, }
}
///|
fn TcMemargCheck::address_type(self : TcMemargCheck) -> ValType {
if self.address64 {
ValType::i64()
} else {
ValType::i32()
}
}
///|
fn memarg_check(
st : TcState,
ma : MemArg,
default_mem : MemIdx,
nbits : Int32,
) -> TcMemargCheck {
let MemArg(U32(align_pow_u), mem_opt, U64(offset)) = ma
let mem = match mem_opt {
None => default_mem
Some(m) => m
}
let MemType(lim, _) = match st.env.get_memtype(mem) {
None => return TcMemargCheck::failure("invalid memory index in memarg")
Some(mt) => mt
}
// (1) alignment: 2^n <= N/8
let max_align_bytes = nbits / 8
if max_align_bytes <= 0 {
return TcMemargCheck::failure("invalid access width")
}
let align_pow = align_pow_u.reinterpret_as_int()
if align_pow < 0 {
return TcMemargCheck::failure("negative alignment power")
}
if align_pow >= 64 {
return TcMemargCheck::failure("alignment too large")
}
let align_bytes : UInt64 = 1 << align_pow
if align_bytes > max_align_bytes.to_uint64() {
return TcMemargCheck::failure("memarg alignment too large for access width")
}
// (2) offset: m < 2^|at|
let bits = lim.mem_addr_bits()
if bits == 32 {
if offset >= 1 << 32 {
return TcMemargCheck::failure("memarg offset out of range for i32 memory")
}
} else {
// i64 memory: offset is UInt64 so always < 2^64
()
}
TcMemargCheck::success(bits == 64)
}
///|
fn typecheck_load(
st : TcState,
ma : MemArg,
nbits : Int32,
result : ValType,
) -> TcResult {
let checked = memarg_check(st, ma, MemIdx::new(0), nbits)
match checked.error {
Some(error) => return Err(error)
None => ()
}
let at = checked.address_type()
let st1 = match st.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st1.push1(result))
}
///|
fn memarg_check_atomic(
st : TcState,
ma : MemArg,
default_mem : MemIdx,
nbits : Int32,
) -> TcMemargCheck {
let checked = memarg_check(st, ma, default_mem, nbits)
if checked.error is Some(_) {
return checked
}
// Preserve the existing second lookup. The immutable index selection is
// repeated only here, keeping the common result to two value-type fields.
let MemArg(_, mem_opt, _) = ma
let mem = match mem_opt {
None => default_mem
Some(mem) => mem
}
match st.env.get_memtype(mem) {
None => return TcMemargCheck::failure("invalid memory index in memarg")
Some(_) => ()
}
checked
}
///|
fn typecheck_atomic_load(
st : TcState,
ma : MemArg,
nbits : Int32,
result : ValType,
) -> TcResult {
let checked = memarg_check_atomic(st, ma, MemIdx::new(0), nbits)
match checked.error {
Some(error) => return Err(error)
None => ()
}
let at = checked.address_type()
let st1 = match st.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st1.push1(result))
}
///|
fn typecheck_store(
st : TcState,
ma : MemArg,
nbits : Int32,
value : ValType,
) -> TcResult {
let checked = memarg_check(st, ma, MemIdx::new(0), nbits)
match checked.error {
Some(error) => return Err(error)
None => ()
}
let at = checked.address_type()
// pop value first (top of stack)
let st1 = match st.pop_expect(value) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop address
let st2 = match st1.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2)
}
///|
fn typecheck_atomic_store(
st : TcState,
ma : MemArg,
nbits : Int32,
value : ValType,
) -> TcResult {
let checked = memarg_check_atomic(st, ma, MemIdx::new(0), nbits)
match checked.error {
Some(error) => return Err(error)
None => ()
}
let at = checked.address_type()
let st1 = match st.pop_expect(value) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2)
}
///|
fn typecheck_atomic_notify(st : TcState, ma : MemArg) -> TcResult {
let checked = memarg_check_atomic(st, ma, MemIdx::new(0), 32)
match checked.error {
Some(error) => return Err(error)
None => ()
}
let at = checked.address_type()
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push1(ValType::i32()))
}
///|
fn typecheck_atomic_wait(
st : TcState,
ma : MemArg,
nbits : Int32,
expected : ValType,
) -> TcResult {
let checked = memarg_check_atomic(st, ma, MemIdx::new(0), nbits)
match checked.error {
Some(error) => return Err(error)
None => ()
}
let at = checked.address_type()
let st1 = match st.pop_expect(ValType::i64()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(expected) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match st2.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st3.push1(ValType::i32()))
}
///|
fn typecheck_atomic_rmw(
st : TcState,
ma : MemArg,
nbits : Int32,
ty : ValType,
) -> TcResult {
let checked = memarg_check_atomic(st, ma, MemIdx::new(0), nbits)
match checked.error {
Some(error) => return Err(error)
None => ()
}
let at = checked.address_type()
let st1 = match st.pop_expect(ty) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push1(ty))
}
///|
fn typecheck_atomic_cmpxchg(
st : TcState,
ma : MemArg,
nbits : Int32,
ty : ValType,
) -> TcResult {
let checked = memarg_check_atomic(st, ma, MemIdx::new(0), nbits)
match checked.error {
Some(error) => return Err(error)
None => ()
}
let at = checked.address_type()
let st1 = match st.pop_expect(ty) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ty) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match st2.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st3.push1(ty))
}
///|
fn typecheck_loop(st : TcState, bt : BlockType, body : Expr) -> TcResult {
let (params, results) = match st.env.expand_blocktype(bt) {
Err(e) => return Err(e)
Ok(x) => x
}
// consume params
let st1 = match st.pop_types(params) {
Err(e) => return Err(e)
Ok(s) => s
}
// loop label is params (not results)
let env2 = st1.env.with_label(params)
let st_body0 = TcState::{
env: env2,
stack: tc_stack_from_types(params),
reachable: st1.reachable,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals: tc_fork_initialized_for_body(
st1.initialized_locals,
body.0,
),
}
let st_body = match Typecheck::typecheck(body, st_body0) {
Err(e) => return Err(e)
Ok(s) => s
}
tc_state_finish_loop(st, st1, st_body, results)
}
///|
// Complete a prechecked control region without walking its instructions again.
pub fn tc_state_finish_loop(
st : TcState,
st1 : TcState,
st_body : TcState,
results : Array[ValType],
) -> Result[TcState, String] {
let (reaches_merge, loop_escape) = match
normalize_loop_exit(st_body, results) {
Err(e) => return Err(e)
Ok(x) => x
}
if reaches_merge {
let out = TcState::{
env: st.env,
stack: st1.stack,
reachable: st.reachable,
polymorphic: st.polymorphic,
escape: st.escape,
reachable_escape_depths: st.reachable_escape_depths,
initialized_locals: st1.initialized_locals,
}.merge_child_reachable_escape_depths(st_body)
return Ok(out.push_types(results))
}
Ok(
TcState::{
env: st.env,
stack: st1.stack,
reachable: false,
polymorphic: st.polymorphic,
escape: if st.reachable {
loop_escape
} else {
st.escape
},
reachable_escape_depths: st.reachable_escape_depths,
initialized_locals: st.initialized_locals,
}
.merge_child_reachable_escape_depths(st_body)
.push_types(results),
)
}
///|
fn typecheck_ref_null(st : TcState, rt : RefType) -> TcResult {
if Validate::validate(rt, st.env) is Err(t) {
return Err(t)
}
Ok(st.push1(ValType::ref_null_type(rt)))
}
///|
fn typecheck_ref_is_null(st : TcState) -> TcResult {
let (st1, v) = match st.pop1() {
Err(e) => return Err(e)
Ok(pair) => pair
}
match v {
RefTypeValType(_) | BotValType => Ok(st1.push1(ValType::i32()))
_ => Err("ref.is_null expects a reference type")
}
}
///|
fn inferred_ref_func_heap_type(env : Env, f : FuncIdx) -> HeapType {
match env.get_functypeidx_by_funcidx(f) {
Some(tidx) => return HeapType::new(tidx)
None => ()
}
let ft = match env.get_functype_by_funcidx(f) {
Some(ft) => ft
None => return HeapType::abs(AbsHeapType::func())
}
let mut inferred : TypeIdx? = None
for i in 0..
if FuncType::new(params, results) == ft {
let next = TypeIdx::new(i.reinterpret_as_uint())
match inferred {
None => inferred = Some(next)
Some(_) => return HeapType::abs(AbsHeapType::func())
}
}
_ => ()
}
}
match inferred {
Some(tidx) => HeapType::new(tidx)
None => HeapType::abs(AbsHeapType::func())
}
}
///|
fn typecheck_ref_func(st : TcState, f : FuncIdx) -> TcResult {
if !st.env.has_func(f) {
return Err("invalid function index")
}
let heap_type = inferred_ref_func_heap_type(st.env, f)
let exact = match heap_type {
HeapType(_) | DefTypeHeapType(_) => st.env.infer_exact_references
AbsHeapTypeHeapType(_) | SharedAbsHeapTypeHeapType(_) => false
}
Ok(st.push1(ValType::ref_type(RefType::new(false, heap_type, exact~))))
}
///|
fn typecheck_ref_as_non_null(st : TcState) -> TcResult {
let (st1, v) = match st.pop1() {
Err(e) => return Err(e)
Ok(pair) => pair
}
match v {
BotValType => Ok(st1.push1(ValType::bottom()))
RefTypeValType(rt) =>
match rt {
HeapTypeRefType(true, exact, ht) =>
Ok(st1.push1(ValType::ref_type(RefType::new(false, ht, exact~))))
HeapTypeRefType(false, _, _) => Ok(st1.push1(ValType::ref_type(rt)))
AbsHeapTypeRefType(abs) =>
Ok(
st1.push1(
ValType::ref_type(RefType::new(false, HeapType::abs(abs))),
),
)
}
_ => Err("ref.as_non_null expects a reference type")
}
}
///|
fn typecheck_ref_eq(st : TcState) -> TcResult {
let (st1, right) = match st.pop1() {
Ok(p) => p
Err(e) => return Err(e)
}
let (st2, left) = match st1.pop1() {
Ok(p) => p
Err(e) => return Err(e)
}
let unshared = ValType::eqref_null()
let shared = ValType::ref_type(
RefType::new(true, HeapType::shared_abs(AbsHeapType::eq())),
)
if (
(left is BotValType || Match::matches(left, unshared, st.env)) &&
(right is BotValType || Match::matches(right, unshared, st.env))
) ||
(
(left is BotValType || Match::matches(left, shared, st.env)) &&
(right is BotValType || Match::matches(right, shared, st.env))
) {
Ok(st2.push1(ValType::i32()))
} else {
Err("ref.eq requires equality references with matching shareability")
}
}
///|
fn typecheck_ref_get_desc(st : TcState, type_idx : TypeIdx) -> TcResult {
let (st1, rt_in_opt) = match st.pop_ref_or_bot() {
Err(e) => return Err(e)
Ok(x) => x
}
match rt_in_opt {
None => ()
Some(rt_in) => ignore(rt_in)
}
let result_vt = match st.env.descriptor_result_type(type_idx, rt_in_opt) {
Ok(vt) => vt
Err(e) => return Err(e)
}
Ok(st1.push1(result_vt))
}
///|
fn typecheck_ref_test_or_cast_types_compatible(
target : RefType,
operand : RefType,
env : Env,
) -> Bool {
let target_domain = heaptype_validation_domain(target.get_heap_type(), env)
target_domain >= 0 &&
target_domain == heaptype_validation_domain(operand.get_heap_type(), env)
}
///|
fn typecheck_ref_test(
st : TcState,
nullable : Bool,
exact : Bool,
ht : HeapType,
) -> TcResult {
let rt = RefType::new(nullable, ht, exact~)
if Validate::validate(rt, st.env) is Err(t) {
return Err("invalid ref.test target reftype: " + t)
}
let (st1, rt_in) = match st.pop_ref_or_bot() {
Err(e) => return Err(e)
Ok(pair) => pair
}
match rt_in {
Some(rt_in) =>
if !typecheck_ref_test_or_cast_types_compatible(rt, rt_in, st.env) {
return Err("ref.test target does not match operand type")
}
None => ()
}
Ok(st1.push1(ValType::i32()))
}
///|
fn typecheck_ref_cast(
st : TcState,
nullable : Bool,
exact : Bool,
ht : HeapType,
) -> TcResult {
let rt = RefType::new(nullable, ht, exact~)
if Validate::validate(rt, st.env) is Err(t) {
return Err("invalid ref.cast target reftype: " + t)
}
let (st1, rt_in_opt) = match st.pop_ref_or_bot() {
Err(e) => return Err(e)
Ok(x) => x
}
match rt_in_opt {
None => ()
Some(rt_in) =>
if !typecheck_ref_test_or_cast_types_compatible(rt, rt_in, st.env) {
return Err("ref.cast target does not match operand type")
}
}
Ok(st1.push1(ValType::ref_type(rt)))
}
///|
fn typecheck_ref_test_desc(
st : TcState,
nullable : Bool,
ht : HeapType,
) -> TcResult {
let rt = match st.env.resolve_descriptor_target_ref_type(nullable, ht) {
Ok(t) => t
Err(e) => return Err(e)
}
let (st1, v) = match st.pop1() {
Err(e) => return Err(e)
Ok(pair) => pair
}
let rt_in = match v {
RefTypeValType(r) => r
_ => return Err("ref.test_desc expects a reference operand")
}
if !descriptor_compatible(rt, rt_in, st.env) {
return Err("ref.test_desc target does not match operand type")
}
Ok(st1.push1(ValType::i32()))
}
///|
fn typecheck_ref_cast_desc_eq_descriptor_matches(
descriptor : RefType,
target : RefType,
env : Env,
) -> Bool {
match env.resolve_heaptype_subtype(descriptor.get_heap_type()) {
Some(st) =>
match st.describes_type() {
Some(described_idx) => {
if descriptor.is_nullable() {
return false
}
let described = RefType::new(
target.is_nullable(),
HeapType::new(described_idx),
exact=target.is_exact(),
)
Match::matches(described, target, env)
}
None => false
}
None => false
}
}
///|
fn typecheck_ref_cast_desc_eq_maybe_descriptor_operand(
descriptor : RefType,
env : Env,
) -> Bool {
match env.resolve_heaptype_subtype(descriptor.get_heap_type()) {
Some(st) =>
match st.describes_type() {
Some(_) => true
None => false
}
None => false
}
}
///|
fn typecheck_ref_cast_desc_eq(
st : TcState,
nullable : Bool,
exact : Bool,
ht : HeapType,
) -> TcResult {
let rt = match st.env.resolve_descriptor_target_ref_type(nullable, ht) {
Ok(t) =>
if exact {
RefType::new(t.is_nullable(), t.get_heap_type(), exact=true)
} else {
t
}
Err(e) => return Err(e)
}
match ht {
HeapType(type_idx) =>
match st.env.resolve_struct_descriptor_type(type_idx) {
Ok(Some(_)) => ()
Ok(None) =>
return Err("ref.cast_desc_eq target requires descriptor metadata")
Err(e) => return Err(e)
}
_ => return Err("ref.cast_desc_eq target requires descriptor metadata")
}
let (st1, first_opt) = match st.pop_ref_or_bot() {
Err(e) => return Err(e)
Ok(x) => x
}
match first_opt {
None => Ok(st1.push1(ValType::ref_type(rt)))
Some(first) =>
if typecheck_ref_cast_desc_eq_descriptor_matches(first, rt, st.env) {
let (st2, rt_in_opt) = match st1.pop_ref_or_bot() {
Err(e) => return Err(e)
Ok(x) => x
}
match rt_in_opt {
None => ()
Some(rt_in) =>
if !typecheck_ref_test_or_cast_types_compatible(rt, rt_in, st.env) {
return Err("ref.cast_desc_eq target does not match operand type")
}
}
Ok(st2.push1(ValType::ref_type(rt)))
} else {
if typecheck_ref_cast_desc_eq_maybe_descriptor_operand(first, st.env) {
return Err("ref.cast_desc_eq target does not match operand type")
}
// Compatibility with Starshine's older one-operand descriptor-cast
// surface. Current custom-descriptor binaries put the descriptor
// operand on top of the inspected reference; locally built legacy
// fixtures still model only the inspected reference.
if !typecheck_ref_test_or_cast_types_compatible(rt, first, st.env) {
return Err("ref.cast_desc_eq target does not match operand type")
}
Ok(st1.push1(ValType::ref_type(rt)))
}
}
}
///|
fn typecheck_struct_new(st : TcState, x : TypeIdx) -> TcResult {
match st.env.resolve_struct_descriptor_type(x) {
Ok(Some(_)) =>
return Err(
"struct.new requires struct.new_desc for descriptor-bearing struct types",
)
Ok(None) => ()
Err(e) => return Err(e)
}
let fields = match st.env.resolve_struct_fields(x) {
Err(e) => return Err(e)
Ok(fs) => fs
}
// Compute expected argument types: unpack(storage) for each field, in order.
let args : Array[ValType] = []
for f in fields {
let FieldType(stor, _) = f
args.push(stor.unpack())
}
let st1 = match st.pop_types(args) {
Err(e) => return Err(e)
Ok(s) => s
}
// push (ref x)
Ok(
st1.push1(
ValType::ref_type(
RefType::new(
false,
HeapType::new(x),
exact=st.env.infer_exact_references,
),
),
),
)
}
///|
fn typecheck_struct_new_default(st : TcState, x : TypeIdx) -> TcResult {
match st.env.resolve_struct_descriptor_type(x) {
Ok(Some(_)) =>
return Err(
"struct.new_default requires struct.new_default_desc for descriptor-bearing struct types",
)
Ok(None) => ()
Err(e) => return Err(e)
}
let fields = match st.env.resolve_struct_fields(x) {
Err(e) => return Err(e)
Ok(fs) => fs
}
for f in fields {
let FieldType(stor, _) = f
let vt = stor.unpack()
if !has_default(vt) {
return Err("struct.new_default: no default for a non-null field type")
}
}
Ok(
st.push1(
ValType::ref_type(
RefType::new(
false,
HeapType::new(x),
exact=st.env.infer_exact_references,
),
),
),
)
}
///|
fn typecheck_struct_new_desc(st : TcState, x : TypeIdx) -> TcResult {
let descriptor_type = match st.env.resolve_struct_descriptor_type(x) {
Ok(Some(t)) => t
Ok(None) =>
return Err("struct.new_desc requires a descriptor-bearing struct type")
Err(e) => return Err(e)
}
let fields = match st.env.resolve_struct_fields(x) {
Err(e) => return Err(e)
Ok(fs) => fs
}
let args : Array[ValType] = []
for f in fields {
let FieldType(stor, _) = f
args.push(stor.unpack())
}
args.push(
ValType::ref_type(
RefType::new(true, HeapType::new(descriptor_type), exact=true),
),
)
let st1 = match st.pop_types(args) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(
st1.push1(
ValType::ref_type(RefType::new(false, HeapType::new(x), exact=true)),
),
)
}
///|
fn typecheck_struct_new_default_desc(st : TcState, x : TypeIdx) -> TcResult {
let descriptor_type = match st.env.resolve_struct_descriptor_type(x) {
Ok(Some(t)) => t
Ok(None) =>
return Err(
"struct.new_default_desc requires a descriptor-bearing struct type",
)
Err(e) => return Err(e)
}
let fields = match st.env.resolve_struct_fields(x) {
Err(e) => return Err(e)
Ok(fs) => fs
}
for f in fields {
let FieldType(stor, _) = f
let vt = stor.unpack()
if !has_default(vt) {
return Err(
"struct.new_default_desc: no default for a non-null field type",
)
}
}
let st1 = match
st.pop_expect(
ValType::ref_type(
RefType::new(true, HeapType::new(descriptor_type), exact=true),
),
) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(
st1.push1(
ValType::ref_type(RefType::new(false, HeapType::new(x), exact=true)),
),
)
}
///|
fn typecheck_struct_get(
st : TcState,
x : TypeIdx,
i : U32,
atomic~ : Bool = false,
) -> TcResult {
let fields = match st.env.resolve_struct_fields(x) {
Err(e) => return Err(e)
Ok(fs) => fs
}
let FieldType(stor, _) = match get_struct_field(fields, i) {
Err(e) => return Err(e)
Ok(ft) => ft
}
if stor.is_packed() {
return Err(
"struct.get used on packed field; use struct.get_s or struct.get_u",
)
}
let val_ty = if atomic {
match aggregate_atomic_value_type(stor, st.env, references=true) {
Ok(vt) => vt
Err(error) => return Err(error)
}
} else {
stor.unpack()
}
let st1 = match
st.pop_expect(ValType::ref_type(RefType::new(true, HeapType::new(x)))) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st1.push1(val_ty))
}
///|
fn typecheck_struct_get_s(st : TcState, x : TypeIdx, i : U32) -> TcResult {
let fields = match st.env.resolve_struct_fields(x) {
Err(e) => return Err(e)
Ok(fs) => fs
}
let FieldType(stor, _) = match get_struct_field(fields, i) {
Err(e) => return Err(e)
Ok(ft) => ft
}
if !stor.is_packed() {
return Err("struct.get_s requires packed field")
}
let st1 = match
st.pop_expect(ValType::ref_type(RefType::new(true, HeapType::new(x)))) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st1.push1(ValType::i32()))
}
///|
fn typecheck_struct_get_u(st : TcState, x : TypeIdx, i : U32) -> TcResult {
// identical checks, only semantics differ at runtime
typecheck_struct_get_s(st, x, i)
}
///|
fn typecheck_struct_atomic_get(
st : TcState,
_order : AtomicOrder,
x : TypeIdx,
i : U32,
) -> TcResult {
typecheck_struct_get(st, x, i, atomic=true)
}
///|
fn typecheck_struct_atomic_get_s(
st : TcState,
_order : AtomicOrder,
x : TypeIdx,
i : U32,
) -> TcResult {
typecheck_struct_get_s(st, x, i)
}
///|
fn typecheck_struct_atomic_get_u(
st : TcState,
_order : AtomicOrder,
x : TypeIdx,
i : U32,
) -> TcResult {
typecheck_struct_get_u(st, x, i)
}
///|
fn aggregate_atomic_value_type(
stor : StorageType,
env : Env,
references~ : Bool = false,
equality~ : Bool = false,
) -> Result[ValType, String] {
if stor.is_packed() {
return Err("aggregate atomic RMW/cmpxchg requires non-packed field")
}
let vt = stor.unpack()
match vt {
NumTypeValType(I32NumType) | NumTypeValType(I64NumType) => Ok(vt)
RefTypeValType(rt) if references => {
let domain = heaptype_validation_domain(rt.get_heap_type(), env)
if domain != 0 && domain != 8 {
return Err(
"aggregate atomic reference storage must be an anyref subtype",
)
}
if equality {
let eq = if domain == 8 {
HeapType::shared_abs(AbsHeapType::eq())
} else {
HeapType::abs(AbsHeapType::eq())
}
if !Match::matches(rt.get_heap_type(), eq, env) {
return Err("aggregate atomic cmpxchg requires an eqref subtype")
}
}
Ok(vt)
}
_ =>
Err("aggregate atomic storage requires i32, i64, or an allowed reference")
}
}
///|
fn aggregate_atomic_store_type(
stor : StorageType,
env : Env,
) -> Result[ValType, String] {
if stor.is_packed() {
Ok(ValType::i32())
} else {
aggregate_atomic_value_type(stor, env, references=true)
}
}
///|
fn aggregate_atomic_expected_type(vt : ValType, env : Env) -> ValType {
match vt {
RefTypeValType(rt) =>
ValType::ref_type(
RefType::new(
true,
if heaptype_validation_domain(rt.get_heap_type(), env) == 8 {
HeapType::shared_abs(AbsHeapType::eq())
} else {
HeapType::abs(AbsHeapType::eq())
},
),
)
_ => vt
}
}
///|
fn typecheck_struct_atomic_rmw(
st : TcState,
op : @lib.AggregateAtomicRmwOp,
x : TypeIdx,
i : U32,
) -> TcResult {
let fields = match st.env.resolve_struct_fields(x) {
Err(e) => return Err(e)
Ok(fs) => fs
}
let ft = match get_struct_field(fields, i) {
Err(e) => return Err(e)
Ok(f) => f
}
if !ft.is_mutable() {
return Err("struct.atomic.rmw on immutable field")
}
let FieldType(stor, _) = ft
let val_ty = match
aggregate_atomic_value_type(
stor,
st.env,
references=op == @lib.AggregateAtomicRmwOp::xchg(),
) {
Err(e) => return Err(e)
Ok(vt) => vt
}
let st1 = match st.pop_expect(val_ty) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match
st1.pop_expect(ValType::ref_type(RefType::new(true, HeapType::new(x)))) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push1(val_ty))
}
///|
fn typecheck_struct_atomic_cmpxchg(
st : TcState,
x : TypeIdx,
i : U32,
) -> TcResult {
let fields = match st.env.resolve_struct_fields(x) {
Err(e) => return Err(e)
Ok(fs) => fs
}
let ft = match get_struct_field(fields, i) {
Err(e) => return Err(e)
Ok(f) => f
}
if !ft.is_mutable() {
return Err("struct.atomic.rmw.cmpxchg on immutable field")
}
let FieldType(stor, _) = ft
let val_ty = match
aggregate_atomic_value_type(stor, st.env, references=true, equality=true) {
Err(e) => return Err(e)
Ok(vt) => vt
}
let st1 = match st.pop_expect(val_ty) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match
st1.pop_expect(aggregate_atomic_expected_type(val_ty, st.env)) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match
st2.pop_expect(ValType::ref_type(RefType::new(true, HeapType::new(x)))) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st3.push1(val_ty))
}
///|
fn typecheck_struct_set(
st : TcState,
x : TypeIdx,
i : U32,
atomic~ : Bool = false,
) -> TcResult {
let fields = match st.env.resolve_struct_fields(x) {
Err(e) => return Err(e)
Ok(fs) => fs
}
let ft = match get_struct_field(fields, i) {
Err(e) => return Err(e)
Ok(f) => f
}
if !ft.is_mutable() {
return Err("struct.set on immutable field")
}
let FieldType(stor, _) = ft
let val_ty = if atomic {
match aggregate_atomic_store_type(stor, st.env) {
Ok(vt) => vt
Err(error) => return Err(error)
}
} else {
stor.unpack()
}
// pop value then ref
let st1 = match st.pop_expect(val_ty) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match
st1.pop_expect(ValType::ref_type(RefType::new(true, HeapType::new(x)))) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2)
}
///|
fn typecheck_unary(st : TcState, t_in : ValType, t_out : ValType) -> TcResult {
if st.pop_expect_error(t_in) is Some(error) {
return Err(error)
}
Ok(st.push1(t_out))
}
///|
fn typecheck_binary(st : TcState, t : ValType, out : ValType) -> TcResult {
if st.pop_expect_error(t) is Some(error) {
return Err(error)
}
if st.pop_expect_error(t) is Some(error) {
return Err(error)
}
Ok(st.push1(out))
}
///|
fn typecheck_v128_ternary(st : TcState) -> TcResult {
// v128 v128 v128 -> v128
let st1 = match st.pop_expect(ValType::v128()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::v128()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match st2.pop_expect(ValType::v128()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st3.push1(ValType::v128()))
}
///|
fn typecheck_compare(st : TcState, t : ValType) -> TcResult {
typecheck_binary(st, t, ValType::i32())
}
///|
fn typecheck_v128_unary(st : TcState) -> TcResult {
typecheck_unary(st, ValType::v128(), ValType::v128())
}
///|
fn typecheck_v128_binary(st : TcState) -> TcResult {
typecheck_binary(st, ValType::v128(), ValType::v128())
}
///|
fn typecheck_v128_compare(st : TcState) -> TcResult {
// v128 v128 -> v128 (SIMD comparisons produce v128 masks)
typecheck_v128_binary(st)
}
///|
fn typecheck_v128_shift(st : TcState) -> TcResult {
// v128 i32 -> v128 (shift count is i32)
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::v128()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push1(ValType::v128()))
}
///|
fn typecheck_lane_index(
lane : LaneIdx,
lane_count : Byte,
) -> Result[Unit, String] {
let LaneIdx(n) = lane
if n < lane_count {
Ok(())
} else {
Err("invalid lane index")
}
}
///|
fn typecheck_lane_extract(
st : TcState,
out : ValType,
lane : LaneIdx,
lane_count : Byte,
) -> TcResult {
// v128 -> out (lane index is an immediate, not on stack)
match typecheck_lane_index(lane, lane_count) {
Err(e) => return Err(e)
Ok(_) => ()
}
let st1 = match st.pop_expect(ValType::v128()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st1.push1(out))
}
///|
fn typecheck_lane_replace(
st : TcState,
in_v : ValType,
lane : LaneIdx,
lane_count : Byte,
) -> TcResult {
// v128, in_v -> v128
match typecheck_lane_index(lane, lane_count) {
Err(e) => return Err(e)
Ok(_) => ()
}
let st1 = match st.pop_expect(in_v) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::v128()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push1(ValType::v128()))
}
///|
fn typecheck_splat(st : TcState, in_v : ValType) -> TcResult {
// in_v -> v128
typecheck_unary(st, in_v, ValType::v128())
}
///|
fn typecheck_v128_const(st : TcState) -> TcResult {
Ok(st.push1(ValType::v128()))
}
///|
fn typecheck_v128_mem_load(
st : TcState,
ma : MemArg,
nbits : Int32,
) -> TcResult {
let checked = memarg_check(st, ma, MemIdx::new(0), nbits)
match checked.error {
Some(error) => return Err(error)
None => ()
}
let at = checked.address_type()
let st1 = match st.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st1.push1(ValType::v128()))
}
///|
fn typecheck_v128_mem_store(
st : TcState,
ma : MemArg,
nbits : Int32,
) -> TcResult {
let checked = memarg_check(st, ma, MemIdx::new(0), nbits)
match checked.error {
Some(error) => return Err(error)
None => ()
}
let at = checked.address_type()
// pop value first
let st1 = match st.pop_expect(ValType::v128()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop address
let st2 = match st1.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2)
}
///|
fn typecheck_v128_mem_load_zero(
st : TcState,
ma : MemArg,
nbits : Int32,
) -> TcResult {
typecheck_v128_mem_load(st, ma, nbits)
}
///|
fn typecheck_v128_load_lane(
st : TcState,
ma : MemArg,
nbits : Int32,
lane : LaneIdx,
lane_count : Byte,
) -> TcResult {
match typecheck_lane_index(lane, lane_count) {
Err(e) => return Err(e)
Ok(_) => ()
}
let checked = memarg_check(st, ma, MemIdx::new(0), nbits)
match checked.error {
Some(error) => return Err(error)
None => ()
}
let at = checked.address_type()
// pop existing vector (top of stack)
let st1 = match st.pop_expect(ValType::v128()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop address
let st2 = match st1.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push1(ValType::v128()))
}
///|
fn typecheck_v128_store_lane(
st : TcState,
ma : MemArg,
nbits : Int32,
lane : LaneIdx,
lane_count : Byte,
) -> TcResult {
match typecheck_lane_index(lane, lane_count) {
Err(e) => return Err(e)
Ok(_) => ()
}
let checked = memarg_check(st, ma, MemIdx::new(0), nbits)
match checked.error {
Some(error) => return Err(error)
None => ()
}
let at = checked.address_type()
// pop vector (top of stack)
let st1 = match st.pop_expect(ValType::v128()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop address
let st2 = match st1.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2)
}
///|
fn typecheck_v128_mem_load_splat(
st : TcState,
ma : MemArg,
nbits : Int32,
) -> TcResult {
typecheck_v128_mem_load(st, ma, nbits)
}
///|
fn typecheck_v128_mem_load_extend64(st : TcState, ma : MemArg) -> TcResult {
typecheck_v128_mem_load(st, ma, 64)
}
///|
fn typecheck_memory_size(st : TcState, mem : MemIdx) -> TcResult {
let at = match st.mem_at_of(mem) {
Err(e) => return Err(e)
Ok(t) => t
}
Ok(st.push1(at))
}
///|
fn typecheck_memory_grow(st : TcState, mem : MemIdx) -> TcResult {
let at = match st.mem_at_of(mem) {
Err(e) => return Err(e)
Ok(t) => t
}
let st1 = match st.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st1.push1(at))
}
///|
fn typecheck_memory_init(
st : TcState,
data : DataIdx,
mem : MemIdx,
) -> TcResult {
if !st.env.has_data(data) {
return Err("invalid data segment index")
}
let at = match st.mem_at_of(mem) {
Err(e) => return Err(e)
Ok(t) => t
}
// pop len:i32, src:i32, dst:at
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match st2.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st3)
}
///|
fn typecheck_data_drop(st : TcState, data : DataIdx) -> TcResult {
if !st.env.has_data(data) {
return Err("invalid data segment index")
}
Ok(st)
}
///|
fn typecheck_memory_copy(st : TcState, dst : MemIdx, src : MemIdx) -> TcResult {
let MemType(lim1, _) = match st.env.get_memtype(dst) {
None => return Err("invalid dest memory index")
Some(mt) => mt
}
let MemType(lim2, _) = match st.env.get_memtype(src) {
None => return Err("invalid src memory index")
Some(mt) => mt
}
let at1 = lim1.addr_valtype()
let at2 = lim2.addr_valtype()
let at_len = min_addr_valtype(lim1, lim2)
// pop len, src, dst
let st1 = match st.pop_expect(at_len) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(at2) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match st2.pop_expect(at1) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st3)
}
///|
fn typecheck_memory_fill(st : TcState, mem : MemIdx) -> TcResult {
let at = match st.mem_at_of(mem) {
Err(e) => return Err(e)
Ok(t) => t
}
// pop len:at, val:i32, dst:at
let st1 = match st.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match st2.pop_expect(at) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st3)
}
///|
fn typecheck_ref_conversion(
st : TcState,
expected : ValType,
result_heap : AbsHeapType,
) -> TcResult {
let (st1, actual) = match st.pop1() {
Err(e) => return Err(e)
Ok(pair) => pair
}
match actual {
BotValType => Ok(st1.push1(ValType::bottom()))
RefTypeValType(ref_type) =>
if Match::matches(actual, expected, st.env) {
Ok(
st1.push1(
ValType::ref_type(
RefType::new(ref_type.is_nullable(), HeapType::abs(result_heap)),
),
),
)
} else {
Err("type mismatch")
}
_ => Err("type mismatch")
}
}
///|
fn typecheck_any_convert_extern(st : TcState) -> TcResult {
typecheck_ref_conversion(st, ValType::externref(), AbsHeapType::any())
}
///|
fn typecheck_extern_convert_any(st : TcState) -> TcResult {
typecheck_ref_conversion(st, ValType::anyref(), AbsHeapType::extern_())
}
///|
fn typecheck_ref_i31(st : TcState) -> TcResult {
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st1.push1(ValType::i31ref()))
}
///|
fn typecheck_ref_i31_shared(st : TcState) -> TcResult {
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(
st1.push1(
ValType::ref_type(
RefType::new(false, HeapType::shared_abs(AbsHeapType::i31())),
),
),
)
}
///|
fn typecheck_i31_get(st : TcState) -> TcResult {
let (next, actual) = match st.pop1() {
Ok(p) => p
Err(e) => return Err(e)
}
let shared = ValType::ref_type(
RefType::new(true, HeapType::shared_abs(AbsHeapType::i31())),
)
if actual is BotValType ||
Match::matches(actual, ValType::i31ref_nullable(), st.env) ||
Match::matches(actual, shared, st.env) {
Ok(next.push1(ValType::i32()))
} else {
Err("i31.get requires an i31 reference")
}
}
///|
fn typecheck_array_new(st : TcState, x : TypeIdx) -> TcResult {
let FieldType(sto, _) = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(ft) => ft
}
let elem_vt = sto.unpack()
// pop length : i32
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop init value : elem_vt
let st2 = match st1.pop_expect(elem_vt) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(
st2.push1(
ValType::ref_type(
RefType::new(
false,
HeapType::new(x),
exact=st.env.infer_exact_references,
),
),
),
)
}
///|
fn typecheck_array_new_default(st : TcState, x : TypeIdx) -> TcResult {
let FieldType(sto, _) = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(ft) => ft
}
let elem_vt = sto.unpack()
if !has_default(elem_vt) {
return Err("array.new_default: no default for element type")
}
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(
st1.push1(
ValType::ref_type(
RefType::new(
false,
HeapType::new(x),
exact=st.env.infer_exact_references,
),
),
),
)
}
///|
fn typecheck_array_new_fixed(st : TcState, x : TypeIdx, n : U32) -> TcResult {
let FieldType(sto, _) = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(ft) => ft
}
let elem_vt = sto.unpack()
let U32(nu) = n
let count = nu.reinterpret_as_int()
if count < 0 {
return Err("array.new_fixed: negative length")
}
let mut cur = st
// pop count times
for _ in 0.. return Err(e)
Ok(s) => s
}
}
Ok(
cur.push1(
ValType::ref_type(
RefType::new(
false,
HeapType::new(x),
exact=st.env.infer_exact_references,
),
),
),
)
}
///|
fn typecheck_array_get(
st : TcState,
x : TypeIdx,
atomic~ : Bool = false,
) -> TcResult {
let FieldType(sto, _) = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(ft) => ft
}
if sto.is_packed() {
return Err(
"array.get used on packed element; use array.get_s or array.get_u",
)
}
let elem_ty = if atomic {
match aggregate_atomic_value_type(sto, st.env, references=true) {
Ok(vt) => vt
Err(error) => return Err(error)
}
} else {
sto.unpack()
}
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::ref_array_nullable(x)) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push1(elem_ty))
}
///|
fn typecheck_array_get_s(st : TcState, x : TypeIdx) -> TcResult {
let FieldType(sto, _) = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(ft) => ft
}
if !sto.is_packed() {
return Err("array.get_s requires packed element type")
}
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::ref_array_nullable(x)) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st2.push1(ValType::i32()))
}
///|
fn typecheck_array_get_u(st : TcState, x : TypeIdx) -> TcResult {
// same typing as get_s
typecheck_array_get_s(st, x)
}
///|
fn typecheck_array_set(
st : TcState,
x : TypeIdx,
atomic~ : Bool = false,
) -> TcResult {
let ft = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(f) => f
}
if !ft.is_mutable() {
return Err("array.set on immutable array element type")
}
let FieldType(sto, _) = ft
let elem_vt = if atomic {
match aggregate_atomic_store_type(sto, st.env) {
Ok(vt) => vt
Err(error) => return Err(error)
}
} else {
sto.unpack()
}
// pop value
let st1 = match st.pop_expect(elem_vt) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop index
let st2 = match st1.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop array ref
let st3 = match st2.pop_expect(ValType::ref_array_nullable(x)) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st3)
}
///|
fn typecheck_array_atomic_rmw(
st : TcState,
op : @lib.AggregateAtomicRmwOp,
x : TypeIdx,
) -> TcResult {
let ft = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(f) => f
}
if !ft.is_mutable() {
return Err("array.atomic.rmw on immutable array element type")
}
let FieldType(sto, _) = ft
let elem_vt = match
aggregate_atomic_value_type(
sto,
st.env,
references=op == @lib.AggregateAtomicRmwOp::xchg(),
) {
Err(e) => return Err(e)
Ok(vt) => vt
}
let st1 = match st.pop_expect(elem_vt) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match st2.pop_expect(ValType::ref_array_nullable(x)) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st3.push1(elem_vt))
}
///|
fn typecheck_array_atomic_cmpxchg(st : TcState, x : TypeIdx) -> TcResult {
let ft = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(f) => f
}
if !ft.is_mutable() {
return Err("array.atomic.rmw.cmpxchg on immutable array element type")
}
let FieldType(sto, _) = ft
let elem_vt = match
aggregate_atomic_value_type(sto, st.env, references=true, equality=true) {
Err(e) => return Err(e)
Ok(vt) => vt
}
let st1 = match st.pop_expect(elem_vt) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match
st1.pop_expect(aggregate_atomic_expected_type(elem_vt, st.env)) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match st2.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st4 = match st3.pop_expect(ValType::ref_array_nullable(x)) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st4.push1(elem_vt))
}
///|
fn typecheck_array_len(st : TcState) -> TcResult {
let (next, actual) = match st.pop1() {
Ok(p) => p
Err(e) => return Err(e)
}
let unshared = ValType::ref_type(
RefType::new(true, HeapType::abs(AbsHeapType::array())),
)
let shared = ValType::ref_type(
RefType::new(true, HeapType::shared_abs(AbsHeapType::array())),
)
if Match::matches(actual, unshared, st.env) ||
Match::matches(actual, shared, st.env) {
Ok(next.push1(ValType::i32()))
} else {
Err("array.len requires an array reference")
}
}
///|
fn typecheck_array_fill(st : TcState, x : TypeIdx) -> TcResult {
let ft = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(f) => f
}
if !ft.is_mutable() {
return Err("array.fill on immutable array element type")
}
let FieldType(sto, _) = ft
let elem_vt = sto.unpack()
// pop len
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop value
let st2 = match st1.pop_expect(elem_vt) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop start
let st3 = match st2.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop array ref
let st4 = match st3.pop_expect(ValType::ref_null_array_of(x)) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st4)
}
///|
fn typecheck_array_copy(st : TcState, x : TypeIdx, y : TypeIdx) -> TcResult {
let ft_x = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(f) => f
}
let ft_y = match st.env.resolve_array_field(y) {
Err(e) => return Err(e)
Ok(f) => f
}
if !ft_x.is_mutable() {
return Err("array.copy requires destination array to be mutable")
}
let FieldType(sto_x, _) = ft_x
let FieldType(sto_y, _) = ft_y
let vx = sto_x.unpack()
let vy = sto_y.unpack()
if !Match::matches(vy, vx, st.env) {
return Err(
"array.copy source element type does not match destination element type",
)
}
// pop len
let st = match st.pop_expect(ValType::i32()) {
Err(t) => return Err(t)
Ok(t) => t
}
// pop src index
let st = match st.pop_expect(ValType::i32()) {
Err(t) => return Err(t)
Ok(t) => t
}
// pop src array
let st = match st.pop_expect(ValType::ref_null_array_of(y)) {
Err(t) => return Err(t)
Ok(t) => t
}
// pop dst index
let st = match st.pop_expect(ValType::i32()) {
Err(t) => return Err(t)
Ok(t) => t
}
// pop dst array
let st = match st.pop_expect(ValType::ref_null_array_of(x)) {
Err(t) => return Err(t)
Ok(t) => t
}
Ok(st)
}
///|
fn typecheck_array_init_data(
st : TcState,
x : TypeIdx,
y : DataIdx,
) -> TcResult {
if !st.env.has_data(y) {
return Err("invalid data segment index")
}
let ft = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(f) => f
}
if !ft.is_mutable() {
return Err("array.init_data requires array element to be mutable")
}
let FieldType(sto, _) = ft
let vt = sto.unpack()
match vt {
NumTypeValType(_) => ()
VecTypeValType => ()
_ =>
return Err(
"array.init_data requires unpack(element) to be numtype or vectype",
)
}
// Stack: (ref null x) i32 i32 i32 -> ε
// pop len, src, dst
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match st2.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop array ref
let st4 = match st3.pop_expect(ValType::ref_null_array_of(x)) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st4)
}
///|
fn typecheck_array_init_elem(
st : TcState,
x : TypeIdx,
y : ElemIdx,
) -> TcResult {
let elem = match st.env.get_elem(y) {
None => return Err("invalid elem segment index")
Some(e) => e
}
let ft = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(f) => f
}
if !ft.is_mutable() {
return Err("array.init_elem requires array element to be mutable")
}
let FieldType(sto, _) = ft
// Spec: C.elems[y] ≤ zt (storage type), so storage type must be a ref valtype.
let rt_array = match sto {
ValTypeStorageType(RefTypeValType(rt)) => rt
_ =>
return Err(
"array.init_elem requires array storage type to be a reference type",
)
}
let rt_seg = elem.reftype()
if !Match::matches(rt_seg, rt_array, st.env) {
return Err(
"array.init_elem elem segment type does not match array storage type",
)
}
// Stack: (ref null x) i32 i32 i32 -> ε
// pop len, src, dst
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st3 = match st2.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
// pop array ref
let st4 = match st3.pop_expect(ValType::ref_null_array_of(x)) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(st4)
}
///|
fn typecheck_array_new_elem(st : TcState, x : TypeIdx, y : ElemIdx) -> TcResult {
let elem = match st.env.get_elem(y) {
None => return Err("invalid elem segment index")
Some(e) => e
}
let FieldType(sto, _mut) = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(ft) => ft
}
let rt_array = match sto {
ValTypeStorageType(RefTypeValType(rt)) => rt
_ =>
return Err(
"array.new_elem requires array element storage type to be a reftype",
)
}
let rt_seg = elem.reftype()
if !Match::matches(rt_seg, rt_array, st.env) {
return Err(
"array.new_elem elem segment type does not match array element reftype",
)
}
// Stack: i32 i32 -> (ref x)
// (the two i32 are typically src and len; order doesn't matter for typing as both are i32)
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(
st2.push1(
ValType::ref_type(
RefType::new(
false,
HeapType::new(x),
exact=st.env.infer_exact_references,
),
),
),
)
}
///|
fn typecheck_array_new_data(st : TcState, x : TypeIdx, y : DataIdx) -> TcResult {
if !st.env.has_data(y) {
return Err("invalid data segment index")
}
let FieldType(sto, _mut) = match st.env.resolve_array_field(x) {
Err(e) => return Err(e)
Ok(ft) => ft
}
let vt = sto.unpack()
match vt {
NumTypeValType(_) => ()
VecTypeValType => ()
_ =>
return Err(
"array.new_data requires unpack(element) to be numtype or vectype",
)
}
// Stack: i32 i32 -> (ref x)
let st1 = match st.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
let st2 = match st1.pop_expect(ValType::i32()) {
Err(e) => return Err(e)
Ok(s) => s
}
Ok(
st2.push1(
ValType::ref_type(
RefType::new(
false,
HeapType::new(x),
exact=st.env.infer_exact_references,
),
),
),
)
}
///|
fn typecheck_string_array_ref(
st : TcState,
op_name : String,
expected_storage : StorageType,
require_mutable : Bool,
) -> TcResult {
let want_array_ref = ValType::ref_type(
RefType::new(true, HeapType::abs(AbsHeapType::array())),
)
let (st1, actual) = match st.pop1() {
Ok(v) => v
Err(e) => return Err(e)
}
if actual == ValType::bottom() {
return Ok(st1)
}
if !Match::matches(actual, want_array_ref, st.env) {
return Err(op_name + " requires a nullable array reference operand")
}
let rt = match actual {
RefTypeValType(rt) => rt
_ => return Err(op_name + " requires a nullable array reference operand")
}
let subtype = match st.env.resolve_heaptype_subtype(rt.get_heap_type()) {
Some(subtype) => subtype
None => return Err(op_name + " requires a concrete array type operand")
}
let field_type = match subtype.get_comptype() {
ArrayCompType(field_type) => field_type
_ => return Err(op_name + " requires an array type operand")
}
if require_mutable && !field_type.is_mutable() {
return Err(op_name + " requires a mutable array element type")
}
if field_type.get_storage_type() != expected_storage {
return Err(op_name + " requires a matching array element storage type")
}
Ok(st1)
}
///|
fn typecheck_string_new_array(
st : TcState,
expected_storage : StorageType,
) -> TcResult {
let st1 = match st.pop_expect(ValType::i32()) {
Ok(v) => v
Err(e) => return Err(e)
}
let st2 = match st1.pop_expect(ValType::i32()) {
Ok(v) => v
Err(e) => return Err(e)
}
let st3 = match
typecheck_string_array_ref(st2, "string.new_array", expected_storage, false) {
Ok(v) => v
Err(e) => return Err(e)
}
Ok(st3.push1(typecheck_nonnull_string_type()))
}
///|
fn typecheck_string_encode_array(
st : TcState,
op_name : String,
expected_storage : StorageType,
) -> TcResult {
let st1 = match st.pop_expect(ValType::i32()) {
Ok(v) => v
Err(e) => return Err(e)
}
let st2 = match
typecheck_string_array_ref(st1, op_name, expected_storage, true) {
Ok(v) => v
Err(e) => return Err(e)
}
let st3 = match st2.pop_expect(ValType::stringref()) {
Ok(v) => v
Err(e) => return Err(e)
}
Ok(st3.push1(ValType::i32()))
}
///|
fn typecheck_nonnull_string_type() -> ValType {
ValType::ref_type(RefType::new(false, HeapType::abs(AbsHeapType::string_())))
}
///|
fn typecheck_string_const(st : TcState) -> TcResult {
Ok(st.push1(typecheck_nonnull_string_type()))
}
///|
fn typecheck_string_measure_wtf16(st : TcState) -> TcResult {
match st.pop_expect(ValType::stringref()) {
Ok(next) => Ok(next.push1(ValType::i32()))
Err(err) => Err(err)
}
}
///|
fn typecheck_string_binary(st : TcState, result : ValType) -> TcResult {
let st1 = match st.pop_expect(ValType::stringref()) {
Ok(next) => next
Err(err) => return Err(err)
}
match st1.pop_expect(ValType::stringref()) {
Ok(next) => Ok(next.push1(result))
Err(err) => Err(err)
}
}
///|
fn typecheck_stringview_wtf16_get_codeunit(st : TcState) -> TcResult {
let st1 = match st.pop_expect(ValType::i32()) {
Ok(next) => next
Err(err) => return Err(err)
}
match st1.pop_expect(ValType::stringref()) {
Ok(next) => Ok(next.push1(ValType::i32()))
Err(err) => Err(err)
}
}
///|
fn typecheck_stringview_wtf16_slice(st : TcState) -> TcResult {
let st1 = match st.pop_expect(ValType::i32()) {
Ok(next) => next
Err(err) => return Err(err)
}
let st2 = match st1.pop_expect(ValType::i32()) {
Ok(next) => next
Err(err) => return Err(err)
}
match st2.pop_expect(ValType::stringref()) {
Ok(next) => Ok(next.push1(typecheck_nonnull_string_type()))
Err(err) => Err(err)
}
}
///|
fn typecheck_array_load_result(op : @lib.LoadOp) -> ValType? {
match op {
@lib.I32LoadOp
| @lib.I32Load8SOp
| @lib.I32Load8UOp
| @lib.I32Load16SOp
| @lib.I32Load16UOp => Some(ValType::i32())
@lib.I64LoadOp
| @lib.I64Load8SOp
| @lib.I64Load8UOp
| @lib.I64Load16SOp
| @lib.I64Load16UOp
| @lib.I64Load32SOp
| @lib.I64Load32UOp => Some(ValType::i64())
@lib.F32LoadOp => Some(ValType::f32())
@lib.F64LoadOp => Some(ValType::f64())
@lib.V128LoadOp => Some(ValType::v128())
_ => None
}
}
///|
fn typecheck_array_store_value(op : @lib.StoreOp) -> ValType? {
match op {
@lib.I32StoreOp | @lib.I32Store8Op | @lib.I32Store16Op =>
Some(ValType::i32())
@lib.I64StoreOp
| @lib.I64Store8Op
| @lib.I64Store16Op
| @lib.I64Store32Op => Some(ValType::i64())
@lib.F32StoreOp => Some(ValType::f32())
@lib.F64StoreOp => Some(ValType::f64())
@lib.V128StoreOp => Some(ValType::v128())
_ => None
}
}
///|
fn typecheck_backing_array_field(
st : TcState,
type_idx : TypeIdx,
require_mutable : Bool,
) -> Result[Unit, String] {
let field = match st.env.resolve_array_field(type_idx) {
Ok(field) => field
Err(err) => return Err(err)
}
let FieldType(storage, mutability) = field
match storage {
@lib.PackTypeStorageType(_)
| @lib.ValTypeStorageType(@lib.NumTypeValType(_))
| @lib.ValTypeStorageType(@lib.VecTypeValType) => ()
_ => return Err("array memory operations require numeric or vector storage")
}
if require_mutable && mutability != Mut::var_() {
return Err("backing-array stores require mutable array elements")
}
Ok(())
}
///|
fn typecheck_array_load_op(
st : TcState,
op : @lib.LoadOp,
type_idx : TypeIdx,
ma : MemArg,
) -> TcResult {
if typecheck_array_memory_argument(ma, typecheck_array_load_alignment(op))
is Err(err) {
return Err(err)
}
let result = match typecheck_array_load_result(op) {
Some(result) => result
None => return Err("unsupported backing-array load opcode")
}
match typecheck_backing_array_field(st, type_idx, false) {
Ok(_) => ()
Err(err) => return Err(err)
}
let st1 = match st.pop_expect(ValType::i32()) {
Ok(next) => next
Err(err) => return Err(err)
}
match st1.pop_expect(ValType::ref_array_nullable(type_idx)) {
Ok(next) => Ok(next.push1(result))
Err(err) => Err(err)
}
}
///|
fn typecheck_array_store_op(
st : TcState,
op : @lib.StoreOp,
type_idx : TypeIdx,
ma : MemArg,
) -> TcResult {
if typecheck_array_memory_argument(ma, typecheck_array_store_alignment(op))
is Err(err) {
return Err(err)
}
let value = match typecheck_array_store_value(op) {
Some(value) => value
None => return Err("unsupported backing-array store opcode")
}
match typecheck_backing_array_field(st, type_idx, true) {
Ok(_) => ()
Err(err) => return Err(err)
}
let st1 = match st.pop_expect(value) {
Ok(next) => next
Err(err) => return Err(err)
}
let st2 = match st1.pop_expect(ValType::i32()) {
Ok(next) => next
Err(err) => return Err(err)
}
st2.pop_expect(ValType::ref_array_nullable(type_idx))
}
///|
fn typecheck_cont_ref_type(
type_idx : TypeIdx,
nullable : Bool,
exact : Bool,
) -> ValType {
ValType::ref_type(RefType::new(nullable, HeapType::new(type_idx), exact~))
}
///|
fn typecheck_cont_func_type_idx(
st : TcState,
type_idx : TypeIdx,
) -> Result[TypeIdx, String] {
match st.env.resolve_comptype(type_idx) {
Some(ContCompType(func_type)) => Ok(func_type)
_ => Err("expected a continuation type")
}
}
///|
fn typecheck_cont_new(st : TcState, type_idx : TypeIdx) -> TcResult {
let func_type = match typecheck_cont_func_type_idx(st, type_idx) {
Ok(func_type) => func_type
Err(err) => return Err(err)
}
let expected_func = ValType::ref_type(
RefType::new(true, HeapType::new(func_type)),
)
match st.pop_expect(expected_func) {
Ok(next) => Ok(next.push1(typecheck_cont_ref_type(type_idx, false, true)))
Err(err) => Err(err)
}
}
///|
fn typecheck_cont_bind(
st : TcState,
source_type : TypeIdx,
target_type : TypeIdx,
) -> TcResult {
let FuncType(source_params, source_results) = match
st.env.resolve_cont_functype(source_type) {
Some(sig) => sig
None => return Err("cont.bind source type is not a continuation type")
}
let FuncType(target_params, target_results) = match
st.env.resolve_cont_functype(target_type) {
Some(sig) => sig
None => return Err("cont.bind target type is not a continuation type")
}
if source_params.length() < target_params.length() {
return Err("cont.bind source has fewer parameters than target")
}
let bound_count = source_params.length() - target_params.length()
for idx = 0; idx < target_params.length(); idx = idx + 1 {
if !Match::matches(
target_params[idx],
source_params[bound_count + idx],
st.env,
) {
return Err("cont.bind remaining parameter type mismatch")
}
}
if !Match::matches(source_results, target_results, st.env) {
return Err("cont.bind result type mismatch")
}
let st1 = match
st.pop_expect(typecheck_cont_ref_type(source_type, true, false)) {
Ok(next) => next
Err(err) => return Err(err)
}
let bound_types : Array[ValType] = []
for idx = 0; idx < bound_count; idx = idx + 1 {
bound_types.push(source_params[idx])
}
match st1.pop_types(bound_types) {
Ok(next) =>
Ok(next.push1(typecheck_cont_ref_type(target_type, false, true)))
Err(err) => Err(err)
}
}
///|
fn typecheck_suspend(st : TcState, tag : TagIdx) -> TcResult {
let FuncType(params, results) = match st.env.resolve_tag_functype(tag) {
Some(sig) => sig
None => return Err("suspend tag must exist and use a function type")
}
match st.pop_types(params) {
Ok(next) => Ok(next.push_types(results))
Err(err) => Err(err)
}
}
///|
fn typecheck_resume_handler_cont(
st : TcState,
handler_type : ValType,
tag_results : Array[ValType],
cont_results : Array[ValType],
) -> Result[Unit, String] {
let handler_cont_type = match handler_type {
RefTypeValType(HeapTypeRefType(_, _, HeapType(type_idx))) => type_idx
_ =>
return Err("resume handler label must receive a continuation reference")
}
let FuncType(handler_params, handler_results) = match
st.env.resolve_cont_functype(handler_cont_type) {
Some(sig) => sig
None => return Err("resume handler label continuation type is invalid")
}
if !Match::matches(handler_params, tag_results, st.env) ||
!Match::matches(tag_results, handler_params, st.env) {
return Err("resume handler continuation parameter type mismatch")
}
if !Match::matches(handler_results, cont_results, st.env) ||
!Match::matches(cont_results, handler_results, st.env) {
return Err("resume handler continuation result type mismatch")
}
Ok(())
}
///|
fn typecheck_resume_handlers(
st : TcState,
cont_results : Array[ValType],
handlers : Array[ResumeHandler],
) -> Result[Unit, String] {
for handler in handlers {
match handler {
ResumeOnLabel(tag, label) => {
let FuncType(tag_params, tag_results) = match
st.env.resolve_tag_functype(tag) {
Some(sig) => sig
None => return Err("resume handler tag must exist")
}
let label_types = match st.env.get_label_types(label) {
Some(types) => types
None => return Err("resume handler label must exist")
}
if label_types.length() != tag_params.length() + 1 {
return Err("resume handler label arity mismatch")
}
for idx = 0; idx < tag_params.length(); idx = idx + 1 {
if !Match::matches(tag_params[idx], label_types[idx], st.env) {
return Err("resume handler label tag payload mismatch")
}
}
match
typecheck_resume_handler_cont(
st,
label_types[label_types.length() - 1],
tag_results,
cont_results,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
ResumeOnSwitch(tag) => {
let FuncType(tag_params, tag_results) = match
st.env.resolve_tag_functype(tag) {
Some(sig) => sig
None => return Err("resume switch handler tag must exist")
}
if !tag_params.is_empty() {
return Err("resume switch handler tag must have no parameters")
}
if tag_results != cont_results {
return Err("resume switch handler tag result mismatch")
}
}
}
}
Ok(())
}
///|
fn TcState::record_resume_edges(
state : TcState,
handlers : Array[ResumeHandler],
) -> TcState {
let mut next = state
for handler in handlers {
match handler {
ResumeOnLabel(_, label) =>
next = next.record_reachable_escape_label(state.reachable, label)
ResumeOnSwitch(_) => ()
}
}
next
}
///|
fn typecheck_resume(
st : TcState,
type_idx : TypeIdx,
handlers : Array[ResumeHandler],
) -> TcResult {
let FuncType(params, results) = match st.env.resolve_cont_functype(type_idx) {
Some(sig) => sig
None => return Err("resume type is not a continuation type")
}
match typecheck_resume_handlers(st, results, handlers) {
Ok(_) => ()
Err(err) => return Err(err)
}
let st1 = match
st.pop_expect(typecheck_cont_ref_type(type_idx, true, false)) {
Ok(next) => next
Err(err) => return Err(err)
}
match st1.pop_types(params) {
Ok(next) => Ok(next.push_types(results).record_resume_edges(handlers))
Err(err) => Err(err)
}
}
///|
fn typecheck_resume_throw(
st : TcState,
type_idx : TypeIdx,
tag : TagIdx,
handlers : Array[ResumeHandler],
) -> TcResult {
let FuncType(_, results) = match st.env.resolve_cont_functype(type_idx) {
Some(sig) => sig
None => return Err("resume_throw type is not a continuation type")
}
let FuncType(tag_params, tag_results) = match
st.env.resolve_tag_functype(tag) {
Some(sig) => sig
None => return Err("resume_throw tag must exist")
}
if !tag_results.is_empty() {
return Err("resume_throw tag must have no results")
}
match typecheck_resume_handlers(st, results, handlers) {
Ok(_) => ()
Err(err) => return Err(err)
}
let st1 = match
st.pop_expect(typecheck_cont_ref_type(type_idx, true, false)) {
Ok(next) => next
Err(err) => return Err(err)
}
match st1.pop_types(tag_params) {
Ok(next) => Ok(next.push_types(results).record_resume_edges(handlers))
Err(err) => Err(err)
}
}
///|
fn typecheck_resume_throw_ref(
st : TcState,
type_idx : TypeIdx,
handlers : Array[ResumeHandler],
) -> TcResult {
let FuncType(_, results) = match st.env.resolve_cont_functype(type_idx) {
Some(sig) => sig
None => return Err("resume_throw_ref type is not a continuation type")
}
match typecheck_resume_handlers(st, results, handlers) {
Ok(_) => ()
Err(err) => return Err(err)
}
let st1 = match
st.pop_expect(typecheck_cont_ref_type(type_idx, true, false)) {
Ok(next) => next
Err(err) => return Err(err)
}
match st1.pop_expect(ValType::ref_null_exn()) {
Ok(next) => Ok(next.push_types(results).record_resume_edges(handlers))
Err(err) => Err(err)
}
}
///|
fn typecheck_stack_switch(
st : TcState,
type_idx : TypeIdx,
tag : TagIdx,
) -> TcResult {
let FuncType(source_params, source_results) = match
st.env.resolve_cont_functype(type_idx) {
Some(sig) => sig
None => return Err("switch type is not a continuation type")
}
if source_params.is_empty() {
return Err("switch continuation must have a next-continuation parameter")
}
let next_cont_type = match source_params[source_params.length() - 1] {
RefTypeValType(HeapTypeRefType(_, _, HeapType(type_idx))) => type_idx
_ =>
return Err(
"switch continuation last parameter must be a continuation reference",
)
}
let FuncType(target_params, target_results) = match
st.env.resolve_cont_functype(next_cont_type) {
Some(sig) => sig
None => return Err("switch next-continuation parameter type is invalid")
}
let FuncType(tag_params, tag_results) = match
st.env.resolve_tag_functype(tag) {
Some(sig) => sig
None => return Err("switch tag must exist")
}
if !tag_params.is_empty() {
return Err("switch tag must have no parameters")
}
if !Match::matches(source_results, tag_results, st.env) ||
!Match::matches(tag_results, target_results, st.env) {
return Err("switch tag or continuation result type mismatch")
}
let st1 = match
st.pop_expect(typecheck_cont_ref_type(type_idx, true, false)) {
Ok(next) => next
Err(err) => return Err(err)
}
let operand_types : Array[ValType] = []
for idx = 0; idx < source_params.length() - 1; idx = idx + 1 {
operand_types.push(source_params[idx])
}
match st1.pop_types(operand_types) {
Ok(next) => Ok(next.push_types(target_params))
Err(err) => Err(err)
}
}
///|
pub impl Typecheck for Instruction with fn typecheck(self, st) {
match self {
Unreachable => Ok(st.set_unreachable())
Nop => Ok(st)
I32Const(_) => Ok(st.push1(ValType::i32()))
I64Const(_) => Ok(st.push1(ValType::i64()))
F32Const(_) => Ok(st.push1(ValType::f32()))
F64Const(_) => Ok(st.push1(ValType::f64()))
I32Load(ma) => typecheck_load(st, ma, 32, ValType::i32())
I64Load(ma) => typecheck_load(st, ma, 64, ValType::i64())
F32Load(ma) => typecheck_load(st, ma, 32, ValType::f32())
F64Load(ma) => typecheck_load(st, ma, 64, ValType::f64())
I32Load8S(ma) => typecheck_load(st, ma, 8, ValType::i32())
I32Load8U(ma) => typecheck_load(st, ma, 8, ValType::i32())
I32Load16S(ma) => typecheck_load(st, ma, 16, ValType::i32())
I32Load16U(ma) => typecheck_load(st, ma, 16, ValType::i32())
I64Load8S(ma) => typecheck_load(st, ma, 8, ValType::i64())
I64Load8U(ma) => typecheck_load(st, ma, 8, ValType::i64())
I64Load16S(ma) => typecheck_load(st, ma, 16, ValType::i64())
I64Load16U(ma) => typecheck_load(st, ma, 16, ValType::i64())
I64Load32S(ma) => typecheck_load(st, ma, 32, ValType::i64())
I64Load32U(ma) => typecheck_load(st, ma, 32, ValType::i64())
I32AtomicLoad(_, ma) => typecheck_atomic_load(st, ma, 32, ValType::i32())
I64AtomicLoad(_, ma) => typecheck_atomic_load(st, ma, 64, ValType::i64())
I32AtomicLoad8U(_, ma) => typecheck_atomic_load(st, ma, 8, ValType::i32())
I32AtomicLoad16U(_, ma) => typecheck_atomic_load(st, ma, 16, ValType::i32())
I64AtomicLoad8U(_, ma) => typecheck_atomic_load(st, ma, 8, ValType::i64())
I64AtomicLoad16U(_, ma) => typecheck_atomic_load(st, ma, 16, ValType::i64())
I64AtomicLoad32U(_, ma) => typecheck_atomic_load(st, ma, 32, ValType::i64())
I32Store(ma) => typecheck_store(st, ma, 32, ValType::i32())
I64Store(ma) => typecheck_store(st, ma, 64, ValType::i64())
F32Store(ma) => typecheck_store(st, ma, 32, ValType::f32())
F64Store(ma) => typecheck_store(st, ma, 64, ValType::f64())
I32Store8(ma) => typecheck_store(st, ma, 8, ValType::i32())
I32Store16(ma) => typecheck_store(st, ma, 16, ValType::i32())
I64Store8(ma) => typecheck_store(st, ma, 8, ValType::i64())
I64Store16(ma) => typecheck_store(st, ma, 16, ValType::i64())
I64Store32(ma) => typecheck_store(st, ma, 32, ValType::i64())
I32AtomicStore(_, ma) => typecheck_atomic_store(st, ma, 32, ValType::i32())
I64AtomicStore(_, ma) => typecheck_atomic_store(st, ma, 64, ValType::i64())
I32AtomicStore8(_, ma) => typecheck_atomic_store(st, ma, 8, ValType::i32())
I32AtomicStore16(_, ma) =>
typecheck_atomic_store(st, ma, 16, ValType::i32())
I64AtomicStore8(_, ma) => typecheck_atomic_store(st, ma, 8, ValType::i64())
I64AtomicStore16(_, ma) =>
typecheck_atomic_store(st, ma, 16, ValType::i64())
I64AtomicStore32(_, ma) =>
typecheck_atomic_store(st, ma, 32, ValType::i64())
Drop => typecheck_drop(st)
Select(None) => typecheck_select_untyped(st)
Select(Some(ts)) => typecheck_select_typed(st, ts)
LocalGet(i) => typecheck_local_get(st, i)
LocalSet(i) => typecheck_local_set(st, i)
LocalTee(i) => typecheck_local_tee(st, i)
GlobalGet(i) => typecheck_global_get(st, i)
GlobalSet(i) => typecheck_global_set(st, i)
TableGet(i) => typecheck_table_get(st, i)
TableSet(i) => typecheck_table_set(st, i)
TableSize(i) => typecheck_table_size(st, i)
TableGrow(i) => typecheck_table_grow(st, i)
TableCopy(i0, i1) => typecheck_table_copy(st, i0, i1)
TableInit(x, y) => typecheck_table_init(st, x, y)
TableFill(x) => typecheck_table_fill(st, x)
ElemDrop(y) => typecheck_elem_drop(st, y)
MemorySize(m) => typecheck_memory_size(st, m)
MemoryGrow(m) => typecheck_memory_grow(st, m)
MemoryAtomicNotify(ma) => typecheck_atomic_notify(st, ma)
MemoryAtomicWait32(ma) => typecheck_atomic_wait(st, ma, 32, ValType::i32())
MemoryAtomicWait64(ma) => typecheck_atomic_wait(st, ma, 64, ValType::i64())
AtomicFence(_) => Ok(st)
MemoryInit(d, m) => typecheck_memory_init(st, d, m)
DataDrop(d) => typecheck_data_drop(st, d)
MemoryCopy(d, s) => typecheck_memory_copy(st, d, s)
MemoryFill(m) => typecheck_memory_fill(st, m)
AtomicRmw(_, op, ma) =>
match op {
I32AtomicRmwAddOp => typecheck_atomic_rmw(st, ma, 32, ValType::i32())
I64AtomicRmwAddOp => typecheck_atomic_rmw(st, ma, 64, ValType::i64())
I32AtomicRmw8AddUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i32())
I32AtomicRmw16AddUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i32())
I64AtomicRmw8AddUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i64())
I64AtomicRmw16AddUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i64())
I64AtomicRmw32AddUOp => typecheck_atomic_rmw(st, ma, 32, ValType::i64())
I32AtomicRmwSubOp => typecheck_atomic_rmw(st, ma, 32, ValType::i32())
I64AtomicRmwSubOp => typecheck_atomic_rmw(st, ma, 64, ValType::i64())
I32AtomicRmw8SubUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i32())
I32AtomicRmw16SubUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i32())
I64AtomicRmw8SubUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i64())
I64AtomicRmw16SubUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i64())
I64AtomicRmw32SubUOp => typecheck_atomic_rmw(st, ma, 32, ValType::i64())
I32AtomicRmwAndOp => typecheck_atomic_rmw(st, ma, 32, ValType::i32())
I64AtomicRmwAndOp => typecheck_atomic_rmw(st, ma, 64, ValType::i64())
I32AtomicRmw8AndUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i32())
I32AtomicRmw16AndUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i32())
I64AtomicRmw8AndUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i64())
I64AtomicRmw16AndUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i64())
I64AtomicRmw32AndUOp => typecheck_atomic_rmw(st, ma, 32, ValType::i64())
I32AtomicRmwOrOp => typecheck_atomic_rmw(st, ma, 32, ValType::i32())
I64AtomicRmwOrOp => typecheck_atomic_rmw(st, ma, 64, ValType::i64())
I32AtomicRmw8OrUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i32())
I32AtomicRmw16OrUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i32())
I64AtomicRmw8OrUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i64())
I64AtomicRmw16OrUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i64())
I64AtomicRmw32OrUOp => typecheck_atomic_rmw(st, ma, 32, ValType::i64())
I32AtomicRmwXorOp => typecheck_atomic_rmw(st, ma, 32, ValType::i32())
I64AtomicRmwXorOp => typecheck_atomic_rmw(st, ma, 64, ValType::i64())
I32AtomicRmw8XorUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i32())
I32AtomicRmw16XorUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i32())
I64AtomicRmw8XorUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i64())
I64AtomicRmw16XorUOp => typecheck_atomic_rmw(st, ma, 16, ValType::i64())
I64AtomicRmw32XorUOp => typecheck_atomic_rmw(st, ma, 32, ValType::i64())
I32AtomicRmwXchgOp => typecheck_atomic_rmw(st, ma, 32, ValType::i32())
I64AtomicRmwXchgOp => typecheck_atomic_rmw(st, ma, 64, ValType::i64())
I32AtomicRmw8XchgUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i32())
I32AtomicRmw16XchgUOp =>
typecheck_atomic_rmw(st, ma, 16, ValType::i32())
I64AtomicRmw8XchgUOp => typecheck_atomic_rmw(st, ma, 8, ValType::i64())
I64AtomicRmw16XchgUOp =>
typecheck_atomic_rmw(st, ma, 16, ValType::i64())
I64AtomicRmw32XchgUOp =>
typecheck_atomic_rmw(st, ma, 32, ValType::i64())
}
AtomicCmpxchg(_, op, ma) =>
match op {
I32AtomicRmwCmpxchgOp =>
typecheck_atomic_cmpxchg(st, ma, 32, ValType::i32())
I64AtomicRmwCmpxchgOp =>
typecheck_atomic_cmpxchg(st, ma, 64, ValType::i64())
I32AtomicRmw8CmpxchgUOp =>
typecheck_atomic_cmpxchg(st, ma, 8, ValType::i32())
I32AtomicRmw16CmpxchgUOp =>
typecheck_atomic_cmpxchg(st, ma, 16, ValType::i32())
I64AtomicRmw8CmpxchgUOp =>
typecheck_atomic_cmpxchg(st, ma, 8, ValType::i64())
I64AtomicRmw16CmpxchgUOp =>
typecheck_atomic_cmpxchg(st, ma, 16, ValType::i64())
I64AtomicRmw32CmpxchgUOp =>
typecheck_atomic_cmpxchg(st, ma, 32, ValType::i64())
}
Br(l) => {
// Preserve the reachable target depth even though this path becomes
// unreachable. A sibling path may continue, and an enclosing block must
// still account for this branch when validating its merge result.
let was_reachable = st.reachable
match typecheck_br(st, l) {
Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
Err(err) => Err(err)
}
}
Block(bt, e) => typecheck_block(st, bt, e)
If(bt, th, el) => typecheck_if(st, bt, th, el)
BrIf(l) => {
let was_reachable = st.reachable
match typecheck_br_if(st, l) {
Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
Err(err) => Err(err)
}
}
BrTable(ls, ldef) => {
let was_reachable = st.reachable
match typecheck_br_table(st, ls, ldef) {
Ok(out) => {
let mut out = out.record_reachable_escape_label(was_reachable, ldef)
for l in ls {
out = out.record_reachable_escape_label(was_reachable, l)
}
Ok(out)
}
Err(err) => Err(err)
}
}
BrOnNull(l) => {
let was_reachable = st.reachable
match typecheck_br_on_null(st, l) {
Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
Err(err) => Err(err)
}
}
BrOnNonNull(l) => {
let was_reachable = st.reachable
match typecheck_br_on_non_null(st, l) {
Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
Err(err) => Err(err)
}
}
BrOnCast(l, op, ht1, ht2) => {
let was_reachable = st.reachable
match typecheck_br_on_cast(st, l, op, ht1, ht2) {
Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
Err(err) => Err(err)
}
}
BrOnCastFail(l, op, ht1, ht2) => {
let was_reachable = st.reachable
match typecheck_br_on_cast_fail(st, l, op, ht1, ht2) {
Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
Err(err) => Err(err)
}
}
BrOnCastDescEq(l, source, target) => {
let was_reachable = st.reachable
match typecheck_br_on_cast_desc_eq(st, l, source, target, false) {
Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
Err(err) => Err(err)
}
}
BrOnCastDescEqFail(l, source, target) => {
let was_reachable = st.reachable
match typecheck_br_on_cast_desc_eq(st, l, source, target, true) {
Ok(out) => Ok(out.record_reachable_escape_label(was_reachable, l))
Err(err) => Err(err)
}
}
Return => typecheck_return(st)
Call(f) => typecheck_call(st, f)
CallIndirect(tidx, t) => typecheck_call_indirect(st, tidx, t)
CallRef(tidx) => typecheck_call_ref(st, tidx)
ReturnCall(fidx) => typecheck_return_call(st, fidx)
ReturnCallIndirect(tidx, t) => typecheck_return_call_indirect(st, tidx, t)
ReturnCallRef(tidx) => typecheck_return_call_ref(st, tidx)
WaitqueueNew =>
Ok(
st.push1(
ValType::ref_type(
RefType::new(false, HeapType::shared_abs(AbsHeapType::waitqueue())),
),
),
)
WaitqueueNotify => typecheck_waitqueue_notify(st)
StructWait(type_idx, field) => typecheck_struct_wait(st, type_idx, field)
Publish =>
match st.pop1() {
Ok((next, RefTypeValType(ref_type))) =>
Ok(next.push1(ValType::ref_type(ref_type)))
Ok((next, BotValType)) => Ok(next.push1(ValType::bottom()))
Ok(_) => Err("publish expects a reference")
Err(err) => Err(err)
}
ContNew(type_idx) => typecheck_cont_new(st, type_idx)
ContBind(source_type, target_type) =>
typecheck_cont_bind(st, source_type, target_type)
Suspend(tag) => typecheck_suspend(st, tag)
Resume(type_idx, handlers) => typecheck_resume(st, type_idx, handlers)
ResumeThrow(type_idx, tag, handlers) =>
typecheck_resume_throw(st, type_idx, tag, handlers)
ResumeThrowRef(type_idx, handlers) =>
typecheck_resume_throw_ref(st, type_idx, handlers)
StackSwitch(type_idx, tag) => typecheck_stack_switch(st, type_idx, tag)
Throw(t) => typecheck_throw(st, t)
ThrowRef => typecheck_throw_ref(st)
Rethrow(depth) => typecheck_rethrow(st, depth)
Try(bt, body, catches, delegate) =>
typecheck_legacy_try(st, bt, body, catches, delegate)
TryTable(bt, cs, e) => typecheck_try_table(st, bt, cs, e)
Loop(bt, e) => typecheck_loop(st, bt, e)
RefNull(ht) => typecheck_ref_null(st, ht)
RefIsNull => typecheck_ref_is_null(st)
RefFunc(f) => typecheck_ref_func(st, f)
RefAsNonNull => typecheck_ref_as_non_null(st)
RefEq => typecheck_ref_eq(st)
StringConst(_) => typecheck_string_const(st)
StringMeasureWtf16 => typecheck_string_measure_wtf16(st)
StringConcat => typecheck_string_binary(st, typecheck_nonnull_string_type())
StringEq => typecheck_string_binary(st, ValType::i32())
StringAsWtf16 =>
match st.pop_expect(ValType::stringref()) {
Ok(next) => Ok(next.push1(typecheck_nonnull_string_type()))
Err(err) => Err(err)
}
StringViewWtf16GetCodeunit => typecheck_stringview_wtf16_get_codeunit(st)
StringViewWtf16Slice => typecheck_stringview_wtf16_slice(st)
StringNewUtf8Array =>
typecheck_string_new_array(st, StorageType::pack_type(PackType::i8()))
StringNewWtf16Array =>
typecheck_string_new_array(st, StorageType::pack_type(PackType::i16()))
StringEncodeUtf8Array =>
typecheck_string_encode_array(
st,
"string.encode_utf8_array",
StorageType::pack_type(PackType::i8()),
)
StringEncodeWtf16Array =>
typecheck_string_encode_array(
st,
"string.encode_wtf16_array",
StorageType::pack_type(PackType::i16()),
)
StringNewLossyUtf8Array =>
typecheck_string_new_array(st, StorageType::pack_type(PackType::i8()))
StringNewWtf8Array =>
typecheck_string_new_array(st, StorageType::pack_type(PackType::i8()))
StringEncodeLossyUtf8Array =>
typecheck_string_encode_array(
st,
"string.encode_lossy_utf8_array",
StorageType::pack_type(PackType::i8()),
)
StringEncodeWtf8Array =>
typecheck_string_encode_array(
st,
"string.encode_wtf8_array",
StorageType::pack_type(PackType::i8()),
)
RefGetDesc(type_idx) => typecheck_ref_get_desc(st, type_idx)
RefTest(nullable, exact, ht) => typecheck_ref_test(st, nullable, exact, ht)
RefCast(nullable, exact, ht) => typecheck_ref_cast(st, nullable, exact, ht)
RefTestDesc(nullable, ht) => typecheck_ref_test_desc(st, nullable, ht)
RefCastDescEq(nullable, exact, ht) =>
typecheck_ref_cast_desc_eq(st, nullable, exact, ht)
AnyConvertExtern => typecheck_any_convert_extern(st)
ExternConvertAny => typecheck_extern_convert_any(st)
RefI31 => typecheck_ref_i31(st)
RefI31Shared => typecheck_ref_i31_shared(st)
I31GetS => typecheck_i31_get(st)
I31GetU => typecheck_i31_get(st)
StructNew(x) => typecheck_struct_new(st, x)
StructNewDesc(x) => typecheck_struct_new_desc(st, x)
StructNewDefault(x) => typecheck_struct_new_default(st, x)
StructNewDefaultDesc(x) => typecheck_struct_new_default_desc(st, x)
StructGet(x, i) => typecheck_struct_get(st, x, i)
StructGetS(x, i) => typecheck_struct_get_s(st, x, i)
StructGetU(x, i) => typecheck_struct_get_u(st, x, i)
StructAtomicGet(order, x, i) => typecheck_struct_atomic_get(st, order, x, i)
StructAtomicGetS(order, x, i) =>
typecheck_struct_atomic_get_s(st, order, x, i)
StructAtomicGetU(order, x, i) =>
typecheck_struct_atomic_get_u(st, order, x, i)
StructAtomicRmw(_, op, x, i) => typecheck_struct_atomic_rmw(st, op, x, i)
StructAtomicCmpxchg(_, x, i) => typecheck_struct_atomic_cmpxchg(st, x, i)
StructSet(x, i) => typecheck_struct_set(st, x, i)
StructAtomicSet(_, x, i) => typecheck_struct_set(st, x, i, atomic=true)
ArrayNew(x) => typecheck_array_new(st, x)
ArrayNewDefault(x) => typecheck_array_new_default(st, x)
ArrayNewFixed(x, n) => typecheck_array_new_fixed(st, x, n)
ArrayGet(x) => typecheck_array_get(st, x)
ArrayGetS(x) => typecheck_array_get_s(st, x)
ArrayGetU(x) => typecheck_array_get_u(st, x)
ArrayAtomicGet(_, x) => typecheck_array_get(st, x, atomic=true)
ArrayAtomicGetS(_, x) => typecheck_array_get_s(st, x)
ArrayAtomicGetU(_, x) => typecheck_array_get_u(st, x)
ArraySet(x) => typecheck_array_set(st, x)
ArrayAtomicSet(_, x) => typecheck_array_set(st, x, atomic=true)
ArrayAtomicRmw(_, op, x) => typecheck_array_atomic_rmw(st, op, x)
ArrayAtomicCmpxchg(_, x) => typecheck_array_atomic_cmpxchg(st, x)
ArrayLoad(op, x, ma) => typecheck_array_load_op(st, op, x, ma)
ArrayStore(op, x, ma) => typecheck_array_store_op(st, op, x, ma)
ArrayLen => typecheck_array_len(st)
ArrayFill(x) => typecheck_array_fill(st, x)
ArrayCopy(x, y) => typecheck_array_copy(st, x, y)
ArrayInitData(x, y) => typecheck_array_init_data(st, x, y)
ArrayInitElem(x, y) => typecheck_array_init_elem(st, x, y)
ArrayNewElem(x, y) => typecheck_array_new_elem(st, x, y)
ArrayNewData(x, y) => typecheck_array_new_data(st, x, y)
// ===== i32 relational =====
I32Eqz => typecheck_unary(st, ValType::i32(), ValType::i32())
I32Eq => typecheck_compare(st, ValType::i32())
I32Ne => typecheck_compare(st, ValType::i32())
I32LtS => typecheck_compare(st, ValType::i32())
I32LtU => typecheck_compare(st, ValType::i32())
I32GtS => typecheck_compare(st, ValType::i32())
I32GtU => typecheck_compare(st, ValType::i32())
I32LeS => typecheck_compare(st, ValType::i32())
I32LeU => typecheck_compare(st, ValType::i32())
I32GeS => typecheck_compare(st, ValType::i32())
I32GeU => typecheck_compare(st, ValType::i32())
// ===== i64 relational =====
I64Eqz => typecheck_unary(st, ValType::i64(), ValType::i32())
I64Eq => typecheck_compare(st, ValType::i64())
I64Ne => typecheck_compare(st, ValType::i64())
I64LtS => typecheck_compare(st, ValType::i64())
I64LtU => typecheck_compare(st, ValType::i64())
I64GtS => typecheck_compare(st, ValType::i64())
I64GtU => typecheck_compare(st, ValType::i64())
I64LeS => typecheck_compare(st, ValType::i64())
I64LeU => typecheck_compare(st, ValType::i64())
I64GeS => typecheck_compare(st, ValType::i64())
I64GeU => typecheck_compare(st, ValType::i64())
// ===== f32 relational =====
F32Eq => typecheck_compare(st, ValType::f32())
F32Ne => typecheck_compare(st, ValType::f32())
F32Lt => typecheck_compare(st, ValType::f32())
F32Gt => typecheck_compare(st, ValType::f32())
F32Le => typecheck_compare(st, ValType::f32())
F32Ge => typecheck_compare(st, ValType::f32())
// ===== f64 relational =====
F64Eq => typecheck_compare(st, ValType::f64())
F64Ne => typecheck_compare(st, ValType::f64())
F64Lt => typecheck_compare(st, ValType::f64())
F64Gt => typecheck_compare(st, ValType::f64())
F64Le => typecheck_compare(st, ValType::f64())
F64Ge => typecheck_compare(st, ValType::f64())
// ===== i32 unary =====
I32Clz => typecheck_unary(st, ValType::i32(), ValType::i32())
I32Ctz => typecheck_unary(st, ValType::i32(), ValType::i32())
I32Popcnt => typecheck_unary(st, ValType::i32(), ValType::i32())
// ===== i64 unary =====
I64Clz => typecheck_unary(st, ValType::i64(), ValType::i64())
I64Ctz => typecheck_unary(st, ValType::i64(), ValType::i64())
I64Popcnt => typecheck_unary(st, ValType::i64(), ValType::i64())
// ===== i32 binary =====
I32Add => typecheck_binary(st, ValType::i32(), ValType::i32())
I32Sub => typecheck_binary(st, ValType::i32(), ValType::i32())
I32Mul => typecheck_binary(st, ValType::i32(), ValType::i32())
I32DivS => typecheck_binary(st, ValType::i32(), ValType::i32())
I32DivU => typecheck_binary(st, ValType::i32(), ValType::i32())
I32RemS => typecheck_binary(st, ValType::i32(), ValType::i32())
I32RemU => typecheck_binary(st, ValType::i32(), ValType::i32())
I32And => typecheck_binary(st, ValType::i32(), ValType::i32())
I32Or => typecheck_binary(st, ValType::i32(), ValType::i32())
I32Xor => typecheck_binary(st, ValType::i32(), ValType::i32())
I32Shl => typecheck_binary(st, ValType::i32(), ValType::i32())
I32ShrS => typecheck_binary(st, ValType::i32(), ValType::i32())
I32ShrU => typecheck_binary(st, ValType::i32(), ValType::i32())
I32Rotl => typecheck_binary(st, ValType::i32(), ValType::i32())
I32Rotr => typecheck_binary(st, ValType::i32(), ValType::i32())
// ===== i64 binary =====
I64Add => typecheck_binary(st, ValType::i64(), ValType::i64())
I64Sub => typecheck_binary(st, ValType::i64(), ValType::i64())
I64Mul => typecheck_binary(st, ValType::i64(), ValType::i64())
I64DivS => typecheck_binary(st, ValType::i64(), ValType::i64())
I64DivU => typecheck_binary(st, ValType::i64(), ValType::i64())
I64RemS => typecheck_binary(st, ValType::i64(), ValType::i64())
I64RemU => typecheck_binary(st, ValType::i64(), ValType::i64())
I64And => typecheck_binary(st, ValType::i64(), ValType::i64())
I64Or => typecheck_binary(st, ValType::i64(), ValType::i64())
I64Xor => typecheck_binary(st, ValType::i64(), ValType::i64())
I64Shl => typecheck_binary(st, ValType::i64(), ValType::i64())
I64ShrS => typecheck_binary(st, ValType::i64(), ValType::i64())
I64ShrU => typecheck_binary(st, ValType::i64(), ValType::i64())
I64Rotl => typecheck_binary(st, ValType::i64(), ValType::i64())
I64Rotr => typecheck_binary(st, ValType::i64(), ValType::i64())
// ===== f32 unary =====
F32Abs => typecheck_unary(st, ValType::f32(), ValType::f32())
F32Neg => typecheck_unary(st, ValType::f32(), ValType::f32())
F32Ceil => typecheck_unary(st, ValType::f32(), ValType::f32())
F32Floor => typecheck_unary(st, ValType::f32(), ValType::f32())
F32Trunc => typecheck_unary(st, ValType::f32(), ValType::f32())
F32Nearest => typecheck_unary(st, ValType::f32(), ValType::f32())
F32Sqrt => typecheck_unary(st, ValType::f32(), ValType::f32())
// ===== f32 binary =====
F32Add => typecheck_binary(st, ValType::f32(), ValType::f32())
F32Sub => typecheck_binary(st, ValType::f32(), ValType::f32())
F32Mul => typecheck_binary(st, ValType::f32(), ValType::f32())
F32Div => typecheck_binary(st, ValType::f32(), ValType::f32())
F32Min => typecheck_binary(st, ValType::f32(), ValType::f32())
F32Max => typecheck_binary(st, ValType::f32(), ValType::f32())
F32Copysign => typecheck_binary(st, ValType::f32(), ValType::f32())
// ===== f64 unary =====
F64Abs => typecheck_unary(st, ValType::f64(), ValType::f64())
F64Neg => typecheck_unary(st, ValType::f64(), ValType::f64())
F64Ceil => typecheck_unary(st, ValType::f64(), ValType::f64())
F64Floor => typecheck_unary(st, ValType::f64(), ValType::f64())
F64Trunc => typecheck_unary(st, ValType::f64(), ValType::f64())
F64Nearest => typecheck_unary(st, ValType::f64(), ValType::f64())
F64Sqrt => typecheck_unary(st, ValType::f64(), ValType::f64())
// ===== f64 binary =====
F64Add => typecheck_binary(st, ValType::f64(), ValType::f64())
F64Sub => typecheck_binary(st, ValType::f64(), ValType::f64())
F64Mul => typecheck_binary(st, ValType::f64(), ValType::f64())
F64Div => typecheck_binary(st, ValType::f64(), ValType::f64())
F64Min => typecheck_binary(st, ValType::f64(), ValType::f64())
F64Max => typecheck_binary(st, ValType::f64(), ValType::f64())
F64Copysign => typecheck_binary(st, ValType::f64(), ValType::f64())
// ===== conversions / reinterprets =====
// i32.wrap_i64 : i64 -> i32
I32WrapI64 => typecheck_unary(st, ValType::i64(), ValType::i32())
// i32.trunc_f32/f64_{s,u} : f32/f64 -> i32
I32TruncF32S => typecheck_unary(st, ValType::f32(), ValType::i32())
I32TruncF32U => typecheck_unary(st, ValType::f32(), ValType::i32())
I32TruncF64S => typecheck_unary(st, ValType::f64(), ValType::i32())
I32TruncF64U => typecheck_unary(st, ValType::f64(), ValType::i32())
// i64.extend_i32_{s,u} : i32 -> i64
I64ExtendI32S => typecheck_unary(st, ValType::i32(), ValType::i64())
I64ExtendI32U => typecheck_unary(st, ValType::i32(), ValType::i64())
// i64.trunc_f32/f64_{s,u} : f32/f64 -> i64
I64TruncF32S => typecheck_unary(st, ValType::f32(), ValType::i64())
I64TruncF32U => typecheck_unary(st, ValType::f32(), ValType::i64())
I64TruncF64S => typecheck_unary(st, ValType::f64(), ValType::i64())
I64TruncF64U => typecheck_unary(st, ValType::f64(), ValType::i64())
// f32.convert_i32/i64_{s,u} : i32/i64 -> f32
F32ConvertI32S => typecheck_unary(st, ValType::i32(), ValType::f32())
F32ConvertI32U => typecheck_unary(st, ValType::i32(), ValType::f32())
F32ConvertI64S => typecheck_unary(st, ValType::i64(), ValType::f32())
F32ConvertI64U => typecheck_unary(st, ValType::i64(), ValType::f32())
// f32.demote_f64 : f64 -> f32
F32DemoteF64 => typecheck_unary(st, ValType::f64(), ValType::f32())
// f64.convert_i32/i64_{s,u} : i32/i64 -> f64
F64ConvertI32S => typecheck_unary(st, ValType::i32(), ValType::f64())
F64ConvertI32U => typecheck_unary(st, ValType::i32(), ValType::f64())
F64ConvertI64S => typecheck_unary(st, ValType::i64(), ValType::f64())
F64ConvertI64U => typecheck_unary(st, ValType::i64(), ValType::f64())
// f64.promote_f32 : f32 -> f64
F64PromoteF32 => typecheck_unary(st, ValType::f32(), ValType::f64())
// reinterprets
I32ReinterpretF32 => typecheck_unary(st, ValType::f32(), ValType::i32())
I64ReinterpretF64 => typecheck_unary(st, ValType::f64(), ValType::i64())
F32ReinterpretI32 => typecheck_unary(st, ValType::i32(), ValType::f32())
F64ReinterpretI64 => typecheck_unary(st, ValType::i64(), ValType::f64())
// sign-extension (unary)
I32Extend8S => typecheck_unary(st, ValType::i32(), ValType::i32())
I32Extend16S => typecheck_unary(st, ValType::i32(), ValType::i32())
I64Extend8S => typecheck_unary(st, ValType::i64(), ValType::i64())
I64Extend16S => typecheck_unary(st, ValType::i64(), ValType::i64())
I64Extend32S => typecheck_unary(st, ValType::i64(), ValType::i64())
// saturating trunc (same typing as trunc)
I32TruncSatF32S => typecheck_unary(st, ValType::f32(), ValType::i32())
I32TruncSatF32U => typecheck_unary(st, ValType::f32(), ValType::i32())
I32TruncSatF64S => typecheck_unary(st, ValType::f64(), ValType::i32())
I32TruncSatF64U => typecheck_unary(st, ValType::f64(), ValType::i32())
I64TruncSatF32S => typecheck_unary(st, ValType::f32(), ValType::i64())
I64TruncSatF32U => typecheck_unary(st, ValType::f32(), ValType::i64())
I64TruncSatF64S => typecheck_unary(st, ValType::f64(), ValType::i64())
I64TruncSatF64U => typecheck_unary(st, ValType::f64(), ValType::i64())
V128Not => typecheck_v128_unary(st)
V128And => typecheck_v128_binary(st)
V128Andnot => typecheck_v128_binary(st)
V128Or => typecheck_v128_binary(st)
V128Xor => typecheck_v128_binary(st)
V128Bitselect => typecheck_v128_ternary(st)
V128AnyTrue => typecheck_unary(st, ValType::v128(), ValType::i32())
// Plain v128 load/store
V128Load(ma) => typecheck_v128_mem_load(st, ma, 128)
V128Store(ma) => typecheck_v128_mem_store(st, ma, 128)
// Load and extend (64-bit loads)
V128Load8x8S(ma) => typecheck_v128_mem_load_extend64(st, ma)
V128Load8x8U(ma) => typecheck_v128_mem_load_extend64(st, ma)
V128Load16x4S(ma) => typecheck_v128_mem_load_extend64(st, ma)
V128Load16x4U(ma) => typecheck_v128_mem_load_extend64(st, ma)
V128Load32x2S(ma) => typecheck_v128_mem_load_extend64(st, ma)
V128Load32x2U(ma) => typecheck_v128_mem_load_extend64(st, ma)
// Splat loads
V128Load8Splat(ma) => typecheck_v128_mem_load_splat(st, ma, 8)
V128Load16Splat(ma) => typecheck_v128_mem_load_splat(st, ma, 16)
V128Load32Splat(ma) => typecheck_v128_mem_load_splat(st, ma, 32)
V128Load64Splat(ma) => typecheck_v128_mem_load_splat(st, ma, 64)
// Lane loads
V128Load8Lane(ma, lane) =>
typecheck_v128_load_lane(st, ma, 8, lane, b'\x10')
V128Load16Lane(ma, lane) =>
typecheck_v128_load_lane(st, ma, 16, lane, b'\x08')
V128Load32Lane(ma, lane) =>
typecheck_v128_load_lane(st, ma, 32, lane, b'\x04')
V128Load64Lane(ma, lane) =>
typecheck_v128_load_lane(st, ma, 64, lane, b'\x02')
// Lane stores
V128Store8Lane(ma, lane) =>
typecheck_v128_store_lane(st, ma, 8, lane, b'\x10')
V128Store16Lane(ma, lane) =>
typecheck_v128_store_lane(st, ma, 16, lane, b'\x08')
V128Store32Lane(ma, lane) =>
typecheck_v128_store_lane(st, ma, 32, lane, b'\x04')
V128Store64Lane(ma, lane) =>
typecheck_v128_store_lane(st, ma, 64, lane, b'\x02')
// load*_zero
V128Load32Zero(ma) => typecheck_v128_mem_load_zero(st, ma, 32)
V128Load64Zero(ma) => typecheck_v128_mem_load_zero(st, ma, 64)
V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
typecheck_v128_const(st)
I8x16Shuffle(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
// v128 v128 -> v128
typecheck_v128_binary(st)
I8x16Swizzle => typecheck_v128_binary(st) // v128 v128 -> v128
I8x16Splat => typecheck_splat(st, ValType::i32()) // i32 -> v128 (only low 8 bits used)
I16x8Splat => typecheck_splat(st, ValType::i32()) // i32 -> v128
I32x4Splat => typecheck_splat(st, ValType::i32()) // i32 -> v128
I64x2Splat => typecheck_splat(st, ValType::i64()) // i64 -> v128
F32x4Splat => typecheck_splat(st, ValType::f32()) // f32 -> v128
F64x2Splat => typecheck_splat(st, ValType::f64()) // f64 -> v128
I8x16ExtractLaneS(lane) =>
typecheck_lane_extract(st, ValType::i32(), lane, b'\x10')
I8x16ExtractLaneU(lane) =>
typecheck_lane_extract(st, ValType::i32(), lane, b'\x10')
I16x8ExtractLaneS(lane) =>
typecheck_lane_extract(st, ValType::i32(), lane, b'\x08')
I16x8ExtractLaneU(lane) =>
typecheck_lane_extract(st, ValType::i32(), lane, b'\x08')
I32x4ExtractLane(lane) =>
typecheck_lane_extract(st, ValType::i32(), lane, b'\x04')
I64x2ExtractLane(lane) =>
typecheck_lane_extract(st, ValType::i64(), lane, b'\x02')
F32x4ExtractLane(lane) =>
typecheck_lane_extract(st, ValType::f32(), lane, b'\x04')
F64x2ExtractLane(lane) =>
typecheck_lane_extract(st, ValType::f64(), lane, b'\x02')
I8x16ReplaceLane(lane) =>
typecheck_lane_replace(st, ValType::i32(), lane, b'\x10')
I16x8ReplaceLane(lane) =>
typecheck_lane_replace(st, ValType::i32(), lane, b'\x08')
I32x4ReplaceLane(lane) =>
typecheck_lane_replace(st, ValType::i32(), lane, b'\x04')
I64x2ReplaceLane(lane) =>
typecheck_lane_replace(st, ValType::i64(), lane, b'\x02')
F32x4ReplaceLane(lane) =>
typecheck_lane_replace(st, ValType::f32(), lane, b'\x04')
F64x2ReplaceLane(lane) =>
typecheck_lane_replace(st, ValType::f64(), lane, b'\x02')
I8x16Eq => typecheck_v128_compare(st)
I8x16Ne => typecheck_v128_compare(st)
I8x16LtS => typecheck_v128_compare(st)
I8x16LtU => typecheck_v128_compare(st)
I8x16GtS => typecheck_v128_compare(st)
I8x16GtU => typecheck_v128_compare(st)
I8x16LeS => typecheck_v128_compare(st)
I8x16LeU => typecheck_v128_compare(st)
I8x16GeS => typecheck_v128_compare(st)
I8x16GeU => typecheck_v128_compare(st)
I16x8Eq => typecheck_v128_compare(st)
I16x8Ne => typecheck_v128_compare(st)
I16x8LtS => typecheck_v128_compare(st)
I16x8LtU => typecheck_v128_compare(st)
I16x8GtS => typecheck_v128_compare(st)
I16x8GtU => typecheck_v128_compare(st)
I16x8LeS => typecheck_v128_compare(st)
I16x8LeU => typecheck_v128_compare(st)
I16x8GeS => typecheck_v128_compare(st)
I16x8GeU => typecheck_v128_compare(st)
I32x4Eq => typecheck_v128_compare(st)
I32x4Ne => typecheck_v128_compare(st)
I32x4LtS => typecheck_v128_compare(st)
I32x4LtU => typecheck_v128_compare(st)
I32x4GtS => typecheck_v128_compare(st)
I32x4GtU => typecheck_v128_compare(st)
I32x4LeS => typecheck_v128_compare(st)
I32x4LeU => typecheck_v128_compare(st)
I32x4GeS => typecheck_v128_compare(st)
I32x4GeU => typecheck_v128_compare(st)
F32x4Eq => typecheck_v128_compare(st)
F32x4Ne => typecheck_v128_compare(st)
F32x4Lt => typecheck_v128_compare(st)
F32x4Gt => typecheck_v128_compare(st)
F32x4Le => typecheck_v128_compare(st)
F32x4Ge => typecheck_v128_compare(st)
F64x2Eq => typecheck_v128_compare(st)
F64x2Ne => typecheck_v128_compare(st)
F64x2Lt => typecheck_v128_compare(st)
F64x2Gt => typecheck_v128_compare(st)
F64x2Le => typecheck_v128_compare(st)
F64x2Ge => typecheck_v128_compare(st)
I8x16Abs => typecheck_v128_unary(st)
I8x16Neg => typecheck_v128_unary(st)
I8x16Popcnt => typecheck_v128_unary(st)
I8x16AllTrue => typecheck_unary(st, ValType::v128(), ValType::i32())
I8x16Bitmask => typecheck_unary(st, ValType::v128(), ValType::i32())
I8x16NarrowI16x8S => typecheck_v128_binary(st)
I8x16NarrowI16x8U => typecheck_v128_binary(st)
I8x16Shl => typecheck_v128_shift(st)
I8x16ShrS => typecheck_v128_shift(st)
I8x16ShrU => typecheck_v128_shift(st)
I8x16Add => typecheck_v128_binary(st)
I8x16AddSatS => typecheck_v128_binary(st)
I8x16AddSatU => typecheck_v128_binary(st)
I8x16Sub => typecheck_v128_binary(st)
I8x16SubSatS => typecheck_v128_binary(st)
I8x16SubSatU => typecheck_v128_binary(st)
I8x16MinS => typecheck_v128_binary(st)
I8x16MinU => typecheck_v128_binary(st)
I8x16MaxS => typecheck_v128_binary(st)
I8x16MaxU => typecheck_v128_binary(st)
I8x16AvgrU => typecheck_v128_binary(st)
I16x8Abs => typecheck_v128_unary(st)
I16x8Neg => typecheck_v128_unary(st)
I16x8Q15mulrSatS => typecheck_v128_binary(st)
I16x8AllTrue => typecheck_unary(st, ValType::v128(), ValType::i32())
I16x8Bitmask => typecheck_unary(st, ValType::v128(), ValType::i32())
I16x8NarrowI32x4S => typecheck_v128_binary(st)
I16x8NarrowI32x4U => typecheck_v128_binary(st)
I16x8ExtendLowI8x16S => typecheck_v128_unary(st)
I16x8ExtendHighI8x16S => typecheck_v128_unary(st)
I16x8ExtendLowI8x16U => typecheck_v128_unary(st)
I16x8ExtendHighI8x16U => typecheck_v128_unary(st)
I16x8Shl => typecheck_v128_shift(st)
I16x8ShrS => typecheck_v128_shift(st)
I16x8ShrU => typecheck_v128_shift(st)
I16x8Add => typecheck_v128_binary(st)
I16x8AddSatS => typecheck_v128_binary(st)
I16x8AddSatU => typecheck_v128_binary(st)
I16x8Sub => typecheck_v128_binary(st)
I16x8SubSatS => typecheck_v128_binary(st)
I16x8SubSatU => typecheck_v128_binary(st)
I16x8Mul => typecheck_v128_binary(st)
I16x8MinS => typecheck_v128_binary(st)
I16x8MinU => typecheck_v128_binary(st)
I16x8MaxS => typecheck_v128_binary(st)
I16x8MaxU => typecheck_v128_binary(st)
I16x8AvgrU => typecheck_v128_binary(st)
I32x4Abs => typecheck_v128_unary(st)
I32x4Neg => typecheck_v128_unary(st)
I32x4AllTrue => typecheck_unary(st, ValType::v128(), ValType::i32())
I32x4Bitmask => typecheck_unary(st, ValType::v128(), ValType::i32())
I32x4ExtendLowI16x8S => typecheck_v128_unary(st)
I32x4ExtendHighI16x8S => typecheck_v128_unary(st)
I32x4ExtendLowI16x8U => typecheck_v128_unary(st)
I32x4ExtendHighI16x8U => typecheck_v128_unary(st)
I32x4Shl => typecheck_v128_shift(st)
I32x4ShrS => typecheck_v128_shift(st)
I32x4ShrU => typecheck_v128_shift(st)
I32x4Add => typecheck_v128_binary(st)
I32x4Sub => typecheck_v128_binary(st)
I32x4Mul => typecheck_v128_binary(st)
I32x4MinS => typecheck_v128_binary(st)
I32x4MinU => typecheck_v128_binary(st)
I32x4MaxS => typecheck_v128_binary(st)
I32x4MaxU => typecheck_v128_binary(st)
I32x4DotI16x8S => typecheck_v128_binary(st)
I64x2Abs => typecheck_v128_unary(st)
I64x2Neg => typecheck_v128_unary(st)
I64x2AllTrue => typecheck_unary(st, ValType::v128(), ValType::i32())
I64x2Bitmask => typecheck_unary(st, ValType::v128(), ValType::i32())
I64x2Shl => typecheck_v128_shift(st)
I64x2ShrS => typecheck_v128_shift(st)
I64x2ShrU => typecheck_v128_shift(st)
I64x2Add => typecheck_v128_binary(st)
I64x2Sub => typecheck_v128_binary(st)
I64x2Mul => typecheck_v128_binary(st)
I64x2Eq => typecheck_v128_compare(st)
I64x2Ne => typecheck_v128_compare(st)
I64x2LtS => typecheck_v128_compare(st)
I64x2GtS => typecheck_v128_compare(st)
I64x2LeS => typecheck_v128_compare(st)
I64x2GeS => typecheck_v128_compare(st)
F32x4Abs => typecheck_v128_unary(st)
F32x4Neg => typecheck_v128_unary(st)
F32x4Sqrt => typecheck_v128_unary(st)
F32x4Add => typecheck_v128_binary(st)
F32x4Sub => typecheck_v128_binary(st)
F32x4Mul => typecheck_v128_binary(st)
F32x4Div => typecheck_v128_binary(st)
F32x4Min => typecheck_v128_binary(st)
F32x4Max => typecheck_v128_binary(st)
F32x4Pmin => typecheck_v128_binary(st)
F32x4Pmax => typecheck_v128_binary(st)
F32x4Ceil => typecheck_v128_unary(st)
F32x4Floor => typecheck_v128_unary(st)
F32x4Trunc => typecheck_v128_unary(st)
F32x4Nearest => typecheck_v128_unary(st)
F64x2Abs => typecheck_v128_unary(st)
F64x2Neg => typecheck_v128_unary(st)
F64x2Sqrt => typecheck_v128_unary(st)
F64x2Add => typecheck_v128_binary(st)
F64x2Sub => typecheck_v128_binary(st)
F64x2Mul => typecheck_v128_binary(st)
F64x2Div => typecheck_v128_binary(st)
F64x2Min => typecheck_v128_binary(st)
F64x2Max => typecheck_v128_binary(st)
F64x2Pmin => typecheck_v128_binary(st)
F64x2Pmax => typecheck_v128_binary(st)
F64x2Ceil => typecheck_v128_unary(st)
F64x2Floor => typecheck_v128_unary(st)
F64x2Trunc => typecheck_v128_unary(st)
F64x2Nearest => typecheck_v128_unary(st)
I32x4TruncSatF32x4S => typecheck_v128_unary(st)
I32x4TruncSatF32x4U => typecheck_v128_unary(st)
F32x4ConvertI32x4S => typecheck_v128_unary(st)
F32x4ConvertI32x4U => typecheck_v128_unary(st)
I32x4TruncSatF64x2SZero => typecheck_v128_unary(st)
I32x4TruncSatF64x2UZero => typecheck_v128_unary(st)
F64x2ConvertLowI32x4S => typecheck_v128_unary(st)
F64x2ConvertLowI32x4U => typecheck_v128_unary(st)
F32x4DemoteF64x2Zero => typecheck_v128_unary(st)
F64x2PromoteLowF32x4 => typecheck_v128_unary(st)
I8x16RelaxedSwizzle => typecheck_v128_binary(st)
I32x4RelaxedTruncF32x4S => typecheck_v128_unary(st)
I32x4RelaxedTruncF32x4U => typecheck_v128_unary(st)
I32x4RelaxedTruncZeroF64x2S => typecheck_v128_unary(st)
I32x4RelaxedTruncZeroF64x2U => typecheck_v128_unary(st)
F32x4RelaxedMadd => typecheck_v128_ternary(st)
F32x4RelaxedNmadd => typecheck_v128_ternary(st)
F64x2RelaxedMadd => typecheck_v128_ternary(st)
F64x2RelaxedNmadd => typecheck_v128_ternary(st)
I8x16RelaxedLaneselect => typecheck_v128_ternary(st)
I16x8RelaxedLaneselect => typecheck_v128_ternary(st)
I32x4RelaxedLaneselect => typecheck_v128_ternary(st)
I64x2RelaxedLaneselect => typecheck_v128_ternary(st)
F32x4RelaxedMin => typecheck_v128_binary(st)
F32x4RelaxedMax => typecheck_v128_binary(st)
F64x2RelaxedMin => typecheck_v128_binary(st)
F64x2RelaxedMax => typecheck_v128_binary(st)
I16x8RelaxedQ15mulrS => typecheck_v128_binary(st)
I16x8RelaxedDotI8x16I7x16S => typecheck_v128_binary(st)
I32x4RelaxedDotI8x16I7x16AddS => typecheck_v128_ternary(st)
// ===== extadd pairwise (unary) =====
I16x8ExtaddPairwiseI8x16S => typecheck_v128_unary(st)
I16x8ExtaddPairwiseI8x16U => typecheck_v128_unary(st)
I32x4ExtaddPairwiseI16x8S => typecheck_v128_unary(st)
I32x4ExtaddPairwiseI16x8U => typecheck_v128_unary(st)
// ===== extend (unary) =====
I64x2ExtendLowI32x4S => typecheck_v128_unary(st)
I64x2ExtendLowI32x4U => typecheck_v128_unary(st)
I64x2ExtendHighI32x4S => typecheck_v128_unary(st)
I64x2ExtendHighI32x4U => typecheck_v128_unary(st)
// ===== extmul low/high (binary) =====
I16x8ExtmulLowI8x16S => typecheck_v128_binary(st)
I16x8ExtmulLowI8x16U => typecheck_v128_binary(st)
I16x8ExtmulHighI8x16S => typecheck_v128_binary(st)
I16x8ExtmulHighI8x16U => typecheck_v128_binary(st)
I32x4ExtmulLowI16x8S => typecheck_v128_binary(st)
I32x4ExtmulLowI16x8U => typecheck_v128_binary(st)
I32x4ExtmulHighI16x8S => typecheck_v128_binary(st)
I32x4ExtmulHighI16x8U => typecheck_v128_binary(st)
I64x2ExtmulLowI32x4S => typecheck_v128_binary(st)
I64x2ExtmulLowI32x4U => typecheck_v128_binary(st)
I64x2ExtmulHighI32x4S => typecheck_v128_binary(st)
I64x2ExtmulHighI32x4U => typecheck_v128_binary(st)
// _ => Err("unimplemented")
}
}
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
///|
fn assert_stack(state : TcState, expected : Array[ValType]) -> Unit {
if !equals(state.stack, expected) {
abort("Stack mismatch: expected \{expected}, got \{state.stack}")
}
}
///|
fn[T] assert_ok(result : Result[T, String]) -> T {
match result {
Ok(v) => v
Err(e) => abort("Expected Ok, got Err: \{e}")
}
}
///|
fn[T : Show] assert_err(result : Result[T, String]) -> Unit {
match result {
Ok(v) => abort("Expected Err, got Ok: \{v}")
Err(_) => ()
}
}
///|
fn anyref_type() -> ValType {
ValType::ref_type(RefType::abs(AbsHeapType::any()))
}
///|
fn eqref_type() -> ValType {
ValType::ref_type(RefType::abs(AbsHeapType::eq()))
}
///|
fn i31ref_type() -> ValType {
ValType::ref_type(RefType::new(false, HeapType::abs(AbsHeapType::i31())))
}
///|
fn nullable_funcref() -> ValType {
ValType::ref_type(RefType::new(true, HeapType::abs(AbsHeapType::func())))
}
///|
fn nullable_externref() -> ValType {
ValType::ref_type(RefType::new(true, HeapType::abs(AbsHeapType::extern_())))
}
///|
fn non_nullable_funcref() -> ValType {
ValType::ref_type(RefType::new(false, HeapType::abs(AbsHeapType::func())))
}
///|
fn non_nullable_externref() -> ValType {
ValType::ref_type(RefType::new(false, HeapType::abs(AbsHeapType::extern_())))
}
///|
fn tc_state_all_locals_initialized(
env : Env,
expanded_slot_visits~ : Array[Int32]? = None,
) -> Array[Bool] {
ignore(expanded_slot_visits)
Array::make(env.locals.length(), true)
}
///|
pub fn make_state(env : Env, stack : Array[ValType]) -> TcState {
let initialized_locals = tc_state_all_locals_initialized(env)
{
env,
stack: stack.copy(),
reachable: true,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals,
}
}
///|
pub fn make_state_owned(env : Env, stack : Array[ValType]) -> TcState {
let initialized_locals = tc_state_all_locals_initialized(env)
{
env,
stack,
reachable: true,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals,
}
}
///|
pub fn tc_state_with_stack(state : TcState, stack : Array[ValType]) -> TcState {
{
env: state.env,
stack: stack.copy(),
reachable: state.reachable,
polymorphic: state.polymorphic,
escape: state.escape,
reachable_escape_depths: state.reachable_escape_depths,
initialized_locals: state.initialized_locals,
}
}
///|
pub fn tc_state_clone(state : TcState) -> TcState {
{
env: state.env,
stack: state.stack.copy(),
reachable: state.reachable,
polymorphic: state.polymorphic,
escape: state.escape,
reachable_escape_depths: state.reachable_escape_depths.copy(),
initialized_locals: state.initialized_locals.copy(),
}
}
///|
pub fn tc_state_with_owned_stack(
state : TcState,
stack : Array[ValType],
) -> TcState {
{
env: state.env,
stack,
reachable: state.reachable,
polymorphic: state.polymorphic,
escape: state.escape,
reachable_escape_depths: state.reachable_escape_depths,
initialized_locals: state.initialized_locals,
}
}
///|
// Fork a child scope with an owned stack and no inherited escape observations.
// Matching local storage preserves definite-initialization facts. A different
// local environment keeps tc_state_new's established all-initialized policy.
pub fn tc_state_fork_body(
state : TcState,
env : Env,
stack : Array[ValType],
) -> TcState {
if !physical_equal(state.env.locals, env.locals) {
return TcState::{
..tc_state_new(env, stack, state.reachable, NoTcEscape),
polymorphic: false,
}
}
TcState::{
env,
stack,
reachable: state.reachable,
polymorphic: false,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals: state.initialized_locals,
}
}
///|
pub fn tc_state_new(
env : Env,
stack : Array[ValType],
reachable : Bool,
escape : TcEscape,
) -> TcState {
let initialized_locals = tc_state_all_locals_initialized(env)
{
env,
stack,
reachable,
polymorphic: !reachable,
escape,
reachable_escape_depths: [],
initialized_locals,
}
}
///|
pub fn tc_escape_none() -> TcEscape {
NoTcEscape
}
///|
pub fn tc_escape_terminal() -> TcEscape {
TerminalTcEscape
}
///|
pub fn tc_state_pop_expect(
state : TcState,
expected : ValType,
) -> Result[TcState, String] {
state.pop_expect(expected)
}
///|
pub fn tc_state_pop_types(
state : TcState,
types : Array[ValType],
) -> Result[TcState, String] {
state.pop_types(types)
}
///|
pub fn tc_state_push_types(state : TcState, types : Array[ValType]) -> TcState {
state.push_types(types)
}
///|
pub fn tc_state_validate_end_stack(
state : TcState,
results : Array[ValType],
) -> Result[Unit, String] {
validate_end_stack(state, results)
}
///|
fn typecheck_expr_with_escape_summary_for_test(
expr : Expr,
state : TcState,
) -> Result[(TcState, Array[Int32]), String] {
match Typecheck::typecheck(expr, state) {
Ok(out) => Ok((out, out.reachable_escape_depths))
Err(err) => Err(err)
}
}
///|
pub fn tc_state_normalize_untyped_block_exit(
state : TcState,
results : Array[ValType],
) -> Result[(Bool, TcEscape), String] {
normalize_untyped_block_exit(state, results)
}
///|
pub fn tc_state_normalize_untyped_if_branch_exit(
state : TcState,
results : Array[ValType],
) -> Result[(Bool, TcEscape), String] {
normalize_untyped_if_branch_exit(state, results)
}
///|
test "tc_state_validate_end_stack reports reachable underflow on empty stack" {
let state = tc_state_new(empty_env(), [], true, NoTcEscape)
let result = tc_state_validate_end_stack(state, [ValType::i32()])
@debug.assert_eq(result, Err("stack underflow"))
}
///|
test "tc_state_validate_end_stack accepts unreachable virtual-bottom suffixes" {
let state = TcState::{
env: empty_env(),
stack: [],
reachable: false,
polymorphic: true,
escape: NoTcEscape,
reachable_escape_depths: [],
initialized_locals: [],
}
let result = tc_state_validate_end_stack(state, [ValType::i32()])
@debug.assert_eq(result, Ok(()))
}
///|
test "typecheck expression summary records only reachable branch escapes" {
let env = empty_env().with_label([ValType::i32()])
let state = make_state(env, [])
let expr = Expr::new([
Instruction::i32_const(@lib.I32::new(1)),
Instruction::i32_const(@lib.I32::new(0)),
Instruction::br_if(LabelIdx::new(0)),
Instruction::unreachable_(),
Instruction::i32_const(@lib.I32::new(2)),
Instruction::i32_const(@lib.I32::new(0)),
Instruction::br_if(LabelIdx::new(0)),
])
match typecheck_expr_with_escape_summary_for_test(expr, state) {
Ok((_, depths)) => @debug.assert_eq(depths, [0])
Err(err) =>
fail("expected expression summary typecheck success, got \{err}")
}
let nested_state = make_state(empty_env().with_label([ValType::i32()]), [])
let nested = Expr::new([
Instruction::block(
BlockType::void_(),
Expr::new([
Instruction::i32_const(@lib.I32::new(1)),
Instruction::i32_const(@lib.I32::new(0)),
Instruction::br_if(LabelIdx::new(1)),
Instruction::drop(),
]),
),
])
match typecheck_expr_with_escape_summary_for_test(nested, nested_state) {
Ok((_, depths)) => @debug.assert_eq(depths, [0])
Err(err) =>
fail("expected nested expression summary typecheck success, got \{err}")
}
}
///|
pub fn tc_state_merge_nonfallthrough_if_exits(
left : TcEscape,
right : TcEscape,
) -> TcEscape {
merge_nonfallthrough_if_exits(left, right)
}
///|
pub fn tc_state_typecheck_catch_clause(
state : TcState,
catch_ : Catch,
) -> Result[Unit, String] {
match typecheck_catch_clause(state, catch_) {
Ok(_) => Ok(())
Err(err) => Err(err)
}
}
// =============================================================================
// CONTROL INSTRUCTIONS
// =============================================================================
///|
test "Typecheck Unreachable" {
let state = make_state(empty_env(), [])
let t = Instruction::unreachable_()
let new_state = assert_ok(Typecheck::typecheck(t, state))
@debug.assert_eq(new_state.reachable, false)
}
///|
test "Typecheck Nop" {
let state = make_state(empty_env(), [ValType::i32()])
let t = Instruction::nop()
let new_state = assert_ok(Typecheck::typecheck(t, state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck Block empty" {
let env = empty_env()
let state = make_state(env, [])
let block = Instruction::block(BlockType::void_(), Expr::new([]))
let new_state = assert_ok(Typecheck::typecheck(block, state))
assert_stack(new_state, [])
}
///|
test "Typecheck Block with result" {
let env = empty_env()
let state = make_state(env, [])
let block = Instruction::block(
BlockType::val_type(ValType::i32()),
Expr::new([Instruction::i32_const(I32(42))]),
)
let new_state = assert_ok(Typecheck::typecheck(block, state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck Block preserves outer stack" {
let env = empty_env()
let state = make_state(env, [ValType::i64()])
let block = Instruction::block(
BlockType::val_type(ValType::i32()),
Expr::new([Instruction::i32_const(I32(1))]),
)
let new_state = assert_ok(Typecheck::typecheck(block, state))
assert_stack(new_state, [ValType::i64(), ValType::i32()])
}
///|
test "Typecheck Loop empty" {
let env = empty_env()
let state = make_state(env, [])
let loop_instr = Instruction::loop_(BlockType::void_(), Expr::new([]))
let new_state = assert_ok(Typecheck::typecheck(loop_instr, state))
assert_stack(new_state, [])
}
///|
test "Typecheck If without else" {
let env = empty_env()
let state = make_state(env, [ValType::i32()])
let if_instr = Instruction::if_(
BlockType::void_(),
[Instruction::nop()],
None,
)
let new_state = assert_ok(Typecheck::typecheck(if_instr, state))
assert_stack(new_state, [])
}
///|
test "Typecheck If with else" {
let env = empty_env()
let state = make_state(env, [ValType::i32()])
let if_instr = Instruction::if_(
BlockType::val_type(ValType::i32()),
[Instruction::i32_const(I32(1))],
Some([Instruction::i32_const(I32(2))]),
)
let new_state = assert_ok(Typecheck::typecheck(if_instr, state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck If fails without condition" {
let env = empty_env()
let state = make_state(env, [])
let if_instr = Instruction::if_(
BlockType::void_(),
[Instruction::nop()],
None,
)
assert_err(Typecheck::typecheck(if_instr, state))
}
///|
test "Typecheck loop accepts nested if branches that escape to the loop and function return" {
let env = empty_env().with_return_type(Some([ValType::i32()]))
let state = make_state(env, [])
let loop_instr = Instruction::loop_(
BlockType::val_type(ValType::i32()),
Expr::new([
Instruction::i32_const(I32(1)),
Instruction::if_(
BlockType::void_(),
[
Instruction::i32_const(I32(1)),
Instruction::if_(
BlockType::void_(),
[Instruction::br(LabelIdx::new(2))],
Some([Instruction::i32_const(I32(0)), Instruction::return_()]),
),
],
Some([Instruction::i32_const(I32(1)), Instruction::return_()]),
),
// Closing a child frame does not make this frame polymorphic.
Instruction::unreachable_(),
]),
)
let new_state = assert_ok(Typecheck::typecheck(loop_instr, state))
assert_false(new_state.reachable)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck Br" {
let env = empty_env().with_labels([[]])
let state = make_state(env, [])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::br(LabelIdx::new(0)), state),
)
@debug.assert_eq(new_state.reachable, false)
}
///|
test "Typecheck Br with values" {
let env = empty_env().with_labels([[ValType::i32()]])
let state = make_state(env, [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::br(LabelIdx::new(0)), state),
)
@debug.assert_eq(new_state.reachable, false)
}
///|
test "Typecheck Br invalid label" {
let env = empty_env().with_labels([])
let state = make_state(env, [])
let t = Instruction::br(LabelIdx::new(0))
assert_err(Typecheck::typecheck(t, state))
}
///|
test "Typecheck BrIf" {
let env = empty_env().with_labels([[]])
let state = make_state(env, [ValType::i32()])
let t = Instruction::br_if(LabelIdx::new(0))
let new_state = assert_ok(Typecheck::typecheck(t, state))
assert_stack(new_state, [])
}
///|
test "Typecheck BrIf with values" {
let env = empty_env().with_labels([[ValType::i32()]])
let state = make_state(env, [ValType::i32(), ValType::i32()])
let t = Instruction::br_if(LabelIdx::new(0))
let new_state = assert_ok(Typecheck::typecheck(t, state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck BrTable" {
let env = empty_env().with_labels([[], []])
let state = make_state(env, [ValType::i32()])
let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
let new_state = assert_ok(Typecheck::typecheck(br_table, state))
@debug.assert_eq(new_state.reachable, false)
}
///|
test "Typecheck BrTable with values" {
// Labels expect an i32 value to be passed
let env = empty_env().with_labels([[ValType::i32()], [ValType::i32()]])
// Stack: [i32 (branch value), i32 (selector)]
let state = make_state(env, [ValType::i32(), ValType::i32()])
let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
let new_state = assert_ok(Typecheck::typecheck(br_table, state))
@debug.assert_eq(new_state.reachable, false)
}
///|
test "Typecheck BrTable with multiple values" {
// Labels expect i32, i64 to be passed
let env = empty_env().with_labels([
[ValType::i32(), ValType::i64()],
[ValType::i32(), ValType::i64()],
])
// Stack: [i32, i64 (branch values), i32 (selector)]
let state = make_state(env, [ValType::i32(), ValType::i64(), ValType::i32()])
let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
let new_state = assert_ok(Typecheck::typecheck(br_table, state))
@debug.assert_eq(new_state.reachable, false)
}
///|
test "Typecheck BrTable multiple targets" {
let env = empty_env().with_labels([[], [], [], []])
let state = make_state(env, [ValType::i32()])
// Jump table with 3 entries plus default
let br_table = Instruction::br_table(
[LabelIdx::new(0), LabelIdx::new(1), LabelIdx::new(2)],
LabelIdx::new(3),
)
let new_state = assert_ok(Typecheck::typecheck(br_table, state))
@debug.assert_eq(new_state.reachable, false)
}
///|
test "Typecheck BrTable invalid default label" {
let env = empty_env().with_labels([[]])
let state = make_state(env, [ValType::i32()])
let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(5)) // invalid default
assert_err(Typecheck::typecheck(br_table, state))
}
///|
test "Typecheck BrTable invalid table label" {
let env = empty_env().with_labels([[]])
let state = make_state(env, [ValType::i32()])
let br_table = Instruction::br_table(
[LabelIdx::new(0), LabelIdx::new(99)],
LabelIdx::new(0),
) // invalid in table
assert_err(Typecheck::typecheck(br_table, state))
}
///|
test "Typecheck BrTable mismatched label types" {
// Labels have different arities - this is invalid
let env = empty_env().with_labels([[ValType::i32()], []])
let state = make_state(env, [ValType::i32(), ValType::i32()])
let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
assert_err(Typecheck::typecheck(br_table, state))
}
///|
test "Typecheck BrTable mismatched label value types" {
// Labels have same arity but different types
let env = empty_env().with_labels([[ValType::i32()], [ValType::i64()]])
let state = make_state(env, [ValType::i32(), ValType::i32()])
let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
assert_err(Typecheck::typecheck(br_table, state))
}
///|
test "Typecheck BrTable missing selector" {
let env = empty_env().with_labels([[], []])
let state = make_state(env, []) // empty stack - no selector
let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
assert_err(Typecheck::typecheck(br_table, state))
}
///|
test "Typecheck BrTable missing branch values" {
// Labels expect an i32 but stack only has the selector
let env = empty_env().with_labels([[ValType::i32()], [ValType::i32()]])
let state = make_state(env, [ValType::i32()]) // only selector, no branch value
let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
assert_err(Typecheck::typecheck(br_table, state))
}
///|
test "Typecheck BrTable wrong selector type" {
let env = empty_env().with_labels([[], []])
let state = make_state(env, [ValType::i64()]) // wrong type for selector
let br_table = Instruction::br_table([LabelIdx::new(0)], LabelIdx::new(1))
assert_err(Typecheck::typecheck(br_table, state))
}
///|
test "Typecheck Return" {
let env = empty_env().with_return_type(Some([]))
let state = make_state(env, [])
let t = Instruction::return_()
let new_state = assert_ok(Typecheck::typecheck(t, state))
@debug.assert_eq(new_state.reachable, false)
}
///|
test "Typecheck Return with value" {
let env = empty_env().with_return_type(Some([ValType::i32()]))
let state = make_state(env, [ValType::i32()])
let t = Instruction::return_()
let new_state = assert_ok(Typecheck::typecheck(t, state))
@debug.assert_eq(new_state.reachable, false)
}
// =============================================================================
// PARAMETRIC INSTRUCTIONS
// =============================================================================
///|
test "Typecheck Drop i32" {
let state = make_state(empty_env(), [ValType::i32()])
let t = Instruction::drop()
let new_state = assert_ok(Typecheck::typecheck(t, state))
assert_stack(new_state, [])
}
///|
test "Typecheck Drop i64" {
let state = make_state(empty_env(), [ValType::i64()])
let t = Instruction::drop()
let new_state = assert_ok(Typecheck::typecheck(t, state))
assert_stack(new_state, [])
}
///|
test "Typecheck Drop fails empty stack" {
let state = make_state(empty_env(), [])
let t = Instruction::drop()
assert_err(Typecheck::typecheck(t, state))
}
///|
test "Typecheck Select without annotation" {
let state = make_state(empty_env(), [
ValType::i32(),
ValType::i32(),
ValType::i32(),
])
let t = Instruction::select(types=None)
let new_state = assert_ok(Typecheck::typecheck(t, state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck Select with annotation" {
let state = make_state(empty_env(), [
ValType::i32(),
ValType::i32(),
ValType::i32(),
])
let t = Instruction::select(types=Some([ValType::i32()]))
let new_state = assert_ok(Typecheck::typecheck(t, state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck Select type mismatch fails" {
let state = make_state(empty_env(), [
ValType::i32(),
ValType::i64(),
ValType::i32(),
])
let t = Instruction::select(types=None)
assert_err(Typecheck::typecheck(t, state))
}
// =============================================================================
// VARIABLE INSTRUCTIONS
// =============================================================================
///|
test "Typecheck LocalGet" {
let env : Env = {
..empty_env(),
locals: Locals::from_types([ValType::i32()]),
}
let state = make_state(env, [])
let t = Instruction::local_get(LocalIdx::new(0))
let new_state = assert_ok(Typecheck::typecheck(t, state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck LocalGet invalid index" {
let env : Env = { ..empty_env(), locals: Locals::empty(), }
let state = make_state(env, [])
let t = Instruction::local_get(LocalIdx::new(0))
assert_err(Typecheck::typecheck(t, state))
}
///|
test "Typecheck LocalSet" {
let env : Env = {
..empty_env(),
locals: Locals::from_types([ValType::i32()]),
}
let state = make_state(env, [ValType::i32()])
let t = Instruction::local_set(LocalIdx::new(0))
let new_state = assert_ok(Typecheck::typecheck(t, state))
assert_stack(new_state, [])
}
///|
test "Typecheck LocalSet type mismatch" {
let env = Env::{
..empty_env(),
locals: Locals::from_types([ValType::i32()]),
}
let state = make_state(env, [ValType::i64()])
let t = Instruction::local_set(LocalIdx::new(0))
assert_err(Typecheck::typecheck(t, state))
}
///|
test "Typecheck LocalTee" {
let env : Env = {
..empty_env(),
locals: Locals::from_types([ValType::i32()]),
}
let state = make_state(env, [ValType::i32()])
let t = Instruction::local_tee(LocalIdx::new(0))
let new_state = assert_ok(Typecheck::typecheck(t, state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck GlobalGet" {
let env : Env = {
..empty_env(),
globals: [GlobalType::new(ValType::i32(), false)],
}
let state = make_state(env, [])
let t = Instruction::global_get(GlobalIdx::new(0))
let new_state = assert_ok(Typecheck::typecheck(t, state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck GlobalSet mutable" {
let env : Env = {
..empty_env(),
globals: [GlobalType::new(ValType::i32(), true)],
}
let state = make_state(env, [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::global_set(GlobalIdx::new(0)), state),
)
assert_stack(new_state, [])
}
///|
test "Typecheck GlobalSet immutable fails" {
let env : Env = {
..empty_env(),
globals: [GlobalType::new(ValType::i32(), false)],
}
let state = make_state(env, [ValType::i32()])
assert_err(
Typecheck::typecheck(Instruction::global_set(GlobalIdx::new(0)), state),
)
}
// =============================================================================
// TABLE INSTRUCTIONS
// =============================================================================
///|
test "Typecheck TableGet" {
let table_type = TableType::new(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
Limits::i32(0, None),
)
let env : Env = { ..empty_env(), tables: [table_type], }
let state = make_state(env, [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::table_get(TableIdx::new(0)), state),
)
assert_stack(new_state, [nullable_funcref()])
}
///|
test "Typecheck TableSet" {
let table_type = TableType::new(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
Limits::i32(0, None),
)
let env : Env = { ..empty_env(), tables: [table_type], }
let state = make_state(env, [ValType::i32(), nullable_funcref()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::table_set(TableIdx::new(0)), state),
)
assert_stack(new_state, [])
}
///|
test "Typecheck TableSize" {
let table_type = TableType::new(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
Limits::i32(0, None),
)
let env : Env = { ..empty_env(), tables: [table_type], }
let state = make_state(env, [])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::table_size(TableIdx::new(0)), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck TableGrow" {
let table_type = TableType::new(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
Limits::i32(0, None),
)
let env : Env = { ..empty_env(), tables: [table_type], }
let state = make_state(env, [nullable_funcref(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::table_grow(TableIdx::new(0)), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck TableFill" {
let table_type = TableType::new(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
Limits::i32(0, None),
)
let env : Env = { ..empty_env(), tables: [table_type], }
let state = make_state(env, [
ValType::i32(),
nullable_funcref(),
ValType::i32(),
])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::table_fill(TableIdx::new(0)), state),
)
assert_stack(new_state, [])
}
///|
test "Typecheck TableCopy" {
let table_type = TableType::new(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
Limits::i32(0, None),
)
let env : Env = { ..empty_env(), tables: [table_type, table_type], }
let state = make_state(env, [ValType::i32(), ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::table_copy(TableIdx::new(0), TableIdx::new(1)),
state,
),
)
assert_stack(new_state, [])
}
// =============================================================================
// MEMORY INSTRUCTIONS
// =============================================================================
///|
test "Typecheck I32Load" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_load(mem_arg), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I64Load" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_load(mem_arg), state),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck F32Load" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32_load(mem_arg), state),
)
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F64Load" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f64_load(mem_arg), state),
)
assert_stack(new_state, [ValType::f64()])
}
///|
test "Typecheck I32Load8S" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_load8s(mem_arg), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Load8U" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_load8u(mem_arg), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Load16S" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_load16s(mem_arg), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Load16U" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_load16u(mem_arg), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I64Load8S" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_load8s(mem_arg), state),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck I64Load32S" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_load32s(mem_arg), state),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck I32Store" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32(), ValType::i32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_store(mem_arg), state),
)
assert_stack(new_state, [])
}
///|
test "Typecheck I64Store" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32(), ValType::i64()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_store(mem_arg), state),
)
assert_stack(new_state, [])
}
///|
test "Typecheck F32Store" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32(), ValType::f32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32_store(mem_arg), state),
)
assert_stack(new_state, [])
}
///|
test "Typecheck F64Store" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32(), ValType::f64()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f64_store(mem_arg), state),
)
assert_stack(new_state, [])
}
///|
test "Typecheck I32Store8" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32(), ValType::i32()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_store8(mem_arg), state),
)
assert_stack(new_state, [])
}
///|
test "Typecheck I64Store32" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32(), ValType::i64()])
let mem_arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_store32(mem_arg), state),
)
assert_stack(new_state, [])
}
///|
test "Typecheck MemorySize" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::memory_size(MemIdx::new(0)), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck MemoryGrow" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::memory_grow(MemIdx::new(0)), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck MemoryCopy" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32(), ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::memory_copy(MemIdx::new(0), MemIdx::new(0)),
state,
),
)
assert_stack(new_state, [])
}
///|
test "Typecheck MemoryFill" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32(), ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::memory_fill(MemIdx::new(0)), state),
)
assert_stack(new_state, [])
}
///|
test "Typecheck MemoryFill memory64 length uses address type" {
let env : Env = {
..empty_env(),
mems: [MemType::new(Limits::i64(1UL, None))],
}
let state = make_state(env, [ValType::i64(), ValType::i32(), ValType::i64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::memory_fill(MemIdx::new(0)), state),
)
assert_stack(new_state, [])
}
// =============================================================================
// NUMERIC CONSTANTS
// =============================================================================
///|
test "Typecheck I32Const" {
let state = make_state(empty_env(), [])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_const(I32(42)), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I64Const" {
let state = make_state(empty_env(), [])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_const(I64(42L)), state),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck F32Const" {
let state = make_state(empty_env(), [])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32_const(F32(3.14)), state),
)
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F64Const" {
let state = make_state(empty_env(), [])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f64_const(F64(3.14)), state),
)
assert_stack(new_state, [ValType::f64()])
}
// =============================================================================
// I32 COMPARISON OPERATIONS
// =============================================================================
///|
test "Typecheck I32Eqz" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_eqz(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Eq" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_eq(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Ne" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_ne(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32LtS" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_lt_s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32LtU" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_lt_u(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32GtS" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_gt_s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32GtU" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_gt_u(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32LeS" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_le_s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32LeU" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_le_u(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32GeS" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_ge_s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32GeU" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_ge_u(), state),
)
assert_stack(new_state, [ValType::i32()])
}
// =============================================================================
// I64 COMPARISON OPERATIONS
// =============================================================================
///|
test "Typecheck I64Eqz" {
let state = make_state(empty_env(), [ValType::i64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_eqz(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I64Eq" {
let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_eq(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I64Ne" {
let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_ne(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I64LtS" {
let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_lt_s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I64GtU" {
let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_gt_u(), state),
)
assert_stack(new_state, [ValType::i32()])
}
// =============================================================================
// F32 COMPARISON OPERATIONS
// =============================================================================
///|
test "Typecheck F32Eq" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_eq(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck F32Ne" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_ne(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck F32Lt" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_lt(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck F32Gt" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_gt(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck F32Le" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_le(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck F32Ge" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_ge(), state))
assert_stack(new_state, [ValType::i32()])
}
// =============================================================================
// F64 COMPARISON OPERATIONS
// =============================================================================
///|
test "Typecheck F64Eq" {
let state = make_state(empty_env(), [ValType::f64(), ValType::f64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_eq(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck F64Lt" {
let state = make_state(empty_env(), [ValType::f64(), ValType::f64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_lt(), state))
assert_stack(new_state, [ValType::i32()])
}
// =============================================================================
// I32 ARITHMETIC OPERATIONS
// =============================================================================
///|
test "Typecheck I32Clz" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_clz(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Ctz" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_ctz(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Popcnt" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_popcnt(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Add" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_add(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Sub" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_sub(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Mul" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_mul(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32DivS" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_div_s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32DivU" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_div_u(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32RemS" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_rem_s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32RemU" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_rem_u(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32And" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_and(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Or" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_or(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Xor" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_xor(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Shl" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i32_shl(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32ShrS" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_shr_s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32ShrU" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_shr_u(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Rotl" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_rotl(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Rotr" {
let state = make_state(empty_env(), [ValType::i32(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_rotr(), state),
)
assert_stack(new_state, [ValType::i32()])
}
// =============================================================================
// I64 ARITHMETIC OPERATIONS
// =============================================================================
///|
test "Typecheck I64Clz" {
let state = make_state(empty_env(), [ValType::i64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_clz(), state))
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck I64Add" {
let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_add(), state))
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck I64Sub" {
let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_sub(), state))
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck I64Mul" {
let state = make_state(empty_env(), [ValType::i64(), ValType::i64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::i64_mul(), state))
assert_stack(new_state, [ValType::i64()])
}
// =============================================================================
// F32 ARITHMETIC OPERATIONS
// =============================================================================
///|
test "Typecheck F32Abs" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_abs(), state))
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32Neg" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_neg(), state))
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32Ceil" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32_ceil(), state),
)
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32Floor" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32_floor(), state),
)
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32Sqrt" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32_sqrt(), state),
)
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32Add" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_add(), state))
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32Sub" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_sub(), state))
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32Mul" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_mul(), state))
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32Div" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_div(), state))
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32Min" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_min(), state))
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32Max" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f32_max(), state))
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32Copysign" {
let state = make_state(empty_env(), [ValType::f32(), ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32_copysign(), state),
)
assert_stack(new_state, [ValType::f32()])
}
// =============================================================================
// F64 ARITHMETIC OPERATIONS
// =============================================================================
///|
test "Typecheck F64Abs" {
let state = make_state(empty_env(), [ValType::f64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_abs(), state))
assert_stack(new_state, [ValType::f64()])
}
///|
test "Typecheck F64Neg" {
let state = make_state(empty_env(), [ValType::f64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_neg(), state))
assert_stack(new_state, [ValType::f64()])
}
///|
test "Typecheck F64Add" {
let state = make_state(empty_env(), [ValType::f64(), ValType::f64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_add(), state))
assert_stack(new_state, [ValType::f64()])
}
///|
test "Typecheck F64Sub" {
let state = make_state(empty_env(), [ValType::f64(), ValType::f64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_sub(), state))
assert_stack(new_state, [ValType::f64()])
}
///|
test "Typecheck F64Mul" {
let state = make_state(empty_env(), [ValType::f64(), ValType::f64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_mul(), state))
assert_stack(new_state, [ValType::f64()])
}
///|
test "Typecheck F64Div" {
let state = make_state(empty_env(), [ValType::f64(), ValType::f64()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::f64_div(), state))
assert_stack(new_state, [ValType::f64()])
}
// =============================================================================
// CONVERSION OPERATIONS
// =============================================================================
///|
test "Typecheck I32WrapI64" {
let state = make_state(empty_env(), [ValType::i64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_wrap_i64(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32TruncF32S" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_trunc_f32s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32TruncF32U" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_trunc_f32u(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32TruncF64S" {
let state = make_state(empty_env(), [ValType::f64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_trunc_f64s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32TruncF64U" {
let state = make_state(empty_env(), [ValType::f64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_trunc_f64u(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I64ExtendI32S" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_extend_i32s(), state),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck I64ExtendI32U" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_extend_i32u(), state),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck I64TruncF32S" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_trunc_f32s(), state),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck I64TruncF64S" {
let state = make_state(empty_env(), [ValType::f64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_trunc_f64s(), state),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck F32ConvertI32S" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32_convert_i32s(), state),
)
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32ConvertI32U" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32_convert_i32u(), state),
)
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32ConvertI64S" {
let state = make_state(empty_env(), [ValType::i64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32_convert_i64s(), state),
)
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F32DemoteF64" {
let state = make_state(empty_env(), [ValType::f64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32_demote_f64(), state),
)
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F64ConvertI32S" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f64_convert_i32s(), state),
)
assert_stack(new_state, [ValType::f64()])
}
///|
test "Typecheck F64ConvertI64S" {
let state = make_state(empty_env(), [ValType::i64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f64_convert_i64s(), state),
)
assert_stack(new_state, [ValType::f64()])
}
///|
test "Typecheck F64PromoteF32" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f64_promote_f32(), state),
)
assert_stack(new_state, [ValType::f64()])
}
///|
test "Typecheck I32ReinterpretF32" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_reinterpret_f32(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I64ReinterpretF64" {
let state = make_state(empty_env(), [ValType::f64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_reinterpret_f64(), state),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck F32ReinterpretI32" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32_reinterpret_i32(), state),
)
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F64ReinterpretI64" {
let state = make_state(empty_env(), [ValType::i64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f64_reinterpret_i64(), state),
)
assert_stack(new_state, [ValType::f64()])
}
// =============================================================================
// SIGN EXTENSION OPERATIONS
// =============================================================================
///|
test "Typecheck I32Extend8S" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_extend8s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32Extend16S" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_extend16s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I64Extend8S" {
let state = make_state(empty_env(), [ValType::i64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_extend8s(), state),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck I64Extend16S" {
let state = make_state(empty_env(), [ValType::i64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_extend16s(), state),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck I64Extend32S" {
let state = make_state(empty_env(), [ValType::i64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_extend32s(), state),
)
assert_stack(new_state, [ValType::i64()])
}
// =============================================================================
// SATURATING TRUNCATION
// =============================================================================
///|
test "Typecheck I32TruncSatF32S" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_trunc_sat_f32s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32TruncSatF32U" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_trunc_sat_f32u(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32TruncSatF64S" {
let state = make_state(empty_env(), [ValType::f64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32_trunc_sat_f64s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I64TruncSatF32S" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_trunc_sat_f32s(), state),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck I64TruncSatF64S" {
let state = make_state(empty_env(), [ValType::f64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64_trunc_sat_f64s(), state),
)
assert_stack(new_state, [ValType::i64()])
}
// =============================================================================
// REFERENCE INSTRUCTIONS
// =============================================================================
///|
test "Typecheck RefNull func" {
let state = make_state(empty_env(), [])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::ref_null(HeapType::abs(AbsHeapType::func())),
state,
),
)
assert_stack(new_state, [nullable_funcref()])
}
///|
test "Typecheck RefNull extern" {
let state = make_state(empty_env(), [])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::ref_null(HeapType::abs(AbsHeapType::extern_())),
state,
),
)
assert_stack(new_state, [nullable_externref()])
}
///|
test "Typecheck RefIsNull" {
let state = make_state(empty_env(), [nullable_funcref()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::ref_is_null(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck RefIsNull accepts non-null references" {
let state = make_state(empty_env(), [ValType::i31ref()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::ref_is_null(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck RefEq" {
let eq_ref = ValType::ref_type(
RefType::new(true, HeapType::abs(AbsHeapType::eq())),
)
let state = make_state(empty_env(), [eq_ref, eq_ref])
let new_state = assert_ok(Typecheck::typecheck(Instruction::ref_eq(), state))
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck RefAsNonNull" {
let state = make_state(empty_env(), [nullable_funcref()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::ref_as_non_null(), state),
)
assert_stack(new_state, [non_nullable_funcref()])
}
///|
test "Typecheck RefAsNonNull accepts abstract nullable externref" {
let externref = ValType::ref_type(RefType::abs(AbsHeapType::extern_()))
let state = make_state(empty_env(), [externref])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::ref_as_non_null(), state),
)
assert_stack(new_state, [non_nullable_externref()])
}
///|
test "Typecheck RefAsNonNull accepts unreachable and non-null operands" {
let unreachable_state = assert_ok(
Typecheck::typecheck(
Instruction::unreachable_(),
make_state(empty_env(), []),
),
)
let after_unreachable = assert_ok(
Typecheck::typecheck(Instruction::ref_as_non_null(), unreachable_state),
)
assert_stack(after_unreachable, [ValType::bottom()])
let non_nullable_anyref = ValType::ref_type(
RefType::new(false, HeapType::abs(AbsHeapType::any())),
)
let non_null_state = make_state(empty_env(), [non_nullable_anyref])
let after_non_null = assert_ok(
Typecheck::typecheck(Instruction::ref_as_non_null(), non_null_state),
)
assert_stack(after_non_null, [non_nullable_anyref])
}
// BrOnNonNull tests
///|
test "Typecheck BrOnNonNull basic" {
let env = empty_env().with_labels([[non_nullable_funcref()]])
let state = make_state(env, [nullable_funcref()])
let result = Typecheck::typecheck(
Instruction::br_on_non_null(LabelIdx::new(0)),
state,
)
let new_state = assert_ok(result)
assert_stack(new_state, [])
}
///|
test "Typecheck BrOnNonNull with prefix types" {
let env = empty_env().with_labels([[ValType::i32(), non_nullable_funcref()]])
let state = make_state(env, [ValType::i32(), nullable_funcref()])
let result = Typecheck::typecheck(
Instruction::br_on_non_null(LabelIdx::new(0)),
state,
)
let new_state = assert_ok(result)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck BrOnNonNull with externref" {
let env = empty_env().with_labels([[non_nullable_externref()]])
let state = make_state(env, [nullable_externref()])
let result = Typecheck::typecheck(
Instruction::br_on_non_null(LabelIdx::new(0)),
state,
)
let new_state = assert_ok(result)
assert_stack(new_state, [])
}
///|
test "Typecheck BrOnNonNull accepts statically non-null ref operand" {
let env = empty_env().with_labels([[@lib.ValType::i31ref()]])
let state = make_state(env, [@lib.ValType::i31ref()])
let result = Typecheck::typecheck(
Instruction::br_on_non_null(LabelIdx::new(0)),
state,
)
let new_state = assert_ok(result)
assert_stack(new_state, [])
}
///|
test "Typecheck BrOnNonNull accepts unreachable polymorphic operand" {
let env = empty_env().with_labels([[non_nullable_externref()]])
let unreachable_state = assert_ok(
Typecheck::typecheck(Instruction::unreachable_(), make_state(env, [])),
)
let result = Typecheck::typecheck(
Instruction::br_on_non_null(LabelIdx::new(0)),
unreachable_state,
)
let new_state = assert_ok(result)
@debug.assert_eq(new_state.reachable, false)
assert_stack(new_state, [])
}
///|
test "Typecheck BrOnNonNull rejects empty label payloads" {
let env = empty_env().with_labels([[]])
let state = make_state(env, [nullable_funcref()])
let result = Typecheck::typecheck(
Instruction::br_on_non_null(LabelIdx::new(0)),
state,
)
match result {
Ok(_) => fail("expected empty br_on_non_null label payloads to fail")
Err(msg) => @debug.assert_eq(msg, "br_on_non_null label type too short")
}
}
///|
test "Typecheck BrOnNonNull fails with wrong label type" {
let env = empty_env().with_labels([[ValType::i32()]])
let state = make_state(env, [nullable_funcref()])
let result = Typecheck::typecheck(
Instruction::br_on_non_null(LabelIdx::new(0)),
state,
)
assert_true(result is Err(_))
}
// BrOnNull tests
///|
test "Typecheck BrOnNull basic" {
let env = empty_env().with_labels([[]]) // label takes no values
let state = make_state(env, [nullable_funcref()])
let result = Typecheck::typecheck(
Instruction::br_on_null(LabelIdx::new(0)),
state,
)
let new_state = assert_ok(result)
// Fallthrough pushes non-nullable ref
assert_stack(new_state, [non_nullable_funcref()])
}
///|
test "Typecheck BrOnNull accepts statically non-null ref operand" {
let env = empty_env().with_labels([[]])
let state = make_state(env, [@lib.ValType::i31ref()])
let result = Typecheck::typecheck(
Instruction::br_on_null(LabelIdx::new(0)),
state,
)
let new_state = assert_ok(result)
assert_stack(new_state, [@lib.ValType::i31ref()])
}
///|
test "Typecheck BrOnNull with prefix types" {
let env = empty_env().with_labels([[ValType::i32()]])
let state = make_state(env, [ValType::i32(), nullable_funcref()])
let result = Typecheck::typecheck(
Instruction::br_on_null(LabelIdx::new(0)),
state,
)
let new_state = assert_ok(result)
assert_stack(new_state, [ValType::i32(), non_nullable_funcref()])
}
///|
test "Typecheck BrOnNull with externref" {
let env = empty_env().with_labels([[]])
let state = make_state(env, [nullable_externref()])
let result = Typecheck::typecheck(
Instruction::br_on_null(LabelIdx::new(0)),
state,
)
let new_state = assert_ok(result)
assert_stack(new_state, [non_nullable_externref()])
}
///|
test "Typecheck RefI31" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(Typecheck::typecheck(Instruction::ref_i31(), state))
@debug.assert_eq(new_state.stack.length(), 1)
}
///|
test "Typecheck I31GetS" {
let state = make_state(empty_env(), [i31ref_type()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i31_get_s(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I31GetU" {
let state = make_state(empty_env(), [i31ref_type()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i31_get_u(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck AnyConvertExtern" {
let state = make_state(empty_env(), [nullable_externref()])
let result = Typecheck::typecheck(Instruction::any_convert_extern(), state)
let new_state = assert_ok(result)
assert_stack(new_state, [ValType::anyref()])
}
///|
test "Typecheck AnyConvertExtern preserves non-null operands" {
let state = make_state(empty_env(), [non_nullable_externref()])
let result = Typecheck::typecheck(Instruction::any_convert_extern(), state)
let new_state = assert_ok(result)
assert_stack(new_state, [
ValType::ref_type(RefType::new(false, HeapType::abs(AbsHeapType::any()))),
])
}
///|
test "Typecheck ExternConvertAny" {
let any_ref = ValType::ref_type(
RefType::new(true, HeapType::abs(AbsHeapType::any())),
)
let state = make_state(empty_env(), [any_ref])
let result = Typecheck::typecheck(Instruction::extern_convert_any(), state)
let new_state = assert_ok(result)
assert_stack(new_state, [ValType::externref()])
}
///|
test "Typecheck ExternConvertAny preserves non-null operands" {
let any_ref = ValType::ref_type(
RefType::new(false, HeapType::abs(AbsHeapType::any())),
)
let state = make_state(empty_env(), [any_ref])
let result = Typecheck::typecheck(Instruction::extern_convert_any(), state)
let new_state = assert_ok(result)
assert_stack(new_state, [non_nullable_externref()])
}
// =============================================================================
// VECTOR (SIMD) INSTRUCTIONS
// =============================================================================
///|
test "Typecheck I32Const pushes i32" {
let env = empty_env()
let state = make_state_owned(env, [])
let result = Typecheck::typecheck(Instruction::i32_const(I32(42)), state)
match result {
Ok(new_state) => @debug.assert_eq(new_state.stack, [ValType::i32()])
Err(e) => fail("I32Const should typecheck: \{e}")
}
}
///|
test "Typecheck I32Add consumes two i32s and produces one" {
let env = empty_env()
let state = make_state_owned(env, [ValType::i32(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i32_add(), state)
match result {
Ok(new_state) => @debug.assert_eq(new_state.stack, [ValType::i32()])
Err(e) => fail("I32Add should typecheck: \{e}")
}
}
///|
test "Typecheck I32Add fails with insufficient stack" {
let env = empty_env()
let state = make_state_owned(env, [])
let result = Typecheck::typecheck(Instruction::i32_add(), state)
match result {
Ok(_) => fail("I32Add should fail with empty stack")
Err(_) => ()
}
}
///|
test "Typecheck Drop removes top of stack" {
let env = empty_env()
let state = make_state_owned(env, [ValType::i32()])
let result = Typecheck::typecheck(Instruction::drop(), state)
match result {
Ok(new_state) => @debug.assert_eq(new_state.stack, [])
Err(e) => fail("Drop should typecheck: \{e}")
}
}
///|
test "Typecheck Unreachable makes state unreachable" {
let env = empty_env()
let state = make_state_owned(env, [])
let result = Typecheck::typecheck(Instruction::unreachable_(), state)
match result {
Ok(new_state) => @debug.assert_eq(new_state.reachable, false)
Err(e) => fail("Unreachable should typecheck: \{e}")
}
}
///|
test "Typecheck empty Expr" {
let env = empty_env()
let state = make_state_owned(env, [])
let expr = Expr::new([])
let result = Typecheck::typecheck(expr, state)
match result {
Ok(new_state) => @debug.assert_eq(new_state.stack, [])
Err(e) => fail("Empty Expr should typecheck: \{e}")
}
}
///|
test "Typecheck Expr sequence" {
let env = empty_env()
let state = make_state_owned(env, [])
let expr = Expr::new([
Instruction::i32_const(I32(1)),
Instruction::i32_const(I32(2)),
Instruction::i32_add(),
])
let result = Typecheck::typecheck(expr, state)
match result {
Ok(new_state) => @debug.assert_eq(new_state.stack, [ValType::i32()])
Err(e) => fail("Expr sequence should typecheck: \{e}")
}
}
///|
test "Typecheck I64Const pushes i64" {
let env = empty_env()
let state = make_state_owned(env, [])
let result = Typecheck::typecheck(Instruction::i64_const(I64(100L)), state)
match result {
Ok(new_state) => @debug.assert_eq(new_state.stack, [ValType::i64()])
Err(e) => fail("I64Const should typecheck: \{e}")
}
}
///|
test "Typecheck type mismatch fails" {
let env = empty_env()
let state = make_state_owned(env, [ValType::i64(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i32_add(), state)
match result {
Ok(_) => fail("I32Add should fail with mismatched types")
Err(_) => ()
}
}
///|
test "Expr typecheck errors include instruction and stack context" {
let expression = Expr::new([
Instruction::i64_const(I64(1L)),
Instruction::i32_const(I32(2)),
Instruction::i32_add(),
])
match Typecheck::typecheck(expression, make_state_owned(empty_env(), [])) {
Ok(_) => fail("expected mixed scalar add to fail")
Err(error) => {
assert_true(error.contains("instruction[2]"))
assert_true(error.contains("stack="))
assert_true(error.contains("type mismatch"))
}
}
}
///|
test "Typecheck V128Const" {
let state = make_state(empty_env(), [])
let instr = Instruction::v128_const(
b'\x00', b'\x01', b'\x02', b'\x03', b'\x04', b'\x05', b'\x06', b'\x07', b'\x08',
b'\x09', b'\x0a', b'\x0b', b'\x0c', b'\x0d', b'\x0e', b'\x0f',
)
let new_state = assert_ok(Typecheck::typecheck(instr, state))
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck V128Load" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32()])
let arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::v128_load(arg), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck V128Store" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32(), ValType::v128()])
let arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(Instruction::v128_store(arg), state),
)
assert_stack(new_state, [])
}
///|
test "Typecheck I8x16Splat" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i8x16_splat(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I16x8Splat" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i16x8_splat(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I32x4Splat" {
let state = make_state(empty_env(), [ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32x4_splat(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I64x2Splat" {
let state = make_state(empty_env(), [ValType::i64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i64x2_splat(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck F32x4Splat" {
let state = make_state(empty_env(), [ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32x4_splat(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck F64x2Splat" {
let state = make_state(empty_env(), [ValType::f64()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f64x2_splat(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I8x16ExtractLaneS" {
let state = make_state(empty_env(), [ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::i8x16_extract_lane_s(LaneIdx::new(0)),
state,
),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I16x8ExtractLaneU" {
let state = make_state(empty_env(), [ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::i16x8_extract_lane_u(LaneIdx::new(3)),
state,
),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I32x4ExtractLane" {
let state = make_state(empty_env(), [ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::i32x4_extract_lane(LaneIdx::new(1)),
state,
),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck I64x2ExtractLane" {
let state = make_state(empty_env(), [ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::i64x2_extract_lane(LaneIdx::new(0)),
state,
),
)
assert_stack(new_state, [ValType::i64()])
}
///|
test "Typecheck F32x4ExtractLane" {
let state = make_state(empty_env(), [ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::f32x4_extract_lane(LaneIdx::new(2)),
state,
),
)
assert_stack(new_state, [ValType::f32()])
}
///|
test "Typecheck F64x2ExtractLane" {
let state = make_state(empty_env(), [ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::f64x2_extract_lane(LaneIdx::new(1)),
state,
),
)
assert_stack(new_state, [ValType::f64()])
}
///|
test "Typecheck I8x16ReplaceLane" {
let state = make_state(empty_env(), [ValType::v128(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::i8x16_replace_lane(LaneIdx::new(0)),
state,
),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I16x8ReplaceLane" {
let state = make_state(empty_env(), [ValType::v128(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::i16x8_replace_lane(LaneIdx::new(2)),
state,
),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I32x4ReplaceLane" {
let state = make_state(empty_env(), [ValType::v128(), ValType::i32()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::i32x4_replace_lane(LaneIdx::new(1)),
state,
),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I64x2ReplaceLane" {
let state = make_state(empty_env(), [ValType::v128(), ValType::i64()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::i64x2_replace_lane(LaneIdx::new(0)),
state,
),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck F32x4ReplaceLane" {
let state = make_state(empty_env(), [ValType::v128(), ValType::f32()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::f32x4_replace_lane(LaneIdx::new(3)),
state,
),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck F64x2ReplaceLane" {
let state = make_state(empty_env(), [ValType::v128(), ValType::f64()])
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::f64x2_replace_lane(LaneIdx::new(1)),
state,
),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck V128Not" {
let state = make_state(empty_env(), [ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::v128_not(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I8x16Abs" {
let state = make_state(empty_env(), [ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i8x16_abs(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I16x8Neg" {
let state = make_state(empty_env(), [ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i16x8_neg(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I32x4AllTrue" {
let state = make_state(empty_env(), [ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32x4_all_true(), state),
)
assert_stack(new_state, [ValType::i32()])
}
///|
test "Typecheck V128And" {
let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::v128_and(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I8x16Add" {
let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i8x16_add(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I16x8Mul" {
let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i16x8_mul(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I32x4Sub" {
let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32x4_sub(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck F32x4Div" {
let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32x4_div(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I8x16Eq" {
let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i8x16_eq(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I16x8LtS" {
let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i16x8_lt_s(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck I32x4GtU" {
let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::i32x4_gt_u(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck F32x4Le" {
let state = make_state(empty_env(), [ValType::v128(), ValType::v128()])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::f32x4_le(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck V128Bitselect" {
let state = make_state(empty_env(), [
ValType::v128(),
ValType::v128(),
ValType::v128(),
])
let new_state = assert_ok(
Typecheck::typecheck(Instruction::v128_bitselect(), state),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck V128Load8Lane" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32(), ValType::v128()])
let arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::v128_load8_lane(arg, LaneIdx::new(0)),
state,
),
)
assert_stack(new_state, [ValType::v128()])
}
///|
test "Typecheck V128Store16Lane" {
let env : Env = { ..empty_env(), mems: [MemType::new(Limits::i32(1, None))], }
let state = make_state(env, [ValType::i32(), ValType::v128()])
let arg = MemArg::new(U32(0), None, U64(0))
let new_state = assert_ok(
Typecheck::typecheck(
Instruction::v128_store16_lane(arg, LaneIdx::new(2)),
state,
),
)
assert_stack(new_state, [])
}
// =============================================================================
// CALL INSTRUCTIONS
// =============================================================================
///|
test "Typecheck Call" {
let func_type = FuncType::new([ValType::i32()], [ValType::i64()])
let env = empty_env().push_func(func_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(Instruction::call(FuncIdx::new(0)), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck Call no args no results" {
let func_type = FuncType::new([], [])
let env = empty_env().push_func(func_type)
let state = make_state(env, [])
let result = Typecheck::typecheck(Instruction::call(FuncIdx::new(0)), state)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck Call invalid func index" {
let env = empty_env()
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(Instruction::call(FuncIdx::new(0)), state)
assert_err(result)
}
///|
test "Typecheck Call type mismatch" {
let func_type = FuncType::new([ValType::i32()], [])
let env = empty_env().push_func(func_type)
let state = make_state(env, [ValType::i64()])
let result = Typecheck::typecheck(Instruction::call(FuncIdx::new(0)), state)
assert_err(result)
}
///|
test "Typecheck CallIndirect" {
let func_subtype = SubType::comp_type(
CompType::func([ValType::i32()], [ValType::i64()]),
)
let table_type = TableType::new(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
Limits::i32(1, None),
)
let env = { ..empty_env(), global_types: [func_subtype], }.push_table(
table_type,
)
let state = make_state(env, [ValType::i32(), ValType::i32()])
let result = Typecheck::typecheck(
Instruction::call_indirect(TypeIdx::new(0), TableIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck ReturnCall" {
let func_type = FuncType::new([ValType::i32()], [ValType::i64()])
let env = empty_env()
.push_func(func_type)
.with_return_type(Some([ValType::i64()]))
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::return_call(FuncIdx::new(0)),
state,
)
let final_state = assert_ok(result)
@debug.assert_eq(final_state.reachable, false)
}
///|
test "Typecheck ReturnCallIndirect" {
let func_subtype = SubType::comp_type(
CompType::func([ValType::i32()], [ValType::i64()]),
)
let table_type = TableType::new(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
Limits::i32(1, None),
)
let env = { ..empty_env(), global_types: [func_subtype], }
.push_table(table_type)
.with_return_type(Some([ValType::i64()]))
let state = make_state(env, [ValType::i32(), ValType::i32()])
let result = Typecheck::typecheck(
Instruction::return_call_indirect(TypeIdx::new(0), TableIdx::new(0)),
state,
)
let final_state = assert_ok(result)
@debug.assert_eq(final_state.reachable, false)
}
///|
test "Typecheck CallRef" {
let func_subtype = SubType::comp_type(
CompType::func([ValType::i32()], [ValType::i64()]),
)
let env = { ..empty_env(), global_types: [func_subtype], }
let ref_type = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [ValType::i32(), ref_type])
let result = Typecheck::typecheck(
Instruction::call_ref(TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck ReturnCallRef" {
let func_subtype = SubType::comp_type(
CompType::func([ValType::i32()], [ValType::i64()]),
)
let env = { ..empty_env(), global_types: [func_subtype], }.with_return_type(
Some([ValType::i64()]),
)
let ref_type = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [ValType::i32(), ref_type])
let result = Typecheck::typecheck(
Instruction::return_call_ref(TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
@debug.assert_eq(final_state.reachable, false)
}
// =============================================================================
// REFERENCE INSTRUCTIONS
// =============================================================================
///|
test "Typecheck RefFunc" {
let func_type = FuncType::new([], [])
let env = empty_env().push_func(func_type)
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::ref_func(FuncIdx::new(0)),
state,
)
let final_state = assert_ok(result)
@debug.assert_eq(final_state.stack.length(), 1)
}
///|
test "Typecheck RefFunc infers concrete function heap type when unique" {
let ft = FuncType::new([], [])
let st = SubType::comp_type(CompType::func([], []))
let env = empty_env().with_types([st]).push_func(ft)
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::ref_func(FuncIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
),
])
}
///|
test "Typecheck RefFunc prefers declared function type index when available" {
let ft = FuncType::new([], [])
let st0 = SubType::comp_type(CompType::func([], []))
let st1 = SubType::comp_type(CompType::func([], []))
let env = empty_env()
.with_types([st0, st1])
.push_func_with_typeidx(ft, Some(TypeIdx::new(1)))
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::ref_func(FuncIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(1)), exact=true),
),
])
}
///|
test "Typecheck RefFunc invalid index" {
let env = empty_env()
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::ref_func(FuncIdx::new(0)),
state,
)
assert_err(result)
}
///|
test "Typecheck RefTest non-nullable" {
let env = empty_env()
let state = make_state(env, [anyref_type()])
let result = Typecheck::typecheck(
Instruction::ref_test(false, HeapType::abs(AbsHeapType::i31())),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck RefTest eq from any" {
let env = empty_env()
let state = make_state(env, [anyref_type()])
let result = Typecheck::typecheck(
Instruction::ref_test(true, HeapType::abs(AbsHeapType::eq())),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck RefTest struct from eq" {
let env = empty_env()
let state = make_state(env, [eqref_type()])
let result = Typecheck::typecheck(
Instruction::ref_test(true, HeapType::abs(AbsHeapType::struct_())),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck RefTest rejects operands from a distinct reference hierarchy" {
let env = empty_env()
// i31ref on stack, testing for funcref - these are incompatible hierarchies
let state = make_state(env, [i31ref_type()])
let result = Typecheck::typecheck(
Instruction::ref_test(true, HeapType::abs(AbsHeapType::func())),
state,
)
assert_true(result is Err(_))
}
///|
test "Typecheck RefTest with concrete type recursive" {
let struct_subtype = SubType::comp_type(CompType::struct_([]))
let env = empty_env().with_rectype(RecType::new(struct_subtype))
let state = make_state(env, [anyref_type()])
let result = Typecheck::typecheck(
Instruction::ref_test(true, HeapType::new(TypeIdx::rec(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck RefTest with concrete type in global types" {
let struct_subtype = SubType::comp_type(CompType::struct_([]))
let env : Env = { ..empty_env(), global_types: [struct_subtype], }
let state = make_state(env, [anyref_type()])
let result = Typecheck::typecheck(
Instruction::ref_test(true, HeapType::new(TypeIdx::new(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck exact RefTest accepts exact concrete source" {
let struct_subtype = SubType::comp_type(CompType::struct_([]))
let env : Env = { ..empty_env(), global_types: [struct_subtype], }
let exact_struct_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0U)), exact=true),
)
let state = make_state(env, [exact_struct_ref])
let result = Typecheck::typecheck(
Instruction::ref_test_exact(false, HeapType::new(TypeIdx::new(0U))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck exact RefTest rejects abstract exact target" {
let env = empty_env()
let state = make_state(env, [anyref_type()])
let result = Typecheck::typecheck(
Instruction::ref_test_exact(false, HeapType::abs(AbsHeapType::eq())),
state,
)
assert_err(result)
}
///|
test "Typecheck RefCast non-nullable" {
let env = empty_env()
let state = make_state(env, [anyref_type()])
let result = Typecheck::typecheck(
Instruction::ref_cast(false, HeapType::abs(AbsHeapType::i31())),
state,
)
let final_state = assert_ok(result)
@debug.assert_eq(final_state.stack.length(), 1)
}
///|
test "Typecheck RefCast accepts exact source for inexact same heap target" {
let struct_type = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::val_type(ValType::funcref()), Var),
]),
)
let env : Env = { ..empty_env(), global_types: [struct_type], }
let exact_struct_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0U)), exact=true),
)
let state = make_state(env, [exact_struct_ref])
let result = Typecheck::typecheck(
Instruction::ref_cast(false, HeapType::new(TypeIdx::new(0U))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [
ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(0U)))),
])
}
///|
test "Typecheck RefGetDesc stack effect" {
let env = typecheck_descriptor_struct_env()
let state = make_state(env, [
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
),
])
let result = Typecheck::typecheck(
Instruction::ref_get_desc(TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(1)), exact=true),
),
])
}
///|
test "Typecheck RefTestDesc stack effect" {
let env = empty_env()
let state = make_state(env, [anyref_type()])
let result = Typecheck::typecheck(
Instruction::ref_test_desc(true, HeapType::abs(AbsHeapType::eq())),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck RefCastDescEq stack effect" {
let described = SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(descriptor=Some(TypeIdx::new(1U))),
)
let descriptor = SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::new(0U))),
)
let env = Env::{ ..empty_env(), global_types: [described, descriptor], }
let state = make_state(env, [
anyref_type(),
ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(1U)))),
])
let result = Typecheck::typecheck(
Instruction::ref_cast_desc_eq(true, HeapType::new(TypeIdx::new(0U))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::ref_null(HeapType::new(TypeIdx::new(0U)))])
}
///|
test "Typecheck exact RefCastDescEq accepts inexact descriptor operand" {
let env = typecheck_descriptor_struct_env()
let state = make_state(env, [
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0U)), exact=true),
),
ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(1U)))),
])
let result = Typecheck::typecheck(
Instruction::ref_cast_desc_eq_type(
RefType::new(true, HeapType::new(TypeIdx::new(0U)), exact=true),
),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [
ValType::ref_type(
RefType::new(true, HeapType::new(TypeIdx::new(0U)), exact=true),
),
])
}
///|
test "Typecheck exact RefCastDescEq accepts nullable strict-subtype operands" {
// OI-J descriptor exactness validation: Binaryen and wasm-tools accept a
// nullable strict-subtype reference operand for an exact nullable supertype
// descriptor cast. Non-null subtype values may trap at runtime, while null can
// still pass the nullable target, so final validation must not reject the
// Binaryen-shaped exactified cast.
let base = SubType::new(
false,
[],
CompType::struct_([]),
metadata=TypeMetadata::new(descriptor=Some(TypeIdx::new(1U))),
)
let base_descriptor = SubType::new(
false,
[],
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::new(0U))),
)
let subtype = SubType::new(
false,
[TypeIdx::new(0U)],
CompType::struct_([]),
metadata=TypeMetadata::new(descriptor=Some(TypeIdx::new(3U))),
)
let subtype_descriptor = SubType::new(
false,
[TypeIdx::new(1U)],
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::new(2U))),
)
let env = Env::{
..empty_env(),
global_types: [base, base_descriptor, subtype, subtype_descriptor],
}
let state = make_state(env, [
ValType::ref_type(RefType::new(true, HeapType::new(TypeIdx::new(2U)))),
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(1U)), exact=true),
),
])
let result = Typecheck::typecheck(
Instruction::ref_cast_desc_eq_type(
RefType::new(true, HeapType::new(TypeIdx::new(0U)), exact=true),
),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [
ValType::ref_type(
RefType::new(true, HeapType::new(TypeIdx::new(0U)), exact=true),
),
])
}
///|
test "Typecheck RefCastDescEq rejects mismatched descriptor describes target" {
let described = SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(descriptor=Some(TypeIdx::new(2U))),
)
let other = SubType::comp_type(CompType::struct_([]))
let descriptor = SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::new(1U))),
)
let env = Env::{
..empty_env(),
global_types: [described, other, descriptor],
}
let state = make_state(env, [
ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(0U)))),
ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(2U)))),
])
let result = Typecheck::typecheck(
Instruction::ref_cast_desc_eq(false, HeapType::new(TypeIdx::new(0U))),
state,
)
assert_err(result)
}
///|
test "Typecheck RefCastDescEq rejects nullable descriptor operand" {
let env = typecheck_descriptor_struct_env()
let state = make_state(env, [
ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(0U)))),
ValType::ref_type(RefType::new(true, HeapType::new(TypeIdx::new(1U)))),
])
let result = Typecheck::typecheck(
Instruction::ref_cast_desc_eq(false, HeapType::new(TypeIdx::new(0U))),
state,
)
assert_err(result)
}
///|
test "Typecheck RefCastDescEq rejects descriptorless target" {
let target = SubType::comp_type(CompType::struct_([]))
let descriptor = SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::new(0U))),
)
let env = Env::{ ..empty_env(), global_types: [target, descriptor], }
let state = make_state(env, [
ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(0U)))),
ValType::ref_type(RefType::new(false, HeapType::new(TypeIdx::new(1U)))),
])
let result = Typecheck::typecheck(
Instruction::ref_cast_desc_eq(false, HeapType::new(TypeIdx::new(0U))),
state,
)
assert_err(result)
}
///|
test "Typecheck BrOnCast" {
let nullable_i31 = ValType::ref_type(
RefType::new(true, HeapType::abs(AbsHeapType::i31())),
)
let env = empty_env().with_labels([[nullable_i31]])
let state = make_state(env, [anyref_type()])
let cast_op = CastOp::new(true, true)
let t = Instruction::br_on_cast(
LabelIdx::new(0),
cast_op.0,
HeapType::abs(AbsHeapType::any()),
cast_op.1,
HeapType::abs(AbsHeapType::i31()),
)
let result = Typecheck::typecheck(t, state)
ignore(assert_ok(result))
}
///|
test "Typecheck BrOnCast rejects empty label payloads" {
let env = empty_env().with_labels([[]])
let state = make_state(env, [anyref_type()])
let cast_op = CastOp::new(true, true)
let t = Instruction::br_on_cast(
LabelIdx::new(0),
cast_op.0,
HeapType::abs(AbsHeapType::any()),
cast_op.1,
HeapType::abs(AbsHeapType::i31()),
)
let result = Typecheck::typecheck(t, state)
match result {
Ok(_) => fail("expected empty br_on_cast label payloads to fail")
Err(msg) => @debug.assert_eq(msg, "br_on_cast label type too short")
}
}
///|
test "Typecheck BrOnCastFail" {
let env = empty_env().with_labels([[anyref_type()]])
let state = make_state(env, [anyref_type()])
let cast_op = CastOp::new(true, true)
let t = Instruction::br_on_cast_fail(
LabelIdx::new(0),
cast_op.0,
HeapType::abs(AbsHeapType::any()),
cast_op.1,
HeapType::abs(AbsHeapType::i31()),
)
let result = Typecheck::typecheck(t, state)
ignore(assert_ok(result))
}
///|
test "Typecheck BrOnCastFail rejects empty label payloads" {
let env = empty_env().with_labels([[]])
let state = make_state(env, [anyref_type()])
let cast_op = CastOp::new(true, true)
let t = Instruction::br_on_cast_fail(
LabelIdx::new(0),
cast_op.0,
HeapType::abs(AbsHeapType::any()),
cast_op.1,
HeapType::abs(AbsHeapType::i31()),
)
let result = Typecheck::typecheck(t, state)
match result {
Ok(_) => fail("expected empty br_on_cast_fail label payloads to fail")
Err(msg) => @debug.assert_eq(msg, "br_on_cast_fail label type too short")
}
}
// =============================================================================
// EXCEPTION HANDLING INSTRUCTIONS
// =============================================================================
///|
test "Typecheck Throw" {
let tag_subtype = SubType::comp_type(CompType::func([ValType::i32()], []))
let env = { ..empty_env(), global_types: [tag_subtype], }.push_tag(
TagType::new(TypeIdx::new(0)),
)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(Instruction::throw_(TagIdx::new(0)), state)
let final_state = assert_ok(result)
@debug.assert_eq(final_state.reachable, false)
}
///|
test "Typecheck Throw invalid tag" {
let env = empty_env()
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(Instruction::throw_(TagIdx::new(0)), state)
assert_err(result)
}
///|
test "Typecheck ThrowRef" {
let exnref = ValType::ref_type(
RefType::new(false, HeapType::abs(AbsHeapType::exn())),
)
let env = empty_env()
let state = make_state(env, [exnref])
let result = Typecheck::typecheck(Instruction::throw_ref(), state)
let final_state = assert_ok(result)
@debug.assert_eq(final_state.reachable, false)
}
///|
test "Typecheck TryTable empty" {
let env = empty_env()
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::try_table(BlockType::void_(), [], Expr::new([])),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck TryTable with result" {
let env = empty_env()
let state = make_state(env, [])
let block_type = BlockType::val_type(ValType::i32())
let result = Typecheck::typecheck(
Instruction::try_table(
block_type,
[],
Expr::new([Instruction::i32_const(I32(42))]),
),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck TryTable with param input" {
let env = empty_env().with_rectype(
RecType::new(SubType::comp_type(CompType::func([ValType::i32()], []))),
)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::try_table(
BlockType::type_idx(TypeIdx::new(0)),
[],
Expr::new([Instruction::drop()]),
),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck TryTable catch rejects invalid tag index" {
let tag_sig = SubType::comp_type(CompType::func([], []))
let env = { ..empty_env(), global_types: [tag_sig], }.push_tag(
TagType::new(TypeIdx::new(0)),
)
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::try_table(
BlockType::void_(),
[Catch::new(TagIdx::new(1), LabelIdx::new(0))],
Expr::new([Instruction::nop()]),
),
state,
)
assert_err(result)
}
///|
test "Typecheck TryTable catch_ref rejects invalid tag index" {
let tag_sig = SubType::comp_type(CompType::func([], []))
let env = { ..empty_env(), global_types: [tag_sig], }.push_tag(
TagType::new(TypeIdx::new(0)),
)
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::try_table(
BlockType::void_(),
[Catch::ref_(TagIdx::new(1), LabelIdx::new(0))],
Expr::new([Instruction::nop()]),
),
state,
)
assert_err(result)
}
///|
test "Typecheck TryTable catch label 0 uses outer depth semantics" {
let tag_sig = SubType::comp_type(CompType::func([ValType::i64()], []))
let env = { ..empty_env(), global_types: [tag_sig], }
.push_tag(TagType::new(TypeIdx::new(0)))
.with_labels([[ValType::i64()]])
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::try_table(
BlockType::val_type(ValType::i32()),
[Catch::new(TagIdx::new(0), LabelIdx::new(0))],
Expr::new([Instruction::i32_const(I32(7))]),
),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck TryTable catch label 1 uses outer depth semantics" {
let tag_sig = SubType::comp_type(CompType::func([ValType::i64()], []))
let env = { ..empty_env(), global_types: [tag_sig], }
.push_tag(TagType::new(TypeIdx::new(0)))
.with_labels([[ValType::i64()], [ValType::i32()]])
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::try_table(
BlockType::val_type(ValType::i32()),
[Catch::new(TagIdx::new(0), LabelIdx::new(1))],
Expr::new([Instruction::i32_const(I32(9))]),
),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
// =============================================================================
// STRUCT INSTRUCTIONS
// =============================================================================
///|
fn typecheck_descriptor_struct_env() -> Env {
let described = SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(descriptor=Some(TypeIdx::new(1U))),
)
let descriptor = SubType::comp_type(
CompType::struct_([]),
metadata=TypeMetadata::new(describes=Some(TypeIdx::new(0U))),
)
{ ..empty_env(), global_types: [described, descriptor], }
}
///|
test "Typecheck StructNew" {
let struct_type = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::val_type(ValType::i32()), Var),
]),
)
let env = { ..empty_env(), global_types: [struct_type], }
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::struct_new(TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
@debug.assert_eq(final_state.stack.length(), 1)
}
///|
test "Typecheck StructNewDefault" {
let struct_type = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::val_type(ValType::i32()), Var),
]),
)
let env = { ..empty_env(), global_types: [struct_type], }
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::struct_new_default(TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
@debug.assert_eq(final_state.stack.length(), 1)
}
///|
test "Typecheck StructNewDesc" {
let env = typecheck_descriptor_struct_env()
let state = make_state(env, [
ValType::ref_type(
RefType::new(true, HeapType::new(TypeIdx::new(1U)), exact=true),
),
])
let result = Typecheck::typecheck(
Instruction::struct_new_default_desc(TypeIdx::new(0U)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0U)), exact=true),
),
])
}
///|
test "Typecheck StructGet" {
let struct_type = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::val_type(ValType::i32()), Var),
]),
)
let env = { ..empty_env(), global_types: [struct_type], }
let struct_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [struct_ref])
let result = Typecheck::typecheck(
Instruction::struct_get(TypeIdx::new(0), U32(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck StructGetS" {
let struct_type = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::pack_type(PackType::i8()), Var),
]),
)
let env = { ..empty_env(), global_types: [struct_type], }
let struct_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [struct_ref])
let result = Typecheck::typecheck(
Instruction::struct_get_s(TypeIdx::new(0), U32(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck StructGetU" {
let struct_type = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::pack_type(PackType::i16()), Var),
]),
)
let env = { ..empty_env(), global_types: [struct_type], }
let struct_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [struct_ref])
let result = Typecheck::typecheck(
Instruction::struct_get_u(TypeIdx::new(0), U32(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck StructAtomicGet" {
let struct_type = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::val_type(ValType::i64()), Var),
]),
)
let env = { ..empty_env(), global_types: [struct_type], }
let struct_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [struct_ref])
let result = Typecheck::typecheck(
Instruction::struct_atomic_get(
AtomicOrder::acq_rel(),
TypeIdx::new(0),
U32(0),
),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck StructAtomicGetS" {
let struct_type = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::pack_type(PackType::i8()), Const),
]),
)
let env = { ..empty_env(), global_types: [struct_type], }
let struct_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [struct_ref])
let result = Typecheck::typecheck(
Instruction::struct_atomic_get_s(
AtomicOrder::seq_cst(),
TypeIdx::new(0),
U32(0),
),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck StructAtomicGetU" {
let struct_type = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::pack_type(PackType::i16()), Var),
]),
)
let env = { ..empty_env(), global_types: [struct_type], }
let struct_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [struct_ref])
let result = Typecheck::typecheck(
Instruction::struct_atomic_get_u(
AtomicOrder::acq_rel(),
TypeIdx::new(0),
U32(0),
),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck StructAtomicGet rejects packed field without signedness" {
let struct_type = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::pack_type(PackType::i8()), Var),
]),
)
let env = { ..empty_env(), global_types: [struct_type], }
let struct_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [struct_ref])
match
Typecheck::typecheck(
Instruction::struct_atomic_get(
AtomicOrder::acq_rel(),
TypeIdx::new(0),
U32(0),
),
state,
) {
Ok(_) => fail("expected struct.atomic.get packed-field rejection")
Err(message) => assert_true(message.contains("packed field"))
}
}
///|
test "Typecheck StructAtomicRmw and cmpxchg" {
let struct_type = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::val_type(ValType::i32()), Var),
]),
)
let env = { ..empty_env(), global_types: [struct_type], }
let struct_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let rmw_state = make_state(env, [struct_ref, ValType::i32()])
let rmw = assert_ok(
Typecheck::typecheck(
Instruction::struct_atomic_rmw(
@lib.AggregateAtomicRmwOp::add(),
TypeIdx::new(0),
U32(0),
),
rmw_state,
),
)
assert_stack(rmw, [ValType::i32()])
let cmp_state = make_state(env, [struct_ref, ValType::i32(), ValType::i32()])
let cmp = assert_ok(
Typecheck::typecheck(
Instruction::struct_atomic_cmpxchg(TypeIdx::new(0), U32(0)),
cmp_state,
),
)
assert_stack(cmp, [ValType::i32()])
}
///|
test "Typecheck StructAtomicRmw rejects invalid field shapes" {
let packed_struct = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::pack_type(PackType::i8()), Var),
]),
)
let immutable_struct = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::val_type(ValType::i32()), Const),
]),
)
let env = { ..empty_env(), global_types: [packed_struct, immutable_struct], }
let ref0 = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let ref1 = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(1))),
)
match
Typecheck::typecheck(
Instruction::struct_atomic_rmw(
@lib.AggregateAtomicRmwOp::add(),
TypeIdx::new(0),
U32(0),
),
make_state(env, [ref0, ValType::i32()]),
) {
Ok(_) => fail("expected packed aggregate atomic rejection")
Err(message) => assert_true(message.contains("non-packed"), msg=message)
}
match
Typecheck::typecheck(
Instruction::struct_atomic_rmw(
@lib.AggregateAtomicRmwOp::add(),
TypeIdx::new(1),
U32(0),
),
make_state(env, [ref1, ValType::i32()]),
) {
Ok(_) => fail("expected immutable aggregate atomic rejection")
Err(message) => assert_true(message.contains("immutable"), msg=message)
}
match
Typecheck::typecheck(
Instruction::struct_atomic_rmw(
@lib.AggregateAtomicRmwOp::add(),
TypeIdx::new(0),
U32(3),
),
make_state(env, [ref0, ValType::i32()]),
) {
Ok(_) => fail("expected invalid field index rejection")
Err(message) => assert_true(message.contains("out of range"), msg=message)
}
}
///|
test "Typecheck StructSet" {
let struct_type = SubType::comp_type(
CompType::struct_([
FieldType::new(StorageType::val_type(ValType::i32()), Var),
]),
)
let env = { ..empty_env(), global_types: [struct_type], }
let struct_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [struct_ref, ValType::i32()])
let result = Typecheck::typecheck(
Instruction::struct_set(TypeIdx::new(0), U32(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
// =============================================================================
// ARRAY INSTRUCTIONS
// =============================================================================
///|
test "Typecheck ArrayNew" {
let array_type = SubType::comp_type(
CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
)
let env = { ..empty_env(), global_types: [array_type], }
let state = make_state(env, [ValType::i32(), ValType::i32()])
let result = Typecheck::typecheck(
Instruction::array_new(TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
),
])
}
///|
test "Typecheck ArrayNewDefault" {
let array_type = SubType::comp_type(
CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
)
let env = { ..empty_env(), global_types: [array_type], }
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::array_new_default(TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
),
])
}
///|
test "Typecheck ArrayNewFixed" {
let array_type = SubType::comp_type(
CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
)
let env = { ..empty_env(), global_types: [array_type], }
let state = make_state(env, [ValType::i32(), ValType::i32(), ValType::i32()])
let result = Typecheck::typecheck(
Instruction::array_new_fixed(TypeIdx::new(0), U32(3)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
),
])
}
///|
test "Typecheck ArrayNewData" {
let array_type = SubType::comp_type(
CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
)
let data = Data::new(DataMode::passive(), b"")
let env = { ..empty_env(), global_types: [array_type], }.push_data(data)
let state = make_state(env, [ValType::i32(), ValType::i32()])
let result = Typecheck::typecheck(
Instruction::array_new_data(TypeIdx::new(0), DataIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
),
])
}
///|
test "Typecheck ArrayNewElem" {
let funcref = RefType::abs(AbsHeapType::func())
let array_type = SubType::comp_type(
CompType::array(
FieldType::new(StorageType::val_type(ValType::ref_type(funcref)), Var),
),
)
let elem = Elem::new(ElemMode::passive(), ElemKind::funcs([]))
let env = { ..empty_env(), global_types: [array_type], }.push_elem(elem)
let state = make_state(env, [ValType::i32(), ValType::i32()])
let result = Typecheck::typecheck(
Instruction::array_new_elem(TypeIdx::new(0), ElemIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [
ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0)), exact=true),
),
])
}
///|
test "Typecheck StringNewWtf16Array" {
let array_type = SubType::comp_type(
CompType::array(
FieldType::new(StorageType::pack_type(PackType::i16()), Const),
),
)
let env = { ..empty_env(), global_types: [array_type], }
let state = make_state(env, [
ValType::ref_array_nullable(TypeIdx::new(0)),
ValType::i32(),
ValType::i32(),
])
let result = Typecheck::typecheck(
Instruction::string_new_wtf16_array(),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [typecheck_nonnull_string_type()])
}
///|
test "Typecheck StringConst" {
let state = make_state(empty_env(), [])
let result = Typecheck::typecheck(Instruction::string_const(b"hello"), state)
let final_state = assert_ok(result)
assert_stack(final_state, [typecheck_nonnull_string_type()])
}
///|
test "Typecheck StringEncodeUtf8Array" {
let array_type = SubType::comp_type(
CompType::array(FieldType::new(StorageType::pack_type(PackType::i8()), Var)),
)
let env = { ..empty_env(), global_types: [array_type], }
let state = make_state(env, [
ValType::stringref(),
ValType::ref_array_nullable(TypeIdx::new(0)),
ValType::i32(),
])
let result = Typecheck::typecheck(
Instruction::string_encode_utf8_array(),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck StringEncodeWtf16Array rejects immutable arrays" {
let array_type = SubType::comp_type(
CompType::array(
FieldType::new(StorageType::pack_type(PackType::i16()), Const),
),
)
let env = { ..empty_env(), global_types: [array_type], }
let state = make_state(env, [
ValType::stringref(),
ValType::ref_array_nullable(TypeIdx::new(0)),
ValType::i32(),
])
match Typecheck::typecheck(Instruction::string_encode_wtf16_array(), state) {
Err(err) => assert_true(err.contains("mutable"))
Ok(_) => fail("expected immutable string.encode_wtf16_array array to fail")
}
}
///|
test "Typecheck ArrayGet" {
let array_type = SubType::comp_type(
CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
)
let env = { ..empty_env(), global_types: [array_type], }
let array_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [array_ref, ValType::i32()])
let result = Typecheck::typecheck(
Instruction::array_get(TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck ArrayGetS" {
let array_type = SubType::comp_type(
CompType::array(FieldType::new(StorageType::pack_type(PackType::i8()), Var)),
)
let env = { ..empty_env(), global_types: [array_type], }
let array_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [array_ref, ValType::i32()])
let result = Typecheck::typecheck(
Instruction::array_get_s(TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck ArrayGetU" {
let array_type = SubType::comp_type(
CompType::array(
FieldType::new(StorageType::pack_type(PackType::i16()), Var),
),
)
let env = { ..empty_env(), global_types: [array_type], }
let array_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [array_ref, ValType::i32()])
let result = Typecheck::typecheck(
Instruction::array_get_u(TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck ArraySet" {
let array_type = SubType::comp_type(
CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
)
let env = { ..empty_env(), global_types: [array_type], }
let array_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [array_ref, ValType::i32(), ValType::i32()])
let result = Typecheck::typecheck(
Instruction::array_set(TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck ArrayLen" {
let array_ref = ValType::ref_type(
RefType::new(false, HeapType::abs(AbsHeapType::array())),
)
let env = empty_env()
let state = make_state(env, [array_ref])
let result = Typecheck::typecheck(Instruction::array_len(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck ArrayFill" {
let array_type = SubType::comp_type(
CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
)
let env = { ..empty_env(), global_types: [array_type], }
let array_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [
array_ref,
ValType::i32(),
ValType::i32(),
ValType::i32(),
])
let result = Typecheck::typecheck(
Instruction::array_fill(TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck ArrayCopy" {
let array_type = SubType::comp_type(
CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
)
let env = { ..empty_env(), global_types: [array_type], }
let array_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [
array_ref,
ValType::i32(),
array_ref,
ValType::i32(),
ValType::i32(),
])
let result = Typecheck::typecheck(
Instruction::array_copy(TypeIdx::new(0), TypeIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck ArrayInitData" {
let array_type = SubType::comp_type(
CompType::array(FieldType::new(StorageType::val_type(ValType::i32()), Var)),
)
let data = Data::new(DataMode::passive(), b"")
let env = { ..empty_env(), global_types: [array_type], }.push_data(data)
let array_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [
array_ref,
ValType::i32(),
ValType::i32(),
ValType::i32(),
])
let result = Typecheck::typecheck(
Instruction::array_init_data(TypeIdx::new(0), DataIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck ArrayInitElem" {
let funcref = RefType::abs(AbsHeapType::func())
let array_type = SubType::comp_type(
CompType::array(
FieldType::new(StorageType::val_type(ValType::ref_type(funcref)), Var),
),
)
let elem = Elem::new(ElemMode::passive(), ElemKind::funcs([]))
let env = { ..empty_env(), global_types: [array_type], }.push_elem(elem)
let array_ref = ValType::ref_type(
RefType::new(false, HeapType::new(TypeIdx::new(0))),
)
let state = make_state(env, [
array_ref,
ValType::i32(),
ValType::i32(),
ValType::i32(),
])
let result = Typecheck::typecheck(
Instruction::array_init_elem(TypeIdx::new(0), ElemIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
// =============================================================================
// MEMORY INIT/DATA INSTRUCTIONS
// =============================================================================
///|
test "Typecheck MemoryInit" {
let mem_type = MemType::new(Limits::i32(1, None))
let data = Data::new(DataMode::passive(), b"")
let env = empty_env().push_mem(mem_type).push_data(data)
let state = make_state(env, [ValType::i32(), ValType::i32(), ValType::i32()])
let result = Typecheck::typecheck(
Instruction::memory_init(DataIdx::new(0), MemIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck DataDrop" {
let data = Data::new(DataMode::passive(), b"")
let env = empty_env().push_data(data)
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::data_drop(DataIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck DataDrop invalid index" {
let env = empty_env()
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::data_drop(DataIdx::new(0)),
state,
)
assert_err(result)
}
// =============================================================================
// TABLE INIT/ELEM INSTRUCTIONS
// =============================================================================
///|
test "Typecheck TableInit" {
let table_type = TableType::new(
RefType::new(true, HeapType::abs(AbsHeapType::func())),
Limits::i32(1, None),
)
let elem = Elem::new(ElemMode::passive(), ElemKind::funcs([]))
let env = empty_env().push_table(table_type).push_elem(elem)
let state = make_state(env, [ValType::i32(), ValType::i32(), ValType::i32()])
let result = Typecheck::typecheck(
Instruction::table_init(ElemIdx::new(0), TableIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck ElemDrop" {
let elem = Elem::new(ElemMode::passive(), ElemKind::funcs([]))
let env = empty_env().push_elem(elem)
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::elem_drop(ElemIdx::new(0)),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck ElemDrop invalid index" {
let env = empty_env()
let state = make_state(env, [])
let result = Typecheck::typecheck(
Instruction::elem_drop(ElemIdx::new(0)),
state,
)
assert_err(result)
}
// =============================================================================
// ADDITIONAL NUMERIC INSTRUCTIONS (missing from original tests)
// =============================================================================
///|
test "Typecheck I64LtU" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_lt_u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck I64GtS" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_gt_s(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck I64LeS" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_le_s(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck I64LeU" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_le_u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck I64GeS" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_ge_s(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck I64GeU" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_ge_u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck I64Ctz" {
let env = empty_env()
let state = make_state(env, [ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_ctz(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64Popcnt" {
let env = empty_env()
let state = make_state(env, [ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_popcnt(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64DivS" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_div_s(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64DivU" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_div_u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64RemS" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_rem_s(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64RemU" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_rem_u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64And" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_and(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64Or" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_or(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64Xor" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_xor(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64Shl" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_shl(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64ShrS" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_shr_s(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64ShrU" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_shr_u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64Rotl" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_rotl(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64Rotr" {
let env = empty_env()
let state = make_state(env, [ValType::i64(), ValType::i64()])
let result = Typecheck::typecheck(Instruction::i64_rotr(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck F64Ne" {
let env = empty_env()
let state = make_state(env, [ValType::f64(), ValType::f64()])
let result = Typecheck::typecheck(Instruction::f64_ne(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck F64Gt" {
let env = empty_env()
let state = make_state(env, [ValType::f64(), ValType::f64()])
let result = Typecheck::typecheck(Instruction::f64_gt(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck F64Le" {
let env = empty_env()
let state = make_state(env, [ValType::f64(), ValType::f64()])
let result = Typecheck::typecheck(Instruction::f64_le(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck F64Ge" {
let env = empty_env()
let state = make_state(env, [ValType::f64(), ValType::f64()])
let result = Typecheck::typecheck(Instruction::f64_ge(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck F64Ceil" {
let env = empty_env()
let state = make_state(env, [ValType::f64()])
let result = Typecheck::typecheck(Instruction::f64_ceil(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f64()])
}
///|
test "Typecheck F64Floor" {
let env = empty_env()
let state = make_state(env, [ValType::f64()])
let result = Typecheck::typecheck(Instruction::f64_floor(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f64()])
}
///|
test "Typecheck F64Trunc" {
let env = empty_env()
let state = make_state(env, [ValType::f64()])
let result = Typecheck::typecheck(Instruction::f64_trunc(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f64()])
}
///|
test "Typecheck F64Nearest" {
let env = empty_env()
let state = make_state(env, [ValType::f64()])
let result = Typecheck::typecheck(Instruction::f64_nearest(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f64()])
}
///|
test "Typecheck F64Sqrt" {
let env = empty_env()
let state = make_state(env, [ValType::f64()])
let result = Typecheck::typecheck(Instruction::f64_sqrt(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f64()])
}
///|
test "Typecheck F64Min" {
let env = empty_env()
let state = make_state(env, [ValType::f64(), ValType::f64()])
let result = Typecheck::typecheck(Instruction::f64_min(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f64()])
}
///|
test "Typecheck F64Max" {
let env = empty_env()
let state = make_state(env, [ValType::f64(), ValType::f64()])
let result = Typecheck::typecheck(Instruction::f64_max(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f64()])
}
///|
test "Typecheck F64Copysign" {
let env = empty_env()
let state = make_state(env, [ValType::f64(), ValType::f64()])
let result = Typecheck::typecheck(Instruction::f64_copysign(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f64()])
}
///|
test "Typecheck F32Trunc" {
let env = empty_env()
let state = make_state(env, [ValType::f32()])
let result = Typecheck::typecheck(Instruction::f32_trunc(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f32()])
}
///|
test "Typecheck F32Nearest" {
let env = empty_env()
let state = make_state(env, [ValType::f32()])
let result = Typecheck::typecheck(Instruction::f32_nearest(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f32()])
}
///|
test "Typecheck I64TruncF32U" {
let env = empty_env()
let state = make_state(env, [ValType::f32()])
let result = Typecheck::typecheck(Instruction::i64_trunc_f32u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64TruncF64U" {
let env = empty_env()
let state = make_state(env, [ValType::f64()])
let result = Typecheck::typecheck(Instruction::i64_trunc_f64u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck F32ConvertI64U" {
let env = empty_env()
let state = make_state(env, [ValType::i64()])
let result = Typecheck::typecheck(Instruction::f32_convert_i64u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f32()])
}
///|
test "Typecheck F64ConvertI32U" {
let env = empty_env()
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(Instruction::f64_convert_i32u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f64()])
}
///|
test "Typecheck F64ConvertI64U" {
let env = empty_env()
let state = make_state(env, [ValType::i64()])
let result = Typecheck::typecheck(Instruction::f64_convert_i64u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::f64()])
}
///|
test "Typecheck I32TruncSatF64U" {
let env = empty_env()
let state = make_state(env, [ValType::f64()])
let result = Typecheck::typecheck(Instruction::i32_trunc_sat_f64u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck I64TruncSatF32U" {
let env = empty_env()
let state = make_state(env, [ValType::f32()])
let result = Typecheck::typecheck(Instruction::i64_trunc_sat_f32u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64TruncSatF64U" {
let env = empty_env()
let state = make_state(env, [ValType::f64()])
let result = Typecheck::typecheck(Instruction::i64_trunc_sat_f64u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
// =============================================================================
// ADDITIONAL LOAD/STORE INSTRUCTIONS
// =============================================================================
///|
test "Typecheck I64Load8U" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::i64_load8u(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64Load16S" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::i64_load16s(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64Load16U" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::i64_load16u(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I64Load32U" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::i64_load32u(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i64()])
}
///|
test "Typecheck I32Store16" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32(), ValType::i32()])
let result = Typecheck::typecheck(
Instruction::i32_store16(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck I64Store8" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32(), ValType::i64()])
let result = Typecheck::typecheck(
Instruction::i64_store8(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
///|
test "Typecheck I64Store16" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32(), ValType::i64()])
let result = Typecheck::typecheck(
Instruction::i64_store16(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [])
}
// =============================================================================
// ADDITIONAL SIMD INSTRUCTIONS
// =============================================================================
///|
test "Typecheck I8x16Shuffle" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let t = Instruction::i8x16_shuffle(
LaneIdx::new(0),
LaneIdx::new(1),
LaneIdx::new(2),
LaneIdx::new(3),
LaneIdx::new(4),
LaneIdx::new(5),
LaneIdx::new(6),
LaneIdx::new(7),
LaneIdx::new(8),
LaneIdx::new(9),
LaneIdx::new(10),
LaneIdx::new(11),
LaneIdx::new(12),
LaneIdx::new(13),
LaneIdx::new(14),
LaneIdx::new(15),
)
let result = Typecheck::typecheck(t, state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck I8x16Swizzle" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_swizzle(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128AnyTrue" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::v128_any_true(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck I8x16AllTrue" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_all_true(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck I8x16Bitmask" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_bitmask(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck I16x8Bitmask" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_bitmask(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck I32x4Bitmask" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i32x4_bitmask(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck I64x2Bitmask" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_bitmask(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::i32()])
}
///|
test "Typecheck V128Or" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::v128_or(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128Xor" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::v128_xor(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128Andnot" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::v128_andnot(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck I8x16Neg" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_neg(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck I8x16Popcnt" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_popcnt(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck I8x16Sub" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_sub(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck I8x16AddSatS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_add_sat_s(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck I8x16AddSatU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_add_sat_u(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck I64x2Add" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_add(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck I64x2Sub" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_sub(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck I64x2Mul" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_mul(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F32x4Add" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_add(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F32x4Sub" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_sub(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F32x4Mul" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_mul(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F64x2Add" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_add(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F64x2Sub" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_sub(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F64x2Mul" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_mul(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F64x2Div" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_div(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F32x4Abs" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_abs(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F32x4Neg" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_neg(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F32x4Sqrt" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_sqrt(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F64x2Abs" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_abs(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F64x2Neg" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_neg(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck F64x2Sqrt" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_sqrt(), state)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128Load32Zero" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::v128_load32_zero(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128Load64Zero" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::v128_load64_zero(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128Load8x8S" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::v128_load8x8s(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128Load8x8U" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::v128_load8x8u(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128Load16x4S" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::v128_load16x4s(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128Load16x4U" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::v128_load16x4u(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128Load32x2S" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::v128_load32x2s(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128Load32x2U" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::v128_load32x2u(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128Load8Splat" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::v128_load8_splat(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
///|
test "Typecheck V128Load16Splat" {
let mem_type = MemType::new(Limits::i32(1, None))
let env = empty_env().push_mem(mem_type)
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::v128_load16_splat(MemArg::new(U32(0), None, U64(0))),
state,
)
let final_state = assert_ok(result)
assert_stack(final_state, [ValType::v128()])
}
// =============================================================================
// SIMD LOAD TESTS (Missing)
// =============================================================================
///|
test "Typecheck V128Load32Splat" {
let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::v128_load32_splat(MemArg::new(U32(0), None, U64(0))),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck V128Load64Splat" {
let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
let state = make_state(env, [ValType::i32()])
let result = Typecheck::typecheck(
Instruction::v128_load64_splat(MemArg::new(U32(0), None, U64(0))),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck V128Load32Lane" {
let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
let state = make_state(env, [ValType::i32(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::v128_load32_lane(
MemArg::new(U32(0), None, U64(0)),
LaneIdx::new(0),
),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck V128Load64Lane" {
let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
let state = make_state(env, [ValType::i32(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::v128_load64_lane(
MemArg::new(U32(0), None, U64(0)),
LaneIdx::new(0),
),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
// =============================================================================
// SIMD STORE TESTS (Missing)
// =============================================================================
///|
test "Typecheck V128Store8Lane" {
let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
let state = make_state(env, [ValType::i32(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::v128_store8_lane(
MemArg::new(U32(0), None, U64(0)),
LaneIdx::new(0),
),
state,
)
let state = assert_ok(result)
assert_stack(state, [])
}
///|
test "Typecheck V128Store32Lane" {
let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
let state = make_state(env, [ValType::i32(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::v128_store32_lane(
MemArg::new(U32(0), None, U64(0)),
LaneIdx::new(0),
),
state,
)
let state = assert_ok(result)
assert_stack(state, [])
}
///|
test "Typecheck V128Store64Lane" {
let env = empty_env().push_mem(MemType::new(Limits::i32(1, None)))
let state = make_state(env, [ValType::i32(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::v128_store64_lane(
MemArg::new(U32(0), None, U64(0)),
LaneIdx::new(0),
),
state,
)
let state = assert_ok(result)
assert_stack(state, [])
}
// =============================================================================
// SIMD CONVERSION TESTS
// =============================================================================
///|
test "Typecheck F32x4DemoteF64x2Zero" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::f32x4_demote_f64x2_zero(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2PromoteLowF32x4" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::f64x2_promote_low_f32x4(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4TruncSatF32x4S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_trunc_sat_f32x4s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4TruncSatF32x4U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_trunc_sat_f32x4u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4ConvertI32x4S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_convert_i32x4s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4ConvertI32x4U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_convert_i32x4u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4TruncSatF64x2SZero" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_trunc_sat_f64x2s_zero(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4TruncSatF64x2UZero" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_trunc_sat_f64x2u_zero(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2ConvertLowI32x4S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::f64x2_convert_low_i32x4s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2ConvertLowI32x4U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::f64x2_convert_low_i32x4u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
// =============================================================================
// SIMD i8x16 TESTS (Missing)
// =============================================================================
///|
test "Typecheck I8x16NarrowI16x8S" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_narrow_i16x8s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I8x16NarrowI16x8U" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_narrow_i16x8u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I8x16Shl" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i8x16_shl(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I8x16ShrS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i8x16_shr_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I8x16ShrU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i8x16_shr_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I8x16SubSatS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_sub_sat_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I8x16SubSatU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_sub_sat_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I8x16MinS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_min_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I8x16MinU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_min_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I8x16MaxS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_max_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I8x16MaxU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_max_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I8x16AvgrU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_avgr_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
// =============================================================================
// SIMD i16x8 TESTS (Missing)
// =============================================================================
///|
test "Typecheck I16x8Abs" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_abs(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8AllTrue" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_all_true(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::i32()])
}
///|
test "Typecheck I16x8Q15mulrSatS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8q15mulr_sat_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8NarrowI32x4S" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_narrow_i32x4s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8NarrowI32x4U" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_narrow_i32x4u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8ExtendLowI8x16S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i16x8_extend_low_i8x16s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8ExtendHighI8x16S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i16x8_extend_high_i8x16s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8ExtendLowI8x16U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i16x8_extend_low_i8x16u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8ExtendHighI8x16U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i16x8_extend_high_i8x16u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8Shl" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i16x8_shl(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8ShrS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i16x8_shr_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8ShrU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i16x8_shr_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8Add" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_add(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8AddSatS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_add_sat_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8AddSatU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_add_sat_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8Sub" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_sub(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8SubSatS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_sub_sat_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8SubSatU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_sub_sat_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8MinS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_min_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8MinU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_min_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8MaxS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_max_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8MaxU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_max_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8AvgrU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i16x8_avgr_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8ExtmulLowI8x16S" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i16x8_extmul_low_i8x16s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8ExtmulHighI8x16S" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i16x8_extmul_high_i8x16s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8ExtmulLowI8x16U" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i16x8_extmul_low_i8x16u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8ExtmulHighI8x16U" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i16x8_extmul_high_i8x16u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8ExtaddPairwiseI8x16S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i16x8_extadd_pairwise_i8x16s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8ExtaddPairwiseI8x16U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i16x8_extadd_pairwise_i8x16u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
// =============================================================================
// SIMD i32x4 TESTS (Missing)
// =============================================================================
///|
test "Typecheck I32x4Abs" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i32x4_abs(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4Neg" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i32x4_neg(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4ExtendLowI16x8S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_extend_low_i16x8s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4ExtendHighI16x8S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_extend_high_i16x8s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4ExtendLowI16x8U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_extend_low_i16x8u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4ExtendHighI16x8U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_extend_high_i16x8u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4Shl" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i32x4_shl(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4ShrS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i32x4_shr_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4ShrU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i32x4_shr_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4Add" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i32x4_add(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4Mul" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i32x4_mul(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4MinS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i32x4_min_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4MinU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i32x4_min_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4MaxS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i32x4_max_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4MaxU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i32x4_max_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4DotI16x8S" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i32x4_dot_i16x8s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4ExtmulLowI16x8S" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_extmul_low_i16x8s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4ExtmulHighI16x8S" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_extmul_high_i16x8s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4ExtmulLowI16x8U" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_extmul_low_i16x8u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4ExtmulHighI16x8U" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_extmul_high_i16x8u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4ExtaddPairwiseI16x8S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_extadd_pairwise_i16x8s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4ExtaddPairwiseI16x8U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_extadd_pairwise_i16x8u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
// =============================================================================
// SIMD i64x2 TESTS (Missing)
// =============================================================================
///|
test "Typecheck I64x2Abs" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_abs(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2Neg" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_neg(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2AllTrue" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_all_true(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::i32()])
}
///|
test "Typecheck I64x2ExtendLowI32x4S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i64x2_extend_low_i32x4s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2ExtendHighI32x4S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i64x2_extend_high_i32x4s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2ExtendLowI32x4U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i64x2_extend_low_i32x4u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2ExtendHighI32x4U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i64x2_extend_high_i32x4u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2Shl" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i64x2_shl(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2ShrS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i64x2_shr_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2ShrU" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::i32()])
let result = Typecheck::typecheck(Instruction::i64x2_shr_u(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2Eq" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_eq(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2Ne" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_ne(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2LtS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_lt_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2GtS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_gt_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2LeS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_le_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2GeS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i64x2_ge_s(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2ExtmulLowI32x4S" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i64x2_extmul_low_i32x4s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2ExtmulHighI32x4S" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i64x2_extmul_high_i32x4s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2ExtmulLowI32x4U" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i64x2_extmul_low_i32x4u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2ExtmulHighI32x4U" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i64x2_extmul_high_i32x4u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
// =============================================================================
// SIMD f32x4 TESTS (Missing)
// =============================================================================
///|
test "Typecheck F32x4Ceil" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_ceil(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4Floor" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_floor(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4Trunc" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_trunc(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4Nearest" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_nearest(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4Min" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_min(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4Max" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_max(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4Pmin" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_pmin(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4Pmax" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_pmax(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
// =============================================================================
// SIMD f64x2 TESTS (Missing)
// =============================================================================
///|
test "Typecheck F64x2Ceil" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_ceil(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2Floor" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_floor(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2Trunc" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_trunc(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2Nearest" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_nearest(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2Min" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_min(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2Max" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_max(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2Pmin" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_pmin(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2Pmax" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_pmax(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
// =============================================================================
// SIMD RELAXED TESTS
// =============================================================================
///|
test "Typecheck I8x16RelaxedSwizzle" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::i8x16_relaxed_swizzle(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4RelaxedTruncF32x4S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_relaxed_trunc_f32x4s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4RelaxedTruncF32x4U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_relaxed_trunc_f32x4u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4RelaxedTruncZeroF64x2S" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_relaxed_trunc_zero_f64x2s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4RelaxedTruncZeroF64x2U" {
let env = empty_env()
let state = make_state(env, [ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i32x4_relaxed_trunc_zero_f64x2u(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4RelaxedMadd" {
let env = empty_env()
let state = make_state(env, [
ValType::v128(),
ValType::v128(),
ValType::v128(),
])
let result = Typecheck::typecheck(Instruction::f32x4_relaxed_madd(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4RelaxedNmadd" {
let env = empty_env()
let state = make_state(env, [
ValType::v128(),
ValType::v128(),
ValType::v128(),
])
let result = Typecheck::typecheck(Instruction::f32x4_relaxed_nmadd(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2RelaxedMadd" {
let env = empty_env()
let state = make_state(env, [
ValType::v128(),
ValType::v128(),
ValType::v128(),
])
let result = Typecheck::typecheck(Instruction::f64x2_relaxed_madd(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2RelaxedNmadd" {
let env = empty_env()
let state = make_state(env, [
ValType::v128(),
ValType::v128(),
ValType::v128(),
])
let result = Typecheck::typecheck(Instruction::f64x2_relaxed_nmadd(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I8x16RelaxedLaneselect" {
let env = empty_env()
let state = make_state(env, [
ValType::v128(),
ValType::v128(),
ValType::v128(),
])
let result = Typecheck::typecheck(
Instruction::i8x16_relaxed_laneselect(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8RelaxedLaneselect" {
let env = empty_env()
let state = make_state(env, [
ValType::v128(),
ValType::v128(),
ValType::v128(),
])
let result = Typecheck::typecheck(
Instruction::i16x8_relaxed_laneselect(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4RelaxedLaneselect" {
let env = empty_env()
let state = make_state(env, [
ValType::v128(),
ValType::v128(),
ValType::v128(),
])
let result = Typecheck::typecheck(
Instruction::i32x4_relaxed_laneselect(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I64x2RelaxedLaneselect" {
let env = empty_env()
let state = make_state(env, [
ValType::v128(),
ValType::v128(),
ValType::v128(),
])
let result = Typecheck::typecheck(
Instruction::i64x2_relaxed_laneselect(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4RelaxedMin" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_relaxed_min(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F32x4RelaxedMax" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f32x4_relaxed_max(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2RelaxedMin" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_relaxed_min(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck F64x2RelaxedMax" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(Instruction::f64x2_relaxed_max(), state)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8RelaxedQ15mulrS" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i16x8_relaxed_q15mulr_s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I16x8RelaxedDotI8x16I7x16S" {
let env = empty_env()
let state = make_state(env, [ValType::v128(), ValType::v128()])
let result = Typecheck::typecheck(
Instruction::i16x8_relaxed_dot_i8x16i7x16s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
test "Typecheck I32x4RelaxedDotI8x16I7x16AddS" {
let env = empty_env()
let state = make_state(env, [
ValType::v128(),
ValType::v128(),
ValType::v128(),
])
let result = Typecheck::typecheck(
Instruction::i32x4_relaxed_dot_i8x16i7x16_add_s(),
state,
)
let state = assert_ok(result)
assert_stack(state, [ValType::v128()])
}
///|
fn typecheck_array_memory_argument(
ma : MemArg,
maximum : UInt32,
) -> Result[Unit, String] {
let MemArg(U32(align), memory, U64(offset)) = ma
if memory is Some(_) {
return Err("array memory argument cannot select a linear memory")
}
if offset > 0xffffffffUL {
return Err("array memory offset exceeds u32")
}
if align > maximum {
return Err("array memory alignment exceeds access width")
}
Ok(())
}
///|
fn typecheck_array_load_alignment(op : @lib.LoadOp) -> UInt32 {
match op {
@lib.I32Load8SOp | @lib.I32Load8UOp | @lib.I64Load8SOp | @lib.I64Load8UOp =>
0U
@lib.I32Load16SOp
| @lib.I32Load16UOp
| @lib.I64Load16SOp
| @lib.I64Load16UOp => 1U
@lib.I32LoadOp | @lib.F32LoadOp | @lib.I64Load32SOp | @lib.I64Load32UOp =>
2U
@lib.I64LoadOp | @lib.F64LoadOp => 3U
@lib.V128LoadOp => 4U
_ => 0U
}
}
///|
fn typecheck_array_store_alignment(op : @lib.StoreOp) -> UInt32 {
match op {
@lib.I32Store8Op | @lib.I64Store8Op => 0U
@lib.I32Store16Op | @lib.I64Store16Op => 1U
@lib.I32StoreOp | @lib.F32StoreOp | @lib.I64Store32Op => 2U
@lib.I64StoreOp | @lib.F64StoreOp => 3U
@lib.V128StoreOp => 4U
_ => 0U
}
}
///|
fn typecheck_waitqueue_notify(st : TcState) -> TcResult {
let queue = ValType::ref_null(HeapType::shared_abs(AbsHeapType::waitqueue()))
match st.pop_types([queue, ValType::i32()]) {
Err(err) => Err(err)
Ok(next) => Ok(next.push1(ValType::i32()))
}
}
///|
fn typecheck_struct_wait(
st : TcState,
type_idx : TypeIdx,
field : U32,
) -> TcResult {
let fields = match st.env.resolve_struct_fields(type_idx) {
Err(err) => return Err(err)
Ok(fields) => fields
}
match get_struct_field(fields, field) {
Err(err) => return Err(err)
Ok(_) => ()
}
let receiver = ValType::ref_null(HeapType::new(type_idx))
let queue = ValType::ref_null(HeapType::shared_abs(AbsHeapType::waitqueue()))
match st.pop_types([receiver, queue, ValType::i32(), ValType::i64()]) {
Err(err) => Err(err)
Ok(next) => Ok(next.push1(ValType::i32()))
}
}
///|
test "expression probe preserves failure without the discarded outer diagnostic" {
let body = Instruction::block(
BlockType::void_(),
Expr::new([Instruction::nop(), Instruction::i32_add()]),
)
let direct = match
Typecheck::typecheck(body, make_state_owned(Env::new(), [])) {
Err(error) => error
Ok(_) => abort("intentionally invalid body must fail typechecking")
}
let probe = typecheck_expr_for_probe(
Expr::new([body]),
make_state_owned(Env::new(), []),
)
match probe {
Err(error) => assert_eq(error, direct)
Ok(_) => fail("intentionally invalid probe must retain the typing failure")
}
// Normal validation retains full context for user-facing diagnostics.
match
Typecheck::typecheck(Expr::new([body]), make_state_owned(Env::new(), [])) {
Err(error) => {
assert_true(error.contains("instruction[0]"))
assert_true(error.length() > direct.length())
}
Ok(_) => fail("normal validation must also reject the invalid body")
}
}
///|
test "expression probe retains stack state and first failure" {
for
body in [
[
Instruction::i32_const(@lib.I32::new(7)),
Instruction::i32_const(@lib.I32::new(9)),
Instruction::i32_add(),
],
[
Instruction::block(
BlockType::void_(),
Expr::new([Instruction::br(LabelIdx::new(0U))]),
),
Instruction::i64_const(@lib.I64::new(3L)),
],
[Instruction::unreachable_(), Instruction::i32_add()],
] {
let expected = Typecheck::typecheck(
Expr::new(body),
make_state_owned(Env::new(), []),
).unwrap()
let actual = typecheck_expr_for_probe(
Expr::new(body),
make_state_owned(Env::new(), []),
).unwrap()
assert_eq(actual.stack, expected.stack)
assert_eq(actual.reachable, expected.reachable)
assert_eq(actual.escape, expected.escape)
assert_eq(actual.reachable_escape_depths, expected.reachable_escape_depths)
assert_eq(actual.initialized_locals, expected.initialized_locals)
}
let body = Expr::new([
Instruction::i32_add(),
Instruction::local_get(LocalIdx::new(99U)),
])
let initial = make_state_owned(Env::new(), [])
let first = Typecheck::typecheck(Instruction::i32_add(), initial).unwrap_err()
assert_eq(
typecheck_expr_for_probe(body, make_state_owned(Env::new(), [])).unwrap_err(),
first,
)
}