Skip to content

Commit c193748

Browse files
ZJIT: Add a simple HIR validator (ruby#13780)
This PR adds a simple validator for ZJIT's HIR. See issue #591
1 parent 0bb44f2 commit c193748

2 files changed

Lines changed: 131 additions & 0 deletions

File tree

zjit/src/codegen.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,10 @@ fn compile_iseq(iseq: IseqPtr) -> Option<Function> {
10851085
}
10861086
};
10871087
function.optimize();
1088+
if let Err(err) = function.validate() {
1089+
debug!("ZJIT: compile_iseq: {err:?}");
1090+
return None;
1091+
}
10881092
Some(function)
10891093
}
10901094

zjit/src/hir.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,17 @@ impl<T: Copy + Into<usize> + PartialEq> UnionFind<T> {
900900
}
901901
}
902902

903+
#[derive(Debug, PartialEq)]
904+
pub enum ValidationError {
905+
// All validation errors come with the function's representation as the first argument.
906+
BlockHasNoTerminator(String, BlockId),
907+
// The terminator and its actual position
908+
TerminatorNotAtEnd(String, BlockId, InsnId, usize),
909+
/// Expected length, actual length
910+
MismatchedBlockArity(String, BlockId, usize, usize),
911+
}
912+
913+
903914
/// A [`Function`], which is analogous to a Ruby ISeq, is a control-flow graph of [`Block`]s
904915
/// containing instructions.
905916
#[derive(Debug)]
@@ -2005,6 +2016,51 @@ impl Function {
20052016
None => {},
20062017
}
20072018
}
2019+
2020+
2021+
/// Validates the following:
2022+
/// 1. Basic block jump args match parameter arity.
2023+
/// 2. Every terminator must be in the last position.
2024+
/// 3. Every block must have a terminator.
2025+
fn validate_block_terminators_and_jumps(&self) -> Result<(), ValidationError> {
2026+
for block_id in self.rpo() {
2027+
let mut block_has_terminator = false;
2028+
let insns = &self.blocks[block_id.0].insns;
2029+
for (idx, insn_id) in insns.iter().enumerate() {
2030+
let insn = self.find(*insn_id);
2031+
match &insn {
2032+
Insn::Jump(BranchEdge{target, args})
2033+
| Insn::IfTrue { val: _, target: BranchEdge{target, args} }
2034+
| Insn::IfFalse { val: _, target: BranchEdge{target, args}} => {
2035+
let target_block = &self.blocks[target.0];
2036+
let target_len = target_block.params.len();
2037+
let args_len = args.len();
2038+
if target_len != args_len {
2039+
return Err(ValidationError::MismatchedBlockArity(format!("{:?}", self), block_id, target_len, args_len))
2040+
}
2041+
}
2042+
_ => {}
2043+
}
2044+
if !insn.is_terminator() {
2045+
continue;
2046+
}
2047+
block_has_terminator = true;
2048+
if idx != insns.len() - 1 {
2049+
return Err(ValidationError::TerminatorNotAtEnd(format!("{:?}", self), block_id, *insn_id, idx));
2050+
}
2051+
}
2052+
if !block_has_terminator {
2053+
return Err(ValidationError::BlockHasNoTerminator(format!("{:?}", self), block_id));
2054+
}
2055+
}
2056+
Ok(())
2057+
}
2058+
2059+
/// Run all validation passes we have.
2060+
pub fn validate(&self) -> Result<(), ValidationError> {
2061+
self.validate_block_terminators_and_jumps()?;
2062+
Ok(())
2063+
}
20082064
}
20092065

20102066
impl<'a> std::fmt::Display for FunctionPrinter<'a> {
@@ -2241,6 +2297,7 @@ pub enum ParseError {
22412297
StackUnderflow(FrameState),
22422298
UnknownParameterType(ParameterType),
22432299
MalformedIseq(u32), // insn_idx into iseq_encoded
2300+
Validation(ValidationError),
22442301
}
22452302

22462303
/// Return the number of locals in the current ISEQ (includes parameters)
@@ -2966,6 +3023,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
29663023
}
29673024

