Reassigning variable in do syntax is not optimized, ``Ref`` is, what is the difference?

question

#1

I frequently stumble about examples similar to the following, where I have a helper function which supports do style anonymous functions

helper(cont, range) = for i ∈ range
  cont(i)
end

and want to write something which uses it by for instance accumulating the results

function f()
  a = 0
  helper(1:4) do i
    a += i
  end
  a
end

the generated code looks super complicated

julia> @code_llvm f()
julia> @code_llvm f()
; Function f
; Location: D:\ProjectsJulia\Traits\test\tmp.jl:76
; Function Attrs: uwtable
define nonnull %jl_value_t addrspace(10)* @japi1_f_37345(%jl_value_t addrspace(10)*, %jl_value_t add
rspace(10)**, i32) #0 {
top:
  %gcframe = alloca %jl_value_t addrspace(10)*, i32 4
  %3 = bitcast %jl_value_t addrspace(10)** %gcframe to i8*
  call void @llvm.memset.p0i8.i32(i8* %3, i8 0, i32 32, i32 0, i1 false)
  %4 = alloca %jl_value_t addrspace(10)**, align 8
  store volatile %jl_value_t addrspace(10)** %1, %jl_value_t addrspace(10)*** %4, align 8
  %5 = call %jl_value_t*** inttoptr (i64 18155948864 to %jl_value_t*** ()*)() #6
; Function Type; {
; Location: boot.jl:328
  %6 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 0
  %7 = bitcast %jl_value_t addrspace(10)** %6 to i64*
  store i64 4, i64* %7
  %8 = getelementptr %jl_value_t**, %jl_value_t*** %5, i32 0
  %9 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1
  %10 = bitcast %jl_value_t addrspace(10)** %9 to %jl_value_t***
  %11 = load %jl_value_t**, %jl_value_t*** %8
  store %jl_value_t** %11, %jl_value_t*** %10
  %12 = bitcast %jl_value_t*** %8 to %jl_value_t addrspace(10)***
  store %jl_value_t addrspace(10)** %gcframe, %jl_value_t addrspace(10)*** %12
  %13 = bitcast %jl_value_t*** %5 to i8*
  %14 = call noalias nonnull %jl_value_t addrspace(10)* @jl_gc_pool_alloc(i8* %13, i32 1488, i32 16)
 #1
  %15 = bitcast %jl_value_t addrspace(10)* %14 to %jl_value_t addrspace(10)* addrspace(10)*
  %16 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)* addrspace(10)* %15, i64
 -1
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 21670413472 to %jl_valu
e_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)* addrspace(10)* %16
  %17 = addrspacecast %jl_value_t addrspace(10)* %14 to %jl_value_t addrspace(11)*
  %18 = bitcast %jl_value_t addrspace(11)* %17 to %jl_value_t addrspace(10)* addrspace(11)*
;}
  %19 = bitcast %jl_value_t addrspace(10)* %14 to %jl_value_t addrspace(10)* addrspace(10)*
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 70946288 to %jl_value_t
*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)* addrspace(10)* %19, align 8
  %20 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 3
  store %jl_value_t addrspace(10)* %14, %jl_value_t addrspace(10)** %20
; Location: D:\ProjectsJulia\Traits\test\tmp.jl:77
  %21 = call noalias nonnull %jl_value_t addrspace(10)* @jl_gc_pool_alloc(i8* %13, i32 1488, i32 16)
 #1
  %22 = bitcast %jl_value_t addrspace(10)* %21 to %jl_value_t addrspace(10)* addrspace(10)*
  %23 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)* addrspace(10)* %22, i64
 -1
  store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 135523168 to %jl_value_
t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)* addrspace(10)* %23
  %24 = bitcast %jl_value_t addrspace(10)* %21 to %jl_value_t addrspace(10)* addrspace(10)*
  store %jl_value_t addrspace(10)* %14, %jl_value_t addrspace(10)* addrspace(10)* %24, align 8
  %25 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 2
  store %jl_value_t addrspace(10)* %21, %jl_value_t addrspace(10)** %25
; Function helper; {
; Location: D:\ProjectsJulia\Traits\test\tmp.jl:62
  %26 = call nonnull %jl_value_t addrspace(10)* @"julia_#31_37346"(%jl_value_t addrspace(10)* nonnull %21, i64 1)
  %27 = call nonnull %jl_value_t addrspace(10)* @"julia_#31_37346"(%jl_value_t addrspace(10)* nonnull %21, i64 2)
  %28 = call nonnull %jl_value_t addrspace(10)* @"julia_#31_37346"(%jl_value_t addrspace(10)* nonnull %21, i64 3)
  %29 = call nonnull %jl_value_t addrspace(10)* @"julia_#31_37346"(%jl_value_t addrspace(10)* nonnull %21, i64 4)
;}
; Location: D:\ProjectsJulia\Traits\test\tmp.jl:80
  %30 = load %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)* addrspace(11)* %18, align 8
  %31 = icmp eq %jl_value_t addrspace(10)* %30, null
  br i1 %31, label %err, label %pass

