From 64e34b2a90435f77e90dbc8f8c287de3eba962a8 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Thu, 12 Mar 2026 17:24:10 +0100 Subject: [PATCH 01/34] Add litmus parser for X86 --- LeanCats/LitmusParser/Enumeration.lean | 159 ++++++++++++++ LeanCats/LitmusParser/Helpers.lean | 95 +++++++++ LeanCats/LitmusParser/Types.lean | 96 +++++++++ LeanCats/LitmusParser/X86.lean | 281 +++++++++++++++++++++++++ 4 files changed, 631 insertions(+) create mode 100644 LeanCats/LitmusParser/Enumeration.lean create mode 100644 LeanCats/LitmusParser/Helpers.lean create mode 100644 LeanCats/LitmusParser/Types.lean create mode 100644 LeanCats/LitmusParser/X86.lean diff --git a/LeanCats/LitmusParser/Enumeration.lean b/LeanCats/LitmusParser/Enumeration.lean new file mode 100644 index 0000000..df3bc9b --- /dev/null +++ b/LeanCats/LitmusParser/Enumeration.lean @@ -0,0 +1,159 @@ +/- + LitmusParser/Enumeration.lean — Architecture-independent RF / CO / FR enumeration + and candidate execution assembly. + + All functions here operate on `GeneratedEvents` and `ExistsConstraint`, + which are produced by architecture-specific event generators. +-/ +import LeanCats.LitmusParser.Types + +namespace LitmusParser + +-- ════════════════════════════════════════════════════════════════ +-- Combinatorial Helpers +-- ════════════════════════════════════════════════════════════════ + +/-- Cartesian product of arrays of choices. -/ +private def cartesianProduct (choices : Array (Array α)) : Array (Array α) := Id.run do + let mut result : Array (Array α) := #[#[]] + for opts in choices do + let mut newResult : Array (Array α) := #[] + for partial_ in result do + for opt in opts do + newResult := newResult.push (partial_.push opt) + result := newResult + return result + +/-- Generate all permutations of a list. (partial since termination proof is nontrivial) -/ +private partial def permutations [DecidableEq α] : List α → List (List α) + | [] => [[]] + | xs => xs.flatMap fun x => + (permutations (xs.erase x)).map (x :: ·) + +/-- Convert a total order (array of write-ids) into co pairs: (earlier, later). -/ +private def orderToPairs (order : Array Nat) : Array (Nat × Nat) := Id.run do + let mut pairs : Array (Nat × Nat) := #[] + for i in [:order.size] do + for j in [i+1:order.size] do + pairs := pairs.push (order[i]!, order[j]!) + return pairs + +-- ════════════════════════════════════════════════════════════════ +-- RF Enumeration +-- ════════════════════════════════════════════════════════════════ + +/-- For each read event, find all writes (init + program) to the same location. -/ +def rfChoicesPerRead (gen : GeneratedEvents) : Array (Array Nat) := Id.run do + let allWrites := gen.initWrites ++ gen.progWrites + let mut choices : Array (Array Nat) := #[] + for r in gen.reads do + let mut writesForLoc : Array Nat := #[] + for w in allWrites do + if w.loc == r.loc && w.id != r.id then writesForLoc := writesForLoc.push w.id + choices := choices.push writesForLoc + return choices + +/-- Enumerate all possible rf assignments. `result[i]` = write-id that reads[i] reads from. -/ +def enumerateRfAssignments (gen : GeneratedEvents) : Array (Array Nat) := + cartesianProduct (rfChoicesPerRead gen) + +/-- Given an rf assignment, determine the value each read sees. -/ +def rfToReadValues (gen : GeneratedEvents) (rfAssign : Array Nat) : Array (Nat × Nat) := Id.run do + let allWrites := gen.initWrites ++ gen.progWrites + let mut result : Array (Nat × Nat) := #[] + for i in [:gen.reads.size] do + let writeId := rfAssign[i]! + let mut writeVal : Nat := 0 + for w in allWrites do + if w.id == writeId then + writeVal := w.val.getD 0 + break + result := result.push (gen.reads[i]!.id, writeVal) + return result + +/-- Check whether an rf assignment satisfies the `exists` constraint. -/ +def checkConstraint (gen : GeneratedEvents) (rfAssign : Array Nat) + (constraint : ExistsConstraint) : Bool := Id.run do + let readVals := rfToReadValues gen rfAssign + for conj in constraint.conjuncts do + let mut lastReadVal : Option Nat := none + for (readId, reg, tid) in gen.readRegs do + if tid == conj.tid && reg == conj.reg then + for (rid, val) in readVals do + if rid == readId then + lastReadVal := some val + break + match lastReadVal with + | some v => if v != conj.val then return false + | none => return false + return true + +-- ════════════════════════════════════════════════════════════════ +-- CO Enumeration +-- ════════════════════════════════════════════════════════════════ + +/-- For each location, enumerate all total orders on its writes (init first). -/ +def coOrdersPerLocation (gen : GeneratedEvents) : Array (Array (Array Nat)) := Id.run do + let mut result : Array (Array (Array Nat)) := #[] + for locIdx in [:gen.locs.size] do + let initW := gen.initWrites.filter (·.loc == locIdx) |>.map (·.id) + let progW := gen.progWrites.filter (·.loc == locIdx) |>.map (·.id) + let perms := permutations progW.toList + let mut orders : Array (Array Nat) := #[] + for perm in perms do + orders := orders.push (initW ++ perm.toArray) + if orders.isEmpty then orders := #[initW] + result := result.push orders + return result + +/-- Compute fr = rf⁻¹ ; co. -/ +def computeFr (rf : Array (Nat × Nat)) (co : Array (Nat × Nat)) : Array (Nat × Nat) := Id.run do + let mut fr : Array (Nat × Nat) := #[] + for (w, r) in rf do + for (w1, w2) in co do + if w == w1 then fr := fr.push (r, w2) + return fr + +-- ════════════════════════════════════════════════════════════════ +-- Candidate Execution Assembly +-- ════════════════════════════════════════════════════════════════ + +/-- Enumerate all candidate executions that satisfy the `exists` constraint. + Architecture-independent: takes `GeneratedEvents` produced by any arch-specific generator. -/ +def enumerateCandidateExecutions (gen : GeneratedEvents) (constraint : ExistsConstraint) + : Array ConcreteCandExec := Id.run do + let rfAssignments := enumerateRfAssignments gen + let validRfs := rfAssignments.filter (checkConstraint gen · constraint) + let coPerLoc := coOrdersPerLocation gen + let coChoices := cartesianProduct coPerLoc + + let mut results : Array ConcreteCandExec := #[] + + for rfAssign in validRfs do + let mut rfEdges : Array (Nat × Nat) := #[] + for i in [:gen.reads.size] do + rfEdges := rfEdges.push (rfAssign[i]!, gen.reads[i]!.id) + + for coChoice in coChoices do + let mut coEdges : Array (Nat × Nat) := #[] + for locOrder in coChoice do + coEdges := coEdges ++ orderToPairs locOrder + let frEdges := computeFr rfEdges coEdges + + -- Annotate read events with the values they see from rf + let readVals := rfToReadValues gen rfAssign + let mut updatedEvents := gen.allEvents + for (rid, rval) in readVals do + for j in [:updatedEvents.size] do + if updatedEvents[j]!.id == rid then + updatedEvents := updatedEvents.set! j { updatedEvents[j]! with val := some rval } + + let exec : ConcreteCandExec := { + allEvents := updatedEvents, initWrites := gen.initWrites, + reads := gen.reads, writes := gen.progWrites, + po := gen.po, rf := rfEdges, co := coEdges, fr := frEdges + } + results := results.push exec + return results + +end LitmusParser diff --git a/LeanCats/LitmusParser/Helpers.lean b/LeanCats/LitmusParser/Helpers.lean new file mode 100644 index 0000000..3ec50ff --- /dev/null +++ b/LeanCats/LitmusParser/Helpers.lean @@ -0,0 +1,95 @@ +/- + LitmusParser/Helpers.lean — String-level parser helpers and generic (architecture-independent) + parsers for litmus test formats. +-/ +import LeanCats.LitmusParser.Types + +namespace LitmusParser + +-- ════════════════════════════════════════════════════════════════ +-- String Utilities +-- ════════════════════════════════════════════════════════════════ + +/-- Helper: trim whitespace, returning String (not Slice). -/ +def strim (s : String) : String := s.trimAscii.toString + +/-- Helper: drop prefix, returning String. -/ +def sdropPrefix (s : String) (pfx : String) : String := + if s.startsWith pfx then (s.drop pfx.length).toString else s + +/-- Helper: drop suffix, returning String. -/ +def sdropSuffix (s : String) (sfx : String) : String := + if s.endsWith sfx then (s.take (s.length - sfx.length)).toString else s + +/-- Extract text between the first occurrence of `open_` and the matching `close_`. -/ +def extractBetween (s : String) (open_ close_ : Char) : Option String := Id.run do + let mut inside := false + let mut acc : String := "" + for c in s.toList do + if !inside then + if c == open_ then inside := true + else + if c == close_ then return some acc + acc := acc.push c + return none + +/-- Check if a string contains a given substring. -/ +def scontains (s : String) (sub : String) : Bool := + (s.splitOn sub).length > 1 + +/-- Parse a natural number from a string (only digits). -/ +def parseNat (s : String) : Option Nat := + let digits := s.toList.filter Char.isDigit + if digits.isEmpty then none + else some (digits.foldl (fun acc c => acc * 10 + (c.toNat - '0'.toNat)) 0) + +/-- Look up the index of a location name in a location array. -/ +def locIndex (locs : Array String) (name : String) : Nat := + match locs.findIdx? (· == name) with + | some i => i + | none => locs.size + +-- ════════════════════════════════════════════════════════════════ +-- Generic Parsers (shared across architectures) +-- ════════════════════════════════════════════════════════════════ + +/-- Parse initial state: `{ x=0; y=0; }` → `[("x", 0), ("y", 0)]` -/ +def parseInitState (line : String) : Array (String × Nat) := Id.run do + let some inner := extractBetween line '{' '}' + | return #[] + let parts := inner.splitOn ";" + let mut result : Array (String × Nat) := #[] + for part in parts do + let trimmed := strim part + if trimmed.isEmpty then continue + let eqParts := trimmed.splitOn "=" + if eqParts.length ≥ 2 then + let locName := strim eqParts[0]! + let valStr := strim eqParts[1]! + if let some val := parseNat valStr then + result := result.push (locName, val) + return result + +/-- Parse the exists clause: `exists (0:EAX=0 /\ 1:EAX=0)` -/ +def parseExists (line : String) : ExistsConstraint := Id.run do + let some inner := extractBetween line '(' ')' + | return { conjuncts := #[] } + -- Split on "/\" + let parts := inner.splitOn "/\\" + let mut conjuncts : Array ExistsConjunct := #[] + for part in parts do + let p := strim part + let colonParts := p.splitOn ":" + if colonParts.length < 2 then continue + let tidStr := strim colonParts[0]! + let rest := strim colonParts[1]! + let eqParts := rest.splitOn "=" + if eqParts.length < 2 then continue + let reg := strim eqParts[0]! + let valStr := strim eqParts[1]! + if let some tid := parseNat tidStr then + if let some val := parseNat valStr then + conjuncts := conjuncts.push { tid, reg, val } + return { conjuncts } + +end LitmusParser diff --git a/LeanCats/LitmusParser/Types.lean b/LeanCats/LitmusParser/Types.lean new file mode 100644 index 0000000..16808ca --- /dev/null +++ b/LeanCats/LitmusParser/Types.lean @@ -0,0 +1,96 @@ +/- + LitmusParser/Types.lean — Architecture-independent types for candidate executions. +-/ +import LeanCats.Data + +open Data + +namespace LitmusParser + +-- ════════════════════════════════════════════════════════════════ +-- Exists Constraint (shared across all architectures) +-- ════════════════════════════════════════════════════════════════ + +/-- One conjunct in the exists clause: thread `tid`, register `reg` has value `val`. -/ +structure ExistsConjunct where + tid : Nat + reg : String + val : Nat + deriving Repr, BEq, DecidableEq, Inhabited + +/-- The `exists (...)` constraint: a conjunction of register-value assertions. -/ +structure ExistsConstraint where + conjuncts : Array ExistsConjunct + deriving Repr, Inhabited + +-- ════════════════════════════════════════════════════════════════ +-- Concrete Event & Candidate Execution +-- ════════════════════════════════════════════════════════════════ + +/-- A concrete event with all fields computable. -/ +structure CEvent where + id : Nat + t_id : Nat + op : Op + loc : Nat -- location index (mapped from name) + locName: String := "" -- original name for display + val : Option Nat -- value written, or value read (once rf is chosen) + isInit : Bool := false + deriving Repr, BEq, DecidableEq, Inhabited + +instance : ToString CEvent where + toString e := + let opStr := if e.op == .write then (if e.isInit then "IW" else "W") + else if e.op == .read then "R" + else if e.op == .fence then "F" + else "B" + let valStr := match e.val with | some v => s!"={v}" | none => "" + s!"{opStr}({e.locName}{valStr})[id={e.id},tid={e.t_id}]" + +instance : Hashable CEvent where + hash e := hash e.id + +/-- A concrete candidate execution with array-based computable relations. -/ +structure ConcreteCandExec where + allEvents : Array CEvent + initWrites : Array CEvent + reads : Array CEvent + writes : Array CEvent -- program writes only (not init) + po : Array (Nat × Nat) -- (from_id, to_id) + rf : Array (Nat × Nat) -- (write_id, read_id) + co : Array (Nat × Nat) -- (earlier_write_id, later_write_id) + fr : Array (Nat × Nat) -- derived: rf⁻¹ ; co + deriving Repr, Inhabited + +instance : ToString ConcreteCandExec where + toString e := + let fmtRel (name : String) (rel : Array (Nat × Nat)) : String := + let pairs := rel.toList.map fun (a, b) => s!"({a},{b})" + s!"{name}: [{", ".intercalate pairs}]" + let eventStrs := e.allEvents.toList.map toString + s!"Events: [{", ".intercalate eventStrs}]\n" ++ + fmtRel "po" e.po ++ "\n" ++ + fmtRel "rf" e.rf ++ "\n" ++ + fmtRel "co" e.co ++ "\n" ++ + fmtRel "fr" e.fr + +-- ════════════════════════════════════════════════════════════════ +-- Generated Events (output of architecture-specific event generation) +-- ════════════════════════════════════════════════════════════════ + +/-- Result of generating events from a parsed litmus test. + Each architecture produces this as input to the generic enumeration. -/ +structure GeneratedEvents where + allEvents : Array CEvent + initWrites : Array CEvent + reads : Array CEvent + progWrites : Array CEvent + fences : Array CEvent + po : Array (Nat × Nat) + locs : Array String + initVals : Array Nat + /-- For each read event: (event_id, register_name, thread_id). -/ + readRegs : Array (Nat × String × Nat) + deriving Repr, Inhabited + +end LitmusParser diff --git a/LeanCats/LitmusParser/X86.lean b/LeanCats/LitmusParser/X86.lean new file mode 100644 index 0000000..bb04df4 --- /dev/null +++ b/LeanCats/LitmusParser/X86.lean @@ -0,0 +1,281 @@ +/- + LitmusParser/X86.lean — X86-specific litmus test parsing and event generation. + + Other architectures (e.g., ARM, RISC-V) can follow the same pattern: + 1. Define an instruction type. + 2. Implement instruction parsing. + 3. Implement `generateEvents` producing `GeneratedEvents`. + 4. Call the shared `enumerateCandidateExecutions`. +-/ +import LeanCats.LitmusParser.Helpers +import LeanCats.LitmusParser.Enumeration + +open Data + +namespace LitmusParser.X86 + +-- ════════════════════════════════════════════════════════════════ +-- X86 Instruction Set & Parsed Litmus Test +-- ════════════════════════════════════════════════════════════════ + +/-- A single x86 instruction from a litmus test. -/ +inductive LitmusInst where + | store (loc : String) (val : Nat) -- MOV [loc],$val + | load (reg : String) (loc : String) -- MOV reg,[loc] + | fence -- MFENCE + deriving Repr, BEq, DecidableEq, Inhabited + +/-- A fully parsed X86 litmus test. -/ +structure ParsedLitmus where + arch : String := "X86" + name : String := "" + initState : Array (String × Nat) -- (location_name, initial_value) + threads : Array (Array LitmusInst) -- threads[tid][program_order_index] + constraint : ExistsConstraint + deriving Repr, Inhabited + +-- ════════════════════════════════════════════════════════════════ +-- X86 Instruction Parser +-- ════════════════════════════════════════════════════════════════ + +/-- Parse one x86 instruction string like `MOV [x],$1` or `MOV EAX,[y]` or `MFENCE`. -/ +def parseOneInst (s : String) : Option LitmusInst := Id.run do + let s := strim s + if s.isEmpty then return none + + -- MFENCE + if s.toUpper == "MFENCE" then return some .fence + + -- Must start with MOV (case-insensitive) + let tokens := s.splitOn " " |>.filter (strim · != "") + if tokens.length < 2 then return none + let mnemonic := tokens[0]!.toUpper + -- Rejoin the rest so we can split on ',' + let rest := ",".intercalate (tokens.drop 1) + let operands := rest.splitOn "," + if mnemonic != "MOV" || operands.length < 2 then return none + + let op1 := strim operands[0]! -- destination + let op2 := strim operands[1]! -- source + + -- Store: MOV [loc],$val + if op1.startsWith "[" then + let locName := strim (sdropSuffix (sdropPrefix op1 "[") "]") + let valStr := strim (sdropPrefix op2 "$") + if let some val := parseNat valStr then + return some (.store locName val) + return none + + -- Load: MOV reg,[loc] + if op2.startsWith "[" then + let locName := strim (sdropSuffix (sdropPrefix op2 "[") "]") + return some (.load op1 locName) + + return none + +-- ════════════════════════════════════════════════════════════════ +-- X86 Thread Program Parser +-- ════════════════════════════════════════════════════════════════ + +/-- Parse thread instruction columns from the program lines. -/ +def parseThreadPrograms (lines : Array String) : Array (Array LitmusInst) := Id.run do + if lines.isEmpty then return #[] + let firstLine := lines[0]! + let cols := firstLine.splitOn "|" + let numThreads := cols.length + let mut threads : Array (Array LitmusInst) := #[] + for _ in [:numThreads] do threads := threads.push #[] + + for i in [1:lines.size] do + let line := strim (sdropSuffix (strim lines[i]!) ";") + if line.isEmpty then continue + let cols := line.splitOn "|" + for tid in [:numThreads] do + if tid < cols.length then + let instStr := strim cols[tid]! + if let some inst := parseOneInst instStr then + threads := threads.set! tid (threads[tid]!.push inst) + return threads + +-- ════════════════════════════════════════════════════════════════ +-- X86 Main Parser +-- ════════════════════════════════════════════════════════════════ + +/-- Parse a complete X86 litmus test string into `ParsedLitmus`. -/ +def parseLitmus (input : String) : Except String ParsedLitmus := do + let lines := (input.splitOn "\n" |>.map strim |>.filter (· != "")).toArray + if lines.size < 3 then + throw "Litmus test too short" + + -- Line 0: architecture and test name + let headerTokens := lines[0]!.splitOn " " |>.filter (· != "") + let arch := if headerTokens.length > 0 then headerTokens[0]! else "X86" + let name := if headerTokens.length > 1 then headerTokens[1]! else "" + + -- Find lines containing '{' (initial state) and 'exists' (constraint) + let mut initState : Array (String × Nat) := #[] + let mut initLineIdx : Nat := 0 + let mut constraint : ExistsConstraint := { conjuncts := #[] } + let mut existsLineIdx : Nat := lines.size + + for i in [:lines.size] do + if scontains lines[i]! "{" then + initState := parseInitState lines[i]! + initLineIdx := i + if lines[i]!.toLower.startsWith "exists" then + constraint := parseExists lines[i]! + existsLineIdx := i + + -- Everything between initState and exists is the program + let mut programLines : Array String := #[] + for i in [initLineIdx + 1 : existsLineIdx] do + let l := lines[i]! + if !l.startsWith "\"" then + programLines := programLines.push l + + let threads := parseThreadPrograms programLines + + return { + arch + name + initState + threads + constraint + } + +-- ════════════════════════════════════════════════════════════════ +-- X86 Event Generation +-- ════════════════════════════════════════════════════════════════ + +/-- Map location names to Nat indices for an X86 litmus test. -/ +def buildLocMap (initState : Array (String × Nat)) (threads : Array (Array LitmusInst)) + : Array String := Id.run do + let mut locs : Array String := #[] + for (loc, _) in initState do + if !locs.contains loc then locs := locs.push loc + for thread in threads do + for inst in thread do + let loc := match inst with + | .store l _ => l + | .load _ l => l + | .fence => "" + if loc != "" && !locs.contains loc then locs := locs.push loc + return locs + +/-- Generate events and program-order edges from a parsed X86 litmus test. -/ +def generateEvents (test : ParsedLitmus) : GeneratedEvents := Id.run do + let locs := buildLocMap test.initState test.threads + let mut initVals : Array Nat := #[] + for _ in [:locs.size] do initVals := initVals.push 0 + for (loc, val) in test.initState do + let idx := locIndex locs loc + if idx < initVals.size then initVals := initVals.set! idx val + + let initIdBase := 1000 + let mut allEvents : Array CEvent := #[] + let mut initWrites : Array CEvent := #[] + + -- Create initial write events (one per location) + for i in [:locs.size] do + let ev : CEvent := { + id := initIdBase + i, t_id := initIdBase, op := .write, + loc := i, locName := locs[i]!, val := some initVals[i]!, isInit := true + } + allEvents := allEvents.push ev + initWrites := initWrites.push ev + + -- Create instruction events + let mut reads : Array CEvent := #[] + let mut progWrites : Array CEvent := #[] + let mut fences : Array CEvent := #[] + let mut po : Array (Nat × Nat) := #[] + let mut readRegs : Array (Nat × String × Nat) := #[] + let mut nextId : Nat := 1 + + for tid in [:test.threads.size] do + let thread := test.threads[tid]! + let mut prevId : Option Nat := none + for inst in thread do + let evId := nextId + nextId := nextId + 1 + let ev : CEvent := match inst with + | .store loc val => + let li := locIndex locs loc + { id := evId, t_id := tid, op := .write, loc := li, + locName := loc, val := some val, isInit := false } + | .load _reg loc => + let li := locIndex locs loc + { id := evId, t_id := tid, op := .read, loc := li, + locName := loc, val := none, isInit := false } + | .fence => + { id := evId, t_id := tid, op := .fence, loc := 0, + locName := "", val := none, isInit := false } + + allEvents := allEvents.push ev + match inst with + | .store _ _ => progWrites := progWrites.push ev + | .load reg _ => + reads := reads.push ev + readRegs := readRegs.push (evId, reg, tid) + | .fence => fences := fences.push ev + + if let some prev := prevId then po := po.push (prev, evId) + prevId := some evId + + return { allEvents, initWrites, reads, progWrites, fences, po, locs, initVals, readRegs } + +-- ════════════════════════════════════════════════════════════════ +-- X86 Convenience API +-- ════════════════════════════════════════════════════════════════ + +/-- Parse an X86 litmus test string and enumerate all candidate executions. -/ +def parseLitmusAndEnumerate (input : String) : Except String (Array ConcreteCandExec) := do + let test ← parseLitmus input + let gen := generateEvents test + return enumerateCandidateExecutions gen test.constraint + +-- ════════════════════════════════════════════════════════════════ +-- Example X86 Litmus Test Strings +-- ════════════════════════════════════════════════════════════════ + +def sbLitmusStr : String := +"X86 SB +{ x=0; y=0; } +P0 | P1 ; +MOV [x],$1 | MOV [y],$1 ; +MOV EAX,[y] | MOV EAX,[x] ; +exists (0:EAX=0 /\\ 1:EAX=0)" + +def mpLitmusStr : String := +"X86 MP +{ x=0; y=0; } +P0 | P1 ; +MOV [x],$1 | MOV EAX,[y] ; +MOV [y],$1 | MOV EBX,[x] ; +exists (1:EAX=1 /\\ 1:EBX=0)" + +def lbLitmusStr : String := +"X86 LB +{ x=0; y=0; } +P0 | P1 ; +MOV EAX,[x] | MOV EAX,[y] ; +MOV [y],$1 | MOV [x],$1 ; +exists (0:EAX=1 /\\ 1:EAX=1)" + +-- ════════════════════════════════════════════════════════════════ +-- Computable Tests +-- ════════════════════════════════════════════════════════════════ + +/-- Helper: format one litmus result set. -/ +def reportResults (label : String) (input : String) : String := + match parseLitmusAndEnumerate input with + | .ok results => + let header := s!"{label}: {results.size} candidate execution(s) matching constraint\n" + results.foldl (fun acc r => acc ++ s!"---\n{r}\n") header + | .error e => s!"Error: {e}" + +#eval! reportResults "SB" sbLitmusStr +#eval! reportResults "MP" mpLitmusStr +#eval! reportResults "LB" lbLitmusStr + +end LitmusParser.X86 From 0267e5733e5ccbc8619a5885723c659a52b1732e Mon Sep 17 00:00:00 2001 From: YangWiz Date: Thu, 12 Mar 2026 20:23:13 +0100 Subject: [PATCH 02/34] Add more litmus test files. --- LeanCats/examples/tests/2W.litmus | 6 ++++++ LeanCats/examples/tests/3LB.litmus | 6 ++++++ LeanCats/examples/tests/3MP.litmus | 6 ++++++ LeanCats/examples/tests/3SB.litmus | 6 ++++++ LeanCats/examples/tests/3SB_MFence.litmus | 7 +++++++ LeanCats/examples/tests/4LB.litmus | 6 ++++++ LeanCats/examples/tests/4SB.litmus | 6 ++++++ LeanCats/examples/tests/CoRR.litmus | 6 ++++++ LeanCats/examples/tests/CoRW.litmus | 6 ++++++ LeanCats/examples/tests/CoWR.litmus | 6 ++++++ LeanCats/examples/tests/CoWW.litmus | 6 ++++++ LeanCats/examples/tests/IRIW.litmus | 6 ++++++ LeanCats/examples/tests/IRIW_MFence.litmus | 7 +++++++ LeanCats/examples/tests/ISA2.litmus | 6 ++++++ LeanCats/examples/tests/ISA2_MFence.litmus | 7 +++++++ LeanCats/examples/tests/LB.litmus | 6 ++++++ LeanCats/examples/tests/LB_MFence.litmus | 7 +++++++ LeanCats/examples/tests/MP.litmus | 6 ++++++ LeanCats/examples/tests/MP2.litmus | 6 ++++++ LeanCats/examples/tests/MP_MFence.litmus | 7 +++++++ LeanCats/examples/tests/RRW.litmus | 6 ++++++ LeanCats/examples/tests/RWC.litmus | 6 ++++++ LeanCats/examples/tests/RWC_MFence.litmus | 7 +++++++ LeanCats/examples/tests/SB.litmus | 6 ++++++ LeanCats/examples/tests/SB_MFence.litmus | 7 +++++++ LeanCats/examples/tests/SB_opt.litmus | 7 +++++++ LeanCats/examples/tests/WRC.litmus | 6 ++++++ LeanCats/examples/tests/WRR.litmus | 6 ++++++ LeanCats/examples/tests/WRW.litmus | 7 +++++++ LeanCats/examples/tests/WWC.litmus | 6 ++++++ 30 files changed, 189 insertions(+) create mode 100644 LeanCats/examples/tests/2W.litmus create mode 100644 LeanCats/examples/tests/3LB.litmus create mode 100644 LeanCats/examples/tests/3MP.litmus create mode 100644 LeanCats/examples/tests/3SB.litmus create mode 100644 LeanCats/examples/tests/3SB_MFence.litmus create mode 100644 LeanCats/examples/tests/4LB.litmus create mode 100644 LeanCats/examples/tests/4SB.litmus create mode 100644 LeanCats/examples/tests/CoRR.litmus create mode 100644 LeanCats/examples/tests/CoRW.litmus create mode 100644 LeanCats/examples/tests/CoWR.litmus create mode 100644 LeanCats/examples/tests/CoWW.litmus create mode 100644 LeanCats/examples/tests/IRIW.litmus create mode 100644 LeanCats/examples/tests/IRIW_MFence.litmus create mode 100644 LeanCats/examples/tests/ISA2.litmus create mode 100644 LeanCats/examples/tests/ISA2_MFence.litmus create mode 100644 LeanCats/examples/tests/LB.litmus create mode 100644 LeanCats/examples/tests/LB_MFence.litmus create mode 100644 LeanCats/examples/tests/MP.litmus create mode 100644 LeanCats/examples/tests/MP2.litmus create mode 100644 LeanCats/examples/tests/MP_MFence.litmus create mode 100644 LeanCats/examples/tests/RRW.litmus create mode 100644 LeanCats/examples/tests/RWC.litmus create mode 100644 LeanCats/examples/tests/RWC_MFence.litmus create mode 100644 LeanCats/examples/tests/SB.litmus create mode 100644 LeanCats/examples/tests/SB_MFence.litmus create mode 100644 LeanCats/examples/tests/SB_opt.litmus create mode 100644 LeanCats/examples/tests/WRC.litmus create mode 100644 LeanCats/examples/tests/WRR.litmus create mode 100644 LeanCats/examples/tests/WRW.litmus create mode 100644 LeanCats/examples/tests/WWC.litmus diff --git a/LeanCats/examples/tests/2W.litmus b/LeanCats/examples/tests/2W.litmus new file mode 100644 index 0000000..c31dacf --- /dev/null +++ b/LeanCats/examples/tests/2W.litmus @@ -0,0 +1,6 @@ +X86 2W +{ x=0; } +P0 | P1 ; +MOV [x],$1 | MOV [x],$2 ; +MOV EAX,[x] | MOV EBX,[x] ; +exists (0:EAX=2 /\ 1:EBX=1) diff --git a/LeanCats/examples/tests/3LB.litmus b/LeanCats/examples/tests/3LB.litmus new file mode 100644 index 0000000..4d9d169 --- /dev/null +++ b/LeanCats/examples/tests/3LB.litmus @@ -0,0 +1,6 @@ +X86 3LB +{ x=0; y=0; z=0; } +P0 | P1 | P2 ; +MOV EAX,[x] | MOV EBX,[y] | MOV ECX,[z] ; +MOV [z],$1 | MOV [x],$1 | MOV [y],$1 ; +exists (0:EAX=1 /\ 1:EBX=1 /\ 2:ECX=1) diff --git a/LeanCats/examples/tests/3MP.litmus b/LeanCats/examples/tests/3MP.litmus new file mode 100644 index 0000000..2807c16 --- /dev/null +++ b/LeanCats/examples/tests/3MP.litmus @@ -0,0 +1,6 @@ +X86 3MP +{ x=0; y=0; z=0; } +P0 | P1 | P2 ; +MOV [x],$1 | MOV EAX,[x] | MOV EBX,[y] ; +MOV [y],$1 | MOV [z],$1 | MOV ECX,[z] ; +exists (1:EAX=1 /\ 2:EBX=1 /\ 2:ECX=0) diff --git a/LeanCats/examples/tests/3SB.litmus b/LeanCats/examples/tests/3SB.litmus new file mode 100644 index 0000000..47b5cc0 --- /dev/null +++ b/LeanCats/examples/tests/3SB.litmus @@ -0,0 +1,6 @@ +X86 3SB +{ x=0; y=0; z=0; } +P0 | P1 | P2 ; +MOV [x],$1 | MOV [y],$1 | MOV [z],$1 ; +MOV EAX,[y] | MOV EBX,[z] | MOV ECX,[x] ; +exists (0:EAX=0 /\ 1:EBX=0 /\ 2:ECX=0) diff --git a/LeanCats/examples/tests/3SB_MFence.litmus b/LeanCats/examples/tests/3SB_MFence.litmus new file mode 100644 index 0000000..f78dea1 --- /dev/null +++ b/LeanCats/examples/tests/3SB_MFence.litmus @@ -0,0 +1,7 @@ +X86 3SB+MFence +{ x=0; y=0; z=0; } +P0 | P1 | P2 ; +MOV [x],$1 | MOV [y],$1 | MOV [z],$1 ; +MFENCE | MFENCE | MFENCE ; +MOV EAX,[y] | MOV EBX,[z] | MOV ECX,[x] ; +exists (0:EAX=0 /\ 1:EBX=0 /\ 2:ECX=0) diff --git a/LeanCats/examples/tests/4LB.litmus b/LeanCats/examples/tests/4LB.litmus new file mode 100644 index 0000000..5edeb4f --- /dev/null +++ b/LeanCats/examples/tests/4LB.litmus @@ -0,0 +1,6 @@ +X86 4LB +{ x=0; y=0; z=0; w=0; } +P0 | P1 | P2 | P3 ; +MOV EAX,[x] | MOV EBX,[y] | MOV ECX,[z] | MOV EDX,[w] ; +MOV [w],$1 | MOV [x],$1 | MOV [y],$1 | MOV [z],$1 ; +exists (0:EAX=1 /\ 1:EBX=1 /\ 2:ECX=1 /\ 3:EDX=1) diff --git a/LeanCats/examples/tests/4SB.litmus b/LeanCats/examples/tests/4SB.litmus new file mode 100644 index 0000000..766db16 --- /dev/null +++ b/LeanCats/examples/tests/4SB.litmus @@ -0,0 +1,6 @@ +X86 4SB +{ x=0; y=0; z=0; w=0; } +P0 | P1 | P2 | P3 ; +MOV [x],$1 | MOV [y],$1 | MOV [z],$1 | MOV [w],$1 ; +MOV EAX,[y] | MOV EBX,[z] | MOV ECX,[w] | MOV EDX,[x] ; +exists (0:EAX=0 /\ 1:EBX=0 /\ 2:ECX=0 /\ 3:EDX=0) diff --git a/LeanCats/examples/tests/CoRR.litmus b/LeanCats/examples/tests/CoRR.litmus new file mode 100644 index 0000000..e5e3eaf --- /dev/null +++ b/LeanCats/examples/tests/CoRR.litmus @@ -0,0 +1,6 @@ +X86 CoRR +{ x=0; } +P0 | P1 ; +MOV [x],$1 | MOV EAX,[x] ; + | MOV EBX,[x] ; +exists (1:EAX=0 /\ 1:EBX=1) diff --git a/LeanCats/examples/tests/CoRW.litmus b/LeanCats/examples/tests/CoRW.litmus new file mode 100644 index 0000000..9107c74 --- /dev/null +++ b/LeanCats/examples/tests/CoRW.litmus @@ -0,0 +1,6 @@ +X86 CoRW +{ x=0; } +P0 | P1 ; +MOV EAX,[x] | MOV EBX,[x] ; +MOV [x],$1 | MOV [x],$2 ; +exists (0:EAX=2 /\ 1:EBX=0) diff --git a/LeanCats/examples/tests/CoWR.litmus b/LeanCats/examples/tests/CoWR.litmus new file mode 100644 index 0000000..538a889 --- /dev/null +++ b/LeanCats/examples/tests/CoWR.litmus @@ -0,0 +1,6 @@ +X86 CoWR +{ x=0; } +P0 | P1 ; +MOV [x],$1 | MOV EAX,[x] ; + | MOV EBX,[x] ; +exists (1:EAX=1 /\ 1:EBX=0) diff --git a/LeanCats/examples/tests/CoWW.litmus b/LeanCats/examples/tests/CoWW.litmus new file mode 100644 index 0000000..3e6843e --- /dev/null +++ b/LeanCats/examples/tests/CoWW.litmus @@ -0,0 +1,6 @@ +X86 CoWW +{ x=0; } +P0 | P1 ; +MOV [x],$1 | MOV [x],$2 ; +MOV EAX,[x] | MOV EBX,[x] ; +exists (0:EAX=2 /\ 1:EBX=1) diff --git a/LeanCats/examples/tests/IRIW.litmus b/LeanCats/examples/tests/IRIW.litmus new file mode 100644 index 0000000..bae63f8 --- /dev/null +++ b/LeanCats/examples/tests/IRIW.litmus @@ -0,0 +1,6 @@ +X86 IRIW +{ x=0; y=0; } +P0 | P1 | P2 | P3 ; +MOV [x],$1 | MOV [y],$1 | MOV EAX,[x] | MOV ECX,[y] ; + | | MOV EBX,[y] | MOV EDX,[x] ; +exists (2:EAX=1 /\ 2:EBX=0 /\ 3:ECX=1 /\ 3:EDX=0) diff --git a/LeanCats/examples/tests/IRIW_MFence.litmus b/LeanCats/examples/tests/IRIW_MFence.litmus new file mode 100644 index 0000000..5758b0a --- /dev/null +++ b/LeanCats/examples/tests/IRIW_MFence.litmus @@ -0,0 +1,7 @@ +X86 IRIW+MFence +{ x=0; y=0; } +P0 | P1 | P2 | P3 ; +MOV [x],$1 | MOV [y],$1 | MOV EAX,[x] | MOV ECX,[y] ; + | | MFENCE | MFENCE ; + | | MOV EBX,[y] | MOV EDX,[x] ; +exists (2:EAX=1 /\ 2:EBX=0 /\ 3:ECX=1 /\ 3:EDX=0) diff --git a/LeanCats/examples/tests/ISA2.litmus b/LeanCats/examples/tests/ISA2.litmus new file mode 100644 index 0000000..321f39e --- /dev/null +++ b/LeanCats/examples/tests/ISA2.litmus @@ -0,0 +1,6 @@ +X86 ISA2 +{ x=0; y=0; z=0; } +P0 | P1 | P2 ; +MOV [x],$1 | MOV EAX,[x] | MOV EBX,[y] ; +MOV [y],$1 | MOV [z],$1 | MOV ECX,[z] ; +exists (1:EAX=1 /\ 2:EBX=1 /\ 2:ECX=0) diff --git a/LeanCats/examples/tests/ISA2_MFence.litmus b/LeanCats/examples/tests/ISA2_MFence.litmus new file mode 100644 index 0000000..af26f70 --- /dev/null +++ b/LeanCats/examples/tests/ISA2_MFence.litmus @@ -0,0 +1,7 @@ +X86 ISA2+MFence +{ x=0; y=0; z=0; } +P0 | P1 | P2 ; +MOV [x],$1 | MOV EAX,[x] | MOV EBX,[y] ; +MOV [y],$1 | MFENCE | MOV ECX,[z] ; + | MOV [z],$1 | ; +exists (1:EAX=1 /\ 2:EBX=1 /\ 2:ECX=0) diff --git a/LeanCats/examples/tests/LB.litmus b/LeanCats/examples/tests/LB.litmus new file mode 100644 index 0000000..d81d6f2 --- /dev/null +++ b/LeanCats/examples/tests/LB.litmus @@ -0,0 +1,6 @@ +X86 LB +{ x=0; y=0; } +P0 | P1 ; +MOV EAX,[x] | MOV EAX,[y] ; +MOV [y],$1 | MOV [x],$1 ; +exists (0:EAX=1 /\ 1:EAX=1) diff --git a/LeanCats/examples/tests/LB_MFence.litmus b/LeanCats/examples/tests/LB_MFence.litmus new file mode 100644 index 0000000..ab7f320 --- /dev/null +++ b/LeanCats/examples/tests/LB_MFence.litmus @@ -0,0 +1,7 @@ +X86 LB+MFence +{ x=0; y=0; } +P0 | P1 ; +MOV EAX,[x] | MOV EBX,[y] ; +MFENCE | MFENCE ; +MOV [y],$1 | MOV [x],$1 ; +exists (0:EAX=1 /\ 1:EBX=1) diff --git a/LeanCats/examples/tests/MP.litmus b/LeanCats/examples/tests/MP.litmus new file mode 100644 index 0000000..6a15376 --- /dev/null +++ b/LeanCats/examples/tests/MP.litmus @@ -0,0 +1,6 @@ +X86 MP +{ x=0; y=0; } +P0 | P1 ; +MOV [x],$1 | MOV EAX,[y] ; +MOV [y],$1 | MOV EBX,[x] ; +exists (1:EAX=1 /\ 1:EBX=0) diff --git a/LeanCats/examples/tests/MP2.litmus b/LeanCats/examples/tests/MP2.litmus new file mode 100644 index 0000000..d71a8c1 --- /dev/null +++ b/LeanCats/examples/tests/MP2.litmus @@ -0,0 +1,6 @@ +X86 MP2 +{ x=0; y=0; } +P0 | P1 | P2 ; +MOV [x],$1 | MOV EAX,[y] | MOV ECX,[y] ; +MOV [y],$1 | MOV EBX,[x] | MOV EDX,[x] ; +exists (1:EAX=1 /\ 1:EBX=0 /\ 2:ECX=1 /\ 2:EDX=0) diff --git a/LeanCats/examples/tests/MP_MFence.litmus b/LeanCats/examples/tests/MP_MFence.litmus new file mode 100644 index 0000000..c6f847c --- /dev/null +++ b/LeanCats/examples/tests/MP_MFence.litmus @@ -0,0 +1,7 @@ +X86 MP+MFence +{ x=0; y=0; } +P0 | P1 ; +MOV [x],$1 | MOV EAX,[y] ; +MFENCE | MFENCE ; +MOV [y],$1 | MOV EBX,[x] ; +exists (1:EAX=1 /\ 1:EBX=0) diff --git a/LeanCats/examples/tests/RRW.litmus b/LeanCats/examples/tests/RRW.litmus new file mode 100644 index 0000000..d5d0777 --- /dev/null +++ b/LeanCats/examples/tests/RRW.litmus @@ -0,0 +1,6 @@ +X86 RRW +{ x=0; y=0; } +P0 | P1 ; +MOV EAX,[x] | MOV [x],$1 ; +MOV EBX,[y] | MOV [y],$1 ; +exists (0:EAX=0 /\ 0:EBX=1) diff --git a/LeanCats/examples/tests/RWC.litmus b/LeanCats/examples/tests/RWC.litmus new file mode 100644 index 0000000..db3e1a1 --- /dev/null +++ b/LeanCats/examples/tests/RWC.litmus @@ -0,0 +1,6 @@ +X86 RWC +{ x=0; y=0; } +P0 | P1 | P2 ; +MOV [x],$1 | MOV EAX,[x] | MOV EBX,[x] ; + | MOV [y],$1 | MOV ECX,[y] ; +exists (1:EAX=1 /\ 2:EBX=0 /\ 2:ECX=1) diff --git a/LeanCats/examples/tests/RWC_MFence.litmus b/LeanCats/examples/tests/RWC_MFence.litmus new file mode 100644 index 0000000..28e554d --- /dev/null +++ b/LeanCats/examples/tests/RWC_MFence.litmus @@ -0,0 +1,7 @@ +X86 RWC+MFence +{ x=0; y=0; } +P0 | P1 | P2 ; +MOV [x],$1 | MOV EAX,[x] | MOV EBX,[x] ; + | MFENCE | MOV ECX,[y] ; + | MOV [y],$1 | ; +exists (1:EAX=1 /\ 2:EBX=0 /\ 2:ECX=1) diff --git a/LeanCats/examples/tests/SB.litmus b/LeanCats/examples/tests/SB.litmus new file mode 100644 index 0000000..763f9a4 --- /dev/null +++ b/LeanCats/examples/tests/SB.litmus @@ -0,0 +1,6 @@ +X86 SB +{ x=0; y=0; } +P0 | P1 ; +MOV [x],$1 | MOV [y],$1 ; +MOV EAX,[y] | MOV EAX,[x] ; +exists (0:EAX=0 /\ 1:EAX=0) diff --git a/LeanCats/examples/tests/SB_MFence.litmus b/LeanCats/examples/tests/SB_MFence.litmus new file mode 100644 index 0000000..dd36cf4 --- /dev/null +++ b/LeanCats/examples/tests/SB_MFence.litmus @@ -0,0 +1,7 @@ +X86 SB+MFence +{ x=0; y=0; } +P0 | P1 ; +MOV [x],$1 | MOV [y],$1 ; +MFENCE | MFENCE ; +MOV EAX,[y] | MOV EBX,[x] ; +exists (0:EAX=0 /\ 1:EBX=0) diff --git a/LeanCats/examples/tests/SB_opt.litmus b/LeanCats/examples/tests/SB_opt.litmus new file mode 100644 index 0000000..4ff5f0b --- /dev/null +++ b/LeanCats/examples/tests/SB_opt.litmus @@ -0,0 +1,7 @@ +X86 SB+opt +{ x=0; y=0; z=0; } +P0 | P1 ; +MOV [x],$1 | MOV [y],$1 ; +MOV EAX,[y] | MOV EBX,[x] ; +MOV [z],$1 | MOV ECX,[z] ; +exists (0:EAX=0 /\ 1:EBX=0 /\ 1:ECX=0) diff --git a/LeanCats/examples/tests/WRC.litmus b/LeanCats/examples/tests/WRC.litmus new file mode 100644 index 0000000..c5ae451 --- /dev/null +++ b/LeanCats/examples/tests/WRC.litmus @@ -0,0 +1,6 @@ +X86 WRC +{ x=0; y=0; } +P0 | P1 | P2 ; +MOV [x],$1 | MOV EAX,[x] | MOV EBX,[y] ; + | MOV [y],$1 | MOV ECX,[x] ; +exists (1:EAX=1 /\ 2:EBX=1 /\ 2:ECX=0) diff --git a/LeanCats/examples/tests/WRR.litmus b/LeanCats/examples/tests/WRR.litmus new file mode 100644 index 0000000..8f29a21 --- /dev/null +++ b/LeanCats/examples/tests/WRR.litmus @@ -0,0 +1,6 @@ +X86 WRR +{ x=0; } +P0 | P1 ; +MOV [x],$1 | MOV EAX,[x] ; + | MOV EBX,[x] ; +exists (1:EAX=1 /\ 1:EBX=0) diff --git a/LeanCats/examples/tests/WRW.litmus b/LeanCats/examples/tests/WRW.litmus new file mode 100644 index 0000000..c161ae4 --- /dev/null +++ b/LeanCats/examples/tests/WRW.litmus @@ -0,0 +1,7 @@ +X86 WRW +{ x=0; y=0; } +P0 | P1 ; +MOV [x],$1 | MOV EBX,[x] ; +MOV EAX,[y] | MOV ECX,[y] ; +MOV [y],$1 | ; +exists (0:EAX=0 /\ 1:EBX=1 /\ 1:ECX=1) diff --git a/LeanCats/examples/tests/WWC.litmus b/LeanCats/examples/tests/WWC.litmus new file mode 100644 index 0000000..e794d41 --- /dev/null +++ b/LeanCats/examples/tests/WWC.litmus @@ -0,0 +1,6 @@ +X86 WWC +{ x=0; y=0; } +P0 | P1 ; +MOV [x],$1 | MOV EAX,[y] ; +MOV [y],$1 | MOV EBX,[x] ; +exists (1:EAX=1 /\ 1:EBX=0) From 8482bf4cd0e9767a75f5bfb14e6f9018bccaea48 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Thu, 12 Mar 2026 21:10:51 +0100 Subject: [PATCH 03/34] Add bridge from Litmus test to graph. --- LeanCats/LitmusGraphBridge.lean | 93 ++++++++++++++++++++++++++ LeanCats/LitmusParser.lean | 15 +++++ LeanCats/LitmusParser/Enumeration.lean | 18 +++-- LeanCats/LitmusParser/X86.lean | 11 ++- LeanCats/LitmusParserTest.lean | 81 ++++++++++++++++++++++ LeanCats/LitmusReader.lean | 66 ++++++++++++++++++ LeanCats/LitmusReaderTest.lean | 49 ++++++++++++++ LeanCats/Macro.lean | 65 +++++++++--------- 8 files changed, 356 insertions(+), 42 deletions(-) create mode 100644 LeanCats/LitmusGraphBridge.lean create mode 100644 LeanCats/LitmusParser.lean create mode 100644 LeanCats/LitmusParserTest.lean create mode 100644 LeanCats/LitmusReader.lean create mode 100644 LeanCats/LitmusReaderTest.lean diff --git a/LeanCats/LitmusGraphBridge.lean b/LeanCats/LitmusGraphBridge.lean new file mode 100644 index 0000000..7373d5e --- /dev/null +++ b/LeanCats/LitmusGraphBridge.lean @@ -0,0 +1,93 @@ +/- + LitmusGraphBridge.lean + + Conversion from `LitmusParser.ConcreteCandExec` to `LitmusGraph.ConcreteExecution`. + + The two types represent the same concept — a concrete candidate execution — but + with different representations: +-/ +import LeanCats.LitmusGraph +import LeanCats.LitmusParser + +open Data Litmus LitmusParser LitmusGraph + +namespace LitmusGraphBridge + +/-- Convert a `CEvent` (from LitmusParser) to a `Data.Event` (used by LitmusGraph). + + Field mapping: + - `CEvent.id` → `Event.id` + - `CEvent.t_id` → `Event.t_id` + - `CEvent.op` → `Event.effect.op` + - `CEvent.loc` → `Event.effect.location` + - `CEvent.val` → `Event.effect.value` + - `CEvent.isInit` → `Event.effect.isFirstWrite` + - `Event.tag` is set to `⟨Normal, Normal.none⟩` (no extended tag information + is produced by the litmus parser) +-/ +def ceventToEvent (e : CEvent) : Data.Event := { + id := e.id + t_id := e.t_id + effect := { + op := e.op + location := e.loc + value := e.val + isFirstWrite := e.isInit + isFinalWrite := false -- not tracked by the parser; inferred by model checkers + } + tag := ⟨Litmus.Normal, Litmus.Normal.none⟩ +} + +/-- Convert a `ConcreteCandExec` (LitmusParser) to a `ConcreteExecution` (LitmusGraph). + + Parameters: + - `candExec` — the parsed candidate execution from LitmusParser + + The initial-write thread id is inferred automatically from `candExec.initWrites`. + Program threads are labelled `P0`, `P1`, …; the init-write thread is labelled `IW`. + + Relations (`po`, `rf`, `co`, `fr`) in `ConcreteCandExec` are stored as + `Array (Nat × Nat)` (pairs of event ids). This function builds a hash map + from id to `Data.Event` and uses it to lift each pair to `(Data.Event × Data.Event)`. + Location names are recovered directly from `candExec.allEvents` via `CEvent.locName`. +-/ +def candExecToConcreteExec + (candExec : ConcreteCandExec) + : LitmusGraph.ConcreteExecution := + -- 1. Convert every CEvent to a Data.Event + let events := candExec.allEvents.map ceventToEvent + + -- 1.5. Build a location-id → location-name lookup table from the parser events + let locMap : Std.HashMap Nat String := + candExec.allEvents.foldl (fun acc e => + if e.locName.isEmpty then acc else acc.insert e.loc e.locName) {} + + -- 2. Build an id → Event lookup table + let idMap : Std.HashMap Nat Data.Event := + events.foldl (fun acc e => acc.insert e.id e) {} + + -- 3. Lift a relation from (Nat × Nat) to (Event × Event), dropping missing ids + let convertRel (rel : Array (Nat × Nat)) : Array (Data.Event × Data.Event) := + rel.filterMap fun (srcId, tgtId) => do + let src ← idMap.get? srcId + let tgt ← idMap.get? tgtId + return (src, tgt) + + -- 4. Determine the init-write thread id (used for labelling) + let initTid : Nat := + match candExec.initWrites[0]? with + | some (e : CEvent) => e.t_id + | none => Nat.succ (events.foldl (fun m (e : Data.Event) => max m e.t_id) 0) + + { + events := events + locName := fun n => (locMap.get? n).getD s!"loc{n}" + threadName := fun n => if n == initTid then "IW" else s!"P{n}" + po := convertRel candExec.po + rf := convertRel candExec.rf + co := convertRel candExec.co + fr := convertRel candExec.fr + rmw := #[] + } + +end LitmusGraphBridge diff --git a/LeanCats/LitmusParser.lean b/LeanCats/LitmusParser.lean new file mode 100644 index 0000000..2aebbe9 --- /dev/null +++ b/LeanCats/LitmusParser.lean @@ -0,0 +1,15 @@ +/- + LitmusParser.lean — Re-exports all LitmusParser sub-modules. + + Architecture-independent: + • LitmusParser.Types — CEvent, ConcreteCandExec, GeneratedEvents, … + • LitmusParser.Helpers — String utilities, parseInitState, parseExists + • LitmusParser.Enumeration — RF/CO/FR enumeration, enumerateCandidateExecutions + + Architecture-specific: + • LitmusParser.X86 — X86 instruction parsing & event generation +-/ +import LeanCats.LitmusParser.Types +import LeanCats.LitmusParser.Helpers +import LeanCats.LitmusParser.Enumeration +import LeanCats.LitmusParser.X86 diff --git a/LeanCats/LitmusParser/Enumeration.lean b/LeanCats/LitmusParser/Enumeration.lean index df3bc9b..e0eef7e 100644 --- a/LeanCats/LitmusParser/Enumeration.lean +++ b/LeanCats/LitmusParser/Enumeration.lean @@ -118,18 +118,16 @@ def computeFr (rf : Array (Nat × Nat)) (co : Array (Nat × Nat)) : Array (Nat -- Candidate Execution Assembly -- ════════════════════════════════════════════════════════════════ -/-- Enumerate all candidate executions that satisfy the `exists` constraint. +/-- Enumerate all candidate executions. Architecture-independent: takes `GeneratedEvents` produced by any arch-specific generator. -/ -def enumerateCandidateExecutions (gen : GeneratedEvents) (constraint : ExistsConstraint) - : Array ConcreteCandExec := Id.run do +def enumerateCandidateExecutions (gen : GeneratedEvents) : Array ConcreteCandExec := Id.run do let rfAssignments := enumerateRfAssignments gen - let validRfs := rfAssignments.filter (checkConstraint gen · constraint) let coPerLoc := coOrdersPerLocation gen let coChoices := cartesianProduct coPerLoc let mut results : Array ConcreteCandExec := #[] - for rfAssign in validRfs do + for rfAssign in rfAssignments do let mut rfEdges : Array (Nat × Nat) := #[] for i in [:gen.reads.size] do rfEdges := rfEdges.push (rfAssign[i]!, gen.reads[i]!.id) @@ -156,4 +154,14 @@ def enumerateCandidateExecutions (gen : GeneratedEvents) (constraint : ExistsCon results := results.push exec return results +/-- Enumerate candidate executions that satisfy the `exists` constraint. -/ +def enumerateConstrainedCandidateExecutions (gen : GeneratedEvents) (constraint : ExistsConstraint) + : Array ConcreteCandExec := + (enumerateCandidateExecutions gen).filter fun exec => + let rfAssign := gen.reads.map fun r => + match exec.rf.find? (fun (_, rid) => rid == r.id) with + | some (wid, _) => wid + | none => 0 + checkConstraint gen rfAssign constraint + end LitmusParser diff --git a/LeanCats/LitmusParser/X86.lean b/LeanCats/LitmusParser/X86.lean index bb04df4..9c3d879 100644 --- a/LeanCats/LitmusParser/X86.lean +++ b/LeanCats/LitmusParser/X86.lean @@ -232,7 +232,14 @@ def generateEvents (test : ParsedLitmus) : GeneratedEvents := Id.run do def parseLitmusAndEnumerate (input : String) : Except String (Array ConcreteCandExec) := do let test ← parseLitmus input let gen := generateEvents test - return enumerateCandidateExecutions gen test.constraint + return enumerateCandidateExecutions gen + +/-- Parse an X86 litmus test string and enumerate candidate executions + satisfying the `exists` constraint. -/ +def parseLitmusAndEnumerateConstrained (input : String) : Except String (Array ConcreteCandExec) := do + let test ← parseLitmus input + let gen := generateEvents test + return enumerateConstrainedCandidateExecutions gen test.constraint -- ════════════════════════════════════════════════════════════════ -- Example X86 Litmus Test Strings @@ -270,7 +277,7 @@ exists (0:EAX=1 /\\ 1:EAX=1)" def reportResults (label : String) (input : String) : String := match parseLitmusAndEnumerate input with | .ok results => - let header := s!"{label}: {results.size} candidate execution(s) matching constraint\n" + let header := s!"{label}: {results.size} candidate execution(s)\n" results.foldl (fun acc r => acc ++ s!"---\n{r}\n") header | .error e => s!"Error: {e}" diff --git a/LeanCats/LitmusParserTest.lean b/LeanCats/LitmusParserTest.lean new file mode 100644 index 0000000..72286f1 --- /dev/null +++ b/LeanCats/LitmusParserTest.lean @@ -0,0 +1,81 @@ +/- + LitmusParserTest.lean — Programmatically test that the litmus parser can parse + all 30 litmus test files in LeanCats/examples/tests/. +-/ +import LeanCats.LitmusParser + +open LitmusParser LitmusParser.X86 + +def testDir : String := "LeanCats/examples/tests" + +def allLitmusFiles : Array String := #[ + "2W.litmus", + "3LB.litmus", + "3MP.litmus", + "3SB.litmus", + "3SB_MFence.litmus", + "4LB.litmus", + "4SB.litmus", + "CoRR.litmus", + "CoRW.litmus", + "CoWR.litmus", + "CoWW.litmus", + "IRIW.litmus", + "IRIW_MFence.litmus", + "ISA2.litmus", + "ISA2_MFence.litmus", + "LB.litmus", + "LB_MFence.litmus", + "MP.litmus", + "MP2.litmus", + "MP_MFence.litmus", + "RRW.litmus", + "RWC.litmus", + "RWC_MFence.litmus", + "SB.litmus", + "SB_MFence.litmus", + "SB_opt.litmus", + "WRC.litmus", + "WRR.litmus", + "WRW.litmus", + "WWC.litmus" +] + +/-- Run parse+enumerate on every file; return (passed, failed) counts + failure list. -/ +def runAllTests : IO (Nat × Nat × Array String) := do + let mut passed : Nat := 0 + let mut failed : Nat := 0 + let mut failures : Array String := #[] + for fname in allLitmusFiles do + let path := testDir ++ "/" ++ fname + let contents ← IO.FS.readFile path + match parseLitmusAndEnumerate contents with + | .ok execs => + passed := passed + 1 + IO.println s!" PASS {fname} ({execs.size} candidate(s))" + | .error e => + failed := failed + 1 + failures := failures.push fname + IO.println s!" FAIL {fname} — {e}" + return (passed, failed, failures) + +def main : IO Unit := do + IO.println "════════════════════════════════════════════════════" + IO.println " X86 Litmus Parser — Test Suite" + IO.println s!" Directory: {testDir}" + IO.println s!" Files: {allLitmusFiles.size}" + IO.println "════════════════════════════════════════════════════" + let (passed, failed, failures) ← runAllTests + IO.println "────────────────────────────────────────────────────" + IO.println s!" Results: {passed} passed, {failed} failed" + if failures.isEmpty then + IO.println " ✓ All tests passed." + else + IO.println " ✗ Failures:" + for f in failures do + IO.println s!" {f}" + IO.println "════════════════════════════════════════════════════" + if failed > 0 then + IO.Process.exit 1 + +#eval main diff --git a/LeanCats/LitmusReader.lean b/LeanCats/LitmusReader.lean new file mode 100644 index 0000000..e29ace2 --- /dev/null +++ b/LeanCats/LitmusReader.lean @@ -0,0 +1,66 @@ +/- + LitmusReader.lean — Read litmus test files from disk and enumerate candidate executions. + + Usage (inside a Lean file): + + ``` + -- Read a litmus file, parse it, enumerate candidate executions, and print results. + #litmus "examples/tests/SB.litmus" + + -- Read and bind the candidate executions to a definition. + deflitmus sb_execs < "examples/tests/SB.litmus" > + #eval sb_execs.size + ``` +-/ +import Lean +import LeanCats.LitmusParser + +open Lean Elab Command Meta +open LitmusParser LitmusParser.X86 + +/-- Read a litmus file from disk (relative to the workspace root). -/ +private def readLitmusFile (path : String) : IO String := + IO.FS.readFile path + +/-- Parse and enumerate candidate executions from a litmus file string. -/ +private def processLitmus (contents : String) : Except String (Array ConcreteCandExec) := + X86.parseLitmusAndEnumerate contents + +/-- Format candidate executions for display. -/ +private def formatResults (label : String) (execs : Array ConcreteCandExec) : String := + let header := s!"{label}: {execs.size} candidate execution(s)\n" + execs.foldl (fun acc r => acc ++ s!"---\n{r}\n") header + +/-- `#litmus "path/to/test.litmus"` — read, parse, enumerate, and print results. -/ +elab "#litmus" path:str : command => do + let filePath := path.getString + let contents ← IO.FS.readFile filePath + match processLitmus contents with + | .ok execs => + let label := filePath.splitOn "/" |>.getLast! + logInfo (formatResults label execs) + | .error e => + throwError s!"Litmus parse error: {e}" + +/-- `deflitmus name < "path/to/test.litmus" >` — read a litmus file and define + `name : Array ConcreteCandExec` in the environment. -/ +elab "deflitmus" name:ident "<" path:str ">" : command => do + let filePath := path.getString + let contents ← IO.FS.readFile filePath + match processLitmus contents with + | .ok execs => + -- Build the term by first turning the execution array into source text, + -- then storing the raw string + reparsing at use-site via a thunk. + -- For simplicity, we define it as an opaque constant backed by native_decide / IO. + -- The simplest approach: store the file content and re-parse at #eval time. + let contentLit := Lean.Syntax.mkStrLit contents + let cmd ← `( + def $(name) : Array LitmusParser.ConcreteCandExec := + match LitmusParser.X86.parseLitmusAndEnumerate $contentLit with + | .ok execs => execs + | .error _ => #[] + ) + elabCommand cmd + logInfo s!"Defined '{name.getId}' with {execs.size} candidate execution(s)" + | .error e => + throwError s!"Litmus parse error ({filePath}): {e}" diff --git a/LeanCats/LitmusReaderTest.lean b/LeanCats/LitmusReaderTest.lean new file mode 100644 index 0000000..a581a19 --- /dev/null +++ b/LeanCats/LitmusReaderTest.lean @@ -0,0 +1,49 @@ +/- + LitmusReaderTest.lean — Test the litmus file reader commands. +-/ +import LeanCats.LitmusReader +import LeanCats.LitmusGraph +import LeanCats.LitmusGraphBridge + +-- Classic 2-thread tests +#litmus "LeanCats/examples/tests/SB.litmus" +#litmus "LeanCats/examples/tests/MP.litmus" +#litmus "LeanCats/examples/tests/LB.litmus" +#litmus "LeanCats/examples/tests/CoWW.litmus" +#litmus "LeanCats/examples/tests/CoWR.litmus" +#litmus "LeanCats/examples/tests/CoRW.litmus" +#litmus "LeanCats/examples/tests/CoRR.litmus" +#litmus "LeanCats/examples/tests/WRR.litmus" +#litmus "LeanCats/examples/tests/RRW.litmus" +#litmus "LeanCats/examples/tests/WWC.litmus" +#litmus "LeanCats/examples/tests/WRW.litmus" +#litmus "LeanCats/examples/tests/2W.litmus" + +deflitmus SB <"LeanCats/examples/tests/SB.litmus"> + +def a := LitmusGraphBridge.candExecToConcreteExec SB[0]! +#html LitmusGraph.toOrderedHtml a + +-- With MFENCE +#litmus "LeanCats/examples/tests/SB_MFence.litmus" +#litmus "LeanCats/examples/tests/MP_MFence.litmus" +#litmus "LeanCats/examples/tests/LB_MFence.litmus" + +-- 3-thread tests +#litmus "LeanCats/examples/tests/WRC.litmus" +#litmus "LeanCats/examples/tests/RWC.litmus" +#litmus "LeanCats/examples/tests/ISA2.litmus" +#litmus "LeanCats/examples/tests/3SB.litmus" +#litmus "LeanCats/examples/tests/3LB.litmus" +#litmus "LeanCats/examples/tests/3MP.litmus" +#litmus "LeanCats/examples/tests/RWC_MFence.litmus" +#litmus "LeanCats/examples/tests/ISA2_MFence.litmus" +#litmus "LeanCats/examples/tests/MP2.litmus" +#litmus "LeanCats/examples/tests/SB_opt.litmus" +#litmus "LeanCats/examples/tests/3SB_MFence.litmus" + +-- 4-thread tests +#litmus "LeanCats/examples/tests/IRIW.litmus" +#litmus "LeanCats/examples/tests/IRIW_MFence.litmus" +#litmus "LeanCats/examples/tests/4SB.litmus" +#litmus "LeanCats/examples/tests/4LB.litmus" diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index 2248d87..f8ba9dc 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -20,8 +20,6 @@ syntax "[reserved|" reserved "," cat_ident "," cat_ident "]" : term syntax "[predefined-relations|" predefined_relations "," cat_ident "," cat_ident "]" : term syntax "[dsl-term|" dsl_term "," cat_ident "," cat_ident "]" : term -initialize tagsAccExt : HashMapExtension String (List String) ← mkHashMapExtension `tags String (List String) - -- Walk any cat_ident syntax tree, collect all ident leaves, and join with "_". -- This handles plain idents, tick-prefixed ('ONCE), and multi-hyphen (rcu-lock, after-unlock-lock). partial def catIdentToName (stx : Syntax) : Name := @@ -62,7 +60,7 @@ macro_rules `(SetRel.comp ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) | `([expr| [ $i:expr ], $evts, $X ]) => - `(SetRel.mkId `([expr| $i, $evts, $X]) ) + `(SetRel.mkId ([expr| $i, $evts, $X])) | `([expr| $e₁:expr * $e₂:expr, $evts, $X]) => `(CatRel.prod ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) @@ -145,27 +143,28 @@ macro_rules macro_rules | `([predefined-relations| fr, $_, $X]) => - let rfIdent := mkIdent "_fr".toName - `($X.$rfIdent) + let nm := mkIdent "fr".toName + `($X.$nm) | `([predefined-relations| po, $_, $X]) => - let rfIdent := mkIdent "_po".toName - `($X.$rfIdent) + let nm := mkIdent "po".toName + `($X.$nm) | `([predefined-relations| rf, $_, $X]) => - let rfIdent := mkIdent "_rf".toName - `($X.$rfIdent) + let nm := mkIdent "rf".toName + `($X.$nm) | `([predefined-relations| rfe, $_, $X]) => - let rfIdent := mkIdent "_rf".toName - `($X.$rfIdent) + let nm := mkIdent "rf".toName + `($X.$nm) | `([predefined-relations| rmw, $_, $X]) => - let rfIdent := mkIdent "_rmw".toName - `($X.$rfIdent) + let nm := mkIdent "rmw".toName + `($X.$nm) - | `([predefined-relations| co, $evts, $_]) => - `(CatRel.co.wellformed $evts) + | `([predefined-relations| co, $_, $X]) => + let co' := mkIdent "co".toName + `($X.$co') macro_rules | `([keyword| and]) => Lean.Macro.throwUnsupported @@ -194,24 +193,24 @@ macro_rules | `([assertion| empty]) => `(CatRel.IsEmpty) macro_rules - | `([annotable-events| W, $evts, $X]) => + | `([annotable-events| W, $evts, $_]) => let nm := mkIdent "W".toName - `(($X.$evts.$nm : Set Event)) - | `([annotable-events| R, $evts, $X]) => + `(($evts.$nm : Set Event)) + | `([annotable-events| R, $evts, $_]) => let nm := mkIdent "R".toName - `(($X.$evts.$nm : Set Event)) - | `([annotable-events| B, $evts, $X]) => + `(($evts.$nm : Set Event)) + | `([annotable-events| B, $evts, $_]) => let nm := mkIdent "B".toName - `(($X.$evts.$nm : Set Event)) - | `([annotable-events| F, $evts, $X]) => + `(($evts.$nm : Set Event)) + | `([annotable-events| F, $evts, $_]) => let nm := mkIdent "F".toName - `(($X.$evts.$nm : Set Event)) - | `([annotable-events| RMW, $evts, $X]) => + `(($evts.$nm : Set Event)) + | `([annotable-events| RMW, $evts, $_]) => let nm := mkIdent "RMW".toName - `(($X.$evts.$nm : Set Event)) - | `([annotable-events| SRCU, $evts, $X]) => + `(($evts.$nm : Set Event)) + | `([annotable-events| SRCU, $evts, $_]) => let nm := mkIdent "SRCU".toName - `(($X.$evts.$nm : Set Event)) + `(($evts.$nm : Set Event)) namespace TestAnnotableEvents variable (evts : Events) [IsStrictTotalOrder Event (CatRel.preCo evts)] (x : CandidateExecution evts) @@ -221,13 +220,13 @@ end TestAnnotableEvents macro_rules -- | `([predefined-events| ___]) => __ TODO!(figure all the definiations of all the events. (⋃?)) - | `([predefined-events| IW, $evts, $X]) => + | `([predefined-events| IW, $evts, $_]) => let nm := mkIdent "IW".toName - `($X.$evts.$nm) + `($evts.$nm) - | `([predefined-events| M, $evts, $X]) => + | `([predefined-events| M, $evts, $_]) => let nm := mkIdent "M".toName - `($X.$evts.$nm) + `($evts.$nm) | `([predefined-events| $a:annotable_events, $evts, $X]) => `([annotable-events| $a, $evts, $X]) @@ -402,10 +401,6 @@ let Release = RELEASE \ R \ FailedRMW let Mb = MB \ FailedRMW let Noreturn = NORETURN \ W -enum srcu = Srcu_lock || Srcu_unlock || Sync_srcu -instructions SRCU[srcu] -let Srcu = Srcu_lock | Srcu_unlock | Sync_srcu - let Marked = (~M) | IW | ONCE | RELEASE | ACQUIRE | MB | RMW | Srcu-lock | Srcu-unlock let Plain = M \ Marked From 5ec054132d3f835f707b58ddd5275ccdffae7c51 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Fri, 13 Mar 2026 17:25:19 +0100 Subject: [PATCH 04/34] Fix the identifier type mismatch bug. --- LeanCats/Macro.lean | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index f8ba9dc..318b4a5 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -134,7 +134,7 @@ macro_rules macro_rules | `([dsl-term| $i:cat_ident, $evts, $X]) => - `($i $evts $X) + `($i $evts) macro_rules | `([reserved| $r:predefined_relations, $evts, $X]) => @@ -369,12 +369,14 @@ abbrev domain (r : SetRel Event Event) := SetRel.dom r abbrev range (r : SetRel Event Event) := SetRel.cod r [model| test - let acq = [M] + let acq = M ] +#reduce test.acq + namespace LinuxTest [model| linux - +let a = W enum Accesses = ONCE || RELEASE || ACQUIRE || @@ -395,18 +397,10 @@ enum Barriers = wmb || after-srcu-read-unlock instructions F[Barriers] -let FailedRMW = RMW \ (domain(rmw) | range(rmw)) -let Acquire = ACQUIRE \ W \ FailedRMW -let Release = RELEASE \ R \ FailedRMW -let Mb = MB \ FailedRMW -let Noreturn = NORETURN \ W - -let Marked = (~M) | IW | ONCE | RELEASE | ACQUIRE | MB | RMW | Srcu-lock | Srcu-unlock -let Plain = M \ Marked - +let c = NORETURN * W ] +#reduce LinuxTest.linux.NORETURN -#reduce linux.Plain -- Check the instruction sets end LinuxTest From 88d94e3a6211e7b027065a119d164aba93dfb56a Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 14 Mar 2026 03:24:11 +0100 Subject: [PATCH 05/34] Fix the naming bugs. --- LeanCats/Macro.lean | 74 +++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index 318b4a5..436db0b 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -19,6 +19,7 @@ syntax "[predefined-events|" predefined_events "," cat_ident "," cat_ident "]" : syntax "[reserved|" reserved "," cat_ident "," cat_ident "]" : term syntax "[predefined-relations|" predefined_relations "," cat_ident "," cat_ident "]" : term syntax "[dsl-term|" dsl_term "," cat_ident "," cat_ident "]" : term +syntax "[dsl-term|" dsl_term "," cat_ident "]" : term -- Walk any cat_ident syntax tree, collect all ident leaves, and join with "_". -- This handles plain idents, tick-prefixed ('ONCE), and multi-hyphen (rcu-lock, after-unlock-lock). @@ -134,6 +135,8 @@ macro_rules macro_rules | `([dsl-term| $i:cat_ident, $evts, $X]) => + `($i $evts $X) + | `([dsl-term| $i:cat_ident, $evts]) => `($i $evts) macro_rules @@ -193,24 +196,24 @@ macro_rules | `([assertion| empty]) => `(CatRel.IsEmpty) macro_rules - | `([annotable-events| W, $evts, $_]) => + | `([annotable-events| W, $evts, $X]) => let nm := mkIdent "W".toName - `(($evts.$nm : Set Event)) - | `([annotable-events| R, $evts, $_]) => + `(($X.$evts.$nm : Set Event)) + | `([annotable-events| R, $evts, $X]) => let nm := mkIdent "R".toName - `(($evts.$nm : Set Event)) - | `([annotable-events| B, $evts, $_]) => + `(($X.$evts.$nm : Set Event)) + | `([annotable-events| B, $evts, $X]) => let nm := mkIdent "B".toName - `(($evts.$nm : Set Event)) - | `([annotable-events| F, $evts, $_]) => + `(($X.$evts.$nm : Set Event)) + | `([annotable-events| F, $evts, $X]) => let nm := mkIdent "F".toName - `(($evts.$nm : Set Event)) - | `([annotable-events| RMW, $evts, $_]) => + `(($X.$evts.$nm : Set Event)) + | `([annotable-events| RMW, $evts, $X]) => let nm := mkIdent "RMW".toName - `(($evts.$nm : Set Event)) - | `([annotable-events| SRCU, $evts, $_]) => + `(($X.$evts.$nm : Set Event)) + | `([annotable-events| SRCU, $evts, $X]) => let nm := mkIdent "SRCU".toName - `(($evts.$nm : Set Event)) + `(($X.$evts.$nm : Set Event)) namespace TestAnnotableEvents variable (evts : Events) [IsStrictTotalOrder Event (CatRel.preCo evts)] (x : CandidateExecution evts) @@ -271,7 +274,7 @@ macro_rules return mkNullNode #[] -- namespace LKMM --- [inst| let rcu-fn = +-- [inst| let rcu-fn =$[ -- unmatched-locks = Rcu-lock \ domain(matched) -- and unmatched-unlocks = Rcu-unlock \ range(matched) -- and unmatched = unmatched-locks | unmatched-unlocks @@ -297,14 +300,14 @@ we generate: @[command_elab catinst] def elabCatInst : CommandElab := fun stx => do match stx with - | `([inst| instructions $a:annotable_events [ $c:cat_ident ] , $evts:cat_ident , $X:cat_ident]) => do + | `([inst| instructions { $a:annotable_events,* }[ $c:cat_ident ] , $evts:cat_ident , $X:cat_ident]) => do dbg_trace "entering elabCatInst" let currNamespace <- getCurrNamespace -- This is used to get the full name with namespace. let typeName := Name.updatePrefix c.getId currNamespace let info <- getConstInfoInduct typeName - dbg_trace typeName + -- dbg_trace typeName let commands <- info.ctors.mapM ( fun ctor => do @@ -314,10 +317,15 @@ def elabCatInst : CommandElab := fun stx => do if (<-getEnv).contains ctorName then return (TSyntax.mk $ mkNullNode #[]) else - let ctorDef <- - `( + let ctorDef <- `({e | e.tag = ⟨$(mkIdent typeName), $(mkIdent ctor)⟩ }) + + let inters : TSyntax `term ← a.getElems.foldlM + (fun (acc : TSyntax `term) (ae_i : TSyntax `annotable_events) => do + `( $acc ∩ [annotable-events| $ae_i, $evts, $X] )) ctorDef + + let ctorDef <- `( abbrev $(mkIdent ctorName) : - Set Event := {e | e.tag = ⟨$(mkIdent typeName), $(mkIdent ctor)⟩ } ∩ ([annotable-events| $a, $evts, $X]) + Set Event := $inters ) return ctorDef ) @@ -339,6 +347,8 @@ macro_rules let ret := #[nstart] ++ #[vars] ++ insts ++ #[nend] return mkNullNode ret +set_option pp.rawOnError true + -- Linux-kernel memory consistency model ("linux.bell" excerpt) -- Comments (*...*) and tick-prefixes (') are stripped by the preprocessor -- before these lines reach the Lean syntax; we write the cleaned form here. @@ -350,9 +360,11 @@ macro_rules after_spinlock || after_unlock_lock || after_srcu_read_unlock - instructions F[Barriers] + instructions {W}[Barriers] ] +#check t.after_atomic' + #check t.Barriers.after_atomic' #reduce t.after_atomic @@ -374,15 +386,14 @@ abbrev range (r : SetRel Event Event) := SetRel.cod r #reduce test.acq -namespace LinuxTest -[model| linux -let a = W +[model| lkmm + enum Accesses = ONCE || RELEASE || ACQUIRE || NORETURN || MB -instructions R[Accesses] +instructions {R}[Accesses] enum Barriers = wmb || rmb || @@ -395,12 +406,17 @@ enum Barriers = wmb || after-spinlock || after-unlock-lock || after-srcu-read-unlock -instructions F[Barriers] +instructions {F, B}[Barriers] -let c = NORETURN * W -] -#reduce LinuxTest.linux.NORETURN +let FailedRMW = RMW \ (domain(rmw) | range(rmw)) +let Acquire = ACQUIRE \ W \ FailedRMW +let Release = RELEASE \ R \ FailedRMW +let Mb = MB \ FailedRMW +let Noreturn = NORETURN \ W +let Marked = (~M) | IW | ONCE | RELEASE | ACQUIRE | MB | RMW + +let Plain = M \ Marked +] --- Check the instruction sets -end LinuxTest +#reduce lkmm.Marked From 2ae5a7b28ce046eb47831162e97aafa51de25e86 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 14 Mar 2026 03:28:28 +0100 Subject: [PATCH 06/34] change the locations of litmus test files --- LeanCats/Basic.lean | 1 + LeanCats/CatPreprocessor.lean | 66 +++++++- LeanCats/Cats/examples/models/lkmm.bell | 49 ++++++ LeanCats/Cats/examples/models/lkmm.cat | 145 ++++++++++++++++++ LeanCats/{ => Cats}/examples/tests/2W.litmus | 0 LeanCats/{ => Cats}/examples/tests/3LB.litmus | 0 LeanCats/{ => Cats}/examples/tests/3MP.litmus | 0 LeanCats/{ => Cats}/examples/tests/3SB.litmus | 0 .../examples/tests/3SB_MFence.litmus | 0 LeanCats/{ => Cats}/examples/tests/4LB.litmus | 0 LeanCats/{ => Cats}/examples/tests/4SB.litmus | 0 .../{ => Cats}/examples/tests/CoRR.litmus | 0 .../{ => Cats}/examples/tests/CoRW.litmus | 0 .../{ => Cats}/examples/tests/CoWR.litmus | 0 .../{ => Cats}/examples/tests/CoWW.litmus | 0 .../{ => Cats}/examples/tests/IRIW.litmus | 0 .../examples/tests/IRIW_MFence.litmus | 0 .../{ => Cats}/examples/tests/ISA2.litmus | 0 .../examples/tests/ISA2_MFence.litmus | 0 LeanCats/{ => Cats}/examples/tests/LB.litmus | 0 .../examples/tests/LB_MFence.litmus | 0 LeanCats/{ => Cats}/examples/tests/MP.litmus | 0 LeanCats/{ => Cats}/examples/tests/MP2.litmus | 0 .../examples/tests/MP_MFence.litmus | 0 LeanCats/{ => Cats}/examples/tests/RRW.litmus | 0 LeanCats/{ => Cats}/examples/tests/RWC.litmus | 0 .../examples/tests/RWC_MFence.litmus | 0 LeanCats/{ => Cats}/examples/tests/SB.litmus | 0 .../examples/tests/SB_MFence.litmus | 0 .../{ => Cats}/examples/tests/SB_opt.litmus | 0 LeanCats/{ => Cats}/examples/tests/WRC.litmus | 0 LeanCats/{ => Cats}/examples/tests/WRR.litmus | 0 LeanCats/{ => Cats}/examples/tests/WRW.litmus | 0 LeanCats/{ => Cats}/examples/tests/WWC.litmus | 0 LeanCats/{Reader.lean => ModelReader.lean} | 2 +- LeanCats/Syntax.lean | 2 +- lakefile.lean | 3 + 37 files changed, 265 insertions(+), 3 deletions(-) create mode 100644 LeanCats/Cats/examples/models/lkmm.bell create mode 100644 LeanCats/Cats/examples/models/lkmm.cat rename LeanCats/{ => Cats}/examples/tests/2W.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/3LB.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/3MP.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/3SB.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/3SB_MFence.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/4LB.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/4SB.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/CoRR.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/CoRW.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/CoWR.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/CoWW.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/IRIW.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/IRIW_MFence.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/ISA2.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/ISA2_MFence.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/LB.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/LB_MFence.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/MP.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/MP2.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/MP_MFence.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/RRW.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/RWC.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/RWC_MFence.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/SB.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/SB_MFence.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/SB_opt.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/WRC.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/WRR.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/WRW.litmus (100%) rename LeanCats/{ => Cats}/examples/tests/WWC.litmus (100%) rename LeanCats/{Reader.lean => ModelReader.lean} (97%) diff --git a/LeanCats/Basic.lean b/LeanCats/Basic.lean index e873b3c..1d6f8af 100644 --- a/LeanCats/Basic.lean +++ b/LeanCats/Basic.lean @@ -8,6 +8,7 @@ open CatRel This definination is different with the formal semantics, because the `co` is defined in [stdlib.cat](https://github.com/herd/herdtools7/blob/2a7599f8ecdbde0ed67925daf6534c1a0c26d535/herd-www/cat_includes/stdlib.cat) and by computation, so should declare it as the base relation. -/ structure CandidateExecution (evts : Events) where + evts := evts idUnique := ∀ e₁ e₂ : Event, (e₁ ∈ evts ∧ e₂ ∈ evts) -> e₁.id ≠ e₂.id po := evts.po rf : SetRel Event Event diff --git a/LeanCats/CatPreprocessor.lean b/LeanCats/CatPreprocessor.lean index 63a4360..0dfbedd 100644 --- a/LeanCats/CatPreprocessor.lean +++ b/LeanCats/CatPreprocessor.lean @@ -1,4 +1,6 @@ +import Std.Data.HashMap + namespace String @[specialize] def foldl2Aux {α : Type u} (f : α → Char → Char → α) (s : String) (stopPos : Pos.Raw) (i : Pos.Raw) (a : α) : α := @@ -72,13 +74,71 @@ def removeTickAndCapitalize (s : String) : String := let rest := stripped.drop 1 (firstChar.toUpper.toString ++ rest) +private structure InstrGroup where + firstIdx : Nat + events : Array String + +private def parseInstructionsLine? (line : String) : Option (Array String × String) := + let trimmed := (String.trimAscii line).toString + if !(trimmed.startsWith "instructions ") then + none + else + let rest := (String.dropPrefix trimmed "instructions ").toString + match rest.splitOn "[" with + | [evtsRaw, tagsAndTail] => + match tagsAndTail.splitOn "]" with + | [] => none + | tag::_ => + let eventText := (String.trimAscii evtsRaw).toString + let events := + if eventText.startsWith "{" && eventText.endsWith "}" then + let body := ((eventText.drop 1).dropEnd 1).toString + (body.splitOn ",").map (fun s => (String.trimAscii s).toString) |>.toArray + else + #[eventText] + let tagTrimmed := (String.trimAscii tag).toString + if events.isEmpty || tagTrimmed.isEmpty then none + else some (events, tagTrimmed) + | _ => none + +private def mergeInstructionTagLines (input : String) : String := Id.run do + let lines := input.splitOn "\n" + let mut groups : Std.HashMap String InstrGroup := {} + + for i in [:lines.length] do + let line := lines[i]! + match parseInstructionsLine? line with + | none => pure () + | some (events, tag) => + match groups.get? tag with + | none => + groups := groups.insert tag { firstIdx := i, events := events } + | some g => + let merged := events.foldl (fun acc e => if acc.contains e then acc else acc.push e) g.events + groups := groups.insert tag { g with events := merged } + + let mut out : List String := [] + for i in [:lines.length] do + let line := lines[i]! + match parseInstructionsLine? line with + | none => out := line :: out + | some (_, tag) => + match groups.get? tag with + | some g => + if g.firstIdx == i then + let mergedEvts := String.intercalate ", " g.events.toList + out := ("instructions {" ++ mergedEvts ++ "}[" ++ tag ++ "]") :: out + | none => out := line :: out + + String.intercalate "\n" out.reverse + def removeComments (input : String) : String := let removedTick := removeFrontTick input let headProcessed : String := match removedTick.toList with | [] => .ofList [] | '"'::rest => (String.ofList rest).foldl processHead (String.ofList [], false) |>.1 | s => .ofList s - removeBlockComments headProcessed + mergeInstructionTagLines (removeBlockComments headProcessed) #eval removeFrontTick "'example || 'string" @@ -118,3 +178,7 @@ def enums_test := "enum Accesses = 'ONCE (*READ_ONCE,WRITE_ONCE*) || 'MB (*xchg(),cmpxchg(),...*)" #eval removeComments enums_test + +def instructions_test := "instructions R[Accesses]\ninstructions W[Accesses]\ninstructions RMW[Accesses]\ninstructions F[Barriers]" + +#eval removeComments instructions_test diff --git a/LeanCats/Cats/examples/models/lkmm.bell b/LeanCats/Cats/examples/models/lkmm.bell new file mode 100644 index 0000000..7a047d9 --- /dev/null +++ b/LeanCats/Cats/examples/models/lkmm.bell @@ -0,0 +1,49 @@ +(* + * Copyright (C) 2015 Jade Alglave , + * Copyright (C) 2016 Luc Maranget for Inria + * Copyright (C) 2017 Alan Stern , + * Andrea Parri + * + * An earlier version of this file appeared in the companion webpage for + * "Frightening small children and disconcerting grown-ups: Concurrency + * in the Linux kernel" by Alglave, Maranget, McKenney, Parri, and Stern, + * which appeared in ASPLOS 2018. + *) + +enum Accesses = 'ONCE (*READ_ONCE,WRITE_ONCE*) || + 'RELEASE (*smp_store_release*) || + 'ACQUIRE (*smp_load_acquire*) || + 'NORETURN (* R of non-return RMW *) || + 'MB (*xchg(),cmpxchg(),...*) +instructions R[Accesses] +instructions W[Accesses] +instructions RMW[Accesses] + +enum Barriers = 'wmb (*smp_wmb*) || + 'rmb (*smp_rmb*) || + 'MB (*smp_mb*) || + 'barrier (*barrier*) || + 'rcu-lock (*rcu_read_lock*) || + 'rcu-unlock (*rcu_read_unlock*) || + 'sync-rcu (*synchronize_rcu*) || + 'before-atomic (*smp_mb__before_atomic*) || + 'after-atomic (*smp_mb__after_atomic*) || + 'after-spinlock (*smp_mb__after_spinlock*) || + 'after-unlock-lock (*smp_mb__after_unlock_lock*) || + 'after-srcu-read-unlock (*smp_mb__after_srcu_read_unlock*) +instructions F[Barriers] + +(* + * Filter out syntactic annotations that do not provide the corresponding + * semantic ordering, such as Acquire on a store or Mb on a failed RMW. + *) +let FailedRMW = RMW \ (domain(rmw) | range(rmw)) +let Acquire = ACQUIRE \ W \ FailedRMW +let Release = RELEASE \ R \ FailedRMW +let Mb = MB \ FailedRMW +let Noreturn = NORETURN \ W + +(* Compute marked and plain memory accesses *) +let Marked = (~M) | IW | ONCE | RELEASE | ACQUIRE | MB | RMW | + LKR | LKW | UL | LF | RL | RU +let Plain = M \ Marked diff --git a/LeanCats/Cats/examples/models/lkmm.cat b/LeanCats/Cats/examples/models/lkmm.cat new file mode 100644 index 0000000..8a7fafe --- /dev/null +++ b/LeanCats/Cats/examples/models/lkmm.cat @@ -0,0 +1,145 @@ +(* + * Copyright (C) 2015 Jade Alglave , + * Copyright (C) 2016 Luc Maranget for Inria + * Copyright (C) 2017 Alan Stern , + * Andrea Parri + * + * An earlier version of this file appeared in the companion webpage for + * "Frightening small children and disconcerting grown-ups: Concurrency + * in the Linux kernel" by Alglave, Maranget, McKenney, Parri, and Stern, + * which appeared in ASPLOS 2018. + *) + +(* + * File "lock.cat" handles locks and is experimental. + * It can be replaced by include "cos.cat" for tests that do not use locks. + *) + +include "lock.cat" + +(*******************) +(* Basic relations *) +(*******************) + +(* Release Acquire *) +let acq-po = [Acquire] ; po ; [M] +let po-rel = [M] ; po ; [Release] +let po-unlock-rf-lock-po = po ; [UL] ; rf ; [LKR] ; po + +(* Fences *) +let R4rmb = R \ Noreturn (* Reads for which rmb works *) +let rmb = [R4rmb] ; fencerel(Rmb) ; [R4rmb] +let wmb = [W] ; fencerel(Wmb) ; [W] +let mb = ([M] ; fencerel(Mb) ; [M]) | + ([M] ; fencerel(Before-atomic) ; [RMW] ; po? ; [M]) | + ([M] ; po? ; [RMW] ; fencerel(After-atomic) ; [M]) | + ([M] ; po? ; [LKW] ; fencerel(After-spinlock) ; [M]) | + ([M] ; po ; [UL] ; (co | po) ; [LKW] ; + fencerel(After-unlock-lock) ; [M]) +let strong-fence = mb + +let nonrw-fence = strong-fence | po-rel | acq-po +let fence = nonrw-fence | wmb | rmb +let barrier = fencerel(Barrier | Rmb | Wmb | Mb | + Before-atomic | After-atomic | Acquire | Release) | + (po ; [Release]) | ([Acquire] ; po) + +(**********************************) +(* Fundamental coherence ordering *) +(**********************************) + +(* Sequential Consistency Per Variable *) +let com = rf | co | fr +acyclic po-loc | com as coherence + +(* Atomic Read-Modify-Write *) +empty rmw & (fre ; coe) as atomic + +(**********************************) +(* Instruction execution ordering *) +(**********************************) + +(* Preserved Program Order *) +let dep = addr | data +let rwdep = (dep | ctrl) ; [W] +let overwrite = co | fr +let to-w = rwdep | (overwrite & int) | (addr ; [Plain] ; wmb) +let to-r = addr | (dep ; [Marked] ; rfi) +let ppo = to-r | to-w | fence | (po-unlock-rf-lock-po & int) + +(* Propagation: Ordering from release operations and strong fences. *) +let A-cumul(r) = (rfe ; [Marked])? ; r +let cumul-fence = [Marked] ; (A-cumul(strong-fence | po-rel) | wmb | + po-unlock-rf-lock-po) ; [Marked] +let prop = [Marked] ; (overwrite & ext)? ; cumul-fence* ; + [Marked] ; rfe? ; [Marked] + +(* + * Happens Before: Ordering from the passage of time. + * No fences needed here for prop because relation confined to one process. + *) +let hb = [Marked] ; (ppo | rfe | ((prop \ id) & int)) ; [Marked] +acyclic hb as happens-before + +(****************************************) +(* Write and fence propagation ordering *) +(****************************************) + +(* Propagation: Each non-rf link needs a strong fence. *) +let pb = prop ; strong-fence ; hb* ; [Marked] +acyclic pb as propagation + +(* + * The happens-before, propagation are all + * expressions of temporal ordering. They could be replaced by + * a single constraint on an "executes-before" relation, xb: + * + * let xb = hb | pb | rb + * acyclic xb as executes-before + *) + +(*********************************) +(* Plain accesses and data races *) +(*********************************) + +(* Warn about plain writes and marked accesses in the same region *) +let mixed-accesses = ([Plain & W] ; (po-loc \ barrier) ; [Marked]) | + ([Marked] ; (po-loc \ barrier) ; [Plain & W]) +flag ~empty mixed-accesses as mixed-accesses + +(* Executes-before and visibility *) +let xbstar = (hb | pb | rb)* +let vis = cumul-fence* ; rfe? ; [Marked] ; + ((strong-fence ; [Marked] ; xbstar) | (xbstar & int)) + +(* Boundaries for lifetimes of plain accesses *) +let w-pre-bounded = [Marked] ; (addr | fence)? +let r-pre-bounded = [Marked] ; (addr | nonrw-fence | + ([R4rmb] ; fencerel(Rmb) ; [~Noreturn]))? +let w-post-bounded = fence? ; [Marked] +let r-post-bounded = (nonrw-fence | ([~Noreturn] ; fencerel(Rmb) ; [R4rmb]))? ; + [Marked] + +(* Visibility and executes-before for plain accesses *) +let ww-vis = fence | (strong-fence ; xbstar ; w-pre-bounded) | + (w-post-bounded ; vis ; w-pre-bounded) +let wr-vis = fence | (strong-fence ; xbstar ; r-pre-bounded) | + (w-post-bounded ; vis ; r-pre-bounded) +let rw-xbstar = fence | (r-post-bounded ; xbstar ; w-pre-bounded) + +(* Potential races *) +let pre-race = ext & ((Plain * M) | ((M \ IW) * Plain)) + +(* Coherence requirements for plain accesses *) +let wr-incoh = pre-race & rf & rw-xbstar^-1 +let rw-incoh = pre-race & fr & wr-vis^-1 +let ww-incoh = pre-race & co & ww-vis^-1 +empty (wr-incoh | rw-incoh | ww-incoh) as plain-coherence + +(* Actual races *) +let ww-nonrace = ww-vis & ((Marked * W) | rw-xbstar) & ((W * Marked) | wr-vis) +let ww-race = (pre-race & co) \ ww-nonrace +let wr-race = (pre-race & (co? ; rf)) \ wr-vis +let rw-race = (pre-race & fr) \ rw-xbstar + +flag ~empty (ww-race | wr-race | rw-race) as data-race \ No newline at end of file diff --git a/LeanCats/examples/tests/2W.litmus b/LeanCats/Cats/examples/tests/2W.litmus similarity index 100% rename from LeanCats/examples/tests/2W.litmus rename to LeanCats/Cats/examples/tests/2W.litmus diff --git a/LeanCats/examples/tests/3LB.litmus b/LeanCats/Cats/examples/tests/3LB.litmus similarity index 100% rename from LeanCats/examples/tests/3LB.litmus rename to LeanCats/Cats/examples/tests/3LB.litmus diff --git a/LeanCats/examples/tests/3MP.litmus b/LeanCats/Cats/examples/tests/3MP.litmus similarity index 100% rename from LeanCats/examples/tests/3MP.litmus rename to LeanCats/Cats/examples/tests/3MP.litmus diff --git a/LeanCats/examples/tests/3SB.litmus b/LeanCats/Cats/examples/tests/3SB.litmus similarity index 100% rename from LeanCats/examples/tests/3SB.litmus rename to LeanCats/Cats/examples/tests/3SB.litmus diff --git a/LeanCats/examples/tests/3SB_MFence.litmus b/LeanCats/Cats/examples/tests/3SB_MFence.litmus similarity index 100% rename from LeanCats/examples/tests/3SB_MFence.litmus rename to LeanCats/Cats/examples/tests/3SB_MFence.litmus diff --git a/LeanCats/examples/tests/4LB.litmus b/LeanCats/Cats/examples/tests/4LB.litmus similarity index 100% rename from LeanCats/examples/tests/4LB.litmus rename to LeanCats/Cats/examples/tests/4LB.litmus diff --git a/LeanCats/examples/tests/4SB.litmus b/LeanCats/Cats/examples/tests/4SB.litmus similarity index 100% rename from LeanCats/examples/tests/4SB.litmus rename to LeanCats/Cats/examples/tests/4SB.litmus diff --git a/LeanCats/examples/tests/CoRR.litmus b/LeanCats/Cats/examples/tests/CoRR.litmus similarity index 100% rename from LeanCats/examples/tests/CoRR.litmus rename to LeanCats/Cats/examples/tests/CoRR.litmus diff --git a/LeanCats/examples/tests/CoRW.litmus b/LeanCats/Cats/examples/tests/CoRW.litmus similarity index 100% rename from LeanCats/examples/tests/CoRW.litmus rename to LeanCats/Cats/examples/tests/CoRW.litmus diff --git a/LeanCats/examples/tests/CoWR.litmus b/LeanCats/Cats/examples/tests/CoWR.litmus similarity index 100% rename from LeanCats/examples/tests/CoWR.litmus rename to LeanCats/Cats/examples/tests/CoWR.litmus diff --git a/LeanCats/examples/tests/CoWW.litmus b/LeanCats/Cats/examples/tests/CoWW.litmus similarity index 100% rename from LeanCats/examples/tests/CoWW.litmus rename to LeanCats/Cats/examples/tests/CoWW.litmus diff --git a/LeanCats/examples/tests/IRIW.litmus b/LeanCats/Cats/examples/tests/IRIW.litmus similarity index 100% rename from LeanCats/examples/tests/IRIW.litmus rename to LeanCats/Cats/examples/tests/IRIW.litmus diff --git a/LeanCats/examples/tests/IRIW_MFence.litmus b/LeanCats/Cats/examples/tests/IRIW_MFence.litmus similarity index 100% rename from LeanCats/examples/tests/IRIW_MFence.litmus rename to LeanCats/Cats/examples/tests/IRIW_MFence.litmus diff --git a/LeanCats/examples/tests/ISA2.litmus b/LeanCats/Cats/examples/tests/ISA2.litmus similarity index 100% rename from LeanCats/examples/tests/ISA2.litmus rename to LeanCats/Cats/examples/tests/ISA2.litmus diff --git a/LeanCats/examples/tests/ISA2_MFence.litmus b/LeanCats/Cats/examples/tests/ISA2_MFence.litmus similarity index 100% rename from LeanCats/examples/tests/ISA2_MFence.litmus rename to LeanCats/Cats/examples/tests/ISA2_MFence.litmus diff --git a/LeanCats/examples/tests/LB.litmus b/LeanCats/Cats/examples/tests/LB.litmus similarity index 100% rename from LeanCats/examples/tests/LB.litmus rename to LeanCats/Cats/examples/tests/LB.litmus diff --git a/LeanCats/examples/tests/LB_MFence.litmus b/LeanCats/Cats/examples/tests/LB_MFence.litmus similarity index 100% rename from LeanCats/examples/tests/LB_MFence.litmus rename to LeanCats/Cats/examples/tests/LB_MFence.litmus diff --git a/LeanCats/examples/tests/MP.litmus b/LeanCats/Cats/examples/tests/MP.litmus similarity index 100% rename from LeanCats/examples/tests/MP.litmus rename to LeanCats/Cats/examples/tests/MP.litmus diff --git a/LeanCats/examples/tests/MP2.litmus b/LeanCats/Cats/examples/tests/MP2.litmus similarity index 100% rename from LeanCats/examples/tests/MP2.litmus rename to LeanCats/Cats/examples/tests/MP2.litmus diff --git a/LeanCats/examples/tests/MP_MFence.litmus b/LeanCats/Cats/examples/tests/MP_MFence.litmus similarity index 100% rename from LeanCats/examples/tests/MP_MFence.litmus rename to LeanCats/Cats/examples/tests/MP_MFence.litmus diff --git a/LeanCats/examples/tests/RRW.litmus b/LeanCats/Cats/examples/tests/RRW.litmus similarity index 100% rename from LeanCats/examples/tests/RRW.litmus rename to LeanCats/Cats/examples/tests/RRW.litmus diff --git a/LeanCats/examples/tests/RWC.litmus b/LeanCats/Cats/examples/tests/RWC.litmus similarity index 100% rename from LeanCats/examples/tests/RWC.litmus rename to LeanCats/Cats/examples/tests/RWC.litmus diff --git a/LeanCats/examples/tests/RWC_MFence.litmus b/LeanCats/Cats/examples/tests/RWC_MFence.litmus similarity index 100% rename from LeanCats/examples/tests/RWC_MFence.litmus rename to LeanCats/Cats/examples/tests/RWC_MFence.litmus diff --git a/LeanCats/examples/tests/SB.litmus b/LeanCats/Cats/examples/tests/SB.litmus similarity index 100% rename from LeanCats/examples/tests/SB.litmus rename to LeanCats/Cats/examples/tests/SB.litmus diff --git a/LeanCats/examples/tests/SB_MFence.litmus b/LeanCats/Cats/examples/tests/SB_MFence.litmus similarity index 100% rename from LeanCats/examples/tests/SB_MFence.litmus rename to LeanCats/Cats/examples/tests/SB_MFence.litmus diff --git a/LeanCats/examples/tests/SB_opt.litmus b/LeanCats/Cats/examples/tests/SB_opt.litmus similarity index 100% rename from LeanCats/examples/tests/SB_opt.litmus rename to LeanCats/Cats/examples/tests/SB_opt.litmus diff --git a/LeanCats/examples/tests/WRC.litmus b/LeanCats/Cats/examples/tests/WRC.litmus similarity index 100% rename from LeanCats/examples/tests/WRC.litmus rename to LeanCats/Cats/examples/tests/WRC.litmus diff --git a/LeanCats/examples/tests/WRR.litmus b/LeanCats/Cats/examples/tests/WRR.litmus similarity index 100% rename from LeanCats/examples/tests/WRR.litmus rename to LeanCats/Cats/examples/tests/WRR.litmus diff --git a/LeanCats/examples/tests/WRW.litmus b/LeanCats/Cats/examples/tests/WRW.litmus similarity index 100% rename from LeanCats/examples/tests/WRW.litmus rename to LeanCats/Cats/examples/tests/WRW.litmus diff --git a/LeanCats/examples/tests/WWC.litmus b/LeanCats/Cats/examples/tests/WWC.litmus similarity index 100% rename from LeanCats/examples/tests/WWC.litmus rename to LeanCats/Cats/examples/tests/WWC.litmus diff --git a/LeanCats/Reader.lean b/LeanCats/ModelReader.lean similarity index 97% rename from LeanCats/Reader.lean rename to LeanCats/ModelReader.lean index a447a18..7fb8b89 100644 --- a/LeanCats/Reader.lean +++ b/LeanCats/ModelReader.lean @@ -35,7 +35,7 @@ elab "defcat" "<" filename:str ">" : command => do section Test -defcat <"linux.bell.test"> +defcat <"examples/models/lkmm.bell"> #check Accesses.ACQUIRE #check Accesses.MB diff --git a/LeanCats/Syntax.lean b/LeanCats/Syntax.lean index 9e70739..fe479a9 100644 --- a/LeanCats/Syntax.lean +++ b/LeanCats/Syntax.lean @@ -119,7 +119,7 @@ syntax "flag" assertion expr "as" expr : inst syntax "let" cat_ident "=" expr : inst syntax "enum" cat_ident "=" sepBy(cat_ident, "||") : inst -- event class can be R W F B RMW or a custom name like SRCU -syntax "instructions" annotable_events "[" expr "]" : inst +syntax "instructions" "{" annotable_events,+ "}" "[" expr "]" : inst syntax "(*" ident* "*)" : inst syntax "include" str : inst diff --git a/lakefile.lean b/lakefile.lean index 2031075..9775248 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -14,6 +14,9 @@ lean_exe "lean-cats" where lean_exe "test-to-string" where root := `LeanCats.TestToString +lean_exe "litmus-parser-test" where + root := `LeanCats.LitmusParserTest + require "leanprover-community" / "mathlib" -- You should replace v0.0.3 with the latest version published under Releases From cbf947eee18d60600b5b4dacb6e8aba9acb57d1e Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 14 Mar 2026 03:30:38 +0100 Subject: [PATCH 07/34] fix events definitions in syntax. --- LeanCats/Syntax.lean | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LeanCats/Syntax.lean b/LeanCats/Syntax.lean index fe479a9..19a559c 100644 --- a/LeanCats/Syntax.lean +++ b/LeanCats/Syntax.lean @@ -70,10 +70,10 @@ syntax "B" : annotable_events -- branch events syntax "F" : annotable_events -- fence events syntax "RMW" : annotable_events -- read-modify-write events syntax "SRCU" : annotable_events -- srcu events +syntax "IW" : annotable_events -- initial writes +syntax "M" : annotable_events -- memory events, M = W ∪ R syntax "___" : predefined_events -- all events -syntax "IW" : predefined_events -- initial writes -syntax "M" : predefined_events -- memory events, M = W ∪ R syntax annotable_events : predefined_events /- defined_relations: -/ From b9fb4c230711d769d0d580edf52e61a31de36c79 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 14 Mar 2026 03:32:32 +0100 Subject: [PATCH 08/34] Add support for M --- LeanCats/Macro.lean | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index 436db0b..ad34dcf 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -214,6 +214,9 @@ macro_rules | `([annotable-events| SRCU, $evts, $X]) => let nm := mkIdent "SRCU".toName `(($X.$evts.$nm : Set Event)) + | `([annotable-events| M, $evts, $X]) => + let nm := mkIdent "M".toName + `(($X.$evts.$nm : Set Event)) namespace TestAnnotableEvents variable (evts : Events) [IsStrictTotalOrder Event (CatRel.preCo evts)] (x : CandidateExecution evts) @@ -393,7 +396,7 @@ enum Accesses = ONCE || ACQUIRE || NORETURN || MB -instructions {R}[Accesses] +instructions {R, M}[Accesses] enum Barriers = wmb || rmb || From 9e976efdb5afe7bd2151afb7b11408b4563321cb Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 14 Mar 2026 13:17:35 +0100 Subject: [PATCH 09/34] Fix assertion unnaming bugs. --- LeanCats/Macro.lean | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index ad34dcf..90d56ee 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -249,13 +249,13 @@ macro_rules | `([inst| include $_filename:str , $_ , $_]) => return mkNullNode | `([inst| let $nm:cat_ident = $e, $evts, $X]) => - `(abbrev $nm := [expr|$e, $evts, $X]) + `(@[simp] def $nm := [expr|$e, $evts, $X]) - | `([inst| $a:assertion $e as $_:cat_ident, $evts, $X]) => do - `([assertion| $a] ([expr| $e, $evts, $X])) + | `([inst| $a:assertion $e as $nm:cat_ident, $evts, $X]) => do + `(def $nm := ([assertion| $a] ([expr| $e, $evts, $X]))) | `([inst| ~$a:assertion $e as $nm:cat_ident, $evts, $X]) => do - `([assertion| $a] (¬[expr| $e, $evts, $X])) + `(def $nm := [assertion| $a] (¬[expr| $e, $evts, $X])) | `([inst| enum $nm:cat_ident = $[ $tags:cat_ident ]||*, $_, $_]) => do let nmIdent : TSyntax `ident := nm @@ -420,6 +420,16 @@ let Noreturn = NORETURN \ W let Marked = (~M) | IW | ONCE | RELEASE | ACQUIRE | MB | RMW let Plain = M \ Marked + +-- Acquire-Release +let acq_po = [Acquire] ; po ; [M] +let po_rel = [M] ; po ; [Release] + +-- SCPV +let com = rf | co | fr + +acyclic po | com as t + ] -#reduce lkmm.Marked +#reduce lkmm.Plain From 17186038652c6a8d45940a63c0948f4e75662c03 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 14 Mar 2026 16:00:17 +0100 Subject: [PATCH 10/34] Add external relations support. --- LeanCats/Macro.lean | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index 90d56ee..8803719 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -55,7 +55,7 @@ macro_rules `(Set.union ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) | `([expr| $e₁:expr & $e₂:expr, $evts, $X]) => - `(CatRel.inter ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) + `(Set.inter ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) | `([expr| $e₁:expr ; $e₂:expr, $evts, $X]) => `(SetRel.comp ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) @@ -379,9 +379,15 @@ set_option pp.rawOnError true #check t.Barriers.wmb' #reduce t.Barriers.wmb' -abbrev domain (r : SetRel Event Event) := SetRel.dom r +@[simp] def domain (r : SetRel Event Event) := SetRel.dom r -abbrev range (r : SetRel Event Event) := SetRel.cod r +@[simp] def range (r : SetRel Event Event) := SetRel.cod r + +@[simp] def po_loc (evts : Events) (X : CandidateExecution evts) := X.po ∩ CatRel.Rel.internal + +@[simp] def fre (evts : Events) (X : CandidateExecution evts) := X.fr ∩ CatRel.Rel.internal + +@[simp] def coe (evts : Events) (X : CandidateExecution evts) := X.co ∩ CatRel.Rel.internal [model| test let acq = M @@ -427,9 +433,10 @@ let po_rel = [M] ; po ; [Release] -- SCPV let com = rf | co | fr +acyclic po_loc | com as coherence -acyclic po | com as t - +-- Atomic Read-Modify-Write +empty rmw & (fre ; coe) as atomic ] -#reduce lkmm.Plain +#reduce lkmm.coherence From 2e740aee61f995f98deb349f0872bd60feb3bf35 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 14 Mar 2026 17:55:43 +0100 Subject: [PATCH 11/34] Add more relations support. --- LeanCats/Relations.lean | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/LeanCats/Relations.lean b/LeanCats/Relations.lean index ebec5f7..44a0e6e 100644 --- a/LeanCats/Relations.lean +++ b/LeanCats/Relations.lean @@ -35,13 +35,24 @@ theorem RelProdIsSetProd (s₁ s₂ : Event -> Prop) (e₁ e₂ : Event) : aesop } -abbrev Acyclic (r : SetRel Event Event) := ∀a : Event, ¬ Relation.TransGen (λ e₁ e₂ ↦ (e₁, e₂) ∈ r) a a +abbrev SetRel.ReflexiveTrans (r : SetRel Event Event) := + {(e₁, e₂) | Relation.ReflTransGen (λ a b ↦ (a, b) ∈ r) e₁ e₂} -@[simp] def Rel.internal (e₁ e₂ : Event) : Prop := - e₁.t_id = e₂.t_id +abbrev SetRel.TransGen (r : SetRel Event Event) := + {(e₁, e₂) | Relation.TransGen (λ a b ↦ (a, b) ∈ r) e₁ e₂} -@[simp] def Rel.external (e₁ e₂ : Event) : Prop := - ¬ (Rel.internal e₁ e₂) +abbrev SetRel.Acyclic (r : SetRel Event Event) := ∀a : Event, (a, a) ∉ SetRel.TransGen r + +abbrev SetRel.IsEmpty (r : SetRel Event Event) := ∀e₁ e₂ : Event, (e₁, e₂) ∉ r + +@[simp] def Rel.location : SetRel Event Event := + {(e₁, e₂) | e₁.effect.location = e₂.effect.location } + +@[simp] def Rel.internal : SetRel Event Event := + {(e₁, e₂) | e₁.t_id = e₂.t_id} + +@[simp] def Rel.external : SetRel Event Event := + {(e₁, e₂) | (e₁, e₂) ∉ Rel.internal} @[simp] def Rel.empty (_ _ : Event) : Prop := False From cd8446cf0d38f7178dfa92942fd31dec1304a5ea Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 14 Mar 2026 18:09:19 +0100 Subject: [PATCH 12/34] diff the union operations between events and relations. --- LeanCats/Macro.lean | 148 +++++++++++++++++++++++++++++----------- LeanCats/Relations.lean | 3 + 2 files changed, 110 insertions(+), 41 deletions(-) diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index 8803719..3222ef5 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -44,6 +44,12 @@ instance : Coe (TSyntax `ident) (TSyntax `cat_ident) where instance : Coe (TSyntax `ident) (TSyntax `annotable_events) where coe s := mkNode `annotable_events #[s] +instance : Coe (TSyntax `predefined_events) (TSyntax `expr) where + coe s := mkNode `expr #[s] + +instance : Coe (TSyntax `predefined_relations) (TSyntax `expr) where + coe s := mkNode `expr #[s] + #check Set Event -- Set α -> Set (α × α) @@ -51,7 +57,10 @@ def SetRel.mkId (s : Set Event) : SetRel Event Event := fun (e₁, e₂) => e₁ = e₂ ∧ e₁ ∈ s macro_rules - | `([expr| $e₁:expr | $e₂:expr, $evts, $X]) => + | `([expr| $e₁:predefined_relations | $e₂:predefined_relations, $evts, $X]) => + `(CatRel.SetRel.union ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) + + | `([expr| $e₁:predefined_events | $e₂:predefined_events , $evts, $X]) => `(Set.union ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) | `([expr| $e₁:expr & $e₂:expr, $evts, $X]) => @@ -75,6 +84,15 @@ macro_rules | `([expr| $e^-1, $evts, $X]) => `(Rel.inv ([expr| $e, $evts, $X])) + | `([expr| $e ?, $evts, $X]) => + `(([expr| $e, $evts, $X]) ∪ {(e₁, e₂) | e₁ = e₂}) + + | `([expr| $e *, $evts, $X]) => + `(([expr| $e, $evts, $X]) ∪ {(e₁, e₂) | e₁ = e₂}) + + | `([expr| $e +, $evts, $X]) => + `(([expr| $e, $evts, $X])) + | `([expr| $r:reserved, $evts, $X]) => `([reserved| $r, $evts, $X]) @@ -135,9 +153,7 @@ macro_rules macro_rules | `([dsl-term| $i:cat_ident, $evts, $X]) => - `($i $evts $X) - | `([dsl-term| $i:cat_ident, $evts]) => - `($i $evts) + `($i $evts $X) macro_rules | `([reserved| $r:predefined_relations, $evts, $X]) => @@ -157,10 +173,6 @@ macro_rules let nm := mkIdent "rf".toName `($X.$nm) - | `([predefined-relations| rfe, $_, $X]) => - let nm := mkIdent "rf".toName - `($X.$nm) - | `([predefined-relations| rmw, $_, $X]) => let nm := mkIdent "rmw".toName `($X.$nm) @@ -169,6 +181,34 @@ macro_rules let co' := mkIdent "co".toName `($X.$co') + | `([predefined-relations| data, $_, $X]) => + let nm := mkIdent "data".toName + `($X.$nm) + + | `([predefined-relations| addr, $_, $X]) => + let nm := mkIdent "addr".toName + `($X.$nm) + + | `([predefined-relations| ctrl, $_, $X]) => + let nm := mkIdent "ctrl".toName + `($X.$nm) + + | `([predefined-relations| wmb, $_, $X]) => + let nm := mkIdent "wmb".toName + `($X.$nm) + + | `([predefined-relations| fence, $_, $X]) => + let nm := mkIdent "fence".toName + `($X.$nm) + + | `([predefined-relations| rmb , $_, $X]) => + let nm := mkIdent "rmb".toName + `($X.$nm) + + | `([predefined-relations| mb , $_, $X]) => + let nm := mkIdent "mb".toName + `($X.$nm) + macro_rules | `([keyword| and]) => Lean.Macro.throwUnsupported | `([keyword| as]) => Lean.Macro.throwUnsupported @@ -191,9 +231,9 @@ macro_rules | `([keyword| $a:assertion]) => `([assertion| $a]) macro_rules - | `([assertion| irreflexive]) => `(CatRel.Irreflexive) - | `([assertion| acyclic]) => `(CatRel.Acyclic) - | `([assertion| empty]) => `(CatRel.IsEmpty) + | `([assertion| irreflexive]) => `(CatRel.SetRel.Irreflexive) + | `([assertion| acyclic]) => `(CatRel.SetRel.Acyclic) + | `([assertion| empty]) => `(CatRel.SetRel.IsEmpty) macro_rules | `([annotable-events| W, $evts, $X]) => @@ -238,7 +278,7 @@ macro_rules `([annotable-events| $a, $evts, $X]) namespace TestPredefinedEvents -variable (evts : Events) [IsStrictTotalOrder Event (CatRel.preCo evts)] (x : CandidateExecution evts) +variable (evts) [IsStrictTotalOrder Event (CatRel.preCo evts)] (x : CandidateExecution evts) def a := [predefined-events| R, evts, x] #reduce a @@ -251,6 +291,9 @@ macro_rules | `([inst| let $nm:cat_ident = $e, $evts, $X]) => `(@[simp] def $nm := [expr|$e, $evts, $X]) + | `([inst| let $nm:cat_ident ( $arg:cat_ident ) = $e:expr, $evts, $X]) => do + `(@[simp] def $nm ($arg:ident : (evts : Events) -> CandidateExecution evts -> SetRel Event Event) := [expr| $e, $evts, $X]) + | `([inst| $a:assertion $e as $nm:cat_ident, $evts, $X]) => do `(def $nm := ([assertion| $a] ([expr| $e, $evts, $X]))) @@ -261,7 +304,7 @@ macro_rules let nmIdent : TSyntax `ident := nm -- Convert each cat_ident tag to a plain Lean ident (handles multi-hyphen names like rcu-lock → rcu_lock, and adds trailing '). let tagIdents : Array (TSyntax `ident) := tags.map (fun t => - mkIdent (Name.mkSimple ((catIdentToName t.raw).toString ++ "'"))) + mkIdent (Name.mkSimple ((catIdentToName t.raw).toString))) let indef <- `( inductive $nmIdent where $[| $tagIdents:ident ]* ) @@ -315,7 +358,7 @@ def elabCatInst : CommandElab := fun stx => do let commands <- info.ctors.mapM ( fun ctor => do -- Make the constructors name correct by removing the end tick. - let ctorName : Name := ctor.lastComponentAsString.dropEnd 1 |>.toName + let ctorName : Name := ctor.lastComponentAsString.dropEnd 1 |>.toName |>.capitalize -- TODO(Nekolas): Make this part `∩ [annotable-events| $a]` work. if (<-getEnv).contains ctorName then return (TSyntax.mk $ mkNullNode #[]) @@ -350,16 +393,14 @@ macro_rules let ret := #[nstart] ++ #[vars] ++ insts ++ #[nend] return mkNullNode ret -set_option pp.rawOnError true - -- Linux-kernel memory consistency model ("linux.bell" excerpt) -- Comments (*...*) and tick-prefixes (') are stripped by the preprocessor -- before these lines reach the Lean syntax; we write the cleaned form here. [model| t enum Barriers = - wmb || rmb || barrier || rcu_read_lock || rcu_read_unlock || + wmb' || rmb' || barrier' || rcu_read_lock || rcu_read_unlock || rcu_lock || rcu_unlock || sync_rcu || - before_atomic || after_atomic || + before_atomic || after_atomic' || after_spinlock || after_unlock_lock || after_srcu_read_unlock @@ -369,7 +410,7 @@ set_option pp.rawOnError true #check t.after_atomic' #check t.Barriers.after_atomic' -#reduce t.after_atomic +#reduce t.After_atomic -- The tag name will be capilized automatically. -- https://github.com/herd/herdtools7/blob/2ad8eadf3246b66c4e03248d80bde8a11b7d00fb/lib/BellName.ml#L31 @@ -383,39 +424,42 @@ set_option pp.rawOnError true @[simp] def range (r : SetRel Event Event) := SetRel.cod r -@[simp] def po_loc (evts : Events) (X : CandidateExecution evts) := X.po ∩ CatRel.Rel.internal +@[simp] def po_loc (evts : Events) (X : CandidateExecution evts) := X.po ∩ CatRel.Rel.location + +@[simp] def fre (evts : Events) (X : CandidateExecution evts) := X.fr ∩ CatRel.Rel.external + +@[simp] def rfe (evts : Events) (X : CandidateExecution evts) := X.fr ∩ CatRel.Rel.external -@[simp] def fre (evts : Events) (X : CandidateExecution evts) := X.fr ∩ CatRel.Rel.internal +@[simp] def rfi (evts : Events) (X : CandidateExecution evts) := X.rf ∩ CatRel.Rel.internal -@[simp] def coe (evts : Events) (X : CandidateExecution evts) := X.co ∩ CatRel.Rel.internal +@[simp] def coe (evts : Events) (X : CandidateExecution evts) := X.co ∩ CatRel.Rel.external + +@[simp] def int (_: Events) (_ : CandidateExecution evts) := CatRel.Rel.internal [model| test let acq = M ] +[model| testlk +let A-cumul(r) = rf ; r +] + #reduce test.acq [model| lkmm -enum Accesses = ONCE || - RELEASE || - ACQUIRE || - NORETURN || - MB -instructions {R, M}[Accesses] - -enum Barriers = wmb || - rmb || - barrier || - rcu-lock || - rcu-unlock || - sync-rcu || - before-atomic || - after-atomic || - after-spinlock || - after-unlock-lock || - after-srcu-read-unlock -instructions {F, B}[Barriers] +enum Accesses = ONCE' || + RELEASE' || + ACQUIRE' || + NORETURN' || + MB' +instructions {R, W, RMW}[Accesses] + +enum Barriers = wmb' || + rmb' || + barrier' + +instructions {F}[Barriers] let FailedRMW = RMW \ (domain(rmw) | range(rmw)) let Acquire = ACQUIRE \ W \ FailedRMW @@ -427,6 +471,8 @@ let Marked = (~M) | IW | ONCE | RELEASE | ACQUIRE | MB | RMW let Plain = M \ Marked +let strong_fence = mb + -- Acquire-Release let acq_po = [Acquire] ; po ; [M] let po_rel = [M] ; po ; [Release] @@ -437,6 +483,26 @@ acyclic po_loc | com as coherence -- Atomic Read-Modify-Write empty rmw & (fre ; coe) as atomic + +-- Preserved Program Order +let dep = addr | data +let rwdep = (dep | ctrl) ; [W] +let overwrite = co | fr +let to_w = rwdep | (overwrite & int) | (addr ; [Plain] ; wmb) +let to_r = addr | (dep ; [Marked] ; rfi) +let ppo = to_r | to_w | fence + +let A_cumul(r) = (rfe ; [Marked])? ; r + +let cumul_fence = [Marked] ; (A_cumul(strong_fence | po_rel) | wmb) ; [Marked] +let prop = [Marked] ; (overwrite & ext)? ; cumul_fence* ; [Marked] ; (rfe)? ; [Marked] + +let hb = [Marked] ; (ppo | rfe | ((prop \ id) & int)) ; [Marked] +acyclic hb as happens-before + +let pb = prop ; strong_fence ; hb* ; [Marked] +acyclic pb as propagation ] #reduce lkmm.coherence +#reduce lkmm.atomic diff --git a/LeanCats/Relations.lean b/LeanCats/Relations.lean index 44a0e6e..9ec6eac 100644 --- a/LeanCats/Relations.lean +++ b/LeanCats/Relations.lean @@ -35,6 +35,9 @@ theorem RelProdIsSetProd (s₁ s₂ : Event -> Prop) (e₁ e₂ : Event) : aesop } +@[simp] def SetRel.union (r₁ r₂ : SetRel Event Event) := + {(e₁, e₂) | (e₁, e₂) ∈ r₁ ∨ (e₁, e₂) ∈ r₂} + abbrev SetRel.ReflexiveTrans (r : SetRel Event Event) := {(e₁, e₂) | Relation.ReflTransGen (λ a b ↦ (a, b) ∈ r) e₁ e₂} From be9d44d6e792c4041b518385717633b6c3e34040 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 14 Mar 2026 20:49:00 +0100 Subject: [PATCH 13/34] Fix A-cumul bugs by adding arg variable in macro passing --- LeanCats/Macro.lean | 135 +++++++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 63 deletions(-) diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index 3222ef5..2866ea3 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -10,16 +10,15 @@ open Lean Elab Command Term Meta open Data syntax "[model|" ident inst* "]" : command -syntax (name := catexpr) "[expr|" expr "," cat_ident "," cat_ident "]" : term +syntax (name := catexpr) "[expr|" expr "," cat_ident "," cat_ident "," cat_ident "]" : term syntax "[keyword|" keyword "]" : term syntax "[assertion|" assertion "]" : term -syntax (name := catinst) "[inst|" inst "," cat_ident "," cat_ident "]" : command +syntax (name := catinst) "[inst|" inst "," cat_ident "," cat_ident "," cat_ident "]" : command syntax "[annotable-events|" annotable_events "," cat_ident "," cat_ident "]" : term -- Set syntax "[predefined-events|" predefined_events "," cat_ident "," cat_ident "]" : term syntax "[reserved|" reserved "," cat_ident "," cat_ident "]" : term syntax "[predefined-relations|" predefined_relations "," cat_ident "," cat_ident "]" : term -syntax "[dsl-term|" dsl_term "," cat_ident "," cat_ident "]" : term -syntax "[dsl-term|" dsl_term "," cat_ident "]" : term +syntax "[dsl-term|" dsl_term "," cat_ident "," cat_ident "," cat_ident "]" : term -- Walk any cat_ident syntax tree, collect all ident leaves, and join with "_". -- This handles plain idents, tick-prefixed ('ONCE), and multi-hyphen (rcu-lock, after-unlock-lock). @@ -57,54 +56,59 @@ def SetRel.mkId (s : Set Event) : SetRel Event Event := fun (e₁, e₂) => e₁ = e₂ ∧ e₁ ∈ s macro_rules - | `([expr| $e₁:predefined_relations | $e₂:predefined_relations, $evts, $X]) => - `(CatRel.SetRel.union ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) + | `([dsl-term| $i:cat_ident, $evts, $X, $arg]) => + -- Apply the arg instead of using the id in the env. + if arg.getId = i.getId then + `($i) + else + `($i $evts $X) - | `([expr| $e₁:predefined_events | $e₂:predefined_events , $evts, $X]) => - `(Set.union ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) +macro_rules + | `([expr| $e₁:expr | $e₂:expr, $evts, $X, $arg]) => + `(CatRel.CatUnion.union ([expr| $e₁, $evts, $X, $arg]) ([expr| $e₂, $evts, $X, $arg])) - | `([expr| $e₁:expr & $e₂:expr, $evts, $X]) => - `(Set.inter ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) + | `([expr| $e₁:expr & $e₂:expr, $evts, $X, $arg]) => + `(Set.inter ([expr| $e₁, $evts, $X, $arg]) ([expr| $e₂, $evts, $X, $arg])) - | `([expr| $e₁:expr ; $e₂:expr, $evts, $X]) => - `(SetRel.comp ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) + | `([expr| $e₁:expr ; $e₂:expr, $evts, $X, $arg]) => + `(SetRel.comp ([expr| $e₁, $evts, $X, $arg]) ([expr| $e₂, $evts, $X, $arg])) - | `([expr| [ $i:expr ], $evts, $X ]) => - `(SetRel.mkId ([expr| $i, $evts, $X])) + | `([expr| [ $i:expr ], $evts, $X, $arg]) => + `(SetRel.mkId ([expr| $i, $evts, $X, $arg])) - | `([expr| $e₁:expr * $e₂:expr, $evts, $X]) => - `(CatRel.prod ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) + | `([expr| $e₁:expr * $e₂:expr, $evts, $X, $arg]) => + `(CatRel.prod ([expr| $e₁, $evts, $X, $arg]) ([expr| $e₂, $evts, $X, $arg])) - | `([expr| ~ $e:expr, $evts, $X]) => - `(Set.compl ([expr| $e, $evts, $X])) + | `([expr| ~ $e:expr, $evts, $X, $arg]) => + `(Set.compl ([expr| $e, $evts, $X, $arg])) - | `([expr| $e₁:expr \ $e₂:expr, $evts, $X]) => - `(Set.diff ([expr| $e₁, $evts, $X]) ([expr| $e₂, $evts, $X])) + | `([expr| $e₁:expr \ $e₂:expr, $evts, $X, $arg]) => + `(Set.diff ([expr| $e₁, $evts, $X, $arg]) ([expr| $e₂, $evts, $X, $arg])) - | `([expr| $e^-1, $evts, $X]) => - `(Rel.inv ([expr| $e, $evts, $X])) + | `([expr| $e^-1, $evts, $X, $arg]) => + `(Rel.inv ([expr| $e, $evts, $X, $arg])) - | `([expr| $e ?, $evts, $X]) => - `(([expr| $e, $evts, $X]) ∪ {(e₁, e₂) | e₁ = e₂}) + | `([expr| $e ?, $evts, $X, $arg]) => + `(([expr| $e, $evts, $X, $arg]) ∪ {(e₁, e₂) | e₁ = e₂}) - | `([expr| $e *, $evts, $X]) => - `(([expr| $e, $evts, $X]) ∪ {(e₁, e₂) | e₁ = e₂}) + | `([expr| $e *, $evts, $X, $arg]) => + `(([expr| $e, $evts, $X, $arg]) ∪ {(e₁, e₂) | e₁ = e₂}) - | `([expr| $e +, $evts, $X]) => - `(([expr| $e, $evts, $X])) + | `([expr| $e +, $evts, $X, $arg]) => + `(([expr| $e, $evts, $X, $arg])) - | `([expr| $r:reserved, $evts, $X]) => + | `([expr| $r:reserved, $evts, $X, $_]) => `([reserved| $r, $evts, $X]) - | `([expr| ($e:expr), $evts, $X]) => - `([expr| $e, $evts, $X]) + | `([expr| ($e:expr), $evts, $X, $arg]) => + `([expr| $e, $evts, $X, $arg]) - | `([expr| $t:dsl_term, $evts, $X]) => - `(([dsl-term| $t, $evts, $X])) + | `([expr| $t:dsl_term, $evts, $X, $arg]) => + `(([dsl-term| $t, $evts, $X, $arg])) - | `([expr| $i:cat_ident ($e:expr), $evts, $X]) => do + | `([expr| $i:dsl_term ($e:expr), $evts, $X, $arg]) => do -- function call. - `(($i) ([expr| $e, $evts, $X])) + `(([dsl-term| $i, $evts, $X, $arg]) ([expr| $e, $evts, $X, $arg])) -- @[term_elab catexpr] -- def elabCatExpr : TermElab := fun stx type? => do @@ -151,10 +155,6 @@ macro_rules -- | _ => Lean.Elab.throwUnsupportedSyntax -- -- elabTerm expandedStx expectedType? -macro_rules - | `([dsl-term| $i:cat_ident, $evts, $X]) => - `($i $evts $X) - macro_rules | `([reserved| $r:predefined_relations, $evts, $X]) => `([predefined-relations| $r, $evts, $X]) @@ -286,21 +286,22 @@ end TestPredefinedEvents macro_rules -- We just ignore the include inst. - | `([inst| include $_filename:str , $_ , $_]) => return mkNullNode + | `([inst| include $_filename:str , $_ , $_, $_]) => return mkNullNode - | `([inst| let $nm:cat_ident = $e, $evts, $X]) => - `(@[simp] def $nm := [expr|$e, $evts, $X]) + | `([inst| let $nm:cat_ident = $e, $evts, $X, $arg]) => + `(@[simp] def $nm := [expr|$e, $evts, $X, $arg]) - | `([inst| let $nm:cat_ident ( $arg:cat_ident ) = $e:expr, $evts, $X]) => do - `(@[simp] def $nm ($arg:ident : (evts : Events) -> CandidateExecution evts -> SetRel Event Event) := [expr| $e, $evts, $X]) + | `([inst| let $nm:cat_ident ( $arg:cat_ident ) = $e:expr, $evts, $X, $_]) => do + -- This is where we use the real arg. + `(@[simp] def $nm ($arg:ident : SetRel Event Event) := [expr| $e, $evts, $X, $arg]) - | `([inst| $a:assertion $e as $nm:cat_ident, $evts, $X]) => do - `(def $nm := ([assertion| $a] ([expr| $e, $evts, $X]))) + | `([inst| $a:assertion $e as $nm:cat_ident, $evts, $X, $arg]) => do + `(def $nm := ([assertion| $a] ([expr| $e, $evts, $X, $arg]))) - | `([inst| ~$a:assertion $e as $nm:cat_ident, $evts, $X]) => do - `(def $nm := [assertion| $a] (¬[expr| $e, $evts, $X])) + | `([inst| ~$a:assertion $e as $nm:cat_ident, $evts, $X, $arg]) => do + `(def $nm := [assertion| $a] (¬[expr| $e, $evts, $X, $arg])) - | `([inst| enum $nm:cat_ident = $[ $tags:cat_ident ]||*, $_, $_]) => do + | `([inst| enum $nm:cat_ident = $[ $tags:cat_ident ]||*, $_, $_, $_]) => do let nmIdent : TSyntax `ident := nm -- Convert each cat_ident tag to a plain Lean ident (handles multi-hyphen names like rcu-lock → rcu_lock, and adds trailing '). let tagIdents : Array (TSyntax `ident) := tags.map (fun t => @@ -315,7 +316,7 @@ macro_rules let ret := #[indef] ++ aliases return mkNullNode ret - | `([inst| flag $_:assertion $_:expr as $_:expr, $_, $_]) => do + | `([inst| flag $_:assertion $_:expr as $_:expr, $_, $_, $_]) => do -- We ignore the flag for now, since it doesn't change the states of the execution, it's just used to witness the assertion. return mkNullNode #[] @@ -346,7 +347,7 @@ we generate: @[command_elab catinst] def elabCatInst : CommandElab := fun stx => do match stx with - | `([inst| instructions { $a:annotable_events,* }[ $c:cat_ident ] , $evts:cat_ident , $X:cat_ident]) => do + | `([inst| instructions { $a:annotable_events,* }[ $c:cat_ident ] , $evts:cat_ident , $X:cat_ident, $_:cat_ident]) => do dbg_trace "entering elabCatInst" let currNamespace <- getCurrNamespace -- This is used to get the full name with namespace. @@ -383,11 +384,12 @@ macro_rules -- Create the model. | `([model| $n:ident $xs:inst*]) => do let nstart <- `(namespace $n) + let placeHolder := mkIdent `__ let evts := mkIdent `evts let X := mkIdent `x let vars <- `(variable ($evts : Events) [IsStrictTotalOrder Event (CatRel.preCo $evts)] ($X : CandidateExecution $evts)) let nend <- `(end $n) - let insts <- xs.mapM (fun ins => `([inst| $ins, $evts, $X])) + let insts <- xs.mapM (fun ins => `([inst| $ins, $evts, $X, $placeHolder])) -- let insts : Array (TSyntax `command) := #[] let ret := #[nstart] ++ #[vars] ++ insts ++ #[nend] @@ -420,9 +422,9 @@ macro_rules #check t.Barriers.wmb' #reduce t.Barriers.wmb' -@[simp] def domain (r : SetRel Event Event) := SetRel.dom r +@[simp] def domain (evts : Events) (_ : CandidateExecution evts) (r : SetRel Event Event) := SetRel.dom r -@[simp] def range (r : SetRel Event Event) := SetRel.cod r +@[simp] def range (evts : Events) (_ : CandidateExecution evts) (r : SetRel Event Event) := SetRel.cod r @[simp] def po_loc (evts : Events) (X : CandidateExecution evts) := X.po ∩ CatRel.Rel.location @@ -434,7 +436,7 @@ macro_rules @[simp] def coe (evts : Events) (X : CandidateExecution evts) := X.co ∩ CatRel.Rel.external -@[simp] def int (_: Events) (_ : CandidateExecution evts) := CatRel.Rel.internal +@[simp] def int (evts : Events) (_ : CandidateExecution evts) := CatRel.Rel.internal [model| test let acq = M @@ -442,9 +444,14 @@ macro_rules [model| testlk let A-cumul(r) = rf ; r + +let acq_po = [M] ; po ; [M] +let FailedRMW = rf | acq_po +let c = A-cumul(rf) ] -#reduce test.acq +#reduce testlk.FailedRMW +#reduce testlk.c [model| lkmm @@ -494,14 +501,16 @@ let ppo = to_r | to_w | fence let A_cumul(r) = (rfe ; [Marked])? ; r -let cumul_fence = [Marked] ; (A_cumul(strong_fence | po_rel) | wmb) ; [Marked] -let prop = [Marked] ; (overwrite & ext)? ; cumul_fence* ; [Marked] ; (rfe)? ; [Marked] +let a = A_cumul(po_rel) -let hb = [Marked] ; (ppo | rfe | ((prop \ id) & int)) ; [Marked] -acyclic hb as happens-before - -let pb = prop ; strong_fence ; hb* ; [Marked] -acyclic pb as propagation +-- let cumul_fence = [Marked] ; (A_cumul(strong_fence | po_rel) | wmb) ; [Marked] +-- let prop = [Marked] ; (overwrite & ext)? ; cumul_fence* ; [Marked] ; (rfe)? ; [Marked] +-- +-- let hb = [Marked] ; (ppo | rfe | ((prop \ id) & int)) ; [Marked] +-- acyclic hb as happens-before +-- +-- let pb = prop ; strong_fence ; hb* ; [Marked] +-- acyclic pb as propagation ] #reduce lkmm.coherence From b48a268e4f064b8ff16a4d6b10fb61c9310397b4 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 14 Mar 2026 20:57:45 +0100 Subject: [PATCH 14/34] Add basic LKMM model support. --- LeanCats/Macro.lean | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index 2866ea3..d8a8d39 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -181,6 +181,9 @@ macro_rules let co' := mkIdent "co".toName `($X.$co') + | `([predefined-relations| id, $_, $_]) => + `(SetRel.id) + | `([predefined-relations| data, $_, $X]) => let nm := mkIdent "data".toName `($X.$nm) @@ -438,6 +441,8 @@ macro_rules @[simp] def int (evts : Events) (_ : CandidateExecution evts) := CatRel.Rel.internal +@[simp] def ext (evts : Events) (_ : CandidateExecution evts) := CatRel.Rel.internal + [model| test let acq = M ] @@ -503,15 +508,18 @@ let A_cumul(r) = (rfe ; [Marked])? ; r let a = A_cumul(po_rel) --- let cumul_fence = [Marked] ; (A_cumul(strong_fence | po_rel) | wmb) ; [Marked] --- let prop = [Marked] ; (overwrite & ext)? ; cumul_fence* ; [Marked] ; (rfe)? ; [Marked] +let cumul_fence = [Marked] ; (A_cumul(strong_fence | po_rel) | wmb) ; [Marked] +let prop = [Marked] ; (overwrite & ext)? ; cumul_fence* ; [Marked] ; (rfe)? ; [Marked] -- --- let hb = [Marked] ; (ppo | rfe | ((prop \ id) & int)) ; [Marked] --- acyclic hb as happens-before +let hb = [Marked] ; (ppo | rfe | ((prop \ id) & int)) ; [Marked] + +acyclic hb as happens_before -- --- let pb = prop ; strong_fence ; hb* ; [Marked] --- acyclic pb as propagation +let pb = prop ; strong_fence ; hb* ; [Marked] +acyclic pb as propagation ] #reduce lkmm.coherence #reduce lkmm.atomic +#reduce lkmm.happens_before +#reduce lkmm.propagation From 0505815c8b95eee02bdfae8fb15e4f980544cffa Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 14 Mar 2026 21:00:37 +0100 Subject: [PATCH 15/34] Clean up debugging code. --- LeanCats/Macro.lean | 78 +++------------------------------------------ 1 file changed, 4 insertions(+), 74 deletions(-) diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index d8a8d39..5dd520a 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -49,8 +49,6 @@ instance : Coe (TSyntax `predefined_events) (TSyntax `expr) where instance : Coe (TSyntax `predefined_relations) (TSyntax `expr) where coe s := mkNode `expr #[s] -#check Set Event - -- Set α -> Set (α × α) def SetRel.mkId (s : Set Event) : SetRel Event Event := fun (e₁, e₂) => e₁ = e₂ ∧ e₁ ∈ s @@ -261,12 +259,6 @@ macro_rules let nm := mkIdent "M".toName `(($X.$evts.$nm : Set Event)) -namespace TestAnnotableEvents -variable (evts : Events) [IsStrictTotalOrder Event (CatRel.preCo evts)] (x : CandidateExecution evts) -def a := [annotable-events| R, evts, x] -#reduce a -end TestAnnotableEvents - macro_rules -- | `([predefined-events| ___]) => __ TODO!(figure all the definiations of all the events. (⋃?)) | `([predefined-events| IW, $evts, $_]) => @@ -280,13 +272,6 @@ macro_rules | `([predefined-events| $a:annotable_events, $evts, $X]) => `([annotable-events| $a, $evts, $X]) -namespace TestPredefinedEvents -variable (evts) [IsStrictTotalOrder Event (CatRel.preCo evts)] (x : CandidateExecution evts) -def a := [predefined-events| R, evts, x] - -#reduce a -end TestPredefinedEvents - macro_rules -- We just ignore the include inst. | `([inst| include $_filename:str , $_ , $_, $_]) => return mkNullNode @@ -323,19 +308,6 @@ macro_rules -- We ignore the flag for now, since it doesn't change the states of the execution, it's just used to witness the assertion. return mkNullNode #[] --- namespace LKMM --- [inst| let rcu-fn =$[ --- unmatched-locks = Rcu-lock \ domain(matched) --- and unmatched-unlocks = Rcu-unlock \ range(matched) --- and unmatched = unmatched-locks | unmatched-unlocks --- and unmatched-po = [unmatched]; po; [unmatched] --- and unmatched-locks-to-unlocks = --- [unmatched-locks]; po; [unmatched-unlocks] --- and matched = matched | (unmatched-locks-to-unlocks \ --- (unmatched-po; unmatched-po))] --- --- end LKMM - /-- Processes `instructions A[EnumType]` by generating a definition for each constructor of `EnumType`. Specifically, for each constructor `C` of `EnumType`, we generate: @@ -351,13 +323,11 @@ we generate: def elabCatInst : CommandElab := fun stx => do match stx with | `([inst| instructions { $a:annotable_events,* }[ $c:cat_ident ] , $evts:cat_ident , $X:cat_ident, $_:cat_ident]) => do - dbg_trace "entering elabCatInst" let currNamespace <- getCurrNamespace -- This is used to get the full name with namespace. let typeName := Name.updatePrefix c.getId currNamespace let info <- getConstInfoInduct typeName - -- dbg_trace typeName let commands <- info.ctors.mapM ( fun ctor => do @@ -398,33 +368,6 @@ macro_rules let ret := #[nstart] ++ #[vars] ++ insts ++ #[nend] return mkNullNode ret --- Linux-kernel memory consistency model ("linux.bell" excerpt) --- Comments (*...*) and tick-prefixes (') are stripped by the preprocessor --- before these lines reach the Lean syntax; we write the cleaned form here. -[model| t - enum Barriers = - wmb' || rmb' || barrier' || rcu_read_lock || rcu_read_unlock || - rcu_lock || rcu_unlock || sync_rcu || - before_atomic || after_atomic' || - after_spinlock || after_unlock_lock || - after_srcu_read_unlock - - instructions {W}[Barriers] -] - -#check t.after_atomic' - -#check t.Barriers.after_atomic' -#reduce t.After_atomic - --- The tag name will be capilized automatically. --- https://github.com/herd/herdtools7/blob/2ad8eadf3246b66c4e03248d80bde8a11b7d00fb/lib/BellName.ml#L31 - --- Spot-check generated names --- This tags used as the event tags, we don't refer them directly. -#check t.Barriers.wmb' -#reduce t.Barriers.wmb' - @[simp] def domain (evts : Events) (_ : CandidateExecution evts) (r : SetRel Event Event) := SetRel.dom r @[simp] def range (evts : Events) (_ : CandidateExecution evts) (r : SetRel Event Event) := SetRel.cod r @@ -443,21 +386,6 @@ macro_rules @[simp] def ext (evts : Events) (_ : CandidateExecution evts) := CatRel.Rel.internal -[model| test - let acq = M -] - -[model| testlk -let A-cumul(r) = rf ; r - -let acq_po = [M] ; po ; [M] -let FailedRMW = rf | acq_po -let c = A-cumul(rf) -] - -#reduce testlk.FailedRMW -#reduce testlk.c - [model| lkmm enum Accesses = ONCE' || @@ -510,11 +438,13 @@ let a = A_cumul(po_rel) let cumul_fence = [Marked] ; (A_cumul(strong_fence | po_rel) | wmb) ; [Marked] let prop = [Marked] ; (overwrite & ext)? ; cumul_fence* ; [Marked] ; (rfe)? ; [Marked] --- + +-- Happends Before Relation let hb = [Marked] ; (ppo | rfe | ((prop \ id) & int)) ; [Marked] acyclic hb as happens_before --- + +-- Propagation Before Relation let pb = prop ; strong_fence ; hb* ; [Marked] acyclic pb as propagation ] From b71a72c0933ba5030c71a40cf2c87dc663401f13 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 14 Mar 2026 21:01:06 +0100 Subject: [PATCH 16/34] Clean up comments --- LeanCats/Macro.lean | 45 --------------------------------------------- 1 file changed, 45 deletions(-) diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index 5dd520a..b6ce4df 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -108,51 +108,6 @@ macro_rules -- function call. `(([dsl-term| $i, $evts, $X, $arg]) ([expr| $e, $evts, $X, $arg])) --- @[term_elab catexpr] --- def elabCatExpr : TermElab := fun stx type? => do --- match stx with --- | `([expr| $i:cat_ident ($e:expr), $evts, $X]) => do --- let catName : Name := catIdentToName i.raw --- -- We need to check if it's tag set accumulated by the `instructions` command. --- let tagsMap : Option (List String) <- tagsAccExt.find? catName.toString --- match tagsMap with --- | some annotableEvts => do --- -- If it's an instruction set, we need to generate the union of all the tags in the set. --- let annotedEvts <- annotableEvts.mapM (fun a => `([annotable-events| $(mkIdent a.toName), $evts, $X])) --- let dnf <- annotedEvts.foldlM (fun acc evt => do --- let con <- `($acc ∨ [annotable-events| $evt, $evts, $X]) --- return con --- ) (mk mkNullNode #[]) --- --- dbg_trace dnf --- --- let currNamespace <- getCurrNamespace --- -- This is used to get the full name with namespace. --- let typeName := Name.updatePrefix i.getId currNamespace --- let env <- getEnv --- --- let info <- getConstInfoInduct typeName --- dbg_trace typeName --- --- let commands <- info.ctors.mapM ( --- fun ctor => do --- -- Make the constructors name correct by removing the end tick. --- let ctorName : Name := ctor.lastComponentAsString.dropEnd 1 |>.toName --- -- TODO(Nekolas): Make this part `∩ [annotable-events| $a]` work. --- let ctorDef <- --- `( --- abbrev $(mkIdent ctorName) : --- Set Event := {e | e.tag = $(mkIdent ctor) } ∩ $dnf --- ) --- return ctorDef --- ) --- --- pure (← elabTerm (← `(($i $evts $X) ([expr| $e, $evts, $X]))) type?) --- | none => --- pure (← elabTerm (← `(($i $evts $X) ([expr| $e, $evts, $X]))) type?) --- | _ => Lean.Elab.throwUnsupportedSyntax --- -- elabTerm expandedStx expectedType? - macro_rules | `([reserved| $r:predefined_relations, $evts, $X]) => `([predefined-relations| $r, $evts, $X]) From 087f54663d2fdbeffc771173dd10da5f4510f813 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Thu, 19 Mar 2026 22:33:02 +0100 Subject: [PATCH 17/34] Fix theorem cause by changing the SetRel --- LeanCats/Theorems.lean | 54 ++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/LeanCats/Theorems.lean b/LeanCats/Theorems.lean index 8e4f3ba..948866f 100644 --- a/LeanCats/Theorems.lean +++ b/LeanCats/Theorems.lean @@ -10,18 +10,29 @@ lemma internalImpliesPoOrPoMinusOne {e₁ e₂ : Event} (evts : Events) : internal evts e₁ e₂ -> e₁ ≠ e₂ -> po evts e₁ e₂ ∨ po evts e₂ e₁ := by simp - intro he₁in - intro he₂in - intro htideq - intro hneq + intros he₁in he₂in htideq hneq simp [po] have hidneq : e₁.id ≠ e₂.id := by intro hideq apply hneq apply Iff.mpr - apply evts.uniqueId - exact hideq + have h : e₁ = e₂ := + by apply (event_id_unique e₁ e₂ hideq) + + apply Iff.intro + { + intro h' + exact h' + } + { + intro h' + exact h' + } + have h : e₁ = e₂ := + by apply (event_id_unique e₁ e₂ hideq) + + contradiction have hle_or_gt : e₁.id < e₂.id ∨ e₁.id > e₂.id := by @@ -124,14 +135,11 @@ lemma strictPartialOrderImpliesAcyclic lemma AcyclicImpliesIrreflexive {r : Rel Event Event} (hnt : ∀e, ¬TransGen r e e) - : Irreflexive r := + : Std.Irrefl r := by - unfold Irreflexive - intro x - intro hrflx - apply hnt x - apply TransGen.single - exact hrflx + apply Std.Irrefl.mk + intro e hre + exact hnt e (TransGen.single hre) instance {r : Rel Event Event} @@ -139,25 +147,25 @@ instance (hnt : ∀e, ¬TransGen r e e) : IsStrictOrder Event r where irrefl := by - apply AcyclicImpliesIrreflexive - exact hnt + intros e hre + apply hnt e + exact (TransGen.single hre) trans := by apply ht lemma ayclicMono - {r₁ r₂ : Rel Event Event} - (hacyc : Acyclic r₂) - (hsub : ∀ a b, r₁ a b -> r₂ a b) - : Acyclic r₁ := + {r₁ r₂ : SetRel Event Event} + (hacyc : SetRel.Acyclic r₂) + (hsub : ∀ a b, (a, b) ∈ r₁ -> (a, b) ∈ r₂) + : SetRel.Acyclic r₁ := by - have htransub : ∀ a b, TransGen r₁ a b -> TransGen r₂ a b := + have htransub : ∀ a b, TransGen (λ e₁ e₂ ↦ (e₁, e₂) ∈ r₁) a b -> TransGen (λ e₁ e₂ ↦ (e₁, e₂) ∈ r₂) a b := by intro a b apply TransGen.mono apply hsub - unfold Acyclic at * - intro e - intro hr₁trans + unfold SetRel.Acyclic at * + intro e hr₁trans apply hacyc apply htransub exact hr₁trans From 66811e265f0767fec3387123ed1bc634b447b81c Mon Sep 17 00:00:00 2001 From: YangWiz Date: Thu, 19 Mar 2026 22:37:13 +0100 Subject: [PATCH 18/34] Add model comparision --- LeanCats/tsoWeakerThanSc.lean | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/LeanCats/tsoWeakerThanSc.lean b/LeanCats/tsoWeakerThanSc.lean index 0ed6034..5189022 100644 --- a/LeanCats/tsoWeakerThanSc.lean +++ b/LeanCats/tsoWeakerThanSc.lean @@ -1,5 +1,5 @@ import LeanCats.Macro -import LeanCats.Reader +import LeanCats.ModelReader import LeanCats.Data import LeanCats.Relations import LeanCats.Theorems @@ -10,7 +10,6 @@ defcat <"sc.cat"> theorem scvtso (evts : Data.Events) - [IsStrictTotalOrder Data.Event (CatRel.preCo evts)] (X : CandidateExecution evts) : sc.sc evts X → tso.tso evts X := by @@ -19,8 +18,7 @@ by intro sc apply ayclicMono sc simp - intro a b - intro tso + intro a b tso cases tso with | inl h => { @@ -40,6 +38,7 @@ by rename_i h apply Or.inr apply Or.inr + simp [CatRel.CatUnion.union] at * aesop } } From 98cdf1969c7c0c2f3d0babeef4cd336879efb559 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Wed, 25 Mar 2026 00:52:15 +0100 Subject: [PATCH 19/34] Add co totality to Events.preCo Change Events.preCo from a plain Prop to a structure with two fields: - wellTyped: the existing well-typedness condition on co pairs - total: co is a total order on same-location writes Update all wellformed.co instances in Litmus.lean to prove both fields. --- LeanCats/Data.lean | 25 ++++++- LeanCats/Litmus.lean | 154 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 150 insertions(+), 29 deletions(-) diff --git a/LeanCats/Data.lean b/LeanCats/Data.lean index 0e9c9be..99b7f34 100644 --- a/LeanCats/Data.lean +++ b/LeanCats/Data.lean @@ -28,6 +28,9 @@ structure Event where effect : Effect -- Action performed tag : Σ tagType : Type, tagType +inductive RMW where + | trmw + -- Unsafe. axiom event_id_unique : ∀ e₁ e₂ : Event, e₁.id = e₂.id -> e₁ = e₂ @@ -77,13 +80,20 @@ structure Events where instance : Membership Event Events where mem := fun es e => e ∈ es.all -@[simp] def Events.preCo (evts : Events) (co : SetRel Event Event) : Prop := - ∀ e₁ e₂ : Event, (e₁, e₂) ∈ co -> +structure Events.preCo (evts : Events) (co : SetRel Event Event) : Prop where + /-- Every pair in co consists of writes in evts at the same location. -/ + wellTyped : ∀ e₁ e₂ : Event, (e₁, e₂) ∈ co → e₁ ∈ evts.all ∧ e₂ ∈ evts.all ∧ e₁.effect.op = Op.write ∧ e₂.effect.op = Op.write ∧ e₁.effect.location = e₂.effect.location + /-- co is total: any two distinct writes to the same location are co-ordered. -/ + total : ∀ e₁ e₂ : Event, + e₁ ∈ evts.W → e₂ ∈ evts.W + → e₁.effect.location = e₂.effect.location + → e₁ ≠ e₂ + → (e₁, e₂) ∈ co ∨ (e₂, e₁) ∈ co class wellformed.co (evts : Events) (corel : SetRel Event Event) : Prop where -- The Type is the Prop, and the proof is the term, do not use := @@ -91,12 +101,23 @@ class wellformed.co (evts : Events) (corel : SetRel Event Event) : Prop where trans : ∀ e₁ e₂ e₃, (e₁, e₂) ∈ corel -> (e₂, e₃) ∈ corel -> (e₁, e₃) ∈ corel preco : evts.preCo corel +@[simp] def wellformed.rmw (evts : Events) (rmw : SetRel Event Event) : Prop := + rmw ⊆ {(e₁, e₂) | + e₁.tag = ⟨RMW, RMW.trmw⟩ + ∧ e₂.tag = ⟨RMW, RMW.trmw⟩ + ∧ e₁ ∈ evts.R + ∧ e₂ ∈ evts.W + ∧ e₁.effect.location = e₂.effect.location} + @[simp] def wellformed.rf (evts : Events) (rf : SetRel Event Event) : Prop := ∀ (w r : Event), (w, r) ∈ rf -> w ∈ evts.W ∧ r ∈ evts.R ∧ w.effect.location = r.effect.location ∧ r.id ≠ w.id +@[simp] def wellformed.po (po : SetRel Event Event) : Prop := + ∀ x y z, (x, y) ∈ po -> (y, z) ∈ po -> (x, z) ∈ po + @[simp] def Events.po (evts : Events) : SetRel Event Event := λ (a, b) => a ∈ evts.all ∧ b ∈ evts.all ∧ a.t_id = b.t_id ∧ a.id < b.id diff --git a/LeanCats/Litmus.lean b/LeanCats/Litmus.lean index bc02843..e7afa29 100644 --- a/LeanCats/Litmus.lean +++ b/LeanCats/Litmus.lean @@ -3,6 +3,16 @@ import LeanCats.Basic import LeanCats.Data open Data namespace Litmus + +instance instWellformedPo (evts : Data.Events) : wellformed.po evts.po := by + intro x y z hxy hyz + rcases hxy with ⟨hx, hy, hxyTid, hxyLt⟩ + rcases hyz with ⟨_, hz, hyzTid, hyzLt⟩ + exact ⟨hx, hz, Eq.trans hxyTid hyzTid, Nat.lt_trans hxyLt hyzLt⟩ + +instance instWellformedRmwEmpty (evts : Data.Events) : wellformed.rmw evts (∅ : SetRel Event Event) := by + intro e h + exact False.elim h -- X86 SB -- "Fre PodWR Fre PodWR" -- { x=0; y=0; } @@ -60,20 +70,44 @@ def co_mem_list : List (Event × Event) := [(initWx, inst1writeX), (initWy, inst instance : wellformed.co evtsInput co where irrefl := by aesop trans := by aesop - preco := by aesop + preco := { + wellTyped := by aesop + total := by + intro e₁ e₂ he₁ he₂ hloc hne + simp only [Set.mem_insert_iff, Set.mem_singleton_iff] at he₁ he₂ + rcases he₁ with rfl | rfl | rfl | rfl <;> + rcases he₂ with rfl | rfl | rfl | rfl <;> + simp_all [co] + } @[simp] def test1 : CandidateExecution evtsInput := { - uniqueId := by aesop + prePo := instWellformedPo evtsInput + uniqueId := by + intro e₁ e₂ _ _ hne hid + exact hne (Data.event_id_unique e₁ e₂ hid) rf := {(initWy, inst2readY), (initWx, inst4readX)} - rfInst := by aesop + rfInst := by + intro w r hrf + rcases hrf with h | h + · rcases h with ⟨rfl, rfl⟩ + simp + · rcases h with ⟨rfl, rfl⟩ + simp co := co rmw := ∅ + preRMW := instWellformedRmwEmpty evtsInput + wmb := ∅ + mb := ∅ + data := ∅ + ctrl := ∅ + fence := ∅ + addr := ∅ } /-- The SB candidate execution has a cycle in `co ∪ rf ∪ fr ∪ po`: `inst1writeX →[po] inst2readY →[fr] inst3writeY →[po] inst4readX →[fr] inst1writeX` This witnesses that the execution is NOT SC-consistent. -/ -theorem FindCycle : ¬ CatRel.Acyclic (test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po) := by +theorem FindCycle : ¬ CatRel.SetRel.Acyclic (test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po) := by intro h apply h inst1writeX -- Prove each event is in evtsInput.all (needed for po membership) @@ -82,20 +116,26 @@ theorem FindCycle : ¬ CatRel.Acyclic (test1.co ∪ test1.rf ∪ test1.fr ∪ te have mem3 : inst3writeY ∈ evtsInput.all := by simp [Events.all] have mem4 : inst4readX ∈ evtsInput.all := by simp [Events.all] -- Step 1: inst1writeX →[po] inst2readY (same thread P0, id 1 < 2) - have h1 : (inst1writeX, inst2readY) ∈ test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po := - Or.inr ⟨mem1, mem2, rfl, by decide⟩ + have h1po : (inst1writeX, inst2readY) ∈ test1.po := by + have h1evts : (inst1writeX, inst2readY) ∈ evtsInput.po := + ⟨mem1, mem2, rfl, by decide⟩ + simpa [test1] using h1evts + have h1 : (inst1writeX, inst2readY) ∈ test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po := Or.inr h1po -- Step 2: inst2readY →[fr] inst3writeY (via rf⁻¹;co, witness initWy) have h2 : (inst2readY, inst3writeY) ∈ test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po := by left; right - simp only [test1, SetRel.mem_comp, SetRel.mem_inv] + simp only [test1] exact ⟨initWy, by simp, by simp [co]⟩ -- Step 3: inst3writeY →[po] inst4readX (same thread P1, id 3 < 4) - have h3 : (inst3writeY, inst4readX) ∈ test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po := - Or.inr ⟨mem3, mem4, rfl, by decide⟩ + have h3po : (inst3writeY, inst4readX) ∈ test1.po := by + have h3evts : (inst3writeY, inst4readX) ∈ evtsInput.po := + ⟨mem3, mem4, rfl, by decide⟩ + simpa [test1] using h3evts + have h3 : (inst3writeY, inst4readX) ∈ test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po := Or.inr h3po -- Step 4: inst4readX →[fr] inst1writeX (via rf⁻¹;co, witness initWx) have h4 : (inst4readX, inst1writeX) ∈ test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po := by left; right - simp only [test1, SetRel.mem_comp, SetRel.mem_inv] + simp only [test1] exact ⟨initWx, by simp, by simp [co]⟩ exact .head h1 (.head h2 (.head h3 (.single h4))) @@ -138,21 +178,45 @@ theorem FindCycle : ¬ CatRel.Acyclic (test1.co ∪ test1.rf ∪ test1.fr ∪ te instance : wellformed.co mp_evts mp_co where irrefl := by aesop trans := by aesop - preco := by aesop + preco := { + wellTyped := by aesop + total := by + intro e₁ e₂ he₁ he₂ hloc hne + simp only [Set.mem_insert_iff, Set.mem_singleton_iff] at he₁ he₂ + rcases he₁ with rfl | rfl | rfl | rfl <;> + rcases he₂ with rfl | rfl | rfl | rfl <;> + simp_all [mp_co] + } -- rf: mp_readY sees y=1 from mp_writeY; mp_readX sees x=0 from mp_initWx @[simp] def mp_test : CandidateExecution mp_evts := { - uniqueId := by aesop + prePo := instWellformedPo mp_evts + uniqueId := by + intro e₁ e₂ _ _ hne hid + exact hne (Data.event_id_unique e₁ e₂ hid) rf := {(mp_writeY, mp_readY), (mp_initWx, mp_readX)} - rfInst := by aesop + rfInst := by + intro w r hrf + rcases hrf with h | h + · rcases h with ⟨rfl, rfl⟩ + simp + · rcases h with ⟨rfl, rfl⟩ + simp co := mp_co rmw := ∅ + preRMW := instWellformedRmwEmpty mp_evts + wmb := ∅ + mb := ∅ + data := ∅ + ctrl := ∅ + fence := ∅ + addr := ∅ } /-- The MP candidate execution has a cycle in `co ∪ rf ∪ fr ∪ po`: `mp_writeX →[po] mp_writeY →[rf] mp_readY →[po] mp_readX →[fr] mp_writeX` Forbidden under SC. -/ -theorem mp_FindCycle : ¬ CatRel.Acyclic (mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po) := by +theorem mp_FindCycle : ¬ CatRel.SetRel.Acyclic (mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po) := by intro h apply h mp_writeX have mem1 : mp_writeX ∈ mp_evts.all := by simp [Events.all] @@ -160,18 +224,24 @@ theorem mp_FindCycle : ¬ CatRel.Acyclic (mp_test.co ∪ mp_test.rf ∪ mp_test. have mem3 : mp_readY ∈ mp_evts.all := by simp [Events.all] have mem4 : mp_readX ∈ mp_evts.all := by simp [Events.all] -- Step 1: mp_writeX →[po] mp_writeY (P0, id 101 < 102) - have h1 : (mp_writeX, mp_writeY) ∈ mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po := - Or.inr ⟨mem1, mem2, rfl, by decide⟩ + have h1po : (mp_writeX, mp_writeY) ∈ mp_test.po := by + have h1evts : (mp_writeX, mp_writeY) ∈ mp_evts.po := + ⟨mem1, mem2, rfl, by decide⟩ + simpa [mp_test] using h1evts + have h1 : (mp_writeX, mp_writeY) ∈ mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po := Or.inr h1po -- Step 2: mp_writeY →[rf] mp_readY have h2 : (mp_writeY, mp_readY) ∈ mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po := by left; left; right; simp -- Step 3: mp_readY →[po] mp_readX (P1, id 103 < 104) - have h3 : (mp_readY, mp_readX) ∈ mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po := - Or.inr ⟨mem3, mem4, rfl, by decide⟩ + have h3po : (mp_readY, mp_readX) ∈ mp_test.po := by + have h3evts : (mp_readY, mp_readX) ∈ mp_evts.po := + ⟨mem3, mem4, rfl, by decide⟩ + simpa [mp_test] using h3evts + have h3 : (mp_readY, mp_readX) ∈ mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po := Or.inr h3po -- Step 4: mp_readX →[fr] mp_writeX (via rf⁻¹;co, witness mp_initWx) have h4 : (mp_readX, mp_writeX) ∈ mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po := by left; right - simp only [mp_test, SetRel.mem_comp, SetRel.mem_inv] + simp only [mp_test] exact ⟨mp_initWx, by simp, by simp [mp_co]⟩ exact .head h1 (.head h2 (.head h3 (.single h4))) @@ -216,22 +286,46 @@ theorem mp_FindCycle : ¬ CatRel.Acyclic (mp_test.co ∪ mp_test.rf ∪ mp_test. instance : wellformed.co lb_evts lb_co where irrefl := by aesop trans := by aesop - preco := by aesop + preco := { + wellTyped := by aesop + total := by + intro e₁ e₂ he₁ he₂ hloc hne + simp only [Set.mem_insert_iff, Set.mem_singleton_iff] at he₁ he₂ + rcases he₁ with rfl | rfl | rfl | rfl <;> + rcases he₂ with rfl | rfl | rfl | rfl <;> + simp_all [lb_co] + } -- rf: lb_readX sees x=1 from lb_writeX; lb_readY sees y=1 from lb_writeY @[simp] def lb_test : CandidateExecution lb_evts := { - uniqueId := by aesop + prePo := instWellformedPo lb_evts + uniqueId := by + intro e₁ e₂ _ _ hne hid + exact hne (Data.event_id_unique e₁ e₂ hid) rf := {(lb_writeX, lb_readX), (lb_writeY, lb_readY)} - rfInst := by aesop + rfInst := by + intro w r hrf + rcases hrf with h | h + · rcases h with ⟨rfl, rfl⟩ + simp + · rcases h with ⟨rfl, rfl⟩ + simp co := lb_co rmw := ∅ + preRMW := instWellformedRmwEmpty lb_evts + wmb := ∅ + mb := ∅ + data := ∅ + ctrl := ∅ + fence := ∅ + addr := ∅ } /-- The LB candidate execution has a cycle in `co ∪ rf ∪ fr ∪ po`: `lb_readX →[po] lb_writeY →[rf] lb_readY →[po] lb_writeX →[rf] lb_readX` The cycle uses only `po` and `rf` — no `fr` edges are needed. Forbidden under SC and TSO; allowed under ARM/POWER. -/ -theorem lb_FindCycle : ¬ CatRel.Acyclic (lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po) := by +theorem lb_FindCycle : ¬ CatRel.SetRel.Acyclic (lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po) := by intro h apply h lb_readX have mem1 : lb_readX ∈ lb_evts.all := by simp [Events.all] @@ -239,14 +333,20 @@ theorem lb_FindCycle : ¬ CatRel.Acyclic (lb_test.co ∪ lb_test.rf ∪ lb_test. have mem3 : lb_readY ∈ lb_evts.all := by simp [Events.all] have mem4 : lb_writeX ∈ lb_evts.all := by simp [Events.all] -- Step 1: lb_readX →[po] lb_writeY (P0, id 201 < 202) - have h1 : (lb_readX, lb_writeY) ∈ lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po := - Or.inr ⟨mem1, mem2, rfl, by decide⟩ + have h1po : (lb_readX, lb_writeY) ∈ lb_test.po := by + have h1evts : (lb_readX, lb_writeY) ∈ lb_evts.po := + ⟨mem1, mem2, rfl, by decide⟩ + simpa [lb_test] using h1evts + have h1 : (lb_readX, lb_writeY) ∈ lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po := Or.inr h1po -- Step 2: lb_writeY →[rf] lb_readY have h2 : (lb_writeY, lb_readY) ∈ lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po := by left; left; right; simp -- Step 3: lb_readY →[po] lb_writeX (P1, id 203 < 204) - have h3 : (lb_readY, lb_writeX) ∈ lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po := - Or.inr ⟨mem3, mem4, rfl, by decide⟩ + have h3po : (lb_readY, lb_writeX) ∈ lb_test.po := by + have h3evts : (lb_readY, lb_writeX) ∈ lb_evts.po := + ⟨mem3, mem4, rfl, by decide⟩ + simpa [lb_test] using h3evts + have h3 : (lb_readY, lb_writeX) ∈ lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po := Or.inr h3po -- Step 4: lb_writeX →[rf] lb_readX have h4 : (lb_writeX, lb_readX) ∈ lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po := by left; left; right; simp From 4d61bbf29bbabe98cc7fa7b06461e94c7c3447ef Mon Sep 17 00:00:00 2001 From: YangWiz Date: Wed, 25 Mar 2026 15:55:23 +0100 Subject: [PATCH 20/34] =?UTF-8?q?Add=20rf=20uniqueness,=20co=20totality,?= =?UTF-8?q?=20and=20rf;fr=E2=8A=86co=20theorem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wellformed.rf: extend to a structure with wellTyped (existing conditions) and unique (each read has at most one rf source) - Events.preCo: extend to a structure with wellTyped (existing conditions) and total (co is a total order on same-location writes) - CandidateExecution: remove fr as a settable field; define it as a transparent @[simp] def so theorems can unfold it - Litmus.lean: update all rfInst and preco proofs for the new structures - Theorems.lean: prove rf_fr_subset_co (rf;fr⊆co via rf uniqueness) --- LeanCats/Basic.lean | 13 ++++++++- LeanCats/Data.lean | 7 +++-- LeanCats/Litmus.lean | 63 ++++++++++++++++++++++++++++-------------- LeanCats/Theorems.lean | 20 ++++++++++++++ 4 files changed, 79 insertions(+), 24 deletions(-) diff --git a/LeanCats/Basic.lean b/LeanCats/Basic.lean index 1d6f8af..506cac3 100644 --- a/LeanCats/Basic.lean +++ b/LeanCats/Basic.lean @@ -11,13 +11,24 @@ structure CandidateExecution (evts : Events) where evts := evts idUnique := ∀ e₁ e₂ : Event, (e₁ ∈ evts ∧ e₂ ∈ evts) -> e₁.id ≠ e₂.id po := evts.po + [prePo: wellformed.po po] rf : SetRel Event Event rfInst : wellformed.rf evts rf co : SetRel Event Event [preCo : wellformed.co evts co] - fr := rf.inv.comp co rmw : SetRel Event Event + [preRMW : wellformed.rmw evts rmw] + wmb : SetRel Event Event + data : SetRel Event Event + addr : SetRel Event Event + ctrl : SetRel Event Event + fence : SetRel Event Event + mb : SetRel Event Event uniqueId : ∀ (e₁ e₂ : Event), e₁ ∈ evts.all → e₂ ∈ evts.all -> e₁ ≠ e₂ → e₁.id ≠ e₂.id + +/-- from-reads: always defined as rf⁻¹ ; co, so it is transparent to the kernel. -/ +@[simp] def CandidateExecution.fr {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := + X.rf.inv.comp X.co diff --git a/LeanCats/Data.lean b/LeanCats/Data.lean index 99b7f34..c43fed3 100644 --- a/LeanCats/Data.lean +++ b/LeanCats/Data.lean @@ -109,11 +109,14 @@ class wellformed.co (evts : Events) (corel : SetRel Event Event) : Prop where ∧ e₂ ∈ evts.W ∧ e₁.effect.location = e₂.effect.location} -@[simp] def wellformed.rf (evts : Events) (rf : SetRel Event Event) : Prop := - ∀ (w r : Event), (w, r) ∈ rf -> +structure wellformed.rf (evts : Events) (rel : SetRel Event Event) : Prop where + /-- Every rf pair is a write to a read at the same location. -/ + wellTyped : ∀ (w r : Event), (w, r) ∈ rel → w ∈ evts.W ∧ r ∈ evts.R ∧ w.effect.location = r.effect.location ∧ r.id ≠ w.id + /-- rf is functional: each read reads from at most one write. -/ + unique : ∀ (w₁ w₂ r : Event), (w₁, r) ∈ rel → (w₂, r) ∈ rel → w₁ = w₂ @[simp] def wellformed.po (po : SetRel Event Event) : Prop := ∀ x y z, (x, y) ∈ po -> (y, z) ∈ po -> (x, z) ∈ po diff --git a/LeanCats/Litmus.lean b/LeanCats/Litmus.lean index e7afa29..6960767 100644 --- a/LeanCats/Litmus.lean +++ b/LeanCats/Litmus.lean @@ -86,13 +86,20 @@ instance : wellformed.co evtsInput co where intro e₁ e₂ _ _ hne hid exact hne (Data.event_id_unique e₁ e₂ hid) rf := {(initWy, inst2readY), (initWx, inst4readX)} - rfInst := by - intro w r hrf - rcases hrf with h | h - · rcases h with ⟨rfl, rfl⟩ - simp - · rcases h with ⟨rfl, rfl⟩ - simp + rfInst := { + wellTyped := by + intro w r hrf + rcases hrf with h | h + · rcases h with ⟨rfl, rfl⟩; simp + · rcases h with ⟨rfl, rfl⟩; simp + unique := by + intro w₁ w₂ r h1 h2 + simp only [Set.mem_insert_iff, Set.mem_singleton_iff, Prod.mk.injEq] at h1 h2 + rcases h1 with ⟨hw1, hr1⟩ | ⟨hw1, hr1⟩ <;> + rcases h2 with ⟨hw2, hr2⟩ | ⟨hw2, hr2⟩ <;> + subst hw1 hw2 <;> + first | rfl | exact absurd (hr1 ▸ hr2) (by decide) + } co := co rmw := ∅ preRMW := instWellformedRmwEmpty evtsInput @@ -195,13 +202,20 @@ instance : wellformed.co mp_evts mp_co where intro e₁ e₂ _ _ hne hid exact hne (Data.event_id_unique e₁ e₂ hid) rf := {(mp_writeY, mp_readY), (mp_initWx, mp_readX)} - rfInst := by - intro w r hrf - rcases hrf with h | h - · rcases h with ⟨rfl, rfl⟩ - simp - · rcases h with ⟨rfl, rfl⟩ - simp + rfInst := { + wellTyped := by + intro w r hrf + rcases hrf with h | h + · rcases h with ⟨rfl, rfl⟩; simp + · rcases h with ⟨rfl, rfl⟩; simp + unique := by + intro w₁ w₂ r h1 h2 + simp only [Set.mem_insert_iff, Set.mem_singleton_iff, Prod.mk.injEq] at h1 h2 + rcases h1 with ⟨hw1, hr1⟩ | ⟨hw1, hr1⟩ <;> + rcases h2 with ⟨hw2, hr2⟩ | ⟨hw2, hr2⟩ <;> + subst hw1 hw2 <;> + first | rfl | exact absurd (hr1 ▸ hr2) (by decide) + } co := mp_co rmw := ∅ preRMW := instWellformedRmwEmpty mp_evts @@ -303,13 +317,20 @@ instance : wellformed.co lb_evts lb_co where intro e₁ e₂ _ _ hne hid exact hne (Data.event_id_unique e₁ e₂ hid) rf := {(lb_writeX, lb_readX), (lb_writeY, lb_readY)} - rfInst := by - intro w r hrf - rcases hrf with h | h - · rcases h with ⟨rfl, rfl⟩ - simp - · rcases h with ⟨rfl, rfl⟩ - simp + rfInst := { + wellTyped := by + intro w r hrf + rcases hrf with h | h + · rcases h with ⟨rfl, rfl⟩; simp + · rcases h with ⟨rfl, rfl⟩; simp + unique := by + intro w₁ w₂ r h1 h2 + simp only [Set.mem_insert_iff, Set.mem_singleton_iff, Prod.mk.injEq] at h1 h2 + rcases h1 with ⟨hw1, hr1⟩ | ⟨hw1, hr1⟩ <;> + rcases h2 with ⟨hw2, hr2⟩ | ⟨hw2, hr2⟩ <;> + subst hw1 hw2 <;> + first | rfl | exact absurd (hr1 ▸ hr2) (by decide) + } co := lb_co rmw := ∅ preRMW := instWellformedRmwEmpty lb_evts diff --git a/LeanCats/Theorems.lean b/LeanCats/Theorems.lean index 948866f..8222c14 100644 --- a/LeanCats/Theorems.lean +++ b/LeanCats/Theorems.lean @@ -1,5 +1,6 @@ import LeanCats.Relations import LeanCats.Data +import LeanCats.Basic open Relation open CatRel open Data @@ -153,6 +154,7 @@ instance trans := by apply ht +@[simp, aesop safe apply] lemma ayclicMono {r₁ r₂ : SetRel Event Event} (hacyc : SetRel.Acyclic r₂) @@ -173,3 +175,21 @@ lemma ayclicMono --- tso : Relation.TransGen --- (Rel.po evts ∩ (prod W W ∪ prod R (R ∪ W)) ∪ union (external evts ∪ Rel.rf evts) (co evts ∪ Rel.fr evts co)) x x --- ⊢ Relation.TransGen (fun x y => (Rel.rf evts x y ∨ co evts x y ∨ Rel.fr evts co x y) ∨ Rel.po evts x y) ?x ?x + +/-- Composing rf then fr yields co: if `w` reads-from `r`, and `r` is from-read of `w'`, + then `w` coherence-precedes `w'`. + + Proof sketch: unfolding `fr = rf⁻¹ ; co` gives a witness `w₁` with + `(w₁, r) ∈ rf` and `(w₁, w') ∈ co`; rf-uniqueness forces `w = w₁`; + substituting gives `(w, w') ∈ co`. -/ +theorem rf_fr_subset_co + {evts : Events} + (X : CandidateExecution evts) + (w r w' : Event) + (hrf : (w, r) ∈ X.rf) + (hfr : (r, w') ∈ X.fr) : + (w, w') ∈ X.co := by + simp only [CandidateExecution.fr] at hfr + obtain ⟨w₁, h₁, h₂⟩ := hfr + simp only [SetRel.inv] at h₁ + exact X.rfInst.unique w w₁ r hrf h₁ ▸ h₂ From 93b2203f6c1b153eed131b9fde6b702f5b3e0237 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Wed, 25 Mar 2026 15:58:41 +0100 Subject: [PATCH 21/34] Prove co_acyclic and fr_co_subset_fr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit co_acyclic: co is acyclic, following from wellformed.co's irrefl and trans via the existing strictPartialOrderImpliesAcyclic lemma. fr_co_subset_fr: fr;co ⊆ fr — if (r,w)∈fr and (w,w')∈co then (r,w')∈fr. Proof unfolds fr, applies co transitivity, and repacks. --- LeanCats/Theorems.lean | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/LeanCats/Theorems.lean b/LeanCats/Theorems.lean index 8222c14..8cb87fc 100644 --- a/LeanCats/Theorems.lean +++ b/LeanCats/Theorems.lean @@ -193,3 +193,31 @@ theorem rf_fr_subset_co obtain ⟨w₁, h₁, h₂⟩ := hfr simp only [SetRel.inv] at h₁ exact X.rfInst.unique w w₁ r hrf h₁ ▸ h₂ + +/-- co is acyclic: no write can coherence-precede itself. + Follows directly from co being a strict partial order (irrefl + trans). -/ +theorem co_acyclic + {evts : Events} + (X : CandidateExecution evts) : + SetRel.Acyclic X.co := by + let r : Rel Event Event := fun e₁ e₂ => (e₁, e₂) ∈ X.co + have hiso : IsStrictOrder Event r := + { irrefl := fun e h => X.preCo.irrefl e h + trans := fun a b c hab hbc => X.preCo.trans a b c hab hbc } + intro a ha + exact strictPartialOrderImpliesAcyclic hiso a ha + +/-- Composing fr then co yields fr: if `r` is from-read of `w`, and `w` co-precedes `w'`, + then `r` is from-read of `w'`. + Proof: unfold fr to get witness `w₀` with `(w₀,r)∈rf` and `(w₀,w)∈co`; + co-transitivity gives `(w₀,w')∈co`; re-pack as fr. -/ +theorem fr_co_subset_fr + {evts : Events} + (X : CandidateExecution evts) + (r w w' : Event) + (hfr : (r, w) ∈ X.fr) + (hco : (w, w') ∈ X.co) : + (r, w') ∈ X.fr := by + simp only [CandidateExecution.fr] at * + obtain ⟨w₀, h_inv, h_co⟩ := hfr + exact ⟨w₀, h_inv, X.preCo.trans w₀ w w' h_co hco⟩ From ab09f9637e0f133098c20ff146cfa1adef6c097a Mon Sep 17 00:00:00 2001 From: YangWiz Date: Wed, 25 Mar 2026 16:00:43 +0100 Subject: [PATCH 22/34] Misc updates to cat preprocessor, syntax, and model files --- LeanCats/ArmWeakerThanX86.lean | 0 LeanCats/CatPreprocessor.lean | 44 +++++++++++++++++++++--- LeanCats/Cats/tso.cat | 12 +++---- LeanCats/LitmusGraph.lean | 2 +- LeanCats/LitmusParserTest.lean | 2 +- LeanCats/LitmusReaderTest.lean | 62 +++++++++++++++++----------------- LeanCats/Macro.lean | 9 +++++ LeanCats/ModelReader.lean | 8 ----- LeanCats/Relations.lean | 8 ++++- LeanCats/Syntax.lean | 24 ++++++------- LeanCats/tsoWeakerThanSc.lean | 60 +++++++++++++++++++------------- 11 files changed, 140 insertions(+), 91 deletions(-) create mode 100644 LeanCats/ArmWeakerThanX86.lean diff --git a/LeanCats/ArmWeakerThanX86.lean b/LeanCats/ArmWeakerThanX86.lean new file mode 100644 index 0000000..e69de29 diff --git a/LeanCats/CatPreprocessor.lean b/LeanCats/CatPreprocessor.lean index 0dfbedd..9b33ab5 100644 --- a/LeanCats/CatPreprocessor.lean +++ b/LeanCats/CatPreprocessor.lean @@ -60,13 +60,47 @@ private def processHead (accDone : String × Bool) : Char → String × Bool := | '"' => (acc, true) | _ => (acc, false) +private def isCatIdentChar (c : Char) : Bool := + c.isAlphanum || c == '_' || c == '-' + +private structure TickRewriteState where + acc : String := "" + tok : String := "" + pendingTick : Bool := false + +private def flushTickState (st : TickRewriteState) : TickRewriteState := + if st.tok.isEmpty then + if st.pendingTick then + { acc := st.acc.push '\'', tok := "", pendingTick := false } + else + st + else + let acc := + if st.pendingTick then st.acc ++ st.tok ++ "'" + else st.acc ++ st.tok + { acc := acc, tok := "", pendingTick := false } + +/-- Rewrite CAT tick-prefixed identifiers from `'TAG` to `TAG'`. -/ def removeFrontTick (input : String) : String := - (input.splitOn.map (fun s => s.stripPrefix "\'")) |> (String.intercalate " ") - |>.splitOn "\n" |>.map (fun s => s.stripPrefix "\'") |> (String.intercalate "\n") - |>.splitOn "\t" |>.map (fun s => s.stripPrefix "\'") |> (String.intercalate " ") + let st := input.toList.foldl (fun st c => + if isCatIdentChar c then + { st with tok := st.tok.push c } + else if c == '\'' then + if st.tok.isEmpty then + if st.pendingTick then + { acc := st.acc.push '\'', tok := "", pendingTick := true } + else + { st with pendingTick := true } + else + { acc := st.acc ++ st.tok ++ "'", tok := "", pendingTick := false } + else + let st := flushTickState st + { st with acc := st.acc.push c } + ) {} + (flushTickState st).acc def removeTickAndCapitalize (s : String) : String := - let stripped := (s.dropPrefix "\'").toString + let stripped := (s.dropPrefix "\'").toString ++ "'" if stripped.isEmpty then stripped else @@ -172,7 +206,7 @@ def Filename.mkName (inp : String) : Lean.Name := Id.run do def enums_test := "enum Accesses = 'ONCE (*READ_ONCE,WRITE_ONCE*) || - 'RELEASE (*smp_store_release*) || + 'Release (*smp_store_release*) || 'ACQUIRE (*smp_load_acquire*) || 'NORETURN (* R of non-return RMW *) || 'MB (*xchg(),cmpxchg(),...*)" diff --git a/LeanCats/Cats/tso.cat b/LeanCats/Cats/tso.cat index 9303932..2f0b285 100644 --- a/LeanCats/Cats/tso.cat +++ b/LeanCats/Cats/tso.cat @@ -1,10 +1,6 @@ include "cos.cat" -(* Communication relations that order events*) -let com-tso = rf | co | fr -(* Program order that orders events *) -let po1-tso = po & (W*W | R*M) - -(* TSO global-happens-before *) -let ghb = po1-tso | com-tso -acyclic ghb as tso \ No newline at end of file +let xppo = ((W*W) | (R*W) | (R*R)) & po +let At = domain(rmw) | range(rmw) +let implied = po;[At | F] | [At | F];po +acyclic (implied | xppo | rfe | fr | co) as tso \ No newline at end of file diff --git a/LeanCats/LitmusGraph.lean b/LeanCats/LitmusGraph.lean index e0864b3..6319c24 100644 --- a/LeanCats/LitmusGraph.lean +++ b/LeanCats/LitmusGraph.lean @@ -435,7 +435,7 @@ private def sbEvents : Array Event := #[initWx, initWy, inst1writeX, inst2readY, inst3writeY, inst4readX] private def sbRf : Array (Event × Event) := - #[(initWy, inst2readY), (inst1writeX, inst4readX)] + #[(initWy, inst2readY), (initWx, inst4readX)] private def sbCo : Array (Event × Event) := #[(initWx, inst1writeX), (initWy, inst3writeY)] diff --git a/LeanCats/LitmusParserTest.lean b/LeanCats/LitmusParserTest.lean index 72286f1..cd29f1a 100644 --- a/LeanCats/LitmusParserTest.lean +++ b/LeanCats/LitmusParserTest.lean @@ -6,7 +6,7 @@ import LeanCats.LitmusParser open LitmusParser LitmusParser.X86 -def testDir : String := "LeanCats/examples/tests" +def testDir : String := "LeanCats/Cats/examples/tests" def allLitmusFiles : Array String := #[ "2W.litmus", diff --git a/LeanCats/LitmusReaderTest.lean b/LeanCats/LitmusReaderTest.lean index a581a19..a5c22d6 100644 --- a/LeanCats/LitmusReaderTest.lean +++ b/LeanCats/LitmusReaderTest.lean @@ -6,44 +6,44 @@ import LeanCats.LitmusGraph import LeanCats.LitmusGraphBridge -- Classic 2-thread tests -#litmus "LeanCats/examples/tests/SB.litmus" -#litmus "LeanCats/examples/tests/MP.litmus" -#litmus "LeanCats/examples/tests/LB.litmus" -#litmus "LeanCats/examples/tests/CoWW.litmus" -#litmus "LeanCats/examples/tests/CoWR.litmus" -#litmus "LeanCats/examples/tests/CoRW.litmus" -#litmus "LeanCats/examples/tests/CoRR.litmus" -#litmus "LeanCats/examples/tests/WRR.litmus" -#litmus "LeanCats/examples/tests/RRW.litmus" -#litmus "LeanCats/examples/tests/WWC.litmus" -#litmus "LeanCats/examples/tests/WRW.litmus" -#litmus "LeanCats/examples/tests/2W.litmus" +#litmus "LeanCats/Cats/examples/tests/SB.litmus" +#litmus "LeanCats/Cats/examples/tests/MP.litmus" +#litmus "LeanCats/Cats/examples/tests/LB.litmus" +#litmus "LeanCats/Cats/examples/tests/CoWW.litmus" +#litmus "LeanCats/Cats/examples/tests/CoWR.litmus" +#litmus "LeanCats/Cats/examples/tests/CoRW.litmus" +#litmus "LeanCats/Cats/examples/tests/CoRR.litmus" +#litmus "LeanCats/Cats/examples/tests/WRR.litmus" +#litmus "LeanCats/Cats/examples/tests/RRW.litmus" +#litmus "LeanCats/Cats/examples/tests/WWC.litmus" +#litmus "LeanCats/Cats/examples/tests/WRW.litmus" +#litmus "LeanCats/Cats/examples/tests/2W.litmus" -deflitmus SB <"LeanCats/examples/tests/SB.litmus"> +deflitmus SB <"LeanCats/Cats/examples/tests/SB.litmus"> def a := LitmusGraphBridge.candExecToConcreteExec SB[0]! #html LitmusGraph.toOrderedHtml a -- With MFENCE -#litmus "LeanCats/examples/tests/SB_MFence.litmus" -#litmus "LeanCats/examples/tests/MP_MFence.litmus" -#litmus "LeanCats/examples/tests/LB_MFence.litmus" +#litmus "LeanCats/Cats/examples/tests/SB_MFence.litmus" +#litmus "LeanCats/Cats/examples/tests/MP_MFence.litmus" +#litmus "LeanCats/Cats/examples/tests/LB_MFence.litmus" -- 3-thread tests -#litmus "LeanCats/examples/tests/WRC.litmus" -#litmus "LeanCats/examples/tests/RWC.litmus" -#litmus "LeanCats/examples/tests/ISA2.litmus" -#litmus "LeanCats/examples/tests/3SB.litmus" -#litmus "LeanCats/examples/tests/3LB.litmus" -#litmus "LeanCats/examples/tests/3MP.litmus" -#litmus "LeanCats/examples/tests/RWC_MFence.litmus" -#litmus "LeanCats/examples/tests/ISA2_MFence.litmus" -#litmus "LeanCats/examples/tests/MP2.litmus" -#litmus "LeanCats/examples/tests/SB_opt.litmus" -#litmus "LeanCats/examples/tests/3SB_MFence.litmus" +#litmus "LeanCats/Cats/examples/tests/WRC.litmus" +#litmus "LeanCats/Cats/examples/tests/RWC.litmus" +#litmus "LeanCats/Cats/examples/tests/ISA2.litmus" +#litmus "LeanCats/Cats/examples/tests/3SB.litmus" +#litmus "LeanCats/Cats/examples/tests/3LB.litmus" +#litmus "LeanCats/Cats/examples/tests/3MP.litmus" +#litmus "LeanCats/Cats/examples/tests/RWC_MFence.litmus" +#litmus "LeanCats/Cats/examples/tests/ISA2_MFence.litmus" +#litmus "LeanCats/Cats/examples/tests/MP2.litmus" +#litmus "LeanCats/Cats/examples/tests/SB_opt.litmus" +#litmus "LeanCats/Cats/examples/tests/3SB_MFence.litmus" -- 4-thread tests -#litmus "LeanCats/examples/tests/IRIW.litmus" -#litmus "LeanCats/examples/tests/IRIW_MFence.litmus" -#litmus "LeanCats/examples/tests/4SB.litmus" -#litmus "LeanCats/examples/tests/4LB.litmus" +#litmus "LeanCats/Cats/examples/tests/IRIW.litmus" +#litmus "LeanCats/Cats/examples/tests/IRIW_MFence.litmus" +#litmus "LeanCats/Cats/examples/tests/4SB.litmus" +#litmus "LeanCats/Cats/examples/tests/4LB.litmus" diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index b6ce4df..0aaab90 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -404,7 +404,16 @@ let pb = prop ; strong_fence ; hb* ; [Marked] acyclic pb as propagation ] +#reduce lkmm.ACQUIRE #reduce lkmm.coherence #reduce lkmm.atomic #reduce lkmm.happens_before #reduce lkmm.propagation + +[model| tso_x86 + +let xppo = ((W*W) | (R*W) | (R*R)) & po +let At = domain(rmw) | range(rmw) +let implied = po;[At | F] | [At | F];po +acyclic (implied | xppo | rfe | fr | co) as tso +] diff --git a/LeanCats/ModelReader.lean b/LeanCats/ModelReader.lean index 7fb8b89..fb2d022 100644 --- a/LeanCats/ModelReader.lean +++ b/LeanCats/ModelReader.lean @@ -32,11 +32,3 @@ elab "defcat" "<" filename:str ">" : command => do -- Add the declaration to the environment dbg_trace model evalCat model - -section Test - -defcat <"examples/models/lkmm.bell"> -#check Accesses.ACQUIRE -#check Accesses.MB - -end Test diff --git a/LeanCats/Relations.lean b/LeanCats/Relations.lean index 9ec6eac..1087be4 100644 --- a/LeanCats/Relations.lean +++ b/LeanCats/Relations.lean @@ -38,6 +38,13 @@ theorem RelProdIsSetProd (s₁ s₂ : Event -> Prop) (e₁ e₂ : Event) : @[simp] def SetRel.union (r₁ r₂ : SetRel Event Event) := {(e₁, e₂) | (e₁, e₂) ∈ r₁ ∨ (e₁, e₂) ∈ r₂} +class CatUnion (α : Type*) where + union : α → α → α + +@[reducible] instance : CatUnion (Set Event) where union := Set.union + +@[reducible] instance : CatUnion (SetRel Event Event) where union := CatRel.SetRel.union + abbrev SetRel.ReflexiveTrans (r : SetRel Event Event) := {(e₁, e₂) | Relation.ReflTransGen (λ a b ↦ (a, b) ∈ r) e₁ e₂} @@ -149,7 +156,6 @@ structure co.wellformed : Prop := ∃w, isWrite w ∧ rf evts w e1 ∧ co.wellformed evts w e2 - def com (evts : Events) [IsStrictTotalOrder Event (preCo evts)] diff --git a/LeanCats/Syntax.lean b/LeanCats/Syntax.lean index 19a559c..2189679 100644 --- a/LeanCats/Syntax.lean +++ b/LeanCats/Syntax.lean @@ -54,15 +54,6 @@ syntax "empty" : assertion syntax "acyclic" : assertion syntax "~"assertion : assertion -syntax "_" : name -syntax "O" : name -syntax "ext" : name -syntax "FW" : name -syntax "id" : name -syntax "loc" : name -syntax "narrower" : name -syntax "wider" : name - /- table events. -/ syntax "W" : annotable_events -- write events syntax "R" : annotable_events -- read events @@ -79,14 +70,19 @@ syntax annotable_events : predefined_events /- defined_relations: -/ syntax "O" : predefined_relations -- empty relation syntax "rf" : predefined_relations -- read from -syntax "rfe" : predefined_relations -- read from external syntax "fr" : predefined_relations -- from read syntax "co" : predefined_relations -- from read syntax "id" : predefined_relations -- identity syntax "loc" : predefined_relations -- same location -syntax "ext" : predefined_relations -- external (different pids) syntax "po" : predefined_relations -- program order syntax "rmw" : predefined_relations -- read-modify-write +syntax "mb" : predefined_relations -- read-modify-write +syntax "data" : predefined_relations -- data dependencies, starts with a read +syntax "ctrl" : predefined_relations -- control dependencies, starts with a read +syntax "addr" : predefined_relations -- address dependencies, starts with a read +syntax "rmb" : predefined_relations -- read memory barrier, read -> read +syntax "wmb" : predefined_relations -- write memory barrier, write -> write +syntax "fence" : predefined_relations -- fence barrier syntax keyword : dsl_term syntax num : dsl_term @@ -104,12 +100,15 @@ syntax expr "&" expr : expr syntax expr ";" expr : expr syntax expr "\\" expr : expr syntax:60 expr:60 "*" expr:61 : expr +syntax:70 expr "*" : expr -- Reflexive Transitive Closure. +syntax:70 expr "+" : expr -- Transitive Closure. syntax expr "^" expr : expr syntax expr "+" expr : expr syntax expr "-" expr : expr +syntax expr "?" : expr syntax:71 expr "^-1" : expr -- The procedure will return a value, so we can use it in the expression. -syntax cat_ident "(" expr,* ")" : expr +syntax dsl_term "(" expr,* ")" : expr syntax "[" expr "]" : expr @@ -117,6 +116,7 @@ syntax assertion expr ("as" cat_ident)? : inst -- The flag is used to witness the assertion, so it doesn't change the states of the execution, we could just ignore it. syntax "flag" assertion expr "as" expr : inst syntax "let" cat_ident "=" expr : inst +syntax "let" cat_ident "(" cat_ident,* ")" "=" expr : inst syntax "enum" cat_ident "=" sepBy(cat_ident, "||") : inst -- event class can be R W F B RMW or a custom name like SRCU syntax "instructions" "{" annotable_events,+ "}" "[" expr "]" : inst diff --git a/LeanCats/tsoWeakerThanSc.lean b/LeanCats/tsoWeakerThanSc.lean index 5189022..2b52ccb 100644 --- a/LeanCats/tsoWeakerThanSc.lean +++ b/LeanCats/tsoWeakerThanSc.lean @@ -17,28 +17,40 @@ by simp intro sc apply ayclicMono sc - simp - intro a b tso + simp [CatRel.CatUnion.union] at * + intro a b h + rcases h with hImplied | h + · rcases hImplied with ⟨mid, hpo_amid, htail⟩ + rcases htail with hId | hComp + · have hmid_eq_b : mid = b := hId.1 + subst hmid_eq_b + exact Or.inl hpo_amid + · rcases hComp with ⟨x, hIdMidX, hpo_xb⟩ + have hmid_eq_x : mid = x := hIdMidX.1 + subst hmid_eq_x + exact Or.inl (X.prePo _ _ _ hpo_amid hpo_xb) + · rcases h with hxppo | h + · exact Or.inl hxppo.2 + · rcases h with hrfe | h + · exact Or.inr (Or.inr (Or.inl hrfe.1)) + · rcases h with hfr | hco + · exact Or.inr (Or.inr (Or.inl hfr)) + · exact Or.inr (Or.inr (Or.inr hco)) + +private def sbEvents : Array Event := + #[initWx, initWy, inst1writeX, inst2readY, inst3writeY, inst4readX] + +private def sbRf : Array (Event × Event) := + #[(initWx, inst2readY), (initWy, inst4readX)] + +private def sbCo : Array (Event × Event) := + #[(initWx, inst1writeX), (initWy, inst3writeY)] - cases tso with - | inl h => { - simp at h - apply Or.inl - obtain ⟨l, r⟩ := h - exact l - } - | inr h => { - obtain ⟨l⟩ := h - { - apply Or.inr - apply Or.inl - exact l - } - { - rename_i h - apply Or.inr - apply Or.inr - simp [CatRel.CatUnion.union] at * - aesop - } - } +def sbExecution : ConcreteExecution := { + events := sbEvents + po := computeDirectPo sbEvents + rf := sbRf + co := sbCo + fr := computeFr sbRf sbCo + rmw := #[] +} From d2bbac77fd11cb7b2d6e5c32370ccbdea0f35d15 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Wed, 25 Mar 2026 22:53:10 +0100 Subject: [PATCH 23/34] Add CoWR well-formedness, fix rfe/ext bugs, scaffold X86 vs LKMM proof - Add CoWR condition to CandidateExecution: a read cannot observe a write older than the most recent same-location po-predecessor, needed to prove LKMM coherence from TSO acyclicity - Instantiate coWR for all three litmus tests (SB, MP, LB) - Fix two bugs in Macro.lean: rfe was X.fr instead of X.rf, and ext was Rel.internal instead of Rel.external - Add X86WeakerThanLKMM.lean with theorem statement (proof pending) - Fix tsoWeakerThanSc.lean imports and remove broken ConcreteExecution Co-Authored-By: Claude Sonnet 4.6 --- LeanCats/Basic.lean | 8 ++++++++ LeanCats/Litmus.lean | 23 +++++++++++++++++++++++ LeanCats/Macro.lean | 4 ++-- LeanCats/X86WeakerThanLKMM.lean | 19 +++++++++++++++++++ LeanCats/tsoWeakerThanSc.lean | 21 +++------------------ 5 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 LeanCats/X86WeakerThanLKMM.lean diff --git a/LeanCats/Basic.lean b/LeanCats/Basic.lean index 506cac3..fec8149 100644 --- a/LeanCats/Basic.lean +++ b/LeanCats/Basic.lean @@ -28,6 +28,14 @@ structure CandidateExecution (evts : Events) where e₁ ∈ evts.all → e₂ ∈ evts.all -> e₁ ≠ e₂ → e₁.id ≠ e₂.id + /-- CoWR: if a write w is program-order before a read r at the same location, + then r cannot observe a write older than w in coherence order. + Formally: the rf-source of r is either w itself or co-after w. -/ + coWR : ∀ (w r w' : Event), + (w, r) ∈ evts.po + → w.effect.location = r.effect.location + → (w', r) ∈ rf + → (w, w') ∈ co ∨ w = w' /-- from-reads: always defined as rf⁻¹ ; co, so it is transparent to the kernel. -/ @[simp] def CandidateExecution.fr {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := diff --git a/LeanCats/Litmus.lean b/LeanCats/Litmus.lean index 6960767..750dbdd 100644 --- a/LeanCats/Litmus.lean +++ b/LeanCats/Litmus.lean @@ -109,6 +109,13 @@ instance : wellformed.co evtsInput co where ctrl := ∅ fence := ∅ addr := ∅ + coWR := by + intro w r w' hpo hloc hrf + simp only [Events.po, Set.mem_setOf_eq] at hpo + simp only [Set.mem_insert_iff, Set.mem_singleton_iff, Prod.mk.injEq] at hrf + rcases hrf with ⟨hw', hr⟩ | ⟨hw', hr⟩ <;> + subst hr <;> + simp_all [co, evtsInput, Events.po] } /-- The SB candidate execution has a cycle in `co ∪ rf ∪ fr ∪ po`: @@ -225,6 +232,14 @@ instance : wellformed.co mp_evts mp_co where ctrl := ∅ fence := ∅ addr := ∅ + coWR := by + intro w r w' hpo hloc hrf + simp only [Events.po, Set.mem_setOf_eq] at hpo + simp only [Set.mem_insert_iff, Set.mem_singleton_iff, Prod.mk.injEq] at hrf + rcases hrf with ⟨hw', hr⟩ | ⟨hw', hr⟩ <;> + subst hr <;> + simp_all [mp_co, mp_evts, Events.all, Set.mem_insert_iff, Set.mem_singleton_iff] <;> + omega } /-- The MP candidate execution has a cycle in `co ∪ rf ∪ fr ∪ po`: @@ -340,6 +355,14 @@ instance : wellformed.co lb_evts lb_co where ctrl := ∅ fence := ∅ addr := ∅ + coWR := by + intro w r w' hpo hloc hrf + simp only [Events.po, Set.mem_setOf_eq] at hpo + simp only [Set.mem_insert_iff, Set.mem_singleton_iff, Prod.mk.injEq] at hrf + rcases hrf with ⟨hw', hr⟩ | ⟨hw', hr⟩ <;> + subst hr <;> + simp_all [lb_co, lb_evts, Events.all, Set.mem_insert_iff, Set.mem_singleton_iff] <;> + omega } /-- The LB candidate execution has a cycle in `co ∪ rf ∪ fr ∪ po`: diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index 0aaab90..ee3ab8d 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -331,7 +331,7 @@ macro_rules @[simp] def fre (evts : Events) (X : CandidateExecution evts) := X.fr ∩ CatRel.Rel.external -@[simp] def rfe (evts : Events) (X : CandidateExecution evts) := X.fr ∩ CatRel.Rel.external +@[simp] def rfe (evts : Events) (X : CandidateExecution evts) := X.rf ∩ CatRel.Rel.external @[simp] def rfi (evts : Events) (X : CandidateExecution evts) := X.rf ∩ CatRel.Rel.internal @@ -339,7 +339,7 @@ macro_rules @[simp] def int (evts : Events) (_ : CandidateExecution evts) := CatRel.Rel.internal -@[simp] def ext (evts : Events) (_ : CandidateExecution evts) := CatRel.Rel.internal +@[simp] def ext (evts : Events) (_ : CandidateExecution evts) := CatRel.Rel.external [model| lkmm diff --git a/LeanCats/X86WeakerThanLKMM.lean b/LeanCats/X86WeakerThanLKMM.lean new file mode 100644 index 0000000..a9ab561 --- /dev/null +++ b/LeanCats/X86WeakerThanLKMM.lean @@ -0,0 +1,19 @@ +import LeanCats.Macro +import LeanCats.Data +import LeanCats.Relations +import LeanCats.Theorems +import LeanCats.Basic + +/-- x86/TSO is weaker than LKMM: any execution consistent under x86/TSO is also + consistent under LKMM. x86 operations are plain R/W with no LKMM access-type + annotations, so the annotation-dependent LKMM constraints (happens_before, + propagation) are vacuously satisfied, and coherence is the meaningful obligation. -/ +theorem tso_x86_subset_lkmm + (evts : Data.Events) + (X : CandidateExecution evts) + (hTso : tso_x86.tso evts X) : + lkmm.coherence evts X ∧ + lkmm.atomic evts X ∧ + lkmm.happens_before evts X ∧ + lkmm.propagation evts X := by + sorry diff --git a/LeanCats/tsoWeakerThanSc.lean b/LeanCats/tsoWeakerThanSc.lean index 2b52ccb..6018475 100644 --- a/LeanCats/tsoWeakerThanSc.lean +++ b/LeanCats/tsoWeakerThanSc.lean @@ -4,6 +4,9 @@ import LeanCats.Data import LeanCats.Relations import LeanCats.Theorems import LeanCats.Basic +import LeanCats.Litmus + +open Litmus defcat <"tso.cat"> defcat <"sc.cat"> @@ -36,21 +39,3 @@ by · rcases h with hfr | hco · exact Or.inr (Or.inr (Or.inl hfr)) · exact Or.inr (Or.inr (Or.inr hco)) - -private def sbEvents : Array Event := - #[initWx, initWy, inst1writeX, inst2readY, inst3writeY, inst4readX] - -private def sbRf : Array (Event × Event) := - #[(initWx, inst2readY), (initWy, inst4readX)] - -private def sbCo : Array (Event × Event) := - #[(initWx, inst1writeX), (initWy, inst3writeY)] - -def sbExecution : ConcreteExecution := { - events := sbEvents - po := computeDirectPo sbEvents - rf := sbRf - co := sbCo - fr := computeFr sbRf sbCo - rmw := #[] -} From 215d8c3d2b21680bb69351bea5f74eda0df8808f Mon Sep 17 00:00:00 2001 From: YangWiz Date: Thu, 26 Mar 2026 22:56:13 +0100 Subject: [PATCH 24/34] Add tactic for wellformness, and add one litmus test for lkmm. --- LeanCats/Basic.lean | 78 +++- LeanCats/Linux/litmus.lean | 140 ++++++ LeanCats/Litmus.lean | 158 +++---- LeanCats/Macro.lean | 12 +- LeanCats/Theorems.lean | 23 +- LeanCats/X86WeakerThanLKMM.lean | 19 - LeanCats/X86WeakerThanLKMM.lean.test | 640 +++++++++++++++++++++++++++ LeanCats/tsoWeakerThanSc.lean | 3 - tmp_check_no_macro.lean | 3 + tmp_lkmm_check.lean | 6 + tmp_sig_check.lean | 9 + tmp_tag_check.lean | 8 + 12 files changed, 948 insertions(+), 151 deletions(-) create mode 100644 LeanCats/Linux/litmus.lean delete mode 100644 LeanCats/X86WeakerThanLKMM.lean create mode 100644 LeanCats/X86WeakerThanLKMM.lean.test create mode 100644 tmp_check_no_macro.lean create mode 100644 tmp_lkmm_check.lean create mode 100644 tmp_sig_check.lean create mode 100644 tmp_tag_check.lean diff --git a/LeanCats/Basic.lean b/LeanCats/Basic.lean index fec8149..1788949 100644 --- a/LeanCats/Basic.lean +++ b/LeanCats/Basic.lean @@ -10,33 +10,71 @@ by computation, so should declare it as the base relation. -/ structure CandidateExecution (evts : Events) where evts := evts idUnique := ∀ e₁ e₂ : Event, (e₁ ∈ evts ∧ e₂ ∈ evts) -> e₁.id ≠ e₂.id - po := evts.po - [prePo: wellformed.po po] - rf : SetRel Event Event - rfInst : wellformed.rf evts rf - co : SetRel Event Event - [preCo : wellformed.co evts co] - rmw : SetRel Event Event - [preRMW : wellformed.rmw evts rmw] - wmb : SetRel Event Event - data : SetRel Event Event - addr : SetRel Event Event - ctrl : SetRel Event Event - fence : SetRel Event Event - mb : SetRel Event Event + po' := evts.po + [prePo: wellformed.po po'] + rf' : SetRel Event Event := ∅ + rfInst : wellformed.rf evts rf' + co' : SetRel Event Event := ∅ + [preCo : wellformed.co evts co'] + rmw' : SetRel Event Event := ∅ + [preRMW : wellformed.rmw evts rmw'] + wmb' : SetRel Event Event := ∅ + data' : SetRel Event Event := ∅ + addr' : SetRel Event Event := ∅ + ctrl' : SetRel Event Event := ∅ + fence' : SetRel Event Event := ∅ + mb' : SetRel Event Event := ∅ uniqueId : ∀ (e₁ e₂ : Event), e₁ ∈ evts.all → e₂ ∈ evts.all -> e₁ ≠ e₂ → e₁.id ≠ e₂.id - /-- CoWR: if a write w is program-order before a read r at the same location, - then r cannot observe a write older than w in coherence order. - Formally: the rf-source of r is either w itself or co-after w. -/ + -- Internal reads-from implies program order: if a write and its read + -- are on the same thread, the write must precede the read in po. + rfiPo : ∀ (w r : Event), + (w, r) ∈ rf' + → w.t_id = r.t_id + → (w, r) ∈ evts.po coWR : ∀ (w r w' : Event), (w, r) ∈ evts.po → w.effect.location = r.effect.location - → (w', r) ∈ rf - → (w, w') ∈ co ∨ w = w' + → (w', r) ∈ rf' + → (w, w') ∈ co' ∨ w = w' + +@[simp] abbrev CandidateExecution.po {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.po' +@[simp] abbrev CandidateExecution.rf {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.rf' +@[simp] abbrev CandidateExecution.co {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.co' +@[simp] abbrev CandidateExecution.rmw {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.rmw' +@[simp] abbrev CandidateExecution.wmb {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.wmb' +@[simp] abbrev CandidateExecution.data {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.data' +@[simp] abbrev CandidateExecution.addr {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.addr' +@[simp] abbrev CandidateExecution.ctrl {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.ctrl' +@[simp] abbrev CandidateExecution.fence {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.fence' +@[simp] abbrev CandidateExecution.mb {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.mb' /-- from-reads: always defined as rf⁻¹ ; co, so it is transparent to the kernel. -/ @[simp] def CandidateExecution.fr {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := - X.rf.inv.comp X.co + X.rf'.inv.comp X.co' + +/-- The `uniqueId` field of any `CandidateExecution`: since `event_id_unique` makes identity + determined solely by ID, any two distinct events must have distinct IDs. -/ +theorem uniqueId_by_id (evts : Events) : + ∀ (e₁ e₂ : Event), e₁ ∈ evts.all → e₂ ∈ evts.all → e₁ ≠ e₂ → e₁.id ≠ e₂.id := + fun _ _ _ _ hne hid => hne (Data.event_id_unique _ _ hid) + +/-- Tactic for proving the `rfiPo` and `coWR` obligations of a `CandidateExecution` + for concrete litmus tests with finite event sets. + + Strategy: unfold all set memberships with standard `Set` simp lemmas plus any + user-supplied lemmas (typically the `co` and `evts` `@[simp]` definitions), then + close by `omega` (handles numeric contradictions on IDs / thread IDs) with + `simp_all` as a pre-processing step when `omega` alone is insufficient. -/ +macro "candidateExecution_wf" "[" lemmas:Lean.Parser.Tactic.simpLemma,* "]" : tactic => + `(tactic| + (intros + simp only [Set.mem_insert_iff, Set.mem_singleton_iff, Prod.mk.injEq, + Events.po, Events.all, Set.mem_setOf_eq, $lemmas,*] at * + first + | omega + | aesop + | (simp_all [Set.mem_insert_iff, Set.mem_singleton_iff, + Events.all, $lemmas,*] <;> omega))) diff --git a/LeanCats/Linux/litmus.lean b/LeanCats/Linux/litmus.lean new file mode 100644 index 0000000..2e2e75d --- /dev/null +++ b/LeanCats/Linux/litmus.lean @@ -0,0 +1,140 @@ +import LeanCats.Basic +import LeanCats.Data +import LeanCats.Macro +import LeanCats.Theorems + +open Data + +namespace LinuxLitmus + +instance instWellformedPo (evts : Data.Events) : wellformed.po evts.po := by + intro x y z hxy hyz + rcases hxy with ⟨hx, hy, hxyTid, hxyLt⟩ + rcases hyz with ⟨_, hz, hyzTid, hyzLt⟩ + exact ⟨hx, hz, Eq.trans hxyTid hyzTid, Nat.lt_trans hxyLt hyzLt⟩ + +instance instWellformedRmwEmpty (evts : Data.Events) : wellformed.rmw evts (∅ : SetRel Event Event) := by + intro e h + exact False.elim h + +abbrev x := 0 + +@[simp] abbrev initOpX : Data.Effect := + Data.Effect.mk Data.Op.write x (some 0) true false +@[simp] abbrev wOpX : Data.Effect := + Data.Effect.mk Data.Op.write x (some 1) false false +@[simp] abbrev rOpX1 : Data.Effect := + Data.Effect.mk Data.Op.read x (some 1) false false +@[simp] abbrev rOpX0 : Data.Effect := + Data.Effect.mk Data.Op.read x (some 0) false false + +@[simp] abbrev initWx : Data.Event := + Data.Event.mk 100 10 initOpX ⟨lkmm.Accesses, lkmm.Accesses.ONCE'⟩ +@[simp] abbrev p0wX : Data.Event := + Data.Event.mk 1 0 wOpX ⟨lkmm.Accesses, lkmm.Accesses.ONCE'⟩ +@[simp] abbrev p1r0 : Data.Event := + Data.Event.mk 2 1 rOpX1 ⟨lkmm.Accesses, lkmm.Accesses.ONCE'⟩ +@[simp] abbrev p1r1 : Data.Event := + Data.Event.mk 3 2 rOpX0 ⟨lkmm.Accesses, lkmm.Accesses.ONCE'⟩ + +@[simp] abbrev corr_evts : Data.Events := + Data.Events.mk {initWx} {p1r0, p1r1} {initWx, p0wX} {} {} {} {} + +@[simp] def corr_co : SetRel Event Event := {(initWx, p0wX)} +@[simp] def corr_po : SetRel Event Event := {(p1r0, p1r1)} + +instance : wellformed.co corr_evts corr_co where + irrefl := by aesop + trans := by aesop + preco := { + wellTyped := by aesop + total := by candidateExecution_wf [] + } + +/-- C CoRR+poonceonce+Once + +P0: WRITE_ONCE(*x, 1) +P1: r0 = READ_ONCE(*x); r1 = READ_ONCE(*x) +Outcome: witness uses `r0=1 ∧ r1=0`. + +Chosen rf edges: `p0wX -> p1r0` and `initWx -> p1r1`. -/ +@[simp] def corr_rf : SetRel Event Event := {(p0wX, p1r0), (initWx, p1r1)} + +@[simp] def corr_rfInst : wellformed.rf corr_evts corr_rf := + Data.wellformed.rf.mk + (by + intro w r hrf + aesop (add simp [corr_rf, corr_evts, Events.all])) + (by + intro w₁ w₂ r h1 h2 + aesop (add simp [corr_rf])) + +def corr_test : CandidateExecution corr_evts := + @CandidateExecution.mk corr_evts + corr_evts + True + corr_po + (by candidateExecution_wf [corr_po]) + corr_rf + corr_rfInst + corr_co + (inferInstance : wellformed.co corr_evts corr_co) + (∅ : SetRel Event Event) + (instWellformedRmwEmpty corr_evts) + (∅ : SetRel Event Event) + (∅ : SetRel Event Event) + (∅ : SetRel Event Event) + (∅ : SetRel Event Event) + (∅ : SetRel Event Event) + (∅ : SetRel Event Event) + (uniqueId_by_id corr_evts) + (by candidateExecution_wf [corr_rf]) + (by + candidateExecution_wf [corr_rf, corr_co, corr_evts] + · rcases a with ⟨hwAll, _, htid, hlt⟩ + have hwCases : w = p0wX ∨ w = p1r0 ∨ w = p1r1 ∨ w = initWx := by + simpa [corr_evts, Events.all] using hwAll + exfalso + rcases hwCases with rfl | rfl | rfl | rfl + · cases htid + · exact False.elim (Nat.lt_irrefl _ hlt) + · cases htid + · cases htid + · rcases a with ⟨hwAll, _, htid, hlt⟩ + have hwCases : w = p0wX ∨ w = p1r0 ∨ w = p1r1 ∨ w = initWx := by + simpa [corr_evts, Events.all] using hwAll + exfalso + rcases hwCases with rfl | rfl | rfl | rfl + · cases htid + · cases htid + · exact False.elim (Nat.lt_irrefl _ hlt) + · cases htid) + +theorem corr_FindCycle : ¬ (lkmm.coherence corr_evts corr_test) := by + intro hacyc + let rel : SetRel Event Event := po_loc corr_evts corr_test ∪ lkmm.com corr_evts corr_test + have hrf0 : (initWx, p1r1) ∈ rel := by + right + left + simp [corr_test, corr_rf] + have hfr : (p1r1, p0wX) ∈ rel := by + right + right + right + refine ⟨initWx, ?_, ?_⟩ + · have hrf0' : (initWx, p1r1) ∈ corr_test.rf' := by + simpa [corr_test, corr_rf] using hrf0 + simpa [SetRel.inv] using hrf0' + · simp [corr_test, corr_co] + have hrf1 : (p0wX, p1r0) ∈ rel := by + right + left + simp [corr_test, corr_rf] + have hpo : (p1r0, p1r1) ∈ rel := by + left + exact ⟨by simp [corr_test, corr_po], by simp⟩ + have hcycle : Relation.TransGen (fun x y => (x, y) ∈ rel) p1r1 p1r1 := + .head hfr (.head hrf1 (.single hpo)) + exact hacyc p1r1 (by simpa [rel, lkmm.coherence] using hcycle) + +end LinuxLitmus diff --git a/LeanCats/Litmus.lean b/LeanCats/Litmus.lean index 750dbdd..7acee87 100644 --- a/LeanCats/Litmus.lean +++ b/LeanCats/Litmus.lean @@ -72,20 +72,13 @@ instance : wellformed.co evtsInput co where trans := by aesop preco := { wellTyped := by aesop - total := by - intro e₁ e₂ he₁ he₂ hloc hne - simp only [Set.mem_insert_iff, Set.mem_singleton_iff] at he₁ he₂ - rcases he₁ with rfl | rfl | rfl | rfl <;> - rcases he₂ with rfl | rfl | rfl | rfl <;> - simp_all [co] + total := by candidateExecution_wf [co] } @[simp] def test1 : CandidateExecution evtsInput := { prePo := instWellformedPo evtsInput - uniqueId := by - intro e₁ e₂ _ _ hne hid - exact hne (Data.event_id_unique e₁ e₂ hid) - rf := {(initWy, inst2readY), (initWx, inst4readX)} + uniqueId := uniqueId_by_id evtsInput + rf' := {(initWy, inst2readY), (initWx, inst4readX)} rfInst := { wellTyped := by intro w r hrf @@ -100,28 +93,23 @@ instance : wellformed.co evtsInput co where subst hw1 hw2 <;> first | rfl | exact absurd (hr1 ▸ hr2) (by decide) } - co := co - rmw := ∅ + co' := co + rmw' := ∅ preRMW := instWellformedRmwEmpty evtsInput - wmb := ∅ - mb := ∅ - data := ∅ - ctrl := ∅ - fence := ∅ - addr := ∅ - coWR := by - intro w r w' hpo hloc hrf - simp only [Events.po, Set.mem_setOf_eq] at hpo - simp only [Set.mem_insert_iff, Set.mem_singleton_iff, Prod.mk.injEq] at hrf - rcases hrf with ⟨hw', hr⟩ | ⟨hw', hr⟩ <;> - subst hr <;> - simp_all [co, evtsInput, Events.po] + wmb' := ∅ + mb' := ∅ + data' := ∅ + ctrl' := ∅ + fence' := ∅ + addr' := ∅ + rfiPo := by candidateExecution_wf [] + coWR := by candidateExecution_wf [co, evtsInput] } /-- The SB candidate execution has a cycle in `co ∪ rf ∪ fr ∪ po`: `inst1writeX →[po] inst2readY →[fr] inst3writeY →[po] inst4readX →[fr] inst1writeX` This witnesses that the execution is NOT SC-consistent. -/ -theorem FindCycle : ¬ CatRel.SetRel.Acyclic (test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po) := by +theorem FindCycle : ¬ CatRel.SetRel.Acyclic (test1.co' ∪ test1.rf' ∪ test1.fr ∪ test1.po') := by intro h apply h inst1writeX -- Prove each event is in evtsInput.all (needed for po membership) @@ -130,24 +118,24 @@ theorem FindCycle : ¬ CatRel.SetRel.Acyclic (test1.co ∪ test1.rf ∪ test1.fr have mem3 : inst3writeY ∈ evtsInput.all := by simp [Events.all] have mem4 : inst4readX ∈ evtsInput.all := by simp [Events.all] -- Step 1: inst1writeX →[po] inst2readY (same thread P0, id 1 < 2) - have h1po : (inst1writeX, inst2readY) ∈ test1.po := by + have h1po : (inst1writeX, inst2readY) ∈ test1.po' := by have h1evts : (inst1writeX, inst2readY) ∈ evtsInput.po := ⟨mem1, mem2, rfl, by decide⟩ simpa [test1] using h1evts - have h1 : (inst1writeX, inst2readY) ∈ test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po := Or.inr h1po + have h1 : (inst1writeX, inst2readY) ∈ test1.co' ∪ test1.rf' ∪ test1.fr ∪ test1.po' := Or.inr h1po -- Step 2: inst2readY →[fr] inst3writeY (via rf⁻¹;co, witness initWy) - have h2 : (inst2readY, inst3writeY) ∈ test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po := by + have h2 : (inst2readY, inst3writeY) ∈ test1.co' ∪ test1.rf' ∪ test1.fr ∪ test1.po' := by left; right simp only [test1] exact ⟨initWy, by simp, by simp [co]⟩ -- Step 3: inst3writeY →[po] inst4readX (same thread P1, id 3 < 4) - have h3po : (inst3writeY, inst4readX) ∈ test1.po := by + have h3po : (inst3writeY, inst4readX) ∈ test1.po' := by have h3evts : (inst3writeY, inst4readX) ∈ evtsInput.po := ⟨mem3, mem4, rfl, by decide⟩ simpa [test1] using h3evts - have h3 : (inst3writeY, inst4readX) ∈ test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po := Or.inr h3po + have h3 : (inst3writeY, inst4readX) ∈ test1.co' ∪ test1.rf' ∪ test1.fr ∪ test1.po' := Or.inr h3po -- Step 4: inst4readX →[fr] inst1writeX (via rf⁻¹;co, witness initWx) - have h4 : (inst4readX, inst1writeX) ∈ test1.co ∪ test1.rf ∪ test1.fr ∪ test1.po := by + have h4 : (inst4readX, inst1writeX) ∈ test1.co' ∪ test1.rf' ∪ test1.fr ∪ test1.po' := by left; right simp only [test1] exact ⟨initWx, by simp, by simp [co]⟩ @@ -194,21 +182,14 @@ instance : wellformed.co mp_evts mp_co where trans := by aesop preco := { wellTyped := by aesop - total := by - intro e₁ e₂ he₁ he₂ hloc hne - simp only [Set.mem_insert_iff, Set.mem_singleton_iff] at he₁ he₂ - rcases he₁ with rfl | rfl | rfl | rfl <;> - rcases he₂ with rfl | rfl | rfl | rfl <;> - simp_all [mp_co] + total := by candidateExecution_wf [mp_co] } -- rf: mp_readY sees y=1 from mp_writeY; mp_readX sees x=0 from mp_initWx @[simp] def mp_test : CandidateExecution mp_evts := { prePo := instWellformedPo mp_evts - uniqueId := by - intro e₁ e₂ _ _ hne hid - exact hne (Data.event_id_unique e₁ e₂ hid) - rf := {(mp_writeY, mp_readY), (mp_initWx, mp_readX)} + uniqueId := uniqueId_by_id mp_evts + rf' := {(mp_writeY, mp_readY), (mp_initWx, mp_readX)} rfInst := { wellTyped := by intro w r hrf @@ -223,29 +204,23 @@ instance : wellformed.co mp_evts mp_co where subst hw1 hw2 <;> first | rfl | exact absurd (hr1 ▸ hr2) (by decide) } - co := mp_co - rmw := ∅ + co' := mp_co + rmw' := ∅ preRMW := instWellformedRmwEmpty mp_evts - wmb := ∅ - mb := ∅ - data := ∅ - ctrl := ∅ - fence := ∅ - addr := ∅ - coWR := by - intro w r w' hpo hloc hrf - simp only [Events.po, Set.mem_setOf_eq] at hpo - simp only [Set.mem_insert_iff, Set.mem_singleton_iff, Prod.mk.injEq] at hrf - rcases hrf with ⟨hw', hr⟩ | ⟨hw', hr⟩ <;> - subst hr <;> - simp_all [mp_co, mp_evts, Events.all, Set.mem_insert_iff, Set.mem_singleton_iff] <;> - omega + wmb' := ∅ + mb' := ∅ + data' := ∅ + ctrl' := ∅ + fence' := ∅ + addr' := ∅ + rfiPo := by candidateExecution_wf [] + coWR := by candidateExecution_wf [mp_co, mp_evts] } /-- The MP candidate execution has a cycle in `co ∪ rf ∪ fr ∪ po`: `mp_writeX →[po] mp_writeY →[rf] mp_readY →[po] mp_readX →[fr] mp_writeX` Forbidden under SC. -/ -theorem mp_FindCycle : ¬ CatRel.SetRel.Acyclic (mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po) := by +theorem mp_FindCycle : ¬ CatRel.SetRel.Acyclic (mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr ∪ mp_test.po') := by intro h apply h mp_writeX have mem1 : mp_writeX ∈ mp_evts.all := by simp [Events.all] @@ -253,22 +228,22 @@ theorem mp_FindCycle : ¬ CatRel.SetRel.Acyclic (mp_test.co ∪ mp_test.rf ∪ m have mem3 : mp_readY ∈ mp_evts.all := by simp [Events.all] have mem4 : mp_readX ∈ mp_evts.all := by simp [Events.all] -- Step 1: mp_writeX →[po] mp_writeY (P0, id 101 < 102) - have h1po : (mp_writeX, mp_writeY) ∈ mp_test.po := by + have h1po : (mp_writeX, mp_writeY) ∈ mp_test.po' := by have h1evts : (mp_writeX, mp_writeY) ∈ mp_evts.po := ⟨mem1, mem2, rfl, by decide⟩ simpa [mp_test] using h1evts - have h1 : (mp_writeX, mp_writeY) ∈ mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po := Or.inr h1po + have h1 : (mp_writeX, mp_writeY) ∈ mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr ∪ mp_test.po' := Or.inr h1po -- Step 2: mp_writeY →[rf] mp_readY - have h2 : (mp_writeY, mp_readY) ∈ mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po := by + have h2 : (mp_writeY, mp_readY) ∈ mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr ∪ mp_test.po' := by left; left; right; simp -- Step 3: mp_readY →[po] mp_readX (P1, id 103 < 104) - have h3po : (mp_readY, mp_readX) ∈ mp_test.po := by + have h3po : (mp_readY, mp_readX) ∈ mp_test.po' := by have h3evts : (mp_readY, mp_readX) ∈ mp_evts.po := ⟨mem3, mem4, rfl, by decide⟩ simpa [mp_test] using h3evts - have h3 : (mp_readY, mp_readX) ∈ mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po := Or.inr h3po + have h3 : (mp_readY, mp_readX) ∈ mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr ∪ mp_test.po' := Or.inr h3po -- Step 4: mp_readX →[fr] mp_writeX (via rf⁻¹;co, witness mp_initWx) - have h4 : (mp_readX, mp_writeX) ∈ mp_test.co ∪ mp_test.rf ∪ mp_test.fr ∪ mp_test.po := by + have h4 : (mp_readX, mp_writeX) ∈ mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr ∪ mp_test.po' := by left; right simp only [mp_test] exact ⟨mp_initWx, by simp, by simp [mp_co]⟩ @@ -317,21 +292,14 @@ instance : wellformed.co lb_evts lb_co where trans := by aesop preco := { wellTyped := by aesop - total := by - intro e₁ e₂ he₁ he₂ hloc hne - simp only [Set.mem_insert_iff, Set.mem_singleton_iff] at he₁ he₂ - rcases he₁ with rfl | rfl | rfl | rfl <;> - rcases he₂ with rfl | rfl | rfl | rfl <;> - simp_all [lb_co] + total := by candidateExecution_wf [lb_co] } -- rf: lb_readX sees x=1 from lb_writeX; lb_readY sees y=1 from lb_writeY @[simp] def lb_test : CandidateExecution lb_evts := { prePo := instWellformedPo lb_evts - uniqueId := by - intro e₁ e₂ _ _ hne hid - exact hne (Data.event_id_unique e₁ e₂ hid) - rf := {(lb_writeX, lb_readX), (lb_writeY, lb_readY)} + uniqueId := uniqueId_by_id lb_evts + rf' := {(lb_writeX, lb_readX), (lb_writeY, lb_readY)} rfInst := { wellTyped := by intro w r hrf @@ -346,30 +314,24 @@ instance : wellformed.co lb_evts lb_co where subst hw1 hw2 <;> first | rfl | exact absurd (hr1 ▸ hr2) (by decide) } - co := lb_co - rmw := ∅ + co' := lb_co + rmw' := ∅ preRMW := instWellformedRmwEmpty lb_evts - wmb := ∅ - mb := ∅ - data := ∅ - ctrl := ∅ - fence := ∅ - addr := ∅ - coWR := by - intro w r w' hpo hloc hrf - simp only [Events.po, Set.mem_setOf_eq] at hpo - simp only [Set.mem_insert_iff, Set.mem_singleton_iff, Prod.mk.injEq] at hrf - rcases hrf with ⟨hw', hr⟩ | ⟨hw', hr⟩ <;> - subst hr <;> - simp_all [lb_co, lb_evts, Events.all, Set.mem_insert_iff, Set.mem_singleton_iff] <;> - omega + wmb' := ∅ + mb' := ∅ + data' := ∅ + ctrl' := ∅ + fence' := ∅ + addr' := ∅ + rfiPo := by candidateExecution_wf [] + coWR := by candidateExecution_wf [lb_co, lb_evts] } /-- The LB candidate execution has a cycle in `co ∪ rf ∪ fr ∪ po`: `lb_readX →[po] lb_writeY →[rf] lb_readY →[po] lb_writeX →[rf] lb_readX` The cycle uses only `po` and `rf` — no `fr` edges are needed. Forbidden under SC and TSO; allowed under ARM/POWER. -/ -theorem lb_FindCycle : ¬ CatRel.SetRel.Acyclic (lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po) := by +theorem lb_FindCycle : ¬ CatRel.SetRel.Acyclic (lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr ∪ lb_test.po') := by intro h apply h lb_readX have mem1 : lb_readX ∈ lb_evts.all := by simp [Events.all] @@ -377,22 +339,22 @@ theorem lb_FindCycle : ¬ CatRel.SetRel.Acyclic (lb_test.co ∪ lb_test.rf ∪ l have mem3 : lb_readY ∈ lb_evts.all := by simp [Events.all] have mem4 : lb_writeX ∈ lb_evts.all := by simp [Events.all] -- Step 1: lb_readX →[po] lb_writeY (P0, id 201 < 202) - have h1po : (lb_readX, lb_writeY) ∈ lb_test.po := by + have h1po : (lb_readX, lb_writeY) ∈ lb_test.po' := by have h1evts : (lb_readX, lb_writeY) ∈ lb_evts.po := ⟨mem1, mem2, rfl, by decide⟩ simpa [lb_test] using h1evts - have h1 : (lb_readX, lb_writeY) ∈ lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po := Or.inr h1po + have h1 : (lb_readX, lb_writeY) ∈ lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr ∪ lb_test.po' := Or.inr h1po -- Step 2: lb_writeY →[rf] lb_readY - have h2 : (lb_writeY, lb_readY) ∈ lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po := by + have h2 : (lb_writeY, lb_readY) ∈ lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr ∪ lb_test.po' := by left; left; right; simp -- Step 3: lb_readY →[po] lb_writeX (P1, id 203 < 204) - have h3po : (lb_readY, lb_writeX) ∈ lb_test.po := by + have h3po : (lb_readY, lb_writeX) ∈ lb_test.po' := by have h3evts : (lb_readY, lb_writeX) ∈ lb_evts.po := ⟨mem3, mem4, rfl, by decide⟩ simpa [lb_test] using h3evts - have h3 : (lb_readY, lb_writeX) ∈ lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po := Or.inr h3po + have h3 : (lb_readY, lb_writeX) ∈ lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr ∪ lb_test.po' := Or.inr h3po -- Step 4: lb_writeX →[rf] lb_readX - have h4 : (lb_writeX, lb_readX) ∈ lb_test.co ∪ lb_test.rf ∪ lb_test.fr ∪ lb_test.po := by + have h4 : (lb_writeX, lb_readX) ∈ lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr ∪ lb_test.po' := by left; left; right; simp exact .head h1 (.head h2 (.head h3 (.single h4))) diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index ee3ab8d..62b13b7 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -239,10 +239,10 @@ macro_rules `(@[simp] def $nm ($arg:ident : SetRel Event Event) := [expr| $e, $evts, $X, $arg]) | `([inst| $a:assertion $e as $nm:cat_ident, $evts, $X, $arg]) => do - `(def $nm := ([assertion| $a] ([expr| $e, $evts, $X, $arg]))) + `(@[simp] def $nm := ([assertion| $a] ([expr| $e, $evts, $X, $arg]))) | `([inst| ~$a:assertion $e as $nm:cat_ident, $evts, $X, $arg]) => do - `(def $nm := [assertion| $a] (¬[expr| $e, $evts, $X, $arg])) + `(@[simp] def $nm := [assertion| $a] (¬[expr| $e, $evts, $X, $arg])) | `([inst| enum $nm:cat_ident = $[ $tags:cat_ident ]||*, $_, $_, $_]) => do let nmIdent : TSyntax `ident := nm @@ -327,15 +327,15 @@ macro_rules @[simp] def range (evts : Events) (_ : CandidateExecution evts) (r : SetRel Event Event) := SetRel.cod r -@[simp] def po_loc (evts : Events) (X : CandidateExecution evts) := X.po ∩ CatRel.Rel.location +@[simp] def po_loc (evts : Events) (X : CandidateExecution evts) := X.po' ∩ CatRel.Rel.location @[simp] def fre (evts : Events) (X : CandidateExecution evts) := X.fr ∩ CatRel.Rel.external -@[simp] def rfe (evts : Events) (X : CandidateExecution evts) := X.rf ∩ CatRel.Rel.external +@[simp] def rfe (evts : Events) (X : CandidateExecution evts) := X.rf' ∩ CatRel.Rel.external -@[simp] def rfi (evts : Events) (X : CandidateExecution evts) := X.rf ∩ CatRel.Rel.internal +@[simp] def rfi (evts : Events) (X : CandidateExecution evts) := X.rf' ∩ CatRel.Rel.internal -@[simp] def coe (evts : Events) (X : CandidateExecution evts) := X.co ∩ CatRel.Rel.external +@[simp] def coe (evts : Events) (X : CandidateExecution evts) := X.co' ∩ CatRel.Rel.external @[simp] def int (evts : Events) (_ : CandidateExecution evts) := CatRel.Rel.internal diff --git a/LeanCats/Theorems.lean b/LeanCats/Theorems.lean index 8cb87fc..dd698ab 100644 --- a/LeanCats/Theorems.lean +++ b/LeanCats/Theorems.lean @@ -172,6 +172,19 @@ lemma ayclicMono apply htransub exact hr₁trans +/-- Generalisation of `ayclicMono`: acyclicity transfers when every edge of r₁ + is reachable (possibly in multiple steps) in r₂. -/ +lemma ayclicMono_trans + {r₁ r₂ : SetRel Event Event} + (hacyc : SetRel.Acyclic r₂) + (hsub : ∀ a b, (a, b) ∈ r₁ → (a, b) ∈ SetRel.TransGen r₂) : + SetRel.Acyclic r₁ := by + unfold SetRel.Acyclic at * + intro a ha + simp only [SetRel.TransGen, Set.mem_setOf_eq] at * + apply hacyc a + exact Relation.TransGen.closed (fun x y h => hsub x y h) ha + --- tso : Relation.TransGen --- (Rel.po evts ∩ (prod W W ∪ prod R (R ∪ W)) ∪ union (external evts ∪ Rel.rf evts) (co evts ∪ Rel.fr evts co)) x x --- ⊢ Relation.TransGen (fun x y => (Rel.rf evts x y ∨ co evts x y ∨ Rel.fr evts co x y) ∨ Rel.po evts x y) ?x ?x @@ -186,9 +199,9 @@ theorem rf_fr_subset_co {evts : Events} (X : CandidateExecution evts) (w r w' : Event) - (hrf : (w, r) ∈ X.rf) + (hrf : (w, r) ∈ X.rf') (hfr : (r, w') ∈ X.fr) : - (w, w') ∈ X.co := by + (w, w') ∈ X.co' := by simp only [CandidateExecution.fr] at hfr obtain ⟨w₁, h₁, h₂⟩ := hfr simp only [SetRel.inv] at h₁ @@ -199,8 +212,8 @@ theorem rf_fr_subset_co theorem co_acyclic {evts : Events} (X : CandidateExecution evts) : - SetRel.Acyclic X.co := by - let r : Rel Event Event := fun e₁ e₂ => (e₁, e₂) ∈ X.co + SetRel.Acyclic X.co' := by + let r : Rel Event Event := fun e₁ e₂ => (e₁, e₂) ∈ X.co' have hiso : IsStrictOrder Event r := { irrefl := fun e h => X.preCo.irrefl e h trans := fun a b c hab hbc => X.preCo.trans a b c hab hbc } @@ -216,7 +229,7 @@ theorem fr_co_subset_fr (X : CandidateExecution evts) (r w w' : Event) (hfr : (r, w) ∈ X.fr) - (hco : (w, w') ∈ X.co) : + (hco : (w, w') ∈ X.co') : (r, w') ∈ X.fr := by simp only [CandidateExecution.fr] at * obtain ⟨w₀, h_inv, h_co⟩ := hfr diff --git a/LeanCats/X86WeakerThanLKMM.lean b/LeanCats/X86WeakerThanLKMM.lean deleted file mode 100644 index a9ab561..0000000 --- a/LeanCats/X86WeakerThanLKMM.lean +++ /dev/null @@ -1,19 +0,0 @@ -import LeanCats.Macro -import LeanCats.Data -import LeanCats.Relations -import LeanCats.Theorems -import LeanCats.Basic - -/-- x86/TSO is weaker than LKMM: any execution consistent under x86/TSO is also - consistent under LKMM. x86 operations are plain R/W with no LKMM access-type - annotations, so the annotation-dependent LKMM constraints (happens_before, - propagation) are vacuously satisfied, and coherence is the meaningful obligation. -/ -theorem tso_x86_subset_lkmm - (evts : Data.Events) - (X : CandidateExecution evts) - (hTso : tso_x86.tso evts X) : - lkmm.coherence evts X ∧ - lkmm.atomic evts X ∧ - lkmm.happens_before evts X ∧ - lkmm.propagation evts X := by - sorry diff --git a/LeanCats/X86WeakerThanLKMM.lean.test b/LeanCats/X86WeakerThanLKMM.lean.test new file mode 100644 index 0000000..c53bf1b --- /dev/null +++ b/LeanCats/X86WeakerThanLKMM.lean.test @@ -0,0 +1,640 @@ +import LeanCats.Macro +import LeanCats.Data +import LeanCats.Relations +import LeanCats.Theorems +import LeanCats.Basic + +open CatRel Data Relation + +-- ════════════════════════════════════════════════════════════════ +-- § Helper definitions +-- ════════════════════════════════════════════════════════════════ + +private abbrev tso_rel {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := + tso_x86.implied evts X ∪ tso_x86.xppo evts X ∪ rfe evts X ∪ X.fr ∪ X.co + +private abbrev coh_rel {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := + po_loc evts X ∪ X.rf ∪ X.co ∪ X.fr + +private abbrev tso_step {evts : Events} (X : CandidateExecution evts) := + fun (a b : Event) => (a, b) ∈ tso_rel X + +private abbrev coh_step {evts : Events} (X : CandidateExecution evts) := + fun (a b : Event) => (a, b) ∈ coh_rel X + +-- ════════════════════════════════════════════════════════════════ +-- § Lemmas: coherence edges into TSO +-- ════════════════════════════════════════════════════════════════ + +private lemma co_tso {evts : Events} (X : CandidateExecution evts) + {a b : Event} (h : (a, b) ∈ X.co) : tso_step X a b := + Or.inr h + +private lemma fr_tso {evts : Events} (X : CandidateExecution evts) + {a b : Event} (h : (a, b) ∈ X.fr) : tso_step X a b := + Or.inl (Or.inl (Or.inl (Or.inr h))) + +private lemma rfe_tso {evts : Events} (X : CandidateExecution evts) + {a b : Event} (hrf : (a, b) ∈ X.rf) (hext : a.t_id ≠ b.t_id) : + tso_step X a b := by + apply Or.inl; apply Or.inl; apply Or.inr + simp [rfe, Rel.external, Rel.internal] + exact ⟨hrf, hext⟩ + +private lemma xppo_tso {evts : Events} (X : CandidateExecution evts) + {a b : Event} (hpo : (a, b) ∈ X.po) + (hk : (a.effect.op = Op.write ∧ b.effect.op = Op.write) ∨ + (a.effect.op = Op.read ∧ b.effect.op = Op.write) ∨ + (a.effect.op = Op.read ∧ b.effect.op = Op.read)) : + tso_step X a b := by + apply Or.inl; apply Or.inl; apply Or.inl; apply Or.inr + simp [tso_x86.xppo, CatRel.prod, Set.prod, CatRel.W, CatRel.R, + CatRel.CatUnion.union, SetRel.union, Data.writes, Data.reads] + exact ⟨by rcases hk with ⟨h1,h2⟩|⟨h1,h2⟩|⟨h1,h2⟩ + · exact Or.inl ⟨h1,h2⟩ + · exact Or.inr (Or.inl ⟨h1,h2⟩) + · exact Or.inr (Or.inr ⟨h1,h2⟩), hpo⟩ + +-- ════════════════════════════════════════════════════════════════ +-- § rf/co helpers +-- ════════════════════════════════════════════════════════════════ + +private lemma rf_is_write {evts : Events} (X : CandidateExecution evts) + {w r : Event} (h : (w, r) ∈ X.rf) : w.effect.op = Op.write := + (X.rfInst.wellTyped w r h).1 + +private lemma rf_is_read {evts : Events} (X : CandidateExecution evts) + {w r : Event} (h : (w, r) ∈ X.rf) : r.effect.op = Op.read := + (X.rfInst.wellTyped w r h).2.1 + +private lemma rf_same_loc {evts : Events} (X : CandidateExecution evts) + {w r : Event} (h : (w, r) ∈ X.rf) : w.effect.location = r.effect.location := + (X.rfInst.wellTyped w r h).2.2.1 + +private lemma co_is_write_left {evts : Events} (X : CandidateExecution evts) + {a b : Event} (h : (a, b) ∈ X.co) : a.effect.op = Op.write := + (X.preCo.preco.wellTyped a b h).2.2.1 + +private lemma co_is_write_right {evts : Events} (X : CandidateExecution evts) + {a b : Event} (h : (a, b) ∈ X.co) : b.effect.op = Op.write := + (X.preCo.preco.wellTyped a b h).2.2.2.1 + +-- fr source is a read (since fr = rf⁻¹;co, the source is an rf target) +private lemma fr_is_read {evts : Events} (X : CandidateExecution evts) + {r w : Event} (h : (r, w) ∈ X.fr) : r.effect.op = Op.read := by + simp only [CandidateExecution.fr] at h + obtain ⟨w', hinv, _⟩ := h + simp only [SetRel.inv] at hinv + exact rf_is_read X hinv + +private lemma fr_is_write {evts : Events} (X : CandidateExecution evts) + {r w : Event} (h : (r, w) ∈ X.fr) : w.effect.op = Op.write := by + simp only [CandidateExecution.fr] at h + obtain ⟨w', _, hco⟩ := h + exact co_is_write_right X hco + +-- coWR-based: po(w,r) + sameloc + fr(r,w') → co(w,w') +private lemma po_fr_to_co {evts : Events} (X : CandidateExecution evts) + {w r w' : Event} + (hpo : (w, r) ∈ X.po) (hloc : w.effect.location = r.effect.location) + (hfr : (r, w') ∈ X.fr) : (w, w') ∈ X.co := by + simp only [CandidateExecution.fr] at hfr + obtain ⟨w'', hinv, hco⟩ := hfr + simp only [SetRel.inv] at hinv + rcases X.coWR w r w'' hpo hloc hinv with h | h + · exact X.preCo.trans w w'' w' h hco + · rw [h]; exact hco + +-- ════════════════════════════════════════════════════════════════ +-- § State machine for processing a coherence path +-- +-- We process a TransGen(coh) path starting from a write. +-- The "state" tracks whether we have a pending write-to-read +-- edge that hasn't been emitted as a TSO step yet. +-- +-- Invariant I(origin, w, cur): +-- TransGen(tso) origin w ∧ po(w, cur) ∧ sameloc ∧ w∈W ∧ cur∈R +-- (or w = cur if cur is a write and TransGen(tso) origin cur) +-- ════════════════════════════════════════════════════════════════ + +/-- The "state" after processing a prefix of the coherence path. + - `origin` is the starting write of the cycle. + - `AtWrite w`: we have TransGen(tso) origin w, and w is the current position (a write). + - `PendingRead w r`: we have TransGen(tso) origin w, with po(w,r), sameloc, w∈W, r∈R. + r is the current position (a read). + Note: in both cases, if origin = w, we use TransGen (at least one step). + We handle the "no steps yet" case separately. -/ + +-- For the formal proof, we track: given TransGen(coh) a b with a a write, +-- produce (w, state) where state tracks whether b is write-reachable +-- via TransGen(tso) or pending. + +-- Let's simplify and just prove: TransGen(coh) a a with a a write → False. + +-- We'll do this by defining a well-founded measure on events. +-- The measure: the unique id of the event, which decreases along po. +-- But co/fr/rf can go in any direction w.r.t. ids... + +-- OK let me just code it up directly with TransGen induction. + +-- The main lemma uses the following state: +-- Given TransGen(coh) w₀ b with w₀ a write, +-- ∃ w, (w₀ = w ∨ TransGen(tso) w₀ w) ∧ +-- (w = b ∨ (po(w,b) ∧ sameloc ∧ w∈W ∧ b∈R)) + +-- I.e., either we've reached b via tso, or we have a pending write w +-- po-before b. + +-- State type +private inductive PathState {evts : Events} (X : CandidateExecution evts) (origin : Event) (cur : Event) : Prop where + | atWrite (w : Event) : + (origin = w ∨ TransGen (tso_step X) origin w) → + w = cur → + PathState X origin cur + | pendingRead (w : Event) : + (origin = w ∨ TransGen (tso_step X) origin w) → + (w, cur) ∈ X.po → + w.effect.location = cur.effect.location → + w.effect.op = Op.write → + cur.effect.op = Op.read → + PathState X origin cur + +-- Process a single coh edge from PathState +private lemma step_state {evts : Events} (X : CandidateExecution evts) + {origin cur next : Event} + (hstate : PathState X origin cur) + (hedge : coh_step X cur next) : + PathState X origin next := by + simp only [coh_step, coh_rel, CatRel.CatUnion.union, SetRel.union, Set.mem_setOf_eq] at hedge + rcases hstate with ⟨w, hw_reach, hw_eq⟩ | ⟨w, hw_reach, hpo_wc, hloc_wc, hw_write, hc_read⟩ + · -- AtWrite: w = cur, cur is a write + subst hw_eq + rcases hedge with ((hpo_loc | hrf) | hco) | hfr + · -- po_loc(w, next) + simp only [po_loc, Set.mem_inter_iff, Rel.location, Set.mem_setOf_eq] at hpo_loc + obtain ⟨hpo, hloc⟩ := hpo_loc + -- w is a write. What is next? + -- We don't know next's op directly. Let's case-split. + by_cases hn_read : next.effect.op = Op.read + · -- W*R po_loc: pending state + exact PathState.pendingRead w hw_reach hpo hloc hw_write hn_read + · -- W*W (or fence/branch, but we treat as write for xppo) + have hn_write : next.effect.op = Op.write := by + cases next.effect.op <;> simp_all + have htso := xppo_tso X hpo (Or.inl ⟨hw_write, hn_write⟩) + exact PathState.atWrite next + (Or.inr (hw_reach.elim (fun h => h ▸ TransGen.single htso) + (fun h => h.tail htso))) + rfl + · -- rf(w, next): w is write, next is read + have hn_read := rf_is_read X hrf + by_cases hext : w.t_id ≠ next.t_id + · -- rfe: tso step, arrive at read + -- After rfe, next is a read. We don't have a pending write po-before next. + -- But we DO have TransGen(tso) origin next. + -- We need to continue from next (a read). + -- From a read, the next coh edge will be fr or po_loc (all in tso from a read). + -- But our state needs to be either AtWrite or PendingRead. + -- next is a read, so we can't be AtWrite. + -- For PendingRead, we'd need a write po-before next at same loc. + -- We have rf(w, next) which is rfe. w and next are different threads. + -- So po(w, next) is false (po requires same thread). + -- Hmm. We need a different state for "arrived at read via rfe". + -- But every edge from a read is in tso, so the next step will resolve this. + -- Let me handle this by making AtWrite work for reads too, or adding a third state. + -- Actually: after rfe(w, next), the next edge from next (a read) will be + -- fr, po_loc(RW), or po_loc(RR). All are xppo or fr, i.e., tso steps. + -- So we can EAGERLY look ahead... but we're processing one edge at a time. + -- Let me add an AtRead state. + sorry + · -- rfi: pending state via rfiPo + push_neg at hext + have hpo := X.rfiPo w next hrf hext + exact PathState.pendingRead w hw_reach hpo (rf_same_loc X hrf) hw_write hn_read + · -- co(w, next): tso step + have hn_write := co_is_write_right X hco + exact PathState.atWrite next + (Or.inr (hw_reach.elim (fun h => h ▸ TransGen.single (co_tso X hco)) + (fun h => h.tail (co_tso X hco)))) + rfl + · -- fr(w, next): w must be a read for fr, but w is a write. Contradiction. + have := fr_is_read X hfr + simp [hw_write] at this + · -- PendingRead: w is a write, cur is a read, po(w, cur), sameloc + rcases hedge with ((hpo_loc | hrf) | hco) | hfr + · -- po_loc(cur, next): cur is read + simp only [po_loc, Set.mem_inter_iff, Rel.location, Set.mem_setOf_eq] at hpo_loc + obtain ⟨hpo_cn, hloc_cn⟩ := hpo_loc + by_cases hn_read : next.effect.op = Op.read + · -- R*R po_loc: extend pending state + have hpo_wn := X.prePo w cur next hpo_wc hpo_cn + have hloc_wn : w.effect.location = next.effect.location := by + rw [hloc_wc, hloc_cn] + exact PathState.pendingRead w hw_reach hpo_wn hloc_wn hw_write hn_read + · -- R*W po_loc: resolve pending → xppo(WW) + have hn_write : next.effect.op = Op.write := by + cases next.effect.op <;> simp_all + have hpo_wn := X.prePo w cur next hpo_wc hpo_cn + have htso := xppo_tso X hpo_wn (Or.inl ⟨hw_write, hn_write⟩) + exact PathState.atWrite next + (Or.inr (hw_reach.elim (fun h => h ▸ TransGen.single htso) + (fun h => h.tail htso))) + rfl + · -- rf(cur, next): cur is read, rf needs write source. Contradiction. + have := rf_is_write X hrf + simp [hc_read] at this + · -- co(cur, next): cur is read, co needs write source. Contradiction. + have := co_is_write_left X hco + simp [hc_read] at this + · -- fr(cur, next): resolve pending → co via coWR + have hn_write := fr_is_write X hfr + have hco := po_fr_to_co X hpo_wc hloc_wc hfr + have htso := co_tso X hco + exact PathState.atWrite next + (Or.inr (hw_reach.elim (fun h => h ▸ TransGen.single htso) + (fun h => h.tail htso))) + rfl + +-- Ugh, the rfe case breaks the two-state machine. Let me add a third state. + +-- Actually, I realize there's a much simpler fix: after rfe(w, next), +-- I can look at the rf(w, next) and create a "virtual" pending state. +-- Even though w and next are on different threads, w.loc = next.loc (from rf), +-- and we just need to handle the next edge from next appropriately. +-- BUT: PendingRead requires po(w, next), which needs same thread. +-- And coWR also requires po. + +-- The key realization: after rfe, the read `next` is on a DIFFERENT thread from w. +-- We've emitted a tso step (rfe) to reach `next`. +-- From `next`, every edge is in tso (since next is a read). +-- So we can treat `next` as if we're at AtWrite(next) except next is a read. +-- The issue is AtWrite assumes cur is a write. + +-- Let me generalize AtWrite to AtEvent, allowing reads too. +-- Then from AtEvent(cur) with cur a read, the next edge is: +-- - fr: tso step → AtEvent(next) with next a write +-- - po_loc(R,W): xppo(RW) tso step → AtEvent(next) write +-- - po_loc(R,R): xppo(RR) tso step → AtEvent(next) read (stays AtEvent) +-- - rf: impossible (cur is read) +-- - co: impossible (cur is read) + +-- For the cycle closure: if cur = origin and origin is a write, +-- AtEvent with cur being origin works. +-- PendingRead with cur being origin: origin is a write, but PendingRead needs cur∈R. Contradiction. + +-- So let me redefine the state: + +end -- close the section to redefine + +-- Clean restart with three cases + +-- I'll use a simple two-state approach: +-- State1: ∃ w, (origin=w ∨ TG tso origin w) ∧ w=cur [at any event] +-- State2: ∃ w, (origin=w ∨ TG tso origin w) ∧ po(w,cur) ∧ sameloc ∧ w∈W ∧ cur∈R [pending] + +-- State1 at origin (a write) → TG tso origin origin → contradiction with hTso +-- State2 at origin → origin∈R, but origin∈W → contradiction + +-- Processing edges from State1 (cur = w): +-- co(w,next): TG tso step → State1(next) +-- fr(w,next): w must be read (fr source is read). If w is write → impossible unless... +-- Actually w can be anything in State1. If w is a write, fr(w,next) means write is in fr. +-- fr = rf⁻¹;co, source of fr is rf target = read. So w must be read. If w is write, impossible. +-- If w is a read: fr → tso step → State1(next) +-- po_loc(w,next): +-- If w is write and next is read: → State2(w, next) +-- If w is write and next is write: xppo(WW) → State1(next) +-- If w is read and next is write: xppo(RW) → State1(next) +-- If w is read and next is read: xppo(RR) → State1(next) +-- rf(w,next): +-- w must be write (rf source). +-- next is read (rf target). +-- If same thread (rfi): rfiPo → po(w,next) → State2(w, next) +-- If diff thread (rfe): tso step → State1(next) [next is a read, that's OK for State1] + +-- Processing edges from State2 (pending write w, cur is read): +-- fr(cur,next): po_fr_to_co → co(w,next) → tso step → State1(next) +-- po_loc(cur,next): +-- If next is read: RR, extend pending: po(w,next) by transitivity → State2(w, next) +-- If next is write: RW, po(w,next) by transitivity → xppo(WW) → State1(next) +-- rf(cur,next): cur is read, rf source must be write → impossible +-- co(cur,next): cur is read, co source must be write → impossible + +-- This works! State1 allows cur to be any event type. The only issue is: +-- At cycle closure (cur = origin, a write): +-- State1: origin = w ∨ TG tso origin w. Since w = cur = origin: origin = origin (trivial) or TG tso origin origin. +-- If origin = origin: no tso steps emitted. Need to show this can't happen in a cycle. +-- If TG tso origin origin: contradiction with hTso. +-- State2: origin ∈ R but origin ∈ W: contradiction. + +-- For the "no tso steps" case: this means every edge in the cycle was +-- either rfi (→ State2) or WR po_loc (→ State2). But State2 is resolved only by +-- fr or RW po_loc, which emit tso steps. So if no tso steps were emitted, +-- the cycle never entered State2. But rfi and WR po_loc both go to State2. +-- So the cycle has NO rfi and NO WR po_loc edges. +-- The remaining edges (co, fr, rfe, WW/RW/RR po_loc) all emit tso steps. +-- So every edge emits a tso step → at least one step. +-- But we said no tso steps... contradiction with the cycle having ≥1 edge. + +-- More precisely: in a cycle (TransGen, so ≥1 edge), every edge either emits a tso step +-- or goes to State2 (which will be resolved by a later tso-emitting edge). +-- If the very first edge emits a tso step, we're done (TG tso origin w ≠ origin). +-- If the very first edge goes to State2, then some later edge resolves it (emitting tso). + +-- Formally, I'll track (origin = w ∨ TransGen tso origin w) and show that for cycles, +-- it must be the TransGen case. + +-- Actually, a simpler approach: track ReflTransGen instead of (= ∨ TransGen), +-- and separately argue that the result is TransGen (non-trivial). +-- To argue non-triviality: the cycle has ≥1 edge. If the final state is State1(origin) +-- with origin = w, then ReflTransGen tso origin origin. But we need TransGen. +-- We can show: if the cycle has ≥2 edges, at least one emits a tso step. +-- Or: handle the 1-edge cycle separately. + +-- 1-edge cycle: TransGen.single (coh_step origin origin). So (origin, origin) ∈ coh_rel. +-- - co(origin, origin): co irreflexive → impossible. +-- - fr(origin, origin): fr = rf⁻¹;co. rf(w, origin), co(w, origin) for some w. +-- But also rf(w, origin) means origin is a read. origin is a write → impossible. +-- - rf(origin, origin): rf(origin, origin) means origin is both write and read → impossible +-- (or: rf well-typed says r.id ≠ w.id, so origin.id ≠ origin.id → impossible). +-- - po_loc(origin, origin): po(origin, origin) means origin.id < origin.id → impossible. +-- So 1-edge cycles are impossible. + +-- So for ≥2 edges, at least one edge emits a tso step. + +-- Great! Let me implement this. + +private inductive CohState {evts : Events} (X : CandidateExecution evts) + (origin : Event) (cur : Event) : Prop where + | atEvent (w : Event) : + ReflTransGen (tso_step X) origin w → + w = cur → + CohState X origin cur + | pendingRead (w : Event) : + ReflTransGen (tso_step X) origin w → + (w, cur) ∈ X.po → + w.effect.location = cur.effect.location → + w.effect.op = Op.write → + cur.effect.op = Op.read → + CohState X origin cur + +private lemma coh_state_step {evts : Events} (X : CandidateExecution evts) + {origin cur next : Event} + (hstate : CohState X origin cur) + (hedge : coh_step X cur next) : + CohState X origin next := by + simp only [coh_step, coh_rel, CatRel.CatUnion.union, SetRel.union, Set.mem_setOf_eq] at hedge + rcases hstate with ⟨w, hw_path, hw_eq⟩ | ⟨w, hw_path, hpo_wc, hloc_wc, hw_write, hc_read⟩ + · -- AtEvent: w = cur + subst hw_eq + rcases hedge with ((hpo_loc | hrf) | hco) | hfr + · -- po_loc + simp only [po_loc, Set.mem_inter_iff, Rel.location, Set.mem_setOf_eq] at hpo_loc + obtain ⟨hpo, hloc⟩ := hpo_loc + by_cases hw_w : w.effect.op = Op.write + · by_cases hn_r : next.effect.op = Op.read + · exact CohState.pendingRead w hw_path hpo hloc hw_w hn_r + · have hn_w : next.effect.op = Op.write := by cases next.effect.op <;> simp_all + exact CohState.atEvent next (hw_path.tail (xppo_tso X hpo (Or.inl ⟨hw_w, hn_w⟩))) rfl + · -- w is a read (or fence/branch, treat as read for xppo) + by_cases hw_r : w.effect.op = Op.read + · by_cases hn_w : next.effect.op = Op.write + · exact CohState.atEvent next (hw_path.tail (xppo_tso X hpo (Or.inr (Or.inl ⟨hw_r, hn_w⟩)))) rfl + · by_cases hn_r : next.effect.op = Op.read + · exact CohState.atEvent next (hw_path.tail (xppo_tso X hpo (Or.inr (Or.inr ⟨hw_r, hn_r⟩)))) rfl + · -- next is fence/branch, treat as write + have hn_w : next.effect.op = Op.write := by cases next.effect.op <;> simp_all + exact CohState.atEvent next (hw_path.tail (xppo_tso X hpo (Or.inr (Or.inl ⟨hw_r, hn_w⟩)))) rfl + · -- w is fence/branch, treat as write for xppo + have hw_w : w.effect.op = Op.write := by cases w.effect.op <;> simp_all + exact absurd hw_w hw_w -- contradiction with hw_w above + -- Actually: hw_w was ¬(w.effect.op = Op.write) and we derived w.effect.op = Op.write. + -- That's the contradiction. + · -- rf + by_cases hext : w.t_id ≠ next.t_id + · exact CohState.atEvent next (hw_path.tail (rfe_tso X hrf hext)) rfl + · push_neg at hext + have hw_w := rf_is_write X hrf + have hn_r := rf_is_read X hrf + exact CohState.pendingRead w hw_path (X.rfiPo w next hrf hext) + (rf_same_loc X hrf) hw_w hn_r + · -- co + exact CohState.atEvent next (hw_path.tail (co_tso X hco)) rfl + · -- fr + exact CohState.atEvent next (hw_path.tail (fr_tso X hfr)) rfl + · -- PendingRead: w is write, cur is read, po(w, cur), sameloc + rcases hedge with ((hpo_loc | hrf) | hco) | hfr + · -- po_loc(cur, next) + simp only [po_loc, Set.mem_inter_iff, Rel.location, Set.mem_setOf_eq] at hpo_loc + obtain ⟨hpo_cn, hloc_cn⟩ := hpo_loc + by_cases hn_r : next.effect.op = Op.read + · -- RR: extend pending + exact CohState.pendingRead w hw_path + (X.prePo w cur next hpo_wc hpo_cn) + (by rw [hloc_wc, hloc_cn]) hw_write hn_r + · -- RW: resolve pending + have hn_w : next.effect.op = Op.write := by cases next.effect.op <;> simp_all + have hpo_wn := X.prePo w cur next hpo_wc hpo_cn + exact CohState.atEvent next + (hw_path.tail (xppo_tso X hpo_wn (Or.inl ⟨hw_write, hn_w⟩))) rfl + · -- rf(cur, next): cur is read, rf source is write → contradiction + have := rf_is_write X hrf + simp [hc_read] at this + · -- co(cur, next): cur is read, co source is write → contradiction + have := co_is_write_left X hco + simp [hc_read] at this + · -- fr(cur, next): resolve pending via coWR + have hco := po_fr_to_co X hpo_wc hloc_wc hfr + exact CohState.atEvent next (hw_path.tail (co_tso X hco)) rfl + +-- Apply the state machine to the full TransGen path +private lemma coh_transgen_state {evts : Events} (X : CandidateExecution evts) + {a b : Event} + (hpath : TransGen (coh_step X) a b) : + CohState X a b := by + have hinit : CohState X a a := CohState.atEvent a ReflTransGen.refl rfl + induction hpath with + | single h => exact coh_state_step X hinit h + | tail _ hlast ih => exact coh_state_step X ih hlast + +-- The key: for a cycle, extract TransGen(tso) from the state +private lemma coh_cycle_gives_tso_cycle {evts : Events} (X : CandidateExecution evts) + {a : Event} + (hcycle : TransGen (coh_step X) a a) + (ha_write : a.effect.op = Op.write) : + TransGen (tso_step X) a a := by + have hstate := coh_transgen_state X hcycle + rcases hstate with ⟨w, hw_path, hw_eq⟩ | ⟨w, hw_path, hpo, _, _, ha_read⟩ + · -- AtEvent: w = a, ReflTransGen(tso) a w = a + subst hw_eq + -- hw_path : ReflTransGen (tso_step X) a a + -- We need TransGen. ReflTransGen is either refl or TransGen. + rcases hw_path.cases_head with h | ⟨b, hab, hba⟩ + · -- Refl: no tso steps emitted. Show this is impossible. + -- In a cycle with ≥1 edge, at least one step must emit a tso edge. + -- A 1-edge cycle is impossible (shown below). + -- For ≥2 edges, at least one emits a tso step. + -- But we need a cleaner argument. Let's show the 1-edge case is impossible. + exfalso + -- The cycle has ≥1 edge. Process the first edge. + -- If the first edge emits a tso step, ReflTransGen would be non-trivial. + -- So the first edge must NOT emit a tso step. + -- Non-tso-emitting edges from a write: rfi (→ PendingRead), WR po_loc (→ PendingRead) + -- After PendingRead, the state is resolved by a later edge (emitting tso). + -- But the resolved state has a non-trivial ReflTransGen. Contradiction with hw_path being refl. + -- Let me show this more carefully. + -- Since ReflTransGen(tso) a a is exactly refl (no steps), every edge in the cycle + -- is "absorbed" without emitting tso. But: + -- From AtEvent(a), first edge: + -- rfi → PendingRead(a, r) with ReflTransGen.refl + -- WR po_loc → PendingRead(a, r) with ReflTransGen.refl + -- Any other → emits tso step, ReflTransGen ≠ refl. Contradiction. + -- From PendingRead, the edge must be fr or RW po_loc or RR po_loc. + -- fr → emits tso step (co via coWR). Contradiction. + -- RW po_loc → emits tso step (xppo). Contradiction. + -- RR po_loc → PendingRead with same ReflTransGen. No emission. + -- So the only non-emitting pattern is: (rfi or WR po_loc), then RR po_loc*, then... + -- But eventually we must return to a (a write). The exit from PendingRead to a write + -- is either fr or RW po_loc, both of which emit. + -- So at least one tso step is emitted. Contradiction. + -- Formal proof: induction on the cycle. + -- Actually, this is complex. Let me prove it differently. + -- Claim: coh_rel has no self-loops. I.e., (a,a) ∉ coh_rel. + -- Then a 1-edge cycle is impossible. + -- For ≥2 edges, I'll show the state machine always emits ≥1 tso step. + -- But this requires a separate induction... + -- Alternatively: use a DIFFERENT invariant that tracks TransGen directly. + -- I.e., after at least one tso-emitting edge, switch to TransGen. + -- This is doable but requires more cases. + -- For now, let me prove it by showing that a ReflTransGen.refl result + -- from a cycle is impossible. + -- Process the cycle: TransGen(coh) a a. Unfold the first step. + rcases hcycle with ⟨h_single⟩ | @⟨_, mid, hrest, hlast⟩ + · -- Single edge: (a, a) ∈ coh_rel. + -- co(a,a): irrefl. fr(a,a): need rf(w,a) and co(w,a), but also a is write. + -- rf(a,a): a.id ≠ a.id. po_loc(a,a): a.id < a.id. + simp only [coh_step, coh_rel, CatRel.CatUnion.union, SetRel.union, Set.mem_setOf_eq] at h_single + rcases h_single with ((hpo_loc | hrf) | hco) | hfr + · simp only [po_loc, Set.mem_inter_iff] at hpo_loc + obtain ⟨hpo, _⟩ := hpo_loc + simp only [Events.po, Set.mem_setOf_eq] at hpo + exact Nat.lt_irrefl _ hpo.2.2.2 + · have := (X.rfInst.wellTyped a a hrf).2.2.2 + exact this rfl + · exact X.preCo.irrefl a hco + · have hr := fr_is_read X hfr + simp [ha_write] at hr + · -- ≥2 edges: TransGen(coh) a mid, then (mid, a) ∈ coh + -- We have coh_transgen_state on the full cycle giving refl. + -- But the sub-path a → mid also gives a state. + have hstate_mid := coh_transgen_state X hrest + -- Now process the last edge (mid, a) from hstate_mid. + have hstate_final := coh_state_step X hstate_mid hlast + -- hstate_final should be the same as our overall state. + -- Our overall state was AtEvent(a) with ReflTransGen.refl. + -- Let's check: hstate_final is CohState X a a. + -- If it's AtEvent(w) with w = a and ReflTransGen tso a w: + -- We need ReflTransGen tso a a. If non-trivial → contradiction. + -- Actually this IS our assumption (refl case). So hstate_final might also be refl. + -- Hmm, this doesn't directly help. + -- Let me try a different approach: show directly that in the cycle, + -- the state_mid at mid must involve a non-trivial path if mid ≠ a, + -- or a pending read state. + -- This is getting too complex inline. Let me prove a separate lemma + -- that ReflTransGen can be upgraded to TransGen when the cycle has ≥2 edges. + -- Actually, the simplest approach: use a stronger state that tracks + -- whether at least one tso step has been emitted. + sorry + · exact ReflTransGen.head hab hba |>.transGen_of_reflTransGen_of_ne (by + intro heq; subst heq + -- This means we went a → b → ... → a with tso steps. + -- We have hab : tso_step X a b and hba : ReflTransGen tso b a + -- We need to show this is impossible... but it's not, it's exactly what we want! + sorry) + -- This approach isn't working cleanly. Let me restructure. + sorry + · -- PendingRead: a is read. But a is a write. Contradiction. + simp [ha_write] at ha_read + +-- OK the above is getting messy. Let me use a cleaner approach. +-- Track `TransGen` directly by handling the first edge specially. + +-- CLEAN APPROACH: Process edges one by one, with state being either: +-- (a) ∃ w, TransGen(tso) origin w ∧ (w = cur ∨ pending(w, cur)) +-- (b) origin = cur ∧ no tso steps yet (initial state, before first tso-emitting edge) +-- (c) pending(origin, cur) ∧ no tso steps yet + +-- When we emit the first tso step, we move from (b)/(c) to (a). +-- At cycle end, we must be in (a) with w = origin → TransGen tso origin origin. +-- Being in (b) means origin = cur = origin with no steps → need to show impossible (cycle has ≥1 edge). +-- Being in (c) means pending(origin, origin) → origin is read, contradiction. + +-- Let me formalize this with a single inductive type. + +-- Actually, you know what, let me just use a completely different proof structure. +-- Instead of state machine processing, prove it via well-founded induction. + +-- SIMPLEST POSSIBLE APPROACH: +-- Define wco(a,b) = either: +-- (1) (a,b) ∈ tso_rel, or +-- (2) ∃ mid, (a, mid) ∈ rfi, (mid, b) ∈ coh_rel [rfi;coh single step] +-- (3) ∃ mid, (a, mid) ∈ WR po_loc, (mid, b) ∈ coh_rel [WR po_loc;coh single step] +-- Then show: +-- (A) coh_rel ⊆ TransGen(wco) +-- (B) wco ⊆ TransGen(tso_rel) +-- This gives Acyclic(tso) → Acyclic(wco) via ayclicMono_trans → Acyclic(coh) via another ayclicMono_trans. + +-- For (B), each wco edge: +-- Case 1: trivially in TransGen(tso). +-- Case 2: rfi(a,mid);coh(mid,b). +-- mid is a read. +-- coh(mid,b) cases: +-- - fr(mid,b): rf_fr_subset_co → co(a,b) → tso single step ✓ +-- - po_loc(mid,b) with RW: po trans (rfiPo) → xppo(WW) ✓ +-- - po_loc(mid,b) with RR: po trans → po(a,b) with WR... this gives WR po_loc(a,b). +-- This is NOT in tso! So case 2 with RR po_loc doesn't map to single tso step. +-- It would be wco case 3 (WR po_loc; next coh step)... but that's circular! +-- Hmm, so rfi;(RR po_loc) = WR po_loc (after composition). And WR po_loc needs the NEXT +-- edge to resolve. So we'd need wco to be rfi;coh;coh;...;coh until we hit a write. +-- This requires unbounded composition. + +-- SO this simple approach doesn't work either. The issue is chains of reads. + +-- THE CORRECT SIMPLE APPROACH: +-- Define wco(a,b) = ∃ chain of coh edges from a to b where a is a write and +-- all intermediate events are reads at the same location in po order. +-- This is essentially: a→(rfi|WR po_loc)→(RR po_loc)*→(fr|RW po_loc)→b +-- And this maps to a single tso step (co or xppo). + +-- But this requires an existential over paths, which is hard to define as a relation. + +-- Alternative: use TransGen of a restricted relation. +-- Let R = {(a,b) ∈ coh | a is read → b is read → same thread → po(a,b)} +-- ... this is getting too convoluted. + +-- LET ME JUST PROVE THE THEOREM DIRECTLY WITH THE STATE MACHINE. +-- I'll use a better state that distinguishes "some tso step emitted" from "no tso step yet". + +sorry -- placeholder, will be replaced + +/-- TSO acyclicity implies LKMM coherence. -/ +theorem tso_implies_lkmm_coherence + (evts : Data.Events) + (X : CandidateExecution evts) + (hTso : tso_x86.tso evts X) : + lkmm.coherence evts X := by + sorry + +-- ════════════════════════════════════════════════════════════════ +-- § Remaining LKMM constraints +-- ════════════════════════════════════════════════════════════════ + +theorem tso_x86_subset_lkmm + (evts : Data.Events) + (X : CandidateExecution evts) + (hTso : tso_x86.tso evts X) : + lkmm.coherence evts X ∧ + lkmm.atomic evts X ∧ + lkmm.happens_before evts X ∧ + lkmm.propagation evts X := + ⟨tso_implies_lkmm_coherence evts X hTso, by sorry, by sorry, by sorry⟩ diff --git a/LeanCats/tsoWeakerThanSc.lean b/LeanCats/tsoWeakerThanSc.lean index 6018475..4a36d35 100644 --- a/LeanCats/tsoWeakerThanSc.lean +++ b/LeanCats/tsoWeakerThanSc.lean @@ -4,9 +4,6 @@ import LeanCats.Data import LeanCats.Relations import LeanCats.Theorems import LeanCats.Basic -import LeanCats.Litmus - -open Litmus defcat <"tso.cat"> defcat <"sc.cat"> diff --git a/tmp_check_no_macro.lean b/tmp_check_no_macro.lean new file mode 100644 index 0000000..968536c --- /dev/null +++ b/tmp_check_no_macro.lean @@ -0,0 +1,3 @@ +import LeanCats.Basic +import LeanCats.Data +#check lkmm.Accesses diff --git a/tmp_lkmm_check.lean b/tmp_lkmm_check.lean new file mode 100644 index 0000000..e20fc99 --- /dev/null +++ b/tmp_lkmm_check.lean @@ -0,0 +1,6 @@ +import LeanCats.Macro + +#check lkmm.ONCE +#check lkmm.Accesses +#check lkmm.Accesses.ONCE' +#check lkmm.Accesses.RELEASE' diff --git a/tmp_sig_check.lean b/tmp_sig_check.lean new file mode 100644 index 0000000..b3bcffb --- /dev/null +++ b/tmp_sig_check.lean @@ -0,0 +1,9 @@ +import LeanCats.Basic +import LeanCats.Data +import LeanCats.Macro + +#check Data.Effect.mk +#check Data.Event.mk +#check Data.Events.mk +#check Data.wellformed.rf.mk +#check CandidateExecution.mk diff --git a/tmp_tag_check.lean b/tmp_tag_check.lean new file mode 100644 index 0000000..b0d9f4a --- /dev/null +++ b/tmp_tag_check.lean @@ -0,0 +1,8 @@ +import LeanCats.Data +import LeanCats.Macro +open Data + +def e : Data.Event := + { id := 1, t_id := 0, + effect := { op := Data.Op.read, location := 0, value := some 0, isFirstWrite := false, isFinalWrite := false }, + tag := ⟨lkmm.Accesses, lkmm.Accesses.ONCE'⟩ } From 4c582a29b525eef06df632cde315a2f7709cfa2c Mon Sep 17 00:00:00 2001 From: YangWiz Date: Thu, 26 Mar 2026 22:57:11 +0100 Subject: [PATCH 25/34] delete temp files. --- tmp_check_no_macro.lean | 3 --- tmp_lkmm_check.lean | 6 ------ tmp_sig_check.lean | 9 --------- tmp_tag_check.lean | 8 -------- 4 files changed, 26 deletions(-) delete mode 100644 tmp_check_no_macro.lean delete mode 100644 tmp_lkmm_check.lean delete mode 100644 tmp_sig_check.lean delete mode 100644 tmp_tag_check.lean diff --git a/tmp_check_no_macro.lean b/tmp_check_no_macro.lean deleted file mode 100644 index 968536c..0000000 --- a/tmp_check_no_macro.lean +++ /dev/null @@ -1,3 +0,0 @@ -import LeanCats.Basic -import LeanCats.Data -#check lkmm.Accesses diff --git a/tmp_lkmm_check.lean b/tmp_lkmm_check.lean deleted file mode 100644 index e20fc99..0000000 --- a/tmp_lkmm_check.lean +++ /dev/null @@ -1,6 +0,0 @@ -import LeanCats.Macro - -#check lkmm.ONCE -#check lkmm.Accesses -#check lkmm.Accesses.ONCE' -#check lkmm.Accesses.RELEASE' diff --git a/tmp_sig_check.lean b/tmp_sig_check.lean deleted file mode 100644 index b3bcffb..0000000 --- a/tmp_sig_check.lean +++ /dev/null @@ -1,9 +0,0 @@ -import LeanCats.Basic -import LeanCats.Data -import LeanCats.Macro - -#check Data.Effect.mk -#check Data.Event.mk -#check Data.Events.mk -#check Data.wellformed.rf.mk -#check CandidateExecution.mk diff --git a/tmp_tag_check.lean b/tmp_tag_check.lean deleted file mode 100644 index b0d9f4a..0000000 --- a/tmp_tag_check.lean +++ /dev/null @@ -1,8 +0,0 @@ -import LeanCats.Data -import LeanCats.Macro -open Data - -def e : Data.Event := - { id := 1, t_id := 0, - effect := { op := Data.Op.read, location := 0, value := some 0, isFirstWrite := false, isFinalWrite := false }, - tag := ⟨lkmm.Accesses, lkmm.Accesses.ONCE'⟩ } From 1be00179a61c269ac4abc05be8ecb505170034ae Mon Sep 17 00:00:00 2001 From: YangWiz Date: Thu, 26 Mar 2026 23:29:34 +0100 Subject: [PATCH 26/34] Fix renaming bugs. --- LeanCats/Basic.lean | 13 +------------ LeanCats/Linux/litmus.lean | 31 ++++--------------------------- LeanCats/Macro.lean | 26 +++++++++++++------------- LeanCats/Theorems.lean | 10 +++++----- 4 files changed, 23 insertions(+), 57 deletions(-) diff --git a/LeanCats/Basic.lean b/LeanCats/Basic.lean index 1788949..556b79c 100644 --- a/LeanCats/Basic.lean +++ b/LeanCats/Basic.lean @@ -40,19 +40,8 @@ structure CandidateExecution (evts : Events) where → (w', r) ∈ rf' → (w, w') ∈ co' ∨ w = w' -@[simp] abbrev CandidateExecution.po {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.po' -@[simp] abbrev CandidateExecution.rf {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.rf' -@[simp] abbrev CandidateExecution.co {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.co' -@[simp] abbrev CandidateExecution.rmw {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.rmw' -@[simp] abbrev CandidateExecution.wmb {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.wmb' -@[simp] abbrev CandidateExecution.data {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.data' -@[simp] abbrev CandidateExecution.addr {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.addr' -@[simp] abbrev CandidateExecution.ctrl {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.ctrl' -@[simp] abbrev CandidateExecution.fence {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.fence' -@[simp] abbrev CandidateExecution.mb {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.mb' - /-- from-reads: always defined as rf⁻¹ ; co, so it is transparent to the kernel. -/ -@[simp] def CandidateExecution.fr {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := +@[simp] def CandidateExecution.fr' {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := X.rf'.inv.comp X.co' /-- The `uniqueId` field of any `CandidateExecution`: since `event_id_unique` makes identity diff --git a/LeanCats/Linux/litmus.lean b/LeanCats/Linux/litmus.lean index 2e2e75d..53743ea 100644 --- a/LeanCats/Linux/litmus.lean +++ b/LeanCats/Linux/litmus.lean @@ -7,15 +7,9 @@ open Data namespace LinuxLitmus -instance instWellformedPo (evts : Data.Events) : wellformed.po evts.po := by - intro x y z hxy hyz - rcases hxy with ⟨hx, hy, hxyTid, hxyLt⟩ - rcases hyz with ⟨_, hz, hyzTid, hyzLt⟩ - exact ⟨hx, hz, Eq.trans hxyTid hyzTid, Nat.lt_trans hxyLt hyzLt⟩ - instance instWellformedRmwEmpty (evts : Data.Events) : wellformed.rmw evts (∅ : SetRel Event Event) := by intro e h - exact False.elim h + contradiction abbrev x := 0 @@ -90,25 +84,8 @@ def corr_test : CandidateExecution corr_evts := (uniqueId_by_id corr_evts) (by candidateExecution_wf [corr_rf]) (by - candidateExecution_wf [corr_rf, corr_co, corr_evts] - · rcases a with ⟨hwAll, _, htid, hlt⟩ - have hwCases : w = p0wX ∨ w = p1r0 ∨ w = p1r1 ∨ w = initWx := by - simpa [corr_evts, Events.all] using hwAll - exfalso - rcases hwCases with rfl | rfl | rfl | rfl - · cases htid - · exact False.elim (Nat.lt_irrefl _ hlt) - · cases htid - · cases htid - · rcases a with ⟨hwAll, _, htid, hlt⟩ - have hwCases : w = p0wX ∨ w = p1r0 ∨ w = p1r1 ∨ w = initWx := by - simpa [corr_evts, Events.all] using hwAll - exfalso - rcases hwCases with rfl | rfl | rfl | rfl - · cases htid - · cases htid - · exact False.elim (Nat.lt_irrefl _ hlt) - · cases htid) + candidateExecution_wf [corr_rf, corr_co, corr_evts, Events.po, Events.all] + ) theorem corr_FindCycle : ¬ (lkmm.coherence corr_evts corr_test) := by intro hacyc @@ -123,7 +100,7 @@ theorem corr_FindCycle : ¬ (lkmm.coherence corr_evts corr_test) := by right refine ⟨initWx, ?_, ?_⟩ · have hrf0' : (initWx, p1r1) ∈ corr_test.rf' := by - simpa [corr_test, corr_rf] using hrf0 + simp [corr_test, corr_rf] simpa [SetRel.inv] using hrf0' · simp [corr_test, corr_co] have hrf1 : (p0wX, p1r0) ∈ rel := by diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index 62b13b7..3d0cb97 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -115,54 +115,54 @@ macro_rules macro_rules | `([predefined-relations| fr, $_, $X]) => - let nm := mkIdent "fr".toName + let nm := mkIdent "fr'".toName `($X.$nm) | `([predefined-relations| po, $_, $X]) => - let nm := mkIdent "po".toName + let nm := mkIdent "po'".toName `($X.$nm) | `([predefined-relations| rf, $_, $X]) => - let nm := mkIdent "rf".toName + let nm := mkIdent "rf'".toName `($X.$nm) | `([predefined-relations| rmw, $_, $X]) => - let nm := mkIdent "rmw".toName + let nm := mkIdent "rmw'".toName `($X.$nm) | `([predefined-relations| co, $_, $X]) => - let co' := mkIdent "co".toName + let co' := mkIdent "co'".toName `($X.$co') | `([predefined-relations| id, $_, $_]) => `(SetRel.id) | `([predefined-relations| data, $_, $X]) => - let nm := mkIdent "data".toName + let nm := mkIdent "data'".toName `($X.$nm) | `([predefined-relations| addr, $_, $X]) => - let nm := mkIdent "addr".toName + let nm := mkIdent "addr'".toName `($X.$nm) | `([predefined-relations| ctrl, $_, $X]) => - let nm := mkIdent "ctrl".toName + let nm := mkIdent "ctrl'".toName `($X.$nm) | `([predefined-relations| wmb, $_, $X]) => - let nm := mkIdent "wmb".toName + let nm := mkIdent "wmb'".toName `($X.$nm) | `([predefined-relations| fence, $_, $X]) => - let nm := mkIdent "fence".toName + let nm := mkIdent "fence'".toName `($X.$nm) | `([predefined-relations| rmb , $_, $X]) => - let nm := mkIdent "rmb".toName + let nm := mkIdent "rmb'".toName `($X.$nm) | `([predefined-relations| mb , $_, $X]) => - let nm := mkIdent "mb".toName + let nm := mkIdent "mb'".toName `($X.$nm) macro_rules @@ -329,7 +329,7 @@ macro_rules @[simp] def po_loc (evts : Events) (X : CandidateExecution evts) := X.po' ∩ CatRel.Rel.location -@[simp] def fre (evts : Events) (X : CandidateExecution evts) := X.fr ∩ CatRel.Rel.external +@[simp] def fre (evts : Events) (X : CandidateExecution evts) := X.fr' ∩ CatRel.Rel.external @[simp] def rfe (evts : Events) (X : CandidateExecution evts) := X.rf' ∩ CatRel.Rel.external diff --git a/LeanCats/Theorems.lean b/LeanCats/Theorems.lean index dd698ab..6b30f72 100644 --- a/LeanCats/Theorems.lean +++ b/LeanCats/Theorems.lean @@ -200,9 +200,9 @@ theorem rf_fr_subset_co (X : CandidateExecution evts) (w r w' : Event) (hrf : (w, r) ∈ X.rf') - (hfr : (r, w') ∈ X.fr) : + (hfr : (r, w') ∈ X.fr') : (w, w') ∈ X.co' := by - simp only [CandidateExecution.fr] at hfr + simp only [CandidateExecution.fr'] at hfr obtain ⟨w₁, h₁, h₂⟩ := hfr simp only [SetRel.inv] at h₁ exact X.rfInst.unique w w₁ r hrf h₁ ▸ h₂ @@ -228,9 +228,9 @@ theorem fr_co_subset_fr {evts : Events} (X : CandidateExecution evts) (r w w' : Event) - (hfr : (r, w) ∈ X.fr) + (hfr : (r, w) ∈ X.fr') (hco : (w, w') ∈ X.co') : - (r, w') ∈ X.fr := by - simp only [CandidateExecution.fr] at * + (r, w') ∈ X.fr' := by + simp only [CandidateExecution.fr'] at * obtain ⟨w₀, h_inv, h_co⟩ := hfr exact ⟨w₀, h_inv, X.preCo.trans w₀ w w' h_co hco⟩ From 4e5ce816fcac7aaa83f611a1326598236a9a2b92 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Fri, 27 Mar 2026 00:11:12 +0100 Subject: [PATCH 27/34] Fix tactic --- LeanCats/Basic.lean | 12 ++++-------- LeanCats/Linux/litmus.lean | 37 ++++++++++++++----------------------- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/LeanCats/Basic.lean b/LeanCats/Basic.lean index 556b79c..c3446d3 100644 --- a/LeanCats/Basic.lean +++ b/LeanCats/Basic.lean @@ -57,13 +57,9 @@ theorem uniqueId_by_id (evts : Events) : user-supplied lemmas (typically the `co` and `evts` `@[simp]` definitions), then close by `omega` (handles numeric contradictions on IDs / thread IDs) with `simp_all` as a pre-processing step when `omega` alone is insufficient. -/ -macro "candidateExecution_wf" "[" lemmas:Lean.Parser.Tactic.simpLemma,* "]" : tactic => +macro "candidateExecution_wf" : tactic => `(tactic| - (intros - simp only [Set.mem_insert_iff, Set.mem_singleton_iff, Prod.mk.injEq, - Events.po, Events.all, Set.mem_setOf_eq, $lemmas,*] at * - first - | omega + (all_goals repeat first | aesop - | (simp_all [Set.mem_insert_iff, Set.mem_singleton_iff, - Events.all, $lemmas,*] <;> omega))) + | (casesm _ ∈ _ + aesop))) diff --git a/LeanCats/Linux/litmus.lean b/LeanCats/Linux/litmus.lean index 53743ea..082ab7d 100644 --- a/LeanCats/Linux/litmus.lean +++ b/LeanCats/Linux/litmus.lean @@ -42,7 +42,7 @@ instance : wellformed.co corr_evts corr_co where trans := by aesop preco := { wellTyped := by aesop - total := by candidateExecution_wf [] + total := by candidateExecution_wf } /-- C CoRR+poonceonce+Once @@ -64,28 +64,19 @@ Chosen rf edges: `p0wX -> p1r0` and `initWx -> p1r1`. -/ aesop (add simp [corr_rf])) def corr_test : CandidateExecution corr_evts := - @CandidateExecution.mk corr_evts - corr_evts - True - corr_po - (by candidateExecution_wf [corr_po]) - corr_rf - corr_rfInst - corr_co - (inferInstance : wellformed.co corr_evts corr_co) - (∅ : SetRel Event Event) - (instWellformedRmwEmpty corr_evts) - (∅ : SetRel Event Event) - (∅ : SetRel Event Event) - (∅ : SetRel Event Event) - (∅ : SetRel Event Event) - (∅ : SetRel Event Event) - (∅ : SetRel Event Event) - (uniqueId_by_id corr_evts) - (by candidateExecution_wf [corr_rf]) - (by - candidateExecution_wf [corr_rf, corr_co, corr_evts, Events.po, Events.all] - ) + { + po' := corr_po + prePo := by candidateExecution_wf + rf' := corr_rf + rfInst := corr_rfInst + co' := corr_co + preRMW := instWellformedRmwEmpty corr_evts + uniqueId := uniqueId_by_id corr_evts + rfiPo := by + candidateExecution_wf + coWR := by + candidateExecution_wf + } theorem corr_FindCycle : ¬ (lkmm.coherence corr_evts corr_test) := by intro hacyc From ea9dfdcd1f7fb0f21e50374509d56d8cb311a146 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Fri, 27 Mar 2026 00:20:00 +0100 Subject: [PATCH 28/34] Fix bugs in tactic --- LeanCats/Basic.lean | 3 ++- LeanCats/Linux/litmus.lean | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/LeanCats/Basic.lean b/LeanCats/Basic.lean index c3446d3..c50666a 100644 --- a/LeanCats/Basic.lean +++ b/LeanCats/Basic.lean @@ -60,6 +60,7 @@ theorem uniqueId_by_id (evts : Events) : macro "candidateExecution_wf" : tactic => `(tactic| (all_goals repeat first - | aesop + | simp at * + aesop | (casesm _ ∈ _ aesop))) diff --git a/LeanCats/Linux/litmus.lean b/LeanCats/Linux/litmus.lean index 082ab7d..6b322ee 100644 --- a/LeanCats/Linux/litmus.lean +++ b/LeanCats/Linux/litmus.lean @@ -57,11 +57,11 @@ Chosen rf edges: `p0wX -> p1r0` and `initWx -> p1r1`. -/ @[simp] def corr_rfInst : wellformed.rf corr_evts corr_rf := Data.wellformed.rf.mk (by - intro w r hrf - aesop (add simp [corr_rf, corr_evts, Events.all])) + candidateExecution_wf + ) (by - intro w₁ w₂ r h1 h2 - aesop (add simp [corr_rf])) + candidateExecution_wf + ) def corr_test : CandidateExecution corr_evts := { From 6bcc51f3e1665d010d0c96d535e3d59af1803127 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Fri, 27 Mar 2026 00:49:00 +0100 Subject: [PATCH 29/34] refactor the project --- LeanCats.lean | 15 ++++++ LeanCats/ArmWeakerThanX86.lean | 0 ...{litmus.lean => CoRR_poonceonce_Once.lean} | 0 LeanCats/Litmus.lean | 48 ++++++++--------- ...WeakerThanSc.lean => TsoWeakerThanSc.lean} | 2 +- README.md | 53 ++++++++++++++++++- lakefile.lean | 6 +-- 7 files changed, 95 insertions(+), 29 deletions(-) delete mode 100644 LeanCats/ArmWeakerThanX86.lean rename LeanCats/Linux/{litmus.lean => CoRR_poonceonce_Once.lean} (100%) rename LeanCats/{tsoWeakerThanSc.lean => TsoWeakerThanSc.lean} (95%) diff --git a/LeanCats.lean b/LeanCats.lean index 5469e46..5e3c11b 100644 --- a/LeanCats.lean +++ b/LeanCats.lean @@ -1,3 +1,18 @@ -- This module serves as the root of the `LeanCats` library. -- Import modules here that should be built as part of the library. +import LeanCats.Data +import LeanCats.HashMapExt +import LeanCats.Relations import LeanCats.Basic +import LeanCats.Syntax +import LeanCats.CatPreprocessor +import LeanCats.Macro +import LeanCats.Theorems +import LeanCats.TsoWeakerThanSc +import LeanCats.ModelReader +import LeanCats.Litmus +import LeanCats.LitmusParser +import LeanCats.LitmusGraph +import LeanCats.LitmusGraphBridge +import LeanCats.LitmusReader +import LeanCats.Linux.CoRR_poonceonce_Once diff --git a/LeanCats/ArmWeakerThanX86.lean b/LeanCats/ArmWeakerThanX86.lean deleted file mode 100644 index e69de29..0000000 diff --git a/LeanCats/Linux/litmus.lean b/LeanCats/Linux/CoRR_poonceonce_Once.lean similarity index 100% rename from LeanCats/Linux/litmus.lean rename to LeanCats/Linux/CoRR_poonceonce_Once.lean diff --git a/LeanCats/Litmus.lean b/LeanCats/Litmus.lean index 7acee87..fe59fc3 100644 --- a/LeanCats/Litmus.lean +++ b/LeanCats/Litmus.lean @@ -72,7 +72,7 @@ instance : wellformed.co evtsInput co where trans := by aesop preco := { wellTyped := by aesop - total := by candidateExecution_wf [co] + total := by candidateExecution_wf } @[simp] def test1 : CandidateExecution evtsInput := { @@ -102,14 +102,14 @@ instance : wellformed.co evtsInput co where ctrl' := ∅ fence' := ∅ addr' := ∅ - rfiPo := by candidateExecution_wf [] - coWR := by candidateExecution_wf [co, evtsInput] + rfiPo := by candidateExecution_wf + coWR := by candidateExecution_wf } /-- The SB candidate execution has a cycle in `co ∪ rf ∪ fr ∪ po`: `inst1writeX →[po] inst2readY →[fr] inst3writeY →[po] inst4readX →[fr] inst1writeX` This witnesses that the execution is NOT SC-consistent. -/ -theorem FindCycle : ¬ CatRel.SetRel.Acyclic (test1.co' ∪ test1.rf' ∪ test1.fr ∪ test1.po') := by +theorem FindCycle : ¬ CatRel.SetRel.Acyclic (test1.co' ∪ test1.rf' ∪ test1.fr' ∪ test1.po') := by intro h apply h inst1writeX -- Prove each event is in evtsInput.all (needed for po membership) @@ -122,9 +122,9 @@ theorem FindCycle : ¬ CatRel.SetRel.Acyclic (test1.co' ∪ test1.rf' ∪ test1. have h1evts : (inst1writeX, inst2readY) ∈ evtsInput.po := ⟨mem1, mem2, rfl, by decide⟩ simpa [test1] using h1evts - have h1 : (inst1writeX, inst2readY) ∈ test1.co' ∪ test1.rf' ∪ test1.fr ∪ test1.po' := Or.inr h1po + have h1 : (inst1writeX, inst2readY) ∈ test1.co' ∪ test1.rf' ∪ test1.fr' ∪ test1.po' := Or.inr h1po -- Step 2: inst2readY →[fr] inst3writeY (via rf⁻¹;co, witness initWy) - have h2 : (inst2readY, inst3writeY) ∈ test1.co' ∪ test1.rf' ∪ test1.fr ∪ test1.po' := by + have h2 : (inst2readY, inst3writeY) ∈ test1.co' ∪ test1.rf' ∪ test1.fr' ∪ test1.po' := by left; right simp only [test1] exact ⟨initWy, by simp, by simp [co]⟩ @@ -133,9 +133,9 @@ theorem FindCycle : ¬ CatRel.SetRel.Acyclic (test1.co' ∪ test1.rf' ∪ test1. have h3evts : (inst3writeY, inst4readX) ∈ evtsInput.po := ⟨mem3, mem4, rfl, by decide⟩ simpa [test1] using h3evts - have h3 : (inst3writeY, inst4readX) ∈ test1.co' ∪ test1.rf' ∪ test1.fr ∪ test1.po' := Or.inr h3po + have h3 : (inst3writeY, inst4readX) ∈ test1.co' ∪ test1.rf' ∪ test1.fr' ∪ test1.po' := Or.inr h3po -- Step 4: inst4readX →[fr] inst1writeX (via rf⁻¹;co, witness initWx) - have h4 : (inst4readX, inst1writeX) ∈ test1.co' ∪ test1.rf' ∪ test1.fr ∪ test1.po' := by + have h4 : (inst4readX, inst1writeX) ∈ test1.co' ∪ test1.rf' ∪ test1.fr' ∪ test1.po' := by left; right simp only [test1] exact ⟨initWx, by simp, by simp [co]⟩ @@ -182,7 +182,7 @@ instance : wellformed.co mp_evts mp_co where trans := by aesop preco := { wellTyped := by aesop - total := by candidateExecution_wf [mp_co] + total := by candidateExecution_wf } -- rf: mp_readY sees y=1 from mp_writeY; mp_readX sees x=0 from mp_initWx @@ -213,14 +213,14 @@ instance : wellformed.co mp_evts mp_co where ctrl' := ∅ fence' := ∅ addr' := ∅ - rfiPo := by candidateExecution_wf [] - coWR := by candidateExecution_wf [mp_co, mp_evts] + rfiPo := by candidateExecution_wf + coWR := by candidateExecution_wf } /-- The MP candidate execution has a cycle in `co ∪ rf ∪ fr ∪ po`: `mp_writeX →[po] mp_writeY →[rf] mp_readY →[po] mp_readX →[fr] mp_writeX` Forbidden under SC. -/ -theorem mp_FindCycle : ¬ CatRel.SetRel.Acyclic (mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr ∪ mp_test.po') := by +theorem mp_FindCycle : ¬ CatRel.SetRel.Acyclic (mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr' ∪ mp_test.po') := by intro h apply h mp_writeX have mem1 : mp_writeX ∈ mp_evts.all := by simp [Events.all] @@ -232,18 +232,18 @@ theorem mp_FindCycle : ¬ CatRel.SetRel.Acyclic (mp_test.co' ∪ mp_test.rf' ∪ have h1evts : (mp_writeX, mp_writeY) ∈ mp_evts.po := ⟨mem1, mem2, rfl, by decide⟩ simpa [mp_test] using h1evts - have h1 : (mp_writeX, mp_writeY) ∈ mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr ∪ mp_test.po' := Or.inr h1po + have h1 : (mp_writeX, mp_writeY) ∈ mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr' ∪ mp_test.po' := Or.inr h1po -- Step 2: mp_writeY →[rf] mp_readY - have h2 : (mp_writeY, mp_readY) ∈ mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr ∪ mp_test.po' := by + have h2 : (mp_writeY, mp_readY) ∈ mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr' ∪ mp_test.po' := by left; left; right; simp -- Step 3: mp_readY →[po] mp_readX (P1, id 103 < 104) have h3po : (mp_readY, mp_readX) ∈ mp_test.po' := by have h3evts : (mp_readY, mp_readX) ∈ mp_evts.po := ⟨mem3, mem4, rfl, by decide⟩ simpa [mp_test] using h3evts - have h3 : (mp_readY, mp_readX) ∈ mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr ∪ mp_test.po' := Or.inr h3po + have h3 : (mp_readY, mp_readX) ∈ mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr' ∪ mp_test.po' := Or.inr h3po -- Step 4: mp_readX →[fr] mp_writeX (via rf⁻¹;co, witness mp_initWx) - have h4 : (mp_readX, mp_writeX) ∈ mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr ∪ mp_test.po' := by + have h4 : (mp_readX, mp_writeX) ∈ mp_test.co' ∪ mp_test.rf' ∪ mp_test.fr' ∪ mp_test.po' := by left; right simp only [mp_test] exact ⟨mp_initWx, by simp, by simp [mp_co]⟩ @@ -292,7 +292,7 @@ instance : wellformed.co lb_evts lb_co where trans := by aesop preco := { wellTyped := by aesop - total := by candidateExecution_wf [lb_co] + total := by candidateExecution_wf } -- rf: lb_readX sees x=1 from lb_writeX; lb_readY sees y=1 from lb_writeY @@ -323,15 +323,15 @@ instance : wellformed.co lb_evts lb_co where ctrl' := ∅ fence' := ∅ addr' := ∅ - rfiPo := by candidateExecution_wf [] - coWR := by candidateExecution_wf [lb_co, lb_evts] + rfiPo := by candidateExecution_wf + coWR := by candidateExecution_wf } /-- The LB candidate execution has a cycle in `co ∪ rf ∪ fr ∪ po`: `lb_readX →[po] lb_writeY →[rf] lb_readY →[po] lb_writeX →[rf] lb_readX` The cycle uses only `po` and `rf` — no `fr` edges are needed. Forbidden under SC and TSO; allowed under ARM/POWER. -/ -theorem lb_FindCycle : ¬ CatRel.SetRel.Acyclic (lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr ∪ lb_test.po') := by +theorem lb_FindCycle : ¬ CatRel.SetRel.Acyclic (lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr' ∪ lb_test.po') := by intro h apply h lb_readX have mem1 : lb_readX ∈ lb_evts.all := by simp [Events.all] @@ -343,18 +343,18 @@ theorem lb_FindCycle : ¬ CatRel.SetRel.Acyclic (lb_test.co' ∪ lb_test.rf' ∪ have h1evts : (lb_readX, lb_writeY) ∈ lb_evts.po := ⟨mem1, mem2, rfl, by decide⟩ simpa [lb_test] using h1evts - have h1 : (lb_readX, lb_writeY) ∈ lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr ∪ lb_test.po' := Or.inr h1po + have h1 : (lb_readX, lb_writeY) ∈ lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr' ∪ lb_test.po' := Or.inr h1po -- Step 2: lb_writeY →[rf] lb_readY - have h2 : (lb_writeY, lb_readY) ∈ lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr ∪ lb_test.po' := by + have h2 : (lb_writeY, lb_readY) ∈ lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr' ∪ lb_test.po' := by left; left; right; simp -- Step 3: lb_readY →[po] lb_writeX (P1, id 203 < 204) have h3po : (lb_readY, lb_writeX) ∈ lb_test.po' := by have h3evts : (lb_readY, lb_writeX) ∈ lb_evts.po := ⟨mem3, mem4, rfl, by decide⟩ simpa [lb_test] using h3evts - have h3 : (lb_readY, lb_writeX) ∈ lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr ∪ lb_test.po' := Or.inr h3po + have h3 : (lb_readY, lb_writeX) ∈ lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr' ∪ lb_test.po' := Or.inr h3po -- Step 4: lb_writeX →[rf] lb_readX - have h4 : (lb_writeX, lb_readX) ∈ lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr ∪ lb_test.po' := by + have h4 : (lb_writeX, lb_readX) ∈ lb_test.co' ∪ lb_test.rf' ∪ lb_test.fr' ∪ lb_test.po' := by left; left; right; simp exact .head h1 (.head h2 (.head h3 (.single h4))) diff --git a/LeanCats/tsoWeakerThanSc.lean b/LeanCats/TsoWeakerThanSc.lean similarity index 95% rename from LeanCats/tsoWeakerThanSc.lean rename to LeanCats/TsoWeakerThanSc.lean index 4a36d35..1b30339 100644 --- a/LeanCats/tsoWeakerThanSc.lean +++ b/LeanCats/TsoWeakerThanSc.lean @@ -32,7 +32,7 @@ by · rcases h with hxppo | h · exact Or.inl hxppo.2 · rcases h with hrfe | h - · exact Or.inr (Or.inr (Or.inl hrfe.1)) + · exact Or.inr (Or.inl hrfe.1) · rcases h with hfr | hco · exact Or.inr (Or.inr (Or.inl hfr)) · exact Or.inr (Or.inr (Or.inr hco)) diff --git a/README.md b/README.md index bb6aab0..e9fe087 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,54 @@ # lean-cats -This is the Lean repository for parsing the file.cat (see the [Herding Cats](https://arxiv.org/pdf/1308.6810)) to analyze the relaxed memory model. \ No newline at end of file +`lean-cats` is a Lean 4 project for experimenting with weak memory models and litmus tests. +It supports: + +- A CAT-style DSL in Lean (`[model| ... ]`) for defining models. +- Candidate executions (`CandidateExecution`) and core relations (`po`, `rf`, `co`, `fr`). +- Litmus parsing/enumeration plus reader commands (`#litmus`, `deflitmus`). +- Model loading from `.cat` / `.bell` files (`defcat <"...">`). + +## Usage + +### Building + +```bash +git clone +cd lean-cat +lake update +lake build +``` + +### Running the included tools + +```bash +lake exe litmus-parser-test +``` + +### Typechecking a file + +```bash +lake env lean LeanCats/Linux/litmus.lean +``` + +### Using the reader commands + +```lean +#litmus "LeanCats/Cats/examples/tests/SB.litmus" +deflitmus SB <"LeanCats/Cats/examples/tests/SB.litmus"> + +defcat <"sc.cat"> +defcat <"tso.cat"> +``` + +## Where to look + +- `LeanCats/Macro.lean`: CAT DSL (`[model| ... ]`). +- `LeanCats/Basic.lean`: `CandidateExecution`. +- `LeanCats/LitmusParser/*`: litmus parser/enumerator. +- `LeanCats/LitmusReader.lean` and `LeanCats/ModelReader.lean`: `#litmus` / `deflitmus` / `defcat`. + +## References + +- Herding Cats (CAT): +- herdtools7 ecosystem: \ No newline at end of file diff --git a/lakefile.lean b/lakefile.lean index 9775248..a9db6b1 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -11,12 +11,12 @@ lean_lib «LeanCats» where lean_exe "lean-cats" where root := `Main -lean_exe "test-to-string" where - root := `LeanCats.TestToString - lean_exe "litmus-parser-test" where root := `LeanCats.LitmusParserTest +lean_lib «LeanCatsReaderTest» where + roots := #[`LeanCats.LitmusReaderTest] + require "leanprover-community" / "mathlib" -- You should replace v0.0.3 with the latest version published under Releases From 442f0f1b173098a652c939b9054cf162e7f5c76c Mon Sep 17 00:00:00 2001 From: YangWiz Date: Fri, 27 Mar 2026 03:42:28 +0100 Subject: [PATCH 30/34] Change the definition of M --- LeanCats/Cats/examples/models/bpf.cat | 88 +++++++++++++++++++++++++++ LeanCats/Data.lean | 3 +- LeanCats/Macro.lean | 5 +- 3 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 LeanCats/Cats/examples/models/bpf.cat diff --git a/LeanCats/Cats/examples/models/bpf.cat b/LeanCats/Cats/examples/models/bpf.cat new file mode 100644 index 0000000..b4338be --- /dev/null +++ b/LeanCats/Cats/examples/models/bpf.cat @@ -0,0 +1,88 @@ +(*************) +(* Utilities *) +(*************) + +(* Single event atomic ops are marked with both R and W. + * These events are marked with SC if the op returns a value. + *) +let RMW = [R & W] & [SC] + +(* Atomic ops with separate R and W events are related by the amo relation. + * both of these R and W events are marked with SC if the op returns a value. + *) +let SRMW = (SC * SC) & amo + +(* Both single and double event atomics when marked with SC act as full + * barriers: + * 1. Single event RMW with SC: + * [M] -> RMWsc-> [M] + * + * 2. Double event RMW with SC: + * [M] -> Rsc -> Wsc -> [M] + *) +let po_amo_fetch = ([M];po;RMW) | (RMW;po;[M]) | ([M];po;[domain(SRMW)]) | ([range(SRMW)];po;[M]) + +show po_amo_fetch as atomicfetch + +(* Release Consistency processor consistent (RCpc) *) +let load_acquire = ([AQ];po;[M]) +let store_release = ([M];po;[RL]) +let rcpc = load_acquire | store_release + +(****************) +(* Dependencies *) +(****************) + +let addr_dep = [R];addr;[M] +let data_dep = [R];data;[W] +let ctrl_dep = [R];ctrl;[W] + +show addr_dep as addr +show data_dep as data +show ctrl_dep as ctrl + +(**********************) +(* ppo and prop rules *) +(**********************) + +(* Compute coherence relation *) +include "cos-opt.cat" +let com = co | rf | fr + +let ppo = +(* Explicit synchronization *) + po_amo_fetch | rcpc +(* Syntactic Dependencies *) +| addr_dep +| data_dep +| ctrl_dep +(* Pipeline Dependencies *) +| [M];(addr|data);[W];rfi;[R] +| [M];addr;[M];po;[W] +(* Overlapping-address ordering *) +| (coi | fri) + +(* Propagation ordering from SC and release operations *) +let A-cumul = rfe? ; (po_amo_fetch | store_release) +let prop = (coe | fre)? ; A-cumul* ; rfe? + +(**********) +(* Axioms *) +(**********) + +(* Sc per location *) +acyclic com | po-loc as Coherence + +(* No-Thin-Air and Observation *) +let hb = ppo | rfe | ((prop \ id) & int) +acyclic hb as Happens-before + +(* Propagation *) +let pb = prop ; po_amo_fetch ; hb* +acyclic pb as Propagation + +(* Atomicity *) +empty rmw & (fre;coe) as Atomic + +(* Atomic fetch as a fence *) +acyclic po_amo_fetch | com \ No newline at end of file diff --git a/LeanCats/Data.lean b/LeanCats/Data.lean index c43fed3..6d6adab 100644 --- a/LeanCats/Data.lean +++ b/LeanCats/Data.lean @@ -72,10 +72,9 @@ structure Events where (B : Set Event) (F : Set Event) (RMW : Set Event) - (M : Set Event) @[simp] def Events.all (evts : Events) := - evts.IW ∪ evts.R ∪ evts.W ∪ evts.B ∪ evts.F ∪ evts.RMW ∪ evts.M + evts.IW ∪ evts.R ∪ evts.W ∪ evts.B ∪ evts.F ∪ evts.RMW instance : Membership Event Events where mem := fun es e => e ∈ es.all diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index 3d0cb97..f03cf0e 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -211,8 +211,9 @@ macro_rules let nm := mkIdent "SRCU".toName `(($X.$evts.$nm : Set Event)) | `([annotable-events| M, $evts, $X]) => - let nm := mkIdent "M".toName - `(($X.$evts.$nm : Set Event)) + let reads := mkIdent "R".toName + let writes := mkIdent "W".toName + `(($X.$evts.$reads ∪ $X.$evts.$writes : Set Event)) macro_rules -- | `([predefined-events| ___]) => __ TODO!(figure all the definiations of all the events. (⋃?)) From 5ce3429391ac1d0d3d0ae080064882349fbabcef Mon Sep 17 00:00:00 2001 From: YangWiz Date: Fri, 27 Mar 2026 04:00:23 +0100 Subject: [PATCH 31/34] Fix bugs in macro --- LeanCats/Basic.lean | 1 + LeanCats/Macro.lean | 8 ++++---- LeanCats/Syntax.lean | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/LeanCats/Basic.lean b/LeanCats/Basic.lean index c50666a..3aa4138 100644 --- a/LeanCats/Basic.lean +++ b/LeanCats/Basic.lean @@ -24,6 +24,7 @@ structure CandidateExecution (evts : Events) where ctrl' : SetRel Event Event := ∅ fence' : SetRel Event Event := ∅ mb' : SetRel Event Event := ∅ + SYNC' : SetRel Event Event := ∅ uniqueId : ∀ (e₁ e₂ : Event), e₁ ∈ evts.all → e₂ ∈ evts.all -> e₁ ≠ e₂ diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index f03cf0e..21bbbee 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -165,6 +165,10 @@ macro_rules let nm := mkIdent "mb'".toName `($X.$nm) + | `([predefined-relations| SYNC , $_, $X]) => + let nm := mkIdent "SYNC'".toName + `($X.$nm) + macro_rules | `([keyword| and]) => Lean.Macro.throwUnsupported | `([keyword| as]) => Lean.Macro.throwUnsupported @@ -221,10 +225,6 @@ macro_rules let nm := mkIdent "IW".toName `($evts.$nm) - | `([predefined-events| M, $evts, $_]) => - let nm := mkIdent "M".toName - `($evts.$nm) - | `([predefined-events| $a:annotable_events, $evts, $X]) => `([annotable-events| $a, $evts, $X]) diff --git a/LeanCats/Syntax.lean b/LeanCats/Syntax.lean index 2189679..3a2441d 100644 --- a/LeanCats/Syntax.lean +++ b/LeanCats/Syntax.lean @@ -83,6 +83,7 @@ syntax "addr" : predefined_relations -- address dependencies, starts with a read syntax "rmb" : predefined_relations -- read memory barrier, read -> read syntax "wmb" : predefined_relations -- write memory barrier, write -> write syntax "fence" : predefined_relations -- fence barrier +syntax "SYNC" : predefined_relations -- SYNC instruction for mips. syntax keyword : dsl_term syntax num : dsl_term From 0ce4742939b394d8079d49fb2c63302beeb43139 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Fri, 27 Mar 2026 16:47:45 +0100 Subject: [PATCH 32/34] Add proof for x86 and mips --- LeanCats.lean | 1 + LeanCats/Basic.lean | 6 +- LeanCats/Cats/examples/models/mips.cat | 12 ++++ LeanCats/Cats/{ => examples/models}/sc.cat | 0 LeanCats/Cats/{ => examples/models}/tso.cat | 0 LeanCats/Cats/examples/models/tsox.cat | 4 ++ LeanCats/Cats/examples/models/x86.cat | 4 ++ LeanCats/Macro.lean | 29 ++++++--- LeanCats/ModelReader.lean | 2 +- LeanCats/Syntax.lean | 5 +- LeanCats/mipsWeakerThanTSO.lean | 69 +++++++++++++++++++++ 11 files changed, 120 insertions(+), 12 deletions(-) create mode 100644 LeanCats/Cats/examples/models/mips.cat rename LeanCats/Cats/{ => examples/models}/sc.cat (100%) rename LeanCats/Cats/{ => examples/models}/tso.cat (100%) create mode 100644 LeanCats/Cats/examples/models/tsox.cat create mode 100644 LeanCats/Cats/examples/models/x86.cat create mode 100644 LeanCats/mipsWeakerThanTSO.lean diff --git a/LeanCats.lean b/LeanCats.lean index 5e3c11b..d9c29a6 100644 --- a/LeanCats.lean +++ b/LeanCats.lean @@ -9,6 +9,7 @@ import LeanCats.CatPreprocessor import LeanCats.Macro import LeanCats.Theorems import LeanCats.TsoWeakerThanSc +import LeanCats.mipsWeakerThanTSO import LeanCats.ModelReader import LeanCats.Litmus import LeanCats.LitmusParser diff --git a/LeanCats/Basic.lean b/LeanCats/Basic.lean index 3aa4138..b8f1399 100644 --- a/LeanCats/Basic.lean +++ b/LeanCats/Basic.lean @@ -24,7 +24,11 @@ structure CandidateExecution (evts : Events) where ctrl' : SetRel Event Event := ∅ fence' : SetRel Event Event := ∅ mb' : SetRel Event Event := ∅ - SYNC' : SetRel Event Event := ∅ + SYNC' : Set Event := ∅ + -- Specific fence event sets depend on the test architecture, + -- their name is always uppercase and derives from the mnemonic of the instruction that generates them. + syncInF : ∀ (e : Event), e ∈ SYNC' → e ∈ evts.F + uniqueId : ∀ (e₁ e₂ : Event), e₁ ∈ evts.all → e₂ ∈ evts.all -> e₁ ≠ e₂ diff --git a/LeanCats/Cats/examples/models/mips.cat b/LeanCats/Cats/examples/models/mips.cat new file mode 100644 index 0000000..256eaee --- /dev/null +++ b/LeanCats/Cats/examples/models/mips.cat @@ -0,0 +1,12 @@ +let fencerel(r) = (po & (_ * r)) ; po +let com = rf | fr | co +acyclic po_loc | com as uniproc + +let sync = try fencerel(SYNC) with 0 + +empty rmw & (fre;coe) as atomic + +let ppo = po & (R * M) | sync + +let ghb = ppo | rfe | fr | co +acyclic ghb as pso \ No newline at end of file diff --git a/LeanCats/Cats/sc.cat b/LeanCats/Cats/examples/models/sc.cat similarity index 100% rename from LeanCats/Cats/sc.cat rename to LeanCats/Cats/examples/models/sc.cat diff --git a/LeanCats/Cats/tso.cat b/LeanCats/Cats/examples/models/tso.cat similarity index 100% rename from LeanCats/Cats/tso.cat rename to LeanCats/Cats/examples/models/tso.cat diff --git a/LeanCats/Cats/examples/models/tsox.cat b/LeanCats/Cats/examples/models/tsox.cat new file mode 100644 index 0000000..71f431b --- /dev/null +++ b/LeanCats/Cats/examples/models/tsox.cat @@ -0,0 +1,4 @@ +let xppo = ((W*W) | (R*W) | (R*R)) & po +let At = domain(rmw) | range(rmw) +let implied = po;[At | F] | [At | F];po +acyclic (implied | xppo | rfe | fr | co) as tso \ No newline at end of file diff --git a/LeanCats/Cats/examples/models/x86.cat b/LeanCats/Cats/examples/models/x86.cat new file mode 100644 index 0000000..f0b9aa1 --- /dev/null +++ b/LeanCats/Cats/examples/models/x86.cat @@ -0,0 +1,4 @@ +let xppo = ((W*W) | (R*W) | (R*R)) & po +let At = domain(rmw) | range(rmw) +let implied = po;[At | F] | [At | F];po +acyclic (implied | xppo | rfe | fr | co) as tso diff --git a/LeanCats/Macro.lean b/LeanCats/Macro.lean index 21bbbee..0aa0ce9 100644 --- a/LeanCats/Macro.lean +++ b/LeanCats/Macro.lean @@ -53,6 +53,21 @@ instance : Coe (TSyntax `predefined_relations) (TSyntax `expr) where def SetRel.mkId (s : Set Event) : SetRel Event Event := fun (e₁, e₂) => e₁ = e₂ ∧ e₁ ∈ s +@[simp] theorem SetRel.dom_mkId (s : Set Event) : SetRel.dom (SetRel.mkId s) = s := by + ext e + constructor + · intro h + rcases h with ⟨e', heq, hs⟩ + simpa [SetRel.mkId] using hs + · intro hs + exact ⟨e, rfl, hs⟩ + +instance : Coe (Set Event) (SetRel Event Event) where + coe := SetRel.mkId + +instance : Coe (SetRel Event Event) (Set Event) where + coe := SetRel.dom + macro_rules | `([dsl-term| $i:cat_ident, $evts, $X, $arg]) => -- Apply the arg instead of using the id in the env. @@ -92,6 +107,9 @@ macro_rules | `([expr| $e *, $evts, $X, $arg]) => `(([expr| $e, $evts, $X, $arg]) ∪ {(e₁, e₂) | e₁ = e₂}) + | `([expr| try $e with $_, $evts, $X, $arg]) => + `(([expr| $e, $evts, $X, $arg])) + | `([expr| $e +, $evts, $X, $arg]) => `(([expr| $e, $evts, $X, $arg])) @@ -199,6 +217,9 @@ macro_rules | `([annotable-events| W, $evts, $X]) => let nm := mkIdent "W".toName `(($X.$evts.$nm : Set Event)) + | `([annotable-events| _, $evts, $X]) => + let nm := mkIdent "all".toName + `(($X.$evts.$nm : Set Event)) | `([annotable-events| R, $evts, $X]) => let nm := mkIdent "R".toName `(($X.$evts.$nm : Set Event)) @@ -410,11 +431,3 @@ acyclic pb as propagation #reduce lkmm.atomic #reduce lkmm.happens_before #reduce lkmm.propagation - -[model| tso_x86 - -let xppo = ((W*W) | (R*W) | (R*R)) & po -let At = domain(rmw) | range(rmw) -let implied = po;[At | F] | [At | F];po -acyclic (implied | xppo | rfe | fr | co) as tso -] diff --git a/LeanCats/ModelReader.lean b/LeanCats/ModelReader.lean index fb2d022..5891291 100644 --- a/LeanCats/ModelReader.lean +++ b/LeanCats/ModelReader.lean @@ -26,7 +26,7 @@ def evalCat (s : String) : Lean.Elab.Command.CommandElabM Unit := do -- we can call IO under the CommandM. elab "defcat" "<" filename:str ">" : command => do let fn := Filename.mkName filename.getString - let path := "LeanCats/Cats/" ++ filename.getString + let path := "LeanCats/Cats/examples/models/" ++ filename.getString let s <- IO.FS.readFile path let model := "[model| " ++ fn.toString ++ " " ++ (removeComments s) ++ "]" -- Add the declaration to the environment diff --git a/LeanCats/Syntax.lean b/LeanCats/Syntax.lean index 3a2441d..3b1d52d 100644 --- a/LeanCats/Syntax.lean +++ b/LeanCats/Syntax.lean @@ -63,8 +63,8 @@ syntax "RMW" : annotable_events -- read-modify-write events syntax "SRCU" : annotable_events -- srcu events syntax "IW" : annotable_events -- initial writes syntax "M" : annotable_events -- memory events, M = W ∪ R +syntax "_" : annotable_events -- all events -syntax "___" : predefined_events -- all events syntax annotable_events : predefined_events /- defined_relations: -/ @@ -110,8 +110,9 @@ syntax expr "?" : expr syntax:71 expr "^-1" : expr -- The procedure will return a value, so we can use it in the expression. syntax dsl_term "(" expr,* ")" : expr - syntax "[" expr "]" : expr +-- Error handling in OCaml, we can just ignore it. +syntax "try" expr "with" expr : expr syntax assertion expr ("as" cat_ident)? : inst -- The flag is used to witness the assertion, so it doesn't change the states of the execution, we could just ignore it. diff --git a/LeanCats/mipsWeakerThanTSO.lean b/LeanCats/mipsWeakerThanTSO.lean new file mode 100644 index 0000000..1b85810 --- /dev/null +++ b/LeanCats/mipsWeakerThanTSO.lean @@ -0,0 +1,69 @@ +import LeanCats.Macro +import LeanCats.ModelReader +import LeanCats.Data +import LeanCats.Relations +import LeanCats.Theorems +import LeanCats.Basic + +defcat <"mips.cat"> +defcat <"tsox.cat"> + +theorem mipsWeakerThanTso + (evts : Data.Events) + (X : CandidateExecution evts) + : tsox.tso evts X → mips.pso evts X := +by + intro htso + unfold tsox.tso at htso + unfold mips.pso + simp + apply ayclicMono_trans htso + intro a b h + simp [CatRel.CatUnion.union, Set.mem_setOf_eq] at h + let rtso : SetRel Data.Event Data.Event := + CatRel.CatUnion.union (tsox.implied evts X) + (CatRel.CatUnion.union (tsox.xppo evts X) + (CatRel.CatUnion.union (rfe evts X) (CatRel.CatUnion.union X.fr' X.co'))) + change Relation.TransGen (fun e₁ e₂ => (e₁, e₂) ∈ rtso) a b + rcases h with hppo | h + · rcases hppo with ⟨hpo, hppo_core⟩ + rcases hppo_core with hpo_rm | hsync_raw + · have hxppo : (a, b) ∈ tsox.xppo evts X := by + rcases hpo_rm with ⟨haR, hbM⟩ + rcases hbM with hbR | hbW + · refine ⟨?_, hpo⟩ + exact Or.inr (Or.inr ⟨haR, hbR⟩) + · refine ⟨?_, hpo⟩ + exact Or.inr (Or.inl ⟨haR, hbW⟩) + have hstep : (a, b) ∈ rtso := by + exact Or.inr (Or.inl hxppo) + exact Relation.TransGen.single hstep + · rcases hsync_raw with ⟨mid, hhead, hpo_midb⟩ + rcases hhead with ⟨hpo_amid, hall_sync⟩ + rcases hall_sync with ⟨_, hmid_syncdom⟩ + have hmidSYNC : mid ∈ X.SYNC' := by + simpa [SetRel.dom_mkId] using hmid_syncdom + have hsyncInF : X.SYNC' ⊆ X.evts.F := by + exact X.syncInF + have hmidF : mid ∈ X.evts.F := hsyncInF hmidSYNC + have himplied_ab : (a, b) ∈ tsox.implied evts X := by + unfold tsox.implied + simp [CatRel.CatUnion.union] + refine ⟨mid, hpo_amid, ?_⟩ + refine Or.inr ?_ + refine ⟨mid, ?_, hpo_midb⟩ + exact ⟨rfl, Or.inr hmidF⟩ + have hstep : (a, b) ∈ rtso := by + exact Or.inl himplied_ab + exact Relation.TransGen.single hstep + · rcases h with hrfe | h + · have hstep : (a, b) ∈ rtso := by + exact Or.inr (Or.inr (Or.inl hrfe)) + exact Relation.TransGen.single hstep + · rcases h with hfr | hco + · have hstep : (a, b) ∈ rtso := by + exact Or.inr (Or.inr (Or.inr (Or.inl hfr))) + exact Relation.TransGen.single hstep + · have hstep : (a, b) ∈ rtso := by + exact Or.inr (Or.inr (Or.inr (Or.inr hco))) + exact Relation.TransGen.single hstep From 6629050cfc5694565b2a442462dd73ea848b93f8 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Sat, 28 Mar 2026 02:44:24 +0100 Subject: [PATCH 33/34] Add one counter example for mips and x86 --- LeanCats.lean | 1 + LeanCats/Basic.lean | 5 - LeanCats/Linux/CoRR_poonceonce_Once.lean | 11 +- LeanCats/Linux/X86_SB_rfi_pos.lean | 201 +++++++++++++++++++++++ LeanCats/Theorems.lean | 16 ++ LeanCats/mipsWeakerThanTSO.lean | 97 +++++------ 6 files changed, 275 insertions(+), 56 deletions(-) create mode 100644 LeanCats/Linux/X86_SB_rfi_pos.lean diff --git a/LeanCats.lean b/LeanCats.lean index d9c29a6..f23d9cb 100644 --- a/LeanCats.lean +++ b/LeanCats.lean @@ -17,3 +17,4 @@ import LeanCats.LitmusGraph import LeanCats.LitmusGraphBridge import LeanCats.LitmusReader import LeanCats.Linux.CoRR_poonceonce_Once +import LeanCats.Linux.X86_SB_rfi_pos diff --git a/LeanCats/Basic.lean b/LeanCats/Basic.lean index b8f1399..9e85c91 100644 --- a/LeanCats/Basic.lean +++ b/LeanCats/Basic.lean @@ -39,11 +39,6 @@ structure CandidateExecution (evts : Events) where (w, r) ∈ rf' → w.t_id = r.t_id → (w, r) ∈ evts.po - coWR : ∀ (w r w' : Event), - (w, r) ∈ evts.po - → w.effect.location = r.effect.location - → (w', r) ∈ rf' - → (w, w') ∈ co' ∨ w = w' /-- from-reads: always defined as rf⁻¹ ; co, so it is transparent to the kernel. -/ @[simp] def CandidateExecution.fr' {evts : Events} (X : CandidateExecution evts) : SetRel Event Event := diff --git a/LeanCats/Linux/CoRR_poonceonce_Once.lean b/LeanCats/Linux/CoRR_poonceonce_Once.lean index 6b322ee..f453d13 100644 --- a/LeanCats/Linux/CoRR_poonceonce_Once.lean +++ b/LeanCats/Linux/CoRR_poonceonce_Once.lean @@ -29,10 +29,10 @@ abbrev x := 0 @[simp] abbrev p1r0 : Data.Event := Data.Event.mk 2 1 rOpX1 ⟨lkmm.Accesses, lkmm.Accesses.ONCE'⟩ @[simp] abbrev p1r1 : Data.Event := - Data.Event.mk 3 2 rOpX0 ⟨lkmm.Accesses, lkmm.Accesses.ONCE'⟩ + Data.Event.mk 3 1 rOpX0 ⟨lkmm.Accesses, lkmm.Accesses.ONCE'⟩ @[simp] abbrev corr_evts : Data.Events := - Data.Events.mk {initWx} {p1r0, p1r1} {initWx, p0wX} {} {} {} {} + Data.Events.mk {initWx} {p1r0, p1r1} {initWx, p0wX} {} {} {} @[simp] def corr_co : SetRel Event Event := {(initWx, p0wX)} @[simp] def corr_po : SetRel Event Event := {(p1r0, p1r1)} @@ -65,17 +65,20 @@ Chosen rf edges: `p0wX -> p1r0` and `initWx -> p1r1`. -/ def corr_test : CandidateExecution corr_evts := { + evts := corr_evts po' := corr_po prePo := by candidateExecution_wf rf' := corr_rf rfInst := corr_rfInst co' := corr_co + rmw' := ∅ preRMW := instWellformedRmwEmpty corr_evts + syncInF := by + intro e h + contradiction uniqueId := uniqueId_by_id corr_evts rfiPo := by candidateExecution_wf - coWR := by - candidateExecution_wf } theorem corr_FindCycle : ¬ (lkmm.coherence corr_evts corr_test) := by diff --git a/LeanCats/Linux/X86_SB_rfi_pos.lean b/LeanCats/Linux/X86_SB_rfi_pos.lean new file mode 100644 index 0000000..1e0e34c --- /dev/null +++ b/LeanCats/Linux/X86_SB_rfi_pos.lean @@ -0,0 +1,201 @@ +import LeanCats.Basic +import LeanCats.Data +import LeanCats.Macro +import LeanCats.ModelReader +import LeanCats.Data +import LeanCats.Relations +import LeanCats.Theorems +import LeanCats.Basic + +defcat <"mips.cat"> +defcat <"tsox.cat"> + +set_option maxHeartbeats 2000000 + +open Data + +namespace Litmus + + +instance instWellformedPo (evts : Data.Events) : wellformed.po evts.po := by + intro x y z hxy hyz + rcases hxy with ⟨hx, hy, hxyTid, hxyLt⟩ + rcases hyz with ⟨_, hz, hyzTid, hyzLt⟩ + exact ⟨hx, hz, Eq.trans hxyTid hyzTid, Nat.lt_trans hxyLt hyzLt⟩ + +instance instWellformedRmwEmpty (evts : Data.Events) : wellformed.rmw evts (∅ : SetRel Event Event) := by + intro e h + contradiction + +abbrev x := 0 +abbrev y := 1 + +inductive Normal where +| none : Normal + +@[simp] abbrev initOpX : Data.Effect := + Data.Effect.mk Data.Op.write x (some 0) true false +@[simp] abbrev initOpY : Data.Effect := + Data.Effect.mk Data.Op.write y (some 0) true false +@[simp] abbrev wOpX : Data.Effect := + Data.Effect.mk Data.Op.write x (some 1) false false +@[simp] abbrev wOpY : Data.Effect := + Data.Effect.mk Data.Op.write y (some 1) false false +@[simp] abbrev rOpX1 : Data.Effect := + Data.Effect.mk Data.Op.read x (some 1) false false +@[simp] abbrev rOpY1 : Data.Effect := + Data.Effect.mk Data.Op.read y (some 1) false false +@[simp] abbrev rOpX0 : Data.Effect := + Data.Effect.mk Data.Op.read x (some 0) false false +@[simp] abbrev rOpY0 : Data.Effect := + Data.Effect.mk Data.Op.read y (some 0) false false + +@[simp] abbrev initWx : Data.Event := + Data.Event.mk 100 10 initOpX ⟨Normal, Normal.none⟩ +@[simp] abbrev initWy : Data.Event := + Data.Event.mk 101 10 initOpY ⟨Normal, Normal.none⟩ + +@[simp] abbrev p0wX : Data.Event := + Data.Event.mk 1 0 wOpX ⟨Normal, Normal.none⟩ +@[simp] abbrev p0rX1 : Data.Event := + Data.Event.mk 2 0 rOpX1 ⟨Normal, Normal.none⟩ +@[simp] abbrev p0rY0 : Data.Event := + Data.Event.mk 3 0 rOpY0 ⟨Normal, Normal.none⟩ + +@[simp] abbrev p1wY : Data.Event := + Data.Event.mk 4 1 wOpY ⟨Normal, Normal.none⟩ +@[simp] abbrev p1rY1 : Data.Event := + Data.Event.mk 5 1 rOpY1 ⟨Normal, Normal.none⟩ +@[simp] abbrev p1rX0 : Data.Event := + Data.Event.mk 6 1 rOpX0 ⟨Normal, Normal.none⟩ + +/-- X86 SB+rfi-pos + +P0: W(x)=1; R(x)=1; R(y)=0 +P1: W(y)=1; R(y)=1; R(x)=0 -/ +@[simp] abbrev sbrfi_evts : Data.Events := + Data.Events.mk + {initWx, initWy} + {p0rX1, p0rY0, p1rY1, p1rX0} + {initWx, initWy, p0wX, p1wY} + {} + {} + {} + +@[simp] def sbrfi_co : SetRel Event Event := + {(initWx, p0wX), (initWy, p1wY)} + +instance : wellformed.co sbrfi_evts sbrfi_co where + irrefl := by aesop + trans := by aesop + preco := { + wellTyped := by aesop + total := by candidateExecution_wf + } + +/-- rf edges encode the expected outcome: + - rfi: p0wX -> p0rX1 and p1wY -> p1rY1 + - reads of 0 from init writes: initWy -> p0rY0 and initWx -> p1rX0 -/ +@[simp] def sbrfi_rf : SetRel Event Event := + {(p0wX, p0rX1), (p1wY, p1rY1), (initWy, p0rY0), (initWx, p1rX0)} + +@[simp] def sbrfi_rfInst : wellformed.rf sbrfi_evts sbrfi_rf := + Data.wellformed.rf.mk + (by + candidateExecution_wf + ) + (by + candidateExecution_wf + ) + +@[simp] def sbrfi_test : CandidateExecution sbrfi_evts := + { + evts := sbrfi_evts + po' := sbrfi_evts.po + prePo := instWellformedPo sbrfi_evts + rf' := sbrfi_rf + rfInst := sbrfi_rfInst + co' := sbrfi_co + rmw' := ∅ + preRMW := instWellformedRmwEmpty sbrfi_evts + uniqueId := uniqueId_by_id sbrfi_evts + syncInF := by + intro e h + contradiction + rfiPo := by + intro w r hrf htid + simp [sbrfi_rf] at hrf + rcases hrf with h | h | h | h + · rcases h with ⟨rfl, rfl⟩ + exact ⟨by simp [Data.Events.all], by simp [Data.Events.all], rfl, by decide⟩ + · rcases h with ⟨rfl, rfl⟩ + exact ⟨by simp [Data.Events.all], by simp [Data.Events.all], rfl, by decide⟩ + · rcases h with ⟨rfl, rfl⟩ + simp at htid + · rcases h with ⟨rfl, rfl⟩ + simp at htid + } + +example : tsox.tso sbrfi_evts sbrfi_test := by + simp only [tsox.tso] + apply acyclic_of_rank (fun e => match e.id with + | 100 => 0 -- initWx + | 101 => 1 -- initWy + | 2 => 2 -- p0rX1 + | 5 => 3 -- p1rY1 + | 3 => 4 -- p0rY0 + | 6 => 5 -- p1rX0 + | 4 => 6 -- p1wY + | 1 => 7 -- p0wX + | _ => 8) + intro a b hab + simp only [CatRel.CatUnion.union, CatRel.SetRel.union, Set.mem_setOf_eq, + tsox.implied, tsox.xppo, tsox.At, rfe, + CandidateExecution.fr', SetRel.inv, SetRel.comp, + sbrfi_test, sbrfi_rf, sbrfi_co, sbrfi_evts, + Data.Events.all, Data.Events.po, SetRel.mkId, + CatRel.prod, Set.mem_prod, Set.prod, + CatRel.W, CatRel.R, + CatRel.Rel.external, CatRel.Rel.internal, + Set.mem_inter_iff, Set.mem_union, Set.mem_setOf_eq, + Set.mem_insert_iff, Set.mem_singleton_iff, Set.mem_empty_iff_false, + Set.mem_preimage, Prod.swap, + SetRel.dom, SetRel.cod, SetRel.dom_mkId, + Prod.mk.injEq, Prod.fst, Prod.snd, + and_false, false_and, false_or, or_false, + not_true, not_false_eq_true, + exists_false, exists_eq_left, exists_eq_left'] at hab + -- Eliminate implied branch (rmw = ∅ makes At = ∅, so SetRel.mkId of ∅ has no members) + rcases hab with ⟨mid, _, hmid_in | ⟨mid2, hmid2_in, _⟩⟩ | hab' + · obtain ⟨_, h⟩ := hmid_in; exact absurd h nofun + · obtain ⟨_, h⟩ := hmid2_in; exact absurd h nofun + -- Now hab' is: xppo ∨ rfe ∨ fr ∨ co + rcases hab' with hxppo | (hrfe | (hfr | hco)) + -- xppo: ((W×W)|(R×W)|(R×R)) ∩ po + · obtain ⟨hprod, hpo⟩ := hxppo + simp only [Set.mem_setOf_eq] at hprod + change a ∈ sbrfi_evts.all ∧ b ∈ sbrfi_evts.all ∧ a.t_id = b.t_id ∧ a.id < b.id at hpo + obtain ⟨_, _, htid, hlt⟩ := hpo + rcases hprod with (⟨ha, hb⟩ | ⟨ha, hb⟩ | ⟨ha, hb⟩) <;> + rcases ha with rfl | rfl | rfl | rfl <;> + rcases hb with rfl | rfl | rfl | rfl <;> + simp_all + -- rfe: rf ∩ external (only external pairs survive) + · obtain ⟨hrf, htid⟩ := hrfe + rcases hrf with ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ + · exact absurd rfl htid -- p0wX → p0rX1: same thread, contradiction + · exact absurd rfl htid -- p1wY → p1rY1: same thread, contradiction + · decide -- initWy → p0rY0: 1 < 4 ✓ + · decide -- initWx → p1rX0: 0 < 5 ✓ + -- fr: rf⁻¹ ; co + · obtain ⟨w, hrf_inv, hco⟩ := hfr + rcases hrf_inv with ⟨hw, ha⟩ | ⟨hw, ha⟩ | ⟨hw, ha⟩ | ⟨hw, ha⟩ <;> + rcases hco with ⟨hw', hb⟩ | ⟨hw', hb⟩ <;> + subst ha hb hw <;> simp_all <;> (try decide) <;> (try (subst hw'; decide)) + -- co + · rcases hco with ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ <;> decide + +example : ¬ mips.uniproc sbrfi_evts sbrfi_test := by + simp + +end Litmus diff --git a/LeanCats/Theorems.lean b/LeanCats/Theorems.lean index 6b30f72..8d9beb4 100644 --- a/LeanCats/Theorems.lean +++ b/LeanCats/Theorems.lean @@ -234,3 +234,19 @@ theorem fr_co_subset_fr simp only [CandidateExecution.fr'] at * obtain ⟨w₀, h_inv, h_co⟩ := hfr exact ⟨w₀, h_inv, X.preCo.trans w₀ w w' h_co hco⟩ + +/-- If a function f : Event → ℕ strictly increases along every edge of r, + then r is acyclic. A transitive path can only increase f, so no cycle exists. -/ +lemma acyclic_of_rank + {r : SetRel Event Event} + (f : Event → ℕ) + (hf : ∀ a b, (a, b) ∈ r → f a < f b) : + SetRel.Acyclic r := by + intro a ha + simp only [SetRel.TransGen, Set.mem_setOf_eq] at ha + have key : ∀ x y, Relation.TransGen (fun e₁ e₂ => (e₁, e₂) ∈ r) x y → f x < f y := by + intro x y hxy + induction hxy with + | single h => exact hf _ _ h + | tail _ h ih => exact Nat.lt_trans ih (hf _ _ h) + exact Nat.lt_irrefl _ (key a a ha) diff --git a/LeanCats/mipsWeakerThanTSO.lean b/LeanCats/mipsWeakerThanTSO.lean index 1b85810..d58d4d2 100644 --- a/LeanCats/mipsWeakerThanTSO.lean +++ b/LeanCats/mipsWeakerThanTSO.lean @@ -8,6 +8,9 @@ import LeanCats.Basic defcat <"mips.cat"> defcat <"tsox.cat"> +-- SC is not sc per-location, thus X86 is not strictly stronger than mips. +-- The X86 is not strong enough to prove the atomicity +-- Because the X86 only make sure the execution is acyclic, but it's not. theorem mipsWeakerThanTso (evts : Data.Events) (X : CandidateExecution evts) @@ -17,53 +20,53 @@ by unfold tsox.tso at htso unfold mips.pso simp - apply ayclicMono_trans htso - intro a b h - simp [CatRel.CatUnion.union, Set.mem_setOf_eq] at h - let rtso : SetRel Data.Event Data.Event := - CatRel.CatUnion.union (tsox.implied evts X) - (CatRel.CatUnion.union (tsox.xppo evts X) - (CatRel.CatUnion.union (rfe evts X) (CatRel.CatUnion.union X.fr' X.co'))) - change Relation.TransGen (fun e₁ e₂ => (e₁, e₂) ∈ rtso) a b - rcases h with hppo | h - · rcases hppo with ⟨hpo, hppo_core⟩ - rcases hppo_core with hpo_rm | hsync_raw - · have hxppo : (a, b) ∈ tsox.xppo evts X := by - rcases hpo_rm with ⟨haR, hbM⟩ - rcases hbM with hbR | hbW - · refine ⟨?_, hpo⟩ - exact Or.inr (Or.inr ⟨haR, hbR⟩) - · refine ⟨?_, hpo⟩ - exact Or.inr (Or.inl ⟨haR, hbW⟩) - have hstep : (a, b) ∈ rtso := by - exact Or.inr (Or.inl hxppo) - exact Relation.TransGen.single hstep - · rcases hsync_raw with ⟨mid, hhead, hpo_midb⟩ - rcases hhead with ⟨hpo_amid, hall_sync⟩ - rcases hall_sync with ⟨_, hmid_syncdom⟩ - have hmidSYNC : mid ∈ X.SYNC' := by - simpa [SetRel.dom_mkId] using hmid_syncdom - have hsyncInF : X.SYNC' ⊆ X.evts.F := by - exact X.syncInF - have hmidF : mid ∈ X.evts.F := hsyncInF hmidSYNC - have himplied_ab : (a, b) ∈ tsox.implied evts X := by - unfold tsox.implied - simp [CatRel.CatUnion.union] - refine ⟨mid, hpo_amid, ?_⟩ - refine Or.inr ?_ - refine ⟨mid, ?_, hpo_midb⟩ - exact ⟨rfl, Or.inr hmidF⟩ - have hstep : (a, b) ∈ rtso := by - exact Or.inl himplied_ab - exact Relation.TransGen.single hstep - · rcases h with hrfe | h - · have hstep : (a, b) ∈ rtso := by - exact Or.inr (Or.inr (Or.inl hrfe)) - exact Relation.TransGen.single hstep - · rcases h with hfr | hco - · have hstep : (a, b) ∈ rtso := by - exact Or.inr (Or.inr (Or.inr (Or.inl hfr))) + . apply ayclicMono_trans htso + intro a b h + simp [CatRel.CatUnion.union, Set.mem_setOf_eq] at h + let rtso : SetRel Data.Event Data.Event := + CatRel.CatUnion.union (tsox.implied evts X) + (CatRel.CatUnion.union (tsox.xppo evts X) + (CatRel.CatUnion.union (rfe evts X) (CatRel.CatUnion.union X.fr' X.co'))) + change Relation.TransGen (fun e₁ e₂ => (e₁, e₂) ∈ rtso) a b + rcases h with hppo | h + · rcases hppo with ⟨hpo, hppo_core⟩ + rcases hppo_core with hpo_rm | hsync_raw + · have hxppo : (a, b) ∈ tsox.xppo evts X := by + rcases hpo_rm with ⟨haR, hbM⟩ + rcases hbM with hbR | hbW + · refine ⟨?_, hpo⟩ + exact Or.inr (Or.inr ⟨haR, hbR⟩) + · refine ⟨?_, hpo⟩ + exact Or.inr (Or.inl ⟨haR, hbW⟩) + have hstep : (a, b) ∈ rtso := by + exact Or.inr (Or.inl hxppo) + exact Relation.TransGen.single hstep + · rcases hsync_raw with ⟨mid, hhead, hpo_midb⟩ + rcases hhead with ⟨hpo_amid, hall_sync⟩ + rcases hall_sync with ⟨_, hmid_syncdom⟩ + have hmidSYNC : mid ∈ X.SYNC' := by + simpa [SetRel.dom_mkId] using hmid_syncdom + have hsyncInF : X.SYNC' ⊆ X.evts.F := by + exact X.syncInF + have hmidF : mid ∈ X.evts.F := hsyncInF hmidSYNC + have himplied_ab : (a, b) ∈ tsox.implied evts X := by + unfold tsox.implied + simp [CatRel.CatUnion.union] + refine ⟨mid, hpo_amid, ?_⟩ + refine Or.inr ?_ + refine ⟨mid, ?_, hpo_midb⟩ + exact ⟨rfl, Or.inr hmidF⟩ + have hstep : (a, b) ∈ rtso := by + exact Or.inl himplied_ab exact Relation.TransGen.single hstep + · rcases h with hrfe | h · have hstep : (a, b) ∈ rtso := by - exact Or.inr (Or.inr (Or.inr (Or.inr hco))) + exact Or.inr (Or.inr (Or.inl hrfe)) exact Relation.TransGen.single hstep + · rcases h with hfr | hco + · have hstep : (a, b) ∈ rtso := by + exact Or.inr (Or.inr (Or.inr (Or.inl hfr))) + exact Relation.TransGen.single hstep + · have hstep : (a, b) ∈ rtso := by + exact Or.inr (Or.inr (Or.inr (Or.inr hco))) + exact Relation.TransGen.single hstep From b6e4f595c6a4f07dd0ff5963df81c1c97f43b769 Mon Sep 17 00:00:00 2001 From: YangWiz Date: Tue, 31 Mar 2026 01:18:41 +0200 Subject: [PATCH 34/34] Add SB and LB litmus tests --- LeanCats/Cats/examples/models/bpf.cat | 11 +- LeanCats/Cats/examples/models/tso.cat | 2 + LeanCats/Cats/examples/models/x86.cat | 1 + LeanCats/Linux/LB.lean | 233 ++++++++++++++++++++++++++ LeanCats/Linux/SB.lean | 220 ++++++++++++++++++++++++ LeanCats/Linux/X86_SB_rfi_pos.lean | 4 - LeanCats/Linux/X86_mips_rf.lean | 137 +++++++++++++++ 7 files changed, 595 insertions(+), 13 deletions(-) create mode 100644 LeanCats/Linux/LB.lean create mode 100644 LeanCats/Linux/SB.lean create mode 100644 LeanCats/Linux/X86_mips_rf.lean diff --git a/LeanCats/Cats/examples/models/bpf.cat b/LeanCats/Cats/examples/models/bpf.cat index b4338be..75a592e 100644 --- a/LeanCats/Cats/examples/models/bpf.cat +++ b/LeanCats/Cats/examples/models/bpf.cat @@ -12,15 +12,8 @@ let RMW = [R & W] & [SC] *) let SRMW = (SC * SC) & amo -(* Both single and double event atomics when marked with SC act as full - * barriers: - * 1. Single event RMW with SC: - * [M] -> RMWsc-> [M] - * - * 2. Double event RMW with SC: - * [M] -> Rsc -> Wsc -> [M] - *) -let po_amo_fetch = ([M];po;RMW) | (RMW;po;[M]) | ([M];po;[domain(SRMW)]) | ([range(SRMW)];po;[M]) +(* Ignore the SRCU part *) +let po_amo_fetch = ([M];po;RMW) | (RMW;po;[M]) show po_amo_fetch as atomicfetch diff --git a/LeanCats/Cats/examples/models/tso.cat b/LeanCats/Cats/examples/models/tso.cat index 2f0b285..73c7c5b 100644 --- a/LeanCats/Cats/examples/models/tso.cat +++ b/LeanCats/Cats/examples/models/tso.cat @@ -1,5 +1,7 @@ include "cos.cat" +empty rmw & (fre;coe) + let xppo = ((W*W) | (R*W) | (R*R)) & po let At = domain(rmw) | range(rmw) let implied = po;[At | F] | [At | F];po diff --git a/LeanCats/Cats/examples/models/x86.cat b/LeanCats/Cats/examples/models/x86.cat index f0b9aa1..f9c30c1 100644 --- a/LeanCats/Cats/examples/models/x86.cat +++ b/LeanCats/Cats/examples/models/x86.cat @@ -2,3 +2,4 @@ let xppo = ((W*W) | (R*W) | (R*R)) & po let At = domain(rmw) | range(rmw) let implied = po;[At | F] | [At | F];po acyclic (implied | xppo | rfe | fr | co) as tso +acyclic (po_loc | rf | fr | co) as uniproc diff --git a/LeanCats/Linux/LB.lean b/LeanCats/Linux/LB.lean new file mode 100644 index 0000000..0b415cd --- /dev/null +++ b/LeanCats/Linux/LB.lean @@ -0,0 +1,233 @@ +import LeanCats.Basic +import LeanCats.Data +import LeanCats.Macro +import LeanCats.ModelReader +import LeanCats.Data +import LeanCats.Relations +import LeanCats.Theorems +import LeanCats.Basic +import LeanCats.mipsWeakerThanTSO + +-- In this litmus test, we want to show that sometimes the X86 is weaker than mips because of the sc-per-location. + +-- init x = 0, y = 0 +-- +-- rX rY +-- Wy=1 rX=1 + +-- exists: rX0 = 1 ∧ rX1 = 1 + +open Data + +namespace Litmus + + +instance instWellformedPo (evts : Data.Events) : wellformed.po evts.po := by + intro x y z hxy hyz + rcases hxy with ⟨hx, hy, hxyTid, hxyLt⟩ + rcases hyz with ⟨_, hz, hyzTid, hyzLt⟩ + exact ⟨hx, hz, Eq.trans hxyTid hyzTid, Nat.lt_trans hxyLt hyzLt⟩ + +instance instWellformedRmwEmpty (evts : Data.Events) : wellformed.rmw evts (∅ : SetRel Event Event) := by + intro e h + contradiction + +abbrev x := 0 +abbrev y := 1 + +inductive Normal where +| none : Normal + +@[simp] abbrev initOpX : Data.Effect := + Data.Effect.mk Data.Op.write x (some 0) true false +@[simp] abbrev initOpY : Data.Effect := + Data.Effect.mk Data.Op.write y (some 0) true false +@[simp] abbrev wOpX : Data.Effect := + Data.Effect.mk Data.Op.write x (some 1) false false +@[simp] abbrev wOpY : Data.Effect := + Data.Effect.mk Data.Op.write y (some 1) false false +@[simp] abbrev rOpX1 : Data.Effect := + Data.Effect.mk Data.Op.read x (some 1) false false +@[simp] abbrev rOpY1 : Data.Effect := + Data.Effect.mk Data.Op.read y (some 1) false false + +@[simp] abbrev initWx : Data.Event := + Data.Event.mk 100 10 initOpX ⟨Normal, Normal.none⟩ +@[simp] abbrev initWy : Data.Event := + Data.Event.mk 101 10 initOpY ⟨Normal, Normal.none⟩ + +@[simp] abbrev p0wX : Data.Event := + Data.Event.mk 3 0 wOpX ⟨Normal, Normal.none⟩ +@[simp] abbrev p0rY0 : Data.Event := + Data.Event.mk 1 0 rOpY1 ⟨Normal, Normal.none⟩ + +@[simp] abbrev p1wY : Data.Event := + Data.Event.mk 6 1 wOpY ⟨Normal, Normal.none⟩ +@[simp] abbrev p1rX0 : Data.Event := + Data.Event.mk 4 1 rOpX1 ⟨Normal, Normal.none⟩ + +/-- X86 SB+rfi-pos + +P0: W(x)=1; R(x)=1; R(y)=0 +P1: W(y)=1; R(y)=1; R(x)=0 -/ +@[simp] abbrev sb_evts : Data.Events := + Data.Events.mk + {initWx, initWy} + {p0rY0, p1rX0} + {initWx, initWy, p0wX, p1wY} + {} + {} + {} + +@[simp] def sb_co : SetRel Event Event := + {(initWx, p0wX), (initWy, p1wY)} + +instance : wellformed.co sb_evts sb_co where + irrefl := by aesop + trans := by aesop + preco := { + wellTyped := by aesop + total := by candidateExecution_wf + } + +/-- rf edges encode the expected outcome: + - rfi: p0wX -> p0rX1 and p1wY -> p1rY1 + - reads of 0 from init writes: initWy -> p0rY0 and initWx -> p1rX0 -/ +@[simp] def sb_rf : SetRel Event Event := + {(p1wY, p0rY0), (p0wX, p1rX0)} + +@[simp] def sb_rfInst : wellformed.rf sb_evts sb_rf := + Data.wellformed.rf.mk + (by + candidateExecution_wf + ) + (by + candidateExecution_wf + ) + +@[simp] def sb_test : CandidateExecution sb_evts := + { + evts := sb_evts + po' := sb_evts.po + prePo := instWellformedPo sb_evts + rf' := sb_rf + rfInst := sb_rfInst + co' := sb_co + rmw' := ∅ + preRMW := instWellformedRmwEmpty sb_evts + uniqueId := uniqueId_by_id sb_evts + syncInF := by + intro e h + contradiction + rfiPo := by + candidateExecution_wf + } + +defcat <"mips.cat"> +defcat <"tsox.cat"> +defcat <"sc.cat"> + +example : ¬ sc.sc sb_evts sb_test := +by + intro hsc + simp [sc.sc, CatRel.CatUnion.union, sb_test, sb_rf, sb_co, CandidateExecution.fr', SetRel.comp, SetRel.inv] at hsc + apply hsc p0rY0 + exact + Relation.TransGen.head (a := p0rY0) (b := p0wX) + (by + left + exact ⟨by simp [Data.Events.all], by simp [Data.Events.all], rfl, by decide⟩) + (Relation.TransGen.head (a := p0wX) (b := p1rX0) + (by + right + left + simp) + (Relation.TransGen.head (a := p1rX0) (b := p1wY) + (by + left + exact ⟨by simp [Data.Events.all], by simp [Data.Events.all], rfl, by decide⟩) + (Relation.TransGen.single (a := p1wY) (b := p0rY0) + (by + right + left + simp)))) + +theorem sb_tso : ¬ tsox.tso sb_evts sb_test := +by + intro htso + unfold tsox.tso at htso + let rtso : SetRel Data.Event Data.Event := + CatRel.CatUnion.union (tsox.implied sb_evts sb_test) + (CatRel.CatUnion.union (tsox.xppo sb_evts sb_test) + (CatRel.CatUnion.union (rfe sb_evts sb_test) + (CatRel.CatUnion.union sb_test.fr' sb_test.co'))) + have h1xppo : (p0rY0, p0wX) ∈ tsox.xppo sb_evts sb_test := by + refine ⟨?_, ?_⟩ + · exact Or.inr (Or.inl ⟨by simp, by simp⟩) + · exact ⟨by simp [Data.Events.all], by simp [Data.Events.all], rfl, by decide⟩ + have h2rfe : (p0wX, p1rX0) ∈ rfe sb_evts sb_test := by + refine ⟨?_, ?_⟩ + · simp [sb_test, sb_rf] + · simp [CatRel.Rel.external] + have h3xppo : (p1rX0, p1wY) ∈ tsox.xppo sb_evts sb_test := by + refine ⟨?_, ?_⟩ + · exact Or.inr (Or.inl ⟨by simp, by simp⟩) + · exact ⟨by simp [Data.Events.all], by simp [Data.Events.all], rfl, by decide⟩ + have h4rfe : (p1wY, p0rY0) ∈ rfe sb_evts sb_test := by + refine ⟨?_, ?_⟩ + · simp [sb_test, sb_rf] + · simp [CatRel.Rel.external] + have h1 : (p0rY0, p0wX) ∈ rtso := by exact Or.inr (Or.inl h1xppo) + have h2 : (p0wX, p1rX0) ∈ rtso := by exact Or.inr (Or.inr (Or.inl h2rfe)) + have h3 : (p1rX0, p1wY) ∈ rtso := by exact Or.inr (Or.inl h3xppo) + have h4 : (p1wY, p0rY0) ∈ rtso := by exact Or.inr (Or.inr (Or.inl h4rfe)) + apply htso p0rY0 + exact Relation.TransGen.head h1 (Relation.TransGen.head h2 (Relation.TransGen.head h3 (Relation.TransGen.single h4))) + +example : ¬ mips.pso sb_evts sb_test := +by + intro hmips + have hacyc : CatRel.SetRel.Acyclic + (CatRel.CatUnion.union (mips.ppo sb_evts sb_test) + (CatRel.CatUnion.union (rfe sb_evts sb_test) + (CatRel.CatUnion.union sb_test.fr' sb_test.co'))) := by + simpa [mips.pso, CatRel.CatUnion.union] using hmips + + have h1ppo : (p0rY0, p0wX) ∈ mips.ppo sb_evts sb_test := by + refine ⟨?_, ?_⟩ + · exact ⟨by simp [Data.Events.all], by simp [Data.Events.all], rfl, by decide⟩ + · left + exact ⟨by simp, Or.inr (by simp)⟩ + have h2rfe : (p0wX, p1rX0) ∈ rfe sb_evts sb_test := by + refine ⟨?_, ?_⟩ + · simp [sb_test, sb_rf] + · simp [CatRel.Rel.external] + have h3ppo : (p1rX0, p1wY) ∈ mips.ppo sb_evts sb_test := by + refine ⟨?_, ?_⟩ + · exact ⟨by simp [Data.Events.all], by simp [Data.Events.all], rfl, by decide⟩ + · left + exact ⟨by simp, Or.inr (by simp)⟩ + have h4rfe : (p1wY, p0rY0) ∈ rfe sb_evts sb_test := by + refine ⟨?_, ?_⟩ + · simp [sb_test, sb_rf] + · simp [CatRel.Rel.external] + + have h1 : (p0rY0, p0wX) ∈ CatRel.CatUnion.union (mips.ppo sb_evts sb_test) + (CatRel.CatUnion.union (rfe sb_evts sb_test) + (CatRel.CatUnion.union sb_test.fr' sb_test.co')) := by + exact Or.inl h1ppo + have h2 : (p0wX, p1rX0) ∈ CatRel.CatUnion.union (mips.ppo sb_evts sb_test) + (CatRel.CatUnion.union (rfe sb_evts sb_test) + (CatRel.CatUnion.union sb_test.fr' sb_test.co')) := by + exact Or.inr (Or.inl h2rfe) + have h3 : (p1rX0, p1wY) ∈ CatRel.CatUnion.union (mips.ppo sb_evts sb_test) + (CatRel.CatUnion.union (rfe sb_evts sb_test) + (CatRel.CatUnion.union sb_test.fr' sb_test.co')) := by + exact Or.inl h3ppo + have h4 : (p1wY, p0rY0) ∈ CatRel.CatUnion.union (mips.ppo sb_evts sb_test) + (CatRel.CatUnion.union (rfe sb_evts sb_test) + (CatRel.CatUnion.union sb_test.fr' sb_test.co')) := by + exact Or.inr (Or.inl h4rfe) + + apply hacyc p0rY0 + exact Relation.TransGen.head h1 (Relation.TransGen.head h2 (Relation.TransGen.head h3 (Relation.TransGen.single h4))) diff --git a/LeanCats/Linux/SB.lean b/LeanCats/Linux/SB.lean new file mode 100644 index 0000000..19dec88 --- /dev/null +++ b/LeanCats/Linux/SB.lean @@ -0,0 +1,220 @@ +import LeanCats.Basic +import LeanCats.Data +import LeanCats.Macro +import LeanCats.ModelReader +import LeanCats.Data +import LeanCats.Relations +import LeanCats.Theorems +import LeanCats.Basic +import LeanCats.mipsWeakerThanTSO + +-- In this litmus test, we want to show that sometimes the X86 is weaker than mips because of the sc-per-location. + +-- init x = 0 +-- +-- t0 t1 +-- Wx = 1 Wx = 2 +-- rX rX + +-- exists: rX0 = 2 ∧ rX1 = 1 +-- The X86 allows this. +-- But mips doesn't + +open Data + +namespace Litmus + + +instance instWellformedPo (evts : Data.Events) : wellformed.po evts.po := by + intro x y z hxy hyz + rcases hxy with ⟨hx, hy, hxyTid, hxyLt⟩ + rcases hyz with ⟨_, hz, hyzTid, hyzLt⟩ + exact ⟨hx, hz, Eq.trans hxyTid hyzTid, Nat.lt_trans hxyLt hyzLt⟩ + +instance instWellformedRmwEmpty (evts : Data.Events) : wellformed.rmw evts (∅ : SetRel Event Event) := by + intro e h + contradiction + +abbrev x := 0 +abbrev y := 1 + +inductive Normal where +| none : Normal + +@[simp] abbrev initOpX : Data.Effect := + Data.Effect.mk Data.Op.write x (some 0) true false +@[simp] abbrev initOpY : Data.Effect := + Data.Effect.mk Data.Op.write y (some 0) true false +@[simp] abbrev wOpX : Data.Effect := + Data.Effect.mk Data.Op.write x (some 1) false false +@[simp] abbrev wOpY : Data.Effect := + Data.Effect.mk Data.Op.write y (some 1) false false +@[simp] abbrev rOpX1 : Data.Effect := + Data.Effect.mk Data.Op.read x (some 1) false false +@[simp] abbrev rOpY1 : Data.Effect := + Data.Effect.mk Data.Op.read y (some 1) false false +@[simp] abbrev rOpX0 : Data.Effect := + Data.Effect.mk Data.Op.read x (some 0) false false +@[simp] abbrev rOpY0 : Data.Effect := + Data.Effect.mk Data.Op.read y (some 0) false false + +@[simp] abbrev initWx : Data.Event := + Data.Event.mk 100 10 initOpX ⟨Normal, Normal.none⟩ +@[simp] abbrev initWy : Data.Event := + Data.Event.mk 101 10 initOpY ⟨Normal, Normal.none⟩ + +@[simp] abbrev p0wX : Data.Event := + Data.Event.mk 1 0 wOpX ⟨Normal, Normal.none⟩ +@[simp] abbrev p0rY0 : Data.Event := + Data.Event.mk 3 0 rOpY0 ⟨Normal, Normal.none⟩ + +@[simp] abbrev p1wY : Data.Event := + Data.Event.mk 4 1 wOpY ⟨Normal, Normal.none⟩ +@[simp] abbrev p1rX0 : Data.Event := + Data.Event.mk 6 1 rOpX0 ⟨Normal, Normal.none⟩ + +/-- X86 SB+rfi-pos + +P0: W(x)=1; R(x)=1; R(y)=0 +P1: W(y)=1; R(y)=1; R(x)=0 -/ +@[simp] abbrev sb_evts : Data.Events := + Data.Events.mk + {initWx, initWy} + {p0rY0, p1rX0} + {initWx, initWy, p0wX, p1wY} + {} + {} + {} + +@[simp] def sb_co : SetRel Event Event := + {(initWx, p0wX), (initWy, p1wY)} + +instance : wellformed.co sb_evts sb_co where + irrefl := by aesop + trans := by aesop + preco := { + wellTyped := by aesop + total := by candidateExecution_wf + } + +/-- rf edges encode the expected outcome: + - rfi: p0wX -> p0rX1 and p1wY -> p1rY1 + - reads of 0 from init writes: initWy -> p0rY0 and initWx -> p1rX0 -/ +@[simp] def sb_rf : SetRel Event Event := + {(initWy, p0rY0), (initWx, p1rX0)} + +@[simp] def sb_rfInst : wellformed.rf sb_evts sb_rf := + Data.wellformed.rf.mk + (by + candidateExecution_wf + ) + (by + candidateExecution_wf + ) + +@[simp] def sb_test : CandidateExecution sb_evts := + { + evts := sb_evts + po' := sb_evts.po + prePo := instWellformedPo sb_evts + rf' := sb_rf + rfInst := sb_rfInst + co' := sb_co + rmw' := ∅ + preRMW := instWellformedRmwEmpty sb_evts + uniqueId := uniqueId_by_id sb_evts + syncInF := by + intro e h + contradiction + rfiPo := by + candidateExecution_wf + } + +defcat <"mips.cat"> +defcat <"tsox.cat"> +defcat <"sc.cat"> + +example : ¬ sc.sc sb_evts sb_test := +by + intro hsc + simp [sc.sc, CatRel.CatUnion.union, sb_test, sb_rf, sb_co, CandidateExecution.fr', SetRel.comp, SetRel.inv] at hsc + apply hsc p0wX + exact + Relation.TransGen.head (a := p0wX) (b := p0rY0) + (by + left + exact ⟨by simp [Data.Events.all], by simp [Data.Events.all], rfl, by decide⟩) + (Relation.TransGen.head (a := p0rY0) (b := p1wY) + (by + right + right + left + exact ⟨initWy, by simp, by simp⟩) + (Relation.TransGen.head (a := p1wY) (b := p1rX0) + (by + left + exact ⟨by simp [Data.Events.all], by simp [Data.Events.all], rfl, by decide⟩) + (Relation.TransGen.single (a := p1rX0) (b := p0wX) + (by + right + right + left + exact ⟨initWx, by simp, by simp⟩)))) + +theorem sb_tso : tsox.tso sb_evts sb_test := +by + simp only [tsox.tso] + apply acyclic_of_rank (fun e => match e.id with + | 100 => 0 -- initWx + | 101 => 1 -- initWy + | 3 => 2 -- p0rY0 + | 6 => 3 -- p1rX0 + | 4 => 4 -- p1wY + | 1 => 5 -- p0wX + | _ => 6) + intro a b hab + simp only [CatRel.CatUnion.union, CatRel.SetRel.union, Set.mem_setOf_eq, + tsox.implied, tsox.xppo, tsox.At, rfe, + CandidateExecution.fr', SetRel.inv, SetRel.comp, + sb_test, sb_rf, sb_co, sb_evts, + Data.Events.all, Data.Events.po, SetRel.mkId, + CatRel.prod, Set.mem_prod, Set.prod, + CatRel.W, CatRel.R, + CatRel.Rel.external, CatRel.Rel.internal, + Set.mem_inter_iff, Set.mem_union, Set.mem_setOf_eq, + Set.mem_insert_iff, Set.mem_singleton_iff, Set.mem_empty_iff_false, + Set.mem_preimage, Prod.swap, + SetRel.dom, SetRel.cod, SetRel.dom_mkId, + Prod.mk.injEq, Prod.fst, Prod.snd, + and_false, false_and, false_or, or_false, + not_true, not_false_eq_true, + exists_false, exists_eq_left, exists_eq_left'] at hab + -- implied is impossible since rmw = ∅ + rcases hab with ⟨mid, _, hmid_in | ⟨mid2, hmid2_in, _⟩⟩ | hab' + · obtain ⟨_, h⟩ := hmid_in + exact absurd h nofun + · obtain ⟨_, h⟩ := hmid2_in + exact absurd h nofun + -- remaining: xppo ∨ rfe ∨ fr ∨ co + rcases hab' with hxppo | (hrfe | (hfr | hco)) + · obtain ⟨hprod, hpo⟩ := hxppo + simp only [Set.mem_setOf_eq] at hprod + change a ∈ sb_evts.all ∧ b ∈ sb_evts.all ∧ a.t_id = b.t_id ∧ a.id < b.id at hpo + obtain ⟨_, _, htid, hlt⟩ := hpo + rcases hprod with (⟨ha, hb⟩ | ⟨ha, hb⟩ | ⟨ha, hb⟩) <;> + rcases ha with rfl | rfl | rfl | rfl <;> + rcases hb with rfl | rfl | rfl | rfl <;> + simp_all + · obtain ⟨hrf, htid⟩ := hrfe + rcases hrf with ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ + · decide + · decide + · obtain ⟨w, hrf_inv, hco⟩ := hfr + rcases hrf_inv with ⟨hw, ha⟩ | ⟨hw, ha⟩ <;> + rcases hco with ⟨hw', hb⟩ | ⟨hw', hb⟩ <;> + subst ha hb hw <;> simp_all <;> (try decide) <;> (try (subst hw'; decide)) + · rcases hco with ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ <;> decide + +example : mips.pso sb_evts sb_test := +by + exact mipsWeakerThanTso sb_evts sb_test sb_tso diff --git a/LeanCats/Linux/X86_SB_rfi_pos.lean b/LeanCats/Linux/X86_SB_rfi_pos.lean index 1e0e34c..1982833 100644 --- a/LeanCats/Linux/X86_SB_rfi_pos.lean +++ b/LeanCats/Linux/X86_SB_rfi_pos.lean @@ -16,7 +16,6 @@ open Data namespace Litmus - instance instWellformedPo (evts : Data.Events) : wellformed.po evts.po := by intro x y z hxy hyz rcases hxy with ⟨hx, hy, hxyTid, hxyLt⟩ @@ -195,7 +194,4 @@ example : tsox.tso sbrfi_evts sbrfi_test := by -- co · rcases hco with ⟨rfl, rfl⟩ | ⟨rfl, rfl⟩ <;> decide -example : ¬ mips.uniproc sbrfi_evts sbrfi_test := by - simp - end Litmus diff --git a/LeanCats/Linux/X86_mips_rf.lean b/LeanCats/Linux/X86_mips_rf.lean new file mode 100644 index 0000000..2ca4bcd --- /dev/null +++ b/LeanCats/Linux/X86_mips_rf.lean @@ -0,0 +1,137 @@ +import LeanCats.Basic +import LeanCats.Data +import LeanCats.Macro +import LeanCats.ModelReader +import LeanCats.Data +import LeanCats.Relations +import LeanCats.Theorems +import LeanCats.Basic + +defcat <"mips.cat"> +defcat <"tsox.cat"> + +-- In this litmus test, we want to show that sometimes the X86 is weaker than mips because of the sc-per-location. + +open Data + +namespace Litmus + + +instance instWellformedPo (evts : Data.Events) : wellformed.po evts.po := by + intro x y z hxy hyz + rcases hxy with ⟨hx, hy, hxyTid, hxyLt⟩ + rcases hyz with ⟨_, hz, hyzTid, hyzLt⟩ + exact ⟨hx, hz, Eq.trans hxyTid hyzTid, Nat.lt_trans hxyLt hyzLt⟩ + +instance instWellformedRmwEmpty (evts : Data.Events) : wellformed.rmw evts (∅ : SetRel Event Event) := by + intro e h + contradiction + +abbrev x := 0 +abbrev y := 1 + +inductive Normal where +| none : Normal + +@[simp] abbrev initOpX : Data.Effect := + Data.Effect.mk Data.Op.write x (some 0) true false +@[simp] abbrev initOpY : Data.Effect := + Data.Effect.mk Data.Op.write y (some 0) true false +@[simp] abbrev wOpX : Data.Effect := + Data.Effect.mk Data.Op.write x (some 1) false false +@[simp] abbrev wOpY : Data.Effect := + Data.Effect.mk Data.Op.write y (some 1) false false +@[simp] abbrev rOpX1 : Data.Effect := + Data.Effect.mk Data.Op.read x (some 1) false false +@[simp] abbrev rOpY1 : Data.Effect := + Data.Effect.mk Data.Op.read y (some 1) false false +@[simp] abbrev rOpX0 : Data.Effect := + Data.Effect.mk Data.Op.read x (some 0) false false +@[simp] abbrev rOpY0 : Data.Effect := + Data.Effect.mk Data.Op.read y (some 0) false false + +@[simp] abbrev initWx : Data.Event := + Data.Event.mk 100 10 initOpX ⟨Normal, Normal.none⟩ +@[simp] abbrev initWy : Data.Event := + Data.Event.mk 101 10 initOpY ⟨Normal, Normal.none⟩ + +@[simp] abbrev p0wX : Data.Event := + Data.Event.mk 1 0 wOpX ⟨Normal, Normal.none⟩ +@[simp] abbrev p0rX1 : Data.Event := + Data.Event.mk 2 0 rOpX1 ⟨Normal, Normal.none⟩ +@[simp] abbrev p0rY0 : Data.Event := + Data.Event.mk 3 0 rOpY0 ⟨Normal, Normal.none⟩ + +@[simp] abbrev p1wY : Data.Event := + Data.Event.mk 4 1 wOpY ⟨Normal, Normal.none⟩ +@[simp] abbrev p1rY1 : Data.Event := + Data.Event.mk 5 1 rOpY1 ⟨Normal, Normal.none⟩ +@[simp] abbrev p1rX0 : Data.Event := + Data.Event.mk 6 1 rOpX0 ⟨Normal, Normal.none⟩ + +/-- X86 SB+rfi-pos + +P0: W(x)=1; R(x)=1; R(y)=0 +P1: W(y)=1; R(y)=1; R(x)=0 -/ +@[simp] abbrev sbrfi_evts : Data.Events := + Data.Events.mk + {initWx, initWy} + {p0rX1, p0rY0, p1rY1, p1rX0} + {initWx, initWy, p0wX, p1wY} + {} + {} + {} + +@[simp] def sbrfi_co : SetRel Event Event := + {(initWx, p0wX), (initWy, p1wY)} + +instance : wellformed.co sbrfi_evts sbrfi_co where + irrefl := by aesop + trans := by aesop + preco := { + wellTyped := by aesop + total := by candidateExecution_wf + } + +/-- rf edges encode the expected outcome: + - rfi: p0wX -> p0rX1 and p1wY -> p1rY1 + - reads of 0 from init writes: initWy -> p0rY0 and initWx -> p1rX0 -/ +@[simp] def sbrfi_rf : SetRel Event Event := + {(p0wX, p0rX1), (p1wY, p1rY1), (initWy, p0rY0), (initWx, p1rX0)} + +@[simp] def sbrfi_rfInst : wellformed.rf sbrfi_evts sbrfi_rf := + Data.wellformed.rf.mk + (by + candidateExecution_wf + ) + (by + candidateExecution_wf + ) + +@[simp] def sbrfi_test : CandidateExecution sbrfi_evts := + { + evts := sbrfi_evts + po' := sbrfi_evts.po + prePo := instWellformedPo sbrfi_evts + rf' := sbrfi_rf + rfInst := sbrfi_rfInst + co' := sbrfi_co + rmw' := ∅ + preRMW := instWellformedRmwEmpty sbrfi_evts + uniqueId := uniqueId_by_id sbrfi_evts + syncInF := by + intro e h + contradiction + rfiPo := by + intro w r hrf htid + simp [sbrfi_rf] at hrf + rcases hrf with h | h | h | h + · rcases h with ⟨rfl, rfl⟩ + exact ⟨by simp [Data.Events.all], by simp [Data.Events.all], rfl, by decide⟩ + · rcases h with ⟨rfl, rfl⟩ + exact ⟨by simp [Data.Events.all], by simp [Data.Events.all], rfl, by decide⟩ + · rcases h with ⟨rfl, rfl⟩ + simp at htid + · rcases h with ⟨rfl, rfl⟩ + simp at htid + }