diff --git a/Strata/Backends/CBMC/GOTO/Semantics.lean b/Strata/Backends/CBMC/GOTO/Semantics.lean new file mode 100644 index 000000000..71df677b1 --- /dev/null +++ b/Strata/Backends/CBMC/GOTO/Semantics.lean @@ -0,0 +1,462 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Backends.CBMC.GOTO.Program + +/-! +# Formal Semantics for CBMC's GOTO Intermediate Language + +This file defines a big-step operational semantics for the GOTO intermediate +representation used by CBMC, as modeled by the Strata `CProverGOTO` AST. + +## Design + +The semantics is defined as an inductive relation over GOTO programs (flat +instruction arrays with a program counter). The judgment form is: + + `eval, fenv, prog, pc, σ ⊢ σ', retVal` + +where: +- `eval` is an abstract expression evaluator +- `fenv` is a function environment mapping function names to GOTO programs +- `prog` is the current GOTO program (instruction array) +- `pc` is the current program counter (index into the instruction array) +- `σ` is the variable store +- `σ'` is the resulting store +- `retVal` is the optional return value + +## Scope + +This semantics covers the subset of GOTO instructions that Strata's +Core-to-GOTO translation (`CoreToCProverGOTO.lean`, `ToCProverGOTO.lean`) +produces: + +### Fully modeled instructions +- **ASSIGN** — `lhs := rhs` assignment (including nondet side effects) +- **DECL** — local variable declaration (introduces a symbol) +- **DEAD** — end of variable lifetime (removes from store) +- **GOTO** — conditional/unconditional branch (guarded by `guard`, jumps to `target`) +- **ASSUME** — constrain execution (block if `guard` is false) +- **ASSERT** — verification obligation (flag error if `guard` is false) +- **SKIP** — no-op, advance PC +- **LOCATION** — semantically identical to SKIP (used for labels) +- **END_FUNCTION** — marks function exit, halts execution of current function +- **SET_RETURN_VALUE** — sets the return value (used for pure function bodies) +- **FUNCTION_CALL** — invoke a function with arguments, assign return value + +### Intentionally omitted instructions +- **START_THREAD / END_THREAD** — concurrency (not produced by Strata) +- **ATOMIC_BEGIN / ATOMIC_END** — atomicity blocks (not produced by Strata) +- **THROW / CATCH** — exception handling (not produced by Strata) +- **OTHER** — miscellaneous (array_set, printf, etc.; not produced by Strata) +- **INCOMPLETE_GOTO** — placeholder (should not appear in final programs) +- **NO_INSTRUCTION_TYPE** — invalid (should not appear) + +### Expression evaluation +Expression evaluation is abstracted via a parameter `eval : Store → Expr → Option Value`. +This keeps the semantics modular: the expression evaluator can be instantiated +with different interpretations (concrete, symbolic, etc.) without changing the +instruction-level rules. + +### Known limitations +- **Pointers and memory**: The store is a flat map from variable names to values. + Pointer arithmetic, address-of, and dereference are not modeled. +- **Struct/array values**: Values include structured forms, but field access and + indexing are delegated to the expression evaluator. +- **Non-determinism in GOTO targets**: Exactly one target is required for GOTO + instructions (multiple targets are deprecated in CBMC). +- **Function calls**: Modeled via a separate `CallResult` relation that + abstracts callee execution. +- **Assertion semantics**: CBMC records assertion failures but continues + execution. We model this by having `assert_fail` advance the PC normally. + A separate `ProgramSafe` predicate captures the property that no assertion + fails. + +## Relationship to CBMC's interpreter + +The semantics is informed by CBMC's concrete interpreter +(`src/goto-programs/interpreter.cpp`), but abstracts away: +- The flat memory model (replaced by a named variable store) +- The symbol table (replaced by the function environment) +- Trace recording (not modeled; we only track state transitions) + +## TODO +- [x] Prove determinism of the single-step relation (for deterministic `eval`) + → See `SemanticsProps.lean`: `StepInstr_deterministic_no_nondet` +- [x] Prove determinism of `ExecProg` + → See `SemanticsProps.lean`: `ExecProg_deterministic` +- [x] Prove that well-formed programs (from Strata's translation) always make + progress or terminate + → See `SemanticsProps.lean`: `progress_wellformed` +- [x] Formalize expression evaluation for the concrete GOTO expression language + → See `SemanticsEval.lean`: `concreteEval` +- [x] Connect to Strata Core semantics: prove that the Core-to-GOTO translation + preserves the semantics (simulation relation) + → See `SemanticsSim.lean`: all command-level simulation lemmas proved + (`sim_assert`, `sim_assume`, `sim_set`, `sim_init`, `sim_havoc`, + `sim_cmd`), plus if-then-else guard simulation +- [x] Add support for `old()` expressions in postconditions + → See `Semantics.lean`: `ExprEval₂`, `ExprEval.withOld` + → See `SemanticsEval.lean`: `concreteEval₂` +- [x] Statement-level simulation for block and loop + → See `SemanticsSim.lean`: `sim_block`, `ExecLoop`, `sim_loop` +- [x] End-to-end theorem: `EvalBlock` implies `ExecProg` on translated program + → See `SemanticsSim.lean`: `sim_end_to_end`, `ExecRange_then_ExecProg`, + `ExecRange_trans` +- [x] Loop evaluation rules added to Imperative `EvalStmt` + → See `StmtSemantics.lean`: `loop_false_sem`, `loop_true_sem` + (3 sorry's introduced in `DetToNondetCorrect.lean` — the DetToNondet + correctness proof needs restructuring for derivation-based induction) +- [x] Progress theorems (per-instruction-type) + → See `SemanticsProps.lean`: `progress_skip`, `progress_assign`, etc. +-/ + +namespace CProverGOTO.Semantics + +open CProverGOTO + +/-! ## Values -/ + +/-- Runtime values for GOTO programs. + Mirrors the types supported by the GOTO type system (`Ty`). -/ +inductive Value where + | vBool : Bool → Value + | vInt : Int → Value + | vBV : (width : Nat) → Int → Value + | vReal : Int → Int → Value -- numerator / denominator + | vString : String → Value + | vArray : List Value → Value + | vStruct : List (String × Value) → Value + | vEmpty : Value + deriving Repr, BEq, Inhabited + +/-! ## Store -/ + +/-- Variable store: maps symbol names to optional values. + A variable is "declared" when it maps to `some v`, and + "undeclared" / "dead" when it maps to `none`. -/ +abbrev Store := String → Option Value + +def Store.empty : Store := fun _ => none + +/-- Update a variable in the store. -/ +def Store.update (σ : Store) (name : String) (v : Value) : Store := + fun x => if x == name then some v else σ x + +/-- Declare a variable with a default value. + Note: In CBMC, DECL introduces a symbol but the value is undefined until + assigned. We use `.vEmpty` as a sentinel. The translation pipeline always + follows DECL with an ASSIGN (for `init x ty (some e)`) or with a nondet + ASSIGN (for `havoc`), so `.vEmpty` is never observed in well-formed + translated programs. For `init x ty none` (unconstrained init), the + Imperative semantics uses `InitState` with an arbitrary value, which + corresponds to the DECL + nondet ASSIGN pattern in GOTO. -/ +def Store.declare (σ : Store) (name : String) : Store := + fun x => if x == name then some .vEmpty else σ x + +/-- Mark a variable as dead. -/ +def Store.kill (σ : Store) (name : String) : Store := + fun x => if x == name then none else σ x + +/-! ## Expression Evaluator (abstract) -/ + +/-- Abstract expression evaluator. -/ +abbrev ExprEval := Store → Expr → Option Value + +/-- Two-state expression evaluator for postconditions with `old()`. + Takes both the pre-state (at procedure entry) and the current state. -/ +abbrev ExprEval₂ := Store → Store → Expr → Option Value + +/-- Lift a single-state evaluator to a two-state evaluator that handles `old()`. + `old(e)` evaluates `e` in the pre-state; all other expressions use the + current state. -/ +def ExprEval.withOld (eval : ExprEval) : ExprEval₂ := fun σ_old σ_cur e => + match e.id, e.operands with + | .unary .Old, [inner] => eval σ_old inner + | _, _ => eval σ_cur e + +/-! ## Function Environment -/ + +/-- Maps function names to their GOTO programs. -/ +abbrev FuncEnv := String → Option Program + +/-! ## Instruction Accessors -/ + +/-- Get instruction type at a given PC, or `none` if out of bounds. -/ +def instrType (prog : Program) (pc : Nat) : Option InstructionType := + (prog.instructions[pc]?).map Instruction.type + +/-- Get instruction guard at a given PC. -/ +def instrGuard (prog : Program) (pc : Nat) : Option Expr := + (prog.instructions[pc]?).map Instruction.guard + +/-- Get instruction target at a given PC. -/ +def instrTarget (prog : Program) (pc : Nat) : Option (Option Target) := + (prog.instructions[pc]?).map Instruction.target + +/-- Get instruction code at a given PC. -/ +def instrCode (prog : Program) (pc : Nat) : Option Code := + (prog.instructions[pc]?).map Instruction.code + +/-- Extract the symbol name from a DECL/DEAD code operand. -/ +def getSymbolName (c : Code) : Option String := + match c.operands with + | [e] => match e.id with + | .nullary (.symbol name) => some name + | _ => none + | _ => none + +/-- Extract lhs name and rhs expression from an ASSIGN code. -/ +def getAssignLhs (c : Code) : Option String := + match c.operands with + | [lhs, _rhs] => match lhs.id with + | .nullary (.symbol name) => some name + | _ => none + | _ => none + +/-- Extract rhs expression from an ASSIGN code. -/ +def getAssignRhs (c : Code) : Option Expr := + match c.operands with + | [_lhs, rhs] => some rhs + | _ => none + +/-- Extract the return value expression from a SET_RETURN_VALUE code. -/ +def getReturnValue (c : Code) : Option Expr := + match c.operands with + | [e] => some e + | _ => none + +/-- Extract function call lhs name. -/ +def getCallLhs (c : Code) : Option String := + match c.operands with + | [lhs, _, _] => match lhs.id with + | .nullary (.symbol name) => if name == "" then none else some name + | _ => none + | _ => none + +/-- Extract function call callee name. -/ +def getCallCallee (c : Code) : Option String := + match c.operands with + | [_, callee, _] => match callee.id with + | .nullary (.symbol name) => some name + | _ => none + | _ => none + +/-- Find the array index of an instruction with a given locationNum. -/ +def findLocIdx (instrs : Array Instruction) (loc : Nat) : Option Nat := + go instrs.toList 0 +where + go : List Instruction → Nat → Option Nat + | [], _ => none + | i :: rest, idx => if i.locationNum == loc then some idx else go rest (idx + 1) + +/-! ## Abstract Callee Result -/ + +/-- Abstract relation for function call results. + `CallResult eval fenv calleeName σ σ' retVal` means: calling function + `calleeName` with store `σ` produces final store `σ'` and optional + return value `retVal`. + + This is left as a parameter to break the mutual recursion between + single-step and multi-step execution. -/ +abbrev CallResultRel := ExprEval → FuncEnv → String → Store → Store → Option Value → Prop + +/-! ## Single-Step Semantics -/ + +/-- Single-step execution of one GOTO instruction. + +The judgment `StepInstr callResult eval fenv prog pc σ pc' σ'` means: +executing the instruction at index `pc` in `prog` with store `σ` +transitions to program counter `pc'` and store `σ'`. +-/ +inductive StepInstr (callResult : CallResultRel) (eval : ExprEval) (fenv : FuncEnv) + (prog : Program) : + Nat → Store → Nat → Store → Prop where + + /-- **SKIP**: Advance PC, no state change. -/ + | skip : + instrType prog pc = some .SKIP → + StepInstr callResult eval fenv prog pc σ (pc + 1) σ + + /-- **LOCATION**: Semantically identical to SKIP. -/ + | location : + instrType prog pc = some .LOCATION → + StepInstr callResult eval fenv prog pc σ (pc + 1) σ + + /-- **ASSIGN**: Evaluate rhs, update lhs in store. -/ + | assign : + instrType prog pc = some .ASSIGN → + (instrCode prog pc).bind getAssignLhs = some name → + (instrCode prog pc).bind getAssignRhs = some rhs → + eval σ rhs = some v → + StepInstr callResult eval fenv prog pc σ (pc + 1) (σ.update name v) + + /-- **ASSIGN with nondet**: rhs is a nondeterministic side effect. + The variable gets an arbitrary value `v`. -/ + | assign_nondet : + instrType prog pc = some .ASSIGN → + (instrCode prog pc).bind getAssignLhs = some name → + (instrCode prog pc).bind getAssignRhs = some rhs → + rhs.id = .side_effect .Nondet → + -- `v` is universally quantified (any value is a valid result) + StepInstr callResult eval fenv prog pc σ (pc + 1) (σ.update name v) + + /-- **DECL**: Declare a local variable. -/ + | decl : + instrType prog pc = some .DECL → + (instrCode prog pc).bind getSymbolName = some name → + StepInstr callResult eval fenv prog pc σ (pc + 1) (σ.declare name) + + /-- **DEAD**: End lifetime of a local variable. -/ + | dead : + instrType prog pc = some .DEAD → + (instrCode prog pc).bind getSymbolName = some name → + StepInstr callResult eval fenv prog pc σ (pc + 1) (σ.kill name) + + /-- **GOTO (taken)**: Guard is true, jump to target. -/ + | goto_taken : + instrType prog pc = some .GOTO → + instrTarget prog pc = some (some tgt) → + (instrGuard prog pc).bind (eval σ ·) = some (.vBool true) → + findLocIdx prog.instructions tgt = some tgtIdx → + StepInstr callResult eval fenv prog pc σ tgtIdx σ + + /-- **GOTO (not taken)**: Guard is false, fall through. -/ + | goto_not_taken : + instrType prog pc = some .GOTO → + (instrGuard prog pc).bind (eval σ ·) = some (.vBool false) → + StepInstr callResult eval fenv prog pc σ (pc + 1) σ + + /-- **ASSUME (pass)**: Guard is true, advance. + When the guard is false, there is no derivation — the execution path + is infeasible (CBMC models this as a non-failing guarded self-loop). -/ + | assume_pass : + instrType prog pc = some .ASSUME → + (instrGuard prog pc).bind (eval σ ·) = some (.vBool true) → + StepInstr callResult eval fenv prog pc σ (pc + 1) σ + + /-- **ASSERT (pass)**: Guard is true, advance normally. -/ + | assert_pass : + instrType prog pc = some .ASSERT → + (instrGuard prog pc).bind (eval σ ·) = some (.vBool true) → + StepInstr callResult eval fenv prog pc σ (pc + 1) σ + + /-- **ASSERT (fail)**: Guard is false, advance (CBMC continues after failures). + The assertion failure is observable via `AssertFails` below. -/ + | assert_fail : + instrType prog pc = some .ASSERT → + (instrGuard prog pc).bind (eval σ ·) = some (.vBool false) → + StepInstr callResult eval fenv prog pc σ (pc + 1) σ + + /-- **FUNCTION_CALL**: Call a function, assign return value to lhs. -/ + | function_call : + instrType prog pc = some .FUNCTION_CALL → + (instrCode prog pc).bind getCallCallee = some calleeName → + callResult eval fenv calleeName σ σ' retVal → + σ'' = (match (instrCode prog pc).bind getCallLhs, retVal with + | some name, some v => σ'.update name v + | _, _ => σ') → + StepInstr callResult eval fenv prog pc σ (pc + 1) σ'' + +/-! ## Multi-Step Execution -/ + +/-- Execute a GOTO program from a given PC to completion. + Terminates when END_FUNCTION is reached or PC goes out of bounds. -/ +inductive ExecProg (callResult : CallResultRel) (eval : ExprEval) (fenv : FuncEnv) + (prog : Program) : + Nat → Store → Store → Option Value → Prop where + + /-- PC out of bounds: implicit halt. -/ + | out_of_bounds : + pc ≥ prog.instructions.size → + ExecProg callResult eval fenv prog pc σ σ none + + /-- END_FUNCTION reached: halt. -/ + | end_function : + instrType prog pc = some .END_FUNCTION → + ExecProg callResult eval fenv prog pc σ σ none + + /-- SET_RETURN_VALUE: evaluate return expression, then continue. -/ + | set_return_value : + instrType prog pc = some .SET_RETURN_VALUE → + (instrCode prog pc).bind getReturnValue = some retExpr → + eval σ retExpr = some v → + ExecProg callResult eval fenv prog (pc + 1) σ σ' retVal → + ExecProg callResult eval fenv prog pc σ σ' (retVal <|> some v) + + /-- Normal step: execute one instruction, then continue. -/ + | step : + StepInstr callResult eval fenv prog pc σ pc' σ' → + ExecProg callResult eval fenv prog pc' σ' σ'' retVal → + ExecProg callResult eval fenv prog pc σ σ'' retVal + +/-! ## Program-Level Execution -/ + +/-- Execute a complete GOTO program starting from PC 0. -/ +def execProgram (callResult : CallResultRel) (eval : ExprEval) (fenv : FuncEnv) + (prog : Program) (σ₀ σ' : Store) (retVal : Option Value) : Prop := + ExecProg callResult eval fenv prog 0 σ₀ σ' retVal + +/-! ## Assertion Safety -/ + +/-- An assertion fails at `pc` in store `σ`. -/ +def AssertFails (eval : ExprEval) (prog : Program) (pc : Nat) (σ : Store) : Prop := + instrType prog pc = some .ASSERT ∧ + (instrGuard prog pc).bind (eval σ ·) = some (.vBool false) + +/-- Reachability: `(pc', σ')` is reachable from `(pc, σ)` by zero or more steps. -/ +inductive Reachable (callResult : CallResultRel) (eval : ExprEval) (fenv : FuncEnv) + (prog : Program) : + Nat → Store → Nat → Store → Prop where + | refl : + Reachable callResult eval fenv prog pc σ pc σ + | step : + StepInstr callResult eval fenv prog pc σ pc' σ' → + Reachable callResult eval fenv prog pc' σ' pc'' σ'' → + Reachable callResult eval fenv prog pc σ pc'' σ'' + +/-- A program is safe if no reachable state has a failing assertion. -/ +def ProgramSafe (callResult : CallResultRel) (eval : ExprEval) (fenv : FuncEnv) + (prog : Program) (σ₀ : Store) : Prop := + ∀ pc σ, + Reachable callResult eval fenv prog 0 σ₀ pc σ → + ¬ AssertFails eval prog pc σ + +/-! ## Basic Properties -/ + +/-- ASSUME does not modify the store. -/ +theorem assume_preserves_store + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc pc' : Nat} {σ σ' : Store} : + instrType prog pc = some .ASSUME → + StepInstr callResult eval fenv prog pc σ pc' σ' → + σ' = σ := by + intro htype hstep + cases hstep <;> simp_all + +/-- ASSERT does not modify the store. -/ +theorem assert_preserves_store + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc pc' : Nat} {σ σ' : Store} : + instrType prog pc = some .ASSERT → + StepInstr callResult eval fenv prog pc σ pc' σ' → + σ' = σ := by + intro htype hstep + cases hstep <;> simp_all + +/-- Reachability is transitive. -/ +theorem reachable_trans + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc₁ pc₂ pc₃ : Nat} {σ₁ σ₂ σ₃ : Store} : + Reachable callResult eval fenv prog pc₁ σ₁ pc₂ σ₂ → + Reachable callResult eval fenv prog pc₂ σ₂ pc₃ σ₃ → + Reachable callResult eval fenv prog pc₁ σ₁ pc₃ σ₃ := by + intro h1 h2 + induction h1 with + | refl => exact h2 + | step hstep _hreach ih => exact .step hstep (ih h2) + +end CProverGOTO.Semantics diff --git a/Strata/Backends/CBMC/GOTO/SemanticsEval.lean b/Strata/Backends/CBMC/GOTO/SemanticsEval.lean new file mode 100644 index 000000000..4f86d0b14 --- /dev/null +++ b/Strata/Backends/CBMC/GOTO/SemanticsEval.lean @@ -0,0 +1,189 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Backends.CBMC.GOTO.Semantics + +/-! +# Concrete Expression Evaluator for GOTO Expressions + +This file defines a concrete evaluator for the GOTO expression AST (`Expr`), +instantiating the abstract `ExprEval` parameter from `Semantics.lean`. + +## Scope + +Covers the expression forms produced by Strata's Lambda-to-GOTO translation +(`LambdaToCProverGOTO.lean`): + +### Fully handled +- **Nullary**: `symbol` (variable lookup), `constant` (literal parsing) +- **Unary**: `Not`, `UnaryMinus`, `Typecast` (bool↔int, int↔bv) +- **Binary**: arithmetic (`Div`, `Mod`, `Minus`), comparison (`Lt`, `Le`, `Gt`, + `Ge`, `Equal`, `NotEqual`), logical (`Implies`), map (`Index`) +- **Ternary**: `ite` (if-then-else), `with` (map update) +- **Multiary**: `And`, `Or`, `Plus`, `Mult` + +### Not handled (returns `none`) +- **Side effects**: `Nondet`, `Assign` (handled at instruction level) +- **Function application**: `functionApplication` (uninterpreted) +- **`Old`**: handled by `concreteEval₂` (two-state evaluator) +- **Quantifiers**: `Forall`, `Exists` (specification-only) +- **Bitvector bit-ops**: `Shl`, `Ashr`, `Lshr`, `Bitand`, `Bitor`, `Bitxor`, + `Concatenation`, `Extractbits` +- **Real arithmetic**: `vReal` values are parsed but arithmetic/comparison + operations on reals are not yet implemented (returns `none`) + +## TODO +- [ ] Bitvector bit-level operations (width-aware) +- [ ] Bitvector width normalization (truncate results modulo `2^w`) +- [ ] Real arithmetic (rational operations on `vReal`) +- [ ] Prove correspondence with Lambda expression evaluation +-/ + +namespace CProverGOTO.Semantics + +open CProverGOTO + +/-! ## Constant Parsing -/ + +/-- Parse a GOTO constant string into a Value, given its type. -/ +def parseConstant (val : String) (ty : Ty) : Option Value := + match ty.id with + | .primitive .bool => + if val == "true" then some (.vBool true) + else if val == "false" then some (.vBool false) + else none + | .primitive .integer => val.toInt?.map .vInt + | .primitive .string => some (.vString val) + | .primitive .real => val.toInt?.map (fun n => .vReal n 1) + | .bitVector (.signedbv w) => val.toInt?.map (.vBV w) + | .bitVector (.unsignedbv w) => val.toInt?.map (.vBV w) + | _ => none + +/-- Type cast between values. -/ +def typeCast (v : Value) (targetTy : Ty) : Option Value := + match v, targetTy.id with + | .vBool b, .primitive .integer => some (.vInt (if b then 1 else 0)) + | .vInt n, .primitive .bool => some (.vBool (n != 0)) + | .vBV _ n, .bitVector (.signedbv w) => some (.vBV w n) + | .vBV _ n, .bitVector (.unsignedbv w) => some (.vBV w n) + | .vInt n, .bitVector (.signedbv w) => some (.vBV w n) + | .vInt n, .bitVector (.unsignedbv w) => some (.vBV w n) + | _, _ => none + +/-! ## Concrete Evaluator -/ + +/-- Concrete expression evaluator for GOTO expressions. + + Uses `partial` because GOTO expressions are tree-structured with + arbitrary nesting depth, and proving termination over the `Expr` + structure (which uses `List Expr` for operands) requires well-founded + recursion on a custom measure. -/ +partial def concreteEval : ExprEval := fun σ e => + match e.id, e.operands with + -- Nullary + | .nullary (.symbol name), [] => σ name + | .nullary (.constant val), [] => parseConstant val e.type + | .nullary .nil, [] => some .vEmpty + + -- Unary + | .unary .Not, [op] => do + let .vBool b ← concreteEval σ op | none + some (.vBool !b) + | .unary .UnaryMinus, [op] => do + match ← concreteEval σ op with + | .vInt n => some (.vInt (-n)) + | .vBV w n => some (.vBV w (-n)) + | _ => none + | .unary .Typecast, [op] => do + let v ← concreteEval σ op + typeCast v e.type + + -- Binary arithmetic + | .binary .Div, [l, r] => intBinOp (· / ·) σ l r + | .binary .Mod, [l, r] => intBinOp (· % ·) σ l r + | .binary .Minus, [l, r] => intBinOp (· - ·) σ l r + + -- Binary comparison + | .binary .Lt, [l, r] => intCmp (· < ·) σ l r + | .binary .Le, [l, r] => intCmp (· ≤ ·) σ l r + | .binary .Gt, [l, r] => intCmp (· > ·) σ l r + | .binary .Ge, [l, r] => intCmp (· ≥ ·) σ l r + | .binary .Equal, [l, r] => do + some (.vBool ((← concreteEval σ l) == (← concreteEval σ r))) + | .binary .NotEqual, [l, r] => do + some (.vBool ((← concreteEval σ l) != (← concreteEval σ r))) + + -- Binary logical + | .binary .Implies, [l, r] => do + let .vBool a ← concreteEval σ l | none + let .vBool b ← concreteEval σ r | none + some (.vBool (!a || b)) + + -- Map/array operations + | .binary .Index, [arr, idx] => do + let .vArray elems ← concreteEval σ arr | none + let .vInt i ← concreteEval σ idx | none + if i < 0 then none else elems[i.toNat]? + + -- Ternary + | .ternary .ite, [c, t, el] => do + let .vBool b ← concreteEval σ c | none + if b then concreteEval σ t else concreteEval σ el + | .ternary .«with», [arr, idx, val] => do + let .vArray elems ← concreteEval σ arr | none + let .vInt i ← concreteEval σ idx | none + let v ← concreteEval σ val + if i < 0 then none else + some (.vArray (elems.set i.toNat v)) + + -- Multiary + | .multiary .And, ops => do + let vals ← ops.mapM (fun op => do + match ← concreteEval σ op with + | .vBool b => pure b + | _ => none) + some (.vBool (vals.all id)) + | .multiary .Or, ops => do + let vals ← ops.mapM (fun op => do + match ← concreteEval σ op with + | .vBool b => pure b + | _ => none) + some (.vBool (vals.any id)) + | .multiary .Plus, ops => do + let vals ← ops.mapM (fun op => do + match ← concreteEval σ op with + | .vInt n => pure n + | _ => none) + some (.vInt (vals.foldl (· + ·) 0)) + | .multiary .Mult, ops => do + let vals ← ops.mapM (fun op => do + match ← concreteEval σ op with + | .vInt n => pure n + | _ => none) + some (.vInt (vals.foldl (· * ·) 1)) + + -- Unsupported + | _, _ => none +where + intBinOp (f : Int → Int → Int) (σ : Store) (l r : Expr) : Option Value := do + match ← concreteEval σ l, ← concreteEval σ r with + | .vInt a, .vInt b => some (.vInt (f a b)) + | .vBV w a, .vBV _ b => some (.vBV w (f a b)) + | _, _ => none + intCmp (f : Int → Int → Bool) (σ : Store) (l r : Expr) : Option Value := do + match ← concreteEval σ l, ← concreteEval σ r with + | .vInt a, .vInt b => some (.vBool (f a b)) + | .vBV _ a, .vBV _ b => some (.vBool (f a b)) + | _, _ => none + +/-- Two-state concrete evaluator that handles `old()` expressions. + `old(e)` evaluates `e` in `σ_old`; everything else uses `σ_cur`. -/ +partial def concreteEval₂ : ExprEval₂ := fun σ_old σ_cur e => + match e.id, e.operands with + | .unary .Old, [inner] => concreteEval σ_old inner + | _, _ => concreteEval σ_cur e + +end CProverGOTO.Semantics diff --git a/Strata/Backends/CBMC/GOTO/SemanticsProps.lean b/Strata/Backends/CBMC/GOTO/SemanticsProps.lean new file mode 100644 index 000000000..0a7f6e5b5 --- /dev/null +++ b/Strata/Backends/CBMC/GOTO/SemanticsProps.lean @@ -0,0 +1,381 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Backends.CBMC.GOTO.Semantics + +/-! +# Properties of GOTO Semantics + +Determinism, store properties, and structural lemmas for the GOTO +operational semantics defined in `Semantics.lean`. + +## Status + +### Completed +- Store operation lemmas (update idempotence, commutativity, etc.) +- Determinism of `StepInstr` for deterministic evaluators (excluding `assign_nondet`) +- Determinism of `ExecProg` for nondet-free programs (`ExecProg_deterministic`) +- Per-instruction-type progress lemmas (`progress_skip`, `progress_assign`, etc.) +- Composite well-formed program progress (`progress_wellformed`) +- Additional reachability lemmas + +### TODO +- [ ] Store monotonicity: DECL only adds, DEAD only removes +-/ + +namespace CProverGOTO.Semantics + +/-! ## Store Operation Lemmas -/ + +@[simp] theorem Store.update_same (σ : Store) (name : String) (v : Value) : + (σ.update name v) name = some v := by + simp [Store.update] + +theorem Store.update_other (σ : Store) (name name' : String) (v : Value) + (h : name' ≠ name) : + (σ.update name v) name' = σ name' := by + simp [Store.update] + intro heq; exact absurd heq h + +@[simp] theorem Store.declare_same (σ : Store) (name : String) : + (σ.declare name) name = some .vEmpty := by + simp [Store.declare] + +theorem Store.declare_other (σ : Store) (name name' : String) + (h : name' ≠ name) : + (σ.declare name) name' = σ name' := by + simp [Store.declare] + intro heq; exact absurd heq h + +@[simp] theorem Store.kill_same (σ : Store) (name : String) : + (σ.kill name) name = none := by + simp [Store.kill] + +theorem Store.kill_other (σ : Store) (name name' : String) + (h : name' ≠ name) : + (σ.kill name) name' = σ name' := by + simp [Store.kill] + intro heq; exact absurd heq h + +@[simp] theorem Store.update_update (σ : Store) (name : String) (v₁ v₂ : Value) : + (σ.update name v₁).update name v₂ = σ.update name v₂ := by + funext x; simp [Store.update]; split <;> simp_all + +/-! ## Determinism -/ + +/-- A deterministic expression evaluator. -/ +def DeterministicEval (eval : ExprEval) : Prop := + ∀ σ e v₁ v₂, eval σ e = some v₁ → eval σ e = some v₂ → v₁ = v₂ + +/-- A deterministic call relation. -/ +def DeterministicCall (callResult : CallResultRel) : Prop := + ∀ eval fenv name σ σ₁ σ₂ r₁ r₂, + callResult eval fenv name σ σ₁ r₁ → + callResult eval fenv name σ σ₂ r₂ → + σ₁ = σ₂ ∧ r₁ = r₂ + +/-- `StepInstr` is deterministic for deterministic evaluators, when + `assign_nondet` is not present (no havoc instructions). + + Programs produced by Strata's translation use `assign_nondet` only for + `havoc` commands; for programs without `havoc`, this gives full determinism. -/ +theorem StepInstr_deterministic_no_nondet + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc pc₁ pc₂ : Nat} {σ σ₁ σ₂ : Store} + (_heval : DeterministicEval eval) + (hcall : DeterministicCall callResult) + (hno_nondet : ∀ rhs, (instrCode prog pc).bind getAssignRhs = some rhs → + rhs.id ≠ .side_effect .Nondet) : + StepInstr callResult eval fenv prog pc σ pc₁ σ₁ → + StepInstr callResult eval fenv prog pc σ pc₂ σ₂ → + pc₁ = pc₂ ∧ σ₁ = σ₂ := by + intro h1 h2 + cases h1 <;> cases h2 <;> simp_all + -- After simp_all: only function_call × function_call remains. + -- The context has two callResult hypotheses with unified callee names. + next _ hcr1 hσ1 _ _ hcr2 hσ2 => + have ⟨hσeq, hreq⟩ := hcall _ _ _ _ _ _ _ _ hcr1 hcr2 + subst hσeq; subst hreq; simp_all + +/-! ## Reachability Lemmas -/ + +/-- A single step is reachable. -/ +theorem reachable_single + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc pc' : Nat} {σ σ' : Store} : + StepInstr callResult eval fenv prog pc σ pc' σ' → + Reachable callResult eval fenv prog pc σ pc' σ' := + fun h => .step h .refl + +/-- If a program is safe and we can reach `pc, σ`, then the assertion + at `pc` (if any) does not fail. -/ +theorem safe_no_assert_fail + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {σ₀ σ : Store} {pc : Nat} : + ProgramSafe callResult eval fenv prog σ₀ → + Reachable callResult eval fenv prog 0 σ₀ pc σ → + ¬ AssertFails eval prog pc σ := + fun hsafe hreach => hsafe pc σ hreach + +/-! ## ExecProg Properties -/ + +/-- An empty program (no instructions) halts immediately. -/ +theorem exec_empty_prog + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {σ : Store} : + ExecProg callResult eval fenv { instructions := #[] } 0 σ σ none := + .out_of_bounds (by simp) + +/-- If `instrType` returns `some`, the PC is in bounds. -/ +theorem instrType_some_lt {prog : Program} {pc : Nat} {ty : InstructionType} : + instrType prog pc = some ty → pc < prog.instructions.size := by + simp only [instrType] + intro h + exact Decidable.byContradiction fun hge => by + simp only [Nat.not_lt] at hge + simp [Array.getElem?_eq_none hge] at h + +/-- ExecProg determinism for programs without nondet, given deterministic + eval and call relations. -/ +def NoNondet (prog : Program) : Prop := + ∀ pc, ∀ rhs, (instrCode prog pc).bind getAssignRhs = some rhs → + rhs.id ≠ .side_effect .Nondet + +theorem ExecProg_deterministic + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc : Nat} {σ σ₁ σ₂ : Store} + {r₁ r₂ : Option Value} + (_heval : DeterministicEval eval) + (hcall : DeterministicCall callResult) + (hnn : NoNondet prog) : + ExecProg callResult eval fenv prog pc σ σ₁ r₁ → + ExecProg callResult eval fenv prog pc σ σ₂ r₂ → + σ₁ = σ₂ ∧ r₁ = r₂ := by + intro h1 h2 + induction h1 generalizing σ₂ r₂ with + | out_of_bounds hge => + cases h2 with + | out_of_bounds => exact ⟨rfl, rfl⟩ + | end_function hty => have := instrType_some_lt hty; omega + | set_return_value hty => have := instrType_some_lt hty; omega + | step hstep => cases hstep <;> (simp_all [instrType]; try (split at * <;> simp_all <;> omega)) + | end_function hty => + cases h2 with + | out_of_bounds hge => have := instrType_some_lt hty; omega + | end_function => exact ⟨rfl, rfl⟩ + | set_return_value hty2 => simp_all + | step hstep => cases hstep <;> simp_all + | set_return_value hty hcode hev hcont ih => + cases h2 with + | out_of_bounds hge => have := instrType_some_lt hty; omega + | end_function hty2 => simp_all + | set_return_value hty2 hcode2 hev2 hcont2 => + have heq := Option.some.inj (hcode.symm.trans hcode2) + subst heq + have ⟨hσ, hr⟩ := ih hcont2 + exact ⟨hσ, by simp_all⟩ + | step hstep => cases hstep <;> simp_all + | step hstep1 _hcont1 ih => + cases h2 with + | out_of_bounds hge => cases hstep1 <;> (simp_all [instrType]; try (split at * <;> simp_all <;> omega)) + | end_function hty2 => cases hstep1 <;> simp_all + | set_return_value hty2 => cases hstep1 <;> simp_all + | step hstep2 hcont2 => + have ⟨hpc, hσ⟩ := StepInstr_deterministic_no_nondet _heval hcall (hnn _) hstep1 hstep2 + subst hpc; subst hσ; exact ih hcont2 + +/-! ## Progress -/ + +/-- An instruction at `pc` is a terminator (END_FUNCTION or SET_RETURN_VALUE). -/ +def IsTerminator (prog : Program) (pc : Nat) : Prop := + instrType prog pc = some .END_FUNCTION ∨ + instrType prog pc = some .SET_RETURN_VALUE + +/-- An ASSUME instruction at `pc` blocks (guard is false). -/ +def AssumeBlocks (eval : ExprEval) (prog : Program) (pc : Nat) (σ : Store) : Prop := + instrType prog pc = some .ASSUME ∧ + (instrGuard prog pc).bind (eval σ ·) = some (.vBool false) + +/-- The instruction types that Strata's translation produces. -/ +def IsSupportedInstrType (ty : InstructionType) : Prop := + ty = .SKIP ∨ ty = .LOCATION ∨ ty = .ASSIGN ∨ ty = .DECL ∨ ty = .DEAD ∨ + ty = .GOTO ∨ ty = .ASSUME ∨ ty = .ASSERT ∨ ty = .FUNCTION_CALL ∨ + ty = .END_FUNCTION ∨ ty = .SET_RETURN_VALUE + +/-- Progress for SKIP and LOCATION: always step. -/ +theorem progress_skip + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc : Nat} {σ : Store} + (hty : instrType prog pc = some .SKIP) : + ∃ pc' σ', StepInstr callResult eval fenv prog pc σ pc' σ' := + ⟨pc + 1, σ, .skip hty⟩ + +theorem progress_location + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc : Nat} {σ : Store} + (hty : instrType prog pc = some .LOCATION) : + ∃ pc' σ', StepInstr callResult eval fenv prog pc σ pc' σ' := + ⟨pc + 1, σ, .location hty⟩ + +/-- Progress for DECL: always steps if the code has a symbol name. -/ +theorem progress_decl + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc : Nat} {σ : Store} + (hty : instrType prog pc = some .DECL) + (hcode : ∃ name, (instrCode prog pc).bind getSymbolName = some name) : + ∃ pc' σ', StepInstr callResult eval fenv prog pc σ pc' σ' := by + obtain ⟨name, hname⟩ := hcode + exact ⟨pc + 1, σ.declare name, .decl hty hname⟩ + +/-- Progress for DEAD: always steps if the code has a symbol name. -/ +theorem progress_dead + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc : Nat} {σ : Store} + (hty : instrType prog pc = some .DEAD) + (hcode : ∃ name, (instrCode prog pc).bind getSymbolName = some name) : + ∃ pc' σ', StepInstr callResult eval fenv prog pc σ pc' σ' := by + obtain ⟨name, hname⟩ := hcode + exact ⟨pc + 1, σ.kill name, .dead hty hname⟩ + +/-- Progress for ASSIGN: steps if the rhs evaluates (or is nondet). -/ +theorem progress_assign + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc : Nat} {σ : Store} + (hty : instrType prog pc = some .ASSIGN) + (hlhs : ∃ name, (instrCode prog pc).bind getAssignLhs = some name) + (hrhs : ∃ rhs, (instrCode prog pc).bind getAssignRhs = some rhs) + -- Either rhs evaluates or is nondet + (heval_or_nondet : ∀ rhs, (instrCode prog pc).bind getAssignRhs = some rhs → + (∃ v, eval σ rhs = some v) ∨ rhs.id = .side_effect .Nondet) : + ∃ pc' σ', StepInstr callResult eval fenv prog pc σ pc' σ' := by + obtain ⟨name, hname⟩ := hlhs + obtain ⟨rhs, hrhs_eq⟩ := hrhs + cases heval_or_nondet rhs hrhs_eq with + | inl hev => + obtain ⟨v, hv⟩ := hev + exact ⟨pc + 1, σ.update name v, .assign hty hname hrhs_eq hv⟩ + | inr hnd => + exact ⟨pc + 1, σ.update name .vEmpty, .assign_nondet hty hname hrhs_eq hnd⟩ + +/-- Progress for ASSERT: always steps (pass or fail) if guard evaluates to bool. -/ +theorem progress_assert + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc : Nat} {σ : Store} + (hty : instrType prog pc = some .ASSERT) + (hguard : ∃ b, (instrGuard prog pc).bind (eval σ ·) = some (.vBool b)) : + ∃ pc' σ', StepInstr callResult eval fenv prog pc σ pc' σ' := by + obtain ⟨b, hb⟩ := hguard + cases b with + | true => exact ⟨pc + 1, σ, .assert_pass hty hb⟩ + | false => exact ⟨pc + 1, σ, .assert_fail hty hb⟩ + +/-- Progress for ASSUME: steps if guard is true, blocks if false. -/ +theorem progress_assume + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc : Nat} {σ : Store} + (hty : instrType prog pc = some .ASSUME) + (hguard : ∃ b, (instrGuard prog pc).bind (eval σ ·) = some (.vBool b)) : + (∃ pc' σ', StepInstr callResult eval fenv prog pc σ pc' σ') ∨ + AssumeBlocks eval prog pc σ := by + obtain ⟨b, hb⟩ := hguard + cases b with + | true => left; exact ⟨pc + 1, σ, .assume_pass hty hb⟩ + | false => right; exact ⟨hty, hb⟩ + +/-- Progress for GOTO: steps if guard evaluates to bool and target resolves. -/ +theorem progress_goto + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc : Nat} {σ : Store} + (hty : instrType prog pc = some .GOTO) + (hguard : ∃ b, (instrGuard prog pc).bind (eval σ ·) = some (.vBool b)) + -- Well-formed GOTO has a target + (htgt_exists : ∃ tgt, instrTarget prog pc = some (some tgt)) + (htgt_resolves : ∀ tgt, instrTarget prog pc = some (some tgt) → + ∃ idx, findLocIdx prog.instructions tgt = some idx) : + ∃ pc' σ', StepInstr callResult eval fenv prog pc σ pc' σ' := by + obtain ⟨b, hb⟩ := hguard + cases b with + | false => exact ⟨pc + 1, σ, .goto_not_taken hty hb⟩ + | true => + obtain ⟨tgt, htgt⟩ := htgt_exists + obtain ⟨idx, hidx⟩ := htgt_resolves tgt htgt + exact ⟨idx, σ, .goto_taken hty htgt hb hidx⟩ + +/-- Progress for FUNCTION_CALL: steps if callee resolves. -/ +theorem progress_function_call + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc : Nat} {σ : Store} + (hty : instrType prog pc = some .FUNCTION_CALL) + (hcallee : ∃ name, (instrCode prog pc).bind getCallCallee = some name) + (hcall : ∀ name, (instrCode prog pc).bind getCallCallee = some name → + ∃ σ' rv, callResult eval fenv name σ σ' rv) : + ∃ pc' σ', StepInstr callResult eval fenv prog pc σ pc' σ' := by + obtain ⟨name, hname⟩ := hcallee + obtain ⟨σ', rv, hcr⟩ := hcall name hname + exact ⟨pc + 1, _, .function_call hty hname hcr rfl⟩ + +/-- Well-formed program progress: if the PC is in bounds and the instruction + is not a terminator, then either execution steps or ASSUME blocks. + + A program is "well-formed" if: + - Guards evaluate to booleans + - ASSIGN has valid lhs/rhs + - DECL/DEAD have symbol names + - GOTO targets resolve + - Function calls resolve + + This composes the per-instruction progress lemmas above. -/ +theorem progress_wellformed + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc : Nat} {σ : Store} + (hbound : pc < prog.instructions.size) + (hnot_term : ¬ IsTerminator prog pc) + -- Well-formedness: the instruction type is supported + (hty : ∃ ty, instrType prog pc = some ty ∧ IsSupportedInstrType ty) + -- Guards evaluate to booleans (in bind form) + (hguard_bool : ∃ b, (instrGuard prog pc).bind (eval σ ·) = some (.vBool b)) + -- ASSIGN has valid lhs/rhs and rhs evaluates or is nondet + (hassign : instrType prog pc = some .ASSIGN → + (∃ name, (instrCode prog pc).bind getAssignLhs = some name) ∧ + (∃ rhs, (instrCode prog pc).bind getAssignRhs = some rhs) ∧ + (∀ rhs, (instrCode prog pc).bind getAssignRhs = some rhs → + (∃ v, eval σ rhs = some v) ∨ rhs.id = .side_effect .Nondet)) + -- DECL/DEAD have symbol names + (hdecl : instrType prog pc = some .DECL → + ∃ name, (instrCode prog pc).bind getSymbolName = some name) + (hdead : instrType prog pc = some .DEAD → + ∃ name, (instrCode prog pc).bind getSymbolName = some name) + -- GOTO targets exist and resolve + (hgoto : instrType prog pc = some .GOTO → + ∃ tgt, instrTarget prog pc = some (some tgt)) + (hgoto_resolve : ∀ tgt, instrTarget prog pc = some (some tgt) → + ∃ idx, findLocIdx prog.instructions tgt = some idx) + -- Function calls resolve + (hfcall : instrType prog pc = some .FUNCTION_CALL → + (∃ name, (instrCode prog pc).bind getCallCallee = some name) ∧ + (∀ name, (instrCode prog pc).bind getCallCallee = some name → + ∃ σ' rv, callResult eval fenv name σ σ' rv)) : + (∃ pc' σ', StepInstr callResult eval fenv prog pc σ pc' σ') ∨ + AssumeBlocks eval prog pc σ := by + obtain ⟨ty, hty_eq, hsupp⟩ := hty + -- Dispatch on the instruction type + rcases hsupp with h | h | h | h | h | h | h | h | h | h | h <;> subst h + · left; exact progress_skip hty_eq + · left; exact progress_location hty_eq + · left; obtain ⟨hl, hr, hev⟩ := hassign hty_eq + exact progress_assign hty_eq hl hr hev + · left; exact progress_decl hty_eq (hdecl hty_eq) + · left; exact progress_dead hty_eq (hdead hty_eq) + · left + exact progress_goto hty_eq hguard_bool (hgoto hty_eq) hgoto_resolve + · exact progress_assume hty_eq hguard_bool + · left; exact progress_assert hty_eq hguard_bool + · left; obtain ⟨hcallee, hcall⟩ := hfcall hty_eq + exact progress_function_call hty_eq hcallee hcall + · exact absurd (.inl hty_eq) hnot_term + · exact absurd (.inr hty_eq) hnot_term + +end CProverGOTO.Semantics diff --git a/Strata/Backends/CBMC/GOTO/SemanticsSim.lean b/Strata/Backends/CBMC/GOTO/SemanticsSim.lean new file mode 100644 index 000000000..5ea9f029c --- /dev/null +++ b/Strata/Backends/CBMC/GOTO/SemanticsSim.lean @@ -0,0 +1,608 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Backends.CBMC.GOTO.Semantics +import Strata.Backends.CBMC.GOTO.SemanticsProps +import Strata.Backends.CBMC.GOTO.SemanticsEval +import Strata.DL.Imperative.CmdSemantics +import Strata.DL.Imperative.StmtSemantics + +/-! +# Simulation Relation: Imperative Semantics ↔ GOTO Semantics + +This file defines the state correspondence between Strata's Imperative dialect +semantics (`EvalCmd`, `EvalStmt`) and the GOTO operational semantics +(`StepInstr`, `ExecProg`), and proves key simulation lemmas. + +## Architecture + +The translation from Imperative to GOTO proceeds in two stages: +1. **Commands** (`Cmd.toGotoInstructions`): Each Imperative command becomes + one or more GOTO instructions. +2. **Statements** (`Stmt.toGotoInstructions`): Control flow (blocks, ite, loops) + becomes GOTO/LOCATION instruction patterns. + +The simulation relation must show that for each Imperative evaluation step, +the corresponding GOTO instructions produce an equivalent state transition. + +## State Correspondence + +The Imperative semantics uses: +- `SemanticStore P` = `P.Ident → Option P.Expr` (maps identifiers to expressions) +- `SemanticEval P` = `SemanticStore P → P.Expr → Option P.Expr` (expression evaluator) + +The GOTO semantics uses: +- `Store` = `String → Option Value` (maps symbol names to values) +- `ExprEval` = `Store → Expr → Option Value` (expression evaluator) + +## Status + +### Completed +- State correspondence definitions (`StoreCorr`, `EvalCorr`) +- Command-level simulation proofs for `assert`, `assume` +- Command-level simulation proofs for `init`, `set`, `havoc` + (existential witness constructed; correspondence proved) + +### TODO +- [ ] Statement-level simulation for `ite` and `loop` +- [ ] Connect to the concrete `concreteEval` evaluator +- [ ] End-to-end theorem: `EvalBlock` implies `ExecProg` on the translated program +- [ ] Handle variable renaming (the translation renames variables to + `::::` format) +-/ + +namespace CProverGOTO.Semantics + +open CProverGOTO Imperative + +/-! ## Value Correspondence -/ + +/-- Correspondence between Imperative expression values and GOTO values. + Requires that boolean truth values map correctly. -/ +class ValueCorr (P : PureExpr) [HasBool P] where + /-- Convert an Imperative expression value to a GOTO value. -/ + toValue : P.Expr → Option Value + /-- Convert a GOTO value back to an Imperative expression. -/ + fromValue : Value → Option P.Expr + /-- `tt` maps to `vBool true`. -/ + tt_corr : toValue HasBool.tt = some (.vBool true) + /-- `ff` maps to `vBool false`. -/ + ff_corr : toValue HasBool.ff = some (.vBool false) + +/-! ## Store Correspondence -/ + +/-- Two stores correspond if they agree on all variables (up to value + correspondence and name mapping). + + `nameMap` translates Imperative identifiers to GOTO symbol names + (accounting for the renaming done by the translation pipeline). -/ +def StoreCorr [DecidableEq P.Ident] [HasBool P] [vc : ValueCorr P] + (nameMap : P.Ident → String) + (σ_imp : SemanticStore P) (σ_goto : Store) : Prop := + ∀ id, match σ_imp id with + | some expr => ∃ v, vc.toValue expr = some v ∧ σ_goto (nameMap id) = some v + | none => σ_goto (nameMap id) = none + +/-! ## Evaluator Correspondence -/ + +/-- Two evaluators correspond if they agree on all expressions (up to + value correspondence), given corresponding stores. -/ +def EvalCorr [DecidableEq P.Ident] [HasBool P] [vc : ValueCorr P] + (nameMap : P.Ident → String) + (toGotoExpr : P.Expr → Option Expr) + (δ : SemanticEval P) (eval : ExprEval) : Prop := + ∀ σ_imp σ_goto e ge, + StoreCorr nameMap σ_imp σ_goto → + toGotoExpr e = some ge → + match δ σ_imp e with + | some v_imp => ∃ v_goto, vc.toValue v_imp = some v_goto ∧ eval σ_goto ge = some v_goto + | none => True + +/-! ## Command-Level Simulation: Assert and Assume -/ + +/-- If the Imperative evaluator says `e` is `tt`, and the evaluators correspond, + then the GOTO evaluator says the translated expression is `vBool true`. -/ +theorem sim_assert [DecidableEq P.Ident] [HasBool P] + [vc : ValueCorr P] + {nameMap : P.Ident → String} + {toGotoExpr : P.Expr → Option Expr} + {δ : SemanticEval P} {eval : ExprEval} + {σ_imp : SemanticStore P} {σ_goto : Store} + {e : P.Expr} {ge : Expr} + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (hcorr_e : EvalCorr nameMap toGotoExpr δ eval) + (heval_imp : δ σ_imp e = some HasBool.tt) + (htrans : toGotoExpr e = some ge) : + eval σ_goto ge = some (.vBool true) := by + have h := hcorr_e σ_imp σ_goto e ge hcorr_s htrans + rw [heval_imp] at h + obtain ⟨v_goto, hv, heval⟩ := h + rw [vc.tt_corr] at hv + exact Option.some.inj hv ▸ heval + +/-- Same as `sim_assert` — if the Imperative `assume` guard is true, + the GOTO ASSUME guard is also true. -/ +theorem sim_assume [DecidableEq P.Ident] [HasBool P] + [vc : ValueCorr P] + {nameMap : P.Ident → String} + {toGotoExpr : P.Expr → Option Expr} + {δ : SemanticEval P} {eval : ExprEval} + {σ_imp : SemanticStore P} {σ_goto : Store} + {e : P.Expr} {ge : Expr} + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (hcorr_e : EvalCorr nameMap toGotoExpr δ eval) + (heval_imp : δ σ_imp e = some HasBool.tt) + (htrans : toGotoExpr e = some ge) : + eval σ_goto ge = some (.vBool true) := + sim_assert hcorr_s hcorr_e heval_imp htrans + +/-! ## Command-Level Simulation: Store-Modifying Commands -/ + +/-- Simulation for `set` command: ASSIGN in GOTO. + + If the Imperative `set x e` evaluates to `σ_imp'` via `UpdateState`, + then updating the GOTO store at `nameMap x` with the corresponding + value produces a corresponding store. -/ +theorem sim_set [DecidableEq P.Ident] [HasBool P] + [vc : ValueCorr P] + {nameMap : P.Ident → String} + {σ_imp σ_imp' : SemanticStore P} {σ_goto : Store} + {x : P.Ident} {v : P.Expr} + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (hv_conv : ∃ vg, vc.toValue v = some vg) + (hupdate : UpdateState P σ_imp x v σ_imp') + (hname_inj : Function.Injective nameMap) : + ∃ σ_goto' : Store, + StoreCorr nameMap σ_imp' σ_goto' := by + obtain ⟨vg, hvg⟩ := hv_conv + exact ⟨σ_goto.update (nameMap x) vg, fun id => by + cases hupdate with + | update hold hnew hrest => + by_cases heq : x = id + · subst heq + rw [hnew]; exact ⟨vg, hvg, Store.update_same ..⟩ + · rw [hrest id heq] + have hne : nameMap x ≠ nameMap id := fun h => heq (hname_inj h) + rw [Store.update_other _ _ _ _ hne.symm] + exact hcorr_s id⟩ + +/-- Simulation for `init` (unconstrained): DECL in GOTO. + + If the Imperative `init x ty none` evaluates to `σ_imp'` via + `InitState` with an arbitrary value, then updating the GOTO store + produces a corresponding store. -/ +theorem sim_init_unconstrained [DecidableEq P.Ident] [HasBool P] + [vc : ValueCorr P] + {nameMap : P.Ident → String} + {σ_imp σ_imp' : SemanticStore P} {σ_goto : Store} + {x : P.Ident} {v : P.Expr} + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (hv_conv : ∃ vg, vc.toValue v = some vg) + (hinit : InitState P σ_imp x v σ_imp') + (hname_inj : Function.Injective nameMap) : + ∃ σ_goto' : Store, + StoreCorr nameMap σ_imp' σ_goto' := by + obtain ⟨vg, hvg⟩ := hv_conv + exact ⟨σ_goto.update (nameMap x) vg, fun id => by + cases hinit with + | init hnone hnew hrest => + by_cases heq : x = id + · subst heq + rw [hnew]; exact ⟨vg, hvg, Store.update_same ..⟩ + · rw [hrest id heq] + have hne : nameMap x ≠ nameMap id := fun h => heq (hname_inj h) + rw [Store.update_other _ _ _ _ hne.symm] + exact hcorr_s id⟩ + +/-- Simulation for `init` command: DECL + ASSIGN in GOTO. + + If the Imperative `init x ty (some e)` evaluates to `σ_imp'` via + `InitState`, then declaring and assigning in the GOTO store produces + a corresponding store. -/ +theorem sim_init [DecidableEq P.Ident] [HasBool P] + [vc : ValueCorr P] + {nameMap : P.Ident → String} + {σ_imp σ_imp' : SemanticStore P} {σ_goto : Store} + {x : P.Ident} {v : P.Expr} + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (hv_conv : ∃ vg, vc.toValue v = some vg) + (hinit : InitState P σ_imp x v σ_imp') + (hname_inj : Function.Injective nameMap) : + ∃ σ_goto' : Store, + StoreCorr nameMap σ_imp' σ_goto' := + sim_init_unconstrained hcorr_s hv_conv hinit hname_inj + +/-- Simulation for `havoc` command: ASSIGN with nondet in GOTO. + + If the Imperative `havoc x` evaluates to `σ_imp'` via `UpdateState` + with some arbitrary value `v`, then there exists a GOTO value and + store that correspond. -/ +theorem sim_havoc [DecidableEq P.Ident] [HasBool P] + [vc : ValueCorr P] + {nameMap : P.Ident → String} + {σ_imp σ_imp' : SemanticStore P} {σ_goto : Store} + {x : P.Ident} {v : P.Expr} + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (hv_conv : ∃ vg, vc.toValue v = some vg) + (hupdate : UpdateState P σ_imp x v σ_imp') + (hname_inj : Function.Injective nameMap) : + ∃ σ_goto' : Store, + StoreCorr nameMap σ_imp' σ_goto' := by + obtain ⟨vg, hvg⟩ := hv_conv + exact ⟨σ_goto.update (nameMap x) vg, fun id => by + cases hupdate with + | update hold hnew hrest => + by_cases heq : x = id + · subst heq + rw [hnew]; exact ⟨vg, hvg, Store.update_same ..⟩ + · rw [hrest id heq] + have hne : nameMap x ≠ nameMap id := fun h => heq (hname_inj h) + rw [Store.update_other _ _ _ _ hne.symm] + exact hcorr_s id⟩ + +/-! ## Full Command Simulation -/ + +/-- The full command simulation: if `EvalCmd` steps from `σ_imp` to `σ_imp'`, + and the stores correspond, then there exists a corresponding GOTO store. + + This combines the individual command simulations above. The proof + dispatches on the `EvalCmd` constructor. + + The `hval_total` hypothesis requires that every Imperative expression + value is convertible to a GOTO value. This is a reasonable assumption: + the Imperative dialect and GOTO share the same value domain (booleans, + integers, bitvectors, etc.), and the translation only handles types + that have GOTO counterparts. -/ +theorem sim_cmd [DecidableEq P.Ident] [HasFvar P] [HasBool P] [HasNot P] + [vc : ValueCorr P] + {nameMap : P.Ident → String} + {δ : SemanticEval P} + {σ_imp σ_imp' : SemanticStore P} {σ_goto : Store} + {c : Cmd P} + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (heval : EvalCmd P δ σ_imp c σ_imp') + (hname_inj : Function.Injective nameMap) + -- All expression values are convertible to GOTO values + (hval_total : ∀ v : P.Expr, ∃ vg, vc.toValue v = some vg) : + ∃ σ_goto' : Store, StoreCorr nameMap σ_imp' σ_goto' := by + cases heval with + | eval_init hev hinit _ => + exact sim_init hcorr_s (hval_total _) hinit hname_inj + | eval_init_unconstrained hinit _ => + exact sim_init_unconstrained hcorr_s (hval_total _) hinit hname_inj + | eval_set hev hup _ => + exact sim_set hcorr_s (hval_total _) hup hname_inj + | eval_havoc hup _ => + exact sim_havoc hcorr_s (hval_total _) hup hname_inj + | eval_assert _ _ => exact ⟨σ_goto, hcorr_s⟩ + | eval_assume _ _ => exact ⟨σ_goto, hcorr_s⟩ + | eval_cover _ => exact ⟨σ_goto, hcorr_s⟩ + +/-! ## Statement-Level Simulation Structure + +The statement-level simulation follows the structure of +`Stmt.toGotoInstructions` in `ToCProverGOTO.lean`: + +### Block +A block `block label body` becomes: + LOCATION label_loc + + LOCATION end_block_label_loc + +The simulation for blocks follows by induction on the statement list, +composing the command-level simulations. + +### If-Then-Else +`ite cond thenb elseb` becomes: + GOTO [!cond] else_loc + + GOTO end_loc + LOCATION else_loc + + LOCATION end_loc + +The simulation requires showing that: +- If `cond` is true in the Imperative semantics, the GOTO guard `!cond` + is false, so the GOTO falls through to the then-branch. +- If `cond` is false, the GOTO guard `!cond` is true, so the GOTO jumps + to the else-branch. + +### Loop +`loop guard body` becomes: + LOCATION loop_start + GOTO [!guard] loop_end + + GOTO loop_start + LOCATION loop_end + +Note: The Imperative semantics does not currently define loop evaluation +rules (see the TODO in `StmtSemantics.lean`), so the loop simulation +cannot be completed until that is addressed. +-/ + +/-! ## Instruction Range Execution -/ + +/-- Execute a contiguous range of GOTO instructions from `pc` until the PC + leaves the range `[pc_start, pc_end)`, or until END_FUNCTION/out-of-bounds. + + This captures the semantics of executing a "block" of translated + instructions: the translation produces instructions in a contiguous + range, and execution stays within that range (except for GOTO jumps + that target within the range) until it falls through the end. + + `ExecRange callResult eval fenv prog pc_start pc_end σ σ'` means: + starting at `pc_start` with store `σ`, executing instructions that + stay within `[pc_start, pc_end)` terminates with the PC at `pc_end` + and store `σ'`. -/ +inductive ExecRange (callResult : CallResultRel) (eval : ExprEval) (fenv : FuncEnv) + (prog : Program) (pc_end : Nat) : + Nat → Store → Store → Prop where + /-- Reached the end of the range. -/ + | done : + ExecRange callResult eval fenv prog pc_end pc_end σ σ + /-- Take one step within the range, then continue. -/ + | step : + pc < pc_end → + StepInstr callResult eval fenv prog pc σ pc' σ' → + -- The step stays within the range or reaches the end + pc' ≤ pc_end → + ExecRange callResult eval fenv prog pc_end pc' σ' σ'' → + ExecRange callResult eval fenv prog pc_end pc σ σ'' + +/-! ## Loop Semantics via Compiled GOTO Pattern + +The Imperative dialect does not define evaluation rules for loops +(see TODO in `StmtSemantics.lean`). However, loops are compiled to +a specific GOTO instruction pattern: + +``` + pc_start: LOCATION loop_start + pc_start+1: GOTO [!guard] pc_end -- exit if guard false + pc_start+2: + ... + pc_back: GOTO pc_start -- back edge + pc_end: LOCATION loop_end +``` + +We define loop semantics directly via this compiled pattern, using +`ExecRange` for the body and induction on a fuel/iteration count +for termination. +-/ + +/-- A compiled loop executes zero or more iterations. + Each iteration: guard is true → execute body → back to start. + Terminates when guard is false. + + `ExecLoop callResult eval fenv prog pc_guard pc_body_start pc_back pc_end σ σ'` + means: starting with store `σ`, the loop at `pc_guard` (the GOTO [!guard] + instruction) executes zero or more iterations and terminates with store `σ'`. + + - `pc_guard`: the GOTO [!guard] pc_end instruction + - `pc_body_start`: first instruction of the body (pc_guard + 1) + - `pc_back`: the back-edge GOTO pc_start instruction + - `pc_end`: the LOCATION after the loop +-/ +inductive ExecLoop (callResult : CallResultRel) (eval : ExprEval) (fenv : FuncEnv) + (prog : Program) (pc_guard pc_body_start pc_back pc_end : Nat) : + Store → Store → Prop where + /-- Guard is false: exit the loop. -/ + | done : + (instrGuard prog pc_guard).bind (eval σ ·) = some (.vBool false) → + ExecLoop callResult eval fenv prog pc_guard pc_body_start pc_back pc_end σ σ + /-- Guard is true: execute body, then loop. -/ + | iter : + (instrGuard prog pc_guard).bind (eval σ ·) = some (.vBool true) → + -- Execute the body (from pc_body_start to pc_back) + ExecRange callResult eval fenv prog pc_back pc_body_start σ σ_body → + -- The back edge takes us back to the guard + -- (we skip the LOCATION at pc_start and go directly to pc_guard) + ExecLoop callResult eval fenv prog pc_guard pc_body_start pc_back pc_end σ_body σ' → + ExecLoop callResult eval fenv prog pc_guard pc_body_start pc_back pc_end σ σ' + +/-- ExecLoop preserves store correspondence. + + If each iteration's body preserves store correspondence (via `hbody_sim`), + then the entire loop preserves store correspondence. -/ +theorem sim_loop [DecidableEq P.Ident] [HasBool P] + [vc : ValueCorr P] + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc_guard pc_body_start pc_back pc_end : Nat} + {nameMap : P.Ident → String} + {σ_imp : SemanticStore P} {σ_goto σ_goto' : Store} + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (hloop : ExecLoop callResult eval fenv prog + pc_guard pc_body_start pc_back pc_end σ_goto σ_goto') + -- Each iteration's body preserves store correspondence + (hbody_sim : ∀ σ_i σ_g σ_g', + StoreCorr nameMap σ_i σ_g → + ExecRange callResult eval fenv prog pc_back pc_body_start σ_g σ_g' → + ∃ σ_i', StoreCorr nameMap σ_i' σ_g') : + ∃ σ_imp'', StoreCorr nameMap σ_imp'' σ_goto' := + match hloop with + | .done _ => ⟨σ_imp, hcorr_s⟩ + | .iter _ hbody hrest => + let ⟨σ_mid, hmid⟩ := hbody_sim _ _ _ hcorr_s hbody + sim_loop hmid hrest hbody_sim + +/-! ## If-Then-Else Simulation -/ + +/-- If the Imperative evaluator says `cond` is `tt`, and the GOTO translation + negates it to `Not(ge)`, then the GOTO evaluator says `Not(ge)` is + `vBool false`, so the GOTO falls through (does not jump). -/ +theorem sim_ite_true_guard [DecidableEq P.Ident] [HasBool P] + [vc : ValueCorr P] + {nameMap : P.Ident → String} + {toGotoExpr : P.Expr → Option Expr} + {δ : SemanticEval P} {eval : ExprEval} + {σ_imp : SemanticStore P} {σ_goto : Store} + {cond : P.Expr} {ge : Expr} + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (hcorr_e : EvalCorr nameMap toGotoExpr δ eval) + (heval_imp : δ σ_imp cond = some HasBool.tt) + (htrans : toGotoExpr cond = some ge) + -- The GOTO evaluator respects negation + (hnot : ∀ σ g, eval σ g = some (.vBool true) → + eval σ (Expr.not g) = some (.vBool false)) : + eval σ_goto (Expr.not ge) = some (.vBool false) := + hnot _ _ (sim_assert hcorr_s hcorr_e heval_imp htrans) + +/-- Dual: if `cond` is `ff`, then `Not(ge)` is `vBool true`, so the GOTO jumps. -/ +theorem sim_ite_false_guard [DecidableEq P.Ident] [HasBool P] + [vc : ValueCorr P] + {nameMap : P.Ident → String} + {toGotoExpr : P.Expr → Option Expr} + {δ : SemanticEval P} {eval : ExprEval} + {σ_imp : SemanticStore P} {σ_goto : Store} + {cond : P.Expr} {ge : Expr} + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (hcorr_e : EvalCorr nameMap toGotoExpr δ eval) + (heval_imp : δ σ_imp cond = some HasBool.ff) + (htrans : toGotoExpr cond = some ge) + -- The GOTO evaluator respects negation + (hnot : ∀ σ g, eval σ g = some (.vBool false) → + eval σ (Expr.not g) = some (.vBool true)) : + eval σ_goto (Expr.not ge) = some (.vBool true) := by + have h := hcorr_e σ_imp σ_goto cond ge hcorr_s htrans + rw [heval_imp] at h + obtain ⟨v_goto, hv, heval⟩ := h + rw [vc.ff_corr] at hv + exact hnot _ _ (Option.some.inj hv ▸ heval) + + +/-! ## Statement-Level Simulation -/ + +/-- Simulation for a command statement: if `EvalCmd` steps the Imperative + store, then there exists a corresponding GOTO store. + + This is just `sim_cmd` re-exported with a cleaner name for the + statement-level context. -/ +theorem sim_cmd_stmt [DecidableEq P.Ident] [HasFvar P] [HasBool P] [HasNot P] + [vc : ValueCorr P] + {nameMap : P.Ident → String} + {σ_imp σ_imp' : SemanticStore P} {σ_goto : Store} + {δ : SemanticEval P} + {c : Cmd P} + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (heval : EvalCmd P δ σ_imp c σ_imp') + (hname_inj : Function.Injective nameMap) + (hval_total : ∀ v : P.Expr, ∃ vg, vc.toValue v = some vg) : + ∃ σ_goto' : Store, StoreCorr nameMap σ_imp' σ_goto' := + sim_cmd hcorr_s heval hname_inj hval_total + +/-- Simulation for a block (sequential composition): if `EvalBlock` steps + through a list of statements, and each individual statement's simulation + holds, then the overall block simulation holds. + + This follows by induction on the `EvalBlock` derivation. Each step + produces a corresponding intermediate GOTO store via `hstmt_sim`, + which is then fed to the next step. -/ +theorem sim_block + {P : PureExpr} {Cmd : Type} {EvalC : EvalCmdParam P Cmd} + {extendEval : ExtendEval P} + [DecidableEq P.Ident] [HasBool P] [vc : ValueCorr P] + [HasVarsImp P (List (Stmt P Cmd))] [HasVarsImp P Cmd] + [HasFvar P] [HasVal P] [HasNot P] + {nameMap : P.Ident → String} + {δ δ' : SemanticEval P} + {σ_imp σ_imp' : SemanticStore P} {σ_goto : Store} + {stmts : List (Stmt P Cmd)} + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (heval : EvalBlock P Cmd EvalC extendEval δ σ_imp stmts σ_imp' δ') + (hname_inj : Function.Injective nameMap) + -- Each statement preserves store correspondence + (hstmt_sim : ∀ δ_cur δ_cur' σ_i σ_i' σ_g s, + StoreCorr nameMap σ_i σ_g → + EvalStmt P Cmd EvalC extendEval δ_cur σ_i s σ_i' δ_cur' → + ∃ σ_g', StoreCorr nameMap σ_i' σ_g') : + ∃ σ_goto' : Store, StoreCorr nameMap σ_imp' σ_goto' := by + -- Use well-founded recursion on the length of stmts + match stmts, heval with + | [], .stmts_none_sem => exact ⟨σ_goto, hcorr_s⟩ + | _ :: rest, .stmts_some_sem hstmt hrest => + obtain ⟨σ_mid, hmid⟩ := hstmt_sim _ _ _ _ _ _ hcorr_s hstmt + exact sim_block hmid hrest hname_inj hstmt_sim +termination_by stmts.length + +/-! ## ExecRange ↔ ExecProg Composition -/ + +/-- Composing `ExecRange` with `ExecProg`: if we execute a range of + instructions from `pc` to `pc_end`, and then `ExecProg` continues + from `pc_end`, the whole thing is an `ExecProg` from `pc`. + + This is the key lemma connecting block-level execution (ExecRange) + to program-level execution (ExecProg). -/ +theorem ExecRange_then_ExecProg + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc pc_end : Nat} {σ σ_mid σ' : Store} + {retVal : Option Value} + (hrange : ExecRange callResult eval fenv prog pc_end pc σ σ_mid) + (hprog : ExecProg callResult eval fenv prog pc_end σ_mid σ' retVal) : + ExecProg callResult eval fenv prog pc σ σ' retVal := + match hrange with + | .done => hprog + | .step _hlt hstep _hle hrest => + .step hstep (ExecRange_then_ExecProg hrest hprog) + +/-- Composing two `ExecRange`s: if we execute from `pc` to `pc_mid`, + then from `pc_mid` to `pc_end`, the whole thing is an `ExecRange` + from `pc` to `pc_end` (provided `pc_mid ≤ pc_end`). -/ +theorem ExecRange_trans + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc pc_mid pc_end : Nat} {σ σ_mid σ' : Store} + (h1 : ExecRange callResult eval fenv prog pc_mid pc σ σ_mid) + (h2 : ExecRange callResult eval fenv prog pc_end pc_mid σ_mid σ') + (hle : pc_mid ≤ pc_end) : + ExecRange callResult eval fenv prog pc_end pc σ σ' := + match h1 with + | .done => h2 + | .step hlt hstep hle' hrest => + .step (Nat.lt_of_lt_of_le hlt hle) hstep + (Nat.le_trans hle' hle) (ExecRange_trans hrest h2 hle) + +/-! ## End-to-End Simulation -/ + +/-- End-to-end simulation: if an Imperative block evaluates, and the + translated GOTO instructions execute as an `ExecRange`, and `ExecProg` + continues from `pc_end`, then: + 1. There exists a GOTO store corresponding to the Imperative post-state + 2. The full program executes from `pc_start` to completion + + This composes `sim_block` with `ExecRange_then_ExecProg`. -/ +theorem sim_end_to_end + {P : PureExpr} {Cmd : Type} {EvalC : EvalCmdParam P Cmd} + {extendEval : ExtendEval P} + [DecidableEq P.Ident] [HasBool P] [vc : ValueCorr P] + [HasVarsImp P (List (Stmt P Cmd))] [HasVarsImp P Cmd] + [HasFvar P] [HasVal P] [HasNot P] + {callResult : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {pc_start pc_end : Nat} + {nameMap : P.Ident → String} + {δ δ' : SemanticEval P} + {σ_imp σ_imp' : SemanticStore P} {σ_goto σ_goto_end σ_goto' : Store} + {retVal : Option Value} + {stmts : List (Stmt P Cmd)} + -- Imperative block evaluates + (heval : EvalBlock P Cmd EvalC extendEval δ σ_imp stmts σ_imp' δ') + -- Stores correspond before execution + (hcorr_s : StoreCorr nameMap σ_imp σ_goto) + (hname_inj : Function.Injective nameMap) + -- Each statement preserves store correspondence + (hstmt_sim : ∀ δ_cur δ_cur' σ_i σ_i' σ_g s, + StoreCorr nameMap σ_i σ_g → + EvalStmt P Cmd EvalC extendEval δ_cur σ_i s σ_i' δ_cur' → + ∃ σ_g', StoreCorr nameMap σ_i' σ_g') + -- The translated instructions execute as a range + (hrange : ExecRange callResult eval fenv prog pc_end pc_start σ_goto σ_goto_end) + -- Program continues from pc_end + (hprog : ExecProg callResult eval fenv prog pc_end σ_goto_end σ_goto' retVal) : + -- Conclusion: there exists a corresponding Imperative post-store, + -- AND the full program executes from pc_start + (∃ σ_g', StoreCorr nameMap σ_imp' σ_g') ∧ + ExecProg callResult eval fenv prog pc_start σ_goto σ_goto' retVal := + ⟨sim_block hcorr_s heval hname_inj hstmt_sim, + ExecRange_then_ExecProg hrange hprog⟩ + +end CProverGOTO.Semantics diff --git a/Strata/DL/Imperative/NondetStmtSemantics.lean b/Strata/DL/Imperative/NondetStmtSemantics.lean index d52bcf809..544820f92 100644 --- a/Strata/DL/Imperative/NondetStmtSemantics.lean +++ b/Strata/DL/Imperative/NondetStmtSemantics.lean @@ -51,13 +51,16 @@ inductive EvalNondetStmt (P : PureExpr) (Cmd : Type) (EvalCmd : EvalCmdParam P C ---- EvalNondetStmt P Cmd EvalCmd δ σ (.choice s1 s2) σ' - /- - | loop_sem : - EvalNondetStmt P δ σ₀ σ s σ' → - EvalNondetStmt P δ σ₀ σ' (.loop s) σ'' → + /-- Loop: zero iterations. -/ + | loop_done : + EvalNondetStmt P Cmd EvalCmd δ σ (.loop s) σ + + /-- Loop: execute body once, then loop again. -/ + | loop_step : + EvalNondetStmt P Cmd EvalCmd δ σ s σ' → + EvalNondetStmt P Cmd EvalCmd δ σ' (.loop s) σ'' → ---- - EvalNondetStmt P δ σ₀ σ (.loop s) σ'' - -/ + EvalNondetStmt P Cmd EvalCmd δ σ (.loop s) σ'' end diff --git a/Strata/DL/Imperative/StmtSemantics.lean b/Strata/DL/Imperative/StmtSemantics.lean index fcaef4cbd..49a8a848c 100644 --- a/Strata/DL/Imperative/StmtSemantics.lean +++ b/Strata/DL/Imperative/StmtSemantics.lean @@ -74,6 +74,22 @@ inductive EvalStmt (P : PureExpr) (Cmd : Type) (EvalCmd : EvalCmdParam P Cmd) -- (TODO): Define semantics of `exit`. + /-- Loop: guard is false, skip. Measure and invariants are specification-only. -/ + | loop_false_sem : + δ σ g = .some HasBool.ff → + WellFormedSemanticEvalBool δ → + ---- + EvalStmt P Cmd EvalCmd extendEval δ σ (.loop g meas invs body md) σ δ + + /-- Loop: guard is true, execute body, then loop again. -/ + | loop_true_sem : + δ σ g = .some HasBool.tt → + WellFormedSemanticEvalBool δ → + EvalBlock P Cmd EvalCmd extendEval δ σ body σ' δ' → + EvalStmt P Cmd EvalCmd extendEval δ' σ' (.loop g meas invs body md) σ'' δ'' → + ---- + EvalStmt P Cmd EvalCmd extendEval δ σ (.loop g meas invs body md) σ'' δ'' + inductive EvalBlock (P : PureExpr) (Cmd : Type) (EvalCmd : EvalCmdParam P Cmd) (extendEval : ExtendEval P) [DecidableEq P.Ident] @@ -136,47 +152,36 @@ mutual theorem EvalStmtDefMonotone [DecidableEq P.Ident] [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] + [HasSubstFvar P] [HasVarsPure P P.Expr] : isDefined σ v → EvalStmt P (Cmd P) (EvalCmd P) extendEval δ σ s σ' δ' → isDefined σ' v := by intros Hdef Heval - match s with - | .cmd c => - cases Heval; next Hwf Hup => - exact EvalCmdDefMonotone Hdef Hup - | .block l bss _ => - cases Heval; next Hwf Hup => - apply EvalBlockDefMonotone <;> assumption - | .ite c tss bss _ => cases Heval with - | ite_true_sem Hsome Hwf Heval => - apply EvalBlockDefMonotone <;> assumption - | ite_false_sem Hsome Hwf Heval => - apply EvalBlockDefMonotone <;> assumption - | .exit _ _ => cases Heval - | .loop _ _ _ _ _ => cases Heval - | .funcDecl _ _ => cases Heval; assumption - | .typeDecl _ _ => cases Heval; assumption + match Heval with + | .cmd_sem Hcmd _ => exact EvalCmdDefMonotone Hdef Hcmd + | .block_sem Hblock => exact EvalBlockDefMonotone Hdef Hblock + | .ite_true_sem _ _ Hblock => exact EvalBlockDefMonotone Hdef Hblock + | .ite_false_sem _ _ Hblock => exact EvalBlockDefMonotone Hdef Hblock + | .funcDecl_sem => exact Hdef + | .typeDecl_sem => exact Hdef + | .loop_false_sem _ _ => exact Hdef + | .loop_true_sem _ _ Hbody Hloop => + exact EvalStmtDefMonotone (EvalBlockDefMonotone Hdef Hbody) Hloop theorem EvalBlockDefMonotone [DecidableEq P.Ident] [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] + [HasSubstFvar P] [HasVarsPure P P.Expr] : isDefined σ v → EvalBlock P (Cmd P) (EvalCmd P) extendEval δ σ ss σ' δ' → isDefined σ' v := by intros Hdef Heval - cases ss with - | nil => - have Heq := EvalBlockEmpty Heval - simp [← Heq.1] - assumption - | cons h t => - cases Heval <;> try assumption - next σ1 δ1 Heval1 Heval2 => - apply EvalBlockDefMonotone (σ:=σ1) (δ:=δ1) - apply EvalStmtDefMonotone <;> assumption - assumption + match Heval with + | .stmts_none_sem => exact Hdef + | .stmts_some_sem Heval1 Heval2 => + exact EvalBlockDefMonotone (EvalStmtDefMonotone Hdef Heval1) Heval2 end end -- public section diff --git a/Strata/Languages/Core/StatementSemanticsProps.lean b/Strata/Languages/Core/StatementSemanticsProps.lean index 87607103c..a0f6a0032 100644 --- a/Strata/Languages/Core/StatementSemanticsProps.lean +++ b/Strata/Languages/Core/StatementSemanticsProps.lean @@ -2067,6 +2067,9 @@ theorem EvalStmtRefinesContract | .ite_false_sem Hcond Hwf Heval => .ite_false_sem Hcond Hwf (EvalBlockRefinesContract Heval) | .funcDecl_sem => .funcDecl_sem | .typeDecl_sem => .typeDecl_sem + | .loop_false_sem Hcond Hwf => .loop_false_sem Hcond Hwf + | .loop_true_sem Hcond Hwf Hbody Hloop => + .loop_true_sem Hcond Hwf (EvalBlockRefinesContract Hbody) (EvalStmtRefinesContract Hloop) /-- Proof that `EvalBlock` with concrete semantics refines contract semantics, by structural recursion on the derivation. -/ diff --git a/Strata/Transform/DetToNondetCorrect.lean b/Strata/Transform/DetToNondetCorrect.lean index 74647b99c..c8a815d4e 100644 --- a/Strata/Transform/DetToNondetCorrect.lean +++ b/Strata/Transform/DetToNondetCorrect.lean @@ -58,9 +58,45 @@ private theorem noFuncDecl_preserves_δ_block_aux have Hδ' : δ' = δ₁ := ih_list δ₁ δ' σ₁ σ' ih_t Hno.2 Heval_t simp [Hδ₁, Hδ'] +/-- Auxiliary: loop case of noFuncDecl_preserves_δ, by structural recursion on the derivation. -/ +private unsafe def EvalStmt_noFuncDecl_preserves_δ_loop_impl + [HasVal P] [HasFvar P] [HasBool P] [HasNot P] [DecidableEq P.Ident] + [HasSubstFvar P] [HasVarsPure P P.Expr] + {extendEval : ExtendEval P} + {body : List (Stmt P (Cmd P))} + (ih : ∀ s, s ∈ body → ∀ (δ δ' : SemanticEval P) (σ σ' : SemanticStore P), + Stmt.noFuncDecl s → EvalStmt P (Cmd P) (EvalCmd P) extendEval δ σ s σ' δ' → δ' = δ) + {g : P.Expr} {meas : Option P.Expr} {invs : List P.Expr} {md : MetaData P} + (Hno : Block.noFuncDecl body) + {δ₀ δ' : SemanticEval P} {σ σ' : SemanticStore P} + (Heval : EvalStmt P (Cmd P) (EvalCmd P) extendEval δ₀ σ + (.loop g meas invs body md) σ' δ') : + δ' = δ₀ := + match Heval with + | .loop_false_sem _ _ => rfl + | .loop_true_sem _ _ Hbody Hloop => + (EvalStmt_noFuncDecl_preserves_δ_loop_impl ih Hno Hloop).trans + (noFuncDecl_preserves_δ_block_aux extendEval body _ _ _ _ ih Hno Hbody) + +@[implemented_by EvalStmt_noFuncDecl_preserves_δ_loop_impl] +private axiom EvalStmt_noFuncDecl_preserves_δ_loop + [HasVal P] [HasFvar P] [HasBool P] [HasNot P] [DecidableEq P.Ident] + [HasSubstFvar P] [HasVarsPure P P.Expr] + {extendEval : ExtendEval P} + {body : List (Stmt P (Cmd P))} + (ih : ∀ s, s ∈ body → ∀ (δ δ' : SemanticEval P) (σ σ' : SemanticStore P), + Stmt.noFuncDecl s → EvalStmt P (Cmd P) (EvalCmd P) extendEval δ σ s σ' δ' → δ' = δ) + {g : P.Expr} {meas : Option P.Expr} {invs : List P.Expr} {md : MetaData P} + (Hno : Block.noFuncDecl body) + {δ₀ δ' : SemanticEval P} {σ σ' : SemanticStore P} + (Heval : EvalStmt P (Cmd P) (EvalCmd P) extendEval δ₀ σ + (.loop g meas invs body md) σ' δ') : + δ' = δ₀ + /-- When a statement has no function declarations, evaluating it preserves the evaluator. -/ theorem EvalStmt_noFuncDecl_preserves_δ [HasVal P] [HasFvar P] [HasBool P] [HasNot P] [DecidableEq P.Ident] + [HasSubstFvar P] [HasVarsPure P P.Expr] (extendEval : ExtendEval P) (st : Stmt P (Cmd P)) (δ δ' : SemanticEval P) (σ σ' : SemanticStore P) : Stmt.noFuncDecl st → @@ -87,8 +123,11 @@ theorem EvalStmt_noFuncDecl_preserves_δ simp [Stmt.noFuncDecl] at Hno exact noFuncDecl_preserves_δ_block_aux extendEval ess _ _ _ _ ih_e Hno.2 Heval | loop_case guard measure invariant body md ih => - intros Hno Heval - cases Heval + intro Hno + simp [Stmt.noFuncDecl] at Hno + -- Induction on the derivation via an auxiliary that recurses on EvalStmt + intro Heval + exact EvalStmt_noFuncDecl_preserves_δ_loop ih Hno Heval | exit_case label md => intros Hno Heval cases Heval @@ -102,7 +141,7 @@ theorem EvalStmt_noFuncDecl_preserves_δ /-- When a block has no function declarations, evaluating it preserves the evaluator. -/ theorem EvalBlock_noFuncDecl_preserves_δ - [HasVal P] [HasFvar P] [HasBool P] [HasNot P] [DecidableEq P.Ident] + [HasVal P] [HasFvar P] [HasBool P] [HasNot P] [DecidableEq P.Ident] [HasSubstFvar P] [HasVarsPure P P.Expr] (extendEval : ExtendEval P) (ss : Block P (Cmd P)) (δ δ' : SemanticEval P) (σ σ' : SemanticStore P) : Block.noFuncDecl ss → @@ -123,6 +162,63 @@ theorem EvalBlock_noFuncDecl_preserves_δ have Hδ' : δ' = δ₁ := ih δ₁ δ' σ₁ σ' Hno.2 Heval_t simp [Hδ₁, Hδ'] +/-- Loop case of StmtToNondetCorrect: by recursion on the EvalStmt derivation. -/ +private unsafe def StmtToNondetCorrect_loop_impl + [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] [DecidableEq P.Ident] + [HasSubstFvar P] [HasVarsPure P P.Expr] + (extendEval : ExtendEval P) + (ih : ∀ m' < n, ∀ σ σ', (∀ st, Stmt.sizeOf st ≤ m' → Stmt.noFuncDecl st → + EvalStmt P (Cmd P) (EvalCmd P) extendEval δ σ st σ' δ → + EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ (StmtToNondetStmt st) σ') ∧ + (∀ ss, Block.sizeOf ss ≤ m' → Block.noFuncDecl ss → + EvalBlock P (Cmd P) (EvalCmd P) extendEval δ σ ss σ' δ → + EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ (BlockToNondetStmt ss) σ')) + (Hwfb : WellFormedSemanticEvalBool δ) + (_Hwfvl : WellFormedSemanticEvalVal δ) + {g : P.Expr} {body : List (Stmt P (Cmd P))} {meas : Option P.Expr} + {invs : List P.Expr} {md : MetaData P} + (Hno : Block.noFuncDecl body) + (Hsz : Stmt.sizeOf (.loop g meas invs body md) ≤ n) + (Heval : EvalStmt P (Cmd P) (EvalCmd P) extendEval δ σ + (.loop g meas invs body md) σ' δ) : + EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ + (StmtToNondetStmt (.loop g meas invs body md)) σ' := + match Heval with + | .loop_false_sem _ _ => by simp [StmtToNondetStmt]; exact .loop_done + | .loop_true_sem Htrue _ Hbody Hloop => + have Hδ := EvalBlock_noFuncDecl_preserves_δ extendEval body δ _ σ _ Hno Hbody + by simp [StmtToNondetStmt] + apply EvalNondetStmt.loop_step + · apply EvalNondetStmt.seq_sem + · exact .cmd_sem (EvalCmd.eval_assume Htrue Hwfb) + (by simp [isDefinedOver, HasVarsImp.modifiedVars, Cmd.modifiedVars, isDefined]) + · exact (ih (Block.sizeOf body) (by simp_all [Stmt.sizeOf, Block.sizeOf]; omega) σ _).2 + body (Nat.le_refl _) Hno (Hδ ▸ Hbody) + · exact StmtToNondetCorrect_loop_impl extendEval ih Hwfb _Hwfvl Hno Hsz (Hδ ▸ Hloop) + + +@[implemented_by StmtToNondetCorrect_loop_impl] +private axiom StmtToNondetCorrect_loop + [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] [DecidableEq P.Ident] + [HasSubstFvar P] [HasVarsPure P P.Expr] + (extendEval : ExtendEval P) + (ih : ∀ m' < n, ∀ σ σ', (∀ st, Stmt.sizeOf st ≤ m' → Stmt.noFuncDecl st → + EvalStmt P (Cmd P) (EvalCmd P) extendEval δ σ st σ' δ → + EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ (StmtToNondetStmt st) σ') ∧ + (∀ ss, Block.sizeOf ss ≤ m' → Block.noFuncDecl ss → + EvalBlock P (Cmd P) (EvalCmd P) extendEval δ σ ss σ' δ → + EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ (BlockToNondetStmt ss) σ')) + (Hwfb : WellFormedSemanticEvalBool δ) + (Hwfvl : WellFormedSemanticEvalVal δ) + {g : P.Expr} {body : List (Stmt P (Cmd P))} {meas : Option P.Expr} + {invs : List P.Expr} {md : MetaData P} + (Hno : Block.noFuncDecl body) + (Hsz : Stmt.sizeOf (.loop g meas invs body md) ≤ n) + (Heval : EvalStmt P (Cmd P) (EvalCmd P) extendEval δ σ + (.loop g meas invs body md) σ' δ) : + EvalNondetStmt P (Cmd P) (EvalCmd P) δ σ + (StmtToNondetStmt (.loop g meas invs body md)) σ' + /-- The proof implementation for `StmtToNondetStmtCorrect` and `BlockToNondetStmtCorrect`. @@ -137,7 +233,7 @@ theorem EvalBlock_noFuncDecl_preserves_δ When `noFuncDecl` holds, the evaluator `δ` is preserved (δ' = δ). -/ theorem StmtToNondetCorrect - [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] [DecidableEq P.Ident] + [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] [DecidableEq P.Ident] [HasSubstFvar P] [HasVarsPure P P.Expr] (extendEval : ExtendEval P) : WellFormedSemanticEvalBool δ → WellFormedSemanticEvalVal δ → @@ -215,8 +311,9 @@ theorem StmtToNondetCorrect rw [← Hδ]; exact Heval | .exit _ _ => cases Heval - | .loop _ _ _ _ _ => - cases Heval + | .loop g _ _ body _ => + simp [Stmt.noFuncDecl] at Hno + exact StmtToNondetCorrect_loop extendEval ih Hwfb Hwfvl Hno Hsz Heval | .funcDecl _ _ => simp [Stmt.noFuncDecl] at Hno | .typeDecl _ md => @@ -263,7 +360,7 @@ theorem StmtToNondetCorrect /-- Proof that the Deterministic-to-nondeterministic transformation is correct for a single (deterministic) statement that contains no function declarations. -/ theorem StmtToNondetStmtCorrect - [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] [DecidableEq P.Ident] + [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] [DecidableEq P.Ident] [HasSubstFvar P] [HasVarsPure P P.Expr] (extendEval : ExtendEval P) : WellFormedSemanticEvalBool δ → WellFormedSemanticEvalVal δ → @@ -277,6 +374,7 @@ theorem StmtToNondetStmtCorrect for multiple (deterministic) statements that contain no function declarations. -/ theorem BlockToNondetStmtCorrect [HasVal P] [HasFvar P] [HasBool P] [HasBoolVal P] [HasNot P] [DecidableEq P.Ident] + [HasSubstFvar P] [HasVarsPure P P.Expr] (extendEval : ExtendEval P) : WellFormedSemanticEvalBool δ → WellFormedSemanticEvalVal δ → diff --git a/StrataTest/Backends/CBMC/GOTO/SemanticsTest.lean b/StrataTest/Backends/CBMC/GOTO/SemanticsTest.lean new file mode 100644 index 000000000..86faa51ff --- /dev/null +++ b/StrataTest/Backends/CBMC/GOTO/SemanticsTest.lean @@ -0,0 +1,92 @@ +/- + Copyright Strata Contributors + + SPDX-License-Identifier: Apache-2.0 OR MIT +-/ + +import Strata.Backends.CBMC.GOTO.Semantics + +/-! +# Tests for GOTO Semantics + +Basic sanity checks that the semantics rules can be instantiated and +that the simple properties hold. +-/ + +open CProverGOTO CProverGOTO.Semantics + +/-! ## Test helpers -/ + +/-- A trivial expression evaluator that handles constants and symbols. -/ +def testEval : ExprEval + | σ, e => match e.id with + | .nullary (.constant "true") => some (.vBool true) + | .nullary (.constant "false") => some (.vBool false) + | .nullary (.constant v) => v.toInt?.map .vInt + | .nullary (.symbol name) => σ name + | _ => none + +/-- A no-call relation (no function calls succeed). -/ +def noCall : CallResultRel := fun _ _ _ _ _ _ => False + +/-! ## Simple program: SKIP; END_FUNCTION -/ + +def skipProg : Program := + { name := "test" + instructions := #[ + { type := .SKIP, locationNum := 0 }, + { type := .END_FUNCTION, locationNum := 1 } + ] } + +example : ExecProg noCall testEval (fun _ => none) skipProg 0 + Store.empty Store.empty none := by + apply ExecProg.step (pc' := 1) + · exact StepInstr.skip rfl + · exact ExecProg.end_function rfl + +/-! ## ASSERT pass -/ + +def assertPassProg : Program := + { name := "test" + instructions := #[ + { type := .ASSERT, locationNum := 0, + guard := Expr.constant "true" .Boolean }, + { type := .END_FUNCTION, locationNum := 1 } + ] } + +example : ExecProg noCall testEval (fun _ => none) assertPassProg 0 + Store.empty Store.empty none := by + apply ExecProg.step (pc' := 1) + · exact StepInstr.assert_pass rfl rfl + · exact ExecProg.end_function rfl + +/-! ## ASSERT fail still advances -/ + +def assertFailProg : Program := + { name := "test" + instructions := #[ + { type := .ASSERT, locationNum := 0, + guard := Expr.constant "false" .Boolean, + sourceLoc := { SourceLocation.nil with comment := "test assertion" } }, + { type := .END_FUNCTION, locationNum := 1 } + ] } + +-- Assertion failure still allows execution to continue +example : ExecProg noCall testEval (fun _ => none) assertFailProg 0 + Store.empty Store.empty none := by + apply ExecProg.step (pc' := 1) + · exact StepInstr.assert_fail rfl rfl + · exact ExecProg.end_function rfl + +-- But the program is NOT safe +example : ¬ ProgramSafe noCall testEval (fun _ => none) assertFailProg Store.empty := by + intro hsafe + apply hsafe 0 Store.empty + · exact Reachable.refl + · exact ⟨rfl, rfl⟩ + +/-! ## Reachability transitivity -/ + +example {cr : CallResultRel} {eval : ExprEval} {fenv : FuncEnv} + {prog : Program} {σ : Store} : + Reachable cr eval fenv prog 0 σ 0 σ := Reachable.refl