Skip to content
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

Merged
merged 28 commits into from
Jul 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b4e4b4b
pull julia_save out of the external API and into the atexit hook
vtjnash Jul 6, 2015
0ab5f86
forward the exit status to the atexit hook, so it can avoid recursing…
vtjnash Jul 8, 2015
3c3f46d
consider exit-on-segv to be an exceptional exit status, per common un…
vtjnash Jul 9, 2015
aacf7d3
automatically search cache modules when calling require(::Symbol) or …
vtjnash Dec 11, 2014
5346017
first cut at automatically caching @Base.cacheable modules
vtjnash Dec 29, 2014
e78535c
make dump/restore sigatomic
vtjnash Jul 5, 2015
c8119f8
give the dump serializer the ability to handle a list of modules to s…
vtjnash Jul 6, 2015
ef3ec3d
reimplement require/include to better support precompilation
vtjnash Jul 8, 2015
fee6081
fix a bug where jl_typeof(jl_emptytuple) was a singleton type missing…
vtjnash Jul 9, 2015
22dfad5
record binding module in new generic functions
vtjnash Jul 10, 2015
b3423f2
empty typename->cache so that flagref can rebuild it cleanly
vtjnash Jul 10, 2015
6ceff1c
fix dt->uid assignment
vtjnash Jul 11, 2015
39bf819
fix a bug with remote restore-incremental return type handling
vtjnash Jul 11, 2015
d81f6d2
allow proper backref handling of the element that happened to land at…
vtjnash Jul 11, 2015
efcc709
sysimg does not need to call workspace() anymore. this sly module rep…
vtjnash Jul 11, 2015
e8a1c74
Add missing base_include method
timholy Jul 11, 2015
c43f244
Fix serialization warning from workers
timholy Jul 11, 2015
f7ad4e0
Add `require` deprecations
timholy Jul 12, 2015
e30ce8d
Use julia-versioning in libs path
timholy Jul 12, 2015
1b07543
back out changes making base_include multinode-aware
vtjnash Jul 13, 2015
d98075f
revert all changes to include in this branch
vtjnash Jul 13, 2015
3c58804
minor cleanups and add `compile` entry point
JeffBezanson Jul 14, 2015
824846c
incremental serialization inferface cleanup
vtjnash Jul 15, 2015
69acadd
avoid serializing ghost fields
vtjnash Jul 16, 2015
198a29c
restore jl_globalref_type cache optimization for MODE_AST
vtjnash Jul 16, 2015
81d5ee9
more agressive error messages when stuff is going to break incrementa…
vtjnash Jul 16, 2015
1cc8eb3
test toplevel_load directly, rather than using current_module() == Ma…
vtjnash Jul 17, 2015
e2d842a
add incremental compile docs
vtjnash Jul 17, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ New language features
* The syntax `function foo end` can be used to introduce a generic function without
yet adding any methods ([#8283]).

* Incremental compilation of modules: ``Base.compile(module::Symbol)`` (stored in `~/.julia/lib/v0.4`)

* See manual section on `Module initialization and precompilation` (under `Modules`) for details and errata.

* New option `--compile-incremental={yes|no}` added to invoke the equivalent of ``Base.compile`` from the command line.

Language changes
----------------

Expand Down Expand Up @@ -450,6 +456,8 @@ Deprecated or removed

* `sync_gc_total_bytes` -> `jl_gc_sync_total_bytes`

* `require(::AbstractString)` and `reload` (see news about addition of `compile`)

Julia v0.3.0 Release Notes
==========================

Expand Down
4 changes: 2 additions & 2 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@
# module::Module
#end

#type Box{T}
# contents::T
#type Box
# contents::Any
#end

#abstract Ref{T}
Expand Down
7 changes: 6 additions & 1 deletion base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,9 @@ let reqarg = Set(UTF8String["--home", "-H",
end
# load file immediately on all processors
if opts.load != C_NULL
require(bytestring(opts.load))
@sync for p in procs()
@async remotecall_fetch(p, include, bytestring(opts.load))
end
end
# eval expression
if opts.eval != C_NULL
Expand Down Expand Up @@ -322,13 +324,16 @@ is_interactive = false
isinteractive() = (is_interactive::Bool)

const LOAD_PATH = ByteString[]
const LOAD_CACHE_PATH = ByteString[]
function init_load_path()
vers = "v$(VERSION.major).$(VERSION.minor)"
if haskey(ENV,"JULIA_LOAD_PATH")
prepend!(LOAD_PATH, split(ENV["JULIA_LOAD_PATH"], @windows? ';' : ':'))
end
push!(LOAD_PATH,abspath(JULIA_HOME,"..","local","share","julia","site",vers))
push!(LOAD_PATH,abspath(JULIA_HOME,"..","share","julia","site",vers))
push!(LOAD_CACHE_PATH,abspath(homedir(),".julia","lib",vers))
push!(LOAD_CACHE_PATH,abspath(JULIA_HOME,"..","usr","lib","julia")) #TODO: fixme
end

function load_juliarc()
Expand Down
32 changes: 31 additions & 1 deletion base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,6 @@ end
@deprecate mmap_bitarray{N}(::Type{Bool}, dims::NTuple{N,Integer}, s::IOStream, offset::FileOffset=position(s)) mmap(s, BitArray, dims, offset)
@deprecate mmap_bitarray{N}(dims::NTuple{N,Integer}, s::IOStream, offset=position(s)) mmap(s, BitArray, dims, offset)


# T[a:b] and T[a:s:b]
@noinline function getindex{T<:Union{Char,Number}}(::Type{T}, r::Range)
depwarn("T[a:b] concatenation is deprecated; use T[a:b;] instead", :getindex)
Expand All @@ -654,3 +653,34 @@ function getindex{T<:Union{Char,Number}}(::Type{T}, r1::Range, rs::Range...)
end
return a
end

function require(mod::AbstractString)
depwarn("`require` is deprecated, use `using` or `import` instead", :require)
require(symbol(require_filename(mod)))
end
function require(f::AbstractString, fs::AbstractString...)
require(f)
for fn in fs
require(fn)
end
end
export require
function require_filename(name::AbstractString)
# This function can be deleted when the deprecation for `require`
# is deleted.
# While we could also strip off the absolute path, the user may be
# deliberately directing to a different file than what got
# cached. So this takes a conservative approach.
if endswith(name, ".jl")
tmp = name[1:end-3]
for prefix in LOAD_CACHE_PATH
path = joinpath(prefix, tmp*".ji")
if isfile(path)
return tmp
end
end
end
name
end
const reload = require
export reload
2 changes: 0 additions & 2 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1096,8 +1096,6 @@ export
evalfile,
include,
include_string,
reload,
require,

# RTS internals
finalizer,
Expand Down
1 change: 0 additions & 1 deletion base/interactiveutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,6 @@ function workspace()
Expr(:toplevel,
:(const Base = $(Expr(:quote, b))),
:(const LastMain = $(Expr(:quote, last)))))
empty!(package_list)
empty!(package_locks)
nothing
end
Expand Down
220 changes: 148 additions & 72 deletions base/loading.jl
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
Expand All @@ -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
Expand All @@ -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)
Copy link
Member

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 load X. But then if X does using 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 checking current_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.

Copy link
Member Author

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.

Copy link
Member Author

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 conditionally require some more code (presumably depending on myid()). 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this does not share any behavior with the old require

I don't understand --- this is still the function called by using and import, and it still broadcasts to all processors. There is definitely significant overlap.

Here's an example:

file a.jl:

import b
println("loading a")

file b.jl:

println("loading b")

Start julia -p 2 and run using a. Output from 0.3 and master:

loading b
Warning: requiring "b" did not define a corresponding module.
loading a
    From worker 3:  loading b
    From worker 2:  loading b
Warning: requiring "b" did not define a corresponding module.
Warning: requiring "b" did not define a corresponding module.
    From worker 3:  loading a
    From worker 2:  loading a
Warning: requiring "a" did not define a corresponding module.

Output from this branch:

loading b
    From worker 3:  loading b
    From worker 2:  loading b
    From worker 3:  loading b
Warning: requiring "b" did not define a corresponding module.
    From worker 3:  loading a
    From worker 2:  loading b
Warning: requiring "b" did not define a corresponding module.
    From worker 2:  loading a
Warning: requiring "b" did not define a corresponding module.
loading a
Warning: requiring "a" did not define a corresponding module.

Each worker loaded b.jl twice. I agree this case is not very important, but there is no reason to make this change.

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This value doesn't seem to be used?

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions base/options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ immutable JLOptions
outputbc::Ptr{UInt8}
outputo::Ptr{UInt8}
outputji::Ptr{UInt8}
incremental::Int8
end

JLOptions() = unsafe_load(cglobal(:jl_options, JLOptions))
Loading