Core.Compiler.Const and parametric types

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?

1 Like

FWIW,

julia> @code_llvm f(x)

;  @ REPL[3]:1 within `f'
define i64 @julia_f_12364({ i64 } addrspace(11)* nocapture nonnull readonly dereferenceable(8)) {
top:
; ┌ @ sysimg.jl:18 within `getproperty'
   %1 = getelementptr inbounds { i64 }, { i64 } addrspace(11)* %0, i64 0, i32 0
; └
; ┌ @ int.jl:53 within `+'
   %2 = load i64, i64 addrspace(11)* %1, align 8
   %3 = add i64 %2, 7
; └
  ret i64 %3
}

julia> @code_llvm g(x)

;  @ REPL[4]:1 within `g'
define i64 @julia_g_12365({ i64 } addrspace(11)* nocapture nonnull readonly dereferenceable(8)) {
top:
; ┌ @ sysimg.jl:18 within `getproperty'
   %1 = getelementptr inbounds { i64 }, { i64 } addrspace(11)* %0, i64 0, i32 0
; └
; ┌ @ int.jl:53 within `+'
   %2 = load i64, i64 addrspace(11)* %1, align 8
   %3 = add i64 %2, 7
; └
  ret i64 %3
}

ie they compile to be exactly identical.

Also, sub-ns benchmarks are not very informative.
https://github.com/JuliaCI/BenchmarkTools.jl/issues/130

3 Likes