Documentation

Core.GuardedPlusCal.Semantics.Denotational

The denotational semantics of Guarded PlusCal, as three relations per syntactic form: reducing (a step to a successor state, emitting a list of observable behaviors), aborting (a state from which the form goes wrong), and diverging (a state from which it runs forever).

They are plain definitions rather than Reduce/Abort/Diverge instances, so the ⟦·⟧*/⟦·⟧⊥/⟦·⟧∞ notations do not apply here. Those classes take their second argument as an outParam, and with the value type abstract it occurs only there — nothing in a Statement or an AtomicBranch mentions it — so Lean cannot order the synthesis of the ExprSemantics argument. Nothing needs the classes: StrongRefinement takes the relations as plain Sets. The instances can be registered later, against the concrete TLA⁺ value type.

Blocking and aborting are deliberately different: a statement with no reducing transition and no aborting state is a guard that is simply not enabled yet, which is what await, receive on an empty FIFO, and with x ∈ {} all are. That distinction is why ExprSemantics exposes isBool and isSet — without them a non-boolean guard would be indistinguishable from a false one.

The expression layer underneath is abstract (ComputableTLAPlus.ExprSemantics), to be refined to the real TLA⁺ semantics later; see that file's module doc.

Channels are kept out of the main memory, in a separate FIFOs map, so a local state is a variable memory beside a channel memory. An expression that reaches into a channel therefore has no meaning at all here rather than a wrong one, which is what lets the expression layer stay ignorant of channels entirely.

This file stops at AtomicBranch. Threads, processes and algorithms are above it: a thread has no denotation of its own, only the labels it owns, and the process and algorithm layers are defined by fixed points over a step relation. See Core/NetworkPlusCal/Semantics/Denotational.lean's Thread.labels.

Failure modes a well-formedness side condition could assume away are stated instead: a receive with a bad channel, an await on a non-boolean and a with x ∈ e on a non-set all abort here rather than blocking or being ruled out by assumption.

@[reducible, inline]
abbrev GuardedPlusCal.ChanKey (V : Type u) :

The key identifying a single FIFO: the channel's name together with its resolved index path. Only the name and the evaluated indices are observable, and this is also the shape a Behavior.send reports.

