Why the compiler can't optimize this simple code?

Indeed. If you look at @code_llvm optimize=false, which is what Julia provides as information to LLVM:

  %box_Int64 = call nonnull align 8 dereferenceable(8) ptr @ijl_box_int64(i64 signext %"new::A.i") #2
  %box_Int643 = call nonnull align 8 dereferenceable(8) ptr @ijl_box_int64(i64 signext %"v::Int64") #2
  %jl_f_setfield_ret = call nonnull ptr (ptr, ptr, ...) @julia.call(ptr @jl_f_setfield, ptr null, ptr %"new::A", ptr %box_Int64, ptr %box_Int643)

After optimizations, LLVM does actually know that this is constant.

  %box_Int64 = call nonnull align 8 dereferenceable(8) ptr @ijl_box_int64(i64 signext 1) #11
  %box_Int643 = call nonnull align 8 dereferenceable(8) ptr @ijl_box_int64(i64 signext %"v::Int64") #11
  ; ...
  %jl_f_setfield_ret = call nonnull ptr @jl_f_setfield(ptr null, ptr nonnull %jlcallframe1, i32 3)

But we haven’t written a pass and LLVM does not have nearly enough information to turn a setfield with an eventually const element like that into the corresponding pointer store.

One way (at the expense of bloating the IR perhaps), would be for Julia to the inlined code for all possible variants in the hope that LLVM will be able to const-propagate. This is complicated by the fact that the setfield(v, ::Int) version is probably less common than setfield(v, ::Symbol).

If anyone wants to try here are some crumbs:

So when fld is not constant, but well-typed, one would need to loop over all possible fields and emit a select, the tricky things is to also get all the errors etc right, and handle symbols.

Then we would need to benchmark and study the impact on codesize, a similar change would also be necessary for getfield since those ought to be symmetric.

6 Likes