What does `$(Expr(:static_parameter, 1))` do?

After I ran across this here, @dfdx pointed me to @Oscar_Smith 's suggestion of better documentation for this here. But I still have no idea what this does, or how to handle it when working with ASTs. Can someone give some more detail please?

2 Likes

It’s not valid in the surface-level AST, only in unspecialized Julia IR, so you should never encounter it in macros. It refers to the static parameters of a method definition, i.e. the names on the right side of the where. You can see that in the following example all references to S were replaced by Expr(:static_parameter, 1) and similarly for T:

julia> f(x::S, y::T) where {S, T} = S, T
f (generic function with 1 method)

julia> @code_lowered f(1, 2)
CodeInfo(
1 ─ %1 = Core.tuple($(Expr(:static_parameter, 1)), $(Expr(:static_parameter, 2)))
└──      return %1
)
1 Like

Thanks @simeonschaub . This seems tricky, because the semantics depend on values outside the expression (the types corresponding to static parameters), and those values are never explicitly assigned in the lowered code. So in your example,

CodeInfo(
1 ─ %1 = Core.tuple($(Expr(:static_parameter, 1)), $(Expr(:static_parameter, 2)))
└──      return %1
)

All we have is “static parameters of the method” but no information about what that method is. Is that right? So in general, lowered code isn’t enough to run things, we need information about the call stack at the AST level?

What does the compiler gain by not assigning the static parameters… statically? I mean, I would have expected something more like

CodeInfo(
1 ─ %1 = Core.Int64
│   %2 = Core.Int64
│   %3 = Core.tuple(%1, %2)
└──      return %3
)

I guess more to the point, what are best practices for working with static parameters?

1 Like

I’d assume you would have an problem with overspecialization here?

Yes. Not really at the AST-level or the entire call stack, but you need to know about the method specialization.

That’s exactly what the optimizer will eventually do (if it’s specializing on concrete types):

julia> @code_typed f(1, 2)
CodeInfo(
1 ─     return (Int64, Int64)
) => Tuple{DataType, DataType}

That’s not what code_lowered represents though, lowering is purely based on the original AST and doesn’t reason about types.

1 Like

It entirely depends on what you’re doing. Tools like Cassette typically call Meta.partially_inline! to prepare lowered code for rewriting and emitting from a generated function, but there’s a lot more additional complexity that comes with that, so I’d recommend just using those packages if that’s what you want to do.

1 Like

Right, as @simeonschaub points out, I was confusing lowered vs typed code.

That’s interesting, thanks. That function also has a really nice docstring, so that’s also helpful.

Can we safely assume that Expr(:static_parameter, n) always refers to the parameters of the function being currently analyzed? At least in Umlaut I actually have access to the function and its arguments, so perhaps I can get the values of the static parameters with something like this:

julia> f(::Val{N}) where N = N + 1

julia> @code_lowered f(Val(3))
CodeInfo(
1 ─ %1 = $(Expr(:static_parameter, 1)) + 1
└──      return %1
)

julia> f(Val(4))
5

julia> mi = Base.method_instances(f, (4,))[1]
MethodInstance for f(::4)

julia> mi.sparam_vals[1]
4

AFAIU, yes.

1 Like