Skip to content

Use assignment instead of destructuring to avoid invalidations#60807

Closed
JamesWrigley wants to merge 1 commit intoJuliaLang:masterfrom
JamesWrigley:compiler-invalidations
Closed

Use assignment instead of destructuring to avoid invalidations#60807
JamesWrigley wants to merge 1 commit intoJuliaLang:masterfrom
JamesWrigley:compiler-invalidations

Conversation

@JamesWrigley
Copy link
Member

@JamesWrigley JamesWrigley commented Jan 24, 2026

For some reason destructuring caused the argtypes variable to be boxed, which led to everything else using it being inferred as Any, making the code vulnerable to invalidations.

Tested on 1.12 because Cthulhu doesn't work on nightly ATM. Mostly fixes these invalidations from DimensionalData:

 inserting axes(r::DimensionalData.Dimensions.DimUnitRange) @ DimensionalData.Dimensions ~/git/DimensionalData.jl/src/Dimensions/dimunitrange.jl:38 invalidated:                                                                               
   backedges:  1: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{UInt8}) (3 children)                                                                                                             
               2: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Compiler.MethodMatchInfo}) (7 children)                                                                                          
               3: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Compiler.ApplyCallInfo}) (7 children)                                                                                            
               4: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Union{Nothing, Compiler.ConstResult}}) (7 children)                                                                              
               5: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Union{Nothing, Compiler.AbstractIterationInfo}}) (7 children)                                                                    
               6: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Compiler.CallMeta}) (7 children)                                                                                                 
               7: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Compiler.NewPhiCNode2}) (7 children)                                                                                             
               8: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Tuple{LineNumberNode, Expr}}) (7 children)                                                                                       
               9: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{String}) (24 children)                                                                                                           
              10: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Any}) (273 children)                 

Which went down to:

 inserting axes(r::DimensionalData.Dimensions.DimUnitRange) @ DimensionalData.Dimensions /gpfs/exfel/data/scratch/wrigleyj/julia-depot/packages/DimensionalData/9wtiE/src/Dimensions/dimunitrange.jl:38 invalidated:
   backedges:  1: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Compiler.MethodMatchInfo}) (3 children)
               2: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Compiler.ApplyCallInfo}) (3 children)
               3: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Compiler.CallMeta}) (3 children)
               4: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Compiler.NewPhiCNode2}) (3 children)
               5: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{UInt8}) (3 children)
               6: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{String}) (3 children)
               7: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Tuple{LineNumberNode, Expr}}) (3 children)
               8: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Any}) (4 children)
               9: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Union{Nothing, Compiler.AbstractIterationInfo}}) (4 children)
              10: superseding axes(A) @ Base abstractarray.jl:96 with MethodInstance for axes(::AbstractRange{Union{Nothing, Compiler.ConstResult}}) (4 children)
