-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
don't force global binding resolution in compiled code #11456
Conversation
1bf4b64
to
1e0d0c9
Compare
overly slow access (#11456)
As I thought, this happens extremely rarely. There were a couple cases very early in bootstrap, and some cases where globals use delayed initialization (i.e. some function contains |
What does this mean for statically compiling a package with const global initialized in |
That's a very good point. Currently, Non-const globals assigned in As soon as we compile |
(Actually I think I finally understand what this PR is about. I didn't know there's distinction between global variable and imported values.)
I'm more wondering about sth like
This is usually true. However, I guess unless you can teach the compilation to understand eval, it will fail to recognize a lot of global definitions. (I don't know how often it is used but I've seen it in
Although not backed by many real world examples, I would still like #11098 to be implemented. IMHO, that would make identifying |
There is not really a distinction between global variables and imported variables; it's just a matter of knowing where a value is stored. |
If code uses lots of |
Actually one thing I somehow forgot to ask just now. Would it be possible to fix it "from the other direction", i.e. by making |
I'm not sure what you mean. If code begins with |
I'm thinking of sth like this. f() = g()
__init__() = @eval g() = 1 Of course it would be more complicated in real package but my point is that a global value used by other functions might be assigned with |
I was commenting on f() = g
try
f()
end
g = 1
f() works but f() = g
try
f()
end
using A.g
f() doesn't. Is it possible to fix it by making |
Code like that cannot be statically compiled in an efficient way. However, if you don't care about static compilation, there's no problem. As long as you don't try to call |
I see. I guess this would be what the static compilation needs to care about but it feels like such code can be slower if forced to be static compiled. |
No, because with
|
To be honest, this is the first time I noticed that julia's In [1]: import imp, sys
In [2]: A = imp.new_module('A')
In [3]: exec("a = 1", A.__dict__)
In [4]: A.a
Out[4]: 1
In [5]: sys.modules["A"] = A
In [6]: from A import a
In [7]: a
Out[7]: 1
In [8]: exec("a = 2", A.__dict__)
In [9]: A.a
Out[9]: 2
In [10]: a
Out[10]: 1 julia> module A
a = 1
end
julia> A.a
1
julia> using A.a
julia> a
1
julia> A.eval(:(a = 2))
2
julia> A.a
2
julia> a
2 I'm not sure which one is better but I am a little surprised.... Is there any code/feature that relies on this behavior? |
Oh, that is interesting. Our behavior is strictly more expressive. This is how bindings are supposed to behave; for example think of symbols in shared libraries, where one library owns a location and many other libraries can reference it. If you want a copy, you can always easily get one with |
OTOH, binding behavior can always be achieved with using I brought up the question about what is relying on this behavior because python seems to be fine with this behavior and it can possibly also solve the issue addressed by this PR without performance penalty. |
Indeed, non-const globals are themselves pretty rare. One example is |
90c7425
to
3029070
Compare
For what it's worth, I find the Python behavior less surprising. I would expect importing a name to be a shorthand for assigning the name in the current module rather than what we're currently doing, which is making the name a shorthand for the fully qualified version. Of course, our module Foo
export bar
bar = 1
end
module Baz
using Foo
get_bar() = bar
function set_bar(val)
global bar = val
end
end Depending on whether |
Won't that give a override imported name warning if you call |
OK the warning only shows up when IMHO, this is actually an argument for the python behavior since it is a little surprising... If we make |
Two different Julia sessions: julia> module Foo
export bar
bar = 1
end
julia> module Baz
using Foo
get_bar() = bar
function set_bar(val)
global bar = val
end
end
julia> Baz.get_bar()
1 In this session julia> module Foo
export bar
bar = 1
end
julia> module Baz
using Foo
get_bar() = bar
function set_bar(val)
global bar = val
end
end
julia> Baz.set_bar(:qux)
:qux
julia> Baz.get_bar()
:qux In this session |
And this is the "surprise" I'm talking about. |
Right, but in that case everyone who has complained that exports from Base "steal" names will actually be correct: any name exported by any module that you use will be unavailable, and in particular, since non-bare modules have an implict |
Ah. Right. I guess python doesn't have this issue because everything are wrapped in modules. (which, IMO, is not a bad thing to do and, if I remember correctly, has been brought up before.) |
A compromise approach would be that julia> module A
a = 1
end
julia> A.a
1
julia> using A.a
julia> a # causes `global a = A.a` to happen right now
1
julia> A.eval(:(a = 2))
2
julia> A.a
2
julia> a # `Main.a` is not the same as `A.a` they just referred to the same value
1 @JeffBezanson, would that make this issue any easier? |
I like this schematics although it's beyond my current knowledge to tell how easy it is to get everything right... |
Talked with @JeffBezanson on the phone about this. He convinced me that the way that However, this example is still disturbing. We decided that instead of resolving the binding of julia> module Foo
export bar
bar = 1
end
julia> module Baz
using Foo
get_bar() = bar
function set_bar(val)
global bar = val
end
end
julia> Baz.get_bar()
ERROR: UndefVarError: bar not defined
in get_bar at none:3 In particular, the behavior of |
I guess that still doesn't help with using function __init__()
@eval get_some_const() = $(ccall(:get_some_runtime_const, Cint, ()))
end But I've kind of convinced myself that those can be workaround (e.g. by defining a const global instead). |
that cannot be found yet. fixes #7864
340d1bd
to
19ae2ea
Compare
don't force global binding resolution in compiled code
Building against LLVM-SVN
|
@StefanKarpinski @JeffBezanson Sorry to bring this up again but this just come into my mind. So the argument of keeping the current binding behavior is that we can override sth like i.e. instead of writing |
To implement #964 we will probably do something like that. A global will always be bound to a specific object. A mutable global will be bound to a |
Yeah, @vtjnash just reminds me (#12010 (comment)) that you've already said this although apparently I didn't think about it this way because of the context.... |
Fall back to run time lookup when compiled code needs to access a global that cannot be found yet.
Fixes #7864.
This has performance implications; the run time lookup is very slow. However this probably doesn't happen very often. We only hit this case if you try to run (and therefore compile) code that uses a global that is not available yet, for example:
However,
g
could have been provided by a package, and in that case we used to do something totally wrong. With this PR it works better, butf()
will be really slow. I suspect this code pattern mostly happens at the prompt, but I'm not sure. I should load a bunch of packages and instrument how many times this happens. If necessary we can fix the performance by caching the result of jl_get_binding for each such global variable use.