@@ -414,10 +414,18 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
414414 }
415415 }
416416
417+ // Track the last snapshot for each block — used for SendDirect exit
418+ let mut last_snapshot_id: Option < InsnId > = None ;
419+
417420 // Compile all instructions in this block
418421 for & insn_id in block. insns ( ) {
419422 let insn = function. find ( insn_id) ;
420423
424+ // Track snapshots for SendDirect exit
425+ if matches ! ( insn, Insn :: Snapshot { .. } ) {
426+ last_snapshot_id = Some ( insn_id) ;
427+ }
428+
421429 // Helper to get a previously compiled operand
422430 macro_rules! cl_opnd {
423431 ( $id: expr) => {
@@ -1005,35 +1013,43 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
10051013 builder. ins ( ) . brif ( is_qundef, exit_block, & [ ] , cont_block, & [ ] ) ;
10061014
10071015 // Exit block: callee returned Qundef (side-exit or compilation failure).
1008- // Pop the callee frame and restore the caller's state so the
1009- // interpreter can re-execute the Send instruction correctly .
1016+ // Pop callee frame and use the LAST SNAPSHOT state (not the Send's
1017+ // synthesized state which includes kwargs the interpreter doesn't expect) .
10101018 builder. seal_block ( exit_block) ;
10111019 builder. switch_to_block ( exit_block) ;
1020+
10121021 // Pop callee frame: ec->cfp = callee_cfp + SIZEOF_CONTROL_FRAME
10131022 let exit_ec = builder. use_var ( ec_var) ;
10141023 let frame_sz = builder. ins ( ) . iconst ( cl_types:: I64 , RUBY_SIZEOF_CONTROL_FRAME as i64 ) ;
10151024 let exit_callee_cfp = builder. use_var ( cfp_var) ;
10161025 let exit_caller_cfp = builder. ins ( ) . iadd ( exit_callee_cfp, frame_sz) ;
10171026 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) ) ;
1027+
1028+ // Use the last snapshot's state for the side exit — this has the
1029+ // original stack before kwargs were synthesized by SendDirect
1030+ if let Some ( snap_id) = last_snapshot_id {
1031+ if let Insn :: Snapshot { state : snap_state } = function. find ( snap_id) {
1032+ let snap_pc = builder. ins ( ) . iconst ( cl_types:: I64 , snap_state. pc as i64 ) ;
1033+ builder. ins ( ) . store ( MemFlags :: trusted ( ) , snap_pc, exit_caller_cfp, Offset32 :: new ( RUBY_OFFSET_CFP_PC ) ) ;
1034+ let snap_sp = builder. ins ( ) . iadd_imm ( sp, ( snap_state. stack ( ) . count ( ) * SIZEOF_VALUE ) as i64 ) ;
1035+ builder. ins ( ) . store ( MemFlags :: trusted ( ) , snap_sp, exit_caller_cfp, Offset32 :: new ( RUBY_OFFSET_CFP_SP ) ) ;
1036+ for ( idx, & sid) in snap_state. stack ( ) . enumerate ( ) {
1037+ if let Some ( val) = cl_opnds[ sid. 0 ] {
1038+ let offset = ( idx as i32 ) * SIZEOF_VALUE_I32 ;
1039+ builder. ins ( ) . store ( MemFlags :: trusted ( ) , val, sp, Offset32 :: new ( offset) ) ;
1040+ }
1041+ }
1042+ for ( idx, & lid) in snap_state. locals ( ) . enumerate ( ) {
1043+ if let Some ( val) = cl_opnds[ lid. 0 ] {
1044+ let ep_offset = local_idx_to_ep_offset ( iseq, idx) ;
1045+ let byte_offset = -( ep_offset + 1 ) * SIZEOF_VALUE_I32 ;
1046+ builder. ins ( ) . store ( MemFlags :: trusted ( ) , val, sp, Offset32 :: new ( byte_offset) ) ;
1047+ }
1048+ }
10361049 }
1050+ } else {
1051+ // No snapshot — just save PC
1052+ builder. ins ( ) . store ( MemFlags :: trusted ( ) , pc_val, exit_caller_cfp, Offset32 :: new ( RUBY_OFFSET_CFP_PC ) ) ;
10371053 }
10381054 builder. ins ( ) . return_ ( & [ qundef] ) ;
10391055
0 commit comments