Skip to content

Commit

Permalink
WIP: Add sysimage autoload mechanism
Browse files Browse the repository at this point in the history
This is the second part of the plan described in #40414
(though complimentary to the PR itself). In particular, this
PR makes it possible to quickly replace a system image during
initial startup. This is done by adding a hook early in the
startup sequence (after the system image, but before any
dependent libraries are initialized) for Julia to look at
the specified project file and decide to load a different
sysimage instead.

In the current version of the PR, this works as following:
 - If the `--autoload` argument is specified, julia will hash
   the contents of the currently active project's manifest path.
 - If a corresponding .so is found in `~/.julia/sysimages`, it will
   load that sysimage instead.
 - If not, loading will proceed as usual, a warning is generated
   but before any user code is run, Julia will `require` any
   dependencies specified in the Project.toml.

The third point is there such that independent of whether or not
the system image is found, the environment upon transfer of
control to the user is always the same (e.g. a package may
have type-pirated a method, which is available independent
of whether the user ever explicitly did `using`).

This is highly incomplete. In particular, these scheme to find
the system image needs to take account of preferences and should
probably exlcude any packages that are `dev`'ed (or their
dependents). I'm not sure I'll have the time to get around to
finishing this, but I'm hoping somebody else would be willing
to jump in for that part. The underlying mechanism seems to work
fine at this point, so this work should be mostly confined to
loading.jl.
  • Loading branch information
