Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for indexing in @atomic macro #54707

Merged
merged 17 commits into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------
Expand Down
230 changes: 195 additions & 35 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1100,26 +1100,43 @@ If no `order` is specified it defaults to :sequentially_consistent.
@atomic a.b.x += addend
@atomic :release a.b.x = new
@atomic :acquire_release a.b.x += addend
@atomic m[idx] = new
@atomic m[idx] += addend
@atomic :release m[idx] = new
@atomic :acquire_release m[idx] += addend

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, 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, order, op, addend, idx...)[2]` call,
with `order` defaulting to `:sequentially_consistent`.

@atomic a.b.x max arg2
@atomic a.b.x + arg2
@atomic max(a.b.x, arg2)
@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_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.

Expand Down Expand Up @@ -1152,8 +1169,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, :(::))
Expand All @@ -1180,11 +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)
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)
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
Expand All @@ -1207,19 +1258,29 @@ 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)
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


"""
@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_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.

Expand All @@ -1237,8 +1298,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))
Expand All @@ -1251,22 +1327,33 @@ 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)
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


"""
@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_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.

Expand All @@ -1283,7 +1370,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
Expand All @@ -1295,8 +1382,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))
Expand All @@ -1312,27 +1425,42 @@ 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)
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, $success_order, $fail_order, $exp, $rep, $(idcs...)))
else
old_new = esc(old_new)
return :(replaceindex_atomic!($x, $success_order, $fail_order, $old_new::Pair..., $(idcs...)))
end
end
error("@atomicreplace expression missing field access or indexing")
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, 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.

Expand All @@ -1352,12 +1480,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))
Expand All @@ -1375,7 +1530,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)
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
Loading
Loading