Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
64e34b2
Add litmus parser for X86
nek0las Mar 12, 2026
0267e57
Add more litmus test files.
nek0las Mar 12, 2026
8482bf4
Add bridge from Litmus test to graph.
nek0las Mar 12, 2026
5ec0541
Fix the identifier type mismatch bug.
nek0las Mar 13, 2026
88d94e3
Fix the naming bugs.
nek0las Mar 14, 2026
2ae5a7b
change the locations of litmus test files
nek0las Mar 14, 2026
cbf947e
fix events definitions in syntax.
nek0las Mar 14, 2026
b9fb4c2
Add support for M
nek0las Mar 14, 2026
9e976ef
Fix assertion unnaming bugs.
nek0las Mar 14, 2026
1718603
Add external relations support.
nek0las Mar 14, 2026
2e740ae
Add more relations support.
nek0las Mar 14, 2026
cd8446c
diff the union operations between events and relations.
nek0las Mar 14, 2026
be9d44d
Fix A-cumul bugs by adding arg variable in macro passing
nek0las Mar 14, 2026
b48a268
Add basic LKMM model support.
nek0las Mar 14, 2026
0505815
Clean up debugging code.
nek0las Mar 14, 2026
b71a72c
Clean up comments
nek0las Mar 14, 2026
087f546
Fix theorem cause by changing the SetRel
nek0las Mar 19, 2026
66811e2
Add model comparision
nek0las Mar 19, 2026
98cdf19
Add co totality to Events.preCo
nek0las Mar 24, 2026
4d61bbf
Add rf uniqueness, co totality, and rf;fr⊆co theorem
nek0las Mar 25, 2026
93b2203
Prove co_acyclic and fr_co_subset_fr
nek0las Mar 25, 2026
ab09f96
Misc updates to cat preprocessor, syntax, and model files
nek0las Mar 25, 2026
d2bbac7
Add CoWR well-formedness, fix rfe/ext bugs, scaffold X86 vs LKMM proof
nek0las Mar 25, 2026
215d8c3
Add tactic for wellformness, and add one litmus test for lkmm.
nek0las Mar 26, 2026
4c582a2
delete temp files.
nek0las Mar 26, 2026
1be0017
Fix renaming bugs.
nek0las Mar 26, 2026
4e5ce81
Fix tactic
nek0las Mar 26, 2026
ea9dfdc
Fix bugs in tactic
nek0las Mar 26, 2026
6bcc51f
refactor the project
nek0las Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions LeanCats.lean
Original file line number Diff line number Diff line change
@@ -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
58 changes: 51 additions & 7 deletions LeanCats/Basic.lean
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,59 @@ 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
rfInst : wellformed.rf evts rf
co : SetRel Event Event
[preCo : wellformed.co evts co]
fr := rf.inv.comp co
rmw : 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
-- 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'

/-- 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'

/-- 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" : tactic =>
`(tactic|
(all_goals repeat first
| simp at *
aesop
| (casesm _ ∈ _
aesop)))
110 changes: 104 additions & 6 deletions LeanCats/CatPreprocessor.lean
Original file line number Diff line number Diff line change
@@ -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 : α) : α :=
Expand Down Expand Up @@ -58,27 +60,119 @@ 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
let firstChar := String.Pos.Raw.get stripped ⟨0⟩
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"

Expand Down Expand Up @@ -112,9 +206,13 @@ 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(),...*)"

#eval removeComments enums_test

def instructions_test := "instructions R[Accesses]\ninstructions W[Accesses]\ninstructions RMW[Accesses]\ninstructions F[Barriers]"

#eval removeComments instructions_test
49 changes: 49 additions & 0 deletions LeanCats/Cats/examples/models/lkmm.bell
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
(*
* Copyright (C) 2015 Jade Alglave <j.alglave@ucl.ac.uk>,
* Copyright (C) 2016 Luc Maranget <luc.maranget@inria.fr> for Inria
* Copyright (C) 2017 Alan Stern <stern@rowland.harvard.edu>,
* Andrea Parri <parri.andrea@gmail.com>
*
* 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
Loading
Loading