Relaxed Memory Model

The execution of a WebAssembly program gives rise to a trace of events. WebAssembly’s relaxed memory model constrains the observable behaviours of the program’s execution by defining a consistency condition on the trace of events.

Note

A relaxed memory model is necessary to describe the behaviour of programs exhibiting shared memory concurrency. WebAssembly’s relaxed memory model is heavily based on those of C/C++11 and JavaScript. The relaxed memory model described here is derived from the following article: 1.

Preliminary Definitions

time(act at timep time)=timetimep(act at timep time)=timeploc(rdord loc byte notears?)=locloc(wrord loc byte notears?)=locloc(rmw loc byte1 byte2)=locloc(wait loc s64)=locloc(woken loc)=locloc(timeout loc)=locloc(notify loc u32 u32)=locord(rdord loc byte notears?)=ordord(wrord loc byte notears?)=ordord(rmw loc byte1 byte2)=seqcstoverlap(act1,act2)=(range(act1)range(act2)ϵ)same(act1,act2)=(range(act1)=range(act2))reading(act)=(read(act)ϵ)writing(act)=(write(act)ϵ)susp(u32,wait reg[u32] s64)=wait reg[u32] s64susp(u32,woken reg[u32])=woken reg[u32]susp(u32,timeout reg[u32])=timeout reg[u32]susp(u32,notify reg[u32] u32 u32)=notify reg[u32] u32 u32susp(u32,act)=ϵ(otherwise)read(rdord loc byte notears?)=byteread(rmw loc byte1 byte2)=byte1read(act)=ϵ(otherwise)write(wrord loc byte notears?)=bytewrite(rmw loc byte1 byte2)=byte2write(act)=ϵ(otherwise)offset(act)=u32(if loc(act)=reg[u32])sync(act1,act2)=(same(act1,act2)ord(act1)=ord(act2)=seqcst)range(act)=[u32u32+n1](if loc(act)=reg[u32]n=max(|read(act)|,|write(act)|))tearfree(rdord loc byte)=(if ord=unordord=init)tearfree(wrord loc byte)=(if ord=unordord=init)tearfree(act)=(otherwise)id(act)=act

The above operations on actions are raised to operations on events, indexed by region.

funcreg(act1 act act2 at timep time)=func(act)(if loc(act)=reg[u32])funcreg(act1 act act2 at timep time,act3 act act4 at timep time)=func(act,act)(if loc(act)=loc(act)=reg[u32])

Traces

Todo

novel notation here?

A trace is a coinductive list of events. A trace is considered to be a pre-execution of a given global configuration if it represents the events emitted by the coinductive closure of the global reduction relation on that configuration, such that all of the trace’s consituent events have unique time stamps that are totally ordered according to the reduction order.

configevtconfigconfig:trevttr,time(evt)tottime(evt)time(evt)time(tr)timep(evt)timep(tr)config:evt tr

When a WebAssembly program is executed, all behaviours observed during that execution must correspond to a single consistent pre-execution of that execution’s starting configuration.

Consistency

reg, regtr consistent-withtr consistent
i,regitr suspensions-consistentevtRreadingreg(tr),evtW,trregevtR reads-each-from evtWevtI,evttr,ordreg(evtI)=initevtIevtoverlap(evtI,evt)evtIhbevtregtr consistent-with
|evtW|=|readreg(evtR)|i<|evtW|,trregievtR reads-from (evtW[i])regevtR no-tear evtWtrregevtR reads-each-from evtW
evtRevtWevtWwritingreg(tr)trregi,kevtR value-consistent evtWtrregkevtR hb-consistent evtWtrregevtR sc-last-visible evtWtrregievtR reads-from evtW
readreg(evtR)[i]=writereg(evtW)[j]k=offsetreg(evtR)+i=offsetreg(evtW)+jtrregi,kevtR value-consistent evtW
¬(evtRhbevtW)syncreg(evtW,evtR)evtWhbevtRevtWwritingreg(tr),evtWhbevtWhbevtRkrangereg(evtW)trregkevtR hb-consistent evtW
evtWwritingreg(tr),evtWhbevtRevtWtotevtWtotevtRsyncreg(evtW,evtR)¬syncreg(evtW,evtR)evtWhbevtWtotevtR¬syncreg(evtW,evtR)evtWtotevtWhbevtR¬syncreg(evtW,evtW)trregevtR sc-last-visible evtW
tearfreereg(evtR)|{evtWevtW | samereg(evtR,evtW)tearfreereg(evtW)}|1regevtR no-tear evtW
suspreg(i,tr)=trregitr suspensions-consistent-with(ϵ)evt,evttr, evttotevtevthbevtregitr suspensions-consistent
regiϵ suspensions-consistent-with(time)
idreg(evt)=(wait reg[i] s64)regitr suspensions-consistent-with(time(evt) time)regievt tr suspensions-consistent-with(time)
idreg(evt)=(timeout reg[i])regitr suspensions-consistent-with(time time)regievt tr suspensions-consistent-with(time timep(evt) time)
idregn(evtn)=(woken reg[i])idreg(evtN)=(notify reg[i] n k)n<km=0regitr suspensions-consistent-with(timem)regievtN evtn tr suspensions-consistent-with(timem timepn(evtn))

