JuliaLang / julia

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

compiling top-level expressions forces global binding resolution #14055

Open 315234 opened 8 years ago

315234 commented 8 years ago

Sorry the title is so vague but I can't get my head around what is happening here. Minimal code which reproduces this bug:

if true # required to exhibit bug
    upper = ones(4)
    lower = ones(4)

    # WARNING: imported binding overwritten
    # required to exhibit bug
    diag  = ones(5)

    A = Tridiagonal(lower,diag,upper)

    for i=1:10 # required to exhibit bug
    end
end

Output:

$ julia --version
julia version 0.4.0

$ julia bug.jl
WARNING: imported binding for diag overwritten in module Main
ERROR: LoadError: MethodError: `convert` has no method matching convert(::Type{Tridiagonal{T}}, ::Array{Float64,1}, ::Array{Float64,1}, ::Array{Float64,1})
This may have arisen from a call to the constructor Tridiagonal{T}(...),
since type constructors fall back to convert methods.
Closest candidates are:
  Tridiagonal{T}(::Array{T,1}, ::Array{T,1}, ::Array{T,1}, !Matched::Array{T,1})
  Tridiagonal{T}(::Array{T,1}, ::Array{T,1}, ::Array{T,1})
  Tridiagonal{Tl,Td,Tu}(::Array{Tl,1}, ::Array{Td,1}, ::Array{Tu,1})
  ...
 [inlined code] from bug.jl:9
 in anonymous at no file:7
 in include at .../julia/0.4.0/lib/julia/sys.dylib
 in include_from_node1 at .../julia/0.4.0/lib/julia/sys.dylib
 in process_options at .../julia/0.4.0/lib/julia/sys.dylib
 in _start at .../julia/0.4.0/lib/julia/sys.dylib
while loading bug.jl, in expression starting on line 1

Removing the if or for blocks results in this bug not triggering, so it looks like this has something to do with soft vs hard scope maybe. Rebinding an existing variable name is also required.

JeffBezanson commented 4 years ago

Just happened upon this and have been thinking about it a bit. There are kind of two issues. (1) Inference and codegen cause all the bindings they examine to get resolved. That's not ideal, but is a practical necessity. (2) This case, where the same block of code reads and writes a new global variable. It's not a problem inside functions, because the function would have to contain a global declaration, which takes effect as soon as the method is defined (even before inference). But at the top level there need not be a global declaration, and if there were we couldn't run it in advance since top-level code might conditionally avoid executing the declaration. So we end up in this bad case where inference causes diag to be resolved, but completely ignores the assignment until run time.

We could tell inference and codegen not to resolve bindings in top-level code, but that could lead to really bad and unpredictable performance. Or a hybrid approach could be avoiding binding resolution just for names that are assigned somewhere (those being the "less predictable" bindings).