Skip to content

Commit 23dabef

Browse files
add support for indexing in @atomic macro (#54707)
Following the discussion in #54642 Implemented: - [x] `modifyindex_atomic!`, `swapindex_atomic!`, `replaceindex_atomic!` for `GenericMemory` - [x] `getindex_atomic`, `setindex_atomic!`, `setindexonce_atomic!` for `GenericMemory` - [x] add support for references in `@atomic` macros - [x] add support for vararg indices in `@atomic` macros - [x] tests - [x] update docstrings with example usage - ~[ ] update Atomics section of the manual (?)~ - [x] news @oscardssmith @vtjnash # New `@atomic` transformations implemented here: ```julia julia> @macroexpand (@atomic a[i1,i2]) :(Base.getindex_atomic(a, :sequentially_consistent, i1, i2)) julia> @macroexpand (@atomic order a[i1,i2]) :(Base.getindex_atomic(a, order, i1, i2)) julia> @macroexpand (@atomic a[i1,i2] = 2.0) :(Base.setindex_atomic!(a, :sequentially_consistent, 2.0, i1, i2)) julia> @macroexpand (@atomic order a[i1,i2] = 2.0) :(Base.setindex_atomic!(a, order, 2.0, i1, i2)) julia> @macroexpand (@AtomicSwap a[i1,i2] = 2.0) :(Base.swapindex_atomic!(a, :sequentially_consistent, 2.0, i1, i2)) julia> @macroexpand (@AtomicSwap order a[i1,i2] = 2.0) :(Base.swapindex_atomic!(a, order, 2.0, i1, i2)) julia> @macroexpand (@atomic a[i1,i2] += 2.0) :((Base.modifyindex_atomic!(a, :sequentially_consistent, +, 2.0, i1, i2))[2]) julia> @macroexpand (@atomic order a[i1,i2] += 2.0) :((Base.modifyindex_atomic!(a, order, +, 2.0, i1, i2))[2]) julia> @macroexpand (@atomiconce a[i1,i2] = 2.0) :(Base.setindexonce_atomic!(a, :sequentially_consistent, :sequentially_consistent, 2.0, i1, i2)) julia> @macroexpand (@atomiconce o1 o2 a[i1,i2] = 2.0) :(Base.setindexonce_atomic!(a, o1, o2, 2.0, i1, i2)) julia> @macroexpand (@atomicreplace a[i1,i2] (2.0=>3.0)) :(Base.replaceindex_atomic!(a, :sequentially_consistent, :sequentially_consistent, 2.0, 3.0, i1, i2)) julia> @macroexpand (@atomicreplace o1 o2 a[i1,i2] (2.0=>3.0)) :(Base.replaceindex_atomic!(a, o1, o2, 2.0, 3.0, i1, i2)) ``` --------- Co-authored-by: Oscar Smith <[email protected]>
1 parent 0ef2bb6 commit 23dabef

File tree

5 files changed

+408
-43
lines changed

5 files changed

+408
-43
lines changed

NEWS.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ New language features
66

77
- A new keyword argument `usings::Bool` has been added to `names`. By using this, we can now
88
find all the names available in module `A` by `names(A; all=true, imported=true, usings=true)`. ([#54609])
9+
- the `@atomic(...)` macro family supports now the reference assignment syntax, e.g.
10+
`@atomic :monotonic v[3] += 4` modifies `v[3]` atomically with monotonic ordering semantics. ([#54707])
11+
The supported syntax allows
12+
- atomic fetch (`x = @atomic v[3]`),
13+
- atomic set (`@atomic v[3] = 4`),
14+
- atomic modify (`@atomic v[3] += 2`),
15+
- atomic set once (`@atomiconce v[3] = 2`),
16+
- atomic swap (`x = @atomicswap v[3] = 2`), and
17+
- atomic replace (`x = @atomicreplace v[3] 2=>5`).
918

1019
Language changes
1120
----------------

base/expr.jl

Lines changed: 195 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,26 +1101,43 @@ If no `order` is specified it defaults to :sequentially_consistent.
11011101
@atomic a.b.x += addend
11021102
@atomic :release a.b.x = new
11031103
@atomic :acquire_release a.b.x += addend
1104+
@atomic m[idx] = new
1105+
@atomic m[idx] += addend
1106+
@atomic :release m[idx] = new
1107+
@atomic :acquire_release m[idx] += addend
11041108
11051109
Perform the store operation expressed on the right atomically and return the
11061110
new value.
11071111
1108-
With `=`, this operation translates to a `setproperty!(a.b, :x, new)` call.
1109-
With any operator also, this operation translates to a `modifyproperty!(a.b,
1110-
:x, +, addend)[2]` call.
1112+
With assignment (`=`), this operation translates to a `setproperty!(a.b, :x, new)`
1113+
or, in case of reference, to a `setindex_atomic!(m, order, new, idx)` call,
1114+
with `order` defaulting to `:sequentially_consistent`.
1115+
1116+
With any modifying operator this operation translates to a
1117+
`modifyproperty!(a.b, :x, op, addend)[2]` or, in case of reference, to a
1118+
`modifyindex_atomic!(m, order, op, addend, idx...)[2]` call,
1119+
with `order` defaulting to `:sequentially_consistent`.
11111120
11121121
@atomic a.b.x max arg2
11131122
@atomic a.b.x + arg2
11141123
@atomic max(a.b.x, arg2)
11151124
@atomic :acquire_release max(a.b.x, arg2)
11161125
@atomic :acquire_release a.b.x + arg2
11171126
@atomic :acquire_release a.b.x max arg2
1127+
@atomic m[idx] max arg2
1128+
@atomic m[idx] + arg2
1129+
@atomic max(m[idx], arg2)
1130+
@atomic :acquire_release max(m[idx], arg2)
1131+
@atomic :acquire_release m[idx] + arg2
1132+
@atomic :acquire_release m[idx] max arg2
11181133
11191134
Perform the binary operation expressed on the right atomically. Store the
1120-
result into the field in the first argument and return the values `(old, new)`.
1121-
1122-
This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` call.
1135+
result into the field or the reference in the first argument, and return the values
1136+
`(old, new)`.
11231137
1138+
This operation translates to a `modifyproperty!(a.b, :x, func, arg2)` or,
1139+
in case of reference to a `modifyindex_atomic!(m, order, func, arg2, idx)` call,
1140+
with `order` defaulting to `:sequentially_consistent`.
11241141
11251142
See [Per-field atomics](@ref man-atomics) section in the manual for more details.
11261143
@@ -1153,8 +1170,36 @@ julia> @atomic a.x max 5 # again change field x of a to the max value, with sequ
11531170
10 => 10
11541171
```
11551172
1173+
```jldoctest
1174+
julia> mem = AtomicMemory{Int}(undef, 2);
1175+
1176+
julia> @atomic mem[1] = 2 # set mem[1] to value 2 with sequential consistency
1177+
2
1178+
1179+
julia> @atomic :monotonic mem[1] # fetch the first value of mem, with monotonic consistency
1180+
2
1181+
1182+
julia> @atomic mem[1] += 1 # increment the first value of mem, with sequential consistency
1183+
3
1184+
1185+
julia> @atomic mem[1] + 1 # increment the first value of mem, with sequential consistency
1186+
3 => 4
1187+
1188+
julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency
1189+
4
1190+
1191+
julia> @atomic max(mem[1], 10) # change the first value of mem to the max value, with sequential consistency
1192+
4 => 10
1193+
1194+
julia> @atomic mem[1] max 5 # again change the first value of mem to the max value, with sequential consistency
1195+
10 => 10
1196+
```
1197+
11561198
!!! compat "Julia 1.7"
1157-
This functionality requires at least Julia 1.7.
1199+
Atomic fields functionality requires at least Julia 1.7.
1200+
1201+
!!! compat "Julia 1.12"
1202+
Atomic reference functionality requires at least Julia 1.12.
11581203
"""
11591204
macro atomic(ex)
11601205
if !isa(ex, Symbol) && !is_expr(ex, :(::))
@@ -1181,11 +1226,17 @@ function make_atomic(order, ex)
11811226
return :(getproperty($l, $r, $order))
11821227
elseif isexpr(ex, :call, 3)
11831228
return make_atomic(order, ex.args[2], ex.args[1], ex.args[3])
1229+
elseif isexpr(ex, :ref)
1230+
x, idcs = esc(ex.args[1]), map(esc, ex.args[2:end])
1231+
return :(getindex_atomic($x, $order, $(idcs...)))
11841232
elseif ex.head === :(=)
11851233
l, r = ex.args[1], esc(ex.args[2])
11861234
if is_expr(l, :., 2)
11871235
ll, lr = esc(l.args[1]), esc(l.args[2])
11881236
return :(setproperty!($ll, $lr, $r, $order))
1237+
elseif is_expr(l, :ref)
1238+
x, idcs = esc(l.args[1]), map(esc, l.args[2:end])
1239+
return :(setindex_atomic!($x, $order, $r, $(idcs...)))
11891240
end
11901241
end
11911242
if length(ex.args) == 2
@@ -1208,19 +1259,29 @@ function make_atomic(order, ex)
12081259
end
12091260
function make_atomic(order, a1, op, a2)
12101261
@nospecialize
1211-
is_expr(a1, :., 2) || error("@atomic modify expression missing field access")
1212-
a1l, a1r, op, a2 = esc(a1.args[1]), esc(a1.args[2]), esc(op), esc(a2)
1213-
return :(modifyproperty!($a1l, $a1r, $op, $a2, $order))
1262+
if is_expr(a1, :., 2)
1263+
a1l, a1r, op, a2 = esc(a1.args[1]), esc(a1.args[2]), esc(op), esc(a2)
1264+
return :(modifyproperty!($a1l, $a1r, $op, $a2, $order))
1265+
elseif is_expr(a1, :ref)
1266+
x, idcs, op, a2 = esc(a1.args[1]), map(esc, a1.args[2:end]), esc(op), esc(a2)
1267+
return :(modifyindex_atomic!($x, $order, $op, $a2, $(idcs...)))
1268+
end
1269+
error("@atomic modify expression missing field access or indexing")
12141270
end
12151271

12161272

12171273
"""
12181274
@atomicswap a.b.x = new
12191275
@atomicswap :sequentially_consistent a.b.x = new
1276+
@atomicswap m[idx] = new
1277+
@atomicswap :sequentially_consistent m[idx] = new
12201278
1221-
Stores `new` into `a.b.x` and returns the old value of `a.b.x`.
1279+
Stores `new` into `a.b.x` (`m[idx]` in case of reference) and returns the old
1280+
value of `a.b.x` (the old value stored at `m[idx]`, respectively).
12221281
1223-
This operation translates to a `swapproperty!(a.b, :x, new)` call.
1282+
This operation translates to a `swapproperty!(a.b, :x, new)` or,
1283+
in case of reference, `swapindex_atomic!(mem, order, new, idx)` call,
1284+
with `order` defaulting to `:sequentially_consistent`.
12241285
12251286
See [Per-field atomics](@ref man-atomics) section in the manual for more details.
12261287
@@ -1238,8 +1299,23 @@ julia> @atomic a.x # fetch field x of a, with sequential consistency
12381299
4
12391300
```
12401301
1302+
```jldoctest
1303+
julia> mem = AtomicMemory{Int}(undef, 2);
1304+
1305+
julia> @atomic mem[1] = 1;
1306+
1307+
julia> @atomicswap mem[1] = 4 # replace the first value of `mem` with 4, with sequential consistency
1308+
1
1309+
1310+
julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency
1311+
4
1312+
```
1313+
12411314
!!! compat "Julia 1.7"
1242-
This functionality requires at least Julia 1.7.
1315+
Atomic fields functionality requires at least Julia 1.7.
1316+
1317+
!!! compat "Julia 1.12"
1318+
Atomic reference functionality requires at least Julia 1.12.
12431319
"""
12441320
macro atomicswap(order, ex)
12451321
order isa QuoteNode || (order = esc(order))
@@ -1252,22 +1328,33 @@ function make_atomicswap(order, ex)
12521328
@nospecialize
12531329
is_expr(ex, :(=), 2) || error("@atomicswap expression missing assignment")
12541330
l, val = ex.args[1], esc(ex.args[2])
1255-
is_expr(l, :., 2) || error("@atomicswap expression missing field access")
1256-
ll, lr = esc(l.args[1]), esc(l.args[2])
1257-
return :(swapproperty!($ll, $lr, $val, $order))
1331+
if is_expr(l, :., 2)
1332+
ll, lr = esc(l.args[1]), esc(l.args[2])
1333+
return :(swapproperty!($ll, $lr, $val, $order))
1334+
elseif is_expr(l, :ref)
1335+
x, idcs = esc(l.args[1]), map(esc, l.args[2:end])
1336+
return :(swapindex_atomic!($x, $order, $val, $(idcs...)))
1337+
end
1338+
error("@atomicswap expression missing field access or indexing")
12581339
end
12591340

12601341

12611342
"""
12621343
@atomicreplace a.b.x expected => desired
12631344
@atomicreplace :sequentially_consistent a.b.x expected => desired
12641345
@atomicreplace :sequentially_consistent :monotonic a.b.x expected => desired
1346+
@atomicreplace m[idx] expected => desired
1347+
@atomicreplace :sequentially_consistent m[idx] expected => desired
1348+
@atomicreplace :sequentially_consistent :monotonic m[idx] expected => desired
12651349
12661350
Perform the conditional replacement expressed by the pair atomically, returning
12671351
the values `(old, success::Bool)`. Where `success` indicates whether the
12681352
replacement was completed.
12691353
1270-
This operation translates to a `replaceproperty!(a.b, :x, expected, desired)` call.
1354+
This operation translates to a `replaceproperty!(a.b, :x, expected, desired)` or,
1355+
in case of reference, to a
1356+
`replaceindex_atomic!(mem, success_order, fail_order, expected, desired, idx)` call,
1357+
with both orders defaulting to `:sequentially_consistent`.
12711358
12721359
See [Per-field atomics](@ref man-atomics) section in the manual for more details.
12731360
@@ -1284,7 +1371,7 @@ julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with
12841371
julia> @atomic a.x # fetch field x of a, with sequential consistency
12851372
2
12861373
1287-
julia> @atomicreplace a.x 1 => 2 # replace field x of a with 2 if it was 1, with sequential consistency
1374+
julia> @atomicreplace a.x 1 => 3 # replace field x of a with 2 if it was 1, with sequential consistency
12881375
(old = 2, success = false)
12891376
12901377
julia> xchg = 2 => 0; # replace field x of a with 0 if it was 2, with sequential consistency
@@ -1296,8 +1383,34 @@ julia> @atomic a.x # fetch field x of a, with sequential consistency
12961383
0
12971384
```
12981385
1386+
```jldoctest
1387+
julia> mem = AtomicMemory{Int}(undef, 2);
1388+
1389+
julia> @atomic mem[1] = 1;
1390+
1391+
julia> @atomicreplace mem[1] 1 => 2 # replace the first value of mem with 2 if it was 1, with sequential consistency
1392+
(old = 1, success = true)
1393+
1394+
julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency
1395+
2
1396+
1397+
julia> @atomicreplace mem[1] 1 => 3 # replace field x of a with 2 if it was 1, with sequential consistency
1398+
(old = 2, success = false)
1399+
1400+
julia> xchg = 2 => 0; # replace field x of a with 0 if it was 2, with sequential consistency
1401+
1402+
julia> @atomicreplace mem[1] xchg
1403+
(old = 2, success = true)
1404+
1405+
julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency
1406+
0
1407+
```
1408+
12991409
!!! compat "Julia 1.7"
1300-
This functionality requires at least Julia 1.7.
1410+
Atomic fields functionality requires at least Julia 1.7.
1411+
1412+
!!! compat "Julia 1.12"
1413+
Atomic reference functionality requires at least Julia 1.12.
13011414
"""
13021415
macro atomicreplace(success_order, fail_order, ex, old_new)
13031416
fail_order isa QuoteNode || (fail_order = esc(fail_order))
@@ -1313,27 +1426,42 @@ macro atomicreplace(ex, old_new)
13131426
end
13141427
function make_atomicreplace(success_order, fail_order, ex, old_new)
13151428
@nospecialize
1316-
is_expr(ex, :., 2) || error("@atomicreplace expression missing field access")
1317-
ll, lr = esc(ex.args[1]), esc(ex.args[2])
1318-
if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>)
1319-
exp, rep = esc(old_new.args[2]), esc(old_new.args[3])
1320-
return :(replaceproperty!($ll, $lr, $exp, $rep, $success_order, $fail_order))
1321-
else
1322-
old_new = esc(old_new)
1323-
return :(replaceproperty!($ll, $lr, $old_new::Pair..., $success_order, $fail_order))
1429+
if is_expr(ex, :., 2)
1430+
ll, lr = esc(ex.args[1]), esc(ex.args[2])
1431+
if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>)
1432+
exp, rep = esc(old_new.args[2]), esc(old_new.args[3])
1433+
return :(replaceproperty!($ll, $lr, $exp, $rep, $success_order, $fail_order))
1434+
else
1435+
old_new = esc(old_new)
1436+
return :(replaceproperty!($ll, $lr, $old_new::Pair..., $success_order, $fail_order))
1437+
end
1438+
elseif is_expr(ex, :ref)
1439+
x, idcs = esc(ex.args[1]), map(esc, ex.args[2:end])
1440+
if is_expr(old_new, :call, 3) && old_new.args[1] === :(=>)
1441+
exp, rep = esc(old_new.args[2]), esc(old_new.args[3])
1442+
return :(replaceindex_atomic!($x, $success_order, $fail_order, $exp, $rep, $(idcs...)))
1443+
else
1444+
old_new = esc(old_new)
1445+
return :(replaceindex_atomic!($x, $success_order, $fail_order, $old_new::Pair..., $(idcs...)))
1446+
end
13241447
end
1448+
error("@atomicreplace expression missing field access or indexing")
13251449
end
13261450

13271451
"""
13281452
@atomiconce a.b.x = value
13291453
@atomiconce :sequentially_consistent a.b.x = value
13301454
@atomiconce :sequentially_consistent :monotonic a.b.x = value
1455+
@atomiconce m[idx] = value
1456+
@atomiconce :sequentially_consistent m[idx] = value
1457+
@atomiconce :sequentially_consistent :monotonic m[idx] = value
13311458
13321459
Perform the conditional assignment of value atomically if it was previously
1333-
unset, returning the value `success::Bool`. Where `success` indicates whether
1334-
the assignment was completed.
1460+
unset. Returned value `success::Bool` indicates whether the assignment was completed.
13351461
1336-
This operation translates to a `setpropertyonce!(a.b, :x, value)` call.
1462+
This operation translates to a `setpropertyonce!(a.b, :x, value)` or,
1463+
in case of reference, to a `setindexonce_atomic!(m, success_order, fail_order, value, idx)` call,
1464+
with both orders defaulting to `:sequentially_consistent`.
13371465
13381466
See [Per-field atomics](@ref man-atomics) section in the manual for more details.
13391467
@@ -1353,12 +1481,39 @@ true
13531481
julia> @atomic a.x # fetch field x of a, with sequential consistency
13541482
1
13551483
1356-
julia> @atomiconce a.x = 1 # set field x of a to 1, if unset, with sequential consistency
1484+
julia> @atomiconce :monotonic a.x = 2 # set field x of a to 1, if unset, with monotonic consistence
13571485
false
13581486
```
13591487
1488+
```jldoctest
1489+
julia> mem = AtomicMemory{Vector{Int}}(undef, 1);
1490+
1491+
julia> isassigned(mem, 1)
1492+
false
1493+
1494+
julia> @atomiconce mem[1] = [1] # set the first value of mem to [1], if unset, with sequential consistency
1495+
true
1496+
1497+
julia> isassigned(mem, 1)
1498+
true
1499+
1500+
julia> @atomic mem[1] # fetch the first value of mem, with sequential consistency
1501+
1-element Vector{Int64}:
1502+
1
1503+
1504+
julia> @atomiconce :monotonic mem[1] = [2] # set the first value of mem to [2], if unset, with monotonic
1505+
false
1506+
1507+
julia> @atomic mem[1]
1508+
1-element Vector{Int64}:
1509+
1
1510+
```
1511+
13601512
!!! compat "Julia 1.11"
1361-
This functionality requires at least Julia 1.11.
1513+
Atomic fields functionality requires at least Julia 1.11.
1514+
1515+
!!! compat "Julia 1.12"
1516+
Atomic reference functionality requires at least Julia 1.12.
13621517
"""
13631518
macro atomiconce(success_order, fail_order, ex)
13641519
fail_order isa QuoteNode || (fail_order = esc(fail_order))
@@ -1376,7 +1531,12 @@ function make_atomiconce(success_order, fail_order, ex)
13761531
@nospecialize
13771532
is_expr(ex, :(=), 2) || error("@atomiconce expression missing assignment")
13781533
l, val = ex.args[1], esc(ex.args[2])
1379-
is_expr(l, :., 2) || error("@atomiconce expression missing field access")
1380-
ll, lr = esc(l.args[1]), esc(l.args[2])
1381-
return :(setpropertyonce!($ll, $lr, $val, $success_order, $fail_order))
1534+
if is_expr(l, :., 2)
1535+
ll, lr = esc(l.args[1]), esc(l.args[2])
1536+
return :(setpropertyonce!($ll, $lr, $val, $success_order, $fail_order))
1537+
elseif is_expr(l, :ref)
1538+
x, idcs = esc(l.args[1]), map(esc, l.args[2:end])
1539+
return :(setindexonce_atomic!($x, $success_order, $fail_order, $val, $(idcs...)))
1540+
end
1541+
error("@atomiconce expression missing field access or indexing")
13821542
end

0 commit comments

Comments
 (0)