Stream: helpdesk (published)

Topic: ✔ Base.getproperty, type stability, and constant propagation


view this post on Zulip Mark Kittisopikul (Jun 27 2022 at 23:29):

I am a bit confused how the use of Base.getproperty is type stable. I understand that this is due to constant propagation, but I am confused how that works in this case.

view this post on Zulip Mark Kittisopikul (Jun 27 2022 at 23:34):

Why can Julia figure out the type in the second case but not the first case?

julia> @code_warntype (a = 5, b = 5.0).a
MethodInstance for getproperty(::NamedTuple{(:a, :b), Tuple{Int64, Float64}}, ::Symbol)
  from getproperty(x, f::Symbol) in Base at Base.jl:42
Arguments
  #self#::Core.Const(getproperty)
  x::NamedTuple{(:a, :b), Tuple{Int64, Float64}}
  f::Symbol
Body::Union{Float64, Int64}
1 ─      nothing
│   %2 = Base.getfield(x, f)::Union{Float64, Int64}
└──      return %2


julia> @code_warntype (x->x.a)((a = 5, b = 5.0))
MethodInstance for (::var"#21#22")(::NamedTuple{(:a, :b), Tuple{Int64, Float64}})
  from (::var"#21#22")(x) in Main at REPL[36]:1
Arguments
  #self#::Core.Const(var"#21#22"())
  x::NamedTuple{(:a, :b), Tuple{Int64, Float64}}
Body::Int64
1 ─ %1 = Base.getproperty(x, :a)::Int64
└──      return %1

view this post on Zulip Michael Fiano (Jun 27 2022 at 23:43):

That is a really good question. I've been staring at this for 5 minutes with my compiler theory background, and I'd expect the latter to have a more difficult time. I'm fairly new to Julia's compiler, only looked at certain parts of it, but it seems like this deserves an issue the harder I stare at it.

view this post on Zulip Mark Kittisopikul (Jun 27 2022 at 23:49):

My rough understanding is that in the first case we're looking at Base.getproperty itself. In the second case, we've wrapped it into an anonymous function. By wrapping it into an anonymous function, we've allowed constant propagation to occur.

Is there a special case within the compiler forBase.getproperty that triggers constant propagation within a function? Is it important that we are using a symbol?

view this post on Zulip Michael Fiano (Jun 27 2022 at 23:49):

The only real difference is the former is in the toplevel lexical environment, and the latter is in the lexical environment of the function., but they are both immutable constants and should be stack/register allocated, so this is weird.

view this post on Zulip Michael Fiano (Jun 27 2022 at 23:59):

@Mark Kittisopikul https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/15

view this post on Zulip Mark Kittisopikul (Jun 28 2022 at 00:01):

Yes, I've been reading that. I think I see what Kristoffer is saying now after reading it for the 5th time though.

view this post on Zulip Mark Kittisopikul (Jun 28 2022 at 00:01):

julia> @macroexpand @code_warntype (a = 5, b = 5.0).a
:(InteractiveUtils.code_warntype(getproperty, (Base.typesof)((a = 5, b = 5.0), :a)))

view this post on Zulip Michael Fiano (Jun 28 2022 at 00:02):

I am trying to make sense of it still. It seems to be just how @code_warntype is implemented. Maybe try looking at the lowered or native code of both?

view this post on Zulip Michael Fiano (Jun 28 2022 at 00:04):

Cthulhu might come in really handy here

view this post on Zulip Mark Kittisopikul (Jun 28 2022 at 00:05):

In the first case, we are just asking for result of giving getproperty a NamedTuple and a Symbol. There is no constant :a to propagate.

julia> InteractiveUtils.code_warntype(getproperty, Tuple{NamedTuple{(:a, :b), Tuple{Int64, Float64}}, Symbol})
MethodInstance for getproperty(::NamedTuple{(:a, :b), Tuple{Int64, Float64}}, ::Symbol)
  from getproperty(x, f::Symbol) in Base at Base.jl:42
Arguments
  #self#::Core.Const(getproperty)
  x::NamedTuple{(:a, :b), Tuple{Int64, Float64}}
  f::Symbol
Body::Union{Float64, Int64}
1 ─      nothing
│   %2 = Base.getfield(x, f)::Union{Float64, Int64}
└──      return %2

view this post on Zulip Michael Fiano (Jun 28 2022 at 00:08):

Aye. This seems to be another case of "prefer functions over toplevel data"

view this post on Zulip chriselrod (Jun 28 2022 at 02:06):

Yeah, put the second into a function but with .a as a constant, and it'll propagate just fine.

view this post on Zulip Notification Bot (Jun 28 2022 at 05:09):

Mark Kittisopikul has marked this topic as resolved.

view this post on Zulip Mark Kittisopikul (Jun 28 2022 at 05:10):

I learned something here. I still want to explore the bounds of how this works, but I think I understand more than when I did several hours ago.

view this post on Zulip Sukera (Jun 28 2022 at 19:01):

"don't benchmark in global scope" really should be "don't inspect compiler optimizations in global scope" :thinking: if it requires constant prop, you need a compiled context to propagate in, which only happens in functions


Last updated: Dec 28 2024 at 04:38 UTC