Any
matches any Julia type. You can think of it similar to what a void pointer might be in C. This pointer may then point to something that has a type tag. We can only figure out what the type of the pointed to object is by deferencing the pointer. Additionally, the type of the pointed to memory could change.
As a contrived example, I will use references below which are similar to pointers. I will then compile the functions to LLVM IR.
The example using Int
compiles to a few simple LLVM instructions. In fact, it figured out that I’m just multiplying the value by 2, meaning we can just do a left shift.
The example using Any
results in many more LLVM instructions. Here we have to carefully dereference the pointers, figure out the types, and then apply a generic add function, dynamically dispatching at runtime. As you can see this is considerably less efficient than the Int
example. It becomes similar to what an untyped language like Python might do in its default execution model.
julia> r_int = Ref{Int}(1)
Base.RefValue{Int64}(1)
julia> r_any = Ref{Any}(1)
Base.RefValue{Any}(1)
julia> f(r) = r[] + r[]
f (generic function with 3 methods)
julia> @code_llvm f(r_int)
; @ REPL[82]:1 within `f`
define i64 @julia_f_832({}* noundef nonnull align 8 dereferenceable(8) %0) #0 {
top:
; ┌ @ refvalue.jl:59 within `getindex`
; │┌ @ Base.jl:37 within `getproperty`
%1 = bitcast {}* %0 to i64*
%2 = load i64, i64* %1, align 8
; └└
; ┌ @ int.jl:87 within `+`
%3 = shl i64 %2, 1
; â””
ret i64 %3
}
julia> @code_llvm f(r_any)
; @ REPL[82]:1 within `f`
define nonnull {}* @julia_f_834({}* noundef nonnull align 8 dereferenceable(8) %0) #0 {
top:
%1 = alloca [2 x {}*], align 8
%gcframe8 = alloca [3 x {}*], align 16
%gcframe8.sub = getelementptr inbounds [3 x {}*], [3 x {}*]* %gcframe8, i64 0, i64 0
%2 = bitcast [3 x {}*]* %gcframe8 to i8*
call void @llvm.memset.p0i8.i64(i8* align 16 %2, i8 0, i64 24, i1 true)
%thread_ptr = call i8* asm "mrs $0, tpidr_el0", "=r"() #7
%tls_ppgcstack = getelementptr i8, i8* %thread_ptr, i64 16
%3 = bitcast i8* %tls_ppgcstack to {}****
%tls_pgcstack = load {}***, {}**** %3, align 8
; ┌ @ refvalue.jl:59 within `getindex`
; │┌ @ Base.jl:37 within `getproperty`
%4 = bitcast [3 x {}*]* %gcframe8 to i64*
store i64 4, i64* %4, align 16
%5 = load {}**, {}*** %tls_pgcstack, align 8
%6 = getelementptr inbounds [3 x {}*], [3 x {}*]* %gcframe8, i64 0, i64 1
%7 = bitcast {}** %6 to {}***
store {}** %5, {}*** %7, align 8
%8 = bitcast {}*** %tls_pgcstack to {}***
store {}** %gcframe8.sub, {}*** %8, align 8
%9 = bitcast {}* %0 to {}**
%getfield = load atomic {}*, {}** %9 unordered, align 8
%.not = icmp eq {}* %getfield, null
br i1 %.not, label %fail, label %pass4
fail: ; preds = %top
call void @ijl_throw({}* inttoptr (i64 530752553744 to {}*))
unreachable
pass4: ; preds = %top
%.sub = getelementptr inbounds [2 x {}*], [2 x {}*]* %1, i64 0, i64 0
%10 = getelementptr inbounds [3 x {}*], [3 x {}*]* %gcframe8, i64 0, i64 2
store {}* %getfield, {}** %10, align 16
; └└
store {}* %getfield, {}** %.sub, align 8
%11 = getelementptr inbounds [2 x {}*], [2 x {}*]* %1, i64 0, i64 1
store {}* %getfield, {}** %11, align 8
%12 = call nonnull {}* @ijl_apply_generic({}* inttoptr (i64 530685332464 to {}*), {}** nonnull %.sub, i32 2)
%13 = load {}*, {}** %6, align 8
%14 = bitcast {}*** %tls_pgcstack to {}**
store {}* %13, {}** %14, align 8
ret {}* %12
}
To summarize, if the type is known we can compile to a succint set of instructions that we can determine at compile time. Any
represents a wildcard unknown type, forcing us to figure out what needs to be done at run time.