From 8078aa5a763badeed1e30a209203baaff00e478c Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Thu, 6 Jun 2024 11:34:31 +0200 Subject: [PATCH 01/15] add modify, swap, replace for GenericMemory at index --- base/genericmemory.jl | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/base/genericmemory.jl b/base/genericmemory.jl index 77dbe01af8ad8..8a15f64d6f308 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -213,6 +213,7 @@ function setindex!(A::Memory{T}, x, i1::Int) where {T} memoryrefset!(ref, val, :not_atomic, @_boundscheck) return A end + function setindex!(A::Memory{T}, x, i1::Int, i2::Int, I::Int...) where {T} @inline @boundscheck (i2 == 1 && all(==(1), I)) || throw_boundserror(A, (i1, i2, I...)) @@ -313,3 +314,51 @@ end end end view(m::GenericMemory, inds::Colon) = view(m, eachindex(m)) + +# modify, swap and replace at index +function modifyindex!( + mem::GenericMemory, + i::Int, + op, + val, + order = default_access_order(mem), +) + memref = memoryref(mem, i) + return Core.memoryrefmodify!(memref, op, val, order, @_boundscheck) +end + +function swapindex!( + mem::GenericMemory, + i::Int, + val, + order = default_access_order(mem), +) + T = eltype(mem) + memref = memoryref(mem, i) + return Core.memoryrefswap!( + memref, + val isa T ? val : convert(T, val)::T, + order, + @_boundscheck + ) +end + +function replaceindex!( + mem::GenericMemory, + i::Int, + expected, + desired, + success_order = default_access_order(mem), + fail_order = default_access_order(mem), +) + T = eltype(mem) + memref = memoryref(mem, i) + return Core.memoryrefreplace!( + memref, + expected, + desired isa T ? desired : convert(T, desired)::T, + success_order, + fail_order, + @_boundscheck, + ) +end From 7a048a04eb9e7e2b144392a66a31ed4133461579 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Fri, 7 Jun 2024 12:14:20 +0200 Subject: [PATCH 02/15] add atomic getindex, setindex, setindexonce --- base/genericmemory.jl | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/base/genericmemory.jl b/base/genericmemory.jl index 8a15f64d6f308..5a5f25eb03419 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -316,6 +316,49 @@ end view(m::GenericMemory, inds::Colon) = view(m, eachindex(m)) # modify, swap and replace at index +function getindex_atomic( + mem::GenericMemory, + i::Int, + order=default_access_order(mem) +) + memref = memoryref(mem, i) + return memoryrefget(memref, order, @_boundscheck) +end + +function setindex_atomic!( + mem::GenericMemory, + i::Int, + val, + order=default_access_order(mem) +) + T = eltype(mem) + memref = memoryref(mem, i) + return memoryrefset!( + memref, + val isa T ? val : convert(T, val)::T, + order, + @_boundscheck + ) +end + +function setindexonce_atomic!( + mem::GenericMemory, + i::Int, + val, + success_order=default_access_order(mem), + fail_order=default_access_order(mem) +) + T = eltype(mem) + memref = memoryref(mem, i) + return Core.memoryrefsetonce!( + memref, + val isa T ? val : convert(T, val)::T, + success_order, + fail_order, + @_boundscheck + ) +end + function modifyindex!( mem::GenericMemory, i::Int, From e829265d513c77f6143ecbb24c5d5d8ae3a529e6 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Fri, 7 Jun 2024 12:15:47 +0200 Subject: [PATCH 03/15] implement @atomic macros for indexing --- base/expr.jl | 66 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/base/expr.jl b/base/expr.jl index cbeb16dd8d79b..402ff22b98d12 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -1180,11 +1180,17 @@ function make_atomic(order, ex) return :(getproperty($l, $r, $order)) elseif isexpr(ex, :call, 3) return make_atomic(order, ex.args[2], ex.args[1], ex.args[3]) + elseif isexpr(ex, :ref, 2) + x, idx = esc(ex.args[1]), esc(ex.args[2]) + return :(getindex_atomic($x, $idx, $order)) elseif ex.head === :(=) l, r = ex.args[1], esc(ex.args[2]) if is_expr(l, :., 2) ll, lr = esc(l.args[1]), esc(l.args[2]) return :(setproperty!($ll, $lr, $r, $order)) + elseif is_expr(l, :ref, 2) + x, idx = esc(l.args[1]), esc(l.args[2]) + return :(setindex_atomic!($x, $idx, $r, $order)) end end if length(ex.args) == 2 @@ -1207,9 +1213,14 @@ function make_atomic(order, ex) end function make_atomic(order, a1, op, a2) @nospecialize - is_expr(a1, :., 2) || error("@atomic modify expression missing field access") - a1l, a1r, op, a2 = esc(a1.args[1]), esc(a1.args[2]), esc(op), esc(a2) - return :(modifyproperty!($a1l, $a1r, $op, $a2, $order)) + if is_expr(a1, :., 2) + a1l, a1r, op, a2 = esc(a1.args[1]), esc(a1.args[2]), esc(op), esc(a2) + return :(modifyproperty!($a1l, $a1r, $op, $a2, $order)) + elseif is_expr(a1, :ref, 2) + x, idx, op, a2 = esc(a1.args[1]), esc(a1.args[2]), esc(op), esc(a2) + return :(modifyindex!($x, $idx, $op, $a2, $order)) + end + error("@atomic modify expression missing field access or indexing") end @@ -1251,9 +1262,14 @@ function make_atomicswap(order, ex) @nospecialize is_expr(ex, :(=), 2) || error("@atomicswap expression missing assignment") l, val = ex.args[1], esc(ex.args[2]) - is_expr(l, :., 2) || error("@atomicswap expression missing field access") - ll, lr = esc(l.args[1]), esc(l.args[2]) - return :(swapproperty!($ll, $lr, $val, $order)) + if is_expr(l, :., 2) + ll, lr = esc(l.args[1]), esc(l.args[2]) + return :(swapproperty!($ll, $lr, $val, $order)) + elseif is_expr(l, :ref, 2) + x, idx = esc(l.args[1]), esc(l.args[2]) + return :(swapindex!($x, $idx, $val, $order)) + end + error("@atomicswap expression missing field access or indexing") end @@ -1312,15 +1328,26 @@ macro atomicreplace(ex, old_new) end function make_atomicreplace(success_order, fail_order, ex, old_new) @nospecialize - is_expr(ex, :., 2) || error("@atomicreplace expression missing field access") - ll, lr = esc(ex.args[1]), esc(ex.args[2]) - if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>) - exp, rep = esc(old_new.args[2]), esc(old_new.args[3]) - return :(replaceproperty!($ll, $lr, $exp, $rep, $success_order, $fail_order)) - else - old_new = esc(old_new) - return :(replaceproperty!($ll, $lr, $old_new::Pair..., $success_order, $fail_order)) + if is_expr(ex, :., 2) + ll, lr = esc(ex.args[1]), esc(ex.args[2]) + if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>) + exp, rep = esc(old_new.args[2]), esc(old_new.args[3]) + return :(replaceproperty!($ll, $lr, $exp, $rep, $success_order, $fail_order)) + else + old_new = esc(old_new) + return :(replaceproperty!($ll, $lr, $old_new::Pair..., $success_order, $fail_order)) + end + elseif is_expr(ex, :ref, 2) + x, idx = esc(ex.args[1]), esc(ex.args[2]) + if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>) + exp, rep = esc(old_new.args[2]), esc(old_new.args[3]) + return :(replaceindex!($x, $idx, $exp, $rep, $success_order, $fail_order)) + else + old_new = esc(old_new) + return :(replaceindex!($x, $idx, $old_new::Pair..., $success_order, $fail_order)) + end end + error("@atomicreplace expression missing field access or indexing") end """ @@ -1375,7 +1402,12 @@ function make_atomiconce(success_order, fail_order, ex) @nospecialize is_expr(ex, :(=), 2) || error("@atomiconce expression missing assignment") l, val = ex.args[1], esc(ex.args[2]) - is_expr(l, :., 2) || error("@atomiconce expression missing field access") - ll, lr = esc(l.args[1]), esc(l.args[2]) - return :(setpropertyonce!($ll, $lr, $val, $success_order, $fail_order)) + if is_expr(l, :., 2) + ll, lr = esc(l.args[1]), esc(l.args[2]) + return :(setpropertyonce!($ll, $lr, $val, $success_order, $fail_order)) + elseif is_expr(l, :ref, 2) + x, idx = esc(l.args[1]), esc(l.args[2]) + return :(setindexonce_atomic!($x, $idx, $val, $success_order, $fail_order)) + end + error("@atomiconce expression missing field access or indexing") end From c7f721c8ffc473fa4aab30873b84e68448a556a5 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Fri, 7 Jun 2024 16:13:24 +0200 Subject: [PATCH 04/15] add tests for @atomic indexing --- test/atomics.jl | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/atomics.jl b/test/atomics.jl index 0d6c841073ad5..9877ad68bcb64 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -423,6 +423,69 @@ let a = ARefxy(1, -1) @test_throws ConcurrencyViolationError @atomicreplace :monotonic :acquire a.x xchg end +let a = AtomicMemory{Float64}(undef, 2) + @test_throws CanonicalIndexError a[1] = 3 + + @test Base.setindex_atomic!(a, 1, 2) == 2.0 + @test Base.setindex_atomic!(a, 2, 3) == 3.0 + + # normal and atomic get + + @test a[1] == 2.0 + @test a[2] == 3.0 + + @test (@atomic a[1]) == a[1] + + # atomic set, swap and modify + let a = a, (old::Float64, new::Int) = (a[1], 10) + # old and new are intentionally of different types to test inner conversion + @test (@atomic a[1] = new) == new + @test a[1] == new + @atomic a[1] = old + + @test (@atomicswap a[1] = new) == old + @test a[1] == new + @atomic a[1] = old + + @test (@atomic a[1] += new) == new + old + @test a[1] == new + old + @atomic a[1] = old + end + + # atomicreplace + let a = a, (old::Float64, new::Int) = (a[1], 10) + # old and new are intentionally of different types to test inner conversion + @test (@atomicreplace a[1] old => new) == (old=old, success=true) + @test a[1] == new + @atomic a[1] = old + + @test (@atomicreplace a[1] new => old) == (old=old, success=false) + @test a[1] == old + @atomic a[1] = old + + @test (@atomicreplace a[1] Pair(old, new)) == (old=old, success=true) + @test a[1] == new + @atomic a[1] = old + + @test (@atomicreplace a[1] Pair(new, old)) == (old=old, success=false) + @test a[1] == old + @atomic a[1] = old + end + + # @atomiconce + let a = a, b = AtomicMemory{Vector{Int}}(undef, 1) + @test (@atomiconce a[1] = 3) == false + + @test !isassigned(b, 1) + val = [1, 2, 3] + @test (@atomiconce b[1] = val) == true + @test b[1] == val + + @test !(@atomiconce b[1] = [1, 2]) + @test b[1] == val + end +end + let a = ARefxy{Union{Nothing,Integer}}() @test_throws ConcurrencyViolationError @atomiconce :not_atomic a.x = 2 @test true === @atomiconce a.x = 1 From 6980cd32b04def2fb407cb8597e00deeaa163e65 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Fri, 7 Jun 2024 16:37:09 +0200 Subject: [PATCH 05/15] wrap for @atomic tests in @testset --- test/atomics.jl | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/atomics.jl b/test/atomics.jl index 9877ad68bcb64..04ab6d0ba7161 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -424,6 +424,8 @@ let a = ARefxy(1, -1) end let a = AtomicMemory{Float64}(undef, 2) +@testset "@atomic with AtomicMemory" begin + a = AtomicMemory{Float64}(undef, 2) @test_throws CanonicalIndexError a[1] = 3 @test Base.setindex_atomic!(a, 1, 2) == 2.0 @@ -436,8 +438,8 @@ let a = AtomicMemory{Float64}(undef, 2) @test (@atomic a[1]) == a[1] - # atomic set, swap and modify - let a = a, (old::Float64, new::Int) = (a[1], 10) + @testset "atomic set, swap and modify" begin + (old::Float64, new::Int) = (a[1], 10) # old and new are intentionally of different types to test inner conversion @test (@atomic a[1] = new) == new @test a[1] == new @@ -452,8 +454,8 @@ let a = AtomicMemory{Float64}(undef, 2) @atomic a[1] = old end - # atomicreplace - let a = a, (old::Float64, new::Int) = (a[1], 10) + @testset "@atomicreplace" begin + (old::Float64, new::Int) = (a[1], 10) # old and new are intentionally of different types to test inner conversion @test (@atomicreplace a[1] old => new) == (old=old, success=true) @test a[1] == new @@ -472,8 +474,8 @@ let a = AtomicMemory{Float64}(undef, 2) @atomic a[1] = old end - # @atomiconce - let a = a, b = AtomicMemory{Vector{Int}}(undef, 1) + @testset "@atomiconce" begin + b = AtomicMemory{Vector{Int}}(undef, 1) @test (@atomiconce a[1] = 3) == false @test !isassigned(b, 1) From 2b9cd4f14ad6d1f868dc408975a8339abfa0ffd7 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Fri, 7 Jun 2024 21:45:20 +0200 Subject: [PATCH 06/15] test atomic index operations on variety of types --- test/atomics.jl | 128 +++++++++++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 55 deletions(-) diff --git a/test/atomics.jl b/test/atomics.jl index 04ab6d0ba7161..d064acc936430 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -91,6 +91,7 @@ primitive type Int24 <: Signed 24 end # integral padding Int24(x::Int) = Core.Intrinsics.trunc_int(Int24, x) Base.Int(x::PadIntB) = x.a + (Int(x.b) << 8) + (Int(x.c) << 16) Base.:(+)(x::PadIntA, b::Int) = PadIntA(x.b + b) +Base.:(==)(x::PadIntA, b::Int) = x == PadIntA(b) Base.:(+)(x::PadIntB, b::Int) = PadIntB(Int(x) + b) Base.:(+)(x::Int24, b::Int) = Core.Intrinsics.add_int(x, Int24(b)) Base.show(io::IO, x::PadIntA) = print(io, "PadIntA(", x.b, ")") @@ -423,69 +424,86 @@ let a = ARefxy(1, -1) @test_throws ConcurrencyViolationError @atomicreplace :monotonic :acquire a.x xchg end -let a = AtomicMemory{Float64}(undef, 2) -@testset "@atomic with AtomicMemory" begin - a = AtomicMemory{Float64}(undef, 2) - @test_throws CanonicalIndexError a[1] = 3 - - @test Base.setindex_atomic!(a, 1, 2) == 2.0 - @test Base.setindex_atomic!(a, 2, 3) == 3.0 +function _test_atomic_get_set_swap_modify(T, x, y, z) + @testset "atomic get,set,swap,modify" begin + mem = AtomicMemory{T}(undef, 2) + @test_throws CanonicalIndexError mem[1] = 3 - # normal and atomic get + @test Base.setindex_atomic!(mem, 1, x) == x + @test mem[1] == x + @test Base.setindex_atomic!(mem, 2, y) == y + @test mem[2] == y - @test a[1] == 2.0 - @test a[2] == 3.0 + @test (@atomic mem[1]) == x + @test (@atomic mem[2]) == y - @test (@atomic a[1]) == a[1] - - @testset "atomic set, swap and modify" begin - (old::Float64, new::Int) = (a[1], 10) + (old, new) = (mem[2], z) # old and new are intentionally of different types to test inner conversion - @test (@atomic a[1] = new) == new - @test a[1] == new - @atomic a[1] = old - - @test (@atomicswap a[1] = new) == old - @test a[1] == new - @atomic a[1] = old - - @test (@atomic a[1] += new) == new + old - @test a[1] == new + old - @atomic a[1] = old + @test (@atomic mem[2] = new) == new + @test mem[2] == new + @atomic mem[2] = old + + @test (@atomicswap mem[2] = new) == old + @test mem[2] == new + @atomic mem[2] = old + + try + old + new + @test (@atomic mem[2] += new) == old + new + @test mem[2] == old + new + @atomic mem[2] = old + catch err + if !(err isa MethodError) + rethrow(err) + end + end end +end - @testset "@atomicreplace" begin - (old::Float64, new::Int) = (a[1], 10) - # old and new are intentionally of different types to test inner conversion - @test (@atomicreplace a[1] old => new) == (old=old, success=true) - @test a[1] == new - @atomic a[1] = old - - @test (@atomicreplace a[1] new => old) == (old=old, success=false) - @test a[1] == old - @atomic a[1] = old - - @test (@atomicreplace a[1] Pair(old, new)) == (old=old, success=true) - @test a[1] == new - @atomic a[1] = old - - @test (@atomicreplace a[1] Pair(new, old)) == (old=old, success=false) - @test a[1] == old - @atomic a[1] = old +function _test_atomic_setonce_replace(T, initial, desired) + @testset "atomic setonce,replace" begin + mem = AtomicMemory{T}(undef, 2) + if isassigned(mem, 2) + @test (@atomiconce mem[2] = initial) == false + @atomic mem[2] = initial + else + @test (@atomiconce mem[2] = initial) == true + @test mem[2] == initial + @test (@atomiconce mem[2] = desired) == false + @test mem[2] == initial + @test !isassigned(mem, 1) + end + + expected = @atomic mem[2] + @test (@atomicreplace mem[2] expected => desired) == (old=expected, success=true) + @test mem[2] == desired + + @atomic mem[2] = expected + @test (@atomicreplace mem[2] desired => desired) == (old=expected, success=false) + @test mem[2] == expected + + @atomic mem[2] = expected + @test (@atomicreplace mem[2] Pair(expected, desired)) == (old=expected, success=true) + @test mem[2] == desired + + @atomic mem[2] = expected + @test (@atomicreplace mem[2] Pair(desired, desired)) == (old=initial, success=false) + @test mem[2] == expected end +end +@testset "@atomic with AtomicMemory" begin - @testset "@atomiconce" begin - b = AtomicMemory{Vector{Int}}(undef, 1) - @test (@atomiconce a[1] = 3) == false - - @test !isassigned(b, 1) - val = [1, 2, 3] - @test (@atomiconce b[1] = val) == true - @test b[1] == val - - @test !(@atomiconce b[1] = [1, 2]) - @test b[1] == val - end + _test_atomic_get_set_swap_modify(Float64, rand(), rand(), 10) + _test_atomic_get_set_swap_modify(PadIntA, 123_1, 123_2, 10) + _test_atomic_get_set_swap_modify(Union{Nothing,Int}, 123_1, nothing, 10) + _test_atomic_get_set_swap_modify(Union{Nothing,Int}, 123_1, 234_5, 10) + _test_atomic_get_set_swap_modify(Vector{BigInt}, BigInt[1, 2, 3], BigInt[1, 2], [2, 4]) + + _test_atomic_setonce_replace(Float64, rand(), 42) + _test_atomic_setonce_replace(PadIntA, 123_1, 123_2) + _test_atomic_setonce_replace(Union{Nothing,Int}, 123_1, nothing) + _test_atomic_setonce_replace(Vector{BigInt}, BigInt[1, 2], [3, 4]) + _test_atomic_setonce_replace(String, "abc", "cab") end let a = ARefxy{Union{Nothing,Integer}}() From 31933796000991093bbcc7b094111fe87df3e0c5 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Wed, 19 Jun 2024 21:02:44 +0200 Subject: [PATCH 07/15] document @atomic memory refs functionality --- base/expr.jl | 160 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 140 insertions(+), 20 deletions(-) diff --git a/base/expr.jl b/base/expr.jl index 402ff22b98d12..6489778c3e050 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -1097,16 +1097,22 @@ Mark `var` or `ex` as being performed atomically, if `ex` is a supported express If no `order` is specified it defaults to :sequentially_consistent. @atomic a.b.x = new - @atomic a.b.x += addend + @atomic a.b.x += added @atomic :release a.b.x = new - @atomic :acquire_release a.b.x += addend + @atomic :acquire_release a.b.x += added + @atomic m[idx] = new + @atomic m[idx] += added + @atomic :release m[idx] = new + @atomic :acquire_release m[idx] += added Perform the store operation expressed on the right atomically and return the new value. -With `=`, this operation translates to a `setproperty!(a.b, :x, new)` call. -With any operator also, this operation translates to a `modifyproperty!(a.b, -:x, +, addend)[2]` call. +With assignment (`=`), this operation translates to a `setproperty!(a.b, :x, new)` +or, in case of reference, to a `setindex_atomic!(m, idx, new)` call. +With any modifying operator this operation translates to a +`modifyproperty!(a.b, :x, op, added)[2]` or, in case of reference, to a +`modifyindex!(m, idx, op, added)[2]` call. @atomic a.b.x max arg2 @atomic a.b.x + arg2 @@ -1114,12 +1120,19 @@ With any operator also, this operation translates to a `modifyproperty!(a.b, @atomic :acquire_release max(a.b.x, arg2) @atomic :acquire_release a.b.x + arg2 @atomic :acquire_release a.b.x max arg2 + @atomic m[idx] max arg2 + @atomic m[idx] + arg2 + @atomic max(m[idx], arg2) + @atomic :acquire_release max(m[idx], arg2) + @atomic :acquire_release m[idx] + arg2 + @atomic :acquire_release m[idx] max arg2 Perform the binary operation expressed on the right atomically. Store the -result into the field in the first argument and return the values `(old, new)`. - -This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` call. +result into the field or the reference in the first argument, and return the values +`(old, new)`. +This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` or, +in case of reference to a `modifyindex!(m, idx, func, arg2)` call. See [Per-field atomics](@ref man-atomics) section in the manual for more details. @@ -1152,8 +1165,36 @@ julia> @atomic a.x max 5 # again change field x of a to the max value, with sequ 10 => 10 ``` +```jldoctest +julia> mem = AtomicMemory{Int}(undef, 2); + +julia> @atomic mem[1] = 2 # set mem[1] to value 2 with sequential consistency +2 + +julia> @atomic :monotonic mem[1] # fetch the first value of mem, with monotonic consistency +2 + +julia> @atomic mem[1] += 1 # increment the first value of mem, with sequential consistency +3 + +julia> @atomic mem[1] + 1 # increment the first value of mem, with sequential consistency +3 => 4 + +julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency +4 + +julia> @atomic max(mem[1], 10) # change the first value of mem to the max value, with sequential consistency +4 => 10 + +julia> @atomic mem[1] max 5 # again change the first value of mem to the max value, with sequential consistency +10 => 5 +``` + !!! compat "Julia 1.7" - This functionality requires at least Julia 1.7. + Atomic fields functionality requires at least Julia 1.7. + +!!! compat "Julia 1.12" + Atomic reference functionality requires at least Julia 1.12. """ macro atomic(ex) if !isa(ex, Symbol) && !is_expr(ex, :(::)) @@ -1227,10 +1268,14 @@ end """ @atomicswap a.b.x = new @atomicswap :sequentially_consistent a.b.x = new + @atomicswap m[idx] = new + @atomicswap :sequentially_consistent m[idx] = new -Stores `new` into `a.b.x` and returns the old value of `a.b.x`. +Stores `new` into `a.b.x` (`m[idx]` in case of reference) and returns the old +value of `a.b.x` (the old value stored at `m[idx]`, respectively). -This operation translates to a `swapproperty!(a.b, :x, new)` call. +This operation translates to a `swapproperty!(a.b, :x, new)` or, +in case of reference, `swapindex!(mem, idx, new)` call. See [Per-field atomics](@ref man-atomics) section in the manual for more details. @@ -1248,8 +1293,23 @@ julia> @atomic a.x # fetch field x of a, with sequential consistency 4 ``` +```jldoctest +julia> mem = AtomicMemory{Int}(undef, 2); + +julia> @atomic mem[1] = 1; + +julia> @atomicswap mem[1] = 4 # replace the first value of `mem` with 4, with sequential consistency +1 + +julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency +4 +``` + !!! compat "Julia 1.7" - This functionality requires at least Julia 1.7. + Atomic fields functionality requires at least Julia 1.7. + +!!! compat "Julia 1.12" + Atomic reference functionality requires at least Julia 1.12. """ macro atomicswap(order, ex) order isa QuoteNode || (order = esc(order)) @@ -1277,12 +1337,16 @@ end @atomicreplace a.b.x expected => desired @atomicreplace :sequentially_consistent a.b.x expected => desired @atomicreplace :sequentially_consistent :monotonic a.b.x expected => desired + @atomicreplace m[idx] expected => desired + @atomicreplace :sequentially_consistent m[idx] expected => desired + @atomicreplace :sequentially_consistent :monotonic m[idx] expected => desired Perform the conditional replacement expressed by the pair atomically, returning the values `(old, success::Bool)`. Where `success` indicates whether the replacement was completed. -This operation translates to a `replaceproperty!(a.b, :x, expected, desired)` call. +This operation translates to a `replaceproperty!(a.b, :x, expected, desired)` or, +in case of reference, to a `replaceindex!(mem, idx, expected, desired)` call. See [Per-field atomics](@ref man-atomics) section in the manual for more details. @@ -1299,7 +1363,7 @@ julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with julia> @atomic a.x # fetch field x of a, with sequential consistency 2 -julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with sequential consistency +julia> @atomicreplace a.x 1 => 3 # replace field x of a with 2 if it was 1, with sequential consistency (old = 2, success = false) julia> xchg = 2 => 0; # replace field x of a with 0 if it was 2, with sequential consistency @@ -1311,8 +1375,34 @@ julia> @atomic a.x # fetch field x of a, with sequential consistency 0 ``` +```jldoctest +julia> mem = AtomicMemory{Int}(undef, 2); + +julia> @atomic mem[1] = 1; + +julia> @atomicreplace mem[1] 1 => 2 # replace the first value of mem with 2 if it was 1, with sequential consistency +(old = 1, success = true) + +julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency +2 + +julia> @atomicreplace mem[1] 1 => 3 # replace field x of a with 2 if it was 1, with sequential consistency +(old = 2, success = false) + +julia> xchg = 2 => 0; # replace field x of a with 0 if it was 2, with sequential consistency + +julia> @atomicreplace mem[1] xchg +(old = 2, success = true) + +julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency +0 +``` + !!! compat "Julia 1.7" - This functionality requires at least Julia 1.7. + Atomic fields functionality requires at least Julia 1.7. + +!!! compat "Julia 1.12" + Atomic reference functionality requires at least Julia 1.12. """ macro atomicreplace(success_order, fail_order, ex, old_new) fail_order isa QuoteNode || (fail_order = esc(fail_order)) @@ -1354,12 +1444,15 @@ end @atomiconce a.b.x = value @atomiconce :sequentially_consistent a.b.x = value @atomiconce :sequentially_consistent :monotonic a.b.x = value + @atomiconce m[idx] = value + @atomiconce :sequentially_consistent m[idx] = value + @atomiconce :sequentially_consistent :monotonic m[idx] = value Perform the conditional assignment of value atomically if it was previously -unset, returning the value `success::Bool`. Where `success` indicates whether -the assignment was completed. +unset. Returned value `success::Bool` indicates whether the assignment was completed. -This operation translates to a `setpropertyonce!(a.b, :x, value)` call. +This operation translates to a `setpropertyonce!(a.b, :x, value)` or, +in case of reference, to a `setindexonce_atomic!(m, idx, value)` call. See [Per-field atomics](@ref man-atomics) section in the manual for more details. @@ -1379,12 +1472,39 @@ true julia> @atomic a.x # fetch field x of a, with sequential consistency 1 -julia> @atomiconce a.x = 1 # set field x of a to 1, if unset, with sequential consistency +julia> @atomiconce :monotonic a.x = 2 # set field x of a to 1, if unset, with monotonic consistence false ``` +```jldoctest +julia> mem = AtomicMemory{Vector{Int}}(undef, 1); + +julia> isassigned(mem, 1) +false + +julia> @atomiconce mem[1] = [1] # set the first value of mem to [1], if unset, with sequential consistency +true + +julia> isassigned(mem, 1) +true + +julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency +1-element Vector{Int64}: + 1 + +julia> @atomiconce :monotonic mem[1] = [2] # set the first value of mem to [2], if unset, with monotonic +false + +julia> @atomic mem[1] +1-element Vector{Int64}: + 1 +``` + !!! compat "Julia 1.11" - This functionality requires at least Julia 1.11. + Atomic fields functionality requires at least Julia 1.11. + +!!! compat "Julia 1.12" + Atomic reference functionality requires at least Julia 1.12. """ macro atomiconce(success_order, fail_order, ex) fail_order isa QuoteNode || (fail_order = esc(fail_order)) From 9760b7311fc54afc41b00b32d62416716f3317e5 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Wed, 19 Jun 2024 21:03:54 +0200 Subject: [PATCH 08/15] add new @atomic functionality to language features --- NEWS.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS.md b/NEWS.md index 75d0bc1d1ce3a..c01175cbcf925 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,15 @@ New language features - A new keyword argument `usings::Bool` has been added to `names`. By using this, we can now find all the names available in module `A` by `names(A; all=true, imported=true, usings=true)`. ([#54609]) +- the `@atomic(...)` macro family supports now the reference assignment syntax, e.g. + `@atomic :monotonic v[3] += 4` modifies `v[3]` atomically with monotonic ordering semantics. ([#54707]) + The supported syntax allows + - atomic fetch (`x = @atomic v[3]`), + - atomic set (`@atomic v[3] = 4`), + - atomic modify (`@atomic v[3] += 2`), + - atomic set once (`@atomiconce v[3] = 2`), + - atomic swap (`x = @atomicswap v[3] = 2`), and + - atomic replace (`x = @atomicreplace v[3] 2=>5`). Language changes ---------------- From f4e6ca25c46628e43468c47ad1e0bf4e1d1fd403 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Wed, 19 Jun 2024 21:31:42 +0200 Subject: [PATCH 09/15] update AtomicMemory docstring --- base/genericmemory.jl | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/base/genericmemory.jl b/base/genericmemory.jl index e472708303d38..fb5ef35040c12 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -38,20 +38,23 @@ Memory AtomicMemory{T} == GenericMemory{:atomic, T, Core.CPU} Fixed-size [`DenseVector{T}`](@ref DenseVector). -Access to its any of its elements is performed atomically (with `:monotonic` ordering). -Setting any of the elements must be accomplished using the `@atomic` macro and explicitly specifying ordering. +Fetching of any of its individual elements is performed atomically +(with `:monotonic` ordering by default). !!! warning - Each element is independently atomic when accessed, and cannot be set non-atomically. - Currently the `@atomic` macro and higher level interface have not been completed, - but the building blocks for a future implementation are the internal intrinsics - `Core.memoryrefget`, `Core.memoryrefset!`, `Core.memoryref_isassigned`, `Core.memoryrefswap!`, - `Core.memoryrefmodify!`, and `Core.memoryrefreplace!`. + The access to `AtomicMemory` must be done by either using the [`@atomic`](@ref) + macro or the lower level interface functions: `Base.getindex_atomic`, + `Base.setindex_atomic!`, `Base.setindexonce_atomic!`, + `Base.swapindex!`, `Base.modifyindex!`, and `Base.replaceindex!`. -For details, see [Atomic Operations](@ref man-atomic-operations) +For details, see [Atomic Operations](@ref man-atomic-operations) as well as macros +[`@atomic`](@ref), [`@atomiconce`](@ref), [`@atomicswap`](@ref), and [`@atomicreplace`](@ref). !!! compat "Julia 1.11" This type requires Julia 1.11 or later. + +!!! compat "Julia 1.12" + Lower level interface functions or `@atomic` macro requires Julia 1.12 or later. """ AtomicMemory From 42bb2661401ca508095a9a9970130cf1500b2ca1 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Wed, 26 Jun 2024 15:40:33 +0200 Subject: [PATCH 10/15] addend is the noun form of added (not a typo) --- base/expr.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/base/expr.jl b/base/expr.jl index 6489778c3e050..d605043e06cf2 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -1097,13 +1097,13 @@ Mark `var` or `ex` as being performed atomically, if `ex` is a supported express If no `order` is specified it defaults to :sequentially_consistent. @atomic a.b.x = new - @atomic a.b.x += added + @atomic a.b.x += addend @atomic :release a.b.x = new - @atomic :acquire_release a.b.x += added + @atomic :acquire_release a.b.x += addend @atomic m[idx] = new - @atomic m[idx] += added + @atomic m[idx] += addend @atomic :release m[idx] = new - @atomic :acquire_release m[idx] += added + @atomic :acquire_release m[idx] += addend Perform the store operation expressed on the right atomically and return the new value. @@ -1111,8 +1111,8 @@ new value. With assignment (`=`), this operation translates to a `setproperty!(a.b, :x, new)` or, in case of reference, to a `setindex_atomic!(m, idx, new)` call. With any modifying operator this operation translates to a -`modifyproperty!(a.b, :x, op, added)[2]` or, in case of reference, to a -`modifyindex!(m, idx, op, added)[2]` call. +`modifyproperty!(a.b, :x, op, addend)[2]` or, in case of reference, to a +`modifyindex!(m, idx, op, addend)[2]` call. @atomic a.b.x max arg2 @atomic a.b.x + arg2 From b2343da01b1141d03f96d42d0abf2344ee296eb9 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Wed, 26 Jun 2024 15:56:46 +0200 Subject: [PATCH 11/15] add suffix `_atomic` to (swap|modify|replace)index! --- base/expr.jl | 16 ++++++++-------- base/genericmemory.jl | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/base/expr.jl b/base/expr.jl index d605043e06cf2..b9c885999f67c 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -1112,7 +1112,7 @@ With assignment (`=`), this operation translates to a `setproperty!(a.b, :x, new or, in case of reference, to a `setindex_atomic!(m, idx, new)` call. With any modifying operator this operation translates to a `modifyproperty!(a.b, :x, op, addend)[2]` or, in case of reference, to a -`modifyindex!(m, idx, op, addend)[2]` call. +`modifyindex_atomic!(m, idx, op, addend)[2]` call. @atomic a.b.x max arg2 @atomic a.b.x + arg2 @@ -1132,7 +1132,7 @@ result into the field or the reference in the first argument, and return the val `(old, new)`. This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` or, -in case of reference to a `modifyindex!(m, idx, func, arg2)` call. +in case of reference to a `modifyindex_atomic!(m, idx, func, arg2)` call. See [Per-field atomics](@ref man-atomics) section in the manual for more details. @@ -1259,7 +1259,7 @@ function make_atomic(order, a1, op, a2) return :(modifyproperty!($a1l, $a1r, $op, $a2, $order)) elseif is_expr(a1, :ref, 2) x, idx, op, a2 = esc(a1.args[1]), esc(a1.args[2]), esc(op), esc(a2) - return :(modifyindex!($x, $idx, $op, $a2, $order)) + return :(modifyindex_atomic!($x, $idx, $op, $a2, $order)) end error("@atomic modify expression missing field access or indexing") end @@ -1275,7 +1275,7 @@ Stores `new` into `a.b.x` (`m[idx]` in case of reference) and returns the old value of `a.b.x` (the old value stored at `m[idx]`, respectively). This operation translates to a `swapproperty!(a.b, :x, new)` or, -in case of reference, `swapindex!(mem, idx, new)` call. +in case of reference, `swapindex_atomic!(mem, idx, new)` call. See [Per-field atomics](@ref man-atomics) section in the manual for more details. @@ -1327,7 +1327,7 @@ function make_atomicswap(order, ex) return :(swapproperty!($ll, $lr, $val, $order)) elseif is_expr(l, :ref, 2) x, idx = esc(l.args[1]), esc(l.args[2]) - return :(swapindex!($x, $idx, $val, $order)) + return :(swapindex_atomic!($x, $idx, $val, $order)) end error("@atomicswap expression missing field access or indexing") end @@ -1346,7 +1346,7 @@ the values `(old, success::Bool)`. Where `success` indicates whether the replacement was completed. This operation translates to a `replaceproperty!(a.b, :x, expected, desired)` or, -in case of reference, to a `replaceindex!(mem, idx, expected, desired)` call. +in case of reference, to a `replaceindex_atomic!(mem, idx, expected, desired)` call. See [Per-field atomics](@ref man-atomics) section in the manual for more details. @@ -1431,10 +1431,10 @@ function make_atomicreplace(success_order, fail_order, ex, old_new) x, idx = esc(ex.args[1]), esc(ex.args[2]) if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>) exp, rep = esc(old_new.args[2]), esc(old_new.args[3]) - return :(replaceindex!($x, $idx, $exp, $rep, $success_order, $fail_order)) + return :(replaceindex_atomic!($x, $idx, $exp, $rep, $success_order, $fail_order)) else old_new = esc(old_new) - return :(replaceindex!($x, $idx, $old_new::Pair..., $success_order, $fail_order)) + return :(replaceindex_atomic!($x, $idx, $old_new::Pair..., $success_order, $fail_order)) end end error("@atomicreplace expression missing field access or indexing") diff --git a/base/genericmemory.jl b/base/genericmemory.jl index fb5ef35040c12..152bd7f5ac554 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -45,7 +45,7 @@ Fetching of any of its individual elements is performed atomically The access to `AtomicMemory` must be done by either using the [`@atomic`](@ref) macro or the lower level interface functions: `Base.getindex_atomic`, `Base.setindex_atomic!`, `Base.setindexonce_atomic!`, - `Base.swapindex!`, `Base.modifyindex!`, and `Base.replaceindex!`. + `Base.swapindex_atomic!`, `Base.modifyindex_atomic!`, and `Base.replaceindex_atomic!`. For details, see [Atomic Operations](@ref man-atomic-operations) as well as macros [`@atomic`](@ref), [`@atomiconce`](@ref), [`@atomicswap`](@ref), and [`@atomicreplace`](@ref). @@ -384,7 +384,7 @@ function setindexonce_atomic!( ) end -function modifyindex!( +function modifyindex_atomic!( mem::GenericMemory, i::Int, op, @@ -395,7 +395,7 @@ function modifyindex!( return Core.memoryrefmodify!(memref, op, val, order, @_boundscheck) end -function swapindex!( +function swapindex_atomic!( mem::GenericMemory, i::Int, val, @@ -411,7 +411,7 @@ function swapindex!( ) end -function replaceindex!( +function replaceindex_atomic!( mem::GenericMemory, i::Int, expected, From c04437e72901c3b825b305a8c6721b54a6eda213 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Wed, 26 Jun 2024 20:31:59 +0200 Subject: [PATCH 12/15] @atomic macros: add support for Vararg indices --- base/expr.jl | 58 ++++++++++++++++++++++++------------------- base/genericmemory.jl | 30 ++++++++++------------ test/atomics.jl | 4 +-- 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/base/expr.jl b/base/expr.jl index b9c885999f67c..ebec0f40ef2dd 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -1109,10 +1109,13 @@ Perform the store operation expressed on the right atomically and return the new value. With assignment (`=`), this operation translates to a `setproperty!(a.b, :x, new)` -or, in case of reference, to a `setindex_atomic!(m, idx, new)` call. +or, in case of reference, to a `setindex_atomic!(m, order, new, idx)` call, +with `order` defaulting to `:sequentially_consistent`. + With any modifying operator this operation translates to a `modifyproperty!(a.b, :x, op, addend)[2]` or, in case of reference, to a -`modifyindex_atomic!(m, idx, op, addend)[2]` call. +`modifyindex_atomic!(m, order, op, addend, idx...)[2]` call, +with `order` defaulting to `:sequentially_consistent`. @atomic a.b.x max arg2 @atomic a.b.x + arg2 @@ -1132,7 +1135,8 @@ result into the field or the reference in the first argument, and return the val `(old, new)`. This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` or, -in case of reference to a `modifyindex_atomic!(m, idx, func, arg2)` call. +in case of reference to a `modifyindex_atomic!(m, order, func, arg2, idx)` call, +with `order` defaulting to `:sequentially_consistent`. See [Per-field atomics](@ref man-atomics) section in the manual for more details. @@ -1221,17 +1225,17 @@ function make_atomic(order, ex) return :(getproperty($l, $r, $order)) elseif isexpr(ex, :call, 3) return make_atomic(order, ex.args[2], ex.args[1], ex.args[3]) - elseif isexpr(ex, :ref, 2) - x, idx = esc(ex.args[1]), esc(ex.args[2]) - return :(getindex_atomic($x, $idx, $order)) + elseif isexpr(ex, :ref) + x, idcs = esc(ex.args[1]), map(esc, ex.args[2:end]) + return :(getindex_atomic($x, $order, $(idcs...))) elseif ex.head === :(=) l, r = ex.args[1], esc(ex.args[2]) if is_expr(l, :., 2) ll, lr = esc(l.args[1]), esc(l.args[2]) return :(setproperty!($ll, $lr, $r, $order)) - elseif is_expr(l, :ref, 2) - x, idx = esc(l.args[1]), esc(l.args[2]) - return :(setindex_atomic!($x, $idx, $r, $order)) + elseif is_expr(l, :ref) + x, idcs = esc(l.args[1]), map(esc, l.args[2:end]) + return :(setindex_atomic!($x, $order, $r, $(idcs...))) end end if length(ex.args) == 2 @@ -1257,9 +1261,9 @@ function make_atomic(order, a1, op, a2) if is_expr(a1, :., 2) a1l, a1r, op, a2 = esc(a1.args[1]), esc(a1.args[2]), esc(op), esc(a2) return :(modifyproperty!($a1l, $a1r, $op, $a2, $order)) - elseif is_expr(a1, :ref, 2) - x, idx, op, a2 = esc(a1.args[1]), esc(a1.args[2]), esc(op), esc(a2) - return :(modifyindex_atomic!($x, $idx, $op, $a2, $order)) + elseif is_expr(a1, :ref) + x, idcs, op, a2 = esc(a1.args[1]), map(esc, a1.args[2:end]), esc(op), esc(a2) + return :(modifyindex_atomic!($x, $order, $op, $a2, $(idcs...))) end error("@atomic modify expression missing field access or indexing") end @@ -1275,7 +1279,8 @@ Stores `new` into `a.b.x` (`m[idx]` in case of reference) and returns the old value of `a.b.x` (the old value stored at `m[idx]`, respectively). This operation translates to a `swapproperty!(a.b, :x, new)` or, -in case of reference, `swapindex_atomic!(mem, idx, new)` call. +in case of reference, `swapindex_atomic!(mem, order, new, idx)` call, +with `order` defaulting to `:sequentially_consistent`. See [Per-field atomics](@ref man-atomics) section in the manual for more details. @@ -1325,9 +1330,9 @@ function make_atomicswap(order, ex) if is_expr(l, :., 2) ll, lr = esc(l.args[1]), esc(l.args[2]) return :(swapproperty!($ll, $lr, $val, $order)) - elseif is_expr(l, :ref, 2) - x, idx = esc(l.args[1]), esc(l.args[2]) - return :(swapindex_atomic!($x, $idx, $val, $order)) + elseif is_expr(l, :ref) + x, idcs = esc(l.args[1]), map(esc, l.args[2:end]) + return :(swapindex_atomic!($x, $order, $val, $(idcs...))) end error("@atomicswap expression missing field access or indexing") end @@ -1346,7 +1351,9 @@ the values `(old, success::Bool)`. Where `success` indicates whether the replacement was completed. This operation translates to a `replaceproperty!(a.b, :x, expected, desired)` or, -in case of reference, to a `replaceindex_atomic!(mem, idx, expected, desired)` call. +in case of reference, to a +`replaceindex_atomic!(mem, success_order, fail_order, expected, desired, idx)` call, +with both orders defaulting to `:sequentially_consistent`. See [Per-field atomics](@ref man-atomics) section in the manual for more details. @@ -1427,14 +1434,14 @@ function make_atomicreplace(success_order, fail_order, ex, old_new) old_new = esc(old_new) return :(replaceproperty!($ll, $lr, $old_new::Pair..., $success_order, $fail_order)) end - elseif is_expr(ex, :ref, 2) - x, idx = esc(ex.args[1]), esc(ex.args[2]) + elseif is_expr(ex, :ref) + x, idcs = esc(ex.args[1]), map(esc, ex.args[2:end]) if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>) exp, rep = esc(old_new.args[2]), esc(old_new.args[3]) - return :(replaceindex_atomic!($x, $idx, $exp, $rep, $success_order, $fail_order)) + return :(replaceindex_atomic!($x, $success_order, $fail_order, $exp, $rep, $(idcs...))) else old_new = esc(old_new) - return :(replaceindex_atomic!($x, $idx, $old_new::Pair..., $success_order, $fail_order)) + return :(replaceindex_atomic!($x, $success_order, $fail_order, $old_new::Pair..., $(idcs...))) end end error("@atomicreplace expression missing field access or indexing") @@ -1452,7 +1459,8 @@ Perform the conditional assignment of value atomically if it was previously unset. Returned value `success::Bool` indicates whether the assignment was completed. This operation translates to a `setpropertyonce!(a.b, :x, value)` or, -in case of reference, to a `setindexonce_atomic!(m, idx, value)` call. +in case of reference, to a `setindexonce_atomic!(m, success_order, fail_order, value, idx)` call, +with both orders defaulting to `:sequentially_consistent`. See [Per-field atomics](@ref man-atomics) section in the manual for more details. @@ -1525,9 +1533,9 @@ function make_atomiconce(success_order, fail_order, ex) if is_expr(l, :., 2) ll, lr = esc(l.args[1]), esc(l.args[2]) return :(setpropertyonce!($ll, $lr, $val, $success_order, $fail_order)) - elseif is_expr(l, :ref, 2) - x, idx = esc(l.args[1]), esc(l.args[2]) - return :(setindexonce_atomic!($x, $idx, $val, $success_order, $fail_order)) + elseif is_expr(l, :ref) + x, idcs = esc(l.args[1]), map(esc, l.args[2:end]) + return :(setindexonce_atomic!($x, $success_order, $fail_order, $val, $(idcs...))) end error("@atomiconce expression missing field access or indexing") end diff --git a/base/genericmemory.jl b/base/genericmemory.jl index 152bd7f5ac554..090407fa74e82 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -341,20 +341,16 @@ end view(m::GenericMemory, inds::Colon) = view(m, eachindex(m)) # modify, swap and replace at index -function getindex_atomic( - mem::GenericMemory, - i::Int, - order=default_access_order(mem) -) +function getindex_atomic(mem::GenericMemory, order::Symbol, i::Int) memref = memoryref(mem, i) return memoryrefget(memref, order, @_boundscheck) end function setindex_atomic!( mem::GenericMemory, - i::Int, + order::Symbol, val, - order=default_access_order(mem) + i::Int, ) T = eltype(mem) memref = memoryref(mem, i) @@ -368,10 +364,10 @@ end function setindexonce_atomic!( mem::GenericMemory, - i::Int, + success_order::Symbol, + fail_order::Symbol, val, - success_order=default_access_order(mem), - fail_order=default_access_order(mem) + i::Int, ) T = eltype(mem) memref = memoryref(mem, i) @@ -386,10 +382,10 @@ end function modifyindex_atomic!( mem::GenericMemory, - i::Int, + order::Symbol, op, val, - order = default_access_order(mem), + i::Int, ) memref = memoryref(mem, i) return Core.memoryrefmodify!(memref, op, val, order, @_boundscheck) @@ -397,9 +393,9 @@ end function swapindex_atomic!( mem::GenericMemory, - i::Int, + order::Symbol, val, - order = default_access_order(mem), + i::Int, ) T = eltype(mem) memref = memoryref(mem, i) @@ -413,11 +409,11 @@ end function replaceindex_atomic!( mem::GenericMemory, - i::Int, + success_order::Symbol, + fail_order::Symbol, expected, desired, - success_order = default_access_order(mem), - fail_order = default_access_order(mem), + i::Int, ) T = eltype(mem) memref = memoryref(mem, i) diff --git a/test/atomics.jl b/test/atomics.jl index d064acc936430..14ef9fc22bd65 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -429,9 +429,9 @@ function _test_atomic_get_set_swap_modify(T, x, y, z) mem = AtomicMemory{T}(undef, 2) @test_throws CanonicalIndexError mem[1] = 3 - @test Base.setindex_atomic!(mem, 1, x) == x + @test Base.setindex_atomic!(mem, Base.default_access_order(mem), x, 1) == x @test mem[1] == x - @test Base.setindex_atomic!(mem, 2, y) == y + @test Base.setindex_atomic!(mem, Base.default_access_order(mem), y, 2) == y @test mem[2] == y @test (@atomic mem[1]) == x From 86b7dab099cda0ea5b2497561fb9308843a943be Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Mon, 1 Jul 2024 00:07:24 +0200 Subject: [PATCH 13/15] generate vararg methods for atomic indexing fns --- base/multidimensional.jl | 34 +++++++++++++++++++++++++++ test/atomics.jl | 50 ++++++++++++++++++++++------------------ 2 files changed, 61 insertions(+), 23 deletions(-) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index ee483afe5b705..b4625c12620e3 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -685,6 +685,40 @@ end # IteratorsMD using .IteratorsMD +# from genericmemory.jl: +## generate vararg methods for atomic indexing +for ex in ( + :(getindex_atomic(mem::GenericMemory, order::Symbol, i::Int)), + :(setindex_atomic!(mem::GenericMemory, order::Symbol, val, i::Int)), + :(setindexonce_atomic!(mem::GenericMemory, success_order::Symbol, fail_order::Symbol, val, i::Int)), + :(modifyindex_atomic!(mem::GenericMemory, order::Symbol, op, val, i::Int)), + :(swapindex_atomic!(mem::GenericMemory, order::Symbol, val, i::Int)), + :(replaceindex_atomic!(mem::GenericMemory, success_order::Symbol, fail_order::Symbol, expected, desired, i::Int,)), +) + fn = ex.args[1] + args = ex.args[2:end-1] + + @eval begin + function $fn($(args...), i::Union{Integer,CartesianIndex}...) + return $fn($(args...), CartesianIndex(to_indices($(args[1]), i))) + end + + function $fn($(args...), i::CartesianIndex) + return $fn($(args...), Tuple(i)...) + end + + function $fn($(args...), i::Integer...) + idcs = to_indices($(args[1]), i) + S = IndexStyle($(args[1])) + if isa(S, IndexLinear) + return $fn($(args...), _to_linear_index($(args[1]), idcs...)) + else + return $fn($(args...), _to_subscript_indices($(args[1]), idcs...)) + end + end + end +end + ## Bounds-checking with CartesianIndex # Disallow linear indexing with CartesianIndex @inline checkbounds(::Type{Bool}, A::AbstractArray, i::CartesianIndex) = diff --git a/test/atomics.jl b/test/atomics.jl index 14ef9fc22bd65..3df9e7d0f63c0 100644 --- a/test/atomics.jl +++ b/test/atomics.jl @@ -434,24 +434,26 @@ function _test_atomic_get_set_swap_modify(T, x, y, z) @test Base.setindex_atomic!(mem, Base.default_access_order(mem), y, 2) == y @test mem[2] == y + idx = UInt32(2) + @test (@atomic mem[1]) == x - @test (@atomic mem[2]) == y + @test (@atomic mem[idx]) == y - (old, new) = (mem[2], z) + (old, new) = (mem[idx], z) # old and new are intentionally of different types to test inner conversion - @test (@atomic mem[2] = new) == new - @test mem[2] == new - @atomic mem[2] = old + @test (@atomic mem[idx] = new) == new + @test mem[idx] == new + @atomic mem[idx] = old - @test (@atomicswap mem[2] = new) == old - @test mem[2] == new - @atomic mem[2] = old + @test (@atomicswap mem[idx] = new) == old + @test mem[idx] == new + @atomic mem[idx] = old try old + new - @test (@atomic mem[2] += new) == old + new - @test mem[2] == old + new - @atomic mem[2] = old + @test (@atomic mem[idx] += new) == old + new + @test mem[idx] == old + new + @atomic mem[idx] = old catch err if !(err isa MethodError) rethrow(err) @@ -474,21 +476,23 @@ function _test_atomic_setonce_replace(T, initial, desired) @test !isassigned(mem, 1) end - expected = @atomic mem[2] - @test (@atomicreplace mem[2] expected => desired) == (old=expected, success=true) - @test mem[2] == desired + idx = UInt(2) + + expected = @atomic mem[idx] + @test (@atomicreplace mem[idx] expected => desired) == (old=expected, success=true) + @test mem[idx] == desired - @atomic mem[2] = expected - @test (@atomicreplace mem[2] desired => desired) == (old=expected, success=false) - @test mem[2] == expected + @atomic mem[idx] = expected + @test (@atomicreplace mem[idx] desired => desired) == (old=expected, success=false) + @test mem[idx] == expected - @atomic mem[2] = expected - @test (@atomicreplace mem[2] Pair(expected, desired)) == (old=expected, success=true) - @test mem[2] == desired + @atomic mem[idx] = expected + @test (@atomicreplace mem[idx] Pair(expected, desired)) == (old=expected, success=true) + @test mem[idx] == desired - @atomic mem[2] = expected - @test (@atomicreplace mem[2] Pair(desired, desired)) == (old=initial, success=false) - @test mem[2] == expected + @atomic mem[idx] = expected + @test (@atomicreplace mem[idx] Pair(desired, desired)) == (old=initial, success=false) + @test mem[idx] == expected end end @testset "@atomic with AtomicMemory" begin From 2138110548b09b9cf60a3c6725e39c6a5ba4b4a7 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Mon, 1 Jul 2024 00:08:31 +0200 Subject: [PATCH 14/15] [whitespace] signatures in one line when possible --- base/genericmemory.jl | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/base/genericmemory.jl b/base/genericmemory.jl index 090407fa74e82..db930d0de23c8 100644 --- a/base/genericmemory.jl +++ b/base/genericmemory.jl @@ -340,18 +340,13 @@ end end view(m::GenericMemory, inds::Colon) = view(m, eachindex(m)) -# modify, swap and replace at index +# get, set(once), modify, swap and replace at index, atomically function getindex_atomic(mem::GenericMemory, order::Symbol, i::Int) memref = memoryref(mem, i) return memoryrefget(memref, order, @_boundscheck) end -function setindex_atomic!( - mem::GenericMemory, - order::Symbol, - val, - i::Int, -) +function setindex_atomic!(mem::GenericMemory, order::Symbol, val, i::Int) T = eltype(mem) memref = memoryref(mem, i) return memoryrefset!( @@ -380,23 +375,12 @@ function setindexonce_atomic!( ) end -function modifyindex_atomic!( - mem::GenericMemory, - order::Symbol, - op, - val, - i::Int, -) +function modifyindex_atomic!(mem::GenericMemory, order::Symbol, op, val, i::Int) memref = memoryref(mem, i) return Core.memoryrefmodify!(memref, op, val, order, @_boundscheck) end -function swapindex_atomic!( - mem::GenericMemory, - order::Symbol, - val, - i::Int, -) +function swapindex_atomic!(mem::GenericMemory, order::Symbol, val, i::Int) T = eltype(mem) memref = memoryref(mem, i) return Core.memoryrefswap!( From c8bc0302c871a75b6ae3abd1c549429b6f0bb918 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Tue, 2 Jul 2024 17:20:14 +0200 Subject: [PATCH 15/15] fix doctests --- base/expr.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/expr.jl b/base/expr.jl index 125ec1371765c..a58ae62c21a5f 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -1192,7 +1192,7 @@ julia> @atomic max(mem[1], 10) # change the first value of mem to the max value, 4 => 10 julia> @atomic mem[1] max 5 # again change the first value of mem to the max value, with sequential consistency -10 => 5 +10 => 10 ``` !!! compat "Julia 1.7"