# Unexpected type instability with getproperty but not setproperty!

**URL:** <https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975>\
**Category:** Performance\
**Created:** [July 30, 2019, 12:50pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975 "2019-07-30T12:50:13Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![cshenton](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cshenton/32/9326_2.png) [@cshenton](https://discourse.julialang.org/u/cshenton)\
**Post date:** [July 30, 2019, 12:50pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/1 "2019-07-30T12:50:13Z")

</div>

Julia 1.1.0 on macOS mojave

In the following example, I’d expect branch elimination to make both of the following type stable, however `getproperty` isn’t type stable and is allocating. Could someone shed some light on why this is happening?

```julia

mutable struct Entry
    id::Int
    data::Vector{Float64}
end

function Base.getproperty(e::Entry, s::Symbol)
    if s === :value
        e.data[e.id]
    else
        getfield(e, s)
    end
end

function Base.setproperty!(e::Entry, s::Symbol, c)
    if s === :value
        e.data[e.id] = c
    else
        setfield!(e, s, c)
    end
end

function example_read(e)
    e.value
end

function example_write(e)
    e.value = 0.0
end

function main()
    storage = randn(1000000)
    entry = Entry(123, storage)
    @code_warntype example_read(entry)
    @code_warntype example_write(entry)
end

main()

```

Which results in

```julia
Body::Union{Float64, Int64, Array{Float64,1}}
1 ─ %1 = invoke Base.getproperty(_2::Entry, :value::Symbol)::Union{Float64, Int64, Array{Float64,1}}
└── return %1
Body::Float64
1 ─ goto #3 if not false
2 ─ nothing
3 ┄ %3 = (Main.getfield)(e, :data)::Array{Float64,1}
│ %4 = (Main.getfield)(e, :id)::Int64
│ (Base.arrayset)(true, %3, 0.0, %4)
└── return 0.0

```

(you’ll have to trust me that the improvement in the user interface for my use case is worth the effort here. This is just a minimal example)

How can I make the above type stable?

---

<div class="post-metadata">

**Author:** ![Karajan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/karajan/32/8545_2.png) [@Karajan](https://discourse.julialang.org/u/Karajan)\
**Post date:** [July 30, 2019, 1:07pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/2 "2019-07-30T13:07:59Z")

</div>

The problem seems to be the return type of `getproperty`, which either:

- returns a `Float64` if `s === :value` or else
- some other field. The options in your case are an `Int` (that would be `id`) or a `Vector{Float64}` (that would be `data`).

If you can get any of these, the matching type would be `Union{Float64, Int64, Vector{Float64}}` which is exactly what you got.

So if you want to be able to do what you are doing I don’t think you can avoid the type instability (think: what would you want the return type to be instead of the `Union` above?).

---

<div class="post-metadata">

**Author:** ![cshenton](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cshenton/32/9326_2.png) [@cshenton](https://discourse.julialang.org/u/cshenton)\
**Post date:** [July 30, 2019, 1:12pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/3 "2019-07-30T13:12:31Z")

</div>

I would have expected the other branches to be eliminated since `:value` is a compile time constant, re Stefan’s comment here [How to use `getproperty`/`setproperty!`?](https://discourse.julialang.org/t/how-to-use-getproperty-setproperty/16033).

For example:

```julia
const branch = true
example() = branch ? 0.0 : 0
@code_warntype example()

```

example() “looks” type unstable, but the compiler correctly eliminates the branch and then infers the type, meaning no type instability.

```julia
Body::Float64
1 ─ return 0.0

```

I’m asking why the same isn’t happening here, since (I think) `:value` is a compile time constant.

---

<div class="post-metadata">

**Author:** ![cshenton](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cshenton/32/9326_2.png) [@cshenton](https://discourse.julialang.org/u/cshenton)\
**Post date:** [July 30, 2019, 1:15pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/4 "2019-07-30T13:15:59Z")

</div>

Hmm but the same isn’t true if it takes an argument.

```julia
const branch = :value
example(foo) = foo === :value ? 0.0 : 0
@code_warntype example(branch)

```

yields

```julia
Body::Union{Float64, Int64}
1 ─ %1 = (foo === :value)::Bool
└── goto #3 if not %1
2 ─ return 0.0
3 ─ return 0

```

So I guess there’s only so much inference the compiler can do in that case. I’ll see if dispatching on value types doesn’t introduce much overhead.

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [July 30, 2019, 1:20pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/5 "2019-07-30T13:20:27Z")

</div>

As you’ve noted, `s` is not a compile time constant so you can’t at compile time tell if it is equal to `:value` or not. Have you tried a function that actually uses this syntax? The `s` is not a compile time constant in the `getproperty` definition (how could it define different behaviors then?), it’s constant in the context that calls `getproperty`. That constant will be propagated through the `getproperty` definition as long as the definition is not too complicated.

---

<div class="post-metadata">

**Author:** ![cshenton](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cshenton/32/9326_2.png) [@cshenton](https://discourse.julialang.org/u/cshenton)\
**Post date:** [July 30, 2019, 1:33pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/6 "2019-07-30T13:33:29Z")

</div>

So the context here is that I’m trying to provide a mutable struct like API for users writing some sort of kernel that will then be executed against data in some backing store (here represented by the `Vector{Float64}`. So the idea is that the user can write code against the entry itself like:

```julia
function user_code(e::Entry)
    current = e.value
    e.value = current ^ 2
end

```

In reality there would be several entry types each with a particular set of fields. Then behind the scenes I can execute their code like so.

```julia
function main()
    storage = randn(1000000)
    entry = Entry(0, storage)

    @time for i=1:1000000
        entry.id = i
        user_code(entry)
    end
end
# 0.055401 seconds (999.49 k allocations: 15.251 MiB)

```

So in the context of `user_code` in `main`, the value for `s` passed into `getpropery` is a compile time constant right? Since `:value` is written there in the code. That’s my confusion I guess. It’s constant in the calling context, but it doesn’t seem to get propagated.

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [July 30, 2019, 1:49pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/7 "2019-07-30T13:49:22Z")

</div>

Can you show the code warn output for that?

---

<div class="post-metadata">

**Author:** ![cshenton](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cshenton/32/9326_2.png) [@cshenton](https://discourse.julialang.org/u/cshenton)\
**Post date:** [July 30, 2019, 1:52pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/8 "2019-07-30T13:52:45Z")

</div>

New main (using previous definition of `user_code`)

```julia
function main()
    storage = randn(1000000)
    entry = Entry(1, storage)

    @code_warntype user_code(entry)
end

main()

```

Output

```julia
Body::Union{Float64, Int64}
1 ── %1 = invoke Base.getproperty(_2::Entry, :value::Symbol)::Union{Float64, Int64, Array{Float64,1}}
│ %2 = Base.literal_pow::Core.Compiler.Const(Base.literal_pow, false)
│ %3 = Main.:^::Core.Compiler.Const(^, false)
│ %4 = (isa)(%1, Float64)::Bool
└─── goto #3 if not %4
2 ── %6 = π (%1, Float64)
│ %7 = invoke %2(%3::typeof(^), %6::Float64, $(QuoteNode(Val{2}()))::Val{2})::Union{Float64, Int64}
└─── goto #8
3 ── %9 = (isa)(%1, Int64)::Bool
└─── goto #5 if not %9
4 ── %11 = π (%1, Int64)
│ %12 = (Base.mul_int)(%11, %11)::Int64
└─── goto #8
5 ── %14 = (isa)(%1, Array{Float64,1})::Bool
└─── goto #7 if not %14
6 ── %16 = π (%1, Array{Float64,1})
│ (%3)(%16, 2)
│ $(Expr(:unreachable))
└─── $(Expr(:unreachable))
7 ┄─ (Core.throw)(ErrorException("fatal error in type inference (type bound)"))
└─── $(Expr(:unreachable))
8 ┄─ %22 = φ (#2 => %7, #4 => %12)::Union{Float64, Int64}
└─── goto #10 if not false
9 ── nothing
10 ┄ %25 = (isa)(%22, Float64)::Bool
└─── goto #15 if not %25
11 ─ %27 = π (%22, Float64)
│ %28 = (:value === :value)::Bool
└─── goto #13 if not %28
12 ─ %30 = (Main.getfield)(e, :data)::Array{Float64,1}
│ %31 = (Main.getfield)(e, :id)::Int64
│ (Base.arrayset)(true, %30, %27, %31)
└─── goto #14
13 ─ (Main.setfield!)(e, :value, %27)
└─── goto #14
14 ┄ goto #21
15 ─ %37 = (isa)(%22, Int64)::Bool
└─── goto #20 if not %37
16 ─ %39 = π (%22, Int64)
│ %40 = (:value === :value)::Bool
└─── goto #18 if not %40
17 ─ %42 = (Main.getfield)(e, :data)::Array{Float64,1}
│ %43 = (Main.getfield)(e, :id)::Int64
│ %44 = (Base.sitofp)(Float64, %39)::Float64
│ (Base.arrayset)(true, %42, %44, %43)
└─── goto #19
18 ─ (Main.setfield!)(e, :value, %39)
└─── goto #19
19 ┄ goto #21
20 ─ (Core.throw)(ErrorException("fatal error in type inference (type bound)"))
└─── $(Expr(:unreachable))
21 ┄ return %22

```

So yep seems like something is going awry, though my understanding of the Julia IR is pretty much non existent.

---

<div class="post-metadata">

**Author:** ![cshenton](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cshenton/32/9326_2.png) [@cshenton](https://discourse.julialang.org/u/cshenton)\
**Post date:** [July 30, 2019, 1:56pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/9 "2019-07-30T13:56:02Z")

</div>

Similar situation on 1.3.0-alpha:

```julia
Variables
  #self#::Core.Compiler.Const(user_code, false)
  e::Entry
  current::Union{Float64, Int64, Array{Float64,1}}

Body::Union{Float64, Int64}
1 ─ (current = Base.getproperty(e, :value))
│ %2 = current::Union{Float64, Int64, Array{Float64,1}}
│ %3 = Core.apply_type(Base.Val, 2)::Core.Compiler.Const(Val{2}, false)
│ %4 =
(%3)()::Core.Compiler.Const(Val{2}(), false)
│ %5 = Base.literal_pow(Main.:^, %2, %4)::Union{Float64, Int64}
│ Base.setproperty!(e, :value, %5)
└── return %5

```

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [July 30, 2019, 1:56pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/10 "2019-07-30T13:56:16Z")

</div>

Constant propagation doesn’t really work with recursion. Use:

```julia
function Base.getproperty(e::Entry, s::Symbol)
    if s === :value
        getfield(e, :data)[getfield(e, :id)]
    else
        getfield(e, s)
    end
end

```

---

<div class="post-metadata">

**Author:** ![cshenton](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cshenton/32/9326_2.png) [@cshenton](https://discourse.julialang.org/u/cshenton)\
**Post date:** [July 30, 2019, 1:58pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/11 "2019-07-30T13:58:17Z")

</div>

> [@kristoffer.carlsson](#):
>
> function Base.getproperty(e::Entry, s::Symbol) if s === :value getfield(e, :data)[getfield(e, :id)] else getfield(e, s) end end

Wow you absolute hero, this totally fixes everything, and it makes perfect sense why what I was trying to do wouldn’t work.

Thanks everyone for your help. This is going to let me provide a super slick interface that doesn’t compromise on performance.

---

<div class="post-metadata">

**Author:** ![sdewaele](https://avatars.discourse-cdn.com/v4/letter/s/7ab992/32.png) [@sdewaele](https://discourse.julialang.org/u/sdewaele)\
**Post date:** [February 2, 2020, 12:29am UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/12 "2020-02-02T00:29:07Z")

</div>

Unfortunately, type instability reappears if we have more than property, as in this example:

```julia
struct MyStruct
  z::Float64
end

function Base.getproperty(x::MyStruct,s::Symbol)
  if s === :a
    x.z
  elseif s === :b
    0
  else
    getfield(x,s)
  end
end

f() = MyStruct(1.0).a

@code_warntype f()

```

returns:

```julia
Variables
  #self#::Core.Compiler.Const(f, false)

Body::Union{Float64, Int64}
1 ─ %1 = Main.MyStruct(1.0)::Core.Compiler.Const(MyStruct(1.0), false)
│ %2 = Base.getproperty(%1, :a)::Union{Float64, Int64}
└── return %2

```

Any thoughts on how to fix this?

```julia
julia> versioninfo()
Julia Version 1.3.1
Commit 2d5741174c (2019-12-30 21:36 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.1 (ORCJIT, skylake)
Environment:
  JULIA_NUM_THREADS = 2

```

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [February 2, 2020, 1:08am UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/13 "2020-02-02T01:08:36Z")

</div>

The issue is that your definition of `getproperty` is recursive, and this is too hard for const-prop / inliner / inference. You should write

```julia
julia> @inline function Base.getproperty(e::Entry, s::Symbol)
           if s === :value
               getfield(e, :data)[getfield(e, :id)]
           else
               getfield(e, s)
           end
       end

```

---

<div class="post-metadata">

**Author:** ![Shuhua](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/shuhua/32/27618_2.png) [@Shuhua](https://discourse.julialang.org/u/Shuhua)\
**Post date:** [February 3, 2021, 10:36am UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/14 "2021-02-03T10:36:59Z")

</div>

When inspecting the `getproperty` code, why do we need to wrap it in a function like `f() = MyStruct(1.0).a` above?

```julia
struct MyStruct
    z::Float64
  end
  
  function Base.getproperty(x::MyStruct,s::Symbol)
    if s === :a
      getfield(x, :z)
    elseif s === :b
      0
    else
      getfield(x,s)
    end
  end
  
  f() = MyStruct(1.0).a
  
  @code_warntype MyStruct(1.0).a

```

which gives output

```julia
Variables
  #self#::Core.Compiler.Const(f, false)

Body::Float64
1 ─ %1 = Main.MyStruct(1.0)::Core.Compiler.Const(MyStruct(1.0), false)
│ %2 = Base.getproperty(%1, :a)::Core.Compiler.Const(1.0, false)
└── return %2

```

However, unwrapped `@code_warntype MyStruct(1.0).a` yields

```julia
Variables
  #self#::Core.Compiler.Const(getproperty, false)
  x::MyStruct
  s::Symbol

Body::Union{Float64, Int64}
1 ─ %1 = (s === :a)::Bool
└── goto #3 if not %1
2 ─ %3 = Main.getfield(x, :z)::Float64
└── return %3
3 ─ %5 = (s === :b)::Bool
└── goto #5 if not %5
4 ─ return 0
5 ─ %8 = Main.getfield(x, s)::Float64
└── return %8

```

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [February 3, 2021, 5:44pm UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/15 "2021-02-03T17:44:37Z")

</div>

> [@Shuhua](#):
>
> When inspecting the `getproperty` code, why do we need to wrap it in a function like `f() = MyStruct(1.0).a` above?

Because otherwise you won’t get the constant propagation needed. Without a wrapper you are just asking what the return type of calling the function with any `Symbol` is.

---

<div class="post-metadata">

**Author:** ![Shuhua](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/shuhua/32/27618_2.png) [@Shuhua](https://discourse.julialang.org/u/Shuhua)\
**Post date:** [February 4, 2021, 2:43am UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/16 "2021-02-04T02:43:07Z")

</div>

> [@kristoffer.carlsson](#):
>
> asking what the return type of calling the function with any `Symbol` is

Thanks for your reply. But I still do not understand. We are calling `MyStruct(1.0).a` in `@code_warntype MyStruct(1.0).a`, and the symbol is obviously `:a`. Why any symbol?

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [February 4, 2021, 6:08am UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/17 "2021-02-04T06:08:45Z")

</div>

Because of how the way that macro works (it calls `typeof` on all the variables). You can use `@macroexpand` to see what it gets expanded to.

---

<div class="post-metadata">

**Author:** ![Shuhua](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/shuhua/32/27618_2.png) [@Shuhua](https://discourse.julialang.org/u/Shuhua)\
**Post date:** [February 4, 2021, 6:34am UTC](https://discourse.julialang.org/t/unexpected-type-instability-with-getproperty-but-not-setproperty/26975/18 "2021-02-04T06:34:02Z")

</div>

Got it now. Thank you. For reference:

```julia
 @macroexpand @code_warntype MyStruct(1.0).a

```

gives

```julia
:(InteractiveUtils.code_warntype(getproperty, (Base.typesof)(MyStruct(1.0), :a)))

```

while `(Base.typesof)(MyStruct(1.0), :a)` yields `Tuple{MyStruct,Symbol}`. That is essentially calling `getproperty` with any symbol.
