590 likes | 756 Views
Verifying Concurrent Programs with Relaxed Conflict Detection. Tayfun Elmas , Ismail Kuru , Serdar Taşıran , Omer Subasi Koç University Istanbul, Turkey. Relaxed Conflict Detection. Pattern Traverse/Take snapshot of (large portion of) global state Do l ocal computation
E N D
Verifying Concurrent Programs withRelaxed Conflict Detection TayfunElmas, Ismail Kuru, Serdar Taşıran, Omer SubasiKoç UniversityIstanbul, Turkey
Relaxed Conflict Detection • Pattern • Traverse/Take snapshotof (large portion of) global state • Do local computation • Update small portion of global state • Very common • Concurrent data structures • Parallelized optimization algorithms • Cloud computing • Performance optimizations for transactional memory • Ignore WAR conflicts (Titos et al., HiPEAC ‘12) • Early release/early discard (e.g., Kozyrakis et al., WTW ‘06) • Performance problem: Most operations conflict • Solution: Program so that some conflicts can be ignored • Verification problem: How do you reason about correctness?
Motivating Example: Sorted Linked List Insert 5 5 Head 3 1 9 12 6 17 15 16 Insert 16
Motivating Example: Sorted Linked List Insert 5 5 Head 3 1 9 12 6 17 15 16 Insert 16
Head 3 1 9 12 6 17 15 READ
5 WRITE Head 3 1 9 12 6 17 15 READ
Write-After-Readconflict 5 WRITE Head 3 1 9 12 6 17 15 WRITE READ 16
Conventional TM conflict detectionenforces conflict serializability • Does WriteSet(Tx1) intersect(ReadSet(Tx1) + WriteSet(Tx2)) ? • Any two concurrent insertions conflict! Write-After-Readconflict 5 WRITE Head 3 1 9 12 6 17 15 WRITE READ 16
Linked List: Insert list_insert(list_t *listPtr, node_t *node) { atomic { *curr = listPtr->head; do { prev = curr; curr = curr->next; } while (curr != NULL && curr->key < node->key); node->next = curr; prev->next = node; } } Transactional memory: • Optimistic concurrency • Track read and write accesses a transaction performs • ReadSet(Tx1) • WriteSet(Tx1) • Enforce conflict serializability • At commit time for Tx1for all concurrent transactions Tx2, check WriteSet(Tx2) (ReadSet(Tx1) WriteSet(Tx1)) = • Abort and retry Tx1 otherwise
But, allowing both insertions OK even if we ignore WAR conflict • Conflict serializability too strict • Options: • Fine-grain, hand-crafted concurrency • Transactional memory + relaxed conflict detection 5 Head 3 1 9 12 6 17 15 16
Relaxed conflict detection • Programmer tells TM to ignore this kind of conflict • Need to reason that this still results in a correct program Write-After-Readconflict 5 WRITE Head 3 1 9 12 6 17 15 WRITE READ 16
Linked List: Insert list_insert(list_t *listPtr, node_t *node) { atomic { *curr = listPtr->head; Read do {phase prev = curr; curr = curr->next; } while (curr != NULL && curr->key < node->key); node->next = curr; Write prev->next = node; phase } }
Linked List: Insert list_insert(list_t *listPtr, node_t *node) { atomic { *curr = listPtr->head; do { prev = curr; curr = curr->next; } while (curr != NULL && curr->key < node->key); node->next = curr; prev->next = node; assert(node is in the list && list is sorted); } } Strict conflict detection:Can reason about transaction code sequentially.
Linked List: Insert list_insert(list_t *listPtr, node_t *node) { atomic [!WAR]{ *curr = listPtr->head; do { prev = curr; curr = curr->next; } while (curr != NULL && curr->key < node->key); node->next = curr; prev->next = node; assert(node is in the list && list is sorted); } } • Ignore Write-After-Readconflicts: • Writes by others interfere with transaction • Cannot reasonsequentially 5 READ 6; WRITE 5 3 1 6
Arguing that the !WAR block is atomic list_insert(list_t *listPtr, node_t *node) { atomic [!WAR]{ *curr = listPtr->head; do { prev = curr; curr = curr->next; } while (curr != NULL && curr->key < node->key); node->next = curr; prev->next = node; assert(node is in the list && list is sorted); } } • Would like these actions to be “right movers” • Can commute to the right of any actionby another thread • Atomicity of this block guaranteed by TM • Write-write conflictsnot ignored
Making !WAR block atomic list_insert(list_t *listPtr, node_t *node) { atomic [!WAR]{ *curr = listPtr->head; do { prev = curr; currT1 = currT1->next; } while (curr != NULL && curr->key < node->key); node->next = curr; prev->next = node; assert(node is in the list && list is sorted); } } 5 READ 6; WRITE 5 3 1 6 nodeT2->next = currT2;
Making !WAR block atomic list_insert(list_t *listPtr, node_t *node) { atomic [!WAR]{ *curr = listPtr->head; do { prev = curr; currT1 = currT1->next; } while (curr != NULL && curr->key < node->key); node->next = curr; prev->next = node; assert(node is in the list && list is sorted); } } Ignored WAR conflict:currT1 = currT1->next; does not move to the right of nodeT2->next = currT2; nodeT2->next = currT2;
Abstraction list_insert(list_t *listPtr, node_t *node) { atomic [!WAR]{ *curr = listPtr->head; do { (currT1= currT1->next)+;havocprev; assume prev->next = curr; assume prev->key < node->key; } while (curr != NULL && curr->key < node->key); node->next = curr; prev->next = node; assert(node is in the list && list is sorted); } } 5 WRITE 5; READ 5, 6; 3 1 6 • Abstract action right mover • Now block is atomic. • Sequential verification
1. Sequentially verify the original code list_insert(list_t *listPtr, node_t *node) { *curr = listPtr->head; do { prev = curr; curr = curr->next; } while (curr != NULL && curr->key < node->key); node->next = curr; prev->next = node; assert(node is in the list && list is sorted); } }
2. Apply program transformation list_insert(list_t *listPtr, node_t *node) { *curr = listPtr->head; do { (currT1 = currT1->next)+; havocprev; assume prev->next = curr; assume prev->key < node->key; } while (curr != NULL && curr->key < node->key); node->next = curr; prev->next = node; assert(node is in the list && list is sorted); } } • Do global readabstractions Abstract transaction becomes atomic.
3. Prove abstract codesequentially list_insert(list_t *listPtr, node_t *node) { *curr = listPtr->head; do { (currT1 = currT1->next)+; havocprev; assume prev->next = curr; assume prev->key < node->key; } while (curr != NULL && curr->key < node->key); node->next = curr; prev->next = node; assert(node is in the list && list is sorted); } }
Relaxed Conflict Detection • Pattern • Traverse/Take snapshotof (large portion of) global state • Do local computation • Update small portion of global state • Concurrent data structures, optimization algorithms, cloud computing • Performance optimizations for transactional memory • Ignore WAR conflicts (Titos et al., HiPEAC ‘12) • Early release/early discard (e.g., Kozyrakis et al., WTW ‘06) • Issue: Snapshot may be stale • But programmer thinks this is OK (as long as there is no write-write conflict with another operation) • Problem: • How to verify properties of code running under a relaxed consistency model
WAR Conflict Pattern read x read y read x write x write y
WAR Conflict Pattern read x read y read x write x write y WAR conflict OK
WAR Conflict Pattern read x read y read x write x write y WAR conflict OK as long as noWAW conflict
WAR Conflict Pattern read x read y read x write x write y read x read y read x write x write x WAR conflict OK WAR conflict WAW conflict as long as noWAW conflict
WAR Conflict Pattern read x read y read x write x write y read x read y read x write x write x WAR conflict OK WAR conflict A B O R T WAW conflict as long as noWAW conflict
Labyrinth: Grid Routing • FindRoute(p1,p2) • Route a wire on the gridconnecting points p1 and p2 • Wires must not touch • FindRoute operation • Take snapshot of grid • Find shortest path from p1 to p2 • Write path to shared memoryif path does not overlap others • Overlap = write-write conflict • Stale snapshot OK as long as computed path does not overlap others • Same path could have been computed even with up-to-date snapshot
Write-After-Readconflict 5 WRITE Head 3 1 9 12 6 17 15 WRITE READ 16
Non-problematic Interleaving snapshotGS := GlobStn; (x, xVal) := localComp(snapshotGS, args);atomic { GlobSt := GlobSt [x->xVal]; } GlobSt1 := GlobSt0[y->yVal]; … GlobSt2 := GlobSt1[z->zVal]; … GlobStn:= GlobStn-1[w->wVal]; …
Actual Interleaving snapshotGS:= GlobSt0; snapshotGS:= GlobStn; (x, xVal) := localComp(snapshotGS, args);atomic { GlobSt := GlobSt [x->xVal]; } GlobSt1 := GlobSt0[y->yVal]; … GlobSt2 := GlobSt1[z->zVal]; … GlobStn:= GlobStn-1[w->wVal]; … This is OKbecauselocalComp(GlobStn, args) andlocalComp(GlobSt0,args)would/might have produced same result.
Correctness Argument • Blue transaction’s snapshot is stale • Blue and green accesses conflict • But, end state consistent with • green executed serially, then • blue executed serially 5 WRITE Head 3 1 9 12 6 17 15 WRITE READ 16
QED: A Proof System for Concurrent Programs http://qed.codeplex.com [POPL ’09, TACAS ‘10, PADTAD ‘10, VSTTE ‘10] Shaz Qadeer Microsoft ResearchRedmond, WA Serdar Taşıran, TayfunElmas, Ali Sezgin Koç UniversityIstanbul, Turkey
Intuition for proof steps • Abstract some actions • Prove non-interference • Commute • Prove larger blocks atomic • Abstract some actions • Prove non-interference • Commute • Prove larger blocks atomic
Coarser Atomic Actions • Difficult to prove • Fine-grain concurrency • Annotations at every • interleaving point • Easy to prove • Larger atomic blocks • Local, sequential analysis • within atomic blocks P1 P2 Pn . . . check Correct
QED’s Idea of Abstraction 36 abstracted by s1 If for all : s1 s1 1. If error error then s1 s2 s1 s2 2. Else, if then s1 or error
Flavors of Abstraction 37 Adding non-determinism if (x == 1) y := y + 1; if (*) y := y + 1; t := x; havoc t; “Read abstraction” assume x != t; skip; Adding assertions (more behaviors that might go wrong) assert (lock_owner == tid); x := t + 1; x := t + 1;
QED Transformations: Reduction I,P I,P’ If [S1] is a right mover or [S2] is a left mover P’ [ S1 ] ; [ S2 ] P [ S1; S2]
Reduction 39 ; ; right-mover: ... 12 ... n ... For each execution: Exist equivalent executions: ... 1 2 ... n ... ........... ... 1 2 ... n ... ... 12 ... n ; ...
Mover check in QED: Static, local, semantic A B S1 S2 S3 A Right-mover ? All actions in program run by different thread B A S1 T2 S3 ... For each : ... B ... ; ; A B B A First-order verification condition
Atomicity Proof Idea sshotGS:= GlobSt0; (x, xVal) := localComp(sshotGS, args);atomic { GlobSt := GlobSt [x->xVal]; } • Need this to be a “Right mover” • Must commute to the right of all other concurrent actions GlobSt1 := GlobSt0[y->yVal]; … GlobSt2 := GlobSt1[z->zVal]; … GlobStn:= GlobStn-1[w->wVal]; … Atomicity ensured bystrict conflict detection
Reasoning using Lipton’s reduction and abstraction • QED-like proof (“A Calculus of Atomic Actions”, POPL ’09) • Costly pairwise mover checks avoided Atomicity Proof Idea sshotGS:= GlobSt0; (x, xVal) := localComp(sshotGS, args);atomic { GlobSt := GlobSt [x->xVal]; } • Need this to be a “Right mover” • Must commute to the right of all other concurrent actions GlobSt1 := GlobSt0[y->yVal]; … GlobSt2 := GlobSt1[z->zVal]; … GlobStn:= GlobStn-1[w->wVal]; … Atomicity ensured bystrict conflict detection
Atomicity Proof Idea sshotGS:= GlobSt0; (x, xVal) := localComp(sshotGS, args);atomic { GlobSt := GlobSt [x->xVal]; } • Not a “Right mover” • Does not commute to the right of global state updates GlobSt1 := GlobSt0[y->yVal]; … GlobSt2 := GlobSt1[z->zVal]; … GlobStn:= GlobStn-1[w->wVal]; …
Want to read 6 again! Reads and Updates do not Commute 5 WRITE 5;READ 5 5 READ 6; WRITE 5 3 1 6 3 1 6
Atomicity Proof Idea sshotGS:= GlobSt0; (x, xVal) := localComp(sshotGS, args);atomic { GlobSt := GlobSt [x->xVal]; } • Not a “Right mover” • Does not commute to the right of global state updates • Idea: Abstract this read • Add non-determinismso it can read GlobSt1, GlobSt2, …, GlobStn GlobSt1 := GlobSt0[y->yVal]; … GlobSt2 := GlobSt1[z->zVal]; … GlobStn:= GlobStn-1[w->wVal]; …
Want to read 6 again! Abstraction intuition 5 WRITE 5;READ 5 5 READ 6; WRITE 5 3 1 6 3 1 6 Need to jump over 5. 5 ABSTRACT READ 6; WRITE 5 WRITE 5; ABSTRACT READ 6 5 3 1 6 3 1 6
Want to read 6 again! Abstraction intuition 5 WRITE 5;READ 5 5 READ 6; WRITE 5 3 1 6 3 1 6 curr= curr->next; abstracted bycurr= curr->next*; but don’t go past key. Need to jump over 5. 5 ABSTRACT READ 6; WRITE 5 WRITE 5; ABSTRACT READ 6 5 3 1 6 3 1 6
Abstracting Read Accesses • Abstraction invariant AbsT,x: A transition invariant for variable of type T at address x • l := x.fbecomes <obj = copy(x); havoc l; assume AbsT,x(state, state[obj.fl]) > • “Read returns a non-deterministic value satisfying abstraction invariant” • For the linked list insertion example Absnode,x(old_st, new_st) = Reachold_st(list->head, x) ==>Reachnew_st(list->head, x) ∧Reachnew_st(x, old(x->next)) ∧Sortedold_st(list) ∧Sortednew_st(list)
Abstracting Accesses • Annotate write accesses: • x.f := m becomes <assert AbsT,x(st, st[x.f m]); x := m > • Assertion: Proof obligation, ensures the abstraction invariant • By construction, abstract reads commute to the right of annotated writes • Assertions become proof obligations • Discharged using sequential reasoning • Abstraction invariant verified when operations are atomic • Soundness guaranteed by theorem • Special case of QED soundness theorem • No pairwise mover check needed!
Ongoing Work: Obtaining the (Sequential) Specification • When interpreted sequentially, the program may already have a specification • Spec already existed for Labyrinth and Genome • Specifications (invariants) may be inferred mechanically • Done for the Genome benchmark using the Celia tool • Invariant Synthesis for Programs Manipulating Lists with Unbounded DataA. Bouajjani, C. Dragoi, C. Enea, A. Rezine, and M. Sighireanu CAV'10. • On Inter-Procedural Analysis of Programs with Lists and DataA. Bouajjani, C. Dragoi, C. Enea, and M. SighireanuPLDI'11.