Skip to content

Commit e4e489a

Browse files
aviateskvtjnash
authored andcommitted
effects: fix effects of atomic pointer operations (#57806)
The `_unsetindex!(::GenericMemoryRef)` function was being concretely evaluated (meaning the code was actually being run during inference) by the `REPLInterpreter`. The root cause is that the Base compiler system forgot to mark `atomic_pointerset` as "effect-free". This is simply a bug in the effect system. It didn't cause problems during normal Base compilation because `atomic_pointerset` was correctly marked as "consistent" (concrete evaluation requires both "consistent" and "effect-free"). However, this was still a pretty risky situation. The reason this only caused problems with REPL completion is that the `REPLInterpreter` intentionally ignores the "consistent" requirement under certain conditions to achieve better completion accuracy. This is usually fine, but it relies on "effect-free" being correctly marked. So, when there's a critical bug like this in the effect system, these kinds of dangerous issues can occur. As discussed with Jameson earlier, the effects of atomic pointer operations are not precisely defined. This commit includes the minimal changes necessary to fix #57780, but a more extensive audit is planned for later. - closes #57780 --------- Co-authored-by: Jameson Nash <vtjnash@gmail.com> (cherry picked from commit 6a32f7a)
1 parent dd47fcb commit e4e489a

File tree

3 files changed

+72
-23
lines changed

3 files changed

+72
-23
lines changed

Compiler/src/tfuncs.jl

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2427,11 +2427,8 @@ const _ARGMEM_BUILTINS = Any[
24272427
]
24282428

24292429
const _INCONSISTENT_INTRINSICS = Any[
2430-
Intrinsics.pointerref, # this one is volatile
2431-
Intrinsics.sqrt_llvm_fast, # this one may differ at runtime (by a few ulps)
2432-
Intrinsics.have_fma, # this one depends on the runtime environment
2433-
Intrinsics.cglobal, # cglobal lookup answer changes at runtime
2434-
# ... and list fastmath intrinsics:
2430+
# all is_pure_intrinsic_infer plus
2431+
# ... all the unsound fastmath functions which should have been in is_pure_intrinsic_infer
24352432
# join(string.("Intrinsics.", sort(filter(endswith("_fast")∘string, names(Core.Intrinsics)))), ",\n")
24362433
Intrinsics.add_float_fast,
24372434
Intrinsics.div_float_fast,
@@ -2956,36 +2953,48 @@ function intrinsic_nothrow(f::IntrinsicFunction, argtypes::Vector{Any})
29562953
return intrinsic_exct(SimpleInferenceLattice.instance, f, argtypes) === Union{}
29572954
end
29582955

2959-
# whether `f` is pure for inference
2960-
function is_pure_intrinsic_infer(f::IntrinsicFunction)
2961-
return !(f === Intrinsics.pointerref || # this one is volatile
2962-
f === Intrinsics.pointerset || # this one is never effect-free
2963-
f === Intrinsics.llvmcall || # this one is never effect-free
2964-
f === Intrinsics.sqrt_llvm_fast || # this one may differ at runtime (by a few ulps)
2965-
f === Intrinsics.have_fma || # this one depends on the runtime environment
2966-
f === Intrinsics.cglobal) # cglobal lookup answer changes at runtime
2956+
function _is_effect_free_infer(f::IntrinsicFunction)
2957+
return !(f === Intrinsics.pointerset ||
2958+
f === Intrinsics.atomic_pointerref ||
2959+
f === Intrinsics.atomic_pointerset ||
2960+
f === Intrinsics.atomic_pointerswap ||
2961+
# f === Intrinsics.atomic_pointermodify ||
2962+
f === Intrinsics.atomic_pointerreplace ||
2963+
f === Intrinsics.atomic_fence)
29672964
end
29682965

2969-
# whether `f` is effect free if nothrow
2970-
function intrinsic_effect_free_if_nothrow(@nospecialize f)
2971-
return f === Intrinsics.pointerref ||
2972-
f === Intrinsics.have_fma ||
2973-
is_pure_intrinsic_infer(f)
2966+
# whether `f` is pure for inference
2967+
function is_pure_intrinsic_infer(f::IntrinsicFunction, is_effect_free::Union{Nothing,Bool}=nothing)
2968+
if is_effect_free === nothing
2969+
is_effect_free = _is_effect_free_infer(f)
2970+
end
2971+
return is_effect_free && !(
2972+
f === Intrinsics.llvmcall || # can do arbitrary things
2973+
f === Intrinsics.atomic_pointermodify || # can do arbitrary things
2974+
f === Intrinsics.pointerref || # this one is volatile
2975+
f === Intrinsics.sqrt_llvm_fast || # this one may differ at runtime (by a few ulps)
2976+
f === Intrinsics.have_fma || # this one depends on the runtime environment
2977+
f === Intrinsics.cglobal) # cglobal lookup answer changes at runtime
29742978
end
29752979

29762980
function intrinsic_effects(f::IntrinsicFunction, argtypes::Vector{Any})
29772981
if f === Intrinsics.llvmcall
29782982
# llvmcall can do arbitrary things
29792983
return Effects()
2984+
elseif f === atomic_pointermodify
2985+
# atomic_pointermodify has memory effects, plus any effects from the ModifyOpInfo
2986+
return Effects()
29802987
end
2981-
if contains_is(_INCONSISTENT_INTRINSICS, f)
2982-
consistent = ALWAYS_FALSE
2983-
else
2988+
is_effect_free = _is_effect_free_infer(f)
2989+
effect_free = is_effect_free ? ALWAYS_TRUE : ALWAYS_FALSE
2990+
if ((is_pure_intrinsic_infer(f, is_effect_free) && !contains_is(_INCONSISTENT_INTRINSICS, f)) ||
2991+
f === Intrinsics.pointerset || f === Intrinsics.atomic_pointerset || f === Intrinsics.atomic_fence)
29842992
consistent = ALWAYS_TRUE
2993+
else
2994+
consistent = ALWAYS_FALSE
29852995
end
2986-
effect_free = !(f === Intrinsics.pointerset) ? ALWAYS_TRUE : ALWAYS_FALSE
29872996
nothrow = intrinsic_nothrow(f, argtypes)
2988-
inaccessiblememonly = ALWAYS_TRUE
2997+
inaccessiblememonly = is_effect_free && !(f === Intrinsics.pointerref) ? ALWAYS_TRUE : ALWAYS_FALSE
29892998
return Effects(EFFECTS_TOTAL; consistent, effect_free, nothrow, inaccessiblememonly)
29902999
end
29913000

Compiler/test/effects.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,3 +1427,37 @@ end |> !Compiler.is_nothrow
14271427
@test Base.infer_effects((UInt64,)) do x
14281428
return Core.Intrinsics.uitofp(Int64, x)
14291429
end |> !Compiler.is_nothrow
1430+
1431+
# effects modeling for pointer-related intrinsics
1432+
let effects = Base.infer_effects(Core.Intrinsics.pointerref, Tuple{Vararg{Any}})
1433+
@test !Compiler.is_consistent(effects)
1434+
@test Compiler.is_effect_free(effects)
1435+
@test !Compiler.is_inaccessiblememonly(effects)
1436+
end
1437+
let effects = Base.infer_effects(Core.Intrinsics.pointerset, Tuple{Vararg{Any}})
1438+
@test Compiler.is_consistent(effects)
1439+
@test !Compiler.is_effect_free(effects)
1440+
end
1441+
# effects modeling for atomic intrinsics
1442+
# these functions especially need to be marked !effect_free since they imply synchronization
1443+
for atomicfunc = Any[
1444+
Core.Intrinsics.atomic_pointerref,
1445+
Core.Intrinsics.atomic_pointerset,
1446+
Core.Intrinsics.atomic_pointerswap,
1447+
Core.Intrinsics.atomic_pointerreplace,
1448+
Core.Intrinsics.atomic_fence]
1449+
@test !Compiler.is_effect_free(Base.infer_effects(atomicfunc, Tuple{Vararg{Any}}))
1450+
end
1451+
1452+
# effects modeling for intrinsics that can do arbitrary things
1453+
let effects = Base.infer_effects(Core.Intrinsics.llvmcall, Tuple{Vararg{Any}})
1454+
@test effects == Compiler.Effects()
1455+
end
1456+
let effects = Base.infer_effects(Core.Intrinsics.atomic_pointermodify, Tuple{Vararg{Any}})
1457+
@test effects == Compiler.Effects()
1458+
end
1459+
1460+
# JuliaLang/julia#57780
1461+
let effects = Base.infer_effects(Base._unsetindex!, (MemoryRef{String},))
1462+
@test !Compiler.is_effect_free(effects)
1463+
end

stdlib/REPL/test/replcompletions.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2484,3 +2484,9 @@ let (c, r, res) = test_complete_context("global xxx::Number = Base.", Main)
24842484
@test res
24852485
@test "pi" c
24862486
end
2487+
2488+
# JuliaLang/julia#57780
2489+
const issue57780 = ["a", "b", "c"]
2490+
const issue57780_orig = copy(issue57780)
2491+
test_complete_context("empty!(issue57780).", Main)
2492+
@test issue57780 == issue57780_orig

0 commit comments

Comments
 (0)