Typed code from Cthulhu without the patch
abstract_call_known(interp::Compiler.AbstractInterpreter, f, arginfo::Compiler.ArgInfo, si::Compiler.StmtInfo, sv::Union{Compiler.IRInterpretationState, Compiler.InferenceState}, max_methods::Int64) @ Compiler ~/.julia/juliaup/julia-1.12.4+0.x64.linux.gnu/share/julia/Compiler/src/abstractinterpretation.jl:2641                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
2641 function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f::Any),                                                                          
2642         arginfo::ArgInfo, si::StmtInfo, sv::AbsIntState,                                                                                                  
2643         max_methods::Int = get_max_methods(interp, f, sv))                                                                                                                                                                                                                                                                
2644     (; fargs::Union{Nothing, Vector{Any}}, argtypes::Core.Box) = arginfo                                                                                                                                                                                                                                                  
2645     la::Any = length(argtypes)::Any                                                                                                                       
2646     𝕃ᵢ = typeinf_lattice(interp)                                                                                                                          
2647     if isa(f::Any, Builtin)                                                                                                                               
2648         if f::Any === _apply_iterate                                                                                                                                                                                                                                                                                      
2649             return abstract_apply(interp, argtypes, si, sv, max_methods)                                                                                  
2650         elseif f::Any === invoke                                                                                                                                                                                                          
2651             return abstract_invoke(interp, arginfo, si, sv)                                                                                               
2652         elseif f::Any === modifyfield! || f::Any === Core.modifyglobal! ||                                                                                
2653                f::Any === Core.memoryrefmodify! || f::Any === atomic_pointermodify                                                                        
2654             return abstract_modifyop!(interp, f::Any, argtypes, si, sv)                                                                                                                                                                                                                                                   
2655         elseif f::Any === Core.finalizer                                                                                                                  
2656             return abstract_finalizer(interp, argtypes, sv)                                                                                               
2657         elseif f::Any === applicable                                                                                                                      
2658             return abstract_applicable(interp, argtypes, sv, max_methods)                                                                                 
2659         elseif f::Any === throw                                                                                                                           
2660             return abstract_throw(interp, argtypes, sv)                                                                                                   
2661         elseif f::Any === Core.throw_methoderror                                                                                                          
2662             return abstract_throw_methoderror(interp, argtypes, sv)                                                                                       
2663         elseif f::Any === Core.getglobal                                                                                                                  
2664             return Future(abstract_eval_getglobal(interp, sv, si.saw_latestworld, argtypes)::Any)                                                                                                                                                                                                                         
2665         elseif f::Any === Core.setglobal!                                                                                                                                                                                                 
2666             return Future(abstract_eval_setglobal!(interp, sv, si.saw_latestworld, argtypes)::Any)                                                        
2667         elseif f::Any === Core.swapglobal!                                                                                                                
2668             return Future(abstract_eval_swapglobal!(interp, sv, si.saw_latestworld, argtypes)::Any)                                                       
2669         elseif f::Any === Core.setglobalonce!                                                                                                             
2670             return Future(abstract_eval_setglobalonce!(interp, sv, si.saw_latestworld, argtypes)::Any)                                                                                                                                    
2671         elseif f::Any === Core.replaceglobal!                                                                                                             
2672             return Future(abstract_eval_replaceglobal!(interp, sv, si.saw_latestworld, argtypes)::Any)                                                    
2673         elseif f::Any === Core.getfield && argtypes_are_actually_getglobal(argtypes)                                                                      
2674             return Future(abstract_eval_getglobal(interp, sv, si.saw_latestworld, argtypes)::Any)                                                         
2675         elseif f::Any === Core.isdefined && argtypes_are_actually_getglobal(argtypes)::Any                                                                
2676             return Future(abstract_eval_isdefinedglobal(interp, argtypes[2], argtypes[3], Const(true),                                                                                                                                                                                                                    
2677                 (length(argtypes) == 4)::Any ? argtypes[4] : Const(:unordered),                                                                           
2678                 si.saw_latestworld, sv))                                                                                                                                                                                                                                                                                  
2679         elseif f::Any === Core.isdefinedglobal                                                                                                            
2680             return Future(abstract_eval_isdefinedglobal(interp, sv, si.saw_latestworld, argtypes)::Any)                                                   
2681         elseif f::Any === Core.get_binding_type                                                                                                           
2682             return Future(abstract_eval_get_binding_type(interp, sv, argtypes)::Any)                                                                      
2683         end                                                                                                                                               
2684         rt::Any = abstract_call_builtin(interp, f::Any, arginfo, sv)::Any                                                                                 
2685         ft::Any = popfirst!(argtypes)::Any                                                                                                                
2686         effects::Core.Box = builtin_effects(𝕃ᵢ, f::Any, argtypes, rt::Any)                                                                                                                                                                
2687         if effects.nothrow::Any                                                                                                                           
2688             exct::Core.Box = Union{}                                                                                                                                                                                                                                                                                      
2689         else                                                                                                                                              
2690             exct::Core.Box = builtin_exct(𝕃ᵢ, f::Any, argtypes, rt::Any)::Type                                                                            
2691         end                                                                                                                                               
2692         pushfirst!(argtypes, ft::Any)::Any                                                                                                                
2693         refinements = nothing                                                                                                                                                                                                                                                                                             
2694         if sv isa InferenceState && f === typeassert                                                                                                                                                                                                                                                                      
2695             # perform very limited back-propagation of invariants after this type assertion                                                                                                                                                                                                                               
2696             if rt !== Bottom && isa(fargs, Vector{Any})                                                                                                                                                                                                                                                                   
2697                 farg2 = ssa_def_slot(fargs[2], sv)                                                                                                                                                                                                                                                                        
2698                 if farg2 isa SlotNumber                                                                                                                                                                                                                                                                                   
2699                     refinements = SlotRefinement(farg2, rt)                                                                                                                                                                                                                                                               
2700                 end                                                                                                                                                                                                                                                                                                       
2701             end                                                                                                                                           
2702         end                                                                                                                                               
2703         return Future(CallMeta(rt::Any, exct, effects, NoCallInfo(), refinements))                                                                                                                                                        
2704     elseif isa(f::Any, Core.OpaqueClosure)                                                                                                                                                                                                
2705         # calling an OpaqueClosure about which we have no information returns no information                                                                                                                                              
2706         return Future(CallMeta(typeof(f::Any)::Type{Core.OpaqueClosure{A, R}} where {A, R}.parameters[2]::Any, Any, Effects(), NoCallInfo()))                                                                                             
2707     elseif f::Any === TypeVar && !isvarargtype(argtypes[end]::Any)                                                                                                                                                                        
2708         # Manually look through the definition of TypeVar to                                                                                                                                                                              
2709         # make sure to be able to get `PartialTypeVar`s out.                                                                                                                                                                              
2710         2  la::Any  4::Any || return Future(CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()))                                                                                                                                        
2711         # make sure generic code is prepared for inlining if needed later                                                                                                                                                                 
2712         let T = Any[Type{TypeVar}, Any, Any, Any]                                                                                                                                                                                         
2713             resize!(T, la::Any)                                                                                                                                                                                                           
2714             atype::Type = Tuple{T...}::Type                                                                                                                                                                                               
2715             T[1] = Const(TypeVar)                                                                                                                                                                                                         
2716             let call = abstract_call_gf_by_type(interp, f::Any, ArgInfo(nothing, T), si, atype::Type, sv, max_methods)::Future                                      
2717                 return Future{CallMeta}(call, interp, sv) do call, interp, sv                                                                                                                                                             
2718                     n = argtypes[2]                                                                                                                                                                                                       
2719                     ub_var = Const(Any)                                                                                                                                                                                                   
2720                     lb_var = Const(Union{})                                                                                                                                                                                               
2721                     if la == 4                                                                                                                                                                                                            
2722                         ub_var = argtypes[4]                                                                                                                                                                                              
2723                         lb_var = argtypes[3]                                                                                                                                                                                              
2724                     elseif la == 3                                                                                                                                                                                                        
2725                         ub_var = argtypes[3]                                                                                                                                                                                              
2726                     end                                                                                                                                                                                                                   
2727                     pT = typevar_tfunc(𝕃ᵢ, n, lb_var, ub_var)                                                                                                                                                                             
2728                     typevar_argtypes = Any[n, lb_var, ub_var]                                                                                                                                                                             
2729                     effects = builtin_effects(𝕃ᵢ, Core._typevar, typevar_argtypes, pT)                                                                                                                                                    
2730                     if effects.nothrow                                                                                                                                                                                                    
2731                         exct = Union{}                                                                                                                                                                                                    
2732                     else                                                                                                                                                                                                                  
2733                         exct = builtin_exct(𝕃ᵢ, Core._typevar, typevar_argtypes, pT)                                                                                                                                                      
2734                     end                                                                                                                                                                                                                   
2735                     return CallMeta(pT, exct, effects, call.info)                                                                                                                                                                         
2736                 end                                                                                                                                                                                                                       
2737             end                                                                                                                                                                                                                           
2738         end                                                                                                                                                                                                                               
2739     elseif f::Any === UnionAll                                                                                                                                                                                                            
2740         let call = abstract_call_gf_by_type(interp, f::Any, ArgInfo(nothing, Any[Const(UnionAll), Any, Any]), si, Tuple{Type{UnionAll}, Any, Any}, sv, max_methods)::Future
2741             return Future{CallMeta}(call, interp, sv) do call, interp, sv                                                                                                                                                                 
2742                 return abstract_call_unionall(interp, argtypes, call)                                                                                                                                                                     
2743             end                                                                                                                                                                                                                           
2744         end                                                                                                                                                                                                                               
2745     elseif f::Any === Tuple && (la::Any == 2)::Any                                                                                                                                                                                        
2746         aty::Any = argtypes[2]::Any                                                                                                                                                                                                       
2747         ty::Any = isvarargtype(aty) ? unwrapva(aty)::Any : widenconst(aty)::Any                                                                                                                                                           
2748         if !isconcretetype(ty::Any)                                                                                                                                                                                                       
2749             return Future(CallMeta(Tuple, Any, EFFECTS_UNKNOWN, NoCallInfo()))                                                                                                                                                            
2750         end                                                                                                                                                                                                                               
2751     elseif is_return_type(f::Any)                                                                                                                                                                                                         
2752         return return_type_tfunc(interp, argtypes, si, sv)                                                                                                                                                                                
2753     elseif (la::Any == 3)::Any && f::Any === Core.:(!==)                                                                                                                                                                                  
2754         # mark !== as exactly a negated call to ===                                                                                                                                                                                       
2755         let callfuture = abstract_call_gf_by_type(interp, f::Any, ArgInfo(fargs::Union{Nothing, Vector{Any}}, Any[Const(f::Any), Any, Any]), si, Tuple{typeof(f::Any), Any, Any}, sv, max_methods)::Future,
2756             rtfuture = abstract_call_known(interp, (===), arginfo, si, sv, max_methods)::Future                                                                                                                                           
2757             return Future{CallMeta}(isready(callfuture) && isready(rtfuture), interp, sv) do interp, sv                                                                                                                                   
2758                 local rty = rtfuture[].rt                                                                                                                                                                                                 
2759                 if isa(rty, Conditional)                                                                                                                                                                                                  
2760                     return CallMeta(Conditional(rty.slot, rty.elsetype, rty.thentype), Bottom, EFFECTS_TOTAL, NoCallInfo()) # swap if-else
2761                 elseif isa(rty, Const)                                                                                                                                                                                                    
2762                     return CallMeta(Const(rty.val === false), Bottom, EFFECTS_TOTAL, MethodResultPure())                                                                                                                                  
2763                 end                                                                                                                                                                                                                       
2764                 return callfuture[]                                                                                                                                                                                                       
2765             end                                                                                                                                                                                                                           
2766         end                                                                                                                                                                                                                               
2767     elseif (la::Any == 3)::Any && f::Any === Core.:(>:)                                                                                                                                                                                   
2768         # mark issupertype as a exact alias for issubtype                                                                                                                                                                                 
2769         # swap T1 and T2 arguments and call <:                                                                                                                                                                                            
2770         if fargs !== nothing && length(fargs) == 3                                                                                                                                                                                        
2771             fargs = Any[<:, fargs[3]::Any, fargs[2]::Any]                                                                                                                                                                                 
2772         else                                                                                                                                                                                                                              
2773             fargs = nothing                                                                                                                                                                                                               
2774         end                                                                                                                                                                                                                               
2775         argtypes = Any[typeof(<:), argtypes[3], argtypes[2]]                                                                                                                                                                              
2776         return abstract_call_known(interp, <:, ArgInfo(fargs::Union{Nothing, Vector{Any}}, argtypes)::Any, si, sv, max_methods)
2777     elseif (la::Any == 2)::Any && f::Any === Core.typename                                                                                                                                                                                
2778         return Future(CallMeta(typename_static(argtypes[2]::Any)::Any, Bottom, EFFECTS_TOTAL, MethodResultPure()))                                                                                                                        
2779     elseif f::Any === Core._hasmethod                                                                                                                                                                                                     
2780         return Future(_hasmethod_tfunc(interp, argtypes, sv))                                                                                                                                                                             
2781     end                                                                                                                                                                                                                                   
2782     atype::Type = argtypes_to_type(argtypes)::Any                                                                                                                                                                                         
2783     return abstract_call_gf_by_type(interp, f::Any, arginfo, si, atype::Type, sv, max_methods)::Future                                                                                                                                    
2784 end                                                                                                                                                                                                   

Discovered when looking into rafaqz/DimensionalData.jl#1046.

For some reason destructuring caused the `argtypes` variable to be boxed, which
led to everything else using it being inferred as `Any`, making the code
vulnerable to invalidations.
@nsajko
Copy link
Member

nsajko commented Jan 24, 2026

I've done fixes like this before, and the proper fix is usually elsewhere in the method body. The root cause could, for example, be some type instability, perhaps related to closure capture boxing.

Off-topic gripe: too bad that maintainability suffers from the style in which the code is written, making this into a neverending game of whack-a-mole, with some other type instability likely to happen again sooner or later in the same method. IMO it would be good to switch to a style that:

  • used smaller method bodies (this one is huge)

  • used let as much as possible to further reduce the scopes of variables

@JamesWrigley
Copy link
Member Author

Oops, actually I made a mistake here and applied the fix to the wrong function because I made the change on a different machine...

In fact you already fixed this exact problem with abstract_call_known() in #57582 🙃 Which explains why the invalidations don't appear on nightly. I'll close this, sorry for the noise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants