Skip to content

Commit 98bb4ab

Browse files
authored
implement syncscopes everywhere (#20)
1 parent b099010 commit 98bb4ab

File tree

10 files changed

+297
-352
lines changed

10 files changed

+297
-352
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ LLVM = "929cbde3-209d-540e-8aea-75f648917ca0"
1010
UnsafeAtomicsLLVM = ["LLVM"]
1111

1212
[compat]
13-
LLVM = "8.1, 9"
13+
LLVM = "9"
1414
julia = "1.10"
1515

1616
[extras]

ext/UnsafeAtomicsLLVM/atomics.jl

Lines changed: 29 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -89,68 +89,12 @@ for (julia, llvm) in pairs(_llvm_from_julia_ordering)
8989
@eval llvm_from_julia_ordering(::Val{$(QuoteNode(julia))}) = Val{$llvm}()
9090
end
9191

92-
"""
93-
@dynamic_order(order) do order
94-
... use order ...
95-
end
96-
It is expanded to an expression similar to:
97-
if order === :not_atomic
98-
let order = Val(:not_atomic)
99-
... use order ...
100-
end
101-
elseif order === :unordered
102-
let order = Val(:unordered)
103-
... use order ...
104-
end
105-
elseif ...
106-
...
107-
else
108-
throw(ConcurrencyViolationError(...))
109-
end
110-
This is used for helping the compiler to optimize expressions such as
111-
`atomic_pointerref(ptr, :monotonic)` and also to avoid abstract run-time dispatch.
112-
"""
113-
macro dynamic_order(thunk, order)
114-
@assert Meta.isexpr(thunk, :->, 2) && Meta.isexpr(thunk.args[1], :tuple, 1)
115-
ordervar = esc(thunk.args[1].args[1])
116-
body = esc(thunk.args[2])
117-
expr = foldr(
118-
keys(_llvm_from_julia_ordering),
119-
init = :(throw(ConcurrencyViolationError("invalid atomic ordering: ", order))),
120-
) do key, r
121-
quote
122-
if order === $(QuoteNode(key))
123-
let $ordervar = Val{$(QuoteNode(key))}()
124-
$body
125-
end
126-
else
127-
$r
128-
end
129-
end
130-
end
131-
quote
132-
order = $(esc(order))
133-
$expr
134-
end
135-
end
136-
13792
_valueof(::Val{x}) where {x} = x
13893

139-
@inline function atomic_pointerref(pointer, order::Symbol)
140-
@dynamic_order(order) do order
141-
atomic_pointerref(pointer, order)
142-
end
143-
end
144-
145-
@inline function atomic_pointerset(pointer, x, order::Symbol)
146-
@dynamic_order(order) do order
147-
atomic_pointerset(pointer, x, order)
148-
end
149-
end
150-
151-
@generated function atomic_pointerref(ptr::LLVMPtr{T,A}, order::AllOrdering) where {T,A}
94+
@generated function atomic_pointerref(ptr::LLVMPtr{T,A}, order::AllOrdering, sync) where {T,A}
15295
sizeof(T) == 0 && return T.instance
153-
llvm_order = _valueof(llvm_from_julia_ordering(order()))
96+
llvm_order = _valueof(llvm_from_julia_ordering(order()))
97+
llvm_syncscope = _valueof(sync())
15498
@dispose ctx = Context() begin
15599
eltyp = convert(LLVMType, T)
156100

@@ -170,6 +114,7 @@ end
170114
typed_ptr = bitcast!(builder, parameters(llvm_f)[1], T_typed_ptr)
171115
ld = load!(builder, eltyp, typed_ptr)
172116
ordering!(ld, llvm_order)
117+
syncscope!(ld, SyncScope(string(llvm_syncscope)))
173118

174119
if A != 0
175120
metadata(ld)[LLVM.MD_tbaa] = tbaa_addrspace(A)
@@ -187,6 +132,7 @@ end
187132
ptr::LLVMPtr{T,A},
188133
x::T,
189134
order::AllOrdering,
135+
sync,
190136
) where {T,A}
191137
if sizeof(T) == 0
192138
# Mimicking what `Core.Intrinsics.atomic_pointerset` generates.
@@ -197,7 +143,8 @@ end
197143
return ptr
198144
end
199145
end
200-
llvm_order = _valueof(llvm_from_julia_ordering(order()))
146+
llvm_order = _valueof(llvm_from_julia_ordering(order()))
147+
llvm_syncscope = _valueof(sync())
201148
@dispose ctx = Context() begin
202149
eltyp = convert(LLVMType, T)
203150
T_ptr = convert(LLVMType, ptr)
@@ -216,6 +163,7 @@ end
216163
val = parameters(llvm_f)[2]
217164
st = store!(builder, val, typed_ptr)
218165
ordering!(st, llvm_order)
166+
syncscope!(st, SyncScope(string(llvm_syncscope)))
219167

