Actual source code: cupmatomics.hpp

  1: #pragma once

  3: /*====================================================================================*/
  4: /*                             Atomic operations on device                            */
  5: /*====================================================================================*/
  6: #include <petscdevice_cupm.h>
  7: #include <petscsystypes.h>

  9: /* In terms of function overloading, long long int is a different type than int64_t, which PetscInt might be defined to.
 10:    We prefer long long int over PetscInt (int64_t), since CUDA atomics are built around (unsigned) long long int.
 11:  */
 12: typedef long long int          llint;
 13: typedef unsigned long long int ullint;

 15: #if PetscDefined(USING_NVCC)
 16: /*
 17:   Atomic Insert (exchange) operations

 19:   CUDA C Programming Guide V10.1 Chapter B.12.1.3:

 21:   int atomicExch(int* address, int val);
 22:   unsigned int atomicExch(unsigned int* address, unsigned int val);
 23:   unsigned long long int atomicExch(unsigned long long int* address, unsigned long long int val);
 24:   float atomicExch(float* address, float val);

 26:   reads the 32-bit or 64-bit word old located at the address in global or shared
 27:   memory and stores val back to memory at the same address. These two operations are
 28:   performed in one atomic transaction. The function returns old.

 30:   PETSc notes:

 32:   It may be useful in PetscSFFetchAndOp with op = MPI_REPLACE.

 34:   VecScatter with multiple entries scattered to the same location using INSERT_VALUES does not need
 35:   atomic insertion, since it does not need the old value. A 32-bit or 64-bit store instruction should
 36:   be atomic itself.

 38:   With bs>1 and a unit > 64-bits, the current element-wise atomic approach can not guarantee the whole
 39:   insertion is atomic. Hope no user codes rely on that.
 40: */
 41: __device__ static double atomicExch(double *address, double val)
 42: {
 43:   return __longlong_as_double(atomicExch((ullint *)address, __double_as_longlong(val)));
 44: }

 46: __device__ static llint atomicExch(llint *address, llint val)
 47: {
 48:   return (llint)(atomicExch((ullint *)address, (ullint)val));
 49: }

 51: template <typename Type>
 52: struct AtomicInsert {
 53:   __device__ Type operator()(Type &x, Type y) const { return atomicExch(&x, y); }
 54: };

 56:   #if defined(PETSC_HAVE_COMPLEX)
 57:     #if defined(PETSC_USE_REAL_DOUBLE)
 58: /* CUDA does not support 128-bit atomics. Users should not insert different 128-bit PetscComplex values to the same location */
 59: template <>
 60: struct AtomicInsert<PetscComplex> {
 61:   __device__ PetscComplex operator()(PetscComplex &x, PetscComplex y) const
 62:   {
 63:     PetscComplex         old, *z = &old;
 64:     double              *xp = (double *)&x, *yp = (double *)&y;
 65:     AtomicInsert<double> op;
 66:     z[0] = op(xp[0], yp[0]);
 67:     z[1] = op(xp[1], yp[1]);
 68:     return old; /* The returned value may not be atomic. It can be mix of two ops. Caller should discard it. */
 69:   }
 70: };
 71:     #elif defined(PETSC_USE_REAL_SINGLE)
 72: template <>
 73: struct AtomicInsert<PetscComplex> {
 74:   __device__ PetscComplex operator()(PetscComplex &x, PetscComplex y) const
 75:   {
 76:     double              *xp = (double *)&x, *yp = (double *)&y;
 77:     AtomicInsert<double> op;
 78:     return op(xp[0], yp[0]);
 79:   }
 80: };
 81:     #endif
 82:   #endif

 84: /*
 85:   Atomic add operations

 87:   CUDA C Programming Guide V10.1 Chapter B.12.1.1:

 89:   int atomicAdd(int* address, int val);
 90:   unsigned int atomicAdd(unsigned int* address,unsigned int val);
 91:   unsigned long long int atomicAdd(unsigned long long int* address,unsigned long long int val);
 92:   float atomicAdd(float* address, float val);
 93:   double atomicAdd(double* address, double val);
 94:   __half2 atomicAdd(__half2 *address, __half2 val);
 95:   __half atomicAdd(__half *address, __half val);

 97:   reads the 16-bit, 32-bit or 64-bit word old located at the address in global or shared memory, computes (old + val),
 98:   and stores the result back to memory at the same address. These three operations are performed in one atomic transaction. The
 99:   function returns old.

101:   The 32-bit floating-point version of atomicAdd() is only supported by devices of compute capability 2.x and higher.
102:   The 64-bit floating-point version of atomicAdd() is only supported by devices of compute capability 6.x and higher.
103:   The 32-bit __half2 floating-point version of atomicAdd() is only supported by devices of compute capability 6.x and
104:   higher. The atomicity of the __half2 add operation is guaranteed separately for each of the two __half elements;
105:   the entire __half2 is not guaranteed to be atomic as a single 32-bit access.
106:   The 16-bit __half floating-point version of atomicAdd() is only supported by devices of compute capability 7.x and higher.
107: */
108: __device__ static llint atomicAdd(llint *address, llint val)
109: {
110:   return (llint)atomicAdd((ullint *)address, (ullint)val);
111: }

