Consider
struct data{M}
b::Int
end
get(::data{M}) where M = M
f(x::data) = x.b + get(x)
g(x::data{M}) where M = x.b + M
x = data{7}(8)
Then
julia> @code_warntype get(x)
Body::Int64
1 1 ─ return $(Expr(:static_parameter, 1)) │
julia> @code_warntype f(x)
Body::Int64
1 1 ─ %1 = (Base.getfield)(x, :b)::Int64 │╻ getproperty
│ %2 = (Base.add_int)(%1, 7)::Int64 │╻ +
└── return %2 │
julia> @code_warntype g(x)
Body::Int64
1 1 ─ %1 = (Base.getfield)(x, :b)::Int64 │╻ getproperty
│ %2 = $(Expr(:static_parameter, 1))::Core.Compiler.Const(7, false) │
│ %3 = (Base.add_int)(%1, %2)::Int64 │╻ +
└── return %3
To me it looks like the definition of f is better and more efficient than the definition of g because the get function gets inlined into f and provides more detailed type information, while g has to rely on a more mysterious type called Core.Compiler.Const, not knowing that M will be an Int for x.
julia> @btime f($x)
0.025 ns (0 allocations: 0 bytes)
15
julia> @btime g($x)
0.025 ns (0 allocations: 0 bytes)
15
With this trivial example, the performance is the same, but I still think that f might be better than g… especially when the functions have more complex logic, which could be optimized by knowing the value.
Any thoughts on this? Could someone explain Core.Compiler.Const in more detail?