220168
if A != 0
221169
metadata(st)[LLVM.MD_tbaa] = tbaa_addrspace(A)
@@ -254,14 +202,12 @@ const binoptable = [
254202

255203
const AtomicRMWBinOpVal = Union{(Val{binop} for (_, _, binop) in binoptable)...}
256204

257-
# LLVM API accepts string literal as a syncscope argument.
258-
@inline syncscope_to_string(::Type{Val{S}}) where {S} = string(S)
259-
260205
@generated function llvm_atomic_op(
261206
binop::AtomicRMWBinOpVal,
262207
ptr::LLVMPtr{T,A},
263208
val::T,
264209
order::LLVMOrderingVal,
210+
sync,
265211
) where {T,A}
266212
@dispose ctx = Context() begin
267213
T_val = convert(LLVMType, T)
@@ -270,21 +216,21 @@ const AtomicRMWBinOpVal = Union{(Val{binop} for (_, _, binop) in binoptable)...}
270216
T_typed_ptr = LLVM.PointerType(T_val, A)
271217

272218
llvm_f, _ = create_function(T_val, [T_ptr, T_val])
219+
llvm_syncscope = _valueof(sync())
273220

274221
@dispose builder = IRBuilder() begin
275222
entry = BasicBlock(llvm_f, "entry")
276223
position!(builder, entry)
277224

278225
typed_ptr = bitcast!(builder, parameters(llvm_f)[1], T_typed_ptr)
279226

280-
single_threaded = false
281227
rv = atomic_rmw!(
282228
builder,
283229
_valueof(binop()),
284230
typed_ptr,
285231
parameters(llvm_f)[2],
286232
_valueof(order()),
287-
single_threaded,
233+
SyncScope(string(llvm_syncscope))
288234
)
289235

290236
ret!(builder, rv)
@@ -294,139 +240,40 @@ const AtomicRMWBinOpVal = Union{(Val{binop} for (_, _, binop) in binoptable)...}
294240
end
295241
end
296242

297-
@generated function llvm_atomic_op(
298-
binop::AtomicRMWBinOpVal,
299-
ptr::LLVMPtr{T,A},
300-
val::T,
301-
order::LLVMOrderingVal,
302-
syncscope::Val{S},
303-
) where {T,A,S}
304-
@dispose ctx = Context() begin
305-
T_val = convert(LLVMType, T)
306-
T_ptr = convert(LLVMType, ptr)
307-
308-
T_typed_ptr = LLVM.PointerType(T_val, A)
309-
llvm_f, _ = create_function(T_val, [T_ptr, T_val])
310-
311-
@dispose builder = IRBuilder() begin
312-
entry = BasicBlock(llvm_f, "entry")
313-
position!(builder, entry)
314-
315-
typed_ptr = bitcast!(builder, parameters(llvm_f)[1], T_typed_ptr)
316-
rv = atomic_rmw!(
317-
builder,
318-
_valueof(binop()),
319-
typed_ptr,
320-
parameters(llvm_f)[2],
321-
_valueof(order()),
322-
syncscope_to_string(syncscope),
323-
)
324-
325-
ret!(builder, rv)
326-
end
327-
call_function(llvm_f, T, Tuple{LLVMPtr{T,A},T}, :ptr, :val)
328-
end
329-
end
330-
331-
@inline function atomic_pointermodify(pointer, op::OP, x, order::Symbol) where {OP}
332-
@dynamic_order(order) do order
333-
atomic_pointermodify(pointer, op, x, order)
334-
end
335-
end
336-
337-
@inline function atomic_pointermodify(
338-
ptr::LLVMPtr{T},
339-
op,
340-
x::T,
341-
::Val{:not_atomic},
342-
) where {T}
343-
old = atomic_pointerref(ptr, Val(:not_atomic))
344-
new = op(old, x)
345-
atomic_pointerset(ptr, new, Val(:not_atomic))
346-
return old => new
347-
end
348-
349243
@inline function atomic_pointermodify(
350244
ptr::LLVMPtr{T},
351245
::typeof(right),
352246
x::T,
353247
order::AtomicOrdering,
354-
) where {T}
248+
sync::Val{S}
249+
) where {T, S}
355250
old = llvm_atomic_op(
356251
Val(LLVM.API.LLVMAtomicRMWBinOpXchg),
357252
ptr,
358253
x,
359254
llvm_from_julia_ordering(order),
255+
sync
360256
)
361257
return old => x
362258
end
363259

