Fact-checked by Grok 2 weeks ago

Memory ordering

Memory ordering is the set of rules in that govern the perceived sequence of memory operations—such as loads and stores—across multiple processors or threads in a shared-memory system, ensuring that concurrent executions produce predictable results despite hardware optimizations like buffering and reordering. These rules form a critical component of memory consistency models, which specify the contract between software and hardware regarding allowed behaviors in multithreaded programs. The foundational concept emerged in the late 1970s with Leslie Lamport's definition of , the strongest and most intuitive model, which requires that the outcome of any execution appears as if all memory operations from all processors were performed in a single consistent with each processor's program order. Under SC, operations execute atomically and without reordering relative to the program's sequential intent, providing a straightforward guarantee for programmers but limiting hardware performance optimizations due to its strict constraints. To address these limitations, subsequent models relaxed ordering requirements for better scalability and speed in multiprocessor systems. Relaxed memory consistency models, such as introduced by Dubois, Scheurich, and Briggs in 1988, permit certain reorderings of memory accesses (e.g., allowing loads to bypass earlier stores) while preserving correctness through explicit points like fences or barriers. Other prominent variants include total store order (TSO), implemented in x86 architectures, which allows store-to-load reordering via write buffers but maintains store-store order; and more permissive models like those in , , and architectures, which support extensive reordering to exploit pipelines. These models balance performance gains—such as reduced latency in cache-coherent systems—with the need for programmers to use atomic operations or memory barriers to enforce necessary orders. In modern multicore processors, GPUs, and heterogeneous systems, memory ordering ensures data race freedom and correct , enabling efficient parallel programming while mitigating subtle bugs from weak models. Tools like herd7 and ppcmem facilitate validation of these behaviors, and programming languages such as C++ and expose ordering primitives (e.g., std::memory_order) to bridge models with software. Ongoing continues to refine these models for emerging architectures, emphasizing "SC for data-race-free" programs to simplify development without sacrificing efficiency.

Memory Consistency Models

Sequential Consistency

Sequential consistency is a memory consistency model in which all memory operations from multiple threads appear to execute in a single global that is consistent with the program order within each individual , as if all operations were serialized in some interleaving across threads. This model ensures that the outcome of any execution matches the result of executing the operations of all processors in some sequential order, with each processor's operations appearing in the specified program order. The concept was introduced by in 1979 to define conditions under which a multiprocessor system correctly executes multiprocess programs, emphasizing the need for a straightforward semantic guarantee in concurrent environments. Lamport formalized as requiring that memory accesses be atomic and that the system behave as though operations are interleaved into a single sequence respecting per-process orders. Key properties of sequential consistency include the guarantee that every read operation returns the value of the most recent write in the global order, with no reordering of operations across threads permitted, thereby eliminating out-of-order visibility issues. These properties simplify and reasoning about concurrent code, as programmers can assume an intuitive sequential execution model without needing to account for hardware- or compiler-induced reorderings. Formally, sequential consistency can be described as the interleaving of operations from all threads into a single total sequence where, for each thread, the relative order of its own operations is preserved, and all reads observe writes that precede them in this global sequence. This interleaving ensures that the apparent execution order is equivalent to some of all operations. A representative example is Dekker's algorithm for mutual exclusion between two processes using shared flags, which relies on sequential consistency to prevent races. In this algorithm, two processes (P1 and P2) set their respective flags and check the other's flag before entering a critical section:
Initially: flag1 = flag2 = 0

P1:
  flag1 = 1
  if (flag2 == 0) {
    // critical section
  }

P2:
  flag2 = 1
  if (flag1 == 0) {
    // critical section
  }
Under sequential consistency, the writes to flags are visible in a total order respecting program orders, prohibiting the outcome where both processes read the other's flag as 0 after both writes have occurred, thus ensuring mutual exclusion without races. This success highlights how sequential consistency provides a strong baseline for correct concurrent behavior in algorithms like Dekker's.

Relaxed Consistency Models

