JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.89k stars 5.49k forks source link

unnecessary boxing of variables that are not reassigned within a closure #56561

Open aviatesk opened 2 weeks ago

aviatesk commented 2 weeks ago

In cases like the following code, where a variable defined in the parent scope of a closure is used inside the closure and could potentially be reassigned in the parent scope, it seems to get boxed even if it is never reassigned within the closure itself.

julia> callf(f, a) = f(a);

julia> function demo_box(a)
           x = sin(a)
           x = identity(x)
           callf(a) do a
               a + x
           end
       end;

julia> code_lowered(demo_box, (Float64,))
1-element Vector{Core.CodeInfo}:
 CodeInfo(
1 ─       Core.NewvarNode(:(#18))
│         x@_4 = Core.Box()
│   %3  =   dynamic Main.sin(a)
│   %4  = x@_4
│           builtin Core.setfield!(%4, :contents, %3)
│   %6  = Main.identity
│   %7  = x@_4
│   %8  =   builtin Core.isdefined(%7, :contents)
└──       goto #3 if not %8
2 ─       goto #4
3 ─       Core.NewvarNode(:(x@_5))
└──       x@_5
4 ┄ %13 = x@_4
│   %14 =   builtin Core.getfield(%13, :contents)
│   %15 =   dynamic (%6)(%14)
│   %16 = x@_4
│           builtin Core.setfield!(%16, :contents, %15)
│   %18 = Main.callf
│   %19 = Main.:(var"#demo_box##8#demo_box##9")
│   %20 = x@_4
│         #18 = %new(%19, %20)
│   %22 = #18
│   %23 =   dynamic (%18)(%22, a)
└──       return %23
)

This boxing appears completely unnecessary to me. Would it be possible to make a simple change to lowering to fix this issue?

aviatesk commented 2 weeks ago

/cc @JeffBezanson Could I hear your thought on this? I'm sorry if this is a known issue.

mbauman commented 2 weeks ago

I think the challenge is that lowering needs to prove that no re-assignments occur after closure creation, because if any do, then the closure's captured variable needs to be able to be changed. Ref https://github.com/JuliaLang/julia/issues/15276#issuecomment-248068935

Functions called with the do syntax typically immediately evaluate and discard the anonymous function, but it might escape — possibly even to a global. There's no way lowering could know this.

julia> function demo_box(a)
           x = sin(a)
           f = identity() do a
               a + x
           end
           @show f(1)
           x = 123123
           @show f(1)
       end
demo_box (generic function with 1 method)

julia> demo_box(1.23)
f(1) = 1.9424888019316975
f(1) = 123124