Skip to content

Commit

Permalink
track requires dependencies alongside include dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
vtjnash committed Jan 3, 2018
1 parent df022d2 commit 61312e4
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 62 deletions.
77 changes: 46 additions & 31 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -530,15 +530,15 @@ const include_callbacks = Any[]
const _concrete_dependencies = Pair{Symbol, UInt64}[] # these dependency versions are "set in stone", and the process should try to avoid invalidating them
const _require_dependencies = Any[] # a list of (mod, path, mtime) tuples that are the file dependencies of the module currently being precompiled
const _track_dependencies = Ref(false) # set this to true to track the list of file dependencies
function _include_dependency(modstring::AbstractString, _path::AbstractString)
function _include_dependency(mod::Module, _path::AbstractString)
prev = source_path(nothing)
if prev === nothing
path = abspath(_path)
else
path = joinpath(dirname(prev), _path)
end
if _track_dependencies[]
push!(_require_dependencies, (modstring, normpath(path), mtime(path)))
push!(_require_dependencies, (mod, normpath(path), mtime(path)))
end
return path, prev
end
Expand All @@ -554,7 +554,7 @@ This is only needed if your module depends on a file that is not used via `inclu
no effect outside of compilation.
"""
function include_dependency(path::AbstractString)
_include_dependency("#__external__", path)
_include_dependency(Main, path)
return nothing
end

Expand Down Expand Up @@ -623,6 +623,9 @@ all platforms, including those with case-insensitive filesystems like macOS and
Windows.
"""
function require(into::Module, mod::Symbol)
if _track_dependencies[]
push!(_require_dependencies, (into, "\0$mod", 0.0))
end
info = nothing
if !root_module_exists(mod)
info = _require(into, mod)
Expand All @@ -647,8 +650,8 @@ function require(into::Module, mod::Symbol)
return m
end

const loaded_modules = ObjectIdDict()
const module_keys = ObjectIdDict()
#const loaded_modules = ObjectIdDict()
#const module_keys = ObjectIdDict()

function register_root_module(key, m::Module)
if haskey(loaded_modules, key)
Expand Down Expand Up @@ -695,11 +698,6 @@ function unreference_module(key)
end

function _require(into::Module, mod::Symbol)
# dependency-tracking is only used for one top-level include(path),
# and is not applied recursively to imported modules:
old_track_dependencies = _track_dependencies[]
_track_dependencies[] = false

# handle recursive calls to require
loading = get(package_locks, mod, false)
if loading !== false
Expand Down Expand Up @@ -787,7 +785,6 @@ function _require(into::Module, mod::Symbol)
toplevel_load[] = last
loading = pop!(package_locks, mod)
notify(loading, all=true)
_track_dependencies[] = old_track_dependencies
end
return info
end
Expand Down Expand Up @@ -824,7 +821,7 @@ function source_dir()
end

function include_relative(mod::Module, _path::String)
path, prev = _include_dependency(string(mod), _path)
path, prev = _include_dependency(mod, _path)
for callback in include_callbacks # to preserve order, must come before Core.include
invokelatest(callback, mod, path)
end
Expand Down Expand Up @@ -987,19 +984,37 @@ function parse_cache_header(f::IO)
push!(modules, sym => uuid)
end
totbytes = ntoh(read(f, Int64)) # total bytes for file dependencies
# read the list of files
files = Tuple{String,String,Float64}[]
# read the list of requirements
# and split the list into include and requires statements
includes = Tuple{String, String, Float64}[]
requires = Pair{String, String}[]
while true
n1 = ntoh(read(f, Int32))
n1 == 0 && break
@assert n1 >= 0 "EOF while reading cache header" # probably means this wasn't a valid file to be read by Base.parse_cache_header
modname = String(read(f, n1))
n2 = ntoh(read(f, Int32))
@assert n2 >= 0 "EOF while reading cache header" # probably means this wasn't a valid file to be read by Base.parse_cache_header
filename = String(read(f, n2))
push!(files, (modname, filename, ntoh(read(f, Float64))))
totbytes -= 8 + n1 + n2 + 8
n2 == 0 && break
depname = String(read(f, n2))
mtime = ntoh(read(f, Float64))
n1 = ntoh(read(f, Int32))
if n1 == 0
modname = "Main" # remap anything loaded outside this cache files modules to have occurred in `Main`
else
modname = String(modules[n1][1])
while true
n1 = ntoh(read(f, Int32))
totbytes -= 4
n1 == 0 && break
modname = string(modname, ".", String(read(f, n1)))
totbytes -= n1
end
end
if depname[1] == '\0'
push!(requires, modname => depname[2:end])
else
push!(includes, (modname, depname, mtime))
end
totbytes -= 4 + 4 + n2 + 8
end
@show includes
@show requires
@assert totbytes == 12 "header of cache file appears to be corrupt"
srctextpos = ntoh(read(f, Int64))
# read the list of modules that are required to be present during loading
Expand All @@ -1011,7 +1026,7 @@ function parse_cache_header(f::IO)
uuid = ntoh(read(f, UInt64)) # module UUID
push!(required_modules, sym => uuid)
end
return modules, files, required_modules, srctextpos
return modules, (includes, requires), required_modules, srctextpos
end

function parse_cache_header(cachefile::String)
Expand All @@ -1025,8 +1040,8 @@ function parse_cache_header(cachefile::String)
end

function cache_dependencies(f::IO)
defs, files, modules = parse_cache_header(f)
return modules, map(mod_fl_mt -> (mod_fl_mt[2], mod_fl_mt[3]), files) # discard the module
defs, (includes, requires), modules = parse_cache_header(f)
return modules, map(mod_fl_mt -> (mod_fl_mt[2], mod_fl_mt[3]), includes) # discard the module
end

function cache_dependencies(cachefile::String)
Expand All @@ -1040,10 +1055,10 @@ function cache_dependencies(cachefile::String)
end

function read_dependency_src(io::IO, filename::AbstractString)
modules, files, required_modules, srctextpos = parse_cache_header(io)
modules, (includes, requires), required_modules, srctextpos = parse_cache_header(io)
srctextpos == 0 && error("no source-text stored in cache file")
seek(io, srctextpos)
_read_dependency_src(io, filename)
return _read_dependency_src(io, filename)
end

function _read_dependency_src(io::IO, filename::AbstractString)
Expand Down Expand Up @@ -1079,7 +1094,7 @@ function stale_cachefile(into::Module, modpath::String, cachefile::String)
@debug "Rejecting cache file $cachefile due to it containing an invalid cache header"
return true # invalid cache file
end
modules, files, required_modules = parse_cache_header(io)
(modules, (includes, requires), required_modules) = parse_cache_header(io)
modules = Dict{Symbol, UInt64}(modules)

# Check if transitive dependencies can be fullfilled
Expand Down Expand Up @@ -1111,11 +1126,11 @@ function stale_cachefile(into::Module, modpath::String, cachefile::String)
end

# now check if this file is fresh relative to its source files
if !samefile(files[1][2], modpath)
@debug "Rejecting cache file $cachefile because it is for file $(files[1][2])) not file $modpath"
if !samefile(includes[1][2], modpath)
@debug "Rejecting cache file $cachefile because it is for file $(includes[1][2])) not file $modpath"
return true # cache file was compiled from a different path
end
for (_, f, ftime_req) in files
for (_, f, ftime_req) in includes
# Issue #13606: compensate for Docker images rounding mtimes
# Issue #20837: compensate for GlusterFS truncating mtimes to microseconds
ftime = mtime(f)
Expand Down
79 changes: 50 additions & 29 deletions src/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -1037,7 +1037,7 @@ static void write_mod_list(ios_t *s, jl_array_t *a)
}

// "magic" string and version header of .ji file
static const int JI_FORMAT_VERSION = 4;
static const int JI_FORMAT_VERSION = 5;
static const char JI_MAGIC[] = "\373jli\r\n\032\n"; // based on PNG signature
static const uint16_t BOM = 0xFEFF; // byte-order marker
static void write_header(ios_t *s)
Expand Down Expand Up @@ -1070,11 +1070,22 @@ static void write_work_list(ios_t *s)
write_int32(s, 0);
}

static void write_module_path(ios_t *s, jl_module_t *depmod)
{
if (depmod->parent == jl_main_module || depmod->parent == depmod)
return;
const char *mname = jl_symbol_name(depmod->name);
size_t slen = strlen(mname);
write_module_path(s, depmod->parent);
write_int32(s, slen);
ios_write(s, mname, slen);
}

// serialize the global _require_dependencies array of pathnames that
// are include depenencies
static int64_t write_dependency_list(ios_t *s, jl_array_t **udepsp)
static int64_t write_dependency_list(ios_t *s, jl_array_t **udepsp, jl_array_t *mod_array)
{
size_t total_size = 0;
int64_t initial_pos = 0;
int64_t pos = 0;
static jl_array_t *deps = NULL;
if (!deps)
Expand All @@ -1091,37 +1102,44 @@ static int64_t write_dependency_list(ios_t *s, jl_array_t **udepsp)
jl_array_t *udeps = (*udepsp = deps && unique_func ? (jl_array_t*)jl_apply(uniqargs, 2) : NULL);
jl_get_ptls_states()->world_age = last_age;

if (udeps) {
size_t l = jl_array_len(udeps);
for (size_t i=0; i < l; i++) {
jl_value_t *dep = jl_fieldref(jl_array_ptr_ref(udeps, i), 0);
size_t slen = jl_string_len(dep);
dep = jl_fieldref(jl_array_ptr_ref(udeps, i), 1);
slen += jl_string_len(dep);
total_size += 8 + slen + 8;
}
total_size += 4 + 8;
}
// write the total size so that we can quickly seek past all of the
// write a placeholder for total size so that we can quickly seek past all of the
// dependencies if we don't need them
write_uint64(s, total_size);
initial_pos = ios_pos(s);
write_uint64(s, 0);
if (udeps) {
size_t l = jl_array_len(udeps);
for (size_t i=0; i < l; i++) {
size_t i, l = jl_array_len(udeps);
for (i = 0; i < l; i++) {
jl_value_t *deptuple = jl_array_ptr_ref(udeps, i);
jl_value_t *dep = jl_fieldref(deptuple, 0); // evaluating module (as string)
jl_value_t *dep = jl_fieldref(deptuple, 1); // file abspath
size_t slen = jl_string_len(dep);
write_int32(s, slen);
ios_write(s, jl_string_data(dep), slen);
dep = jl_fieldref(deptuple, 1); // file abspath
slen = jl_string_len(dep);
write_int32(s, slen);
ios_write(s, jl_string_data(dep), slen);
write_float64(s, jl_unbox_float64(jl_fieldref(deptuple, 2))); // mtime
jl_module_t *depmod = (jl_module_t*)jl_fieldref(deptuple, 0); // evaluating module
jl_module_t *depmod_top = depmod;
while (depmod_top->parent != jl_main_module && depmod_top->parent != depmod_top)
depmod_top = depmod_top->parent;
unsigned provides = 0;
size_t j, lj = jl_array_len(serializer_worklist);
for (j = 0; j < lj; j++) {
jl_module_t *workmod = (jl_module_t*)jl_array_ptr_ref(serializer_worklist, j);
if (workmod->parent == jl_main_module || workmod->parent == workmod) {
++provides;
if (workmod == depmod_top) {
write_int32(s, provides);
write_module_path(s, depmod);
break;
}
}
}
write_int32(s, 0);
}
write_int32(s, 0); // terminator, for ease of reading
// write a dummy file position to indicate the beginning of the source-text
pos = ios_pos(s);
ios_seek(s, initial_pos);
write_uint64(s, pos - initial_pos);
ios_seek(s, pos);
write_int64(s, 0);
}
return pos;
Expand Down Expand Up @@ -2288,7 +2306,7 @@ JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist)
serializer_worklist = worklist;
write_header(&f);
write_work_list(&f);
int64_t srctextpos = write_dependency_list(&f, &udeps);
int64_t srctextpos = write_dependency_list(&f, &udeps, mod_array);
write_mod_list(&f, mod_array); // this can return errors during deserialize,
// best to keep it early (before any actual initialization)

Expand Down Expand Up @@ -2347,20 +2365,23 @@ JL_DLLEXPORT int jl_save_incremental(const char *fname, jl_array_t *worklist)
ios_t srctext;
for (i = 0; i < len; i++) {
jl_value_t *deptuple = jl_array_ptr_ref(udeps, i);
jl_value_t *dep = jl_fieldref(deptuple, 0); // module name
jl_value_t *depmod = jl_fieldref(deptuple, 0); // module
// Dependencies declared with `include_dependency` are excluded
// because these may not be Julia code (and could be huge)
if (strcmp(jl_string_data(dep), "#__external__") != 0) {
dep = jl_fieldref(deptuple, 1); // file abspath
ios_t *srctp = ios_file(&srctext, jl_string_data(dep), 1, 0, 0, 0);
if (depmod != (jl_value_t*)jl_main_module) {
jl_value_t *dep = jl_fieldref(deptuple, 1); // file abspath
const char *depstr = jl_string_data(dep);
if (!depstr[0])
continue;
ios_t *srctp = ios_file(&srctext, depstr, 1, 0, 0, 0);
if (!srctp) {
jl_printf(JL_STDERR, "WARNING: could not cache source text for \"%s\".\n",
jl_string_data(dep));
continue;
}
size_t slen = jl_string_len(dep);
write_int32(&f, slen);
ios_write(&f, jl_string_data(dep), slen);
ios_write(&f, depstr, slen);
posfile = ios_pos(&f);
write_uint64(&f, 0); // placeholder for length of this file in bytes
uint64_t filelen = (uint64_t) ios_copyall(&f, &srctext);
Expand Down
13 changes: 11 additions & 2 deletions test/compile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ try
import $Foo2_module: $Foo2_module, override
import $FooBase_module.hash
import Test
module Inner
import $FooBase_module.hash
using ..$Foo_module
import ..$Foo2_module
end
struct typeB
y::typeA
Expand Down Expand Up @@ -202,10 +207,14 @@ try
@test string(Base.Docs.doc(Foo.foo)) == "foo function\n"
@test string(Base.Docs.doc(Foo.Bar.bar)) == "bar function\n"

modules, deps, required_modules = Base.parse_cache_header(cachefile)
modules, (deps, requires), required_modules = Base.parse_cache_header(cachefile)
discard_module = mod_fl_mt -> (mod_fl_mt[2], mod_fl_mt[3])
@test modules == [Foo_module => Base.module_uuid(Foo)]
@test map(x -> x[1], sort(discard_module.(deps))) == [Foo_file, joinpath(dir, "bar.jl"), joinpath(dir, "foo.jl")]
@test map(x -> x[2], deps) == [ Foo_file, joinpath(dir, "foo.jl"), joinpath(dir, "bar.jl") ]
@test requires == [ string(Foo_module) => string(FooBase_module),
string(Foo_module) => string(Foo2_module),
string(Foo_module) => "Test",
string(Foo_module, ".Inner") => string(FooBase_module) ]
srctxt = Base.read_dependency_src(cachefile, Foo_file)
@test !isempty(srctxt) && srctxt == read(Foo_file, String)
@test_throws ErrorException Base.read_dependency_src(cachefile, "/tmp/nonexistent.txt")
Expand Down

0 comments on commit 61312e4

Please sign in to comment.