From 7f18f767ab5711b03eee98a03594e909e5510365 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 17 Nov 2023 01:51:06 +0900 Subject: [PATCH 01/38] inference: enhance memoryop tfunc robustness (#52185) Make them able to handle potential `Vararg` argument. Also adds plenty of tests. --- base/compiler/tfuncs.jl | 11 ++++++----- test/compiler/inference.jl | 31 ++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 32b523bf32671d..a8610eb8fd6535 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1992,7 +1992,7 @@ end add_tfunc(memoryref_isassigned, 3, 3, memoryref_isassigned_tfunc, 20) @nospecs function memoryref_tfunc(๐•ƒ::AbstractLattice, mem) - a = widenconst(mem) + a = widenconst(unwrapva(mem)) if !has_free_typevars(a) unw = unwrap_unionall(a) if isa(unw, DataType) && unw.name === GenericMemory.body.body.body.name @@ -2006,7 +2006,10 @@ add_tfunc(memoryref_isassigned, 3, 3, memoryref_isassigned_tfunc, 20) return GenericMemoryRef end @nospecs function memoryref_tfunc(๐•ƒ::AbstractLattice, ref, idx) - memoryref_tfunc(๐•ƒ, ref, idx, Const(true)) + if isvarargtype(idx) + idx = unwrapva(idx) + end + return memoryref_tfunc(๐•ƒ, ref, idx, Const(true)) end @nospecs function memoryref_tfunc(๐•ƒ::AbstractLattice, ref, idx, boundscheck) memoryref_builtin_common_errorcheck(ref, Const(:not_atomic), boundscheck) || return Bottom @@ -2021,12 +2024,10 @@ add_tfunc(memoryref, 1, 3, memoryref_tfunc, 1) end add_tfunc(memoryrefoffset, 1, 1, memoryrefoffset_tfunc, 5) - - @nospecs function memoryref_builtin_common_errorcheck(mem, order, boundscheck) hasintersect(widenconst(mem), GenericMemoryRef) || return false hasintersect(widenconst(order), Symbol) || return false - hasintersect(widenconst(boundscheck), Bool) || return false + hasintersect(widenconst(unwrapva(boundscheck)), Bool) || return false return true end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 6872ad0bcc0bc3..ace17baeb5859b 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -1580,15 +1580,21 @@ end f_typeof_tfunc(x) = typeof(x) @test Base.return_types(f_typeof_tfunc, (Union{<:T, Int} where T<:Complex,)) == Any[Union{Type{Int}, Type{Complex{T}} where T<:Real}] -# memoryref_tfunc, memoryrefget_tfunc, memoryrefset!_tfunc +# memoryref_tfunc, memoryrefget_tfunc, memoryrefset!_tfunc, memoryref_isassigned, memoryrefoffset_tfunc let memoryref_tfunc(@nospecialize xs...) = Core.Compiler.memoryref_tfunc(Core.Compiler.fallback_lattice, xs...) memoryrefget_tfunc(@nospecialize xs...) = Core.Compiler.memoryrefget_tfunc(Core.Compiler.fallback_lattice, xs...) + memoryref_isassigned_tfunc(@nospecialize xs...) = Core.Compiler.memoryref_isassigned_tfunc(Core.Compiler.fallback_lattice, xs...) memoryrefset!_tfunc(@nospecialize xs...) = Core.Compiler.memoryrefset!_tfunc(Core.Compiler.fallback_lattice, xs...) + memoryrefoffset_tfunc(@nospecialize xs...) = Core.Compiler.memoryrefoffset_tfunc(Core.Compiler.fallback_lattice, xs...) + interp = Core.Compiler.NativeInterpreter() + builtin_tfunction(@nospecialize xs...) = Core.Compiler.builtin_tfunction(interp, xs..., nothing) @test memoryref_tfunc(Memory{Int}) == MemoryRef{Int} @test memoryref_tfunc(Memory{Integer}) == MemoryRef{Integer} @test memoryref_tfunc(MemoryRef{Int}, Int) == MemoryRef{Int} + @test memoryref_tfunc(MemoryRef{Int}, Vararg{Int}) == MemoryRef{Int} @test memoryref_tfunc(MemoryRef{Int}, Int, Symbol) == Union{} @test memoryref_tfunc(MemoryRef{Int}, Int, Bool) == MemoryRef{Int} + @test memoryref_tfunc(MemoryRef{Int}, Int, Vararg{Bool}) == MemoryRef{Int} @test memoryref_tfunc(Memory{Int}, Int) == Union{} @test memoryref_tfunc(Any, Any, Any) == Any # also probably could be GenericMemoryRef @test memoryref_tfunc(Any, Any) == Any # also probably could be GenericMemoryRef @@ -1603,6 +1609,20 @@ let memoryref_tfunc(@nospecialize xs...) = Core.Compiler.memoryref_tfunc(Core.Co @test memoryrefget_tfunc(MemoryRef{Int}, String, Bool) === Union{} @test memoryrefget_tfunc(MemoryRef{Int}, Symbol, String) === Union{} @test memoryrefget_tfunc(Any, Any, Any) === Any + @test builtin_tfunction(Core.memoryrefget, Any[MemoryRef{Int}, Vararg{Any}]) == Int + @test builtin_tfunction(Core.memoryrefget, Any[MemoryRef{Int}, Symbol, Bool, Vararg{Bool}]) == Int + @test memoryref_isassigned_tfunc(MemoryRef{Any}, Symbol, Bool) === Bool + @test memoryref_isassigned_tfunc(MemoryRef{Any}, Any, Any) === Bool + @test memoryref_isassigned_tfunc(MemoryRef{<:Integer}, Symbol, Bool) === Bool + @test memoryref_isassigned_tfunc(GenericMemoryRef, Symbol, Bool) === Bool + @test memoryref_isassigned_tfunc(GenericMemoryRef{:not_atomic}, Symbol, Bool) === Bool + @test memoryref_isassigned_tfunc(Vector{Int}, Symbol, Bool) === Union{} + @test memoryref_isassigned_tfunc(String, Symbol, Bool) === Union{} + @test memoryref_isassigned_tfunc(MemoryRef{Int}, String, Bool) === Union{} + @test memoryref_isassigned_tfunc(MemoryRef{Int}, Symbol, String) === Union{} + @test memoryref_isassigned_tfunc(Any, Any, Any) === Bool + @test builtin_tfunction(Core.memoryref_isassigned, Any[MemoryRef{Int}, Vararg{Any}]) == Bool + @test builtin_tfunction(Core.memoryref_isassigned, Any[MemoryRef{Int}, Symbol, Bool, Vararg{Bool}]) == Bool @test memoryrefset!_tfunc(MemoryRef{Int}, Int, Symbol, Bool) === MemoryRef{Int} let ua = MemoryRef{<:Integer} @test memoryrefset!_tfunc(ua, Int, Symbol, Bool) === ua @@ -1617,6 +1637,15 @@ let memoryref_tfunc(@nospecialize xs...) = Core.Compiler.memoryref_tfunc(Core.Co @test memoryrefset!_tfunc(GenericMemoryRef{:not_atomic}, Any, Any, Any) === GenericMemoryRef{:not_atomic} @test memoryrefset!_tfunc(GenericMemoryRef, Any, Any, Any) === GenericMemoryRef @test memoryrefset!_tfunc(Any, Any, Any, Any) === Any # also probably could be GenericMemoryRef + @test builtin_tfunction(Core.memoryrefset!, Any[MemoryRef{Int}, Vararg{Any}]) == MemoryRef{Int} + @test builtin_tfunction(Core.memoryrefset!, Any[MemoryRef{Int}, Vararg{Symbol}]) == Union{} + @test builtin_tfunction(Core.memoryrefset!, Any[MemoryRef{Int}, Any, Symbol, Vararg{Bool}]) == MemoryRef{Int} + @test builtin_tfunction(Core.memoryrefset!, Any[MemoryRef{Int}, Any, Symbol, Bool, Vararg{Any}]) == MemoryRef{Int} + @test memoryrefoffset_tfunc(MemoryRef) == memoryrefoffset_tfunc(GenericMemoryRef) == Int + @test memoryrefoffset_tfunc(Memory) == memoryrefoffset_tfunc(GenericMemory) == Union{} + @test builtin_tfunction(Core.memoryrefoffset, Any[Vararg{MemoryRef}]) == Int + @test builtin_tfunction(Core.memoryrefoffset, Any[Vararg{Any}]) == Int + @test builtin_tfunction(Core.memoryrefoffset, Any[Vararg{Memory}]) == Union{} end let tuple_tfunc(@nospecialize xs...) = From 40e56a50cca62b55a70bbd62bdb935d5a00dd30c Mon Sep 17 00:00:00 2001 From: Diogo Netto <61364108+d-netto@users.noreply.github.com> Date: Thu, 16 Nov 2023 16:15:03 -0300 Subject: [PATCH 02/38] add some instrumentation to measure page utilization per size class (#52164) One of the limitations is that it's only accurate right after the GC. Still might be helpful for observability purposes. --- src/gc.c | 34 ++++++++++++++++++++++++++++++++++ src/gc.h | 8 ++++++++ 2 files changed, 42 insertions(+) diff --git a/src/gc.c b/src/gc.c index 38a4a5f60bcd75..6860871587952b 100644 --- a/src/gc.c +++ b/src/gc.c @@ -1413,6 +1413,38 @@ int jl_gc_classify_pools(size_t sz, int *osize) // sweep phase +gc_fragmentation_stat_t gc_page_fragmentation_stats[JL_GC_N_POOLS]; + +extern gc_fragmentation_stat_t gc_page_fragmentation_stats[JL_GC_N_POOLS]; + +STATIC_INLINE void gc_update_page_fragmentation_data(jl_gc_pagemeta_t *pg) JL_NOTSAFEPOINT +{ +#ifdef GC_MEASURE_PAGE_FRAGMENTATION + gc_fragmentation_stat_t *stats = &gc_page_fragmentation_stats[pg->pool_n]; + jl_atomic_fetch_add(&stats->n_freed_objs, pg->nfree); + jl_atomic_fetch_add(&stats->n_pages_allocd, 1); +#endif +} + +STATIC_INLINE void gc_dump_page_utilization_data(void) JL_NOTSAFEPOINT +{ +#ifdef GC_MEASURE_PAGE_FRAGMENTATION + for (int i = 0; i < JL_GC_N_POOLS; i++) { + gc_fragmentation_stat_t *stats = &gc_page_fragmentation_stats[i]; + double utilization = 1.0; + size_t n_freed_objs = jl_atomic_load_relaxed(&stats->n_freed_objs); + size_t n_pages_allocd = jl_atomic_load_relaxed(&stats->n_pages_allocd); + if (n_pages_allocd != 0) { + utilization -= ((double)n_freed_objs * (double)jl_gc_sizeclasses[i]) / (double)n_pages_allocd / (double)GC_PAGE_SZ; + } + jl_safe_printf("Size class %d: %.2f%% utilization\n", jl_gc_sizeclasses[i], utilization * 100.0); + jl_atomic_store_relaxed(&stats->n_freed_objs, 0); + jl_atomic_store_relaxed(&stats->n_pages_allocd, 0); + } + jl_safe_printf("-----------------------------------------\n"); +#endif +} + int64_t buffered_pages = 0; // Returns pointer to terminal pointer of list rooted at *pfl. @@ -1522,6 +1554,7 @@ static void gc_sweep_page(jl_gc_pool_t *p, jl_gc_page_stack_t *allocd, jl_gc_pag push_lf_back(&global_page_pool_lazily_freed, pg); } } + gc_update_page_fragmentation_data(pg); gc_time_count_page(freedall, pg_skpd); jl_ptls_t ptls = gc_all_tls_states[pg->thread_n]; jl_atomic_fetch_add(&ptls->gc_num.pool_live_bytes, GC_PAGE_SZ - GC_PAGE_OFFSET - nfree * osize); @@ -1735,6 +1768,7 @@ static void gc_sweep_pool(void) #else gc_free_pages(); #endif + gc_dump_page_utilization_data(); gc_time_pool_end(current_sweep_full); } diff --git a/src/gc.h b/src/gc.h index 4830d2d123c5d9..227dc5b6b7a53a 100644 --- a/src/gc.h +++ b/src/gc.h @@ -234,6 +234,14 @@ STATIC_INLINE jl_gc_pagemeta_t *pop_lf_back(jl_gc_page_stack_t *pool) JL_NOTSAFE } } +// data structures for tracking fragmentation in the pool allocator +// #define GC_MEASURE_PAGE_FRAGMENTATION + +typedef struct { + _Atomic(size_t) n_freed_objs; + _Atomic(size_t) n_pages_allocd; +} gc_fragmentation_stat_t; + #ifdef _P64 #define REGION0_PG_COUNT (1 << 16) #define REGION1_PG_COUNT (1 << 16) From b1c8e120d1c73655578a557b4fbe3d6e142dadcc Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Thu, 16 Nov 2023 14:51:25 -0500 Subject: [PATCH 03/38] Use pkgimages for coverage & malloc tracking by ignoring native code in tracked packages (#52123) --- NEWS.md | 5 ++ base/loading.jl | 52 +++++++++++++++++++-- base/util.jl | 3 -- pkgimage.mk | 2 - src/codegen.cpp | 12 +++-- src/init.c | 5 -- src/jloptions.c | 9 ---- src/staticdata.c | 6 ++- stdlib/Test/docs/src/index.md | 12 +++++ test/cmdlineargs.jl | 13 +++++- test/loading.jl | 44 +++++++++++++++++ test/precompile.jl | 2 +- test/project/deps/CovTest.jl/Project.toml | 3 ++ test/project/deps/CovTest.jl/src/CovTest.jl | 26 +++++++++++ 14 files changed, 164 insertions(+), 30 deletions(-) create mode 100644 test/project/deps/CovTest.jl/Project.toml create mode 100644 test/project/deps/CovTest.jl/src/CovTest.jl diff --git a/NEWS.md b/NEWS.md index 18e7f5d87316ce..30dd2e6e037832 100644 --- a/NEWS.md +++ b/NEWS.md @@ -23,6 +23,11 @@ Language changes allows users to safely tear down background state (such as closing Timers and sending disconnect notifications to heartbeat tasks) and cleanup other resources when the program wants to begin exiting. +* Code coverage and malloc tracking is no longer generated during the package precompilation stage. + Further, during these modes pkgimage caches are now used for packages that are not being tracked. + Meaning that coverage testing (the default for `julia-actions/julia-runtest`) will by default use + pkgimage caches for all other packages than the package being tested, likely meaning faster test + execution. ([#52123]) Compiler/Runtime improvements ----------------------------- diff --git a/base/loading.jl b/base/loading.jl index 125e9b1302fb58..021c32b7c26541 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1044,7 +1044,21 @@ const TIMING_IMPORTS = Threads.Atomic{Int}(0) # these return either the array of modules loaded from the path / content given # or an Exception that describes why it couldn't be loaded # and it reconnects the Base.Docs.META -function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}, depmods::Vector{Any}) +function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}, depmods::Vector{Any}, ignore_native::Union{Nothing,Bool}=nothing) + if isnothing(ignore_native) + if JLOptions().code_coverage == 0 && JLOptions().malloc_log == 0 + ignore_native = false + else + io = open(path, "r") + try + iszero(isvalid_cache_header(io)) && return ArgumentError("Invalid header in cache file $path.") + _, (includes, _, _), _, _, _, _, _, _ = parse_cache_header(io, path) + ignore_native = pkg_tracked(includes) + finally + close(io) + end + end + end assert_havelock(require_lock) timing_imports = TIMING_IMPORTS[] > 0 try @@ -1056,7 +1070,7 @@ function _include_from_serialized(pkg::PkgId, path::String, ocachepath::Union{No if ocachepath !== nothing @debug "Loading object cache file $ocachepath for $pkg" - sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring), ocachepath, depmods, false, pkg.name) + sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring, Cint), ocachepath, depmods, false, pkg.name, ignore_native) else @debug "Loading cache file $path for $pkg" sv = ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint, Cstring), path, depmods, false, pkg.name) @@ -1495,15 +1509,45 @@ function _tryrequire_from_serialized(modkey::PkgId, path::String, ocachepath::Un return loaded end +# returns whether the package is tracked in coverage or malloc tracking based on +# JLOptions and includes +function pkg_tracked(includes) + if JLOptions().code_coverage == 0 && JLOptions().malloc_log == 0 + return false + elseif JLOptions().code_coverage == 1 || JLOptions().malloc_log == 1 # user + # Just say true. Pkgimages aren't in Base + return true + elseif JLOptions().code_coverage == 2 || JLOptions().malloc_log == 2 # all + return true + elseif JLOptions().code_coverage == 3 || JLOptions().malloc_log == 3 # tracked path + if JLOptions().tracked_path == C_NULL + return false + else + tracked_path = unsafe_string(JLOptions().tracked_path) + if isempty(tracked_path) + return false + else + return any(includes) do inc + startswith(inc.filename, tracked_path) + end + end + end + end +end + # loads a precompile cache file, ignoring stale_cachefile tests # load the best available (non-stale) version of all dependent modules first function _tryrequire_from_serialized(pkg::PkgId, path::String, ocachepath::Union{Nothing, String}) assert_havelock(require_lock) local depmodnames io = open(path, "r") + ignore_native = false try iszero(isvalid_cache_header(io)) && return ArgumentError("Invalid header in cache file $path.") - _, _, depmodnames, _, _, _, clone_targets, _ = parse_cache_header(io, path) + _, (includes, _, _), depmodnames, _, _, _, clone_targets, _ = parse_cache_header(io, path) + + ignore_native = pkg_tracked(includes) + pkgimage = !isempty(clone_targets) if pkgimage ocachepath !== nothing || return ArgumentError("Expected ocachepath to be provided") @@ -1529,7 +1573,7 @@ function _tryrequire_from_serialized(pkg::PkgId, path::String, ocachepath::Union depmods[i] = dep end # then load the file - return _include_from_serialized(pkg, path, ocachepath, depmods) + return _include_from_serialized(pkg, path, ocachepath, depmods, ignore_native) end # returns `nothing` if require found a precompile cache for this sourcepath, but couldn't load it diff --git a/base/util.jl b/base/util.jl index 6a9f219e403c02..a3771f4ae9dc49 100644 --- a/base/util.jl +++ b/base/util.jl @@ -242,9 +242,6 @@ function julia_cmd(julia=joinpath(Sys.BINDIR, julia_exename()); cpu_target::Unio end if opts.use_pkgimages == 0 push!(addflags, "--pkgimages=no") - else - # If pkgimage is set, malloc_log and code_coverage should not - @assert opts.malloc_log == 0 && opts.code_coverage == 0 end return `$julia -C$cpu_target -J$image_file $addflags` end diff --git a/pkgimage.mk b/pkgimage.mk index 0a4823b70e4a20..83c66bd94c7020 100644 --- a/pkgimage.mk +++ b/pkgimage.mk @@ -35,12 +35,10 @@ $$(BUILDDIR)/stdlib/$1.debug.image: export JULIA_CPU_TARGET=$(JULIA_CPU_TARGET) $$(BUILDDIR)/stdlib/$1.release.image: $$($1_SRCS) $$(addsuffix .release.image,$$(addprefix $$(BUILDDIR)/stdlib/,$2)) $(build_private_libdir)/sys.$(SHLIB_EXT) @$$(call PRINT_JULIA, $$(call spawn,$$(JULIA_EXECUTABLE)) --startup-file=no --check-bounds=yes -e 'Base.compilecache(Base.identify_package("$1"))') - @$$(call PRINT_JULIA, $$(call spawn,$$(JULIA_EXECUTABLE)) --startup-file=no --pkgimages=no -e 'Base.compilecache(Base.identify_package("$1"))') @$$(call PRINT_JULIA, $$(call spawn,$$(JULIA_EXECUTABLE)) --startup-file=no -e 'Base.compilecache(Base.identify_package("$1"))') touch $$@ $$(BUILDDIR)/stdlib/$1.debug.image: $$($1_SRCS) $$(addsuffix .debug.image,$$(addprefix $$(BUILDDIR)/stdlib/,$2)) $(build_private_libdir)/sys-debug.$(SHLIB_EXT) @$$(call PRINT_JULIA, $$(call spawn,$$(JULIA_EXECUTABLE)) --startup-file=no --check-bounds=yes -e 'Base.compilecache(Base.identify_package("$1"))') - @$$(call PRINT_JULIA, $$(call spawn,$$(JULIA_EXECUTABLE)) --startup-file=no --pkgimages=no -e 'Base.compilecache(Base.identify_package("$1"))') @$$(call PRINT_JULIA, $$(call spawn,$$(JULIA_EXECUTABLE)) --startup-file=no -e 'Base.compilecache(Base.identify_package("$1"))') touch $$@ else diff --git a/src/codegen.cpp b/src/codegen.cpp index b9c796f2b3c1a5..3209b076b57462 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -8450,15 +8450,19 @@ static jl_llvm_functions_t cursor = -1; }; + // If a pkgimage or sysimage is being generated, disable tracking. + // This means sysimage build or pkgimage precompilation workloads aren't tracked. auto do_coverage = [&] (bool in_user_code, bool is_tracked) { - return (coverage_mode == JL_LOG_ALL || + return (jl_generating_output() == 0 && + (coverage_mode == JL_LOG_ALL || (in_user_code && coverage_mode == JL_LOG_USER) || - (is_tracked && coverage_mode == JL_LOG_PATH)); + (is_tracked && coverage_mode == JL_LOG_PATH))); }; auto do_malloc_log = [&] (bool in_user_code, bool is_tracked) { - return (malloc_log_mode == JL_LOG_ALL || + return (jl_generating_output() == 0 && + (malloc_log_mode == JL_LOG_ALL || (in_user_code && malloc_log_mode == JL_LOG_USER) || - (is_tracked && malloc_log_mode == JL_LOG_PATH)); + (is_tracked && malloc_log_mode == JL_LOG_PATH))); }; SmallVector current_lineinfo, new_lineinfo; auto coverageVisitStmt = [&] (size_t dbg) { diff --git a/src/init.c b/src/init.c index 3e4bbf4e077bcf..925ef0018048fd 100644 --- a/src/init.c +++ b/src/init.c @@ -814,11 +814,6 @@ JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) #endif #endif - if ((jl_options.outputo || jl_options.outputbc || jl_options.outputasm) && - (jl_options.code_coverage || jl_options.malloc_log)) { - jl_error("cannot generate code-coverage or track allocation information while generating a .o, .bc, or .s output file"); - } - jl_init_rand(); jl_init_runtime_ccall(); jl_init_tasks(); diff --git a/src/jloptions.c b/src/jloptions.c index 13ee06b862c327..cc00e102c16b14 100644 --- a/src/jloptions.c +++ b/src/jloptions.c @@ -333,7 +333,6 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) const char **cmds = NULL; int codecov = JL_LOG_NONE; int malloclog = JL_LOG_NONE; - int pkgimage_explicit = 0; int argc = *argcp; char **argv = *argvp; char *endptr; @@ -469,7 +468,6 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) jl_errorf("julia: invalid argument to --compiled-modules={yes|no|existing} (%s)", optarg); break; case opt_pkgimages: - pkgimage_explicit = 1; if (!strcmp(optarg,"yes")) jl_options.use_pkgimages = JL_OPTIONS_USE_PKGIMAGES_YES; else if (!strcmp(optarg,"no")) @@ -860,13 +858,6 @@ JL_DLLEXPORT void jl_parse_opts(int *argcp, char ***argvp) "This is a bug, please report it.", c); } } - if (codecov || malloclog) { - if (pkgimage_explicit && jl_options.use_pkgimages) { - jl_errorf("julia: Can't use --pkgimages=yes together " - "with --track-allocation or --code-coverage."); - } - jl_options.use_pkgimages = 0; - } jl_options.code_coverage = codecov; jl_options.malloc_log = malloclog; int proc_args = *argcp < optind ? *argcp : optind; diff --git a/src/staticdata.c b/src/staticdata.c index bf9ad12cd28b69..f34f64b28e3218 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -3675,7 +3675,7 @@ JL_DLLEXPORT void jl_restore_system_image_data(const char *buf, size_t len) JL_SIGATOMIC_END(); } -JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, jl_array_t *depmods, int completeinfo, const char *pkgname) +JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, jl_array_t *depmods, int completeinfo, const char *pkgname, int ignore_native) { void *pkgimg_handle = jl_dlopen(fname, JL_RTLD_LAZY); if (!pkgimg_handle) { @@ -3696,6 +3696,10 @@ JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, j jl_image_t pkgimage = jl_init_processor_pkgimg(pkgimg_handle); + if (ignore_native){ + memset(&pkgimage.fptrs, 0, sizeof(pkgimage.fptrs)); + } + jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_handle, pkgimg_data, &pkgimage, *plen, depmods, completeinfo, pkgname, false); return mod; diff --git a/stdlib/Test/docs/src/index.md b/stdlib/Test/docs/src/index.md index 3d133419a17922..ddb5d580ef8322 100644 --- a/stdlib/Test/docs/src/index.md +++ b/stdlib/Test/docs/src/index.md @@ -491,3 +491,15 @@ Using `Test.jl`, more complicated tests can be added for packages but this shoul ```@meta DocTestSetup = nothing ``` + +### Code Coverage + +Code coverage tracking during tests can be enabled using the `pkg> test --coverage` flag (or at a lower level using the +[`--code-coverage`](@ref command-line-interface) julia arg). This is on by default in the +[julia-runtest](https://github.com/julia-actions/julia-runtest) GitHub action. + +To evaluate coverage either manually inspect the `.cov` files that are generated beside the source files locally, +or in CI use the [julia-processcoverage](https://github.com/julia-actions/julia-processcoverage) GitHub action. + +!!! compat "Julia 1.11" + Since Julia 1.11, coverage is not collected during the package precompilation phase. diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index 26cee030f91109..1c90352af29c91 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -62,8 +62,19 @@ end @testset "julia_cmd" begin julia_basic = Base.julia_cmd() + function get_julia_cmd(arg) + io = Base.BufferStream() + cmd = `$julia_basic $arg -e 'print(repr(Base.julia_cmd()))'` + try + run(pipeline(cmd, stdout=io, stderr=io)) + catch + @error "cmd failed" cmd read(io, String) + rethrow() + end + return read(io, String) + end + opts = Base.JLOptions() - get_julia_cmd(arg) = strip(read(`$julia_basic $arg -e 'print(repr(Base.julia_cmd()))'`, String), ['`']) for (arg, default) in ( ("-C$(unsafe_string(opts.cpu_target))", false), diff --git a/test/loading.jl b/test/loading.jl index efb83a6e00335d..d39bdceb53341e 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -1333,3 +1333,47 @@ end @test filesize(cache_path) != cache_size end end + +@testset "code coverage disabled during precompilation" begin + mktempdir() do depot + cov_test_dir = joinpath(@__DIR__, "project", "deps", "CovTest.jl") + cov_cache_dir = joinpath(depot, "compiled", "v$(VERSION.major).$(VERSION.minor)", "CovTest") + function rm_cov_files() + for cov_file in filter(endswith(".cov"), readdir(joinpath(cov_test_dir, "src"), join=true)) + rm(cov_file) + end + @test !cov_exists() + end + cov_exists() = !isempty(filter(endswith(".cov"), readdir(joinpath(cov_test_dir, "src")))) + + rm_cov_files() # clear out any coverage files first + @test !cov_exists() + + cd(cov_test_dir) do + # In our depot, precompile CovTest.jl with coverage on + @test success(addenv( + `$(Base.julia_cmd()) --startup-file=no --pkgimage=yes --code-coverage=@ --project -e 'using CovTest; exit(0)'`, + "JULIA_DEPOT_PATH" => depot, + )) + @test !isempty(filter(!endswith(".ji"), readdir(cov_cache_dir))) # check that object cache file(s) exists + @test !cov_exists() + rm_cov_files() + + # same again but call foo(), which is in the pkgimage, and should generate coverage + @test success(addenv( + `$(Base.julia_cmd()) --startup-file=no --pkgimage=yes --code-coverage=@ --project -e 'using CovTest; foo(); exit(0)'`, + "JULIA_DEPOT_PATH" => depot, + )) + @test cov_exists() + rm_cov_files() + + # same again but call bar(), which is NOT in the pkgimage, and should generate coverage + @test success(addenv( + `$(Base.julia_cmd()) --startup-file=no --pkgimage=yes --code-coverage=@ --project -e 'using CovTest; bar(); exit(0)'`, + "JULIA_DEPOT_PATH" => depot, + )) + @test cov_exists() + rm_cov_files() + end + end +end diff --git a/test/precompile.jl b/test/precompile.jl index 29ca79ef5fce33..bb87e1f6b1dc79 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1778,7 +1778,7 @@ precompile_test_harness("PkgCacheInspector") do load_path end if ocachefile !== nothing - sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring), ocachefile, depmods, true, "PCI") + sv = ccall(:jl_restore_package_image_from_file, Any, (Cstring, Any, Cint, Cstring, Cint), ocachefile, depmods, true, "PCI", false) else sv = ccall(:jl_restore_incremental, Any, (Cstring, Any, Cint, Cstring), cachefile, depmods, true, "PCI") end diff --git a/test/project/deps/CovTest.jl/Project.toml b/test/project/deps/CovTest.jl/Project.toml new file mode 100644 index 00000000000000..97fb2c7d9cfce9 --- /dev/null +++ b/test/project/deps/CovTest.jl/Project.toml @@ -0,0 +1,3 @@ +name = "CovTest" +uuid = "f1f4390d-b815-473a-b5dd-5af6e1d717cb" +version = "0.1.0" diff --git a/test/project/deps/CovTest.jl/src/CovTest.jl b/test/project/deps/CovTest.jl/src/CovTest.jl new file mode 100644 index 00000000000000..bd172fc3a00f48 --- /dev/null +++ b/test/project/deps/CovTest.jl/src/CovTest.jl @@ -0,0 +1,26 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module CovTest + +function foo() + x = 1 + y = 2 + z = x * y + return z +end + +function bar() + x = 1 + y = 2 + z = x * y + return z +end + +if Base.generating_output() + # precompile foo but not bar + foo() +end + +export foo, bar + +end #module From f26947b4f5aee73135581bb14290c1c00102b8e3 Mon Sep 17 00:00:00 2001 From: Diogo Netto <61364108+d-netto@users.noreply.github.com> Date: Thu, 16 Nov 2023 23:09:09 -0300 Subject: [PATCH 04/38] cap the number of GC threads to number of cpu cores (#52192) --- src/threading.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/threading.c b/src/threading.c index 18c502cf94387a..e20e7cbf80f265 100644 --- a/src/threading.c +++ b/src/threading.c @@ -681,6 +681,7 @@ void jl_init_threading(void) } } + int cpu = jl_cpu_threads(); jl_n_markthreads = jl_options.nmarkthreads - 1; jl_n_sweepthreads = jl_options.nsweepthreads; if (jl_n_markthreads == -1) { // --gcthreads not specified @@ -709,8 +710,20 @@ void jl_init_threading(void) else { jl_n_markthreads = (nthreads / 2) - 1; } + // if `--gcthreads` or ENV[NUM_GCTHREADS_NAME] was not specified, + // cap the number of threads that may run the mark phase to + // the number of CPU cores + if (jl_n_markthreads + 1 >= cpu) { + jl_n_markthreads = cpu - 1; + } } } + // warn the user if they try to run with a number + // of GC threads which is larger than the number + // of physical cores + if (jl_n_markthreads + 1 > cpu) { + jl_safe_printf("WARNING: running Julia with %d GC threads on %d CPU cores\n", jl_n_markthreads + 1, cpu); + } int16_t ngcthreads = jl_n_markthreads + jl_n_sweepthreads; jl_all_tls_states_size = nthreads + nthreadsi + ngcthreads; From 1cb85ad3fb1ba32e18be9be67775d1b36dc27079 Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Fri, 17 Nov 2023 03:41:28 +0100 Subject: [PATCH 05/38] Revert "Support sorting iterators (#46104)" (#52010) Co-authored-by: Lilith Orion Hafner --- base/sort.jl | 43 ++------------------------------------ stdlib/REPL/src/docview.jl | 2 +- test/sorting.jl | 36 ------------------------------- 3 files changed, 3 insertions(+), 78 deletions(-) diff --git a/base/sort.jl b/base/sort.jl index f35add7a55b35d..90b43c040d9690 100644 --- a/base/sort.jl +++ b/base/sort.jl @@ -5,8 +5,7 @@ module Sort using Base.Order using Base: copymutable, midpoint, require_one_based_indexing, uinttype, - sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit, - IteratorSize, HasShape, IsInfinite, tail + sub_with_overflow, add_with_overflow, OneTo, BitSigned, BitIntegerType, top_set_bit import Base: sort, @@ -1474,12 +1473,6 @@ end Variant of [`sort!`](@ref) that returns a sorted copy of `v` leaving `v` itself unmodified. -Returns something [`similar`](@ref) to `v` when `v` is an `AbstractArray` and uses -[`collect`](@ref) to support arbitrary non-`AbstractArray` iterables. - -!!! compat "Julia 1.10" - `sort` of arbitrary iterables requires at least Julia 1.10. - # Examples ```jldoctest julia> v = [3, 1, 2]; @@ -1497,39 +1490,7 @@ julia> v 2 ``` """ -function sort(v; kws...) - size = IteratorSize(v) - size == HasShape{0}() && throw(ArgumentError("$v cannot be sorted")) - size == IsInfinite() && throw(ArgumentError("infinite iterator $v cannot be sorted")) - sort!(collect(v); kws...) -end -sort(v::AbstractVector; kws...) = sort!(copymutable(v); kws...) # for method disambiguation -sort(::AbstractString; kws...) = - throw(ArgumentError("sort(::AbstractString) is not supported")) -sort(::Tuple; kws...) = - throw(ArgumentError("sort(::Tuple) is only supported for NTuples")) - -function sort(x::NTuple{N}; lt::Function=isless, by::Function=identity, - rev::Union{Bool,Nothing}=nothing, order::Ordering=Forward) where N - o = ord(lt,by,rev,order) - if N > 9 - v = sort!(collect(x), DEFAULT_STABLE, o) - tuple((v[i] for i in 1:N)...) - else - _sort(x, o) - end -end -_sort(x::Union{NTuple{0}, NTuple{1}}, o::Ordering) = x -function _sort(x::NTuple, o::Ordering) - a, b = Base.IteratorsMD.split(x, Val(length(x)>>1)) - merge(_sort(a, o), _sort(b, o), o) -end -merge(x::NTuple, y::NTuple{0}, o::Ordering) = x -merge(x::NTuple{0}, y::NTuple, o::Ordering) = y -merge(x::NTuple{0}, y::NTuple{0}, o::Ordering) = x # Method ambiguity -merge(x::NTuple, y::NTuple, o::Ordering) = - (lt(o, y[1], x[1]) ? (y[1], merge(x, tail(y), o)...) : (x[1], merge(tail(x), y, o)...)) - +sort(v::AbstractVector; kws...) = sort!(copymutable(v); kws...) ## partialsortperm: the permutation to sort the first k elements of an array ## diff --git a/stdlib/REPL/src/docview.jl b/stdlib/REPL/src/docview.jl index 8551c5f38fcd3f..41f30204470909 100644 --- a/stdlib/REPL/src/docview.jl +++ b/stdlib/REPL/src/docview.jl @@ -184,7 +184,7 @@ log_nonpublic_access(expr, ::Module, _) = expr function insert_internal_warning(md::Markdown.MD, internal_access::Set{Pair{Module,Symbol}}) if !isempty(internal_access) - items = Any[Any[Markdown.Paragraph(Any[Markdown.Code("", s)])] for s in sort("$mod.$sym" for (mod, sym) in internal_access)] + items = Any[Any[Markdown.Paragraph(Any[Markdown.Code("", s)])] for s in sort!(["$mod.$sym" for (mod, sym) in internal_access])] admonition = Markdown.Admonition("warning", "Warning", Any[ Markdown.Paragraph(Any["The following bindings may be internal; they may change or be removed in future versions:"]), Markdown.List(items, -1, false)]) diff --git a/test/sorting.jl b/test/sorting.jl index 9cb5d717635c9d..bb0fd3cc6392e8 100644 --- a/test/sorting.jl +++ b/test/sorting.jl @@ -88,20 +88,6 @@ end vcat(2000, (x:x+99 for x in 1900:-100:100)..., 1:99) end -function tuple_sort_test(x) - @test issorted(sort(x)) - length(x) > 9 && return # length > 9 uses a vector fallback - @test 0 == @allocated sort(x) -end -@testset "sort(::NTuple)" begin - @test sort((9,8,3,3,6,2,0,8)) == (0,2,3,3,6,8,8,9) - @test sort((9,8,3,3,6,2,0,8), by=x->xรท3) == (2,0,3,3,8,6,8,9) - for i in 1:40 - tuple_sort_test(tuple(rand(i)...)) - end - @test_throws ArgumentError sort((1,2,3.0)) -end - @testset "partialsort" begin @test partialsort([3,6,30,1,9],3) == 6 @test partialsort([3,6,30,1,9],3:4) == [6,9] @@ -544,28 +530,6 @@ end @test isequal(a, [8,6,7,NaN,5,3,0,9]) end -@testset "sort!(iterable)" begin - gen = (x % 7 + 0.1x for x in 1:50) - @test sort(gen) == sort!(collect(gen)) - gen = (x % 7 + 0.1y for x in 1:10, y in 1:5) - @test sort(gen; dims=1) == sort!(collect(gen); dims=1) - @test sort(gen; dims=2) == sort!(collect(gen); dims=2) - - @test_throws ArgumentError("dimension out of range") sort(gen; dims=3) - - @test_throws UndefKeywordError(:dims) sort(gen) - @test_throws UndefKeywordError(:dims) sort(collect(gen)) - @test_throws UndefKeywordError(:dims) sort!(collect(gen)) - - @test_throws ArgumentError sort("string") - @test_throws ArgumentError("1 cannot be sorted") sort(1) - - @test sort(Set((1, 3, 6))) == [1, 3, 6] - @test sort(Dict((1=>9, 3=>2, 6=>5))) == [1=>9, 3=>2, 6=>5] - @test sort(keys(Dict((1=>2, 3=>5, 6=>9)))) == [1, 3, 6] - @test sort(values(Dict((1=>9, 3=>2, 6=>5)))) == [2, 5, 9] -end - @testset "sort!(::AbstractVector{<:Integer}) with short int range" begin a = view([9:-1:0;], :)::SubArray sort!(a) From a65bc9a267837fcf9813bef2fc6eb79d02e25ea5 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 16 Nov 2023 21:55:10 -0500 Subject: [PATCH 06/38] jitlayers: replace sharedbytes intern pool with one that respects alignment (#52182) The llvm optimizations may increase alignment beyond the initial MAX_ALIGN. This pool's alignment was previously only `sizeof(struct { atomic RefCount; size_t Length; char Data[]; })` however, potentially resulting in segfaults at runtime. Fixes #52118. Should make CI much happier. --- src/gc.c | 3 +-- src/jitlayers.cpp | 21 +++++++++++++++------ src/jitlayers.h | 43 +++++++++++++++++++++++++++++++++++++++++++ src/julia_internal.h | 2 +- 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/gc.c b/src/gc.c index 6860871587952b..c68d4e92f9f576 100644 --- a/src/gc.c +++ b/src/gc.c @@ -4099,8 +4099,7 @@ static void *gc_perm_alloc_large(size_t sz, int zero, unsigned align, unsigned o errno = last_errno; jl_may_leak(base); assert(align > 0); - unsigned diff = (offset - (uintptr_t)base) % align; - return (void*)((char*)base + diff); + return (void*)(LLT_ALIGN((uintptr_t)base + offset, (uintptr_t)align) - offset); } STATIC_INLINE void *gc_try_perm_alloc_pool(size_t sz, unsigned align, unsigned offset) JL_NOTSAFEPOINT diff --git a/src/jitlayers.cpp b/src/jitlayers.cpp index a22a5d8a6a2647..2de54559033756 100644 --- a/src/jitlayers.cpp +++ b/src/jitlayers.cpp @@ -1410,10 +1410,12 @@ namespace { struct JITPointersT { - JITPointersT(orc::ExecutionSession &ES) JL_NOTSAFEPOINT : ES(ES) {} + JITPointersT(SharedBytesT &SharedBytes, std::mutex &Lock) JL_NOTSAFEPOINT + : SharedBytes(SharedBytes), Lock(Lock) {} Expected operator()(orc::ThreadSafeModule TSM, orc::MaterializationResponsibility &R) JL_NOTSAFEPOINT { TSM.withModuleDo([&](Module &M) JL_NOTSAFEPOINT { + std::lock_guard locked(Lock); for (auto &GV : make_early_inc_range(M.globals())) { if (auto *Shared = getSharedBytes(GV)) { ++InternedGlobals; @@ -1429,10 +1431,11 @@ namespace { return std::move(TSM); } + private: // optimize memory by turning long strings into memoized copies, instead of // making a copy per object file of output. - // we memoize them using the ExecutionSession's string pool; - // this makes it unsafe to call clearDeadEntries() on the pool. + // we memoize them using a StringSet with a custom-alignment allocator + // to ensure they are properly aligned Constant *getSharedBytes(GlobalVariable &GV) JL_NOTSAFEPOINT { // We could probably technically get away with // interning even external linkage globals, @@ -1458,11 +1461,17 @@ namespace { // Cutoff, since we don't want to intern small strings return nullptr; } - auto Interned = *ES.intern(Data); + Align Required = GV.getAlign().valueOrOne(); + Align Preferred = MaxAlignedAlloc::alignment(Data.size()); + if (Required > Preferred) + return nullptr; + StringRef Interned = SharedBytes.insert(Data).first->getKey(); + assert(llvm::isAddrAligned(Preferred, Interned.data())); return literal_static_pointer_val(Interned.data(), GV.getType()); } - orc::ExecutionSession &ES; + SharedBytesT &SharedBytes; + std::mutex &Lock; }; } @@ -1696,7 +1705,7 @@ JuliaOJIT::JuliaOJIT() #endif LockLayer(ObjectLayer), CompileLayer(ES, LockLayer, std::make_unique>(orc::irManglingOptionsFromTargetOptions(TM->Options), *TM)), - JITPointersLayer(ES, CompileLayer, orc::IRTransformLayer::TransformFunction(JITPointersT(ES))), + JITPointersLayer(ES, CompileLayer, orc::IRTransformLayer::TransformFunction(JITPointersT(SharedBytes, RLST_mutex))), OptimizeLayer(ES, JITPointersLayer, orc::IRTransformLayer::TransformFunction(OptimizerT(*TM, PrintLLVMTimers))), OptSelLayer(ES, OptimizeLayer, orc::IRTransformLayer::TransformFunction(selectOptLevel)), DepsVerifyLayer(ES, OptSelLayer, orc::IRTransformLayer::TransformFunction(validateExternRelocations)), diff --git a/src/jitlayers.h b/src/jitlayers.h index 016f97b92140b6..85cbb2cf8ec286 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -1,6 +1,8 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license #include +#include +#include #include #include @@ -292,6 +294,44 @@ static const inline char *name_from_method_instance(jl_method_instance_t *li) JL return jl_is_method(li->def.method) ? jl_symbol_name(li->def.method->name) : "top-level scope"; } +template +class MaxAlignedAllocImpl + : public AllocatorBase> { + +public: + MaxAlignedAllocImpl() JL_NOTSAFEPOINT = default; + + static Align alignment(size_t Size) JL_NOTSAFEPOINT { + // Define the maximum alignment we expect to require, from offset bytes off + // the returned pointer, this is >= alignof(std::max_align_t), which is too + // small often to actually use. + const size_t MaxAlignment = JL_CACHE_BYTE_ALIGNMENT; + return Align(std::min((size_t)llvm::PowerOf2Ceil(Size), MaxAlignment)); + } + + LLVM_ATTRIBUTE_RETURNS_NONNULL void *Allocate(size_t Size, Align Alignment) { + Align MaxAlign = alignment(Size); + assert(Alignment < MaxAlign); (void)Alignment; + return jl_gc_perm_alloc(Size, 0, MaxAlign.value(), offset); + } + + inline LLVM_ATTRIBUTE_RETURNS_NONNULL + void * Allocate(size_t Size, size_t Alignment) { + return Allocate(Size, Align(Alignment)); + } + + // Pull in base class overloads. + using AllocatorBase::Allocate; + + void Deallocate(const void *Ptr, size_t Size, size_t /*Alignment*/) { abort(); } + + // Pull in base class overloads. + using AllocatorBase::Deallocate; + +private: +}; +using MaxAlignedAlloc = MaxAlignedAllocImpl<>; + typedef JITSymbol JL_JITSymbol; // The type that is similar to SymbolInfo on LLVM 4.0 is actually // `JITEvaluatedSymbol`. However, we only use this type when a JITSymbol @@ -300,6 +340,7 @@ typedef JITSymbol JL_SymbolInfo; using CompilerResultT = Expected>; using OptimizerResultT = Expected; +using SharedBytesT = StringSet::MapEntryTy)>>; class JuliaOJIT { public: @@ -516,6 +557,7 @@ class JuliaOJIT { // Note that this is a safepoint due to jl_get_library_ and jl_dlsym calls void optimizeDLSyms(Module &M); + private: const std::unique_ptr TM; @@ -529,6 +571,7 @@ class JuliaOJIT { std::mutex RLST_mutex{}; int RLST_inc = 0; DenseMap ReverseLocalSymbolTable; + SharedBytesT SharedBytes; std::unique_ptr DLSymOpt; diff --git a/src/julia_internal.h b/src/julia_internal.h index ad3e46cd6307e8..c09b73f8a1052b 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -349,7 +349,7 @@ JL_DLLEXPORT int jl_gc_classify_pools(size_t sz, int *osize) JL_NOTSAFEPOINT; extern uv_mutex_t gc_perm_lock; void *jl_gc_perm_alloc_nolock(size_t sz, int zero, unsigned align, unsigned offset) JL_NOTSAFEPOINT; -void *jl_gc_perm_alloc(size_t sz, int zero, +JL_DLLEXPORT void *jl_gc_perm_alloc(size_t sz, int zero, unsigned align, unsigned offset) JL_NOTSAFEPOINT; void gc_sweep_sysimg(void); From 29be3ef88c0e4e093c6563f50237c37d5ab14a39 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Fri, 17 Nov 2023 08:21:50 -0500 Subject: [PATCH 07/38] Move heap_size batching code into pair of functions (#51611) Co-authored-by: Gabriel Baraldi --- src/gc.c | 126 ++++++++++++++++------------------------------- src/staticdata.c | 11 ++--- 2 files changed, 47 insertions(+), 90 deletions(-) diff --git a/src/gc.c b/src/gc.c index c68d4e92f9f576..bc0361c8ac7a89 100644 --- a/src/gc.c +++ b/src/gc.c @@ -700,6 +700,7 @@ static uint64_t gc_end_time = 0; static int thrash_counter = 0; static int thrashing = 0; // global variables for GC stats +static uint64_t freed_in_runtime = 0; // Resetting the object to a young object, this is used when marking the // finalizer list to collect them the next time because the object is very @@ -1005,6 +1006,22 @@ static void sweep_weak_refs(void) } +STATIC_INLINE void jl_batch_accum_heap_size(jl_ptls_t ptls, uint64_t sz) JL_NOTSAFEPOINT +{ + uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc) + sz; + if (alloc_acc < 16*1024) + jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc); + else { + jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc); + jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); + } +} + +STATIC_INLINE void jl_batch_accum_free_size(jl_ptls_t ptls, uint64_t sz) JL_NOTSAFEPOINT +{ + jl_atomic_store_relaxed(&ptls->gc_num.free_acc, jl_atomic_load_relaxed(&ptls->gc_num.free_acc) + sz); +} + // big value list // Size includes the tag and the tag is not cleared!! @@ -1027,13 +1044,7 @@ STATIC_INLINE jl_value_t *jl_gc_big_alloc_inner(jl_ptls_t ptls, size_t sz) jl_atomic_load_relaxed(&ptls->gc_num.allocd) + allocsz); jl_atomic_store_relaxed(&ptls->gc_num.bigalloc, jl_atomic_load_relaxed(&ptls->gc_num.bigalloc) + 1); - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + allocsz < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + allocsz); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + allocsz); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, allocsz); #ifdef MEMDEBUG memset(v, 0xee, allocsz); #endif @@ -1147,16 +1158,10 @@ void jl_gc_count_allocd(size_t sz) JL_NOTSAFEPOINT jl_ptls_t ptls = jl_current_task->ptls; jl_atomic_store_relaxed(&ptls->gc_num.allocd, jl_atomic_load_relaxed(&ptls->gc_num.allocd) + sz); - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + sz < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + sz); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + sz); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, sz); } - -static void combine_thread_gc_counts(jl_gc_num_t *dest) JL_NOTSAFEPOINT +// Only safe to update the heap inside the GC +static void combine_thread_gc_counts(jl_gc_num_t *dest, int update_heap) JL_NOTSAFEPOINT { int gc_n_threads; jl_ptls_t* gc_all_tls_states; @@ -1170,12 +1175,14 @@ static void combine_thread_gc_counts(jl_gc_num_t *dest) JL_NOTSAFEPOINT dest->realloc += jl_atomic_load_relaxed(&ptls->gc_num.realloc); dest->poolalloc += jl_atomic_load_relaxed(&ptls->gc_num.poolalloc); dest->bigalloc += jl_atomic_load_relaxed(&ptls->gc_num.bigalloc); - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - uint64_t free_acc = jl_atomic_load_relaxed(&ptls->gc_num.free_acc); dest->freed += jl_atomic_load_relaxed(&ptls->gc_num.free_acc); - jl_atomic_store_relaxed(&gc_heap_stats.heap_size, alloc_acc - free_acc + jl_atomic_load_relaxed(&gc_heap_stats.heap_size)); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0); + if (update_heap) { + uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); + freed_in_runtime += jl_atomic_load_relaxed(&ptls->gc_num.free_acc); + jl_atomic_store_relaxed(&gc_heap_stats.heap_size, alloc_acc + jl_atomic_load_relaxed(&gc_heap_stats.heap_size)); + jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); + jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0); + } } } } @@ -1209,7 +1216,7 @@ static int64_t inc_live_bytes(int64_t inc) JL_NOTSAFEPOINT void jl_gc_reset_alloc_count(void) JL_NOTSAFEPOINT { - combine_thread_gc_counts(&gc_num); + combine_thread_gc_counts(&gc_num, 0); inc_live_bytes(gc_num.deferred_alloc + gc_num.allocd); gc_num.allocd = 0; gc_num.deferred_alloc = 0; @@ -3176,7 +3183,7 @@ JL_DLLEXPORT int jl_gc_is_enabled(void) JL_DLLEXPORT void jl_gc_get_total_bytes(int64_t *bytes) JL_NOTSAFEPOINT { jl_gc_num_t num = gc_num; - combine_thread_gc_counts(&num); + combine_thread_gc_counts(&num, 0); // Sync this logic with `base/util.jl:GC_Diff` *bytes = (num.total_allocd + num.deferred_alloc + num.allocd); } @@ -3189,7 +3196,7 @@ JL_DLLEXPORT uint64_t jl_gc_total_hrtime(void) JL_DLLEXPORT jl_gc_num_t jl_gc_num(void) { jl_gc_num_t num = gc_num; - combine_thread_gc_counts(&num); + combine_thread_gc_counts(&num, 0); return num; } @@ -3248,7 +3255,7 @@ size_t jl_maxrss(void); // Only one thread should be running in this function static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) { - combine_thread_gc_counts(&gc_num); + combine_thread_gc_counts(&gc_num, 1); // We separate the update of the graph from the update of live_bytes here // so that the sweep shows a downward trend in memory usage. @@ -3432,6 +3439,8 @@ static int _jl_gc_collect(jl_ptls_t ptls, jl_gc_collection_t collection) gc_num.last_incremental_sweep = gc_end_time; } + jl_atomic_store_relaxed(&gc_heap_stats.heap_size, jl_atomic_load_relaxed(&gc_heap_stats.heap_size) - freed_in_runtime); + freed_in_runtime = 0; size_t heap_size = jl_atomic_load_relaxed(&gc_heap_stats.heap_size); double target_allocs = 0.0; double min_interval = default_collect_interval; @@ -3780,13 +3789,7 @@ JL_DLLEXPORT void *jl_gc_counted_malloc(size_t sz) jl_atomic_load_relaxed(&ptls->gc_num.allocd) + sz); jl_atomic_store_relaxed(&ptls->gc_num.malloc, jl_atomic_load_relaxed(&ptls->gc_num.malloc) + 1); - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + sz < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + sz); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + sz); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, sz); } return data; } @@ -3803,13 +3806,7 @@ JL_DLLEXPORT void *jl_gc_counted_calloc(size_t nm, size_t sz) jl_atomic_load_relaxed(&ptls->gc_num.allocd) + nm*sz); jl_atomic_store_relaxed(&ptls->gc_num.malloc, jl_atomic_load_relaxed(&ptls->gc_num.malloc) + 1); - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + sz < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + sz * nm); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + sz * nm); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, sz * nm); } return data; } @@ -3820,14 +3817,7 @@ JL_DLLEXPORT void jl_gc_counted_free_with_size(void *p, size_t sz) jl_task_t *ct = jl_current_task; free(p); if (pgcstack != NULL && ct->world_age) { - jl_ptls_t ptls = ct->ptls; - uint64_t free_acc = jl_atomic_load_relaxed(&ptls->gc_num.free_acc); - if (free_acc + sz < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, free_acc + sz); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, -(free_acc + sz)); - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0); - } + jl_batch_accum_free_size(ct->ptls, sz); } } @@ -3847,23 +3837,10 @@ JL_DLLEXPORT void *jl_gc_counted_realloc_with_old_size(void *p, size_t old, size int64_t diff = sz - old; if (diff < 0) { - diff = -diff; - uint64_t free_acc = jl_atomic_load_relaxed(&ptls->gc_num.free_acc); - if (free_acc + diff < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, free_acc + diff); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, -(free_acc + diff)); - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0); - } + jl_batch_accum_free_size(ptls, -diff); } else { - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + diff < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + diff); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + diff); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, diff); } } return data; @@ -3948,13 +3925,7 @@ JL_DLLEXPORT void *jl_gc_managed_malloc(size_t sz) jl_atomic_load_relaxed(&ptls->gc_num.allocd) + allocsz); jl_atomic_store_relaxed(&ptls->gc_num.malloc, jl_atomic_load_relaxed(&ptls->gc_num.malloc) + 1); - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + allocsz < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + allocsz); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + allocsz); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, allocsz); #ifdef _OS_WINDOWS_ SetLastError(last_error); #endif @@ -4002,23 +3973,10 @@ static void *gc_managed_realloc_(jl_ptls_t ptls, void *d, size_t sz, size_t olds int64_t diff = allocsz - oldsz; if (diff < 0) { - diff = -diff; - uint64_t free_acc = jl_atomic_load_relaxed(&ptls->gc_num.free_acc); - if (free_acc + diff < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, free_acc + diff); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, -(free_acc + diff)); - jl_atomic_store_relaxed(&ptls->gc_num.free_acc, 0); - } + jl_batch_accum_free_size(ptls, -diff); } else { - uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_num.alloc_acc); - if (alloc_acc + diff < 16*1024) - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, alloc_acc + diff); - else { - jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc + diff); - jl_atomic_store_relaxed(&ptls->gc_num.alloc_acc, 0); - } + jl_batch_accum_heap_size(ptls, diff); } if (allocsz > oldsz) { maybe_record_alloc_to_profile((jl_value_t*)b, allocsz - oldsz, (jl_datatype_t*)jl_buff_tag); diff --git a/src/staticdata.c b/src/staticdata.c index f34f64b28e3218..42a5fdb63b63e0 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -71,7 +71,6 @@ External links: */ #include #include -#include #include // printf #include // PRIxPTR @@ -3522,7 +3521,7 @@ static jl_value_t *jl_validate_cache_file(ios_t *f, jl_array_t *depmods, uint64_ } // TODO?: refactor to make it easier to create the "package inspector" -static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, ios_t *f, jl_image_t *image, jl_array_t *depmods, int completeinfo, const char *pkgname, bool needs_permalloc) +static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, ios_t *f, jl_image_t *image, jl_array_t *depmods, int completeinfo, const char *pkgname, int needs_permalloc) { JL_TIMING(LOAD_IMAGE, LOAD_Pkgimg); jl_timing_printf(JL_TIMING_DEFAULT_BLOCK, pkgname); @@ -3548,7 +3547,7 @@ static jl_value_t *jl_restore_package_image_from_stream(void* pkgimage_handle, i JL_SIGATOMIC_BEGIN(); size_t len = dataendpos - datastartpos; char *sysimg; - bool success = !needs_permalloc; + int success = !needs_permalloc; ios_seek(f, datastartpos); if (needs_permalloc) sysimg = (char*)jl_gc_perm_alloc(len, 0, 64, 0); @@ -3608,7 +3607,7 @@ static void jl_restore_system_image_from_stream(ios_t *f, jl_image_t *image, uin jl_restore_system_image_from_stream_(f, image, NULL, checksum | ((uint64_t)0xfdfcfbfa << 32), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); } -JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(void* pkgimage_handle, const char *buf, jl_image_t *image, size_t sz, jl_array_t *depmods, int completeinfo, const char *pkgname, bool needs_permalloc) +JL_DLLEXPORT jl_value_t *jl_restore_incremental_from_buf(void* pkgimage_handle, const char *buf, jl_image_t *image, size_t sz, jl_array_t *depmods, int completeinfo, const char *pkgname, int needs_permalloc) { ios_t f; ios_static_buffer(&f, (char*)buf, sz); @@ -3625,7 +3624,7 @@ JL_DLLEXPORT jl_value_t *jl_restore_incremental(const char *fname, jl_array_t *d "Cache file \"%s\" not found.\n", fname); } jl_image_t pkgimage = {}; - jl_value_t *ret = jl_restore_package_image_from_stream(NULL, &f, &pkgimage, depmods, completeinfo, pkgname, true); + jl_value_t *ret = jl_restore_package_image_from_stream(NULL, &f, &pkgimage, depmods, completeinfo, pkgname, 1); ios_close(&f); return ret; } @@ -3700,7 +3699,7 @@ JL_DLLEXPORT jl_value_t *jl_restore_package_image_from_file(const char *fname, j memset(&pkgimage.fptrs, 0, sizeof(pkgimage.fptrs)); } - jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_handle, pkgimg_data, &pkgimage, *plen, depmods, completeinfo, pkgname, false); + jl_value_t* mod = jl_restore_incremental_from_buf(pkgimg_handle, pkgimg_data, &pkgimage, *plen, depmods, completeinfo, pkgname, 0); return mod; } From f1f84a2bfd735f8d05f71899b65eee49afc8c5cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= Date: Fri, 17 Nov 2023 14:23:49 +0000 Subject: [PATCH 08/38] [OpenBLAS_jll] Upgrade to v0.3.25 (#52178) See the [release notes of v0.3.25](https://github.com/OpenMathLib/OpenBLAS/releases/tag/v0.3.25). Usual memo to self for the upgrade: * update version number in `stdlib/OpenBLAS_jll/Project.toml` * run `make -f contrib/refresh_checksums.mk -j openblas` to update the checksums * update version number and commit hash in `deps/openblas.version` --- deps/checksums/openblas | 184 +++++++++++++++---------------- deps/openblas.version | 6 +- stdlib/OpenBLAS_jll/Project.toml | 2 +- 3 files changed, 96 insertions(+), 96 deletions(-) diff --git a/deps/checksums/openblas b/deps/checksums/openblas index 9e11a478e74273..b49af255be0e7a 100644 --- a/deps/checksums/openblas +++ b/deps/checksums/openblas @@ -1,94 +1,94 @@ -OpenBLAS.v0.3.24+0.aarch64-apple-darwin-libgfortran5.tar.gz/md5/9b52215f0729a2d96bc86a820c280433 -OpenBLAS.v0.3.24+0.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/b772022a498075f1712e707ed394583af49a25db60bce9d59981827f3d82968e5ddbbd05394c791ce89595b4c2442330870c3a36739e5511a99f166c2805b3ae -OpenBLAS.v0.3.24+0.aarch64-linux-gnu-libgfortran3.tar.gz/md5/d89ee1bb7ebefe186c02354209d60e94 -OpenBLAS.v0.3.24+0.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/ed8178b62bacf8aefe094adb822cfda4cb696161ca712b7b70c635b1f7868d84c2912880010465cb6ba00632e25b3bdcdb9f1bab9307ccb7bf1d7eb7a2a760c7 -OpenBLAS.v0.3.24+0.aarch64-linux-gnu-libgfortran4.tar.gz/md5/271a15b5d77c1501b13934702e0bea3d -OpenBLAS.v0.3.24+0.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/f2dfb1478471870526c3b8a2b8173eb4705b2dcb7e2c8b4fd4acebe24f4bdc999262e998f832a6769a5def4577256b07f4aa76c058abf7e93ea5e814a74d014d -OpenBLAS.v0.3.24+0.aarch64-linux-gnu-libgfortran5.tar.gz/md5/7c47ee25fade4bf4d4fcbd9b8e5feea7 -OpenBLAS.v0.3.24+0.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/b92e08f527c30d74470e1fa8275dfa6f8ce6bdae028394fc1440a55e4b1ddd15d8ab14bf36b87cf400ee22a69c13ce9b78c22889dccb5ffc4945c5293deea445 -OpenBLAS.v0.3.24+0.aarch64-linux-musl-libgfortran3.tar.gz/md5/2c00dd0f2d0e8e3938e02e21ad4fcbed -OpenBLAS.v0.3.24+0.aarch64-linux-musl-libgfortran3.tar.gz/sha512/e61635827d9495bbbe8726a3d7803c786b9304dbb32ba3d99ac5a359ae827b6e48e0db7ffa3fc2279d65e2cda3ca96abccdbcdd86b54e962cc8b7f86937e9594 -OpenBLAS.v0.3.24+0.aarch64-linux-musl-libgfortran4.tar.gz/md5/35297e1b32247edeb41907fca6f18a0a -OpenBLAS.v0.3.24+0.aarch64-linux-musl-libgfortran4.tar.gz/sha512/b8277b5f07b29940f5893f44af4fec3278c8cb7e52db7fb7f416ffe644d47f781d98f8d9fef89dea623762473e78abe61977eff1f4c3f1c9ff44dd833430f897 -OpenBLAS.v0.3.24+0.aarch64-linux-musl-libgfortran5.tar.gz/md5/ce187d3e01cfa633421567c2c5d18509 -OpenBLAS.v0.3.24+0.aarch64-linux-musl-libgfortran5.tar.gz/sha512/408106528aa80ea68808d99544ff4e6485d5912bf7e2586936c0e7758d94a53705388077140b8335714cd75772ef9d5465fa629fdac6939268ba11617076c42d -OpenBLAS.v0.3.24+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/52a7f29bfe47e89f32b5c95f6ce65c29 -OpenBLAS.v0.3.24+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/4816b3e022613b6a7a2073dd292311b29d8e848887f4d0ed7bcf32140bce2e9f15e05d754c48c1575cbaf70fcae8cfec501725df15d6bb1c7ac1ac1c74b59489 -OpenBLAS.v0.3.24+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/6f4aa76cc544ce52da6aa094ee5a1aeb -OpenBLAS.v0.3.24+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/c3428c36e19e9bd89dd0eb809f2617b7f90cbfaadffe9c58bf16c319e5e36d07102f48bafeb7a787bb6a18655ba3688a29ccd4f3724bc9354915d7e55fb64d44 -OpenBLAS.v0.3.24+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/298a83c0550694b3b6c22f6dd460cc9d -OpenBLAS.v0.3.24+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/b8e5ac5304364c950a427384db13ed5c573e7b5e9d16bb7c354b7b5e46ca8e7471bc911a2cf9defbfc933fbfbff315949011b5596532b8863c551de6d543f434 -OpenBLAS.v0.3.24+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/2b584ae3aa496ff871cc735242e63cf4 -OpenBLAS.v0.3.24+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/ba0911f7f6f9f9a5823fa0e98778e4a7d2a9bbd5d674d552fefd20458743b60baf10a49e1ee65a3b934636229296d64b1eece55826f136824f9cae91b6a208c4 -OpenBLAS.v0.3.24+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/2ff249795f3d8796b99536a4bf11670c -OpenBLAS.v0.3.24+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/517c4a99933e439de7ede07376b6f40785406216489f1bd6f995e5d6e444c860df401807abba6dc04ff83dd701f32047fcd4d3fd01660601f27edbc545ea783e -OpenBLAS.v0.3.24+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/f3c3fab4d30655b8cd831511a307d4d3 -OpenBLAS.v0.3.24+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/06d47db95ec374b5238857c1ad595cee882441fd462ae6a030f1a1c4465b617228feb34fd80a97d8ca965bfc87f8d4a3a94a3654b2c0615ef4506fa827e8c155 -OpenBLAS.v0.3.24+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/52a7f29bfe47e89f32b5c95f6ce65c29 -OpenBLAS.v0.3.24+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/4816b3e022613b6a7a2073dd292311b29d8e848887f4d0ed7bcf32140bce2e9f15e05d754c48c1575cbaf70fcae8cfec501725df15d6bb1c7ac1ac1c74b59489 -OpenBLAS.v0.3.24+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/6f4aa76cc544ce52da6aa094ee5a1aeb -OpenBLAS.v0.3.24+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/c3428c36e19e9bd89dd0eb809f2617b7f90cbfaadffe9c58bf16c319e5e36d07102f48bafeb7a787bb6a18655ba3688a29ccd4f3724bc9354915d7e55fb64d44 -OpenBLAS.v0.3.24+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/298a83c0550694b3b6c22f6dd460cc9d -OpenBLAS.v0.3.24+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/b8e5ac5304364c950a427384db13ed5c573e7b5e9d16bb7c354b7b5e46ca8e7471bc911a2cf9defbfc933fbfbff315949011b5596532b8863c551de6d543f434 -OpenBLAS.v0.3.24+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/2b584ae3aa496ff871cc735242e63cf4 -OpenBLAS.v0.3.24+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/ba0911f7f6f9f9a5823fa0e98778e4a7d2a9bbd5d674d552fefd20458743b60baf10a49e1ee65a3b934636229296d64b1eece55826f136824f9cae91b6a208c4 -OpenBLAS.v0.3.24+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/2ff249795f3d8796b99536a4bf11670c -OpenBLAS.v0.3.24+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/517c4a99933e439de7ede07376b6f40785406216489f1bd6f995e5d6e444c860df401807abba6dc04ff83dd701f32047fcd4d3fd01660601f27edbc545ea783e -OpenBLAS.v0.3.24+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/f3c3fab4d30655b8cd831511a307d4d3 -OpenBLAS.v0.3.24+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/06d47db95ec374b5238857c1ad595cee882441fd462ae6a030f1a1c4465b617228feb34fd80a97d8ca965bfc87f8d4a3a94a3654b2c0615ef4506fa827e8c155 -OpenBLAS.v0.3.24+0.i686-linux-gnu-libgfortran3.tar.gz/md5/316162aaa72c7b0938ea3f1e77b904a9 -OpenBLAS.v0.3.24+0.i686-linux-gnu-libgfortran3.tar.gz/sha512/b6ed223ab2ac32938d4283d7fd45a88565ef6d5b361ff28adfd7def4da8bc51895af26d3dff1104e472ae0681dac3af7ba4e75760e1c57e25b2a51599d8152c2 -OpenBLAS.v0.3.24+0.i686-linux-gnu-libgfortran4.tar.gz/md5/6dc4ceeb488810cd8e6e90b05743764f -OpenBLAS.v0.3.24+0.i686-linux-gnu-libgfortran4.tar.gz/sha512/c0a13c43667218fe1ffb4e19fc74ca5b25dbccb74fb57e193fc9343e8659731664e60c1c7a50c243a0825ba8747c97729f2a68bb36421ec6ba646c4f0c507219 -OpenBLAS.v0.3.24+0.i686-linux-gnu-libgfortran5.tar.gz/md5/124d38befd30a1fba95048612eb20911 -OpenBLAS.v0.3.24+0.i686-linux-gnu-libgfortran5.tar.gz/sha512/009d3217b990ec40729db4313526a4dcd977760fde4878c5fd420cce9649f367f348cc1d9f480057ff87b59545421935c7494f9fa7646206f8d114fede13051c -OpenBLAS.v0.3.24+0.i686-linux-musl-libgfortran3.tar.gz/md5/908d7ebace63754a7842e7d1dd338a43 -OpenBLAS.v0.3.24+0.i686-linux-musl-libgfortran3.tar.gz/sha512/8ab5364d7b3a445390b07dad5046d6e428fddf0eeb0b3b36f630b8a3e428fb8544d1a8fb493ba429cd3683dbe70e0a476333d322fe233cec356895b7a94c5670 -OpenBLAS.v0.3.24+0.i686-linux-musl-libgfortran4.tar.gz/md5/cf70732029bae0fc4f87713d75faad9c -OpenBLAS.v0.3.24+0.i686-linux-musl-libgfortran4.tar.gz/sha512/fea55b3aa2d6a8282345d710957401ad9d6350c57bf39d695173f3996c886a05d7f2672f3767397f6e0878c405117bd650923b7de5313fcd855b4846325b22e8 -OpenBLAS.v0.3.24+0.i686-linux-musl-libgfortran5.tar.gz/md5/cb4f6f8c7175c9d2159c213c37f71d86 -OpenBLAS.v0.3.24+0.i686-linux-musl-libgfortran5.tar.gz/sha512/0e92168e196d271105d8d267ed2329ab8b213375ca67539dd91a293576842ed13755f9e21536a48332d0c55e9323349f7c96817e5e80ced06986c8ad1e47a8fc -OpenBLAS.v0.3.24+0.i686-w64-mingw32-libgfortran3.tar.gz/md5/54b061bb2191fdbb39cb8103615d70bd -OpenBLAS.v0.3.24+0.i686-w64-mingw32-libgfortran3.tar.gz/sha512/b45fa1d1f5a8556f0a56f81e602457d98528d2b7b6dccbbad93cedcc3cecbe1a31d3f9543161ae801d0bdbb1413a9a70589ba6f221dc5d81fd3fc17e6e536f7f -OpenBLAS.v0.3.24+0.i686-w64-mingw32-libgfortran4.tar.gz/md5/8590055b4fc585317a2f36fbce1c2a50 -OpenBLAS.v0.3.24+0.i686-w64-mingw32-libgfortran4.tar.gz/sha512/47bc23c59e314606d45e252dedd7ec228c36bf25cf1cc19d64ca2b862407eede5410a6e7594881d8dc263aab0d140bfdde3082d57fa41ea038c286467d8354a3 -OpenBLAS.v0.3.24+0.i686-w64-mingw32-libgfortran5.tar.gz/md5/32120e6d84ff84bd859e080e4f124986 -OpenBLAS.v0.3.24+0.i686-w64-mingw32-libgfortran5.tar.gz/sha512/7ddada5781415099d6b1f198d27277d91a36f6850d957446ccfa7b8b2b38594b18de3ce5b1c7861c97e66897a7df1500531ac2c3ad12c597c7548def1db05fef -OpenBLAS.v0.3.24+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/d7dbb78769e588175612a939ce69c32b -OpenBLAS.v0.3.24+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/a1894b94dcf26d5da9da8ad119b3ecde8e6bf6d3a9d97286c1e405a059ec999a15e69ac6f497b170b485d64d6b324b4f38eb59984ec6b20d5aa6b881be819c4d -OpenBLAS.v0.3.24+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/acda753318cb352a2581c3ebc49d6d42 -OpenBLAS.v0.3.24+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/82bda1158b223fa899b7c9350b2abf0a7b693de75f928e48698ce9555998fc98d357b1b63ac8ca18d935636812141f0ceb58f51c2a1f48faec60cc8c0ca85126 -OpenBLAS.v0.3.24+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/abbd21e61746ec41b03c1efb5dfb884c -OpenBLAS.v0.3.24+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/7b284db00a9ab39b222caa9d4d6f3bcff3cc86560e275b7bac68c8c06c0cbe1c39226f381fba9fa90f52b8273e0c5436897c9ae0507482eb62bdeacc39ef3d35 -OpenBLAS.v0.3.24+0.x86_64-apple-darwin-libgfortran3.tar.gz/md5/36258636e985f73ab926fd4ee0e4cccb -OpenBLAS.v0.3.24+0.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/a53aa38afdb9ca13e14b3420f63c728ad83451a4c7bcda53a9944ff5488c6455367db6f8253983fbe174fd408513f2a48e7fec5d184900113165f2e82a812e7e -OpenBLAS.v0.3.24+0.x86_64-apple-darwin-libgfortran4.tar.gz/md5/780f66fc5707839a98c7bdad0432975b -OpenBLAS.v0.3.24+0.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/52957cdab20b7042e844468eb6fab822937636346807469decfdb6cfb43cd6d8cc479e3e58f0259d593f120ad5ad330006a470c390bfdafbf4865ee04d3fc401 -OpenBLAS.v0.3.24+0.x86_64-apple-darwin-libgfortran5.tar.gz/md5/83e26fedfa1f84a0a13c92e10b9c12f7 -OpenBLAS.v0.3.24+0.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/7a5eddc7486d46f2ef7b285d8d25ed17e0cf6a00c9c74784044b086b0063f0494bbdd28197a6f933f32345167e337fe62400f5d6b76d69f578aeb3a37ab56069 -OpenBLAS.v0.3.24+0.x86_64-linux-gnu-libgfortran3.tar.gz/md5/d4b83cbbd5a06eb84edd40e10cea9c78 -OpenBLAS.v0.3.24+0.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/28b86fbaad0adae2efe39f086f573e19b1f4a1db16f48144cb1ed4212013b82a5a22dabb75c7bb8c2b5f465ea0bfbf413af7e2fac75884baef1a2cfd7dfb0ceb -OpenBLAS.v0.3.24+0.x86_64-linux-gnu-libgfortran4.tar.gz/md5/2be815ebba026781801af79447f85383 -OpenBLAS.v0.3.24+0.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/2d86efa35a0b29e1b96d4063031e64c823440aca01739d2020856e825da22cf9044561f11ebf447674ced34c697c8b43207fe324f64c42c1c62f2edf869698e3 -OpenBLAS.v0.3.24+0.x86_64-linux-gnu-libgfortran5.tar.gz/md5/235767c8f001d56bb8c9e2b4694e94fe -OpenBLAS.v0.3.24+0.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/f326fed57de6017cf1de0e1d2806af9bc0b2897377611472f051634dd862fb3d964131e02f04d7b741a64c8e72241399716510e70132f802d046f54b1d170a35 -OpenBLAS.v0.3.24+0.x86_64-linux-musl-libgfortran3.tar.gz/md5/b202b8750e1f69099875a83be96683a1 -OpenBLAS.v0.3.24+0.x86_64-linux-musl-libgfortran3.tar.gz/sha512/c83a2c0a977b3afbc6fbe408b07888750a89e8dd66e65e1563d609296214b5114cf0b97009e4dea65a2579377d14ff1ef281c20dc327154d8635c23d50261263 -OpenBLAS.v0.3.24+0.x86_64-linux-musl-libgfortran4.tar.gz/md5/63cf99543c8c7d741808775d0e2b133c -OpenBLAS.v0.3.24+0.x86_64-linux-musl-libgfortran4.tar.gz/sha512/5a850adffb7cc1026b81c1780e3ff97875b29e5f794a33e9d1b3feb41d45eb25c9867c4c9e1699d0d78884ed93b32fb90d9ae8b48000939af7ecbe0d53793ac5 -OpenBLAS.v0.3.24+0.x86_64-linux-musl-libgfortran5.tar.gz/md5/2c34974e3d5ae02dcdbb8c091dd39e2f -OpenBLAS.v0.3.24+0.x86_64-linux-musl-libgfortran5.tar.gz/sha512/ec27f0891b9d56fe0ab86811c7a3043a6bee6518952e6ec58d00313cb6de858c8eaa7918692876e3bc8a0e3aac5f5ec563f612af9eacca47ebbdd4bea1153a0e -OpenBLAS.v0.3.24+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/85cbc32584528f6e5f1573eee0815f41 -OpenBLAS.v0.3.24+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/e7d48088e65b74a0e5c4a0714c97ce379ba4edc1f0f9cdf733a41da1a51fcdb2231589ac92d71a28605526c1408fb3eec7bb1f1d914e67cacd37425ce22c450e -OpenBLAS.v0.3.24+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/ad6c269fa00c998ea49311656afbf8cd -OpenBLAS.v0.3.24+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/604067b4b827395cdb7802f88a850e023acfee7e642669567eda0d1ed9612bdf968af4f5e43a25e6b3a213fd118bf3a49cd7a02dc2ef019ee853e31bf4316664 -OpenBLAS.v0.3.24+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/e5f233a4f87cec0c18b7a47073d9ea4d -OpenBLAS.v0.3.24+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/04fda9400f11948ec4f4c088096dd2971b4fbe2c513c755515bee757903f50c3246a5076f1fa095aae26092b0a76847ee1db4c972e235cd154af188600c387d8 -OpenBLAS.v0.3.24+0.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/0134d022448a5cc11f0b92b37600f7ce -OpenBLAS.v0.3.24+0.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/8da81604e96bb917d6300242709056ddd9c07232a0e7a1862cb1972e8ec631af093e3ddad8538e352295de78c19387dcb9ad7eb163c40fe715a8129855fe1dfb -OpenBLAS.v0.3.24+0.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/7f79751f5fb9713152410e665061872d -OpenBLAS.v0.3.24+0.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/50d836e6aac10e3bd181b87946917d77aacfc74bba3d973c12e94dcdac946bcc8d58fb079838784812c27c581c46fb5485bc6e0a192287fc11df46fb7674ec17 -OpenBLAS.v0.3.24+0.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/8b13dbb97a45754c418994d522eee197 -OpenBLAS.v0.3.24+0.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/1883a4b88cede205def3c29d82773379035edc3b04b8738197137a00bcf95726ca689866f5fc1a25914ea09e3fa2775435f8b4fff95738f7fda3857bcd44653d +OpenBLAS.v0.3.25+0.aarch64-apple-darwin-libgfortran5.tar.gz/md5/1acfa1d2dbebaf274812e36b59cdd0f7 +OpenBLAS.v0.3.25+0.aarch64-apple-darwin-libgfortran5.tar.gz/sha512/4cf10b1e5a791dcaea814f4f34b328499405b39c68ed652569e3a070f14c51b935e18d04bc10440b19967a40d21d7ef4c061817a5e6abf1403613e25cd45ef2f +OpenBLAS.v0.3.25+0.aarch64-linux-gnu-libgfortran3.tar.gz/md5/bf8cb41b65e7b60015b67727adca5c50 +OpenBLAS.v0.3.25+0.aarch64-linux-gnu-libgfortran3.tar.gz/sha512/710646a8e7bcf1cb0320777006d328b20f90463dac29f37b0c0472d22a8e00e49bd8aad1a0e71772279bf452e5daef9a6e9b51a5ea2af806fcdcae2999039108 +OpenBLAS.v0.3.25+0.aarch64-linux-gnu-libgfortran4.tar.gz/md5/60ff3026347c9a53f88cc90fd0f29950 +OpenBLAS.v0.3.25+0.aarch64-linux-gnu-libgfortran4.tar.gz/sha512/1dd600ac2b315a051c84529e4653aad77d43c228538ebe94c36b6f68ed14a15c5d7cfad17b49eeba4ce0ec857d7199b767c241a5e9863f7c60d5f516020a8d20 +OpenBLAS.v0.3.25+0.aarch64-linux-gnu-libgfortran5.tar.gz/md5/5b57d42de0fa82c123f792adb6835195 +OpenBLAS.v0.3.25+0.aarch64-linux-gnu-libgfortran5.tar.gz/sha512/e534b6c26100a8c841a1a81dee75cd2ae8c34f5bc3ddcd6b5cc9afdc21d46419459046371fa448c5b9c6d02eeccfb64d10c887c53b52c4744e809c50d3b7d41d +OpenBLAS.v0.3.25+0.aarch64-linux-musl-libgfortran3.tar.gz/md5/d2417bfa94754ca621c875b926dadd66 +OpenBLAS.v0.3.25+0.aarch64-linux-musl-libgfortran3.tar.gz/sha512/249b95d0502f6f82ac7151a38380c8a6cf9314e856a9f545cc086b14eaa3f7804ff24eb8b3430144c3add912aeec8fbfaee40fe30e4245fcafec3e9df3a73484 +OpenBLAS.v0.3.25+0.aarch64-linux-musl-libgfortran4.tar.gz/md5/d68bee54d083f93fa9a96ccaa0b10d6c +OpenBLAS.v0.3.25+0.aarch64-linux-musl-libgfortran4.tar.gz/sha512/909d9d4a9581899c564f6a42f325e50ba7b82f1bf7d2912a9d47cc88e8e13ddda6dff8a40680720f8d0a4a1973f6b6c48ffd0ec51d0323cb08cc73268386a61c +OpenBLAS.v0.3.25+0.aarch64-linux-musl-libgfortran5.tar.gz/md5/f103f1d84b9bbac3d73d70efa79f8f8a +OpenBLAS.v0.3.25+0.aarch64-linux-musl-libgfortran5.tar.gz/sha512/ad1bbfbfce09e7a7eb6d33e87f432530a3347d68b033d877029f8da9e290b129b2ab887eac510b27c9a1d9506679fb3e7e554dc0accfa05c5c950ae40c1501e6 +OpenBLAS.v0.3.25+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/md5/8d92bf601f216b0512e1eb9bb0f4689e +OpenBLAS.v0.3.25+0.armv6l-linux-gnueabihf-libgfortran3.tar.gz/sha512/8abd7f22db9d739c90df3c512ec4958989135e894d45ee91bf364e4c4a07b3dd8640351e596ef4b62f314bcf0ed22a35036499756611f5db1f98bdf1a3130033 +OpenBLAS.v0.3.25+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/md5/c56a5861637867ca4d0a88e2d71e0301 +OpenBLAS.v0.3.25+0.armv6l-linux-gnueabihf-libgfortran4.tar.gz/sha512/301a30c2d5c1a6355516a0520caadf83f1e121e95480bbaf3c39baaedfb10902adea826b289f0e5411ec9bb12eeb0a63fb3630b1ef3780304689a2d2bb97add8 +OpenBLAS.v0.3.25+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/md5/6c8f6582db03396ed30b41deeb706877 +OpenBLAS.v0.3.25+0.armv6l-linux-gnueabihf-libgfortran5.tar.gz/sha512/22e2c5ba0a9d6fe1dea222d24fe5da9b208525656185945a8ef90e201592e8c2692571bde7824651dde50850b0a81f25b5dfd8d4283a097d2f6193507128b587 +OpenBLAS.v0.3.25+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/md5/546e7ec9501a6687784c9285b70cf2d4 +OpenBLAS.v0.3.25+0.armv6l-linux-musleabihf-libgfortran3.tar.gz/sha512/8d306c18e810a22b637c5fa2f35b791b44aec67198b1a2632ff245cf5519cf138d2074eea84e85a348075b77b90eb5ff6342a920e66e508501eefd6c8cabfb6b +OpenBLAS.v0.3.25+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/md5/494c5b1a2df492723aedc05301958606 +OpenBLAS.v0.3.25+0.armv6l-linux-musleabihf-libgfortran4.tar.gz/sha512/01d413b45df28b6c262d2e1c1859a499d10e24cbf32c8954b5fe5f66c6cbc13e113e7834db304beee02a31228f2c5c1bb83e83455551b63cc972cf0d604db843 +OpenBLAS.v0.3.25+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/md5/0e14407a456d8354d4759140b8c2dcab +OpenBLAS.v0.3.25+0.armv6l-linux-musleabihf-libgfortran5.tar.gz/sha512/38b045a701d0dc1cc161877944a6db5154b36437d4122460727f489b05b902a7766e760ace5331a4056f804a8008fb3e123c905adae45839d1eeeadf1f209116 +OpenBLAS.v0.3.25+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/md5/8d92bf601f216b0512e1eb9bb0f4689e +OpenBLAS.v0.3.25+0.armv7l-linux-gnueabihf-libgfortran3.tar.gz/sha512/8abd7f22db9d739c90df3c512ec4958989135e894d45ee91bf364e4c4a07b3dd8640351e596ef4b62f314bcf0ed22a35036499756611f5db1f98bdf1a3130033 +OpenBLAS.v0.3.25+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/md5/c56a5861637867ca4d0a88e2d71e0301 +OpenBLAS.v0.3.25+0.armv7l-linux-gnueabihf-libgfortran4.tar.gz/sha512/301a30c2d5c1a6355516a0520caadf83f1e121e95480bbaf3c39baaedfb10902adea826b289f0e5411ec9bb12eeb0a63fb3630b1ef3780304689a2d2bb97add8 +OpenBLAS.v0.3.25+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/md5/6c8f6582db03396ed30b41deeb706877 +OpenBLAS.v0.3.25+0.armv7l-linux-gnueabihf-libgfortran5.tar.gz/sha512/22e2c5ba0a9d6fe1dea222d24fe5da9b208525656185945a8ef90e201592e8c2692571bde7824651dde50850b0a81f25b5dfd8d4283a097d2f6193507128b587 +OpenBLAS.v0.3.25+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/md5/546e7ec9501a6687784c9285b70cf2d4 +OpenBLAS.v0.3.25+0.armv7l-linux-musleabihf-libgfortran3.tar.gz/sha512/8d306c18e810a22b637c5fa2f35b791b44aec67198b1a2632ff245cf5519cf138d2074eea84e85a348075b77b90eb5ff6342a920e66e508501eefd6c8cabfb6b +OpenBLAS.v0.3.25+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/md5/494c5b1a2df492723aedc05301958606 +OpenBLAS.v0.3.25+0.armv7l-linux-musleabihf-libgfortran4.tar.gz/sha512/01d413b45df28b6c262d2e1c1859a499d10e24cbf32c8954b5fe5f66c6cbc13e113e7834db304beee02a31228f2c5c1bb83e83455551b63cc972cf0d604db843 +OpenBLAS.v0.3.25+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/md5/0e14407a456d8354d4759140b8c2dcab +OpenBLAS.v0.3.25+0.armv7l-linux-musleabihf-libgfortran5.tar.gz/sha512/38b045a701d0dc1cc161877944a6db5154b36437d4122460727f489b05b902a7766e760ace5331a4056f804a8008fb3e123c905adae45839d1eeeadf1f209116 +OpenBLAS.v0.3.25+0.i686-linux-gnu-libgfortran3.tar.gz/md5/fb62eeaa26adad79f27a4871abe4c8f2 +OpenBLAS.v0.3.25+0.i686-linux-gnu-libgfortran3.tar.gz/sha512/1712755c80f7d48052fdc2ffd254b745ddd5d9a90bcf2f25532448d258317664895efdd8b92054a17271349e6371449b3dbf1cbe44d2bab2b2a11e77a0f8207e +OpenBLAS.v0.3.25+0.i686-linux-gnu-libgfortran4.tar.gz/md5/bbb096eb2294179d4bbac42fa08dfa04 +OpenBLAS.v0.3.25+0.i686-linux-gnu-libgfortran4.tar.gz/sha512/6de33c7a98077c82d2d730b2a3046f9d84cf779c46527e70b4e862c2b51b7487d3b5d66eb1693235ab19bb9b254f85677707bfe496ca96e8b2f9840413d52d86 +OpenBLAS.v0.3.25+0.i686-linux-gnu-libgfortran5.tar.gz/md5/25e6e7f428ca9d817ce06dfd1ce440d7 +OpenBLAS.v0.3.25+0.i686-linux-gnu-libgfortran5.tar.gz/sha512/3d7d12c9de5d435fd7d9b8919c6b511d3a825e02d9d303a2222f16d13f7c93b620e6099fb290cd36648f51952c187b9100753e107530be2d40c1e006f44ac453 +OpenBLAS.v0.3.25+0.i686-linux-musl-libgfortran3.tar.gz/md5/0f09043138753743b33bdc1598ea2f98 +OpenBLAS.v0.3.25+0.i686-linux-musl-libgfortran3.tar.gz/sha512/a43510a70e63b534812fb0213af75743c0c78f8d153e61cfa0b3ec0b5a51172db3d3ca5b40429ccfd2fe99eb730db661566d61f825f0c97d079f75933ab17385 +OpenBLAS.v0.3.25+0.i686-linux-musl-libgfortran4.tar.gz/md5/b8c78d226bf548306cb0de09c296fcfd +OpenBLAS.v0.3.25+0.i686-linux-musl-libgfortran4.tar.gz/sha512/165b85271b686da7daad21e2d3be683efcf8beb3208d2ba73e753cdc8405236c673ceb5713f84131a5b44045721c98de70242f600f7823c1597d38156f692ca6 +OpenBLAS.v0.3.25+0.i686-linux-musl-libgfortran5.tar.gz/md5/1f508ddbb3ef7aff7880b34a8caeb032 +OpenBLAS.v0.3.25+0.i686-linux-musl-libgfortran5.tar.gz/sha512/9f603451d957483b5be45dd8d705f0a45f0fa3fa4e32d9431a15b7dc986fbce200d7d8966717564c588ee3a367c949342b63f03b81de387bb90c691cb3b0d2a5 +OpenBLAS.v0.3.25+0.i686-w64-mingw32-libgfortran3.tar.gz/md5/e85605ee1a4d33d43a2fb2e516eaa99f +OpenBLAS.v0.3.25+0.i686-w64-mingw32-libgfortran3.tar.gz/sha512/98512575e60b34df6fe0a2d3f4a0a57d8f9ef1ef7a65b014427776104633acffbbd48bdb8aecdeb54dca1bdee39e1efd6f677dd68028911f2dfbe55f1180f054 +OpenBLAS.v0.3.25+0.i686-w64-mingw32-libgfortran4.tar.gz/md5/673c70c7f29dab76a154b5171f6112a2 +OpenBLAS.v0.3.25+0.i686-w64-mingw32-libgfortran4.tar.gz/sha512/4ec6313ddbdcf3a98d48bc8510d5c120fb3232433738a28e9ccd9beec1e2a0ef401c625b0b97702cd7be7fd6061e2f8ed48f35ae36b46495d952053e0accaaae +OpenBLAS.v0.3.25+0.i686-w64-mingw32-libgfortran5.tar.gz/md5/7242fefbdb29d0ef5f08f39590fb204d +OpenBLAS.v0.3.25+0.i686-w64-mingw32-libgfortran5.tar.gz/sha512/be253db33b2345388550d3e82b8cea4e9416874df5e01b24e96796faace87440178a10e93a47d228b4e31e21754de8b42a067e69aba8ab6267a19e7197f3d54c +OpenBLAS.v0.3.25+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/md5/1fd83d8f353195c7ae1a1fa9ea266171 +OpenBLAS.v0.3.25+0.powerpc64le-linux-gnu-libgfortran3.tar.gz/sha512/0cb87fb1dbe6af1f1aef1bb3e346078a9de39d2ef33a51c99c0fafe67cdb494b1aae567429a9bec8fdfd41c651afe63815b7ecec20ea95fb3d0bedf06eb78188 +OpenBLAS.v0.3.25+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/md5/f2a4141dea91feef792036dd149e552d +OpenBLAS.v0.3.25+0.powerpc64le-linux-gnu-libgfortran4.tar.gz/sha512/58dc721598f36380c65f34cb5d3efeb77bb03605afb82bb08689674893cb399af34dde47002105f072422d031df8f629fbfb4f463a9900c3b09fd6e98b3d007b +OpenBLAS.v0.3.25+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/md5/f472322bd6f463f335d7b9c5b2ae69be +OpenBLAS.v0.3.25+0.powerpc64le-linux-gnu-libgfortran5.tar.gz/sha512/b9530287d7972b599535b4d78a2540e00a49664660c077cf7c50ebcc86e79fb7fb2e6ddd2d1f3e80c1f4f6d6094c4f4a0641e3112994f7b770ac14868c704ec0 +OpenBLAS.v0.3.25+0.x86_64-apple-darwin-libgfortran3.tar.gz/md5/28c498d970d4c659d47981f915448a2e +OpenBLAS.v0.3.25+0.x86_64-apple-darwin-libgfortran3.tar.gz/sha512/f5a5d9d98013b39b26134910ba9e2cbd23caf1bfca1cb9867e972753081cafd90edfa5089747d94b87368aa4cecffcb40314326b82a47ddb4eafa3a63437482e +OpenBLAS.v0.3.25+0.x86_64-apple-darwin-libgfortran4.tar.gz/md5/2888ac4b4ad4f693b652c0054187360b +OpenBLAS.v0.3.25+0.x86_64-apple-darwin-libgfortran4.tar.gz/sha512/0292f8d1ecbc5b357c1b9891944831cf652d1937d62a4d7486dc5c4dea3a9fa54bd1f0d76ba5553fe009d09e68edb79ee1310ac2b296ba61e33ea1ed794f4713 +OpenBLAS.v0.3.25+0.x86_64-apple-darwin-libgfortran5.tar.gz/md5/f3868395464c24b87fe543a630799cb8 +OpenBLAS.v0.3.25+0.x86_64-apple-darwin-libgfortran5.tar.gz/sha512/4790f559458a301cb4ca9ce11bb2004b2bc0a72f7cf88c7dc45572f7c56adb154f7ab76a72e463ac0871019248e4b6ed4129356ab036bc71bee3700235f9f805 +OpenBLAS.v0.3.25+0.x86_64-linux-gnu-libgfortran3.tar.gz/md5/83fde9ea432e7c87413ebedea4062070 +OpenBLAS.v0.3.25+0.x86_64-linux-gnu-libgfortran3.tar.gz/sha512/00428ed95ef6dd1191911e768d929d9a511f34ad1f098c6311df7f2176fb82d67e763326da95fc390ec05854a7a1482c3e2e4efd4b1975653c45a825b4b3ef68 +OpenBLAS.v0.3.25+0.x86_64-linux-gnu-libgfortran4.tar.gz/md5/415f468e44c3762451b39596eb6dd45d +OpenBLAS.v0.3.25+0.x86_64-linux-gnu-libgfortran4.tar.gz/sha512/bda9c30547cc219fdc00d3854ad02708d225a46850c7a95b786b527722e75185787ab2353185007cb9f692a4cd5a3c38d64018a46ee4d400df9d4612371a2a9b +OpenBLAS.v0.3.25+0.x86_64-linux-gnu-libgfortran5.tar.gz/md5/7858a2a1c13b6707f6bee3b021811728 +OpenBLAS.v0.3.25+0.x86_64-linux-gnu-libgfortran5.tar.gz/sha512/0a0e4995652e9459dd54325ee92262261244ace5f065f293459ad91433345efbbdf1e51996ae2629968498a8fc34b0fb64fadde7bc36498a48eeefafde987d5d +OpenBLAS.v0.3.25+0.x86_64-linux-musl-libgfortran3.tar.gz/md5/30f5deef5223ee198acb8c213a7c89b1 +OpenBLAS.v0.3.25+0.x86_64-linux-musl-libgfortran3.tar.gz/sha512/a933e33e6070475450312d0608626811bae98e129adb30d84cf5a3c7d525217e805b0ffb88490931a927b1fda1c9479af953282e47af1f469f993d4d2150da95 +OpenBLAS.v0.3.25+0.x86_64-linux-musl-libgfortran4.tar.gz/md5/86fd8946da2bd4fb97f54c8df5883d5e +OpenBLAS.v0.3.25+0.x86_64-linux-musl-libgfortran4.tar.gz/sha512/d61187e632a0d49c89854d441b9b42fcd9dc7517f4f38514cd17f599c2f9ad3600054bf8457d0e9fb766fe9c7d0c248cd3e5383be2600ac2c538f2d524a8e1de +OpenBLAS.v0.3.25+0.x86_64-linux-musl-libgfortran5.tar.gz/md5/d95539fa91c80db375310ed09d191379 +OpenBLAS.v0.3.25+0.x86_64-linux-musl-libgfortran5.tar.gz/sha512/ab786856a1c4ab47808e9ea2167c6ae42e69d6a11e539b7771464bbc0c7bb1d5482ed2c3c0154fae37bfb1579103e862637675e4d81726d4e3ed633cd7548cd9 +OpenBLAS.v0.3.25+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/md5/09ea7329e39dac562e45f00e6b135ad6 +OpenBLAS.v0.3.25+0.x86_64-unknown-freebsd-libgfortran3.tar.gz/sha512/c0b8b3b6f7c9a326b857e218014bc232165122865c18811bc5c9af28b54c494a4a0ed139101cdf30ff5e8b03f9e8bb4ce32865ab5ba4e5251f841a2bf612eb86 +OpenBLAS.v0.3.25+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/md5/f874721e0ee45094c62cf4a006c5309c +OpenBLAS.v0.3.25+0.x86_64-unknown-freebsd-libgfortran4.tar.gz/sha512/03d57202ef4209f23f83a5faef01c772c2c6b04085d8448b835eec793b50b9f2f13dec2ba1d0a7ac55f6be7df8231a04163ac6d1fccb91976e965fd10bc0e3f1 +OpenBLAS.v0.3.25+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/md5/69de0e673292ccaf3294e785a96cc22b +OpenBLAS.v0.3.25+0.x86_64-unknown-freebsd-libgfortran5.tar.gz/sha512/9ff1dd63a62d5f0d771833a7872d0abdf6e2307aabf57d7b5f1469f75aeda99525beb5a4c2f311cef0b8af3905daab321435b613c9e046a7d7c5c30e2548ba0b +OpenBLAS.v0.3.25+0.x86_64-w64-mingw32-libgfortran3.tar.gz/md5/2cd93681be2f2f781285ca7dbef7eba1 +OpenBLAS.v0.3.25+0.x86_64-w64-mingw32-libgfortran3.tar.gz/sha512/3163b20884e9be0a6d99735c400ca58f52f097fbbcb396be0ce9c29b3d4adebe5cee4ddb91f095a692e1f812c5558690a603bd455c5576c374e7c49e6cfaac7b +OpenBLAS.v0.3.25+0.x86_64-w64-mingw32-libgfortran4.tar.gz/md5/751aff51cf7674bcb68fe3bb94137603 +OpenBLAS.v0.3.25+0.x86_64-w64-mingw32-libgfortran4.tar.gz/sha512/b5fa7477f9033f5e55214fb4e0bd3bf923e2c580c6582b7241739237dc50d269448459efebf75da82415ceb2f547033210f118d9b8c9ab2585fc792253331309 +OpenBLAS.v0.3.25+0.x86_64-w64-mingw32-libgfortran5.tar.gz/md5/de458200baed33ba568f8222a9b72865 +OpenBLAS.v0.3.25+0.x86_64-w64-mingw32-libgfortran5.tar.gz/sha512/d6acccb3a10e3eb01be2aacb4d77f084f022fef3ca3bbf05530cae5c13224b4ab12b62e8e8a3ce1ef6ab0b2cca9e88c687794a8620fe0af1c478323976268d5a openblas-9f815cf1bf16b4e64d4aee681b33558fc090b62a.tar.gz/md5/d890367a6452aa209f396359fb770bc7 openblas-9f815cf1bf16b4e64d4aee681b33558fc090b62a.tar.gz/sha512/0627960fc2582e08fd039c77aab1f4105f18edb1beba52176a791813bb3f1ebb04f52d174814e156826c45c6adf5f3c1d2242e81fdb520a7fff2170cb1277228 diff --git a/deps/openblas.version b/deps/openblas.version index 9790ad1718e78a..7b16df95c9c7f8 100644 --- a/deps/openblas.version +++ b/deps/openblas.version @@ -3,9 +3,9 @@ OPENBLAS_JLL_NAME := OpenBLAS ## source build -OPENBLAS_VER := 0.3.24 -OPENBLAS_BRANCH=v0.3.24 -OPENBLAS_SHA1=9f815cf1bf16b4e64d4aee681b33558fc090b62a +OPENBLAS_VER := 0.3.25 +OPENBLAS_BRANCH=v0.3.25 +OPENBLAS_SHA1=5e1a429eab44731b6668b8f6043c1ea951b0a80b # LAPACK, source-only LAPACK_VER := 3.9.0 diff --git a/stdlib/OpenBLAS_jll/Project.toml b/stdlib/OpenBLAS_jll/Project.toml index ad5041cf4ac817..99a75366e24a75 100644 --- a/stdlib/OpenBLAS_jll/Project.toml +++ b/stdlib/OpenBLAS_jll/Project.toml @@ -1,6 +1,6 @@ name = "OpenBLAS_jll" uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.24+0" +version = "0.3.25+0" [deps] # See note in `src/OpenBLAS_jll.jl` about this dependency. From 8af47bd585fc81ec373167b3ca02010177e7ccf8 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Fri, 17 Nov 2023 23:58:49 +0900 Subject: [PATCH 09/38] irinterp: check if `IRCode` has been compacted (#52203) Adds assertion that checks if `IRCode` passed to irinterp or post-opt analysis has been compacted. And using the invariant from the assertion, this commit also propagates `inst::Instruction` for `scan!` callbacks instead of `stmt` and removes `idx::Int` argument since it is redundant with `inst.idx`. --- base/compiler/optimize.jl | 14 +++++++----- base/compiler/ssair/irinterp.jl | 40 +++++++++++++++++---------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index e974a23d056511..5f4faa4933e0af 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -602,7 +602,7 @@ function scan_non_dataflow_flags!(inst::Instruction, sv::PostOptAnalysisState) end end -function scan_inconsistency!(inst::Instruction, idx::Int, sv::PostOptAnalysisState) +function scan_inconsistency!(inst::Instruction, sv::PostOptAnalysisState) flag = inst[:flag] stmt_inconsistent = iszero(flag & IR_FLAG_CONSISTENT) stmt = inst[:stmt] @@ -625,7 +625,7 @@ function scan_inconsistency!(inst::Instruction, idx::Int, sv::PostOptAnalysisSta end end end - stmt_inconsistent && push!(inconsistent, idx) + stmt_inconsistent && push!(inconsistent, inst.idx) return stmt_inconsistent end @@ -633,7 +633,7 @@ struct ScanStmt sv::PostOptAnalysisState end -function ((; sv)::ScanStmt)(inst::Instruction, idx::Int, lstmt::Int, bb::Int) +function ((; sv)::ScanStmt)(inst::Instruction, lstmt::Int, bb::Int) stmt = inst[:stmt] if isexpr(stmt, :enter) @@ -644,9 +644,9 @@ function ((; sv)::ScanStmt)(inst::Instruction, idx::Int, lstmt::Int, bb::Int) scan_non_dataflow_flags!(inst, sv) - stmt_inconsistent = scan_inconsistency!(inst, idx, sv) + stmt_inconsistent = scan_inconsistency!(inst, sv) - if stmt_inconsistent && idx == lstmt + if stmt_inconsistent && inst.idx == lstmt if isa(stmt, ReturnNode) && isdefined(stmt, :val) sv.all_retpaths_consistent = false elseif isa(stmt, GotoIfNot) @@ -736,6 +736,8 @@ end function ipo_dataflow_analysis!(interp::AbstractInterpreter, ir::IRCode, result::InferenceResult) is_ipo_dataflow_analysis_profitable(result.ipo_effects) || return false + @assert isempty(ir.new_nodes) "IRCode should be compacted before post-opt analysis" + sv = PostOptAnalysisState(result, ir) scanner = BBScanner(ir) @@ -746,7 +748,7 @@ function ipo_dataflow_analysis!(interp::AbstractInterpreter, ir::IRCode, result: check_inconsistentcy!(sv, scanner) else # No longer any dataflow concerns, just scan the flags - scan!(scanner, false) do inst::Instruction, idx::Int, lstmt::Int, bb::Int + scan!(scanner, false) do inst::Instruction, lstmt::Int, bb::Int scan_non_dataflow_flags!(inst, sv) # bail out early if there are no possibilities to refine the effects if !any_refinable(sv) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 0e34cf9ce70bc5..c9c9eb2501f996 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -95,10 +95,10 @@ function kill_terminator_edges!(irsv::IRInterpretationState, term_idx::Int, bb:: end end -function reprocess_instruction!(interp::AbstractInterpreter, idx::Int, bb::Union{Int,Nothing}, - @nospecialize(stmt), @nospecialize(typ), irsv::IRInterpretationState) +function reprocess_instruction!(interp::AbstractInterpreter, inst::Instruction, idx::Int, + bb::Union{Int,Nothing}, irsv::IRInterpretationState) ir = irsv.ir - inst = ir[SSAValue(idx)] + stmt = inst[:stmt] if isa(stmt, GotoIfNot) cond = stmt.cond condval = maybe_extract_const_bool(argextype(cond, ir)) @@ -172,7 +172,7 @@ function reprocess_instruction!(interp::AbstractInterpreter, idx::Int, bb::Union inst[:stmt] = quoted(rt.val) end return true - elseif !โŠ‘(typeinf_lattice(interp), typ, rt) + elseif !โŠ‘(typeinf_lattice(interp), inst[:type], rt) inst[:type] = rt return true end @@ -226,7 +226,7 @@ function scan!(callback, scanner::BBScanner, forwards_only::Bool) lstmt = last(stmts) for idx = stmts inst = ir[SSAValue(idx)] - ret = callback(inst, idx, lstmt, bb) + ret = callback(inst, lstmt, bb) ret === nothing && return true ret::Bool || break idx == lstmt && process_terminator!(inst[:stmt], bb, bb_ip) && forwards_only && return false @@ -236,11 +236,11 @@ function scan!(callback, scanner::BBScanner, forwards_only::Bool) end function populate_def_use_map!(tpdum::TwoPhaseDefUseMap, scanner::BBScanner) - scan!(scanner, false) do inst::Instruction, idx::Int, lstmt::Int, bb::Int + scan!(scanner, false) do inst::Instruction, lstmt::Int, bb::Int for ur in userefs(inst) val = ur[] if isa(val, SSAValue) - push!(tpdum[val.id], idx) + push!(tpdum[val.id], inst.idx) end end return true @@ -255,6 +255,8 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR (; ir, tpdum, ssa_refined) = irsv + @assert isempty(ir.new_nodes) "IRCode should be compacted before irinterp" + all_rets = Int[] scanner = BBScanner(ir) @@ -263,15 +265,16 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR # Fast path: Scan both use counts and refinement in one single pass of # of the instructions. In the absence of backedges, this will # converge. - completed_scan = scan!(scanner, true) do inst::Instruction, idx::Int, lstmt::Int, bb::Int + completed_scan = scan!(scanner, true) do inst::Instruction, lstmt::Int, bb::Int + idx = inst.idx irsv.curridx = idx - stmt = ir.stmts[idx][:stmt] - typ = ir.stmts[idx][:type] - flag = ir.stmts[idx][:flag] + stmt = inst[:stmt] + typ = inst[:type] + flag = inst[:flag] any_refined = false if (flag & IR_FLAG_REFINED) != 0 any_refined = true - ir.stmts[idx][:flag] &= ~IR_FLAG_REFINED + inst[:flag] &= ~IR_FLAG_REFINED end for ur in userefs(stmt) val = ur[] @@ -292,11 +295,11 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR if typ === Bottom && !(idx == lstmt && is_terminator_or_phi) return true end - if (any_refined && reprocess_instruction!(interp, idx, bb, stmt, typ, irsv)) || + if (any_refined && reprocess_instruction!(interp, inst, idx, bb, irsv)) || (externally_refined !== nothing && idx in externally_refined) push!(ssa_refined, idx) - stmt = ir.stmts[idx][:stmt] - typ = ir.stmts[idx][:type] + stmt = inst[:stmt] + typ = inst[:type] end if typ === Bottom && !is_terminator_or_phi kill_terminator_edges!(irsv, lstmt, bb) @@ -316,7 +319,8 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR stmt_ip = BitSetBoundedMinPrioritySet(length(ir.stmts)) # Slow Path Phase 1.A: Complete use scanning - scan!(scanner, false) do inst::Instruction, idx::Int, lstmt::Int, bb::Int + scan!(scanner, false) do inst::Instruction, lstmt::Int, bb::Int + idx = inst.idx irsv.curridx = idx stmt = inst[:stmt] flag = inst[:flag] @@ -356,9 +360,7 @@ function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IR idx = popfirst!(stmt_ip) irsv.curridx = idx inst = ir[SSAValue(idx)] - stmt = inst[:stmt] - typ = inst[:type] - if reprocess_instruction!(interp, idx, nothing, stmt, typ, irsv) + if reprocess_instruction!(interp, inst, idx, nothing, irsv) append!(stmt_ip, tpdum[idx]) end end From 03bbf91902b5dbeb04d71fb02f4f168dd1c3c6df Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Sat, 18 Nov 2023 00:00:13 +0900 Subject: [PATCH 10/38] remove `switch_[to|from]_irinterp` mechanism (#52204) This seems to cause (probably not very profitable) union-splitting all over the place and complicate code generation. Let's just remove it. As a result, this would slightly reduce image sizes too. --- base/compiler/ssair/irinterp.jl | 2 -- base/compiler/typeinfer.jl | 1 - base/compiler/types.jl | 38 +++------------------------------ 3 files changed, 3 insertions(+), 38 deletions(-) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index c9c9eb2501f996..2d609d6dd3d088 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -251,8 +251,6 @@ populate_def_use_map!(tpdum::TwoPhaseDefUseMap, ir::IRCode) = function _ir_abstract_constant_propagation(interp::AbstractInterpreter, irsv::IRInterpretationState; externally_refined::Union{Nothing,BitSet} = nothing) - interp = switch_to_irinterp(interp) - (; ir, tpdum, ssa_refined) = irsv @assert isempty(ir.new_nodes) "IRCode should be compacted before irinterp" diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 29d3c01d151be9..fcb99446198315 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -243,7 +243,6 @@ function finish!(interp::AbstractInterpreter, caller::InferenceState) end function _typeinf(interp::AbstractInterpreter, frame::InferenceState) - interp = switch_from_irinterp(interp) typeinf_nocycle(interp, frame) || return false # frame is now part of a higher cycle # with no active ip's, frame is done frames = frame.callers_in_cycle diff --git a/base/compiler/types.jl b/base/compiler/types.jl index db1d85cdddc40f..dde5335b36f688 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -342,9 +342,6 @@ struct NativeInterpreter <: AbstractInterpreter # Parameters for inference and optimization inf_params::InferenceParams opt_params::OptimizationParams - - # a boolean flag to indicate if this interpreter is performing semi concrete interpretation - irinterp::Bool end function NativeInterpreter(world::UInt = get_world_counter(); @@ -364,7 +361,7 @@ function NativeInterpreter(world::UInt = get_world_counter(); inf_cache = Vector{InferenceResult}() # Initially empty cache - return NativeInterpreter(world, method_table, inf_cache, inf_params, opt_params, #=irinterp=#false) + return NativeInterpreter(world, method_table, inf_cache, inf_params, opt_params) end function NativeInterpreter(interp::NativeInterpreter; @@ -372,9 +369,8 @@ function NativeInterpreter(interp::NativeInterpreter; method_table::CachedMethodTable{InternalMethodTable} = interp.method_table, inf_cache::Vector{InferenceResult} = interp.inf_cache, inf_params::InferenceParams = interp.inf_params, - opt_params::OptimizationParams = interp.opt_params, - irinterp::Bool = interp.irinterp) - return NativeInterpreter(world, method_table, inf_cache, inf_params, opt_params, irinterp) + opt_params::OptimizationParams = interp.opt_params) + return NativeInterpreter(world, method_table, inf_cache, inf_params, opt_params) end # Quickly and easily satisfy the AbstractInterpreter API contract @@ -468,34 +464,6 @@ typeinf_lattice(::AbstractInterpreter) = InferenceLattice(BaseInferenceLattice.i ipo_lattice(::AbstractInterpreter) = InferenceLattice(IPOResultLattice.instance) optimizer_lattice(::AbstractInterpreter) = SimpleInferenceLattice.instance -typeinf_lattice(interp::NativeInterpreter) = interp.irinterp ? - InferenceLattice(SimpleInferenceLattice.instance) : - InferenceLattice(BaseInferenceLattice.instance) -ipo_lattice(interp::NativeInterpreter) = interp.irinterp ? - InferenceLattice(SimpleInferenceLattice.instance) : - InferenceLattice(IPOResultLattice.instance) -optimizer_lattice(interp::NativeInterpreter) = SimpleInferenceLattice.instance - -""" - switch_to_irinterp(interp::AbstractInterpreter) -> irinterp::AbstractInterpreter - -This interface allows `ir_abstract_constant_propagation` to convert `interp` to a new -`irinterp::AbstractInterpreter` to perform semi-concrete interpretation. -`NativeInterpreter` uses this interface to switch its lattice to `optimizer_lattice` during -semi-concrete interpretation on `IRCode`. -""" -switch_to_irinterp(interp::AbstractInterpreter) = interp -switch_to_irinterp(interp::NativeInterpreter) = NativeInterpreter(interp; irinterp=true) - -""" - switch_from_irinterp(irinterp::AbstractInterpreter) -> interp::AbstractInterpreter - -The inverse operation of `switch_to_irinterp`, allowing `typeinf` to convert `irinterp` back -to a new `interp::AbstractInterpreter` to perform ordinary abstract interpretation. -""" -switch_from_irinterp(irinterp::AbstractInterpreter) = irinterp -switch_from_irinterp(irinterp::NativeInterpreter) = NativeInterpreter(irinterp; irinterp=false) - abstract type CallInfo end @nospecialize From 113431532e24c999b89d93136974ead53cd2d534 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Sat, 18 Nov 2023 01:21:29 +0900 Subject: [PATCH 11/38] effects: taint :noub for memory operations (#52186) Currently our effects system does not track `:noub` about memory operations. While this sounds unsafe, it's actually not problematic in most contexts. We restrict concrete-evaluation for anything involing memory operations anyway, as mutable values aren't propagated as compile-time constants (except `Symbol`s). However, it turns out that this is actually causing very dangerous situations in REPL completion context, where `REPLInterpreter` enables aggressive concrete-evaluation that propagates mutable values as constants and ignores the `:consisten`-cy requirement that is usually tainted by memory operations. This commit addresses this issue by tainting `:noub` for memory operations. This commit ends up being somewhat extensive because it would cause significant regression in REPL completions if we make memory ops naively taint `:noub`. Complicating this further is the Base's uses of `boundscheck=false` for peak performance of core memory operations, where bounds checking is conducted separately before performing actual memory operations, e.g.: > essentials.jl ```julia function getindex(A::Array, i::Int) @boundscheck ult_int(bitcast(UInt, sub_int(i, 1)), bitcast(UInt, length(A))) || throw_boundserror(A, (i,)) memoryrefget(memoryref(getfield(A, :ref), i, false), :not_atomic, false) end ``` Here `boundscheck=false` should generally taint `:noub` immediately, but in this kind of case `:noub` can actually be `NOUB_IF_NOINBOUNDS` since it has `@boundscheck` that asserts safety of the memory ops. Note that we could employ a similar technique to `getfield` if we change the above implementation to something like: ```julia function getindex(A::Array, i::Int) memoryrefget(memoryref(getfield(A, :ref), i, @_boundscheck), :not_atomic, @_boundscheck) end ``` although this would end up being slower (check the difference at the code LLVM emits on each implementation if interested). To this end, this commit also introduces new `:noub_if_noinbounds` setting to `@assume_effects` and use it within the base implementation. Now `UInt8` is not enough to represent `EffectsOverride` information, the bit is enlarged to `UInt16`. While this approach might not be ideal, I don't think there is a better way to do this. An alternative I tried before making this commit was to introduce new semantics for `Expr(:boundscheck, ...)` where `Expr(:boundscheck, nothing)` will be constant folded to `false` during codegen while during inference time it does not taint `:noub`. I felt this further complicates the already complex complext `:boundscheck` situation, so I decided to go with the approach to expand the settings set of `@assume_effects`. --- base/array.jl | 28 ++++------ base/boot.jl | 3 +- base/c.jl | 2 +- base/compiler/abstractinterpretation.jl | 4 +- base/compiler/effects.jl | 46 ++++++++-------- base/compiler/optimize.jl | 35 ++++++++---- base/compiler/ssair/inlining.jl | 2 +- base/compiler/ssair/irinterp.jl | 6 +- base/compiler/tfuncs.jl | 41 +++++++++++++- base/compiler/typeinfer.jl | 2 + base/compiler/validation.jl | 2 - base/docs/intrinsicsdocs.jl | 17 +++--- base/essentials.jl | 62 ++++++++++++++------- base/expr.jl | 16 ++++-- base/genericmemory.jl | 11 ---- base/strings/string.jl | 2 +- base/tuple.jl | 3 +- src/common_symbols2.inc | 1 - src/ircode.c | 6 +- src/jltypes.c | 4 +- src/julia.h | 19 ++++--- src/method.c | 5 +- stdlib/REPL/test/replcompletions.jl | 9 +++ stdlib/Serialization/src/Serialization.jl | 15 +++-- test/compiler/effects.jl | 67 +++++++++++++++++++++++ 25 files changed, 277 insertions(+), 131 deletions(-) diff --git a/base/array.jl b/base/array.jl index d883c620428e4a..e90723957afdbc 100644 --- a/base/array.jl +++ b/base/array.jl @@ -124,8 +124,8 @@ const DenseVecOrMat{T} = Union{DenseVector{T}, DenseMatrix{T}} @_safeindex This internal macro converts: -- `getindex(xs::Tuple, )` -> `__inbounds_getindex(args...)` -- `setindex!(xs::Vector, args...)` -> `__inbounds_setindex!(xs, args...)` +- `getindex(xs::Tuple, i::Int)` -> `__safe_getindex(xs, i)` +- `setindex!(xs::Vector{T}, x, i::Int)` -> `__safe_setindex!(xs, x, i)` to tell the compiler that indexing operations within the applied expression are always inbounds and do not need to taint `:consistent` and `:nothrow`. """ @@ -143,10 +143,10 @@ function _safeindex(__module__, ex) for i = 2:length(lhs.args) args[i-1] = _safeindex(__module__, lhs.args[i]) end - return Expr(:call, GlobalRef(__module__, :__inbounds_setindex!), xs, _safeindex(__module__, rhs), args...) + return Expr(:call, GlobalRef(__module__, :__safe_setindex!), xs, _safeindex(__module__, rhs), args...) end elseif ex.head === :ref # xs[i] - return Expr(:call, GlobalRef(__module__, :__inbounds_getindex), ex.args...) + return Expr(:call, GlobalRef(__module__, :__safe_getindex), ex.args...) end args = Vector{Any}(undef, length(ex.args)) for i = 1:length(ex.args) @@ -236,6 +236,7 @@ sizeof(a::Array) = length(a) * elsize(typeof(a)) # n.b. this ignores bitsunion b function isassigned(a::Array, i::Int...) @inline + @_noub_if_noinbounds_meta @boundscheck checkbounds(Bool, a, i...) || return false ii = _sub2ind(size(a), i...) return @inbounds isassigned(memoryref(a.ref, ii, false)) @@ -243,6 +244,7 @@ end function isassigned(a::Vector, i::Int) # slight compiler simplification for the most common case @inline + @_noub_if_noinbounds_meta @boundscheck checkbounds(Bool, a, i) || return false return @inbounds isassigned(memoryref(a.ref, i, false)) end @@ -962,29 +964,23 @@ Dict{String, Int64} with 2 entries: function setindex! end function setindex!(A::Array{T}, x, i::Int) where {T} + @_noub_if_noinbounds_meta @boundscheck (i - 1)%UInt < length(A)%UInt || throw_boundserror(A, (i,)) memoryrefset!(memoryref(A.ref, i, false), x isa T ? x : convert(T,x)::T, :not_atomic, false) return A end function setindex!(A::Array{T}, x, i1::Int, i2::Int, I::Int...) where {T} @inline + @_noub_if_noinbounds_meta @boundscheck checkbounds(A, i1, i2, I...) # generally _to_linear_index requires bounds checking memoryrefset!(memoryref(A.ref, _to_linear_index(A, i1, i2, I...), false), x isa T ? x : convert(T,x)::T, :not_atomic, false) return A end -function __inbounds_setindex!(A::Array{T}, x, i::Int) where {T} - @inline - val = x isa T ? x : convert(T,x)::T - memoryrefset!(memoryref(A.ref, i, false), val, :not_atomic, false) - return A -end -function __inbounds_setindex!(A::Array{T}, x, i1::Int, i2::Int, I::Int...) where {T} - @inline - val = x isa T ? x : convert(T,x)::T - memoryrefset!(memoryref(A.ref, _to_linear_index(A, i1, i2, I...), false), val, :not_atomic, false) - return A -end +__safe_setindex!(A::Vector{T}, x::T, i::Int) where {T} = (@inline; @_nothrow_noub_meta; + memoryrefset!(memoryref(A.ref, i, false), x, :not_atomic, false); return A) +__safe_setindex!(A::Vector{T}, x, i::Int) where {T} = (@inline; + __safe_setindex!(A, convert(T, x)::T, i)) # This is redundant with the abstract fallbacks but needed and helpful for bootstrap function setindex!(A::Array, X::AbstractArray, I::AbstractVector{Int}) diff --git a/base/boot.jl b/base/boot.jl index 7ada0782600b49..74eaed090f879c 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -281,7 +281,8 @@ macro _foldable_meta() #=:terminates_locally=#false, #=:notaskstate=#true, #=:inaccessiblememonly=#true, - #=:noub=#true)) + #=:noub=#true, + #=:noub_if_noinbounds=#false)) end macro inline() Expr(:meta, :inline) end diff --git a/base/c.jl b/base/c.jl index 1182033802aad5..eb552d3507662b 100644 --- a/base/c.jl +++ b/base/c.jl @@ -409,6 +409,6 @@ macro ccall(expr) return ccall_macro_lower(:ccall, ccall_macro_parse(expr)...) end -macro ccall_effects(effects::UInt8, expr) +macro ccall_effects(effects::UInt16, expr) return ccall_macro_lower((:ccall, effects), ccall_macro_parse(expr)...) end diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 73777646fa68bb..77024670b7002b 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2604,7 +2604,7 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes: abstract_eval_value(interp, x, vtypes, sv) end cconv = e.args[5] - if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt8})) + if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt16})) override = decode_effects_override(v[2]) effects = Effects(effects; consistent = override.consistent ? ALWAYS_TRUE : effects.consistent, @@ -2613,7 +2613,7 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes: terminates = override.terminates_globally ? true : effects.terminates, notaskstate = override.notaskstate ? true : effects.notaskstate, inaccessiblememonly = override.inaccessiblememonly ? ALWAYS_TRUE : effects.inaccessiblememonly, - noub = override.noub ? ALWAYS_TRUE : effects.noub) + noub = override.noub ? ALWAYS_TRUE : override.noub_if_noinbounds ? NOUB_IF_NOINBOUNDS : effects.noub) end return RTEffects(t, effects) end diff --git a/base/compiler/effects.jl b/base/compiler/effects.jl index 485ba5e4166657..fc774efbbee85d 100644 --- a/base/compiler/effects.jl +++ b/base/compiler/effects.jl @@ -197,15 +197,14 @@ is_nothrow(effects::Effects) = effects.nothrow is_terminates(effects::Effects) = effects.terminates is_notaskstate(effects::Effects) = effects.notaskstate is_inaccessiblememonly(effects::Effects) = effects.inaccessiblememonly === ALWAYS_TRUE +is_noub(effects::Effects) = effects.noub === ALWAYS_TRUE +is_noub_if_noinbounds(effects::Effects) = effects.noub === NOUB_IF_NOINBOUNDS is_nonoverlayed(effects::Effects) = effects.nonoverlayed -is_noub(effects::Effects, noinbounds::Bool=true) = - effects.noub === ALWAYS_TRUE || (noinbounds && effects.noub === NOUB_IF_NOINBOUNDS) - # implies `is_notaskstate` & `is_inaccessiblememonly`, but not explicitly checked here is_foldable(effects::Effects) = is_consistent(effects) && - is_noub(effects) && + (is_noub(effects) || is_noub_if_noinbounds(effects)) && is_effect_free(effects) && is_terminates(effects) @@ -262,29 +261,32 @@ struct EffectsOverride notaskstate::Bool inaccessiblememonly::Bool noub::Bool + noub_if_noinbounds::Bool end function encode_effects_override(eo::EffectsOverride) - e = 0x00 - eo.consistent && (e |= (0x01 << 0)) - eo.effect_free && (e |= (0x01 << 1)) - eo.nothrow && (e |= (0x01 << 2)) - eo.terminates_globally && (e |= (0x01 << 3)) - eo.terminates_locally && (e |= (0x01 << 4)) - eo.notaskstate && (e |= (0x01 << 5)) - eo.inaccessiblememonly && (e |= (0x01 << 6)) - eo.noub && (e |= (0x01 << 7)) + e = 0x0000 + eo.consistent && (e |= (0x0001 << 0)) + eo.effect_free && (e |= (0x0001 << 1)) + eo.nothrow && (e |= (0x0001 << 2)) + eo.terminates_globally && (e |= (0x0001 << 3)) + eo.terminates_locally && (e |= (0x0001 << 4)) + eo.notaskstate && (e |= (0x0001 << 5)) + eo.inaccessiblememonly && (e |= (0x0001 << 6)) + eo.noub && (e |= (0x0001 << 7)) + eo.noub_if_noinbounds && (e |= (0x0001 << 8)) return e end -function decode_effects_override(e::UInt8) +function decode_effects_override(e::UInt16) return EffectsOverride( - (e & (0x01 << 0)) != 0x00, - (e & (0x01 << 1)) != 0x00, - (e & (0x01 << 2)) != 0x00, - (e & (0x01 << 3)) != 0x00, - (e & (0x01 << 4)) != 0x00, - (e & (0x01 << 5)) != 0x00, - (e & (0x01 << 6)) != 0x00, - (e & (0x01 << 7)) != 0x00) + !iszero(e & (0x0001 << 0)), + !iszero(e & (0x0001 << 1)), + !iszero(e & (0x0001 << 2)), + !iszero(e & (0x0001 << 3)), + !iszero(e & (0x0001 << 4)), + !iszero(e & (0x0001 << 5)), + !iszero(e & (0x0001 << 6)), + !iszero(e & (0x0001 << 7)), + !iszero(e & (0x0001 << 8))) end diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 5f4faa4933e0af..bdf62ac0f10b82 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -563,9 +563,21 @@ function is_ipo_dataflow_analysis_profitable(effects::Effects) is_nothrow(effects) && is_noub(effects)) end -function is_getfield_with_boundscheck_arg(@nospecialize(stmt), sv::PostOptAnalysisState) - is_known_call(stmt, getfield, sv.ir) || return false - length(stmt.args) < 4 && return false +function iscall_with_boundscheck(@nospecialize(stmt), sv::PostOptAnalysisState) + isexpr(stmt, :call) || return false + ft = argextype(stmt.args[1], sv.ir) + f = singleton_type(ft) + f === nothing && return false + if f === getfield + nargs = 4 + elseif f === memoryref || f === memoryrefget || f === memoryref_isassigned + nargs = 4 + elseif f === memoryrefset! + nargs = 5 + else + return false + end + length(stmt.args) < nargs && return false boundscheck = stmt.args[end] argextype(boundscheck, sv.ir) === Bool || return false isa(boundscheck, SSAValue) || return false @@ -573,15 +585,13 @@ function is_getfield_with_boundscheck_arg(@nospecialize(stmt), sv::PostOptAnalys end function is_conditional_noub(inst::Instruction, sv::PostOptAnalysisState) - # Special case: `:boundscheck` into `getfield` stmt = inst[:stmt] - is_getfield_with_boundscheck_arg(stmt, sv) || return false - barg = stmt.args[end] + iscall_with_boundscheck(stmt, sv) || return false + barg = stmt.args[end]::SSAValue bstmt = sv.ir[barg][:stmt] isexpr(bstmt, :boundscheck) || return false # If IR_FLAG_INBOUNDS is already set, no more conditional ub (!isempty(bstmt.args) && bstmt.args[1] === false) && return false - sv.any_conditional_ub = true return true end @@ -596,7 +606,10 @@ function scan_non_dataflow_flags!(inst::Instruction, sv::PostOptAnalysisState) end sv.all_nothrow &= !iszero(flag & IR_FLAG_NOTHROW) if iszero(flag & IR_FLAG_NOUB) - if !is_conditional_noub(inst, sv) + # Special case: `:boundscheck` into `getfield` or memory operations is `:noub_if_noinbounds` + if is_conditional_noub(inst, sv) + sv.any_conditional_ub = true + else sv.all_noub = false end end @@ -606,9 +619,9 @@ function scan_inconsistency!(inst::Instruction, sv::PostOptAnalysisState) flag = inst[:flag] stmt_inconsistent = iszero(flag & IR_FLAG_CONSISTENT) stmt = inst[:stmt] - # Special case: For getfield, we allow inconsistency of the :boundscheck argument + # Special case: For `getfield` and memory operations, we allow inconsistency of the :boundscheck argument (; inconsistent, tpdum) = sv - if is_getfield_with_boundscheck_arg(stmt, sv) + if iscall_with_boundscheck(stmt, sv) for i = 1:(length(stmt.args)-1) val = stmt.args[i] if isa(val, SSAValue) @@ -703,7 +716,7 @@ function check_inconsistentcy!(sv::PostOptAnalysisState, scanner::BBScanner) idx = popfirst!(stmt_ip) inst = ir[SSAValue(idx)] stmt = inst[:stmt] - if is_getfield_with_boundscheck_arg(stmt, sv) + if iscall_with_boundscheck(stmt, sv) any_non_boundscheck_inconsistent = false for i = 1:(length(stmt.args)-1) val = stmt.args[i] diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 12eec689384415..de1b3bd2cb79e1 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -1007,7 +1007,7 @@ function flags_for_effects(effects::Effects) if is_nothrow(effects) flags |= IR_FLAG_NOTHROW end - if is_noub(effects, false) + if is_noub(effects) flags |= IR_FLAG_NOUB end return flags diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 2d609d6dd3d088..fbd2112a1c0fef 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -21,20 +21,20 @@ function concrete_eval_invoke(interp::AbstractInterpreter, try Core._call_in_world_total(world, args...) catch - return Pair{Any,Tuple{Bool,Bool}}(Bottom, (false, is_noub(effects, false))) + return Pair{Any,Tuple{Bool,Bool}}(Bottom, (false, is_noub(effects))) end end return Pair{Any,Tuple{Bool,Bool}}(Const(value), (true, true)) else if is_constprop_edge_recursed(mi, irsv) - return Pair{Any,Tuple{Bool,Bool}}(nothing, (is_nothrow(effects), is_noub(effects, false))) + return Pair{Any,Tuple{Bool,Bool}}(nothing, (is_nothrow(effects), is_noub(effects))) end newirsv = IRInterpretationState(interp, code, mi, argtypes, world) if newirsv !== nothing newirsv.parent = irsv return ir_abstract_constant_propagation(interp, newirsv) end - return Pair{Any,Tuple{Bool,Bool}}(nothing, (is_nothrow(effects), is_noub(effects, false))) + return Pair{Any,Tuple{Bool,Bool}}(nothing, (is_nothrow(effects), is_noub(effects))) end end diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index a8610eb8fd6535..eb43e77885d64a 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -924,6 +924,7 @@ end function getfield_boundscheck(argtypes::Vector{Any}) if length(argtypes) == 2 + isvarargtype(argtypes[2]) && return :unsafe return :on elseif length(argtypes) == 3 boundscheck = argtypes[3] @@ -933,10 +934,10 @@ function getfield_boundscheck(argtypes::Vector{Any}) end elseif length(argtypes) == 4 boundscheck = argtypes[4] + isvarargtype(boundscheck) && return :unsafe else return :unsafe end - isvarargtype(boundscheck) && return :unsafe boundscheck = widenconditional(boundscheck) if widenconst(boundscheck) === Bool if isa(boundscheck, Const) @@ -2144,7 +2145,6 @@ end return boundscheck โŠ‘ Bool && memtype โŠ‘ GenericMemoryRef && order โŠ‘ Symbol end - # Query whether the given builtin is guaranteed not to throw given the argtypes @nospecs function _builtin_nothrow(๐•ƒ::AbstractLattice, f, argtypes::Vector{Any}, rt) โŠ‘ = Core.Compiler.:โŠ‘(๐•ƒ) @@ -2509,8 +2509,43 @@ function builtin_effects(๐•ƒ::AbstractLattice, @nospecialize(f::Builtin), argty else inaccessiblememonly = ALWAYS_FALSE end - return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow, inaccessiblememonly) + if f === memoryref || f === memoryrefget || f === memoryrefset! || f === memoryref_isassigned + noub = memoryop_noub(f, argtypes) ? ALWAYS_TRUE : ALWAYS_FALSE + else + noub = ALWAYS_TRUE + end + return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow, inaccessiblememonly, noub) + end +end + +function memoryop_noub(@nospecialize(f), argtypes::Vector{Any}) + nargs = length(argtypes) + nargs == 0 && return true # must throw and noub + lastargtype = argtypes[end] + isva = isvarargtype(lastargtype) + if f === memoryref + if nargs == 1 && !isva + return true + elseif nargs == 2 && !isva + return true + end + expected_nargs = 3 + elseif f === memoryrefget || f === memoryref_isassigned + expected_nargs = 3 + else + @assert f === memoryrefset! "unexpected memoryop is given" + expected_nargs = 4 + end + if nargs == expected_nargs && !isva + boundscheck = widenconditional(lastargtype) + hasintersect(widenconst(boundscheck), Bool) || return true # must throw and noub + boundscheck isa Const && boundscheck.val === true && return true + elseif nargs > expected_nargs + 1 + return true # must throw and noub + elseif !isva + return true # must throw and noub end + return false end """ diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index fcb99446198315..48558c05f1e8d4 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -445,6 +445,8 @@ function adjust_effects(ipo_effects::Effects, def::Method) end if is_effect_overridden(override, :noub) ipo_effects = Effects(ipo_effects; noub=ALWAYS_TRUE) + elseif is_effect_overridden(override, :noub_if_noinbounds) && ipo_effects.noub !== ALWAYS_TRUE + ipo_effects = Effects(ipo_effects; noub=NOUB_IF_NOINBOUNDS) end return ipo_effects end diff --git a/base/compiler/validation.jl b/base/compiler/validation.jl index 5505f5a2b423ba..ef6602b0827970 100644 --- a/base/compiler/validation.jl +++ b/base/compiler/validation.jl @@ -253,5 +253,3 @@ function is_valid_rvalue(@nospecialize(x)) end is_valid_return(@nospecialize(x)) = is_valid_argument(x) || (isa(x, Expr) && x.head === :lambda) - -is_flag_set(byte::UInt8, flag::UInt8) = (byte & flag) == flag diff --git a/base/docs/intrinsicsdocs.jl b/base/docs/intrinsicsdocs.jl index effc9e859becf0..9f6ec773ff9a87 100644 --- a/base/docs/intrinsicsdocs.jl +++ b/base/docs/intrinsicsdocs.jl @@ -3,14 +3,14 @@ """ Core.IR -The Core.IR module exports the IR object model. +The `Core.IR` module exports the IR object model. """ Core.IR """ Core.IntrinsicFunction <: Core.Builtin <: Function -The Core.IntrinsicFunction function define some basic primitives for what defines the +The `Core.IntrinsicFunction` function define some basic primitives for what defines the abilities and behaviors of a Julia program """ Core.IntrinsicFunction @@ -18,7 +18,7 @@ Core.IntrinsicFunction """ Core.Intrinsics -The Core.Intrinsics module holds the Core.IntrinsicFunction objects. +The `Core.Intrinsics` module holds the `Core.IntrinsicFunction` objects. """ Core.Intrinsics @@ -26,21 +26,21 @@ Core.Intrinsics Core.memoryref(::GenericMemory) Core.memoryref(::GenericMemoryRef, index::Int, [boundscheck::Bool]) -Return a GenericMemoryRef for a GenericMemory. See [`MemoryRef`](@ref). +Return a `GenericMemoryRef` for a `GenericMemory`. See [`MemoryRef`](@ref). """ Core.memoryref """ Core..memoryrefoffset(::GenericMemoryRef) -Return the offset index that was used to construct the MemoryRef. See [`Core.memoryref`](@ref). +Return the offset index that was used to construct the `MemoryRef`. See [`Core.memoryref`](@ref). """ Core.memoryrefoffset """ Core.memoryrefget(::GenericMemoryRef, ordering::Symbol, boundscheck::Bool) -Return the value stored at the MemoryRef, throwing a BoundsError if the Memory is empty. See `ref[]`. +Return the value stored at the `MemoryRef`, throwing a `BoundsError` if the `Memory` is empty. See `ref[]`. The memory ordering specified must be compatible with the `isatomic` parameter. """ Core.memoryrefget @@ -48,7 +48,7 @@ Core.memoryrefget """ Core.memoryrefset!(::GenericMemoryRef, value, ordering::Symbol, boundscheck::Bool) -Store the value to the MemoryRef, throwing a BoundsError if the Memory is empty. See `ref[] = value`. +Store the value to the `MemoryRef`, throwing a `BoundsError` if the `Memory` is empty. See `ref[] = value`. The memory ordering specified must be compatible with the `isatomic` parameter. """ Core.memoryrefset! @@ -56,7 +56,8 @@ Core.memoryrefset! """ Core.memoryref_isassigned(::GenericMemoryRef, ordering::Symbol, boundscheck::Bool) -Return whether there is a value stored at the MemoryRef, returning false if the Memory is empty. See [`isassigned(::Base.RefValue)`](@ref), [`Core.memoryrefget`](@ref). +Return whether there is a value stored at the `MemoryRef`, returning false if the `Memory` +is empty. See [`isassigned(::Base.RefValue)`](@ref), [`Core.memoryrefget`](@ref). The memory ordering specified must be compatible with the `isatomic` parameter. """ Core.memoryref_isassigned diff --git a/base/essentials.jl b/base/essentials.jl index a8a3ec3c153824..106826d140c574 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -13,9 +13,6 @@ length(a::Array) = getfield(getfield(getfield(a, :ref), :mem), :length) length(a::GenericMemory) = getfield(a, :length) throw_boundserror(A, I) = (@noinline; throw(BoundsError(A, I))) -getindex(A::GenericMemory{:not_atomic}, i::Int) = memoryrefget(memoryref(memoryref(A), i, @_boundscheck), :not_atomic, false) -getindex(A::GenericMemoryRef{:not_atomic}) = memoryrefget(A, :not_atomic, @_boundscheck) - # multidimensional getindex will be defined later on ==(a::GlobalRef, b::GlobalRef) = a.mod === b.mod && a.name === b.name @@ -214,7 +211,8 @@ macro _total_meta() #=:terminates_locally=#false, #=:notaskstate=#true, #=:inaccessiblememonly=#true, - #=:noub=#true)) + #=:noub=#true, + #=:noub_if_noinbounds=#false)) end # can be used in place of `@assume_effects :foldable` (supposed to be used for bootstrapping) macro _foldable_meta() @@ -226,19 +224,8 @@ macro _foldable_meta() #=:terminates_locally=#false, #=:notaskstate=#true, #=:inaccessiblememonly=#true, - #=:noub=#true)) -end -# can be used in place of `@assume_effects :nothrow` (supposed to be used for bootstrapping) -macro _nothrow_meta() - return _is_internal(__module__) && Expr(:meta, Expr(:purity, - #=:consistent=#false, - #=:effect_free=#false, - #=:nothrow=#true, - #=:terminates_globally=#false, - #=:terminates_locally=#false, - #=:notaskstate=#false, - #=:inaccessiblememonly=#false, - #=:noub=#false)) + #=:noub=#true, + #=:noub_if_noinbounds=#false)) end # can be used in place of `@assume_effects :terminates_locally` (supposed to be used for bootstrapping) macro _terminates_locally_meta() @@ -250,7 +237,8 @@ macro _terminates_locally_meta() #=:terminates_locally=#true, #=:notaskstate=#false, #=:inaccessiblememonly=#false, - #=:noub=#false)) + #=:noub=#false, + #=:noub_if_noinbounds=#false)) end # can be used in place of `@assume_effects :terminates_globally` (supposed to be used for bootstrapping) macro _terminates_globally_meta() @@ -262,7 +250,8 @@ macro _terminates_globally_meta() #=:terminates_locally=#true, #=:notaskstate=#false, #=:inaccessiblememonly=#false, - #=:noub=#false)) + #=:noub=#false, + #=:noub_if_noinbounds=#false)) end # can be used in place of `@assume_effects :effect_free :terminates_locally` (supposed to be used for bootstrapping) macro _effect_free_terminates_locally_meta() @@ -274,7 +263,34 @@ macro _effect_free_terminates_locally_meta() #=:terminates_locally=#true, #=:notaskstate=#false, #=:inaccessiblememonly=#false, - #=:noub=#false)) + #=:noub=#false, + #=:noub_if_noinbounds=#false)) +end +# can be used in place of `@assume_effects :nothrow :noub` (supposed to be used for bootstrapping) +macro _nothrow_noub_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#false, + #=:nothrow=#true, + #=:terminates_globally=#false, + #=:terminates_locally=#false, + #=:notaskstate=#false, + #=:inaccessiblememonly=#false, + #=:noub=#true, + #=:noub_if_noinbounds=#false)) +end +# can be used in place of `@assume_effects :noub_if_noinbounds` (supposed to be used for bootstrapping) +macro _noub_if_noinbounds_meta() + return _is_internal(__module__) && Expr(:meta, Expr(:purity, + #=:consistent=#false, + #=:effect_free=#false, + #=:nothrow=#false, + #=:terminates_globally=#false, + #=:terminates_locally=#false, + #=:notaskstate=#false, + #=:inaccessiblememonly=#false, + #=:noub=#false, + #=:noub_if_noinbounds=#true)) end # another version of inlining that propagates an inbounds context @@ -285,6 +301,10 @@ macro _nospecializeinfer_meta() return Expr(:meta, :nospecializeinfer) end +getindex(A::GenericMemory{:not_atomic}, i::Int) = (@_noub_if_noinbounds_meta; + memoryrefget(memoryref(memoryref(A), i, @_boundscheck), :not_atomic, false)) +getindex(A::GenericMemoryRef{:not_atomic}) = memoryrefget(A, :not_atomic, @_boundscheck) + function iterate end """ @@ -787,11 +807,13 @@ end # linear indexing function getindex(A::Array, i::Int) + @_noub_if_noinbounds_meta @boundscheck ult_int(bitcast(UInt, sub_int(i, 1)), bitcast(UInt, length(A))) || throw_boundserror(A, (i,)) memoryrefget(memoryref(getfield(A, :ref), i, false), :not_atomic, false) end # simple Array{Any} operations needed for bootstrap function setindex!(A::Array{Any}, @nospecialize(x), i::Int) + @_noub_if_noinbounds_meta @boundscheck ult_int(bitcast(UInt, sub_int(i, 1)), bitcast(UInt, length(A))) || throw_boundserror(A, (i,)) memoryrefset!(memoryref(getfield(A, :ref), i, false), x, :not_atomic, false) return A diff --git a/base/expr.jl b/base/expr.jl index 4e418d140e5a65..3e68c68b2ac4f7 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -477,6 +477,7 @@ The following `setting`s are supported. - `:notaskstate` - `:inaccessiblememonly` - `:noub` +- `:noub_if_noinbounds` - `:foldable` - `:removable` - `:total` @@ -714,8 +715,8 @@ macro assume_effects(args...) ex = nothing idx = length(args) end - (consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub) = - (false, false, false, false, false, false, false, false, false) + consistent = effect_free = nothrow = terminates_globally = terminates_locally = + notaskstate = inaccessiblememonly = noub = noub_if_noinbounds = false for i in 1:idx org_setting = args[i] (setting, val) = compute_assumed_setting(org_setting) @@ -735,6 +736,8 @@ macro assume_effects(args...) inaccessiblememonly = val elseif setting === :noub noub = val + elseif setting === :noub_if_noinbounds + noub_if_noinbounds = val elseif setting === :foldable consistent = effect_free = terminates_globally = noub = val elseif setting === :removable @@ -747,15 +750,18 @@ macro assume_effects(args...) end if is_function_def(inner) return esc(pushmeta!(ex, :purity, - consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub)) + consistent, effect_free, nothrow, terminates_globally, terminates_locally, + notaskstate, inaccessiblememonly, noub, noub_if_noinbounds)) elseif isexpr(ex, :macrocall) && ex.args[1] === Symbol("@ccall") ex.args[1] = GlobalRef(Base, Symbol("@ccall_effects")) insert!(ex.args, 3, Core.Compiler.encode_effects_override(Core.Compiler.EffectsOverride( - consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub))) + consistent, effect_free, nothrow, terminates_globally, terminates_locally, + notaskstate, inaccessiblememonly, noub, noub_if_noinbounds))) return esc(ex) else # anonymous function case return Expr(:meta, Expr(:purity, - consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, noub)) + consistent, effect_free, nothrow, terminates_globally, terminates_locally, + notaskstate, inaccessiblememonly, noub, noub_if_noinbounds)) end end diff --git a/base/genericmemory.jl b/base/genericmemory.jl index 34c452af97ad20..51b25d453e5c45 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -196,17 +196,6 @@ function setindex!(A::Memory{T}, x, i1::Int, i2::Int, I::Int...) where {T} setindex!(A, x, i1) end -function __inbounds_setindex!(A::Memory{T}, x, i1::Int) where {T} - val = x isa T ? x : convert(T,x)::T - ref = memoryref(memoryref(A), i1, false) - memoryrefset!(ref, val, :not_atomic, false) - return A -end -function __inbounds_setindex!(A::Memory{T}, x, i1::Int, i2::Int, I::Int...) where {T} - @boundscheck (i2 == 1 && all(==(1), I)) || throw_boundserror(A, (i1, i2, I...)) - __inbounds_setindex(A, x, i1) -end - # Faster contiguous setindex! with copyto! function setindex!(A::Memory{T}, X::Memory{T}, I::AbstractUnitRange{Int}) where T @inline diff --git a/base/strings/string.jl b/base/strings/string.jl index 9f5995c25c558f..29216ae97aa37b 100644 --- a/base/strings/string.jl +++ b/base/strings/string.jl @@ -87,7 +87,7 @@ end # This is @assume_effects :effect_free :nothrow :terminates_globally @ccall jl_alloc_string(n::Csize_t)::Ref{String}, # but the macro is not available at this time in bootstrap, so we write it manually. -@eval _string_n(n::Integer) = $(Expr(:foreigncall, QuoteNode(:jl_alloc_string), Ref{String}, Expr(:call, Expr(:core, :svec), :Csize_t), 1, QuoteNode((:ccall,0xe)), :(convert(Csize_t, n)))) +@eval _string_n(n::Integer) = $(Expr(:foreigncall, QuoteNode(:jl_alloc_string), Ref{String}, Expr(:call, Expr(:core, :svec), :Csize_t), 1, QuoteNode((:ccall,0x000e)), :(convert(Csize_t, n)))) """ String(s::AbstractString) diff --git a/base/tuple.jl b/base/tuple.jl index c2e0a478cca48f..2e1c5972c407df 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -30,8 +30,7 @@ size(@nospecialize(t::Tuple), d::Integer) = (d == 1) ? length(t) : throw(Argumen axes(@nospecialize t::Tuple) = (OneTo(length(t)),) getindex(@nospecialize(t::Tuple), i::Int) = getfield(t, i, @_boundscheck) getindex(@nospecialize(t::Tuple), i::Integer) = getfield(t, convert(Int, i), @_boundscheck) -__inbounds_getindex(@nospecialize(t::Tuple), i::Int) = getfield(t, i, false) -__inbounds_getindex(@nospecialize(t::Tuple), i::Integer) = getfield(t, convert(Int, i), false) +__safe_getindex(@nospecialize(t::Tuple), i::Int) = (@_nothrow_noub_meta; getfield(t, i, false)) getindex(t::Tuple, r::AbstractArray{<:Any,1}) = (eltype(t)[t[ri] for ri in r]...,) getindex(t::Tuple, b::AbstractArray{Bool,1}) = length(b) == length(t) ? getindex(t, findall(b)) : throw(BoundsError(t, b)) getindex(t::Tuple, c::Colon) = t diff --git a/src/common_symbols2.inc b/src/common_symbols2.inc index 6850fcd3033d1a..a4583fe39f186c 100644 --- a/src/common_symbols2.inc +++ b/src/common_symbols2.inc @@ -50,7 +50,6 @@ jl_symbol("C_NULL"), jl_symbol("oftype"), jl_symbol("_growend!"), jl_symbol("Any"), -jl_symbol("__inbounds_setindex!"), jl_symbol("Tuple"), jl_symbol("float.jl"), jl_symbol("ncodeunits"), diff --git a/src/ircode.c b/src/ircode.c index 5926f48ef1cf86..6ed1740b3667ac 100644 --- a/src/ircode.c +++ b/src/ircode.c @@ -789,7 +789,7 @@ static jl_value_t *jl_decode_value(jl_ircode_state *s) JL_GC_DISABLED typedef jl_value_t jl_string_t; // for local expressibility #define IR_DATASIZE_FLAGS sizeof(uint8_t) -#define IR_DATASIZE_PURITY sizeof(uint8_t) +#define IR_DATASIZE_PURITY sizeof(uint16_t) #define IR_DATASIZE_INLINING_COST sizeof(uint16_t) #define IR_DATASIZE_NSLOTS sizeof(int32_t) typedef enum { @@ -826,7 +826,7 @@ JL_DLLEXPORT jl_string_t *jl_compress_ir(jl_method_t *m, jl_code_info_t *code) code->nospecializeinfer, code->inlining, code->constprop); write_uint8(s.s, flags.packed); static_assert(sizeof(flags.packed) == IR_DATASIZE_FLAGS, "ir_datasize_flags is mismatched with the actual size"); - write_uint8(s.s, code->purity.bits); + write_uint16(s.s, code->purity.bits); static_assert(sizeof(code->purity.bits) == IR_DATASIZE_PURITY, "ir_datasize_purity is mismatched with the actual size"); write_uint16(s.s, code->inlining_cost); static_assert(sizeof(code->inlining_cost) == IR_DATASIZE_INLINING_COST, "ir_datasize_inlining_cost is mismatched with the actual size"); @@ -927,7 +927,7 @@ JL_DLLEXPORT jl_code_info_t *jl_uncompress_ir(jl_method_t *m, jl_code_instance_t code->propagate_inbounds = flags.bits.propagate_inbounds; code->has_fcall = flags.bits.has_fcall; code->nospecializeinfer = flags.bits.nospecializeinfer; - code->purity.bits = read_uint8(s.s); + code->purity.bits = read_uint16(s.s); code->inlining_cost = read_uint16(s.s); size_t nslots = read_int32(&src); diff --git a/src/jltypes.c b/src/jltypes.c index d9cf77927efb70..9f27b14785dbe0 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3132,7 +3132,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_uint8_type, jl_uint8_type, - jl_uint8_type, + jl_uint16_type, jl_uint16_type), jl_emptysvec, 0, 1, 22); @@ -3201,7 +3201,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_bool_type, jl_uint8_type, jl_uint8_type, - jl_uint8_type), + jl_uint16_type), jl_emptysvec, 0, 1, 10); //const static uint32_t method_constfields[1] = { 0x03fc065f }; // (1<<0)|(1<<1)|(1<<2)|(1<<3)|(1<<4)|(1<<6)|(1<<9)|(1<<10)|(1<<18)|(1<<19)|(1<<20)|(1<<21)|(1<<22)|(1<<23)|(1<<24)|(1<<25); diff --git a/src/julia.h b/src/julia.h index 0b3a010c32856f..034280524cd11a 100644 --- a/src/julia.h +++ b/src/julia.h @@ -248,20 +248,21 @@ typedef struct _jl_line_info_node_t { // the following mirrors `struct EffectsOverride` in `base/compiler/effects.jl` typedef union __jl_purity_overrides_t { struct { - uint8_t ipo_consistent : 1; - uint8_t ipo_effect_free : 1; - uint8_t ipo_nothrow : 1; - uint8_t ipo_terminates_globally : 1; + uint16_t ipo_consistent : 1; + uint16_t ipo_effect_free : 1; + uint16_t ipo_nothrow : 1; + uint16_t ipo_terminates_globally : 1; // Weaker form of `terminates` that asserts // that any control flow syntactically in the method // is guaranteed to terminate, but does not make // assertions about any called functions. - uint8_t ipo_terminates_locally : 1; - uint8_t ipo_notaskstate : 1; - uint8_t ipo_inaccessiblememonly : 1; - uint8_t ipo_noub : 1; + uint16_t ipo_terminates_locally : 1; + uint16_t ipo_notaskstate : 1; + uint16_t ipo_inaccessiblememonly : 1; + uint16_t ipo_noub : 1; + uint16_t ipo_noub_if_noinbounds : 1; } overrides; - uint8_t bits; + uint16_t bits; } _jl_purity_overrides_t; // This type describes a single function body diff --git a/src/method.c b/src/method.c index 4e3062fef52465..8b89e66c62eb0f 100644 --- a/src/method.c +++ b/src/method.c @@ -192,7 +192,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve jl_error("In ccall calling convention, expected two argument tuple or symbol."); } JL_TYPECHK(ccall method definition, symbol, jl_get_nth_field(cc, 0)); - JL_TYPECHK(ccall method definition, uint8, jl_get_nth_field(cc, 1)); + JL_TYPECHK(ccall method definition, uint16, jl_get_nth_field(cc, 1)); } jl_exprargset(e, 0, resolve_globals(jl_exprarg(e, 0), module, sparam_vals, binding_effects, 1)); i++; @@ -327,7 +327,7 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) else if (ma == (jl_value_t*)jl_no_constprop_sym) li->constprop = 2; else if (jl_is_expr(ma) && ((jl_expr_t*)ma)->head == jl_purity_sym) { - if (jl_expr_nargs(ma) == 8) { + if (jl_expr_nargs(ma) == 9) { li->purity.overrides.ipo_consistent = jl_unbox_bool(jl_exprarg(ma, 0)); li->purity.overrides.ipo_effect_free = jl_unbox_bool(jl_exprarg(ma, 1)); li->purity.overrides.ipo_nothrow = jl_unbox_bool(jl_exprarg(ma, 2)); @@ -336,6 +336,7 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) li->purity.overrides.ipo_notaskstate = jl_unbox_bool(jl_exprarg(ma, 5)); li->purity.overrides.ipo_inaccessiblememonly = jl_unbox_bool(jl_exprarg(ma, 6)); li->purity.overrides.ipo_noub = jl_unbox_bool(jl_exprarg(ma, 7)); + li->purity.overrides.ipo_noub_if_noinbounds = jl_unbox_bool(jl_exprarg(ma, 8)); } } else diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 9b1effced05e36..868457d6514cdb 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -2139,3 +2139,12 @@ end # issue #51823 @test "include" in test_complete_context("inc", Main)[1] + +# REPL completions should not try to concrete-evaluate !:noub methods +function very_unsafe_method(i::Int) + xs = Any[] + @inbounds xs[i] +end +let t = REPLCompletions.repl_eval_ex(:(unsafe_method(42)), @__MODULE__) + @test isnothing(t) +end diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index 8e8290569cdb72..46081f38cf9aa1 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -80,7 +80,7 @@ const TAGS = Any[ const NTAGS = length(TAGS) @assert NTAGS == 255 -const ser_version = 25 # do not make changes without bumping the version #! +const ser_version = 26 # do not make changes without bumping the version #! format_version(::AbstractSerializer) = ser_version format_version(s::Serializer) = s.version @@ -1058,7 +1058,8 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) isva = deserialize(s)::Bool is_for_opaque_closure = false nospecializeinfer = false - constprop = purity = 0x00 + constprop = 0x00 + purity = 0x0000 template_or_is_opaque = deserialize(s) if isa(template_or_is_opaque, Bool) is_for_opaque_closure = template_or_is_opaque @@ -1068,8 +1069,10 @@ function deserialize(s::AbstractSerializer, ::Type{Method}) if format_version(s) >= 14 constprop = deserialize(s)::UInt8 end - if format_version(s) >= 17 - purity = deserialize(s)::UInt8 + if format_version(s) >= 26 + purity = deserialize(s)::UInt16 + elseif format_version(s) >= 17 + purity = UInt16(deserialize(s)::UInt8) end template = deserialize(s) else @@ -1242,7 +1245,9 @@ function deserialize(s::AbstractSerializer, ::Type{CodeInfo}) if format_version(s) >= 14 ci.constprop = deserialize(s)::UInt8 end - if format_version(s) >= 17 + if format_version(s) >= 26 + ci.purity = deserialize(s)::UInt16 + elseif format_version(s) >= 17 ci.purity = deserialize(s)::UInt8 end if format_version(s) >= 22 diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 7ec1910dfb2a39..96d9017e0699b0 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -1173,3 +1173,70 @@ callgetfield_inbounds(x, f) = @inbounds callgetfield2(x, f) @test Base.infer_effects(callgetfield_inbounds, (Some{Any},Symbol)).noub === Base.infer_effects(callgetfield_inbounds, (Some{Any},Symbol)).noub === Core.Compiler.ALWAYS_FALSE + +# noub modeling for memory ops +let (memoryref, memoryrefget, memoryref_isassigned, memoryrefset!) = + (Core.memoryref, Core.memoryrefget, Core.memoryref_isassigned, Core.memoryrefset!) + function builtin_effects(@nospecialize xs...) + interp = Core.Compiler.NativeInterpreter() + ๐•ƒ = Core.Compiler.typeinf_lattice(interp) + rt = Core.Compiler.builtin_tfunction(interp, xs..., nothing) + return Core.Compiler.builtin_effects(๐•ƒ, xs..., rt) + end + @test Core.Compiler.is_noub(builtin_effects(memoryref, Any[Memory,])) + @test Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Int])) + @test Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Int,Core.Const(true)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Int,Core.Const(false)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Int,Bool])) + @test Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Int,Int])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Int,Vararg{Bool}])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref, Any[MemoryRef,Vararg{Any}])) + @test Core.Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Core.Const(true)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Core.Const(false)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Bool])) + @test Core.Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Int])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Symbol,Vararg{Bool}])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefget, Any[MemoryRef,Vararg{Any}])) + @test Core.Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Core.Const(true)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Core.Const(false)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Bool])) + @test Core.Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Int])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Symbol,Vararg{Bool}])) + @test !Core.Compiler.is_noub(builtin_effects(memoryref_isassigned, Any[MemoryRef,Vararg{Any}])) + @test Core.Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Core.Const(true)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Core.Const(false)])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Bool])) + @test Core.Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Int])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Any,Symbol,Vararg{Bool}])) + @test !Core.Compiler.is_noub(builtin_effects(memoryrefset!, Any[MemoryRef,Vararg{Any}])) + # `:boundscheck` taint should be refined by post-opt analysis + @test Base.infer_effects() do xs::Vector{Any}, i::Int + memoryrefget(memoryref(getfield(xs, :ref), i, Base.@_boundscheck), :not_atomic, Base.@_boundscheck) + end |> Core.Compiler.is_noub_if_noinbounds +end + +# high level tests +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(getindex, (Vector{Int},Int))) +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(getindex, (Vector{Any},Int))) +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(setindex!, (Vector{Int},Int,Int))) +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(setindex!, (Vector{Any},Any,Int))) +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(isassigned, (Vector{Int},Int))) +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(isassigned, (Vector{Any},Int))) +@test Base.infer_effects((Vector{Int},Int)) do xs, i + xs[i] +end |> Core.Compiler.is_noub +@test Base.infer_effects((Vector{Any},Int)) do xs, i + xs[i] +end |> Core.Compiler.is_noub +@test Base.infer_effects((Vector{Int},Int,Int)) do xs, x, i + xs[i] = x +end |> Core.Compiler.is_noub +@test Base.infer_effects((Vector{Any},Any,Int)) do xs, x, i + xs[i] = x +end |> Core.Compiler.is_noub +@test Base.infer_effects((Vector{Int},Int)) do xs, i + @inbounds xs[i] +end |> !Core.Compiler.is_noub +@test Base.infer_effects((Vector{Any},Int)) do xs, i + @inbounds xs[i] +end |> !Core.Compiler.is_noub From 234a7586c397140489d78fd149fa5a67eec56446 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Sat, 18 Nov 2023 01:25:51 +0900 Subject: [PATCH 12/38] post-opt-analysis: use EA to refine `:effect_free` (#51494) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit is aiming to integrate EA into the Base compiler pipeline by using it during the post-opt analysis to refine `:effect_free` information. In doing so, this also generalizes `argescapes` field of `Union{InferenceResult,CodeInstance}` to `analysis_results::AnalysisResults` so that it can hold results of multiple post-optimization analyses, where `AnalysisResults` is designed to be linked-list like data structure. This is because an external `AbstractInterpreter`, like `EscapeAnalyzer`, might perform several post-optimization analyses. Honestly speaking, however, Iโ€™m not completely satisfied with this solution yet. It might make more sense to require a single post-optimization analysis for every `AbstractInterpreter` always, as like what we do for the other fields like `rettype`. Co-authored-by: Keno Fischer --- base/boot.jl | 4 +- base/compiler/optimize.jl | 142 ++++++++++++++++-- .../ssair/EscapeAnalysis/EscapeAnalysis.jl | 13 +- base/compiler/ssair/inlining.jl | 5 + base/compiler/typeinfer.jl | 2 +- base/compiler/types.jl | 29 +++- src/gf.c | 6 +- src/jltypes.c | 2 +- src/julia.h | 2 +- src/opaque_closure.c | 2 +- test/compiler/EscapeAnalysis/EAUtils.jl | 86 ++++------- test/compiler/effects.jl | 64 ++++++-- test/core.jl | 2 +- test/spawn.jl | 14 ++ 14 files changed, 276 insertions(+), 97 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index 74eaed090f879c..ff3502c2f418ea 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -481,12 +481,12 @@ end) function CodeInstance( mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const), @nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt, - ipo_effects::UInt32, effects::UInt32, @nospecialize(argescapes#=::Union{Nothing,Vector{ArgEscapeInfo}}=#), + ipo_effects::UInt32, effects::UInt32, @nospecialize(analysis_results), relocatability::UInt8) return ccall(:jl_new_codeinst, Ref{CodeInstance}, (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8), mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, - ipo_effects, effects, argescapes, + ipo_effects, effects, analysis_results, relocatability) end GlobalRef(m::Module, s::Symbol) = ccall(:jl_module_globalref, Ref{GlobalRef}, (Any, Any), m, s) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index bdf62ac0f10b82..514f2e03c2cae9 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -24,9 +24,7 @@ const IR_FLAG_INLINE = UInt32(1) << 1 # This statement is marked as @noinline by user const IR_FLAG_NOINLINE = UInt32(1) << 2 const IR_FLAG_THROW_BLOCK = UInt32(1) << 3 -# This statement may be removed if its result is unused. In particular, -# it must be both :effect_free and :nothrow. -# TODO: Separate these out. +# This statement was proven :effect_free const IR_FLAG_EFFECT_FREE = UInt32(1) << 4 # This statement was proven not to throw const IR_FLAG_NOTHROW = UInt32(1) << 5 @@ -39,6 +37,12 @@ const IR_FLAG_REFINED = UInt32(1) << 7 # This is :noub == ALWAYS_TRUE const IR_FLAG_NOUB = UInt32(1) << 8 +# TODO: Both of these should eventually go away once +# This is :effect_free == EFFECT_FREE_IF_INACCESSIBLEMEMONLY +const IR_FLAG_EFIIMO = UInt32(1) << 9 +# This is :inaccessiblememonly == INACCESSIBLEMEM_OR_ARGMEMONLY +const IR_FLAG_INACCESSIBLE_OR_ARGMEM = UInt32(1) << 10 + const IR_FLAGS_EFFECTS = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW | IR_FLAG_CONSISTENT | IR_FLAG_NOUB const TOP_TUPLE = GlobalRef(Core, :tuple) @@ -514,7 +518,6 @@ function get!(lazyagdomtree::LazyAugmentedDomtree) return lazyagdomtree.agdomtree = AugmentedDomtree(cfg, domtree) end -# TODO refine `:effect_free` using EscapeAnalysis mutable struct PostOptAnalysisState const result::InferenceResult const ir::IRCode @@ -522,8 +525,10 @@ mutable struct PostOptAnalysisState const tpdum::TwoPhaseDefUseMap const lazypostdomtree::LazyPostDomtree const lazyagdomtree::LazyAugmentedDomtree + const ea_analysis_pending::Vector{Int} all_retpaths_consistent::Bool all_effect_free::Bool + effect_free_if_argmem_only::Union{Nothing,Bool} all_nothrow::Bool all_noub::Bool any_conditional_ub::Bool @@ -532,12 +537,14 @@ mutable struct PostOptAnalysisState tpdum = TwoPhaseDefUseMap(length(ir.stmts)) lazypostdomtree = LazyPostDomtree(ir) lazyagdomtree = LazyAugmentedDomtree(ir) - return new(result, ir, inconsistent, tpdum, lazypostdomtree, lazyagdomtree, true, true, true, true, false) + return new(result, ir, inconsistent, tpdum, lazypostdomtree, lazyagdomtree, Int[], + true, true, nothing, true, true, false) end end give_up_refinements!(sv::PostOptAnalysisState) = - sv.all_retpaths_consistent = sv.all_effect_free = sv.all_nothrow = sv.all_noub = false + sv.all_retpaths_consistent = sv.all_effect_free = sv.effect_free_if_argmem_only = + sv.all_nothrow = sv.all_noub = false function any_refinable(sv::PostOptAnalysisState) effects = sv.result.ipo_effects @@ -547,12 +554,47 @@ function any_refinable(sv::PostOptAnalysisState) (!is_noub(effects) & sv.all_noub)) end -function refine_effects!(sv::PostOptAnalysisState) +struct GetNativeEscapeCache{CodeCache} + code_cache::CodeCache + GetNativeEscapeCache(code_cache::CodeCache) where CodeCache = new{CodeCache}(code_cache) +end +GetNativeEscapeCache(interp::AbstractInterpreter) = GetNativeEscapeCache(code_cache(interp)) +function ((; code_cache)::GetNativeEscapeCache)(mi::MethodInstance) + codeinst = get(code_cache, mi, nothing) + codeinst isa CodeInstance || return false + argescapes = traverse_analysis_results(codeinst) do @nospecialize result + return result isa EscapeAnalysis.ArgEscapeCache ? result : nothing + end + if argescapes !== nothing + return argescapes + end + effects = decode_effects(codeinst.ipo_purity_bits) + if is_effect_free(effects) && is_inaccessiblememonly(effects) + # We might not have run EA on simple frames without any escapes (e.g. when optimization + # is skipped when result is constant-folded by abstract interpretation). If those + # frames aren't inlined, the accuracy of EA for caller context takes a big hit. + # This is a HACK to avoid that, but obviously, a more comprehensive fix would be ideal. + return true + end + return false +end + +function refine_effects!(interp::AbstractInterpreter, sv::PostOptAnalysisState) + if !is_effect_free(sv.result.ipo_effects) && sv.all_effect_free && !isempty(sv.ea_analysis_pending) + ir = sv.ir + nargs = length(ir.argtypes) + estate = EscapeAnalysis.analyze_escapes(ir, nargs, GetNativeEscapeCache(interp)) + argescapes = EscapeAnalysis.ArgEscapeCache(estate) + stack_analysis_result!(sv.result, argescapes) + validate_mutable_arg_escapes!(estate, sv) + end + any_refinable(sv) || return false effects = sv.result.ipo_effects sv.result.ipo_effects = Effects(effects; consistent = sv.all_retpaths_consistent ? ALWAYS_TRUE : effects.consistent, - effect_free = sv.all_effect_free ? ALWAYS_TRUE : effects.effect_free, + effect_free = sv.all_effect_free ? ALWAYS_TRUE : + sv.effect_free_if_argmem_only === true ? EFFECT_FREE_IF_INACCESSIBLEMEMONLY : effects.effect_free, nothrow = sv.all_nothrow ? true : effects.nothrow, noub = sv.all_noub ? (sv.any_conditional_ub ? NOUB_IF_NOINBOUNDS : ALWAYS_TRUE) : effects.noub) return true @@ -584,6 +626,58 @@ function iscall_with_boundscheck(@nospecialize(stmt), sv::PostOptAnalysisState) return true end +function check_all_args_noescape!(sv::PostOptAnalysisState, ir::IRCode, @nospecialize(stmt), + estate::EscapeAnalysis.EscapeState) + stmt isa Expr || return false + if isexpr(stmt, :invoke) + startidx = 2 + elseif isexpr(stmt, :new) + startidx = 1 + else + return false + end + for i = startidx:length(stmt.args) + arg = stmt.args[i] + argt = argextype(arg, ir) + if is_mutation_free_argtype(argt) + continue + end + # See if we can find the allocation + if isa(arg, Argument) + if EscapeAnalysis.has_no_escape(EscapeAnalysis.ignore_argescape(estate[arg])) + # Even if we prove everything else effect_free, the best we can + # say is :effect_free_if_argmem_only + if sv.effect_free_if_argmem_only === nothing + sv.effect_free_if_argmem_only = true + end + else + sv.effect_free_if_argmem_only = false + end + return false + elseif isa(arg, SSAValue) + EscapeAnalysis.has_no_escape(estate[arg]) || return false + check_all_args_noescape!(sv, ir, ir[arg][:stmt], estate) || return false + else + return false + end + end + return true +end + +function validate_mutable_arg_escapes!(estate::EscapeAnalysis.EscapeState, sv::PostOptAnalysisState) + ir = sv.ir + for idx in sv.ea_analysis_pending + # See if any mutable memory was allocated in this function and determined + # not to escape. + inst = ir[SSAValue(idx)] + stmt = inst[:stmt] + if !check_all_args_noescape!(sv, ir, stmt, estate) + return sv.all_effect_free = false + end + end + return true +end + function is_conditional_noub(inst::Instruction, sv::PostOptAnalysisState) stmt = inst[:stmt] iscall_with_boundscheck(stmt, sv) || return false @@ -595,14 +689,30 @@ function is_conditional_noub(inst::Instruction, sv::PostOptAnalysisState) return true end +const IR_FLAGS_NEEDS_EA = IR_FLAG_EFIIMO | IR_FLAG_INACCESSIBLE_OR_ARGMEM + function scan_non_dataflow_flags!(inst::Instruction, sv::PostOptAnalysisState) flag = inst[:flag] + # If we can prove that the argmem does not escape the current function, we can + # refine this to :effect_free. + needs_ea_validation = (flag & IR_FLAGS_NEEDS_EA) == IR_FLAGS_NEEDS_EA stmt = inst[:stmt] - if !isterminator(stmt) && stmt !== nothing - # ignore control flow node โ€“ they are not removable on their own and thus not - # have `IR_FLAG_EFFECT_FREE` but still do not taint `:effect_free`-ness of - # the whole method invocation - sv.all_effect_free &= !iszero(flag & IR_FLAG_EFFECT_FREE) + if !needs_ea_validation + if !isterminator(stmt) && stmt !== nothing + # ignore control flow node โ€“ they are not removable on their own and thus not + # have `IR_FLAG_EFFECT_FREE` but still do not taint `:effect_free`-ness of + # the whole method invocation + sv.all_effect_free &= !iszero(flag & IR_FLAG_EFFECT_FREE) + end + elseif sv.all_effect_free + if (isexpr(stmt, :invoke) || isexpr(stmt, :new) || + # HACK for performance: limit the scope of EA to code with object field access only, + # since its abilities to reason about e.g. arrays are currently very limited anyways. + is_known_call(stmt, setfield!, sv.ir)) + push!(sv.ea_analysis_pending, inst.idx) + else + sv.all_effect_free = false + end end sv.all_nothrow &= !iszero(flag & IR_FLAG_NOTHROW) if iszero(flag & IR_FLAG_NOUB) @@ -747,7 +857,9 @@ function check_inconsistentcy!(sv::PostOptAnalysisState, scanner::BBScanner) end function ipo_dataflow_analysis!(interp::AbstractInterpreter, ir::IRCode, result::InferenceResult) - is_ipo_dataflow_analysis_profitable(result.ipo_effects) || return false + if !is_ipo_dataflow_analysis_profitable(result.ipo_effects) + return false + end @assert isempty(ir.new_nodes) "IRCode should be compacted before post-opt analysis" @@ -772,7 +884,7 @@ function ipo_dataflow_analysis!(interp::AbstractInterpreter, ir::IRCode, result: end end - return refine_effects!(sv) + return refine_effects!(interp, sv) end # run the optimization work diff --git a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl index a770a4a92146c3..70d45819574fdc 100644 --- a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl +++ b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl @@ -610,7 +610,7 @@ end Analyzes escape information in `ir`: - `nargs`: the number of actual arguments of the analyzed call -- `get_escape_cache(::MethodInstance) -> Union{Nothing,ArgEscapeCache}`: +- `get_escape_cache(::MethodInstance) -> Union{Bool,ArgEscapeCache}`: retrieves cached argument escape information """ function analyze_escapes(ir::IRCode, nargs::Int, get_escape_cache) @@ -1061,11 +1061,14 @@ function escape_invoke!(astate::AnalysisState, pc::Int, args::Vector{Any}) first_idx, last_idx = 2, length(args) # TODO inspect `astate.ir.stmts[pc][:info]` and use const-prop'ed `InferenceResult` if available cache = astate.get_escape_cache(mi) - if cache === nothing - return add_conservative_changes!(astate, pc, args, 2) - else - cache = cache::ArgEscapeCache + if cache isa Bool + if cache + return nothing # guaranteed to have no escape + else + return add_conservative_changes!(astate, pc, args, 2) + end end + cache = cache::ArgEscapeCache ret = SSAValue(pc) retinfo = astate.estate[ret] # escape information imposed on the call statement method = mi.def::Method diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index de1b3bd2cb79e1..26dacbc3388f3e 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -1003,6 +1003,11 @@ function flags_for_effects(effects::Effects) end if is_effect_free(effects) flags |= IR_FLAG_EFFECT_FREE + elseif is_effect_free_if_inaccessiblememonly(effects) + flags |= IR_FLAG_EFIIMO + end + if is_inaccessiblemem_or_argmemonly(effects) + flags |= IR_FLAG_INACCESSIBLE_OR_ARGMEM end if is_nothrow(effects) flags |= IR_FLAG_NOTHROW diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 48558c05f1e8d4..c1230980c42a65 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -330,7 +330,7 @@ function CodeInstance(interp::AbstractInterpreter, result::InferenceResult, widenconst(result_type), rettype_const, inferred_result, const_flags, first(valid_worlds), last(valid_worlds), # TODO: Actually do something with non-IPO effects - encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), result.argescapes, + encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), result.analysis_results, relocatability) end diff --git a/base/compiler/types.jl b/base/compiler/types.jl index dde5335b36f688..41fde10dfedf45 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -59,6 +59,16 @@ end abstract type ForwardableArgtypes end +struct AnalysisResults + result + next::AnalysisResults + AnalysisResults(@nospecialize(result), next::AnalysisResults) = new(result, next) + AnalysisResults(@nospecialize(result)) = new(result) + # NullAnalysisResults() = new(nothing) + # global const NULL_ANALYSIS_RESULTS = NullAnalysisResults() +end +const NULL_ANALYSIS_RESULTS = AnalysisResults(nothing) + """ InferenceResult(linfo::MethodInstance, [argtypes::ForwardableArgtypes, ๐•ƒ::AbstractLattice]) @@ -75,14 +85,14 @@ mutable struct InferenceResult valid_worlds::WorldRange # if inference and optimization is finished ipo_effects::Effects # if inference is finished effects::Effects # if optimization is finished - argescapes # ::ArgEscapeCache if optimized, nothing otherwise + analysis_results::AnalysisResults # AnalysisResults with e.g. result::ArgEscapeCache if optimized, otherwise NULL_ANALYSIS_RESULTS is_src_volatile::Bool # `src` has been cached globally as the compressed format already, allowing `src` to be used destructively function InferenceResult(linfo::MethodInstance, cache_argtypes::Vector{Any}, overridden_by_const::BitVector) # def = linfo.def # nargs = def isa Method ? Int(def.nargs) : 0 # @assert length(cache_argtypes) == nargs return new(linfo, cache_argtypes, overridden_by_const, nothing, nothing, - WorldRange(), Effects(), Effects(), nothing, false) + WorldRange(), Effects(), Effects(), NULL_ANALYSIS_RESULTS, false) end end InferenceResult(linfo::MethodInstance, ๐•ƒ::AbstractLattice=fallback_lattice) = @@ -90,6 +100,21 @@ InferenceResult(linfo::MethodInstance, ๐•ƒ::AbstractLattice=fallback_lattice) = InferenceResult(linfo::MethodInstance, argtypes::ForwardableArgtypes, ๐•ƒ::AbstractLattice=fallback_lattice) = InferenceResult(linfo, matching_cache_argtypes(๐•ƒ, linfo, argtypes)...) +function stack_analysis_result!(inf_result::InferenceResult, @nospecialize(result)) + return inf_result.analysis_results = AnalysisResults(result, inf_result.analysis_results) +end + +function traverse_analysis_results(callback, (;analysis_results)::Union{InferenceResult,CodeInstance}) + analysis_results isa AnalysisResults || return nothing + while isdefined(analysis_results, :next) + if (result = callback(analysis_results.result)) !== nothing + return result + end + analysis_results = analysis_results.next + end + return nothing +end + """ inf_params::InferenceParams diff --git a/src/gf.c b/src/gf.c index f2365ee0406700..f964927aa33681 100644 --- a/src/gf.c +++ b/src/gf.c @@ -288,7 +288,7 @@ 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, - uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, + uint32_t ipo_effects, uint32_t effects, jl_value_t *analysis_results, uint8_t relocatability); jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_args_t fptr) JL_GC_DISABLED @@ -486,7 +486,7 @@ 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, - uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, + uint32_t ipo_effects, uint32_t effects, jl_value_t *analysis_results, uint8_t relocatability /*, jl_array_t *edges, int absolute_max*/) { @@ -514,7 +514,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( jl_atomic_store_relaxed(&codeinst->next, NULL); codeinst->ipo_purity_bits = ipo_effects; jl_atomic_store_relaxed(&codeinst->purity_bits, effects); - codeinst->argescapes = argescapes; + codeinst->analysis_results = analysis_results; codeinst->relocatability = relocatability; return codeinst; } diff --git a/src/jltypes.c b/src/jltypes.c index 9f27b14785dbe0..b1830ec4e765e6 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3255,7 +3255,7 @@ void jl_init_types(void) JL_GC_DISABLED //"edges", //"absolute_max", "ipo_purity_bits", "purity_bits", - "argescapes", + "analysis_results", "isspecsig", "precompile", "relocatability", "invoke", "specptr"), // function object decls jl_svec(15, diff --git a/src/julia.h b/src/julia.h index 034280524cd11a..acb700c0bd936e 100644 --- a/src/julia.h +++ b/src/julia.h @@ -436,7 +436,7 @@ typedef struct _jl_code_instance_t { // uint8_t nonoverlayed : 1; // uint8_t notaskstate : 2; // uint8_t inaccessiblememonly : 2; - jl_value_t *argescapes; // escape information of call arguments + jl_value_t *analysis_results; // Analysis results about this code (IPO-safe) // compilation state cache _Atomic(uint8_t) specsigflags; // & 0b001 == specptr is a specialized function signature for specTypes->rettype diff --git a/src/opaque_closure.c b/src/opaque_closure.c index d73beff0f85870..8b7cc7292be229 100644 --- a/src/opaque_closure.c +++ b/src/opaque_closure.c @@ -138,7 +138,7 @@ 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, - uint32_t ipo_effects, uint32_t effects, jl_value_t *argescapes, + uint32_t ipo_effects, uint32_t effects, jl_value_t *analysis_results, uint8_t relocatability); JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tupletype_t *argt, jl_value_t *rt_lb, jl_value_t *rt_ub, diff --git a/test/compiler/EscapeAnalysis/EAUtils.jl b/test/compiler/EscapeAnalysis/EAUtils.jl index 937113c34c872d..27428e07e920a0 100644 --- a/test/compiler/EscapeAnalysis/EAUtils.jl +++ b/test/compiler/EscapeAnalysis/EAUtils.jl @@ -62,14 +62,13 @@ __clear_cache!() = empty!(GLOBAL_EA_CODE_CACHE) # imports import .CC: AbstractInterpreter, NativeInterpreter, WorldView, WorldRange, - InferenceParams, OptimizationParams, get_world_counter, get_inference_cache, code_cache + InferenceParams, OptimizationParams, get_world_counter, get_inference_cache, code_cache, + ipo_dataflow_analysis!, cache_result! # usings using Core: CodeInstance, MethodInstance, CodeInfo using .CC: - InferenceResult, OptimizationState, IRCode, copy as cccopy, - @timeit, convert_to_ircode, slot2reg, compact!, ssa_inlining_pass!, sroa_pass!, - adce_pass!, JLOptions, verify_ir, verify_linetable + InferenceResult, OptimizationState, IRCode using .EA: analyze_escapes, ArgEscapeCache, EscapeInfo, EscapeState struct CodeCache @@ -144,17 +143,31 @@ function CC.setindex!(wvc::WorldView{EscapeAnalyzerCacheView}, ci::CodeInstance, return wvc end -function CC.optimize(interp::EscapeAnalyzer, opt::OptimizationState, caller::InferenceResult) - ir = run_passes_ipo_safe_with_ea(interp, opt.src, opt, caller) - CC.ipo_dataflow_analysis!(interp, ir, caller) - return CC.finish(interp, opt, ir, caller) +function CC.ipo_dataflow_analysis!(interp::EscapeAnalyzer, ir::IRCode, caller::InferenceResult) + # run EA on all frames that have been optimized + nargs = let def = caller.linfo.def; isa(def, Method) ? Int(def.nargs) : 0; end + get_escape_cache = GetEscapeCache(interp) + estate = try + analyze_escapes(ir, nargs, get_escape_cache) + catch err + @error "error happened within EA, inspect `Main.failed_escapeanalysis`" + Main.failed_escapeanalysis = FailedAnalysis(ir, nargs, get_escape_cache) + rethrow(err) + end + if caller.linfo === interp.entry_mi + # return back the result + interp.result = EscapeResultForEntry(CC.copy(ir), estate, caller.linfo) + end + record_escapes!(interp, caller, estate, ir) + + @invoke CC.ipo_dataflow_analysis!(interp::AbstractInterpreter, ir::IRCode, caller::InferenceResult) end function record_escapes!(interp::EscapeAnalyzer, - caller::InferenceResult, estate::EscapeState, cacheir::IRCode) - cache = ArgEscapeCache(estate) - ecache = EscapeCacheInfo(cache, estate, cacheir) - return caller.argescapes = ecache + caller::InferenceResult, estate::EscapeState, ir::IRCode) + argescapes = ArgEscapeCache(estate) + ecacheinfo = EscapeCacheInfo(argescapes, estate, ir) + return CC.stack_analysis_result!(caller, ecacheinfo) end struct GetEscapeCache @@ -162,8 +175,8 @@ struct GetEscapeCache GetEscapeCache(interp::EscapeAnalyzer) = new(interp.escape_cache) end function ((; escape_cache)::GetEscapeCache)(mi::MethodInstance) - cached = get(escape_cache.cache, mi, nothing) - return cached === nothing ? nothing : cached.argescapes + ecacheinfo = get(escape_cache.cache, mi, nothing) + return ecacheinfo === nothing ? false : ecacheinfo.argescapes end struct FailedAnalysis @@ -172,45 +185,12 @@ struct FailedAnalysis get_escape_cache::GetEscapeCache end -function run_passes_ipo_safe_with_ea(interp::EscapeAnalyzer, - ci::CodeInfo, sv::OptimizationState, caller::InferenceResult) - @timeit "convert" ir = convert_to_ircode(ci, sv) - @timeit "slot2reg" ir = slot2reg(ir, ci, sv) - # TODO: Domsorting can produce an updated domtree - no need to recompute here - @timeit "compact 1" ir = compact!(ir) - @timeit "Inlining" ir = ssa_inlining_pass!(ir, sv.inlining, ci.propagate_inbounds) - # @timeit "verify 2" verify_ir(ir) - @timeit "compact 2" ir = compact!(ir) - @timeit "SROA" ir = sroa_pass!(ir, sv.inlining) - @timeit "ADCE" ir = adce_pass!(ir, sv.inlining) - @timeit "compact 3" ir = compact!(ir, true) - if JLOptions().debug_level == 2 - @timeit "verify 3" (verify_ir(ir); verify_linetable(ir.linetable)) - end - nargs = let def = sv.linfo.def; isa(def, Method) ? Int(def.nargs) : 0; end - get_escape_cache = GetEscapeCache(interp) - local estate::EscapeState - try - @timeit "EA" estate = analyze_escapes(ir, nargs, get_escape_cache) - catch err - @error "error happened within EA, inspect `Main.failed_escapeanalysis`" - Main.failed_escapeanalysis = FailedAnalysis(ir, nargs, get_escape_cache) - rethrow(err) - end - if caller.linfo === interp.entry_mi - # return back the result - interp.result = EscapeResultForEntry(cccopy(ir), estate, sv.linfo) - end - record_escapes!(interp, caller, estate, ir) - return ir -end - -function CC.cache_result!(interp::EscapeAnalyzer, result::InferenceResult) - argescapes = result.argescapes - if argescapes isa EscapeCacheInfo - interp.escape_cache.cache[result.linfo] = argescapes +function CC.cache_result!(interp::EscapeAnalyzer, inf_result::InferenceResult) + ecacheinfo = CC.traverse_analysis_results(inf_result) do @nospecialize result + return result isa EscapeCacheInfo ? result : nothing end - return @invoke CC.cache_result!(interp::AbstractInterpreter, result::InferenceResult) + ecacheinfo isa EscapeCacheInfo && (interp.escape_cache.cache[inf_result.linfo] = ecacheinfo) + return @invoke CC.cache_result!(interp::AbstractInterpreter, inf_result::InferenceResult) end # printing @@ -278,7 +258,7 @@ Base.show(io::IO, result::EscapeResult) = print_with_info(io, result) @eval Base.iterate(res::EscapeResult, state=1) = return state > $(fieldcount(EscapeResult)) ? nothing : (getfield(res, state), state+1) -Base.show(io::IO, cached::EscapeCacheInfo) = show(io, EscapeResult(cached.ir, cached.state)) +Base.show(io::IO, ecacheinfo::EscapeCacheInfo) = show(io, EscapeResult(ecacheinfo.ir, ecacheinfo.state)) # adapted from https://github.com/JuliaDebug/LoweredCodeUtils.jl/blob/4612349432447e868cf9285f647108f43bd0a11c/src/codeedges.jl#L881-L897 function print_with_info(io::IO, result::EscapeResult) diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 96d9017e0699b0..5405066263d79f 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -1133,20 +1133,60 @@ end post_opt_refine_effect_free(y, true) end |> Core.Compiler.is_effect_free -# effects for Cmd construction -for f in (() -> `a b c`, () -> `a a$("bb")a $("c")`) - effects = Base.infer_effects(f) - @test Core.Compiler.is_effect_free(effects) - @test Core.Compiler.is_terminates(effects) - @test Core.Compiler.is_noub(effects) - @test !Core.Compiler.is_consistent(effects) +# Check EA-based refinement of :effect_free +Base.@assume_effects :nothrow @noinline _noinline_set!(x) = (x[] = 1; nothing) + +function set_ref_with_unused_arg_1(_) + x = Ref(0) + _noinline_set!(x) + return nothing end -let effects = Base.infer_effects(x -> `a $x`, (Any,)) - @test !Core.Compiler.is_effect_free(effects) - @test !Core.Compiler.is_terminates(effects) - @test !Core.Compiler.is_noub(effects) - @test !Core.Compiler.is_consistent(effects) +function set_ref_with_unused_arg_2(_) + x = @noinline Ref(0) + _noinline_set!(x) + return nothing end +function set_arg_ref!(x) + _noinline_set!(x) + y = Ref(false) + y[] && (Main.x = x) + return nothing +end + +function set_arr_with_unused_arg_1(_) + x = Int[0] + _noinline_set!(x) + return nothing +end +function set_arr_with_unused_arg_2(_) + x = @noinline Int[0] + _noinline_set!(x) + return nothing +end +function set_arg_arr!(x) + _noinline_set!(x) + y = Bool[false] + y[] && (Main.x = x) + return nothing +end + +# This is inferable by type analysis only since the arguments have no mutable memory +@test Core.Compiler.is_effect_free_if_inaccessiblememonly(Base.infer_effects(_noinline_set!, (Base.RefValue{Int},))) +@test Core.Compiler.is_effect_free_if_inaccessiblememonly(Base.infer_effects(_noinline_set!, (Vector{Int},))) +for func in (set_ref_with_unused_arg_1, set_ref_with_unused_arg_2, + set_arr_with_unused_arg_1, set_arr_with_unused_arg_2) + effects = Base.infer_effects(func, (Nothing,)) + @test Core.Compiler.is_inaccessiblememonly(effects) + @test Core.Compiler.is_effect_free(effects) +end + +# These need EA +@test Core.Compiler.is_effect_free(Base.infer_effects(set_ref_with_unused_arg_1, (Base.RefValue{Int},))) +@test Core.Compiler.is_effect_free(Base.infer_effects(set_ref_with_unused_arg_2, (Base.RefValue{Int},))) +@test Core.Compiler.is_effect_free_if_inaccessiblememonly(Base.infer_effects(set_arg_ref!, (Base.RefValue{Int},))) +@test_broken Core.Compiler.is_effect_free(Base.infer_effects(set_arr_with_unused_arg_1, (Vector{Int},))) +@test_broken Core.Compiler.is_effect_free(Base.infer_effects(set_arr_with_unused_arg_2, (Vector{Int},))) +@test_broken Core.Compiler.is_effect_free_if_inaccessiblememonly(Base.infer_effects(set_arg_arr!, (Vector{Int},))) function issue51837(; openquotechar::Char, newlinechar::Char) ncodeunits(openquotechar) == 1 || throw(ArgumentError("`openquotechar` must be a single-byte character")) diff --git a/test/core.jl b/test/core.jl index e9a680cd506c43..00ab41e4ecd487 100644 --- a/test/core.jl +++ b/test/core.jl @@ -14,7 +14,7 @@ include("testenv.jl") # sanity tests that our built-in types are marked correctly for const fields for (T, c) in ( (Core.CodeInfo, []), - (Core.CodeInstance, [:def, :rettype, :rettype_const, :ipo_purity_bits, :argescapes]), + (Core.CodeInstance, [:def, :rettype, :rettype_const, :ipo_purity_bits, :analysis_results]), (Core.Method, [#=:name, :module, :file, :line, :primary_world, :sig, :slot_syms, :external_mt, :nargs, :called, :nospecialize, :nkw, :isva, :is_for_opaque_closure, :constprop=#]), (Core.MethodInstance, [#=:def, :specTypes, :sparam_vals=#]), (Core.MethodTable, [:module]), diff --git a/test/spawn.jl b/test/spawn.jl index 1fab652199ee03..dab18573993752 100644 --- a/test/spawn.jl +++ b/test/spawn.jl @@ -1015,5 +1015,19 @@ end args = ["ab ^` c", " \" ", "\"", ascii95, ascii95, "\"\\\"\\", "", "|", "&&", ";"]; @test Base.shell_escape_wincmd(Base.escape_microsoft_c_args(args...)) == "\"ab ^` c\" \" \\\" \" \"\\\"\" \" !\\\"#\$%^&'^(^)*+,-./0123456789:;^<=^>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^^_`abcdefghijklmnopqrstuvwxyz{^|}~\" \" ^!\\\"#\$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\" \"\\\"\\\\\\\"\\\\\" \"\" ^| ^&^& ;" +end +# effects for Cmd construction +for f in (() -> `a b c`, () -> `a a$("bb")a $("c")`) + effects = Base.infer_effects(f) + @test Core.Compiler.is_effect_free(effects) + @test Core.Compiler.is_terminates(effects) + @test Core.Compiler.is_noub(effects) + @test !Core.Compiler.is_consistent(effects) +end +let effects = Base.infer_effects(x -> `a $x`, (Any,)) + @test !Core.Compiler.is_effect_free(effects) + @test !Core.Compiler.is_terminates(effects) + @test !Core.Compiler.is_noub(effects) + @test !Core.Compiler.is_consistent(effects) end From 045b6f9c8837776d11e75a73841d6c23718c4e08 Mon Sep 17 00:00:00 2001 From: Stefan Karpinski Date: Fri, 17 Nov 2023 11:46:13 -0500 Subject: [PATCH 13/38] invmod(n::BitInteger): efficient native modular inverses (#52180) Implement algorithm described in https://arxiv.org/pdf/2204.04342.pdf. The algorithm is pleasingly simple and efficient and the generic Julia implementation is also really enjoyable. --- NEWS.md | 3 +++ base/intfuncs.jl | 39 ++++++++++++++++++++++++++++++++++++++- test/intfuncs.jl | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 30dd2e6e037832..c9b968da3c1e09 100644 --- a/NEWS.md +++ b/NEWS.md @@ -63,6 +63,9 @@ New library functions New library features -------------------- + +* `invmod(n, T)` where `T` is a native integer type now computes the modular inverse of `n` in the modular integer ring that `T` defines ([#52180]). +* `invmod(n)` is an abbreviation for `invmod(n, typeof(n))` for native integer types ([#52180]). * `replace(string, pattern...)` now supports an optional `IO` argument to write the output to a stream rather than returning a string ([#48625]). * `sizehint!(s, n)` now supports an optional `shrink` argument to disable shrinking ([#51929]). diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 558d55c118b349..ea35da0910736e 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -218,7 +218,7 @@ gcdx(a::T, b::T) where T<:Real = throw(MethodError(gcdx, (a,b))) # multiplicative inverse of n mod m, error if none """ - invmod(n, m) + invmod(n::Integer, m::Integer) Take the inverse of `n` modulo `m`: `y` such that ``n y = 1 \\pmod m``, and ``div(y,m) = 0``. This will throw an error if ``m = 0``, or if @@ -257,6 +257,43 @@ function invmod(n::Integer, m::Integer) return mod(x, m) end +""" + invmod(n::Integer, T) where {T <: Base.BitInteger} + invmod(n::T) where {T <: Base.BitInteger} + +Compute the modular inverse of `n` in the integer ring of type `T`, i.e. modulo +`2^N` where `N = 8*sizeof(T)` (e.g. `N = 32` for `Int32`). In other words these +methods satisfy the following identities: +``` +n * invmod(n) == 1 +(n * invmod(n, T)) % T == 1 +(n % T) * invmod(n, T) == 1 +``` +Note that `*` here is modular multiplication in the integer ring, `T`. + +Specifying the modulus implied by an integer type as an explicit value is often +inconvenient since the modulus is by definition too big to be represented by the +type. + +The modular inverse is computed much more efficiently than the general case +using the algorithm described in https://arxiv.org/pdf/2204.04342.pdf. + +!!! compat "Julia 1.11" + The `invmod(n)` and `invmod(n, T)` methods require Julia 1.11 or later. +""" +invmod(n::Integer, ::Type{T}) where {T<:BitInteger} = invmod(n % T) + +function invmod(n::T) where {T<:BitInteger} + isodd(n) || throw(DomainError(n, "Argument must be odd.")) + x = (3*n โŠป 2) % T + y = (1 - n*x) % T + for _ = 1:trailing_zeros(2*sizeof(T)) + x *= y + true + y *= y + end + return x +end + # ^ for any x supporting * to_power_type(x) = convert(Base._return_type(*, Tuple{typeof(x), typeof(x)}), x) @noinline throw_domerr_powbysq(::Any, p) = throw(DomainError(p, LazyString( diff --git a/test/intfuncs.jl b/test/intfuncs.jl index ceaac235a3da9a..ed661b2806fb5b 100644 --- a/test/intfuncs.jl +++ b/test/intfuncs.jl @@ -221,7 +221,7 @@ end @test_throws MethodError gcdx(MyOtherRational(2//3), MyOtherRational(3//4)) end -@testset "invmod" begin +@testset "invmod(n, m)" begin @test invmod(6, 31) === 26 @test invmod(-1, 3) === 2 @test invmod(1, -3) === -2 @@ -256,6 +256,37 @@ end end end +@testset "invmod(n)" begin + for T in (Int8,UInt8,Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128) + if sizeof(T) โ‰ค 2 + # test full domain for small types + for a = typemin(T)+true:T(2):typemax(T) + b = invmod(a) + @test a * b == 1 + end + else + # test random sample for large types + for _ = 1:2^12 + a = rand(T) | true + b = invmod(a) + @test a * b == 1 + end + end + end +end + +@testset "invmod(n, T)" begin + for S in (Int8,UInt8,Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128), + T in (Int8,UInt8,Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128) + for _ = 1:2^8 + a = rand(S) | true + b = invmod(a, T) + @test (a * b) % T == 1 + @test (a % T) * b == 1 + end + end +end + @testset "powermod" begin @test powermod(2, 3, 5) == 3 @test powermod(2, 3, -5) == -2 From 9aa7980358349ee7017fa614525f571ffa92c55d Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 17 Nov 2023 13:58:01 -0500 Subject: [PATCH 14/38] codegen: ensure i1 bool is widened to i8 before storing (#52189) Teach value_to_pointer to convert primitive types to their stored representation first, to avoid exposing undef bits later (via memcpy). Take this opportunity to also generalizes the support for zext Bool to anywhere inside any struct for changing any bitwidth to a multiple of 8 bytes. This would change a vector like <2 x i4> from occupying i8 to i16 (c.f. LLVM's LangRef), if such an operation were expressible in Julia today. And take this opportunity to do a bit of code cleanup, now that codegen is better and using helpers from LLVM. Fixes #52127 --- src/cgutils.cpp | 3 -- src/codegen.cpp | 27 +++------- src/intrinsics.cpp | 119 ++++++++++++++++++++++++++++++--------------- test/llvmcall2.jl | 9 ++++ 4 files changed, 98 insertions(+), 60 deletions(-) diff --git a/src/cgutils.cpp b/src/cgutils.cpp index ea59d4e298f1ca..b560f84a4db8c7 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -783,9 +783,6 @@ static Type *_julia_struct_to_llvm(jl_codegen_params_t *ctx, LLVMContext &ctxt, lty = JuliaType::get_prjlvalue_ty(ctxt); isvector = false; } - else if (ty == (jl_value_t*)jl_bool_type) { - lty = getInt8Ty(ctxt); - } else if (jl_is_uniontype(ty)) { // pick an Integer type size such that alignment will generally be correct, // and always end with an Int8 (selector byte). diff --git a/src/codegen.cpp b/src/codegen.cpp index 3209b076b57462..e09cec49e6c0ac 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2163,9 +2163,12 @@ static bool valid_as_globalinit(const Value *v) { return isa(v); } +static Value *zext_struct(jl_codectx_t &ctx, Value *V); + static inline jl_cgval_t value_to_pointer(jl_codectx_t &ctx, Value *v, jl_value_t *typ, Value *tindex) { Value *loc; + v = zext_struct(ctx, v); if (valid_as_globalinit(v)) { // llvm can't handle all the things that could be inside a ConstantExpr assert(jl_is_concrete_type(typ)); // not legal to have an unboxed abstract type loc = get_pointer_to_constant(ctx.emission_context, cast(v), Align(julia_alignment(typ)), "_j_const", *jl_Module); @@ -2291,17 +2294,6 @@ static void alloc_def_flag(jl_codectx_t &ctx, jl_varinfo_t& vi) // --- utilities --- -static Constant *undef_value_for_type(Type *T) { - auto tracked = CountTrackedPointers(T); - Constant *undef; - if (tracked.count) - // make sure gc pointers (including ptr_phi of union-split) are initialized to NULL - undef = Constant::getNullValue(T); - else - undef = UndefValue::get(T); - return undef; -} - static void CreateTrap(IRBuilder<> &irbuilder, bool create_new_block) { Function *f = irbuilder.GetInsertBlock()->getParent(); @@ -3574,7 +3566,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (f == jl_builtin_is && nargs == 2) { // emit comparison test Value *ans = emit_f_is(ctx, argv[1], argv[2]); - *ret = mark_julia_type(ctx, ctx.builder.CreateZExt(ans, getInt8Ty(ctx.builder.getContext())), false, jl_bool_type); + *ret = mark_julia_type(ctx, ans, false, jl_bool_type); return true; } @@ -3613,8 +3605,6 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, if (jl_is_type_type(ty.typ) && !jl_has_free_typevars(ty.typ)) { jl_value_t *tp0 = jl_tparam0(ty.typ); Value *isa_result = emit_isa(ctx, arg, tp0, Twine()).first; - if (isa_result->getType() == getInt1Ty(ctx.builder.getContext())) - isa_result = ctx.builder.CreateZExt(isa_result, getInt8Ty(ctx.builder.getContext())); *ret = mark_julia_type(ctx, isa_result, false, jl_bool_type); return true; } @@ -5551,16 +5541,15 @@ static Value *emit_condition(jl_codectx_t &ctx, const jl_cgval_t &condV, const T emit_typecheck(ctx, condV, (jl_value_t*)jl_bool_type, msg); } if (isbool) { - Value *cond = emit_unbox(ctx, getInt8Ty(ctx.builder.getContext()), condV, (jl_value_t*)jl_bool_type); - assert(cond->getType() == getInt8Ty(ctx.builder.getContext())); - return ctx.builder.CreateXor(ctx.builder.CreateTrunc(cond, getInt1Ty(ctx.builder.getContext())), ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 1)); + Value *cond = emit_unbox(ctx, getInt1Ty(ctx.builder.getContext()), condV, (jl_value_t*)jl_bool_type); + return ctx.builder.CreateNot(cond); } if (condV.isboxed) { return ctx.builder.CreateICmpEQ(boxed(ctx, condV), track_pjlvalue(ctx, literal_pointer_val(ctx, jl_false))); } - // not a boolean - return ConstantInt::get(getInt1Ty(ctx.builder.getContext()), 0); // TODO: replace with Undef + // not a boolean (unreachable dead code) + return UndefValue::get(getInt1Ty(ctx.builder.getContext())); } static Value *emit_condition(jl_codectx_t &ctx, jl_value_t *cond, const Twine &msg) diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index b4d493ee808008..fde27b910faeac 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -173,12 +173,7 @@ static Type *INTT(Type *t, const DataLayout &DL) static Value *uint_cnvt(jl_codectx_t &ctx, Type *to, Value *x) { - Type *t = x->getType(); - if (t == to) - return x; - if (to->getPrimitiveSizeInBits() < x->getType()->getPrimitiveSizeInBits()) - return ctx.builder.CreateTrunc(x, to); - return ctx.builder.CreateZExt(x, to); + return ctx.builder.CreateZExtOrTrunc(x, to); } static Constant *julia_const_to_llvm(jl_codectx_t &ctx, const void *ptr, jl_datatype_t *bt) @@ -317,25 +312,90 @@ static Constant *julia_const_to_llvm(jl_codectx_t &ctx, jl_value_t *e) return julia_const_to_llvm(ctx, e, (jl_datatype_t*)bt); } +static Constant *undef_value_for_type(Type *T) { + auto tracked = CountTrackedPointers(T); + Constant *undef; + if (tracked.count) + // make sure gc pointers (including ptr_phi of union-split) are initialized to NULL + undef = Constant::getNullValue(T); + else + undef = UndefValue::get(T); + return undef; +} + +// rebuild a struct type with any i1 Bool (e.g. the llvmcall type) widened to i8 (the native size for memcpy) +static Type *zext_struct_type(Type *T) +{ + if (auto *AT = dyn_cast(T)) { + return ArrayType::get(AT->getElementType(), AT->getNumElements()); + } + else if (auto *ST = dyn_cast(T)) { + SmallVector Elements(ST->element_begin(), ST->element_end()); + for (size_t i = 0; i < Elements.size(); i++) { + Elements[i] = zext_struct_type(Elements[i]); + } + return StructType::get(ST->getContext(), Elements, ST->isPacked()); + } + else if (auto *VT = dyn_cast(T)) { + return VectorType::get(zext_struct_type(VT->getElementType()), VT); + } + else if (auto *IT = dyn_cast(T)) { + unsigned BitWidth = IT->getBitWidth(); + if (alignTo(BitWidth, 8) != BitWidth) + return IntegerType::get(IT->getContext(), alignTo(BitWidth, 8)); + } + return T; +} + +// rebuild a struct with any i1 Bool (e.g. the llvmcall type) widened to i8 (the native size for memcpy) +static Value *zext_struct_helper(jl_codectx_t &ctx, Value *V, Type *T2) +{ + Type *T = V->getType(); + if (T == T2) + return V; + if (auto *AT = dyn_cast(T2)) { + Value *V2 = undef_value_for_type(AT); + for (size_t i = 0; i < AT->getNumElements(); i++) { + Value *E = zext_struct_helper(ctx, ctx.builder.CreateExtractValue(V, i), AT->getElementType()); + V2 = ctx.builder.CreateInsertValue(V2, E, i); + } + return V2; + } + else if (auto *ST = dyn_cast(T2)) { + Value *V2 = undef_value_for_type(ST); + for (size_t i = 0; i < ST->getNumElements(); i++) { + Value *E = zext_struct_helper(ctx, ctx.builder.CreateExtractValue(V, i), ST->getElementType(i)); + V2 = ctx.builder.CreateInsertValue(V2, E, i); + } + return V2; + } + else if (T2->isIntegerTy() || T2->isVectorTy()) { + return ctx.builder.CreateZExt(V, T2); + } + return V; +} + +static Value *zext_struct(jl_codectx_t &ctx, Value *V) +{ + return zext_struct_helper(ctx, V, zext_struct_type(V->getType())); +} + static Value *emit_unboxed_coercion(jl_codectx_t &ctx, Type *to, Value *unboxed) { + if (unboxed->getType() == to) + return unboxed; + if (CastInst::castIsValid(Instruction::Trunc, unboxed, to)) + return ctx.builder.CreateTrunc(unboxed, to); + unboxed = zext_struct(ctx, unboxed); Type *ty = unboxed->getType(); if (ty == to) return unboxed; bool frompointer = ty->isPointerTy(); bool topointer = to->isPointerTy(); const DataLayout &DL = jl_Module->getDataLayout(); - if (ty->isIntegerTy(1) && to->isIntegerTy(8)) { - // bools may be stored internally as int8 - unboxed = ctx.builder.CreateZExt(unboxed, to); - } - else if (ty->isIntegerTy(8) && to->isIntegerTy(1)) { - // bools may be stored internally as int8 - unboxed = ctx.builder.CreateTrunc(unboxed, to); - } - else if (ty->isVoidTy() || DL.getTypeSizeInBits(ty) != DL.getTypeSizeInBits(to)) { + if (ty->isVoidTy() || DL.getTypeSizeInBits(ty) != DL.getTypeSizeInBits(to)) { // this can happen in dead code - //emit_unreachable(ctx); + CreateTrap(ctx.builder); return UndefValue::get(to); } if (frompointer && topointer) { @@ -380,7 +440,7 @@ static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, jl_va if (type_is_ghost(to)) { return NULL; } - //emit_unreachable(ctx); + CreateTrap(ctx.builder); return UndefValue::get(to); // type mismatch error } @@ -446,17 +506,9 @@ static void emit_unbox_store(jl_codectx_t &ctx, const jl_cgval_t &x, Value *dest return; } - Value *unboxed = nullptr; - if (!x.ispointer()) { // already unboxed, but sometimes need conversion - unboxed = x.V; - assert(unboxed); - } - - // bools stored as int8, but can be narrowed to int1 often - if (x.typ == (jl_value_t*)jl_bool_type) - unboxed = emit_unbox(ctx, getInt8Ty(ctx.builder.getContext()), x, (jl_value_t*)jl_bool_type); - - if (unboxed) { + if (!x.ispointer()) { // already unboxed, but sometimes need conversion (e.g. f32 -> i32) + assert(x.V); + Value *unboxed = zext_struct(ctx, x.V); Type *dest_ty = unboxed->getType()->getPointerTo(); if (dest->getType() != dest_ty) dest = emit_bitcast(ctx, dest, dest_ty); @@ -1445,12 +1497,7 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg Intrinsic::smul_with_overflow : Intrinsic::umul_with_overflow))))); FunctionCallee intr = Intrinsic::getDeclaration(jl_Module, intr_id, ArrayRef(t)); - Value *res = ctx.builder.CreateCall(intr, {x, y}); - Value *val = ctx.builder.CreateExtractValue(res, ArrayRef(0)); - setName(ctx.emission_context, val, "checked"); - Value *obit = ctx.builder.CreateExtractValue(res, ArrayRef(1)); - setName(ctx.emission_context, obit, "overflow"); - Value *obyte = ctx.builder.CreateZExt(obit, getInt8Ty(ctx.builder.getContext())); + Value *tupval = ctx.builder.CreateCall(intr, {x, y}); jl_value_t *params[2]; params[0] = xtyp; @@ -1458,10 +1505,6 @@ static Value *emit_untyped_intrinsic(jl_codectx_t &ctx, intrinsic f, Value **arg jl_datatype_t *tuptyp = (jl_datatype_t*)jl_apply_tuple_type_v(params, 2); *newtyp = tuptyp; - Value *tupval; - tupval = UndefValue::get(julia_type_to_llvm(ctx, (jl_value_t*)tuptyp)); - tupval = ctx.builder.CreateInsertValue(tupval, val, ArrayRef(0)); - tupval = ctx.builder.CreateInsertValue(tupval, obyte, ArrayRef(1)); return tupval; } diff --git a/test/llvmcall2.jl b/test/llvmcall2.jl index 07b27fc4074331..e3e89bb916f2d7 100644 --- a/test/llvmcall2.jl +++ b/test/llvmcall2.jl @@ -73,3 +73,12 @@ end jl_str = unsafe_string(str) @test length(jl_str) > 4 end + + +# boolean structs +const NT4I = NTuple{4, VecElement{Int}} +const NT4B = NTuple{4, VecElement{Bool}} +f_nt4b(x, y) = ccall("llvm.sadd.with.overflow", llvmcall, Pair{NT4B, NT4B}, (NT4B, NT4B), x, y) +f_nt4i(x, y) = ccall("llvm.sadd.with.overflow", llvmcall, Pair{NT4I, NT4B}, (NT4I, NT4I), x, y) +@test f_nt4b((false, true, false, true), (false, false, true, true)) === (NT4B((false, true, true, false)) => NT4B((false, false, false, true))) +@test f_nt4i((typemin(Int), 0, typemax(Int), typemax(Int)), (-1, typemax(Int),-1, 1)) === (NT4I((typemax(Int), typemax(Int), typemax(Int)-1, typemin(Int))) => NT4B((true, false, false, true))) From 20440fd9a379f450c3c4317cb320669c9dc6d631 Mon Sep 17 00:00:00 2001 From: Lilith Orion Hafner Date: Fri, 17 Nov 2023 13:46:52 -0600 Subject: [PATCH 15/38] Move "Creating Packages" from Pkg.jl docs (#52102) --- doc/make.jl | 1 + doc/src/manual/style-guide.md | 2 +- doc/src/tutorials/creating-packages.md | 634 +++++++++++++++++++++++++ 3 files changed, 636 insertions(+), 1 deletion(-) create mode 100644 doc/src/tutorials/creating-packages.md diff --git a/doc/make.jl b/doc/make.jl index d0bd52247aa816..da0fb8242913bf 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -128,6 +128,7 @@ BaseDocs = [ StdlibDocs = [stdlib.targetfile for stdlib in STDLIB_DOCS] Tutorials = [ + "tutorials/creating-packages.md", "tutorials/profile.md", "tutorials/external.md", ] diff --git a/doc/src/manual/style-guide.md b/doc/src/manual/style-guide.md index f892d6284fb108..19b4908927187c 100644 --- a/doc/src/manual/style-guide.md +++ b/doc/src/manual/style-guide.md @@ -346,7 +346,7 @@ This would provide custom showing of vectors with a specific new element type. W this should be avoided. The trouble is that users will expect a well-known type like `Vector()` to behave in a certain way, and overly customizing its behavior can make it harder to work with. -## Avoid type piracy +## [Avoid type piracy](@id avoid-type-piracy) "Type piracy" refers to the practice of extending or redefining methods in Base or other packages on types that you have not defined. In extreme cases, you can crash Julia diff --git a/doc/src/tutorials/creating-packages.md b/doc/src/tutorials/creating-packages.md new file mode 100644 index 00000000000000..a01141a2f6e487 --- /dev/null +++ b/doc/src/tutorials/creating-packages.md @@ -0,0 +1,634 @@ +# [Creating Packages](@id creating-packages-tutorial) + +## Generating files for a package + +!!! note + The [PkgTemplates](https://github.com/invenia/PkgTemplates.jl) package offers an easy, repeatable, and + customizable way to generate the files for a new package. It can also generate files needed for Documentation, CI, etc. + We recommend that you use PkgTemplates for creating + new packages instead of using the minimal `pkg> generate` functionality described below. + +To generate the bare minimum files for a new package, use `pkg> generate`. + +```julia-repl +(@v1.8) pkg> generate HelloWorld +``` + +This creates a new project `HelloWorld` in a subdirectory by the same name, with the following files (visualized with the external [`tree` command](https://linux.die.net/man/1/tree)): + +```julia-repl +shell> tree HelloWorld/ +HelloWorld/ +โ”œโ”€โ”€ Project.toml +โ””โ”€โ”€ src + โ””โ”€โ”€ HelloWorld.jl + +2 directories, 2 files +``` + +The `Project.toml` file contains the name of the package, its unique UUID, its version, the authors and potential dependencies: + +```toml +name = "HelloWorld" +uuid = "b4cd1eb8-1e24-11e8-3319-93036a3eb9f3" +version = "0.1.0" +authors = ["Some One "] + +[deps] +``` + +The content of `src/HelloWorld.jl` is: + +```julia +module HelloWorld + +greet() = print("Hello World!") + +end # module +``` + +We can now activate the project by using the path to the directory where it is installed, and load the package: + +```julia-repl +pkg> activate ./HelloWorld + +julia> import HelloWorld + +julia> HelloWorld.greet() +Hello World! +``` + +For the rest of the tutorial we enter inside the directory of the project, for convenience: + +```julia-repl +julia> cd("HelloWorld") +``` + +## Adding dependencies to the project + +Letโ€™s say we want to use the standard library package `Random` and the registered package `JSON` in our project. +We simply `add` these packages (note how the prompt now shows the name of the newly generated project, +since we `activate`d it): + +```julia-repl +(HelloWorld) pkg> add Random JSON + Resolving package versions... + Updating `~/HelloWorld/Project.toml` + [682c06a0] + JSON v0.21.3 + [9a3f8284] + Random + Updating `~/HelloWorld/Manifest.toml` + [682c06a0] + JSON v0.21.3 + [69de0a69] + Parsers v2.4.0 + [ade2ca70] + Dates + ... +``` + +Both `Random` and `JSON` got added to the projectโ€™s `Project.toml` file, and the resulting dependencies got added to the `Manifest.toml` file. +The resolver has installed each package with the highest possible version, while still respecting the compatibility that each package enforces on its dependencies. + +We can now use both `Random` and `JSON` in our project. Changing `src/HelloWorld.jl` to + +```julia +module HelloWorld + +import Random +import JSON + +greet() = print("Hello World!") +greet_alien() = print("Hello ", Random.randstring(8)) + +end # module +``` + +and reloading the package, the new `greet_alien` function that uses `Random` can be called: + +```julia-repl +julia> HelloWorld.greet_alien() +Hello aT157rHV +``` + +## Defining a public API + +If you want your package to be useful to other packages and you want folks to be able to +easily update to newer version of your package when they come out, it is important to +document what behavior will stay consistent across updates. + +Unless you note otherwise, the public API of your package is defined as all the behavior you +describe about public symbols. A public symbol is a symbol that is exported from your +package with the `export` keyword or marked as public with the `public` keyword. When you +change the behavior of something that was previously public so that the new +version no longer conforms to the specifications provided in the old version, you should +adjust your package version number according to [Julia's variant on SemVer](#Version-specifier-format). +If you would like to include a symbol in your public API without exporting it into the +global namespace of folks who call `using YourPackage`, you should mark that symbol as +public with `public that_symbol`. Symbols marked as public with the `public` keyword are +just as public as those marked as public with the `export` keyword, but when folks call +`using YourPackage`, they will still have to qualify access to those symbols with +`YourPackage.that_symbol`. + +Let's say we would like our `greet` function to be part of the public API, but not the +`greet_alien` function. We could the write the following and release it as version `1.0.0`. + +```julia +module HelloWorld + +export greet + +import Random +import JSON + +"Writes a friendly message." +greet() = print("Hello World!") + +"Greet an alien by a randomly generated name." +greet_alien() = print("Hello ", Random.randstring(8)) + +end # module +``` + +Then, if we change `greet` to + +```julia +"Writes a friendly message that is exactly three words long." +greet() = print("Hello Lovely World!") +``` + +We would release the new version as `1.1.0`. This is not breaking +because the new implementation conforms to the old documentation, but +it does add a new feature, that the message must be three words long. + +Later, we may wish to change `greet_alien` to + +```julia +"Greet an alien by a the name of \"Zork\"." +greet_alien() = print("Hello Zork") +``` + +And also export it by changing + +```julia +export greet +``` + +to + +```julia +export greet, greet_alien +``` + +We should release this new version as `1.2.0` because it adds a new feature +`greet_alien` to the public API. Even though `greet_alien` was documented before +and the new version does not conform to the old documentation, this is not breaking +because the old documentation was not attached to a symbol that was exported +at the time so that documentation does not apply across released versions. + +However, if we now wish to change `greet` to + +```julia +"Writes a friendly message that is exactly four words long." +greet() = print("Hello very lovely world") +``` + +we would need to release the new version as `2.0.0`. In version `1.1.0`, we specified that +the greeting would be three words long, and because `greet` was exported, that description +also applies to all future versions until the next breaking release. Because this new +version does not conform to the old specification, it must be tagged as a breaking change. + +Please note that version numbers are free and unlimited. It is okay to use lots of them +(e.g. version `6.62.8`). + +## Adding a build step to the package + +The build step is executed the first time a package is installed or when explicitly invoked with `build`. +A package is built by executing the file `deps/build.jl`. + +```julia-repl +julia> mkpath("deps"); + +julia> write("deps/build.jl", + """ + println("I am being built...") + """); + +(HelloWorld) pkg> build + Building HelloWorld โ†’ `deps/build.log` + Resolving package versions... + +julia> print(readchomp("deps/build.log")) +I am being built... +``` + +If the build step fails, the output of the build step is printed to the console + +```julia-repl +julia> write("deps/build.jl", + """ + error("Ooops") + """); + +(HelloWorld) pkg> build + Building HelloWorld โ†’ `~/HelloWorld/deps/build.log` +ERROR: Error building `HelloWorld`: +ERROR: LoadError: Ooops +Stacktrace: + [1] error(s::String) + @ Base ./error.jl:35 + [2] top-level scope + @ ~/HelloWorld/deps/build.jl:1 + [3] include(fname::String) + @ Base.MainInclude ./client.jl:476 + [4] top-level scope + @ none:5 +in expression starting at /home/kc/HelloWorld/deps/build.jl:1 +``` + +!!! warning + A build step should generally not create or modify any files in the package directory. If you need to store some files + from the build step, use the [Scratch.jl](https://github.com/JuliaPackaging/Scratch.jl) package. + +## Adding tests to the package + +When a package is tested the file `test/runtests.jl` is executed: + +```julia-repl +julia> mkpath("test"); + +julia> write("test/runtests.jl", + """ + println("Testing...") + """); + +(HelloWorld) pkg> test + Testing HelloWorld + Resolving package versions... +Testing... + Testing HelloWorld tests passed +``` + +Tests are run in a new Julia process, where the package itself, and any +test-specific dependencies, are available, see below. + + +!!! warning + Tests should generally not create or modify any files in the package directory. If you need to store some files + from the build step, use the [Scratch.jl](https://github.com/JuliaPackaging/Scratch.jl) package. + +### Test-specific dependencies + +There are two ways of adding test-specific dependencies (dependencies that are not dependencies of the package but will still be available to +load when the package is tested). + +#### `target` based test specific dependencies + +Using this method of adding test-specific dependencies, the packages are added under an `[extras]` section and to a test target, +e.g. to add `Markdown` and `Test` as test dependencies, add the following to the `Project.toml` file: + +```toml +[extras] +Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Markdown", "Test"] +``` + +Note that the only supported targets are `test` and `build`, the latter of which (not recommended) can be used +for any `deps/build.jl` scripts. + +#### Alternative approach: `test/Project.toml` file test specific dependencies + +!!! note + The exact interaction between `Project.toml`, `test/Project.toml` and their corresponding + `Manifest.toml`s are not fully worked out and may be subject to change in future versions. + The older method of adding test-specific dependencies, described in the previous section, + will therefore be supported throughout all Julia 1.X releases. + +In Julia 1.2 and later test dependencies can be declared in `test/Project.toml`. When running +tests, Pkg will automatically merge this and the package Projects to create the test environment. + +!!! note + If no `test/Project.toml` exists Pkg will use the `target` based test specific dependencies. + +To add a test-specific dependency, i.e. a dependency that is available only when testing, +it is thus enough to add this dependency to the `test/Project.toml` project. This can be +done from the Pkg REPL by activating this environment, and then use `add` as one normally +does. Let's add the `Test` standard library as a test dependency: + +```julia-repl +(HelloWorld) pkg> activate ./test +[ Info: activating environment at `~/HelloWorld/test/Project.toml`. + +(test) pkg> add Test + Resolving package versions... + Updating `~/HelloWorld/test/Project.toml` + [8dfed614] + Test + Updating `~/HelloWorld/test/Manifest.toml` + [...] +``` + +We can now use `Test` in the test script and we can see that it gets installed when testing: + +```julia-repl +julia> write("test/runtests.jl", + """ + using Test + @test 1 == 1 + """); + +(test) pkg> activate . + +(HelloWorld) pkg> test + Testing HelloWorld + Resolving package versions... + Updating `/var/folders/64/76tk_g152sg6c6t0b4nkn1vw0000gn/T/tmpPzUPPw/Project.toml` + [d8327f2a] + HelloWorld v0.1.0 [`~/.julia/dev/Pkg/HelloWorld`] + [8dfed614] + Test + Updating `/var/folders/64/76tk_g152sg6c6t0b4nkn1vw0000gn/T/tmpPzUPPw/Manifest.toml` + [d8327f2a] + HelloWorld v0.1.0 [`~/.julia/dev/Pkg/HelloWorld`] + Testing HelloWorld tests passed``` +``` + +## Compatibility on dependencies + +Every dependency should in general have a compatibility constraint on it. +This is an important topic so there is a chapter in the package docs about it: +[Compatibility](https://pkgdocs.julialang.org/v1/compatibility). + +## Weak dependencies + +!!! note + This is a somewhat advanced usage of Pkg which can be skipped for people new to Julia and Julia packages. + +!!! compat + The described feature requires Julia 1.9+. + +A weak dependency is a dependency that will not automatically install when the package is installed but +you can still control what versions of that package are allowed to be installed by setting compatibility on it. +These are listed in the project file under the `[weakdeps]` section: + +```toml +[weakdeps] +SomePackage = "b3785f31-9d33-4cdf-bc73-f646780f1739" + +[compat] +SomePackage = "1.2" +``` + +The current usage of this is almost solely limited to "extensions" which is described in the next section. + +## Conditional loading of code in packages (Extensions) + +!!! note + This is a somewhat advanced usage of Pkg which can be skipped for people new to Julia and Julia packages. + +!!! compat + The described feature requires Julia 1.9+. + +Sometimes one wants to make two or more packages work well together, but may be reluctant (perhaps due to increased load times) to make one an unconditional dependency of the other. +A package *extension* is a module in a file (similar to a package) that is automatically loaded when *some other set of packages* are +loaded into the Julia session. This is very similar to functionality that the external package +[Requires.jl](https://github.com/JuliaPackaging/Requires.jl) provides, but which is now available directly through Julia, +and provides added benefits such as being able to precompile the extension. + +### Code structure + +A useful application of extensions could be for a plotting package that should be able to plot +objects from a wide variety of different Julia packages. +Adding all those different Julia packages as dependencies of the plotting package +could be expensive since they would end up getting loaded even if they were never used. +Instead, the code required to plot objects for specific packages can be put into separate files +(extensions) and these are loaded only when the packages that define the type(s) we want to plot +are loaded. + +Below is an example of how the code can be structured for a use case in which a +`Plotting` package wants to be able to display objects defined in the external package `Contour`. +The file and folder structure shown below is found in the `Plotting` package. + + `Project.toml`: + ```toml +name = "Plotting" +version = "0.1.0" +uuid = "..." + +[weakdeps] +Contour = "d38c429a-6771-53c6-b99e-75d170b6e991" + +[extensions] +# name of extension to the left +# extension dependencies required to load the extension to the right +# use a list for multiple extension dependencies +PlottingContourExt = "Contour" + +[compat] +Contour = "0.6.2" +``` + +`src/Plotting.jl`: +```julia +module Plotting + +function plot(x::Vector) + # Some functionality for plotting a vector here +end + +end # module +``` + +`ext/PlottingContourExt.jl` (can also be in `ext/PlottingContourExt/PlottingContourExt.jl`): +```julia +module PlottingContourExt # Should be same name as the file (just like a normal package) + +using Plotting, Contour + +function Plotting.plot(c::Contour.ContourCollection) + # Some functionality for plotting a contour here +end + +end # module +``` + +Extensions can have any arbitrary name (here `PlottingContourExt`), but using something similar to the format of +this example that makes the extended functionality and dependency of the extension clear is likely a good idea. + +!!! compat + Often you will put the extension dependencies into the `test` target so they are loaded when running e.g. `Pkg.test()`. On earlier Julia versions + this requires you to also put the package in the `[extras]` section. This is unfortunate but the project verifier on older Julia versions will + complain if this is not done. + +!!! note + If you use a manifest generated by a Julia version that does not know about extensions with a Julia version that does + know about them, the extensions will not load. This is because the manifest lacks some information that tells Julia + when it should load these packages. So make sure you use a manifest generated at least the Julia version you are using. + +### Behavior of extensions + +A user that depends only on `Plotting` will not pay the cost of the "extension" inside the `PlottingContourExt` module. +It is only when the `Contour` package actually gets loaded that the `PlottingContourExt` extension is loaded too +and provides the new functionality. + +In our example, the new functionality is an additional _method_, which we add to an existing _function_ from the parent package `Plotting`. +Implementing such methods is among the most standard use cases of package extensions. +Within the parent package, the function to extend can even be defined with zero methods, as follows: + +```julia +function plot end +``` + +!!! note + If one considers `PlottingContourExt` as a completely separate package, it could be argued that defining `Plotting.plot(c::Contour.ContourCollection)` is + [type piracy](@ref avoid-type-piracy) since `PlottingContourExt` _owns_ neither the function `Plotting.plot` nor the type `Contour.ContourCollection`. + However, for extensions, it is ok to assume that the extension owns the functions in its parent package. + +In other situations, one may need to define new symbols in the extension (types, structs, functions, etc.) instead of reusing those from the parent package. +Such symbols are created in a separate module corresponding to the extension, namely `PlottingContourExt`, and thus not in `Plotting` itself. +If extension symbols are needed in the parent package, one must call `Base.get_extension` to retrieve them. +Here is an example showing how a custom type defined in `PlottingContourExt` can be accessed in `Plotting`: + +```julia +ext = Base.get_extension(@__MODULE__, :PlottingContourExt) +if !isnothing(ext) + ContourPlotType = ext.ContourPlotType +end +``` + +On the other hand, accessing extension symbols from a third-party package (i.e. not the parent) is not a recommended practice at the moment. + +### Backwards compatibility + +This section discusses various methods for using extensions on Julia versions that support them, +while simultaneously providing similar functionality on older Julia versions. + +#### Requires.jl + +This section is relevant if you are currently using Requires.jl but want to transition to using extensions (while still having Requires be used on Julia versions that do not support extensions). +This is done by making the following changes (using the example above): + +- Add the following to the package file. This makes it so that Requires.jl loads and inserts the + callback only when extensions are not supported + ```julia + # This symbol is only defined on Julia versions that support extensions + if !isdefined(Base, :get_extension) + using Requires + end + + @static if !isdefined(Base, :get_extension) + function __init__() + @require Contour = "d38c429a-6771-53c6-b99e-75d170b6e991" include("../ext/PlottingContourExt.jl") + end + end + ``` + or if you have other things in your `__init__()` function: + ```julia + if !isdefined(Base, :get_extension) + using Requires + end + + function __init__() + # Other init functionality here + + @static if !isdefined(Base, :get_extension) + @require Contour = "d38c429a-6771-53c6-b99e-75d170b6e991" include("../ext/PlottingContourExt.jl") + end + end + ``` +- Make the following change in the conditionally-loaded code: + ```julia + isdefined(Base, :get_extension) ? (using Contour) : (using ..Contour) + ``` +- Add `Requires` to `[weakdeps]` in your `Project.toml` file, so that it is listed in both `[deps]` and `[weakdeps]`. + Julia 1.9+ knows to not install it as a regular dependency, whereas earlier versions will consider it a dependency. + +The package should now work with Requires.jl on Julia versions before extensions were introduced +and with extensions on more recent Julia versions. + +#### Transition from normal dependency to extension + +This section is relevant if you have a normal dependency that you want to transition be an extension (while still having the dependency be a normal dependency on Julia versions that do not support extensions). +This is done by making the following changes (using the example above): + +- Make sure that the package is **both** in the `[deps]` and `[weakdeps]` section. Newer Julia versions will ignore dependencies in `[deps]` that are also in `[weakdeps]`. +- Add the following to your main package file (typically at the bottom): + ```julia + if !isdefined(Base, :get_extension) + include("../ext/PlottingContourExt.jl") + end + ``` + +#### Using an extension while supporting older Julia versions + +In the case where one wants to use an extension (without worrying about the +feature of the extension begin available on older Julia versions) while still +supporting older Julia versions the packages under `[weakdeps]` should be +duplicated into `[extras]`. This is an unfortunate duplication, but without +doing this the project verifier under older Julia versions will throw an error +if it finds packages under `[compat]` that is not listed in `[extras]`. + +## Package naming guidelines + +Package names should be sensible to most Julia users, *even to those who are not domain experts*. +The following guidelines apply to the `General` registry but may be useful for other package +registries as well. + +Since the `General` registry belongs to the entire community, people may have opinions about +your package name when you publish it, especially if it's ambiguous or can be confused with +something other than what it is. Usually, you will then get suggestions for a new name that +may fit your package better. + +1. Avoid jargon. In particular, avoid acronyms unless there is minimal possibility of confusion. + + * It's ok to say `USA` if you're talking about the USA. + * It's not ok to say `PMA`, even if you're talking about positive mental attitude. +2. Avoid using `Julia` in your package name or prefixing it with `Ju`. + + * It is usually clear from context and to your users that the package is a Julia package. + * Package names already have a `.jl` extension, which communicates to users that `Package.jl` is a Julia package. + * Having Julia in the name can imply that the package is connected to, or endorsed by, contributors + to the Julia language itself. +3. Packages that provide most of their functionality in association with a new type should have pluralized + names. + + * `DataFrames` provides the `DataFrame` type. + * `BloomFilters` provides the `BloomFilter` type. + * In contrast, `JuliaParser` provides no new type, but instead new functionality in the `JuliaParser.parse()` + function. +4. Err on the side of clarity, even if clarity seems long-winded to you. + + * `RandomMatrices` is a less ambiguous name than `RndMat` or `RMT`, even though the latter are shorter. +5. A less systematic name may suit a package that implements one of several possible approaches to + its domain. + + * Julia does not have a single comprehensive plotting package. Instead, `Gadfly`, `PyPlot`, `Winston` + and other packages each implement a unique approach based on a particular design philosophy. + * In contrast, `SortingAlgorithms` provides a consistent interface to use many well-established + sorting algorithms. +6. Packages that wrap external libraries or programs should be named after those libraries or programs. + + * `CPLEX.jl` wraps the `CPLEX` library, which can be identified easily in a web search. + * `MATLAB.jl` provides an interface to call the MATLAB engine from within Julia. +7. Avoid naming a package closely to an existing package + * `Websocket` is too close to `WebSockets` and can be confusing to users. Rather use a new name such as `SimpleWebsockets`. + +## Registering packages + +Once a package is ready it can be registered with the [General Registry](https://github.com/JuliaRegistries/General#registering-a-package-in-general) (see also the [FAQ](https://github.com/JuliaRegistries/General#faq)). +Currently, packages are submitted via [`Registrator`](https://juliaregistrator.github.io/). +In addition to `Registrator`, [`TagBot`](https://github.com/marketplace/actions/julia-tagbot) helps manage the process of tagging releases. + +## Best Practices + +Packages should avoid mutating their own state (writing to files within their package directory). +Packages should, in general, not assume that they are located in a writable location (e.g. if installed as part of a system-wide depot) or even a stable one (e.g. if they are bundled into a system image by [PackageCompiler.jl](https://github.com/JuliaLang/PackageCompiler.jl)). +To support the various use cases in the Julia package ecosystem, the Pkg developers have created a number of auxiliary packages and techniques to help package authors create self-contained, immutable, and relocatable packages: + +* [`Artifacts`](https://pkgdocs.julialang.org/v1/artifacts/) can be used to bundle chunks of data alongside your package, or even allow them to be downloaded on-demand. + Prefer artifacts over attempting to open a file via a path such as `joinpath(@__DIR__, "data", "my_dataset.csv")` as this is non-relocatable. + Once your package has been precompiled, the result of `@__DIR__` will have been baked into your precompiled package data, and if you attempt to distribute this package, it will attempt to load files at the wrong location. + Artifacts can be bundled and accessed easily using the `artifact"name"` string macro. + +* [`Scratch.jl`](https://github.com/JuliaPackaging/Scratch.jl) provides the notion of "scratch spaces", mutable containers of data for packages. + Scratch spaces are designed for data caches that are completely managed by a package and should be removed when the package itself is uninstalled. + For important user-generated data, packages should continue to write out to a user-specified path that is not managed by Julia or Pkg. + +* [`Preferences.jl`](https://github.com/JuliaPackaging/Preferences.jl) allows packages to read and write preferences to the top-level `Project.toml`. + These preferences can be read at runtime or compile-time, to enable or disable different aspects of package behavior. + Packages previously would write out files to their own package directories to record options set by the user or environment, but this is highly discouraged now that `Preferences` is available. From 625bbdef20f59cd546ed841f9c53f00beaba4d8b Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 17 Nov 2023 18:43:52 -0500 Subject: [PATCH 16/38] build: disable DT_TEXTREL warning for i686 (#52211) This warning was distracting and annoying me, so I looked into whether it was a bug. Turns out the warning is harmless (loading the object just will make a copy of the ~2kb text field into memory). So add the code to disable it, either with different assembly or different linker flags. Default to continuing to use the same assembly. --- cli/Makefile | 3 ++- cli/trampolines/trampolines_i686.S | 30 +++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/cli/Makefile b/cli/Makefile index b6a2b48ebf0441..c72ebff2b9bfd0 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -20,7 +20,8 @@ ifeq ($(OS),WINNT) LOADER_LDFLAGS += -municode -mconsole -nostdlib --disable-auto-import \ --disable-runtime-pseudo-reloc -lntdll -lkernel32 -lpsapi else ifeq ($(OS),Linux) -LOADER_LDFLAGS += -Wl,--no-as-needed -ldl -lpthread -rdynamic -lc -Wl,--as-needed +# textoff and notext are aliases to the same option which suppress the TEXTREL warning for i686 +LOADER_LDFLAGS += -Wl,--no-as-needed -ldl -lpthread -rdynamic -lc -Wl,--as-needed -Wl,-z,notext else ifeq ($(OS),FreeBSD) LOADER_LDFLAGS += -Wl,--no-as-needed -ldl -lpthread -rdynamic -lc -Wl,--as-needed else ifeq ($(OS),Darwin) diff --git a/cli/trampolines/trampolines_i686.S b/cli/trampolines/trampolines_i686.S index 3d9cacf0ce652c..f6c46fd6ee49b3 100644 --- a/cli/trampolines/trampolines_i686.S +++ b/cli/trampolines/trampolines_i686.S @@ -3,13 +3,41 @@ #include "common.h" #include "../../src/jl_exported_funcs.inc" +// set this option to 1 to get very slightly slower trampolines which however do not trigger +// this linker warning: +// ld: ./loader_trampolines.o: warning: relocation against `jl_***_addr' in read-only section `.text' +// ld: warning: creating DT_TEXTREL in a shared object +// If you have a large libjulia.so file or other restrictions on using TEXTREL for some +// reason, this may be worthwhile. +// This is not relevant on Windows (though it is valid there), since it always uses +// DT_TEXTREL anyways, and does not support this notion of PIC. +#define USE_PC32 0 + +#if USE_PC32 +.cfi_startproc +julia__x86.get_pc_thunk.ax: + mov (%esp),%eax + ret +.cfi_endproc + +#define CALL(name) \ + call julia__x86.get_pc_thunk.ax; \ + jmpl *(CNAMEADDR(name) - .)(%eax); \ + +#else + +#define CALL(name) \ + jmpl *(CNAMEADDR(name)); \ + +#endif + #define XX(name) \ DEBUGINFO(CNAME(name)); \ .global CNAME(name); \ .cfi_startproc; \ CNAME(name)##:; \ CET_START(); \ - jmpl *(CNAMEADDR(name)); \ + CALL(name); \ ud2; \ .cfi_endproc; \ EXPORT(name); \ From 81afdbc36b365fcbf3ae25b7451c6cb5798c0c3d Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 17 Nov 2023 18:44:45 -0500 Subject: [PATCH 17/38] codegen: remove UB from uninitialized bitstypes in new (#52169) In the time since the creation of issue #26764, there _is_ now 'a way to say to llvm "I don't care what this value is, but it always has to be the same"' using the `freeze` instruction, so we can use that to instruct LLVM to not give us undefined behavior when users are using uninitialized memory. There should not be an impact if users were already avoiding this paradigm and are fully initializing their structs. Fixes #26764 --- src/ccall.cpp | 11 ++++++--- src/cgutils.cpp | 39 ++++++++++++++++++++----------- src/codegen.cpp | 9 +++---- src/intrinsics.cpp | 10 ++++---- src/llvm-alloc-helpers.cpp | 11 ++++++++- src/llvm-alloc-helpers.h | 5 +++- src/llvm-alloc-opt.cpp | 33 ++++++++++++++++++++++---- src/llvm-pass-helpers.cpp | 2 +- test/llvmpasses/alloc-opt-pass.ll | 38 ++++++++++++++++++++++++++++++ 9 files changed, 125 insertions(+), 33 deletions(-) diff --git a/src/ccall.cpp b/src/ccall.cpp index 7755b14bbaf956..ece0ee24908e85 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -1003,8 +1003,9 @@ static Value *box_ccall_result(jl_codectx_t &ctx, Value *result, Value *runtime_ // XXX: need to handle parameterized zero-byte types (singleton) const DataLayout &DL = ctx.builder.GetInsertBlock()->getModule()->getDataLayout(); unsigned nb = DL.getTypeStoreSize(result->getType()); + unsigned align = sizeof(void*); // Allocations are at least pointer aligned MDNode *tbaa = jl_is_mutable(rt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut; - Value *strct = emit_allocobj(ctx, nb, runtime_dt); + Value *strct = emit_allocobj(ctx, nb, runtime_dt, true, align); setName(ctx.emission_context, strct, "ccall_result_box"); init_bits_value(ctx, strct, result, tbaa); return strct; @@ -1383,6 +1384,10 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs) assert(i < nccallargs && i + fc_args_start <= nargs); jl_value_t *argi = args[fc_args_start + i]; argv[i] = emit_expr(ctx, argi); + if (argv[i].typ == jl_bottom_type) { + JL_GC_POP(); + return jl_cgval_t(); + } } // emit roots @@ -1984,7 +1989,7 @@ jl_cgval_t function_sig_t::emit_a_ccall( // XXX: result needs to be zero'd and given a GC root here // and has incorrect write barriers. // instead this code path should behave like `unsafe_load` - result = emit_allocobj(ctx, (jl_datatype_t*)rt); + result = emit_allocobj(ctx, (jl_datatype_t*)rt, true); setName(ctx.emission_context, result, "ccall_sret_box"); sretty = ctx.types().T_jlvalue; sretboxed = true; @@ -2141,7 +2146,7 @@ jl_cgval_t function_sig_t::emit_a_ccall( else if (jlretboxed && !retboxed) { assert(jl_is_datatype(rt)); if (static_rt) { - Value *strct = emit_allocobj(ctx, (jl_datatype_t*)rt); + Value *strct = emit_allocobj(ctx, (jl_datatype_t*)rt, true); setName(ctx.emission_context, strct, "ccall_ret_box"); MDNode *tbaa = jl_is_mutable(rt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut; int boxalign = julia_alignment(rt); diff --git a/src/cgutils.cpp b/src/cgutils.cpp index b560f84a4db8c7..15a1522ee0d571 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -2896,7 +2896,7 @@ static Value *emit_genericmemoryowner(jl_codectx_t &ctx, Value *t) // --- boxing --- -static Value *emit_allocobj(jl_codectx_t &ctx, jl_datatype_t *jt); +static Value *emit_allocobj(jl_codectx_t &ctx, jl_datatype_t *jt, bool fully_initialized); static void init_bits_value(jl_codectx_t &ctx, Value *newv, Value *v, MDNode *tbaa, unsigned alignment = sizeof(void*)) // min alignment in julia's gc is pointer-aligned @@ -3206,7 +3206,7 @@ static Value *box_union(jl_codectx_t &ctx, const jl_cgval_t &vinfo, const SmallB jl_cgval_t vinfo_r = jl_cgval_t(vinfo, (jl_value_t*)jt, NULL); box = _boxed_special(ctx, vinfo_r, t); if (!box) { - box = emit_allocobj(ctx, jt); + box = emit_allocobj(ctx, jt, true); setName(ctx.emission_context, box, "unionbox"); init_bits_cgval(ctx, box, vinfo_r, jl_is_mutable(jt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut); } @@ -3333,7 +3333,7 @@ static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &vinfo, bool is_promotab if (do_promote && is_promotable) { auto IP = ctx.builder.saveIP(); ctx.builder.SetInsertPoint(vinfo.promotion_point); - box = emit_allocobj(ctx, (jl_datatype_t*)jt); + box = emit_allocobj(ctx, (jl_datatype_t*)jt, true); Value *decayed = decay_derived(ctx, box); AllocaInst *originalAlloca = cast(vinfo.V); box->takeName(originalAlloca); @@ -3349,7 +3349,7 @@ static Value *boxed(jl_codectx_t &ctx, const jl_cgval_t &vinfo, bool is_promotab auto arg_typename = [&] JL_NOTSAFEPOINT { return "box::" + std::string(jl_symbol_name(((jl_datatype_t*)(jt))->name->name)); }; - box = emit_allocobj(ctx, (jl_datatype_t*)jt); + box = emit_allocobj(ctx, (jl_datatype_t*)jt, true); setName(ctx.emission_context, box, arg_typename); init_bits_cgval(ctx, box, vinfo, jl_is_mutable(jt) ? ctx.tbaa().tbaa_mutab : ctx.tbaa().tbaa_immut); } @@ -3478,7 +3478,7 @@ static void emit_cpointercheck(jl_codectx_t &ctx, const jl_cgval_t &x, const Twi // allocation for known size object // returns a prjlvalue static Value *emit_allocobj(jl_codectx_t &ctx, size_t static_size, Value *jt, - unsigned align=sizeof(void*)) // Allocations are at least pointer alingned + bool fully_initialized, unsigned align) { ++EmittedAllocObjs; Value *current_task = get_current_task(ctx); @@ -3486,15 +3486,19 @@ static Value *emit_allocobj(jl_codectx_t &ctx, size_t static_size, Value *jt, auto call = ctx.builder.CreateCall(F, {current_task, ConstantInt::get(ctx.types().T_size, static_size), maybe_decay_untracked(ctx, jt)}); call->setAttributes(F->getAttributes()); if (static_size > 0) - call->addRetAttr(Attribute::getWithDereferenceableBytes(ctx.builder.getContext(), static_size)); - call->addRetAttr(Attribute::getWithAlignment(ctx.builder.getContext(), Align(align))); + call->addRetAttr(Attribute::getWithDereferenceableBytes(call->getContext(), static_size)); + call->addRetAttr(Attribute::getWithAlignment(call->getContext(), Align(align))); +#if JL_LLVM_VERSION >= 150000 + if (fully_initialized) + call->addFnAttr(Attribute::get(call->getContext(), Attribute::AllocKind, uint64_t(AllocFnKind::Alloc | AllocFnKind::Uninitialized))); +#endif return call; } -static Value *emit_allocobj(jl_codectx_t &ctx, jl_datatype_t *jt) +static Value *emit_allocobj(jl_codectx_t &ctx, jl_datatype_t *jt, bool fully_initialized) { return emit_allocobj(ctx, jl_datatype_size(jt), ctx.builder.CreateIntToPtr(emit_tagfrom(ctx, jt), ctx.types().T_pjlvalue), - julia_alignment((jl_value_t*)jt)); + fully_initialized, julia_alignment((jl_value_t*)jt)); } // allocation for unknown object from an untracked pointer @@ -3716,14 +3720,20 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg strct = NULL; } else if (init_as_value) { - if (tracked.count) + if (tracked.count) { strct = Constant::getNullValue(lt); - else + } + else { strct = UndefValue::get(lt); + if (nargs < nf) + strct = ctx.builder.CreateFreeze(strct); + } } else { strct = emit_static_alloca(ctx, lt); setName(ctx.emission_context, strct, arg_typename); + if (nargs < nf) + ctx.builder.CreateStore(ctx.builder.CreateFreeze(UndefValue::get(lt)), strct); if (tracked.count) undef_derived_strct(ctx, strct, sty, ctx.tbaa().tbaa_stack); } @@ -3893,7 +3903,7 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg return ret; } } - Value *strct = emit_allocobj(ctx, sty); + Value *strct = emit_allocobj(ctx, sty, nargs >= nf); setName(ctx.emission_context, strct, arg_typename); jl_cgval_t strctinfo = mark_julia_type(ctx, strct, true, ty); strct = decay_derived(ctx, strct); @@ -3926,13 +3936,14 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg return strctinfo; } else { - // 0 fields, ghost or bitstype + // 0 fields, ghost or primitive type if (jl_datatype_nbits(sty) == 0) return ghostValue(ctx, sty); + // n.b. this is not valid IR form to construct a primitive type (use bitcast for example) bool isboxed; Type *lt = julia_type_to_llvm(ctx, ty, &isboxed); assert(!isboxed); - return mark_julia_type(ctx, UndefValue::get(lt), false, ty); + return mark_julia_type(ctx, ctx.builder.CreateFreeze(UndefValue::get(lt)), false, ty); } } diff --git a/src/codegen.cpp b/src/codegen.cpp index e09cec49e6c0ac..632c76caa1c08c 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1052,7 +1052,7 @@ static const auto jl_alloc_obj_func = new JuliaFunction{ auto FnAttrs = AttrBuilder(C); FnAttrs.addAllocSizeAttr(1, None); // returns %1 bytes #if JL_LLVM_VERSION >= 150000 - FnAttrs.addAllocKindAttr(AllocFnKind::Alloc | AllocFnKind::Uninitialized); + FnAttrs.addAllocKindAttr(AllocFnKind::Alloc); #endif #if JL_LLVM_VERSION >= 160000 FnAttrs.addMemoryAttr(MemoryEffects::argMemOnly(ModRefInfo::Ref) | inaccessibleMemOnly(ModRefInfo::ModRef)); @@ -5217,7 +5217,7 @@ static void emit_vi_assignment_unboxed(jl_codectx_t &ctx, jl_varinfo_t &vi, Valu } else { Value *dest = vi.value.V; - if (vi.pTIndex) + if (vi.pTIndex) // TODO: use lifetime-end here instead ctx.builder.CreateStore(UndefValue::get(cast(vi.value.V)->getAllocatedType()), vi.value.V); Type *store_ty = julia_type_to_llvm(ctx, rval_info.constant ? jl_typeof(rval_info.constant) : rval_info.typ); Type *dest_ty = store_ty->getPointerTo(); @@ -7018,7 +7018,7 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con outboxed = (output_type != (jl_value_t*)jl_voidpointer_type); if (outboxed) { assert(jl_datatype_size(output_type) == sizeof(void*) * 4); - Value *strct = emit_allocobj(ctx, (jl_datatype_t*)output_type); + Value *strct = emit_allocobj(ctx, (jl_datatype_t*)output_type, true); setName(ctx.emission_context, strct, "cfun_result"); Value *derived_strct = emit_bitcast(ctx, decay_derived(ctx, strct), ctx.types().T_size->getPointerTo()); MDNode *tbaa = best_tbaa(ctx.tbaa(), output_type); @@ -7194,7 +7194,7 @@ static Function *gen_invoke_wrapper(jl_method_instance_t *lam, jl_value_t *jlret if (!lty->isAggregateType()) // keep "aggregate" type values in place as pointers theArg = ctx.builder.CreateAlignedLoad(lty, theArg, Align(julia_alignment(ty))); } - assert(dyn_cast(theArg) == NULL); + assert(!isa(theArg)); args[idx] = theArg; idx++; } @@ -8962,6 +8962,7 @@ static jl_llvm_functions_t } for (PHINode *PN : ToDelete) { + // This basic block is statically unreachable, thus so is this PHINode PN->replaceAllUsesWith(UndefValue::get(PN->getType())); PN->eraseFromParent(); } diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index fde27b910faeac..81fc9ec1af831c 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -639,7 +639,8 @@ static jl_cgval_t generic_bitcast(jl_codectx_t &ctx, const jl_cgval_t *argv) return mark_julia_type(ctx, vx, false, bt); } else { - Value *box = emit_allocobj(ctx, nb, bt_value_rt); + unsigned align = sizeof(void*); // Allocations are at least pointer aligned + Value *box = emit_allocobj(ctx, nb, bt_value_rt, true, align); setName(ctx.emission_context, box, "bitcast_box"); init_bits_value(ctx, box, vx, ctx.tbaa().tbaa_immut); return mark_julia_type(ctx, box, true, bt->name->wrapper); @@ -698,7 +699,8 @@ static jl_cgval_t generic_cast( else { Value *targ_rt = boxed(ctx, targ); emit_concretecheck(ctx, targ_rt, std::string(jl_intrinsic_name(f)) + ": target type not a leaf primitive type"); - Value *box = emit_allocobj(ctx, nb, targ_rt); + unsigned align = sizeof(void*); // Allocations are at least pointer aligned + Value *box = emit_allocobj(ctx, nb, targ_rt, true, align); setName(ctx.emission_context, box, "cast_box"); init_bits_value(ctx, box, ans, ctx.tbaa().tbaa_immut); return mark_julia_type(ctx, box, true, jlto->name->wrapper); @@ -749,7 +751,7 @@ static jl_cgval_t emit_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) else if (!deserves_stack(ety)) { assert(jl_is_datatype(ety)); uint64_t size = jl_datatype_size(ety); - Value *strct = emit_allocobj(ctx, (jl_datatype_t*)ety); + Value *strct = emit_allocobj(ctx, (jl_datatype_t*)ety, true); setName(ctx.emission_context, strct, "pointerref_box"); im1 = ctx.builder.CreateMul(im1, ConstantInt::get(ctx.types().T_size, LLT_ALIGN(size, jl_datatype_align(ety)))); @@ -905,7 +907,7 @@ static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, jl_cgval_t *argv) if (!deserves_stack(ety)) { assert(jl_is_datatype(ety)); - Value *strct = emit_allocobj(ctx, (jl_datatype_t*)ety); + Value *strct = emit_allocobj(ctx, (jl_datatype_t*)ety, true); setName(ctx.emission_context, strct, "atomic_pointerref_box"); Value *thePtr = emit_unbox(ctx, getInt8PtrTy(ctx.builder.getContext()), e, e.typ); Type *loadT = Type::getIntNTy(ctx.builder.getContext(), nb * 8); diff --git a/src/llvm-alloc-helpers.cpp b/src/llvm-alloc-helpers.cpp index 665c0b1d2e00e7..953ecc1830142a 100644 --- a/src/llvm-alloc-helpers.cpp +++ b/src/llvm-alloc-helpers.cpp @@ -125,6 +125,12 @@ JL_USED_FUNC void AllocUseInfo::dump(llvm::raw_ostream &OS) OS << "hastypeof: " << hastypeof << '\n'; OS << "refload: " << refload << '\n'; OS << "refstore: " << refstore << '\n'; + OS << "allockind:"; + if ((allockind & AllocFnKind::Uninitialized) != AllocFnKind::Unknown) + OS << " uninitialized"; + if ((allockind & AllocFnKind::Zeroed) != AllocFnKind::Unknown) + OS << " zeroed"; + OS << '\n'; OS << "Uses: " << uses.size() << '\n'; for (auto inst: uses) inst->print(OS); @@ -164,8 +170,11 @@ JL_USED_FUNC void AllocUseInfo::dump() #define REMARK(remark) #endif -void jl_alloc::runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArgs required, EscapeAnalysisOptionalArgs options) { +void jl_alloc::runEscapeAnalysis(llvm::CallInst *I, EscapeAnalysisRequiredArgs required, EscapeAnalysisOptionalArgs options) { required.use_info.reset(); + Attribute allockind = I->getFnAttr(Attribute::AllocKind); + if (allockind.isValid()) + required.use_info.allockind = allockind.getAllocKind(); if (I->use_empty()) return; CheckInst::Frame cur{I, 0, I->use_begin(), I->use_end()}; diff --git a/src/llvm-alloc-helpers.h b/src/llvm-alloc-helpers.h index 3bd80704a0888c..49c3b15332a56f 100644 --- a/src/llvm-alloc-helpers.h +++ b/src/llvm-alloc-helpers.h @@ -87,6 +87,8 @@ namespace jl_alloc { bool returned:1; // The object is used in an error function bool haserror:1; + // For checking attributes of "uninitialized" or "zeroed" or unknown + llvm::AllocFnKind allockind; // The alloc has a Julia object reference not in an explicit field. bool has_unknown_objref:1; @@ -105,6 +107,7 @@ namespace jl_alloc { hasunknownmem = false; returned = false; haserror = false; + allockind = llvm::AllocFnKind::Unknown; has_unknown_objref = false; has_unknown_objrefaggr = false; uses.clear(); @@ -153,7 +156,7 @@ namespace jl_alloc { } }; - void runEscapeAnalysis(llvm::Instruction *I, EscapeAnalysisRequiredArgs required, EscapeAnalysisOptionalArgs options=EscapeAnalysisOptionalArgs()); + void runEscapeAnalysis(llvm::CallInst *I, EscapeAnalysisRequiredArgs required, EscapeAnalysisOptionalArgs options=EscapeAnalysisOptionalArgs()); } diff --git a/src/llvm-alloc-opt.cpp b/src/llvm-alloc-opt.cpp index 619fa03ef58dd6..5df4f52aca425c 100644 --- a/src/llvm-alloc-opt.cpp +++ b/src/llvm-alloc-opt.cpp @@ -135,12 +135,13 @@ struct Optimizer { // insert llvm.lifetime.* calls for `ptr` with size `sz` based on the use of `orig`. void insertLifetime(Value *ptr, Constant *sz, Instruction *orig); - void checkInst(Instruction *I); + void checkInst(CallInst *I); void replaceIntrinsicUseWith(IntrinsicInst *call, Intrinsic::ID ID, Instruction *orig_i, Instruction *new_i); void removeAlloc(CallInst *orig_inst); - void moveToStack(CallInst *orig_inst, size_t sz, bool has_ref); + void moveToStack(CallInst *orig_inst, size_t sz, bool has_ref, AllocFnKind allockind); + void initializeAlloca(IRBuilder<> &prolog_builder, AllocaInst *buff, AllocFnKind allockind); void splitOnStack(CallInst *orig_inst); void optimizeTag(CallInst *orig_inst); @@ -288,7 +289,7 @@ void Optimizer::optimizeAll() << "GC allocation moved to stack " << ore::NV("GC Allocation", orig); }); // The object has no fields with mix reference access - moveToStack(orig, sz, has_ref); + moveToStack(orig, sz, has_ref, use_info.allockind); } } @@ -355,7 +356,7 @@ ssize_t Optimizer::getGCAllocSize(Instruction *I) return -1; } -void Optimizer::checkInst(Instruction *I) +void Optimizer::checkInst(CallInst *I) { LLVM_DEBUG(dbgs() << "Running escape analysis on " << *I << "\n"); jl_alloc::EscapeAnalysisRequiredArgs required{use_info, check_stack, pass, *pass.DL}; @@ -598,9 +599,25 @@ void Optimizer::replaceIntrinsicUseWith(IntrinsicInst *call, Intrinsic::ID ID, call->eraseFromParent(); } +void Optimizer::initializeAlloca(IRBuilder<> &prolog_builder, AllocaInst *buff, AllocFnKind allockind) +{ + if ((allockind & AllocFnKind::Uninitialized) != AllocFnKind::Unknown) + return; + assert(!buff->isArrayAllocation()); + Type *T = buff->getAllocatedType(); + Value *Init = UndefValue::get(T); + if ((allockind & AllocFnKind::Zeroed) != AllocFnKind::Unknown) + Init = Constant::getNullValue(T); // zero, as described + else if (allockind == AllocFnKind::Unknown) + Init = Constant::getNullValue(T); // assume zeroed since we didn't find the attribute + else + Init = prolog_builder.CreateFreeze(UndefValue::get(T)); // assume freeze, since LLVM does not natively support this case + prolog_builder.CreateStore(Init, buff); +} + // This function should not erase any safepoint so that the lifetime marker can find and cache // all the original safepoints. -void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) +void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref, AllocFnKind allockind) { ++RemovedAllocs; ++StackAllocs; @@ -643,6 +660,10 @@ void Optimizer::moveToStack(CallInst *orig_inst, size_t sz, bool has_ref) ptr = cast(prolog_builder.CreateBitCast(buff, Type::getInt8PtrTy(prolog_builder.getContext(), buff->getType()->getPointerAddressSpace()))); } insertLifetime(ptr, ConstantInt::get(Type::getInt64Ty(prolog_builder.getContext()), sz), orig_inst); + if (sz != 0 && !has_ref) { // TODO: fix has_ref case too + IRBuilder<> builder(orig_inst); + initializeAlloca(builder, buff, allockind); + } Instruction *new_inst = cast(prolog_builder.CreateBitCast(ptr, JuliaType::get_pjlvalue_ty(prolog_builder.getContext(), buff->getType()->getPointerAddressSpace()))); new_inst->takeName(orig_inst); @@ -927,8 +948,10 @@ void Optimizer::splitOnStack(CallInst *orig_inst) allocty = ArrayType::get(Type::getInt8Ty(pass.getLLVMContext()), field.size); } slot.slot = prolog_builder.CreateAlloca(allocty); + IRBuilder<> builder(orig_inst); insertLifetime(prolog_builder.CreateBitCast(slot.slot, Type::getInt8PtrTy(prolog_builder.getContext())), ConstantInt::get(Type::getInt64Ty(prolog_builder.getContext()), field.size), orig_inst); + initializeAlloca(builder, slot.slot, use_info.allockind); slots.push_back(std::move(slot)); } const auto nslots = slots.size(); diff --git a/src/llvm-pass-helpers.cpp b/src/llvm-pass-helpers.cpp index ed8cc9ed645aeb..d17ce3105135cf 100644 --- a/src/llvm-pass-helpers.cpp +++ b/src/llvm-pass-helpers.cpp @@ -130,7 +130,7 @@ namespace jl_intrinsics { #if JL_LLVM_VERSION >= 160000 FnAttrs.addMemoryAttr(MemoryEffects::argMemOnly(ModRefInfo::Ref) | inaccessibleMemOnly(ModRefInfo::ModRef)); #endif - FnAttrs.addAllocKindAttr(AllocFnKind::Alloc | AllocFnKind::Uninitialized); + FnAttrs.addAllocKindAttr(AllocFnKind::Alloc); FnAttrs.addAttribute(Attribute::WillReturn); FnAttrs.addAttribute(Attribute::NoUnwind); target->addFnAttrs(FnAttrs); diff --git a/test/llvmpasses/alloc-opt-pass.ll b/test/llvmpasses/alloc-opt-pass.ll index 521fd88d582eee..6bee0fd325105e 100644 --- a/test/llvmpasses/alloc-opt-pass.ll +++ b/test/llvmpasses/alloc-opt-pass.ll @@ -81,6 +81,7 @@ L3: ; CHECK-LABEL: @legal_int_types ; CHECK: alloca [12 x i8] ; CHECK-NOT: alloca i96 +; CHECK: store [12 x i8] zeroinitializer, ; CHECK: ret void define void @legal_int_types() { %pgcstack = call {}*** @julia.get_pgcstack() @@ -143,6 +144,7 @@ L2: ; CHECK: alloca ; CHECK-NOT: call token(...) @llvm.julia.gc_preserve_begin ; CHECK: call void @llvm.lifetime.start +; CHECK: store [8 x i8] zeroinitializer, ; CHECK-NOT: call void @llvm.lifetime.end define void @lifetime_no_preserve_end({}* noalias nocapture noundef nonnull sret({}) %0) { %pgcstack = call {}*** @julia.get_pgcstack() @@ -160,3 +162,39 @@ define void @lifetime_no_preserve_end({}* noalias nocapture noundef nonnull sret ret void } ; CHECK-LABEL: }{{$}} + + +; CHECK-LABEL: @initializers +; CHECK: alloca [1 x i8] +; CHECK-DAG: alloca [2 x i8] +; CHECK-DAG: alloca [3 x i8] +; CHECK-DAG: freeze [1 x i8] undef +; CHECK-DAG: store [1 x i8] % +; CHECK-DAG: store [3 x i8] zeroinitializer, +; CHECK-NOT: store +; CHECK-NOT: zeroinitializer +; CHECK: ret void +define void @initializers() { + %pgcstack = call {}*** @julia.get_pgcstack() + %ptls = call {}*** @julia.ptls_states() + %ptls_i8 = bitcast {}*** %ptls to i8* + + %var1 = call {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, i64 1, {} addrspace(10)* @tag) #0 + %var2 = addrspacecast {} addrspace(10)* %var1 to {} addrspace(11)* + %var3 = call {}* @julia.pointer_from_objref({} addrspace(11)* %var2) + + %var4 = call {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, i64 2, {} addrspace(10)* @tag) #1 + %var5 = addrspacecast {} addrspace(10)* %var4 to {} addrspace(11)* + %var6 = call {}* @julia.pointer_from_objref({} addrspace(11)* %var5) + + %var7 = call {} addrspace(10)* @julia.gc_alloc_obj(i8* %ptls_i8, i64 3, {} addrspace(10)* @tag) #2 + %var8 = addrspacecast {} addrspace(10)* %var7 to {} addrspace(11)* + %var9 = call {}* @julia.pointer_from_objref({} addrspace(11)* %var8) + + ret void +} +; CHECK-LABEL: }{{$}} + +attributes #0 = { allockind("alloc") } +attributes #1 = { allockind("alloc,uninitialized") } +attributes #2 = { allockind("alloc,zeroed") } From e754f2036cbfc37ea24a33d02e86e41a9cf56af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20G=C3=B6ttgens?= Date: Sat, 18 Nov 2023 07:30:38 +0100 Subject: [PATCH 18/38] Add missing type annotation reported by JET (#52207) We already know from https://github.com/lgoettgens/julia/blob/d0a3edddda407302930f3b2a742a7c8ebbcafb5b/base/loading.jl#L1212 that the value is not `nothing`. However, type inference does not know that as the check lives in a different function. --- base/loading.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/loading.jl b/base/loading.jl index 021c32b7c26541..ff1a2207a2022e 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1278,7 +1278,7 @@ function _insert_extension_triggers(parent::PkgId, extensions::Dict{String, Any} for (ext, triggers) in extensions triggers = triggers::Union{String, Vector{String}} triggers isa String && (triggers = [triggers]) - id = PkgId(uuid5(parent.uuid, ext), ext) + id = PkgId(uuid5(parent.uuid::UUID, ext), ext) if id in keys(EXT_PRIMED) || haskey(Base.loaded_modules, id) continue # extension is already primed or loaded, don't add it again end From 959b474d0516df77a268d9f23ccda5d2ad32acdf Mon Sep 17 00:00:00 2001 From: Frank Ebel Date: Sat, 18 Nov 2023 11:43:17 +0100 Subject: [PATCH 19/38] docs: update latest stable version (#52215) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e904b3b8d6770d..add11e8cef44b4 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ and then use the command prompt to change into the resulting julia directory. By Julia. However, most users should use the [most recent stable version](https://github.com/JuliaLang/julia/releases) of Julia. You can get this version by running: - git checkout v1.9.3 + git checkout v1.9.4 To build the `julia` executable, run `make` from within the julia directory. From e75dd479ee38907183cb940e572440411ea1c448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= Date: Sat, 18 Nov 2023 11:32:51 +0000 Subject: [PATCH 20/38] [nghttp2_jll] Upgrade to v1.58.0 (#52218) Usual memo to self: * update version in `stdlib/nghttp2_jll/Project.toml` and `deps/nghttp2.version` * update test in `stdlib/nghttp2_jll/test/runtests.jl` * refresh checksums with `make -f contrib/refresh_checksums.mk -j nghttp2` --- deps/checksums/nghttp2 | 68 ++++++++++++++--------------- deps/nghttp2.version | 2 +- stdlib/nghttp2_jll/Project.toml | 2 +- stdlib/nghttp2_jll/test/runtests.jl | 2 +- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/deps/checksums/nghttp2 b/deps/checksums/nghttp2 index 66ae3cbf34c0ef..9c419f5234ffcb 100644 --- a/deps/checksums/nghttp2 +++ b/deps/checksums/nghttp2 @@ -1,34 +1,34 @@ -nghttp2-1.52.0.tar.bz2/md5/bde5874bd8e7e8be3512a621de27b9d5 -nghttp2-1.52.0.tar.bz2/sha512/019ec7a904d1baf8755ffcea0b38acf45ea9c6829d989a530ab35807338ba78d3328b86eebb3106b8372b7a8c51b466974d423e0cd786b6d6d020f0840c160bf -nghttp2.v1.52.0+1.aarch64-apple-darwin.tar.gz/md5/e3d9e07029e184cc55b7e0c4d2e27c7f -nghttp2.v1.52.0+1.aarch64-apple-darwin.tar.gz/sha512/cd098db984f751b00d2cc99d7f7eba0fa830ba178dd85a9dfa679a591e62d57364dcfd74e6a55ef513a0436a8e520b1a5474d4bfa9a8bdcd70e398482b7c9985 -nghttp2.v1.52.0+1.aarch64-linux-gnu.tar.gz/md5/73fe75f3cfa2bd3e804ea39a4eb884a9 -nghttp2.v1.52.0+1.aarch64-linux-gnu.tar.gz/sha512/71f4b2a23ba148b66432797b0db954dbd98fc900045d4572f488b43779aae125f71929e5bba6bbadd30c7998a133c5e5beb70888968bf3b01bb5fe9c9ea0e451 -nghttp2.v1.52.0+1.aarch64-linux-musl.tar.gz/md5/736a24a7eee567851a965558e31489fb -nghttp2.v1.52.0+1.aarch64-linux-musl.tar.gz/sha512/ab36182b04a590b092fae9e3a912a87467e8b01ad40a628a1d2e52910ee513ab327d5d2836df598d5aa8203f60a605d19d0b9636eb35d12a84a1c9d87124604b -nghttp2.v1.52.0+1.armv6l-linux-gnueabihf.tar.gz/md5/56fd32e8d77d4c9d9e2355565f4db19b -nghttp2.v1.52.0+1.armv6l-linux-gnueabihf.tar.gz/sha512/85718e0e5cee35d91a8684ea33d8f965bb30d62dbd6b74a574a2fbc4c1027b1ef23ef68f1dec3f037fa6c5739287329567df9591a69f8f23b23fab2516a0b644 -nghttp2.v1.52.0+1.armv6l-linux-musleabihf.tar.gz/md5/283273d3bf4d53b56d12ef6af2e72f20 -nghttp2.v1.52.0+1.armv6l-linux-musleabihf.tar.gz/sha512/5c1d92cbf5f2f4e1ceb4ee13634c0bceb6ca28abaf9d87cc673f264d274bb96aa095648295e9aa76f86eb0890a426f47c0b942e72610daf722ed8e86b5f0df69 -nghttp2.v1.52.0+1.armv7l-linux-gnueabihf.tar.gz/md5/d7ae84e5365759a42d0fe0360f679b61 -nghttp2.v1.52.0+1.armv7l-linux-gnueabihf.tar.gz/sha512/63212e3ad94d2bc54ca9ebd452d8de8e67aa53c03a3b3033d36da765303e714d8d5c24156ea4fb985acc72fe52e2977e8e8a658cdd9409bd41ecf401c08c1aee -nghttp2.v1.52.0+1.armv7l-linux-musleabihf.tar.gz/md5/a6ad0f25f43b7f1832faeaaadf683ed4 -nghttp2.v1.52.0+1.armv7l-linux-musleabihf.tar.gz/sha512/64b9075c0d819288345d53c5ce88b360d2ca4d24c3d2e81fb53c55f86054b1a3e95d7831b363a4100965cdbf479268a5993d66ef59089a219a97b4151d8fef60 -nghttp2.v1.52.0+1.i686-linux-gnu.tar.gz/md5/9781f6eeb4d24a291d6737e59e74edc1 -nghttp2.v1.52.0+1.i686-linux-gnu.tar.gz/sha512/2b542cb67e78993ef881694dc50c980b57db3761c5f4e11c381afb1b31d1fb8ab0a8b20e1279303a602c07912f21e8ef9d732366b76ab3f356a74b444a5dc78c -nghttp2.v1.52.0+1.i686-linux-musl.tar.gz/md5/08603b9364179ab4cbe0637b9b1b63b5 -nghttp2.v1.52.0+1.i686-linux-musl.tar.gz/sha512/0a5b79709482548c6a713843b670695b4b13d2b219b592d029719da0b4187fe884798fb44e2c511c300f02bab03f2b0b289d49d6256e3ce0b9602a66ea2382bd -nghttp2.v1.52.0+1.i686-w64-mingw32.tar.gz/md5/1abdf0cad466ed0ca0da137809999d8e -nghttp2.v1.52.0+1.i686-w64-mingw32.tar.gz/sha512/04680895ead989fda56b284d8963e7ca31680492c8f77f4c6bd7ca03b9a66ee7529b78cf35e07b2e106f43c9aa543dffd4081b034339803ba95021293d3df997 -nghttp2.v1.52.0+1.powerpc64le-linux-gnu.tar.gz/md5/ae411e40e24cb3f3b07fe8de211b58c6 -nghttp2.v1.52.0+1.powerpc64le-linux-gnu.tar.gz/sha512/7433502d76646e5761ea2707fa65ea5a412c513c70908a4d9ceb504f08121b1f39bcff984543370c221814785b7064f85dedc777a22df5e30a64a64e510e0978 -nghttp2.v1.52.0+1.x86_64-apple-darwin.tar.gz/md5/59f0de0affaa17898e837b5074de68fc -nghttp2.v1.52.0+1.x86_64-apple-darwin.tar.gz/sha512/e639c813373b17d95220640ec2a568e9731cfc32df826610357ec9ff8e9d7e7abe10291140eaeb9342ae69215798bf3f999db7647c23efb4f815b54f4da9cfe4 -nghttp2.v1.52.0+1.x86_64-linux-gnu.tar.gz/md5/6bc8501392d47b349c7463e984dc5909 -nghttp2.v1.52.0+1.x86_64-linux-gnu.tar.gz/sha512/522cc2a8464ee5770c01b83a6b4ecbbcce322efffbd738f7c907643fe85342e785bbc805028d41c2b7404d6241168d1ab37a9db15018623c265b53905bcf060f -nghttp2.v1.52.0+1.x86_64-linux-musl.tar.gz/md5/725a6adc23880b28303017597b974535 -nghttp2.v1.52.0+1.x86_64-linux-musl.tar.gz/sha512/ede5a34b7f71310e4c3cd99b9b61b2453db5dc8117675de12adb1e68c9283cdf821614f49f4d04bdd3b0f17d51a52972ec1e226d0dbdc5462b1a4a1fcc9f39e7 -nghttp2.v1.52.0+1.x86_64-unknown-freebsd.tar.gz/md5/02e68f367dd5f2ceac3a619da402cbb4 -nghttp2.v1.52.0+1.x86_64-unknown-freebsd.tar.gz/sha512/d0522c4f40471cdfc0768863f9b0e97b453b2e0c850417811d4f264fd167622493141beea66a8668b15dc6b9b4ae42a38017b9f31ed59c9205701188df3d84b9 -nghttp2.v1.52.0+1.x86_64-w64-mingw32.tar.gz/md5/e1c8ec6ec2d69b2ac64b114ebf09f8b4 -nghttp2.v1.52.0+1.x86_64-w64-mingw32.tar.gz/sha512/cb43cb138f14717501e852ed388a44d41012e2bb70b6887584b37b4e0f42827d74f17ea85ba4aa0bc09d623dedeef73eee80815c1db2b6858b31251feb0b5580 +nghttp2-1.58.0.tar.bz2/md5/f60a0340da2638eba448df73b6783bc3 +nghttp2-1.58.0.tar.bz2/sha512/7a40afe80587a13bdd7fd85fc7eb666c0dcc18b951a03c65ab4b52429bdf5d2eae27a9bfaa7303e7517e275239397f60dd466170364dfbd1077e25188ba54aca +nghttp2.v1.58.0+0.aarch64-apple-darwin.tar.gz/md5/528dec6d4a54854532d527067dccade7 +nghttp2.v1.58.0+0.aarch64-apple-darwin.tar.gz/sha512/cfc993f23f95ef3b2841d40e704c17816dec22a173a16c5d0bc3c85a092f1d63b69e47112a8a1aeb4e85603a7087746b97fbf9ea91a0ec42b45d38deb78f3e0b +nghttp2.v1.58.0+0.aarch64-linux-gnu.tar.gz/md5/c358e3407e956bddab6fde752c2bd2b5 +nghttp2.v1.58.0+0.aarch64-linux-gnu.tar.gz/sha512/207ef70f4ba231e8e4fd570a5da7931a488a297d2d525983499d874abef268440d3e8408cc953c3deab5219bc8c93034b89bf2f3e4c1179b52cede354ae6d27f +nghttp2.v1.58.0+0.aarch64-linux-musl.tar.gz/md5/eef1a1209fdbf30a71af878e307c4fb2 +nghttp2.v1.58.0+0.aarch64-linux-musl.tar.gz/sha512/9ad61002303add92b3d9842a408092b8b35ac8ac5ab17fabb77f144261c4829a2695111177b4794901efba87f97ed9274aac9935fc702c60189395abbb4d9a3f +nghttp2.v1.58.0+0.armv6l-linux-gnueabihf.tar.gz/md5/2864d534833745ff12ebb86378a90b0b +nghttp2.v1.58.0+0.armv6l-linux-gnueabihf.tar.gz/sha512/091c367466cd547859d06b6d0bd37d424b0ccefc0f41dd4819156a61f98e0cc1eebf6da4171810e2a876fec9b919675efd9240089681ca8d3cd8620f5f40f8f3 +nghttp2.v1.58.0+0.armv6l-linux-musleabihf.tar.gz/md5/30a7b272ed36c45a3a5f58e7406150e5 +nghttp2.v1.58.0+0.armv6l-linux-musleabihf.tar.gz/sha512/c1c96c2e987a700929c0158232ef745a0fe8d93c722174a91b32c767cdc0441ee73059da9f64daca4010367ee005a9037919cdb73079a12f9612f45852549b0d +nghttp2.v1.58.0+0.armv7l-linux-gnueabihf.tar.gz/md5/3940e07955102a7a71030fe4c8aed59f +nghttp2.v1.58.0+0.armv7l-linux-gnueabihf.tar.gz/sha512/5b628901371a36937a16802211707334b02632c8651a5fd01d2e1508926e51c960b693abbea6f629cfb7ca750b6039b5e4385324f2d431f1fe6e47b121a57f30 +nghttp2.v1.58.0+0.armv7l-linux-musleabihf.tar.gz/md5/2d6d0b8adba7bfea20dabde3c87333df +nghttp2.v1.58.0+0.armv7l-linux-musleabihf.tar.gz/sha512/10c72fdc2e635add9f0364dd95fd8404f187068bce05587531d4865f75126c8eaf4f7c154762175d3ed0e85914b21df90c193766f610da91bb6dd75fe93df5fa +nghttp2.v1.58.0+0.i686-linux-gnu.tar.gz/md5/8753bf62f8863b5c3041ef73ed3fe36a +nghttp2.v1.58.0+0.i686-linux-gnu.tar.gz/sha512/741a8f2bd5902df9f06de67ecdcb041951a21e0d32f5acffac780cba7a41be5d67507ca49f65bf636849baf1b07e53871d9ee5624d4e33e1d4ff4137bd785bda +nghttp2.v1.58.0+0.i686-linux-musl.tar.gz/md5/a1b0f85e40bef6f2647a9e1dfa415196 +nghttp2.v1.58.0+0.i686-linux-musl.tar.gz/sha512/0e438073cc6fe39e2b47d7b89467c1cab1abe953e357f782b2cb16de5f9238f063b925ab602558a1d562b3c3f608167dd339a4ec54ec93b27c4f43bd2e54aaed +nghttp2.v1.58.0+0.i686-w64-mingw32.tar.gz/md5/7cf81bd21105f4d9fd5dd4dbf61fcb70 +nghttp2.v1.58.0+0.i686-w64-mingw32.tar.gz/sha512/47624f56df68983d3bd881cab1e88be249b678cb184cc426443c4a8c85481af03cddc65f642f1d88afd300cad88950662a99c5dbd75a459bd99dc56d3f38231c +nghttp2.v1.58.0+0.powerpc64le-linux-gnu.tar.gz/md5/a6e9e975026f658cb2b68418c5f2cee9 +nghttp2.v1.58.0+0.powerpc64le-linux-gnu.tar.gz/sha512/2007cf1420ac0866b4b6b5b463aa9c491abd85b2559bea259efa075e308301b10f1d8e72f86c36c21cf42e891f672ac289b5c81640a83f002c89aefebeefb355 +nghttp2.v1.58.0+0.x86_64-apple-darwin.tar.gz/md5/06c250253f1969f1c8460f3c65ec02a9 +nghttp2.v1.58.0+0.x86_64-apple-darwin.tar.gz/sha512/397bd1ea32617ff005ad4217cdd5652a6a58d5c98837cdb8366aa4d0fd86824673864850aeb3771c3304755d8d5466b881b4007603a9cded924cd6b8b18384f4 +nghttp2.v1.58.0+0.x86_64-linux-gnu.tar.gz/md5/2be0cc258a81106eaea132fbbdfce04f +nghttp2.v1.58.0+0.x86_64-linux-gnu.tar.gz/sha512/1be2b3906b9a5e2cb9af24ea358b92b50e3c7be85e9533784e3721b28219bce555700ebe3edf789123ff471a062dfc22f10eef1d21a6f060a43151f8904495d0 +nghttp2.v1.58.0+0.x86_64-linux-musl.tar.gz/md5/55898f6770dd85835d76e52003e8a1f6 +nghttp2.v1.58.0+0.x86_64-linux-musl.tar.gz/sha512/ad2266563dca33543c5fae9d0cb4daf9f0c6f4ccab8efc30e2eb273be9df33ae43da3007bcf83d19f39b830c11c17f8577014c81fcf5e76dce159f28937166f2 +nghttp2.v1.58.0+0.x86_64-unknown-freebsd.tar.gz/md5/5f9886a4e321bc393dd086106a685466 +nghttp2.v1.58.0+0.x86_64-unknown-freebsd.tar.gz/sha512/d86ba3730bb63143f990bdbe453fa4a0692b434beab372aa9a8e7673c2fddc8c97ebd2e47ac109ffe50172121f2d56ddd40d8d500b73e61a58635edcede8d071 +nghttp2.v1.58.0+0.x86_64-w64-mingw32.tar.gz/md5/98ecb527506234cc69cb8a958a74a002 +nghttp2.v1.58.0+0.x86_64-w64-mingw32.tar.gz/sha512/3941233fdfa82af9a5590f5975728d51e42791eb1b966a4ae7913a24f52500c24dea05e66911b3c5768535b145c99b31e2dc2ab50354648492df4b4b63434578 diff --git a/deps/nghttp2.version b/deps/nghttp2.version index 200e08bf4bfd9e..680be055e44432 100644 --- a/deps/nghttp2.version +++ b/deps/nghttp2.version @@ -3,4 +3,4 @@ NGHTTP2_JLL_NAME := nghttp2 ## source build -NGHTTP2_VER := 1.52.0 +NGHTTP2_VER := 1.58.0 diff --git a/stdlib/nghttp2_jll/Project.toml b/stdlib/nghttp2_jll/Project.toml index d75e0780bd01a0..560a9537a3b216 100644 --- a/stdlib/nghttp2_jll/Project.toml +++ b/stdlib/nghttp2_jll/Project.toml @@ -1,6 +1,6 @@ name = "nghttp2_jll" uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" -version = "1.52.0+1" +version = "1.58.0+0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/stdlib/nghttp2_jll/test/runtests.jl b/stdlib/nghttp2_jll/test/runtests.jl index 2f9af6d6a3338a..8155e4e787fccf 100644 --- a/stdlib/nghttp2_jll/test/runtests.jl +++ b/stdlib/nghttp2_jll/test/runtests.jl @@ -11,5 +11,5 @@ end @testset "nghttp2_jll" begin info = unsafe_load(ccall((:nghttp2_version,libnghttp2), Ptr{nghttp2_info}, (Cint,), 0)) - @test VersionNumber(unsafe_string(info.version_str)) == v"1.52.0" + @test VersionNumber(unsafe_string(info.version_str)) == v"1.58.0" end From 41a6f268ff07f68779bdf6f8f9dd6ebba82a02b5 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sun, 19 Nov 2023 03:29:15 -0500 Subject: [PATCH 21/38] Thread lattice through escape analysis (#52223) As of #51494 is used in the base pipeline, so external abstract interpreters inherit it by default. To avoid breaking them, make sure we properly pass down the lattice. --- base/compiler/optimize.jl | 2 +- .../ssair/EscapeAnalysis/EscapeAnalysis.jl | 14 +++++++------- test/compiler/EscapeAnalysis/EAUtils.jl | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 514f2e03c2cae9..68118f82fb5f22 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -583,7 +583,7 @@ function refine_effects!(interp::AbstractInterpreter, sv::PostOptAnalysisState) if !is_effect_free(sv.result.ipo_effects) && sv.all_effect_free && !isempty(sv.ea_analysis_pending) ir = sv.ir nargs = length(ir.argtypes) - estate = EscapeAnalysis.analyze_escapes(ir, nargs, GetNativeEscapeCache(interp)) + estate = EscapeAnalysis.analyze_escapes(ir, nargs, optimizer_lattice(interp), GetNativeEscapeCache(interp)) argescapes = EscapeAnalysis.ArgEscapeCache(estate) stack_analysis_result!(sv.result, argescapes) validate_mutable_arg_escapes!(estate, sv) diff --git a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl index 70d45819574fdc..820975d4055250 100644 --- a/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl +++ b/base/compiler/ssair/EscapeAnalysis/EscapeAnalysis.jl @@ -27,7 +27,7 @@ using Core.Compiler: # Core.Compiler specific definitions Bottom, IRCode, IR_FLAG_NOTHROW, InferenceResult, SimpleInferenceLattice, argextype, check_effect_free!, fieldcount_noerror, hasintersect, intrinsic_nothrow, is_meta_expr_head, isbitstype, isexpr, println, setfield!_nothrow, singleton_type, - try_compute_field, try_compute_fieldidx, widenconst, โŠ‘ + try_compute_field, try_compute_fieldidx, widenconst, โŠ‘, AbstractLattice include(x) = _TOP_MOD.include(@__MODULE__, x) if _TOP_MOD === Core.Compiler @@ -37,7 +37,6 @@ else end const AInfo = IdSet{Any} -const ๐•ƒโ‚’ = SimpleInferenceLattice.instance """ x::EscapeInfo @@ -598,10 +597,11 @@ struct LivenessChange <: Change end const Changes = Vector{Change} -struct AnalysisState{T} +struct AnalysisState{T, L <: AbstractLattice} ir::IRCode estate::EscapeState changes::Changes + ๐•ƒโ‚’::L get_escape_cache::T end @@ -613,14 +613,14 @@ Analyzes escape information in `ir`: - `get_escape_cache(::MethodInstance) -> Union{Bool,ArgEscapeCache}`: retrieves cached argument escape information """ -function analyze_escapes(ir::IRCode, nargs::Int, get_escape_cache) +function analyze_escapes(ir::IRCode, nargs::Int, ๐•ƒโ‚’::AbstractLattice, get_escape_cache) stmts = ir.stmts nstmts = length(stmts) + length(ir.new_nodes.stmts) tryregions, arrayinfo = compute_frameinfo(ir) estate = EscapeState(nargs, nstmts, arrayinfo) changes = Changes() # keeps changes that happen at current statement - astate = AnalysisState(ir, estate, changes, get_escape_cache) + astate = AnalysisState(ir, estate, changes, ๐•ƒโ‚’, get_escape_cache) local debug_itr_counter = 0 while true @@ -1452,10 +1452,10 @@ function escape_builtin!(::typeof(setfield!), astate::AnalysisState, pc::Int, ar add_escape_change!(astate, val, ssainfo) # compute the throwness of this setfield! call here since builtin_nothrow doesn't account for that @label add_thrown_escapes - if length(args) == 4 && setfield!_nothrow(๐•ƒโ‚’, + if length(args) == 4 && setfield!_nothrow(astate.๐•ƒโ‚’, argextype(args[2], ir), argextype(args[3], ir), argextype(args[4], ir)) return true - elseif length(args) == 3 && setfield!_nothrow(๐•ƒโ‚’, + elseif length(args) == 3 && setfield!_nothrow(astate.๐•ƒโ‚’, argextype(args[2], ir), argextype(args[3], ir)) return true else diff --git a/test/compiler/EscapeAnalysis/EAUtils.jl b/test/compiler/EscapeAnalysis/EAUtils.jl index 27428e07e920a0..27a971173ac700 100644 --- a/test/compiler/EscapeAnalysis/EAUtils.jl +++ b/test/compiler/EscapeAnalysis/EAUtils.jl @@ -148,7 +148,7 @@ function CC.ipo_dataflow_analysis!(interp::EscapeAnalyzer, ir::IRCode, caller::I nargs = let def = caller.linfo.def; isa(def, Method) ? Int(def.nargs) : 0; end get_escape_cache = GetEscapeCache(interp) estate = try - analyze_escapes(ir, nargs, get_escape_cache) + analyze_escapes(ir, nargs, CC.optimizer_lattice(interp), get_escape_cache) catch err @error "error happened within EA, inspect `Main.failed_escapeanalysis`" Main.failed_escapeanalysis = FailedAnalysis(ir, nargs, get_escape_cache) From 19ca07d00fdc34b90d79511acf4ebbe9d2bf9527 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sun, 19 Nov 2023 03:29:29 -0500 Subject: [PATCH 22/38] cfg_simplify: Fix phi node renaming corner case bug (#52224) Depending on the order of basic block, cfg_simplify! could occasionally forget to renumber a phinode edge, causing at best a downstream assertion and at worst a miscompile. The root of this issue is that ordinarily, we detect whether a phinode edge requires later renaming by looking at the last inserted index, and since we process all indices by order, detecting whether we have already inserted a certain value is a simple comparison. However, cfg_simplify! re-arranges the order of basic blocks, so this check no longer works. Instead, we check whether the incoming edge has already been scheduled. This is slightly less precise (because it is possible for the edge itself to not have been scheduled, even though the value has been), but correct and a slight imprecision here at worst causes a negligible compile-time increase. --- base/compiler/ssair/ir.jl | 11 ++++++----- base/compiler/ssair/passes.jl | 12 +++++++++--- test/compiler/irpasses.jl | 31 +++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 3f8160a088e8d3..c2c3d2ac0ed54f 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -1112,7 +1112,7 @@ end (this::Refiner)() = (this.result_flags[this.result_idx] |= IR_FLAG_REFINED; nothing) function process_phinode_values(old_values::Vector{Any}, late_fixup::Vector{Int}, - processed_idx::Int, result_idx::Int, + already_inserted, result_idx::Int, ssa_rename::Vector{Any}, used_ssas::Vector{Int}, new_new_used_ssas::Vector{Int}, do_rename_ssa::Bool, @@ -1123,7 +1123,7 @@ function process_phinode_values(old_values::Vector{Any}, late_fixup::Vector{Int} val = old_values[i] if isa(val, SSAValue) if do_rename_ssa - if val.id > processed_idx + if !already_inserted(i, OldSSAValue(val.id)) push!(late_fixup, result_idx) val = OldSSAValue(val.id) else @@ -1133,7 +1133,7 @@ function process_phinode_values(old_values::Vector{Any}, late_fixup::Vector{Int} used_ssas[val.id] += 1 end elseif isa(val, OldSSAValue) - if val.id > processed_idx + if !already_inserted(i, val) push!(late_fixup, result_idx) else # Always renumber these. do_rename_ssa applies only to actual SSAValues @@ -1293,6 +1293,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr (; result, ssa_rename, late_fixup, used_ssas, new_new_used_ssas) = compact (; cfg_transforms_enabled, fold_constant_branches, bb_rename_succ, bb_rename_pred, result_bbs) = compact.cfg_transform mark_refined! = Refiner(result.flag, result_idx) + already_inserted = (::Int, ssa::OldSSAValue)->ssa.id <= processed_idx if stmt === nothing ssa_rename[idx] = stmt elseif isa(stmt, OldSSAValue) @@ -1461,7 +1462,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr values = stmt.values end - values = process_phinode_values(values, late_fixup, processed_idx, result_idx, ssa_rename, used_ssas, new_new_used_ssas, do_rename_ssa, mark_refined!) + values = process_phinode_values(values, late_fixup, already_inserted, result_idx, ssa_rename, used_ssas, new_new_used_ssas, do_rename_ssa, mark_refined!) # Don't remove the phi node if it is before the definition of its value # because doing so can create forward references. This should only # happen with dead loops, but can cause problems when optimization @@ -1500,7 +1501,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr push!(values, value) end end - result[result_idx][:stmt] = PhiCNode(process_phinode_values(values, late_fixup, processed_idx, result_idx, ssa_rename, used_ssas, new_new_used_ssas, do_rename_ssa, mark_refined!)) + result[result_idx][:stmt] = PhiCNode(process_phinode_values(values, late_fixup, already_inserted, result_idx, ssa_rename, used_ssas, new_new_used_ssas, do_rename_ssa, mark_refined!)) result_idx += 1 else if isa(stmt, SSAValue) diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 85f704e9a076d7..21e0e9b213f1c2 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -2209,7 +2209,8 @@ function cfg_simplify!(ir::IRCode) ms = orig_bb bb_start = true while ms != 0 - for i in bbs[ms].stmts + old_bb_stmts = bbs[ms].stmts + for i in old_bb_stmts node = ir.stmts[i] compact.result[compact.result_idx] = node stmt = node[:stmt] @@ -2221,8 +2222,13 @@ function cfg_simplify!(ir::IRCode) values = phi.values (; ssa_rename, late_fixup, used_ssas, new_new_used_ssas) = compact ssa_rename[i] = SSAValue(compact.result_idx) - processed_idx = i - renamed_values = process_phinode_values(values, late_fixup, processed_idx, compact.result_idx, ssa_rename, used_ssas, new_new_used_ssas, true, nothing) + already_inserted = function (i::Int, val::OldSSAValue) + if val.id in old_bb_stmts + return val.id <= i + end + return bb_rename_pred[phi.edges[i]] < idx + end + renamed_values = process_phinode_values(values, late_fixup, already_inserted, compact.result_idx, ssa_rename, used_ssas, new_new_used_ssas, true, nothing) edges = Int32[] values = Any[] sizehint!(edges, length(phi.edges)); sizehint!(values, length(renamed_values)) diff --git a/test/compiler/irpasses.jl b/test/compiler/irpasses.jl index ef6bf630bd2e5d..204d0400ea701e 100644 --- a/test/compiler/irpasses.jl +++ b/test/compiler/irpasses.jl @@ -1584,4 +1584,35 @@ let (ir, _) = only(Base.code_ircode(f_with_merge_to_entry_block)) Core.Compiler.verify_ir(ir) end +# Test that CFG simplify doesn't leave an un-renamed SSA Value +let m = Meta.@lower 1 + 1 + # Test that CFG simplify doesn't try to merge every block in a loop into + # its predecessor + @assert Meta.isexpr(m, :thunk) + src = m.args[1]::CodeInfo + src.code = Any[ + # Block 1 + GotoIfNot(Argument(1), 3), + # Block 2 + GotoNode(5), + # Block 3 + Expr(:call, Base.inferencebarrier, 1), + GotoNode(6), + # Block 4 + Expr(:call, Base.inferencebarrier, 2), # fallthrough + # Block 5 + PhiNode(Int32[4, 5], Any[SSAValue(3), SSAValue(5)]), + ReturnNode(1) + ] + nstmts = length(src.code) + src.ssavaluetypes = nstmts + src.codelocs = fill(Int32(1), nstmts) + src.ssaflags = fill(Int32(0), nstmts) + ir = Core.Compiler.inflate_ir(src) + Core.Compiler.verify_ir(ir) + ir = Core.Compiler.cfg_simplify!(ir) + Core.Compiler.verify_ir(ir) + @test length(ir.cfg.blocks) == 4 +end + # JET.test_opt(Core.Compiler.cfg_simplify!, (Core.Compiler.IRCode,)) From ec3911cfd46b0413ce435956b372ab7202d127f8 Mon Sep 17 00:00:00 2001 From: Daniel Karrasch Date: Sun, 19 Nov 2023 09:36:43 +0100 Subject: [PATCH 23/38] Add `lmul!` and `rmul!` for `Bidiagonal` (#51777) --- stdlib/LinearAlgebra/src/bidiag.jl | 44 +++++++++++++++++++++++++++-- stdlib/LinearAlgebra/test/bidiag.jl | 12 ++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/stdlib/LinearAlgebra/src/bidiag.jl b/stdlib/LinearAlgebra/src/bidiag.jl index f8cc3ceadcfadb..78c79b6fcefac7 100644 --- a/stdlib/LinearAlgebra/src/bidiag.jl +++ b/stdlib/LinearAlgebra/src/bidiag.jl @@ -426,6 +426,7 @@ end const BandedMatrix = Union{Bidiagonal,Diagonal,Tridiagonal,SymTridiagonal} # or BiDiTriSym const BiTriSym = Union{Bidiagonal,Tridiagonal,SymTridiagonal} +const TriSym = Union{Tridiagonal,SymTridiagonal} const BiTri = Union{Bidiagonal,Tridiagonal} @inline mul!(C::AbstractVector, A::BandedMatrix, B::AbstractVector, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) @inline mul!(C::AbstractMatrix, A::BandedMatrix, B::AbstractVector, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) @@ -433,6 +434,9 @@ const BiTri = Union{Bidiagonal,Tridiagonal} @inline mul!(C::AbstractMatrix, A::AbstractMatrix, B::BandedMatrix, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) @inline mul!(C::AbstractMatrix, A::BandedMatrix, B::BandedMatrix, alpha::Number, beta::Number) = _mul!(C, A, B, MulAddMul(alpha, beta)) +lmul!(A::Bidiagonal, B::AbstractVecOrMat) = @inline _mul!(B, A, B, MulAddMul()) +rmul!(B::AbstractMatrix, A::Bidiagonal) = @inline _mul!(B, B, A, MulAddMul()) + function check_A_mul_B!_sizes(C, A, B) mA, nA = size(A) mB, nB = size(B) @@ -460,7 +464,11 @@ function _diag(A::Bidiagonal, k) end end -function _mul!(C::AbstractMatrix, A::BiTriSym, B::BiTriSym, _add::MulAddMul = MulAddMul()) +_mul!(C::AbstractMatrix, A::BiTriSym, B::TriSym, _add::MulAddMul = MulAddMul()) = + _bibimul!(C, A, B, _add) +_mul!(C::AbstractMatrix, A::BiTriSym, B::Bidiagonal, _add::MulAddMul = MulAddMul()) = + _bibimul!(C, A, B, _add) +function _bibimul!(C, A, B, _add) check_A_mul_B!_sizes(C, A, B) n = size(A,1) n <= 3 && return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) @@ -583,7 +591,7 @@ function _mul!(C::AbstractVecOrMat, A::BiTriSym, B::AbstractVecOrMat, _add::MulA C end -function _mul!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::AbstractMatrix, B::TriSym, _add::MulAddMul = MulAddMul()) require_one_based_indexing(C, A) check_A_mul_B!_sizes(C, A, B) iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) @@ -618,7 +626,37 @@ function _mul!(C::AbstractMatrix, A::AbstractMatrix, B::BiTriSym, _add::MulAddMu C end -function _mul!(C::AbstractMatrix, A::Diagonal, B::BiTriSym, _add::MulAddMul = MulAddMul()) +function _mul!(C::AbstractMatrix, A::AbstractMatrix, B::Bidiagonal, _add::MulAddMul = MulAddMul()) + require_one_based_indexing(C, A) + check_A_mul_B!_sizes(C, A, B) + iszero(_add.alpha) && return _rmul_or_fill!(C, _add.beta) + if size(A, 1) <= 3 || size(B, 2) <= 1 + return mul!(C, Array(A), Array(B), _add.alpha, _add.beta) + end + m, n = size(A) + @inbounds if B.uplo == 'U' + for i in 1:m + for j in n:-1:2 + _modify!(_add, A[i,j] * B.dv[j] + A[i,j-1] * B.ev[j-1], C, (i, j)) + end + _modify!(_add, A[i,1] * B.dv[1], C, (i, 1)) + end + else # uplo == 'L' + for i in 1:m + for j in 1:n-1 + _modify!(_add, A[i,j] * B.dv[j] + A[i,j+1] * B.ev[j], C, (i, j)) + end + _modify!(_add, A[i,n] * B.dv[n], C, (i, n)) + end + end + C +end + +_mul!(C::AbstractMatrix, A::Diagonal, B::Bidiagonal, _add::MulAddMul = MulAddMul()) = + _dibimul!(C, A, B, _add) +_mul!(C::AbstractMatrix, A::Diagonal, B::TriSym, _add::MulAddMul = MulAddMul()) = + _dibimul!(C, A, B, _add) +function _dibimul!(C, A, B, _add) require_one_based_indexing(C) check_A_mul_B!_sizes(C, A, B) n = size(A,1) diff --git a/stdlib/LinearAlgebra/test/bidiag.jl b/stdlib/LinearAlgebra/test/bidiag.jl index a3e5a2f437e932..2fce781e30ab13 100644 --- a/stdlib/LinearAlgebra/test/bidiag.jl +++ b/stdlib/LinearAlgebra/test/bidiag.jl @@ -439,6 +439,9 @@ Random.seed!(1) for op in (+, -, *) @test Array(op(T, T2)) โ‰ˆ op(Tfull, Tfull2) end + A = kron(T.dv, T.dv') + @test T * A โ‰ˆ lmul!(T, copy(A)) + @test A * T โ‰ˆ rmul!(copy(A), T) end # test pass-through of mul! for SymTridiagonal*Bidiagonal TriSym = SymTridiagonal(T.dv, T.ev) @@ -446,7 +449,8 @@ Random.seed!(1) # test pass-through of mul! for AbstractTriangular*Bidiagonal Tri = UpperTriangular(diagm(1 => T.ev)) Dia = Diagonal(T.dv) - @test Array(Tri*T) โ‰ˆ Array(Tri)*Array(T) + @test Array(Tri*T) โ‰ˆ Array(Tri)*Array(T) โ‰ˆ rmul!(copy(Tri), T) + @test Array(T*Tri) โ‰ˆ Array(T)*Array(Tri) โ‰ˆ lmul!(T, copy(Tri)) # test mul! itself for these types for AA in (Tri, Dia) for f in (identity, transpose, adjoint) @@ -459,8 +463,10 @@ Random.seed!(1) for f in (identity, transpose, adjoint) C = relty == Int ? rand(float(elty), n, n) : rand(elty, n, n) B = rand(elty, n, n) - D = copy(C) + 2.0 * Array(T*f(B)) - mul!(C, T, f(B), 2.0, 1.0) โ‰ˆ D + D = C + 2.0 * Array(T*f(B)) + @test mul!(C, T, f(B), 2.0, 1.0) โ‰ˆ D + @test lmul!(T, copy(f(B))) โ‰ˆ T * f(B) + @test rmul!(copy(f(B)), T) โ‰ˆ f(B) * T end # Issue #31870 From c07893d0ab90ef44ea7256fc5877b625761738d3 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Sun, 19 Nov 2023 15:39:19 +0530 Subject: [PATCH 24/38] Cartesian indexing for SubArrays with non-integer AbstractRanges (#52094) With #50457 now merged, an `AbstractRange` of `CartesianIndex`es should use `CartesianIndexing` in constructing a `SubArray`. I've limited the method to integer ranges, as this is the case where we know for sure that the indexing may be linear. Fixes ```julia julia> view(1:2, StepRangeLen(CartesianIndex(1), CartesianIndex(1), 0)) 0-element view(::UnitRange{Int64}, StepRangeLen(CartesianIndex(1,), CartesianIndex(1,), 0)) with eltype Int64 ``` --------- Co-authored-by: N5N3 <2642243996@qq.com> --- base/subarray.jl | 6 ++++-- test/subarray.jl | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/base/subarray.jl b/base/subarray.jl index fa142a8843c63f..464bd660b08560 100644 --- a/base/subarray.jl +++ b/base/subarray.jl @@ -52,8 +52,10 @@ viewindexing(I::Tuple{Slice, Slice, Vararg{Any}}) = (@inline; viewindexing(tail( # A UnitRange can follow Slices, but only if all other indices are scalar viewindexing(I::Tuple{Slice, AbstractUnitRange, Vararg{ScalarIndex}}) = IndexLinear() viewindexing(I::Tuple{Slice, Slice, Vararg{ScalarIndex}}) = IndexLinear() # disambiguate -# In general, ranges are only fast if all other indices are scalar -viewindexing(I::Tuple{AbstractRange, Vararg{ScalarIndex}}) = IndexLinear() +# In general, scalar ranges are only fast if all other indices are scalar +# Other ranges, such as those of `CartesianIndex`es, are not fast even if these +# are followed by `ScalarIndex`es +viewindexing(I::Tuple{AbstractRange{<:ScalarIndex}, Vararg{ScalarIndex}}) = IndexLinear() # All other index combinations are slow viewindexing(I::Tuple{Vararg{Any}}) = IndexCartesian() # Of course, all other array types are slow diff --git a/test/subarray.jl b/test/subarray.jl index da2a88d68408eb..35e376f1a40b43 100644 --- a/test/subarray.jl +++ b/test/subarray.jl @@ -809,3 +809,11 @@ end b = view(a, Base.IdentityUnitRange(4:7)) @test first(b) == a[Base.first_index(b)] end + +@testset "StepRangeLen of CartesianIndex-es" begin + v = view(1:2, StepRangeLen(CartesianIndex(1,1), CartesianIndex(1,1), 0)) + @test isempty(v) + r = StepRangeLen(CartesianIndex(1), CartesianIndex(1), 1) + v = view(1:2, r) + @test v == view(1:2, collect(r)) +end From f5d189fa3dbe5076ef07bf495e5582a73cd1acca Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Mon, 20 Nov 2023 00:50:33 +0900 Subject: [PATCH 25/38] effects: don't taint `:noub` for `:new` allocations (#52222) After #52169, the UB previously associated with allocations with uninitialized fields has been addressed, so there's no longer a need to taint `:noub` for `:new` allocations during abstract interpretation. I believe, even without #52169, uninitialized field does not inherently leads to UB, but just causes inconsistency of the program, since what actually causes UB is `getfield` that accesses into whatever object, but not the allocation itself. --- base/compiler/abstractinterpretation.jl | 20 +++++++++++--------- base/compiler/ssair/inlining.jl | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 77024670b7002b..f173f19fe00809 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2352,24 +2352,22 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp elseif ehead === :new t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv), true) ut = unwrap_unionall(t) - consistent = noub = ALWAYS_FALSE - nothrow = false if isa(ut, DataType) && !isabstracttype(ut) ismutable = ismutabletype(ut) fcount = datatype_fieldcount(ut) nargs = length(e.args) - 1 - if (fcount === nothing || (fcount > nargs && (let t = t + has_any_uninitialized = (fcount === nothing || (fcount > nargs && (let t = t any(i::Int -> !is_undefref_fieldtype(fieldtype(t, i)), (nargs+1):fcount) end))) - # allocation with undefined field leads to undefined behavior and should taint `:noub` + if has_any_uninitialized + # allocation with undefined field is inconsistent always + consistent = ALWAYS_FALSE elseif ismutable - # mutable object isn't `:consistent`, but we still have a chance that + # mutable allocation isn't `:consistent`, but we still have a chance that # return type information later refines the `:consistent`-cy of the method consistent = CONSISTENT_IF_NOTRETURNED - noub = ALWAYS_TRUE else - consistent = ALWAYS_TRUE - noub = ALWAYS_TRUE + consistent = ALWAYS_TRUE # immutable allocation is consistent end if isconcretedispatch(t) nothrow = true @@ -2411,9 +2409,13 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp end else t = refine_partial_type(t) + nothrow = false end + else + consistent = ALWAYS_FALSE + nothrow = false end - effects = Effects(EFFECTS_TOTAL; consistent, nothrow, noub) + effects = Effects(EFFECTS_TOTAL; consistent, nothrow) elseif ehead === :splatnew t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv), true) nothrow = false # TODO: More precision diff --git a/base/compiler/ssair/inlining.jl b/base/compiler/ssair/inlining.jl index 26dacbc3388f3e..f62e773567ac3e 100644 --- a/base/compiler/ssair/inlining.jl +++ b/base/compiler/ssair/inlining.jl @@ -1265,7 +1265,7 @@ function check_effect_free!(ir::IRCode, idx::Int, @nospecialize(stmt), @nospecia elseif nothrow ir.stmts[idx][:flag] |= IR_FLAG_NOTHROW end - if !isexpr(stmt, :call) && !isexpr(stmt, :invoke) + if !(isexpr(stmt, :call) || isexpr(stmt, :invoke)) # There is a bit of a subtle point here, which is that some non-call # statements (e.g. PiNode) can be UB:, however, we consider it # illegal to introduce such statements that actually cause UB (for any From c8ca350832030992fca113ed56d979b6a8ff7fd3 Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sun, 19 Nov 2023 17:11:17 -0500 Subject: [PATCH 26/38] inference: Model type propagation through exceptions (#51754) Currently the type of a caught exception is always modeled as `Any`. This isn't a huge problem, because control flow in Julia is generally assumed to be somewhat slow, so the extra type imprecision of not knowing the return type does not matter all that much. However, there are a few situations where it matters. For example: ``` maybe_getindex(A, i) = try; A[i]; catch e; isa(e, BoundsError) && return nothing; rethrow(); end ``` At present, we cannot infer :nothrow for this method, even if that is the only error type that `A[i]` can throw. This is particularly noticable, since we can now optimize away `:nothrow` exception frames entirely (#51674). Note that this PR still does not make the above example particularly efficient (at least interprocedurally), though specialized codegen could be added on top of this to make that happen. It does however improve the inference result. A second major motivation of this change is that reasoning about exception types is likely to be a major aspect of any future work on interface checking (since interfaces imply the absence of MethodErrors), so this PR lays the groundwork for appropriate modeling of these error paths. Note that this PR adds all the required plumbing, but does not yet have a particularly precise model of error types for our builtins, bailing to `Any` for any builtin not known to be `:nothrow`. This can be improved in follow up PRs as required. --- base/boot.jl | 6 +- base/compiler/abstractinterpretation.jl | 309 ++++++++++++++++-------- base/compiler/effects.jl | 62 +++++ base/compiler/inferencestate.jl | 59 +++-- base/compiler/optimize.jl | 6 +- base/compiler/ssair/ir.jl | 36 ++- base/compiler/ssair/irinterp.jl | 62 +++-- base/compiler/ssair/passes.jl | 20 +- base/compiler/ssair/slot2ssa.jl | 15 +- base/compiler/ssair/verify.jl | 5 +- base/compiler/stmtinfo.jl | 1 + base/compiler/tfuncs.jl | 44 ++-- base/compiler/typeinfer.jl | 26 +- base/compiler/types.jl | 3 +- base/compiler/utilities.jl | 6 +- src/codegen.cpp | 11 +- src/gf.c | 20 +- src/jltypes.c | 12 +- src/julia.h | 1 + src/julia_internal.h | 7 + src/opaque_closure.c | 9 +- stdlib/REPL/src/REPLCompletions.jl | 6 +- test/compiler/AbstractInterpreter.jl | 2 +- test/compiler/inference.jl | 29 ++- test/core.jl | 2 +- test/precompile.jl | 2 +- 26 files changed, 517 insertions(+), 244 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index ff3502c2f418ea..056b14fa5d0ace 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -479,13 +479,13 @@ eval(Core, quote end) function CodeInstance( - mi::MethodInstance, @nospecialize(rettype), @nospecialize(inferred_const), + mi::MethodInstance, @nospecialize(rettype), @nospecialize(exctype), @nospecialize(inferred_const), @nospecialize(inferred), const_flags::Int32, min_world::UInt, max_world::UInt, ipo_effects::UInt32, effects::UInt32, @nospecialize(analysis_results), relocatability::UInt8) return ccall(:jl_new_codeinst, Ref{CodeInstance}, - (Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8), - mi, rettype, inferred_const, inferred, const_flags, min_world, max_world, + (Any, Any, Any, Any, Any, Int32, UInt, UInt, UInt32, UInt32, Any, UInt8), + mi, rettype, exctype, inferred_const, inferred, const_flags, min_world, max_world, ipo_effects, effects, analysis_results, relocatability) end diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index f173f19fe00809..453b55caffa42e 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -18,7 +18,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), # which is all that's required for :consistent-cy. Of course, we don't # know anything else about this statement. effects = Effects(; consistent=ALWAYS_TRUE) - return CallMeta(Any, effects, NoCallInfo()) + return CallMeta(Any, Any, effects, NoCallInfo()) end argtypes = arginfo.argtypes @@ -26,13 +26,13 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), InferenceParams(interp).max_union_splitting, max_methods) if isa(matches, FailedMethodMatch) add_remark!(interp, sv, matches.reason) - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) end (; valid_worlds, applicable, info) = matches update_valid_age!(sv, valid_worlds) napplicable = length(applicable) - rettype = Bottom + rettype = excttype = Bottom edges = MethodInstance[] conditionals = nothing # keeps refinement information of call argument types when the return type is boolean seen = 0 # number of signatures actually inferred @@ -51,6 +51,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), break end this_rt = Bottom + this_exct = Bottom splitunions = false # TODO: this used to trigger a bug in inference recursion detection, and is unmaintained now # sigtuple = unwrap_unionall(sig)::DataType @@ -59,7 +60,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), splitsigs = switchtupleunion(sig) for sig_n in splitsigs result = abstract_call_method(interp, method, sig_n, svec(), multiple_matches, si, sv) - (; rt, edge, effects, volatile_inf_result) = result + (; rt, exct, edge, effects, volatile_inf_result) = result this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] this_arginfo = ArgInfo(fargs, this_argtypes) const_call_result = abstract_call_method_with_const_args(interp, @@ -69,9 +70,17 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), if const_call_result.rt โŠ‘โ‚š rt rt = const_call_result.rt (; effects, const_result, edge) = const_call_result + elseif is_better_effects(const_call_result.effects, effects) + (; effects, const_result, edge) = const_call_result else add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") end + if !(exct โŠ‘โ‚š const_call_result.exct) + exct = const_call_result.exct + (; const_result, edge) = const_call_result + else + add_remark!(interp, sv, "[constprop] Discarded exception type because result was wider than inference") + end end all_effects = merge_effects(all_effects, effects) if const_result !== nothing @@ -82,6 +91,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), end edge === nothing || push!(edges, edge) this_rt = tmerge(this_rt, rt) + this_exct = tmerge(this_exct, exct) if bail_out_call(interp, this_rt, sv) break end @@ -90,9 +100,10 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), this_rt = widenwrappedconditional(this_rt) else result = abstract_call_method(interp, method, sig, match.sparams, multiple_matches, si, sv) - (; rt, edge, effects, volatile_inf_result) = result + (; rt, exct, edge, effects, volatile_inf_result) = result this_conditional = ignorelimited(rt) this_rt = widenwrappedconditional(rt) + this_exct = exct # try constant propagation with argtypes for this match # this is in preparation for inlining, or improving the return result this_argtypes = isa(matches, MethodMatches) ? argtypes : matches.applicable_argtypes[i] @@ -103,15 +114,30 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), if const_call_result !== nothing this_const_conditional = ignorelimited(const_call_result.rt) this_const_rt = widenwrappedconditional(const_call_result.rt) - # return type of const-prop' inference can be wider than that of non const-prop' inference - # e.g. in cases when there are cycles but cached result is still accurate if this_const_rt โŠ‘โ‚š this_rt + # As long as the const-prop result we have is not *worse* than + # what we found out on types, we'd like to use it. Even if the + # end result is exactly equivalent, it is likely that the IR + # we produced while constproping is better than that with + # generic types. + # Return type of const-prop' inference can be wider than that of non const-prop' inference + # e.g. in cases when there are cycles but cached result is still accurate this_conditional = this_const_conditional this_rt = this_const_rt (; effects, const_result, edge) = const_call_result + elseif is_better_effects(const_call_result.effects, effects) + (; effects, const_result, edge) = const_call_result else add_remark!(interp, sv, "[constprop] Discarded because the result was wider than inference") end + # Treat the exception type separately. Currently, constprop often cannot determine the exception type + # because consistent-cy does not apply to exceptions. + if !(this_exct โŠ‘โ‚š const_call_result.exct) + this_exct = const_call_result.exct + (; const_result, edge) = const_call_result + else + add_remark!(interp, sv, "[constprop] Discarded exception type because result was wider than inference") + end end all_effects = merge_effects(all_effects, effects) if const_result !== nothing @@ -125,6 +151,7 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), @assert !(this_conditional isa Conditional || this_rt isa MustAlias) "invalid lattice element returned from inter-procedural context" seen += 1 rettype = tmerge(๐•ƒโ‚š, rettype, this_rt) + excttype = tmerge(๐•ƒโ‚š, excttype, this_exct) if has_conditional(๐•ƒโ‚š, sv) && this_conditional !== Bottom && is_lattice_bool(๐•ƒโ‚š, rettype) && fargs !== nothing if conditionals === nothing conditionals = Any[Bottom for _ in 1:length(argtypes)], @@ -149,12 +176,13 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), if seen โ‰  napplicable # there is unanalyzed candidate, widen type and effects to the top - rettype = Any + rettype = excttype = Any all_effects = Effects() elseif isa(matches, MethodMatches) ? (!matches.fullmatch || any_ambig(matches)) : (!all(matches.fullmatches) || any_ambig(matches)) # Account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. all_effects = Effects(all_effects; nothrow=false) + excttype = tmerge(๐•ƒโ‚š, excttype, MethodError) end rettype = from_interprocedural!(interp, rettype, sv, arginfo, conditionals) @@ -199,7 +227,8 @@ function abstract_call_gf_by_type(interp::AbstractInterpreter, @nospecialize(f), end end end - return CallMeta(rettype, all_effects, info) + + return CallMeta(rettype, excttype, all_effects, info) end struct FailedMethodMatch @@ -490,13 +519,13 @@ function abstract_call_method(interp::AbstractInterpreter, hardlimit::Bool, si::StmtInfo, sv::AbsIntState) if method.name === :depwarn && isdefined(Main, :Base) && method.module === Main.Base add_remark!(interp, sv, "Refusing to infer into `depwarn`") - return MethodCallResult(Any, false, false, nothing, Effects()) + return MethodCallResult(Any, Any, false, false, nothing, Effects()) end sigtuple = unwrap_unionall(sig) sigtuple isa DataType || - return MethodCallResult(Any, false, false, nothing, Effects()) + return MethodCallResult(Any, Any, false, false, nothing, Effects()) all(@nospecialize(x) -> valid_as_lattice(unwrapva(x), true), sigtuple.parameters) || - return MethodCallResult(Union{}, false, false, nothing, EFFECTS_THROWS) # catch bad type intersections early + return MethodCallResult(Union{}, Any, false, false, nothing, EFFECTS_THROWS) # catch bad type intersections early if is_nospecializeinfer(method) sig = get_nospecializeinfer_sig(method, sig, sparams) @@ -521,7 +550,7 @@ function abstract_call_method(interp::AbstractInterpreter, # we have a self-cycle in the call-graph, but not in the inference graph (typically): # break this edge now (before we record it) by returning early # (non-typically, this means that we lose the ability to detect a guaranteed StackOverflow in some cases) - return MethodCallResult(Any, true, true, nothing, Effects()) + return MethodCallResult(Any, Any, true, true, nothing, Effects()) end topmost = nothing edgecycle = true @@ -576,7 +605,7 @@ function abstract_call_method(interp::AbstractInterpreter, # since it's very unlikely that we'll try to inline this, # or want make an invoke edge to its calling convention return type. # (non-typically, this means that we lose the ability to detect a guaranteed StackOverflow in some cases) - return MethodCallResult(Any, true, true, nothing, Effects()) + return MethodCallResult(Any, Any, true, true, nothing, Effects()) end add_remark!(interp, sv, washardlimit ? RECURSION_MSG_HARDLIMIT : RECURSION_MSG) # TODO (#48913) implement a proper recursion handling for irinterp: @@ -622,7 +651,7 @@ function abstract_call_method(interp::AbstractInterpreter, sparams = recomputed[2]::SimpleVector end - (; rt, edge, effects, volatile_inf_result) = typeinf_edge(interp, method, sig, sparams, sv) + (; rt, exct, edge, effects, volatile_inf_result) = typeinf_edge(interp, method, sig, sparams, sv) if edge === nothing edgecycle = edgelimited = true @@ -646,7 +675,7 @@ function abstract_call_method(interp::AbstractInterpreter, end end - return MethodCallResult(rt, edgecycle, edgelimited, edge, effects, volatile_inf_result) + return MethodCallResult(rt, exct, edgecycle, edgelimited, edge, effects, volatile_inf_result) end function edge_matches_sv(interp::AbstractInterpreter, frame::AbsIntState, @@ -745,18 +774,19 @@ end # backedge computation, and concrete evaluation or constant-propagation struct MethodCallResult rt + exct edgecycle::Bool edgelimited::Bool edge::Union{Nothing,MethodInstance} effects::Effects volatile_inf_result::Union{Nothing,VolatileInferenceResult} - function MethodCallResult(@nospecialize(rt), + function MethodCallResult(@nospecialize(rt), @nospecialize(exct), edgecycle::Bool, edgelimited::Bool, edge::Union{Nothing,MethodInstance}, effects::Effects, volatile_inf_result::Union{Nothing,VolatileInferenceResult}=nothing) - return new(rt, edgecycle, edgelimited, edge, effects, volatile_inf_result) + return new(rt, exct, edgecycle, edgelimited, edge, effects, volatile_inf_result) end end @@ -768,15 +798,16 @@ end struct ConstCallResults rt::Any + exct::Any const_result::ConstResult effects::Effects edge::MethodInstance function ConstCallResults( - @nospecialize(rt), + @nospecialize(rt), @nospecialize(exct), const_result::ConstResult, effects::Effects, edge::MethodInstance) - return new(rt, const_result, effects, edge) + return new(rt, exct, const_result, effects, edge) end end @@ -903,11 +934,12 @@ function concrete_eval_call(interp::AbstractInterpreter, edge = result.edge::MethodInstance value = try Core._call_in_world_total(world, f, args...) - catch - # The evaluation threw. By :consistent-cy, we're guaranteed this would have happened at runtime - return ConstCallResults(Bottom, ConcreteResult(edge, result.effects), result.effects, edge) + catch e + # The evaluation threw. By :consistent-cy, we're guaranteed this would have happened at runtime. + # Howevever, at present, :consistency does not mandate the type of the exception + return ConstCallResults(Bottom, Any, ConcreteResult(edge, result.effects), result.effects, edge) end - return ConstCallResults(Const(value), ConcreteResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL, edge) + return ConstCallResults(Const(value), Union{}, ConcreteResult(edge, EFFECTS_TOTAL, value), EFFECTS_TOTAL, edge) end # check if there is a cycle and duplicated inference of `mi` @@ -1170,7 +1202,7 @@ function semi_concrete_eval_call(interp::AbstractInterpreter, if noub effects = Effects(effects; noub = ALWAYS_TRUE) end - return ConstCallResults(rt, SemiConcreteResult(mi, ir, effects), effects, mi) + return ConstCallResults(rt, result.exct, SemiConcreteResult(mi, ir, effects), effects, mi) end end end @@ -1179,7 +1211,7 @@ end function const_prop_call(interp::AbstractInterpreter, mi::MethodInstance, result::MethodCallResult, arginfo::ArgInfo, sv::AbsIntState, - concrete_eval_result::Union{Nothing,ConstCallResults}=nothing) + concrete_eval_result::Union{Nothing, ConstCallResults}=nothing) inf_cache = get_inference_cache(interp) ๐•ƒแตข = typeinf_lattice(interp) inf_result = cache_lookup(๐•ƒแตข, mi, arginfo.argtypes, inf_cache) @@ -1214,7 +1246,8 @@ function const_prop_call(interp::AbstractInterpreter, return nothing end end - return ConstCallResults(inf_result.result, ConstPropResult(inf_result), inf_result.ipo_effects, mi) + return ConstCallResults(inf_result.result, inf_result.exc_result, + ConstPropResult(inf_result), inf_result.ipo_effects, mi) end # TODO implement MustAlias forwarding @@ -1457,7 +1490,7 @@ function abstract_iteration(interp::AbstractInterpreter, @nospecialize(itft), @n # WARNING: Changes to the iteration protocol must be reflected here, # this is not just an optimization. # TODO: this doesn't realize that Array, SimpleVector, Tuple, and NamedTuple do not use the iterate protocol - stateordonet === Bottom && return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(CallMeta[CallMeta(Bottom, call.effects, info)], true)) + stateordonet === Bottom && return AbstractIterationResult(Any[Bottom], AbstractIterationInfo(CallMeta[CallMeta(Bottom, Any, call.effects, info)], true)) valtype = statetype = Bottom ret = Any[] calls = CallMeta[call] @@ -1535,7 +1568,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: sv::AbsIntState, max_methods::Int=get_max_methods(interp, sv)) itft = argtype_by_index(argtypes, 2) aft = argtype_by_index(argtypes, 3) - (itft === Bottom || aft === Bottom) && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + (itft === Bottom || aft === Bottom) && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) aargtypes = argtype_tail(argtypes, 4) aftw = widenconst(aft) if !isa(aft, Const) && !isa(aft, PartialOpaque) && (!isType(aftw) || has_free_typevars(aftw)) @@ -1543,7 +1576,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: add_remark!(interp, sv, "Core._apply_iterate called on a function of a non-concrete type") # bail now, since it seems unlikely that abstract_call will be able to do any better after splitting # this also ensures we don't call abstract_call_gf_by_type below on an IntrinsicFunction or Builtin - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) end end res = Union{} @@ -1602,6 +1635,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: retinfo = UnionSplitApplyCallInfo(retinfos) napplicable = length(ctypes) seen = 0 + exct = effects.nothrow ? Union{} : Any for i = 1:napplicable ct = ctypes[i] arginfo = infos[i] @@ -1619,6 +1653,7 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: seen += 1 push!(retinfos, ApplyCallInfo(call.info, arginfo)) res = tmerge(typeinf_lattice(interp), res, call.rt) + exct = tmerge(typeinf_lattice(interp), exct, call.exct) effects = merge_effects(effects, call.effects) if bail_out_apply(interp, InferenceLoopState(ct, res, effects), sv) add_remark!(interp, sv, "_apply_iterate inference reached maximally imprecise information. Bailing on.") @@ -1628,12 +1663,13 @@ function abstract_apply(interp::AbstractInterpreter, argtypes::Vector{Any}, si:: if seen โ‰  napplicable # there is unanalyzed candidate, widen type and effects to the top res = Any + exct = Any effects = Effects() retinfo = NoCallInfo() # NOTE this is necessary to prevent the inlining processing end # TODO: Add a special info type to capture all the iteration info. # For now, only propagate info if we don't also union-split the iteration - return CallMeta(res, effects, retinfo) + return CallMeta(res, exct, effects, retinfo) end function argtype_by_index(argtypes::Vector{Any}, i::Int) @@ -1882,9 +1918,9 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An na = length(argtypes) if isvarargtype(argtypes[end]) if na โ‰ค 2 - return CallMeta(Any, EFFECTS_THROWS, call.info) + return CallMeta(Any, Any, EFFECTS_THROWS, call.info) elseif na > 4 - return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) end a2 = argtypes[2] a3 = unwrapva(argtypes[3]) @@ -1895,7 +1931,7 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An โŠ‘แตข = โŠ‘(typeinf_lattice(interp)) nothrow = a2 โŠ‘แตข TypeVar && (a3 โŠ‘แตข Type || a3 โŠ‘แตข TypeVar) else - return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) end canconst = true if isa(a3, Const) @@ -1904,10 +1940,10 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An body = a3.parameters[1] canconst = false else - return CallMeta(Any, Effects(EFFECTS_TOTAL; nothrow), call.info) + return CallMeta(Any, Any, Effects(EFFECTS_TOTAL; nothrow), call.info) end if !(isa(body, Type) || isa(body, TypeVar)) - return CallMeta(Any, EFFECTS_THROWS, call.info) + return CallMeta(Any, Any, EFFECTS_THROWS, call.info) end if has_free_typevars(body) if isa(a2, Const) @@ -1916,36 +1952,36 @@ function abstract_call_unionall(interp::AbstractInterpreter, argtypes::Vector{An tv = a2.tv canconst = false else - return CallMeta(Any, EFFECTS_THROWS, call.info) + return CallMeta(Any, Any, EFFECTS_THROWS, call.info) end - isa(tv, TypeVar) || return CallMeta(Any, EFFECTS_THROWS, call.info) + isa(tv, TypeVar) || return CallMeta(Any, Any, EFFECTS_THROWS, call.info) body = UnionAll(tv, body) end ret = canconst ? Const(body) : Type{body} - return CallMeta(ret, Effects(EFFECTS_TOTAL; nothrow), call.info) + return CallMeta(ret, Any, Effects(EFFECTS_TOTAL; nothrow), call.info) end function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgInfo, si::StmtInfo, sv::AbsIntState) ftโ€ฒ = argtype_by_index(argtypes, 2) ft = widenconst(ftโ€ฒ) - ft === Bottom && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + ft === Bottom && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) (types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, 3), false) - isexact || return CallMeta(Any, Effects(), NoCallInfo()) + isexact || return CallMeta(Any, Any, Effects(), NoCallInfo()) unwrapped = unwrap_unionall(types) if types === Bottom || !(unwrapped isa DataType) || unwrapped.name !== Tuple.name - return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) end argtype = argtypes_to_type(argtype_tail(argtypes, 4)) nargtype = typeintersect(types, argtype) - nargtype === Bottom && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) - nargtype isa DataType || return CallMeta(Any, Effects(), NoCallInfo()) # other cases are not implemented below - isdispatchelem(ft) || return CallMeta(Any, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below + nargtype === Bottom && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) + nargtype isa DataType || return CallMeta(Any, Any, Effects(), NoCallInfo()) # other cases are not implemented below + isdispatchelem(ft) || return CallMeta(Any, Any, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below ft = ft::DataType lookupsig = rewrap_unionall(Tuple{ft, unwrapped.parameters...}, types)::Type nargtype = Tuple{ft, nargtype.parameters...} argtype = Tuple{ft, argtype.parameters...} match, valid_worlds = findsup(lookupsig, method_table(interp)) - match === nothing && return CallMeta(Any, Effects(), NoCallInfo()) + match === nothing && return CallMeta(Any, Any, Effects(), NoCallInfo()) update_valid_age!(sv, valid_worlds) method = match.method tienv = ccall(:jl_type_intersection_with_env, Any, (Any, Any), nargtype, method.sig)::SimpleVector @@ -1977,7 +2013,7 @@ function abstract_invoke(interp::AbstractInterpreter, (; fargs, argtypes)::ArgIn rt = from_interprocedural!(interp, rt, sv, arginfo, sig) info = InvokeCallInfo(match, const_result) edge !== nothing && add_invoke_backedge!(sv, lookupsig, edge) - return CallMeta(rt, effects, info) + return CallMeta(rt, Any, effects, info) end function invoke_rewrite(xs::Vector{Any}) @@ -1991,9 +2027,9 @@ function abstract_finalizer(interp::AbstractInterpreter, argtypes::Vector{Any}, if length(argtypes) == 3 finalizer_argvec = Any[argtypes[2], argtypes[3]] call = abstract_call(interp, ArgInfo(nothing, finalizer_argvec), StmtInfo(false), sv, #=max_methods=#1) - return CallMeta(Nothing, Effects(), FinalizerInfo(call.info, call.effects)) + return CallMeta(Nothing, Any, Effects(), FinalizerInfo(call.info, call.effects)) end - return CallMeta(Nothing, Effects(), NoCallInfo()) + return CallMeta(Nothing, Any, Effects(), NoCallInfo()) end # call where the function is known exactly @@ -2015,19 +2051,33 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), return abstract_finalizer(interp, argtypes, sv) elseif f === applicable return abstract_applicable(interp, argtypes, sv, max_methods) + elseif f === throw + if la == 2 + arg2 = argtypes[2] + if isvarargtype(arg2) + exct = tmerge(๐•ƒแตข, unwrapva(argtypes[2]), ArgumentError) + else + exct = arg2 + end + elseif la == 3 && isvarargtype(argtypes[3]) + exct = tmerge(๐•ƒแตข, argtypes[2], ArgumentError) + else + exct = ArgumentError + end + return CallMeta(Union{}, exct, EFFECTS_THROWS, NoCallInfo()) end rt = abstract_call_builtin(interp, f, arginfo, sv) ft = popfirst!(argtypes) effects = builtin_effects(๐•ƒแตข, f, argtypes, rt) pushfirst!(argtypes, ft) - return CallMeta(rt, effects, NoCallInfo()) + return CallMeta(rt, effects.nothrow ? Union{} : Any, effects, NoCallInfo()) elseif isa(f, Core.OpaqueClosure) # calling an OpaqueClosure about which we have no information returns no information - return CallMeta(typeof(f).parameters[2], Effects(), NoCallInfo()) + return CallMeta(typeof(f).parameters[2], Any, Effects(), NoCallInfo()) elseif f === TypeVar # Manually look through the definition of TypeVar to # make sure to be able to get `PartialTypeVar`s out. - (la < 2 || la > 4) && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + (la < 2 || la > 4) && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) n = argtypes[2] ub_var = Const(Any) lb_var = Const(Union{}) @@ -2046,7 +2096,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), end pT = typevar_tfunc(๐•ƒแตข, n, lb_var, ub_var) effects = builtin_effects(๐•ƒแตข, Core._typevar, Any[n, lb_var, ub_var], pT) - return CallMeta(pT, effects, call.info) + return CallMeta(pT, Any, effects, call.info) elseif f === UnionAll call = abstract_call_gf_by_type(interp, f, ArgInfo(nothing, Any[Const(UnionAll), Any, Any]), si, Tuple{Type{UnionAll}, Any, Any}, sv, max_methods) return abstract_call_unionall(interp, argtypes, call) @@ -2054,7 +2104,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), aty = argtypes[2] ty = isvarargtype(aty) ? unwrapva(aty) : widenconst(aty) if !isconcretetype(ty) - return CallMeta(Tuple, EFFECTS_UNKNOWN, NoCallInfo()) + return CallMeta(Tuple, Any, EFFECTS_UNKNOWN, NoCallInfo()) end elseif is_return_type(f) return return_type_tfunc(interp, argtypes, si, sv) @@ -2063,16 +2113,16 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), aty = argtypes[2] if isa(aty, Conditional) call = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Bool]), si, Tuple{typeof(f), Bool}, sv, max_methods) # make sure we've inferred `!(::Bool)` - return CallMeta(Conditional(aty.slot, aty.elsetype, aty.thentype), call.effects, call.info) + return CallMeta(Conditional(aty.slot, aty.elsetype, aty.thentype), Any, call.effects, call.info) end elseif la == 3 && istopfunction(f, :!==) # mark !== as exactly a negated call to === call = abstract_call_gf_by_type(interp, f, ArgInfo(fargs, Any[Const(f), Any, Any]), si, Tuple{typeof(f), Any, Any}, sv, max_methods) rty = abstract_call_known(interp, (===), arginfo, si, sv, max_methods).rt if isa(rty, Conditional) - return CallMeta(Conditional(rty.slot, rty.elsetype, rty.thentype), EFFECTS_TOTAL, NoCallInfo()) # swap if-else + return CallMeta(Conditional(rty.slot, rty.elsetype, rty.thentype), Bottom, EFFECTS_TOTAL, NoCallInfo()) # swap if-else elseif isa(rty, Const) - return CallMeta(Const(rty.val === false), EFFECTS_TOTAL, MethodResultPure()) + return CallMeta(Const(rty.val === false), Bottom, EFFECTS_TOTAL, MethodResultPure()) end return call elseif la == 3 && istopfunction(f, :(>:)) @@ -2086,7 +2136,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), argtypes = Any[typeof(<:), argtypes[3], argtypes[2]] return abstract_call_known(interp, <:, ArgInfo(fargs, argtypes), si, sv, max_methods) elseif la == 2 && istopfunction(f, :typename) - return CallMeta(typename_static(argtypes[2]), EFFECTS_TOTAL, MethodResultPure()) + return CallMeta(typename_static(argtypes[2]), Any, EFFECTS_TOTAL, MethodResultPure()) elseif f === Core._hasmethod return _hasmethod_tfunc(interp, argtypes, sv) end @@ -2125,7 +2175,7 @@ function abstract_call_opaque_closure(interp::AbstractInterpreter, rt = from_interprocedural!(interp, rt, sv, arginfo, match.spec_types) info = OpaqueClosureCallInfo(match, const_result) edge !== nothing && add_backedge!(sv, edge) - return CallMeta(rt, effects, info) + return CallMeta(rt, Any, effects, info) end function most_general_argtypes(closure::PartialOpaque) @@ -2150,13 +2200,13 @@ function abstract_call_unknown(interp::AbstractInterpreter, @nospecialize(ft), wft = widenconst(ft) if hasintersect(wft, Builtin) add_remark!(interp, sv, "Could not identify method table for call") - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) elseif hasintersect(wft, Core.OpaqueClosure) uft = unwrap_unionall(wft) if isa(uft, DataType) - return CallMeta(rewrap_unionall(uft.parameters[2], wft), Effects(), NoCallInfo()) + return CallMeta(rewrap_unionall(uft.parameters[2], wft), Any, Effects(), NoCallInfo()) end - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) end # non-constant function, but the number of arguments is known and the `f` is not a builtin or intrinsic atype = argtypes_to_type(arginfo.argtypes) @@ -2237,31 +2287,30 @@ end function abstract_eval_special_value(interp::AbstractInterpreter, @nospecialize(e), vtypes::Union{VarTable,Nothing}, sv::AbsIntState) if isa(e, QuoteNode) - return RTEffects(Const(e.value), EFFECTS_TOTAL) + return RTEffects(Const(e.value), Union{}, EFFECTS_TOTAL) elseif isa(e, SSAValue) - return RTEffects(abstract_eval_ssavalue(e, sv), EFFECTS_TOTAL) + return RTEffects(abstract_eval_ssavalue(e, sv), Union{}, EFFECTS_TOTAL) elseif isa(e, SlotNumber) - effects = EFFECTS_THROWS if vtypes !== nothing vtyp = vtypes[slot_id(e)] if !vtyp.undef - effects = EFFECTS_TOTAL + return RTEffects(vtyp.typ, Union{}, EFFECTS_TOTAL) end - return RTEffects(vtyp.typ, effects) + return RTEffects(vtyp.typ, UndefVarError, EFFECTS_THROWS) end - return RTEffects(Any, effects) + return RTEffects(Any, UndefVarError, EFFECTS_THROWS) elseif isa(e, Argument) if vtypes !== nothing - return RTEffects(vtypes[slot_id(e)].typ, EFFECTS_TOTAL) + return RTEffects(vtypes[slot_id(e)].typ, Union{}, EFFECTS_TOTAL) else @assert isa(sv, IRInterpretationState) - return RTEffects(sv.ir.argtypes[e.n], EFFECTS_TOTAL) # TODO frame_argtypes(sv)[e.n] and remove the assertion + return RTEffects(sv.ir.argtypes[e.n], Union{}, EFFECTS_TOTAL) # TODO frame_argtypes(sv)[e.n] and remove the assertion end elseif isa(e, GlobalRef) return abstract_eval_globalref(interp, e, sv) end - return RTEffects(Const(e), EFFECTS_TOTAL) + return RTEffects(Const(e), Union{}, EFFECTS_TOTAL) end function abstract_eval_value_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) @@ -2316,17 +2365,18 @@ end struct RTEffects rt + exct effects::Effects - RTEffects(@nospecialize(rt), effects::Effects) = new(rt, effects) + RTEffects(@nospecialize(rt), @nospecialize(exct), effects::Effects) = new(rt, exct, effects) end function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, sv::InferenceState) si = StmtInfo(!call_result_unused(sv, sv.currpc)) - (; rt, effects, info) = abstract_call(interp, arginfo, si, sv) + (; rt, exct, effects, info) = abstract_call(interp, arginfo, si, sv) sv.stmt_info[sv.currpc] = info # mark this call statement as DCE-eligible # TODO better to do this in a single pass based on the `info` object at the end of abstractinterpret? - return RTEffects(rt, effects) + return RTEffects(rt, exct, effects) end function abstract_eval_call(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, @@ -2334,24 +2384,31 @@ function abstract_eval_call(interp::AbstractInterpreter, e::Expr, vtypes::Union{ ea = e.args argtypes = collect_argtypes(interp, ea, vtypes, sv) if argtypes === nothing - return RTEffects(Bottom, Effects()) + return RTEffects(Bottom, Any, Effects()) end arginfo = ArgInfo(ea, argtypes) return abstract_call(interp, arginfo, sv) end +function abstract_eval_the_exception(interp::AbstractInterpreter, sv::InferenceState) + return sv.handlers[sv.handler_at[sv.currpc][2]].exct +end +abstract_eval_the_exception(::AbstractInterpreter, ::IRInterpretationState) = Any + function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) effects = Effects() ehead = e.head ๐•ƒแตข = typeinf_lattice(interp) โŠ‘แตข = โŠ‘(๐•ƒแตข) + exct = Any if ehead === :call - (; rt, effects) = abstract_eval_call(interp, e, vtypes, sv) + (; rt, exct, effects) = abstract_eval_call(interp, e, vtypes, sv) t = rt elseif ehead === :new t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv), true) ut = unwrap_unionall(t) + exct = Union{ErrorException, TypeError} if isa(ut, DataType) && !isabstracttype(ut) ismutable = ismutabletype(ut) fcount = datatype_fieldcount(ut) @@ -2416,6 +2473,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp nothrow = false end effects = Effects(EFFECTS_TOTAL; consistent, nothrow) + nothrow && (exct = Union{}) elseif ehead === :splatnew t, isexact = instanceof_tfunc(abstract_eval_value(interp, e.args[1], vtypes, sv), true) nothrow = false # TODO: More precision @@ -2465,7 +2523,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp end end elseif ehead === :foreigncall - (; rt, effects) = abstract_eval_foreigncall(interp, e, vtypes, sv) + (; rt, exct, effects) = abstract_eval_foreigncall(interp, e, vtypes, sv) t = rt elseif ehead === :cfunction effects = EFFECTS_UNKNOWN @@ -2488,6 +2546,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp sym = e.args[1] t = Bool effects = EFFECTS_TOTAL + exct = Union{} if isa(sym, SlotNumber) && vtypes !== nothing vtyp = vtypes[slot_id(sym)] if vtyp.typ === Bottom @@ -2528,10 +2587,12 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp condt = argextype(stmt.args[2], ir) condval = maybe_extract_const_bool(condt) t = Nothing + exct = UndefVarError effects = EFFECTS_THROWS if condval isa Bool if condval effects = EFFECTS_TOTAL + exct = Union{} else t = Union{} end @@ -2540,13 +2601,16 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp end elseif ehead === :boundscheck t = Bool + exct = Union{} effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE) elseif ehead === :the_exception - t = Any + t = abstract_eval_the_exception(interp, sv) + exct = Union{} effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE) elseif ehead === :static_parameter n = e.args[1]::Int nothrow = false + exct = UndefVarError if 1 <= n <= length(sv.sptypes) sp = sv.sptypes[n] t = sp.typ @@ -2554,15 +2618,21 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp else t = Any end + if nothrow + exct = Union{} + end effects = Effects(EFFECTS_TOTAL; nothrow) elseif ehead === :gc_preserve_begin || ehead === :aliasscope t = Any + exct = Union{} effects = Effects(EFFECTS_TOTAL; consistent=ALWAYS_FALSE, effect_free=EFFECT_FREE_GLOBALLY) elseif ehead === :gc_preserve_end || ehead === :leave || ehead === :pop_exception || ehead === :global || ehead === :popaliasscope t = Nothing + exct = Union{} effects = Effects(EFFECTS_TOTAL; effect_free=EFFECT_FREE_GLOBALLY) elseif ehead === :method t = Method + exct = Union{} effects = Effects(EFFECTS_TOTAL; effect_free=EFFECT_FREE_GLOBALLY) elseif ehead === :thunk t = Any @@ -2578,7 +2648,7 @@ function abstract_eval_statement_expr(interp::AbstractInterpreter, e::Expr, vtyp # and recompute the effects. effects = EFFECTS_TOTAL end - return RTEffects(t, effects) + return RTEffects(t, exct, effects) end # refine the result of instantiation of partially-known type `t` if some invariant can be assumed @@ -2599,7 +2669,7 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes: t = sp_type_rewrap(e.args[2], mi, true) for i = 3:length(e.args) if abstract_eval_value(interp, e.args[i], vtypes, sv) === Bottom - return RTEffects(Bottom, EFFECTS_THROWS) + return RTEffects(Bottom, Any, EFFECTS_THROWS) end end effects = foreigncall_effects(e) do @nospecialize x @@ -2617,7 +2687,7 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, vtypes: inaccessiblememonly = override.inaccessiblememonly ? ALWAYS_TRUE : effects.inaccessiblememonly, noub = override.noub ? ALWAYS_TRUE : override.noub_if_noinbounds ? NOUB_IF_NOINBOUNDS : effects.noub) end - return RTEffects(t, effects) + return RTEffects(t, Any, effects) end function abstract_eval_phi(interp::AbstractInterpreter, phi::PhiNode, vtypes::Union{VarTable,Nothing}, sv::AbsIntState) @@ -2641,11 +2711,11 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), if !isa(e, Expr) if isa(e, PhiNode) add_curr_ssaflag!(sv, IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW) - return abstract_eval_phi(interp, e, vtypes, sv) + return RTEffects(abstract_eval_phi(interp, e, vtypes, sv), Union{}, EFFECTS_TOTAL) end - (; rt, effects) = abstract_eval_special_value(interp, e, vtypes, sv) + (; rt, exct, effects) = abstract_eval_special_value(interp, e, vtypes, sv) else - (; rt, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv) + (; rt, exct, effects) = abstract_eval_statement_expr(interp, e, vtypes, sv) if effects.noub === NOUB_IF_NOINBOUNDS if !iszero(get_curr_ssaflag(sv) & IR_FLAG_INBOUNDS) effects = Effects(effects; noub=ALWAYS_FALSE) @@ -2673,7 +2743,7 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), set_curr_ssaflag!(sv, flags_for_effects(effects), IR_FLAGS_EFFECTS) merge_effects!(interp, sv, effects) - return rt + return RTEffects(rt, exct, effects) end function isdefined_globalref(g::GlobalRef) @@ -2706,7 +2776,7 @@ function abstract_eval_globalref(interp::AbstractInterpreter, g::GlobalRef, sv:: consistent = inaccessiblememonly = ALWAYS_TRUE rt = Union{} end - return RTEffects(rt, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly)) + return RTEffects(rt, nothrow ? Union{} : UndefVarError, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly)) end function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, lhs::GlobalRef, @nospecialize(newty)) @@ -2900,50 +2970,51 @@ end struct BasicStmtChange changes::Union{Nothing,StateUpdate} - type::Any # ::Union{Type, Nothing} - `nothing` if this statement may not be used as an SSA Value + rt::Any # extended lattice element or `nothing` - `nothing` if this statement may not be used as an SSA Value + exct::Any # TODO effects::Effects - BasicStmtChange(changes::Union{Nothing,StateUpdate}, @nospecialize type) = new(changes, type) + BasicStmtChange(changes::Union{Nothing,StateUpdate}, @nospecialize(rt), @nospecialize(exct)) = new(changes, rt, exct) end @inline function abstract_eval_basic_statement(interp::AbstractInterpreter, @nospecialize(stmt), pc_vartable::VarTable, frame::InferenceState) if isa(stmt, NewvarNode) changes = StateUpdate(stmt.slot, VarState(Bottom, true), pc_vartable, false) - return BasicStmtChange(changes, nothing) + return BasicStmtChange(changes, nothing, Union{}) elseif !isa(stmt, Expr) - t = abstract_eval_statement(interp, stmt, pc_vartable, frame) - return BasicStmtChange(nothing, t) + (; rt, exct) = abstract_eval_statement(interp, stmt, pc_vartable, frame) + return BasicStmtChange(nothing, rt, exct) end changes = nothing stmt = stmt::Expr hd = stmt.head if hd === :(=) - t = abstract_eval_statement(interp, stmt.args[2], pc_vartable, frame) - if t === Bottom - return BasicStmtChange(nothing, Bottom) + (; rt, exct) = abstract_eval_statement(interp, stmt.args[2], pc_vartable, frame) + if rt === Bottom + return BasicStmtChange(nothing, Bottom, exct) end lhs = stmt.args[1] if isa(lhs, SlotNumber) - changes = StateUpdate(lhs, VarState(t, false), pc_vartable, false) + changes = StateUpdate(lhs, VarState(rt, false), pc_vartable, false) elseif isa(lhs, GlobalRef) - handle_global_assignment!(interp, frame, lhs, t) + handle_global_assignment!(interp, frame, lhs, rt) elseif !isa(lhs, SSAValue) merge_effects!(interp, frame, EFFECTS_UNKNOWN) end - return BasicStmtChange(changes, t) + return BasicStmtChange(changes, rt, exct) elseif hd === :method fname = stmt.args[1] if isa(fname, SlotNumber) changes = StateUpdate(fname, VarState(Any, false), pc_vartable, false) end - return BasicStmtChange(changes, nothing) + return BasicStmtChange(changes, nothing, Union{}) elseif (hd === :code_coverage_effect || ( hd !== :boundscheck && # :boundscheck can be narrowed to Bool is_meta_expr(stmt))) - return BasicStmtChange(nothing, Nothing) + return BasicStmtChange(nothing, Nothing, Bottom) else - t = abstract_eval_statement(interp, stmt, pc_vartable, frame) - return BasicStmtChange(nothing, t) + (; rt, exct) = abstract_eval_statement(interp, stmt, pc_vartable, frame) + return BasicStmtChange(nothing, rt, exct) end end @@ -3004,9 +3075,9 @@ end function propagate_to_error_handler!(frame::InferenceState, currpc::Int, W::BitSet, ๐•ƒแตข::AbstractLattice, currstate::VarTable) # If this statement potentially threw, propagate the currstate to the # exception handler, BEFORE applying any state changes. - cur_hand = frame.handler_at[currpc] + cur_hand = frame.handler_at[currpc][1] if cur_hand != 0 - enter = frame.src.code[cur_hand]::Expr + enter = frame.src.code[frame.handlers[cur_hand].enter_idx]::Expr l = enter.args[1]::Int exceptbb = block_for_inst(frame.cfg, l) if update_bbstate!(๐•ƒแตข, frame, exceptbb, currstate) @@ -3152,23 +3223,47 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) # Fall through terminator - treat as regular stmt end # Process non control-flow statements - (; changes, type) = abstract_eval_basic_statement(interp, + (; changes, rt, exct) = abstract_eval_basic_statement(interp, stmt, currstate, frame) + if exct !== Union{} + ๐•ƒโ‚š = ipo_lattice(interp) + cur_hand = frame.handler_at[currpc][1] + if cur_hand == 0 + if !โŠ‘(๐•ƒโ‚š, exct, frame.exc_bestguess) + frame.exc_bestguess = tmerge(๐•ƒโ‚š, frame.exc_bestguess, exct) + for (caller, caller_pc) in frame.cycle_backedges + handler = caller.handler_at[caller_pc][1] + if (handler == 0 ? caller.exc_bestguess : caller.handlers[handler].exct) !== Any + push!(caller.ip, block_for_inst(caller.cfg, caller_pc)) + end + end + end + else + handler_frame = frame.handlers[cur_hand] + if !โŠ‘(๐•ƒโ‚š, exct, handler_frame.exct) + handler_frame.exct = tmerge(๐•ƒโ‚š, handler_frame.exct, exct) + enter = frame.src.code[handler_frame.enter_idx]::Expr + l = enter.args[1]::Int + exceptbb = block_for_inst(frame.cfg, l) + push!(W, exceptbb) + end + end + end if (get_curr_ssaflag(frame) & IR_FLAG_NOTHROW) != IR_FLAG_NOTHROW propagate_to_error_handler!(frame, currpc, W, ๐•ƒแตข, currstate) end - if type === Bottom + if rt === Bottom ssavaluetypes[currpc] = Bottom @goto find_next_bb end if changes !== nothing stoverwrite1!(currstate, changes) end - if type === nothing + if rt === nothing ssavaluetypes[currpc] = Any continue end - record_ssa_assign!(๐•ƒแตข, currpc, type, frame) + record_ssa_assign!(๐•ƒแตข, currpc, rt, frame) end # for currpc in bbstart:bbend # Case 1: Fallthrough termination diff --git a/base/compiler/effects.jl b/base/compiler/effects.jl index fc774efbbee85d..d4c972ab89e951 100644 --- a/base/compiler/effects.jl +++ b/base/compiler/effects.jl @@ -171,6 +171,68 @@ function Effects(effects::Effects = _EFFECTS_UNKNOWN; nonoverlayed) end +function is_better_effects(new::Effects, old::Effects) + any_improved = false + if new.consistent == ALWAYS_TRUE + any_improved |= old.consistent != ALWAYS_TRUE + else + if !iszero(new.consistent & CONSISTENT_IF_NOTRETURNED) + old.consistent == ALWAYS_TRUE && return false + any_improved |= iszero(old.consistent & CONSISTENT_IF_NOTRETURNED) + elseif !iszero(new.consistent & CONSISTENT_IF_INACCESSIBLEMEMONLY) + old.consistent == ALWAYS_TRUE && return false + any_improved |= iszero(old.consistent & CONSISTENT_IF_INACCESSIBLEMEMONLY) + else + return false + end + end + if new.effect_free == ALWAYS_TRUE + any_improved |= old.consistent != ALWAYS_TRUE + elseif new.effect_free == EFFECT_FREE_IF_INACCESSIBLEMEMONLY + old.effect_free == ALWAYS_TRUE && return false + any_improved |= old.effect_free != EFFECT_FREE_IF_INACCESSIBLEMEMONLY + elseif new.effect_free != old.effect_free + return false + end + if new.nothrow + any_improved |= !old.nothrow + elseif new.nothrow != old.nothrow + return false + end + if new.terminates + any_improved |= !old.terminates + elseif new.terminates != old.terminates + return false + end + if new.notaskstate + any_improved |= !old.notaskstate + elseif new.notaskstate != old.notaskstate + return false + end + if new.inaccessiblememonly == ALWAYS_TRUE + any_improved |= old.inaccessiblememonly != ALWAYS_TRUE + elseif new.inaccessiblememonly == INACCESSIBLEMEM_OR_ARGMEMONLY + old.inaccessiblememonly == ALWAYS_TRUE && return false + any_improved |= old.inaccessiblememonly != INACCESSIBLEMEM_OR_ARGMEMONLY + elseif new.inaccessiblememonly != old.inaccessiblememonly + return false + end + if new.noub == ALWAYS_TRUE + any_improved |= old.noub != ALWAYS_TRUE + elseif new.noub == NOUB_IF_NOINBOUNDS + old.noub == ALWAYS_TRUE && return false + any_improved |= old.noub != NOUB_IF_NOINBOUNDS + elseif new.noub != old.noub + return false + end + if new.nonoverlayed + any_improved |= !old.nonoverlayed + elseif new.nonoverlayed != old.nonoverlayed + return false + end + return any_improved +end + function merge_effects(old::Effects, new::Effects) return Effects( merge_effectbits(old.consistent, new.consistent), diff --git a/base/compiler/inferencestate.jl b/base/compiler/inferencestate.jl index 4024273944200c..772dbe72d4dc1d 100644 --- a/base/compiler/inferencestate.jl +++ b/base/compiler/inferencestate.jl @@ -203,6 +203,12 @@ const CACHE_MODE_GLOBAL = 0x01 << 0 # cached globally, optimization allowed const CACHE_MODE_LOCAL = 0x01 << 1 # cached locally, optimization allowed const CACHE_MODE_VOLATILE = 0x01 << 2 # not cached, optimization allowed +mutable struct TryCatchFrame + exct + const enter_idx::Int + TryCatchFrame(@nospecialize(exct), enter_idx::Int) = new(exct, enter_idx) +end + mutable struct InferenceState #= information about this method instance =# linfo::MethodInstance @@ -218,7 +224,8 @@ mutable struct InferenceState currbb::Int currpc::Int ip::BitSet#=TODO BoundedMinPrioritySet=# # current active instruction pointers - handler_at::Vector{Int} # current exception handler info + handlers::Vector{TryCatchFrame} + handler_at::Vector{Tuple{Int, Int}} # tuple of current (handler, exception stack) value at the pc ssavalue_uses::Vector{BitSet} # ssavalue sparsity and restart info # TODO: Could keep this sparsely by doing structural liveness analysis ahead of time. bb_vartables::Vector{Union{Nothing,VarTable}} # nothing if not analyzed yet @@ -239,6 +246,7 @@ mutable struct InferenceState unreachable::BitSet # statements that were found to be statically unreachable valid_worlds::WorldRange bestguess #::Type + exc_bestguess ipo_effects::Effects #= flags =# @@ -266,7 +274,7 @@ mutable struct InferenceState currbb = currpc = 1 ip = BitSet(1) # TODO BitSetBoundedMinPrioritySet(1) - handler_at = compute_trycatch(code, BitSet()) + handler_at, handlers = compute_trycatch(code, BitSet()) nssavalues = src.ssavaluetypes::Int ssavalue_uses = find_ssavalue_uses(code, nssavalues) nstmts = length(code) @@ -296,6 +304,7 @@ mutable struct InferenceState valid_worlds = WorldRange(src.min_world, src.max_world == typemax(UInt) ? get_world_counter() : src.max_world) bestguess = Bottom + exc_bestguess = Bottom ipo_effects = EFFECTS_TOTAL insert_coverage = should_insert_coverage(mod, src) @@ -315,9 +324,9 @@ mutable struct InferenceState return new( linfo, world, mod, sptypes, slottypes, src, cfg, method_info, - currbb, currpc, ip, handler_at, ssavalue_uses, bb_vartables, ssavaluetypes, stmt_edges, stmt_info, + currbb, currpc, ip, handlers, handler_at, ssavalue_uses, bb_vartables, ssavaluetypes, stmt_edges, stmt_info, pclimitations, limitations, cycle_backedges, callers_in_cycle, dont_work_on_me, parent, - result, unreachable, valid_worlds, bestguess, ipo_effects, + result, unreachable, valid_worlds, bestguess, exc_bestguess, ipo_effects, restrict_abstract_call_sites, cache_mode, insert_coverage, interp) end @@ -347,16 +356,19 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) empty!(ip) ip.offset = 0 # for _bits_findnext push!(ip, n + 1) - handler_at = fill(0, n) + handler_at = fill((0, 0), n) + handlers = TryCatchFrame[] # start from all :enter statements and record the location of the try for pc = 1:n stmt = code[pc] if isexpr(stmt, :enter) l = stmt.args[1]::Int - handler_at[pc + 1] = pc + push!(handlers, TryCatchFrame(Bottom, pc)) + handler_id = length(handlers) + handler_at[pc + 1] = (handler_id, 0) push!(ip, pc + 1) - handler_at[l] = pc + handler_at[l] = (handler_id, handler_id) push!(ip, l) end end @@ -369,25 +381,26 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) while true # inner loop optimizes the common case where it can run straight from pc to pc + 1 pcยด = pc + 1 # next program-counter (after executing instruction) delete!(ip, pc) - cur_hand = handler_at[pc] - @assert cur_hand != 0 "unbalanced try/catch" + cur_stacks = handler_at[pc] + @assert cur_stacks != (0, 0) "unbalanced try/catch" stmt = code[pc] if isa(stmt, GotoNode) pcยด = stmt.label elseif isa(stmt, GotoIfNot) l = stmt.dest::Int - if handler_at[l] != cur_hand - @assert handler_at[l] == 0 "unbalanced try/catch" - handler_at[l] = cur_hand + if handler_at[l] != cur_stacks + @assert handler_at[l][1] == 0 || handler_at[l][1] == cur_stacks[1] "unbalanced try/catch" + handler_at[l] = cur_stacks push!(ip, l) end elseif isa(stmt, ReturnNode) - @assert !isdefined(stmt, :val) "unbalanced try/catch" + @assert !isdefined(stmt, :val) || cur_stacks[1] == 0 "unbalanced try/catch" break elseif isa(stmt, Expr) head = stmt.head if head === :enter - cur_hand = pc + # Already set above + cur_stacks = (handler_at[pcยด][1], cur_stacks[2]) elseif head === :leave l = 0 for j = 1:length(stmt.args) @@ -403,19 +416,21 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) end l += 1 end + cur_hand = cur_stacks[1] for i = 1:l - cur_hand = handler_at[cur_hand] + cur_hand = handler_at[handlers[cur_hand].enter_idx][1] end - cur_hand == 0 && break + cur_stacks = (cur_hand, cur_stacks[2]) + cur_stacks == (0, 0) && break + elseif head === :pop_exception + cur_stacks = (cur_stacks[1], handler_at[(stmt.args[1]::SSAValue).id][2]) + cur_stacks == (0, 0) && break end end pcยด > n && break # can't proceed with the fast-path fall-through - if handler_at[pcยด] != cur_hand - if handler_at[pcยด] != 0 - @assert false "unbalanced try/catch" - end - handler_at[pcยด] = cur_hand + if handler_at[pcยด] != cur_stacks + handler_at[pcยด] = cur_stacks elseif !in(pcยด, ip) break # already visited end @@ -424,7 +439,7 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet) end @assert first(ip) == n + 1 - return handler_at + return handler_at, handlers end # check if coverage mode is enabled diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index 68118f82fb5f22..43bdebed32b035 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -925,8 +925,10 @@ function run_passes_ipo_safe( # @timeit "verify 2" verify_ir(ir) @pass "compact 2" ir = compact!(ir) @pass "SROA" ir = sroa_pass!(ir, sv.inlining) - @pass "ADCE" ir = adce_pass!(ir, sv.inlining) - @pass "compact 3" ir = compact!(ir, true) + @pass "ADCE" (ir, made_changes) = adce_pass!(ir, sv.inlining) + if made_changes + @pass "compact 3" ir = compact!(ir, true) + end if JLOptions().debug_level == 2 @timeit "verify 3" (verify_ir(ir, true, false, optimizer_lattice(sv.inlining.interp)); verify_linetable(ir.linetable)) end diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index c2c3d2ac0ed54f..e4ded5bd282dab 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -587,6 +587,7 @@ struct CFGTransformState result_bbs::Vector{BasicBlock} bb_rename_pred::Vector{Int} bb_rename_succ::Vector{Int} + domtree::Union{Nothing, DomTree} end # N.B.: Takes ownership of the CFG array @@ -622,11 +623,14 @@ function CFGTransformState!(blocks::Vector{BasicBlock}, allow_cfg_transforms::Bo let blocks = blocks, bb_rename = bb_rename result_bbs = BasicBlock[blocks[i] for i = 1:length(blocks) if bb_rename[i] != -1] end + # TODO: This could be done by just renaming the domtree + domtree = construct_domtree(result_bbs) else bb_rename = Vector{Int}() result_bbs = blocks + domtree = nothing end - return CFGTransformState(allow_cfg_transforms, allow_cfg_transforms, result_bbs, bb_rename, bb_rename) + return CFGTransformState(allow_cfg_transforms, allow_cfg_transforms, result_bbs, bb_rename, bb_rename, domtree) end mutable struct IncrementalCompact @@ -681,7 +685,7 @@ mutable struct IncrementalCompact bb_rename = Vector{Int}() pending_nodes = NewNodeStream() pending_perm = Int[] - return new(code, parent.result, CFGTransformState(false, false, parent.cfg_transform.result_bbs, bb_rename, bb_rename), + return new(code, parent.result, CFGTransformState(false, false, parent.cfg_transform.result_bbs, bb_rename, bb_rename, nothing), ssa_rename, parent.used_ssas, parent.late_fixup, perm, 1, parent.new_new_nodes, parent.new_new_used_ssas, pending_nodes, pending_perm, @@ -942,6 +946,14 @@ function insert_node_here!(compact::IncrementalCompact, newinst::NewInstruction, return inst end +function delete_inst_here!(compact) + # Delete the statement, update refcounts etc + compact[SSAValue(compact.result_idx-1)] = nothing + # Pretend that we never compacted this statement in the first place + compact.result_idx -= 1 + return nothing +end + function getindex(view::TypesView, v::OldSSAValue) id = v.id ir = view.ir.ir @@ -1222,19 +1234,25 @@ end # N.B.: from and to are non-renamed indices function kill_edge!(compact::IncrementalCompact, active_bb::Int, from::Int, to::Int) - # Note: We recursively kill as many edges as are obviously dead. However, this - # may leave dead loops in the IR. We kill these later in a CFG cleanup pass (or - # worstcase during codegen). - (; bb_rename_pred, bb_rename_succ, result_bbs) = compact.cfg_transform + # Note: We recursively kill as many edges as are obviously dead. + (; bb_rename_pred, bb_rename_succ, result_bbs, domtree) = compact.cfg_transform preds = result_bbs[bb_rename_succ[to]].preds succs = result_bbs[bb_rename_pred[from]].succs deleteat!(preds, findfirst(x::Int->x==bb_rename_pred[from], preds)::Int) deleteat!(succs, findfirst(x::Int->x==bb_rename_succ[to], succs)::Int) + if domtree !== nothing + domtree_delete_edge!(domtree, result_bbs, bb_rename_pred[from], bb_rename_succ[to]) + end # Check if the block is now dead - if length(preds) == 0 - for succ in copy(result_bbs[bb_rename_succ[to]].succs) - kill_edge!(compact, active_bb, to, findfirst(x::Int->x==succ, bb_rename_pred)::Int) + if length(preds) == 0 || (domtree !== nothing && bb_unreachable(domtree, bb_rename_succ[to])) + to_succs = result_bbs[bb_rename_succ[to]].succs + for succ in copy(to_succs) + new_succ = findfirst(x::Int->x==succ, bb_rename_pred) + new_succ === nothing && continue + kill_edge!(compact, active_bb, to, new_succ) end + empty!(preds) + empty!(to_succs) if to < active_bb # Kill all statements in the block stmts = result_bbs[bb_rename_succ[to]].stmts diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index fbd2112a1c0fef..5b414f8786e98d 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -46,22 +46,29 @@ end function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, irsv::IRInterpretationState) si = StmtInfo(true) # TODO better job here? - (; rt, effects, info) = abstract_call(interp, arginfo, si, irsv) + (; rt, exct, effects, info) = abstract_call(interp, arginfo, si, irsv) irsv.ir.stmts[irsv.curridx][:info] = info - return RTEffects(rt, effects) + return RTEffects(rt, exct, effects) end +function kill_block!(ir, bb) + # Kill the entire block + stmts = ir.cfg.blocks[bb].stmts + for bidx = stmts + inst = ir[SSAValue(bidx)] + inst[:stmt] = nothing + inst[:type] = Bottom + inst[:flag] = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW + end + ir[SSAValue(last(stmts))][:stmt] = ReturnNode() + return +end + + function update_phi!(irsv::IRInterpretationState, from::Int, to::Int) ir = irsv.ir if length(ir.cfg.blocks[to].preds) == 0 - # Kill the entire block - for bidx = ir.cfg.blocks[to].stmts - inst = ir[SSAValue(bidx)] - inst[:stmt] = nothing - inst[:type] = Bottom - inst[:flag] = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW - end - return + kill_block!(ir, to) end for sidx = ir.cfg.blocks[to].stmts stmt = ir[SSAValue(sidx)][:stmt] @@ -83,15 +90,38 @@ function kill_terminator_edges!(irsv::IRInterpretationState, term_idx::Int, bb:: ir = irsv.ir stmt = ir[SSAValue(term_idx)][:stmt] if isa(stmt, GotoIfNot) - kill_edge!(ir, bb, stmt.dest, update_phi!(irsv)) - kill_edge!(ir, bb, bb+1, update_phi!(irsv)) + kill_edge!(irsv, bb, stmt.dest) + kill_edge!(irsv, bb, bb+1) elseif isa(stmt, GotoNode) - kill_edge!(ir, bb, stmt.label, update_phi!(irsv)) + kill_edge!(irsv, bb, stmt.label) elseif isa(stmt, ReturnNode) # Nothing to do else @assert !isexpr(stmt, :enter) - kill_edge!(ir, bb, bb+1, update_phi!(irsv)) + kill_edge!(irsv, bb, bb+1) + end +end + +function kill_edge!(irsv::IRInterpretationState, from::Int, to::Int) + ir = irsv.ir + kill_edge!(ir, from, to, update_phi!(irsv)) + + lazydomtree = irsv.lazydomtree + domtree = nothing + if isdefined(lazydomtree, :domtree) + domtree = get!(lazydomtree) + domtree_delete_edge!(domtree, ir.cfg.blocks, from, to) + elseif length(ir.cfg.blocks[to].preds) != 0 + # TODO: If we're not maintaining the domtree, computing it just for this + # is slightly overkill - just the dfs tree would be enough. + domtree = get!(lazydomtree) + end + + if domtree !== nothing && bb_unreachable(domtree, to) + kill_block!(ir, to) + for edge in ir.cfg.blocks[to].succs + kill_edge!(irsv, to, edge) + end end end @@ -113,10 +143,10 @@ function reprocess_instruction!(interp::AbstractInterpreter, inst::Instruction, if condval inst[:stmt] = nothing inst[:type] = Any - kill_edge!(ir, bb, stmt.dest, update_phi!(irsv)) + kill_edge!(irsv, bb, stmt.dest) else inst[:stmt] = GotoNode(stmt.dest) - kill_edge!(ir, bb, bb+1, update_phi!(irsv)) + kill_edge!(irsv, bb, bb+1) end return true end diff --git a/base/compiler/ssair/passes.jl b/base/compiler/ssair/passes.jl index 21e0e9b213f1c2..6db47ed537e0eb 100644 --- a/base/compiler/ssair/passes.jl +++ b/base/compiler/ssair/passes.jl @@ -382,7 +382,9 @@ function lift_leaves(compact::IncrementalCompact, field::Int, elseif isexpr(def, :new) typ = unwrap_unionall(widenconst(types(compact)[leaf])) (isa(typ, DataType) && !isabstracttype(typ)) || return nothing - @assert !ismutabletype(typ) + if ismutabletype(typ) + isconst(typ, field) || return nothing + end if length(def.args) < 1+field if field > fieldcount(typ) return nothing @@ -1735,7 +1737,8 @@ function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) phi_uses = fill(0, length(ir.stmts) + length(ir.new_nodes)) all_phis = Int[] unionphis = Pair{Int,Any}[] # sorted - compact = IncrementalCompact(ir) + compact = IncrementalCompact(ir, true) + made_changes = false for ((_, idx), stmt) in compact if isa(stmt, PhiNode) push!(all_phis, idx) @@ -1757,7 +1760,7 @@ function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) # nullify safe `typeassert` calls ty, isexact = instanceof_tfunc(argextype(stmt.args[3], compact), true) if isexact && โŠ‘(๐•ƒโ‚’, argextype(stmt.args[2], compact), ty) - compact[idx] = nothing + delete_inst_here!(compact) continue end end @@ -1799,6 +1802,7 @@ function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) if t === Union{} stmt = compact[SSAValue(phi)][:stmt]::PhiNode kill_phi!(compact, phi_uses, 1:length(stmt.values), SSAValue(phi), stmt, true) + made_changes = true continue elseif t === Any continue @@ -1820,16 +1824,17 @@ function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) end compact.result[phi][:type] = t kill_phi!(compact, phi_uses, to_drop, SSAValue(phi), stmt, false) + made_changes = true end # Perform simple DCE for unused values extra_worklist = Int[] for (idx, nused) in Iterators.enumerate(compact.used_ssas) idx >= compact.result_idx && break nused == 0 || continue - adce_erase!(phi_uses, extra_worklist, compact, idx, false) + made_changes |= adce_erase!(phi_uses, extra_worklist, compact, idx, false) end while !isempty(extra_worklist) - adce_erase!(phi_uses, extra_worklist, compact, pop!(extra_worklist), true) + made_changes |= adce_erase!(phi_uses, extra_worklist, compact, pop!(extra_worklist), true) end # Go back and erase any phi cycles changed = true @@ -1850,10 +1855,11 @@ function adce_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing) while !isempty(extra_worklist) if adce_erase!(phi_uses, extra_worklist, compact, pop!(extra_worklist), true) changed = true + made_changes = true end end end - return complete(compact) + return Pair{IRCode, Bool}(complete(compact), made_changes) end function is_bb_empty(ir::IRCode, bb::BasicBlock) @@ -2203,7 +2209,7 @@ function cfg_simplify!(ir::IRCode) # Run instruction compaction to produce the result, # but we're messing with the CFG # so we don't want compaction to do so independently - compact = IncrementalCompact(ir, CFGTransformState(true, false, cresult_bbs, bb_rename_pred, bb_rename_succ)) + compact = IncrementalCompact(ir, CFGTransformState(true, false, cresult_bbs, bb_rename_pred, bb_rename_succ, nothing)) result_idx = 1 for (idx, orig_bb) in enumerate(result_bbs) ms = orig_bb diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index f3253526d25e98..cbe167926014cd 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -584,7 +584,7 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, end # Record the correct exception handler for all critical sections - handler_at = compute_trycatch(code, BitSet()) + handler_at, handlers = compute_trycatch(code, BitSet()) phi_slots = Vector{Int}[Int[] for _ = 1:length(ir.cfg.blocks)] live_slots = Vector{Int}[Int[] for _ = 1:length(ir.cfg.blocks)] @@ -627,10 +627,12 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, # The slot is live-in into this block. We need to # Create a PhiC node in the catch entry block and # an upsilon node in the corresponding enter block + varstate = sv.bb_vartables[li] + if varstate === nothing + continue + end node = PhiCNode(Any[]) insertpoint = first_insert_for_bb(code, cfg, li) - varstate = sv.bb_vartables[li] - @assert varstate !== nothing vt = varstate[idx] phic_ssa = NewSSAValue( insert_node!(ir, insertpoint, @@ -690,6 +692,9 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, new_nodes = ir.new_nodes @timeit "SSA Rename" while !isempty(worklist) (item::Int, pred, incoming_vals) = pop!(worklist) + if sv.bb_vartables[item] === nothing + continue + end # Rename existing phi nodes first, because their uses occur on the edge # TODO: This isn't necessary if inlining stops replacing arguments by slots. for idx in cfg.blocks[item].stmts @@ -810,8 +815,8 @@ function construct_ssa!(ci::CodeInfo, ir::IRCode, sv::OptimizationState, incoming_vals[id] = Pair{Any, Any}(thisval, thisdef) has_pinode[id] = false enter_idx = idx - while handler_at[enter_idx] != 0 - enter_idx = handler_at[enter_idx] + while handler_at[enter_idx][1] != 0 + (; enter_idx) = handlers[handler_at[enter_idx][1]] leave_block = block_for_inst(cfg, code[enter_idx].args[1]::Int) cidx = findfirst((; slot)::NewPhiCNode2->slot_id(slot)==id, new_phic_nodes[leave_block]) if cidx !== nothing diff --git a/base/compiler/ssair/verify.jl b/base/compiler/ssair/verify.jl index f770c72d0fae51..801836c485e31e 100644 --- a/base/compiler/ssair/verify.jl +++ b/base/compiler/ssair/verify.jl @@ -47,7 +47,10 @@ function check_op(ir::IRCode, domtree::DomTree, @nospecialize(op), use_bb::Int, end use_inst = ir[op] - if isa(use_inst[:stmt], Union{GotoIfNot, GotoNode, ReturnNode}) + if isa(use_inst[:stmt], Union{GotoIfNot, GotoNode, ReturnNode}) && !(isa(use_inst[:stmt], ReturnNode) && !isdefined(use_inst[:stmt], :val)) + # Allow uses of `unreachable`, which may have been inserted when + # an earlier block got deleted, but for some reason we didn't figure + # out yet that this entire block is dead also. @verify_error "At statement %$use_idx: Invalid use of value statement or terminator %$(op.id)" error("") end diff --git a/base/compiler/stmtinfo.jl b/base/compiler/stmtinfo.jl index d634c88cdf8e89..e28858eea60aa2 100644 --- a/base/compiler/stmtinfo.jl +++ b/base/compiler/stmtinfo.jl @@ -10,6 +10,7 @@ and any additional information (`call.info`) for a given generic call. """ struct CallMeta rt::Any + exct::Any effects::Effects info::CallInfo end diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index eb43e77885d64a..97d8bf90c060da 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1374,10 +1374,10 @@ end function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState) nargs = length(argtypes) if !isempty(argtypes) && isvarargtype(argtypes[nargs]) - nargs - 1 <= 6 || return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) - nargs > 3 || return CallMeta(Any, Effects(), NoCallInfo()) + nargs - 1 <= 6 || return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) + nargs > 3 || return CallMeta(Any, Any, Effects(), NoCallInfo()) else - 5 <= nargs <= 6 || return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) + 5 <= nargs <= 6 || return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) end ๐•ƒแตข = typeinf_lattice(interp) o = unwrapva(argtypes[2]) @@ -1399,7 +1399,7 @@ function abstract_modifyfield!(interp::AbstractInterpreter, argtypes::Vector{Any end info = ModifyFieldInfo(callinfo.info) end - return CallMeta(RT, Effects(), info) + return CallMeta(RT, Any, Effects(), info) end @nospecs function replacefield!_tfunc(๐•ƒ::AbstractLattice, o, f, x, v, success_order, failure_order) return replacefield!_tfunc(๐•ƒ, o, f, x, v) @@ -2742,7 +2742,7 @@ end # since abstract_call_gf_by_type is a very inaccurate model of _method and of typeinf_type, # while this assumes that it is an absolutely precise and accurate and exact model of both function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState) - UNKNOWN = CallMeta(Type, EFFECTS_THROWS, NoCallInfo()) + UNKNOWN = CallMeta(Type, Any, EFFECTS_THROWS, NoCallInfo()) if !(2 <= length(argtypes) <= 3) return UNKNOWN end @@ -2771,7 +2771,7 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s end if contains_is(argtypes_vec, Union{}) - return CallMeta(Const(Union{}), EFFECTS_TOTAL, NoCallInfo()) + return CallMeta(Const(Union{}), Union{}, EFFECTS_TOTAL, NoCallInfo()) end # Run the abstract_call without restricting abstract call @@ -2789,33 +2789,33 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s rt = widenslotwrapper(call.rt) if isa(rt, Const) # output was computed to be constant - return CallMeta(Const(typeof(rt.val)), EFFECTS_TOTAL, info) + return CallMeta(Const(typeof(rt.val)), Union{}, EFFECTS_TOTAL, info) end rt = widenconst(rt) if rt === Bottom || (isconcretetype(rt) && !iskindtype(rt)) # output cannot be improved so it is known for certain - return CallMeta(Const(rt), EFFECTS_TOTAL, info) + return CallMeta(Const(rt), Union{}, EFFECTS_TOTAL, info) elseif isa(sv, InferenceState) && !isempty(sv.pclimitations) # conservatively express uncertainty of this result # in two ways: both as being a subtype of this, and # because of LimitedAccuracy causes - return CallMeta(Type{<:rt}, EFFECTS_TOTAL, info) + return CallMeta(Type{<:rt}, Union{}, EFFECTS_TOTAL, info) elseif isa(tt, Const) || isconstType(tt) # input arguments were known for certain # XXX: this doesn't imply we know anything about rt - return CallMeta(Const(rt), EFFECTS_TOTAL, info) + return CallMeta(Const(rt), Union{}, EFFECTS_TOTAL, info) elseif isType(rt) - return CallMeta(Type{rt}, EFFECTS_TOTAL, info) + return CallMeta(Type{rt}, Union{}, EFFECTS_TOTAL, info) else - return CallMeta(Type{<:rt}, EFFECTS_TOTAL, info) + return CallMeta(Type{<:rt}, Union{}, EFFECTS_TOTAL, info) end end # a simplified model of abstract_call_gf_by_type for applicable function abstract_applicable(interp::AbstractInterpreter, argtypes::Vector{Any}, sv::AbsIntState, max_methods::Int) - length(argtypes) < 2 && return CallMeta(Bottom, EFFECTS_THROWS, NoCallInfo()) - isvarargtype(argtypes[2]) && return CallMeta(Bool, EFFECTS_UNKNOWN, NoCallInfo()) + length(argtypes) < 2 && return CallMeta(Bottom, Any, EFFECTS_THROWS, NoCallInfo()) + isvarargtype(argtypes[2]) && return CallMeta(Bool, Any, EFFECTS_UNKNOWN, NoCallInfo()) argtypes = argtypes[2:end] atype = argtypes_to_type(argtypes) matches = find_matching_methods(typeinf_lattice(interp), argtypes, atype, method_table(interp), @@ -2854,7 +2854,7 @@ function abstract_applicable(interp::AbstractInterpreter, argtypes::Vector{Any}, end end end - return CallMeta(rt, EFFECTS_TOTAL, NoCallInfo()) + return CallMeta(rt, Union{}, EFFECTS_TOTAL, NoCallInfo()) end add_tfunc(applicable, 1, INT_INF, @nospecs((๐•ƒ::AbstractLattice, f, args...)->Bool), 40) @@ -2863,26 +2863,26 @@ function _hasmethod_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv if length(argtypes) == 3 && !isvarargtype(argtypes[3]) ftโ€ฒ = argtype_by_index(argtypes, 2) ft = widenconst(ftโ€ฒ) - ft === Bottom && return CallMeta(Bool, EFFECTS_THROWS, NoCallInfo()) + ft === Bottom && return CallMeta(Bool, Any, EFFECTS_THROWS, NoCallInfo()) typeidx = 3 elseif length(argtypes) == 2 && !isvarargtype(argtypes[2]) typeidx = 2 else - return CallMeta(Any, Effects(), NoCallInfo()) + return CallMeta(Any, Any, Effects(), NoCallInfo()) end (types, isexact, isconcrete, istype) = instanceof_tfunc(argtype_by_index(argtypes, typeidx), false) - isexact || return CallMeta(Bool, Effects(), NoCallInfo()) + isexact || return CallMeta(Bool, Any, Effects(), NoCallInfo()) unwrapped = unwrap_unionall(types) if types === Bottom || !(unwrapped isa DataType) || unwrapped.name !== Tuple.name - return CallMeta(Bool, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bool, Any, EFFECTS_THROWS, NoCallInfo()) end if typeidx == 3 - isdispatchelem(ft) || return CallMeta(Bool, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below + isdispatchelem(ft) || return CallMeta(Bool, Any, Effects(), NoCallInfo()) # check that we might not have a subtype of `ft` at runtime, before doing supertype lookup below types = rewrap_unionall(Tuple{ft, unwrapped.parameters...}, types)::Type end mt = ccall(:jl_method_table_for, Any, (Any,), types) if !isa(mt, MethodTable) - return CallMeta(Bool, EFFECTS_THROWS, NoCallInfo()) + return CallMeta(Bool, Any, EFFECTS_THROWS, NoCallInfo()) end match, valid_worlds = findsup(types, method_table(interp)) update_valid_age!(sv, valid_worlds) @@ -2894,7 +2894,7 @@ function _hasmethod_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, sv edge = specialize_method(match)::MethodInstance add_invoke_backedge!(sv, types, edge) end - return CallMeta(rt, EFFECTS_TOTAL, NoCallInfo()) + return CallMeta(rt, Any, EFFECTS_TOTAL, NoCallInfo()) end # N.B.: typename maps type equivalence classes to a single value diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index c1230980c42a65..55026e54f777d6 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -327,7 +327,7 @@ function CodeInstance(interp::AbstractInterpreter, result::InferenceResult, end # relocatability = isa(inferred_result, String) ? inferred_result[end] : UInt8(0) return CodeInstance(result.linfo, - widenconst(result_type), rettype_const, inferred_result, + widenconst(result_type), widenconst(result.exc_result), rettype_const, inferred_result, const_flags, first(valid_worlds), last(valid_worlds), # TODO: Actually do something with non-IPO effects encode_effects(result.ipo_effects), encode_effects(result.ipo_effects), result.analysis_results, @@ -524,7 +524,8 @@ function finish(me::InferenceState, interp::AbstractInterpreter) # inspect whether our inference had a limited result accuracy, # else it may be suitable to cache bestguess = me.bestguess = cycle_fix_limited(me.bestguess, me) - limited_ret = bestguess isa LimitedAccuracy + exc_bestguess = me.exc_bestguess = cycle_fix_limited(me.exc_bestguess, me) + limited_ret = bestguess isa LimitedAccuracy || exc_bestguess isa LimitedAccuracy limited_src = false if !limited_ret gt = me.ssavaluetypes @@ -539,6 +540,7 @@ function finish(me::InferenceState, interp::AbstractInterpreter) me.result.valid_worlds = me.valid_worlds me.result.result = bestguess me.result.ipo_effects = me.ipo_effects = adjust_effects(me) + me.result.exc_result = exc_bestguess if limited_ret # a parent may be cached still, but not this intermediate work: @@ -568,6 +570,7 @@ function finish(me::InferenceState, interp::AbstractInterpreter) me.result.src = me.src # for reflection etc. end end + validate_code_in_debug_mode(me.linfo, me.src, "inferred") nothing end @@ -795,15 +798,16 @@ end ipo_effects(code::CodeInstance) = decode_effects(code.ipo_purity_bits) struct EdgeCallResult - rt #::Type + rt + exct edge::Union{Nothing,MethodInstance} effects::Effects volatile_inf_result::Union{Nothing,VolatileInferenceResult} - function EdgeCallResult(@nospecialize(rt), + function EdgeCallResult(@nospecialize(rt), @nospecialize(exct), edge::Union{Nothing,MethodInstance}, effects::Effects, volatile_inf_result::Union{Nothing,VolatileInferenceResult} = nothing) - return new(rt, edge, effects, volatile_inf_result) + return new(rt, exct, edge, effects, volatile_inf_result) end end @@ -823,14 +827,14 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize rt = cached_return_type(code) effects = ipo_effects(code) update_valid_age!(caller, WorldRange(min_world(code), max_world(code))) - return EdgeCallResult(rt, mi, effects) + return EdgeCallResult(rt, code.exctype, mi, effects) end else cache_mode = CACHE_MODE_GLOBAL # cache edge targets globally by default end if ccall(:jl_get_module_infer, Cint, (Any,), method.module) == 0 && !generating_output(#=incremental=#false) add_remark!(interp, caller, "Inference is disabled for the target module") - return EdgeCallResult(Any, nothing, Effects()) + return EdgeCallResult(Any, Any, nothing, Effects()) end if !is_cached(caller) && frame_parent(caller) === nothing # this caller exists to return to the user @@ -848,7 +852,7 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize add_remark!(interp, caller, "Failed to retrieve source") # can't get the source for this, so we know nothing unlock_mi_inference(interp, mi) - return EdgeCallResult(Any, nothing, Effects()) + return EdgeCallResult(Any, Any, nothing, Effects()) end if is_cached(caller) || frame_parent(caller) !== nothing # don't involve uncached functions in cycle resolution frame.parent = caller @@ -863,15 +867,15 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize volatile_inf_result = isinferred && let inferred_src = result.src isa(inferred_src, CodeInfo) && (is_inlineable(inferred_src) || force_inline) end ? VolatileInferenceResult(result) : nothing - return EdgeCallResult(frame.bestguess, edge, effects, volatile_inf_result) + return EdgeCallResult(frame.bestguess, frame.exc_bestguess, edge, effects, volatile_inf_result) elseif frame === true # unresolvable cycle - return EdgeCallResult(Any, nothing, Effects()) + return EdgeCallResult(Any, Any, nothing, Effects()) end # return the current knowledge about this cycle frame = frame::InferenceState update_valid_age!(caller, frame.valid_worlds) - return EdgeCallResult(frame.bestguess, nothing, adjust_effects(Effects(), method)) + return EdgeCallResult(frame.bestguess, frame.exc_bestguess, nothing, adjust_effects(Effects(), method)) end function cached_return_type(code::CodeInstance) diff --git a/base/compiler/types.jl b/base/compiler/types.jl index 41fde10dfedf45..b98cf09ff7cf1b 100644 --- a/base/compiler/types.jl +++ b/base/compiler/types.jl @@ -81,6 +81,7 @@ mutable struct InferenceResult const argtypes::Vector{Any} const overridden_by_const::BitVector result # extended lattice element if inferred, nothing otherwise + exc_result # like `result`, but for the thrown value src # ::Union{CodeInfo, IRCode, OptimizationState} if inferred copy is available, nothing otherwise valid_worlds::WorldRange # if inference and optimization is finished ipo_effects::Effects # if inference is finished @@ -91,7 +92,7 @@ mutable struct InferenceResult # def = linfo.def # nargs = def isa Method ? Int(def.nargs) : 0 # @assert length(cache_argtypes) == nargs - return new(linfo, cache_argtypes, overridden_by_const, nothing, nothing, + return new(linfo, cache_argtypes, overridden_by_const, nothing, nothing, nothing, WorldRange(), Effects(), Effects(), NULL_ANALYSIS_RESULTS, false) end end diff --git a/base/compiler/utilities.jl b/base/compiler/utilities.jl index 3d1fe354530605..d8ca4d9551656a 100644 --- a/base/compiler/utilities.jl +++ b/base/compiler/utilities.jl @@ -462,14 +462,14 @@ function is_throw_call(e::Expr, code::Vector{Any}) return false end -function mark_throw_blocks!(src::CodeInfo, handler_at::Vector{Int}) +function mark_throw_blocks!(src::CodeInfo, handler_at::Vector{Tuple{Int, Int}}) for stmt in find_throw_blocks(src.code, handler_at) src.ssaflags[stmt] |= IR_FLAG_THROW_BLOCK end return nothing end -function find_throw_blocks(code::Vector{Any}, handler_at::Vector{Int}) +function find_throw_blocks(code::Vector{Any}, handler_at::Vector{Tuple{Int, Int}}) stmts = BitSet() n = length(code) for i in n:-1:1 @@ -482,7 +482,7 @@ function find_throw_blocks(code::Vector{Any}, handler_at::Vector{Int}) elseif s.head === :return # see `ReturnNode` handling elseif is_throw_call(s, code) - if handler_at[i] == 0 + if handler_at[i][1] == 0 push!(stmts, i) end elseif i+1 in stmts diff --git a/src/codegen.cpp b/src/codegen.cpp index 632c76caa1c08c..16f0b6903aa28c 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -8006,7 +8006,16 @@ static jl_llvm_functions_t auto scan_ssavalue = [&](jl_value_t *val) { if (jl_is_ssavalue(val)) { - ctx.ssavalue_usecount[((jl_ssavalue_t*)val)->id-1] += 1; + size_t ssa_idx = ((jl_ssavalue_t*)val)->id-1; + /* + * We technically allow out of bounds SSAValues in dead IR, so make + * sure to bounds check this here. It's still not *good* to leave + * dead code in the IR, because this will conservatively overcount + * it, but let's at least make it not crash. + */ + if (ssa_idx < ctx.ssavalue_usecount.size()) { + ctx.ssavalue_usecount[ssa_idx] += 1; + } return true; } return false; diff --git a/src/gf.c b/src/gf.c index f964927aa33681..fffeb928a18896 100644 --- a/src/gf.c +++ b/src/gf.c @@ -284,13 +284,6 @@ JL_DLLEXPORT jl_value_t *jl_methtable_lookup(jl_methtable_t *mt, jl_value_t *typ // ----- MethodInstance specialization instantiation ----- // -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, - uint32_t ipo_effects, uint32_t effects, jl_value_t *analysis_results, - uint8_t relocatability); - jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_args_t fptr) JL_GC_DISABLED { jl_sym_t *sname = jl_symbol(name); @@ -323,7 +316,7 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a jl_gc_wb(m, mi); jl_code_instance_t *codeinst = jl_new_codeinst(mi, - (jl_value_t*)jl_any_type, jl_nothing, jl_nothing, + (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, jl_nothing, jl_nothing, 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); jl_mi_cache_insert(mi, codeinst); jl_atomic_store_relaxed(&codeinst->specptr.fptr1, fptr); @@ -466,7 +459,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_method_inferred( codeinst = jl_atomic_load_relaxed(&codeinst->next); } codeinst = jl_new_codeinst( - mi, rettype, NULL, NULL, + mi, rettype, (jl_value_t*)jl_any_type, NULL, NULL, 0, min_world, max_world, 0, 0, jl_nothing, 0); jl_mi_cache_insert(mi, codeinst); return codeinst; @@ -483,7 +476,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_codeinst_for_src( } JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( - jl_method_instance_t *mi, jl_value_t *rettype, + jl_method_instance_t *mi, jl_value_t *rettype, jl_value_t *exctype, jl_value_t *inferred_const, jl_value_t *inferred, int32_t const_flags, size_t min_world, size_t max_world, uint32_t ipo_effects, uint32_t effects, jl_value_t *analysis_results, @@ -498,6 +491,7 @@ JL_DLLEXPORT jl_code_instance_t *jl_new_codeinst( codeinst->min_world = min_world; codeinst->max_world = max_world; codeinst->rettype = rettype; + codeinst->exctype = exctype; jl_atomic_store_release(&codeinst->inferred, inferred); //codeinst->edges = NULL; if ((const_flags & 2) == 0) @@ -2455,7 +2449,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t jl_callptr_t unspec_invoke = NULL; if (unspec && (unspec_invoke = jl_atomic_load_acquire(&unspec->invoke))) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, - (jl_value_t*)jl_any_type, NULL, NULL, + (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL, 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); void *unspec_fptr = jl_atomic_load_relaxed(&unspec->specptr.fptr); if (unspec_fptr) { @@ -2482,7 +2476,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t jl_code_info_t *src = jl_code_for_interpreter(mi, world); if (!jl_code_requires_compiler(src, 0)) { jl_code_instance_t *codeinst = jl_new_codeinst(mi, - (jl_value_t*)jl_any_type, NULL, NULL, + (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL, 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); jl_atomic_store_release(&codeinst->invoke, jl_fptr_interpret_call); jl_mi_cache_insert(mi, codeinst); @@ -2516,7 +2510,7 @@ jl_code_instance_t *jl_compile_method_internal(jl_method_instance_t *mi, size_t // only these care about the exact specTypes, otherwise we can use it directly return ucache; } - codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, NULL, NULL, + codeinst = jl_new_codeinst(mi, (jl_value_t*)jl_any_type, (jl_value_t*)jl_any_type, NULL, NULL, 0, 1, ~(size_t)0, 0, 0, jl_nothing, 0); void *unspec_fptr = jl_atomic_load_relaxed(&ucache->specptr.fptr); if (unspec_fptr) { diff --git a/src/jltypes.c b/src/jltypes.c index b1830ec4e765e6..c7c7d213c5317a 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -3244,12 +3244,13 @@ 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(15, + jl_perm_symsvec(16, "def", "next", "min_world", "max_world", "rettype", + "exctype", "rettype_const", "inferred", //"edges", @@ -3258,7 +3259,7 @@ void jl_init_types(void) JL_GC_DISABLED "analysis_results", "isspecsig", "precompile", "relocatability", "invoke", "specptr"), // function object decls - jl_svec(15, + jl_svec(16, jl_method_instance_type, jl_any_type, jl_ulong_type, @@ -3266,6 +3267,7 @@ void jl_init_types(void) JL_GC_DISABLED jl_any_type, jl_any_type, jl_any_type, + jl_any_type, //jl_any_type, //jl_bool_type, jl_uint32_type, jl_uint32_type, @@ -3277,8 +3279,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_emptysvec, 0, 1, 1); jl_svecset(jl_code_instance_type->types, 1, jl_code_instance_type); - const static uint32_t code_instance_constfields[1] = { 0b000001010110001 }; // Set fields 1, 5-6, 8, 10 as const - const static uint32_t code_instance_atomicfields[1] = { 0b110100101000010 }; // Set fields 2, 7, 9, 12, 14-15 as atomic + const static uint32_t code_instance_constfields[1] = { 0b0000010101110001 }; // Set fields 1, 5-7, 9, 11 as const + const static uint32_t code_instance_atomicfields[1] = { 0b1101001010000010 }; // Set fields 2, 8, 10, 13, 15-16 as atomic //Fields 3-4 are only operated on by construction and deserialization, so are const at runtime //Fields 11 and 15 must be protected by locks, and thus all operations on jl_code_instance_t are threadsafe jl_code_instance_type->name->constfields = code_instance_constfields; @@ -3420,8 +3422,8 @@ void jl_init_types(void) JL_GC_DISABLED jl_svecset(jl_methtable_type->types, 10, jl_uint8_type); jl_svecset(jl_method_type->types, 12, jl_method_instance_type); jl_svecset(jl_method_instance_type->types, 6, jl_code_instance_type); - jl_svecset(jl_code_instance_type->types, 13, jl_voidpointer_type); jl_svecset(jl_code_instance_type->types, 14, jl_voidpointer_type); + jl_svecset(jl_code_instance_type->types, 15, jl_voidpointer_type); jl_svecset(jl_binding_type->types, 1, jl_globalref_type); jl_svecset(jl_binding_type->types, 2, jl_binding_type); diff --git a/src/julia.h b/src/julia.h index acb700c0bd936e..cdc3477a30419a 100644 --- a/src/julia.h +++ b/src/julia.h @@ -411,6 +411,7 @@ typedef struct _jl_code_instance_t { // inference state cache jl_value_t *rettype; // return type for fptr + jl_value_t *exctype; // thrown type for fptr jl_value_t *rettype_const; // inferred constant return value, or null _Atomic(jl_value_t *) inferred; // inferred jl_code_info_t (may be compressed), or jl_nothing, or null //TODO: jl_array_t *edges; // stored information about edges from this object diff --git a/src/julia_internal.h b/src/julia_internal.h index c09b73f8a1052b..eeee548a9a4eee 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -622,6 +622,13 @@ JL_DLLEXPORT jl_code_instance_t *jl_get_codeinst_for_src( jl_method_instance_t *jl_get_unspecialized_from_mi(jl_method_instance_t *method JL_PROPAGATES_ROOT); jl_method_instance_t *jl_get_unspecialized(jl_method_t *def JL_PROPAGATES_ROOT); +JL_DLLEXPORT jl_code_instance_t* jl_new_codeinst( + jl_method_instance_t *mi, jl_value_t *rettype, jl_value_t *exctype, + jl_value_t *inferred_const, jl_value_t *inferred, + int32_t const_flags, size_t min_world, size_t max_world, + uint32_t ipo_effects, uint32_t effects, jl_value_t *analysis_results, + uint8_t relocatability); + JL_DLLEXPORT void jl_compile_method_instance(jl_method_instance_t *mi, jl_tupletype_t *types, size_t world); JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types); jl_code_info_t *jl_code_for_interpreter(jl_method_instance_t *lam JL_PROPAGATES_ROOT, size_t world); diff --git a/src/opaque_closure.c b/src/opaque_closure.c index 8b7cc7292be229..7fd6d5a0f86662 100644 --- a/src/opaque_closure.c +++ b/src/opaque_closure.c @@ -134,13 +134,6 @@ jl_opaque_closure_t *jl_new_opaque_closure(jl_tupletype_t *argt, jl_value_t *rt_ jl_method_t *jl_make_opaque_closure_method(jl_module_t *module, jl_value_t *name, int nargs, jl_value_t *functionloc, jl_code_info_t *ci, int isva); -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, - uint32_t ipo_effects, uint32_t effects, jl_value_t *analysis_results, - uint8_t relocatability); - JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tupletype_t *argt, jl_value_t *rt_lb, jl_value_t *rt_ub, jl_module_t *mod, jl_code_info_t *ci, int lineno, jl_value_t *file, int nargs, int isva, jl_value_t *env, int do_compile) { @@ -157,7 +150,7 @@ JL_DLLEXPORT jl_opaque_closure_t *jl_new_opaque_closure_from_code_info(jl_tuplet sigtype = jl_argtype_with_function(env, (jl_value_t*)argt); jl_method_instance_t *mi = jl_specializations_get_linfo((jl_method_t*)root, sigtype, jl_emptysvec); - inst = jl_new_codeinst(mi, rt_ub, NULL, (jl_value_t*)ci, + inst = jl_new_codeinst(mi, rt_ub, (jl_value_t*)jl_any_type, NULL, (jl_value_t*)ci, 0, meth->primary_world, -1, 0, 0, jl_nothing, 0); jl_mi_cache_insert(mi, inst); diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index c89cff0db38b30..2c9d8560aed4d0 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -565,9 +565,9 @@ function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, sv::CC.InferenceState) if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv)) if CC.isdefined_globalref(g) - return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), CC.EFFECTS_TOTAL) + return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL) end - return CC.RTEffects(Union{}, CC.EFFECTS_THROWS) + return CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS) end return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef, sv::CC.InferenceState) @@ -609,7 +609,7 @@ function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f), sv::CC.InferenceState) if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv)) neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE) - result = CC.MethodCallResult(result.rt, result.edgecycle, result.edgelimited, + result = CC.MethodCallResult(result.rt, result.exct, result.edgecycle, result.edgelimited, result.edge, neweffects) end ret = @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any, diff --git a/test/compiler/AbstractInterpreter.jl b/test/compiler/AbstractInterpreter.jl index 9580fc55508ac5..121e7fad55c90b 100644 --- a/test/compiler/AbstractInterpreter.jl +++ b/test/compiler/AbstractInterpreter.jl @@ -330,7 +330,7 @@ function CC.abstract_call(interp::NoinlineInterpreter, ret = @invoke CC.abstract_call(interp::CC.AbstractInterpreter, arginfo::CC.ArgInfo, si::CC.StmtInfo, sv::CC.InferenceState, max_methods::Int) if sv.mod in noinline_modules(interp) - return CC.CallMeta(ret.rt, ret.effects, NoinlineCallInfo(ret.info)) + return CC.CallMeta(ret.rt, ret.exct, ret.effects, NoinlineCallInfo(ret.info)) end return ret end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index ace17baeb5859b..b75e5e5fe87f08 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -4439,8 +4439,8 @@ let x = Tuple{Int,Any}[ #=21=# (0, Expr(:pop_exception, Core.SSAValue(2))) #=22=# (0, Core.ReturnNode(Core.SlotNumber(3))) ] - handler_at = Core.Compiler.compute_trycatch(last.(x), Core.Compiler.BitSet()) - @test handler_at == first.(x) + handler_at, handlers = Core.Compiler.compute_trycatch(last.(x), Core.Compiler.BitSet()) + @test map(x->x[1] == 0 ? 0 : handlers[x[1]].enter_idx, handler_at) == first.(x) end @test only(Base.return_types((Bool,)) do y @@ -5533,3 +5533,28 @@ function test_exit_bottom(s) n end @test only(Base.return_types(test_exit_bottom, Tuple{String})) == Int + +function foo_typed_throw_error() + try + error() + catch e + if isa(e, ErrorException) + return 1.0 + end + end + return 1 +end +@test Base.return_types(foo_typed_throw_error) |> only === Float64 + +will_throw_no_method(x::Int) = 1 +function foo_typed_throw_metherr() + try + will_throw_no_method(1.0) + catch e + if isa(e, MethodError) + return 1.0 + end + end + return 1 +end +@test Base.return_types(foo_typed_throw_metherr) |> only === Float64 diff --git a/test/core.jl b/test/core.jl index 00ab41e4ecd487..c85868c496d919 100644 --- a/test/core.jl +++ b/test/core.jl @@ -14,7 +14,7 @@ include("testenv.jl") # sanity tests that our built-in types are marked correctly for const fields for (T, c) in ( (Core.CodeInfo, []), - (Core.CodeInstance, [:def, :rettype, :rettype_const, :ipo_purity_bits, :analysis_results]), + (Core.CodeInstance, [:def, :rettype, :exctype, :rettype_const, :ipo_purity_bits, :analysis_results]), (Core.Method, [#=:name, :module, :file, :line, :primary_world, :sig, :slot_syms, :external_mt, :nargs, :called, :nospecialize, :nkw, :isva, :is_for_opaque_closure, :constprop=#]), (Core.MethodInstance, [#=:def, :specTypes, :sparam_vals=#]), (Core.MethodTable, [:module]), diff --git a/test/precompile.jl b/test/precompile.jl index bb87e1f6b1dc79..051b750fa7fdfb 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -1684,7 +1684,7 @@ precompile_test_harness("issue #46296") do load_path module CodeInstancePrecompile mi = first(Base.specializations(first(methods(identity)))) - ci = Core.CodeInstance(mi, Any, nothing, nothing, zero(Int32), typemin(UInt), + ci = Core.CodeInstance(mi, Any, Any, nothing, nothing, zero(Int32), typemin(UInt), typemax(UInt), zero(UInt32), zero(UInt32), nothing, 0x00) __init__() = @assert ci isa Core.CodeInstance From 72cd63ce28c50c8c72e009df03dfec608802450e Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Sun, 19 Nov 2023 22:03:15 -0500 Subject: [PATCH 27/38] jitlayers: reduce excess alignment of #52182 (#52210) --- src/jitlayers.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jitlayers.h b/src/jitlayers.h index 85cbb2cf8ec286..ba2e81879a44b1 100644 --- a/src/jitlayers.h +++ b/src/jitlayers.h @@ -306,7 +306,9 @@ class MaxAlignedAllocImpl // the returned pointer, this is >= alignof(std::max_align_t), which is too // small often to actually use. const size_t MaxAlignment = JL_CACHE_BYTE_ALIGNMENT; - return Align(std::min((size_t)llvm::PowerOf2Ceil(Size), MaxAlignment)); + if (Size <= offset) + return Align(1); + return Align(std::min((size_t)llvm::PowerOf2Ceil(Size - offset), MaxAlignment)); } LLVM_ATTRIBUTE_RETURNS_NONNULL void *Allocate(size_t Size, Align Alignment) { From 7327a8f4256b3dd250ba3838990947b6e7798b25 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:23:39 +0900 Subject: [PATCH 28/38] effects: add some more test cases for `Base.@propagate_inbounds` (#52236) --- test/compiler/effects.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index 5405066263d79f..133ff6e9f3ca9d 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -1280,3 +1280,13 @@ end |> !Core.Compiler.is_noub @test Base.infer_effects((Vector{Any},Int)) do xs, i @inbounds xs[i] end |> !Core.Compiler.is_noub +Base.@propagate_inbounds getindex_propagate(xs, i) = xs[i] +getindex_dont_propagate(xs, i) = xs[i] +@test Core.Compiler.is_noub_if_noinbounds(Base.infer_effects(getindex_propagate, (Vector{Any},Int))) +@test Core.Compiler.is_noub(Base.infer_effects(getindex_dont_propagate, (Vector{Any},Int))) +@test Base.infer_effects((Vector{Any},Int)) do xs, i + @inbounds getindex_propagate(xs, i) +end |> !Core.Compiler.is_noub +@test Base.infer_effects((Vector{Any},Int)) do xs, i + @inbounds getindex_dont_propagate(xs, i) +end |> Core.Compiler.is_noub From 5cb0e514a143a7aa4dfb371e25f03c35007fa927 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Mon, 20 Nov 2023 16:45:21 +0100 Subject: [PATCH 29/38] build: ASAN fixes for glibc (#51755) For the `sigsetjmp` bypass; looks like glibc removed the `__libc_siglongjmp` symbol in glibc 2.34, so change to using the approach taking by our `dlopen` wrapper instead. Adopts topolarity's fixes from #50170 Resolves #47698 Co-authored-by: Jameson Nash --- src/dlload.c | 2 +- src/julia.h | 7 +++---- src/task.c | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/dlload.c b/src/dlload.c index f2ba02e14c5d12..484c36a2288864 100644 --- a/src/dlload.c +++ b/src/dlload.c @@ -188,7 +188,7 @@ JL_DLLEXPORT JL_NO_SANITIZE void *jl_dlopen(const char *filename, unsigned flags dlopen = (dlopen_prototype*)dlsym(RTLD_NEXT, "dlopen"); if (!dlopen) return NULL; - void *libdl_handle = dlopen("libdl.so", RTLD_NOW | RTLD_NOLOAD); + void *libdl_handle = dlopen("libdl.so.2", RTLD_NOW | RTLD_NOLOAD); assert(libdl_handle); dlopen = (dlopen_prototype*)dlsym(libdl_handle, "dlopen"); dlclose(libdl_handle); diff --git a/src/julia.h b/src/julia.h index cdc3477a30419a..5198bcab5a2bd1 100644 --- a/src/julia.h +++ b/src/julia.h @@ -2287,10 +2287,9 @@ void (ijl_longjmp)(jmp_buf _Buf, int _Value); #define jl_setjmp_name "sigsetjmp" #endif #define jl_setjmp(a,b) sigsetjmp(a,b) -#if defined(_COMPILER_ASAN_ENABLED_) && __GLIBC__ -// Bypass the ASAN longjmp wrapper - we're unpoisoning the stack ourselves. -JL_DLLIMPORT int __attribute__ ((nothrow)) (__libc_siglongjmp)(jl_jmp_buf buf, int val); -#define jl_longjmp(a,b) __libc_siglongjmp(a,b) +#if defined(_COMPILER_ASAN_ENABLED_) && defined(__GLIBC__) +extern void (*real_siglongjmp)(jmp_buf _Buf, int _Value); +#define jl_longjmp(a,b) real_siglongjmp(a,b) #else #define jl_longjmp(a,b) siglongjmp(a,b) #endif diff --git a/src/task.c b/src/task.c index dca924c3ae09c1..8905b49e87f8e7 100644 --- a/src/task.c +++ b/src/task.c @@ -42,6 +42,13 @@ extern "C" { #endif #if defined(_COMPILER_ASAN_ENABLED_) +#if __GLIBC__ +#include +// Bypass the ASAN longjmp wrapper - we are unpoisoning the stack ourselves, +// since ASAN normally unpoisons far too much. +// c.f. interceptor in jl_dlopen as well +void (*real_siglongjmp)(jmp_buf _Buf, int _Value) = NULL; +#endif static inline void sanitizer_start_switch_fiber(jl_ptls_t ptls, jl_task_t *from, jl_task_t *to) { if (to->copy_stack) __sanitizer_start_switch_fiber(&from->ctx.asan_fake_stack, (char*)ptls->stackbase-ptls->stacksize, ptls->stacksize); @@ -1226,6 +1233,17 @@ void jl_init_tasks(void) JL_GC_DISABLED exit(1); } #endif +#if defined(_COMPILER_ASAN_ENABLED_) && __GLIBC__ + void *libc_handle = dlopen("libc.so.6", RTLD_NOW | RTLD_NOLOAD); + if (libc_handle) { + *(void**)&real_siglongjmp = dlsym(libc_handle, "siglongjmp"); + dlclose(libc_handle); + } + if (real_siglongjmp == NULL) { + jl_safe_printf("failed to get real siglongjmp\n"); + exit(1); + } +#endif } #if defined(_COMPILER_ASAN_ENABLED_) From 67161a3ba8253f64932a3e7023ab81aa20a6bf65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mos=C3=A8=20Giordano?= Date: Mon, 20 Nov 2023 17:10:15 +0000 Subject: [PATCH 30/38] Silence noisy test in `test/cmdlineargs.jl` (#52225) This is spamming ([example](https://buildkite.com/julialang/julia-master/builds/30247#018be235-0cd3-43bb-ba43-cc378ac0d682/792-1056)) ``` From worker 12: ERROR: LoadError: UndefVarError: `@which` not defined in `Main` From worker 12: Stacktrace: From worker 12: [1] top-level scope From worker 12: @ :0 From worker 12: [2] lower From worker 12: @ ./meta.jl:163 [inlined] From worker 12: [3] eval_user_input(errio::IOContext{Base.PipeEndpoint}, ast::Any, show_value::Bool) From worker 12: @ Base ./client.jl:141 From worker 12: [4] run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_file::Bool, color_set::Bool) From worker 12: @ Base ./client.jl:477 From worker 12: [5] repl_main From worker 12: @ Base ./client.jl:561 [inlined] From worker 12: [6] _start() From worker 12: @ Base ./client.jl:535 From worker 12: in expression starting at none:1 ``` in all jobs --- test/cmdlineargs.jl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/test/cmdlineargs.jl b/test/cmdlineargs.jl index 1c90352af29c91..ae81dbb0fefbe7 100644 --- a/test/cmdlineargs.jl +++ b/test/cmdlineargs.jl @@ -4,8 +4,8 @@ import Libdl # helper function for passing input to stdin # and returning the stdout result -function writereadpipeline(input, exename) - p = open(exename, "w+") +function writereadpipeline(input, exename; stderr=nothing) + p = open(pipeline(exename; stderr), "w+") @async begin write(p.in, input) close(p.in) @@ -145,14 +145,19 @@ end let exename = `$(Base.julia_cmd()) --startup-file=no --color=no` # tests for handling of ENV errors - let v = writereadpipeline( + let + io = IOBuffer() + v = writereadpipeline( "println(\"REPL: \", @which(less), @isdefined(InteractiveUtils))", setenv(`$exename -i -E '@assert isempty(LOAD_PATH); push!(LOAD_PATH, "@stdlib"); @isdefined InteractiveUtils'`, "JULIA_LOAD_PATH" => "", "JULIA_DEPOT_PATH" => ";:", - "HOME" => homedir())) + "HOME" => homedir()); + stderr=io) # @which is undefined @test_broken v == ("false\nREPL: InteractiveUtilstrue\n", true) + stderr = String(take!(io)) + @test_broken isempty(stderr) end let v = writereadpipeline("println(\"REPL: \", InteractiveUtils)", setenv(`$exename -i -e 'const InteractiveUtils = 3'`, From 65090d116eecddad5bdef88b1f3d6dace91310da Mon Sep 17 00:00:00 2001 From: Kristoffer Carlsson Date: Tue, 21 Nov 2023 03:08:45 +0100 Subject: [PATCH 31/38] tweak the error printed for `PrecompileError` to indicate that it doesn't have to be due to `__precompile__(false)` (#51367) --- base/loading.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/loading.jl b/base/loading.jl index ff1a2207a2022e..9a3a922298f1ed 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -1747,7 +1747,7 @@ end # we throw PrecompilableError when a module doesn't want to be precompiled struct PrecompilableError <: Exception end function show(io::IO, ex::PrecompilableError) - print(io, "Declaring __precompile__(false) is not allowed in files that are being precompiled.") + print(io, "Error when precompiling module, potentially caused by a __precompile__(false) declaration in the module.") end precompilableerror(ex::PrecompilableError) = true precompilableerror(ex::WrappedException) = precompilableerror(ex.error) From 15ab0262bf015ec9e41df89f718d6718c2bb1ee6 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 21 Nov 2023 02:59:58 +0900 Subject: [PATCH 32/38] inference: add reflection utility for exception type analysis This commit defines functions that mirror our tools for analyzing return types and computational effects. The key point to discuss is that this commit introduces two functions: `Base.exception_types` and `Base.exception_type`. `Base.exception_types` acts like `Base.return_types`, giving a list of exception types for each method that matches with the given call signature. On the other hand, `Base.exception_type` is akin to `Base.infer_effects`, returning a single exception type that covers all potential outcomes entailed by the given call signature. I personally lean towards the latter for its utility, particularly in testing scenarios, but I included `exception_types` too for consistency with `return_types`. I'd welcome any feedback on this approach. --- base/reflection.jl | 264 ++++++++++++++++++++++++++++++++++++++++----- test/reflection.jl | 27 +++++ 2 files changed, 263 insertions(+), 28 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index 304f7a4344fb2f..145a7a9a376d5a 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1692,15 +1692,45 @@ function code_ircode_by_type( return asts end +function _builtin_return_type(interp::Core.Compiler.AbstractInterpreter, + @nospecialize(f::Core.Builtin), @nospecialize(types)) + argtypes = Any[to_tuple_type(types).parameters...] + rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) + return Core.Compiler.widenconst(rt) +end + +function _builtin_effects(interp::Core.Compiler.AbstractInterpreter, + @nospecialize(f::Core.Builtin), @nospecialize(types)) + argtypes = Any[to_tuple_type(types).parameters...] + rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) + return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, argtypes, rt) +end + +check_generated_context(world::UInt) = + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") """ - Base.return_types(f::Function, types::DataType=default_tt(f); - world::UInt=get_world_counter(), interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) + Base.return_types( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) -> rts::Vector{Any} Return a list of possible return types for a given function `f` and argument types `types`. The list corresponds to the results of type inference on all the possible method match candidates for `f` and `types` (see also [`methods(f, types)`](@ref methods). +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `rts::Vector{Any}`: The list of return types that are figured out by inference on + methods matching with the given `f` and `types`. The list's order matches the order + returned by `methods(f, types)`. + # Example ```julia @@ -1722,25 +1752,22 @@ julia> Base.return_types(sum, (Union{Vector{Int},UnitRange{Int}},)) ``` !!! warning - The `return_types` function should not be used from generated functions; + The `Base.return_types` function should not be used from generated functions; doing so will result in an error. """ function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)); world::UInt=get_world_counter(), interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && - error("code reflection cannot be used from generated functions") + check_generated_context(world) if isa(f, Core.OpaqueClosure) _, rt = only(code_typed_opaque_closure(f)) return Any[rt] end - if isa(f, Core.Builtin) - argtypes = Any[to_tuple_type(types).parameters...] - rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) - return Any[Core.Compiler.widenconst(rt)] + rt = _builtin_return_type(interp, f, types) + return Any[rt] end - rts = [] + rts = Any[] tt = signature_type(f, types) matches = _methods_by_ftype(tt, #=lim=#-1, world)::Vector for match in matches @@ -1752,35 +1779,220 @@ function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)); end """ - infer_effects(f, types=default_tt(f); world=get_world_counter(), interp=Core.Compiler.NativeInterpreter(world)) + Base.infer_exception_types( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) -> excts::Vector{Any} -Compute the `Effects` of a function `f` with argument types `types`. The `Effects` represents the computational effects of the function call, such as whether it is free of side effects, guaranteed not to throw an exception, guaranteed to terminate, etc. The `world` and `interp` arguments specify the world counter and the native interpreter to use for the analysis. +Return a list of possible exception types for a given function `f` and argument types `types`. +The list corresponds to the results of type inference on all the possible method match +candidates for `f` and `types` (see also [`methods(f, types)`](@ref methods). +It works like [`Base.return_types`](@ref), but it infers the exception types instead of the return types. # Arguments - `f`: The function to analyze. - `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. - `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. -- `interp` (optional): The native interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. # Returns -- `effects::Effects`: The computed effects of the function call. +- `excts::Vector{Any}`: The list of exception types that are figured out by inference on + methods matching with the given `f` and `types`. The list's order matches the order + returned by `methods(f, types)`. # Example ```julia -julia> function foo(x) - y = x * 2 - return y - end; +julia> throw_if_number(::Number) = error("number is given"); + +julia> throw_if_number(::Any) = nothing; + +julia> Base.infer_exception_types(throw_if_number, (Int,)) +1-element Vector{Any}: + ErrorException + +julia> methods(throw_if_number, (Any,)) +# 2 methods for generic function "throw_if_number" from Main: + [1] throw_if_number(x::Number) + @ REPL[1]:1 + [2] throw_if_number(::Any) + @ REPL[2]:1 + +julia> Base.infer_exception_types(throw_if_number, (Any,)) +2-element Vector{Any}: + ErrorException # the result of inference on `throw_if_number(::Number)` + Union{} # the result of inference on `throw_if_number(::Any)` +``` + +!!! warning + The `Base.infer_exception_types` function should not be used from generated functions; + doing so will result in an error. +""" +function infer_exception_types(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + return Any[Any] # TODO + end + if isa(f, Core.Builtin) + effects = _builtin_effects(interp, f, types) + exct = Core.Compiler.is_nothrow(effects) ? Union{} : Any + return Any[exct] + end + excts = Any[] + tt = signature_type(f, types) + matches = _methods_by_ftype(tt, #=lim=#-1, world)::Vector + for match in matches + match = match::Core.MethodMatch + frame = Core.Compiler.typeinf_frame(interp, match, #=run_optimizer=#false) + if frame === nothing + exct = Any + else + exct = Core.Compiler.widenconst(frame.result.exc_result) + end + push!(excts, exct) + end + return excts +end + +_may_throw_methoderror(matches#=::Core.Compiler.MethodLookupResult=#) = + matches.ambig || !any(match::Core.MethodMatch->match.fully_covers, matches.matches) + +""" + Base.infer_exception_type( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> exct::Type + +Returns the type of exception potentially thrown by the function call specified by `f` and `types`. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `exct::Type`: The inferred type of exception that can be thrown by the function call + specified by the given call signature. -julia> effects = Base.infer_effects(foo, (Int,)) +!!! note + Note that, different from [`Base.infer_exception_types`](@ref), this doesn't give you the list + exception types for every possible matching method with the given `f` and `types`. + It provides a single exception type, taking into account all potential outcomes of + any function call entailed by the given signature type. + +# Example + +```julia +julia> f1(x) = x * 2; + +julia> Base.infer_exception_type(f1, (Int,)) +Union{} +``` + +The exception inferred as `Union{}` indicates that `f1(::Int)` will not throw any exception. + +```julia +julia> f2(x::Int) = x * 2; + +julia> Base.infer_exception_type(f2, (Integer,)) +MethodError +``` + +This case is pretty much the same as with `f1`, but there's a key difference to note. For +`f2`, the argument type is limited to `Int`, while the argument type is given as `Tuple{Integer}`. +Because of this, taking into account the chance of the method error entailed by the call +signature, the exception type is widened to `MethodError`. + +!!! warning + The `Base.infer_exception_type` function should not be used from generated functions; + doing so will result in an error. +""" +function infer_exception_type(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + return Any # TODO + end + if isa(f, Core.Builtin) + effects = _builtin_effects(interp, f, types) + return Core.Compiler.is_nothrow(effects) ? Union{} : Any + end + tt = signature_type(f, types) + matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) + if matches === nothing + # unanalyzable call, i.e. the interpreter world might be newer than the world where + # the `f` is defined, return the unknown exception type + return Any + end + exct = Union{} + if _may_throw_methoderror(matches) + # account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. + exct = Core.Compiler.tmerge(exct, MethodError) + end + for match in matches.matches + match = match::Core.MethodMatch + frame = Core.Compiler.typeinf_frame(interp, match, #=run_optimizer=#false) + frame === nothing && return Any + exct = Core.Compiler.tmerge(exct, Core.Compiler.widenconst(frame.result.exc_result)) + end + return exct +end + +""" + Base.infer_effects( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> effects::Effects + +Returns the possible computation effects of the function call specified by `f` and `types`. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `effects::Effects`: The computed effects of the function call specified by the given call signature. + See the documentation of [`Effects`](@ref Core.Compiler.Effects) or [`Base.@assume_effects`](@ref) + for more information on the various effect properties. + +!!! note + Note that, different from [`Base.return_types`](@ref), this doesn't give you the list + effect analysis results for every possible matching method with the given `f` and `types`. + It provides a single effect, taking into account all potential outcomes of any function + call entailed by the given signature type. + +# Example + +```julia +julia> f1(x) = x * 2; + +julia> Base.infer_effects(f1, (Int,)) (+c,+e,+n,+t,+s,+m,+i) ``` -This function will return an `Effects` object with information about the computational effects of the function `foo` when called with an `Int` argument. See the documentation for `Effects` for more information on the various effect properties. +This function will return an `Effects` object with information about the computational +effects of the function `f1` when called with an `Int` argument. + +```julia +julia> f2(x::Int) = x * 2; + +julia> Base.infer_effects(f2, (Integer,)) +(+c,+e,!n,+t,+s,+m,+i) +``` + +This case is pretty much the same as with `f1`, but there's a key difference to note. For +`f2`, the argument type is limited to `Int`, while the argument type is given as `Tuple{Integer}`. +Because of this, taking into account the chance of the method error entailed by the call +signature, the `:nothrow` bit gets tainted. !!! warning - The `infer_effects` function should not be used from generated functions; + The `Base.infer_effects` function should not be used from generated functions; doing so will result in an error. # See Also @@ -1790,13 +2002,9 @@ This function will return an `Effects` object with information about the computa function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); world::UInt=get_world_counter(), interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && - error("code reflection cannot be used from generated functions") + check_generated_context(world) if isa(f, Core.Builtin) - types = to_tuple_type(types) - argtypes = Any[types.parameters...] - rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) - return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, argtypes, rt) + return _builtin_effects(interp, f, types) end tt = signature_type(f, types) matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) @@ -1806,7 +2014,7 @@ function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); return Core.Compiler.Effects() end effects = Core.Compiler.EFFECTS_TOTAL - if matches.ambig || !any(match::Core.MethodMatch->match.fully_covers, matches.matches) + if _may_throw_methoderror(matches) # account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. effects = Core.Compiler.Effects(effects; nothrow=false) end diff --git a/test/reflection.jl b/test/reflection.jl index 7df6a76dfd0ff8..87f16deacdded1 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -1047,6 +1047,33 @@ ambig_effects_test(a, b) = 1 @test (Base.infer_effects(Core.Intrinsics.mul_int, ()); true) # `intrinsic_effects` shouldn't throw on empty `argtypes` end +@testset "infer_exception_type[s]" begin + # generic functions + @test Base.infer_exception_type(issue41694, (Int,)) == only(Base.infer_exception_types(issue41694, (Int,))) == ErrorException + @test Base.infer_exception_type((Int,)) do x + issue41694(x) + end == Base.infer_exception_types((Int,)) do x + issue41694(x) + end |> only == ErrorException + @test Base.infer_exception_type(issue41694) == only(Base.infer_exception_types(issue41694)) == ErrorException # use `default_tt` + let excts = Base.infer_exception_types(maybe_effectful, (Any,)) + @test any(==(Any), excts) + @test any(==(Union{}), excts) + end + @test Base.infer_exception_type(maybe_effectful, (Any,)) == Any + # `infer_exception_type` should account for MethodError + @test Base.infer_exception_type(issue41694, (Float64,)) == MethodError # definitive dispatch error + @test Base.infer_exception_type(issue41694, (Integer,)) == Union{MethodError,ErrorException} # possible dispatch error + @test Base.infer_exception_type(f_no_methods) == MethodError # no possible matching methods + @test Base.infer_exception_type(ambig_effects_test, (Int,Int)) == MethodError # ambiguity error + @test Base.infer_exception_type(ambig_effects_test, (Int,Any)) == MethodError # ambiguity error + # builtins + @test Base.infer_exception_type(typeof, (Any,)) === only(Base.infer_exception_types(typeof, (Any,))) === Union{} + @test Base.infer_exception_type(===, (Any,Any)) === only(Base.infer_exception_types(===, (Any,Any))) === Union{} + @test (Base.infer_exception_type(setfield!, ()); Base.infer_exception_types(setfield!, ()); true) # `infer_exception_type[s]` shouldn't throw on empty `argtypes` + @test (Base.infer_exception_type(Core.Intrinsics.mul_int, ()); Base.infer_exception_types(Core.Intrinsics.mul_int, ()); true) # `infer_exception_type[s]` shouldn't throw on empty `argtypes` +end + @test Base._methods_by_ftype(Tuple{}, -1, Base.get_world_counter()) == Any[] @test length(methods(Base.Broadcast.broadcasted, Tuple{Any, Any, Vararg})) > length(methods(Base.Broadcast.broadcasted, Tuple{Base.Broadcast.BroadcastStyle, Any, Vararg})) >= From da1258cb91a397dd4f1a31d2fd4af6fb354391bb Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Mon, 20 Nov 2023 18:16:44 +0900 Subject: [PATCH 33/38] `typeinf_local`: factor into `update_cycle_worklists!` utility --- base/compiler/abstractinterpretation.jl | 26 +++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 453b55caffa42e..a811eafbfac02a 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -3086,6 +3086,14 @@ function propagate_to_error_handler!(frame::InferenceState, currpc::Int, W::BitS end end +function update_cycle_worklists!(callback, frame::InferenceState) + for (caller, caller_pc) in frame.cycle_backedges + if callback(caller, caller_pc) + push!(caller.ip, block_for_inst(caller.cfg, caller_pc)) + end + end +end + # make as much progress on `frame` as possible (without handling cycles) function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) @assert !is_inferred(frame) @@ -3204,11 +3212,9 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) elseif isa(stmt, ReturnNode) rt = abstract_eval_value(interp, stmt.val, currstate, frame) if update_bestguess!(interp, frame, currstate, rt) - for (caller, caller_pc) in frame.cycle_backedges - if caller.ssavaluetypes[caller_pc] !== Any - # no reason to revisit if that call-site doesn't affect the final result - push!(caller.ip, block_for_inst(caller.cfg, caller_pc)) - end + update_cycle_worklists!(frame) do caller::InferenceState, caller_pc::Int + # no reason to revisit if that call-site doesn't affect the final result + return caller.ssavaluetypes[caller_pc] !== Any end end ssavaluetypes[frame.currpc] = Any @@ -3231,11 +3237,11 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState) if cur_hand == 0 if !โŠ‘(๐•ƒโ‚š, exct, frame.exc_bestguess) frame.exc_bestguess = tmerge(๐•ƒโ‚š, frame.exc_bestguess, exct) - for (caller, caller_pc) in frame.cycle_backedges - handler = caller.handler_at[caller_pc][1] - if (handler == 0 ? caller.exc_bestguess : caller.handlers[handler].exct) !== Any - push!(caller.ip, block_for_inst(caller.cfg, caller_pc)) - end + update_cycle_worklists!(frame) do caller::InferenceState, caller_pc::Int + caller_handler = caller.handler_at[caller_pc][1] + caller_exct = caller_handler == 0 ? + caller.exc_bestguess : caller.handlers[caller_handler].exct + return caller_exct !== Any end end else From 808d7ac7b8c52a1dc8eb6dc951376347f2c515c0 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Mon, 20 Nov 2023 18:17:13 +0900 Subject: [PATCH 34/38] inference: fix exception type of `typename` call --- base/compiler/abstractinterpretation.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index a811eafbfac02a..b14ebe04796c68 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2136,7 +2136,7 @@ function abstract_call_known(interp::AbstractInterpreter, @nospecialize(f), argtypes = Any[typeof(<:), argtypes[3], argtypes[2]] return abstract_call_known(interp, <:, ArgInfo(fargs, argtypes), si, sv, max_methods) elseif la == 2 && istopfunction(f, :typename) - return CallMeta(typename_static(argtypes[2]), Any, EFFECTS_TOTAL, MethodResultPure()) + return CallMeta(typename_static(argtypes[2]), Bottom, EFFECTS_TOTAL, MethodResultPure()) elseif f === Core._hasmethod return _hasmethod_tfunc(interp, argtypes, sv) end From d85cb0ec0b7cd1b2182612443624e8d2542eb8b9 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Mon, 20 Nov 2023 18:17:28 +0900 Subject: [PATCH 35/38] add missing type annotations --- base/compiler/ssair/irinterp.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/base/compiler/ssair/irinterp.jl b/base/compiler/ssair/irinterp.jl index 5b414f8786e98d..b1c3fddc3df6ef 100644 --- a/base/compiler/ssair/irinterp.jl +++ b/base/compiler/ssair/irinterp.jl @@ -51,7 +51,7 @@ function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, irsv::IRIn return RTEffects(rt, exct, effects) end -function kill_block!(ir, bb) +function kill_block!(ir::IRCode, bb::Int) # Kill the entire block stmts = ir.cfg.blocks[bb].stmts for bidx = stmts @@ -64,7 +64,6 @@ function kill_block!(ir, bb) return end - function update_phi!(irsv::IRInterpretationState, from::Int, to::Int) ir = irsv.ir if length(ir.cfg.blocks[to].preds) == 0 From 8dd0cf5c43af7fe401157c6a5ff6704b71f8fd48 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Mon, 20 Nov 2023 18:19:27 +0900 Subject: [PATCH 36/38] inference: refine `exct` information if `:nothrow` is proven --- base/compiler/abstractinterpretation.jl | 9 ++++---- base/compiler/typeinfer.jl | 16 ++++++++++---- test/compiler/inference.jl | 29 +++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index b14ebe04796c68..d99243a56e969d 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -1196,13 +1196,14 @@ function semi_concrete_eval_call(interp::AbstractInterpreter, # state = InliningState(interp) # ir = ssa_inlining_pass!(irsv.ir, state, propagate_inbounds(irsv)) effects = result.effects - if !is_nothrow(effects) - effects = Effects(effects; nothrow) + if nothrow + effects = Effects(effects; nothrow=true) end if noub - effects = Effects(effects; noub = ALWAYS_TRUE) + effects = Effects(effects; noub=ALWAYS_TRUE) end - return ConstCallResults(rt, result.exct, SemiConcreteResult(mi, ir, effects), effects, mi) + exct = refine_exception_type(result.exct, effects) + return ConstCallResults(rt, exct, SemiConcreteResult(mi, ir, effects), effects, mi) end end end diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 55026e54f777d6..de296089f43be6 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -503,6 +503,11 @@ function adjust_effects(sv::InferenceState) return ipo_effects end +function refine_exception_type(@nospecialize(exc_bestguess), ipo_effects::Effects) + ipo_effects.nothrow && return Bottom + return exc_bestguess +end + # inference completed on `me` # update the MethodInstance function finish(me::InferenceState, interp::AbstractInterpreter) @@ -539,8 +544,8 @@ function finish(me::InferenceState, interp::AbstractInterpreter) end me.result.valid_worlds = me.valid_worlds me.result.result = bestguess - me.result.ipo_effects = me.ipo_effects = adjust_effects(me) - me.result.exc_result = exc_bestguess + ipo_effects = me.result.ipo_effects = me.ipo_effects = adjust_effects(me) + me.result.exc_result = me.exc_bestguess = refine_exception_type(me.exc_bestguess, ipo_effects) if limited_ret # a parent may be cached still, but not this intermediate work: @@ -862,12 +867,13 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize isinferred = is_inferred(frame) edge = isinferred ? mi : nothing effects = isinferred ? frame.result.ipo_effects : adjust_effects(Effects(), method) # effects are adjusted already within `finish` for ipo_effects + exc_bestguess = refine_exception_type(frame.exc_bestguess, effects) # propagate newly inferred source to the inliner, allowing efficient inlining w/o deserialization: # note that this result is cached globally exclusively, we can use this local result destructively volatile_inf_result = isinferred && let inferred_src = result.src isa(inferred_src, CodeInfo) && (is_inlineable(inferred_src) || force_inline) end ? VolatileInferenceResult(result) : nothing - return EdgeCallResult(frame.bestguess, frame.exc_bestguess, edge, effects, volatile_inf_result) + return EdgeCallResult(frame.bestguess, exc_bestguess, edge, effects, volatile_inf_result) elseif frame === true # unresolvable cycle return EdgeCallResult(Any, Any, nothing, Effects()) @@ -875,7 +881,9 @@ function typeinf_edge(interp::AbstractInterpreter, method::Method, @nospecialize # return the current knowledge about this cycle frame = frame::InferenceState update_valid_age!(caller, frame.valid_worlds) - return EdgeCallResult(frame.bestguess, frame.exc_bestguess, nothing, adjust_effects(Effects(), method)) + effects = adjust_effects(Effects(), method) + exc_bestguess = refine_exception_type(frame.exc_bestguess, effects) + return EdgeCallResult(frame.bestguess, exc_bestguess, nothing, effects) end function cached_return_type(code::CodeInstance) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index b75e5e5fe87f08..8cdee8baa90e61 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -5558,3 +5558,32 @@ function foo_typed_throw_metherr() return 1 end @test Base.return_types(foo_typed_throw_metherr) |> only === Float64 + +# using `exct` information if `:nothrow` is proven +Base.@assume_effects :nothrow function sin_nothrow(x::Float64) + x == Inf && return zero(x) + return sin(x) +end +@test Base.infer_exception_type(sin_nothrow, (Float64,)) == Union{} +@test Base.return_types((Float64,)) do x + try + return sin_nothrow(x) + catch err + return err + end +end |> only === Float64 +# for semi-concrete interpretation result too +Base.@constprop :aggressive function sin_maythrow(x::Float64, maythrow::Bool) + if maythrow + return sin(x) + else + return @noinline sin_nothrow(x) + end +end +@test Base.return_types((Float64,)) do x + try + return sin_maythrow(x, false) + catch err + return err + end +end |> only === Float64 From 527af6603f77f353e9d407ebc2b14d905486bf09 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:55:13 +0900 Subject: [PATCH 37/38] inference: add reflection utility for exception type analysis (#52240) This commit defines functions that mirror our tools for analyzing return types and computational effects. The key point to discuss is that this commit introduces two functions: `Base.infer_exception_types` and `Base.infer_exception_type`. `Base.infer_exception_types` acts like `Base.return_types`, giving a list of exception types for each method that matches with the given call signature. On the other hand, `Base.infer_exception_type` is akin to `Base.infer_effects`, returning a single exception type that covers all potential outcomes entailed by the given call signature. I personally lean towards the latter for its utility, particularly in testing scenarios, but I included `infer_exception_types` too for consistency with `return_types`. I'd welcome any feedback on this approach. --- base/reflection.jl | 264 ++++++++++++++++++++++++++++++++++++++++----- test/reflection.jl | 27 +++++ 2 files changed, 263 insertions(+), 28 deletions(-) diff --git a/base/reflection.jl b/base/reflection.jl index 304f7a4344fb2f..145a7a9a376d5a 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1692,15 +1692,45 @@ function code_ircode_by_type( return asts end +function _builtin_return_type(interp::Core.Compiler.AbstractInterpreter, + @nospecialize(f::Core.Builtin), @nospecialize(types)) + argtypes = Any[to_tuple_type(types).parameters...] + rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) + return Core.Compiler.widenconst(rt) +end + +function _builtin_effects(interp::Core.Compiler.AbstractInterpreter, + @nospecialize(f::Core.Builtin), @nospecialize(types)) + argtypes = Any[to_tuple_type(types).parameters...] + rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) + return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, argtypes, rt) +end + +check_generated_context(world::UInt) = + (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && + error("code reflection cannot be used from generated functions") """ - Base.return_types(f::Function, types::DataType=default_tt(f); - world::UInt=get_world_counter(), interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) + Base.return_types( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) -> rts::Vector{Any} Return a list of possible return types for a given function `f` and argument types `types`. The list corresponds to the results of type inference on all the possible method match candidates for `f` and `types` (see also [`methods(f, types)`](@ref methods). +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `rts::Vector{Any}`: The list of return types that are figured out by inference on + methods matching with the given `f` and `types`. The list's order matches the order + returned by `methods(f, types)`. + # Example ```julia @@ -1722,25 +1752,22 @@ julia> Base.return_types(sum, (Union{Vector{Int},UnitRange{Int}},)) ``` !!! warning - The `return_types` function should not be used from generated functions; + The `Base.return_types` function should not be used from generated functions; doing so will result in an error. """ function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)); world::UInt=get_world_counter(), interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && - error("code reflection cannot be used from generated functions") + check_generated_context(world) if isa(f, Core.OpaqueClosure) _, rt = only(code_typed_opaque_closure(f)) return Any[rt] end - if isa(f, Core.Builtin) - argtypes = Any[to_tuple_type(types).parameters...] - rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) - return Any[Core.Compiler.widenconst(rt)] + rt = _builtin_return_type(interp, f, types) + return Any[rt] end - rts = [] + rts = Any[] tt = signature_type(f, types) matches = _methods_by_ftype(tt, #=lim=#-1, world)::Vector for match in matches @@ -1752,35 +1779,220 @@ function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)); end """ - infer_effects(f, types=default_tt(f); world=get_world_counter(), interp=Core.Compiler.NativeInterpreter(world)) + Base.infer_exception_types( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::NativeInterpreter=Core.Compiler.NativeInterpreter(world)) -> excts::Vector{Any} -Compute the `Effects` of a function `f` with argument types `types`. The `Effects` represents the computational effects of the function call, such as whether it is free of side effects, guaranteed not to throw an exception, guaranteed to terminate, etc. The `world` and `interp` arguments specify the world counter and the native interpreter to use for the analysis. +Return a list of possible exception types for a given function `f` and argument types `types`. +The list corresponds to the results of type inference on all the possible method match +candidates for `f` and `types` (see also [`methods(f, types)`](@ref methods). +It works like [`Base.return_types`](@ref), but it infers the exception types instead of the return types. # Arguments - `f`: The function to analyze. - `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. - `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. -- `interp` (optional): The native interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. # Returns -- `effects::Effects`: The computed effects of the function call. +- `excts::Vector{Any}`: The list of exception types that are figured out by inference on + methods matching with the given `f` and `types`. The list's order matches the order + returned by `methods(f, types)`. # Example ```julia -julia> function foo(x) - y = x * 2 - return y - end; +julia> throw_if_number(::Number) = error("number is given"); + +julia> throw_if_number(::Any) = nothing; + +julia> Base.infer_exception_types(throw_if_number, (Int,)) +1-element Vector{Any}: + ErrorException + +julia> methods(throw_if_number, (Any,)) +# 2 methods for generic function "throw_if_number" from Main: + [1] throw_if_number(x::Number) + @ REPL[1]:1 + [2] throw_if_number(::Any) + @ REPL[2]:1 + +julia> Base.infer_exception_types(throw_if_number, (Any,)) +2-element Vector{Any}: + ErrorException # the result of inference on `throw_if_number(::Number)` + Union{} # the result of inference on `throw_if_number(::Any)` +``` + +!!! warning + The `Base.infer_exception_types` function should not be used from generated functions; + doing so will result in an error. +""" +function infer_exception_types(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + return Any[Any] # TODO + end + if isa(f, Core.Builtin) + effects = _builtin_effects(interp, f, types) + exct = Core.Compiler.is_nothrow(effects) ? Union{} : Any + return Any[exct] + end + excts = Any[] + tt = signature_type(f, types) + matches = _methods_by_ftype(tt, #=lim=#-1, world)::Vector + for match in matches + match = match::Core.MethodMatch + frame = Core.Compiler.typeinf_frame(interp, match, #=run_optimizer=#false) + if frame === nothing + exct = Any + else + exct = Core.Compiler.widenconst(frame.result.exc_result) + end + push!(excts, exct) + end + return excts +end + +_may_throw_methoderror(matches#=::Core.Compiler.MethodLookupResult=#) = + matches.ambig || !any(match::Core.MethodMatch->match.fully_covers, matches.matches) + +""" + Base.infer_exception_type( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> exct::Type + +Returns the type of exception potentially thrown by the function call specified by `f` and `types`. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `exct::Type`: The inferred type of exception that can be thrown by the function call + specified by the given call signature. -julia> effects = Base.infer_effects(foo, (Int,)) +!!! note + Note that, different from [`Base.infer_exception_types`](@ref), this doesn't give you the list + exception types for every possible matching method with the given `f` and `types`. + It provides a single exception type, taking into account all potential outcomes of + any function call entailed by the given signature type. + +# Example + +```julia +julia> f1(x) = x * 2; + +julia> Base.infer_exception_type(f1, (Int,)) +Union{} +``` + +The exception inferred as `Union{}` indicates that `f1(::Int)` will not throw any exception. + +```julia +julia> f2(x::Int) = x * 2; + +julia> Base.infer_exception_type(f2, (Integer,)) +MethodError +``` + +This case is pretty much the same as with `f1`, but there's a key difference to note. For +`f2`, the argument type is limited to `Int`, while the argument type is given as `Tuple{Integer}`. +Because of this, taking into account the chance of the method error entailed by the call +signature, the exception type is widened to `MethodError`. + +!!! warning + The `Base.infer_exception_type` function should not be used from generated functions; + doing so will result in an error. +""" +function infer_exception_type(@nospecialize(f), @nospecialize(types=default_tt(f)); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) + check_generated_context(world) + if isa(f, Core.OpaqueClosure) + return Any # TODO + end + if isa(f, Core.Builtin) + effects = _builtin_effects(interp, f, types) + return Core.Compiler.is_nothrow(effects) ? Union{} : Any + end + tt = signature_type(f, types) + matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) + if matches === nothing + # unanalyzable call, i.e. the interpreter world might be newer than the world where + # the `f` is defined, return the unknown exception type + return Any + end + exct = Union{} + if _may_throw_methoderror(matches) + # account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. + exct = Core.Compiler.tmerge(exct, MethodError) + end + for match in matches.matches + match = match::Core.MethodMatch + frame = Core.Compiler.typeinf_frame(interp, match, #=run_optimizer=#false) + frame === nothing && return Any + exct = Core.Compiler.tmerge(exct, Core.Compiler.widenconst(frame.result.exc_result)) + end + return exct +end + +""" + Base.infer_effects( + f, types=default_tt(f); + world::UInt=get_world_counter(), + interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) -> effects::Effects + +Returns the possible computation effects of the function call specified by `f` and `types`. + +# Arguments +- `f`: The function to analyze. +- `types` (optional): The argument types of the function. Defaults to the default tuple type of `f`. +- `world` (optional): The world counter to use for the analysis. Defaults to the current world counter. +- `interp` (optional): The abstract interpreter to use for the analysis. Defaults to a new `Core.Compiler.NativeInterpreter` with the specified `world`. + +# Returns +- `effects::Effects`: The computed effects of the function call specified by the given call signature. + See the documentation of [`Effects`](@ref Core.Compiler.Effects) or [`Base.@assume_effects`](@ref) + for more information on the various effect properties. + +!!! note + Note that, different from [`Base.return_types`](@ref), this doesn't give you the list + effect analysis results for every possible matching method with the given `f` and `types`. + It provides a single effect, taking into account all potential outcomes of any function + call entailed by the given signature type. + +# Example + +```julia +julia> f1(x) = x * 2; + +julia> Base.infer_effects(f1, (Int,)) (+c,+e,+n,+t,+s,+m,+i) ``` -This function will return an `Effects` object with information about the computational effects of the function `foo` when called with an `Int` argument. See the documentation for `Effects` for more information on the various effect properties. +This function will return an `Effects` object with information about the computational +effects of the function `f1` when called with an `Int` argument. + +```julia +julia> f2(x::Int) = x * 2; + +julia> Base.infer_effects(f2, (Integer,)) +(+c,+e,!n,+t,+s,+m,+i) +``` + +This case is pretty much the same as with `f1`, but there's a key difference to note. For +`f2`, the argument type is limited to `Int`, while the argument type is given as `Tuple{Integer}`. +Because of this, taking into account the chance of the method error entailed by the call +signature, the `:nothrow` bit gets tainted. !!! warning - The `infer_effects` function should not be used from generated functions; + The `Base.infer_effects` function should not be used from generated functions; doing so will result in an error. # See Also @@ -1790,13 +2002,9 @@ This function will return an `Effects` object with information about the computa function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); world::UInt=get_world_counter(), interp::Core.Compiler.AbstractInterpreter=Core.Compiler.NativeInterpreter(world)) - (ccall(:jl_is_in_pure_context, Bool, ()) || world == typemax(UInt)) && - error("code reflection cannot be used from generated functions") + check_generated_context(world) if isa(f, Core.Builtin) - types = to_tuple_type(types) - argtypes = Any[types.parameters...] - rt = Core.Compiler.builtin_tfunction(interp, f, argtypes, nothing) - return Core.Compiler.builtin_effects(Core.Compiler.typeinf_lattice(interp), f, argtypes, rt) + return _builtin_effects(interp, f, types) end tt = signature_type(f, types) matches = Core.Compiler.findall(tt, Core.Compiler.method_table(interp)) @@ -1806,7 +2014,7 @@ function infer_effects(@nospecialize(f), @nospecialize(types=default_tt(f)); return Core.Compiler.Effects() end effects = Core.Compiler.EFFECTS_TOTAL - if matches.ambig || !any(match::Core.MethodMatch->match.fully_covers, matches.matches) + if _may_throw_methoderror(matches) # account for the fact that we may encounter a MethodError with a non-covered or ambiguous signature. effects = Core.Compiler.Effects(effects; nothrow=false) end diff --git a/test/reflection.jl b/test/reflection.jl index 7df6a76dfd0ff8..87f16deacdded1 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -1047,6 +1047,33 @@ ambig_effects_test(a, b) = 1 @test (Base.infer_effects(Core.Intrinsics.mul_int, ()); true) # `intrinsic_effects` shouldn't throw on empty `argtypes` end +@testset "infer_exception_type[s]" begin + # generic functions + @test Base.infer_exception_type(issue41694, (Int,)) == only(Base.infer_exception_types(issue41694, (Int,))) == ErrorException + @test Base.infer_exception_type((Int,)) do x + issue41694(x) + end == Base.infer_exception_types((Int,)) do x + issue41694(x) + end |> only == ErrorException + @test Base.infer_exception_type(issue41694) == only(Base.infer_exception_types(issue41694)) == ErrorException # use `default_tt` + let excts = Base.infer_exception_types(maybe_effectful, (Any,)) + @test any(==(Any), excts) + @test any(==(Union{}), excts) + end + @test Base.infer_exception_type(maybe_effectful, (Any,)) == Any + # `infer_exception_type` should account for MethodError + @test Base.infer_exception_type(issue41694, (Float64,)) == MethodError # definitive dispatch error + @test Base.infer_exception_type(issue41694, (Integer,)) == Union{MethodError,ErrorException} # possible dispatch error + @test Base.infer_exception_type(f_no_methods) == MethodError # no possible matching methods + @test Base.infer_exception_type(ambig_effects_test, (Int,Int)) == MethodError # ambiguity error + @test Base.infer_exception_type(ambig_effects_test, (Int,Any)) == MethodError # ambiguity error + # builtins + @test Base.infer_exception_type(typeof, (Any,)) === only(Base.infer_exception_types(typeof, (Any,))) === Union{} + @test Base.infer_exception_type(===, (Any,Any)) === only(Base.infer_exception_types(===, (Any,Any))) === Union{} + @test (Base.infer_exception_type(setfield!, ()); Base.infer_exception_types(setfield!, ()); true) # `infer_exception_type[s]` shouldn't throw on empty `argtypes` + @test (Base.infer_exception_type(Core.Intrinsics.mul_int, ()); Base.infer_exception_types(Core.Intrinsics.mul_int, ()); true) # `infer_exception_type[s]` shouldn't throw on empty `argtypes` +end + @test Base._methods_by_ftype(Tuple{}, -1, Base.get_world_counter()) == Any[] @test length(methods(Base.Broadcast.broadcasted, Tuple{Any, Any, Vararg})) > length(methods(Base.Broadcast.broadcasted, Tuple{Base.Broadcast.BroadcastStyle, Any, Vararg})) >= From a6c656e6c47ff2b1237c92e90ba73ac267fc1dc0 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Tue, 21 Nov 2023 18:06:36 -0300 Subject: [PATCH 38/38] Make have_fma consistent between interpreter and compiled (#52206) Currently the interpreter always returns false. Which isn't very good. Make it follow whatever the JIT will do. --- src/jl_exported_funcs.inc | 1 + src/llvm-cpufeatures.cpp | 4 ++-- src/processor.h | 2 ++ src/processor_arm.cpp | 16 ++++++++++++++++ src/processor_fallback.cpp | 5 +++++ src/processor_x86.cpp | 11 +++++++++++ src/runtime_intrinsics.c | 11 ++++++++--- test/llvmpasses/cpu-features.ll | 2 ++ test/sysinfo.jl | 2 ++ 9 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/jl_exported_funcs.inc b/src/jl_exported_funcs.inc index 6ae91f5cc32d62..6c7af9bf2e245c 100644 --- a/src/jl_exported_funcs.inc +++ b/src/jl_exported_funcs.inc @@ -200,6 +200,7 @@ XX(jl_get_binding_wr) \ XX(jl_get_cpu_name) \ XX(jl_get_cpu_features) \ + XX(jl_cpu_has_fma) \ XX(jl_get_current_task) \ XX(jl_get_default_sysimg_path) \ XX(jl_get_excstack) \ diff --git a/src/llvm-cpufeatures.cpp b/src/llvm-cpufeatures.cpp index 033a43720c96d8..2539c5cd2e37ca 100644 --- a/src/llvm-cpufeatures.cpp +++ b/src/llvm-cpufeatures.cpp @@ -59,7 +59,7 @@ static bool have_fma(Function &intr, Function &caller, const Triple &TT) JL_NOTS StringRef FS = FSAttr.isValid() ? FSAttr.getValueAsString() : jl_ExecutionEngine->getTargetFeatureString(); - SmallVector Features; + SmallVector Features; FS.split(Features, ','); for (StringRef Feature : Features) if (TT.isARM()) { @@ -67,7 +67,7 @@ static bool have_fma(Function &intr, Function &caller, const Triple &TT) JL_NOTS return typ == "f32" || typ == "f64"; else if (Feature == "+vfp4sp") return typ == "f32"; - } else { + } else if (TT.isX86()) { if (Feature == "+fma" || Feature == "+fma4") return typ == "f32" || typ == "f64"; } diff --git a/src/processor.h b/src/processor.h index c22f8cff34a633..a4c8deb9a87961 100644 --- a/src/processor.h +++ b/src/processor.h @@ -226,6 +226,8 @@ JL_DLLEXPORT jl_value_t *jl_get_cpu_name(void); // Return the features of the host CPU as a julia string. JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void); // Dump the name and feature set of the host CPU +JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits); +// Check if the CPU has native FMA instructions; // For debugging only JL_DLLEXPORT void jl_dump_host_cpu(void); JL_DLLEXPORT jl_value_t* jl_check_pkgimage_clones(char* data); diff --git a/src/processor_arm.cpp b/src/processor_arm.cpp index 1cb209dbbcd627..6a09e00cf23e7b 100644 --- a/src/processor_arm.cpp +++ b/src/processor_arm.cpp @@ -1808,6 +1808,22 @@ JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void) return jl_cstr_to_string(jl_get_cpu_features_llvm().c_str()); } +JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits) +{ +#ifdef _CPU_AARCH64_ + return jl_true; +#else + TargetData target = jit_targets.front(); + FeatureList features = target.en.features; + if (bits == 32 && test_nbit(features, Feature::vfp4sp)) + return jl_true; + else if ((bits == 64 || bits == 32) && test_nbit(features, Feature::vfp4)) + return jl_true; + else + return jl_false; +#endif +} + jl_image_t jl_init_processor_sysimg(void *hdl) { if (!jit_targets.empty()) diff --git a/src/processor_fallback.cpp b/src/processor_fallback.cpp index 8c343aa9824702..399d31af05ebf5 100644 --- a/src/processor_fallback.cpp +++ b/src/processor_fallback.cpp @@ -172,6 +172,11 @@ JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void) return jl_cstr_to_string(jl_get_cpu_features_llvm().c_str()); } +JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits) +{ + return jl_false; // Match behaviour of have_fma in src/llvm-cpufeatures.cpp (assume false) +} + JL_DLLEXPORT void jl_dump_host_cpu(void) { jl_safe_printf("CPU: %s\n", host_cpu_name().c_str()); diff --git a/src/processor_x86.cpp b/src/processor_x86.cpp index 361a8c61675416..3f8c62acaed112 100644 --- a/src/processor_x86.cpp +++ b/src/processor_x86.cpp @@ -4,6 +4,7 @@ // CPUID +#include "julia.h" extern "C" JL_DLLEXPORT void jl_cpuid(int32_t CPUInfo[4], int32_t InfoType) { asm volatile ( @@ -1062,6 +1063,16 @@ JL_DLLEXPORT jl_value_t *jl_get_cpu_features(void) return jl_cstr_to_string(jl_get_cpu_features_llvm().c_str()); } +JL_DLLEXPORT jl_value_t *jl_cpu_has_fma(int bits) +{ + TargetData target = jit_targets.front(); + FeatureList features = target.en.features; + if ((bits == 32 || bits == 64) && (test_nbit(features, Feature::fma) || test_nbit(features, Feature::fma4))) + return jl_true; + else + return jl_false; +} + jl_image_t jl_init_processor_sysimg(void *hdl) { if (!jit_targets.empty()) diff --git a/src/runtime_intrinsics.c b/src/runtime_intrinsics.c index 7b4ddcd6e8b29c..4569c2081ae880 100644 --- a/src/runtime_intrinsics.c +++ b/src/runtime_intrinsics.c @@ -1671,10 +1671,15 @@ un_fintrinsic(trunc_float,trunc_llvm) un_fintrinsic(rint_float,rint_llvm) un_fintrinsic(sqrt_float,sqrt_llvm) un_fintrinsic(sqrt_float,sqrt_llvm_fast) +jl_value_t *jl_cpu_has_fma(int bits); JL_DLLEXPORT jl_value_t *jl_have_fma(jl_value_t *typ) { - JL_TYPECHK(have_fma, datatype, typ); - // TODO: run-time feature check? - return jl_false; + JL_TYPECHK(have_fma, datatype, typ); // TODO what about float16/bfloat16? + if (typ == (jl_value_t*)jl_float32_type) + return jl_cpu_has_fma(32); + else if (typ == (jl_value_t*)jl_float64_type) + return jl_cpu_has_fma(64); + else + return jl_false; } diff --git a/test/llvmpasses/cpu-features.ll b/test/llvmpasses/cpu-features.ll index 5125743248fec8..323f5e24015e9a 100644 --- a/test/llvmpasses/cpu-features.ll +++ b/test/llvmpasses/cpu-features.ll @@ -3,6 +3,8 @@ ; RUN: opt -enable-new-pm=1 --opaque-pointers=0 --load-pass-plugin=libjulia-codegen%shlibext -passes='CPUFeatures,simplifycfg' -S %s | FileCheck %s ; RUN: opt -enable-new-pm=1 --opaque-pointers=1 --load-pass-plugin=libjulia-codegen%shlibext -passes='CPUFeatures,simplifycfg' -S %s | FileCheck %s +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128-ni:10:11:12:13" +target triple = "x86_64-linux-gnu" declare i1 @julia.cpu.have_fma.f64() declare double @with_fma(double %0, double %1, double %2) diff --git a/test/sysinfo.jl b/test/sysinfo.jl index e8194b113ba24b..8864e3a48efc72 100644 --- a/test/sysinfo.jl +++ b/test/sysinfo.jl @@ -12,6 +12,8 @@ Base.Sys.loadavg() @test length(ccall(:jl_get_cpu_name, String, ())) != 0 @test length(ccall(:jl_get_cpu_features, String, ())) >= 0 +foo_fma() = Core.Intrinsics.have_fma(Int64) +@test ccall(:jl_cpu_has_fma, Bool, (Cint,), 64) == foo_fma() if Sys.isunix() mktempdir() do tempdir