Repository navigation
feat(dpor): implement dynamic property-aware partial order reduction (Spec 15) #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
136fad5
feat(dpor): implement dynamic property-aware partial order reduction …
SamuelIVX f5011ad
fix(dpor): address CodeRabbit and Graphify review feedback on Spec 15
SamuelIVX 49cf1fb
docs(spec-15): format markdown headings, code fences, and list style
SamuelIVX File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
docs/specs/active/15-property-aware-dpor/01-property-visibility-dpor.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # 15.01 — Dynamic Property-Aware Partial Order Reduction: Visibility Contract & Algorithmic Dispatch | ||
|
|
||
| ## TL;DR | ||
|
|
||
| Dynamic partial order reduction in `DporExplorer` uses sleep sets to avoid exploring redundant interleavings of commuting transitions. This specification defines how state-property visibility integrates with sleep set maintenance, allowing pruning of invisible transitions while guaranteeing that all property state changes and intermediate invariant evaluations remain soundly observed. | ||
|
|
||
| ## Operational Contracts | ||
|
|
||
| ### 1. Invariant Observation Classification | ||
|
|
||
| An invariant `Invariant` evaluates safety predicates against modeled configurations: | ||
|
|
||
| ```java | ||
| public interface Invariant { | ||
| boolean holds(SharedState state, Configuration config); | ||
| default Optional<Set<MemoryLocation>> observedLocations() { return Optional.empty(); } | ||
| } | ||
| ``` | ||
|
|
||
| When `DporExplorer.explore(program, invariant, stateStore, stateVisitor)` is invoked: | ||
| 1. **Case A: `invariant == null`** | ||
| - Standard DPOR exploration. | ||
| - Observations $\mathcal{O} = \text{null}$. | ||
| - Pruning operates under classic Mazurkiewicz equivalence for terminal completion and deadlock freedom. | ||
| 2. **Case B: `invariant != null && invariant.observedLocations().isPresent()`** | ||
| - Known, complete state observation footprint $\mathcal{O} = \text{invariant.observedLocations().get()}$. | ||
| - Executes `dporDfs(...)` with property-aware sleep-set maintenance. | ||
| - Invisible transitions commute and sleep; visible transitions are never put to sleep and visible executions refresh sleeping transitions. | ||
| 3. **Case C: `invariant != null && invariant.observedLocations().isEmpty()`** | ||
| - Unknown observations, configuration-dependent callbacks, or DSL `final` assertions. | ||
| - The explorer cannot verify whether an arbitrary step might alter the predicate's truth value. | ||
| - Fallback to unreduced, exhaustive depth-first search (`dfsDfs`) is strictly preserved. | ||
|
|
||
| ### 2. Transition Visibility Rules | ||
|
|
||
| Given complete observation set $\mathcal{O}$ and step $s$: | ||
| - **Visibility Test**: | ||
| $$\text{Visible}(s, \mathcal{O}) \iff \text{relation.writesOverlap}(s.\text{writes}(), \mathcal{O})$$ | ||
| - A step $s$ is **visible** if any of its declared writes overlaps with any location in $\mathcal{O}$ (accounting for array base-vs-element aliasing). | ||
| - A step $s$ is **invisible** if none of its declared writes overlaps with $\mathcal{O}$. | ||
|
|
||
| ### 3. Sleep Set Maintenance Rules | ||
|
|
||
| In `dporDfs`, when iterating over enabled threads: | ||
| 1. **Rule 1: Visible Transitions Never Enter Sleep Sets** | ||
| When preparing `nextSleepSet` for an explored transition `step`, candidate peer step `otherStep` may only be added to `nextSleepSet` if: | ||
| $$\neg\text{Visible}(\text{otherStep}, \mathcal{O})$$ | ||
| along with existing conditions (independence with `step`, no enable/disable interference, and no future dependent step in the stepping thread). | ||
| 2. **Rule 2: Executing a Visible Transition Refreshes Sleep Sets** | ||
| If the executed step is visible ($\text{Visible}(\text{step}, \mathcal{O})$), executing it modifies the valuation of the property's state footprint $\mathcal{O}$. Any transitions previously sleeping were put to sleep under the prior valuation. Therefore: | ||
| $$\text{nextSleepSet} = \begin{cases} \text{new SleepSet}() & \text{if } \text{Visible}(\text{step}, \mathcal{O}) \\ \text{sleepSet.copyFiltering}(\text{relation}, \text{step}) & \text{if } \neg\text{Visible}(\text{step}, \mathcal{O}) \end{cases}$$ | ||
| 3. **Rule 3: Filtering Retains Independence** | ||
| For invisible executed steps, `copyFiltering` retains only sleeping transitions that are pairwise independent with `step`. Any step dependent on `step` is immediately woken. | ||
|
|
||
| ### 4. Progress and Safety Fallback | ||
|
|
||
| If every enabled transition at a configuration is currently in `sleepSet` (`!explored`), the progress fallback executes all enabled transitions with `new SleepSet()`. This guarantees that linear model execution never deadlocks prematurely due to sleep-set saturation. |
73 changes: 73 additions & 0 deletions
73
docs/specs/active/15-property-aware-dpor/02-soundness-proof.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| # 15.02 — Mathematical Soundness Proof for Property-Aware DPOR | ||
|
|
||
| ## TL;DR | ||
|
|
||
| This document proves that Dynamic Partial Order Reduction using sleep sets with property visibility preserves all state-invariant violations and deadlocks for finite linear threads in `interleave`. | ||
|
|
||
| ## Formal Model & Assumptions | ||
|
|
||
| 1. **Finite Linear Threads**: A program consists of $N$ threads, each with a finite immutable sequence of steps. Program counters strictly advance by 1 on progress (`ADVANCED`, `TERMINATED`) and remain unchanged on `BLOCKED`. There are no loops or backward jumps; the state transition graph is a finite directed acyclic graph (DAG) modulo blocked self-loops. | ||
| 2. **Access Footprints**: For each step $s$, $R(s)$ and $W(s)$ are complete and conservative. Any modification to shared state is in $W(s)$; any state value affecting enabledness, guard evaluation, or outcome is in $R(s)$. | ||
| 3. **Independence**: If $W(a) \cap (R(b) \cup W(b)) = \emptyset$ and $W(b) \cap (R(a) \cup W(a)) = \emptyset$, then $a$ and $b$ commute: | ||
| $$a(b(\sigma)) = b(a(\sigma))$$ | ||
| 4. **State-Only Invariant**: Invariant $I$ is a pure, deterministic function of modeled values at locations $\mathcal{O}$. For any states $\sigma, \sigma'$ where $\forall l \in \mathcal{O}, \sigma(l) = \sigma'(l)$, $I(\sigma) = I(\sigma')$. | ||
| 5. **Exact Visited Store**: State deduplication uses canonical encoding over exact values and program counters (`HashingStateStore`). | ||
|
|
||
| ## Lemmas & Proofs | ||
|
|
||
| ### Lemma 1 (Invisible Transition Valuation Invariance) | ||
|
|
||
| If step $s$ is invisible with respect to $\mathcal{O}$ ($\text{writesOverlap}(W(s), \mathcal{O}) = \emptyset$), then for any configuration $\sigma$: | ||
| $$I(s(\sigma)) = I(\sigma)$$ | ||
|
|
||
| *Proof*: | ||
| Because $s$ writes to no location in $\mathcal{O}$, the projection of state $\sigma$ onto $\mathcal{O}$ is identical to the projection of $s(\sigma)$ onto $\mathcal{O}$. Since $I$ depends exclusively on $\mathcal{O}$, $I(s(\sigma)) = I(\sigma)$. $\blacksquare$ | ||
|
|
||
| ### Lemma 2 (Intermediate State Invariance Under Invisible Commutation) | ||
|
|
||
| Let $s_1$ and $s_2$ be two independent transitions ($s_1 \, I \, s_2$) enabled at $\sigma$. | ||
| Consider schedule $\pi_1 = \sigma \xrightarrow{s_1} s_1(\sigma) \xrightarrow{s_2} s_1(s_2(\sigma))$ and schedule $\pi_2 = \sigma \xrightarrow{s_2} s_2(\sigma) \xrightarrow{s_1} s_2(s_1(\sigma))$. | ||
| By independence, $s_1(s_2(\sigma)) = s_2(s_1(\sigma))$. | ||
|
|
||
| - If both $s_1$ and $s_2$ are invisible to $I$: | ||
| $$I(s_1(\sigma)) = I(\sigma) = I(s_2(\sigma))$$ | ||
| The set of invariant valuations visited along $\pi_1$ is $\{ I(\sigma), I(s_1(s_2(\sigma))) \}$. | ||
| The set of invariant valuations visited along $\pi_2$ is $\{ I(\sigma), I(s_1(s_2(\sigma))) \}$. | ||
| The valuations are identical. Pruning either intermediate schedule cannot omit any violation. | ||
|
|
||
| - If $s_1$ is visible and $s_2$ is invisible: | ||
| $$I(s_2(\sigma)) = I(\sigma)$$ | ||
| and | ||
| $$I(s_2(s_1(\sigma))) = I(s_1(\sigma))$$ | ||
| Schedule $\pi_1$ visits valuations: $\{ I(\sigma), I(s_1(\sigma)), I(s_2(s_1(\sigma))) = I(s_1(\sigma)) \} = \{ I(\sigma), I(s_1(\sigma)) \}$. | ||
| Schedule $\pi_2$ visits valuations: $\{ I(\sigma), I(s_2(\sigma)) = I(\sigma), I(s_1(s_2(\sigma))) = I(s_1(\sigma)) \} = \{ I(\sigma), I(s_1(\sigma)) \}$. | ||
| Both schedules visit the exact same set of invariant valuations. $\blacksquare$ | ||
|
|
||
| ### Theorem 1 (Soundness of Visible Sleep-Set Maintenance) | ||
|
|
||
| Under Rules 1, 2, and 3: | ||
| If there exists an execution sequence in the full state graph reaching a state violating $I$, then the property-aware DPOR search discovers a violation trace. | ||
|
|
||
| *Proof*: | ||
| Let $w = s_1 s_2 \dots s_k$ be a shortest witness trace reaching a configuration $\sigma_k$ such that $\neg I(\sigma_k)$. | ||
| If $\neg I(\sigma_0)$ at the initial state, the violation is detected immediately before any transitions are taken. | ||
| Otherwise, consider the branching at $\sigma_0$: | ||
| - If the first transition $s_1$ in $w$ is explored from $\sigma_0$, induction applies on the remaining suffix of length $k-1$. | ||
| - Suppose instead transition $u \neq s_1$ is explored first from $\sigma_0$: | ||
| - In case $s_1$ is visible to $I$, Rule 1 guarantees that $s_1$ is never placed into `nextSleepSet`. Thus, $s_1$ remains enabled and eligible for exploration from $\sigma_0$ when backtracking. | ||
| - When $s_1$ is invisible to $I$ and independent of $u$: | ||
| By Lemma 2, commuting $u$ and $s_1$ yields a valid execution reaching the same state valuation on $\mathcal{O}$. | ||
| - Should $s_1$ and $u$ be dependent, $u$ cannot place $s_1$ to sleep (independence requirement). | ||
| - Whenever $u$ is visible, Rule 2 refreshes the sleep set to empty, ensuring any previously sleeping transitions are re-enabled to observe the updated state. | ||
| Because threads have finite linear lengths and counters strictly increase on progress, induction on remaining step distance $R(\sigma)$ guarantees that a violation-reaching schedule is preserved in the explored search tree. $\blacksquare$ | ||
|
|
||
| ### Interaction Between Sleep Sets and the Visited-State Store | ||
|
|
||
| A known subtlety in model checking is combining sleep sets with state caching (`StateStore.isVisited(config)`). In general cyclic transition systems, premature state pruning can occur if a state is revisited with a sleep set that does not subsume the earlier sleep set. | ||
|
|
||
| In `interleave`, this interaction is provably sound because: | ||
| 1. **Exact Canonical Configuration Identity**: `Configuration` keys (encoded via `CanonicalEncoder`) capture both all modeled shared-state variables and the exact vector of program counters $(pc_0, \dots, pc_{N-1})$. Two configurations are identical if and only if their state valuations and thread positions are identical. | ||
| 2. **Strictly Acyclic Progress**: Every progressing edge advances a thread's program counter by +1. The sum $\sum_{i=0}^{N-1} pc_i$ strictly increases along every progressing path. Therefore, no configuration can ever be an ancestor of itself along a progressing edge; the state graph is a finite DAG. | ||
| 3. **State-Determined Suffixes**: Because threads are deterministic and linear, the set of enabled transitions and subsequent reachable states from configuration $\sigma$ depends strictly on its state and program counters, not on the scheduling history that reached it. | ||
| 4. **Immediate Violation Detection**: The invariant $I(\sigma)$ is checked upon entering `dporDfs` at every configuration $\sigma$ before state-store pruning. If $\sigma$ violates $I$, it is detected and emitted as a `VIOLATION` trace immediately. | ||
| 5. **Future Violation Preservation**: If a violation occurs only in a continuation from $\sigma$, then when $\sigma$ was first visited and marked, all non-sleeping branches from $\sigma$ were explored. By commutativity of independent prefixes, any schedule reaching $\sigma$ shares the same downstream equivalence classes; pruning revisits to $\sigma$ cannot eliminate a reachable property violation. $\blacksquare$ |
48 changes: 48 additions & 0 deletions
48
docs/specs/active/15-property-aware-dpor/03-verification-and-benchmarks.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| # 15.03 — Verification, Benchmarks & Differential Oracles for Spec 15 | ||
|
|
||
| ## TL;DR | ||
|
|
||
| This document details the verification suite, empirical benchmarks, differential testing oracles, and mutation targets for Property-Aware Dynamic Partial Order Reduction in `DporExplorer`. | ||
|
|
||
| ## Verification Matrix | ||
|
|
||
| ### 1. Dedicated Property-Aware Unit Tests (`DporPropertyAwarenessTest.java`) | ||
|
|
||
| | Test Method | Category | Verified Contract | | ||
| |---|---|---| | ||
| | `observedPropertyReducesIndependentInvisibleWork` | Reduction | DPOR explores fewer states than DFS when independent invisible work exists. | | ||
| | `visibleIndependentWritesCannotHideIntermediateViolations` | Soundness | Two visible independent writes to distinct observed variables preserve the intermediate violation trace. | | ||
| | `visibleStepRefreshesSleepSet` | Soundness | Executing a visible transition empties the sleep set so subsequent transitions observe the new state. | | ||
| | `invisibleStepsCommuteSafelyWithoutHidingViolation` | Soundness | Interleaving invisible operations before an assertion or invariant failure does not prune the bug path. | | ||
| | `unknownObservationsFallBackToExhaustiveDfs` | Backward Compat | An invariant with `observedLocations().isEmpty()` triggers exhaustive `dfsDfs`. | | ||
| | `dslFinalPropertyFallsBackToExhaustiveDfs` | Backward Compat | JSON DSL program with `when: final` falls back to exhaustive `dfsDfs`. | | ||
| | `dslAlwaysPropertyAppliesPropertyAwareReduction` | Feature | JSON DSL program with `when: always` derives observations and prunes invisible interleavings. | | ||
|
SamuelIVX marked this conversation as resolved.
|
||
| | `emptyObservationsReducesCompletely` | Edge Case | Invariant with `observedLocations() = Optional.of(Set.of())` treats all steps as invisible and maximizes reduction. | | ||
|
|
||
| ### 2. Differential Oracles | ||
|
|
||
| For all corpus benchmarks: | ||
| - **Verdict Equivalence**: $\text{outcomes}(\text{DPOR}(P, I)) \equiv \text{outcomes}(\text{DFS}(P, I))$ | ||
| - **Violation Preservation**: If $\text{DFS}(P, I)$ contains `VIOLATION`, then $\text{DPOR}(P, I)$ contains `VIOLATION`. | ||
| - **Deadlock Preservation**: If $\text{DFS}(P, I)$ contains `DEADLOCK`, then $\text{DPOR}(P, I)$ contains `DEADLOCK`. | ||
| - **State Monotonicity**: $\text{states}(\text{DPOR}(P, I)) \le \text{states}(\text{DFS}(P, I))$. | ||
|
|
||
| ### 3. BugCorpus Benchmark Suite | ||
|
|
||
| All 8 benchmarks in `BugCorpus.all()` are executed against `DporExplorer`: | ||
| - `peterson`: Expected `COMPLETED` (mutual exclusion holds) | ||
| - `broken-peterson`: Expected `VIOLATION` | ||
| - `broken-peterson-v2`: Expected `VIOLATION` | ||
| - `deadlock`: Expected `DEADLOCK` | ||
| - `double-checked-locking`: Expected `VIOLATION` | ||
| - `lost-update`: Expected `VIOLATION` | ||
| - `lost-update-3t`: Expected `VIOLATION` | ||
| - `torn-counter`: Expected `VIOLATION` | ||
|
|
||
| ## Mutation Targets (PIT) | ||
|
|
||
| Target scope: `dev.samhb.interleave.dpor.DporExplorer` and `dev.samhb.interleave.dpor.SleepSet`. | ||
| - Inverting `stepVisible` condition: caught by `visibleStepRefreshesSleepSet` and `visibleIndependentWritesCannotHideIntermediateViolations`. | ||
| - Inverting `otherInvisible` condition: caught by `observedPropertyReducesIndependentInvisibleWork` (mutant surviving if visibility test omitted). | ||
| - Removing fallback dispatch: caught by `unknownObservationsFallBackToExhaustiveDfs`. | ||
| - Mutating `writesOverlap`: caught by aliasing tests and visible race preservation. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # Spec 15 — Dynamic Property-Aware DPOR Reduction — Spec Set Overview | ||
|
|
||
| This directory contains the specification, formal soundness proofs, visibility contracts, and verification plans for Dynamic Property-Aware Partial Order Reduction in `interleave`. | ||
|
|
||
| ## TL;DR | ||
|
|
||
| Where `DporExplorer` historically disabled all sleep-set reduction whenever an invariant was supplied (falling back to unreduced exhaustive `dfsDfs`), Spec 15 incorporates state-property visibility into dynamic sleep-set exploration. When an invariant declares complete `observedLocations()`, invisible transitions that do not write to the observed footprint are pruned using sleep sets, while visible transitions are never put to sleep and visible executions refresh sleeping transitions. Invariants with unknown observations retain the exhaustive DFS fallback. | ||
|
|
||
| ## Motivation & Background | ||
|
|
||
| - **Historical limitation**: In `DporExplorer.java:91-98`, `if (invariant != null)` unconditionally routed to `dfsDfs`. This prevented DPOR from achieving reduction on any program checked against an invariant, leading to state explosion on programs with unobserved concurrent operations. | ||
| - **Spec 13.08 predecessor**: Spec 13.08 implemented static property-aware reduction via `PropertyPersistentSetComputer` in `StaticPorExplorer`. | ||
| - **Spec 15 objective**: Bring property awareness to DPOR (`DporExplorer`), establishing that sleep sets can soundly prune equivalent schedules for state invariants without sacrificing safety violation detection or deadlock detection. | ||
|
|
||
| ## Spec Set Structure | ||
|
|
||
| | Document | Title | Focus | | ||
| |---|---|---| | ||
| | [01-property-visibility-dpor.md](01-property-visibility-dpor.md) | Visibility Contract & Algorithmic Dispatch | Visibility definition, sleep-set rules, API dispatch, fallback preservation | | ||
| | [02-soundness-proof.md](02-soundness-proof.md) | Mathematical Soundness Proof | Finite-trace commutativity, intermediate predicate invariance, bug preservation | | ||
| | [03-verification-and-benchmarks.md](03-verification-and-benchmarks.md) | Verification, Benchmarks & Oracles | BugCorpus validation, differential checks vs DFS/Static POR, state reduction | | ||
|
|
||
| ## Acceptance Criteria | ||
|
|
||
| 1. **Property-Aware DPOR Reduction**: `DporExplorer.explore(program, invariant)` prunes states via sleep sets when `invariant.observedLocations().isPresent()`. | ||
| 2. **Exhaustive Fallback Preservation**: When `invariant.observedLocations().isEmpty()` (unknown callbacks, configuration-dependent predicates, DSL `final`), DPOR strictly executes exhaustive `dfsDfs`. | ||
| 3. **Soundness & Zero Missed Violations**: | ||
| - All invariant violations and deadlocks across `BugCorpus.all()` are detected identically to `DfsExplorer`. | ||
| - Visible races (such as independent writes to distinct observed variables) are never suppressed by sleep sets. | ||
| 4. **State Reduction**: Explores strictly fewer states than DFS on programs containing independent invisible operations. | ||
| 5. **No Regressions**: All existing tests pass, Javadoc coverage remains complete, and single-worker CI checks succeed. | ||
|
|
||
| ## Commands | ||
|
|
||
| ```bash | ||
| # Run DPOR tests including property awareness | ||
| ./gradlew test --tests "*Dpor*" --max-workers=1 -PpitestThreads=1 | ||
|
|
||
| # Run full project checks | ||
| ./gradlew check javadoc --max-workers=1 -PpitestThreads=1 | ||
| ``` |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.