@@ -89,68 +89,12 @@ for (julia, llvm) in pairs(_llvm_from_julia_ordering)
89
89
@eval llvm_from_julia_ordering (:: Val{$(QuoteNode(julia))} ) = Val {$llvm} ()
90
90
end
91
91
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
-
137
92
_valueof (:: Val{x} ) where {x} = x
138
93
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}
152
95
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 ())
154
98
@dispose ctx = Context () begin
155
99
eltyp = convert (LLVMType, T)
156
100
170
114
typed_ptr = bitcast! (builder, parameters (llvm_f)[1 ], T_typed_ptr)
171
115
ld = load! (builder, eltyp, typed_ptr)
172
116
ordering! (ld, llvm_order)
117
+ syncscope! (ld, SyncScope (string (llvm_syncscope)))
173
118
174
119
if A != 0
175
120
metadata (ld)[LLVM. MD_tbaa] = tbaa_addrspace (A)
187
132
ptr:: LLVMPtr{T,A} ,
188
133
x:: T ,
189
134
order:: AllOrdering ,
135
+ sync,
190
136
) where {T,A}
191
137
if sizeof (T) == 0
192
138
# Mimicking what `Core.Intrinsics.atomic_pointerset` generates.
197
143
return ptr
198
144
end
199
145
end
200
- llvm_order = _valueof (llvm_from_julia_ordering (order ()))
146
+ llvm_order = _valueof (llvm_from_julia_ordering (order ()))
147
+ llvm_syncscope = _valueof (sync ())
201
148
@dispose ctx = Context () begin
202
149
eltyp = convert (LLVMType, T)
203
150
T_ptr = convert (LLVMType, ptr)
216
163
val = parameters (llvm_f)[2 ]
217
164
st = store! (builder, val, typed_ptr)
218
165
ordering! (st, llvm_order)
166
+ syncscope! (st, SyncScope (string (llvm_syncscope)))
219
167
220
168
if A != 0
221
169
metadata (st)[LLVM. MD_tbaa] = tbaa_addrspace (A)
@@ -254,14 +202,12 @@ const binoptable = [
254
202
255
203
const AtomicRMWBinOpVal = Union{(Val{binop} for (_, _, binop) in binoptable). .. }
256
204
257
- # LLVM API accepts string literal as a syncscope argument.
258
- @inline syncscope_to_string (:: Type{Val{S}} ) where {S} = string (S)
259
-
260
205
@generated function llvm_atomic_op (
261
206
binop:: AtomicRMWBinOpVal ,
262
207
ptr:: LLVMPtr{T,A} ,
263
208
val:: T ,
264
209
order:: LLVMOrderingVal ,
210
+ sync,
265
211
) where {T,A}
266
212
@dispose ctx = Context () begin
267
213
T_val = convert (LLVMType, T)
@@ -270,21 +216,21 @@ const AtomicRMWBinOpVal = Union{(Val{binop} for (_, _, binop) in binoptable)...}
270
216
T_typed_ptr = LLVM. PointerType (T_val, A)
271
217
272
218
llvm_f, _ = create_function (T_val, [T_ptr, T_val])
219
+ llvm_syncscope = _valueof (sync ())
273
220
274
221
@dispose builder = IRBuilder () begin
275
222
entry = BasicBlock (llvm_f, " entry" )
276
223
position! (builder, entry)
277
224
278
225
typed_ptr = bitcast! (builder, parameters (llvm_f)[1 ], T_typed_ptr)
279
226
280
- single_threaded = false
281
227
rv = atomic_rmw! (
282
228
builder,
283
229
_valueof (binop ()),
284
230
typed_ptr,
285
231
parameters (llvm_f)[2 ],
286
232
_valueof (order ()),
287
- single_threaded,
233
+ SyncScope ( string (llvm_syncscope))
288
234
)
289
235
290
236
ret! (builder, rv)
@@ -294,139 +240,40 @@ const AtomicRMWBinOpVal = Union{(Val{binop} for (_, _, binop) in binoptable)...}
294
240
end
295
241
end
296
242
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
-
349
243
@inline function atomic_pointermodify (
350
244
ptr:: LLVMPtr{T} ,
351
245
:: typeof (right),
352
246
x:: T ,
353
247
order:: AtomicOrdering ,
354
- ) where {T}
248
+ sync:: Val{S}
249
+ ) where {T, S}
355
250
old = llvm_atomic_op (
356
251
Val (LLVM. API. LLVMAtomicRMWBinOpXchg),
357
252
ptr,
358
253
x,
359
254
llvm_from_julia_ordering (order),
255
+ sync
360
256
)
361
257
return old => x
362
258
end
363
259
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))
417
263
418
264
@inline function atomic_pointermodify (
419
265
ptr:: LLVMPtr{T} ,
420
266
op,
421
267
x:: T ,
422
268
order:: AllOrdering ,
423
- ) where {T}
269
+ sync:: S ,
270
+ ) where {T, S}
424
271
# Should `fail_order` be stronger? Ref: https://github.com/JuliaLang/julia/issues/45256
425
272
fail_order = Val (:monotonic )
426
- old = atomic_pointerref (ptr, fail_order)
273
+ old = atomic_pointerref (ptr, fail_order, sync )
427
274
while true
428
275
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 )
430
277
success && return old => new
431
278
end
432
279
end
437
284
val:: T ,
438
285
success_order:: LLVMOrderingVal ,
439
286
fail_order:: LLVMOrderingVal ,
287
+ sync,
440
288
) where {T,A}
441
289
llvm_success = _valueof (success_order ())
442
290
llvm_fail = _valueof (fail_order ())
291
+ llvm_syncscope = _valueof (sync ())
443
292
@dispose ctx = Context () begin
444
293
T_val = convert (LLVMType, T)
445
294
T_pointee = T_val
@@ -471,15 +320,14 @@ end
471
320
val_int = bitcast! (builder, val_int, T_pointee)
472
321
end
473
322
474
- single_threaded = false
475
323
res = atomic_cmpxchg! (
476
324
builder,
477
325
typed_ptr,
478
326
cmp_int,
479
327
val_int,
480
328
llvm_success,
481
329
llvm_fail,
482
- single_threaded ,
330
+ SyncScope ( string (llvm_syncscope)) ,
483
331
)
484
332
485
333
rv = extract_value! (builder, res, 0 )
@@ -514,42 +362,17 @@ end
514
362
end
515
363
end
516
364
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
-
543
365
@inline function atomic_pointerreplace (
544
366
ptr:: LLVMPtr{T} ,
545
367
expected:: T ,
546
368
desired:: T ,
547
369
:: Val{:not_atomic} ,
548
370
:: Val{:not_atomic} ,
371
+ sync,
549
372
) where {T}
550
- old = atomic_pointerref (ptr, Val (:not_atomic ))
373
+ old = atomic_pointerref (ptr, Val (:not_atomic ), sync )
551
374
if old === expected
552
- atomic_pointerset (ptr, desired, Val (:not_atomic ))
375
+ atomic_pointerset (ptr, desired, Val (:not_atomic ), sync )
553
376
success = true
554
377
else
555
378
success = false
@@ -563,10 +386,12 @@ end
563
386
desired:: T ,
564
387
success_order:: _julia_ordering (∉ ((:not_atomic , :unordered ))),
565
388
fail_order:: _julia_ordering (∉ ((:not_atomic , :unordered , :release , :acquire_release ))),
389
+ sync
566
390
) where {T} = llvm_atomic_cas (
567
391
ptr,
568
392
expected,
569
393
desired,
570
394
llvm_from_julia_ordering (success_order),
571
395
llvm_from_julia_ordering (fail_order),
396
+ sync
572
397
)
0 commit comments