@dataSurfer, you can check for yourself, no need to rely on rumors:
julia> g(c) = f(@inbounds c[1])
g (generic function with 1 method)
julia> f(::Int) = 1
f (generic function with 1 method)
julia> f(::Bool) = 2
f (generic function with 2 methods)
julia> g(Any[3])
1
julia> g(Any[true])
2
julia> code_typed(g, (Vector{Any},))
1-element Array{Any,1}:
CodeInfo(
1 ─ %1 = Base.arrayref(false, c, 1)::Any
│ %2 = (isa)(%1, Bool)::Bool
└── goto #3 if not %2
2 ─ goto #6
3 ─ %5 = (isa)(%1, Int64)::Bool
└── goto #5 if not %5
4 ─ goto #6
5 ─ %8 = Main.f(%1)::Int64
└── goto #6
6 ┄ %10 = φ (#2 => 2, #4 => 1, #5 => %8)::Int64
└── return %10
) => Int64
So, two of the calls are made at compile-time after checking to see if it’s a Bool
or an Int
. But then notice that third Main.f(%1)
call, which is what gets used if the input is neither a Bool
nor an Int
. That’s the runtime-dispatch version, which you can see in greater detail with
julia> code_llvm(g, (Vector{Any},))
define i64 @julia_g_264(%jl_value_t* nonnull align 16 dereferenceable(40)) {
top:
%1 = alloca %jl_value_t*
%gcframe = alloca %jl_value_t*, i32 3, align 16
%2 = bitcast %jl_value_t** %gcframe to i8*
call void @llvm.memset.p0i8.i32(i8* align 16 %2, i8 0, i32 24, i1 false)
%thread_ptr = call i8* asm "movq %fs:0, $0", "=r"()
%ptls_i8 = getelementptr i8, i8* %thread_ptr, i64 -15720
%ptls = bitcast i8* %ptls_i8 to %jl_value_t***
%3 = getelementptr %jl_value_t*, %jl_value_t** %gcframe, i32 0
%4 = bitcast %jl_value_t** %3 to i64*
store i64 4, i64* %4
%5 = getelementptr %jl_value_t**, %jl_value_t*** %ptls, i32 0
%6 = getelementptr %jl_value_t*, %jl_value_t** %gcframe, i32 1
%7 = bitcast %jl_value_t** %6 to %jl_value_t***
%8 = load %jl_value_t**, %jl_value_t*** %5
store %jl_value_t** %8, %jl_value_t*** %7
%9 = bitcast %jl_value_t*** %5 to %jl_value_t***
store %jl_value_t** %gcframe, %jl_value_t*** %9
%10 = bitcast %jl_value_t* %0 to %jl_value_t***
%11 = load %jl_value_t**, %jl_value_t*** %10, align 8
%12 = load %jl_value_t*, %jl_value_t** %11, align 8
%13 = icmp eq %jl_value_t* %12, null
br i1 %13, label %fail, label %pass
L5: ; preds = %pass
%14 = icmp eq %jl_value_t* %28, inttoptr (i64 140386210668768 to %jl_value_t*)
br i1 %14, label %L10, label %L8
L8: ; preds = %L5
%15 = getelementptr %jl_value_t*, %jl_value_t** %gcframe, i32 2
store %jl_value_t* %12, %jl_value_t** %15
%16 = getelementptr %jl_value_t*, %jl_value_t** %1, i32 0
store %jl_value_t* %12, %jl_value_t** %16
%17 = call nonnull %jl_value_t* @jl_apply_generic(%jl_value_t* inttoptr (i64 140386134835232 to %jl_value_t*), %jl_value_t** %1, i32 1)
%18 = bitcast %jl_value_t* %17 to i64*
%19 = load i64, i64* %18, align 8
br label %L10
L10: ; preds = %pass, %L8, %L5
%value_phi = phi i64 [ %19, %L8 ], [ 2, %pass ], [ 1, %L5 ]
%20 = getelementptr %jl_value_t*, %jl_value_t** %gcframe, i32 1
%21 = load %jl_value_t*, %jl_value_t** %20
%22 = getelementptr %jl_value_t**, %jl_value_t*** %ptls, i32 0
%23 = bitcast %jl_value_t*** %22 to %jl_value_t**
store %jl_value_t* %21, %jl_value_t** %23
ret i64 %value_phi
fail: ; preds = %top
call void @jl_throw(%jl_value_t* inttoptr (i64 140386213625328 to %jl_value_t*))
unreachable
pass: ; preds = %top
%24 = bitcast %jl_value_t* %12 to i64*
%25 = getelementptr i64, i64* %24, i64 -1
%26 = load i64, i64* %25
%27 = and i64 %26, -16
%28 = inttoptr i64 %27 to %jl_value_t*
%29 = icmp eq %jl_value_t* %28, inttoptr (i64 140386211212192 to %jl_value_t*)
br i1 %29, label %L10, label %L5
}
Since you seem to know something about C/C++, you can check the source of jl_apply_generic
yourself.
If you add enough methods to f
, then Julia gives up on trying to guess and recompiles g
to only use runtime dispatch (when it can’t figure out what the element type will be in advance):
julia> f(::Float64) = 3
f (generic function with 3 methods)
julia> f(::String) = 4
f (generic function with 4 methods)
julia> code_typed(g, (Vector{Any},))
1-element Array{Any,1}:
CodeInfo(
1 ─ %1 = Base.arrayref(false, c, 1)::Any
│ %2 = Main.f(%1)::Any
└── return %2
) => Any
A key point with this last version is the following: suppose PkgA defines g
and those 4 methods of f
. It precompiles them and saves them to the *.ji
file. Now PkgB uses PkgA and defines a 5th method of f
. g
will not be recompiled because the limit for trying to be clever has already been exceeded. Nevertheless g
will accurately dispatch to the new f
method when fed a container with appropriate elements.
Runtime dispatch, Q.E.D.