Is there a way to apply constant folding with `nameof` for a simple `UnionAll`?

nameof(::UnionAll) does not perform constant folding.

julia> @code_typed nameof(Complex{Float32})
CodeInfo(
1 ─     return :Complex
) => Symbol

julia> @code_typed nameof(Complex)
CodeInfo(
1 ─       nothing::Nothing
2 ┄ %2  = φ (#1 => _2, #3 => %6)::Any
│   %3  = (%2 isa Base.UnionAll)::Bool
└──       goto #4 if not %3
3 ─ %5  = π (%2, UnionAll)
│   %6  = Base.getfield(%5, :body)::Any
└──       goto #2
4 ─       goto #5
5 ─ %9  = Base.nameof(%2)::Any
│         Core.typeassert(%9, Base.Symbol)::Symbol
│   %11 = π (%9, Symbol)
└──       return %11
) => Symbol

At first I thought it was because @nospecialize was specified in Base.unwrap_unionall, but that doesn’t seem to be the case. Of course, the body field of UnionAll is boxed, so it should require special handling.

New, in Julia 1.7:

julia> Base.@aggressive_constprop constprop_unwrap(::Type{T}) where {T} = nameof(T)
constprop_unwrap (generic function with 1 method)

julia> @code_typed constprop_unwrap(Complex{Float32})
CodeInfo(
1 ─     return :Complex
) => Symbol

I’d do something like

@static if VERSION >= v"1.7.0-DEV.421" 
Base.@aggressive_constprop constprop_unwrap(::Type{T}) where {T} = nameof(T)
else
@generated constprop_unwrap(::Type{T}) where {T} = QuoteNode(nameof(T))
end

to add support for older Julia versions.

4 Likes

Today I learned about @aggressive_constprop. Thank you.
However, I don’t think we have achieved the original goal.

julia> @code_typed constprop_unwrap(Complex)
CodeInfo(
1 ─ %1 = invoke Main.nameof($(Expr(:static_parameter, 1))::UnionAll)::Symbol
└──      return %1
) => Symbol

Oops, you’re right. Just stick with the @generated version then.

julia> Base.@aggressive_constprop constprop_unwrap(::Type{T}) where {T} = nameof(T)
constprop_unwrap (generic function with 1 method)

julia> @generated generated_unwrap(::Type{T}) where {T} = QuoteNode(nameof(T))
generated_unwrap (generic function with 1 method)

julia> @code_typed constprop_unwrap(Complex{Float32})
CodeInfo(
1 ─     return :Complex
) => Symbol

julia> @code_typed generated_unwrap(Complex{Float32})
CodeInfo(
1 ─     return :Complex
) => Symbol

julia> @code_typed constprop_unwrap(Complex)
CodeInfo(
1 ─ %1 = invoke Main.nameof($(Expr(:static_parameter, 1))::UnionAll)::Symbol
└──      return %1
) => Symbol

julia> @code_typed generated_unwrap(Complex)
CodeInfo(
1 ─     return :Complex
) => Symbol
1 Like

@generated function is a powerful tool, but it is a little too powerful. In the use case I’m envisioning, the output of nameof is used for conditional branching and not for the final output. In other words, once the compilation is done, there is no need to remember it anymore.

I don’t follow. What do you mean by “remember”?
You want a function to compile, but not be “saved”? I don’t think that is possible for ordinary functions either. Methods and a lot of metadata (e.g. inference results) will be cached.

If you’re only using the results once, then that implies latency is more important than compile times, so spending more time on inference/const prop isn’t worth it?

I agree. So I would like it to behave like a ordinary function.