From f56ddb38fe1cafd316718d88dcd0c48cf41d49b0 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 1 Apr 2021 21:19:40 -0400 Subject: [PATCH] add tests and fixes --- doc/src/base/multi-threading.md | 18 +++- src/builtins.c | 5 +- src/datatype.c | 122 +++++++++++++++--------- src/runtime_intrinsics.c | 2 - test/atomics.jl | 161 ++++++++++++++++++++++++++------ test/intrinsics.jl | 28 ++++-- 6 files changed, 251 insertions(+), 85 deletions(-) diff --git a/doc/src/base/multi-threading.md b/doc/src/base/multi-threading.md index 4f3e4e53634a9..1961043786be9 100644 --- a/doc/src/base/multi-threading.md +++ b/doc/src/base/multi-threading.md @@ -19,9 +19,25 @@ See also [Synchronization](@ref lib-task-sync). ## Atomic operations +```@docs +Base.@atomic +``` + +!!! note + + The following APIs are fairly primitive, and will likely be exposed through an `unsafe_*`-like wrapper. + +``` +Core.Intrinsics.atomic_pointerref(pointer::Ptr{T}, order::Symbol) --> T +Core.Intrinsics.atomic_pointerset(pointer::Ptr{T}, new::T, order::Symbol) --> pointer +Core.Intrinsics.atomic_pointerswap(pointer::Ptr{T}, new::T, order::Symbol) --> old +Core.Intrinsics.atomic_pointermodify(pointer::Ptr{T}, function::(old::T,arg::S)->T, arg::S, order::Symbol) --> old +Core.Intrinsics.atomic_pointercmpswap(pointer::Ptr{T}, expected::Any, new::T, success_order::Symbol, failure_order::Symbol) --> (old, cmp) +``` + !!! warning - The API for atomic operations has not yet been finalized and is likely to change. + The following APIs are deprecated, though support for them is likely to remain for several releases. ```@docs Base.Threads.Atomic diff --git a/src/builtins.c b/src/builtins.c index 8ceb542b5c134..07ebba170c89d 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -969,8 +969,11 @@ JL_CALLABLE(jl_f_cmpswapfield) if (isatomic == (success_order == jl_memory_order_notatomic)) jl_atomic_error(isatomic ? "cmpswapfield!: atomic field cannot be written non-atomically" : "cmpswapfield!: non-atomic field cannot be written atomically"); + if (isatomic == (failure_order == jl_memory_order_notatomic)) + jl_atomic_error(isatomic ? "cmpswapfield!: atomic field cannot be accessed non-atomically" + : "cmpswapfield!: non-atomic field cannot be accessed atomically"); if (failure_order > success_order) - jl_atomic_error("cmpswapfield!: invalid atomic ordering"); + jl_atomic_error("invalid atomic ordering"); v = cmpswap_nth_field(st, v, idx, args[2], args[3], isatomic); // always seq_cst, if isatomic needed at all return v; } diff --git a/src/datatype.c b/src/datatype.c index 8df19412850ff..419399a08e55a 100644 --- a/src/datatype.c +++ b/src/datatype.c @@ -891,6 +891,10 @@ JL_DLLEXPORT int jl_atomic_bool_cmpswap_bits(char *dst, const jl_value_t *expect // n.b.: this can spuriously fail if there are padding bits, the caller should deal with that int success; switch (nb) { + case 0: { + success = 1; + break; + } case 1: { uint8_t y = *(uint8_t*)expected; success = jl_atomic_cmpswap((uint8_t*)dst, &y, *(uint8_t*)src); @@ -941,41 +945,70 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, char *dst, co jl_ptls_t ptls = jl_get_ptls_states(); jl_value_t *y = jl_gc_alloc(ptls, isptr ? nb : tuptyp->size, isptr ? dt : tuptyp); int success; + jl_datatype_t *et = (jl_datatype_t*)jl_typeof(expected); switch (nb) { + case 0: { + success = (dt == et); + break; + } case 1: { uint8_t *y8 = (uint8_t*)y; - *y8 = *(uint8_t*)expected; - success = jl_atomic_cmpswap((uint8_t*)dst, y8, *(uint8_t*)src); + if (dt == et) { + *y8 = *(uint8_t*)expected; + success = jl_atomic_cmpswap((uint8_t*)dst, y8, *(uint8_t*)src); + } + else { + *y8 = jl_atomic_load((uint8_t*)dst); + success = 0; + } break; } case 2: { uint16_t *y16 = (uint16_t*)y; - *y16 = *(uint16_t*)expected; - while (1) { - success = jl_atomic_cmpswap((uint16_t*)dst, y16, *(uint16_t*)src); - if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) - break; + if (dt == et) { + *y16 = *(uint16_t*)expected; + while (1) { + success = jl_atomic_cmpswap((uint16_t*)dst, y16, *(uint16_t*)src); + if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) + break; + } + } + else { + *y16 = jl_atomic_load((uint16_t*)dst); + success = 0; } break; } case 4: { uint32_t *y32 = (uint32_t*)y; - *y32 = *(uint32_t*)expected; - while (1) { - success = jl_atomic_cmpswap((uint32_t*)dst, y32, *(uint32_t*)src); - if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) - break; + if (dt == et) { + *y32 = *(uint32_t*)expected; + while (1) { + success = jl_atomic_cmpswap((uint32_t*)dst, y32, *(uint32_t*)src); + if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) + break; + } + } + else { + *y32 = jl_atomic_load((uint32_t*)dst); + success = 0; } break; } #if MAX_POINTERATOMIC_SIZE > 4 case 8: { uint64_t *y64 = (uint64_t*)y; - *y64 = *(uint64_t*)expected; - while (1) { - success = jl_atomic_cmpswap((uint64_t*)dst, y64, *(uint64_t*)src); - if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) - break; + if (dt == et) { + *y64 = *(uint64_t*)expected; + while (1) { + success = jl_atomic_cmpswap((uint64_t*)dst, y64, *(uint64_t*)src); + if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) + break; + } + } + else { + *y64 = jl_atomic_load((uint64_t*)dst); + success = 0; } break; } @@ -983,11 +1016,17 @@ JL_DLLEXPORT jl_value_t *jl_atomic_cmpswap_bits(jl_datatype_t *dt, char *dst, co #if MAX_POINTERATOMIC_SIZE > 8 case 16: { uint128_t *y128 = (uint128_t*)y; - *y128 = *(uint128_t*)expected; - while (1) { - success = jl_atomic_cmpswap((uint128_t*)dst, y128, *(uint128_t*)src); - if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) - break; + if (dt == et) { + *y128 = *(uint128_t*)expected; + while (1) { + success = jl_atomic_cmpswap((uint128_t*)dst, y128, *(uint128_t*)src); + if (success || !dt->layout->haspadding || !jl_egal__bits(y, expected, dt)) + break; + } + } + else { + *y128 = jl_atomic_load((uint128_t*)dst); + success = 0; } break; } @@ -1569,12 +1608,9 @@ jl_value_t *modify_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_valu if (isunion) { size_t fsz = jl_field_size(st, i); uint8_t *psel = &((uint8_t*)v)[offs + fsz - 1]; - unsigned nth = 0; - if (!jl_find_union_component(ty, jl_typeof(r), &nth)) - assert(0 && "invalid field assignment to isbits union"); - success = (*psel == nth); + success = (jl_typeof(r) == jl_nth_union_component(ty, *psel)); if (success) { - nth = 0; + unsigned nth = 0; if (!jl_find_union_component(ty, yty, &nth)) assert(0 && "invalid field assignment to isbits union"); *psel = nth; @@ -1627,7 +1663,6 @@ jl_value_t *cmpswap_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val JL_GC_POP(); } else { - jl_value_t *rty = jl_typeof(r); int hasptr; int isunion = jl_is_uniontype(ty); if (isunion) { @@ -1637,7 +1672,10 @@ jl_value_t *cmpswap_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val else { hasptr = ((jl_datatype_t*)ty)->layout->npointers > 0; } - size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + jl_value_t *rty = ty; + size_t fsz; + if (!isunion) + fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy int needlock = (isatomic && fsz > MAX_ATOMIC_SIZE); if (isatomic && !needlock) { r = jl_atomic_cmpswap_bits((jl_datatype_t*)rty, (char*)v + offs, r, rhs, fsz); @@ -1646,21 +1684,12 @@ jl_value_t *cmpswap_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val jl_gc_multi_wb(v, rhs); // rhs is immutable } else { + jl_ptls_t ptls = jl_get_ptls_states(); uint8_t *psel; - unsigned nth; - int success; if (isunion) { size_t fsz = jl_field_size(st, i); psel = &((uint8_t*)v)[offs + fsz - 1]; - nth = 0; - success = jl_find_union_component(ty, rty, &nth); - uint8_t sel = *psel; - if (success) - success = nth == sel; - rty = jl_nth_union_component(ty, sel); - } - else { - success = rty == ty; + rty = jl_nth_union_component(rty, *psel); } jl_value_t *params[2]; params[0] = rty; @@ -1668,12 +1697,13 @@ jl_value_t *cmpswap_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val jl_datatype_t *tuptyp = jl_apply_tuple_type_v(params, 2); JL_GC_PROMISE_ROOTED(tuptyp); // (JL_ALWAYS_LEAFTYPE) assert(!jl_field_isptr(tuptyp, 0)); - jl_ptls_t ptls = jl_get_ptls_states(); r = jl_gc_alloc(ptls, tuptyp->size, (jl_value_t*)tuptyp); + int success = (rty == jl_typeof(expected)); if (needlock) jl_lock_value(v); + size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + memcpy((char*)r, (char*)v + offs, fsz); if (success) { - memcpy((char*)r, (char*)v + offs, fsz); if (((jl_datatype_t*)rty)->layout->haspadding) success = jl_egal__bits(r, expected, (jl_datatype_t*)rty); else @@ -1681,10 +1711,10 @@ jl_value_t *cmpswap_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val } *((uint8_t*)r + fsz) = success ? 1 : 0; if (success) { - rty = jl_typeof(rhs); - fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy + jl_value_t *rty = jl_typeof(rhs); + size_t fsz = jl_datatype_size((jl_datatype_t*)rty); // need to shrink-wrap the final copy if (isunion) { - nth = 0; + unsigned nth = 0; if (!jl_find_union_component(ty, rty, &nth)) assert(0 && "invalid field assignment to isbits union"); *psel = nth; @@ -1696,7 +1726,7 @@ jl_value_t *cmpswap_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val if (needlock) jl_unlock_value(v); } - r = undefref_check((jl_datatype_t*)ty, r); + r = undefref_check((jl_datatype_t*)rty, r); if (__unlikely(r == NULL)) jl_throw(jl_undefref_exception); } diff --git a/src/runtime_intrinsics.c b/src/runtime_intrinsics.c index 31e5d1d97ee5b..c8e15b8a1688d 100644 --- a/src/runtime_intrinsics.c +++ b/src/runtime_intrinsics.c @@ -209,8 +209,6 @@ JL_DLLEXPORT jl_value_t *jl_atomic_pointercmpswap(jl_value_t *p, jl_value_t *exp jl_error("pointercmpswap: invalid pointer"); if (jl_typeof(x) != ety) jl_type_error("pointercmpswap", ety, x); - if (jl_typeof(expected) != ety) - jl_type_error("pointercmpswap", ety, expected); size_t nb = jl_datatype_size(ety); if ((nb & (nb - 1)) != 0 || nb > MAX_POINTERATOMIC_SIZE) jl_error("pointercmpswap: invalid atomic operation"); diff --git a/test/atomics.jl b/test/atomics.jl index 4295d62ee5126..eed9978177d88 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -9,7 +9,7 @@ mutable struct ARefxy{T} y::T ARefxy(x::T, y::T) where {T} = new{T}(x, y) ARefxy{T}(x, y) where {T} = new{T}(x, y) - ARefxy{T}() where {T} = new{T}() # unused, but sets ninitialized to 0 + ARefxy{T}() where {T} = new{T}() end mutable struct Refxy{T} @@ -38,6 +38,8 @@ end end copy(r::Union{Refxy,ARefxy}) = typeof(r)(r.x, r.y) +function add(x::T, y)::T where {T}; x + y; end +swap(x, y) = y let T1 = Refxy{NTuple{3,UInt8}}, T2 = ARefxy{NTuple{3,UInt8}} @@ -61,6 +63,35 @@ let (x, y) = (Complex{Int128}(10, 30), Complex{Int128}(20, 40)) @test sizeof(r) == sizeof(ar) - Int(fieldoffset(typeof(ar), 1)) end +@noinline function _test_field_operators(r) + r = r[] + T = typeof(getfield(r, :x)) + @test getfield(r, :x, :sequentially_consistent) === T(12345_10) + @test setfield!(r, :x, T(12345_1), :sequentially_consistent) === T(12345_1) + @test getfield(r, :x, :sequentially_consistent) === T(12345_1) + @test cmpswapfield!(r, :x, 12345_1 % UInt, T(12345_100), :sequentially_consistent, :sequentially_consistent) === (T(12345_1), false) + @test cmpswapfield!(r, :x, T(12345_1), T(12345_100), :sequentially_consistent, :sequentially_consistent) === (T(12345_1), true) + @test getfield(r, :x, :sequentially_consistent) === T(12345_100) + @test cmpswapfield!(r, :x, T(12345_1), T(12345_1), :sequentially_consistent, :sequentially_consistent) === (T(12345_100), false) + @test getfield(r, :x, :sequentially_consistent) === T(12345_100) + @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === T(12345_100) + @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === T(12345_101) + @test getfield(r, :x, :sequentially_consistent) === T(12345_102) + @test swapfield!(r, :x, T(12345_1), :sequentially_consistent) === T(12345_102) + @test getfield(r, :x, :sequentially_consistent) === T(12345_1) + nothing +end +@noinline function test_field_operators(r) + _test_field_operators(Ref(copy(r))) + _test_field_operators(Ref{Any}(copy(r))) + nothing +end +test_field_operators(ARefxy{Int}(12345_10, 12345_20)) +test_field_operators(ARefxy{Any}(12345_10, 12345_20)) +test_field_operators(ARefxy{Union{Nothing,Int}}(12345_10, nothing)) +test_field_operators(ARefxy{Complex{Int32}}(12345_10, 12345_20)) +test_field_operators(ARefxy{Complex{Int128}}(12345_10, 12345_20)) + @noinline function _test_field_orderings(r, x, y) @nospecialize x y r = r[] @@ -126,6 +157,83 @@ end @test setfield!(r, :y, x) === x @test setfield!(r, :y, x, :not_atomic) === x @test getfield(r, :y) === x + + @test_throws ConcurrencyViolationError("invalid atomic ordering") swapfield!(r, :y, y, :u) + @test_throws ConcurrencyViolationError("swapfield!: non-atomic field cannot be written atomically") swapfield!(r, :y, y, :unordered) + @test_throws ConcurrencyViolationError("swapfield!: non-atomic field cannot be written atomically") swapfield!(r, :y, y, :monotonic) + @test_throws ConcurrencyViolationError("swapfield!: non-atomic field cannot be written atomically") swapfield!(r, :y, y, :acquire) + @test_throws ConcurrencyViolationError("swapfield!: non-atomic field cannot be written atomically") swapfield!(r, :y, y, :release) + @test_throws ConcurrencyViolationError("swapfield!: non-atomic field cannot be written atomically") swapfield!(r, :y, y, :acquire_release) + @test_throws ConcurrencyViolationError("swapfield!: non-atomic field cannot be written atomically") swapfield!(r, :y, y, :sequentially_consistent) + @test swapfield!(r, :y, y, :not_atomic) === x + + @test_throws ConcurrencyViolationError("invalid atomic ordering") modifyfield!(r, :y, swap, y, :u) + @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :unordered) + @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :monotonic) + @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :acquire) + @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :release) + @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :acquire_release) + @test_throws ConcurrencyViolationError("modifyfield!: non-atomic field cannot be written atomically") modifyfield!(r, :y, swap, y, :sequentially_consistent) + @test modifyfield!(r, :y, swap, x, :not_atomic) === y + + @test_throws ConcurrencyViolationError("invalid atomic ordering") cmpswapfield!(r, :y, y, y, :u, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: non-atomic field cannot be written atomically") cmpswapfield!(r, :y, y, y, :unordered, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: non-atomic field cannot be written atomically") cmpswapfield!(r, :y, y, y, :monotonic, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: non-atomic field cannot be written atomically") cmpswapfield!(r, :y, y, y, :acquire, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: non-atomic field cannot be written atomically") cmpswapfield!(r, :y, y, y, :release, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: non-atomic field cannot be written atomically") cmpswapfield!(r, :y, y, y, :acquire_release, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: non-atomic field cannot be written atomically") cmpswapfield!(r, :y, y, y, :sequentially_consistent, :not_atomic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") cmpswapfield!(r, :y, y, y, :not_atomic, :u) + @test_throws ConcurrencyViolationError("cmpswapfield!: non-atomic field cannot be accessed atomically") cmpswapfield!(r, :y, y, y, :not_atomic, :unordered) + @test_throws ConcurrencyViolationError("cmpswapfield!: non-atomic field cannot be accessed atomically") cmpswapfield!(r, :y, y, y, :not_atomic, :monotonic) + @test_throws ConcurrencyViolationError("cmpswapfield!: non-atomic field cannot be accessed atomically") cmpswapfield!(r, :y, y, y, :not_atomic, :acquire) + @test_throws ConcurrencyViolationError("invalid atomic ordering") cmpswapfield!(r, :y, y, y, :not_atomic, :release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") cmpswapfield!(r, :y, y, y, :not_atomic, :acquire_release) + @test_throws ConcurrencyViolationError("cmpswapfield!: non-atomic field cannot be accessed atomically") cmpswapfield!(r, :y, y, y, :not_atomic, :sequentially_consistent) + @test cmpswapfield!(r, :y, x, y, :not_atomic, :not_atomic) === (x, true) + @test cmpswapfield!(r, :y, x, y, :not_atomic, :not_atomic) === (y, x === y) + @test cmpswapfield!(r, :y, y, y, :not_atomic) === (y, true) + @test cmpswapfield!(r, :y, y, y) === (y, true) + + @test_throws ConcurrencyViolationError("invalid atomic ordering") swapfield!(r, :x, x, :u) + @test_throws ConcurrencyViolationError("swapfield!: atomic field cannot be written non-atomically") swapfield!(r, :x, x, :not_atomic) + @test_throws ConcurrencyViolationError("swapfield!: atomic field cannot be written non-atomically") swapfield!(r, :x, x) + @test swapfield!(r, :x, x, :unordered) === y + @test swapfield!(r, :x, x, :monotonic) === x + @test swapfield!(r, :x, x, :acquire) === x + @test swapfield!(r, :x, x, :release) === x + @test swapfield!(r, :x, x, :acquire_release) === x + @test swapfield!(r, :x, x, :sequentially_consistent) === x + + @test_throws ConcurrencyViolationError("invalid atomic ordering") modifyfield!(r, :x, swap, x, :u) + @test_throws ConcurrencyViolationError("modifyfield!: atomic field cannot be written non-atomically") modifyfield!(r, :x, swap, x, :not_atomic) + @test_throws ConcurrencyViolationError("modifyfield!: atomic field cannot be written non-atomically") modifyfield!(r, :x, swap, x) + @test modifyfield!(r, :x, swap, x, :unordered) === x + @test modifyfield!(r, :x, swap, x, :monotonic) === x + @test modifyfield!(r, :x, swap, x, :acquire) === x + @test modifyfield!(r, :x, swap, x, :release) === x + @test modifyfield!(r, :x, swap, x, :acquire_release) === x + @test modifyfield!(r, :x, swap, x, :sequentially_consistent) === x + + @test_throws ConcurrencyViolationError("invalid atomic ordering") cmpswapfield!(r, :x, x, x, :u, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: atomic field cannot be written non-atomically") cmpswapfield!(r, :x, x, x) + @test_throws ConcurrencyViolationError("cmpswapfield!: atomic field cannot be written non-atomically") cmpswapfield!(r, :x, y, x, :not_atomic, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: atomic field cannot be accessed non-atomically") cmpswapfield!(r, :x, x, x, :unordered, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: atomic field cannot be accessed non-atomically") cmpswapfield!(r, :x, x, x, :monotonic, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: atomic field cannot be accessed non-atomically") cmpswapfield!(r, :x, x, x, :acquire, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: atomic field cannot be accessed non-atomically") cmpswapfield!(r, :x, x, x, :release, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: atomic field cannot be accessed non-atomically") cmpswapfield!(r, :x, x, x, :acquire_release, :not_atomic) + @test_throws ConcurrencyViolationError("cmpswapfield!: atomic field cannot be accessed non-atomically") cmpswapfield!(r, :x, x, x, :sequentially_consistent, :not_atomic) + @test_throws ConcurrencyViolationError("invalid atomic ordering") cmpswapfield!(r, :x, x, x, :not_atomic, :u) + @test_throws ConcurrencyViolationError("cmpswapfield!: atomic field cannot be written non-atomically") cmpswapfield!(r, :x, x, x, :not_atomic, :unordered) + @test_throws ConcurrencyViolationError("cmpswapfield!: atomic field cannot be written non-atomically") cmpswapfield!(r, :x, x, x, :not_atomic, :monotonic) + @test_throws ConcurrencyViolationError("cmpswapfield!: atomic field cannot be written non-atomically") cmpswapfield!(r, :x, x, x, :not_atomic, :acquire) + @test_throws ConcurrencyViolationError("invalid atomic ordering") cmpswapfield!(r, :x, x, x, :not_atomic, :release) + @test_throws ConcurrencyViolationError("invalid atomic ordering") cmpswapfield!(r, :x, x, x, :not_atomic, :acquire_release) + @test_throws ConcurrencyViolationError("cmpswapfield!: atomic field cannot be written non-atomically") cmpswapfield!(r, :x, x, x, :not_atomic, :sequentially_consistent) + @test cmpswapfield!(r, :x, x, y, :sequentially_consistent, :sequentially_consistent) === (x, true) + @test cmpswapfield!(r, :x, x, y, :sequentially_consistent, :sequentially_consistent) === (y, x === y) + @test cmpswapfield!(r, :x, y, x, :sequentially_consistent) === (y, true) nothing end @noinline function test_field_orderings(r, x, y) @@ -145,32 +253,33 @@ test_field_orderings(ARefxy{Union{Nothing,Missing}}(nothing, missing), nothing, test_field_orderings(ARefxy{Union{Nothing,Int}}(nothing, 12345_1), nothing, 12345_1) test_field_orderings(Complex{Int128}(10, 30), Complex{Int128}(20, 40)) -function add(x::T, y)::T where {T}; x + y; end -@noinline function _test_field_operators(r) +struct UndefComplex{T} + re::T + im::T + UndefComplex{T}() where {T} = new{T}() +end +Base.convert(T::Type{<:UndefComplex}, S) = T() +@noinline function _test_field_undef(r) r = r[] - T = typeof(getfield(r, :x)) - @test getfield(r, :x, :sequentially_consistent) === T(12345_10) - @test setfield!(r, :x, T(12345_1), :sequentially_consistent) === T(12345_1) - @test getfield(r, :x, :sequentially_consistent) === T(12345_1) - @test cmpswapfield!(r, :x, T(12345_1), T(12345_100), :sequentially_consistent, :sequentially_consistent) === (T(12345_1), true) - @test getfield(r, :x, :sequentially_consistent) === T(12345_100) - @test cmpswapfield!(r, :x, T(12345_1), T(12345_1), :sequentially_consistent, :sequentially_consistent) === (T(12345_100), false) - @test getfield(r, :x, :sequentially_consistent) === T(12345_100) - @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === T(12345_100) - @test modifyfield!(r, :x, add, 1, :sequentially_consistent) === T(12345_101) - @test getfield(r, :x, :sequentially_consistent) === T(12345_102) - @test swapfield!(r, :x, T(12345_1), :sequentially_consistent) === T(12345_102) - @test getfield(r, :x, :sequentially_consistent) === T(12345_1) + T = fieldtype(typeof(r), :x) + x = convert(T, 12345_10) + @test_throws UndefRefError getfield(r, :x) + @test_throws UndefRefError getfield(r, :x, :sequentially_consistent) + @test_throws UndefRefError modifyfield!(r, :x, add, 1, :sequentially_consistent) + @test_throws (T === Any ? UndefRefError : TypeError) cmpswapfield!(r, :x, 1, 1.0, :sequentially_consistent) + @test_throws UndefRefError cmpswapfield!(r, :x, 1, x, :sequentially_consistent) + @test_throws UndefRefError getfield(r, :x, :sequentially_consistent) + @test_throws UndefRefError swapfield!(r, :x, x, :sequentially_consistent) + @test getfield(r, :x, :sequentially_consistent) === x === getfield(r, :x) + nothing end -@noinline function test_field_operators(r) - _test_field_operators(Ref(copy(r))) - _test_field_operators(Ref{Any}(copy(r))) +@noinline function test_field_undef(T) + _test_field_undef(Ref(T())) + _test_field_undef(Ref{Any}(T())) nothing end -test_field_operators(ARefxy{Int}(12345_10, 12345_20)) -test_field_operators(ARefxy{Any}(12345_10, 12345_20)) -test_field_operators(ARefxy{Union{Nothing,Int}}(12345_10, nothing)) -test_field_operators(ARefxy{Complex{Int32}}(12345_10, 12345_20)) -test_field_operators(ARefxy{Complex{Int128}}(12345_10, 12345_20)) - -# TODO: need tests of UndefRefException +test_field_undef(ARefxy{BigInt}) +test_field_undef(ARefxy{Any}) +test_field_undef(ARefxy{Union{Nothing,Integer}}) +test_field_undef(ARefxy{UndefComplex{Any}}) +test_field_undef(ARefxy{UndefComplex{UndefComplex{Any}}}) diff --git a/test/intrinsics.jl b/test/intrinsics.jl index 1a45c05b28d1e..6a446e89109c9 100644 --- a/test/intrinsics.jl +++ b/test/intrinsics.jl @@ -101,8 +101,17 @@ let f = Core.Intrinsics.ashr_int end # issue #29929 -@test unsafe_store!(Ptr{Nothing}(C_NULL), nothing) === Ptr{Nothing}(0) -@test unsafe_load(Ptr{Nothing}(0)) === nothing +let p = Ptr{Nothing}(0) + @test unsafe_store!(p, nothing) === C_NULL + @test unsafe_load(p) === nothing + @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === nothing + @test Core.Intrinsics.atomic_pointerset(p, nothing, :sequentially_consistent) === p + @test Core.Intrinsics.atomic_pointerswap(p, nothing, :sequentially_consistent) === nothing + @test Core.Intrinsics.atomic_pointermodify(p, (i, j) -> j, nothing, :sequentially_consistent) === nothing + @test Core.Intrinsics.atomic_pointercmpswap(p, nothing, nothing, :sequentially_consistent, :sequentially_consistent) === (nothing, true) + @test Core.Intrinsics.atomic_pointercmpswap(p, missing, nothing, :sequentially_consistent, :sequentially_consistent) === (nothing, false) +end + struct GhostStruct end @test unsafe_load(Ptr{GhostStruct}(rand(Int))) === GhostStruct() @@ -172,16 +181,16 @@ for TT in (Int8, Int16, Int32, Int64, Int128, Int256, Int512, Complex{Int32}, Co @test_throws TypeError Core.Intrinsics.atomic_pointerset(p, S(1), :sequentially_consistent) @test_throws TypeError Core.Intrinsics.atomic_pointerswap(p, S(100), :sequentially_consistent) @test_throws TypeError Core.Intrinsics.atomic_pointercmpswap(p, T(100), S(2), :sequentially_consistent, :sequentially_consistent) - @test_throws TypeError Core.Intrinsics.atomic_pointercmpswap(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) end @test Core.Intrinsics.pointerref(p, 1, 1) === T(10) === r[] if sizeof(r) > 2 * sizeof(Int) - @test_throws ErrorException Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) - @test_throws ErrorException Core.Intrinsics.atomic_pointerset(p, T(1), :sequentially_consistent) - @test_throws ErrorException Core.Intrinsics.atomic_pointerswap(p, T(100), :sequentially_consistent) - @test_throws ErrorException Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) - @test_throws ErrorException Core.Intrinsics.atomic_pointermodify(p, swap, S(1), :sequentially_consistent) - @test_throws ErrorException Core.Intrinsics.atomic_pointercmpswap(p, T(100), T(2), :sequentially_consistent, :sequentially_consistent) + @test_throws ErrorException("pointerref: invalid atomic operation") Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) + @test_throws ErrorException("pointerset: invalid atomic operation") Core.Intrinsics.atomic_pointerset(p, T(1), :sequentially_consistent) + @test_throws ErrorException("pointerswap: invalid atomic operation") Core.Intrinsics.atomic_pointerswap(p, T(100), :sequentially_consistent) + @test_throws ErrorException("pointerref: invalid atomic operation") Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) + @test_throws ErrorException("pointerref: invalid atomic operation") Core.Intrinsics.atomic_pointermodify(p, swap, S(1), :sequentially_consistent) + @test_throws ErrorException("pointercmpswap: invalid atomic operation") Core.Intrinsics.atomic_pointercmpswap(p, T(100), T(2), :sequentially_consistent, :sequentially_consistent) + @test_throws ErrorException("pointercmpswap: invalid atomic operation") Core.Intrinsics.atomic_pointercmpswap(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) @test Core.Intrinsics.pointerref(p, 1, 1) === T(10) === r[] else TT !== Any && @test_throws TypeError Core.Intrinsics.atomic_pointermodify(p, swap, S(1), :sequentially_consistent) @@ -196,6 +205,7 @@ for TT in (Int8, Int16, Int32, Int64, Int128, Int256, Int512, Complex{Int32}, Co @test Core.Intrinsics.atomic_pointermodify(p, add, T(1), :sequentially_consistent) === T(101) @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(102) @test Core.Intrinsics.atomic_pointerswap(p, T(103), :sequentially_consistent) === T(102) + @test Core.Intrinsics.atomic_pointercmpswap(p, S(100), T(2), :sequentially_consistent, :sequentially_consistent) === (T(103), false) @test Core.Intrinsics.atomic_pointerref(p, :sequentially_consistent) === T(103) end if TT === Any