-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
WIP: static compile part 4 (user-interface) #8745
Changes from all commits
b4e4b4b
0ab5f86
3c3f46d
aacf7d3
5346017
e78535c
c8119f8
ef3ec3d
fee6081
22dfad5
b3423f2
6ceff1c
39bf819
d81f6d2
efcc709
e8a1c74
c43f244
f7ad4e0
e30ce8d
1b07543
d98075f
3c58804
824846c
69acadd
198a29c
81d5ee9
1cc8eb3
e2d842a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,8 +71,8 @@ | |
# module::Module | ||
#end | ||
|
||
#type Box{T} | ||
# contents::T | ||
#type Box | ||
# contents::Any | ||
#end | ||
|
||
#abstract Ref{T} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1096,8 +1096,6 @@ export | |
evalfile, | ||
include, | ||
include_string, | ||
reload, | ||
require, | ||
|
||
# RTS internals | ||
finalizer, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
# This file is a part of Julia. License is MIT: http://julialang.org/license | ||
|
||
# require | ||
# Base.require is the implementation for the `import` statement | ||
|
||
function find_in_path(name::AbstractString) | ||
isabspath(name) && return name | ||
|
@@ -23,8 +23,8 @@ function find_in_path(name::AbstractString) | |
return nothing | ||
end | ||
|
||
find_in_node1_path(name) = myid()==1 ? | ||
find_in_path(name) : remotecall_fetch(1, find_in_path, name) | ||
find_in_node_path(name, node::Int=1) = myid() == node ? | ||
find_in_path(name) : remotecall_fetch(node, find_in_path, name) | ||
|
||
function find_source_file(file) | ||
(isabspath(file) || isfile(file)) && return file | ||
|
@@ -34,62 +34,114 @@ function find_source_file(file) | |
isfile(file2) ? file2 : nothing | ||
end | ||
|
||
# Store list of files and their load time | ||
package_list = Dict{ByteString,Float64}() | ||
# to synchronize multiple tasks trying to require something | ||
package_locks = Dict{ByteString,Any}() | ||
require(f::AbstractString, fs::AbstractString...) = (require(f); for x in fs require(x); end) | ||
|
||
# only broadcast top-level (not nested) requires and reloads | ||
toplevel_load = true | ||
function find_in_cache_path(mod::Symbol) | ||
name = string(mod) | ||
for prefix in LOAD_CACHE_PATH | ||
path = joinpath(prefix, name*".ji") | ||
if isfile(path) | ||
produce(path) | ||
end | ||
end | ||
nothing | ||
end | ||
|
||
function require(name::AbstractString) | ||
path = find_in_node1_path(name) | ||
path == nothing && throw(ArgumentError("$name not found in path")) | ||
function _include_from_serialized(content::Vector{UInt8}) | ||
m = ccall(:jl_restore_incremental_from_buf, UInt, (Ptr{Uint8},Int), content, sizeof(content)) | ||
return m != 0 | ||
end | ||
|
||
if myid() == 1 && toplevel_load | ||
refs = Any[ @spawnat p _require(path) for p in filter(x->x!=1, procs()) ] | ||
_require(path) | ||
for r in refs; wait(r); end | ||
function _require_from_serialized(node::Int, path_to_try::ByteString, toplevel_load::Bool) | ||
if toplevel_load && myid() == 1 && nprocs() > 1 | ||
# broadcast top-level import/using from node 1 (only) | ||
if node == myid() | ||
content = open(readbytes, path_to_try) | ||
else | ||
content = remotecall_fetch(node, open, readbytes, path_to_try) | ||
end | ||
if _include_from_serialized(content) | ||
others = filter(x -> x != myid(), procs()) | ||
refs = Any[ @spawnat p _include_from_serialized(content) for p in others] | ||
for (id, ref) in zip(others, refs) | ||
if !fetch(ref) | ||
warn("node state is inconsistent: node $id failed to load cache from $path_to_try") | ||
end | ||
end | ||
return true | ||
end | ||
elseif node == myid() | ||
if ccall(:jl_restore_incremental, UInt, (Ptr{Uint8},), path_to_try) != 0 | ||
return true | ||
end | ||
else | ||
_require(path) | ||
content = remotecall_fetch(node, open, readbytes, path_to_try) | ||
if _include_from_serialized(content) | ||
return true | ||
end | ||
end | ||
nothing | ||
# otherwise, continue search | ||
return false | ||
end | ||
|
||
function _require(path) | ||
global toplevel_load | ||
if haskey(package_list,path) | ||
loaded, c = package_locks[path] | ||
!loaded && wait(c) | ||
else | ||
last = toplevel_load | ||
toplevel_load = false | ||
try | ||
reload_path(path) | ||
finally | ||
toplevel_load = last | ||
function _require_from_serialized(node::Int, mod::Symbol, toplevel_load::Bool) | ||
name = string(mod) | ||
finder = @spawnat node @task find_in_cache_path(mod) # TODO: switch this to an explicit Channel | ||
while true | ||
path_to_try = remotecall_fetch(node, finder->consume(fetch(finder)), finder) | ||
path_to_try === nothing && return false | ||
if _require_from_serialized(node, path_to_try, toplevel_load) | ||
return true | ||
else | ||
warn("deserialization checks failed while attempting to load cache from $path_to_try") | ||
end | ||
end | ||
end | ||
|
||
function reload(name::AbstractString) | ||
# to synchronize multiple tasks trying to import/using something | ||
package_locks = Dict{Symbol,Condition}() | ||
package_loaded = Set{Symbol}() | ||
|
||
# require always works in Main scope and loads files from node 1 | ||
toplevel_load = true | ||
function require(mod::Symbol) | ||
global toplevel_load | ||
path = find_in_node1_path(name) | ||
path == nothing && throw(ArgumentError("$name not found in path")) | ||
refs = nothing | ||
if myid() == 1 && toplevel_load | ||
refs = Any[ @spawnat p reload_path(path) for p in filter(x->x!=1, procs()) ] | ||
loading = get(package_locks, mod, false) | ||
if loading !== false | ||
# load already in progress for this module | ||
wait(loading) | ||
return | ||
end | ||
package_locks[mod] = Condition() | ||
|
||
last = toplevel_load | ||
toplevel_load = false | ||
try | ||
reload_path(path) | ||
toplevel_load = false | ||
if _require_from_serialized(1, mod, last) | ||
return true | ||
end | ||
if JLOptions().incremental != 0 | ||
# spawn off a new incremental compile task from node 1 for recursive `require` calls | ||
cachefile = compile(mod) | ||
if !_require_from_serialized(1, cachefile, last) | ||
warn("require failed to create a precompiled cache file") | ||
end | ||
return | ||
end | ||
|
||
name = string(mod) | ||
path = find_in_node_path(name, 1) | ||
path === nothing && throw(ArgumentError("$name not found in path")) | ||
if last && myid() == 1 && nprocs() > 1 | ||
# broadcast top-level import/using from node 1 (only) | ||
content = open(readall, path) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This value doesn't seem to be used? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oops, left over from removing the changes to include (i was using this to load the content once, rather than for every remote node individually). |
||
refs = Any[ @spawnat p eval(Main, :(Base.include_from_node1($path))) for p in procs() ] | ||
for r in refs; wait(r); end | ||
else | ||
eval(Main, :(Base.include_from_node1($path))) | ||
end | ||
finally | ||
toplevel_load = last | ||
end | ||
if refs !== nothing | ||
for r in refs; wait(r); end | ||
loading = pop!(package_locks, mod) | ||
notify(loading, all=true) | ||
end | ||
nothing | ||
end | ||
|
@@ -145,38 +197,62 @@ function include_from_node1(path::AbstractString) | |
result | ||
end | ||
|
||
function reload_path(path::AbstractString) | ||
had = haskey(package_list, path) | ||
if !had | ||
package_locks[path] = (false, Condition()) | ||
end | ||
package_list[path] = time() | ||
tls = task_local_storage() | ||
prev = pop!(tls, :SOURCE_PATH, nothing) | ||
try | ||
eval(Main, :(Base.include_from_node1($path))) | ||
catch e | ||
had || delete!(package_list, path) | ||
rethrow(e) | ||
finally | ||
if prev != nothing | ||
tls[:SOURCE_PATH] = prev | ||
end | ||
end | ||
reloaded, c = package_locks[path] | ||
if !reloaded | ||
package_locks[path] = (true, c) | ||
notify(c, all=true) | ||
end | ||
nothing | ||
end | ||
|
||
function evalfile(path::AbstractString, args::Vector{UTF8String}=UTF8String[]) | ||
return eval(Module(:__anon__), | ||
Expr(:toplevel, | ||
:(const ARGS = $args), | ||
:(eval(x) = Core.eval(__anon__,x)), | ||
:(eval(m,x) = Core.eval(m,x)), | ||
:(include($path)))) | ||
:(eval(x) = Main.Core.eval(__anon__,x)), | ||
:(eval(m,x) = Main.Core.eval(m,x)), | ||
:(Main.Base.include($path)))) | ||
end | ||
evalfile(path::AbstractString, args::Vector) = evalfile(path, UTF8String[args...]) | ||
|
||
function create_expr_cache(input::AbstractString, output::AbstractString) | ||
code_object = """ | ||
while !eof(STDIN) | ||
eval(Main, deserialize(STDIN)) | ||
end | ||
""" | ||
io, pobj = open(detach(setenv(`$(julia_cmd()) | ||
--output-ji $output --output-incremental=yes | ||
--startup-file=no --history-file=no | ||
--eval $code_object`, | ||
["JULIA_HOME=$JULIA_HOME", "HOME=$(homedir())"])), "w", STDOUT) | ||
serialize(io, quote | ||
empty!(Base.LOAD_PATH) | ||
append!(Base.LOAD_PATH, $LOAD_PATH) | ||
empty!(Base.LOAD_CACHE_PATH) | ||
append!(Base.LOAD_CACHE_PATH, $LOAD_CACHE_PATH) | ||
empty!(Base.DL_LOAD_PATH) | ||
append!(Base.DL_LOAD_PATH, $DL_LOAD_PATH) | ||
end) | ||
source = source_path(nothing) | ||
if source !== nothing | ||
serialize(io, quote | ||
task_local_storage()[:SOURCE_PATH] = $(source) | ||
end) | ||
end | ||
serialize(io, :(Base.include($(abspath(input))))) | ||
if source !== nothing | ||
serialize(io, quote | ||
delete!(task_local_storage(), :SOURCE_PATH) | ||
end) | ||
end | ||
close(io) | ||
wait(pobj) | ||
return pobj | ||
end | ||
|
||
function compile(mod::Symbol) | ||
myid() == 1 || error("can only compile from node 1") | ||
name = string(mod) | ||
path = find_in_path(name) | ||
path === nothing && throw(ArgumentError("$name not found in path")) | ||
cachepath = LOAD_CACHE_PATH[1] | ||
if !isdir(cachepath) | ||
mkpath(cachepath) | ||
end | ||
cachefile = abspath(cachepath, name*".ji") | ||
create_expr_cache(path, cachefile) | ||
return cachefile | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it's safe to remove these. The problem is that when I first do
using X
, all processors are told to loadX
. But then if X doesusing Y
, node 1 will tell all processors to load Y, even though their existing instructions to load X will do that already. Your version kind of handles that by checkingcurrent_module() === Main
, but that only works if X defines a module. The old version was more robust. A lot of blood, sweat, and tears went into this parallel loading code. I wouldn't change it so casually.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll rename the function in my next PR. this does not share any behavior with the old
require
function.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, fwiw, that's not the failure case that changed. the old behavior protected against the odd case of calling
require
everywhere on some code that didn't define a module, but did conditionallyrequire
some more code (presumably depending onmyid()
). the example you gave is just a boring no-op in both the old and new code.i don't really see the importance of that case.
and, anyways, it's an error to call import (aka the to-be-renamed function
require
) on something that didn't define a module.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand --- this is still the function called by
using
andimport
, and it still broadcasts to all processors. There is definitely significant overlap.Here's an example:
file a.jl:
file b.jl:
Start
julia -p 2
and runusing a
. Output from 0.3 and master:Output from this branch:
Each worker loaded
b.jl
twice. I agree this case is not very important, but there is no reason to make this change.