pass:                                             ; preds = %top
  %32 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1
  %33 = load %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %32
  %34 = getelementptr %jl_value_t**, %jl_value_t*** %5, i32 0
  %35 = bitcast %jl_value_t*** %34 to %jl_value_t addrspace(10)**
  store %jl_value_t addrspace(10)* %33, %jl_value_t addrspace(10)** %35
  ret %jl_value_t addrspace(10)* %30

err:                                              ; preds = %top
  call void @jl_undefined_var_error(%jl_value_t addrspace(12)* addrspacecast (%jl_value_t* inttoptr (i64 70912160 to %jl_value_t*) to %jl_value_t addrspace(12)*))
  unreachable
}

with the little help of Ref, everything condenses nicely to a constant function

function g()
  a = Ref(0)
  helper(1:4) do i
    a.x += i
  end
  a.x
end

gives

julia> @code_llvm g()
; Function g
; Location: D:\ProjectsJulia\Traits\test\tmp.jl:67
; Function Attrs: uwtable
define i64 @julia_g_37350() #0 {
top:
; Location: D:\ProjectsJulia\Traits\test\tmp.jl:71
  ret i64 10
}

What is the decisive difference here?
Can I adapt f() so that it gains the same powerup as g()?

extra: @code_warntype look similar

here the @code_warntype - I cannot spot the decisive difference unfortunately, it looks astonishingly similar to me

julia> @code_warntype f()
Body::Any
76 1 ── %1  = %new(Core.Box)::Core.Box                                                                                                                                           │╻     Type
   │          (Core.setfield!)(%1, :contents, 0)                                                                                                                                 │