113: template <typename Type>
114: struct AtomicAdd {
115:   __device__ Type operator()(Type &x, Type y) const { return atomicAdd(&x, y); }
116: };

118: template <>
119: struct AtomicAdd<double> {
120:   __device__ double operator()(double &x, double y) const
121:   {
122:   #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 600)
123:     return atomicAdd(&x, y);
124:   #else
125:     double *address = &x, val = y;
126:     ullint *address_as_ull = (ullint *)address;
127:     ullint  old            = *address_as_ull, assumed;
128:     do {
129:       assumed = old;
130:       old     = atomicCAS(address_as_ull, assumed, __double_as_longlong(val + __longlong_as_double(assumed)));
131:       /* Note: uses integer comparison to avoid hang in case of NaN (since NaN !=NaN) */
132:     } while (assumed != old);
133:     return __longlong_as_double(old);
134:   #endif
135:   }
136: };

138: template <>
139: struct AtomicAdd<float> {
140:   __device__ float operator()(float &x, float y) const
141:   {
142:   #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ >= 200)
143:     return atomicAdd(&x, y);
144:   #else
145:     float *address = &x, val = y;
146:     int   *address_as_int = (int *)address;
147:     int    old            = *address_as_int, assumed;
148:     do {
149:       assumed = old;
150:       old     = atomicCAS(address_as_int, assumed, __float_as_int(val + __int_as_float(assumed)));
151:       /* Note: uses integer comparison to avoid hang in case of NaN (since NaN !=NaN) */
152:     } while (assumed != old);
153:     return __int_as_float(old);
154:   #endif
155:   }
156: };

158:   #if defined(PETSC_HAVE_COMPLEX)
159: template <>
160: struct AtomicAdd<PetscComplex> {
161:   __device__ PetscComplex operator()(PetscComplex &x, PetscComplex y) const
162:   {
163:     PetscComplex         old, *z = &old;
164:     PetscReal           *xp = (PetscReal *)&x, *yp = (PetscReal *)&y;
165:     AtomicAdd<PetscReal> op;
166:     z[0] = op(xp[0], yp[0]);
167:     z[1] = op(xp[1], yp[1]);
168:     return old; /* The returned value may not be atomic. It can be mix of two ops. Caller should discard it. */
169:   }
170: };
171:   #endif

173:   /*
174:   Atomic Mult operations:

176:   CUDA has no atomicMult at all, so we build our own with atomicCAS
177:  */
178:   #if defined(PETSC_USE_REAL_DOUBLE)
179: __device__ static double atomicMult(double *address, double val)
180: {
181:   ullint *address_as_ull = (ullint *)(address);
182:   ullint  old            = *address_as_ull, assumed;
183:   do {
184:     assumed = old;
185:     /* Other threads can access and modify value of *address_as_ull after the read above and before the write below */
186:     old = atomicCAS(address_as_ull, assumed, __double_as_longlong(val * __longlong_as_double(assumed)));
187:   } while (assumed != old);
188:   return __longlong_as_double(old);
189: }
190:   #elif defined(PETSC_USE_REAL_SINGLE)
191: __device__ static float atomicMult(float *address, float val)
192: {
193:   int *address_as_int = (int *)(address);
194:   int  old            = *address_as_int, assumed;
195:   do {
196:     assumed = old;
197:     old     = atomicCAS(address_as_int, assumed, __float_as_int(val * __int_as_float(assumed)));
198:   } while (assumed != old);
199:   return __int_as_float(old);
200: }
201:   #endif