29683025
fun.profiles = Some(profiles);
3026+
if let Err(e) = fun.validate() {
3027+
return Err(ParseError::Validation(e));
3028+
}
29693029
Ok(fun)
29703030
}
29713031

@@ -3069,6 +3129,72 @@ mod rpo_tests {
30693129
}
30703130
}
30713131

3132+
#[cfg(test)]
3133+
mod validation_tests {
3134+
use super::*;
3135+
3136+
#[track_caller]
3137+
fn assert_matches_err(res: Result<(), ValidationError>, expected: ValidationError) {
3138+
match res {
3139+
Err(validation_err) => {
3140+
assert_eq!(validation_err, expected);
3141+
}
3142+
Ok(_) => assert!(false, "Expected validation error"),
3143+
}
3144+
}
3145+
3146+
#[test]
3147+
fn one_block_no_terminator() {
3148+
let mut function = Function::new(std::ptr::null());
3149+
let entry = function.entry_block;
3150+
let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
3151+
assert_matches_err(function.validate(), ValidationError::BlockHasNoTerminator(format!("{:?}", function), entry));
3152+
}
3153+
3154+
#[test]
3155+
fn one_block_terminator_not_at_end() {
3156+
let mut function = Function::new(std::ptr::null());
3157+
let entry = function.entry_block;
3158+
let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
3159+
let insn_id = function.push_insn(entry, Insn::Return { val });
3160+
function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
3161+
assert_matches_err(function.validate(), ValidationError::TerminatorNotAtEnd(format!("{:?}", function), entry, insn_id, 1));
3162+
}
3163+
3164+
#[test]
3165+
fn iftrue_mismatch_args() {
3166+
let mut function = Function::new(std::ptr::null());
3167+
let entry = function.entry_block;
3168+
let side = function.new_block();
3169+
let exit = function.new_block();
3170+
let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
3171+
function.push_insn(entry, Insn::IfTrue { val, target: BranchEdge { target: side, args: vec![val, val, val] } });
3172+
assert_matches_err(function.validate(), ValidationError::MismatchedBlockArity(format!("{:?}", function), entry, 0, 3));
3173+
}
3174+
3175+
#[test]
3176+
fn iffalse_mismatch_args() {
3177+
let mut function = Function::new(std::ptr::null());
3178+
let entry = function.entry_block;
3179+
let side = function.new_block();
3180+
let exit = function.new_block();
3181+
let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
3182+
function.push_insn(entry, Insn::IfFalse { val, target: BranchEdge { target: side, args: vec![val, val, val] } });
3183+
assert_matches_err(function.validate(), ValidationError::MismatchedBlockArity(format!("{:?}", function), entry, 0, 3));
3184+
}
3185+
3186+
#[test]
3187+
fn jump_mismatch_args() {
3188+
let mut function = Function::new(std::ptr::null());
3189+
let entry = function.entry_block;
3190+
let side = function.new_block();
3191+
let exit = function.new_block();
3192+
let val = function.push_insn(entry, Insn::Const { val: Const::Value(Qnil) });
3193+
function.push_insn(entry, Insn::Jump ( BranchEdge { target: side, args: vec![val, val, val] } ));
3194+
assert_matches_err(function.validate(), ValidationError::MismatchedBlockArity(format!("{:?}", function), entry, 0, 3));
3195+
}
3196+
}
3197+
30723198
#[cfg(test)]
30733199
mod infer_tests {
30743200
use super::*;
@@ -4701,6 +4827,7 @@ mod opt_tests {
47014827
unsafe { crate::cruby::rb_zjit_profile_disable(iseq) };
47024828
let mut function = iseq_to_hir(iseq).unwrap();
47034829
function.optimize();
4830+
function.validate().unwrap();
47044831
assert_function_hir(function, hir);
47054832
}
47064833

0 commit comments

Comments
 (0)