|
243 | 243 | // Deviations from the standard. This does not include new features added:
|
244 | 244 | //
|
245 | 245 | // 1.
|
246 |
| -// Description: Atomic class constructors are not and will not be constexpr. |
247 |
| -// Reasoning : We assert in the constructor that the this pointer is properly aligned. |
248 |
| -// There are no other constexpr functions that can be called in a constexpr |
249 |
| -// context. The only use for constexpr here is const-init time or ensuring |
250 |
| -// that the object's value is placed in the executable at compile-time instead |
251 |
| -// of having to call the ctor at static-init time. If you are using constexpr |
252 |
| -// to solve static-init order fiasco, there are other solutions for that. |
253 |
| -// |
254 |
| -// 2. |
255 | 246 | // Description: Atomics are always lock free
|
256 | 247 | // Reasoning : We don't want people to fall into performance traps where implicit locking
|
257 | 248 | // is done. If your user defined type is large enough to not support atomic
|
258 | 249 | // instructions then your user code should do the locking.
|
259 | 250 | //
|
260 |
| -// 3. |
| 251 | +// 2. |
261 | 252 | // Description: Atomic objects can not be volatile
|
262 | 253 | // Reasoning : Volatile objects do not make sense in the context of eastl::atomic<T>.
|
263 | 254 | // Use the given memory orders to get the ordering you need.
|
264 | 255 | // Atomic objects have to become visible on the bus. See below for details.
|
265 | 256 | //
|
266 |
| -// 4. |
| 257 | +// 3. |
267 | 258 | // Description: Consume memory order is not supported
|
268 | 259 | // Reasoning : See below for the reasoning.
|
269 | 260 | //
|
270 |
| -// 5. |
| 261 | +// 4. |
271 | 262 | // Description: ATOMIC_INIT() macros and the ATOMIC_LOCK_FREE macros are not implemented
|
272 | 263 | // Reasoning : Use the is_lock_free() method instead of the macros.
|
273 | 264 | // ATOMIC_INIT() macros aren't needed since the default constructor value initializes.
|
274 | 265 | //
|
275 |
| -// 6. |
| 266 | +// 5. |
276 | 267 | // Description: compare_exchange failure memory order cannot be stronger than success memory order
|
277 | 268 | // Reasoning : Besides the argument that it ideologically does not make sense that a failure
|
278 | 269 | // of the atomic operation shouldn't have a stricter ordering guarantee than the
|
|
284 | 275 | // that versions of compilers that say they support C++17 do not properly adhere to this
|
285 | 276 | // new requirement in their intrinsics. Thus we will not support this.
|
286 | 277 | //
|
287 |
| -// 7. |
| 278 | +// 6. |
288 | 279 | // Description: All memory orders are distinct types instead of enum values
|
289 | 280 | // Reasoning : This will not affect how the API is used in user code.
|
290 | 281 | // It allows us to statically assert on invalid memory orders since they are compile-time types
|
|
1384 | 1375 | // The read_depends operation can be used on loads from only an eastl::atomic<T*> type. The return pointer of the load must and can only be used to then further load values. And that is it.
|
1385 | 1376 | // If you are unsure, upgrade this load to an acquire operation.
|
1386 | 1377 | //
|
1387 |
| -// MyStruct* ptr = gAtomicPtr.load(read_depends); |
| 1378 | +// MyStruct* ptr = gAtomicPtr.load(memory_order_read_depends); |
1388 | 1379 | // int a = ptr->a;
|
1389 | 1380 | // int b = ptr->b;
|
1390 | 1381 | // return a + b;
|
1391 | 1382 | //
|
1392 | 1383 | // The loads from ptr after the gAtomicPtr load ensure that the correct values of a and b are observed. This pairs with a Release operation on the writer side by releasing gAtomicPtr.
|
1393 | 1384 | //
|
| 1385 | +// |
| 1386 | +// As said above the returned pointer from a .load(memory_order_read_depends) can only be used to then further load values. |
| 1387 | +// Dereferencing(*) and Arrow Dereferencing(->) are valid operations on return values from .load(memory_order_read_depends). |
| 1388 | +// |
| 1389 | +// MyStruct* ptr = gAtomicPtr.load(memory_order_read_depends); |
| 1390 | +// int a = ptr->a; - VALID |
| 1391 | +// int a = *ptr; - VALID |
| 1392 | +// |
| 1393 | +// Since dereferencing is just indexing via some offset from some base address, this also means addition and subtraction of constants is ok. |
| 1394 | +// |
| 1395 | +// int* ptr = gAtomicPtr.load(memory_order_read_depends); |
| 1396 | +// int a = *(ptr + 1) - VALID |
| 1397 | +// int a = *(ptr - 1) - VALID |
| 1398 | +// |
| 1399 | +// Casts also work correctly since casting is just offsetting a pointer depending on the inheritance hierarchy or if using intrusive containers. |
| 1400 | +// |
| 1401 | +// ReadDependsIntrusive** intrusivePtr = gAtomicPtr.load(memory_order_read_depends); |
| 1402 | +// ReadDependsIntrusive* ptr = ((ReadDependsIntrusive*)(((char*)intrusivePtr) - offsetof(ReadDependsIntrusive, next))); |
| 1403 | +// |
| 1404 | +// Base* basePtr = gAtomicPtr.load(memory_order_read_depends); |
| 1405 | +// Dervied* derivedPtr = static_cast<Derived*>(basePtr); |
| 1406 | +// |
| 1407 | +// Both of the above castings from the result of the load are valid for this memory order. |
| 1408 | +// |
| 1409 | +// You can reinterpret_cast the returned pointer value to a uintptr_t to set bits, clear bits, or xor bits but the pointer must be casted back before doing anything else. |
| 1410 | +// |
| 1411 | +// int* ptr = gAtomicPtr.load(memory_order_read_depends); |
| 1412 | +// ptr = reinterpret_cast<int*>(reinterpret_cast<uintptr_t>(ptr) & ~3); |
| 1413 | +// |
| 1414 | +// Do not use any equality or relational operator (==, !=, >, <, >=, <=) results in the computation of offsets before dereferencing. |
| 1415 | +// As we learned above in the Control Dependencies section, CPUs will not order Load-Load Control Dependencies. Relational and equality operators are often compiled using branches. |
| 1416 | +// It doesn't have to be compiled to branched, condition instructions could be used. Or some architectures provide comparison instructions such as set less than which do not need |
| 1417 | +// branches when using the result of the relational operator in arithmetic statements. Then again short circuiting may need to introduct branches since C++ guarantees the |
| 1418 | +// rest of the expression must not be evaluated. |
| 1419 | +// The following odd code is forbidden. |
| 1420 | +// |
| 1421 | +// int* ptr = gAtomicPtr.load(memory_order_read_depends); |
| 1422 | +// int* ptr2 = ptr + (ptr >= 0); |
| 1423 | +// int a = *ptr2; |
| 1424 | +// |
| 1425 | +// Only equality comparisons against nullptr are allowed. This is becase the compiler cannot assume that the address of the loaded value is some known address and substitute our loaded value. |
| 1426 | +// int* ptr = gAtomicPtr.load(memory_order_read_depends); |
| 1427 | +// if (ptr == nullptr); - VALID |
| 1428 | +// if (ptr != nullptr); - VALID |
| 1429 | +// |
| 1430 | +// Thus the above sentence that states: |
| 1431 | +// The return pointer of the load must and can only be used to then further load values. And that is it. |
| 1432 | +// must be respected by the programmer. This memory order is an optimization added for efficient read heavy pointer swapping data structures. IF you are unsure, use memory_order_acquire. |
| 1433 | +// |
1394 | 1434 | // ******** Relaxed && eastl::atomic<T> guarantees ********
|
1395 | 1435 | //
|
1396 | 1436 | // We saw various ways that compiler barriers do not help us and that we need something more granular to make sure accesses are not mangled by the compiler to be considered atomic.
|
|
1586 | 1626 | // ----------------------------------------------------------------------------------------
|
1587 | 1627 | //
|
1588 | 1628 | // In this example it is entirely possible that we observe r0 = 1 && r1 = 0 even though we have source code causality and sequentially consistent operations.
|
1589 |
| -// Observability is tied to the atomic object on which the operation was performed and the thread fence doesn't synchronize-with the fetch_add because there is no |
| 1629 | +// Observability is tied to the atomic object on which the operation was performed and the thread fence doesn't synchronize-with the fetch_add because |
1590 | 1630 | // there is no load above the fence that reads the value from the fetch_add.
|
1591 | 1631 | //
|
1592 | 1632 | // ******** Sequential Consistency Semantics ********
|
|
0 commit comments