203: __device__ static int atomicMult(int *address, int val)
204: {
205:   int *address_as_int = (int *)(address);
206:   int  old            = *address_as_int, assumed;
207:   do {
208:     assumed = old;
209:     old     = atomicCAS(address_as_int, assumed, val * assumed);
210:   } while (assumed != old);
211:   return (int)old;
212: }

214: __device__ static llint atomicMult(llint *address, llint val)
215: {
216:   ullint *address_as_ull = (ullint *)(address);
217:   ullint  old            = *address_as_ull, assumed;
218:   do {
219:     assumed = old;
220:     old     = atomicCAS(address_as_ull, assumed, (ullint)(val * (llint)assumed));
221:   } while (assumed != old);
222:   return (llint)old;
223: }

225: template <typename Type>
226: struct AtomicMult {
227:   __device__ Type operator()(Type &x, Type y) const { return atomicMult(&x, y); }
228: };

230: /*
231:   Atomic Min/Max operations

233:   CUDA C Programming Guide V10.1 Chapter B.12.1.4~5:

235:   int atomicMin(int* address, int val);
236:   unsigned int atomicMin(unsigned int* address,unsigned int val);
237:   unsigned long long int atomicMin(unsigned long long int* address,unsigned long long int val);

239:   reads the 32-bit or 64-bit word old located at the address in global or shared
240:   memory, computes the minimum of old and val, and stores the result back to memory
241:   at the same address. These three operations are performed in one atomic transaction.
242:   The function returns old.
243:   The 64-bit version of atomicMin() is only supported by devices of compute capability 3.5 and higher.

245:   atomicMax() is similar.
246:  */

248:   #if defined(PETSC_USE_REAL_DOUBLE)
249: __device__ static double atomicMin(double *address, double val)
250: {
251:   ullint *address_as_ull = (ullint *)(address);
252:   ullint  old            = *address_as_ull, assumed;
253:   do {
254:     assumed = old;
255:     old     = atomicCAS(address_as_ull, assumed, __double_as_longlong(PetscMin(val, __longlong_as_double(assumed))));
256:   } while (assumed != old);
257:   return __longlong_as_double(old);
258: }

260: __device__ static double atomicMax(double *address, double val)
261: {
262:   ullint *address_as_ull = (ullint *)(address);
263:   ullint  old            = *address_as_ull, assumed;
264:   do {
265:     assumed = old;
266:     old     = atomicCAS(address_as_ull, assumed, __double_as_longlong(PetscMax(val, __longlong_as_double(assumed))));
267:   } while (assumed != old);
268:   return __longlong_as_double(old);
269: }
270:   #elif defined(PETSC_USE_REAL_SINGLE)
271: __device__ static float atomicMin(float *address, float val)
272: {
273:   int *address_as_int = (int *)(address);
274:   int  old            = *address_as_int, assumed;
275:   do {
276:     assumed = old;
277:     old     = atomicCAS(address_as_int, assumed, __float_as_int(PetscMin(val, __int_as_float(assumed))));
278:   } while (assumed != old);
279:   return __int_as_float(old);
280: }

282: __device__ static float atomicMax(float *address, float val)
283: {
284:   int *address_as_int = (int *)(address);
285:   int  old            = *address_as_int, assumed;
286:   do {
287:     assumed = old;
288:     old     = atomicCAS(address_as_int, assumed, __float_as_int(PetscMax(val, __int_as_float(assumed))));
289:   } while (assumed != old);
290:   return __int_as_float(old);
291: }
292:   #endif

