@@ -434,16 +434,22 @@ impl X86Codegen {
434434 let has_slot = find_param_alloca ( func, i)
435435 . and_then ( |( dest, _) | self . state . get_slot ( dest. 0 ) )
436436 . is_some ( ) ;
437+ if std:: env:: var ( "CCC_DEBUG_PARAM_STORE" ) . is_ok ( ) {
438+ let reg = self . reg_assignments . get ( & paramref_dest. 0 ) ;
439+ eprintln ! ( "[PRE-STORE] param {} paramref_dest={} has_slot={} reg={:?}" , i, paramref_dest. 0 , has_slot, reg) ;
440+ }
437441 if !has_slot {
438442 if let Some ( & phys_reg) = self . reg_assignments . get ( & paramref_dest. 0 ) {
439443 // Only pre-store to callee-saved registers (PhysReg 1-5).
440444 // Caller-saved registers (rdi, rsi, r8-r11) cannot be used
441445 // because they overlap with ABI argument registers that
442446 // haven't been saved yet.
443447 let is_callee_saved = phys_reg. 0 >= 1 && phys_reg. 0 <= 5 ;
448+ if std:: env:: var ( "CCC_DEBUG_PARAM_STORE" ) . is_ok ( ) {
449+ let shared = reg_to_params. get ( & phys_reg. 0 ) . is_some_and ( |u| u. len ( ) > 1 ) ;
450+ eprintln ! ( "[PRE-STORE] callee={} shared={} class={:?}" , is_callee_saved, shared, class) ;
451+ }
444452 if is_callee_saved {
445- // Safety check: if another param's dest is also assigned
446- // to this register, skip pre-store to avoid conflicts.
447453 let shared = reg_to_params. get ( & phys_reg. 0 )
448454 . is_some_and ( |users| users. len ( ) > 1 ) ;
449455 if !shared {
@@ -452,15 +458,107 @@ impl X86Codegen {
452458 self . state . out . emit_instr_reg_reg (
453459 " movq" , X86_ARG_REGS [ reg_idx] , dest_reg) ;
454460 self . state . param_pre_stored . insert ( i) ;
455- // Track the source arg register for this callee-saved reg
456- // so register-direct call arg loading can avoid round-trips
457461 self . param_source_regs . insert ( phys_reg. 0 , X86_ARG_REGS [ reg_idx] ) ;
458462 } // TODO: handle StackSlot/SSE params
459463 }
460464 }
461465 }
462466 continue ;
463467 }
468+ } else {
469+ // DEBUG: dump entry block instructions for this param
470+ if std:: env:: var ( "CCC_DEBUG_PARAM_STORE" ) . is_ok ( ) {
471+ if let Some ( ( alloca_dest, _) ) = find_param_alloca ( func, i) {
472+ eprintln ! ( "[PARAM-STORE] param {} has alloca dest={}, no ParamRef" , i, alloca_dest. 0 ) ;
473+ eprintln ! ( "[PARAM-STORE] has_slot={}" , self . state. get_slot( alloca_dest. 0 ) . is_some( ) ) ;
474+ for ( bi, block) in func. blocks . iter ( ) . enumerate ( ) {
475+ for inst in & block. instructions {
476+ match inst {
477+ Instruction :: Store { ptr, val, .. } if ptr. 0 == alloca_dest. 0 =>
478+ eprintln ! ( "[PARAM-STORE] block[{}] Store to alloca: val={:?}" , bi, val) ,
479+ Instruction :: Load { ptr, dest, .. } if ptr. 0 == alloca_dest. 0 =>
480+ eprintln ! ( "[PARAM-STORE] block[{}] Load from alloca: dest={}" , bi, dest. 0 ) ,
481+ Instruction :: Copy { dest, src } =>
482+ eprintln ! ( "[PARAM-STORE] block[{}] Copy dest={} src={:?}" , bi, dest. 0 , src) ,
483+ Instruction :: ParamRef { dest, param_idx, .. } =>
484+ eprintln ! ( "[PARAM-STORE] block[{}] ParamRef dest={} idx={}" , bi, dest. 0 , param_idx) ,
485+ _ => { }
486+ }
487+ }
488+ }
489+ for ( & vid, & reg) in self . reg_assignments . iter ( ) {
490+ eprintln ! ( "[PARAM-STORE] reg_assign: val={} -> PhysReg({})" , vid, reg. 0 ) ;
491+ }
492+ }
493+ }
494+ // No ParamRef for this param. The alloca may have been promoted
495+ // by mem2reg, converting Store/Load chains to direct SSA references.
496+ // After promotion, the param value flows through Copy/Cast chains.
497+ //
498+ // The register allocator may have assigned a promoted value to a
499+ // callee-saved register. We must copy the ABI arg register to it
500+ // in the prologue, because ABI registers get clobbered by calls.
501+ //
502+ // Strategy: find the alloca for this param, then search for Store
503+ // instructions that write TO that alloca. The Store's source value
504+ // (which may be a Copy from the original param) tells us which SSA
505+ // value represents the parameter after store-to-load forwarding.
506+ let has_slot = find_param_alloca ( func, i)
507+ . and_then ( |( dest, _) | self . state . get_slot ( dest. 0 ) )
508+ . is_some ( ) ;
509+ if !has_slot {
510+ if let Some ( ( alloca_dest, _) ) = find_param_alloca ( func, i) {
511+ let alloca_id = alloca_dest. 0 ;
512+ // Collect all SSA values stored to this alloca, then
513+ // propagate through Copy chains to find all derived values.
514+ // Any of these that are register-assigned need the ABI arg
515+ // saved in the prologue.
516+ let mut param_vals: Vec < u32 > = Vec :: new ( ) ;
517+ for block in & func. blocks {
518+ for inst in & block. instructions {
519+ if let Instruction :: Store { ptr, val, .. } = inst {
520+ if ptr. 0 == alloca_id {
521+ if let crate :: ir:: instruction:: Operand :: Value ( v) = val {
522+ param_vals. push ( v. 0 ) ;
523+ }
524+ }
525+ }
526+ }
527+ }
528+ // Propagate through Copy chains
529+ let mut all_vals: crate :: common:: fx_hash:: FxHashSet < u32 > = param_vals. iter ( ) . copied ( ) . collect ( ) ;
530+ let mut changed_prop = true ;
531+ while changed_prop {
532+ changed_prop = false ;
533+ for block in & func. blocks {
534+ for inst in & block. instructions {
535+ if let Instruction :: Copy { dest, src : crate :: ir:: instruction:: Operand :: Value ( v) } = inst {
536+ if all_vals. contains ( & v. 0 ) && all_vals. insert ( dest. 0 ) {
537+ changed_prop = true ;
538+ }
539+ }
540+ }
541+ }
542+ }
543+ // Check if any derived value is callee-saved register-assigned
544+ for & vid in & all_vals {
545+ if let Some ( & phys_reg) = self . reg_assignments . get ( & vid) {
546+ let is_callee_saved = phys_reg. 0 >= 1 && phys_reg. 0 <= 5 ;
547+ if is_callee_saved {
548+ let dest_reg = phys_reg_name ( phys_reg) ;
549+ if let ParamClass :: IntReg { reg_idx } = class {
550+ self . state . out . emit_instr_reg_reg (
551+ " movq" , X86_ARG_REGS [ reg_idx] , dest_reg) ;
552+ self . state . param_pre_stored . insert ( i) ;
553+ self . param_source_regs . insert ( phys_reg. 0 , X86_ARG_REGS [ reg_idx] ) ;
554+ break ;
555+ }
556+ }
557+ }
558+ }
559+ }
560+ continue ;
561+ }
464562 }
465563
466564 let ( slot, ty) = if let Some ( ( dest, ty) ) = find_param_alloca ( func, i) {
0 commit comments