364-
const atomictypes = Any[
365-
Int8,
366-
Int16,
367-
Int32,
368-
Int64,
369-
Int128,
370-
UInt8,
371-
UInt16,
372-
UInt32,
373-
UInt64,
374-
UInt128,
375-
Float16,
376-
Float32,
377-
Float64,
378-
]
379-
380-
for (opname, op, llvmop) in binoptable
381-
opname === :xchg && continue
382-
types = if opname in (:min, :max)
383-
filter(t -> t <: Signed, atomictypes)
384-
elseif opname in (:umin, :umax)
385-
filter(t -> t <: Unsigned, atomictypes)
386-
elseif opname in (:fadd, :fsub, :fmin, :fmax)
387-
filter(t -> t <: AbstractFloat, atomictypes)
388-
else
389-
filter(t -> t <: Integer, atomictypes)
390-
end
391-
for T in types
392-
@eval @inline function atomic_pointermodify(
393-
ptr::LLVMPtr{$T},
394-
::$(typeof(op)),
395-
x::$T,
396-
order::AtomicOrdering,
397-
syncscope::Val{S} = Val{:system}(),
398-
) where {S}
399-
old =
400-
syncscope isa Val{:system} ?
401-
llvm_atomic_op($(Val(llvmop)), ptr, x, llvm_from_julia_ordering(order)) :
402-
llvm_atomic_op(
403-
$(Val(llvmop)),
404-
ptr,
405-
x,
406-
llvm_from_julia_ordering(order),
407-
syncscope,
408-
)
409-
return old => $op(old, x)
410-
end
411-
end
412-
end
413-
414-
@inline atomic_pointerswap(pointer, new) = first(atomic_pointermodify(pointer, right, new))
415-
@inline atomic_pointerswap(pointer, new, order) =
416-
first(atomic_pointermodify(pointer, right, new, order))
260+
# @inline atomic_pointerswap(pointer, new) = first(atomic_pointermodify(pointer, right, new))
261+
@inline atomic_pointerswap(pointer, new, order, sync) =
262+
first(atomic_pointermodify(pointer, right, new, order, sync))
417263

418264
@inline function atomic_pointermodify(
419265
ptr::LLVMPtr{T},
420266
op,
421267
x::T,
422268
order::AllOrdering,
423-
) where {T}
269+
sync::S,
270+
) where {T, S}
424271
# Should `fail_order` be stronger? Ref: https://github.com/JuliaLang/julia/issues/45256
425272
fail_order = Val(:monotonic)
426-
old = atomic_pointerref(ptr, fail_order)
273+
old = atomic_pointerref(ptr, fail_order, sync)
427274
while true
428275
new = op(old, x)
429-
(old, success) = atomic_pointerreplace(ptr, old, new, order, fail_order)
276+
(old, success) = atomic_pointerreplace(ptr, old, new, order, fail_order, sync)
430277
success && return old => new
431278
end
432279
end
@@ -437,9 +284,11 @@ end
437284
val::T,
438285
success_order::LLVMOrderingVal,
439286
fail_order::LLVMOrderingVal,
287+
sync,
440288
) where {T,A}
441289
llvm_success = _valueof(success_order())
442290
llvm_fail = _valueof(fail_order())
291+
llvm_syncscope = _valueof(sync())
443292
@dispose ctx = Context() begin
444293
T_val = convert(LLVMType, T)
445294
T_pointee = T_val
@@ -471,15 +320,14 @@ end
471320
val_int = bitcast!(builder, val_int, T_pointee)
472321
end
473322

474-
single_threaded = false
475323
res = atomic_cmpxchg!(
476324
builder,
477325
typed_ptr,
478326
cmp_int,
479327
val_int,
480328
llvm_success,
481329
llvm_fail,
482-
single_threaded,
330+
SyncScope(string(llvm_syncscope)),
483331
)
484332

485333
rv = extract_value!(builder, res, 0)
@@ -514,42 +362,17 @@ end
514362
end
515363
end
516364

517-
@inline function atomic_pointerreplace(
518-
pointer,
519-
expected,
520-
desired,
521-
success_order::Symbol,
522-
fail_order::Symbol,
523-
)
524-
# This avoids abstract dispatch at run-time but probably too much codegen?
525-
#=
526-
@dynamic_order(success_order) do success_order
527-
@dynamic_order(fail_order) do fail_order
528-
atomic_pointerreplace(pointer, expected, desired, success_order, fail_order)
529-
end
530-
end
531-
=#
532-
533-
# This avoids excessive codegen while hopefully imposes no cost when const-prop works:
534-
so = @dynamic_order(success_order) do success_order
535-
success_order
536-
end
537-
fo = @dynamic_order(fail_order) do fail_order
538-
fail_order
539-
end
540-
return atomic_pointerreplace(pointer, expected, desired, so, fo)
541-
end
542-
543365
@inline function atomic_pointerreplace(
544366
ptr::LLVMPtr{T},
545367
expected::T,
546368
desired::T,
547369
::Val{:not_atomic},
548370
::Val{:not_atomic},
371+
sync,
549372
) where {T}
550-
old = atomic_pointerref(ptr, Val(:not_atomic))
373+
old = atomic_pointerref(ptr, Val(:not_atomic), sync)
551374
if old === expected
552-
atomic_pointerset(ptr, desired, Val(:not_atomic))
375+
atomic_pointerset(ptr, desired, Val(:not_atomic), sync)
553376
success = true
554377
else
555378
success = false
@@ -563,10 +386,12 @@ end
563386
desired::T,
564387
success_order::_julia_ordering(((:not_atomic, :unordered))),
565388
fail_order::_julia_ordering(((:not_atomic, :unordered, :release, :acquire_release))),
389+
sync
566390
) where {T} = llvm_atomic_cas(
567391
ptr,
568392
expected,
569393
desired,
570394
llvm_from_julia_ordering(success_order),
571395
llvm_from_julia_ordering(fail_order),
396+
sync
572397
)

0 commit comments

Comments
 (0)