294:   /*
295:   atomicMin/Max(long long *, long long) are not in Nvidia's documentation. But on OLCF Summit we found
296:   atomicMin/Max/And/Or/Xor(long long *, long long) in /sw/summit/cuda/10.1.243/include/sm_32_atomic_functions.h.
297:   This causes compilation errors with pgi compilers and 64-bit indices:
298:       error: function "atomicMin(long long *, long long)" has already been defined

300:   So we add extra conditions defined(__CUDA_ARCH__) && (__CUDA_ARCH__ < 320)
301: */
302:   #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ < 320)
303: __device__ static llint atomicMin(llint *address, llint val)
304: {
305:   ullint *address_as_ull = (ullint *)(address);
306:   ullint  old            = *address_as_ull, assumed;
307:   do {
308:     assumed = old;
309:     old     = atomicCAS(address_as_ull, assumed, (ullint)(PetscMin(val, (llint)assumed)));
310:   } while (assumed != old);
311:   return (llint)old;
312: }

314: __device__ static llint atomicMax(llint *address, llint val)
315: {
316:   ullint *address_as_ull = (ullint *)(address);
317:   ullint  old            = *address_as_ull, assumed;
318:   do {
319:     assumed = old;
320:     old     = atomicCAS(address_as_ull, assumed, (ullint)(PetscMax(val, (llint)assumed)));
321:   } while (assumed != old);
322:   return (llint)old;
323: }
324:   #endif

326: template <typename Type>
327: struct AtomicMin {
328:   __device__ Type operator()(Type &x, Type y) const { return atomicMin(&x, y); }
329: };
330: template <typename Type>
331: struct AtomicMax {
332:   __device__ Type operator()(Type &x, Type y) const { return atomicMax(&x, y); }
333: };

335: /*
336:   Atomic bitwise operations

338:   CUDA C Programming Guide V10.1 Chapter B.12.2.1 ~ B.12.2.3:

340:   int atomicAnd(int* address, int val);
341:   unsigned int atomicAnd(unsigned int* address,unsigned int val);
342:   unsigned long long int atomicAnd(unsigned long long int* address,unsigned long long int val);

344:   reads the 32-bit or 64-bit word old located at the address in global or shared
345:   memory, computes (old & val), and stores the result back to memory at the same
346:   address. These three operations are performed in one atomic transaction.
347:   The function returns old.

349:   The 64-bit version of atomicAnd() is only supported by devices of compute capability 3.5 and higher.

351:   atomicOr() and atomicXor are similar.
352: */

354:   #if defined(__CUDA_ARCH__) && (__CUDA_ARCH__ < 320) /* Why 320? see comments at atomicMin() above */
355: __device__ static llint atomicAnd(llint *address, llint val)
356: {
357:   ullint *address_as_ull = (ullint *)(address);
358:   ullint  old            = *address_as_ull, assumed;
359:   do {
360:     assumed = old;
361:     old     = atomicCAS(address_as_ull, assumed, (ullint)(val & (llint)assumed));
362:   } while (assumed != old);
363:   return (llint)old;
364: }
365: __device__ static llint atomicOr(llint *address, llint val)
366: {
367:   ullint *address_as_ull = (ullint *)(address);
368:   ullint  old            = *address_as_ull, assumed;
369:   do {
370:     assumed = old;
371:     old     = atomicCAS(address_as_ull, assumed, (ullint)(val | (llint)assumed));
372:   } while (assumed != old);
373:   return (llint)old;
374: }

376: __device__ static llint atomicXor(llint *address, llint val)
377: {
378:   ullint *address_as_ull = (ullint *)(address);
379:   ullint  old            = *address_as_ull, assumed;
380:   do {
381:     assumed = old;
382:     old     = atomicCAS(address_as_ull, assumed, (ullint)(val ^ (llint)assumed));
383:   } while (assumed != old);
384:   return (llint)old;
385: }
386:   #endif

388: template <typename Type>
389: struct AtomicBAND {
390:   __device__ Type operator()(Type &x, Type y) const { return atomicAnd(&x, y); }
391: };
392: template <typename Type>
393: struct AtomicBOR {
394:   __device__ Type operator()(Type &x, Type y) const { return atomicOr(&x, y); }
395: };
396: template <typename Type>
397: struct AtomicBXOR {
398:   __device__ Type operator()(Type &x, Type y) const { return atomicXor(&x, y); }
399: };