Relaxed memory consistency models provide weaker guarantees than sequential consistency by permitting specific reorderings of memory operations across processors, enabling hardware optimizations such as out-of-order execution and non-blocking writes while maintaining sufficient order for correct program behavior. These models relax program order constraints in categories including load-load (reordering two loads), load-store (reordering a load before a subsequent store), store-store (reordering two stores), and store-load (reordering a store before a subsequent load), with the exact relaxations varying by model. For instance, Total Store Order (TSO) permits store-load and load-load reorderings but preserves store-store order and write serialization across processors. Specific relaxed models include Processor Consistency (PC), which further relaxes TSO by allowing stores from one processor to become visible to different processors in varying orders, without requiring a total order on writes. Partial Store Order (PSO) extends TSO by also permitting store-store reorderings, allowing overlapped or pipelined writes to different addresses. Release-Acquire semantics, a form of one-way barrier ordering, ensures that loads and stores following an acquire operation respect the order of prior releases but allows broader relaxations otherwise. Release Consistency variants, such as RCpc (Release Consistency, processor-consistent) and RCsc (Release Consistency, sequentially-consistent), relax all program orders except those enforced by special synchronization operations like acquires and releases; RCpc further relaxes ordering among these special operations compared to RCsc. Weak Ordering (WO) relaxes all orders between ordinary data operations, relying entirely on explicit synchronization points to establish ordering. These models offer significant performance gains by allowing hardware to hide memory latencies and overlap operations, but they introduce programming complexity, as developers must explicitly manage ordering to avoid subtle bugs. A classic example is the failure of the idiom in , where a store to a pointer may become visible to another before the object's full initialization stores, leading to partially constructed objects being accessed. Such errors arise because relaxed models permit store-load reorderings that would prevent, potentially causing non-intuitive execution orders. Architectural implementations reflect these trade-offs: x86 processors adopt TSO, providing stronger ordering than many alternatives but still relaxing loads past stores. In contrast, 's weakly ordered model permits all four relaxation types, maximizing flexibility for power-efficient designs. PowerPC employs a relaxed model similar to , allowing extensive reorderings to support high-performance . This evolution from stronger models like TSO to weaker ones in and PowerPC prioritizes in embedded and server environments over the stricter guarantees of . To mitigate risks in relaxed models, programmers use operations or primitives that act as ordering points, such as release-acquire pairs, to enforce necessary dependencies without full . These mechanisms restore predictable behavior for critical sections, balancing the performance benefits of relaxations with reliable multithreaded code.

Compile-Time Memory Ordering

Program Order in Source Code

Program order in source code refers to the partial ordering of memory operations—such as reads and writes—as they appear sequentially in the program's textual representation, constrained by the language's evaluation rules and sequencing semantics. This order is implied by the structure of expressions, statements, and in the source, ensuring that certain memory accesses are guaranteed to occur in a specific sequence from the perspective of the program's defined behavior, prior to any compilation or execution transformations. For instance, in languages like , program order encompasses accesses to non-volatile variables, volatile-qualified objects (treated as having side effects), and pointer dereferences, all governed by rules that prevent undefined behavior through sequenced evaluations. The evaluation of expressions in C establishes program order through sequence points, which demarcate moments where all prior side effects (including memory writes and volatile reads) must be complete before subsequent evaluations begin. Contrary to common misconception, C does not mandate left-to-right evaluation for most operator operands or function arguments; instead, their order is unspecified unless explicitly sequenced, but side effects from modifications (e.g., increments like i++) are ordered relative to sequence points to avoid . For example, in the expression a[i++] = i;, the increment of i as a side effect must occur before the assignment if separated by a sequence point, but simultaneous modifications to the same object without sequencing lead to , as per C11's rules on unsequenced side effects. Sequence points occur at the end of full expressions (e.g., after semicolons), after the first operand of the ,, logical operators && and || (with short-circuiting implying left-to-right for the latter), and the ?:. Function calls introduce strict sequencing in program order, creating a sequence point after the evaluation of all arguments and the designator but before the actual call, ensuring that any operations in the arguments complete prior to those within the body. This sequencing holds regardless of whether the is inline or not; for inline functions, the may substitute the body, but the standard requires the semantics to mimic a call, preserving the implied order of side effects and accesses. Non-inline calls further enforce of modifications across units, as the called 's effects are sequenced after the call site. An example is f(a++); g();, where the post-increment of a completes before f executes, and g is sequenced after the entire call to f, establishing a clear chain of operation orders. Pointer expressions in source code imply program order through dereferences that access memory locations, subject to aliasing assumptions defined by the language standard to ensure defined behavior. In C11, the strict aliasing rule (6.5p7) mandates that an object's stored value can only be accessed via an lvalue of a compatible type, its signed/unsigned variant, a qualified version, an aggregate containing it, or a character type; violations, such as dereferencing a float* to read an int object, result in undefined behavior. This rule assumes in the source that pointers of incompatible types do not alias the same memory, allowing the program to rely on type-safe access sequences without overlapping interpretations that could disrupt order. For instance, in code like int x = 1; float* p = (float*)&x; *p = 2.0f;, the assignment violates strict aliasing, rendering the subsequent order of x's value undefined, even if the source appears sequential. The standard formalizes these aspects in section 6.5 Expressions, with sequence points detailed in 5.1.2.3p3-6, stating that "In a full expression, the side effects of previous evaluations shall be complete before the next evaluation begins," and evaluations between sequence points are unsequenced, potentially leading to if side effects overlap on the same object. This framework ensures that program order in provides a reliable baseline for memory access sequencing, though compilers may later reorder operations as long as single-threaded observable behavior matches this order.

