Type stability bug in compiler?

If I change a line of my code from

cost = typemax(Float64)

to

cost::Float64 = typemax(Float64)

then it becomes type stable (Any goes away).

How can it be? The rest of the code is somewhat complicated, and I’m not sure I can reproduce this problem in a simplified setting. It’s in a hot section of code, and I’ve spent hours tracking this down. I have no insight into what makes it type stable by putting the type annotation there.

I’d wager there is at least one other assignment to cost, and that is type unstable. Probably inferred as Union{Float64,...}. The annotation gives the compiler permission to make it concrete.

Hard to diagnose without a MWE or the output of @code_warntype.

3 Likes

Is it a global variable? The type assertion tells the compiler that the variable doesn’t change type throughout the evaluation. In general, the compiler can’t assume that about global variables.

2 Likes
@code_warntype hybrid_ransac_fit(proposer, verifier)
MethodInstance for VisualGeometryToolkit.hybrid_ransac_fit(::Tuple{SamplePc7{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, typeof(pc7_to_fund_mat)}, ::Tuple{CalcLosses{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, MaskInliers})
  from hybrid_ransac_fit(proposers, verifiers; test_feasibility, solver_sampler, refine_model, polish_model) in VisualGeometryToolkit at /home/jbpritts/.julia/dev/VisualGeometryToolkit/src/estimators/ransac.jl:39
Arguments
  #self#::Core.Const(VisualGeometryToolkit.hybrid_ransac_fit)
  proposers::Tuple{SamplePc7{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, typeof(pc7_to_fund_mat)}
  verifiers::Tuple{CalcLosses{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, MaskInliers}
Locals
  #949::VisualGeometryToolkit.var"#949#954"
  #948::VisualGeometryToolkit.var"#948#953"
Body::Tuple{MMatrix{3, 3, Float64, 9}, BitVector, Any}
1 ─      (#948 = %new(VisualGeometryToolkit.:(var"#948#953")))
│   %2 = #948::Core.Const(VisualGeometryToolkit.var"#948#953"())
│        (#949 = %new(VisualGeometryToolkit.:(var"#949#954")))
│   %4 = #949::Core.Const(VisualGeometryToolkit.var"#949#954"())
│   %5 = VisualGeometryToolkit.:(var"#hybrid_ransac_fit#946")(%2, %4, VisualGeometryToolkit.nothing, VisualGeometryToolkit.nothing, #self#, proposers, verifiers)::Tuple{MMatrix{3, 3, Float64, 9}, BitVector, Any}
└──      return %5

There are no globals as far as I know. They are all defined withing the context of the function.

Quote the code in triple backticks ``` please :slightly_smiling_face:

That Any goes away with a type annotation.

without MWE it’s impossible to help

 @code_warntype ransac_fit_fund_mat(pc,1.0)
MethodInstance for VisualGeometryToolkit.ransac_fit_fund_mat(::StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}, ::Float64)
  from ransac_fit_fund_mat(pc, σ) in VisualGeometryToolkit at /home/jbpritts/.julia/dev/VisualGeometryToolkit/src/projective/fund_mat/ransac.jl:38
Arguments
  #self#::Core.Const(VisualGeometryToolkit.ransac_fit_fund_mat)
  pc::StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}
  σ@_3::Float64
Locals
  @_4::Int64
  #427::VisualGeometryToolkit.var"#427#428"
  cost::Any
  mask::BitVector
  model::Any
  verifiers::Tuple{NamedTuple{(:calc_loss, :mask_inliers), Tuple{CalcLosses{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, MaskInliers}}}
  proposers::Tuple{NamedTuple{(:sample, :test_sample, :solve), Tuple{SamplePc7{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, VisualGeometryToolkit.var"#427#428", typeof(pc7_to_fund_mat)}}}
  mask_inliers::MaskInliers
  calc_loss::CalcLosses{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}
  sample_pc7::SamplePc7{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}
  t::Float64
  σ@_15::Float64
Body::Tuple{Any, BitVector, Any}
1 ─       (σ@_15 = σ@_3)
│   %2  = Core.tuple(σ@_15, 0.0001)::Core.PartialStruct(Tuple{Float64, Float64}, Any[Float64, Core.Const(0.0001)])
│         (σ@_15 = VisualGeometryToolkit.maximum(%2))
│   %4  = Base.getproperty(pc, :first)::Vector{SVector{3, Float64}}
│   %5  = VisualGeometryToolkit.neltype(%4)::Core.Const(Float64)
│   %6  = VisualGeometryToolkit.one(%5)::Core.Const(1.0)
│         (t = VisualGeometryToolkit.cauchy_ρ(3.84, %6))
│   %8  = Core.apply_type(VisualGeometryToolkit.FmMinPtSample, VisualGeometryToolkit.Float64)::Core.Const(MVector{7, SVector{3, Float64}})
│   %9  = (%8)(VisualGeometryToolkit.undef)::MVector{7, SVector{3, Float64}}
│   %10 = Core.apply_type(VisualGeometryToolkit.FmMinPtSample, VisualGeometryToolkit.Float64)::Core.Const(MVector{7, SVector{3, Float64}})
│   %11 = (%10)(VisualGeometryToolkit.undef)::MVector{7, SVector{3, Float64}}
│   %12 = Core.apply_type(VisualGeometryToolkit.MVector, 7, VisualGeometryToolkit.UInt32)::Core.Const(MVector{7, UInt32})│   %13 = (%12)(VisualGeometryToolkit.undef)::MVector{7, UInt32}
│         (sample_pc7 = VisualGeometryToolkit.SamplePc7(pc, %9, %11, %13))
│   %15 = Core.apply_type(VisualGeometryToolkit.Vector, VisualGeometryToolkit.Float64)::Core.Const(Vector{Float64})
│   %16 = VisualGeometryToolkit.length(pc)::Int64
│   %17 = (%15)(VisualGeometryToolkit.undef, %16)::Vector{Float64}
│         (calc_loss = VisualGeometryToolkit.CalcLosses(pc, %17, σ@_15))
│   %19 = VisualGeometryToolkit.length(pc)::Int64
│   %20 = VisualGeometryToolkit.BitVector(VisualGeometryToolkit.undef, %19)::BitVector
│         (mask_inliers = VisualGeometryToolkit.MaskInliers(%20, t::Core.Const(2.756560961162584)))
│   %22 = (:sample, :test_sample, :solve)::Core.Const((:sample, :test_sample, :solve))
│   %23 = Core.apply_type(Core.NamedTuple, %22)::Core.Const(NamedTuple{(:sample, :test_sample, :solve)})
│   %24 = sample_pc7::SamplePc7{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}
│         (#427 = %new(VisualGeometryToolkit.:(var"#427#428")))
│   %26 = #427::Core.Const(VisualGeometryToolkit.var"#427#428"())
│   %27 = Core.tuple(%24, %26, VisualGeometryToolkit.pc7_to_fund_mat)::Tuple{SamplePc7{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, VisualGeometryToolkit.var"#427#428", typeof(pc7_to_fund_mat)}
│   %28 = (%23)(%27)::NamedTuple{(:sample, :test_sample, :solve), Tuple{SamplePc7{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, VisualGeometryToolkit.var"#427#428", typeof(pc7_to_fund_mat)}}
│         (proposers = Core.tuple(%28))
│   %30 = (:calc_loss, :mask_inliers)::Core.Const((:calc_loss, :mask_inliers))
│   %31 = Core.apply_type(Core.NamedTuple, %30)::Core.Const(NamedTuple{(:calc_loss, :mask_inliers)})
│   %32 = Core.tuple(calc_loss, mask_inliers::Core.PartialStruct(MaskInliers, Any[BitVector, Core.Const(2.756560961162584)]))::Core.PartialStruct(Tuple{CalcLosses{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, MaskInliers}, Any[CalcLosses{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, Core.PartialStruct(MaskInliers, Any[BitVector, Core.Const(2.756560961162584)])])
│   %33 = (%31)(%32)::Core.PartialStruct(NamedTuple{(:calc_loss, :mask_inliers), Tuple{CalcLosses{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, MaskInliers}}, Any[CalcLosses{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, Core.PartialStruct(MaskInliers, Any[BitVector, Core.Const(2.756560961162584)])])
│         (verifiers = Core.tuple(%33))
│   %35 = VisualGeometryToolkit.ransac_fit(proposers, verifiers::Core.PartialStruct(Tuple{NamedTuple{(:calc_loss, :mask_inliers), Tuple{CalcLosses{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, MaskInliers}}}, Any[Core.PartialStruct(NamedTuple{(:calc_loss, :mask_inliers), Tuple{CalcLosses{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, MaskInliers}}, Any[CalcLosses{StructArrays.StructVector{NamedTuple{(:first, :second), Tuple{SVector{3, Float64}, SVector{3, Float64}}}, NamedTuple{(:first, :second), Tuple{Vector{SVector{3, Float64}}, Vector{SVector{3, Float64}}}}, Int64}}, Core.PartialStruct(MaskInliers, Any[BitVector, Core.Const(2.756560961162584)])])]))::Tuple{Any, BitVector, Any}
│   %36 = Base.indexed_iterate(%35, 1)::Core.PartialStruct(Tuple{Any, Int64}, Any[Any, Core.Const(2)])
│         (model = Core.getfield(%36, 1))
│         (@_4 = Core.getfield(%36, 2))
│   %39 = Base.indexed_iterate(%35, 2, @_4::Core.Const(2))::Core.PartialStruct(Tuple{BitVector, Int64}, Any[BitVector, Core.Const(3)])
│         (mask = Core.getfield(%39, 1))
│         (@_4 = Core.getfield(%39, 2))
│   %42 = Base.indexed_iterate(%35, 3, @_4::Core.Const(3))::Core.PartialStruct(Tuple{Any, Int64}, Any[Any, Core.Const(4)])
│         (cost = Core.getfield(%42, 1))
│   %44 = Core.tuple(model, mask, cost)::Tuple{Any, BitVector, Any}
└──       return %44

A MWE means other people have to be able to run the code example showing the issue. See Please read: make it easier to help you if you haven’t already.

of course I would do this, if it were easy to reproduce.

Is the behaviour non-deterministic, the code unpublishable or the example reliant on some non-public data? I can’t think of any other reasons for not being able to share something others can work with.

It would take some time to tease out the dependencies, but I could do it. Regardless, I am putting functions in a tuple and passing it into another method so they can be called. Could this be the cause of the type instability? I see that there are FunctionWrappers.jl for something like this, but I don’t understand when it is needed.

    sample_pc7 = SamplePc7(pc,FmMinPtSample{Float64}(undef),FmMinPtSample{Float64}(undef),MVector{7,UInt32}(undef))
    calc_loss = CalcLosses(pc,Vector{Float64}(undef, length(pc)),σ)
    mask_inliers = MaskInliers(BitVector(undef, length(pc)),t)
    
    proposers = ((sample = sample_pc7, test_sample = (_)->true, solve = pc7_to_fund_mat),)
    verifiers = ((calc_loss = calc_loss, mask_inliers = mask_inliers),)

    model, mask, cost = ransac_fit(proposers, verifiers)

yes, because by default functions argument are not specialized for!

https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing