From 98f47474d8369a72366e60bd8e94ef7011be6f3e Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:40:47 +0900 Subject: [PATCH] optimizer: do not delete statements that may not `:terminate` (#52999) Fixes #52991. Currently this commit marks the test case added in #52954 as `broken` since it has relied on the behavior of #52991. I'm planning to add followup changes in a separate commit. --- base/compiler/optimize.jl | 13 +++++++---- base/dict.jl | 33 +++++++++++----------------- test/compiler/irpasses.jl | 45 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 25 deletions(-) diff --git a/base/compiler/optimize.jl b/base/compiler/optimize.jl index fa1bf2f1ee152..675fe87582b0d 100644 --- a/base/compiler/optimize.jl +++ b/base/compiler/optimize.jl @@ -48,9 +48,9 @@ const IR_FLAG_INACCESSIBLEMEM_OR_ARGMEM = one(UInt32) << 11 const NUM_IR_FLAGS = 12 # sync with julia.h const IR_FLAGS_EFFECTS = - IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW | IR_FLAG_NOUB + IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW | IR_FLAG_TERMINATES | IR_FLAG_NOUB -const IR_FLAGS_REMOVABLE = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW +const IR_FLAGS_REMOVABLE = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW | IR_FLAG_TERMINATES const IR_FLAGS_NEEDS_EA = IR_FLAG_EFIIMO | IR_FLAG_INACCESSIBLEMEM_OR_ARGMEM @@ -69,6 +69,9 @@ function flags_for_effects(effects::Effects) if is_nothrow(effects) flags |= IR_FLAG_NOTHROW end + if is_terminates(effects) + flags |= IR_FLAG_TERMINATES + end if is_inaccessiblemem_or_argmemonly(effects) flags |= IR_FLAG_INACCESSIBLEMEM_OR_ARGMEM end @@ -331,7 +334,8 @@ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospe consistent = is_consistent(effects) effect_free = is_effect_free(effects) nothrow = is_nothrow(effects) - removable = effect_free & nothrow + terminates = is_terminates(effects) + removable = effect_free & nothrow & terminates return (consistent, removable, nothrow) elseif head === :new return new_expr_effect_flags(𝕃ₒ, args, src) @@ -342,7 +346,8 @@ function stmt_effect_flags(𝕃ₒ::AbstractLattice, @nospecialize(stmt), @nospe consistent = is_consistent(effects) effect_free = is_effect_free(effects) nothrow = is_nothrow(effects) - removable = effect_free & nothrow + terminates = is_terminates(effects) + removable = effect_free & nothrow & terminates return (consistent, removable, nothrow) elseif head === :new_opaque_closure length(args) < 4 && return (false, false, false) diff --git a/base/dict.jl b/base/dict.jl index c924e10ab4d6e..4a63ed364b64d 100644 --- a/base/dict.jl +++ b/base/dict.jl @@ -868,34 +868,25 @@ struct PersistentDict{K,V} <: AbstractDict{K,V} @noinline function KeyValue.set(::Type{PersistentDict{K, V}}, ::Nothing, key, val) where {K, V} new{K, V}(HAMT.HAMT{K, V}(key => val)) end - @noinline @Base.assume_effects :effect_free function KeyValue.set(dict::PersistentDict{K, V}, key, val) where {K, V} + @noinline Base.@assume_effects :effect_free :terminates_globally KeyValue.set( + dict::PersistentDict{K, V}, key, val) where {K, V} = @inline _keyvalueset(dict, key, val) + @noinline Base.@assume_effects :nothrow :effect_free :terminates_globally KeyValue.set( + dict::PersistentDict{K, V}, key::K, val::V) where {K, V} = @inline _keyvalueset(dict, key, val) + global function _keyvalueset(dict::PersistentDict{K, V}, key, val) where {K, V} trie = dict.trie h = HAMT.HashState(key) - found, present, trie, i, bi, top, hs = HAMT.path(trie, key, h, #=persistent=# true) + found, present, trie, i, bi, top, hs = HAMT.path(trie, key, h, #=persistent=#true) HAMT.insert!(found, present, trie, i, bi, hs, val) return new{K, V}(top) end - @noinline @Base.assume_effects :nothrow :effect_free function KeyValue.set(dict::PersistentDict{K, V}, key::K, val::V) where {K, V} + @noinline Base.@assume_effects :effect_free :terminates_globally KeyValue.set( + dict::PersistentDict{K, V}, key) where {K, V} = @inline _keyvalueset(dict, key) + @noinline Base.@assume_effects :nothrow :effect_free :terminates_globally KeyValue.set( + dict::PersistentDict{K, V}, key::K) where {K, V} = @inline _keyvalueset(dict, key) + global function _keyvalueset(dict::PersistentDict{K, V}, key) where {K, V} trie = dict.trie h = HAMT.HashState(key) - found, present, trie, i, bi, top, hs = HAMT.path(trie, key, h, #=persistent=# true) - HAMT.insert!(found, present, trie, i, bi, hs, val) - return new{K, V}(top) - end - @noinline @Base.assume_effects :effect_free function KeyValue.set(dict::PersistentDict{K, V}, key) where {K, V} - trie = dict.trie - h = HAMT.HashState(key) - found, present, trie, i, bi, top, _ = HAMT.path(trie, key, h, #=persistent=# true) - if found && present - deleteat!(trie.data, i) - HAMT.unset!(trie, bi) - end - return new{K, V}(top) - end - @noinline @Base.assume_effects :nothrow :effect_free function KeyValue.set(dict::PersistentDict{K, V}, key::K) where {K, V} - trie = dict.trie - h = HAMT.HashState(key) - found, present, trie, i, bi, top, _ = HAMT.path(trie, key, h, #=persistent=# true) + found, present, trie, i, bi, top, _ = HAMT.path(trie, key, h, #=persistent=#true) if found && present deleteat!(trie.data, i) HAMT.unset!(trie, bi) diff --git a/test/compiler/irpasses.jl b/test/compiler/irpasses.jl index 808a4b8c9f1b4..b41241bf6a9a7 100644 --- a/test/compiler/irpasses.jl +++ b/test/compiler/irpasses.jl @@ -1836,3 +1836,48 @@ let code = Any[ @test made_changes @test (ir[Core.SSAValue(length(ir.stmts))][:flag] & Core.Compiler.IR_FLAG_REFINED) != 0 end + +# JuliaLang/julia#52991: statements that may not :terminate should not be deleted +@noinline Base.@assume_effects :effect_free :nothrow function issue52991(n) + local s = 0 + try + while true + yield() + if n - rand(1:10) > 0 + s += 1 + else + break + end + end + catch + end + return s +end +@test !Core.Compiler.is_removable_if_unused(Base.infer_effects(issue52991, (Int,))) +let src = code_typed1((Int,)) do x + issue52991(x) + nothing + end + @test count(isinvoke(:issue52991), src.code) == 1 +end +let t = @async begin + issue52991(11) # this call never terminates + nothing + end + sleep(1) + if istaskdone(t) + ok = false + else + ok = true + schedule(t, InterruptException(); error=true) + end + @test ok +end + +# JuliaLang/julia47664 +@test !fully_eliminated() do + any(isone, Iterators.repeated(0)) +end +@test !fully_eliminated() do + all(iszero, Iterators.repeated(0)) +end