Compiler Reorderings and Optimizations

Compilers perform various optimizations during the process that can rearrange operations to improve , such as reducing the number of instructions or minimizing accesses, while adhering to the language's semantics for single-threaded execution. These reorderings are permissible under the as-if , which allows transformations that do not alter the observable behavior of the program as if it were executed without optimizations. Specifically, in C++, the may reorder loads and stores as long as the final values of non-volatile variables and the sequence of side effects match what would occur in the unoptimized code for a single thread. This enables aggressive optimizations but requires programmers to use explicit mechanisms like memory barriers for multi-threaded correctness. Common compiler optimizations that affect memory ordering include scalar replacement of aggregates (SRA), common subexpression elimination (CSE), and loop-invariant code motion (LICM). SRA replaces references to aggregate types, such as structures or arrays, with scalar variables that can be kept in registers, thereby eliminating or deferring memory stores and loads that would otherwise occur for the entire aggregate. This can reorder memory accesses by promoting parts of the aggregate to registers early, avoiding unnecessary spills to memory. CSE identifies and eliminates duplicate computations, including redundant memory loads from the same location, which reduces the number of memory operations and may reorder independent accesses to exploit parallelism in instruction scheduling. LICM hoists expressions that do not change within a loop body outside the loop, potentially moving memory loads or stores to earlier points in the program, thus altering their relative order with respect to other operations outside the loop. These transformations build upon the source program's sequential order but deviate from it to enhance efficiency, assuming no interdependencies that would violate the as-if rule. Aliasing complications further enable these optimizations through the strict rule, which assumes that pointers of different types do not the same memory location unless explicitly allowed (e.g., via pointers). This permits the to treat accesses through incompatible types as independent, allowing more aggressive reordering and elimination of memory operations. Violations, such as via incompatible pointer casts, result in , where the may produce incorrect code or eliminate accesses entirely. For instance, casting a pointer to reinterpret underlying bits as a different type without using a intermediary invokes , freeing the to assume no overlap and optimize accordingly. In multi-threaded contexts, these optimizations preserve ordering only within a single thread's as-if execution, but they may reorder operations across threads unless constrained by synchronization primitives like atomics or barriers. Non-aliased local variables see their intra-thread order maintained, but independent stores and loads from different threads can be rearranged relative to each other without explicit ordering guarantees, potentially leading to unexpected in concurrent code. For example, consider the following C++ :
cpp
int x = 0;
int y = 0;
x = 1;  // Store 1
y = 2;  // Store 2, [independent](/page/Independent) of x
With optimizations enabled (e.g., -O2), the might emit where the store to y precedes the store to x, as the operations are in single-threaded execution:
assembly
movl $0, -8(%rbp)  # y = 0 (irrelevant)
movl $2, -8(%rbp)  # y = 2
movl $1, -4(%rbp)  # x = 1 (reordered after)
This reordering is valid under the as-if rule for single-threaded programs but could cause one thread to observe y=2 before x=1 in a multi-threaded without barriers. To prevent such reorderings, programmers must insert compile-time barriers or use operations with appropriate memory orders, as defined in the memory model.

Compile-Time Memory Barriers