401: /*
402:   Atomic logical operations:

404:   CUDA has no atomic logical operations at all. We support them on integer types.
405: */

407: /* A template without definition makes any instantiation not using given specializations erroneous at compile time,
408:    which is what we want since we only support 32-bit and 64-bit integers.
409:  */
410: template <typename Type, class Op, int size /* sizeof(Type) */>
411: struct AtomicLogical;

413: template <typename Type, class Op>
414: struct AtomicLogical<Type, Op, 4> {
415:   __device__ Type operator()(Type &x, Type y) const
416:   {
417:     int *address_as_int = (int *)(&x);
418:     int  old            = *address_as_int, assumed;
419:     Op   op;
420:     do {
421:       assumed = old;
422:       old     = atomicCAS(address_as_int, assumed, (int)(op((Type)assumed, y)));
423:     } while (assumed != old);
424:     return (Type)old;
425:   }
426: };

428: template <typename Type, class Op>
429: struct AtomicLogical<Type, Op, 8> {
430:   __device__ Type operator()(Type &x, Type y) const
431:   {
432:     ullint *address_as_ull = (ullint *)(&x);
433:     ullint  old            = *address_as_ull, assumed;
434:     Op      op;
435:     do {
436:       assumed = old;
437:       old     = atomicCAS(address_as_ull, assumed, (ullint)(op((Type)assumed, y)));
438:     } while (assumed != old);
439:     return (Type)old;
440:   }
441: };

443: /* Note land/lor/lxor below are different from LAND etc above. Here we pass arguments by value and return result of ops (not old value) */
444: template <typename Type>
445: struct land {
446:   __device__ Type operator()(Type x, Type y) { return x && y; }
447: };
448: template <typename Type>
449: struct lor {
450:   __device__ Type operator()(Type x, Type y) { return x || y; }
451: };
452: template <typename Type>
453: struct lxor {
454:   __device__ Type operator()(Type x, Type y) { return !x != !y; }
455: };

457: template <typename Type>
458: struct AtomicLAND {
459:   __device__ Type operator()(Type &x, Type y) const
460:   {
461:     AtomicLogical<Type, land<Type>, sizeof(Type)> op;
462:     return op(x, y);
463:   }
464: };
465: template <typename Type>
466: struct AtomicLOR {
467:   __device__ Type operator()(Type &x, Type y) const
468:   {
469:     AtomicLogical<Type, lor<Type>, sizeof(Type)> op;
470:     return op(x, y);
471:   }
472: };
473: template <typename Type>
474: struct AtomicLXOR {
475:   __device__ Type operator()(Type &x, Type y) const
476:   {
477:     AtomicLogical<Type, lxor<Type>, sizeof(Type)> op;
478:     return op(x, y);
479:   }
480: };

482: #elif PetscDefined(USING_HCC)

484:   /*
485:   Atomic Insert (exchange) operations

487:   See Cuda version
488: */
489:   #if PETSC_PKG_HIP_VERSION_LT(4, 4, 0)
490: __device__ static double atomicExch(double *address, double val)
491: {
492:   return __longlong_as_double(atomicExch((ullint *)address, __double_as_longlong(val)));
493: }
494:   #endif

496: __device__ static inline llint atomicExch(llint *address, llint val)
497: {
498:   return (llint)(atomicExch((ullint *)address, (ullint)val));
499: }

501: template <typename Type>
502: struct AtomicInsert {
503:   __device__ Type operator()(Type &x, Type y) const { return atomicExch(&x, y); }
504: };

