Constant propagation with Val and non-struct types

The type Val can be used to handily associate values with constants by writing a method of a function for each pair. It’s even handier to define a function that wraps the values in Val so you don’t have to type it.

For some reason the wrapper stops constant propagation when the values are of type Integer or Symbol. When t in the snippet below is changed to nothing the propagation works and the functions bodies are just return true.

It works when adding @pure but since the compiler doesn’t recognize it on its own and the case seems to be quite simple I’m wondering if I missed something and it could be unsafe to do so. Julia version is 1.1.0.

using InteractiveUtils

const t = :symbol
const T = typeof(t)

selector1(::Val{t}) = true
wrapper1(x::T) = selector1(Val(x))
test1() = wrapper1(t)
@code_warntype test1()

selector2(::Type{Val{t}}) = true
wrapper2(x::T) = selector2(Val{x})
test2() = wrapper2(t)
@code_warntype test2()

Not sure. I thought that for Symbol this could be because isimmutable(:symbol) == false and Symbol.mutable == true. Even though Symbols are functionally immutable, the type system currently doesn’t treat them as such for certain technical reasons (see https://github.com/JuliaLang/julia/issues/30210). This probably breaks constant prop. But then I would have expected the const t = 1 variant to work, so there’s probably something else in play here.

Interestingly, if you define the other case the compiler is then happy to do this at compile-time. Note that @code_warntype doesn’t do inlining by default so it doesn’t display all the optimizations.

julia> @code_typed test1()
CodeInfo(
1 ─ %1 = invoke Main.wrapper1(Main.t::Symbol)::Core.Compiler.Const(true, false)
└──      return %1
) => Bool

julia> selector1(x) = false
selector1 (generic function with 2 methods)

julia> @code_typed test1()
CodeInfo(
1 ─     return true
) => Bool

Of course, I’d recommend just using constant propagation and branches instead of Val dispatch:

const t = :symbol
selector3(x) = x == t
wrapper3(x) = selector3(x)
test3() = wrapper3(t)

julia> @code_typed test3()
CodeInfo(
1 ─     return true
) => Bool
2 Likes

Symbols definitely participate in constant propagation, otherwise getproperty would not work well.

1 Like

That helped! Apparently the compiler knows that the method will return a constant result but still leaves the call in if there is no fallback method present (unless T is a fieldles struct / empty tuple).

Sadly this approach is not possible in my real code because the methods are added dynamically so that the function acts as Dict with inferable value type.

Constant propagation is disabled by recursion, so for anything that’s not trivial, passing things around in types is probably the way to go.

1 Like