diff --git a/Compiler/test/codegen.jl b/Compiler/test/codegen.jl index 4af044fe38c7a..0e0d035aa5edb 100644 --- a/Compiler/test/codegen.jl +++ b/Compiler/test/codegen.jl @@ -887,7 +887,7 @@ let io = IOBuffer() code_llvm(io,foo54166, (Vector{Union{Missing,Int}}, Int, Int), dump_module=true, raw=true) str = String(take!(io)) @test !occursin("jtbaa_unionselbyte", str) - @test occursin("jtbaa_arrayselbyte", str) + @test occursin("jtbaa_memoryselbyte", str) end ex54166 = Union{Missing, Int64}[missing -2; missing -2]; diff --git a/base/errorshow.jl b/base/errorshow.jl index 77007b65d020b..9e0efe4f9c5ff 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -1196,7 +1196,7 @@ end function _backtrace_remove_kwcall_frames!(trace) todelete = findall(trace) do (frame, _) - code = frame.linfo + code = StackTraces.frame_mi(frame) if code isa MethodInstance def = code.def if def isa Method && def.name !== :kwcall && def.sig <: Tuple{typeof(Core.kwcall),NamedTuple,Any,Vararg} @@ -1205,7 +1205,7 @@ function _backtrace_remove_kwcall_frames!(trace) # the argument list, since it has the right line number info) return true end - else + else # this branch may not be needed, from before current keyword argument handling frame.func === :kwcall && return true end return false diff --git a/base/essentials.jl b/base/essentials.jl index 4d8d8abd915af..565cb4c34a136 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -745,6 +745,11 @@ unsafe_convert(::Type{P}, x::Ptr) where {P<:Ptr} = convert(P, x) unsafe_convert(::Type{Ptr{UInt8}}, s::String) = ccall(:jl_string_ptr, Ptr{UInt8}, (Any,), s) unsafe_convert(::Type{Ptr{Int8}}, s::String) = ccall(:jl_string_ptr, Ptr{Int8}, (Any,), s) +# We don't add any _reinterpret methods until we include reinterpretarray.jl, +# but defining the function up front avoids a whole lot of invalidations when we +# do. +function _reinterpret end + """ reinterpret(::Type{Out}, x::In) diff --git a/base/rational.jl b/base/rational.jl index 18bbf606ddc7f..ac3571547c64a 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -685,7 +685,7 @@ function hash(x::Rational{<:BitInteger64}, h::UInt) end end h = hash_integer(pow, h) - h = hash_integer((pow > 0) ? (num << (pow % 64)) : num, h) + h = hash_integer((pow > 0) ? (num << (pow % 8)) : num, h) return h end diff --git a/base/reflection.jl b/base/reflection.jl index f494041dfe75f..d54b2d51b3c10 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1130,13 +1130,20 @@ function hasmethod(f, t, kwnames::Tuple{Vararg{Symbol}}; world::UInt=get_world_c end """ - fbody = bodyfunction(basemethod::Method) + fbody = bodyfunction(basemethod::Method; world::UInt=Base.get_world_counter()) Find the keyword "body function" (the function that contains the body of the method as written, called after all missing keyword-arguments have been assigned default values). `basemethod` is the method you obtain via [`which`](@ref) or [`methods`](@ref). + +The binding of the body function is looked up in the world age given by `world`, which +defaults to the current world counter. + +!!! compat "Julia 1.14" + The `world` keyword argument requires Julia 1.14 or later. Before Julia 1.14, + the binding was looked up in the world age of the calling task. """ -function bodyfunction(basemethod::Method) +function bodyfunction(basemethod::Method; world::UInt=get_world_counter()) fmod = parentmodule(basemethod) # The lowered code for `basemethod` should look like # %1 = mkw(kwvalues..., #self#, args...) @@ -1149,7 +1156,7 @@ function bodyfunction(basemethod::Method) fsym = callexpr.args[0] while true if isa(fsym, Symbol) - return getfield(fmod, fsym) + return invoke_in_world(world, getglobal, fmod, fsym) elseif isa(fsym, GlobalRef) if fsym.mod === Core && fsym.name === :_apply fsym = callexpr.args[1] @@ -1157,9 +1164,9 @@ function bodyfunction(basemethod::Method) fsym = callexpr.args[2] end if isa(fsym, Symbol) - return getfield(fmod, fsym)::Function + return invoke_in_world(world, getglobal, fmod, fsym)::Function elseif isa(fsym, GlobalRef) - return getfield(fsym.mod, fsym.name)::Function + return invoke_in_world(world, getglobal, fsym.mod, fsym.name)::Function elseif isa(fsym, Core.SSAValue) fsym = ast.code[fsym.id-1] else diff --git a/contrib/upstream-sync/reports/346d02acc25c3c4c7d56f2fa825a0af0b6b5cd79.json b/contrib/upstream-sync/reports/346d02acc25c3c4c7d56f2fa825a0af0b6b5cd79.json new file mode 100644 index 0000000000000..2cdc7cf31747d --- /dev/null +++ b/contrib/upstream-sync/reports/346d02acc25c3c4c7d56f2fa825a0af0b6b5cd79.json @@ -0,0 +1,263 @@ +{ + "plan": { + "base_sha": "57d27b961063dae2323809b7ac71d10d5b7154de", + "previous_sha": "81093121d4e6cf469154d977e2ecd6b7488ea0de", + "fetched_sha": "74fb2bea8d751b14e3413a01c12979de46c250b2", + "target_sha": "346d02acc25c3c4c7d56f2fa825a0af0b6b5cd79", + "branch": "sync/julia-81093121d4e6-346d02acc25c", + "status": "ready", + "manual_reasons": [], + "commits": [ + { + "sha": "a414ee183a31f255b75f82f0dd110ac64e74ab9c", + "subject": "codegen: redesign aliasinfo again (#62904)", + "paths": [ + "Compiler/test/codegen.jl", + "src/cgutils.cpp", + "src/clangsa/GCCheckerReporting.cpp", + "src/codegen.cpp", + "src/intrinsics.cpp", + "src/llvm-codegen-shared.h", + "src/llvm-late-gc-lowering.cpp", + "test/llvmpasses/gcroots.ll", + "test/llvmpasses/refinements.ll" + ], + "changed_lines": 682, + "patch_bytes": 68927, + "binary": false + }, + { + "sha": "7485f7ad53e7a52bc834bbcbd2bfbcfcd8ea2810", + "subject": "Declare _reinterpret up front to avoid a ton of invalidations (#62871)", + "paths": [ + "base/essentials.jl" + ], + "changed_lines": 5, + "patch_bytes": 654, + "binary": false + }, + { + "sha": "22e4766fdef43e6b52949531596ea8d924b1b328", + "subject": "deps: pass `CMAKE_POLICY_VERSION_MINIMUM` when configuring ittapi (#63265)", + "paths": [ + "deps/ittapi.mk" + ], + "changed_lines": 2, + "patch_bytes": 712, + "binary": false + }, + { + "sha": "377e4c1869f827140ad5491d123dfda878c50de6", + "subject": "deps: do not build a dependency in order to uninstall it (#63264)", + "paths": [ + "deps/tools/common.mk", + "deps/tools/uninstallers.mk" + ], + "changed_lines": 10, + "patch_bytes": 1545, + "binary": false + }, + { + "sha": "a0c011e53a598d22360a1859ee69471ed719acc4", + "subject": "fixup big rational hashing (#63266)", + "paths": [ + "base/rational.jl", + "test/gmp.jl" + ], + "changed_lines": 13, + "patch_bytes": 1420, + "binary": false + }, + { + "sha": "4d76908384bc853a4faeab2172d669c4ba8ec4ef", + "subject": "gc: never skip weak-processing pages in the quick sweep (#63234)", + "paths": [ + "src/gc-stock.c", + "test/cancellation.jl" + ], + "changed_lines": 30, + "patch_bytes": 2933, + "binary": false + }, + { + "sha": "a261da0be47a8b359c42fe0edfe2d1703c87964a", + "subject": "gc: Make stock-GC memory copies safe under concurrent mutation (#63244)", + "paths": [ + "src/gc-wb-stock.h", + "src/julia.h", + "test/gc/copyto.jl" + ], + "changed_lines": 214, + "patch_bytes": 10530, + "binary": false + }, + { + "sha": "035f7c9c2348982153d39d3a552dfe0d5c7b1aae", + "subject": "doc: split latex symbols from emojis", + "paths": [ + "doc/make.jl", + "doc/src/manual/emoji-input.md", + "doc/src/manual/unicode-input.md", + "stdlib/REPL/docs/src/index.md" + ], + "changed_lines": 198, + "patch_bytes": 9179, + "binary": false + }, + { + "sha": "e177e01f81c8571b2e21e84964446704b7645942", + "subject": "Merge pull request #63273 from JuliaLang/rk/split-latex-emoji-docs", + "paths": [ + "doc/make.jl", + "doc/src/manual/emoji-input.md", + "doc/src/manual/unicode-input.md", + "stdlib/REPL/docs/src/index.md" + ], + "changed_lines": 198, + "patch_bytes": 9179, + "binary": false + }, + { + "sha": "58e8bb8cb772cbcb030a9affb061e0e7cf0265f9", + "subject": "Hide kwcall frames in stacktraces again (#63192)", + "paths": [ + "base/errorshow.jl", + "test/errorshow.jl" + ], + "changed_lines": 54, + "patch_bytes": 4019, + "binary": false + }, + { + "sha": "346d02acc25c3c4c7d56f2fa825a0af0b6b5cd79", + "subject": "reflection: Add `world` keyword argument to `Base.bodyfunction` (#63239)", + "paths": [ + "base/reflection.jl", + "test/reflection.jl" + ], + "changed_lines": 31, + "patch_bytes": 3132, + "binary": false + } + ], + "stdlib_updates": [], + "limits": { + "upstream": "https://github.com/JuliaLang/julia.git", + "branch": "master", + "integrated_sha": "81093121d4e6cf469154d977e2ecd6b7488ea0de", + "max_commits": 20, + "max_changed_lines": 2500, + "max_patch_bytes": 250000 + }, + "remaining_first_parent_commits": [ + "decc791652f4becc636683071b94ddce617b8163", + "a6714ba33042a5aefca3c71c018c901ab46cda19", + "d96818f42f442fd51d506dc900cb2054004df78c", + "e9d1f89e1c79a44c4870fdfe6684ebe617c6e24b", + "6a1694d848366c4e75da889c2a060ebe58ea5cef", + "d024567ba68b457d767953002b34fe3dcc30f844", + "bbb0b4bb07506fa210a2aeae9e5042e1df6d90a2", + "3d9f720a065f30df545b0de30ce65b18115b0cba", + "286cd1c7abff3b55be76a6f6eaad89fb2d573235", + "26d28a1e242ed7947b2c920e328c4e0a0be6fb30", + "74fb2bea8d751b14e3413a01c12979de46c250b2" + ] + }, + "review": { + "target_sha": "346d02acc25c3c4c7d56f2fa825a0af0b6b5cd79", + "decision": "propose", + "summary": "Every incoming commit was reviewed in a separate job against the full merged batch.", + "unresolved": [], + "commits": [ + { + "sha": "a414ee183a31f255b75f82f0dd110ac64e74ab9c", + "risk": "high", + "subsystems": "LLVM code generation and alias analysis in src/codegen.cpp; late-GC metadata recognition in src/llvm-codegen-shared.h and src/llvm-late-gc-lowering.cpp; memory lowering in src/cgutils.cpp; intrinsic lowering in src/intrinsics.cpp; Clang analyzer diagnostics in src/clangsa/GCCheckerReporting.cpp; tests in Compiler/test/codegen.jl and test/llvmpasses/gcroots.ll/refinements.ll.", + "indexing_implications": "Region values (0 and bit flags) are memory-region enum values, not collection positions. LLVM metadata operands (getOperand(0), getOperand(3)), TBAA offset 0, vector capacities, and pointer/field counts retain their native meanings. Late-GC Number(S,V) values are internal SSA identifiers. Existing zero-origin paths in cgutils.cpp (emit_memoryref_direct, emit_memoryref, emit_memoryref_offset), codegen.cpp field lowering, and intrinsics.cpp pointerref/pointerset were inspected and are not changed by this commit.", + "adaptations": "None. The merged code changes alias metadata and GC-rooting classification only. memorybuf_aliasinfo, memoryselbyte, ai_is_private, and best_field_aliasinfo classify storage regions rather than translating positions. No Julia storage-boundary +1/-1 conversion or compiler-ID renumbering is introduced or lost.", + "tests": "Recommended, not executed: make -C test/llvmpasses refinements.ll; make -C test/llvmpasses gcroots.ll; run the foo54166 codegen test in Compiler/test/codegen.jl; run make -C test compiler-foundation in both supported compilation modes; rebuild before execution. Run the c-static-analysis procedure for codegen, late-GC, and Clang-analyzer units. Include checks that memoryref position 0 reaches the first element and that rooted immutable/const regions remain distinct from mutable memorybuf regions. No builds or tests were run." + }, + { + "sha": "7485f7ad53e7a52bc834bbcbd2bfbcfcd8ea2810", + "risk": "low", + "subsystems": "Base bootstrap and scalar/array reinterpretation in `base/essentials.jl`, `base/Base.jl`, and `base/reinterpretarray.jl`.", + "indexing_implications": "No indexing or dimension values change. `_reinterpret` is a function binding, not a position, count, sentinel, or external API value. `reinterpret` calls it for non-primitive values, and `reinterpretarray.jl` later supplies the implementation; existing zero-origin axes and storage offsets remain intact.", + "adaptations": "None. The declaration is compatible with Joolia's staged include order and does not require origin conversion.", + "tests": "Recommended, not executed: rebuild and run `make -C test base-foundation` plus the relevant `reinterpretarray` target. Cover scalar fallback, zero-origin first/last array positions, zero-length arrays, and multichannel reinterpretation." + }, + { + "sha": "22e4766fdef43e6b52949531596ea8d924b1b328", + "risk": "low", + "subsystems": "deps/ittapi CMake configuration and external dependency source builds", + "indexing_implications": "Inspected deps/ittapi.mk, deps/tools/common.mk, deps/ittapi.version, deps/Makefile, and deps/llvm.mk. The new 3.5 literal is a CMake policy-version value, not a position, dimension, count, identifier, or sentinel. Build paths and Make targets are dependency-graph names.", + "adaptations": "None. The merged code matches upstream intent; zero-origin translation would incorrectly alter CMake behavior. No Julia indexing contract is involved.", + "tests": "Recommended, not executed: make -C deps USE_BINARYBUILDER=0 configure-ittapi; make -C deps USE_BINARYBUILDER=0 compile-ittapi. No Julia regression test is meaningful for this build-only change." + }, + { + "sha": "377e4c1869f827140ad5491d123dfda878c50de6", + "risk": "low", + "subsystems": "Dependency build and uninstall infrastructure in deps/tools/common.mk and deps/tools/uninstallers.mk.", + "indexing_implications": "No Julia indexing is involved. Make `word 2`, `wordlist 3,99`, and `firstword` are one-based Make field operations, not Julia positions. Filesystem checks are boolean presence states; absent manifests must not trigger staging or compilation.", + "adaptations": "None. The merged files match the upstream change and correctly avoid building an uninstalled dependency while preserving reconstruction of staging data for an installed dependency whose tar is missing.", + "tests": "Not executed per restrictions. Recommend isolated-prefix checks for uninstall with no manifest, uninstall with a manifest but missing staging tar, `make -C deps USE_BINARYBUILDER=0 compile-zlib`, cleanup/uninstall, and the full CI gate." + }, + { + "sha": "a0c011e53a598d22360a1859ee69471ed719acc4", + "risk": "low", + "subsystems": "Base rational hashing in base/rational.jl (`hash(::Rational{<:BitInteger64})`); GMP/BigInt hashing coverage in test/gmp.jl; generic real hashing in base/hashing.jl and zero-origin `UnsafeLimbView` handling in base/gmp.jl.", + "indexing_implications": "`pow % 8` selects a bit shift within a byte; `pow` is a binary exponent and 8 is a byte width, not a position or dimension. Test variables `i`, `shift`, and `sgn` are exponent/sign/value generators; zero adds an exponent case. `T` iterates types and `h` iterates hash seeds, so neither is an index. The producer/consumer path from rational normalization through BigInt byte hashing was inspected. Joolia's `UnsafeLimbView` already uses zero-origin indexing and limb offsets. No sentinel, axis, tuple position, dimension argument, or foreign index changes.", + "adaptations": "None. The merged `base/rational.jl` correction preserves upstream intent by using the low three exponent bits for the byte-aligned hash payload shift, matching the generic rational path in base/hashing.jl. The added test/gmp.jl cases compare equivalent `Rational{T}` and `Rational{BigInt}` hashes across supported integer widths and seeds. Existing Joolia BigInt storage adaptations in base/gmp.jl are compatible, so changing these expressions or test values would be incorrect.", + "tests": "Recommended on a built Joolia artifact: `JULIA_TEST_FAILFAST=1 ./usr/bin/joolia --startup-file=no --check-bounds=yes test/runtests.jl gmp`, covering zero, positive and negative numerators, byte-boundary shifts, supported `BitInteger` widths, and all three hash seeds. Then run `python3 contrib/ci/run.py --jobs 4`. No tests were executed in this review job." + }, + { + "sha": "4d76908384bc853a4faeab2172d669c4ba8ec4ef", + "risk": "high", + "subsystems": "Stock GC page sweeping and cancellation-token weak child-list cleanup.", + "indexing_implications": "`has_weak_processing` and `has_young` are boolean flags; `nold`, `prev_nold`, `nfree`, `nparents`, and loop variables are counts or traversal counters, not collection positions. The existing zero-based `weak_processing_objs` traversal is unchanged. The guards in `gc_sweep_page` and `gc_sweep_prescan` preserve scanning whenever weak-processing work exists.", + "adaptations": "None. Joolia's existing wholesale-dead-page guard already preserves flagged pages, and the merged upstream change correctly extends that protection to both quick-sweep paths. The test ranges `1:n` and `1:20` represent repetition counts, not positions, so they must remain unchanged.", + "tests": "Recommended, not executed: rebuild and run `make -C src analyze-gc-stock -j8 --output-sync`; run the focused cancellation test with `JULIA_TEST_FAILFAST=1` (for example `make test-revise-cancellation` where available); then run `python3 contrib/ci/run.py --jobs 4`. Exercise repeated minor collections with 200 promoted linked children and verify cancellation completes without dangling walk-lock access; confirm the MMTk skip remains intentional." + }, + { + "sha": "a261da0be47a8b359c42fe0edfe2d1703c87964a", + "risk": "high", + "subsystems": "Stock GC write barriers and concurrent generic-memory copying in src/gc-wb-stock.h; runtime declarations in src/julia.h; GC regression coverage in test/gc/copyto.jl.", + "indexing_implications": "In src/gc-wb-stock.h, n is an element count, stride is a pointer-slot count, and j is a zero-based pointer-layout ordinal passed to src/julia.h:jl_ptr_offset. The test\u2019s explicit copyto! starts and direct reads are collection positions: destination 3/source 1 become 2/0, reads i+2/1 become i+1, and first sentinel 1 becomes 0. Loop bounds and worker labels remain counts or values. end remains the last valid position.", + "adaptations": "Changed test/gc/copyto.jl to use copyto!(dest, 2, src, 0, n), read copied elements at dest[i + 1], read the first sentinel at dest[0], and detect the concurrent copy start at dest[0]. No runtime change was needed because native pointer metadata is already zero-based and counts/ABI behavior are preserved.", + "tests": "Recommended, not run: rebuild the runtime, run the corresponding GC copyto test with one and multiple threads, and exercise n = 1, 2, 3, 4, 5, 65; inline and wide pointer-bearing structs; partial initialization; and the concurrent Memory{Any} race. Verify position 0, copied span 2:(n+1), untouched endpoints, and child liveness after source clearing. Run applicable Clang static analysis and the full CI gate on a built Joolia artifact." + }, + { + "sha": "035f7c9c2348982153d39d3a552dfe0d5c7b1aae", + "risk": "low", + "subsystems": "Documentation generator, Unicode/emoji manuals, and REPL documentation.", + "indexing_implications": "In doc/make.jl, split-field positions changed from [1, 2, 11] to [0, 1, 10]. The enumerate separator guard changed from i > 1 to i > 0 because enumerate is zero-origin. Code points and formatting metadata remain values, not indices.", + "adaptations": "Applied both narrow origin adaptations in doc/make.jl; upstream\u2019s page split and documentation links are unchanged.", + "tests": "Recommended, not executed: build documentation and inspect both generated Unicode and emoji tables, including multiple aliases and combining characters. git diff --check and checkpoint JSON validation were executed." + }, + { + "sha": "e177e01f81c8571b2e21e84964446704b7645942", + "risk": "low", + "subsystems": "Documentation generation (doc/make.jl, UnicodeTables); manual Unicode/emoji pages; REPL documentation; docs tests.", + "indexing_implications": "split(line, \";\") yields an ordinary zero-origin collection, so UnicodeData fields formerly selected at 1, 2, and 11 are positions 0, 1, and 10. enumerate(inputs) yields a zero-origin counter; i is used only to determine whether a separator precedes an alias, so later aliases satisfy i > 0. Code points, Unicode category codes, alignment symbols, paths, and list contents are values or metadata, not positions.", + "adaptations": "Changed doc/make.jl UnicodeData selection from [[1, 2, 11]] to [[0, 1, 10]], and completion separator logic from i > 1 to i > 0. Added test/docs.jl checks for field extraction and rendering multiple aliases. No adaptation was needed in the Markdown or REPL prose files.", + "tests": "Recommended: run the docs test target containing test/docs.jl; build documentation with the repository docs target and inspect both Unicode Input and Emoji Input generated tables, including entries with multiple aliases; run the REPL documentation suite in the standard CI gate. Not executed here because this review job forbids builds and tests." + }, + { + "sha": "58e8bb8cb772cbcb030a9affb061e0e7cf0265f9", + "risk": "medium", + "subsystems": "Base stack-trace filtering in base/errorshow.jl::_backtrace_remove_kwcall_frames!, Base.StackTraces.frame_mi, and test/errorshow.jl.", + "indexing_implications": "The change normalizes StackFrame metadata by unwrapping CodeInstance.def; it does not alter collection positions. The test's st[findfirst(...)] correctly uses Joolia's zero-origin result. Displayed frame labels remain human ordinals.", + "adaptations": "None. The merged implementation already uses the compatible frame_mi helper, and the imported regression test's indexing is consistent with Joolia's zero-origin contract. No source or test edit is justified.", + "tests": "Recommended, not run: rebuild Base, run make test-revise-errorshow, exercise MethodInstance/CodeInstance/no-linfo StackFrames and kwsorter_outer(; s=3), then run python3 contrib/ci/run.py --jobs 4." + }, + { + "sha": "346d02acc25c3c4c7d56f2fa825a0af0b6b5cd79", + "risk": "medium", + "subsystems": "Base reflection (`base/reflection.jl`, `bodyfunction`), Core world-age/global lookup, reflection tests (`test/reflection.jl`).", + "indexing_implications": "Existing Joolia conversions in `bodyfunction` are correct: `callexpr.args[0]` and related arguments are zero-origin Expr positions; `ast.code[end-1]` selects the penultimate statement; `Core.SSAValue.id` remains an internal one-origin identifier and is translated at storage access with `ast.code[fsym.id-1]`. The new `world` and `m.primary_world` values are world versions, not indices. Surrounding `method_argnames` handling was also inspected.", + "adaptations": "None. The commit only adds a typed `world` keyword and replaces world-insensitive `getfield` calls with `invoke_in_world(world, getglobal, ...)`. Additional index conversion would corrupt the existing AST/SSA boundary.", + "tests": "Recommended, not executed: `JULIA_TEST_FAILFAST=1 make test-revise-reflection`, relevant Base foundation checks, and the full CI gate. Exercise the `BodyFunctionWorld` regression with a pre-definition world and `world=m.primary_world`; verify Symbol and GlobalRef lookup paths and existing zero-origin AST/SSA behavior in `test/reflection.jl`." + } + ] + }, + "integrated": true +} diff --git a/contrib/upstream-sync/state.json b/contrib/upstream-sync/state.json index fbbb533faf8bb..22348abb3adb5 100644 --- a/contrib/upstream-sync/state.json +++ b/contrib/upstream-sync/state.json @@ -1,7 +1,7 @@ { "upstream": "https://github.com/JuliaLang/julia.git", "branch": "master", - "integrated_sha": "81093121d4e6cf469154d977e2ecd6b7488ea0de", + "integrated_sha": "346d02acc25c3c4c7d56f2fa825a0af0b6b5cd79", "max_commits": 20, "max_changed_lines": 2500, "max_patch_bytes": 250000 diff --git a/deps/ittapi.mk b/deps/ittapi.mk index f27ae2d6f77d5..788f60c156b0e 100644 --- a/deps/ittapi.mk +++ b/deps/ittapi.mk @@ -5,7 +5,7 @@ ITTAPI_GIT_URL := https://github.com/intel/ittapi.git ITTAPI_TAR_URL = https://api.github.com/repos/intel/ittapi/tarball/$1 $(eval $(call git-external,ittapi,ITTAPI,CMakeLists.txt,,$(SRCCACHE))) -ITTAPI_OPTS := $(CMAKE_COMMON) -DCMAKE_BUILD_TYPE=Release -DITT_API_IPT_SUPPORT= -DITT_API_FORTRAN_SUPPORT=0 +ITTAPI_OPTS := $(CMAKE_COMMON) -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_BUILD_TYPE=Release -DITT_API_IPT_SUPPORT= -DITT_API_FORTRAN_SUPPORT=0 $(BUILDDIR)/$(ITTAPI_SRC_DIR)/build-configured: $(SRCCACHE)/$(ITTAPI_SRC_DIR)/source-extracted mkdir -p $(dir $@) diff --git a/deps/tools/common.mk b/deps/tools/common.mk index bddd95476843b..172ece82409d6 100644 --- a/deps/tools/common.mk +++ b/deps/tools/common.mk @@ -200,9 +200,10 @@ install-$(strip $1): $$(build_prefix)/manifest/$(strip $1) ifeq (exists, $$(shell [ -e $$(build_staging)/$2.tar ] && echo exists )) # clean depends on uninstall only if the staged file exists distclean-$(strip $1) clean-$(strip $1): uninstall-$(strip $1) -else +else ifeq (exists, $$(shell [ -e $$(build_prefix)/manifest/$(strip $1) ] && echo exists )) # uninstall depends on staging only if the staged file doesn't exist -# otherwise, uninstall doesn't actually want the file to be updated first +# but the dep is installed; otherwise, uninstall doesn't actually want +# the file to be updated first (or has nothing to uninstall) uninstall-$(strip $1): | $$(build_staging)/$2.tar endif diff --git a/deps/tools/uninstallers.mk b/deps/tools/uninstallers.mk index 0051786ed1d0a..a7157b16b76d5 100644 --- a/deps/tools/uninstallers.mk +++ b/deps/tools/uninstallers.mk @@ -9,7 +9,10 @@ ifeq (undefined,$$(flavor $$(word 2,$$(MANIFEST_$1)))) MANIFEST_$1 := $$(UNINSTALL_$1) endif UNINST_HOW_$1 := $$(word 2,$$(MANIFEST_$1)) -ifneq ($$(UNINST_HOW_$1),) +ifeq (,$$(wildcard $$(build_prefix)/manifest/$1)) +uninstall-$1: + @echo "skipping uninstall: $1 not installed" +else ifneq ($$(UNINST_HOW_$1),) UNINST_WHO_$1 := $$(firstword $$(MANIFEST_$1)) UNINST_WHERE_$1 := $$(wordlist 3,99,$$(MANIFEST_$1)) $$(eval $$(call $$(UNINST_HOW_$1),$1,$$(UNINST_WHO_$1),$$(UNINST_WHERE_$1))) diff --git a/doc/make.jl b/doc/make.jl index f6f397350f5a0..f762139a00e10 100644 --- a/doc/make.jl +++ b/doc/make.jl @@ -239,6 +239,98 @@ function generate_agent_skill_docs() end AgentSkillDocs = generate_agent_skill_docs() +# Shared machinery for the generated tab-completion tables in +# `manual/unicode-input.md` and `manual/emoji-input.md`. The LaTeX and emoji +# tables are each large enough that rendering both on one page takes the +# generated HTML past Documenter's `size_threshold`, so they live on separate +# pages and both call into this module. +module UnicodeTables + +import Markdown + +const NBSP = '\u00A0' + +# Invert the completion tables: map each completed string to every tab +# completion sequence that produces it. +function tab_completions(symbols...) + completions = Dict{String, Vector{String}}() + for each in symbols, (k, v) in each + completions[v] = push!(get!(completions, v, String[]), k) + end + return completions +end + +# `UnicodeData.txt` is downloaded into the build root by `doc/Makefile`, which +# is not the directory this file lives in for out-of-tree builds. +unicode_data_file() = joinpath(Main.buildrootdoc, "UnicodeData.txt") + +# Parsed once and shared by every page that renders a table. +const UNICODE_NAMES = Dict{UInt32, String}() + +function unicode_data() + isempty(UNICODE_NAMES) || return UNICODE_NAMES + open(unicode_data_file()) do unidata + for line in readlines(unidata) + id, name, desc = split(line, ";")[[0, 1, 10]] + codepoint = parse(UInt32, "0x$id") + UNICODE_NAMES[codepoint] = titlecase(lowercase( + name == "" ? desc : desc == "" ? name : "$name / $desc")) + end + end + return UNICODE_NAMES +end + +# Surround combining characters with no-break spaces (i.e '\u00A0'). Follows the same format +# for how unicode is displayed on the unicode.org website: +# https://util.unicode.org/UnicodeJsps/character.jsp?a=0300 +function fix_combining_chars(char) + cat = Base.Unicode.category_code(char) + return cat == 6 || cat == 8 ? "$NBSP$char$NBSP" : "$char" +end + +function table_entries(completions, unicode_dict) + entries = Any[Any[ + ["Code point(s)"], + ["Character(s)"], + ["Tab completion sequence(s)"], + ["Unicode name(s)"], + ]] + for (chars, inputs) in sort!(collect(completions), by = first) + code_points, unicode_names, characters = String[], String[], String[] + for char in chars + push!(code_points, "U+$(uppercase(string(UInt32(char), base = 16, pad = 5)))") + push!(unicode_names, get(unicode_dict, UInt32(char), "(No Unicode name)")) + push!(characters, isempty(characters) ? fix_combining_chars(char) : "$char") + end + inputs_md = [] + for (i, input) in enumerate(inputs) + i > 0 && push!(inputs_md, ", ") + push!(inputs_md, Markdown.Code("", input)) + end + push!(entries, [ + [join(code_points, " + ")], + [join(characters)], + inputs_md, + [join(unicode_names, " + ")], + ]) + end + table = Markdown.Table(entries, [:l, :c, :l, :l]) + # We also need to wrap the Table in a Markdown.MD "document" + return Markdown.MD([table]) +end + +""" + symbol_table(symbols...) + +Render the tab completions in `symbols` (dictionaries mapping a completion +sequence to the string it expands to, such as `REPL.REPLCompletions.latex_symbols`) +as a Markdown table, annotated with the code points and Unicode names of each +completed character. +""" +symbol_table(symbols...) = table_entries(tab_completions(symbols...), unicode_data()) + +end # module UnicodeTables + Manual = [ "manual/getting-started.md", "manual/installation.md", @@ -280,6 +372,7 @@ Manual = [ "manual/faq.md", "manual/noteworthy-differences.md", "manual/unicode-input.md", + "manual/emoji-input.md", "manual/command-line-interface.md", "manual/worldage.md", ] diff --git a/doc/src/manual/emoji-input.md b/doc/src/manual/emoji-input.md new file mode 100644 index 0000000000000..bb8e453cc3e7c --- /dev/null +++ b/doc/src/manual/emoji-input.md @@ -0,0 +1,23 @@ +# Emoji Input + +The following table lists the emoji that can be entered via tab completion of +their `\:name:` abbreviations in the Julia REPL (and in various other +editing environments). You can also get information on how to type an emoji by +entering it in the REPL help, i.e. by typing `?` and then entering the emoji in +the REPL (e.g., by copy-paste from somewhere you saw the emoji). + +For the LaTeX-like abbreviations that complete to mathematical symbols and +other non-emoji characters, see [Unicode Input](@ref). + +!!! warning + + This table may appear to contain missing characters in the second column, or even + show characters that are inconsistent with the characters as they are rendered in + the Julia REPL. In these cases, users are strongly advised to check their choice + of fonts in their browser and REPL environment, as there are known issues with + glyphs in many fonts. + +```@eval +import REPL +Main.UnicodeTables.symbol_table(REPL.REPLCompletions.emoji_symbols) +``` diff --git a/doc/src/manual/unicode-input.md b/doc/src/manual/unicode-input.md index eba970c051f1e..6e4b1430e5da2 100644 --- a/doc/src/manual/unicode-input.md +++ b/doc/src/manual/unicode-input.md @@ -7,6 +7,9 @@ type a symbol by entering it in the REPL help, i.e. by typing `?` and then entering the symbol in the REPL (e.g., by copy-paste from somewhere you saw the symbol). +Emoji are completed from their own set of abbreviations and are listed +separately in [Emoji Input](@ref). + !!! warning This table may appear to contain missing characters in the second column, or even @@ -16,78 +19,6 @@ the symbol). glyphs in many fonts. ```@eval -# -# Generate a table containing all LaTeX and Emoji tab completions available in the REPL. -# -import REPL, Markdown -const NBSP = '\u00A0' - -function tab_completions(symbols...) - completions = Dict{String, Vector{String}}() - for each in symbols, (k, v) in each - completions[v] = push!(get!(completions, v, String[]), k) - end - return completions -end - -function unicode_data() - file = normpath(@__DIR__, "..", "..", "..", "..", "..", "doc", "UnicodeData.txt") - names = Dict{UInt32, String}() - open(file) do unidata - for line in readlines(unidata) - id, name, desc = split(line, ";")[[1, 2, 11]] - codepoint = parse(UInt32, "0x$id") - names[codepoint] = titlecase(lowercase( - name == "" ? desc : desc == "" ? name : "$name / $desc")) - end - end - return names -end - -# Surround combining characters with no-break spaces (i.e '\u00A0'). Follows the same format -# for how unicode is displayed on the unicode.org website: -# https://util.unicode.org/UnicodeJsps/character.jsp?a=0300 -function fix_combining_chars(char) - cat = Base.Unicode.category_code(char) - return cat == 6 || cat == 8 ? "$NBSP$char$NBSP" : "$char" -end - -function table_entries(completions, unicode_dict) - entries = Any[Any[ - ["Code point(s)"], - ["Character(s)"], - ["Tab completion sequence(s)"], - ["Unicode name(s)"], - ]] - for (chars, inputs) in sort!(collect(completions), by = first) - code_points, unicode_names, characters = String[], String[], String[] - for char in chars - push!(code_points, "U+$(uppercase(string(UInt32(char), base = 16, pad = 5)))") - push!(unicode_names, get(unicode_dict, UInt32(char), "(No Unicode name)")) - push!(characters, isempty(characters) ? fix_combining_chars(char) : "$char") - end - inputs_md = [] - for (i, input) in enumerate(inputs) - i > 1 && push!(inputs_md, ", ") - push!(inputs_md, Markdown.Code("", input)) - end - push!(entries, [ - [join(code_points, " + ")], - [join(characters)], - inputs_md, - [join(unicode_names, " + ")], - ]) - end - table = Markdown.Table(entries, [:l, :c, :l, :l]) - # We also need to wrap the Table in a Markdown.MD "document" - return Markdown.MD([table]) -end - -table_entries( - tab_completions( - REPL.REPLCompletions.latex_symbols, - REPL.REPLCompletions.emoji_symbols - ), - unicode_data() -) +import REPL +Main.UnicodeTables.symbol_table(REPL.REPLCompletions.latex_symbols) ``` diff --git a/src/cgutils.cpp b/src/cgutils.cpp index dcdce621af77d..0d8019583a4f6 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -488,13 +488,12 @@ static SmallVector ExtractTrackedValues(jl_codectx_t &ctx, Value *Src return Ptrs; } -// whether memory in this region cannot be written concurrently, so that -// non-atomic (rather than unordered) loads and stores are permitted -static bool region_is_private(jl_aliasinfo_t::Region r) +// whether this memory cannot be written concurrently, so that non-atomic +// (rather than unordered) loads and stores are permitted +static bool ai_is_private(const jl_aliasinfo_t &ai) { - return r == jl_aliasinfo_t::Region::stack || - r == jl_aliasinfo_t::Region::gcframe || - r == jl_aliasinfo_t::Region::constant; + using Region = jl_aliasinfo_t::Region; + return ai.isConstant() || ai.region == Region::gcframe; } static llvm::SmallVector extract_gc_roots(jl_codectx_t &ctx, Value *data_pointer, jl_datatype_t *typ, size_t npointers, const jl_aliasinfo_t &roots_ai, bool isVolatile=false) @@ -502,7 +501,7 @@ static llvm::SmallVector extract_gc_roots(jl_codectx_t &ctx, Value *da SmallVector gcroots(npointers); if (npointers) { Type *T_prjlvalue = ctx.types().T_prjlvalue; - bool isprivatemem = isa(data_pointer->stripInBoundsOffsets()) || region_is_private(roots_ai.region); + bool isprivatemem = isa(data_pointer->stripInBoundsOffsets()) || ai_is_private(roots_ai); for (size_t i = 0; i < npointers; i++) { Value *field_ptr = emit_ptrgep(ctx, data_pointer, jl_ptr_offset(typ, i) * sizeof(jl_value_t*)); LoadInst *root = ctx.builder.CreateAlignedLoad(T_prjlvalue, field_ptr, Align(sizeof(void*)), isVolatile); @@ -529,7 +528,7 @@ static llvm::SmallVector extract_gc_roots(jl_codectx_t &ctx, const jl_ Type *T_prjlvalue = ctx.types().T_prjlvalue; const jl_aliasinfo_t &roots_ai = val.aliasinfo; Value *p = maybe_decay_tracked(ctx, data_pointer(ctx, val)); - bool isprivatemem = isa(p->stripInBoundsOffsets()) || region_is_private(roots_ai.region); + bool isprivatemem = isa(p->stripInBoundsOffsets()) || ai_is_private(roots_ai); gcroots.resize(npointers, nullptr); for (size_t i = 0; i < npointers; i++) { Value *field_ptr = emit_ptrgep(ctx, p, jl_ptr_offset((jl_datatype_t*)val.typ, i) * sizeof(jl_value_t*)); @@ -1264,13 +1263,10 @@ static void emit_memcpy_llvm(jl_codectx_t &ctx, Value *dst, jl_aliasinfo_t const return; ++EmittedMemcpys; - // the memcpy intrinsic does not allow specifying different alias tags - // for the load part (src_ai) and the store part (dst_ai). - // since the tbaa lattice has to be a tree we unfortunately have - // tbaa(src) ∪ tbaa(dst) = tbaa_root whenever tbaa(src) != tbaa(dst), - // while the merged region metadata keeps the intersection of the - // regions' noalias sets. - auto merged_ai = dst_ai.merge(ctx, src_ai); + // the memcpy intrinsic has no way to give the load part (src_ai) and the store + // part (dst_ai) different alias info, so one claim must cover both; see + // `jl_aliasinfo_t::merge`. + auto merged_ai = dst_ai.merge(src_ai); #if JL_LLVM_VERSION < 210000 ctx.builder.CreateMemCpy(dst, align_dst, src, align_src, sz, is_volatile, merged_ai.tbaa, merged_ai.tbaa_struct, merged_ai.scope, merged_ai.noalias); @@ -1353,7 +1349,7 @@ static void split_value_into(jl_codectx_t &ctx, const jl_cgval_t &x, Align align return; Value *src; std::tie(src, src_ai) = data_pointer_ai(ctx, value_to_pointer(ctx, x)); - bool isprivatemem = isa(src->stripInBoundsOffsets()) || region_is_private(src_ai.region); + bool isprivatemem = isa(src->stripInBoundsOffsets()) || ai_is_private(src_ai); bool hasptr = typ->layout->first_ptr >= 0; size_t npointers = hasptr ? typ->layout->npointers : 0; size_t shrunken_size = split_value_size(typ).first; @@ -1467,9 +1463,9 @@ static std::tuple split_value(jl_codectx_ return "split::" + std::string(jl_symbol_name(typ->name->name)); }); // The alloca contains no pointers (those were split into roots), so this - // write-once copy takes the layout tag of what it holds, as the no-copy + // write-once copy takes the access tag of what it holds, as the no-copy // paths above already do. - jl_aliasinfo_t dst_ai = stack_copy_aliasinfo(ctx, x.aliasinfo, x.typ); + jl_aliasinfo_t dst_ai = private_copy_aliasinfo(ctx, x.aliasinfo, x.typ); split_value_into(ctx, x, x_alignment, alloca, align_dst, dst_ai, false); return std::make_tuple(alloca, std::move(roots), dst_ai); } @@ -1509,7 +1505,7 @@ static void recombine_value(jl_codectx_t &ctx, const jl_cgval_t &x, Value *dst, jl_aliasinfo_t src_ai = x.aliasinfo; size_t npointers = typ->layout->npointers; size_t shrunken_size = split_value_size(typ).first; - bool isprivatemem = isa(dst->stripInBoundsOffsets()) || dst_ai.region == jl_aliasinfo_t::Region::stack; + bool isprivatemem = isa(dst->stripInBoundsOffsets()) || ai_is_private(dst_ai); size_t off = 0; for (size_t i = 0; true; i++) { bool last = i == npointers; @@ -2539,7 +2535,7 @@ static jl_cgval_t typed_load(jl_codectx_t &ctx, Value *ptr, Value *idx_0based, j AllocaInst *lv = emit_static_alloca(ctx, fsz, Align(al)); setName(ctx.emission_context, lv, "immutable_union"); // the copy is a private, write-once stack location - data_ai = stack_copy_aliasinfo(ctx, ai, jltype); + data_ai = private_copy_aliasinfo(ctx, ai, jltype); emit_memcpy(ctx, lv, data_ai, ptr, ai, fsz, Align(al), Align(al)); data = lv; } @@ -3468,7 +3464,7 @@ static bool isLoadFromConstGV(LoadInst *LI) auto load_base = LI->getPointerOperand()->stripInBoundsOffsets(); assert(load_base); // Static analyzer auto gv = dyn_cast(load_base); - if (isLoadFromImmut(LI)) { + if (isLoadFromRootedRegion(LI)) { if (gv) return true; return isLoadFromConstGV(load_base); @@ -3497,9 +3493,16 @@ static jl_aliasinfo_t best_field_aliasinfo(jl_codectx_t &ctx, const jl_cgval_t & if (idx == 1) return ctx.alias().arraysize; } + // A `const` field of an object that is already a constant global: nothing in this + // compilation unit stores to it, so it is described by the immutable `jtbaa_const` + // tag alone. That the tag does not alias the containing struct's is fine for the + // same reason -- there is no store here for a reader to be reordered against. if (strct.V && jl_field_isconst(jt, idx) && isLoadFromConstGV(strct.V)) - return ctx.alias().constant; //TODO: it seems odd to have a field with a tbaa that doesn't alias it's containing struct's tbaa - //Does the fact that this is marked as constant make this fine? + return ctx.alias().constant; + // Specify if this specific field is const or not, which helps with later refinements (especially gc-root-lowering). + if (ai.region == jl_aliasinfo_t::Region::mutfields) + return ai.withRegion(ctx, jl_field_isconst(jt, idx) + ? jl_aliasinfo_t::Region::mutconstdata : jl_aliasinfo_t::Region::mutdata); return ai; } @@ -3661,7 +3664,7 @@ static jl_cgval_t emit_getfield_knownidx(jl_codectx_t &ctx, const jl_cgval_t &st Value *tindex0 = ctx.builder.CreateExtractValue(obj, ArrayRef(ptindex)); Value *tindex = ctx.builder.CreateNUWAdd(ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 1), tindex0); setNameWithField(ctx.emission_context, tindex, get_objname, jt, idx, Twine(".tindex")); - return mark_julia_slot(lv, jfty, tindex, best_aliasinfo(ctx, jfty)); + return mark_julia_slot(lv, jfty, tindex, union_slot_aliasinfo(ctx, jfty)); } else { unsigned st_idx; @@ -4573,8 +4576,9 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg ssize_t promotion_ssa = -1; Value *strct; // This stack object is a write-once copy of an immutable holding no - // pointers (those are split into inline_roots). - const jl_aliasinfo_t strct_ai = best_aliasinfo(ctx, ty).withRegion(ctx, jl_aliasinfo_t::Region::stack); + // pointers (those are split into inline_roots), and must be eligible for promotion later. + const jl_aliasinfo_t strct_ai = best_aliasinfo(ctx, ty); + assert(strct_ai.region == jl_aliasinfo_t::Region::immutdata); SmallVector inline_roots; if (type_is_ghost(lt)) { strct = nullptr; diff --git a/src/clangsa/GCCheckerReporting.cpp b/src/clangsa/GCCheckerReporting.cpp index 62540565122e7..83c7a13ccb7d1 100644 --- a/src/clangsa/GCCheckerReporting.cpp +++ b/src/clangsa/GCCheckerReporting.cpp @@ -189,7 +189,9 @@ PDP GCChecker::GCValueBugVisitor::VisitNode(const ExplodedNode *N, !isFDAnnotatedNotSafepoint(NewSymbolState->FD, BRC.getSourceManager()); bool maybeUnrooted = declHasAnnotation(NewSymbolState->PVD, "julia_maybe_unrooted"); - assert(isFunctionSafepoint || maybeUnrooted); + // `LivenessState::getForArgument` records FD/PVD only when the function + // is not a safepoint or the parameter is JL_MAYBE_UNROOTED. + assert(!isFunctionSafepoint || maybeUnrooted); (void)maybeUnrooted; Pos = PathDiagnosticLocation{NewSymbolState->PVD, BRC.getSourceManager()}; diff --git a/src/codegen.cpp b/src/codegen.cpp index 3510428f66993..1e15badcbbf77 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -47,6 +47,7 @@ #include // support +#include #include #include #include @@ -342,29 +343,31 @@ struct jl_typecache_t { namespace { struct jl_tbaacache_t { - // type-based alias analysis nodes. Indentation of comments indicates hierarchy. - MDNode *tbaa_root = nullptr; // Everything - MDNode *tbaa_gcframe = nullptr; // GC frame + // Type-based alias analysis nodes. Indentation of comments indicates hierarchy. + MDNode *tbaa_root = nullptr; // Everything + MDNode *tbaa_gcframe = nullptr; // GC frame // LLVM should have enough info for alias analysis of non-gcframe stack slot - MDNode *tbaa_stack = nullptr; // untyped stack slot which aren't julia data - MDNode *tbaa_data = nullptr; // Any user data that `pointerset/ref` are allowed to alias - MDNode *tbaa_binding = nullptr; // jl_binding_t::value + MDNode *tbaa_stack = nullptr; // Untyped stack slot which aren't julia data + MDNode *tbaa_memoryselbyte = nullptr;// A selector byte in an isbits Union jl_genericmemory_t + MDNode *tbaa_data = nullptr; // Any user data that `pointerset/ref` are allowed to alias + // This distinguishes what kind of slot is accessed: an ordinary field of a + // user-defined layout, or one of the object headers the runtime manages. Whether + // it may be mutated, and by whom, is a property of the memory it lives in, and is + // described by the alias region instead (see `jl_regions_t`). MDNode *tbaa_value = nullptr; // jl_value_t of statically unknown type; parent of the tags below - MDNode *tbaa_mutab = nullptr; // mutable type - MDNode *tbaa_array = nullptr; // jl_array_t header, mutable - MDNode *tbaa_arrayptr = nullptr; // The pointer inside a jl_array_t (to a memoryref) - MDNode *tbaa_arraysize = nullptr; // A size in a jl_array_t - MDNode *tbaa_datatype = nullptr; // datatype - MDNode *tbaa_immut = nullptr; // immutable type + MDNode *tbaa_field = nullptr; // an ordinary field of a user-defined layout + MDNode *tbaa_ptrmemorybuf = nullptr; // a boxed element slot of a jl_genericmemory_t buffer + MDNode *tbaa_binding = nullptr; // jl_binding_t::value + MDNode *tbaa_array = nullptr; // jl_array_t header + MDNode *tbaa_arrayptr = nullptr; // The pointer inside a jl_array_t (to a memoryref) + MDNode *tbaa_arraysize = nullptr; // A size in a jl_array_t + MDNode *tbaa_datatype = nullptr; // jl_datatype_t fields MDNode *tbaa_memory = nullptr; // jl_genericmemory_t header, never mutated after construction - MDNode *tbaa_memoryptr = nullptr; // The pointer inside a jl_genericmemory_t - MDNode *tbaa_memorylen = nullptr; // The length in a jl_genericmemory_t - MDNode *tbaa_memoryown = nullptr; // The owner in a foreign jl_genericmemory_t - MDNode *tbaa_ptrarraybuf = nullptr; // Data in an array of boxed values - MDNode *tbaa_arraybuf = nullptr; // Data in an array of POD - MDNode *tbaa_arrayselbyte = nullptr; // a selector byte in a isbits Union jl_genericmemory_t - MDNode *tbaa_const = nullptr; // Memory that is immutable by the time LLVM can see it - MDNode *tbaa_coverage = nullptr; // Coverage and malloc-log counters; disjoint from all user-visible memory + MDNode *tbaa_memoryptr = nullptr; // The pointer inside a jl_genericmemory_t + MDNode *tbaa_memorylen = nullptr; // The length in a jl_genericmemory_t + MDNode *tbaa_memoryown = nullptr; // The owner in a foreign jl_genericmemory_t + MDNode *tbaa_const = nullptr; // Memory that is immutable by the time this compile unit can access it + MDNode *tbaa_coverage = nullptr; // Coverage and malloc-log counters; disjoint from all user-visible memory bool initialized = false; jl_tbaacache_t() = default; @@ -386,34 +389,24 @@ struct jl_tbaacache_t { tbaa_root = mbuilder.createTBAAScalarTypeNode("jtbaa", jtbaa); tbaa_gcframe = tbaa_make_child(mbuilder, "jtbaa_gcframe").first; tbaa_stack = tbaa_make_child(mbuilder, "jtbaa_stack").first; + tbaa_memoryselbyte = tbaa_make_child(mbuilder, "jtbaa_memoryselbyte").first; MDNode *tbaa_data_scalar; std::tie(tbaa_data, tbaa_data_scalar) = tbaa_make_child(mbuilder, "jtbaa_data"); - tbaa_binding = tbaa_make_child(mbuilder, "jtbaa_binding", tbaa_data_scalar).first; MDNode *tbaa_value_scalar; std::tie(tbaa_value, tbaa_value_scalar) = tbaa_make_child(mbuilder, "jtbaa_value", tbaa_data_scalar); - MDNode *tbaa_mutab_scalar; - std::tie(tbaa_mutab, tbaa_mutab_scalar) = - tbaa_make_child(mbuilder, "jtbaa_mutab", tbaa_value_scalar); - tbaa_datatype = tbaa_make_child(mbuilder, "jtbaa_datatype", tbaa_mutab_scalar).first; - MDNode *tbaa_immut_scalar; - std::tie(tbaa_immut, tbaa_immut_scalar) = - tbaa_make_child(mbuilder, "jtbaa_immut", tbaa_value_scalar); - tbaa_arraybuf = tbaa_make_child(mbuilder, "jtbaa_arraybuf", tbaa_data_scalar).first; - tbaa_ptrarraybuf = tbaa_make_child(mbuilder, "jtbaa_ptrarraybuf", tbaa_data_scalar).first; - // The header tags must descend from jtbaa_value: an array is also reachable - // through a type-erased pointer (a Union or an abstract type, including Any), - // which `best_aliasinfo` tags jtbaa_value, and a disjoint subtree would make that - // load NoAlias against the stores that initialize the header. + tbaa_field = tbaa_make_child(mbuilder, "jtbaa_field", tbaa_value_scalar).first; + tbaa_ptrmemorybuf = tbaa_make_child(mbuilder, "jtbaa_ptrmemorybuf", tbaa_value_scalar).first; + tbaa_binding = tbaa_make_child(mbuilder, "jtbaa_binding", tbaa_value_scalar).first; + tbaa_datatype = tbaa_make_child(mbuilder, "jtbaa_datatype", tbaa_value_scalar).first; MDNode *tbaa_array_scalar; std::tie(tbaa_array, tbaa_array_scalar) = - tbaa_make_child(mbuilder, "jtbaa_array", tbaa_mutab_scalar); + tbaa_make_child(mbuilder, "jtbaa_array", tbaa_value_scalar); tbaa_arrayptr = tbaa_make_child(mbuilder, "jtbaa_arrayptr", tbaa_array_scalar).first; tbaa_arraysize = tbaa_make_child(mbuilder, "jtbaa_arraysize", tbaa_array_scalar).first; - tbaa_arrayselbyte = tbaa_make_child(mbuilder, "jtbaa_arrayselbyte", tbaa_array_scalar).first; MDNode *tbaa_memory_scalar; std::tie(tbaa_memory, tbaa_memory_scalar) = - tbaa_make_child(mbuilder, "jtbaa_memory", tbaa_immut_scalar); + tbaa_make_child(mbuilder, "jtbaa_memory", tbaa_value_scalar); tbaa_memoryptr = tbaa_make_child(mbuilder, "jtbaa_memoryptr", tbaa_memory_scalar).first; tbaa_memorylen = tbaa_make_child(mbuilder, "jtbaa_memorylen", tbaa_memory_scalar).first; tbaa_memoryown = tbaa_make_child(mbuilder, "jtbaa_memoryown", tbaa_memory_scalar).first; @@ -429,12 +422,21 @@ struct jl_noaliascache_t { // "No aliasing" is inferred if it is implied by any domain. // memory regions domain + // + // A region is worth a scope here only if it says something '!tbaa' cannot. + // A private copy of an immutable value is `immutdata` because LLVM cannot express + // this any more accurately on memcpy, and its privacy is visible from its `alloca` address anyways. + // + // Rooting invariant (relied upon by `isLoadFromRootedRegion`): + // if an access's scope set in this domain is contained in + // {jnoalias_immutdata, jnoalias_mutconstdata}, the base object cannot stop + // referencing a tracked pointer stored there while the base is live, even across phi nodes. struct jl_regions_t { MDNode *gcframe = nullptr; // GC frame - MDNode *stack = nullptr; // Stack slot - MDNode *data = nullptr; // Any user data that `pointerset/ref` are allowed to alias - MDNode *constant = nullptr; // Memory that is immutable by the time LLVM can see it - MDNode *coverage = nullptr; // Coverage and malloc-log counters + MDNode *mutdata = nullptr; // The assignable fields of a mutable jl_value_t + MDNode *mutconstdata = nullptr; // The `const` fields of a mutable jl_value_t + MDNode *immutdata = nullptr; // The payload of an immutable jl_value_t + MDNode *memorybuf = nullptr; // The element data of a jl_genericmemory_t jl_regions_t() = default; @@ -443,10 +445,10 @@ struct jl_noaliascache_t { MDNode *domain = mbuilder.createAliasScopeDomain("jnoalias"); this->gcframe = mbuilder.createAliasScope("jnoalias_gcframe", domain); - this->stack = mbuilder.createAliasScope("jnoalias_stack", domain); - this->data = mbuilder.createAliasScope("jnoalias_data", domain); - this->constant = mbuilder.createAliasScope("jnoalias_const", domain); - this->coverage = mbuilder.createAliasScope("jnoalias_coverage", domain); + this->mutdata = mbuilder.createAliasScope("jnoalias_mutdata", domain); + this->mutconstdata = mbuilder.createAliasScope("jnoalias_mutconstdata", domain); + this->immutdata = mbuilder.createAliasScope("jnoalias_immutdata", domain); + this->memorybuf = mbuilder.createAliasScope("jnoalias_memorybuf", domain); } } regions; @@ -466,7 +468,7 @@ struct jl_noaliascache_t { void initialize(llvm::LLVMContext &context) { if (initialized) { - assert(®ions.constant->getContext() == &context); + assert(®ions.gcframe->getContext() == &context); return; } initialized = true; @@ -1716,16 +1718,48 @@ static void union_alloca_type(jl_uniontype_t *ut, // Alias Analysis Info (analogous to llvm::AAMDNodes) // -// This combines the two orthogonal pieces of alias information codegen tracks -// for a memory location: -// - the memory region it lives in (gcframe / stack / heap data / constant), -// expressed as '!alias.scope' + '!noalias' metadata derived from -// jl_noaliascache_t::jl_regions_t, and -// - the layout/type of the data stored there, expressed as struct-path -// '!tbaa' metadata from the jl_tbaacache_t tree. +// This combines the two orthogonal pieces of alias information codegen tracks for a +// memory location: +// - *where* it lives: the memory region (gcframe / mutable object fields / +// immutable object payload / memory buffer), emitted as '!alias.scope' plus +// '!noalias' from jl_noaliascache_t::jl_regions_t, and +// - *how* it is stored: which kind of slot it is, emitted as a struct-path '!tbaa' +// access tag from the jl_tbaacache_t tree. This describes the containing object, +// not the field's own type: a `memcpy` has one tag for both of its operands, so a +// tag that varied with the data could not survive a copy. namespace { struct jl_aliasinfo_t { - enum class Region { unknown, gcframe, stack, data, constant, coverage }; // See jl_regions_t + // The set of memory regions an access may touch: each region in the set is one + // '!alias.scope' scope, and every region outside it goes in '!noalias'. + // The empty set (`unknown`) adds no region metadata. + enum class Region : uint8_t { + unknown = 0, + // GC-frame is leaked globally after final-gc-lowering pass. + gcframe = 1 << 0, + // The assignable fields of a heap-allocated jl_value_t, notably including + // `Array`'s header: the memory a generic `setfield!` writes. + mutdata = 1 << 1, + // The `const` fields of a mutable jl_value_t, including the `GenericMemory` + // header and `DataType`'s fields. A `setfield!` can never reach these, so + // what they hold stays reachable for as long as the object is live -- which + // is what lets late-gc-lowering root through them. + mutconstdata = 1 << 2, + // The payload of an immutable jl_value_t: on the heap, or in a codegen-private + // write-once copy on the stack. Both are the same value and neither is + // writable once built, so a region has nothing to tell them apart by. + immutdata = 1 << 3, + // The element data of a jl_genericmemory_t. An inline element is an immutable + // value too, but the slot holding it is not: `setindex!` overwrites it in + // place, so unlike `immutdata` this cannot be rooted through. + memorybuf = 1 << 4, + // Any field of a mutable jl_value_t. + mutfields = mutdata | mutconstdata, + // Any field of any jl_value_t. + data = mutfields | immutdata, + // Everything a raw `Ptr` may legally read from our alias regions. + anydata = data | memorybuf, + LLVM_MARK_AS_BITMASK_ENUM(memorybuf) + }; MDNode *tbaa = nullptr; // '!tbaa': Struct-path TBAA. TBAA graph forms a tree (indexed by offset). // Two pointers do not alias if they are not transitive parents @@ -1748,16 +1782,26 @@ struct jl_aliasinfo_t { return tbaa != nullptr; } - // The same layout tag in a different memory region: e.g. a private + // The same access tag in a different memory region: e.g. a private // stack copy of typed data keeps the type's `!tbaa`, while the region // records that it is not heap memory. jl_aliasinfo_t withRegion(jl_codectx_t &ctx, Region r) const { return jl_aliasinfo_t(ctx, r, this->tbaa); } + // Whether the access tag is an immutable one (`jtbaa_const`), meaning nothing + // may store to this memory for as long as this compilation unit can reach it. + // This is the axis constant memory is described on -- it has no region. + bool isConstant() const { + if (!tbaa || tbaa->getNumOperands() < 4) + return false; + auto *imm = mdconst::dyn_extract(tbaa->getOperand(3)); + return imm && !imm->isZero(); + } + // Add !tbaa, !tbaa.struct, !alias.scope, !noalias annotations to an instruction. // - // Also adds `invariant.load` to load instructions from the constant region. + // Also adds `invariant.load` to loads of constant memory. Instruction *decorateInst(Instruction *inst) const { if (this->tbaa) @@ -1769,8 +1813,8 @@ struct jl_aliasinfo_t { if (this->noalias) inst->setMetadata(LLVMContext::MD_noalias, this->noalias); - // If this is in the read-only region, mark the load with "!invariant.load" - if (this->region == Region::constant && isa(inst)) + // If this memory is read-only, mark the load with "!invariant.load" + if (this->isConstant() && isa(inst)) inst->setMetadata(LLVMContext::MD_invariant_load, MDNode::get(inst->getContext(), {})); return inst; @@ -1778,7 +1822,7 @@ struct jl_aliasinfo_t { // Merge two sets of alias information, for an instruction (such as a memcpy) // that accesses both. - jl_aliasinfo_t merge(jl_codectx_t &ctx, const jl_aliasinfo_t &other) const; + jl_aliasinfo_t merge(const jl_aliasinfo_t &other) const; AAMDNodes toAAMDNodes() const { @@ -1793,30 +1837,25 @@ struct jl_aliasinfo_t { // Pre-combined alias info for common memory locations referenced by // codegen. Access lazily via `ctx.alias()`. struct jl_aliascache_t { - // Region::gcframe jl_aliasinfo_t gcframe; // GC frame - // Region::stack jl_aliasinfo_t stack; // untyped stack slot - // Region::data jl_aliasinfo_t data; // Any user data that `pointerset/ref` are allowed to alias - jl_aliasinfo_t binding; // jl_binding_t::value jl_aliasinfo_t value; // jl_value_t of statically unknown type - jl_aliasinfo_t mutab; // mutable type + jl_aliasinfo_t immut; // the payload of an immutable jl_value_t + jl_aliasinfo_t mutab; // the fields of a mutable jl_value_t + jl_aliasinfo_t binding; // jl_binding_t::value jl_aliasinfo_t datatype; // datatype - jl_aliasinfo_t immut; // immutable type - jl_aliasinfo_t ptrarraybuf; // Data in an array of boxed values - jl_aliasinfo_t arraybuf; // Data in an array of POD jl_aliasinfo_t array; // jl_array_t header jl_aliasinfo_t arrayptr; // The pointer inside a jl_array_t (to a memoryref) jl_aliasinfo_t arraysize; // A size in a jl_array_t - jl_aliasinfo_t arrayselbyte; // a selector byte in an isbits Union jl_genericmemory_t jl_aliasinfo_t memory; // jl_genericmemory_t header jl_aliasinfo_t memoryptr; // The pointer inside a jl_genericmemory_t jl_aliasinfo_t memorylen; // The length in a jl_genericmemory_t jl_aliasinfo_t memoryown; // The owner in a foreign jl_genericmemory_t - // Region::constant - jl_aliasinfo_t constant; // Memory that is immutable by the time LLVM can see it - // Region::coverage + jl_aliasinfo_t memorybuf; // Inline elements of a jl_genericmemory_t + jl_aliasinfo_t ptrmemorybuf; // Boxed element slots of a jl_genericmemory_t + jl_aliasinfo_t memoryselbyte; // a selector byte in an isbits Union jl_genericmemory_t + jl_aliasinfo_t constant; // Memory that is immutable by the time this compile unit can access it jl_aliasinfo_t coverage; // Coverage and malloc-log counters bool initialized = false; @@ -2200,62 +2239,46 @@ static void jl_temporary_root(jl_codegen_output_t &ctx, jl_value_t *val) JL_CANS static void jl_temporary_root(jl_codectx_t &ctx, jl_value_t *val) JL_CANSAFEPOINT; jl_aliasinfo_t::jl_aliasinfo_t(jl_codectx_t &ctx, Region r, MDNode *tbaa): tbaa(tbaa), tbaa_struct(nullptr), region(r) { - MDNode *alias_scope = nullptr; jl_noaliascache_t::jl_regions_t regions = ctx.noalias().regions; - switch (r) { - case Region::unknown: - alias_scope = nullptr; - break; - case Region::gcframe: - alias_scope = regions.gcframe; - break; - case Region::stack: - alias_scope = regions.stack; - break; - case Region::data: - alias_scope = regions.data; - break; - case Region::constant: - alias_scope = regions.constant; - break; - case Region::coverage: - alias_scope = regions.coverage; - break; + std::pair all_scopes[] = { + { Region::gcframe, regions.gcframe }, + { Region::mutdata, regions.mutdata }, + { Region::mutconstdata, regions.mutconstdata }, + { Region::immutdata, regions.immutdata }, + { Region::memorybuf, regions.memorybuf }, + }; + // The regions in the set are added to !alias.scope, all others to !noalias + SmallVector scopes; + SmallVector noaliases; + for (auto const ®ion: all_scopes) { + if ((r & region.first) != Region::unknown) + scopes.push_back(region.second); + else + noaliases.push_back(region.second); } - - MDNode *all_scopes[] = { regions.gcframe, regions.stack, regions.data, regions.constant, - regions.coverage }; - if (alias_scope) { - // The matching region is added to !alias.scope - // All other regions are added to !noalias - - SmallVector noaliases; - for (MDNode *scope : all_scopes) { - if (scope == alias_scope) continue; - noaliases.push_back(scope); - } - - Metadata *scopes[1] = { alias_scope }; - this->scope = MDNode::get(ctx.builder.getContext(), ArrayRef(scopes)); + if (!scopes.empty()) { + this->scope = MDNode::get(ctx.builder.getContext(), scopes); this->noalias = MDNode::get(ctx.builder.getContext(), noaliases); } } -jl_aliasinfo_t jl_aliasinfo_t::merge(jl_codectx_t &ctx, const jl_aliasinfo_t &other) const { +jl_aliasinfo_t jl_aliasinfo_t::merge(const jl_aliasinfo_t &other) const { + // Constant memory is never stored to, and lives in no region, so an access that + // also touches it can never be the subject of an aliasing query whose answer it + // changes: keep the other side's claim whole, instead of climbing to the (much + // coarser) common parent tag. + if (this->isConstant() || other.isConstant()) { + jl_aliasinfo_t result = this->isConstant() ? other : *this; + result.tbaa_struct = nullptr; + return result; + } jl_aliasinfo_t result; - // Nothing may store to `jtbaa_const` memory, so an access that also touches it - // is never the subject of a TBAA query whose answer could change: keep the other - // side's precise tag instead of climbing to their (much coarser) common parent. - if (this->tbaa == ctx.tbaa().tbaa_const) - result.tbaa = other.tbaa; - else if (other.tbaa == ctx.tbaa().tbaa_const) - result.tbaa = this->tbaa; - else - result.tbaa = MDNode::getMostGenericTBAA(this->tbaa, other.tbaa); + result.tbaa = MDNode::getMostGenericTBAA(this->tbaa, other.tbaa); result.tbaa_struct = nullptr; result.scope = MDNode::getMostGenericAliasScope(this->scope, other.scope); result.noalias = MDNode::intersect(this->noalias, other.noalias); - result.region = this->region == other.region ? this->region : Region::unknown; + result.region = (this->region == Region::unknown || other.region == Region::unknown) + ? Region::unknown : (this->region | other.region); return result; } @@ -2269,50 +2292,64 @@ void jl_aliascache_t::initialize(jl_codectx_t &ctx) auto &tbaa = ctx.tbaa(); using Region = jl_aliasinfo_t::Region; gcframe = jl_aliasinfo_t(ctx, Region::gcframe, tbaa.tbaa_gcframe); - stack = jl_aliasinfo_t(ctx, Region::stack, tbaa.tbaa_stack); - data = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_data); - binding = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_binding); + stack = jl_aliasinfo_t(ctx, Region::unknown, tbaa.tbaa_stack); + data = jl_aliasinfo_t(ctx, Region::anydata, tbaa.tbaa_data); + binding = jl_aliasinfo_t(ctx, Region::mutdata, tbaa.tbaa_binding); value = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_value); - mutab = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_mutab); - datatype = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_datatype); - immut = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_immut); - ptrarraybuf = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_ptrarraybuf); - arraybuf = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_arraybuf); - array = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_array); - arrayptr = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_arrayptr); - arraysize = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_arraysize); - arrayselbyte = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_arrayselbyte); - memory = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_memory); - memoryptr = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_memoryptr); - memorylen = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_memorylen); - memoryown = jl_aliasinfo_t(ctx, Region::data, tbaa.tbaa_memoryown); - constant = jl_aliasinfo_t(ctx, Region::constant, tbaa.tbaa_const); - coverage = jl_aliasinfo_t(ctx, Region::coverage, tbaa.tbaa_coverage); -} - -// Alias info for the inline data of an `sret` return buffer. Both the caller -// and the callee compute this from the return type, so that the two sides' -// alias info for the same memory stays identical if LLVM later inlines the call. + mutab = jl_aliasinfo_t(ctx, Region::mutfields, tbaa.tbaa_field); + immut = jl_aliasinfo_t(ctx, Region::immutdata, tbaa.tbaa_field); + datatype = jl_aliasinfo_t(ctx, Region::mutconstdata, tbaa.tbaa_datatype); + array = jl_aliasinfo_t(ctx, Region::mutdata, tbaa.tbaa_array); + arrayptr = jl_aliasinfo_t(ctx, Region::mutdata, tbaa.tbaa_arrayptr); + arraysize = jl_aliasinfo_t(ctx, Region::mutdata, tbaa.tbaa_arraysize); + memory = jl_aliasinfo_t(ctx, Region::mutconstdata, tbaa.tbaa_memory); + memoryptr = jl_aliasinfo_t(ctx, Region::mutconstdata, tbaa.tbaa_memoryptr); + memorylen = jl_aliasinfo_t(ctx, Region::mutconstdata, tbaa.tbaa_memorylen); + memoryown = jl_aliasinfo_t(ctx, Region::mutconstdata, tbaa.tbaa_memoryown); + memorybuf = jl_aliasinfo_t(ctx, Region::memorybuf, tbaa.tbaa_field); + ptrmemorybuf = jl_aliasinfo_t(ctx, Region::memorybuf, tbaa.tbaa_ptrmemorybuf); + memoryselbyte = jl_aliasinfo_t(ctx, Region::memorybuf, tbaa.tbaa_memoryselbyte); + constant = jl_aliasinfo_t(ctx, Region::unknown, tbaa.tbaa_const); + coverage = jl_aliasinfo_t(ctx, Region::unknown, tbaa.tbaa_coverage); +} + +// Alias info for the inline data of a type. static jl_aliasinfo_t best_aliasinfo(jl_codectx_t &ctx, jl_value_t *jt); +static jl_aliasinfo_t union_slot_aliasinfo(jl_codectx_t &ctx, jl_value_t *typ); static jl_aliasinfo_t sret_aliasinfo(jl_codectx_t &ctx, jl_value_t *jlretty, bool all_roots) { if (all_roots) return ctx.alias().gcframe; // A union return's payload pointer is a select of the buffer and a box. if (!jl_is_concrete_type(jlretty)) - return best_aliasinfo(ctx, jlretty); + return union_slot_aliasinfo(ctx, jlretty); // Otherwise the buffer holds no tracked pointers (those travel in the separate - // return_roots buffer), so this write-once copy keeps the return type's layout - // tag, with the region recording that it is not heap memory. - return best_aliasinfo(ctx, jlretty).withRegion(ctx, jl_aliasinfo_t::Region::stack); + // return_roots buffer), so it is just another copy of the return value. + const jl_aliasinfo_t ret_ai = best_aliasinfo(ctx, jlretty); + assert(ret_ai.region == jl_aliasinfo_t::Region::immutdata); + return ret_ai; +} +// Alias info for the contents of a union slot that codegen materialized (a phi, a +// local variable of union type, or an `sret` return buffer). Such a slot is addressed +// by a `select` of a private buffer and a box, but is only ever dereferenced for the +// union's inline members, which are immutable by construction -- so `immutdata` is +// a correct refinement. The rooting invariant at `jl_regions_t` permits it too because +// the buffer will not hold tracked pointers (those are managed separately). +// TODO: examine each element of `typ` to build a more precise tag. +static jl_aliasinfo_t union_slot_aliasinfo(jl_codectx_t &ctx, jl_value_t *typ) +{ + (void)typ; + return ctx.alias().immut; } -// Alias info for a private, write-once stack copy of data described by `src_ai`. -// The copy keeps the source's layout tag, but is also known to move to the stack. -static jl_aliasinfo_t stack_copy_aliasinfo(jl_codectx_t &ctx, const jl_aliasinfo_t &src_ai, jl_value_t *typ) +// Alias info for a private, write-once copy of the data `src_ai` describes: the same +// access tag, re-homed in `immutdata`. +static jl_aliasinfo_t private_copy_aliasinfo(jl_codectx_t &ctx, const jl_aliasinfo_t &src_ai, jl_value_t *typ) { - return (src_ai && src_ai.region != jl_aliasinfo_t::Region::constant ? src_ai : best_aliasinfo(ctx, typ)) - .withRegion(ctx, jl_aliasinfo_t::Region::stack); + // Fall back to the type when there is no tag to inherit (a register aggregate) or + // it is `jtbaa_const`, which would claim the stores filling this copy cannot exist. + return (src_ai && !src_ai.isConstant() ? src_ai : best_aliasinfo(ctx, typ)) + .withRegion(ctx, jl_aliasinfo_t::Region::immutdata); } // Select the best (most precise) alias info for a value of the given julia @@ -2334,11 +2371,23 @@ static jl_aliasinfo_t best_aliasinfo(jl_codectx_t &ctx, jl_value_t *jt) return alias.memory; if (jl_is_array_type(jt)) return alias.array; + if (jt == (jl_value_t*)jl_binding_type) + return alias.binding; // If we're here, we know all subtypes are (im)mutable, even if we // don't know what the exact type is return jl_is_mutable(jt) ? alias.mutab : alias.immut; } +// Alias info for the element data of a `jl_genericmemory_t` with this layout. +// This helps with aiding vectorization in seeing the mutable store to the memory +// is disjoint from the reads of all the other objects (such as the array header fields). +static jl_aliasinfo_t memorybuf_aliasinfo(jl_codectx_t &ctx, const jl_datatype_layout_t *layout) +{ + if (layout->flags.arrayelem_isboxed) + return ctx.alias().ptrmemorybuf; + return ctx.alias().memorybuf; +} + static Type *julia_type_to_llvm(jl_codectx_t &ctx, jl_value_t *jt, bool *isboxed = NULL) JL_CANSAFEPOINT; static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval = -1) JL_CANSAFEPOINT; static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, jl_value_t *scope, bool isvol, const jl_aliasinfo_t &ai, AtomicOrdering order=AtomicOrdering::Unordered) JL_CANSAFEPOINT; @@ -2626,15 +2675,10 @@ static inline jl_cgval_t value_to_pointer(jl_codectx_t &ctx, Value *v, jl_value_ Value *loc; v = zext_struct(ctx, v); Align align(julia_alignment(typ)); - // This write-once copy of pointer-free typed data keeps the type's prior layout tag. - // However, there is a subtlety for pointer-containing copies: llvm-late-gc-lowering - // does not correctly check for dominator for alloca when using it as root, so it could - // use it as a refinement base past the time the alloca has already been reused. Since - // this is pretty much the only time that should matter, we currently hack around that - // here rather than fixing it correctly for now. - jl_aliasinfo_t ai = (tindex == nullptr && jl_is_pointerfree(typ) ? - best_aliasinfo(ctx, typ) : ctx.alias().value) - .withRegion(ctx, jl_aliasinfo_t::Region::stack); + // A write-once copy of the value, so it keeps the type's access tag and the + // `immutdata` region the value has anywhere else. + jl_aliasinfo_t ai = best_aliasinfo(ctx, typ) + .withRegion(ctx, jl_aliasinfo_t::Region::immutdata); 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, "_j_const", *jl_Module); @@ -2676,9 +2720,11 @@ static inline jl_cgval_t value_to_pointer(jl_codectx_t &ctx, const jl_cgval_t &v Constant::getNullValue(ctx.types().T_prjlvalue), ptr_field, Align(sizeof(void *))); } } - jl_aliasinfo_t stack_ai = v.V == nullptr ? ctx.alias().gcframe : best_aliasinfo(ctx, v.typ); - recombine_value(ctx, v, loc, stack_ai, align, false); - return mark_julia_slot(loc, v.typ, v.TIndex, stack_ai); + // The pointers written back here are a silent copy of the ones the separate + // roots buffer holds; this buffer is never itself a root. + jl_aliasinfo_t slot_ai = private_copy_aliasinfo(ctx, v.aliasinfo, v.typ); + recombine_value(ctx, v, loc, slot_ai, align, false); + return mark_julia_slot(loc, v.typ, v.TIndex, slot_ai); } if (v.ispointer() || v.V == nullptr) return v; @@ -4215,7 +4261,7 @@ static Value *emit_bits_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, const ai = parg1.aliasinfo; } else { - ai = parg1.aliasinfo.merge(ctx, parg2.aliasinfo); + ai = parg1.aliasinfo.merge(parg2.aliasinfo); } ai.decorateInst(answer); } @@ -4635,7 +4681,7 @@ static bool emit_f_opmemory(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, // parent (the memory object), so a NULL child is fine. emit_write_barrier(ctx, mem, Constant::getNullValue(ctx.types().T_prjlvalue)); emit_aliased_store(ctx, Constant::getNullValue(elty), ptr, Align(al), - isboxed ? ctx.alias().ptrarraybuf : ctx.alias().arraybuf, + memorybuf_aliasinfo(ctx, layout), ctx.noalias().aliasscope.current, storeOrder); if (lock) emit_lockstate_value(ctx, lock, false); @@ -4681,7 +4727,7 @@ static bool emit_f_opmemory(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, } ptindex = emit_ptrgep(ctx, ptindex, idx0); ptr = data; - ai_ptindex = ctx.alias().arrayselbyte; + ai_ptindex = ctx.alias().memoryselbyte; } else { ptr = (layout->size == 0 ? nullptr : emit_memoryref_ptr(ctx, ref, layout)); @@ -4698,7 +4744,7 @@ static bool emit_f_opmemory(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, *ret = typed_store(ctx, ptr, val, cmp, ety, - isboxed ? ctx.alias().ptrarraybuf : ctx.alias().arraybuf, + memorybuf_aliasinfo(ctx, layout), isunion ? nullptr : ctx.noalias().aliasscope.current, data_owner, isboxed, @@ -5070,8 +5116,8 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, data = ctx.builder.CreateInBoundsGEP(AT, data, idx0); } ptindex = emit_ptrgep(ctx, ptindex, idx0); - *ret = typed_load(ctx, data, NULL, ety, ctx.alias().arraybuf, nullptr, false, - AtomicOrdering::NotAtomic, false, 0, nullptr, ptindex, ctx.alias().arrayselbyte); + *ret = typed_load(ctx, data, NULL, ety, memorybuf_aliasinfo(ctx, layout), nullptr, false, + AtomicOrdering::NotAtomic, false, 0, nullptr, ptindex, ctx.alias().memoryselbyte); } else { Value *ptr = (layout->size == 0 ? nullptr : emit_memoryref_ptr(ctx, ref, layout)); @@ -5084,7 +5130,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, emit_lockstate_value(ctx, lock, true); } *ret = typed_load(ctx, ptr, nullptr, ety, - isboxed ? ctx.alias().ptrarraybuf : ctx.alias().arraybuf, + memorybuf_aliasinfo(ctx, layout), ctx.noalias().aliasscope.current, isboxed, Order, maybenull, al); if (needlock) { @@ -5176,7 +5222,7 @@ static bool emit_builtin_call(jl_codectx_t &ctx, jl_cgval_t *ret, jl_value_t *f, elem = emit_ptrgep(ctx, elem, LLT_ALIGN(sizeof(jl_mutex_t), JL_SMALL_BYTE_ALIGNMENT)); // emit this using the same type as BUILTIN(memoryrefget) // so that LLVM may be able to load-load forward them and fold the result - jl_aliasinfo_t ai = isboxed ? ctx.alias().ptrarraybuf : ctx.alias().arraybuf; + jl_aliasinfo_t ai = memorybuf_aliasinfo(ctx, layout); LoadInst *fldv = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, elem, ctx.types().alignof_ptr); fldv->setOrdering(Order); ai.decorateInst(fldv); @@ -6139,7 +6185,7 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, bool is_opaque_clos retval = mark_julia_slot(derived, jlretty, tindex, - best_aliasinfo(ctx, jlretty), + sret_aliasinfo(ctx, jlretty, returninfo.all_roots), make_lazy_gc_roots(return_roots, returninfo.return_roots, ctx.alias().gcframe)); retval.Vboxed = box; break; @@ -6732,7 +6778,7 @@ static void emit_phinode_assign(jl_codectx_t &ctx, ssize_t idx, jl_value_t *r) J if (dest || allunbox || inline_roots) { Value *ptr = nullptr; PHINode *ptr_phi = nullptr; - jl_aliasinfo_t ai = best_aliasinfo(ctx, phiType); + jl_aliasinfo_t ai = union_slot_aliasinfo(ctx, phiType); PHINode *Tindex_phi = PHINode::Create(getInt8Ty(ctx.builder.getContext()), jl_array_nrows(edges), "tindex_phi"); Tindex_phi->insertInto(BB, InsertPt); if (inline_roots) { @@ -9774,7 +9820,7 @@ static jl_llvm_functions_t Value *lv = try_emit_union_alloca(ctx, (jl_uniontype_t*)jt, allunbox, align, nbytes, inline_roots); if (lv) { lv->setName(jl_symbol_name(s)); - varinfo.value = mark_julia_slot(lv, jt, NULL, best_aliasinfo(ctx, jt)); + varinfo.value = mark_julia_slot(lv, jt, NULL, union_slot_aliasinfo(ctx, jt)); varinfo.pTIndex = emit_static_alloca(ctx, 1, Align(1)); setName(ctx.emission_context, varinfo.pTIndex, "tindex"); // TODO: attach debug metadata to this variable @@ -9923,7 +9969,10 @@ static jl_llvm_functions_t if (isboxed) maybe_mark_argument_dereferenceable(param, argType); theArg = mark_julia_type(ctx, Arg, isboxed, argType); - if (theArg.aliasinfo.tbaa == ctx.tbaa().tbaa_immut) + // The caller roots an immutable argument for the whole call and nothing + // may write its payload while we hold it, so the callee may read it as + // constant memory. + if (theArg.aliasinfo.region == jl_aliasinfo_t::Region::immutdata) theArg.aliasinfo = ctx.alias().constant; } attrs[Arg->getArgNo()] = AttributeSet::get(Arg->getContext(), param); // function declaration attributes @@ -10776,7 +10825,7 @@ static jl_llvm_functions_t else if (jl_is_concrete_type(val.typ) || val.constant) { size_t tindex = get_box_tindex((jl_datatype_t*)(val.constant ? jl_typeof(val.constant) : val.typ), phiType); if (tindex && dest && (!VN || !val.isboxed)) { - emit_unionmove(ctx, dest, phiType, best_aliasinfo(ctx, phiType), val, RTindex, nullptr); + emit_unionmove(ctx, dest, phiType, union_slot_aliasinfo(ctx, phiType), val, RTindex, nullptr); } } else { @@ -10792,7 +10841,7 @@ static jl_llvm_functions_t ConstantInt::get(getInt8Ty(ctx.builder.getContext()), 0)); skip = ctx.builder.CreateOr(isboxed, skip); } - emit_unionmove(ctx, dest, phiType, best_aliasinfo(ctx, phiType), val, tindex, skip); + emit_unionmove(ctx, dest, phiType, union_slot_aliasinfo(ctx, phiType), val, tindex, skip); } } assert(new_union.inline_roots.size() <= lroots.size()); diff --git a/src/gc-stock.c b/src/gc-stock.c index 355b8cb9477ad..ff9eb2a878173 100644 --- a/src/gc-stock.c +++ b/src/gc-stock.c @@ -1103,8 +1103,12 @@ static void gc_sweep_page(gc_page_profiler_serializer_t *s, jl_gc_pool_t *p, jl_ goto done; } // For quick sweep, we might be able to skip the page if the page doesn't - // have any young live cell before marking. - if (!current_sweep_full && !pg->has_young) { + // have any young live cell before marking. Weak-processing pages must + // still be scanned: dead promoted cancellation sources must be unlinked + // in the same collection that frees their young referents. Otherwise a + // cancellation walk can reach a dead source with dangling fields. + if (!current_sweep_full && !pg->has_young && + !jl_atomic_load_relaxed(&pg->has_weak_processing)) { assert(!prev_sweep_full || pg->prev_nold >= pg->nold); if (!prev_sweep_full || pg->prev_nold == pg->nold) { freedall = 0; @@ -1355,7 +1359,9 @@ static int gc_sweep_prescan(jl_ptls_t ptls, jl_gc_padded_page_stack_t *new_gc_al if (!pg->has_marked) { should_scan = 0; } - if (!current_sweep_full && !pg->has_young) { + // Keep the quick-sweep condition in sync with gc_sweep_page. + if (!current_sweep_full && !pg->has_young && + !jl_atomic_load_relaxed(&pg->has_weak_processing)) { assert(!prev_sweep_full || pg->prev_nold >= pg->nold); if (!prev_sweep_full || pg->prev_nold == pg->nold) { should_scan = 0; diff --git a/src/gc-wb-stock.h b/src/gc-wb-stock.h index 00fe8b0663b61..f007c41dbf0e6 100644 --- a/src/gc-wb-stock.h +++ b/src/gc-wb-stock.h @@ -60,74 +60,63 @@ STATIC_INLINE void jl_gc_multi_wb(const void *parent, const jl_value_t *ptr) JL_ jl_gc_queue_multiroot((jl_value_t*)parent, ptr, dt); } +// Maximum number of pointer fields to scan before remembering the owner. +#define JL_GC_COPY_SCAN_MAX_POINTERS 4 + STATIC_INLINE void jl_gc_genericmemory_copy_boxed(const jl_value_t *dest_owner, _Atomic(void*) *dest_p, - jl_genericmemory_t *src, _Atomic(void*) *src_p, - size_t n) JL_NOTSAFEPOINT + jl_genericmemory_t *src JL_UNUSED, _Atomic(void*) *src_p, + size_t n) JL_NOTSAFEPOINT { - if (__unlikely(jl_astaggedvalue(dest_owner)->bits.gc == 3 /* GC_OLD_MARKED */ )) { - if (__unlikely(jl_astaggedvalue(dest_owner)->bits.in_image == 1 /* GC_IN_IMAGE_NOT_REMSET */)) { - // GC_MARKED optimizations are invalid for generations >= 2 - jl_gc_queue_root(dest_owner); - } - else if (jl_astaggedvalue(jl_genericmemory_owner(src))->bits.gc != 3 /* GC_OLD_MARKED */) { - // check each value as it is stored, until the first young one queues the - // owner; the rest of the copy then needs no checks - size_t done = 0; - if (dest_p < src_p || dest_p > src_p + n) { - for (; done < n; done++) { // copy forwards - void *val = jl_atomic_load_relaxed(src_p + done); - jl_atomic_store_release(dest_p + done, val); - // `val` is young or old-unmarked (or dest is image and val is non-image) - if (val && !(jl_astaggedvalue(val)->bits.gc & 1 /* GC_MARKED */)) { - jl_gc_queue_root(dest_owner); - ++done; - break; - } - } - dest_p += done; - src_p += done; - } - else { - for (; done < n; done++) { // copy backwards - void *val = jl_atomic_load_relaxed(src_p + n - done - 1); - jl_atomic_store_release(dest_p + n - done - 1, val); - // `val` is young or old-unmarked (or dest is image and val is non-image) - if (val && !(jl_astaggedvalue(val)->bits.gc & 1 /* GC_MARKED */)) { - jl_gc_queue_root(dest_owner); - ++done; - break; - } - } + memmove_refs(dest_p, src_p, n); + if (n == 0 || __likely(jl_astaggedvalue(dest_owner)->bits.gc != 3 /* GC_OLD_MARKED */)) + return; // destination is young or remembered, or the copy is empty + if (n <= JL_GC_COPY_SCAN_MAX_POINTERS && + __likely(jl_astaggedvalue(dest_owner)->bits.in_image != 1 /* GC_IN_IMAGE_NOT_REMSET */)) { + // For small copies into old objects, scan what we just copied and see if all the + // elements were old to avoid adding `dest` to the remset, which saves a full scan + // of the destination memory at the next GC + for (size_t i = 0; i < n; i++) { + void *val = jl_atomic_load_relaxed(dest_p + i); + if (val && !(jl_astaggedvalue(val)->bits.gc & 1 /* GC_MARKED */)) { + jl_gc_queue_root(dest_owner); + return; } - n -= done; } + return; } - memmove_refs(dest_p, src_p, n); + jl_gc_queue_root(dest_owner); + return; } STATIC_INLINE void jl_gc_genericmemory_copy_ptr(const jl_value_t *owner, char *destdata, - jl_genericmemory_t *src, char *srcdata, + jl_genericmemory_t *src JL_UNUSED, char *srcdata, size_t n, jl_datatype_t *dt) JL_NOTSAFEPOINT { - size_t elsz = dt->layout->size; - if (__unlikely(jl_astaggedvalue(owner)->bits.gc == 3 /* GC_OLD_MARKED */)) { - if (__unlikely(jl_astaggedvalue(owner)->bits.in_image == 1 /* GC_IN_IMAGE_NOT_REMSET */)) { - // GC_MARKED optimizations are invalid for generations >= 2 - jl_gc_queue_root(owner); - } - else { - jl_value_t *src_owner = jl_genericmemory_owner(src); - if (jl_astaggedvalue(src_owner)->bits.gc != 3 /* GC_OLD_MARKED */) { - dt = (jl_datatype_t*)jl_tparam1(dt); - for (size_t done = 0; done < n; done++) { - char *s = srcdata + done * elsz; - if (*((jl_value_t**)s + dt->layout->first_ptr) != NULL) - jl_gc_queue_multiroot(owner, s, dt); + const jl_datatype_layout_t *ly = dt->layout; + size_t stride = ly->size / sizeof(void*); + _Atomic(void*) *dest_p = (_Atomic(void*)*)destdata; + memmove_refs(dest_p, (_Atomic(void*)*)srcdata, n * stride); + if (n == 0 || __likely(jl_astaggedvalue(owner)->bits.gc != 3 /* GC_OLD_MARKED */)) + return; // destination is young or remembered, or the copy is empty + if (n * ly->npointers <= JL_GC_COPY_SCAN_MAX_POINTERS && + __likely(jl_astaggedvalue(owner)->bits.in_image != 1 /* GC_IN_IMAGE_NOT_REMSET */)) { + // For small copies into old objects, scan what we just copied and see if all the + // elements were old to avoid adding `dest` to the remset, which saves a full scan + // of the destination memory at the next GC + for (size_t i = 0; i < n; i++) { + for (uint32_t j = 0; j < ly->npointers; j++) { + size_t offset = jl_ptr_offset(dt, j); + void *val = jl_atomic_load_relaxed(dest_p + i * stride + offset); + if (val && !(jl_astaggedvalue(val)->bits.gc & 1 /* GC_MARKED */)) { + jl_gc_queue_root(owner); + return; } } } + return; } - memmove_refs((_Atomic(void*)*)destdata, (_Atomic(void*)*)srcdata, n * elsz / sizeof(void*)); + jl_gc_queue_root(owner); + return; } STATIC_INLINE void jl_gc_genericmemory_clear(const jl_value_t *owner JL_UNUSED, diff --git a/src/intrinsics.cpp b/src/intrinsics.cpp index 5c20afd069685..e46ec60892634 100644 --- a/src/intrinsics.cpp +++ b/src/intrinsics.cpp @@ -518,7 +518,9 @@ static Value *emit_unbox(jl_codectx_t &ctx, Type *to, const jl_cgval_t &x, Maybe std::string type_str = jl_is_datatype(x.typ) ? jl_symbol_name(((jl_datatype_t*)x.typ)->name->name) : ""; return "unbox::" + type_str; }); - auto combined_ai = best_aliasinfo(ctx, x.typ); + // The pointers written back here are a silent copy of the ones the separate + // roots buffer holds; this buffer is never itself a root. + auto combined_ai = private_copy_aliasinfo(ctx, x.aliasinfo, x.typ); recombine_value(ctx, x, combined, combined_ai, alignment, false); p = combined; ai = combined_ai; @@ -1010,14 +1012,13 @@ static jl_cgval_t emit_atomic_pointerref(jl_codectx_t &ctx, ArrayRef setName(ctx.emission_context, strct, "atomic_pointerref_box"); Value *thePtr = emit_unbox(ctx, getPointerTy(ctx.builder.getContext()), e); Type *loadT = Type::getIntNTy(ctx.builder.getContext(), nb * 8); - jl_aliasinfo_t ai = best_aliasinfo(ctx, ety); LoadInst *load = ctx.builder.CreateAlignedLoad(loadT, thePtr, Align(nb)); setName(ctx.emission_context, load, "atomic_pointerref"); - ai.decorateInst(load); + ctx.alias().data.decorateInst(load); load->setOrdering(llvm_order); thePtr = strct; StoreInst *store = ctx.builder.CreateAlignedStore(load, thePtr, Align(julia_alignment(ety))); - ai.decorateInst(store); + best_aliasinfo(ctx, ety).decorateInst(store); return mark_julia_type(ctx, strct, true, ety); } else { @@ -1296,7 +1297,7 @@ static jl_cgval_t emit_ifelse(jl_codectx_t &ctx, jl_cgval_t c, jl_cgval_t x, jl_ y_ptr = decay_derived(ctx, y_ptr); ifelse_result = ctx.builder.CreateSelect(isfalse, y_ptr, x_ptr); setName(ctx.emission_context, ifelse_result, "ifelse_result"); - ifelse_ai = x.aliasinfo.merge(ctx, y.aliasinfo); + ifelse_ai = x.aliasinfo.merge(y.aliasinfo); if (ifelse_ai.tbaa == NULL) { // LLVM won't return a TBAA result for the root, but mark_julia_slot requires it: make it now auto *OffsetNode = ConstantAsMetadata::get(ConstantInt::get(getInt64Ty(ctx.builder.getContext()), 0)); diff --git a/src/julia.h b/src/julia.h index c72dbe97a0f48..89176b96d6f98 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1385,6 +1385,7 @@ JL_DLLEXPORT JL_CONST_FUNC jl_gcframe_t **(jl_get_pgcstack)(void) JL_GLOBALLY_RO #define jl_current_task (container_of(jl_get_pgcstack(), jl_task_t, gcstack)) STATIC_INLINE jl_value_t *jl_genericmemory_owner(jl_genericmemory_t *m JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT; +static inline uint32_t jl_ptr_offset(jl_datatype_t *st, int i) JL_NOTSAFEPOINT; // this is a version of memcpy that preserves atomic memory ordering // which makes it safe to use for objects that can contain memory references diff --git a/src/llvm-codegen-shared.h b/src/llvm-codegen-shared.h index 46868194cf19b..3536465a40e05 100644 --- a/src/llvm-codegen-shared.h +++ b/src/llvm-codegen-shared.h @@ -182,16 +182,59 @@ static inline bool isTBAA(llvm::MDNode *TBAA, std::initializer_list return false; } -// Check if this is a load from an immutable value. The easiest way to do so is -// to look at the AA metadata and see if it derives from jtbaa_immut. -static inline bool isLoadFromImmut(llvm::LoadInst *LI) +// The '!alias.scope' domain naming which of codegen's memory regions an access may touch. +#define JL_REGION_DOMAIN_NAME "jnoalias" + +// The regions in whose domain a base object cannot stop referencing a tracked +// pointer stored in them while the base is live. +static inline bool isRootedRegionName(llvm::StringRef name) { - if (LI->getMetadata(llvm::LLVMContext::MD_invariant_load)) - return true; - llvm::MDNode *TBAA = LI->getMetadata(llvm::LLVMContext::MD_tbaa); - if (isTBAA(TBAA, {"jtbaa_immut", "jtbaa_const", "jtbaa_datatype"})) + return name == "jnoalias_immutdata" || name == "jnoalias_mutconstdata"; +} + +// Whether the object rooting the address `LI` loads from also roots the loaded value +// -- so that late-gc-lowering may refine the loaded pointer to the load's pointer +// operand instead of giving it a gc-frame slot of its own. +// +// This asks whether the slot is ever overwritten, so that what the base references +// here is fixed for as long as it lives. The region records that ('!alias.scope'), the +// access tag ('!tbaa') does not. A load qualifies if its scopes in the +// region domain are nonempty and all rooted. +static inline bool isLoadFromRootedRegion(llvm::LoadInst *LI) +{ + using namespace llvm; + // Constant memory never changes, so the base can never stop referencing what is + // stored here, wherever it lives. This is also the only leg that fires on foreign + // IR carrying no region metadata. + if (LI->getMetadata(LLVMContext::MD_invariant_load)) return true; - return false; + MDNode *scopes = LI->getMetadata(LLVMContext::MD_alias_scope); + if (!scopes) + return false; + bool found = false; + for (const MDOperand &op : scopes->operands()) { + MDNode *scope = dyn_cast_or_null(op.get()); + if (!scope) + continue; + AliasScopeNode snode(scope); + const MDNode *domain = snode.getDomain(); + if (!domain || domain->getNumOperands() < 1) + continue; + MDString *domain_name = dyn_cast(domain->getOperand(0)); + if (!domain_name || domain_name->getString() != JL_REGION_DOMAIN_NAME) + continue; + // A scope is named either by its string key in operand 0 ({name, domain}) + // or, when a self-reference keys it, by a trailing name operand + // ({self, domain, name}); AliasScopeNode reads the latter. + StringRef name = snode.getName(); + if (name.empty()) + if (MDString *key = dyn_cast(scope->getOperand(0))) + name = key->getString(); + if (name.empty() || !isRootedRegionName(name)) + return false; // may reside in a region that can drop the reference + found = true; + } + return found; } static inline bool isConstGV(llvm::GlobalVariable *gv) diff --git a/src/llvm-late-gc-lowering.cpp b/src/llvm-late-gc-lowering.cpp index f848f5c749a6d..9d0596e9f8318 100644 --- a/src/llvm-late-gc-lowering.cpp +++ b/src/llvm-late-gc-lowering.cpp @@ -947,7 +947,7 @@ static bool isLoadFromConstGV(LoadInst *LI, bool &task_local, PhiSet *seen) auto load_base = LI->getPointerOperand()->stripInBoundsOffsets(); assert(load_base); // Static analyzer auto gv = dyn_cast(load_base); - if (isLoadFromImmut(LI)) { + if (isLoadFromRootedRegion(LI)) { if (gv) return true; return isLoadFromConstGV(load_base, task_local, seen); @@ -1360,7 +1360,7 @@ State LateLowerGCFrame::LocalScan(Function &F) { continue; } if (LoadInst *LI = dyn_cast(&I)) { - // If this is a load from an immutable, we know that + // If this is a load from a rooted region, we know that // this object will always be rooted as long as the // object we're loading from is, so we can refine uses // of this object to uses of the object we're loading @@ -1369,7 +1369,7 @@ State LateLowerGCFrame::LocalScan(Function &F) { Type *Ty = LI->getType()->getScalarType(); bool refined_globally = false; bool task_local = false; - if (isLoadFromImmut(LI) && isSpecialPtr(LI->getPointerOperand()->getType())) { + if (isLoadFromRootedRegion(LI) && isSpecialPtr(LI->getPointerOperand()->getType())) { RefinedPtr.push_back(Number(S, LI->getPointerOperand())); } else if (isLoadFromConstGV(LI, task_local)) { diff --git a/stdlib/REPL/docs/src/index.md b/stdlib/REPL/docs/src/index.md index 093b6559c6ac5..5dcdc3795b257 100644 --- a/stdlib/REPL/docs/src/index.md +++ b/stdlib/REPL/docs/src/index.md @@ -453,7 +453,8 @@ julia> α="\alpha[TAB]" # LaTeX completion also works in strings julia> α="α" ``` -A full list of tab-completions can be found in the [Unicode Input](@ref) section of the manual. +Full lists of tab-completions can be found in the [Unicode Input](@ref) and +[Emoji Input](@ref) sections of the manual. Completion of paths works for strings and julia's shell mode: diff --git a/test/cancellation.jl b/test/cancellation.jl index c8b8c7bbb4057..b252811004a9e 100644 --- a/test/cancellation.jl +++ b/test/cancellation.jl @@ -147,6 +147,24 @@ end @test length(kids) == 2 # kept + escaped; the dead child was spliced out @test kept in kids && escaped_tok.source in kids + # Dead promoted children must be unlinked even if their pages have no young + # allocations, before a cancellation walk can reach their freed walk locks. + @noinline function make_promoted_children(root, n) + promoted = [CancellationTokenSource(CancellationToken(root)) for _ in 1:n] + GC.gc(false) # promote the children before allocating their walk locks + foreach(cancel!, promoted) + return nothing + end + for _ in 1:20 + root3 = CancellationTokenSource() + kept3 = CancellationTokenSource(CancellationToken(root3)) + make_promoted_children(root3, 200) + GC.gc(false) + @test cancel!(root3) + # MMTk may retain promoted children until a full collection. + @test live_children(root3) == [kept3] skip=!Base.USING_STOCK_GC + end + # linked sources: a source with several parents is cancelled by any of # them (the graph is a DAG, not just a tree) la = CancellationTokenSource() diff --git a/test/docs.jl b/test/docs.jl index bf579658ea16c..666c0b6e21db5 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -13,6 +13,21 @@ end using Markdown using REPL +# The generated Unicode/emoji documentation tables are built by doc/make.jl. +# Keep the two origin-sensitive operations here covered independently of the +# Documenter build: UnicodeData fields are selected from a zero-origin split, +# and enumerate's zero-origin counter separates subsequent completions. +let fields = vcat(["0041", "LATIN CAPITAL LETTER A"], fill("", 8), ["A"]) + @test fields[[0, 1, 10]] == ["0041", "LATIN CAPITAL LETTER A", "A"] +end +let rendered = Any[] + for (i, input) in enumerate(["\\alpha", "\\Alpha"]) + i > 0 && push!(rendered, ", ") + push!(rendered, Markdown.Code("", input)) + end + @test [x isa Markdown.Code ? x.code : x for x in rendered] == ["\\alpha", ", ", "\\Alpha"] +end + using REPL: @repl, repl_latex, _repl, accessible using InteractiveUtils: apropos diff --git a/test/errorshow.jl b/test/errorshow.jl index a75a7ad8664e7..35981a02f1d88 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -1683,6 +1683,56 @@ let bt @test occursin("simplify_kwargs_type(pos::$Int; kws::@Kwargs{kw1::Float64, kw2::String})", bt_str) end +# the internal `Core.kwcall` sorter frame is hidden in favor of the demangled keyword method +@noinline kwsorter_outer(; s = 4) = kwsorter_inner(s) +@noinline kwsorter_inner(s) = error() +@testset "kwcall frames" begin + # the trace also runs through the keyword calls of whatever invoked this test, so + # identify only the sorter frame belonging to `kwsorter_outer` + function iskwsorterframe(frame, @nospecialize(f)) + code = frame.linfo isa Core.CodeInstance ? frame.linfo.def : frame.linfo + if code isa Core.MethodInstance + def = code.def + return def isa Method && def.name !== :kwcall && + def.sig <: Tuple{typeof(Core.kwcall),NamedTuple,typeof(f),Vararg} + end + return frame.func === :kwcall + end + bt = try + kwsorter_outer(s = 3) + nothing + catch + catch_backtrace() + end + @test bt !== nothing + st = stacktrace(bt) + # the raw trace contains the keyword sorter frame ... + @test count(frame -> iskwsorterframe(frame, kwsorter_outer), st) == 1 + # ... which `process_backtrace` drops + @test !any(((frame, _),) -> iskwsorterframe(frame, kwsorter_outer), Base.process_backtrace(st)) + # leaving the demangled keyword method as the only frame for `kwsorter_outer` + bt_str = sprint(Base.show_backtrace, bt) + @test occursin("kwsorter_outer(; s::$Int)", bt_str) + @test !occursin("kwcall", bt_str) + @test count(l -> occursin(r"\] kwsorter_outer\(", l), split(bt_str, '\n')) == 1 + + # Depending on the debug info available, the sorter frame carries a `MethodInstance`, a + # `CodeInstance`, or no code at all, so check every spelling rather than just the one + # this platform happens to produce. + kwframe = st[findfirst(frame -> iskwsorterframe(frame, kwsorter_outer), st)] + mi = kwframe.linfo isa Core.CodeInstance ? kwframe.linfo.def : kwframe.linfo + @test mi isa Core.MethodInstance + userframe = Base.StackTraces.StackFrame(:f, Symbol("user.jl"), 1) + kept(frame) = [f.func for (f, _) in Base.process_backtrace(Base.StackFrame[frame, userframe])] + @testset "linfo::$(nameof(typeof(linfo)))" for linfo in Any[mi, mi.cache] + # recognized by its signature, whichever way the code is attached + @test kept(Base.StackTraces.StackFrame(kwframe.func, kwframe.file, kwframe.line, + linfo, false, false, 0)) == [:f] + end + # with no code attached at all, the frame is recognized by name instead + @test kept(Base.StackTraces.StackFrame(:kwcall, Symbol("none"), 1)) == [:f] +end + # Test Base.print_with_compare in convert MethodErrors struct TypeCompareError{A,B} <: Exception end let e = @test_throws MethodError convert(TypeCompareError{Float64,1}, TypeCompareError{Float64,2}()) diff --git a/test/gc/copyto.jl b/test/gc/copyto.jl index 042de4072cbc1..75e2b258f0545 100644 --- a/test/gc/copyto.jl +++ b/test/gc/copyto.jl @@ -10,3 +10,119 @@ copyto!(dest, src) for (x, y) in zip(dest, src) @assert x === y end + +# Young references copied into old memories must survive after the source is gone, +# for small and large spans, inline pointer fields, and wide pointer descriptors. +struct CopyInline + first::Any + payload::Int + last::Any +end + +struct CopyWideInline + padding::NTuple{256,UInt} + value::Any +end + +@noinline function copy_young_refs!(dest, make_element, n) + src = [make_element(i) for i in 1:n] + copyto!(dest, 2, src, 0, n) + return nothing +end + +for (make_element, read_element) in ( + (Ref{Int}, x -> x[]), + (i -> CopyInline(nothing, i, Ref(i)), x -> (x.first, x.payload, x.last[])), + (i -> CopyWideInline(ntuple(_ -> UInt(0), 256), Ref(i)), x -> x.value[]), +) + for n in (1, 2, 3, 4, 5, 65), len in (n + 4, 4n + 4) + local dest = fill(make_element(0), len) + for _ in 1:3 + GC.gc(true) + GC.gc(false) + end + copy_young_refs!(dest, make_element, n) + GC.gc(false) + GC.gc(false) + for i in 1:n + @assert read_element(dest[i + 1]) == read_element(make_element(i)) + end + @assert read_element(dest[0]) == read_element(make_element(0)) + @assert read_element(dest[end]) == read_element(make_element(0)) + end +end + +struct CopyUndef + value::Any + CopyUndef() = new() +end + +struct CopyPartial + first::CopyUndef + last::Any +end + +@noinline function copy_partial!(dest) + child = Ref(42) + src = [CopyPartial(CopyUndef(), child)] + weak = WeakRef(child) + copyto!(dest, src) + return weak +end + +let dest = fill(CopyPartial(CopyUndef(), nothing), 8) + for _ in 1:3 + GC.gc(true) + GC.gc(false) + end + weak = copy_partial!(dest) + GC.@preserve dest begin + GC.gc(false) + GC.gc(false) + @assert weak.value !== nothing + @assert weak.value[] == 42 + end +end + +# A concurrent store into an old source must not let a copied young reference escape the barrier. +@noinline copy_race_ready(flag) = flag[] +@noinline copy_race_started(dest) = dest[0] === :src +@noinline copy_race_holds(dest, weak) = dest[end] === weak.value + +@noinline function copy_race_writer!(src, dest, ready) + child = Ref(42) + weak = WeakRef(child) + Threads.atomic_xchg!(ready, true) + while !copy_race_started(dest) end + src[end] = child + return weak +end + +function copy_race_trial(n) + src = fill!(Memory{Any}(undef, n), :src) + dest = fill!(Memory{Any}(undef, n), :dest) + GC.gc(true) + GC.gc(true) + ready = Threads.Atomic{Bool}(false) + result = Ref{WeakRef}() + Threads.@threads :static for worker in 1:2 + if worker == 1 + while !copy_race_ready(ready) end + copyto!(dest, src) + else + result[] = copy_race_writer!(src, dest, ready) + end + end + weak = result[] + copy_race_holds(dest, weak) || return false + src[end] = nothing + GC.@preserve dest begin + GC.gc(false) + @assert weak.value !== nothing "concurrently copied child was collected" + end + return true +end + +if Threads.nthreads(:default) >= 2 + @assert any(_ -> copy_race_trial(1_000_000), 1:10) "copy race interleaving was not exercised" +end diff --git a/test/gmp.jl b/test/gmp.jl index 0567a4fec6d0f..bf293e0f3284b 100644 --- a/test/gmp.jl +++ b/test/gmp.jl @@ -825,7 +825,7 @@ end end @testset "hashing" begin - for i in 1:10:100 + for i in vcat(0, 1:10:100) for shift in vcat(0:8, 9:8:81) for sgn in (1, -1) bint = sgn * (big(11)^i << shift) @@ -835,6 +835,15 @@ end @invoke(hash(bint::Real, Base.HASH_SEED)) @test Base.hash_integer(bint, Base.HASH_SEED) == @invoke(Base.hash_integer(bint::Integer, Base.HASH_SEED)) + + brat = bint // 3 + for T in Base.BitInteger_types + typemin(T) <= bint <= typemax(T) || continue + rat = Rational{T}(brat) + for h in (zero(UInt), Base.HASH_SEED, typemax(UInt)) + @test hash(rat, h) == hash(brat, h) + end + end end end end diff --git a/test/llvmpasses/gcroots.ll b/test/llvmpasses/gcroots.ll index c9a646e88b717..d5876c08067e4 100644 --- a/test/llvmpasses/gcroots.ll +++ b/test/llvmpasses/gcroots.ll @@ -349,9 +349,9 @@ define {} addrspace(10)* @gv_const() { top: %pgcstack = call {}*** @julia.get_pgcstack() %ptls = call {}*** @julia.ptls_states() - %v10 = load {}*, {}** @gv1, !tbaa !2 + %v10 = load {}*, {}** @gv1, !tbaa !2, !invariant.load !3 %v1 = addrspacecast {}* %v10 to {} addrspace(10)* - %v2 = load {} addrspace(10)*, {} addrspace(10)** @gv2, !tbaa !2 + %v2 = load {} addrspace(10)*, {} addrspace(10)** @gv2, !tbaa !2, !invariant.load !3 call void @jl_safepoint() call void @one_arg_boxed({} addrspace(10)* %v1) call void @one_arg_boxed({} addrspace(10)* %v2) @@ -804,3 +804,4 @@ top: !0 = !{!"jtbaa"} !1 = !{!"jtbaa_const", !0, i64 0} !2 = !{!1, !1, i64 0, i64 1} +!3 = !{} diff --git a/test/llvmpasses/refinements.ll b/test/llvmpasses/refinements.ll index 5b277d47319dd..61934185900c9 100644 --- a/test/llvmpasses/refinements.ll +++ b/test/llvmpasses/refinements.ll @@ -16,7 +16,7 @@ define void @argument_refinement({} addrspace(10)* %a) { %pgcstack = call {}*** @julia.get_pgcstack() %ptls = call {}*** @julia.ptls_states() %casted1 = bitcast {} addrspace(10)* %a to {} addrspace(10)* addrspace(10)* - %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1 + %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1, !alias.scope !7 call void @jl_safepoint() %casted2 = bitcast {} addrspace(10)* %loaded1 to i64 addrspace(10)* %loaded2 = load i64, i64 addrspace(10)* %casted2 @@ -31,7 +31,7 @@ define void @heap_refinement1(i64 %a) { %ptls = call {}*** @julia.ptls_states() %aboxed = call {} addrspace(10)* @ijl_box_int64(i64 signext %a) %casted1 = bitcast {} addrspace(10)* %aboxed to {} addrspace(10)* addrspace(10)* - %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1 + %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1, !alias.scope !7 ; OPAQUE: store ptr addrspace(10) %aboxed call void @jl_safepoint() %casted2 = bitcast {} addrspace(10)* %loaded1 to i64 addrspace(10)* @@ -48,7 +48,7 @@ define void @heap_refinement2(i64 %a) { %ptls = call {}*** @julia.ptls_states() %aboxed = call {} addrspace(10)* @ijl_box_int64(i64 signext %a) %casted1 = bitcast {} addrspace(10)* %aboxed to {} addrspace(10)* addrspace(10)* - %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1 + %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1, !alias.scope !7 ; OPAQUE: store ptr addrspace(10) %loaded1 call void @jl_safepoint() %casted2 = bitcast {} addrspace(10)* %loaded1 to i64 addrspace(10)* @@ -64,7 +64,7 @@ define void @issue22770() { %ptls = call {}*** @julia.ptls_states() %y = call {} addrspace(10)* @allocate_some_value() %casted1 = bitcast {} addrspace(10)* %y to {} addrspace(10)* addrspace(10)* - %x = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1 + %x = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1, !alias.scope !7 ; OPAQUE: store ptr addrspace(10) %y, %a = call {} addrspace(10)* @allocate_some_value() @@ -138,7 +138,7 @@ top: L1: %continue = phi i1 [ true, %top ], [ false, %L1 ] %p = phi {} addrspace(10)* [ %x, %top ], [ %v, %L1 ] - %v0 = load {}*, {}** @gv1, !tbaa !4 + %v0 = load {}*, {}** @gv1, !tbaa !4, !invariant.load !18 %v = addrspacecast {}* %v0 to {} addrspace(10)* call void @one_arg_boxed({} addrspace(10)* %v) call void @one_arg_boxed({} addrspace(10)* %p) @@ -164,7 +164,7 @@ L1: ; of the phi node. Therefore, we need only one gc slot for `%a`. %p = phi {} addrspace(10)* [ %x, %top ], [ %v, %L1 ] %ca = bitcast {} addrspace(10)* %a to {} addrspace(10)* addrspace(10)* - %v = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %ca, !tbaa !1 + %v = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %ca, !tbaa !1, !alias.scope !7 call void @one_arg_boxed({} addrspace(10)* %v) call void @one_arg_boxed({} addrspace(10)* %p) br i1 %continue, label %L1, label %L2 @@ -187,7 +187,7 @@ L1: ; `%p` has circular dependency but it can only be derived from `%a` which dominate `%p`. %p = phi {} addrspace(10)* [ %a, %top ], [ %v, %L1 ] %ca = bitcast {} addrspace(10)* %p to {} addrspace(10)* addrspace(10)* - %v = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %ca, !tbaa !1 + %v = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %ca, !tbaa !1, !alias.scope !7 call void @one_arg_boxed({} addrspace(10)* %v) call void @one_arg_boxed({} addrspace(10)* %p) br i1 %continue, label %L1, label %L2 @@ -223,11 +223,135 @@ define {} addrspace(10)* @setfield({} addrspace(10)* %p) { ret {} addrspace(10)* %c } +; The rooting refinement is keyed on the memory region ('!alias.scope'), not on +; the layout description ('!tbaa'): a content tag that says nothing about +; immutability still refines ... +define void @refine_region_without_type_tag({} addrspace(10)* %a) { +; CHECK-LABEL: @refine_region_without_type_tag +; CHECK-NOT: %gcframe + %pgcstack = call {}*** @julia.get_pgcstack() + %ptls = call {}*** @julia.ptls_states() + %casted1 = bitcast {} addrspace(10)* %a to {} addrspace(10)* addrspace(10)* + %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !5, !alias.scope !7 + call void @jl_safepoint() + %casted2 = bitcast {} addrspace(10)* %loaded1 to i64 addrspace(10)* + %loaded2 = load i64, i64 addrspace(10)* %casted2 + ret void +} + +; ... and the same layout tag also describes the element data of a `GenericMemory`, +; which may be overwritten while the memory object stays live. +define void @dont_refine_memorybuf_region({} addrspace(10)* %a) { +; CHECK-LABEL: @dont_refine_memorybuf_region +; OPAQUE: %gcframe = alloca ptr addrspace(10), i32 3 + %pgcstack = call {}*** @julia.get_pgcstack() + %ptls = call {}*** @julia.ptls_states() + %casted1 = bitcast {} addrspace(10)* %a to {} addrspace(10)* addrspace(10)* + %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1, !alias.scope !10 + call void @jl_safepoint() + %casted2 = bitcast {} addrspace(10)* %loaded1 to i64 addrspace(10)* + %loaded2 = load i64, i64 addrspace(10)* %casted2 + ret void +} + +; An access merged across regions may reside in the unrooted one, so any unrooted +; member of the scope list vetoes the refinement. +define void @dont_refine_merged_region({} addrspace(10)* %a) { +; CHECK-LABEL: @dont_refine_merged_region +; OPAQUE: %gcframe = alloca ptr addrspace(10), i32 3 + %pgcstack = call {}*** @julia.get_pgcstack() + %ptls = call {}*** @julia.ptls_states() + %casted1 = bitcast {} addrspace(10)* %a to {} addrspace(10)* addrspace(10)* + %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1, !alias.scope !12 + call void @jl_safepoint() + %casted2 = bitcast {} addrspace(10)* %loaded1 to i64 addrspace(10)* + %loaded2 = load i64, i64 addrspace(10)* %casted2 + ret void +} + +; Scopes from another domain (`@aliasscope`, loop versioning, ...) describe +; something else entirely, even when they happen to share our names. +define void @dont_refine_foreign_domain({} addrspace(10)* %a) { +; CHECK-LABEL: @dont_refine_foreign_domain +; OPAQUE: %gcframe = alloca ptr addrspace(10), i32 3 + %pgcstack = call {}*** @julia.get_pgcstack() + %ptls = call {}*** @julia.ptls_states() + %casted1 = bitcast {} addrspace(10)* %a to {} addrspace(10)* addrspace(10)* + %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1, !alias.scope !15 + call void @jl_safepoint() + %casted2 = bitcast {} addrspace(10)* %loaded1 to i64 addrspace(10)* + %loaded2 = load i64, i64 addrspace(10)* %casted2 + ret void +} + +; No region metadata at all is no information, so we conservatively re-root. +define void @dont_refine_without_region({} addrspace(10)* %a) { +; CHECK-LABEL: @dont_refine_without_region +; OPAQUE: %gcframe = alloca ptr addrspace(10), i32 3 + %pgcstack = call {}*** @julia.get_pgcstack() + %ptls = call {}*** @julia.ptls_states() + %casted1 = bitcast {} addrspace(10)* %a to {} addrspace(10)* addrspace(10)* + %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1 + call void @jl_safepoint() + %casted2 = bitcast {} addrspace(10)* %loaded1 to i64 addrspace(10)* + %loaded2 = load i64, i64 addrspace(10)* %casted2 + ret void +} + +; A `const` field of a mutable object cannot be reassigned either, so it roots what +; it holds just as an immutable's payload does. +define void @refine_mutconst_region({} addrspace(10)* %a) { +; CHECK-LABEL: @refine_mutconst_region +; CHECK-NOT: %gcframe + %pgcstack = call {}*** @julia.get_pgcstack() + %ptls = call {}*** @julia.ptls_states() + %casted1 = bitcast {} addrspace(10)* %a to {} addrspace(10)* addrspace(10)* + %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !tbaa !1, !alias.scope !19 + call void @jl_safepoint() + %casted2 = bitcast {} addrspace(10)* %loaded1 to i64 addrspace(10)* + %loaded2 = load i64, i64 addrspace(10)* %casted2 + ret void +} + +; Foreign IR carrying no region metadata still refines through `!invariant.load`. +define void @refine_invariant_load({} addrspace(10)* %a) { +; CHECK-LABEL: @refine_invariant_load +; CHECK-NOT: %gcframe + %pgcstack = call {}*** @julia.get_pgcstack() + %ptls = call {}*** @julia.ptls_states() + %casted1 = bitcast {} addrspace(10)* %a to {} addrspace(10)* addrspace(10)* + %loaded1 = load {} addrspace(10)*, {} addrspace(10)* addrspace(10)* %casted1, !invariant.load !18 + call void @jl_safepoint() + %casted2 = bitcast {} addrspace(10)* %loaded1 to i64 addrspace(10)* + %loaded2 = load i64, i64 addrspace(10)* %casted2 + ret void +} + attributes #0 = { argmemonly norecurse nounwind readonly } attributes #1 = { inaccessiblememonly norecurse nounwind } !0 = !{!"jtbaa"} !1 = !{!2, !2, i64 0} -!2 = !{!"jtbaa_immut", !0, i64 0} +!2 = !{!"jtbaa_value", !0, i64 0} !3 = !{!"jtbaa_const", !0, i64 0} !4 = !{!3, !3, i64 0, i64 1} +!5 = !{!6, !6, i64 0} +!6 = !{!"jtbaa_memorylen", !2, i64 0} + +; The memory-region '!alias.scope' domain (`jl_regions_t` in codegen.cpp). +!7 = !{!8} +!8 = !{!"jnoalias_immutdata", !9} +!9 = !{!"jnoalias"} +!10 = !{!11} +!11 = !{!"jnoalias_memorybuf", !9} +!12 = !{!8, !11} +; Same scope name, but a domain that is not ours. +!15 = !{!16} +!16 = !{!"jnoalias_immutdata", !17} +!17 = !{!"not_jnoalias"} + +!18 = !{} + +!19 = !{!20} +!20 = !{!"jnoalias_mutconstdata", !9} + diff --git a/test/reflection.jl b/test/reflection.jl index b7d90874bc59c..e9489f2ea7f62 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -1293,6 +1293,20 @@ end @test occursin("f2#", String(nameof(f))) end +module BodyFunctionWorld +const before = Base.get_world_counter() +f(x; a=1) = x + a +end + +@testset "bodyfunction world age" begin + m = only(methods(BodyFunctionWorld.f)) + # the body function is found even when the calling task runs in a world age + # that predates its definition + f = Base.invoke_in_world(BodyFunctionWorld.before, Base.bodyfunction, m) + @test occursin("f#", String(nameof(f))) + @test Base.bodyfunction(m; world=m.primary_world) === f +end + @testset "code_typed(; world)" begin mod = @eval module $(gensym()) end