Skip to content
This repository was archived by the owner on Jan 25, 2022. It is now read-only.

Latest commit

 

History

History
31 lines (16 loc) · 6.73 KB

File metadata and controls

31 lines (16 loc) · 6.73 KB

The memory model provides precise semantics for shared memory events, but it does so without specifying what the agent-order relation of an agent in an execution may be. Yet the agent-order may plausibly vary as a result of the transformations performed by the ECMAScript implementation and the hardware the program is running on. That is obviously the case if were to consider a single agent running in isolation. In the single-agent case, all semantics-preserving transformations are allowed, but, being semantics-preserving, they are not observable to the agent.

The question arises as to which transformations that are allowed in a multi-agent setting, where transformations that affect shared memory writes are observable by other agents. An agent that writes 0 to x and then writes 1 to y can be transformed in a single-agent setting to one that writes 1 to y and then writes 0 to x. But is that a legal transformation if those variables are in shared memory and another agent might observe the order of writes? The transformation would affect the agent-order locally and the happens-before relation globally. And does the answer depend on whether x and y are written with "SeqCst" operations or not?

It is desirable to allow most program transformations that are valid in a single-agent setting in a multi-agent setting, to ensure that the performance of each agent in a multi-agent program is as good as it would be in a single-agent setting. (There seems to be broad agreement in the programming language design community that that is the right trade-off.) But "most" does not mean "all". In the example above, if x or y (or both) were intended to be "SeqCst" writes, and the writes were reordered, not only might happens-before be affected, but also synchronizes-with and memory-order, and a program that before transformations was data-race-free and sequentially consistent might no longer be so. In contrast, if x and y were both "Unordered" then in a data-race-free program the change in write order would not be observable at all.

Furthermore, consider an agent that writes first 0 to x and then (significantly) later writes 1 to x (both writes "Unordered"). In a single-agent setting the first write can be removed provided that x will not be read until the second write has happened. In a multi-agent setting, another agent waiting for the value of x to become 0 might never stop waiting if the first write is removed.

Frequently these transformations are hard to judge. Can an "Unordered" load be moved out of a loop? Even if the loop's termination condition depends on the loaded value? The load is necessarily racy, but the memory model gives some meaning to races; can we depend on that meaning here?

With that background, we outline some rules about program transformations that are intended to be taken as normative but which are likely not exhaustive. These rules are "above" or "prior to" the memory model; they are intended to apply to program transformations that precede the introductions of the events that make up the agent-order.

Let an "agent-order slice" be the subset of the agent-order pertaining to a single agent.

The main rule is that any transformation of an agent-order slice that is valid in the absence of shared memory is valid in the presence of shared memory. The following exceptions to the main rule then apply:

  • Atomics are carved in stone: Program transformations must not cause the "SeqCst" events in an agent-order slice to be reordered with its "Unordered" operations, nor its "SeqCst" operations to be reordered with each other, nor may a program transformation remove a "SeqCst" operation from the agent-order. (In practice, the prohibition on reorderings forces a compiler to assume that every "SeqCst" operation is included in the final memory-order, which it would usually have to assume anyway in the absence of inter-agent program analysis. It also forces the compiler to assume that every call where the callee's effects on the memory-order are unknown may contain "SeqCst" operations.)

  • Reads must be stable: Any given shared memory read must only observe a single value. (For example, if what is semantically a single read in the program is executed multiple times then the program is subsequently allowed to observe only one of the values read. A transformation known as rematerialization can violate this rule.)

  • Writes must be stable: All observable writes to shared memory must follow from program semantics. (For example, a transformation may not introduce certain observable writes, such as by using read-modify-write operations on a larger location to write a smaller datum, writing a value to memory that the program could not have written, or writing a just-read value back to the location it was read from, if that location could have been overwritten by another agent after the read.)

  • Writes must become visible: If an agent writes to a shared memory location non-atomically then an "Unordered" event to the location must be inserted in the agent-order slice before the next "SeqCst" event is introduced by the agent, or before the agent's job ends if there is no next "SeqCst" event. (For example, writes may be moved and coalesced and sometimes reordered between two "SeqCst" operations, but the transformation may not remove every write that updates a location; some write must be preserved.)

(Spec draft note) It's probably desirable not to have the "Atomics are carved in stone" rule, but given that we try to give meaning to races, this rule gives us somewhere to stand in terms of the kinds of reorderings we can expect.

(Spec draft note) Some of the rules overlap in part with the rules of the memory model, but that seems OK: the effect of these rules is not just to add constraints beyond the memory model but equally to free the compiler from a having to interpret the memory model literally in terms of the events that have to be introduced.

The rules apply only to shared memory accesses, so an implementation that knows the program is not accessing shared memory will not be affected by them. In practice, the optimizations that are affected by the rules are of limited value, and a practical implementation could avoid those optimizations even for non-shared memory without significant impact.

Examples of transformations that remain valid (so long as they don't otherwise violate the memory model) are: merging multiple non-atomic reads from the same location, reordering non-atomic reads, introducing speculative non-atomic reads, merging multiple non-atomic writes to the same location, reordering non-atomic writes to different locations, and yes, hoisting non-atomic reads out of loops even if that affects termination. Note in general that aliased TypedArrays make it hard to prove that locations are different.