Skip to content

Commit

Permalink
make atomic_swap a separate macro
Browse files Browse the repository at this point in the history
  • Loading branch information
vtjnash committed May 27, 2021
1 parent c85f660 commit 51936d3
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 31 deletions.
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,7 @@ export
@assert,
@atomic,
@atomic!,
@atomic_swap!,
@atomic_replace!,
@__dot__,
@enum,
Expand Down
75 changes: 48 additions & 27 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -525,14 +525,10 @@ end
@atomic! :acquire_release a.b.x + new()
@atomic! :acquire_release a.b.x max new()
@atomic! a.b.x = new()
@atomic! :acquire_release a.b.x = new()
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)`.
Perform the operation expressed on the right atomically, for the supported
expressions shown, returning the values `(old, new)`.
The first set of operations translates to a `modifyproperty!` call.
The second set of operations translates to a `swapproperty!` call.
This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` call.
See [atomics](#man-atomics) in the manual for more details.
Expand All @@ -542,17 +538,14 @@ julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomic! :sequentially_consistent a.x = 2 # swap field x of a, with sequential consistency
julia> @atomic! a.x + 1 # increment field x of a, with sequential consistency
(1, 2)
julia> @atomic a.x # fetch field x of a, with sequential consistency
2
julia> @atomic! a.x + 1 # increment field x of a, with sequential consistency
(2, 3)
julia> @atomic! max(a.x, 10) # change field x of a to the max value, with sequential consistency
(3, 10)
(2, 10)
julia> @atomic! a.x max 5 # again change field x of a to the max value, with sequential consistency
(10, 10)
Expand All @@ -574,27 +567,55 @@ macro atomic!(ex)
end
function make_atomic!(order, ex)
@nospecialize
if ex isa Expr
if ex.head === :call
length(ex.args) == 3 || error("@atomic modify expression has the wrong number of function arguments")
return make_atomic!(order, ex.args[2], ex.args[1], ex.args[3])
elseif ex.head === :(=)
l, r = ex.args[1], ex.args[2]
is_expr(l, :., 2) || error("@atomic swap expression missing field access")
ll, lr = esc(l.args[1]), esc(l.args[2])
val = esc(r)
return :(local val = $val; (swapproperty!($ll, $lr, val, $order), val))
end
end
error("could not parse @atomic expression $ex")
isexpr(ex, :call, 3) || error("could not parse @atomic! modify expression $ex")
return make_atomic!(order, ex.args[2], ex.args[1], ex.args[3])
end
function make_atomic!(order, a1, op, a2)
@nospecialize
is_expr(a1, :., 2) || error("@atomic modify expression missing field access")
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))
end


"""
@atomic_swap! a.b.x new
@atomic_swap! :sequentially_consistent a.b.x new
Stores `new` into `a.b.x` and returns the old value of `a.b.x`.
This operation translates to a `swapproperty!(a.b, :x, new)` call.
See [atomics](#man-atomics) in the manual for more details.
```jldoctest
julia> mutable struct Atomic{T}; @atomic x::T; end
julia> a = Atomic(1)
Atomic{Int64}(1)
julia> @atomic_swap! a.x 2+2 # replace field x of a with 4, with sequential consistency
1
julia> @atomic a.x # fetch field x of a, with sequential consistency
4
```
"""
macro atomic_swap!(order, ex, val)
order isa QuoteNode || (order = esc(order))
return make_atomic_swap!(order, ex, val)
end
macro atomic_swap!(ex, val)
return make_atomic_swap!(QuoteNode(:sequentially_consistent), ex, val)
end
function make_atomic_swap!(order, ex, val)
@nospecialize
is_expr(ex, :., 2) || error("@atomic_swap! expression missing field access")
l, r, val = esc(ex.args[1]), esc(ex.args[2]), esc(val)
return :(swapproperty!($l, $r, $val, $order))
end


"""
@atomic_replace! a.b.x expected => desired
@atomic_replace! :sequentially_consistent a.b.x expected => desired
Expand Down Expand Up @@ -646,7 +667,7 @@ macro atomic_replace!(ex, old_new)
end
function make_atomic_replace!(success_order, fail_order, ex, old_new)
@nospecialize
is_expr(ex, :., 2) || error("@atomic replace expression missing field access")
is_expr(ex, :., 2) || error("@atomic_replace! 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])
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/multi-threading.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ See also [Synchronization](@ref lib-task-sync).
```@docs
Base.@atomic
Base.@atomic!
Base.@atomic_swap!
Base.@atomic_replace!
```

Expand Down
7 changes: 3 additions & 4 deletions test/atomics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,9 @@ let a = ARefxy(1, -1)
@test_throws ConcurrencyViolationError @atomic! :not_atomic a.x max 30

@test 20 === @atomic a.x
@test (20, 1) === @atomic! a.x = 1
@test (1, 2) === @atomic! :monotonic a.x = 2
@test_throws ConcurrencyViolationError @atomic! :not_atomic a.x = 1

@test 20 === @atomic_swap! a.x 1
@test 1 === @atomic_swap! :monotonic a.x 2
@test_throws ConcurrencyViolationError @atomic_swap! :not_atomic a.x 1

@test 2 === @atomic a.x
@test (2, true) === @atomic_replace! a.x 2 => 1
Expand Down

0 comments on commit 51936d3

Please sign in to comment.