Equations
Instances For

    The name a process instance's own identity is bound to, matching Elaborator/PlusCal.lean's extend "self" .address.

    Equations
    Instances For
      inductive GuardedPlusCal.Behavior (V : Type u) :

      One of the possible observable behaviors exhibited by PlusCal statements. Every event carries the process instance that emitted it — the value bound to selfName in its memory — so that program order within one process is recoverable from a trace that interleaves several. Without it, two prints from different processes would be indistinguishable from two prints of the same process, and their relative order would stop being observable.

      send additionally carries the channel it pushes onto.

      Reception is not an event. The alphabet is exactly print and send, and a recv event would be unsound as an observation of the source program: Guarded2Network defers consumption to a .rx thread that pops the channel ahead of the block that uses the value, so a block whose guard never holds — l: receive(ch, x) ; await FALSE ; goto l' — pops a message in the target while the source block never reduces at all and so never emits anything. No relation up to reordering repairs that: the target event has no source counterpart to be reordered against. What ties the channel's contents to the target's inbox is the refinement invariant relatesTo, per channel.

      Instances For
        @[reducible, inline]
        abbrev GuardedPlusCal.Trace (V : Type u) :

        The trace alphabet: a possibly-infinite sequence of observable events. Monoid (Seq α) makes it the ordered monoid traces are composed in.

        Possibly-infinite and not List, even though no statement or block can emit an infinite trace — Statement.diverging is and a block is finite, so divergence enters only at Algebra (Semantics/Process.lean). A diverging algorithm that keeps sending emits forever, and a List cannot hold what it emits: with a finite trace type Algebra.diverging could only contain executions that fall silent after finitely many events, so every productive divergence would be absent from the denotation outright.

        The type is uniform across the statement, block and algorithm layers rather than finite below and infinite above, so no layer boundary carries a Seq.ofList coercion. Finiteness of the reducing and aborting traces is a derived property rather than a typing constraint: nothing downstream needs it, and no proof relies on cancellativity (Seq has none — an infinite left factor absorbs its right factor, mul_eq_left_of_not_terminates).

        Equations
        Instances For
          @[reducible, inline]
          abbrev GuardedPlusCal.FIFOs (V : Type u) :

          The global map containing FIFOs. Pushes go on the right, pops come off the left.

          Finmap for the reason Memory is one (Core/ComputableTLAPlus/Semantics/Interface.lean): key order is not observable, and letting it into the type turns commutation lemmas false. Updating a channel is insert rather than AList.replace — every rule below establishes F.lookup k = some _ before writing k, so the two agree wherever either is reached, and insert is the one with a usable lookup equation (= some v, not v <$ lookup k F).

          Equations
          Instances For
            @[reducible, inline]

            The local reduction state of an atomic block: the process's own memory, the channels, and a label field that is none while running and some l once a terminal goto has jumped to l. Statement b b' already tracks terminality syntactically and the reduction relations pin the label field's value (none on every source state, some _ only on a goto's target), so nothing is gained by also carrying it at the type level — see Statement.reducing below.

            There is deliberately no third component for with-bound temporaries. That an assignment does not target a block-local binder is a syntactic property, checked by WellFormedness; keeping it in the state would oblige every lemma to translate between two state shapes for no proof-side gain.

            Equations
            Instances For

              Named projections. LocalState is a nested anonymous product, so its components are otherwise reachable only as σ.1/σ.2.1/σ.2.2 or by destructuring at every binding site. Named projections let a proof intro σₜ σₜ' ε σₛ with no pattern at all and reach components by name, destructuring only where it genuinely case-splits on the label.

              The memory component.

              Equations
              Instances For

                The FIFO component.

                Equations
                Instances For

                  The label component: none while running, some l once the block has jumped to l.

                  Equations
                  Instances For
                    @[simp]

                    Resolving one segment of a reference's access path against a memory: a field segment resolves to itself, an index expression to whatever it evaluates to.

                    Instances For

                      Some index expression in a reference's access path has no value. Field segments cannot fail, so only the .inr ones are considered.

                      Equations
                      Instances For

                        Reduction of statements #

                        test e v is the identity transition restricted to states that evaluate e to v.

                        Equations
                        • One or more equations did not get rendered due to their size.
                        Instances For

                          The identity transition, i.e. nothing is performed.

                          Equations
                          • One or more equations did not get rendered due to their size.
                          Instances For

                            No statement can diverge: every constructor of Statement is a single step. Divergence only enters at the block and process levels.

                            Equations
                            Instances For

                              The states from which a guard-class statement is blocked — enabled by nothing yet, as opposed to going wrong. Three cases: await on a boolean that is not TRUE, with x ∈ e on a (present but) empty set, and receive on a channel that resolves to an empty FIFO. Every execution statement blocks nowhere. The trace is 1: a blocked guard emits nothing.

                              Equations
                              Instances For

                                Reduction of blocks #

                                Generic over the index family and the state type: a block reduces by composing its elements' relations left to right, and aborts (or diverges) if any prefix reduces to a state from which the next element does. Nothing here mentions values, so these definitions are reused verbatim for NetworkPlusCal.

                                def GuardedPlusCal.Block.listReducing {α : BoolType} {β : Type u_1} {γ : Type u_2} [Monoid γ] (f : b : Bool⦄ → α bSet (β × γ × β)) (A : List (α false)) :
                                Set (β × γ × β)

                                A possibly-empty list of statements as a relation. The one recursion in this section: a block is this fold over its begin, composed with its last, and every equation about a block is a list equation underneath.

                                A Block is non-empty by construction while a pass can hand back an empty run of statements (Guarded2Network's consumption assignments, for a branch that receives nothing), so the list form has to exist in its own right — and being homogeneous in the guard index, it cannot express a block's possibly-terminal last. That is the whole difference between the two.

                                foldr, not foldl: every proof about one of these is an induction on the list.

                                Equations
                                Instances For
                                  def GuardedPlusCal.Block.listAborting {α : BoolType} {β : Type u_1} {γ : Type u_2} [Monoid γ] (g : b : Bool⦄ → α bSet (β × γ)) (f : b : Bool⦄ → α bSet (β × γ × β)) (A : List (α false)) :
                                  Set (β × γ)

                                  The list counterpart of Block.aborting — and of Block.diverging too. Those two are the same function, so this one serves both, at whichever instantiation the caller passes (Block.diverging_prepend is what states it under the diverging name).

                                  Equations
                                  Instances For
                                    def GuardedPlusCal.Block.reducing {α : BoolType} {β : Type u_1} {γ : Type u_2} [Monoid γ] {b : Bool} (f : b : Bool⦄ → α bSet (β × γ × β)) (B : Block α b) :
                                    Set (β × γ × β)

                                    A block's begin run as a list, then its last. Not a recursion of its own: Block.reducing and Block.listReducing computed the same fold before, differing only in that a block's last statement may be terminal, and keeping two recursions meant every equation had to be proved twice and bridged.

                                    Equations
                                    Instances For
                                      def GuardedPlusCal.Block.aborting {α : BoolType} {β : Type u_1} {γ : Type u_2} [Monoid γ] {b : Bool} (f : b : Bool⦄ → α bSet (β × γ)) (g : b : Bool⦄ → α bSet (β × γ × β)) (B : Block α b) :
                                      Set (β × γ)

                                      A block's begin run as a list, then its last. Not a recursion of its own: Block.reducing and Block.listReducing computed the same fold before, differing only in that a block's last statement may be terminal, and keeping two recursions meant every equation had to be proved twice and bridged.

                                      Equations
                                      Instances For
                                        def GuardedPlusCal.Block.diverging {α : BoolType} {β : Type u_1} {γ : Type u_2} [Monoid γ] {b : Bool} (f : b : Bool⦄ → α bSet (β × γ)) (g : b : Bool⦄ → α bSet (β × γ × β)) (B : Block α b) :
                                        Set (β × γ)

                                        A block's begin run as a list, then its last. Not a recursion of its own: Block.reducing and Block.listReducing computed the same fold before, differing only in that a block's last statement may be terminal, and keeping two recursions meant every equation had to be proved twice and bridged.

                                        Equations
                                        Instances For

                                          A block of Guarded PlusCal statements, all of guard class g.

                                          Equations
                                          • One or more equations did not get rendered due to their size.
                                          Instances For

                                            A block of Guarded PlusCal statements, all of guard class g.

                                            Equations
                                            • One or more equations did not get rendered due to their size.
                                            Instances For

                                              Reduction of atomic branches #

                                              A branch is its precondition followed by its action. A branch with no precondition is the action alone, which is why the missing case composes with the identity relation rather than with .

                                              Equations
                                              • One or more equations did not get rendered due to their size.
                                              Instances For
                                                Equations
                                                • One or more equations did not get rendered due to their size.
                                                Instances For

                                                  The states from which an atomic branch is blocked: its precondition reduces to a state at which some later guard blocks. A branch with no precondition — a bare action — blocks nowhere, since an execution statement never blocks.

                                                  Equations
                                                  • One or more equations did not get rendered due to their size.
                                                  Instances For