From 3a9d4a333fa91a6c1bbf80d1da96999a8e98e9cc Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki Date: Tue, 19 Jul 2022 16:20:51 -0400 Subject: [PATCH] effects: add `:inaccessiblememonly` effect This is a preparatory PR for future improvements on the effects analysis. This commit adds the `:inaccessiblememonly` helper effect, that tracks if a method involves any access or modification on any mutable state. This effect property is basically same as LLVM's `inaccessiblememonly` function attribute, except that it only reasons about mutable memory. This effect property can be considered as a very limited and coarse version of escape analysis and allow us prove `:consistent`-cy or `:effect_free`-ness even in a presence of accesses or modifications on local mutable allocations in cases when we can prove they are local and don't escape. Separate PRs that actually improve the effect analysis accuracy will follow. --- base/boot.jl | 3 +- base/compiler/abstractinterpretation.jl | 23 +++++--- base/compiler/effects.jl | 63 ++++++++++++++------- base/compiler/ssair/show.jl | 14 +++-- base/compiler/tfuncs.jl | 56 +++++++++++++++++-- base/compiler/typeinfer.jl | 8 +++ base/compiler/typeutils.jl | 19 ++++++- base/essentials.jl | 6 +- base/expr.jl | 32 +++++++++-- src/julia.h | 27 +++++---- src/method.c | 3 +- test/compiler/effects.jl | 73 ++++++++++++++++++++++++- 12 files changed, 265 insertions(+), 62 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index 44a0a5c279273..f0045e5b72c91 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -462,7 +462,8 @@ macro _foldable_meta() #=:nothrow=#false, #=:terminates_globally=#true, #=:terminates_locally=#false, - #=:notaskstate=#false)) + #=:notaskstate=#false, + #=:inaccessiblememonly=#false)) end const NTuple{N,T} = Tuple{Vararg{T,N}} diff --git a/base/compiler/abstractinterpretation.jl b/base/compiler/abstractinterpretation.jl index 4dff15340c213..8a72bf03495b5 100644 --- a/base/compiler/abstractinterpretation.jl +++ b/base/compiler/abstractinterpretation.jl @@ -2081,10 +2081,11 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e), override = decode_effects_override(v[2]) effects = Effects( override.consistent ? ALWAYS_TRUE : effects.consistent, - override.effect_free ? true : effects.effect_free, - override.nothrow ? true : effects.nothrow, - override.terminates_globally ? true : effects.terminates, - override.notaskstate ? true : effects.notaskstate, + override.effect_free ? true : effects.effect_free, + override.nothrow ? true : effects.nothrow, + override.terminates_globally ? true : effects.terminates, + override.notaskstate ? true : effects.notaskstate, + override.inaccessiblememonly ? ALWAYS_TRUE : effects.inaccessiblememonly, effects.nonoverlayed) end merge_effects!(sv, effects) @@ -2166,22 +2167,28 @@ end function abstract_eval_global(M::Module, s::Symbol, frame::InferenceState) rt = abstract_eval_global(M, s) - consistent = ALWAYS_FALSE + consistent = inaccessiblememonly = ALWAYS_FALSE nothrow = false if isa(rt, Const) consistent = ALWAYS_TRUE - nothrow = true + if is_mutation_free_argtype(rt) + inaccessiblememonly = ALWAYS_TRUE + nothrow = true + else + nothrow = true + end elseif isdefined(M,s) nothrow = true end - merge_effects!(frame, Effects(EFFECTS_TOTAL; consistent, nothrow)) + merge_effects!(frame, Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly)) return rt end function handle_global_assignment!(interp::AbstractInterpreter, frame::InferenceState, lhs::GlobalRef, @nospecialize(newty)) effect_free = false nothrow = global_assignment_nothrow(lhs.mod, lhs.name, newty) - merge_effects!(frame, Effects(EFFECTS_TOTAL; effect_free, nothrow)) + inaccessiblememonly = ALWAYS_FALSE + merge_effects!(frame, Effects(EFFECTS_TOTAL; effect_free, nothrow, inaccessiblememonly)) return nothing end diff --git a/base/compiler/effects.jl b/base/compiler/effects.jl index 1ab647cc8dcc0..f75de3722e876 100644 --- a/base/compiler/effects.jl +++ b/base/compiler/effects.jl @@ -22,6 +22,14 @@ following meanings: behavior. Note that this currently implies that `noyield` as well, since yielding modifies the state of the current task, though this may be split in the future. +- `inaccessiblememonly::UInt8`: + * `ALWAYS_TRUE`: this method does not access or modify externally accessible mutable memory. + This state corresponds to LLVM's `inaccessiblememonly` function attribute. + * `ALWAYS_FALSE`: this method may access or modify externally accessible mutable memory. + * `INACCESSIBLEMEM_OR_ARGMEMONLY`: this method does not access or modify externally accessible mutable memory, + except that it may access or modify mutable memory pointed to by its call arguments. + This may later be refined to `ALWAYS_TRUE` in a case when call arguments are known to be immutable. + This state corresponds to LLVM's `inaccessiblemem_or_argmemonly` function attribute. - `nonoverlayed::Bool`: indicates that any methods that may be called within this method are not defined in an [overlayed method table](@ref OverlayMethodTable). - `noinbounds::Bool`: indicates this method can't be `:consistent` because of bounds checking. @@ -45,6 +53,7 @@ struct Effects nothrow::Bool terminates::Bool notaskstate::Bool + inaccessiblememonly::UInt8 nonoverlayed::Bool noinbounds::Bool function Effects( @@ -53,6 +62,7 @@ struct Effects nothrow::Bool, terminates::Bool, notaskstate::Bool, + inaccessiblememonly::UInt8, nonoverlayed::Bool, noinbounds::Bool = true) return new( @@ -61,6 +71,7 @@ struct Effects nothrow, terminates, notaskstate, + inaccessiblememonly, nonoverlayed, noinbounds) end @@ -71,10 +82,13 @@ const ALWAYS_FALSE = 0x01 const CONSISTENT_IF_NOTRETURNED = 0x01 << 1 -const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, true, true, true, true, true) -const EFFECTS_THROWS = Effects(ALWAYS_TRUE, true, false, true, true, true) -const EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, false, false, false, false, true) # unknown mostly, but it's not overlayed at least (e.g. it's not a call) -const EFFECTS_UNKNOWN′ = Effects(ALWAYS_FALSE, false, false, false, false, false) # unknown really +# :inaccessiblememonly bits +const INACCESSIBLEMEM_OR_ARGMEMONLY = 0x01 << 1 + +const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, true, true, true, true, ALWAYS_TRUE, true) +const EFFECTS_THROWS = Effects(ALWAYS_TRUE, true, false, true, true, ALWAYS_TRUE, true) +const EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, false, false, false, false, ALWAYS_FALSE, true) # unknown mostly, but it's not overlayed at least (e.g. it's not a call) +const EFFECTS_UNKNOWN′ = Effects(ALWAYS_FALSE, false, false, false, false, ALWAYS_FALSE, false) # unknown really function Effects(e::Effects = EFFECTS_UNKNOWN′; consistent::UInt8 = e.consistent, @@ -82,6 +96,7 @@ function Effects(e::Effects = EFFECTS_UNKNOWN′; nothrow::Bool = e.nothrow, terminates::Bool = e.terminates, notaskstate::Bool = e.notaskstate, + inaccessiblememonly::UInt8 = e.inaccessiblememonly, nonoverlayed::Bool = e.nonoverlayed, noinbounds::Bool = e.noinbounds) return Effects( @@ -90,6 +105,7 @@ function Effects(e::Effects = EFFECTS_UNKNOWN′; nothrow, terminates, notaskstate, + inaccessiblememonly, nonoverlayed, noinbounds) end @@ -101,6 +117,7 @@ function merge_effects(old::Effects, new::Effects) merge_effectbits(old.nothrow, new.nothrow), merge_effectbits(old.terminates, new.terminates), merge_effectbits(old.notaskstate, new.notaskstate), + merge_effectbits(old.inaccessiblememonly, new.inaccessiblememonly), merge_effectbits(old.nonoverlayed, new.nonoverlayed), merge_effectbits(old.noinbounds, new.noinbounds)) end @@ -113,14 +130,15 @@ function merge_effectbits(old::UInt8, new::UInt8) end merge_effectbits(old::Bool, new::Bool) = old & new -is_consistent(effects::Effects) = effects.consistent === ALWAYS_TRUE -is_effect_free(effects::Effects) = effects.effect_free -is_nothrow(effects::Effects) = effects.nothrow -is_terminates(effects::Effects) = effects.terminates -is_notaskstate(effects::Effects) = effects.notaskstate -is_nonoverlayed(effects::Effects) = effects.nonoverlayed +is_consistent(effects::Effects) = effects.consistent === ALWAYS_TRUE +is_effect_free(effects::Effects) = effects.effect_free +is_nothrow(effects::Effects) = effects.nothrow +is_terminates(effects::Effects) = effects.terminates +is_notaskstate(effects::Effects) = effects.notaskstate +is_inaccessiblememonly(effects::Effects) = effects.inaccessiblememonly === ALWAYS_TRUE +is_nonoverlayed(effects::Effects) = effects.nonoverlayed -# implies :notaskstate, but not explicitly checked here +# implies `is_notaskstate` & `is_inaccessiblememonly`, but not explicitly checked here is_foldable(effects::Effects) = is_consistent(effects) && is_effect_free(effects) && @@ -137,13 +155,16 @@ is_removable_if_unused(effects::Effects) = is_consistent_if_notreturned(effects::Effects) = !iszero(effects.consistent & CONSISTENT_IF_NOTRETURNED) +is_inaccessiblemem_or_argmemonly(effects::Effects) = effects.inaccessiblememonly === INACCESSIBLEMEM_OR_ARGMEMONLY + function encode_effects(e::Effects) - return ((e.consistent % UInt32) << 0) | - ((e.effect_free % UInt32) << 2) | - ((e.nothrow % UInt32) << 3) | - ((e.terminates % UInt32) << 4) | - ((e.notaskstate % UInt32) << 5) | - ((e.nonoverlayed % UInt32) << 6) + return ((e.consistent % UInt32) << 0) | + ((e.effect_free % UInt32) << 2) | + ((e.nothrow % UInt32) << 3) | + ((e.terminates % UInt32) << 4) | + ((e.notaskstate % UInt32) << 5) | + ((e.inaccessiblememonly % UInt32) << 6) | + ((e.nonoverlayed % UInt32) << 8) end function decode_effects(e::UInt32) @@ -153,7 +174,8 @@ function decode_effects(e::UInt32) _Bool((e >> 3) & 0x01), _Bool((e >> 4) & 0x01), _Bool((e >> 5) & 0x01), - _Bool((e >> 6) & 0x01)) + UInt8((e >> 6) & 0x01), + _Bool((e >> 8) & 0x03)) end struct EffectsOverride @@ -163,6 +185,7 @@ struct EffectsOverride terminates_globally::Bool terminates_locally::Bool notaskstate::Bool + inaccessiblememonly::Bool end function encode_effects_override(eo::EffectsOverride) @@ -173,6 +196,7 @@ function encode_effects_override(eo::EffectsOverride) eo.terminates_globally && (e |= (0x01 << 3)) eo.terminates_locally && (e |= (0x01 << 4)) eo.notaskstate && (e |= (0x01 << 5)) + eo.inaccessiblememonly && (e |= (0x01 << 6)) return e end @@ -183,5 +207,6 @@ function decode_effects_override(e::UInt8) (e & (0x01 << 2)) != 0x00, (e & (0x01 << 3)) != 0x00, (e & (0x01 << 4)) != 0x00, - (e & (0x01 << 5)) != 0x00) + (e & (0x01 << 5)) != 0x00, + (e & (0x01 << 6)) != 0x00) end diff --git a/base/compiler/ssair/show.jl b/base/compiler/ssair/show.jl index 0bbc80d789d80..4bacd3047f184 100644 --- a/base/compiler/ssair/show.jl +++ b/base/compiler/ssair/show.jl @@ -791,9 +791,9 @@ function show_ir(io::IO, code::Union{IRCode, CodeInfo}, config::IRShowConfig=def end function effectbits_letter(effects::Effects, name::Symbol, suffix::Char) - if name === :consistent - prefix = effects.consistent === ALWAYS_TRUE ? '+' : - effects.consistent === ALWAYS_FALSE ? '!' : '?' + if name === :consistent || name === :inaccessiblememonly + prefix = getfield(effects, name) === ALWAYS_TRUE ? '+' : + getfield(effects, name) === ALWAYS_FALSE ? '!' : '?' else prefix = getfield(effects, name) ? '+' : '!' end @@ -801,9 +801,9 @@ function effectbits_letter(effects::Effects, name::Symbol, suffix::Char) end function effectbits_color(effects::Effects, name::Symbol) - if name === :consistent - color = effects.consistent === ALWAYS_TRUE ? :green : - effects.consistent === ALWAYS_FALSE ? :red : :yellow + if name === :consistent || name === :inaccessiblememonly + color = getfield(effects, name) === ALWAYS_TRUE ? :green : + getfield(effects, name) === ALWAYS_FALSE ? :red : :yellow else color = getfield(effects, name) ? :green : :red end @@ -821,6 +821,8 @@ function Base.show(io::IO, e::Effects) printstyled(io, effectbits_letter(e, :terminates, 't'); color=effectbits_color(e, :terminates)) print(io, ',') printstyled(io, effectbits_letter(e, :notaskstate, 's'); color=effectbits_color(e, :notaskstate)) + print(io, ',') + printstyled(io, effectbits_letter(e, :inaccessiblememonly, 'm'); color=effectbits_color(e, :inaccessiblememonly)) print(io, ')') e.nonoverlayed || printstyled(io, '′'; color=:red) end diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 94dd08508a4df..1499f66e4b20e 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -1845,11 +1845,38 @@ const _CONSISTENT_BUILTINS = Any[ (<:), typeassert, throw, - setfield! + setfield!, +] + +const _INACCESSIBLEMEM_BUILTINS = Any[ + (<:), + (===), + apply_type, + arraysize, + Core.ifelse, + sizeof, + svec, + fieldtype, + isa, + isdefined, + nfields, + throw, + tuple, + typeassert, + typeof, +] + +const _ARGMEM_BUILTINS = Any[ + arrayref, + arrayset, + modifyfield!, + replacefield!, + setfield!, + swapfield!, ] const _SPECIAL_BUILTINS = Any[ - Core._apply_iterate + Core._apply_iterate, ] function isdefined_effects(argtypes::Vector{Any}) @@ -1891,11 +1918,18 @@ function getfield_effects(argtypes::Vector{Any}, @nospecialize(rt)) else nothrow = getfield_nothrow(argtypes) end - return Effects(EFFECTS_TOTAL; consistent, nothrow) + if hasintersect(widenconst(obj), Module) + inaccessiblememonly = getglobal_effects(argtypes, rt).inaccessiblememonly + elseif is_mutation_free_argtype(obj) + inaccessiblememonly = ALWAYS_TRUE + else + inaccessiblememonly = INACCESSIBLEMEM_OR_ARGMEMONLY + end + return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) end function getglobal_effects(argtypes::Vector{Any}, @nospecialize(rt)) - consistent = ALWAYS_FALSE + consistent = inaccessiblememonly = ALWAYS_FALSE nothrow = false if getglobal_nothrow(argtypes) nothrow = true @@ -1903,9 +1937,12 @@ function getglobal_effects(argtypes::Vector{Any}, @nospecialize(rt)) M, s = (argtypes[1]::Const).val::Module, (argtypes[2]::Const).val::Symbol if isconst(M, s) consistent = ALWAYS_TRUE + if is_mutation_free_argtype(rt) + inaccessiblememonly = ALWAYS_TRUE + end end end - return Effects(EFFECTS_TOTAL; consistent, nothrow) + return Effects(EFFECTS_TOTAL; consistent, nothrow, inaccessiblememonly) end function builtin_effects(f::Builtin, argtypes::Vector{Any}, @nospecialize(rt)) @@ -1925,7 +1962,14 @@ function builtin_effects(f::Builtin, argtypes::Vector{Any}, @nospecialize(rt)) consistent = contains_is(_CONSISTENT_BUILTINS, f) ? ALWAYS_TRUE : ALWAYS_FALSE effect_free = (contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f)) nothrow = (!(!isempty(argtypes) && isvarargtype(argtypes[end])) && builtin_nothrow(f, argtypes, rt)) - return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow) + if contains_is(_INACCESSIBLEMEM_BUILTINS, f) + inaccessiblememonly = ALWAYS_TRUE + elseif contains_is(_ARGMEM_BUILTINS, f) + inaccessiblememonly = INACCESSIBLEMEM_OR_ARGMEMONLY + else + inaccessiblememonly = ALWAYS_FALSE + end + return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow, inaccessiblememonly) end end diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 6992d69d24365..9dbd84f6c0426 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -430,6 +430,11 @@ function adjust_effects(sv::InferenceState) # always throwing an error counts or never returning both count as consistent ipo_effects = Effects(ipo_effects; consistent=ALWAYS_TRUE) end + if is_inaccessiblemem_or_argmemonly(ipo_effects) && all(1:narguments(sv)) do i::Int + return is_mutation_free_argtype(sv.slottypes[i]) + end + ipo_effects = Effects(ipo_effects; inaccessiblememonly=ALWAYS_TRUE) + end if is_consistent_if_notreturned(ipo_effects) && is_consistent_argtype(rt) # in a case when the :consistent-cy here is only tainted by mutable allocations # (indicated by `CONSISTENT_IF_NOTRETURNED`), we may be able to refine it if the return @@ -457,6 +462,9 @@ function adjust_effects(sv::InferenceState) if is_effect_overridden(override, :notaskstate) ipo_effects = Effects(ipo_effects; notaskstate=true) end + if is_effect_overridden(override, :inaccessiblememonly) + ipo_effects = Effects(ipo_effects; inaccessiblememonly=ALWAYS_TRUE) + end end return ipo_effects diff --git a/base/compiler/typeutils.jl b/base/compiler/typeutils.jl index a0ada1a15e245..c70394e891c9f 100644 --- a/base/compiler/typeutils.jl +++ b/base/compiler/typeutils.jl @@ -317,7 +317,24 @@ is_immutable_argtype(@nospecialize ty) = is_immutable_type(widenconst(ignorelimi is_immutable_type(@nospecialize ty) = _is_immutable_type(unwrap_unionall(ty)) function _is_immutable_type(@nospecialize ty) if isa(ty, Union) - return is_immutable_type(ty.a) && is_immutable_type(ty.b) + return _is_immutable_type(ty.a) && _is_immutable_type(ty.b) end return !isabstracttype(ty) && !ismutabletype(ty) end + +is_mutation_free_argtype(@nospecialize argtype) = + is_mutation_free_type(widenconst(ignorelimited(argtype))) +is_mutation_free_type(@nospecialize ty) = + _is_mutation_free_type(unwrap_unionall(ty)) +function _is_mutation_free_type(@nospecialize ty) + if isa(ty, Union) + return _is_mutation_free_type(ty.a) && _is_mutation_free_type(ty.b) + end + if isType(ty) || ty === DataType || ty === String || ty === Symbol || ty === SimpleVector + return true + end + # this is okay as access and modifcation on global state are tracked separately + ty === Module && return true + # TODO improve this analysis, e.g. allow `Some{Symbol}` + return isbitstype(ty) +end diff --git a/base/essentials.jl b/base/essentials.jl index 74a2551b814e2..0fb00c367e391 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -210,7 +210,8 @@ macro _total_meta() #=:nothrow=#true, #=:terminates_globally=#true, #=:terminates_locally=#false, - #=:notaskstate=#true)) + #=:notaskstate=#true, + #=:inaccessiblememonly=#true)) end # can be used in place of `@assume_effects :foldable` (supposed to be used for bootstrapping) macro _foldable_meta() @@ -220,7 +221,8 @@ macro _foldable_meta() #=:nothrow=#false, #=:terminates_globally=#true, #=:terminates_locally=#false, - #=:notaskstate=#false)) + #=:notaskstate=#false, + #=:inaccessiblememonly=#true)) end # another version of inlining that propagates an inbounds context diff --git a/base/expr.jl b/base/expr.jl index 64b2afd4f93c8..2a46be767f3f0 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -433,6 +433,7 @@ The following `setting`s are supported. - `:terminates_globally` - `:terminates_locally` - `:notaskstate` +- `:inaccessiblememonly` - `:foldable` - `:total` @@ -570,6 +571,24 @@ moved between tasks without observable results. code that is not `:notaskstate`, but is `:effect_free` and `:consistent` may still be dead-code-eliminated and thus promoted to `:total`. +--- +## `:inaccessiblememonly` + +The `:inaccessiblememonly` setting asserts that the method does not access or modify +externally accessible mutable memory. This means the method can access or modify mutable +memory for newly allocated objects that is not accessible by other methods or top-level +execution before return from the method, but it can not access or modify any mutable +global state or mutable memory pointed to by its arguments. + +!!! note + Below is an incomplete list of examples that invalidate this assumption: + - a global reference or `getglobal` call to access a mutable global variable + - a global assignment or `setglobal!` call to perform assignment to a non-constant global variable + - `setfield!` call that changes a field of a global mutable variable + +!!! note + This `:inaccessiblememonly` assertion covers any other methods called by the annotated method. + --- ## `:foldable` @@ -597,6 +616,7 @@ the following other `setting`s: - `:nothrow` - `:terminates_globally` - `:notaskstate` +- `:inaccessiblememonly` !!! warning `:total` is a very strong assertion and will likely gain additional semantics @@ -625,8 +645,8 @@ Another advantage is that effects introduced by `@assume_effects` are propagated callers interprocedurally while a purity defined by `@pure` is not. """ macro assume_effects(args...) - (consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate) = - (false, false, false, false, false, false, false) + (consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly) = + (false, false, false, false, false, false, false, false) for org_setting in args[1:end-1] (setting, val) = compute_assumed_setting(org_setting) if setting === :consistent @@ -641,10 +661,12 @@ macro assume_effects(args...) terminates_locally = val elseif setting === :notaskstate notaskstate = val + elseif setting === :inaccessiblememonly + inaccessiblememonly = val elseif setting === :foldable consistent = effect_free = terminates_globally = val elseif setting === :total - consistent = effect_free = nothrow = terminates_globally = notaskstate = val + consistent = effect_free = nothrow = terminates_globally = notaskstate = inaccessiblememonly = val else throw(ArgumentError("@assume_effects $org_setting not supported")) end @@ -654,11 +676,11 @@ macro assume_effects(args...) if ex.head === :macrocall && ex.args[1] === Symbol("@ccall") ex.args[1] = GlobalRef(Base, Symbol("@ccall_effects")) insert!(ex.args, 3, Core.Compiler.encode_effects_override(Core.Compiler.EffectsOverride( - consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate + consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly, ))) return esc(ex) end - return esc(pushmeta!(ex, :purity, consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate)) + return esc(pushmeta!(ex, :purity, consistent, effect_free, nothrow, terminates_globally, terminates_locally, notaskstate, inaccessiblememonly)) end function compute_assumed_setting(@nospecialize(setting), val::Bool=true) diff --git a/src/julia.h b/src/julia.h index c297b8cdc43ed..19a94d8f2f15e 100644 --- a/src/julia.h +++ b/src/julia.h @@ -247,6 +247,7 @@ typedef union __jl_purity_overrides_t { // assertions about any called functions. uint8_t ipo_terminates_locally : 1; uint8_t ipo_notaskstate : 1; + uint8_t ipo_inaccessiblememonly : 1; } overrides; uint8_t bits; } _jl_purity_overrides_t; @@ -401,23 +402,25 @@ typedef struct _jl_code_instance_t { union { uint32_t ipo_purity_bits; struct { - uint8_t ipo_consistent : 2; - uint8_t ipo_effect_free : 2; - uint8_t ipo_nothrow : 2; - uint8_t ipo_terminates : 2; - uint8_t ipo_nonoverlayed : 1; - uint8_t ipo_notaskstate : 2; + uint8_t ipo_consistent : 2; + uint8_t ipo_effect_free : 2; + uint8_t ipo_nothrow : 2; + uint8_t ipo_terminates : 2; + uint8_t ipo_nonoverlayed : 1; + uint8_t ipo_notaskstate : 2; + uint8_t ipo_inaccessiblememonly : 2; } ipo_purity_flags; }; union { uint32_t purity_bits; struct { - uint8_t consistent : 2; - uint8_t effect_free : 2; - uint8_t nothrow : 2; - uint8_t terminates : 2; - uint8_t nonoverlayed : 1; - uint8_t notaskstate : 2; + uint8_t consistent : 2; + uint8_t effect_free : 2; + uint8_t nothrow : 2; + uint8_t terminates : 2; + uint8_t nonoverlayed : 1; + uint8_t notaskstate : 2; + uint8_t inaccessiblememonly : 2; } purity_flags; }; #else diff --git a/src/method.c b/src/method.c index af370c4efde4c..1abacfaa58e55 100644 --- a/src/method.c +++ b/src/method.c @@ -322,13 +322,14 @@ static void jl_code_info_set_ir(jl_code_info_t *li, jl_expr_t *ir) else if (ma == (jl_value_t*)jl_no_constprop_sym) li->constprop = 2; else if (jl_is_expr(ma) && ((jl_expr_t*)ma)->head == jl_purity_sym) { - if (jl_expr_nargs(ma) == 6) { + if (jl_expr_nargs(ma) == 7) { li->purity.overrides.ipo_consistent = jl_unbox_bool(jl_exprarg(ma, 0)); li->purity.overrides.ipo_effect_free = jl_unbox_bool(jl_exprarg(ma, 1)); li->purity.overrides.ipo_nothrow = jl_unbox_bool(jl_exprarg(ma, 2)); li->purity.overrides.ipo_terminates_globally = jl_unbox_bool(jl_exprarg(ma, 3)); li->purity.overrides.ipo_terminates_locally = jl_unbox_bool(jl_exprarg(ma, 4)); li->purity.overrides.ipo_notaskstate = jl_unbox_bool(jl_exprarg(ma, 5)); + li->purity.overrides.ipo_inaccessiblememonly = jl_unbox_bool(jl_exprarg(ma, 6)); } } else diff --git a/test/compiler/effects.jl b/test/compiler/effects.jl index ac1605ea93087..6cd795dc4a37d 100644 --- a/test/compiler/effects.jl +++ b/test/compiler/effects.jl @@ -306,7 +306,7 @@ end |> Core.Compiler.is_consistent Base.@assume_effects :effect_free @ccall jl_array_ptr(a::Any)::Ptr{Int} end |> Core.Compiler.is_effect_free -# `getfield_effects` handles union object nicely +# `getfield_effects` handles access to union object nicely @test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Any[Some{String}, Core.Const(:value)], String)) @test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Any[Some{Symbol}, Core.Const(:value)], Symbol)) @test Core.Compiler.is_consistent(Core.Compiler.getfield_effects(Any[Union{Some{Symbol},Some{String}}, Core.Const(:value)], Union{Symbol,String})) @@ -316,3 +316,74 @@ end |> Core.Compiler.is_effect_free end |> Core.Compiler.is_consistent @test Core.Compiler.is_consistent(Base.infer_effects(setindex!, (Base.RefValue{Int}, Int))) + +# :inaccessiblememonly effect +const global constant_global::Int = 42 +const global ConstantType = Ref +global nonconstant_global::Int = 42 +const global constant_mutable_global = Ref(0) +const global constant_global_nonisbits = Some(:foo) +@test Base.infer_effects() do + constant_global +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + ConstantType +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + ConstantType{Any}() +end |> Core.Compiler.is_inaccessiblememonly +@test_broken Base.infer_effects() do + constant_global_nonisbits +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + getglobal(@__MODULE__, :constant_global) +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + nonconstant_global +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + getglobal(@__MODULE__, :nonconstant_global) +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects((Symbol,)) do name + getglobal(@__MODULE__, name) +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects((Int,)) do v + global nonconstant_global = v +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects((Int,)) do v + setglobal!(@__MODULE__, :nonconstant_global, v) +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects((Int,)) do v + constant_mutable_global[] = v +end |> !Core.Compiler.is_inaccessiblememonly +module ConsistentModule +const global constant_global::Int = 42 +const global ConstantType = Ref +end # module +@test Base.infer_effects() do + ConsistentModule.constant_global +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + ConsistentModule.ConstantType +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + ConsistentModule.ConstantType{Any}() +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + getglobal(@__MODULE__, :ConsistentModule).constant_global +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + getglobal(@__MODULE__, :ConsistentModule).ConstantType +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do + getglobal(@__MODULE__, :ConsistentModule).ConstantType{Any}() +end |> Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects((Module,)) do M + M.constant_global +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects((Module,)) do M + M.ConstantType +end |> !Core.Compiler.is_inaccessiblememonly +@test Base.infer_effects() do M + M.ConstantType{Any}() +end |> !Core.Compiler.is_inaccessiblememonly