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: // Thread 2: sync ( ) { ….f … ….f … } l1 e2e1 l2
MUST-NOT-ALIAS( e1, e2 ) l1 and l2 always refer to the same object e1 and e2 never refer to the same object Field f is race-free if: Proving Race Freedom: Traditional Alias Analysis // Thread 1: // Thread 2: sync ( ) { ….f … ….f … } l1 e2e1 ¬ MAY-ALIAS( e1, e2 ) l2 MUST-ALIAS( l1, l2 ) OR
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
Field f is race-free if: Proving Race Freedom: Conditional Must Not Aliasing // Thread 1: // Thread 2: sync ( ) { ….f … ….f … } l1 e2e1 l2 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; … … 1 1,h1 0,h0 for (j = 1; j <= M; j++) fork { x = a[*]; sync (?) { x.g.f = 0; } } N N,h2 N,h1 g 1,h2 g … … i,h2 i i,h1 g
sync (?) { x2.g.f = 0; } Example … … 1 1,h1 … … 0,h0 N N,h2 N,h1 g 1,h2 g i,h2 i i,h1 g 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; } }
MUST-NOT-ALIAS( l1, l2 ) => MUST-NOT-ALIAS( e1, e2 )MUST-NOT-ALIAS( a, a ) => MUST-NOT-ALIAS( x1.g, x2.g ) Field f is race-free if: true for (j = 1; j <= M; j++) fork { x = a[*]; sync (a) { x1.g.f = 0; } } sync (a) { x2.g.f = 0; } Example: Coarse-grained Locking a = new h0[N]; for (i = 1; i <= N; i++) a[i] = new h1; a[i].g = new h2; … … 1 1,h1 … … N N,h2 N,h1 g 1,h2 g i i,h1 g 0,h0 i,h2
sync (?) { x2.g.f = 0; } Example … … 1 1,h1 … … 0,h0 N N,h2 N,h1 g 1,h2 g i,h2 i i,h1 g 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; } }
MUST-NOT-ALIAS( l1, l2 ) => MUST-NOT-ALIAS( e1, e2 )MUST-NOT-ALIAS( x1.g, x2.g ) => MUST-NOT-ALIAS( x1.g, x2.g ) true sync (x2.g) { x2.g.f = 0; } 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 1,h1 … … 0,h0 N N,h2 N,h1 g 1,h2 g i i,h1 g i,h2 Field f is race-free if:
sync (?) { x2.g.f = 0; } Example … … 1 1,h1 … … 0,h0 N N,h2 N,h1 g 1,h2 g i,h2 i i,h1 g 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; } }
MUST-NOT-ALIAS( l1, l2 ) => MUST-NOT-ALIAS( e1, e2 )MUST-NOT-ALIAS( x1, x2 ) => MUST-NOT-ALIAS( x1.g, x2.g ) true (field g of distinct h1 objects linked to distinct h2 objects) 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; } } sync (x2) { x2.g.f = 0; } … … 1 1,h1 … … 0,h0 N N,h2 N,h1 g 1,h2 g i,h2 i i,h1 g Field f is race-free if:
{ … h2 … } = DR({ … h1 … }) iff in every execution: –from distinct h1 objects –we can reach (via 1 or more edges) –only distinct h2 objects Disjoint Reachability Property j,h1 i ≠ j k ≠ l i,h1 k,h2 ► ► l,h2 ► ►
Example: Medium-grained Locking Is {h2} ⊆ DR({h1})? Yes! a = new h0[N]; for (i = 1; i <= N; i++) a[i] = new h1; a[i].g = new h2; … … 1,h1 … … 0,h0 N,h2 N,h1 1,h2i,h2 i,h1 ► ►► ► ► ►
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 a = new h0[N]; for (i = 1; i <= N; i++) a[i] = new h1; a[i].g = new h2; 0,h0 1,h1 1,h2 ► ► Is {h2} ⊆ DR({h1})? Yes!
// Thread 1: // Thread 2: sync (l1) { sync (l2) { … e1.f … … e2.f … } Conditional Must Not Alias Analysis using Disjoint Reachability Analysis PointsTo ( l1 ) PointsTo ( e1 ) PointsTo ( e2 ) MUST-NOT-ALIAS( l1, l2 ) => MUST-NOT-ALIAS( e1, e2 ) PointsTo ( l2 ) Field f is race-free if: –( PointsTo ( e1 ) ∩ PointsTo ( e2 )) ⊆ DR( PointsTo ( l1 ) ∪ PointsTo ( l2 )) –l1 is a prefix of e1 and l2 is a prefix of e2 ⊆ DR
–( PointsTo ( e1 ) ∩ PointsTo ( e2 )) ⊆ DR( PointsTo ( l1 ) ∪ PointsTo ( l2 )) –l1 is a prefix of e1 and l2 is a prefix of e2 –( PointsTo ( x1.g ) ∩ PointsTo ( x2.g )) ⊆ DR( PointsTo ( x1 ) ∪ PointsTo ( x2 )) –x1 is a prefix of x1.g and x2 is a prefix of x2.g 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; } } sync (x2) { x2.g.f = 0; } Field f is race-free if: … … N h2 g h1 h2 i g h1 h2 g 1 h1 … … h0 PointsTo ( x2 ) PointsTo ( x2.g ) PointsTo ( x1 ) PointsTo ( x1.g ) –({h2}) ⊆ DR({h1}) –x1 is a prefix of x1.g and x2 is a prefix of x2.g –true
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: x.f = y; 2:... // no writes to aliases of x.f 3: 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 #lines of Java code app lib app lib time philo ,5823m14s elevator ,1474m43s tsp ,0263m21s jdbm114322,190112,1394m11s jdbf254375,484121,1177m12s pool224345,531114,1215m13s jtds ,190129,01610m42s ftp ,897140,2219m21s
Experimental Results old pairs new pairs likely unlikely original pairs real false real false philo13, elevator59, tsp98, jdbm189, jdbf132, pool241, jtds398, ftp487,
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