Note

The following is a non-normative and non-exhaustive explanation of WebAssembly’s relaxed memory model in plain English. Note that the definition of Consistency is the sole normative definition of the relaxed memory model.

When a WebAssembly operation reads from shared mutable state, the WebAssembly relaxed memory model determines the value that this read access observes, in terms of the write access to the same location(s) that have occurred in the execution.

The WebAssembly memory model is built around the concept of a happens-before transitive partial order between accesses of shared mutable state, hb, which captures a strong notion of causality. All sequential accesses in the same thread are related by hb according to execution order. Certain operations also establish a hb relation between operations of different threads (see atomic accesses below). A read access may never take its value from a write access that comes later in hb. Moreover, if two write accesses ordered by hb come before a read access in hb, the read access must take its value from the later of the two write accesses according to hb. In the case that hb does not uniquely determine a write access that a given read access must take its value from, the read access may non-deterministically take its value from any permitted write.

In the case that a read operation is a multi-byte memory access, the value of each byte may in certain circumstances be determined by a different write event. If this happens, we describe the read operation as tearing. In general, naturally aligned multi-byte reads are not allowed to tear, unless they race with a partially overlapping write or are greater than four bytes in width.

Most WebAssembly accesses of shared mutable state are classified as non-atomic. However a number of operations are classified as performing atomic accesses. Atomic accesses must always be naturally aligned. If an atomic read takes its value from an atomic write of the same width, the write access is fixed as coming before the read access in hb. This is the main mechanism by which a hb relation is established between threads.

WebAssembly’s atomic operations are also required to be sequentially consistent. The relaxed memory model defines a toal order on all events of the execution, tot, and sequentially consistent operations to identical ranges must respect this ordering - i.e. sequentially consistent reads cannot read from any sequentially consistent write of idential range other than the most recent preceding one according to tot.

Some operations such as memory accesses must perform a bounds check in addition to accessing data. The relaxed memory model treats these accesses as additionally accessing a distinguished length location, with the observed value respecting the constraints of the relaxed memory model. Most bounds checks are non-atomic, but bounds checks peformed during instantiation are atomic, and changes to the length (e.g. memory.grow) are modelled as atomic read-modify-write accesses.

In some circumstances, two accesses to overlapping locations may occur in an execution without any relation in hb. This situation is known as a race. If at least one of these accesses is a non-atomic write, we describe this situation as a data race. Unlike some other relaxed memory models, WebAssembly does not declare data races to be undefined behaviour. However, the allowed execution behaviours may still be highly non-deterministic as the lack of hb relations means that reads participating in or overlapping with the location of the data race may non-deterministically observe a number of different values.

The relaxed memory model also describes the concurrent behaviour of WebAssembly’s wait (memory.atomic.wait) and notify (memory.atomic.notify) operations. Each memory location is associated with a queue of waiting threads. A thread suspending as the result of a wait operation enters the queue, and a notify operation to that location will attempt to wake up as many threads as possible from the head of the associated queue, up to the maximum specified by the arguments of the notify operation. All operations on the same location which change the state of that location’s wait queue are sequentially consistent and totally ordered by hb.

1

The semantics of the relaxed memory model is derived from the following article: Conrad Watt, Andreas Rossberg, Jean Pichon-Pharabod. Weakening WebAssembly. Proceedings of the ACM on Programming Languages (OOPSLA 2019). ACM 2019.