@@ -872,7 +872,8 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
872872 let cfp = builder. use_var ( cfp_var) ;
873873 let ec = builder. use_var ( ec_var) ;
874874
875- // Save caller PC and SP
875+ // Save caller PC and SP.
876+ // Match the LIR: save SP below the recv+args that will be consumed.
876877 let pc_val = builder. ins ( ) . iconst ( cl_types:: I64 , state. pc as i64 ) ;
877878 builder. ins ( ) . store ( MemFlags :: trusted ( ) , pc_val, cfp, Offset32 :: new ( RUBY_OFFSET_CFP_PC ) ) ;
878879 let caller_sp_offset = ( ( state. stack ( ) . count ( ) - argc - 1 ) * SIZEOF_VALUE ) as i64 ;
@@ -1004,11 +1005,36 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
10041005 builder. ins ( ) . brif ( is_qundef, exit_block, & [ ] , cont_block, & [ ] ) ;
10051006
10061007 // Exit block: callee returned Qundef (side-exit or compilation failure).
1007- // ec->cfp still points to the callee frame. The callee's cfp->pc was set
1008- // during frame setup, so the interpreter can resume from there.
1009- // Just return Qundef to propagate the bail all the way up.
1008+ // Pop the callee frame and restore the caller's state so the
1009+ // interpreter can re-execute the Send instruction correctly.
10101010 builder. seal_block ( exit_block) ;
10111011 builder. switch_to_block ( exit_block) ;
1012+ // Pop callee frame: ec->cfp = callee_cfp + SIZEOF_CONTROL_FRAME
1013+ let exit_ec = builder. use_var ( ec_var) ;
1014+ let frame_sz = builder. ins ( ) . iconst ( cl_types:: I64 , RUBY_SIZEOF_CONTROL_FRAME as i64 ) ;
1015+ let exit_callee_cfp = builder. use_var ( cfp_var) ;
1016+ let exit_caller_cfp = builder. ins ( ) . iadd ( exit_callee_cfp, frame_sz) ;
1017+ builder. ins ( ) . store ( MemFlags :: trusted ( ) , exit_caller_cfp, exit_ec, Offset32 :: new ( RUBY_OFFSET_EC_CFP as i32 ) ) ;
1018+ // Save caller PC (the Send instruction)
1019+ builder. ins ( ) . store ( MemFlags :: trusted ( ) , pc_val, exit_caller_cfp, Offset32 :: new ( RUBY_OFFSET_CFP_PC ) ) ;
1020+ // Save caller SP at full stack size so the interpreter sees recv+args
1021+ let full_sp = builder. ins ( ) . iadd_imm ( sp, ( state. stack ( ) . count ( ) * SIZEOF_VALUE ) as i64 ) ;
1022+ builder. ins ( ) . store ( MemFlags :: trusted ( ) , full_sp, exit_caller_cfp, Offset32 :: new ( RUBY_OFFSET_CFP_SP ) ) ;
1023+ // Spill caller stack (recv + args)
1024+ for ( idx, & sid) in state. stack ( ) . enumerate ( ) {
1025+ if let Some ( val) = cl_opnds[ sid. 0 ] {
1026+ let offset = ( idx as i32 ) * SIZEOF_VALUE_I32 ;
1027+ builder. ins ( ) . store ( MemFlags :: trusted ( ) , val, sp, Offset32 :: new ( offset) ) ;
1028+ }
1029+ }
1030+ // Spill caller locals
1031+ for ( idx, & lid) in state. locals ( ) . enumerate ( ) {
1032+ if let Some ( val) = cl_opnds[ lid. 0 ] {
1033+ let ep_offset = local_idx_to_ep_offset ( iseq, idx) ;
1034+ let byte_offset = -( ep_offset + 1 ) * SIZEOF_VALUE_I32 ;
1035+ builder. ins ( ) . store ( MemFlags :: trusted ( ) , val, sp, Offset32 :: new ( byte_offset) ) ;
1036+ }
1037+ }
10121038 builder. ins ( ) . return_ ( & [ qundef] ) ;
10131039
10141040 // Continue block: restore CFP and SP
0 commit comments