Conditional Must Not Aliasing for Static Race Detection Mayur Naik Alex Aiken Stanford University The Concurrency Revolution • CPU clock speeds have peaked • Implications for hardware – CPU vendors are shipping multi-core processors • Implications for software – Concurrent programs stand to benefit the most Debugging Concurrent Programs is Hard • Concurrency bugs triggered non-deterministically – Prevalent testing techniques ineffective • A race condition is a common concurrency bug – Two threads can simultaneously access a memory location – At least one access is a write Locking for Race Freedom // Thread 1: sync ( l1) { … e1.f … } // Thread 2: sync (l2 ) { … e2.f … } Proving Race Freedom: Traditional Alias Analysis // Thread 1: sync ( l1) { … e1.f … } // Thread 2: sync (l2 ) { … e2.f … } • Field f is race-free if: MUST-NOT-ALIAS(e1, ¬e2 MAY-ALIAS(e1, e2) e1 and never refer toe2) the same object OR MUST-ALIAS(l1, l1 and l2 always refer tol2) the same object Must Alias Analysis is Hard • Our previous approach (PLDI’06) – performed a may alias analysis – simple approximation of a must alias analysis – effective but unsound • New approach – found must alias analysis unneeded for race detection! – conditional must not alias analysis is sufficient – effective and sound Proving Race Freedom: Conditional Must Not Aliasing // Thread 1: sync ( l1) { … e1.f … } // Thread 2: sync (l2 ) { … e2.f … } • Field f is race-free if: Whenever l1 and l2 refer to different objects, e1 and e2 also refer to different objects MUST-NOT-ALIAS(l1, l2) => MUST-NOT-ALIAS(e1, e2) Example a = new h0[N]; for (i = 1; i <= N; i++) a[i] = new h1; a[i].g = new h2; for (j = 1; j <= M; j++) fork { x = a[*]; sync (?) { x.g.f = 0; } } 1 0,h0 N i 1,h1 … g 1,h2 i,h1 … g g … i,h2 N,h1 … N,h2 Example a = new h0[N]; for (i = 1; i <= N; i++) a[i] = new h1; a[i].g = new h2; for (j = 1; j <= M; j++) fork { x = a[*]; sync (?) { x1.g.f = 0; } } 1 0,h0 N i 1,h1 … g 1,h2 i,h1 … g g … i,h2 N,h1 … sync (?) { x2.g.f = 0; } N,h2 Example: Coarse-grained Locking a = new h0[N]; for (i = 1; i <= N; i++) a[i] = new h1; a[i].g = new h2; for (j = 1; j <= M; j++) fork { x = a[*]; sync (a) { x1.g.f = 0; } } 1 0,h0 N i 1,h1 … g 1,h2 i,h1 … g g … i,h2 N,h1 … sync (a) { x2.g.f = 0; } Field f is race-free if: true MUST-NOT-ALIAS(l1,a)l2) MUST-NOT-ALIAS(a, => => MUST-NOT-ALIAS(x1.g, MUST-NOT-ALIAS(e1, x2.g) e2) N,h2 Example a = new h0[N]; for (i = 1; i <= N; i++) a[i] = new h1; a[i].g = new h2; for (j = 1; j <= M; j++) fork { x = a[*]; sync (?) { x1.g.f = 0; } } 1 0,h0 N i 1,h1 … g 1,h2 i,h1 … g g … i,h2 N,h1 … sync (?) { x2.g.f = 0; } N,h2 Example: Fine-grained Locking a = new h0[N]; for (i = 1; i <= N; i++) a[i] = new h1; a[i].g = new h2; for (j = 1; j <= M; j++) fork { x = a[*]; sync (x1.g) { x1.g.f = 0; } } 1 0,h0 N i 1,h1 … g 1,h2 i,h1 … g g … i,h2 N,h1 … N,h2 sync (x2.g) { x2.g.f = 0; } Field f is race-free if: true MUST-NOT-ALIAS(l1, l2)x2.g) MUST-NOT-ALIAS(x1.g, => MUST-NOT-ALIAS(e1, => MUST-NOT-ALIAS(x1.g, e2) x2.g) Example a = new h0[N]; for (i = 1; i <= N; i++) a[i] = new h1; a[i].g = new h2; for (j = 1; j <= M; j++) fork { x = a[*]; sync (?) { x1.g.f = 0; } } 1 0,h0 N i 1,h1 … g 1,h2 i,h1 … g g … i,h2 N,h1 … sync (?) { x2.g.f = 0; } N,h2 Example: Medium-grained Locking a = new h0[N]; for (i = 1; i <= N; i++) a[i] = new h1; a[i].g = new h2; for (j = 1; j <= M; j++) fork { x = a[*]; sync (x1) { x1.g.f = 0; } } 1 0,h0 N i 1,h1 … g 1,h2 i,h1 … g g … i,h2 N,h1 … N,h2 sync (x2) { x2.g.f = 0; } Field f is race-free if: true (field g of distinct h1x2) objects linked to distinct h2 objects) MUST-NOT-ALIAS(l1, MUST-NOT-ALIAS(x1, l2) => MUST-NOT-ALIAS(x1.g, MUST-NOT-ALIAS(e1, e2)x2.g) Disjoint Reachability Property { … h2 … } = DR({ … h1 … }) iff in every execution: – from distinct h1 objects i,h1 j,h1 k≠l l,h2 ► ► i≠j – we can reach (via 1 or more edges) k,h2 ► ► – only distinct h2 objects Example: Medium-grained Locking 0,h0 ► a = new h0[N]; for (i = 1; i <= N; i++) a[i] = new h1; a[i].g = new h2; 1,h1 … i,h2 ► 1,h2 … N,h1 … N,h2 ► i,h1 ► … Is {h2} ⊆ DR({h1})? Yes! Disjoint Reachability Analysis • Types (a, h): – a is one of { 0, 1, ⊤ } – h is an object allocation site • Effects (a1, h1) ► (a2, h2) – means left object linked to right object via some field • Key property of (1, h1) ► (1, h2) – linked objects created in same loop iteration Example: Medium-grained Locking 0,h0 ► a = new h0[N]; for (i = 1; i <= N; i++) a[i] = new h1; a[i].g = new h2; 1,h1 ► 1,h2 Is {h2} ⊆ DR({h1})? Yes! Conditional Must Not Alias Analysis using Disjoint Reachability Analysis PointsTo(l2) // Thread 1: sync (l1) { … e1.f … } // Thread 2: sync (l2) { … e2.f … } PointsTo(e1) ⊆ DR PointsTo(l1) PointsTo(e2) Field f is race-free if: – (PointsTo(e1) ∩ PointsTo(e2)) ⊆ DR(PointsTo(l1) ∪ PointsTo(l2)) MUST-NOT-ALIAS(l1, l2) => MUST-NOT-ALIAS(e1, e2) – l1 is a prefix of e1 and l2 is a prefix of e2 Example: Medium-grained Locking a = new h0[N]; 1 for (i = 1; i <= N; i++) a[i] = new h1; h1 … a[i].g = new h2; PointsTo(x1) h0 N i h1 … PointsTo h1 (x2) g g g for (j = 1; j <= M; j++) fork { PointsTo(x1.g) h2 … h2 h2 (x2.g) …PointsTo x = a[*]; sync (x2) { sync (x1) { x2.g.f = 0; x1.g.f = 0; } } } Field f is race-free if: – – – (true PointsTo (x1.g) ∩ PointsTo ∩ PointsTo (e2)) (x2.g)) ⊆ DR(⊆PointsTo DR(PointsTo (l1) ∪(x1) PointsTo ∪ PointsTo (l2)) (x2)) ({h2}) ⊆ (e1) DR({h1}) true l1 x1 is a prefix of e1 x1.g andand l2x2 is aisprefix a prefix of of e2x2.g Implementation Aspects • A type is a pair (Π, h) where: – Π is a vector of { 0, 1, ⊤ } values, one per method • All loops transformed to tail-recursive methods • Uniformly handles loops and recursive methods – h is a k-object-sensitive object allocation site Implementation Aspects • Circular dependency between type-and-effect analysis and race freedom • Fact (z = y) valid after line 3 only if field f is race-free: 1: 2: 3: x.f = y; ... // no writes to aliases of x.f z = x.f; • Race detection algorithm performs fixpoint computation – Begins assuming no races – Type-and-effect analysis kills facts as new races are found – Terminates when no more races are found Benchmarks #classes app lib #lines of Java code app lib time philo 2 423 84 110,582 3m14s elevator 5 425 531 111,147 4m43s tsp 4 426 706 116,026 3m21s jdbm 11 432 2,190 112,139 4m11s jdbf 25 437 5,484 121,117 7m12s pool 22 434 5,531 114,121 5m13s jtds 98 461 18,190 129,016 10m42s 118 478 21,897 140,221 9m21s ftp Experimental Results old pairs new pairs likely unlikely original pairs real false real false philo 13,121 0 0 35 3 0 elevator 59,610 0 0 305 0 0 tsp 98,045 0 4 490 12 18 jdbm 189,853 91 0 331 91 0 jdbf 132,041 130 0 295 130 12 pool 241,876 115 0 355 115 5 jtds 398,031 48 0 571 48 13 ftp 487,019 122 34 1624 187 47 Related Work • Vectorizing compilers – loop vectors akin to iteration space and dependence distance • Disjoint reachability – ranging from ownership types to theorem-proving approaches • Race detection – Dynamic (happens-before, lockset, hybrid) – Static (type systems, dataflow analyses, model checkers) • Atomicity checking – atomicity a higher-level property than race freedom – but many atomicity checkers do race detection as first step Summary of Results • Conditional Must Not Aliasing – A new aliasing property and analysis • Disjoint Reachability – A new lightweight shape property and analysis • A new race detection algorithm – Sound – Effective in practice The End http://www.cs.stanford.edu/~mhn/chord.html