Keno committed Apr 13, 2021
1 parent 45c518d commit 811ba70
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 7 deletions.
41 changes: 39 additions & 2 deletions base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -484,15 +484,52 @@ Use [`Base.include`](@ref) to evaluate a file into another module.
"""
MainInclude.include

"""
If the --autoload option is passed, check if there is a better system image to
use instead. The path to this system image is returned.
"""
function __process_autoload__()
init_active_project()
if ACTIVE_PROJECT[] === nothing
return nothing
end
# TODO: This should hash the contents of the manifest plus any preferences
# instead, excluding any dev-ed packages and their dependents. Also, it
# needs a cryptographically secure hash like SHA256.
fname = hash(String(read(project_file_manifest_path(ACTIVE_PROJECT[]))))
init_depot_path()
for path in DEPOT_PATH
img = joinpath(path, "sysimgs", string(fname, ".", Libc.Libdl.dlext))
ccall(:jl_, Cvoid, (Any,), img)
isfile(img) && return img
end
return nothing
end

function _start()
opts = JLOptions()
if opts.autoload != 0 && ACTIVE_PROJECT[] !== nothing
# If autoloading failed, still make sure to load all the packages that
# are in the Project.toml to make sure that whether or not autoloading
# succeeded, the environment behaves identically.
@warn "Autoloading failed. Manually loading dependencies."
# TODO: Instead of this, we could build a .ji file to resolve all
# invalidations, etc. That ji file could then also be picked up by
# PkgCompiler to rebuild the sysimage quickly also.
all_deps = explicit_project_deps_get_all(
env_project_file(ACTIVE_PROJECT[]))
for dep in all_deps
require(dep)
end
end
empty!(ARGS)
append!(ARGS, Core.ARGS)
if ccall(:jl_generating_output, Cint, ()) != 0 && JLOptions().incremental == 0
if ccall(:jl_generating_output, Cint, ()) != 0 && opts.incremental == 0
# clear old invalid pointers
PCRE.__init__()
end
try
exec_options(JLOptions())
exec_options(opts)
catch
invokelatest(display_error, catch_stack())
exit(1)
Expand Down
7 changes: 7 additions & 0 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,13 @@ function explicit_project_deps_get(project_file::String, name::String)::Union{No
return nothing
end

function explicit_project_deps_get_all(project_file::String)
d = parsed_toml(project_file)
deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
deps === nothing && return PkgId[]
[PkgId(UUID(uuid), name) for (name, uuid) in deps if uuid !== nothing]
end

# find `where` stanza and return the PkgId for `name`
# return `nothing` if it did not find `where` (indicating caller should continue searching)
function explicit_manifest_deps_get(project_file::String, where::UUID, name::String)::Union{Nothing,PkgId}
Expand Down
1 change: 1 addition & 0 deletions base/options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct JLOptions
warn_overwrite::Int8
can_inline::Int8
polly::Int8
autoload::Int8
trace_compile::Ptr{UInt8}
fast_math::Int8
worker::Int8
Expand Down
7 changes: 6 additions & 1 deletion src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7888,7 +7888,6 @@ extern "C" void jl_init_llvm(void)
{
jl_page_size = jl_getpagesize();
imaging_mode = jl_options.image_codegen || (jl_generating_output() && !jl_options.incremental);
jl_default_cgparams.generic_context = jl_nothing;
jl_init_debuginfo();

InitializeNativeTarget();
Expand Down Expand Up @@ -8050,6 +8049,11 @@ extern "C" void jl_init_llvm(void)

extern "C" void jl_init_codegen(void)
{
static bool codegen_inited = false;
jl_default_cgparams.generic_context = jl_nothing;
if (codegen_inited)
return;

jl_init_llvm();
// Now that the execution engine exists, initialize all modules
jl_init_jit();
Expand All @@ -8060,6 +8064,7 @@ extern "C" void jl_init_codegen(void)
init_julia_llvm_env(m);

jl_init_intrinsic_functions_codegen();
codegen_inited = true;
}

extern "C" void jl_teardown_codegen()
Expand Down
6 changes: 6 additions & 0 deletions src/gf.c
Original file line number Diff line number Diff line change
Expand Up @@ -2272,6 +2272,12 @@ STATIC_INLINE int sig_match_fast(jl_value_t *arg1t, jl_value_t **args, jl_value_

jl_typemap_entry_t *call_cache[N_CALL_CACHE] JL_GLOBALLY_ROOTED;
static uint8_t pick_which[N_CALL_CACHE];

void jl_reset_call_cache(void)
{
memset(call_cache, 0, sizeof(call_cache));
}

#ifdef JL_GF_PROFILE
size_t ncalls;
void call_cache_stats()
Expand Down
29 changes: 28 additions & 1 deletion src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -734,9 +734,10 @@ void _julia_init(JL_IMAGE_SEARCH rel)
jl_gc_enable(0);

jl_resolve_sysimg_location(rel);
int already_loaded = 0;
// loads sysimg if available, and conditionally sets jl_options.cpu_target
if (jl_options.image_file)
jl_preload_sysimg_so(jl_options.image_file);
already_loaded = jl_preload_sysimg_so(jl_options.image_file);
if (jl_options.cpu_target == NULL)
jl_options.cpu_target = "native";

Expand Down Expand Up @@ -764,6 +765,32 @@ void _julia_init(JL_IMAGE_SEARCH rel)
jl_init_main_module();
jl_load(jl_core_module, "boot.jl");
post_boot_hooks();
} else if (jl_options.autoload) {
// Check if we need to load a different sysimage instead
jl_value_t *f = jl_get_global(jl_base_module, jl_symbol("__process_autoload__"));
if (f) {
size_t last_age = ptls->world_age;
ptls->world_age = jl_get_world_counter();
jl_value_t *new_sysimg = jl_apply(&f, 1);
ptls->world_age = last_age;
if (new_sysimg != jl_nothing) {
jl_typeassert(new_sysimg, jl_string_type);
size_t plen = jl_string_len(new_sysimg);
char *new_sysimg_path = (char*)malloc(plen+1);
memcpy(new_sysimg_path, jl_string_data(new_sysimg), plen);
new_sysimg_path[plen] = '\0';

jl_unload_system_image(!already_loaded);
jl_preload_sysimg_so(new_sysimg_path);
jl_restore_system_image(new_sysimg_path);
free(new_sysimg_path);

jl_init_serializer();
jl_init_root_task(stack_lo, stack_hi);

jl_options.autoload = 0;
}
}
}

if (jl_base_module != NULL) {
Expand Down
6 changes: 6 additions & 0 deletions src/jloptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ jl_options_t jl_options = { 0, // quiet
0, // method overwrite warning
1, // can_inline
JL_OPTIONS_POLLY_ON, // polly
0, // autoload
NULL, // trace_compile
JL_OPTIONS_FAST_MATH_DEFAULT,
0, // worker
Expand Down Expand Up @@ -208,6 +209,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
opt_bug_report,
opt_image_codegen,
opt_rr_detach,
opt_autoload,
};
static const char* const shortopts = "+vhqH:e:E:L:J:C:it:p:O:g:";
static const struct option longopts[] = {
Expand All @@ -231,6 +233,7 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
{ "procs", required_argument, 0, 'p' },
{ "threads", required_argument, 0, 't' },
{ "machine-file", required_argument, 0, opt_machine_file },
{ "autoload", no_argument, 0, opt_autoload },
{ "project", optional_argument, 0, opt_project },
{ "color", required_argument, 0, opt_color },
{ "history-file", required_argument, 0, opt_history_file },
Expand Down Expand Up @@ -446,6 +449,9 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp)
case opt_project:
jl_options.project = optarg ? strdup(optarg) : "@.";
break;
case opt_autoload:
jl_options.autoload=1;
break;
case opt_color:
if (!strcmp(optarg, "yes"))
jl_options.color = JL_OPTIONS_COLOR_ON;
Expand Down
4 changes: 3 additions & 1 deletion src/julia.h
Original file line number Diff line number Diff line change
Expand Up @@ -1656,7 +1656,8 @@ JL_DLLEXPORT void JL_NORETURN jl_exit(int status);
JL_DLLEXPORT const char *jl_pathname_for_handle(void *handle);

JL_DLLEXPORT int jl_deserialize_verify_header(ios_t *s);
JL_DLLEXPORT void jl_preload_sysimg_so(const char *fname);
JL_DLLEXPORT int jl_preload_sysimg_so(const char *fname);
JL_DLLEXPORT void jl_unload_system_image(int dlclose_handle);
JL_DLLEXPORT void jl_set_sysimg_so(void *handle);
JL_DLLEXPORT ios_t *jl_create_system_image(void *);
JL_DLLEXPORT void jl_save_system_image(const char *fname);
Expand Down Expand Up @@ -1998,6 +1999,7 @@ typedef struct {
int8_t warn_overwrite;
int8_t can_inline;
int8_t polly;
int8_t autoload;
const char *trace_compile;
int8_t fast_math;
int8_t worker;
Expand Down
2 changes: 2 additions & 0 deletions src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ extern size_t jl_page_size;
extern jl_function_t *jl_typeinf_func;
extern size_t jl_typeinf_world;
extern jl_typemap_entry_t *call_cache[N_CALL_CACHE] JL_GLOBALLY_ROOTED;
void jl_reset_call_cache(void);
extern jl_array_t *jl_all_methods JL_GLOBALLY_ROOTED;

JL_DLLEXPORT extern int jl_lineno;
Expand Down Expand Up @@ -1338,6 +1339,7 @@ void jl_write_compiler_output(void);
#endif

jl_sym_t *_jl_symbol(const char *str, size_t len) JL_NOTSAFEPOINT;
void jl_reset_symbol_type(void) JL_GC_DISABLED;

// Tools for locally disabling spurious compiler warnings
//
Expand Down
1 change: 1 addition & 0 deletions src/processor.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ typedef struct _jl_sysimg_fptrs_t {
* Return the data about the function pointers selected.
*/
jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl);
void jl_reset_processor_sysimg(void);

// Return the name of the host CPU as a julia string.
JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void);
Expand Down
5 changes: 5 additions & 0 deletions src/processor_x86.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,11 @@ jl_sysimg_fptrs_t jl_init_processor_sysimg(void *hdl)
return parse_sysimg(hdl, sysimg_init_cb);
}

void jl_reset_processor_sysimg()
{
jit_targets.clear();
}

std::pair<std::string,std::vector<std::string>> jl_get_llvm_target(bool imaging, uint32_t &flags)
{
ensure_jit_target(imaging);
Expand Down
64 changes: 62 additions & 2 deletions src/staticdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -1655,18 +1655,77 @@ JL_DLLEXPORT void jl_save_system_image(const char *fname)
JL_SIGATOMIC_END();
}

// Unloads an existing system image. Only valid early in the loading process
// when __init__ hasn't been run yet.
JL_DLLEXPORT void jl_unload_system_image(int dlclose_handle) JL_GC_DISABLED
{
// We must get rid of all non-perm objects before closing the old system
// image. This tries to accomplish that by running two GC passes, and then
// another one after dropping the main module. This should be reasonably
// robust against whatever code may have run to figure out this system
// image, though of course, it is not fully general. The sysimg location
// code needs to take care not to necromance any finalizers, etc.
jl_gc_enable(1);
jl_gc_collect(JL_GC_FULL);
jl_gc_collect(JL_GC_FULL);
jl_ptls_t ptls = jl_get_ptls_states();
// Reset GC roots. We need to make sure there's no marking here at all,
// otherwise, we might find the reference to the root task in "Base" that
// we're about to delete here.
ptls->current_task = ptls->root_task = NULL;
jl_main_module = NULL;
jl_an_empty_vec_any = NULL;
jl_anytuple_type_type = NULL;
jl_emptytuple_type = NULL;
jl_module_init_order = NULL;
jl_reset_call_cache();
jl_gc_collect(JL_GC_FULL);
jl_gc_enable(0);


jl_value_t **const*const tags = get_tags();
for (int i = 0; tags[i] != NULL; i++) {
jl_value_t **tag = tags[i];
*tag = NULL;
}

memset(&sysimg_fptrs, 0, sizeof(sysimg_fptrs));
sysimage_base = 0;
sysimg_gvars_base = NULL;
sysimg_gvars_offsets = NULL;
sysimg_gvars_max = 0;
sysimg_base = NULL;
sysimg_relocs = NULL;

jl_idtable_type = NULL;
jl_idtable_typename = NULL;
jl_bigint_type = NULL;
gmp_limb_size = 0;

jl_reset_processor_sysimg();

if (dlclose_handle)
jl_dlclose(jl_sysimg_handle);

jl_cleanup_serializer2();

jl_sysimg_handle = NULL;
}

// Takes in a path of the form "usr/lib/julia/sys.so" (jl_restore_system_image should be passed the same string)
JL_DLLEXPORT void jl_preload_sysimg_so(const char *fname)
JL_DLLEXPORT int jl_preload_sysimg_so(const char *fname)
{
if (jl_sysimg_handle)
return; // embedded target already called jl_set_sysimg_so
return 1; // embedded target already called jl_set_sysimg_so

char *dot = (char*) strrchr(fname, '.');
int is_ji = (dot && !strcmp(dot, ".ji"));

// Get handle to sys.so
if (!is_ji) // .ji extension => load .ji file only
jl_set_sysimg_so(jl_load_dynamic_library(fname, JL_RTLD_LOCAL | JL_RTLD_NOW, 1));

return 0;
}

// Allow passing in a module handle directly, rather than a path
Expand Down Expand Up @@ -1738,6 +1797,7 @@ static void jl_restore_system_image_from_stream(ios_t *f) JL_GC_DISABLED
jl_value_t **tag = tags[i];
*tag = jl_read_value(&s);
}
jl_reset_symbol_type();
s.ptls->root_task = (jl_task_t*)jl_gc_alloc(s.ptls, sizeof(jl_task_t), jl_task_type);
memset(s.ptls->root_task, 0, sizeof(jl_task_t));
s.ptls->root_task->tls = jl_read_value(&s);
Expand Down
30 changes: 30 additions & 0 deletions src/symbol.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,36 @@ JL_DLLEXPORT jl_sym_t *jl_get_root_symbol(void)
return symtab;
}

void jl_reset_symbol_type(void) JL_GC_DISABLED
{
if (!symtab)
return;
// The symbol type was reset by replacing the sysimage. Update the symtab.
// We could just drop the symtab entirely and let GC handle it, but it's
// much faster to keep the symtab, since allocating the symtab is one
// of the primary things startup does.
arraylist_t workqueue;
arraylist_new(&workqueue, 0);
jl_sym_t *item = symtab;
while (1) {
jl_set_typeof(item, (jl_value_t*)
(((uintptr_t)jl_symbol_type) | GC_OLD_MARKED));
if (item->left) {
if (item->right) {
arraylist_push(&workqueue, (void*)item->right);
}
item = item->left;
continue;
} else if (item->right) {
item = item->right;
continue;
}
if (workqueue.len == 0)
break;
item = (jl_sym_t*)arraylist_pop(&workqueue);
}
}

static uint32_t gs_ctr = 0; // TODO: per-thread
uint32_t jl_get_gs_ctr(void) { return gs_ctr; }
void jl_set_gs_ctr(uint32_t ctr) { gs_ctr = ctr; }
Expand Down

0 comments on commit 811ba70

Please sign in to comment.