77 │    %3  = %new(Main.:(##35#36), %1)::getfield(Main, Symbol("##35#36"))                                                                                                       │
   │          (Base.ifelse)(true, 4, 0)                                                                                                                                          ││╻╷    Type
   │    %5  = (Base.slt_int)(4, 1)::Bool                                                                                                                                         ││╻╷╷╷  iterate
   └───       goto #3 if not %5                                                                                                                                                  │││
   2 ──       goto #4                                                                                                                                                            │││
   3 ──       goto #4                                                                                                                                                            │││
   4 ┄─ %9  = φ (#2 => true, #3 => false)::Bool                                                                                                                                  ││
   │    %10 = φ (#3 => 1)::Int64                                                                                                                                                 ││
   │    %11 = φ (#3 => 1)::Int64                                                                                                                                                 ││
   │    %12 = (Base.not_int)(%9)::Bool                                                                                                                                           ││
   └───       goto #10 if not %12                                                                                                                                                ││
   5 ┄─ %14 = φ (#4 => %10, #9 => %22)::Int64                                                                                                                                    ││
   │    %15 = φ (#4 => %11, #9 => %23)::Int64                                                                                                                                    ││
   │          invoke %3(%14::Int64)                                                                                                                                              ││
   │    %17 = (%15 === 4)::Bool                                                                                                                                                  │││╻     ==
   └───       goto #7 if not %17                                                                                                                                                 │││
   6 ──       goto #8                                                                                                                                                            │││
   7 ── %20 = (Base.add_int)(%15, 1)::Int64                                                                                                                                      │││╻     +
   └───       goto #8                                                                                                                                                            ││╻     iterate
   8 ┄─ %22 = φ (#7 => %20)::Int64                                                                                                                                               ││
   │    %23 = φ (#7 => %20)::Int64                                                                                                                                               ││
   │    %24 = φ (#6 => true, #7 => false)::Bool                                                                                                                                  ││
   │    %25 = (Base.not_int)(%24)::Bool                                                                                                                                          ││
   └───       goto #10 if not %25                                                                                                                                                ││
   9 ──       goto #5                                                                                                                                                            ││
   10 ─       goto #11                                                                                                                                                           ││
80 11 ─ %29 = (Core.isdefined)(%1, :contents)::Bool                                                                                                                              │
   └───       goto #13 if not %29                                                                                                                                                │
   12 ─       goto #14                                                                                                                                                           │
   13 ─       $(Expr(:throw_undef_if_not, :a, false))                                                                                                                            │
   14 ┄ %33 = (Core.getfield)(%1, :contents)::Any                                                                                                                                │
   └───       return %33                                                                                                                                                         │

julia> @code_warntype g()
Body::Int64
67 1 ── %1  = %new(Base.RefValue{Int64}, 0)::Base.RefValue{Int64}                                                                                                          │╻╷╷   Type
68 │          (Base.ifelse)(true, 4, 0)                                                                                                                                    │╻╷╷   Colon
   │    %3  = (Base.slt_int)(4, 1)::Bool                                                                                                                                   ││╻╷╷╷  iterate
   └───       goto #3 if not %3                                                                                                                                            │││
   2 ──       goto #4                                                                                                                                                      │││
   3 ──       goto #4                                                                                                                                                      │││
   4 ┄─ %7  = φ (#2 => true, #3 => false)::Bool                                                                                                                            ││
   │    %8  = φ (#3 => 1)::Int64                                                                                                                                           ││
   │    %9  = φ (#3 => 1)::Int64                                                                                                                                           ││
   │    %10 = (Base.not_int)(%7)::Bool                                                                                                                                     ││
   └───       goto #10 if not %10                                                                                                                                          ││
   5 ┄─ %12 = φ (#4 => %8, #9 => %22)::Int64                                                                                                                               ││
   │    %13 = φ (#4 => %9, #9 => %23)::Int64                                                                                                                               ││
   │    %14 = (Base.getfield)(%1, :x)::Int64                                                                                                                               ││╻╷    #29
   │    %15 = (Base.add_int)(%14, %12)::Int64                                                                                                                              │││╻     +
   │          (Base.setfield!)(%1, :x, %15)                                                                                                                                │││╻     setproperty!
   │    %17 = (%13 === 4)::Bool                                                                                                                                            │││╻     ==
   └───       goto #7 if not %17                                                                                                                                           │││
   6 ──       goto #8                                                                                                                                                      │││
   7 ── %20 = (Base.add_int)(%13, 1)::Int64                                                                                                                                │││╻     +
   └───       goto #8                                                                                                                                                      ││╻     iterate
   8 ┄─ %22 = φ (#7 => %20)::Int64                                                                                                                                         ││
   │    %23 = φ (#7 => %20)::Int64                                                                                                                                         ││
   │    %24 = φ (#6 => true, #7 => false)::Bool                                                                                                                            ││
   │    %25 = (Base.not_int)(%24)::Bool                                                                                                                                    ││
   └───       goto #10 if not %25                                                                                                                                          ││
   9 ──       goto #5                                                                                                                                                      ││
   10 ─       goto #11                                                                                                                                                     ││
71 11 ─ %29 = (Base.getfield)(%1, :x)::Int64                                                                                                                               │╻     getproperty
   └───       return %29            

#2

using Traceur.jl there is a indeed a huge difference, however I am too unexperienced about how to use the Traceur suggestions from @trace f() to improve it towards g()

julia> @trace f()
┌ Warning: dynamic dispatch to (Core.getfield)((Core.getfield)(#self#, a), contents) + i
└ @ D:\ProjectsJulia\Traits\test\tmp.jl:69
┌ Warning: getfield(Main, Symbol("##13#14"))(Core.Box(1)) returns Any
└ @ D:\ProjectsJulia\Traits\test\tmp.jl:69
┌ Warning: dynamic dispatch to (Core.getfield)((Core.getfield)(#self#, a), contents) + i
└ @ D:\ProjectsJulia\Traits\test\tmp.jl:69
┌ Warning: getfield(Main, Symbol("##13#14"))(Core.Box(3)) returns Any
└ @ D:\ProjectsJulia\Traits\test\tmp.jl:69
┌ Warning: dynamic dispatch to (Core.getfield)((Core.getfield)(#self#, a), contents) + i
└ @ D:\ProjectsJulia\Traits\test\tmp.jl:69
┌ Warning: getfield(Main, Symbol("##13#14"))(Core.Box(6)) returns Any
└ @ D:\ProjectsJulia\Traits\test\tmp.jl:69
┌ Warning: dynamic dispatch to (Core.getfield)((Core.getfield)(#self#, a), contents) + i
└ @ D:\ProjectsJulia\Traits\test\tmp.jl:69
┌ Warning: getfield(Main, Symbol("##13#14"))(Core.Box(10)) returns Any
└ @ D:\ProjectsJulia\Traits\test\tmp.jl:69
┌ Warning: f returns Any
└ @ D:\ProjectsJulia\Traits\test\tmp.jl:76

julia> @trace g()
10