Skip to content

Commit

Permalink
Merge branch 'master' into eph/limit-repl
Browse files Browse the repository at this point in the history
  • Loading branch information
ericphanson authored Jul 8, 2024
2 parents 03f5ba9 + 594544d commit 0bb1298
Show file tree
Hide file tree
Showing 16 changed files with 728 additions and 82 deletions.
15 changes: 14 additions & 1 deletion 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 Expand Up @@ -120,7 +129,11 @@ Standard library changes
complete names that have been explicitly `using`-ed. ([#54610])
- REPL completions can now complete input lines like `[import|using] Mod: xxx|` e.g.
complete `using Base.Experimental: @op` to `using Base.Experimental: @opaque`. ([#54719])
- When an object is printed automatically (by being returned in the REPL), its display is now truncated after printing 20 KiB. This does not affect manual calls to `show`, `print`, and so forth. ([#53959])
- the REPL will now warn if it detects a name is being accessed from a module which does not define it (nor has a submodule which defines it),
and for which the name is not public in that module. For example, `map` is defined in Base, and executing `LinearAlgebra.map`
in the REPL will now issue a warning the first time occurs. ([#54872])
- When an object is printed automatically (by being returned in the REPL), its display is now truncated after printing 20 KiB.
This does not affect manual calls to `show`, `print`, and so forth. ([#53959])

#### SuiteSparse

Expand Down
230 changes: 195 additions & 35 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1101,26 +1101,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 @@ -1153,8 +1170,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 => 10
```
!!! 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 @@ -1181,11 +1226,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 @@ -1208,19 +1259,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 @@ -1238,8 +1299,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 @@ -1252,22 +1328,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 @@ -1284,7 +1371,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 @@ -1296,8 +1383,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 @@ -1313,27 +1426,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 @@ -1353,12 +1481,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 @@ -1376,7 +1531,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

0 comments on commit 0bb1298

Please sign in to comment.