Compile-time memory barriers are compiler directives or intrinsics that instruct the to preserve specific ordering constraints in the generated , preventing optimizations that reorder memory accesses across the barrier. These barriers ensure that the relative in the source code is maintained in the output , without necessarily affecting hardware-level reordering. They are essential in concurrent programming to avoid data races caused by aggressive compiler optimizations, such as or that might move loads and stores. The primary types of compile-time memory barriers include full fences, one-way barriers, and relaxed variants. A full , such as that provided by memory_order_seq_cst in C++11, prevents all types of reorderings—loads, stores, and their combinations—across the barrier in both directions, enforcing a for sequentially consistent operations across threads. One-way barriers consist of semantics, which apply to load operations and prevent subsequent memory accesses in the same thread from being reordered before the point (establishing a #LoadLoad and #LoadStore barrier), and release semantics, which apply to store operations and prevent prior memory accesses from being reordered after the release point (establishing a #StoreStore and #LoadStore barrier). Combined acquire-release operations synchronize a release in one thread with an in another, ensuring visibility of writes without requiring a full cross-thread . Relaxed ordering, via memory_order_relaxed, provides no reordering guarantees beyond atomicity, allowing the compiler maximum freedom for optimization. Implementation of these barriers typically involves intrinsics or inline assembly that act as no-op instructions or fences in the assembly output, signaling the to halt reordering at that point. In , the __sync_synchronize() intrinsic issues a full , preventing the from moving any memory operands across it and also inhibiting processor speculation on loads or store queuing. These legacy __sync builtins expand to equivalent __atomic operations using by default, though modern code favors the latter for finer control. Similarly, in C++11 and C11, explicit fences can be inserted using std::atomic_thread_fence or atomic_thread_fence, which enforce the specified memory order without an associated operation, affecting both scheduling and (via code generation) runtime visibility. Language support for compile-time barriers is standardized in and through atomic operations in <stdatomic.h> and <atomic>, respectively, where memory orders are specified as arguments to loads, stores, and fences. For instance, std::atomic<T>::load(std::memory_order_acquire) prevents the compiler from reordering subsequent accesses before the load, mapping to an acquire barrier in the generated code. These orders translate to compiler barriers that inhibit transformations like moving a store after a load; on strongly ordered architectures like x86, acquire/release may compile to plain instructions without extra fences, relying on the hardware model, while on weaker models like , they generate explicit barrier instructions. In , analogous semantics apply to functions like atomic_load_explicit with memory_order_release, ensuring the compiler respects the order for non-atomic accesses synchronized via the fence. A practical usage example is implementing a to protect shared , where barriers prevent races by enforcing of the lock state. The following C++ code demonstrates a basic using std::atomic_flag, with acquire semantics on to block reordering of the lock check with subsequent accesses, and release on clear to ensure writes become visible:
cpp
#include <atomic>

[class](/page/Class) Spinlock {
    std::atomic_flag flag_ = ATOMIC_FLAG_INIT;
public:
    void lock() noexcept {
        while (flag_.test_and_set(std::memory_order_acquire)) {
            // Spin until lock is [free](/page/Free)
        }
    }
    void unlock() noexcept {
        flag_.clear(std::memory_order_release);
    }
};
Here, the ensures that once the lock is obtained, no prior loads or stores in other threads are reordered after the releasing unlock, fixing potential races in the . Despite their utility, compile-time memory barriers have limitations: they solely constrain the compiler's output and do not inherently enforce hardware-level ordering in multiprocessor environments, requiring mechanisms like CPU fences for full across cores. Overuse can degrade performance by inhibiting optimizations, so they are typically paired with relaxed atomics where possible.

Runtime Memory Ordering

Hardware Ordering in Multiprocessor Systems

In symmetric multiprocessing (SMP) systems, multiple identical processors connect to a single shared main memory, forming the foundation for multi-core CPUs that execute parallel workloads efficiently. These systems incorporate multi-level cache hierarchies, with private per-core L1 caches for low-latency access and shared last-level caches (LLC) to reduce contention and bandwidth pressure on main memory. However, in non-uniform memory access (NUMA) extensions of SMP—common in large-scale multi-socket configurations—memory is partitioned across nodes, resulting in asymmetric access latencies that exacerbate coherence overhead and limit scalability as the number of cores increases. Processor-specific memory models dictate the inherent ordering of loads and stores in multiprocessor environments. The x86 architecture implements Total Store Order (TSO), buffering stores in per-processor write queues to decouple execution from commit, while prohibiting loads from reordering past earlier stores to the same , thereby guaranteeing a global on writes visible across processors. ARM processors, by contrast, adopt a weaker relaxed model that permits extensive reorderings, such as loads overtaking stores or independent operations across threads appearing out of program order, to enable aggressive hardware optimizations like without guarantees. Cache coherence protocols underpin data consistency in these shared-memory hierarchies by managing cache line states and coordinating updates. The , used in systems like certain implementations, defines four states—Modified (dirty data unique to one cache), Exclusive (clean data unique to one cache), Shared (clean data in multiple caches), and Invalid (unusable)—ensuring that writes invalidate remote copies and reads fetch the latest value, thus maintaining the single-writer-multiple-reader invariant. , prevalent in most ARM multiprocessors, adds an Owned state for dirty data that can be shared without immediate write-back to memory, optimizing bandwidth by allowing one cache to supply data to others directly. For small systems, snoop-based protocols broadcast coherence traffic over a shared interconnect, enabling caches to eavesdrop and respond locally; in large NUMA systems, directory-based protocols replace broadcasts with a distributed directory tracking line owners and sharers, minimizing traffic but introducing indirection latency. Out-of-order execution in superscalar processors enhances by speculatively completing operations based on data availability rather than program order, yet preserves correct memory semantics through dedicated structures. Store buffers act as a "future file" for retired stores, queuing them until all preceding instructions complete to enforce in-order commits to the memory system, while load queues (often integrated with reservation stations) facilitate address speculation, checks, and forwarding from the store buffer to avoid false dependencies and maintain load-store ordering. The evolution of hardware ordering reflects a progression from strict models in early 1990s RISC designs, which prioritized programmer intuition with sequential consistency at the cost of performance, to relaxed models in the 2000s driven by power and throughput demands, as seen in x86's adoption of TSO via write buffering and ARM/POWER's permission of broader reorderings for speculative hardware. By 2025, RISC-V's RVWMO (RISC-V Weak Memory Ordering) standard—ratified in version 2.0—offers a configurable weak model akin to ARM, where ISA extensions like atomic memory operations (AMOs) and fences allow tailoring of ordering relaxations for diverse multiprocessor implementations while ensuring release consistency for synchronized accesses.

Hardware Memory Barriers

Hardware memory barriers are low-level instructions provided by architectures to enforce specific ordering constraints on memory operations across cores, caches, and protocols, overriding the default relaxed ordering in modern multiprocessor systems. These barriers ensure that loads and stores preceding the barrier complete before subsequent operations, preventing reordering that could lead to incorrect program execution in concurrent environments. In x86 architectures, common barrier types include LFENCE, which serializes all load operations to ensure no subsequent loads bypass prior ones; SFENCE, which orders all store operations so that no later stores precede earlier ones; and MFENCE, a full bidirectional fence that enforces ordering for both loads and stores across the barrier. These instructions interact with the processor's store buffer and load queue, flushing pending stores to the cache and invalidating speculative loads as needed to maintain coherence. On ARM architectures, DMB (Data Memory Barrier) enforces ordering of data accesses across the barrier without necessarily completing them to the system level, DSB (Data Synchronization Barrier) ensures all prior memory operations complete before subsequent ones proceed, and ISB (Instruction Synchronization Barrier) flushes the instruction pipeline to synchronize instruction fetches with prior data changes. PowerPC provides SYNC for a heavyweight synchronization that orders all memory operations and waits for acknowledgment across the system bus, while LWSYNC offers a lighter variant that synchronizes loads and stores without full bus serialization, suitable for release-acquire patterns. In RISC-V, FENCE instructions specify predecessor and successor sets of memory operations (e.g., I for input, O for output, R for reads, W for writes), guaranteeing that no operation in the successor set is visible to other harts until all predecessors complete, with variants like FENCE.I for instruction fetch synchronization. These barriers often interact with virtual memory translation lookaside buffers (TLBs) by invalidating relevant entries or signaling coherence protocols to ensure consistent address mappings across cores. Implementation of hardware barriers typically involves draining the processor's store buffer to push pending writes to the , flushing invalidation queues to propagate messages, and stalling the execution until these operations resolve, which can incur costs of 10 to 100 cycles on modern CPUs depending on the barrier type and system load. For instance, MFENCE on x86 may require of the memory subsystem, potentially blocking until all prior stores are globally visible. In heterogeneous systems, barriers must also coordinate with interconnect fabrics to maintain between CPU and GPU domains. A representative example is the use of barriers in a single-producer single-consumer implemented in x86 assembly, where the producer writes followed by an SFENCE to ensure the is visible before updating a flag, and the consumer uses an LFENCE after checking the flag to guarantee ordered reads of the :
; Producer side (simplified)
[mov](/page/MOV) [data], rax    ; Write [data](/page/Data)
sfence             ; Ensure [store](/page/Store) to [data](/page/Data) is committed
[mov](/page/MOV) [flag], 1      ; Signal ready

; Consumer side (simplified)
[mov](/page/MOV) rcx, [flag]    ; Read flag
lfence             ; Ensure prior loads don't reorder past this
[mov](/page/MOV) rbx, [data]    ; Read [data](/page/Data)
This prevents the from seeing the update before the write. Inserting such barriers in memory-intensive benchmarks like can reduce measured by 5-20% due to overhead, as the barriers the steady-state streaming of loads and stores. By 2025, advances in hardware barriers have focused on integration with , such as in AMD's MI300A , where Fabric enables coherent memory access between CPU and GPU cores, with barriers like DSB ensuring ordering across the fabric without explicit software intervention for unified address spaces.

Compiler-Hardware Interactions

Compilers play a crucial role in translating high-level memory ordering semantics from languages like C++ into architecture-specific that respects the target hardware's . For instance, the C++ std::memory_order_relaxed for atomic operations, which provides no or ordering guarantees beyond atomicity, typically compiles to plain load and store instructions on x86 architectures due to their Total Store Order (TSO) model, where stores are naturally ordered relative to subsequent loads from the same processor. In contrast, on architectures with weaker ordering, memory_order_relaxed typically maps to basic load and store instructions without additional barriers, relying solely on the hardware's provision of atomicity for aligned accesses. This mapping ensures that the compiler leverages hardware strengths while adhering to the C++ memory model, avoiding unnecessary overhead on stronger-ordered platforms like x86. To bridge compile-time semantics with runtime hardware instructions, compilers like GCC and Clang provide intrinsic functions that directly emit architecture-specific assembly. The __atomic_thread_fence builtin, for example, enforces thread-wide memory ordering and translates to an mfence instruction on x86 for sequential consistency (__ATOMIC_SEQ_CST), which serializes all loads and stores. On ARM, the same builtin generates a dmb (data memory barrier) instruction with appropriate options (e.g., full system or inner shareable domain) to achieve the desired ordering, ensuring visibility across cores without affecting intra-thread execution. These intrinsics allow developers to insert precise fences while permitting the compiler to optimize surrounding code, and they support all C11/C++11 memory orders, falling back to locking if lock-free operations are unavailable on the target. During optimization passes, compilers analyze and refine barriers to improve without violating semantics, particularly by eliminating redundant or "dead" barriers based on the architecture's model. In , passes such as AtomicExpand and InstCombine detect and remove unnecessary fences when the hardware provides sufficient ordering, such as omitting acquire/release barriers on x86 where loads and stores inherently satisfy those semantics. employs similar techniques in its atomic expansion and simplification phases, inserting barriers only where required for weaker architectures like while pruning them on TSO-compliant ones to reduce instruction count. Cross-compilation poses challenges here, as optimizers must account for varying models; for example, a barrier deemed dead on x86 might be essential on , requiring target-specific flags or conditional code generation to maintain correctness across platforms. Verification of these compiler-hardware interactions ensures that generated code upholds intended ordering, using dynamic tools like ThreadSanitizer (TSan) to detect violations such as data races arising from improper barrier placement or reordering. TSan instruments code at and to track accesses, reporting anomalies like unsynchronized shared variable reads/writes that could stem from mismatched ordering semantics between compiler output and hardware execution. For stronger guarantees, verify correctness through mathematical proofs; tools like COATCheck model the hardware-OS interface to confirm that compiler-generated barriers align with specifications, preventing subtle inconsistencies in multiprocessor environments. These approaches, often implemented in proof assistants like , have been applied to subsets of and to prove semantic preservation across optimizations. Modern architectures introduce further complexities in compiler-hardware interactions, particularly with variable memory models in designs like ARMv8-A and . ARMv8-A's weakly ordered model requires compilers to insert explicit barriers (e.g., dmb sy for ) for operations that might otherwise reorder freely, with optimizations carefully preserving single-copy atomicity and multi-copy atomicity for loads. 's RVWMO (RISC-V Weak Memory Order) similarly demands targeted fence insertion, such as fence iorw,iorw for full barriers, while its optional Ztso extension allows TSO-like optimizations akin to x86; compilers must detect and adapt via attributes to avoid over- or under-fencing. In 2025, AI accelerators exacerbate these issues with custom interactions, as seen in Apple's series blending weak ordering with TSO modes for efficient tensor operations, prompting compilers to generate hybrid code paths that balance low-latency with coherent multi-accelerator . Emerging trends in custom ASICs for AI, such as those integrating high-bandwidth memory (HBM) with relaxed ordering for massive parallelism, require extended intrinsics and verification to handle non-standard models without sacrificing portability.

References

  1. [1]
    [PDF] A Primer on Memory Consistency and Cache Coherence, Second ...
    Synthesis Lectures on Computer Architecture publishes 50- to 100-page publications on topics pertaining to the science and art of designing, analyzing, ...
  2. [2]
    How to Make a Multiprocessor Computer That Correctly Executes ...
    How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs. Abstract: Many large sequential computers execute operations in a different ...
  3. [3]
    Weak ordering—a new definition - ACM Digital Library
    This model guarantees that all memory accesses will appear to execute atomically and in program order.
  4. [4]
    [PDF] Memory Consistency Models for Shared-Memory Multiprocessors
    The memory consistency model for a shared-memory multiprocessor specifies the behavior of memory with respect to read and write operations from multiple ...
  5. [5]
    [PDF] How to Make a Correct Multiprocess Program Execute Correctly on ...
    [10] Leslie Lamport. How to make a multiprocessor computer that correctly executes multiprocess programs. IEEE Transactions on Computers, C-. 28(9):690–691 ...
  6. [6]
    [PDF] Shared Memory Consistency Models: A Tutorial
    This tutorial describes memory consistency models, which are formal specifications of memory semantics needed for correct and efficient programming in shared ...
  7. [7]
    [PDF] Shared Memory Consistency Models: A Tutorial - Computer
    Shared memory consistency models ensure correct execution by defining shared memory semantics, affecting performance, programmability, and portability. Lack of ...
  8. [8]
    a rigorous and usable programmer's model for x86 multiprocessors ...
    x86-TSO is a new, mathematically precise programmer's model for x86 multiprocessors, designed to be intuitive and avoid issues with existing specifications.
  9. [9]
    The "Double-Checked Locking is Broken" Declaration
    Making it work with explicit memory barriers. It is possible to make the double checked locking pattern work if you have explicit memory barrier instructions.
  10. [10]
    Memory Model - Arm Developer
    The Armv8-A architecture employs a weakly ordered model of memory. This means that the order of memory accesses is not necessarily required to be the same ...
  11. [11]
  12. [12]
  13. [13]
    Optimize Options (Using the GNU Compiler Collection (GCC))
    Perform scalar replacement of aggregates. This pass replaces structure references with scalars to prevent committing structures to memory too early. This ...
  14. [14]
    [basic.lval]
    ### Summary of Strict Aliasing and Type Punning from C++ Draft (basic.lval)
  15. [15]
    Memory Ordering at Compile Time
    ### Summary of Memory Ordering at Compile Time (C++ Context)
  16. [16]
  17. [17]
    Acquire and Release Semantics
    **Summary of Acquire/Release Semantics from https://preshing.com/20120913/acquire-and-release-semantics:**
  18. [18]
    __sync Builtins (Using the GNU Compiler Collection (GCC))
    __sync_synchronize (...) ¶. This built-in function issues a full memory barrier. Built-in Function: type __sync_lock_test_and_set ( type * ptr , type value ...
  19. [19]
    atomic_thread_fence - cppreference.com
    ### Summary of `atomic_thread_fence` and `memory_order` in C11
  20. [20]
    memory_order - cppreference.com
    Sep 4, 2024 · memory_order specifies how memory accesses, including regular, non-atomic memory accesses, are to be ordered around an atomic operation.
  21. [21]
  22. [22]
    [PDF] A better x86 memory model: x86-TSO (extended version)
    Mar 25, 2009 · Stores are not reordered with other stores”): “P10. Writes by a single processor are observed in the same order by all processors”. This ...
  23. [23]
    [PDF] A Tutorial Introduction to the ARM and POWER Relaxed Memory ...
    ARM and IBM POWER multiprocessors have highly relaxed memory models: they make use of a range of hardware optimisations that do not affect the observable ...
  24. [24]
    MESI and MOESI protocols - Arm Developer
    There are a number of standard ways by which cache coherency schemes can operate. Most ARM processors use the MOESI protocol, while the Cortex-A9 uses the MESI ...
  25. [25]
    [PDF] Lecture 18: Snooping vs. Directory Based Coherency
    Individual Cache Block in a. Directory Based System. • States identical to snoopy ... – Large cache size to combat higher memory latencies than snoop caches ...
  26. [26]
    [PDF] Lecture 9: “Modern Superscalar Out-of-Order Processors”
    Sep 27, 2017 · Store buffer is “future file” for memory. Store is consider completed. Latency beyond this point has little effect on the processor throughput.
  27. [27]
    Hardware Memory Models (Memory Models, Part 1) - research!rsc
    Jun 29, 2021 · It appears that all Intel processors did implement x86-TSO from the start, even though it took a decade for Intel to decide to commit to that.
  28. [28]
    [PDF] RISC-V Memory Consistency Model Tutorial
    May 7, 2018 · RISC-V MEMORY MODEL SPECIFICATION. • Chapter 6: RISC-V Weak Memory Ordering (“RVWMO”). • Chapter 20: “Zam” Std. Extension for Misaligned AMOs.
  29. [29]
  30. [30]
    STREAM Benchmark Reference Information - Computer Science
    As this progresses, more and more programs will be limited in performance by the memory bandwidth of the system, rather than by the computational performance of ...Missing: impact barriers
  31. [31]
    MI300A - Exploring the APU advantage - ROCm™ Blogs - AMD
    Feb 9, 2025 · The MI300A implements the unified memory model by copackaging the CPU and GPU cores and directly attaching the CPU cores to the GPU's infinity ...
  32. [32]
    LLVM Atomic Instructions and Concurrency Guide
    The precise fences required varies widely by architecture, but for a simple implementation, most architectures provide a barrier which is strong enough for ...
  33. [33]
    __atomic Builtins (Using the GNU Compiler Collection (GCC))
    The ' __atomic ' builtins can be used with any integral scalar or pointer type that is 1, 2, 4, or 8 bytes in length. 16-byte integral types are also allowed.
  34. [34]
    LLVM's Analysis and Transform Passes
    The well-known scalar replacement of aggregates transformation. This transform breaks up alloca instructions of aggregate type (structure or array) into ...<|separator|>
  35. [35]
    ThreadSanitizer — Clang 22.0.0git documentation - LLVM
    ThreadSanitizer is a tool that detects data races. It consists of a compiler instrumentation module and a run-time library.
  36. [36]
    [PDF] COATCheck: Verifying Memory Ordering at the Hardware-OS Interface
    Our core analysis infrastructure is written in Coq to al- low for formal verification [44]. However, we have not yet completed any formal proofs of correctness; ...
  37. [37]
    [PDF] Progressive Automated Formal Verification of Memory Consistency ...
    Verification using formal methods can provide strong correctness guarantees based on mathematical proofs, and is an excellent fit for MCM verification.
  38. [38]
    Analyzing the memory ordering models of the Apple M1
    While complicating the programming model, ARM's weaker memory ordering allows processors to reorder instructions more freely, potentially reducing ...
  39. [39]
    Tech Forum 2025: ASICs, packaging, and HBM reshape the AI chip ...
    Sep 24, 2025 · The result is a clear shift: high-end AI chips will no longer depend overwhelmingly on TSMC's packaging lines. Memory costs surge with HBM4. On ...