From 7aac48347e76b9b95428b62921ceae04e4074889 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Fri, 14 Jan 2022 03:42:33 -0600 Subject: [PATCH] Add relocatable root compression Currently we can't cache "external" CodeInstances, i.e., those generated by compiling other modules' methods with externally-defined types. For example, consider `push!([], MyPkg.MyType())`: Base owns the method `push!(::Vector{Any}, ::Any)` but doesn't know about `MyType`. While there are several obstacles to caching exteral CodeInstances, the primary one is that in compressed IR, method roots are referenced from a list by index, and the index is defined by order of insertion. This order might change depending on package-loading sequence or other history-dependent factors. If the order isn't consistent, our current serialization techniques would result in corrupted code upon decompression, and that would generally trigger catastrophic failure. To avoid this problem, we simply avoid caching such CodeInstances. This enables roots to be referenced with respect to a `(key, index)` pair, where `key` identifies the module and `index` numbers just those roots with the same `key`. Roots with `key = 0` are considered to be of unknown origin, and CodeInstances referencing such roots will remain unserializable unless all such roots were added at the time of system image creation. To track this additional data, this adds two fields to core types: - to methods, it adds a `nroots_sysimg` field to count the number of roots defined at the time of writing the system image (such occur first in the list of `roots`) - to CodeInstances, it adds a flag `relocatability` having value 1 if every root is "safe," meaning it was either added at sysimg creation or is tagged with a non-zero `key`. Even a single unsafe root will cause this to have value 0. --- base/boot.jl | 6 +-- base/compiler/typeinfer.jl | 7 +-- src/codegen.cpp | 5 ++- src/common_symbols1.inc | 1 - src/common_symbols2.inc | 2 +- src/dump.c | 4 ++ src/gf.c | 15 ++++--- src/ircode.c | 43 ++++++++++++++---- src/jltypes.c | 16 ++++--- src/julia.h | 2 + src/julia_internal.h | 3 ++ src/method.c | 29 ++++++++++++ src/serialize.h | 3 +- src/staticdata.c | 18 ++++++++ src/support/Makefile | 2 +- src/support/rle.c | 92 ++++++++++++++++++++++++++++++++++++++ src/support/rle.h | 48 ++++++++++++++++++++ src/support/test/rletest.c | 60 +++++++++++++++++++++++++ test/precompile.jl | 18 ++++++++ 19 files changed, 341 insertions(+), 33 deletions(-) create mode 100644 src/support/rle.c create mode 100644 src/support/rle.h create mode 100644 src/support/test/rletest.c diff --git a/base/boot.jl b/base/boot.jl index 1469e4d5c8e69..abdd7987ce901 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -418,9 +418,9 @@ eval(Core, :(LineInfoNode(mod::Module, @nospecialize(method), file::Symbol, line $(Expr(:new, :LineInfoNode, :mod, :method, :file, :line, :inlined_at)))) eval(Core, :(CodeInstance(mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const), @nospecialize(inferred), const_flags::Int32, - min_world::UInt, max_world::UInt) = - ccall(:jl_new_codeinst, Ref{CodeInstance}, (Any, Any, Any, Any, Int32, UInt, UInt), - mi, rettype, inferred_const, inferred, const_flags, min_world, max_world))) + min_world::UInt, max_world::UInt, relocatability::UInt8) = + ccall(:jl_new_codeinst, Ref{CodeInstance}, (Any, Any, Any, Any, Int32, UInt, UInt, UInt8), + mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, relocatability))) eval(Core, :(Const(@nospecialize(v)) = $(Expr(:new, :Const, :v)))) eval(Core, :(PartialStruct(@nospecialize(typ), fields::Array{Any, 1}) = $(Expr(:new, :PartialStruct, :typ, :fields)))) eval(Core, :(PartialOpaque(@nospecialize(typ), @nospecialize(env), isva::Bool, parent::MethodInstance, source::Method) = $(Expr(:new, :PartialOpaque, :typ, :env, :isva, :parent, :source)))) diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 47951336035ef..ee7ff881239ca 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -280,7 +280,7 @@ function _typeinf(interp::AbstractInterpreter, frame::InferenceState) end function CodeInstance(result::InferenceResult, @nospecialize(inferred_result), - valid_worlds::WorldRange) + valid_worlds::WorldRange, relocatability::UInt8) local const_flags::Int32 result_type = result.result @assert !(result_type isa LimitedAccuracy) @@ -312,7 +312,7 @@ function CodeInstance(result::InferenceResult, @nospecialize(inferred_result), end return CodeInstance(result.linfo, widenconst(result_type), rettype_const, inferred_result, - const_flags, first(valid_worlds), last(valid_worlds)) + const_flags, first(valid_worlds), last(valid_worlds), relocatability) end # For the NativeInterpreter, we don't need to do an actual cache query to know @@ -386,7 +386,8 @@ function cache_result!(interp::AbstractInterpreter, result::InferenceResult) # TODO: also don't store inferred code if we've previously decided to interpret this function if !already_inferred inferred_result = transform_result_for_cache(interp, linfo, valid_worlds, result.src) - code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds) + relocatability = isa(inferred_result, Vector{UInt8}) ? inferred_result[end] : UInt8(0) + code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds, relocatability) end unlock_mi_inference(interp, linfo) nothing diff --git a/src/codegen.cpp b/src/codegen.cpp index 07b935b1449a1..3b95e9c1e02c8 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -7682,8 +7682,11 @@ jl_compile_result_t jl_emit_codeinst( jl_options.debug_level > 1) { // update the stored code if (codeinst->inferred != (jl_value_t*)src) { - if (jl_is_method(def)) + if (jl_is_method(def)) { src = (jl_code_info_t*)jl_compress_ir(def, src); + assert(jl_typeis(src, jl_array_uint8_type)); + codeinst->relocatability = ((uint8_t*)jl_array_data(src))[jl_array_len(src)-1]; + } codeinst->inferred = (jl_value_t*)src; jl_gc_wb(codeinst, src); } diff --git a/src/common_symbols1.inc b/src/common_symbols1.inc index 80038837be0c4..7d445289e80fa 100644 --- a/src/common_symbols1.inc +++ b/src/common_symbols1.inc @@ -97,4 +97,3 @@ jl_symbol("undef"), jl_symbol("sizeof"), jl_symbol("String"), jl_symbol("namedtuple.jl"), -jl_symbol("pop"), diff --git a/src/common_symbols2.inc b/src/common_symbols2.inc index a28f1ef50af24..c9f4e41b83e33 100644 --- a/src/common_symbols2.inc +++ b/src/common_symbols2.inc @@ -1,3 +1,4 @@ +jl_symbol("pop"), jl_symbol("inbounds"), jl_symbol("strings/string.jl"), jl_symbol("Ref"), @@ -251,4 +252,3 @@ jl_symbol("GitError"), jl_symbol("zeros"), jl_symbol("InexactError"), jl_symbol("LogLevel"), -jl_symbol("between"), diff --git a/src/dump.c b/src/dump.c index afc372696a8f2..dc551580a59aa 100644 --- a/src/dump.c +++ b/src/dump.c @@ -528,6 +528,7 @@ static void jl_serialize_code_instance(jl_serializer_state *s, jl_code_instance_ jl_serialize_value(s, NULL); jl_serialize_value(s, jl_any_type); } + write_uint8(s->s, codeinst->relocatability); jl_serialize_code_instance(s, codeinst->next, skip_partial_opaque); } @@ -705,6 +706,7 @@ static void jl_serialize_value_(jl_serializer_state *s, jl_value_t *v, int as_li jl_serialize_value(s, (jl_value_t*)m->slot_syms); jl_serialize_value(s, (jl_value_t*)m->roots); jl_serialize_value(s, (jl_value_t*)m->root_blocks); + write_int32(s->s, m->nroots_sysimg); jl_serialize_value(s, (jl_value_t*)m->ccallable); jl_serialize_value(s, (jl_value_t*)m->source); jl_serialize_value(s, (jl_value_t*)m->unspecialized); @@ -1577,6 +1579,7 @@ static jl_value_t *jl_deserialize_value_method(jl_serializer_state *s, jl_value_ m->root_blocks = (jl_array_t*)jl_deserialize_value(s, (jl_value_t**)&m->root_blocks); if (m->root_blocks) jl_gc_wb(m, m->root_blocks); + m->nroots_sysimg = read_int32(s->s); m->ccallable = (jl_svec_t*)jl_deserialize_value(s, (jl_value_t**)&m->ccallable); if (m->ccallable) { jl_gc_wb(m, m->ccallable); @@ -1661,6 +1664,7 @@ static jl_value_t *jl_deserialize_value_code_instance(jl_serializer_state *s, jl codeinst->invoke = jl_fptr_const_return; if ((flags >> 3) & 1) codeinst->precompile = 1; + codeinst->relocatability = read_uint8(s->s); codeinst->next = (jl_code_instance_t*)jl_deserialize_value(s, (jl_value_t**)&codeinst->next); jl_gc_wb(codeinst, codeinst->next); if (validate) { diff --git a/src/gf.c b/src/gf.c index 27b19e50c84ea..3d0d76c04fd66 100644 --- a/src/gf.c +++ b/src/gf.c @@ -204,7 +204,7 @@ JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_value_t *typ JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( jl_method_instance_t *mi, jl_value_t *rettype, jl_value_t *inferred_const, jl_value_t *inferred, - int32_t const_flags, size_t min_world, size_t max_world); + int32_t const_flags, size_t min_world, size_t max_world, uint8_t relocatability); JL_DLLEXPORT void jl_mi_cache_insert(jl_method_instance_t *mi JL_ROOTING_ARGUMENT, jl_code_instance_t *ci JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED); @@ -233,7 +233,7 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, jl_nothing, jl_nothing, - 0, 1, ~(size_t)0); + 0, 1, ~(size_t)0, 0); jl_mi_cache_insert(mi, codeinst); codeinst->specptr.fptr1 = fptr; codeinst->invoke = jl_fptr_args; @@ -357,7 +357,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( } codeinst = jl_new_codeinst( mi, rettype, NULL, NULL, - 0, min_world, max_world); + 0, min_world, max_world, 0); jl_mi_cache_insert(mi, codeinst); return codeinst; } @@ -365,7 +365,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_method_instance_t *mi, jl_value_t *rettype, jl_value_t *inferred_const, jl_value_t *inferred, - int32_t const_flags, size_t min_world, size_t max_world + int32_t const_flags, size_t min_world, size_t max_world, uint8_t relocatability /*, jl_array_t *edges, int absolute_max*/) { jl_task_t *ct = jl_current_task; @@ -390,6 +390,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( codeinst->isspecsig = 0; codeinst->precompile = 0; codeinst->next = NULL; + codeinst->relocatability = relocatability; return codeinst; } @@ -1986,7 +1987,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (unspec && jl_atomic_load_relaxed(&unspec->invoke)) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0); + 0, 1, ~(size_t)0, 0); codeinst->isspecsig = 0; codeinst->specptr = unspec->specptr; codeinst->rettype_const = unspec->rettype_const; @@ -2004,7 +2005,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t if (!jl_code_requires_compiler(src)) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0); + 0, 1, ~(size_t)0, 0); codeinst->invoke = jl_fptr_interpret_call; jl_mi_cache_insert(mi, codeinst); record_precompile_statement(mi); @@ -2039,7 +2040,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t return ucache; } codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, - 0, 1, ~(size_t)0); + 0, 1, ~(size_t)0, 0); codeinst->isspecsig = 0; codeinst->specptr = ucache->specptr; codeinst->rettype_const = ucache->rettype_const; diff --git a/src/ircode.c b/src/ircode.c index 3bb60eb7b24d2..5be83ed4caac3 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -26,30 +26,37 @@ typedef struct { // method we're compressing for jl_method_t *method; jl_ptls_t ptls; + uint8_t relocatability; } jl_ircode_state; // --- encoding --- #define jl_encode_value(s, v) jl_encode_value_((s), (jl_value_t*)(v), 0) -static int literal_val_id(jl_ircode_state *s, jl_value_t *v) JL_GC_DISABLED +static void tagged_root(rle_reference *rr, jl_ircode_state *s, int i) +{ + if (!get_root_reference(rr, s->method, i)) + s->relocatability = 0; +} + +static void literal_val_id(rle_reference *rr, jl_ircode_state *s, jl_value_t *v) JL_GC_DISABLED { jl_array_t *rs = s->method->roots; int i, l = jl_array_len(rs); if (jl_is_symbol(v) || jl_is_concrete_type(v)) { for (i = 0; i < l; i++) { if (jl_array_ptr_ref(rs, i) == v) - return i; + return tagged_root(rr, s, i); } } else { for (i = 0; i < l; i++) { if (jl_egal(jl_array_ptr_ref(rs, i), v)) - return i; + return tagged_root(rr, s, i); } } jl_add_method_root(s->method, jl_precompile_toplevel_module, v); - return jl_array_len(rs) - 1; + return tagged_root(rr, s, jl_array_len(rs) - 1); } static void jl_encode_int32(jl_ircode_state *s, int32_t x) @@ -67,6 +74,7 @@ static void jl_encode_int32(jl_ircode_state *s, int32_t x) static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) JL_GC_DISABLED { size_t i; + rle_reference rr; if (v == NULL) { write_uint8(s->s, TAG_NULL); @@ -321,8 +329,13 @@ static void jl_encode_value_(jl_ircode_state *s, jl_value_t *v, int as_literal) if (!as_literal && !(jl_is_uniontype(v) || jl_is_newvarnode(v) || jl_is_tuple(v) || jl_is_linenode(v) || jl_is_upsilonnode(v) || jl_is_pinode(v) || jl_is_slot(v) || jl_is_ssavalue(v))) { - int id = literal_val_id(s, v); + literal_val_id(&rr, s, v); + int id = rr.index; assert(id >= 0); + if (rr.key) { + write_uint8(s->s, TAG_RELOC_METHODROOT); + write_int64(s->s, rr.key); + } if (id < 256) { write_uint8(s->s, TAG_METHODROOT); write_uint8(s->s, id); @@ -577,6 +590,7 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED assert(!ios_eof(s->s)); jl_value_t *v; size_t i, n; + uint64_t key; uint8_t tag = read_uint8(s->s); if (tag > LAST_TAG) return jl_deser_tag(tag); @@ -585,10 +599,15 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED case 0: tag = read_uint8(s->s); return jl_deser_tag(tag); + case TAG_RELOC_METHODROOT: + key = read_uint64(s->s); + tag = read_uint8(s->s); + assert(tag == TAG_METHODROOT || tag == TAG_LONG_METHODROOT); + return lookup_root(s->method, key, tag == TAG_METHODROOT ? read_uint8(s->s) : read_uint16(s->s)); case TAG_METHODROOT: - return jl_array_ptr_ref(s->method->roots, read_uint8(s->s)); + return lookup_root(s->method, 0, read_uint8(s->s)); case TAG_LONG_METHODROOT: - return jl_array_ptr_ref(s->method->roots, read_uint16(s->s)); + return lookup_root(s->method, 0, read_uint16(s->s)); case TAG_SVEC: JL_FALLTHROUGH; case TAG_LONG_SVEC: return jl_decode_value_svec(s, tag); case TAG_COMMONSYM: @@ -706,7 +725,8 @@ JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) jl_ircode_state s = { &dest, m, - jl_current_task->ptls + jl_current_task->ptls, + 1 }; jl_code_info_flags_t flags = code_info_flags(code->pure, code->propagate_inbounds, code->inlineable, code->inferred, code->constprop); @@ -756,6 +776,8 @@ JL_DLLEXPORT jl_array_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) ios_write(s.s, (char*)jl_array_data(code->codelocs), nstmt * sizeof(int32_t)); } + write_uint8(s.s, s.relocatability); + ios_flush(s.s); jl_array_t *v = jl_take_buffer(&dest); ios_close(s.s); @@ -786,7 +808,8 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t jl_ircode_state s = { &src, m, - jl_current_task->ptls + jl_current_task->ptls, + 1 }; jl_code_info_t *code = jl_new_code_info_uninit(); @@ -831,6 +854,8 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t ios_readall(s.s, (char*)jl_array_data(code->codelocs), nstmt * sizeof(int32_t)); } + (void) read_uint8(s.s); // relocatability + assert(ios_getc(s.s) == -1); ios_close(s.s); JL_GC_PUSH1(&code); diff --git a/src/jltypes.c b/src/jltypes.c index 0042388660362..8e25ff9fa9a5f 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -2393,7 +2393,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_method_type = jl_new_datatype(jl_symbol("Method"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(27, + jl_perm_symsvec(28, "name", "module", "file", @@ -2410,6 +2410,7 @@ void jl_init_types(void) JL_GC_DISABLED "generator", // !const "roots", // !const "root_blocks", // !const + "nroots_sysimg", "ccallable", // !const "invokes", // !const "recursion_relation", // !const @@ -2421,7 +2422,7 @@ void jl_init_types(void) JL_GC_DISABLED "pure", "is_for_opaque_closure", "constprop"), - jl_svec(27, + jl_svec(28, jl_symbol_type, jl_module_type, jl_symbol_type, @@ -2438,6 +2439,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, jl_array_any_type, jl_array_uint64_type, + jl_int32_type, jl_simplevector_type, jl_any_type, jl_any_type, @@ -2483,7 +2485,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_code_instance_type = jl_new_datatype(jl_symbol("CodeInstance"), core, jl_any_type, jl_emptysvec, - jl_perm_symsvec(11, + jl_perm_symsvec(12, "def", "next", "min_world", @@ -2493,8 +2495,9 @@ void jl_init_types(void) JL_GC_DISABLED "inferred", //"edges", //"absolute_max", - "isspecsig", "precompile", "invoke", "specptr"), // function object decls - jl_svec(11, + "isspecsig", "precompile", "invoke", "specptr", // function object decls + "relocatability"), + jl_svec(12, jl_method_instance_type, jl_any_type, jl_ulong_type, @@ -2506,7 +2509,8 @@ void jl_init_types(void) JL_GC_DISABLED //jl_bool_type, jl_bool_type, jl_bool_type, - jl_any_type, jl_any_type), // fptrs + jl_any_type, jl_any_type, // fptrs + jl_uint8_type), jl_emptysvec, 0, 1, 1); jl_svecset(jl_code_instance_type->types, 1, jl_code_instance_type); diff --git a/src/julia.h b/src/julia.h index 1f1186606d8cc..a04637041d2ce 100644 --- a/src/julia.h +++ b/src/julia.h @@ -294,6 +294,7 @@ typedef struct _jl_method_t { // Identify roots by module-of-origin. We only track the module for roots added during incremental compilation. // May be NULL if no external roots have been added, otherwise it's a Vector{UInt64} jl_array_t *root_blocks; // RLE (build_id, offset) pairs (even/odd indexing) + int32_t nroots_sysimg; // # of roots stored in the system image jl_svec_t *ccallable; // svec(rettype, sig) if a ccallable entry point is requested for this // cache of specializations of this method for invoke(), i.e. @@ -381,6 +382,7 @@ typedef struct _jl_code_instance_t { _Atomic(jl_fptr_sparam_t) fptr3; // 4 interpreter } specptr; // private data for `jlcall entry point + uint8_t relocatability; // nonzero if all roots are built into sysimg or tagged by module key } jl_code_instance_t; // all values are callable as Functions diff --git a/src/julia_internal.h b/src/julia_internal.h index e5ba0b23a2a66..fd1c642004496 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -10,6 +10,7 @@ #include "support/ptrhash.h" #include "support/strtod.h" #include "gc-alloc-profiler.h" +#include "support/rle.h" #include #if !defined(_WIN32) #include @@ -528,6 +529,8 @@ void jl_resolve_globals_in_ir(jl_array_t *stmts, jl_module_t *m, jl_svec_t *spar int binding_effects); JL_DLLEXPORT void jl_add_method_root(jl_method_t *m, jl_module_t *mod, jl_value_t* root); +int get_root_reference(rle_reference *rr, jl_method_t *m, size_t i); +jl_value_t *lookup_root(jl_method_t *m, uint64_t key, int index); int jl_valid_type_param(jl_value_t *v); diff --git a/src/method.c b/src/method.c index 2d39b7e5923d9..d7de06c01592e 100644 --- a/src/method.c +++ b/src/method.c @@ -732,6 +732,7 @@ JL_DLLEXPORT jl_method_t *jl_new_method_uninit(jl_module_t *module) m->slot_syms = NULL; m->roots = NULL; m->root_blocks = NULL; + m->nroots_sysimg = 0; m->ccallable = NULL; m->module = module; m->external_mt = NULL; @@ -1035,6 +1036,34 @@ JL_DLLEXPORT void jl_add_method_root(jl_method_t *m, jl_module_t *mod, jl_value_ JL_GC_POP(); } +// given the absolute index i of a root, retrieve its relocatable reference +// returns 1 if the root is relocatable +int get_root_reference(rle_reference *rr, jl_method_t *m, size_t i) +{ + if (!m->root_blocks) { + rr->key = 0; + rr->index = i; + return i < m->nroots_sysimg; + } + rle_index_to_reference(rr, i, (uint64_t*)jl_array_data(m->root_blocks), jl_array_len(m->root_blocks), 0); + if (rr->key) + return 1; + return i < m->nroots_sysimg; +} + +// get a root, given its key and index relative to the key +// this is the relocatable way to get a root from m->roots +jl_value_t *lookup_root(jl_method_t *m, uint64_t key, int index) +{ + if (!m->root_blocks) { + assert(key == 0); + return jl_array_ptr_ref(m->roots, index); + } + rle_reference rr = {key, index}; + size_t i = rle_reference_to_index(&rr, (uint64_t*)jl_array_data(m->root_blocks), jl_array_len(m->root_blocks), 0); + return jl_array_ptr_ref(m->roots, i); +} + #ifdef __cplusplus } #endif diff --git a/src/serialize.h b/src/serialize.h index 07ff46fdf96db..63d7c2d360951 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -62,8 +62,9 @@ extern "C" { #define TAG_GOTOIFNOT 54 #define TAG_RETURNNODE 55 #define TAG_ARGUMENT 56 +#define TAG_RELOC_METHODROOT 57 -#define LAST_TAG 56 +#define LAST_TAG 57 #define write_uint8(s, n) ios_putc((n), (s)) #define read_uint8(s) ((uint8_t)ios_getc(s)) diff --git a/src/staticdata.c b/src/staticdata.c index 37415a234808c..f11d94325bbfa 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -1660,6 +1660,23 @@ static void jl_strip_all_codeinfos(void) jl_foreach_reachable_mtable(strip_all_codeinfos_, NULL); } +static int set_nroots_sysimg__(jl_typemap_entry_t *def, void *_env) +{ + jl_method_t *m = def->func.method; + m->nroots_sysimg = m->roots ? jl_array_len(m->roots) : 0; + return 1; +} + +static void set_nroots_sysimg_(jl_methtable_t *mt, void *_env) +{ + jl_typemap_visitor(mt->defs, set_nroots_sysimg__, NULL); +} + +static void jl_set_nroots_sysimg(void) +{ + jl_foreach_reachable_mtable(set_nroots_sysimg_, NULL); +} + // --- entry points --- static void jl_init_serializer2(int); @@ -1675,6 +1692,7 @@ static void jl_save_system_image_to_stream(ios_t *f) JL_GC_DISABLED // strip metadata and IR when requested if (jl_options.strip_metadata || jl_options.strip_ir) jl_strip_all_codeinfos(); + jl_set_nroots_sysimg(); int en = jl_gc_enable(0); jl_init_serializer2(1); diff --git a/src/support/Makefile b/src/support/Makefile index 6083823e95408..a884aa5fd47e0 100644 --- a/src/support/Makefile +++ b/src/support/Makefile @@ -9,7 +9,7 @@ JCPPFLAGS += $(CPPFLAGS) JLDFLAGS += $(LDFLAGS) SRCS := hashing timefuncs ptrhash operators utf8 ios htable bitvector \ - int2str libsupportinit arraylist strtod + int2str libsupportinit arraylist strtod rle ifeq ($(OS),WINNT) SRCS += asprintf strptime ifeq ($(ARCH),i686) diff --git a/src/support/rle.c b/src/support/rle.c new file mode 100644 index 0000000000000..6b64fa8cf9700 --- /dev/null +++ b/src/support/rle.c @@ -0,0 +1,92 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#include "rle.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* iteration */ + +rle_iter_state rle_iter_init(uint64_t key0) +{ + rle_iter_state state = {-1, 0, key0}; + return state; +} + +int rle_iter_increment(rle_iter_state *state, size_t len, uint64_t *rletable, size_t npairs) +{ + state->i += 1; + size_t i = state->i, j = state->j; + if (i >= len) + return 0; + if (rletable) { + while (j < npairs && i >= rletable[j+1]) { + state->key = rletable[j]; + j += 2; + } + state->j = j; + } + return 1; +} + +/* indexing */ + +void rle_index_to_reference(rle_reference *rr, size_t i, uint64_t *rletable, size_t npairs, uint64_t key0) +{ + if (!rletable) { + rr->key = key0; + rr->index = i; + return; + } + // Determine the active key + uint64_t key = key0; + size_t jj = 0; + while (jj < npairs && i >= rletable[jj+1]) { + key = rletable[jj]; + jj += 2; + } + // Subtract the number of preceding items with different keys + uint64_t ckey = key0; + size_t j, start = 0, index = i; + for (j = 0; j < jj; j+=2) { + if (key != ckey) + index -= rletable[j+1] - start; + ckey = rletable[j]; + start = rletable[j+1]; + } + // Return the result + rr->key = key; + rr->index = index; + return; +} + +size_t rle_reference_to_index(rle_reference *rr, uint64_t *rletable, size_t npairs, uint64_t key0) +{ + uint64_t key = rr->key; + size_t index = rr->index, i = index; + if (!rletable) { + assert(key == key0); + return i; + } + uint64_t ckey = key0; + size_t j, start = 0, n; + for (j = 0; j < npairs; j+=2) { + n = rletable[j+1] - start; + if (key != ckey) + i += n; + else { + if (index < n) + break; + index -= n; + } + ckey = rletable[j]; + start = rletable[j+1]; + } + return i; +} + + +#ifdef __cplusplus +} +#endif diff --git a/src/support/rle.h b/src/support/rle.h new file mode 100644 index 0000000000000..f85d9f35c4b80 --- /dev/null +++ b/src/support/rle.h @@ -0,0 +1,48 @@ +// This file is a part of Julia. License is MIT: https://julialang.org/license + +#ifndef JL_RLE_H +#define JL_RLE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/* Run-length encoding (RLE) utilities */ +/* In the RLE table, even indexes encode the key (the item classification), odd indexes encode the item index */ +/* For example, a table + + {77, 3, 88, 5, 77, 8} + + would represent a list where items at indexes 3-4 have key 77, items at indexes 5-7 have key 88, + and items from 8 onward have key 77. Items prior to index 3 have an implicit key passed in as `key0`. +*/ + +/* iteration */ +typedef struct _rle_iter_state_t { + size_t i; // index for the items + size_t j; // index for the rle table + uint64_t key; // current identifier +} rle_iter_state; + +rle_iter_state rle_iter_init(/* implicit value of key for indexes prior to first explicit rle pair */ uint64_t key0); +int rle_iter_increment(rle_iter_state *state, /* number of items */ size_t len, uint64_t *rletable, /*length of rletable */ size_t npairs); + +/* indexing */ +typedef struct { + uint64_t key; + int index; // number of preceding items in the list with the same key +} rle_reference; + +void rle_index_to_reference(rle_reference *rr, /* item index */ size_t i, uint64_t *rletable, size_t npairs, uint64_t key0); +size_t rle_reference_to_index(rle_reference *rr, uint64_t *rletable, size_t npairs, uint64_t key0); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/support/test/rletest.c b/src/support/test/rletest.c new file mode 100644 index 0000000000000..c5c5caa97fde8 --- /dev/null +++ b/src/support/test/rletest.c @@ -0,0 +1,60 @@ +#include +#include +#include "../rle.h" + +int main() +{ + /* Iteration */ + rle_iter_state state = rle_iter_init(22); + int i = 0; + while (rle_iter_increment(&state, 5, NULL, 0)) { + assert(state.key == 22); + assert(state.i == i); + i++; + } + + uint64_t rletable1[4] = {-1, 2, 22, 3}; + state = rle_iter_init(22); + i = 0; + while (rle_iter_increment(&state, 5, rletable1, 4)) { + assert(state.key == (i < 2 ? 22 : (i < 3 ? -1 : 22))); + assert(state.i == i); + i++; + } + + uint64_t rletable2[4] = {-1, 0, 22, 3}; + state = rle_iter_init(22); + i = 0; + while (rle_iter_increment(&state, 5, rletable2, 4)) { + assert(state.key == (i < 3 ? -1 : 22)); + assert(state.i == i); + i++; + } + + state = rle_iter_init(22); + i = 0; + while (rle_iter_increment(&state, 0, rletable2, 4)) { + abort(); + } + + /* Indexing */ + rle_reference rr; + uint64_t rletable3[8] = {0, 0, 5, 2, 22, 3, 0, 5}; + uint64_t keys3[7] = {0, 0, 5, 22, 22, 0, 0}; + int counts3[7] = {0, 1, 0, 0, 1, 2, 3}; + for (i = 0; i < 7; i++) { + rle_index_to_reference(&rr, i, rletable3, 8, 0); + assert(rr.key == keys3[i]); + assert(rr.index == counts3[i]); + assert(rle_reference_to_index(&rr, rletable3, 8, 0) == i); + } + uint64_t rletable4[6] = {5, 2, 22, 3, 0, 5}; // implicit first block + for (i = 0; i < 7; i++) { + rle_index_to_reference(&rr, i, rletable4, 6, 0); + assert(rr.key == keys3[i]); + assert(rr.index == counts3[i]); + assert(rle_reference_to_index(&rr, rletable4, 6, 0) == i); + } + + return 0; +} diff --git a/test/precompile.jl b/test/precompile.jl index 06ff7516b0b4a..88825efa7816e 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -605,7 +605,15 @@ precompile_test_harness("code caching") do dir fpush(X[]) nothing end + function getelsize(list::Vector{T}) where T + n = 0 + for item in list + n += sizeof(T) + end + return n + end precompile(callboth, ()) + precompile(getelsize, (Vector{Int32},)) end """) Base.compilecache(Base.PkgId(string(Cache_module))) @@ -627,6 +635,16 @@ precompile_test_harness("code caching") do dir @test_broken M.X ∈ groups[Mid] # requires caching external compilation results @test M.X2 ∈ groups[rootid(@__MODULE__)] @test !isempty(groups[Bid]) + minternal = which(M.getelsize, (Vector,)) + mi = minternal.specializations[1] + ci = mi.cache + @test ci.relocatability == 1 + Base.invokelatest() do + M.getelsize(M.X2[]) + end + mi = minternal.specializations[2] + ci = mi.cache + @test ci.relocatability == 0 # PkgA loads PkgB, and both add roots to the same method (both before and after loading B) Cache_module2 = :Cachea1544c83560f0c99 write(joinpath(dir, "$Cache_module2.jl"),