Sorry, it took a while to create a minimal working code that reproduces the problem. See the following module:
VERSION >= v"0.4.0-dev+6521" && __precompile__()
module TestConst
export test
# Earth Equatorial radius [m].
const R0 = 6378137.0
# Standard gravitational parameter for Earth [m^3/s^2].
const m0 = 3.986004418e14
function test(n::Number, e::Number)
# Check if the arguments are valid.
if (n <= 0)
throw(ArgumentError("The angular velocity must be greater than 0."))
end
if !( 0. <= e < 1. )
throw(ArgumentError("The eccentricity must be within the interval 0 <= e < 1."))
end
# Auxiliary variables.
sqrt_m0 = sqrt(m0)
sqrt_e2 = sqrt(1-e^2)
# Auxiliary constant to compute the functions.
K1 = 3.0*R0^2*sqrt_m0/(4.0*(1-e^2)^2)
# Declare the functions that must solved for 0.
f1(a, i) = ne + 2.0*K1*a^(-3.5)*cos(i)
end
end
The problem is not related with the global constants, but with the internal K1. This other code reproduces the problem as well:
VERSION >= v"0.4.0-dev+6521" && __precompile__()
module TestConst
export test
function test(n::Number, e::Number)
# Check if the arguments are valid.
if (n <= 0)
throw(ArgumentError("The angular velocity must be greater than 0."))
end
# Auxiliary constant to compute the functions.
K1 = 3.0
# Declare the functions that must solved for 0.
f1(a, i) = K1
end
end
Notice that, if I remove the exceptions, then everything is fine:
Variables:
#self# <optimized out>
n <optimized out>
e <optimized out>
K1 <optimized out>
f1::TestConst.#f1#1{Float64}
No. I have the same problem with constants that are used in functions and have concluded that this is due to the closure bug #15276, the single most annoying remaining issue in Julia (in my opinion). If this and absolutely nothing else were miraculously fixed in 1.0 then the release would be marked a success in my book.
No, there isn’t a problem here. Read the whole code, not just the header:
Variables:
#self# <optimized out>
n::Int64
e <optimized out>
K1::Core.Box
f1::#f1#15
Body:
begin
K1::Core.Box = $(Expr(:new, :(Core.Box)))
NewvarNode(:(f1::#f1#15))
unless (Base.sle_int)(n::Int64, 0)::Bool goto 6 # lin
e 139:
(Main.throw)($(Expr(:new, :(Base.ArgumentError), "The
angular velocity must be greater than 0.")))::Union{}
6: # line 143:
(Core.setfield!)(K1::Core.Box, :contents, 3.0)::Float
64 # line 146:
f1::#f1#15 = $(Expr(:new, :(Main.#f1#15), :(K1)))
return f1::#f1#15
end::#f1#15
Core.Box doesn’t mean that there’s a type instability. It just means the variable gets boxed, here because it might not be needed depending on if there’s an exception or not. But its result is not type-unstable. In fact,
(Core.setfield!)(K1::Core.Box, :contents, 3.0)::Float64 # line 146:
this line says that it knows that the contents will be Float64, so all of the types in the AST are inferred and everything is fine.
Sorry, it is a part of a bigger code and I maybe misunderstood the real problem. See this other code:
VERSION >= v"0.4.0-dev+6521" && __precompile__()
module TestConst
export test
# Earth Equatorial radius [m].
const R0 = 6378137.0
# Standard gravitational parameter for Earth [m^3/s^2].
const m0 = 3.986004418e14
# Perturbation terms based on EGM-08 standard gravitational model [1, pp. 1039].
const J2 = 1.08262617385222e-3
# Earth's orbit mean motion [rad/s]
const ne = (360.0/365.2421897)*pi/180/86400
function test(n::Number, e::Number)
# Check if the arguments are valid.
if (n <= 0)
throw(ArgumentError("The angular velocity must be greater than 0."))
end
if !( 0. <= e < 1. )
throw(ArgumentError("The eccentricity must be within the interval 0 <= e < 1."))
end
# Auxiliary variables.
sqrt_m0 = sqrt(m0)
sqrt_e2 = sqrt(1-e^2)
# Auxiliary constant to compute the functions.
K1 = 3.0*R0^2*J2*sqrt_m0/(4.0*(1-e^2)^2)
# Declare the functions that must solved for 0.
f1(a, i) = ne + 2.0*K1*a^(-3.5)*cos(i)
a_k::Float64 = (m0/n^2)^(1/3)
i_k::Float64 = acos( -ne*a_k^(3.5)/(2*K1) )
f1(a_k, i_k)
end
end
In this case, it is not correcting guessing the return value:
return $(Expr(:invoke, MethodInstance for (::TestConst.#f1#1)(::Float64, ::Float64), :(f1), SSAValue(1), :(i_k)))
end::Any
On the other hand, if I remove K1 from the function f(a,i), then it works:
return $(Expr(:invoke, MethodInstance for (::TestConst.#f1#1)(::Float64, ::Float64), :(f1), SSAValue(0), SSAValue(1)))
end::Float64
NOTE: I know that I can always do K1::Float64, but I want to avoid that.
Actually, I didn’t apply the let block trick correctly previously. The following doesn’t exhibit the performance problem:
module TestConst
export test
function test(n::Number, e::Number)
# Check if the arguments are valid.
if (n <= 0)
throw(ArgumentError("The angular velocity must be greater than 0."))
end
# Auxiliary constant to compute the functions.
K1 = 3.0
# Declare the functions that must solved for 0.
f1 = let K1 = K1
(a, i) -> K1
end
end
end
This isn’t the basic 15276 because a much fancier optimization is required. Naively, the inner function f is visible and could be called throughout the outer test function—before or after K0 is defined. In order to correctly implement that, K0 must be boxed so that it’s state can be either unassigned or assigned. The compiler would have to prove that f cannot be called before K0 defined in order to know that it doesn’t need to do any boxing here. It’s possible but quite tricky. You could try moving the definition of K0 before the error checks or putting the main computation into a separate function body that is called after the checks are done. It’s also worth filing an issue about this situation since we would ideally like to handle it efficiently.