506:   #if defined(PETSC_HAVE_COMPLEX)
507:     #if defined(PETSC_USE_REAL_DOUBLE)
508: template <>
509: struct AtomicInsert<PetscComplex> {
510:   __device__ PetscComplex operator()(PetscComplex &x, PetscComplex y) const
511:   {
512:     PetscComplex         old, *z = &old;
513:     double              *xp = (double *)&x, *yp = (double *)&y;
514:     AtomicInsert<double> op;
515:     z[0] = op(xp[0], yp[0]);
516:     z[1] = op(xp[1], yp[1]);
517:     return old; /* The returned value may not be atomic. It can be mix of two ops. Caller should discard it. */
518:   }
519: };
520:     #elif defined(PETSC_USE_REAL_SINGLE)
521: template <>
522: struct AtomicInsert<PetscComplex> {
523:   __device__ PetscComplex operator()(PetscComplex &x, PetscComplex y) const
524:   {
525:     double              *xp = (double *)&x, *yp = (double *)&y;
526:     AtomicInsert<double> op;
527:     return op(xp[0], yp[0]);
528:   }
529: };
530:     #endif
531:   #endif

533: /*
534:   Atomic add operations

536: */
537: __device__ static inline llint atomicAdd(llint *address, llint val)
538: {
539:   return (llint)atomicAdd((ullint *)address, (ullint)val);
540: }

542: template <typename Type>
543: struct AtomicAdd {
544:   __device__ Type operator()(Type &x, Type y) const { return atomicAdd(&x, y); }
545: };

547: template <>
548: struct AtomicAdd<double> {
549:   __device__ double operator()(double &x, double y) const
550:   {
551:     /* Cuda version does more checks that may be needed */
552:     return atomicAdd(&x, y);
553:   }
554: };

556: template <>
557: struct AtomicAdd<float> {
558:   __device__ float operator()(float &x, float y) const
559:   {
560:     /* Cuda version does more checks that may be needed */
561:     return atomicAdd(&x, y);
562:   }
563: };

565:   #if defined(PETSC_HAVE_COMPLEX)
566: template <>
567: struct AtomicAdd<PetscComplex> {
568:   __device__ PetscComplex operator()(PetscComplex &x, PetscComplex y) const
569:   {
570:     PetscComplex         old, *z = &old;
571:     PetscReal           *xp = (PetscReal *)&x, *yp = (PetscReal *)&y;
572:     AtomicAdd<PetscReal> op;
573:     z[0] = op(xp[0], yp[0]);
574:     z[1] = op(xp[1], yp[1]);
575:     return old; /* The returned value may not be atomic. It can be mix of two ops. Caller should discard it. */
576:   }
577: };
578:   #endif

580:   /*
581:   Atomic Mult operations:

583:   HIP has no atomicMult at all, so we build our own with atomicCAS
584:  */
585:   #if defined(PETSC_USE_REAL_DOUBLE)
586: __device__ static inline double atomicMult(double *address, double val)
587: {
588:   ullint *address_as_ull = (ullint *)(address);
589:   ullint  old            = *address_as_ull, assumed;
590:   do {
591:     assumed = old;
592:     /* Other threads can access and modify value of *address_as_ull after the read above and before the write below */
593:     old = atomicCAS(address_as_ull, assumed, __double_as_longlong(val * __longlong_as_double(assumed)));
594:   } while (assumed != old);
595:   return __longlong_as_double(old);
596: }
597:   #elif defined(PETSC_USE_REAL_SINGLE)
598: __device__ static float atomicMult(float *address, float val)
599: {
600:   int *address_as_int = (int *)(address);
601:   int  old            = *address_as_int, assumed;
602:   do {
603:     assumed = old;
604:     old     = atomicCAS(address_as_int, assumed, __float_as_int(val * __int_as_float(assumed)));
605:   } while (assumed != old);
606:   return __int_as_float(old);
607: }
608:   #endif

610: __device__ static inline int atomicMult(int *address, int val)
611: {
612:   int *address_as_int = (int *)(address);
613:   int  old            = *address_as_int, assumed;
614:   do {
615:     assumed = old;
616:     old     = atomicCAS(address_as_int, assumed, val * assumed);
617:   } while (assumed != old);
618:   return (int)old;
619: }

621: __device__ static inline llint atomicMult(llint *address, llint val)
622: {
623:   ullint *address_as_ull = (ullint *)(address);
624:   ullint  old            = *address_as_ull, assumed;
625:   do {
626:     assumed = old;
627:     old     = atomicCAS(address_as_ull, assumed, (ullint)(val * (llint)assumed));
628:   } while (assumed != old);
629:   return (llint)old;
630: }

