As far as I know, using an @generated
function in statically compiled code is perfectly fine if the input types are statically known. The part that doesn’t work for static compilation is “generate new function bodies” during dynamic dispatch, since static compilation (by definition) doesn’t include dynamic generation of code (i.e., there’s no eval
, and no codegen at all). This is the same case for regular dynamic dispatch that would require code specialization & compilation at runtime.
You can in theory still have dynamic dispatch with just the runtime & without codegen, as long as the specialization you end up calling has already been compiled ahead of time.
You still need to do that with cases of @generated
that are more than just ensuring you have some value available as a constant to encourage the compiler to propagate that constant. As an example of where you need @generated
:
julia> using Base.Cartesian: @ntuple
julia> @generated function foo(x::Val{N}) where N
return quote
@ntuple $N i -> "This is a compile time constant with interpolated $i"
end
end
foo (generic function with 1 method)
julia> @code_warntype foo(Val(2))
MethodInstance for foo(::Val{2})
from foo(x::Val{N}) where N @ Main REPL[4]:1
Static Parameters
N = 2
Arguments
#self#::Core.Const(foo)
x::Core.Const(Val{2}())
Body::Tuple{String, String}
1 ─ %1 = Base.string("This is a compile time constant with interpolated ", 1)::String
│ %2 = Base.string("This is a compile time constant with interpolated ", 2)::String
│ %3 = Core.tuple(%1, %2)::Tuple{String, String}
└── return %3
You can’t interpolate that N
into @ntuple
, due to @ntuple
working on an expression and needs a literal value, which you can only get here with an @generated
function. Compare this to a version without @generated
, which needs optimizations turned on to eliminate the anonymous function:
julia> function bar(x::Val{N}) where N
return ntuple(i -> "This is a runtime value with interpolated $i", x)
end
bar (generic function with 1 method)
julia> @code_warntype bar(Val(2))
MethodInstance for bar(::Val{2})
from bar(x::Val{N}) where N @ Main REPL[6]:1
Static Parameters
N = 2
Arguments
#self#::Core.Const(bar)
x::Core.Const(Val{2}())
Locals
#4::var"#4#5"
Body::Tuple{String, String}
1 ─ (#4 = %new(Main.:(var"#4#5")))
│ %2 = #4::Core.Const(var"#4#5"())
│ %3 = Main.ntuple(%2, x)::Tuple{String, String}
└── return %3
julia> @code_warntype optimize=true bar(Val(2))
MethodInstance for bar(::Val{2})
from bar(x::Val{N}) where N @ Main REPL[6]:1
Static Parameters
N = 2
Arguments
#self#::Core.Const(bar)
x::Core.Const(Val{2}())
Locals
#4::var"#4#5"
Body::Tuple{String, String}
1 ─ %1 = invoke Base.print_to_string("This is a runtime value with interpolated "::String, 1::Vararg{Any})::String
│ %2 = invoke Base.print_to_string("This is a runtime value with interpolated "::String, 2::Vararg{Any})::String
│ %3 = Core.tuple(%1, %2)::Tuple{String, String}
└── return %3
This is just an example of course since there’s an almost-equivalent to @ntuple
in ntuple
, but since the two work at different levels of the compilation stack (an AST transform vs. a maybe-not-applied compiler optimization), the former can give semantic guarantees that the latter can’t. There’s bound to be examples that are a bit more complicated than this where the constant propagation fails, while interpolating into the expression directly still works.