From ef0882ea96b7597904ec74aa4f476b778f81a270 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 13 Nov 2024 20:38:36 +0000 Subject: [PATCH] Add mechanism to disable creating new worlds As a performance optimization, when you are certain only to run all remaining code in a fixed world (e.g. not even eval a new closure or comprehension), such as when all code is defined in a system image and not being used interactively, you can put a call to: Base.Experimental.disable_new_worlds() Which will enable performance optimizations around avoiding tracking backedges and invalidations. --- base/experimental.jl | 9 +++ src/gf.c | 152 ++++++++++++++++++++++++++++------------- src/staticdata.c | 32 ++++++--- src/staticdata_utils.c | 142 +++++++++++++++++++++----------------- src/toplevel.c | 2 +- test/misc.jl | 36 +++++++--- 6 files changed, 243 insertions(+), 130 deletions(-) diff --git a/base/experimental.jl b/base/experimental.jl index 31238d4015b3b..411bb2407cdc5 100644 --- a/base/experimental.jl +++ b/base/experimental.jl @@ -494,4 +494,13 @@ function entrypoint(@nospecialize(argt::Type)) nothing end +""" + Base.Experimental.disable_new_worlds() + +Mark that no new worlds (methods additions, deletions, etc) are permitted to be created at +any future time, allowing for lower latencies for some operations and slightly lower memory +usage, by eliminating the tracking of those possible invalidation. +""" +disable_new_worlds() = ccall(:jl_disable_new_worlds, Cvoid, ()) + end diff --git a/src/gf.c b/src/gf.c index 90b874d614b0c..bbf065a4fac0d 100644 --- a/src/gf.c +++ b/src/gf.c @@ -24,6 +24,7 @@ extern "C" { #endif +_Atomic(int) allow_new_worlds = 1; JL_DLLEXPORT _Atomic(size_t) jl_world_counter = 1; // uses atomic acquire/release jl_mutex_t world_counter_lock; JL_DLLEXPORT size_t jl_get_world_counter(void) JL_NOTSAFEPOINT @@ -1819,38 +1820,42 @@ static void invalidate_backedges(jl_method_instance_t *replaced_mi, size_t max_w // add a backedge from callee to caller JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, jl_value_t *invokesig, jl_code_instance_t *caller) { + if (!jl_atomic_load_relaxed(&allow_new_worlds)) + return; if (invokesig == jl_nothing) invokesig = NULL; // julia uses `nothing` but C uses NULL (#undef) assert(jl_is_method_instance(callee)); assert(jl_is_code_instance(caller)); assert(invokesig == NULL || jl_is_type(invokesig)); JL_LOCK(&callee->def.method->writelock); - int found = 0; - // TODO: use jl_cache_type_(invokesig) like cache_method does to save memory - if (!callee->backedges) { - // lazy-init the backedges array - callee->backedges = jl_alloc_vec_any(0); - jl_gc_wb(callee, callee->backedges); - } - else { - size_t i = 0, l = jl_array_nrows(callee->backedges); - for (i = 0; i < l; i++) { - // optimized version of while (i < l) i = get_next_edge(callee->backedges, i, &invokeTypes, &mi); - jl_value_t *mi = jl_array_ptr_ref(callee->backedges, i); - if (mi != (jl_value_t*)caller) - continue; - jl_value_t *invokeTypes = i > 0 ? jl_array_ptr_ref(callee->backedges, i - 1) : NULL; - if (invokeTypes && jl_is_method_instance(invokeTypes)) - invokeTypes = NULL; - if ((invokesig == NULL && invokeTypes == NULL) || - (invokesig && invokeTypes && jl_types_equal(invokesig, invokeTypes))) { - found = 1; - break; + if (jl_atomic_load_relaxed(&allow_new_worlds)) { + int found = 0; + // TODO: use jl_cache_type_(invokesig) like cache_method does to save memory + if (!callee->backedges) { + // lazy-init the backedges array + callee->backedges = jl_alloc_vec_any(0); + jl_gc_wb(callee, callee->backedges); + } + else { + size_t i = 0, l = jl_array_nrows(callee->backedges); + for (i = 0; i < l; i++) { + // optimized version of while (i < l) i = get_next_edge(callee->backedges, i, &invokeTypes, &mi); + jl_value_t *mi = jl_array_ptr_ref(callee->backedges, i); + if (mi != (jl_value_t*)caller) + continue; + jl_value_t *invokeTypes = i > 0 ? jl_array_ptr_ref(callee->backedges, i - 1) : NULL; + if (invokeTypes && jl_is_method_instance(invokeTypes)) + invokeTypes = NULL; + if ((invokesig == NULL && invokeTypes == NULL) || + (invokesig && invokeTypes && jl_types_equal(invokesig, invokeTypes))) { + found = 1; + break; + } } } + if (!found) + push_edge(callee->backedges, invokesig, caller); } - if (!found) - push_edge(callee->backedges, invokesig, caller); JL_UNLOCK(&callee->def.method->writelock); } @@ -1858,37 +1863,41 @@ JL_DLLEXPORT void jl_method_instance_add_backedge(jl_method_instance_t *callee, JL_DLLEXPORT void jl_method_table_add_backedge(jl_methtable_t *mt, jl_value_t *typ, jl_code_instance_t *caller) { assert(jl_is_code_instance(caller)); + if (!jl_atomic_load_relaxed(&allow_new_worlds)) + return; JL_LOCK(&mt->writelock); - if (!mt->backedges) { - // lazy-init the backedges array - mt->backedges = jl_alloc_vec_any(2); - jl_gc_wb(mt, mt->backedges); - jl_array_ptr_set(mt->backedges, 0, typ); - jl_array_ptr_set(mt->backedges, 1, caller); - } - else { - // check if the edge is already present and avoid adding a duplicate - size_t i, l = jl_array_nrows(mt->backedges); - for (i = 1; i < l; i += 2) { - if (jl_array_ptr_ref(mt->backedges, i) == (jl_value_t*)caller) { - if (jl_types_equal(jl_array_ptr_ref(mt->backedges, i - 1), typ)) { - JL_UNLOCK(&mt->writelock); - return; + if (jl_atomic_load_relaxed(&allow_new_worlds)) { + if (!mt->backedges) { + // lazy-init the backedges array + mt->backedges = jl_alloc_vec_any(2); + jl_gc_wb(mt, mt->backedges); + jl_array_ptr_set(mt->backedges, 0, typ); + jl_array_ptr_set(mt->backedges, 1, caller); + } + else { + // check if the edge is already present and avoid adding a duplicate + size_t i, l = jl_array_nrows(mt->backedges); + for (i = 1; i < l; i += 2) { + if (jl_array_ptr_ref(mt->backedges, i) == (jl_value_t*)caller) { + if (jl_types_equal(jl_array_ptr_ref(mt->backedges, i - 1), typ)) { + JL_UNLOCK(&mt->writelock); + return; + } } } - } - // reuse an already cached instance of this type, if possible - // TODO: use jl_cache_type_(tt) like cache_method does, instead of this linear scan? - for (i = 1; i < l; i += 2) { - if (jl_array_ptr_ref(mt->backedges, i) != (jl_value_t*)caller) { - if (jl_types_equal(jl_array_ptr_ref(mt->backedges, i - 1), typ)) { - typ = jl_array_ptr_ref(mt->backedges, i - 1); - break; + // reuse an already cached instance of this type, if possible + // TODO: use jl_cache_type_(tt) like cache_method does, instead of this linear scan? + for (i = 1; i < l; i += 2) { + if (jl_array_ptr_ref(mt->backedges, i) != (jl_value_t*)caller) { + if (jl_types_equal(jl_array_ptr_ref(mt->backedges, i - 1), typ)) { + typ = jl_array_ptr_ref(mt->backedges, i - 1); + break; + } } } + jl_array_ptr_1d_push(mt->backedges, typ); + jl_array_ptr_1d_push(mt->backedges, (jl_value_t*)caller); } - jl_array_ptr_1d_push(mt->backedges, typ); - jl_array_ptr_1d_push(mt->backedges, (jl_value_t*)caller); } JL_UNLOCK(&mt->writelock); } @@ -2024,10 +2033,55 @@ static void jl_method_table_invalidate(jl_methtable_t *mt, jl_method_t *replaced } } +static int erase_method_backedges(jl_typemap_entry_t *def, void *closure) +{ + jl_method_t *method = def->func.method; + JL_LOCK(&method->writelock); + jl_value_t *specializations = jl_atomic_load_relaxed(&method->specializations); + if (jl_is_svec(specializations)) { + size_t i, l = jl_svec_len(specializations); + for (i = 0; i < l; i++) { + jl_method_instance_t *mi = (jl_method_instance_t*)jl_svecref(specializations, i); + if ((jl_value_t*)mi != jl_nothing) { + mi->backedges = NULL; + } + } + } + else { + jl_method_instance_t *mi = (jl_method_instance_t*)specializations; + mi->backedges = NULL; + } + JL_UNLOCK(&method->writelock); + return 1; +} + +static int erase_all_backedges(jl_methtable_t *mt, void *env) +{ + // removes all method caches + // this might not be entirely safe (GC or MT), thus we only do it very early in bootstrapping + JL_LOCK(&mt->writelock); + mt->backedges = NULL; + JL_UNLOCK(&mt->writelock); + jl_typemap_visitor(jl_atomic_load_relaxed(&mt->defs), erase_method_backedges, env); + return 1; +} + +JL_DLLEXPORT void jl_disable_new_worlds(void) +{ + if (jl_generating_output()) + jl_error("Disabling Method changes is not possible when generating output."); + JL_LOCK(&world_counter_lock); + jl_atomic_store_relaxed(&allow_new_worlds, 0); + JL_UNLOCK(&world_counter_lock); + jl_foreach_reachable_mtable(erase_all_backedges, (void*)NULL); +} + JL_DLLEXPORT void jl_method_table_disable(jl_methtable_t *mt, jl_method_t *method) { jl_typemap_entry_t *methodentry = do_typemap_search(mt, method); JL_LOCK(&world_counter_lock); + if (!jl_atomic_load_relaxed(&allow_new_worlds)) + jl_error("Method changes have been disabled via a call to disable_new_worlds."); JL_LOCK(&mt->writelock); // Narrow the world age on the method to make it uncallable size_t world = jl_atomic_load_relaxed(&jl_world_counter); @@ -2341,6 +2395,8 @@ JL_DLLEXPORT void jl_method_table_insert(jl_methtable_t *mt, jl_method_t *method jl_typemap_entry_t *newentry = jl_method_table_add(mt, method, simpletype); JL_GC_PUSH1(&newentry); JL_LOCK(&world_counter_lock); + if (!jl_atomic_load_relaxed(&allow_new_worlds)) + jl_error("Method changes have been disabled via a call to disable_new_worlds."); size_t world = jl_atomic_load_relaxed(&jl_world_counter) + 1; jl_atomic_store_relaxed(&method->primary_world, world); jl_atomic_store_relaxed(&method->deleted_world, ~(size_t)0); diff --git a/src/staticdata.c b/src/staticdata.c index c2e8d7e3956ea..f5fe088ce1c98 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -89,6 +89,7 @@ External links: #include "julia_assert.h" static const size_t WORLD_AGE_REVALIDATION_SENTINEL = 0x1; +size_t jl_require_world = ~(size_t)0; #include "staticdata_utils.c" #include "precompile_utils.c" @@ -2678,7 +2679,6 @@ jl_genericmemory_t *jl_global_roots_list; jl_genericmemory_t *jl_global_roots_keyset; jl_mutex_t global_roots_lock; extern jl_mutex_t world_counter_lock; -extern size_t jl_require_world; jl_mutex_t precompile_field_replace_lock; jl_svec_t *precompile_field_replace JL_GLOBALLY_ROOTED; @@ -4044,16 +4044,30 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i // Add roots to methods jl_copy_roots(method_roots_list, jl_worklist_key((jl_array_t*)restored)); // Insert method extensions and handle edges + int new_methods = jl_array_nrows(extext_methods) > 0; + if (!new_methods) { + size_t i, l = jl_array_nrows(internal_methods); + for (i = 0; i < l; i++) { + jl_value_t *obj = jl_array_ptr_ref(internal_methods, i); + if (jl_is_method(obj)) { + new_methods = 1; + break; + } + } + } JL_LOCK(&world_counter_lock); - // allocate a world for the new methods, and insert them there, invalidating content as needed - size_t world = jl_atomic_load_relaxed(&jl_world_counter) + 1; - jl_activate_methods(extext_methods, internal_methods, world); - // TODO: inject new_ext_cis into caches here, so the system can see them immediately as potential candidates (before validation) - // allow users to start running in this updated world - jl_atomic_store_release(&jl_world_counter, world); - // now permit more methods to be added again + // allocate a world for the new methods, and insert them there, invalidating content as needed + size_t world = jl_atomic_load_relaxed(&jl_world_counter); + if (new_methods) + world += 1; + jl_activate_methods(extext_methods, internal_methods, world, pkgname); + // TODO: inject new_ext_cis into caches here, so the system can see them immediately as potential candidates (before validation) + // allow users to start running in this updated world + if (new_methods) + jl_atomic_store_release(&jl_world_counter, world); + // now permit more methods to be added again JL_UNLOCK(&world_counter_lock); - // but one of those immediate users is going to be our cache insertions + // but one of those immediate users is going to be our cache insertions jl_insert_backedges((jl_array_t*)edges, (jl_array_t*)new_ext_cis); // restore existing caches (needs to be last) // reinit ccallables jl_reinit_ccallable(&ccallable_list, base, pkgimage_handle); diff --git a/src/staticdata_utils.c b/src/staticdata_utils.c index 32e59d7d7c641..ea0b7216155bd 100644 --- a/src/staticdata_utils.c +++ b/src/staticdata_utils.c @@ -704,10 +704,12 @@ static void jl_add_methods(jl_array_t *external) } } -static void jl_activate_methods(jl_array_t *external, jl_array_t *internal, size_t world) +extern _Atomic(int) allow_new_worlds; +static void jl_activate_methods(jl_array_t *external, jl_array_t *internal, size_t world, const char *pkgname) { size_t i, l = jl_array_nrows(internal); for (i = 0; i < l; i++) { + // allow_new_worlds doesn't matter here, since we aren't actually changing anything external jl_value_t *obj = jl_array_ptr_ref(internal, i); if (jl_typetagis(obj, jl_typemap_entry_type)) { jl_typemap_entry_t *entry = (jl_typemap_entry_t*)obj; @@ -735,11 +737,17 @@ static void jl_activate_methods(jl_array_t *external, jl_array_t *internal, size } } l = jl_array_nrows(external); - for (i = 0; i < l; i++) { - jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_array_ptr_ref(external, i); - jl_methtable_t *mt = jl_method_get_table(entry->func.method); - assert((jl_value_t*)mt != jl_nothing); - jl_method_table_activate(mt, entry); + if (l) { + if (!jl_atomic_load_relaxed(&allow_new_worlds)) { + jl_printf(JL_STDERR, "WARNING: Method changes for %s have been disabled via a call to disable_new_worlds.\n", pkgname); + return; + } + for (i = 0; i < l; i++) { + jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_array_ptr_ref(external, i); + jl_methtable_t *mt = jl_method_get_table(entry->func.method); + assert((jl_value_t*)mt != jl_nothing); + jl_method_table_activate(mt, entry); + } } } @@ -864,71 +872,80 @@ static int jl_verify_method(jl_code_instance_t *codeinst, size_t *minworld, size size_t depth = stack->len; *bp = (char*)HT_NOTFOUND + depth; JL_TIMING(VERIFY_IMAGE, VERIFY_Methods); - jl_value_t *loctag = NULL; - jl_value_t *sig = NULL; - jl_value_t *matches = NULL; - JL_GC_PUSH3(&loctag, &matches, &sig); jl_svec_t *callees = jl_atomic_load_relaxed(&codeinst->edges); assert(jl_is_svec((jl_value_t*)callees)); // verify current edges - for (size_t j = 0; j < jl_svec_len(callees); ) { - jl_value_t *edge = jl_svecref(callees, j); - size_t min_valid2; - size_t max_valid2; - assert(!jl_is_method(edge)); // `Method`-edge isn't allowed for the optimized one-edge format - if (jl_is_code_instance(edge)) - edge = (jl_value_t*)((jl_code_instance_t*)edge)->def; - if (jl_is_method_instance(edge)) { - jl_method_instance_t *mi = (jl_method_instance_t*)edge; - sig = jl_type_intersection(mi->def.method->sig, (jl_value_t*)mi->specTypes); // TODO: ?? - verify_call(sig, callees, j, 1, world, &min_valid2, &max_valid2, &matches); - sig = NULL; - j += 1; - } - else if (jl_is_long(edge)) { - jl_value_t *sig = jl_svecref(callees, j + 1); - size_t nedges = jl_unbox_long(edge); - verify_call(sig, callees, j + 2, nedges, world, &min_valid2, &max_valid2, &matches); - j += 2 + nedges; - edge = sig; - } - else { - jl_method_instance_t *callee = (jl_method_instance_t*)jl_svecref(callees, j + 1); - jl_method_t *meth; - if (jl_is_mtable(callee)) { - // skip the legacy edge (missing backedge) - j += 2; - continue; + if (callees == jl_emptysvec) { + // quick return: no edges to verify (though we probably shouldn't have gotten here from WORLD_AGE_REVALIDATION_SENTINEL) + } + else if (*maxworld == jl_require_world) { + // if no new worlds were allocated since serializing the base module, then no new validation is worth doing right now either + *minworld = *maxworld; + } + else { + jl_value_t *loctag = NULL; + jl_value_t *sig = NULL; + jl_value_t *matches = NULL; + JL_GC_PUSH3(&loctag, &matches, &sig); + for (size_t j = 0; j < jl_svec_len(callees); ) { + jl_value_t *edge = jl_svecref(callees, j); + size_t min_valid2; + size_t max_valid2; + assert(!jl_is_method(edge)); // `Method`-edge isn't allowed for the optimized one-edge format + if (jl_is_code_instance(edge)) + edge = (jl_value_t*)((jl_code_instance_t*)edge)->def; + if (jl_is_method_instance(edge)) { + jl_method_instance_t *mi = (jl_method_instance_t*)edge; + sig = jl_type_intersection(mi->def.method->sig, (jl_value_t*)mi->specTypes); // TODO: ?? + verify_call(sig, callees, j, 1, world, &min_valid2, &max_valid2, &matches); + sig = NULL; + j += 1; } - if (jl_is_code_instance(callee)) - callee = ((jl_code_instance_t*)callee)->def; - if (jl_is_method_instance(callee)) { - meth = callee->def.method; + else if (jl_is_long(edge)) { + jl_value_t *sig = jl_svecref(callees, j + 1); + size_t nedges = jl_unbox_long(edge); + verify_call(sig, callees, j + 2, nedges, world, &min_valid2, &max_valid2, &matches); + j += 2 + nedges; + edge = sig; } else { - assert(jl_is_method(callee)); - meth = (jl_method_t*)callee; + jl_method_instance_t *callee = (jl_method_instance_t*)jl_svecref(callees, j + 1); + jl_method_t *meth; + if (jl_is_mtable(callee)) { + // skip the legacy edge (missing backedge) + j += 2; + continue; + } + if (jl_is_code_instance(callee)) + callee = ((jl_code_instance_t*)callee)->def; + if (jl_is_method_instance(callee)) { + meth = callee->def.method; + } + else { + assert(jl_is_method(callee)); + meth = (jl_method_t*)callee; + } + verify_invokesig(edge, meth, world, &min_valid2, &max_valid2); + j += 2; } - verify_invokesig(edge, meth, world, &min_valid2, &max_valid2); - j += 2; - } - if (*minworld < min_valid2) - *minworld = min_valid2; - if (*maxworld > max_valid2) - *maxworld = max_valid2; - if (max_valid2 != ~(size_t)0 && _jl_debug_method_invalidation) { - jl_array_ptr_1d_push(_jl_debug_method_invalidation, edge); - loctag = jl_cstr_to_string("insert_backedges_callee"); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)codeinst); - jl_array_ptr_1d_push(_jl_debug_method_invalidation, matches); + if (*minworld < min_valid2) + *minworld = min_valid2; + if (*maxworld > max_valid2) + *maxworld = max_valid2; + if (max_valid2 != ~(size_t)0 && _jl_debug_method_invalidation) { + jl_array_ptr_1d_push(_jl_debug_method_invalidation, edge); + loctag = jl_cstr_to_string("insert_backedges_callee"); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, loctag); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)codeinst); + jl_array_ptr_1d_push(_jl_debug_method_invalidation, matches); + } + //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)edge); + //ios_puts(max_valid2 == ~(size_t)0 ? "valid\n" : "INVALID\n", ios_stderr); + if (max_valid2 == 0 && !_jl_debug_method_invalidation) + break; } - //jl_static_show((JL_STREAM*)ios_stderr, (jl_value_t*)edge); - //ios_puts(max_valid2 == ~(size_t)0 ? "valid\n" : "INVALID\n", ios_stderr); - if (max_valid2 == 0 && !_jl_debug_method_invalidation) - break; + JL_GC_POP(); } - JL_GC_POP(); // verify recursive edges (if valid, or debugging) size_t cycle = depth; jl_code_instance_t *cause = codeinst; @@ -979,6 +996,7 @@ static int jl_verify_method(jl_code_instance_t *codeinst, size_t *minworld, size assert(*bp == (char*)HT_NOTFOUND + stack->len + 1); *bp = HT_NOTFOUND; if (_jl_debug_method_invalidation && *maxworld < current_world) { + jl_value_t *loctag; jl_array_ptr_1d_push(_jl_debug_method_invalidation, (jl_value_t*)child); loctag = jl_cstr_to_string("verify_methods"); JL_GC_PUSH1(&loctag); diff --git a/src/toplevel.c b/src/toplevel.c index 56a5f21f43661..e8286a584b119 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -496,7 +496,7 @@ static void body_attributes(jl_array_t *body, int *has_ccall, int *has_defs, int *forced_compile = jl_has_meta(body, jl_force_compile_sym); } -size_t jl_require_world = ~(size_t)0; +extern size_t jl_require_world; static jl_module_t *call_require(jl_module_t *mod, jl_sym_t *var) JL_GLOBALLY_ROOTED { JL_TIMING(LOAD_IMAGE, LOAD_Require); diff --git a/test/misc.jl b/test/misc.jl index 7f9992e22a3d7..5bbc2c3c65fa2 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -1211,10 +1211,7 @@ include("testenv.jl") let flags = Cmd(filter(a->!occursin("depwarn", a), collect(test_exeflags))) local cmd = `$test_exename $flags --depwarn=yes deprecation_exec.jl` - - if !success(pipeline(cmd; stdout=stdout, stderr=stderr)) - error("Deprecation test failed, cmd : $cmd") - end + run(cmd, devnull) end # PR #23664, make sure names don't get added to the default `Main` workspace @@ -1489,7 +1486,7 @@ end # Test that read fault on a prot-none region does not incorrectly give # ReadOnlyMemoryError, but rather crashes the program const MAP_ANONYMOUS_PRIVATE = Sys.isbsd() ? 0x1002 : 0x22 -let script = :( +let script = """ let ptr = Ptr{Cint}(ccall(:jl_mmap, Ptr{Cvoid}, (Ptr{Cvoid}, Csize_t, Cint, Cint, Cint, Int), C_NULL, 16*1024, 0, $MAP_ANONYMOUS_PRIVATE, -1, 0)) @@ -1499,19 +1496,24 @@ let script = :( println(e) end end - ) + """ cmd = if Sys.isunix() # Set the maximum core dump size to 0 to keep this expected crash from # producing a (and potentially overwriting an existing) core dump file - `sh -c "ulimit -c 0; $(Base.shell_escape(Base.julia_cmd())) -e '$script'"` + `sh -c "ulimit -c 0; $(Base.shell_escape(Base.julia_cmd())) -e $(Base.shell_escape(script))"` else - `$(Base.julia_cmd()) -e '$script'` + `$(Base.julia_cmd()) -e $script` + end + p = run(ignorestatus(cmd), devnull, stdout, devnull) + if p.termsignal == 0 + Sys.isunix() ? @test(p.exitcode ∈ (128+7, 128+10, 128+11)) : @test(p.exitcode != 0) # expect SIGBUS (7 on BSDs or 10 on Linux) or SIGSEGV (11) + else + @test(p.termsignal ∈ (7, 10, 11)) end - @test !success(cmd) end # issue #41656 -@test success(`$(Base.julia_cmd()) -e 'isempty(x) = true'`) +run(`$(Base.julia_cmd()) -e 'isempty(x) = true'`) @testset "Base/timing.jl" begin @test Base.jit_total_bytes() >= 0 @@ -1596,3 +1598,17 @@ end end @test !occursin("loop not unrolled", out_err) end + +let errs = IOBuffer() + run(`$(Base.julia_cmd()) -e ' + using Test + @test isdefined(DataType.name.mt, :backedges) + Base.Experimental.disable_new_worlds() + @test_throws "disable_new_worlds" @eval f() = 1 + @test !isdefined(DataType.name.mt, :backedges) + @test_throws "disable_new_worlds" Base.delete_method(which(+, (Int, Int))) + @test 1+1 == 2 + using Dates + '`, devnull, stdout, errs) + @test occursin("disable_new_worlds", String(take!(errs))) +end