[[sec:alloy]] == Formal Axiomatic Specification in Alloy We present a formal specification of the RVWMO memory model in Alloy (https://alloytools.org/). This model is available online at https://github.com/daniellustig/riscv-memory-model. The online material also contains some litmus tests and some examples of how Alloy can be used to model check some of the mappings in Section #sec:memory:porting[[sec:memory:porting]]. ` ` .... //////////////////////////////////////////////////////////////////////////////// // =RVWMO PPO= // Preserved Program Order fun ppo : Event->Event { // same-address ordering po_loc :> Store + rdw + (AMO + StoreConditional) <: rfi // explicit synchronization + ppo_fence + Acquire <: ^po :> MemoryEvent + MemoryEvent <: ^po :> Release + RCsc <: ^po :> RCsc + pair // syntactic dependencies + addrdep + datadep + ctrldep :> Store // pipeline dependencies + (addrdep+datadep).rfi + addrdep.^po :> Store } // the global memory order respects preserved program order fact { ppo in ^gmo } .... ` ` .... //////////////////////////////////////////////////////////////////////////////// // =RVWMO axioms= // Load Value Axiom fun candidates[r: MemoryEvent] : set MemoryEvent { (r.~^gmo & Store & same_addr[r]) // writes preceding r in gmo + (r.^~po & Store & same_addr[r]) // writes preceding r in po } fun latest_among[s: set Event] : Event { s - s.~^gmo } pred LoadValue { all w: Store | all r: Load | w->r in rf <=> w = latest_among[candidates[r]] } // Atomicity Axiom pred Atomicity { all r: Store.~pair | // starting from the lr, no x: Store & same_addr[r] | // there is no store x to the same addr x not in same_hart[r] // such that x is from a different hart, and x in r.~rf.^gmo // x follows (the store r reads from) in gmo, and r.pair in x.^gmo // and r follows x in gmo } // Progress Axiom implicit: Alloy only considers finite executions pred RISCV_mm { LoadValue and Atomicity /* and Progress */ } .... ` ` .... //////////////////////////////////////////////////////////////////////////////// // Basic model of memory sig Hart { // hardware thread start : one Event } sig Address {} abstract sig Event { po: lone Event // program order } abstract sig MemoryEvent extends Event { address: one Address, acquireRCpc: lone MemoryEvent, acquireRCsc: lone MemoryEvent, releaseRCpc: lone MemoryEvent, releaseRCsc: lone MemoryEvent, addrdep: set MemoryEvent, ctrldep: set Event, datadep: set MemoryEvent, gmo: set MemoryEvent, // global memory order rf: set MemoryEvent } sig LoadNormal extends MemoryEvent {} // l{b|h|w|d} sig LoadReserve extends MemoryEvent { // lr pair: lone StoreConditional } sig StoreNormal extends MemoryEvent {} // s{b|h|w|d} // all StoreConditionals in the model are assumed to be successful sig StoreConditional extends MemoryEvent {} // sc sig AMO extends MemoryEvent {} // amo sig NOP extends Event {} fun Load : Event { LoadNormal + LoadReserve + AMO } fun Store : Event { StoreNormal + StoreConditional + AMO } sig Fence extends Event { pr: lone Fence, // opcode bit pw: lone Fence, // opcode bit sr: lone Fence, // opcode bit sw: lone Fence // opcode bit } sig FenceTSO extends Fence {} /* Alloy encoding detail: opcode bits are either set (encoded, e.g., * as f.pr in iden) or unset (f.pr not in iden). The bits cannot be used for * anything else */ fact { pr + pw + sr + sw in iden } // likewise for ordering annotations fact { acquireRCpc + acquireRCsc + releaseRCpc + releaseRCsc in iden } // don't try to encode FenceTSO via pr/pw/sr/sw; just use it as-is fact { no FenceTSO.(pr + pw + sr + sw) } .... ` ` .... //////////////////////////////////////////////////////////////////////////////// // =Basic model rules= // Ordering annotation groups fun Acquire : MemoryEvent { MemoryEvent.acquireRCpc + MemoryEvent.acquireRCsc } fun Release : MemoryEvent { MemoryEvent.releaseRCpc + MemoryEvent.releaseRCsc } fun RCpc : MemoryEvent { MemoryEvent.acquireRCpc + MemoryEvent.releaseRCpc } fun RCsc : MemoryEvent { MemoryEvent.acquireRCsc + MemoryEvent.releaseRCsc } // There is no such thing as store-acquire or load-release, unless it's both fact { Load & Release in Acquire } fact { Store & Acquire in Release } // FENCE PPO fun FencePRSR : Fence { Fence.(pr & sr) } fun FencePRSW : Fence { Fence.(pr & sw) } fun FencePWSR : Fence { Fence.(pw & sr) } fun FencePWSW : Fence { Fence.(pw & sw) } fun ppo_fence : MemoryEvent->MemoryEvent { (Load <: ^po :> FencePRSR).(^po :> Load) + (Load <: ^po :> FencePRSW).(^po :> Store) + (Store <: ^po :> FencePWSR).(^po :> Load) + (Store <: ^po :> FencePWSW).(^po :> Store) + (Load <: ^po :> FenceTSO) .(^po :> MemoryEvent) + (Store <: ^po :> FenceTSO) .(^po :> Store) } // auxiliary definitions fun po_loc : Event->Event { ^po & address.~address } fun same_hart[e: Event] : set Event { e + e.^~po + e.^po } fun same_addr[e: Event] : set Event { e.address.~address } // initial stores fun NonInit : set Event { Hart.start.*po } fun Init : set Event { Event - NonInit } fact { Init in StoreNormal } fact { Init->(MemoryEvent & NonInit) in ^gmo } fact { all e: NonInit | one e.*~po.~start } // each event is in exactly one hart fact { all a: Address | one Init & a.~address } // one init store per address fact { no Init <: po and no po :> Init } .... ` ` .... // po fact { acyclic[po] } // gmo fact { total[^gmo, MemoryEvent] } // gmo is a total order over all MemoryEvents //rf fact { rf.~rf in iden } // each read returns the value of only one write fact { rf in Store <: address.~address :> Load } fun rfi : MemoryEvent->MemoryEvent { rf & (*po + *~po) } //dep fact { no StoreNormal <: (addrdep + ctrldep + datadep) } fact { addrdep + ctrldep + datadep + pair in ^po } fact { datadep in datadep :> Store } fact { ctrldep.*po in ctrldep } fact { no pair & (^po :> (LoadReserve + StoreConditional)).^po } fact { StoreConditional in LoadReserve.pair } // assume all SCs succeed // rdw fun rdw : Event->Event { (Load <: po_loc :> Load) // start with all same_address load-load pairs, - (~rf.rf) // subtract pairs that read from the same store, - (po_loc.rfi) // and subtract out "fri-rfi" patterns } // filter out redundant instances and/or visualizations fact { no gmo & gmo.gmo } // keep the visualization uncluttered fact { all a: Address | some a.~address } //////////////////////////////////////////////////////////////////////////////// // =Optional: opcode encoding restrictions= // the list of blessed fences fact { Fence in Fence.pr.sr + Fence.pw.sw + Fence.pr.pw.sw + Fence.pr.sr.sw + FenceTSO + Fence.pr.pw.sr.sw } pred restrict_to_current_encodings { no (LoadNormal + StoreNormal) & (Acquire + Release) } //////////////////////////////////////////////////////////////////////////////// // =Alloy shortcuts= pred acyclic[rel: Event->Event] { no iden & ^rel } pred total[rel: Event->Event, bag: Event] { all disj e, e': bag | e->e' in rel + ~rel acyclic[rel] } ....