@@ -488,6 +488,8 @@ pub enum Insn {
488488
489489 /// Control flow instructions
490490 Return { val : InsnId } ,
491+ /// Non-local control flow. See the throw YARV instruction
492+ Throw { throw_state : u32 , val : InsnId } ,
491493
492494 /// Fixnum +, -, *, /, %, ==, !=, <, <=, >, >=
493495 FixnumAdd { left : InsnId , right : InsnId , state : InsnId } ,
@@ -527,15 +529,15 @@ impl Insn {
527529 | Insn :: IfTrue { .. } | Insn :: IfFalse { .. } | Insn :: Return { .. }
528530 | Insn :: PatchPoint { .. } | Insn :: SetIvar { .. } | Insn :: ArrayExtend { .. }
529531 | Insn :: ArrayPush { .. } | Insn :: SideExit { .. } | Insn :: SetGlobal { .. }
530- | Insn :: SetLocal { .. } => false ,
532+ | Insn :: SetLocal { .. } | Insn :: Throw { .. } => false ,
531533 _ => true ,
532534 }
533535 }
534536
535537 /// Return true if the instruction ends a basic block and false otherwise.
536538 pub fn is_terminator ( & self ) -> bool {
537539 match self {
538- Insn :: Jump ( _) | Insn :: Return { .. } | Insn :: SideExit { .. } => true ,
540+ Insn :: Jump ( _) | Insn :: Return { .. } | Insn :: SideExit { .. } | Insn :: Throw { .. } => true ,
539541 _ => false ,
540542 }
541543 }
@@ -713,8 +715,25 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
713715 Insn :: ObjToString { val, .. } => { write ! ( f, "ObjToString {val}" ) } ,
714716 Insn :: AnyToString { val, str, .. } => { write ! ( f, "AnyToString {val}, str: {str}" ) } ,
715717 Insn :: SideExit { .. } => write ! ( f, "SideExit" ) ,
716- Insn :: PutSpecialObject { value_type } => {
717- write ! ( f, "PutSpecialObject {}" , value_type)
718+ Insn :: PutSpecialObject { value_type } => write ! ( f, "PutSpecialObject {value_type}" ) ,
719+ Insn :: Throw { throw_state, val } => {
720+ let mut state_string = match throw_state & VM_THROW_STATE_MASK {
721+ RUBY_TAG_NONE => "TAG_NONE" . to_string ( ) ,
722+ RUBY_TAG_RETURN => "TAG_RETURN" . to_string ( ) ,
723+ RUBY_TAG_BREAK => "TAG_BREAK" . to_string ( ) ,
724+ RUBY_TAG_NEXT => "TAG_NEXT" . to_string ( ) ,
725+ RUBY_TAG_RETRY => "TAG_RETRY" . to_string ( ) ,
726+ RUBY_TAG_REDO => "TAG_REDO" . to_string ( ) ,
727+ RUBY_TAG_RAISE => "TAG_RAISE" . to_string ( ) ,
728+ RUBY_TAG_THROW => "TAG_THROW" . to_string ( ) ,
729+ RUBY_TAG_FATAL => "TAG_FATAL" . to_string ( ) ,
730+ tag => format ! ( "{tag}" )
731+ } ;
732+ if throw_state & VM_THROW_NO_ESCAPE_FLAG != 0 {
733+ use std:: fmt:: Write ;
734+ write ! ( state_string, "|NO_ESCAPE" ) ?;
735+ }
736+ write ! ( f, "Throw {state_string}, {val}" )
718737 }
719738 insn => { write ! ( f, "{insn:?}" ) }
720739 }
@@ -1015,6 +1034,7 @@ impl Function {
10151034 }
10161035 } ,
10171036 Return { val } => Return { val : find ! ( * val) } ,
1037+ & Throw { throw_state, val } => Throw { throw_state, val : find ! ( val) } ,
10181038 StringCopy { val, chilled } => StringCopy { val : find ! ( * val) , chilled : * chilled } ,
10191039 StringIntern { val } => StringIntern { val : find ! ( * val) } ,
10201040 Test { val } => Test { val : find ! ( * val) } ,
@@ -1119,7 +1139,7 @@ impl Function {
11191139 match & self . insns [ insn. 0 ] {
11201140 Insn :: Param { .. } => unimplemented ! ( "params should not be present in block.insns" ) ,
11211141 Insn :: SetGlobal { .. } | Insn :: ArraySet { .. } | Insn :: Snapshot { .. } | Insn :: Jump ( _)
1122- | Insn :: IfTrue { .. } | Insn :: IfFalse { .. } | Insn :: Return { .. }
1142+ | Insn :: IfTrue { .. } | Insn :: IfFalse { .. } | Insn :: Return { .. } | Insn :: Throw { .. }
11231143 | Insn :: PatchPoint { .. } | Insn :: SetIvar { .. } | Insn :: ArrayExtend { .. }
11241144 | Insn :: ArrayPush { .. } | Insn :: SideExit { .. } | Insn :: SetLocal { .. } =>
11251145 panic ! ( "Cannot infer type of instruction with no output: {}" , self . insns[ insn. 0 ] ) ,
@@ -1755,6 +1775,7 @@ impl Function {
17551775 Insn :: StringCopy { val, .. }
17561776 | Insn :: StringIntern { val }
17571777 | Insn :: Return { val }
1778+ | Insn :: Throw { val, .. }
17581779 | Insn :: Defined { v : val, .. }
17591780 | Insn :: Test { val }
17601781 | Insn :: SetLocal { val, .. }
@@ -2710,6 +2731,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
27102731 fun. push_insn ( block, Insn :: Return { val : state. stack_pop ( ) ? } ) ;
27112732 break ; // Don't enqueue the next block as a successor
27122733 }
2734+ YARVINSN_throw => {
2735+ fun. push_insn ( block, Insn :: Throw { throw_state : get_arg ( pc, 0 ) . as_u32 ( ) , val : state. stack_pop ( ) ? } ) ;
2736+ break ; // Don't enqueue the next block as a successor
2737+ }
27132738
27142739 // These are opt_send_without_block and all the opt_* instructions
27152740 // specialized to a certain method that could also be serviced
@@ -4620,6 +4645,26 @@ mod tests {
46204645 SideExit
46214646 "# ] ] ) ;
46224647 }
4648+
4649+ #[ test]
4650+ fn throw ( ) {
4651+ eval ( "
4652+ define_method(:throw_return) { return 1 }
4653+ define_method(:throw_break) { break 2 }
4654+ " ) ;
4655+ assert_method_hir_with_opcode ( "throw_return" , YARVINSN_throw , expect ! [ [ r#"
4656+ fn block in <compiled>:
4657+ bb0(v0:BasicObject):
4658+ v2:Fixnum[1] = Const Value(1)
4659+ Throw TAG_RETURN, v2
4660+ "# ] ] ) ;
4661+ assert_method_hir_with_opcode ( "throw_break" , YARVINSN_throw , expect ! [ [ r#"
4662+ fn block in <compiled>:
4663+ bb0(v0:BasicObject):
4664+ v2:Fixnum[2] = Const Value(2)
4665+ Throw TAG_BREAK, v2
4666+ "# ] ] ) ;
4667+ }
46234668}
46244669
46254670#[ cfg( test) ]
0 commit comments