# Type Inference Problem with \`getproperty\`

**URL:** https://discourse.julialang.org/t/type-inference-problem-with-getproperty/54585
**Category:** Performance
**Created:** [February 4, 2021, 3:14am UTC](https://discourse.julialang.org/t/type-inference-problem-with-getproperty/54585 "2021-02-04T03:14:35Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![drbassett](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/drbassett/32/21679_2.png) [@drbassett](https://discourse.julialang.org/u/drbassett)
#### Post date: [February 4, 2021, 3:14am UTC](https://discourse.julialang.org/t/type-inference-problem-with-getproperty/54585/1 "2021-02-04T03:14:35Z")

</div>

I apologize in advance for the long post, but I did a good deal of my own analysis first to make sure I was understanding the situation correctly. I wanted to post my findings to save others the trouble of going through the same steps I did.

Getting to the actual problem, I am puzzled by the code generated by an overload of the `getproperty` function. I created a struct to represent a 2D vector:

```julia
struct Vec2
	x::Float64
	y::Float64
end

```

For implementing math operators, reinterpreting the vector as a tuple is convenient. Likewise, swizzle operators are often useful. Since these are both cheap, data-rearranging operations, `getproperty` seems like a reasonable way to implement them:

```julia
function Base.getproperty(v::Vec2, name::Symbol)
	if name == :tuple
		(v.x, v.y)
	elseif name == :yx
		Vec2(v.y, v.x)
	else
		getfield(v, name)
	end
end

```

Wrapping one of the `getproperty` calls in a function, and calling `code_native` on it reveals the first problem: `getproperty` is not inlined:

```julia
julia> f(v::Vec2) = v.yx;
julia> code_native(f, (Vec2,); syntax=:intel, debuginfo=:none);
        .text
        push rbp
        mov rbp, rsp
        push rsi
        push rdi
        sub rsp, 48
        mov rsi, rcx
        movabs rax, offset getproperty
        lea rdi, [rbp - 32]
        mov r8d, 397788528
        mov rcx, rdi
        call rax
        test dl, dl
        cmovns rax, rdi
        vmovups xmm0, xmmword ptr [rax]
        vmovups xmmword ptr [rsi], xmm0
        mov rax, rsi
        add rsp, 48
        pop rdi
        pop rsi
        pop rbp
        ret
        nop

```

This is easily fixed by adding the `@inline` tag to `getproperty`. After that change, accessing the `yx` property produces the expected code:

```julia
julia> code_native(f, (Vec2,); syntax=:intel, debuginfo=:none);
        .text
        push rbp
        mov rbp, rsp
        mov rax, rcx
        vpermilps xmm0, xmmword ptr [rdx], 78 # xmm0 = mem[2,3,0,1]
        vmovups xmmword ptr [rax], xmm0
        pop rbp
        ret
        nop word ptr cs:[rax + rax]
        nop dword ptr [rax]

```

Not only does the compiler succeed in all the expected constant propagation and branch elimination optimizations, but recognizes that the swizzle operation can be implemented using single permute instruction. Impressive. However, the `tuple` property is still a problem. I won’t bother posting the assembly output, because it is lengthy; suffice it to say that it is very wrong. So, for some reason, wrapping the tuple operation in the `getproperty` function seems to confuse the optimizer. Using `code_warntype` reveals a potential problem. I introduce a temporary variable `x` to make the type of the `tuple` property clear:

```julia
julia> function f(v::Vec2)
           x = v.tuple
           x
       end
julia> code_warntype(f, (Vec2,))
Variables
  #self#::Core.Compiler.Const(f, false)
  v::Vec2
  x::Tuple{Union{Float64, Vec2, Tuple{Any,Any}},Union{Float64, Vec2, Tuple{Any,Any}}}

Body::Tuple{Union{Float64, Vec2, Tuple{Any,Any}},Union{Float64, Vec2, Tuple{Any,Any}}}
1 ─ (x = Base.getproperty(v, :tuple))
└── return x

```

Doing the same exercise for the `yx` property tells a different story:

```julia
julia> function f(v::Vec2)
           x = v.yx
           x
       end
julia> code_warntype(f, (Vec2,))
Variables
  #self#::Core.Compiler.Const(f, false)
  v::Vec2
  x::Vec2

Body::Vec2
1 ─ (x = Base.getproperty(v, :yx))
└── return x

```

So it looks like this is a type inference problem. So question #1 is: how is it that the type of the `yx` property can be properly inferred, but not the `tuple` property?

Moving on, the fact that a `Tuple{Any,Any}` is part of the type indicates to me that the type inference of `(v.x, v.y)` is going awry. After some trial and error, I was able to coerce `getproperty` into doing the right thing:

```julia
@inline function Base.getproperty(v::Vec2, name::Symbol)
	if name == :tuple
		#(v.x, v.y) # Original implementation
		#NTuple{2,Float64}(v.x, v.y) # Doesn't work
		(Float64(v.x), Float64(v.y)) # Works!
		#(getfield(v, :x), getfield(v, :y)) # Also works
	elseif name == :yx
		Vec2(v.y, v.x)
	else
		getfield(v, name)
	end
end

```

With this implementation, I finally get the assembly I expect:

```julia
f(v::Vec2) = v.tuple
julia> code_native(f, (Vec2,); syntax=:intel, debuginfo=:none);
        .text
        push rbp
        mov rbp, rsp
        mov rax, rcx
        vmovups xmm0, xmmword ptr [rdx]
        vmovups xmmword ptr [rax], xmm0
        pop rbp
        ret
        nop word ptr cs:[rax + rax]
        nop dword ptr [rax + rax]

```

Which leads me to question #2 is: why does this particular incantation fix the type inference problem? I would have expected the type inference system to be able to do this transformation on its own.

And finally, question #3 is: is there a better way to write `getproperty` that avoids this problem?

In case it is relevant, here is my sytem information:

```julia
julia> versioninfo()
Julia Version 1.5.3
Commit 788b2c77c1 (2020-11-09 13:37 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i7-5930K CPU @ 3.50GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-9.0.1 (ORCJIT, haswell)

```

---

<div class="post-metadata">

### Author: ![marius311](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marius311/32/3953_2.png) [@marius311](https://discourse.julialang.org/u/marius311)
#### Post date: [February 4, 2021, 3:31am UTC](https://discourse.julialang.org/t/type-inference-problem-with-getproperty/54585/2 "2021-02-04T03:31:16Z")

</div>

The problem is that your `getproperty` is recursive since `v.x` and `v.y` from inside the body will also result in a call to `getproperty`. Recursion isn’t necessarily (or even usually) a problem for inference, but there’s various heuristic limits on how hard inference tries to go through the recursion, made more complicated here by the fact that there’s also constant propagation needed to get the right answer. A more expert than me could probably tell you the details, but I think its basically just hitting those limits.

The reason `:yx` works is because the return value in that case is `Vec2(...)`, so it doesn’t matter what’s in the `...`, the compiler knows the answer can only be a `Vec2`, which is a concrete type.

The solution for `:tuple` is just to use `getfield` instead and skip the recursion, e.g. this works:

```julia
	if name == :tuple
		(getfield(v,:x), getfield(v,:y))

```

---

<div class="post-metadata">

### Author: ![drbassett](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/drbassett/32/21679_2.png) [@drbassett](https://discourse.julialang.org/u/drbassett)
#### Post date: [February 4, 2021, 4:24am UTC](https://discourse.julialang.org/t/type-inference-problem-with-getproperty/54585/3 "2021-02-04T04:24:59Z")

</div>

Thanks for your reply; that clears things up to some extent. I read a similar post where recursion was mentioned as a problem with optimizing `getproperty`, but I was confused since `Vec2(v.y, v.x)` is also recursive, but works just fine. I guess knowing the return type helps the compiler just enough to cause it to do the right thing; but what’s still puzzling to me is why `NTuple{2,Float64}(v.y, v.x)` doesn’t work then. This is almost the same as doing `Vec2(v.y, v.x)` in that the return type is explicit, but still generates the lengthy code.

It seems like the only reliable way to write performant `getproperty` functions is to always use `getfield` inside them, rather than accessing fields through the dot operator. This is unfortunate, because many people will make the same mistake I did (since this is the obvious way to implement custom properties), and their performance may suffer for it. Perhaps the documentation for `getproperty` should be updated to warn people about this problem?

---

<div class="post-metadata">

### Author: ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)
#### Post date: [February 4, 2021, 6:02am UTC](https://discourse.julialang.org/t/type-inference-problem-with-getproperty/54585/4 "2021-02-04T06:02:07Z")

</div>

> [@drbassett](#):
>
> `if name == :tuple`

You may also try to use `if name === :tuple`, note the triple equals signs, to compare against singleton symbols. This is supposed to make it easier for the compiler. I haven’t tried it, but it might make the compiler go further here.

---

<div class="post-metadata">

### Author: ![marius311](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marius311/32/3953_2.png) [@marius311](https://discourse.julialang.org/u/marius311)
#### Post date: [February 4, 2021, 8:44pm UTC](https://discourse.julialang.org/t/type-inference-problem-with-getproperty/54585/5 "2021-02-04T20:44:40Z")

</div>

> [@drbassett](#):
>
> This is unfortunate, because many people will make the same mistake I did (since this is the obvious way to implement custom properties), and their performance may suffer for it. Perhaps the documentation for `getproperty` should be updated to warn people about this problem?

Yea, I think that’d be worthwhile. The current [docstring](https://docs.julialang.org/en/v1/base/base/#Base.getproperty) even shows an example using `getproperty` recursively, so further implies that that’s how you should do it (of couse to be fair it will _sometimes_ be performant). I’d encourage you to open a PR improving it if you have the time!

---

<div class="post-metadata">

### Author: ![drbassett](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/drbassett/32/21679_2.png) [@drbassett](https://discourse.julialang.org/u/drbassett)
#### Post date: [February 6, 2021, 10:17pm UTC](https://discourse.julialang.org/t/type-inference-problem-with-getproperty/54585/6 "2021-02-06T22:17:44Z")

</div>

Agreed, I should have used `===` instead; though I imagine for `Symbol`, `==` and `===` reliably get optimized to the same native code. I did try this for curiosity’s sake, and it didn’t make any difference in the generated native code.

---

<div class="post-metadata">

### Author: ![drbassett](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/drbassett/32/21679_2.png) [@drbassett](https://discourse.julialang.org/u/drbassett)
#### Post date: [February 6, 2021, 10:19pm UTC](https://discourse.julialang.org/t/type-inference-problem-with-getproperty/54585/7 "2021-02-06T22:19:41Z")

</div>

I have submitted a PR adding a documentation warning about this problem here:

[https://github.com/JuliaLang/julia/pull/39551](https://github.com/JuliaLang/julia/pull/39551)