632: template <typename Type>
633: struct AtomicMult {
634:   __device__ Type operator()(Type &x, Type y) const { return atomicMult(&x, y); }
635: };

637:   /*
638:   Atomic Min/Max operations

640:   See CUDA version for comments.
641:  */
642:   #if PETSC_PKG_HIP_VERSION_LT(4, 4, 0)
643:     #if defined(PETSC_USE_REAL_DOUBLE)
644: __device__ static double atomicMin(double *address, double val)
645: {
646:   ullint *address_as_ull = (ullint *)(address);
647:   ullint  old            = *address_as_ull, assumed;
648:   do {
649:     assumed = old;
650:     old     = atomicCAS(address_as_ull, assumed, __double_as_longlong(PetscMin(val, __longlong_as_double(assumed))));
651:   } while (assumed != old);
652:   return __longlong_as_double(old);
653: }

655: __device__ static double atomicMax(double *address, double val)
656: {
657:   ullint *address_as_ull = (ullint *)(address);
658:   ullint  old            = *address_as_ull, assumed;
659:   do {
660:     assumed = old;
661:     old     = atomicCAS(address_as_ull, assumed, __double_as_longlong(PetscMax(val, __longlong_as_double(assumed))));
662:   } while (assumed != old);
663:   return __longlong_as_double(old);
664: }
665:     #elif defined(PETSC_USE_REAL_SINGLE)
666: __device__ static float atomicMin(float *address, float val)
667: {
668:   int *address_as_int = (int *)(address);
669:   int  old            = *address_as_int, assumed;
670:   do {
671:     assumed = old;
672:     old     = atomicCAS(address_as_int, assumed, __float_as_int(PetscMin(val, __int_as_float(assumed))));
673:   } while (assumed != old);
674:   return __int_as_float(old);
675: }

677: __device__ static float atomicMax(float *address, float val)
678: {
679:   int *address_as_int = (int *)(address);
680:   int  old            = *address_as_int, assumed;
681:   do {
682:     assumed = old;
683:     old     = atomicCAS(address_as_int, assumed, __float_as_int(PetscMax(val, __int_as_float(assumed))));
684:   } while (assumed != old);
685:   return __int_as_float(old);
686: }
687:     #endif
688:   #endif

690:   #if PETSC_PKG_HIP_VERSION_LT(5, 7, 0)
691: __device__ static llint atomicMin(llint *address, llint val)
692: {
693:   ullint *address_as_ull = (ullint *)(address);
694:   ullint  old            = *address_as_ull, assumed;
695:   do {
696:     assumed = old;
697:     old     = atomicCAS(address_as_ull, assumed, (ullint)(PetscMin(val, (llint)assumed)));
698:   } while (assumed != old);
699:   return (llint)old;
700: }

702: __device__ static llint atomicMax(llint *address, llint val)
703: {
704:   ullint *address_as_ull = (ullint *)(address);
705:   ullint  old            = *address_as_ull, assumed;
706:   do {
707:     assumed = old;
708:     old     = atomicCAS(address_as_ull, assumed, (ullint)(PetscMax(val, (llint)assumed)));
709:   } while (assumed != old);
710:   return (llint)old;
711: }
712:   #endif

714: template <typename Type>
715: struct AtomicMin {
716:   __device__ Type operator()(Type &x, Type y) const { return atomicMin(&x, y); }
717: };
718: template <typename Type>
719: struct AtomicMax {
720:   __device__ Type operator()(Type &x, Type y) const { return atomicMax(&x, y); }
721: };

723: /*
724:   Atomic bitwise operations
725:   As of ROCm 3.10, the llint atomicAnd/Or/Xor(llint*, llint) is not supported
726: */

