Skip to content

Commit

Permalink
Validate br, br_if, br_table, return, and loop (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
jblebrun authored Jan 19, 2024
1 parent 2a2cf56 commit daf2cd6
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 3 deletions.
28 changes: 27 additions & 1 deletion tests-integration/tests/validation/data/validation.wat
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,30 @@
(if (result i32) (local.get 0) (then (i32.const 7)) (else (i32.const 8))))
)
(assert_return (invoke "simple-if" (i32.const 0)) (i32.const 8))
(assert_return (invoke "simple-if" (i32.const 1)) (i32.const 7))
(assert_return (invoke "simple-if" (i32.const 1)) (i32.const 7))

(module (func (export "type-i32-value") (result i32)
(block (result i32) (i32.ctz (br 0 (i32.const 1))))
))
(assert_return (invoke "type-i32-value") (i32.const 1))

(module (func (export "as-loop-first") (result i32)
(block (result i32) (loop (result i32) (br 1 (i32.const 3)) (i32.const 2)))
))

(assert_return (invoke "as-loop-first") (i32.const 3))

(module (func (export "type-i32-value") (result i32)
(block (result i32) (i32.ctz (br_if 0 (i32.const 1) (i32.const 1))))
))

(func (export "singleton") (param i32) (result i32)
(block
(block
(br_table 1 0 (local.get 0))
(return (i32.const 21))
)
(return (i32.const 20))
)
(i32.const 22)
)
19 changes: 18 additions & 1 deletion wrausmt-format/src/compiler/validation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use {
wrausmt_common::true_or::TrueOr,
wrausmt_runtime::{
instructions::opcodes,
syntax::{types::ValueType, Index, LocalIndex, Module, Opcode, Resolved, UncompiledExpr},
syntax::{
types::ValueType, Index, LabelIndex, LocalIndex, Module, Opcode, Resolved,
UncompiledExpr,
},
},
};

Expand All @@ -27,6 +30,8 @@ pub enum ValidationError {
UnknownOpcode(Opcode),
OpcodeMismatch,
OperandsMismatch,
LabelOutOfRange,
BreakTypeMismatch,
}

/// How to treat Validator issues.
Expand Down Expand Up @@ -190,6 +195,18 @@ impl<'a> Validation<'a> {
Ok(frame)
}

fn label_types(&self, label: &Index<Resolved, LabelIndex>) -> Result<Vec<ValueType>> {
let frame = self
.ctrl_stack
.get(self.ctrl_stack.len() - 1 - label.value() as usize)
.ok_or(ValidationError::LabelOutOfRange)?;
Ok(if frame.opcode == opcodes::LOOP {
frame.start_types.clone()
} else {
frame.end_types.clone()
})
}

fn unreachable(&mut self) -> Result<()> {
let frame = self
.ctrl_stack
Expand Down
50 changes: 49 additions & 1 deletion wrausmt-format/src/compiler/validation/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use {
instructions::opcodes,
syntax::{
types::{NumType, ValueType},
Index, Instruction, LocalIndex, Operands, Resolved, TypeUse,
Index, Instruction, LabelIndex, LocalIndex, Operands, Resolved, TypeUse,
},
},
};
Expand Down Expand Up @@ -102,6 +102,13 @@ impl<'a> Validation<'a> {
Ok(())
}

instr!(opcodes::LOOP => Operands::Block(_, typeuse, ..)) => {
let (start_types, end_types) = self.start_and_end_types_for_typeuse(typeuse);
self.pop_vals(&start_types)?;
self.push_ctrl(opcodes::LOOP, start_types, end_types);
Ok(())
}

instr!(opcodes::IF => Operands::If(_, typeuse, ..)) => {
let (start_types, end_types) = self.start_and_end_types_for_typeuse(typeuse);
self.pop_expect(I32)?;
Expand All @@ -123,6 +130,47 @@ impl<'a> Validation<'a> {
Ok(())
}

instr!(opcodes::BR => Operands::LabelIndex(idx)) => {
self.pop_vals(&self.label_types(idx)?)?;
self.unreachable()?;
Ok(())
}

instr!(opcodes::BR_IF => Operands::LabelIndex(idx)) => {
let break_types = self.label_types(idx)?;
self.pop_expect(I32)?;
self.pop_vals(&break_types)?;
self.push_vals(&break_types);
self.unreachable()?;
Ok(())
}

instr!(opcodes::BR_TABLE => Operands::BrTable(idxes, last)) => {
self.pop_expect(I32)?;
let default_types = self.label_types(last)?;
for idx in idxes {
let break_types = self.label_types(idx)?;
(break_types.len() == default_types.len())
.true_or(ValidationError::BreakTypeMismatch)?;
self.pop_vals(&break_types)?;
self.push_vals(&break_types);
}
self.pop_vals(&default_types)?;
self.unreachable()?;
Ok(())
}

// The return instruction is a shortcut for an unconditional branch
// to the outermost block, which implicitly is the body of the
// current function.
instr!(opcodes::RETURN) => {
let idx: Index<Resolved, LabelIndex> =
Index::unnamed((self.ctrl_stack.len() - 1) as u32);
self.pop_vals(&self.label_types(&idx)?)?;
self.unreachable()?;
Ok(())
}

// 0x20
instr!(opcodes::LOCAL_GET => Operands::LocalIndex(idx)) => {
self.push_val(self.local_type(idx)?);
Expand Down

0 comments on commit daf2cd6

Please sign in to comment.