728: __device__ static inline llint atomicAnd(llint *address, llint val)
729: {
730:   ullint *address_as_ull = (ullint *)(address);
731:   ullint  old            = *address_as_ull, assumed;
732:   do {
733:     assumed = old;
734:     old     = atomicCAS(address_as_ull, assumed, (ullint)(val & (llint)assumed));
735:   } while (assumed != old);
736:   return (llint)old;
737: }
738: __device__ static inline llint atomicOr(llint *address, llint val)
739: {
740:   ullint *address_as_ull = (ullint *)(address);
741:   ullint  old            = *address_as_ull, assumed;
742:   do {
743:     assumed = old;
744:     old     = atomicCAS(address_as_ull, assumed, (ullint)(val | (llint)assumed));
745:   } while (assumed != old);
746:   return (llint)old;
747: }

749: __device__ static inline llint atomicXor(llint *address, llint val)
750: {
751:   ullint *address_as_ull = (ullint *)(address);
752:   ullint  old            = *address_as_ull, assumed;
753:   do {
754:     assumed = old;
755:     old     = atomicCAS(address_as_ull, assumed, (ullint)(val ^ (llint)assumed));
756:   } while (assumed != old);
757:   return (llint)old;
758: }

760: template <typename Type>
761: struct AtomicBAND {
762:   __device__ Type operator()(Type &x, Type y) const { return atomicAnd(&x, y); }
763: };
764: template <typename Type>
765: struct AtomicBOR {
766:   __device__ Type operator()(Type &x, Type y) const { return atomicOr(&x, y); }
767: };
768: template <typename Type>
769: struct AtomicBXOR {
770:   __device__ Type operator()(Type &x, Type y) const { return atomicXor(&x, y); }
771: };

773: /*
774:   Atomic logical operations:

776:   CUDA has no atomic logical operations at all. We support them on integer types.
777: */

779: /* A template without definition makes any instantiation not using given specializations erroneous at compile time,
780:    which is what we want since we only support 32-bit and 64-bit integers.
781:  */
782: template <typename Type, class Op, int size /* sizeof(Type) */>
783: struct AtomicLogical;

785: template <typename Type, class Op>
786: struct AtomicLogical<Type, Op, 4> {
787:   __device__ Type operator()(Type &x, Type y) const
788:   {
789:     int *address_as_int = (int *)(&x);
790:     int  old            = *address_as_int, assumed;
791:     Op   op;
792:     do {
793:       assumed = old;
794:       old     = atomicCAS(address_as_int, assumed, (int)(op((Type)assumed, y)));
795:     } while (assumed != old);
796:     return (Type)old;
797:   }
798: };

800: template <typename Type, class Op>
801: struct AtomicLogical<Type, Op, 8> {
802:   __device__ Type operator()(Type &x, Type y) const
803:   {
804:     ullint *address_as_ull = (ullint *)(&x);
805:     ullint  old            = *address_as_ull, assumed;
806:     Op      op;
807:     do {
808:       assumed = old;
809:       old     = atomicCAS(address_as_ull, assumed, (ullint)(op((Type)assumed, y)));
810:     } while (assumed != old);
811:     return (Type)old;
812:   }
813: };

815: /* Note land/lor/lxor below are different from LAND etc above. Here we pass arguments by value and return result of ops (not old value) */
816: template <typename Type>
817: struct land {
818:   __device__ Type operator()(Type x, Type y) { return x && y; }
819: };
820: template <typename Type>
821: struct lor {
822:   __device__ Type operator()(Type x, Type y) { return x || y; }
823: };
824: template <typename Type>
825: struct lxor {
826:   __device__ Type operator()(Type x, Type y) { return !x != !y; }
827: };

829: template <typename Type>
830: struct AtomicLAND {
831:   __device__ Type operator()(Type &x, Type y) const
832:   {
833:     AtomicLogical<Type, land<Type>, sizeof(Type)> op;
834:     return op(x, y);
835:   }
836: };
837: template <typename Type>
838: struct AtomicLOR {
839:   __device__ Type operator()(Type &x, Type y) const
840:   {
841:     AtomicLogical<Type, lor<Type>, sizeof(Type)> op;
842:     return op(x, y);
843:   }
844: };
845: template <typename Type>
846: struct AtomicLXOR {
847:   __device__ Type operator()(Type &x, Type y) const
848:   {
849:     AtomicLogical<Type, lxor<Type>, sizeof(Type)> op;
850:     return op(x, y);
851:   }
852: };
853: #endif