Verifying a Concurrent Garbage Collector
Delphine Demange Suresh Jagannathan Gustavo Petri David Pichardie Jan Vitek Yannick Zakowski
Verifying a Concurrent Garbage Collector Delphine Demange Suresh - - PowerPoint PPT Presentation
Verifying a Concurrent Garbage Collector Delphine Demange Suresh Jagannathan Gustavo Petri David Pichardie Jan Vitek Yannick Zakowski Why are high level languages difficult to compile? Easy to compile in C Obj.f := v What about Java? A
Delphine Demange Suresh Jagannathan Gustavo Petri David Pichardie Jan Vitek Yannick Zakowski
Easy to compile in C What about Java?
/* we don’t want to mark when we’re idle because during the idle phase at the beginning of GC we may be rotating mark bits. this prevents races where two threads end up double-marking an object because they see different values of curShaded. this is particularly why we query ts->gc.tracing. it’s possible that a thread will have used stale shade values in its fast path check. worse, memory model issues could mean that the value of gc->curShaded we see here could be stale. we don’t want to perform marking with a stale value of curShaded. checking ts->gc.tracing, which is set by safepoints and hence side-steps cache coherence, ensures that we only proceed when we have the latest shade values. */
Challenge: Certify a state-of-the-art Concurrent Garbage Collector Methodology: An IR to Implement and Verify Runtime Services
TSO
TSO
This tutorial closely follows Damien Doligez’s phd (1995)
How to automatically reclaim unused memory ? unused
root root root
Theorem: Reachable memory is never reclaimed
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Periodically stop user code and perform:
marking cells black
free non-black cells Color conventions:
Version 1
Version 2
Version 3
1 mutator, 1 collector, no thread local variables
Update(x,f,y) == MarkGray(y); x.f = y MarkGray(x) == if x.color = WHITE then x.color = GRAY
COLLECTOR
Mark: foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat foreach x ∈ OBJECTS if x.color == GRAY foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until < ∀x, x.color ≠ GRAY > Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
1 mutator, 1 collector, no thread local variables MUTATOR [...] Udpate(x1,f1,y1); [...] Udpate(x2,f2,y2); [...] Alloc(); x1.f1 = y1 x2.f2 = y2 new O();
Update(x,f,y) == MarkGray(y); x.f = y MarkGray(x) == if x.color = WHITE then x.color = GRAY
COLLECTOR
Mark: foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat foreach x ∈ OBJECTS if x.color == GRAY foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until < ∀x, x.color ≠ GRAY > Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
Invariants 1.During Scan, every reachable white object is reachable from at least one grey object 2.Every path from a black object to a white object contains a grey
1 mutator, 1 collector, no thread local variables
Why we need mutator’s help
1 mutator, 1 collector, no thread local variables COLLECTOR
Mark: foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat foreach x ∈ OBJECTS if x.color == GRAY foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until < ∀x, x.color ≠ GRAY > Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
Why we need mutator’s help
1 mutator, 1 collector, no thread local variables COLLECTOR
Mark: foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat foreach x ∈ OBJECTS if x.color == GRAY foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until < ∀x, x.color ≠ GRAY > Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
Why we need mutator’s help
1 mutator, 1 collector, no thread local variables COLLECTOR
Mark: foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat foreach x ∈ OBJECTS if x.color == GRAY foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until < ∀x, x.color ≠ GRAY > Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
Mutator
1 mutator, 1 collector, thread local variables
Mark: foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat no_gray = true; foreach x ∈ OBJECTS if x.color == GRAY no_gray = false; foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until no_gray Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
The collector has not access to all mutator roots...
1 mutator, 1 collector, thread local variables
Mark: foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat no_gray = true; foreach x ∈ OBJECTS if x.color == GRAY no_gray = false; foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until no_gray Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
The collector has not access to all mutator roots...
mark your roots please
1 mutator, 1 collector, thread local variables
Mark: Handshake(); foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat no_gray = true; foreach x ∈ OBJECTS if x.color == GRAY no_gray = false; foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until no_gray Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
MUTATOR [...] Udpate(x1,f1,y1); [...] Cooperate(); [...] Alloc(); [...]
1 mutator, 1 collector, thread local variables
Handshake() = statusC = Next(statusC); while (statusm ≠ statusC) skip;
Cooperate() = if statusm ≠ statusC then foreach r ∈ LOCAL_ROOTS do MarkGray(r); statusm = statusC;
1 mutator, 1 collector, thread local variables
Threads mark their roots Collector waits for mutators to mark their roots
Mark: Handshake(); foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat no_gray = true; foreach x ∈ OBJECTS if x.color == GRAY no_gray = false; foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until no_gray Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
thread local
r
This is not correct yet!
1 mutator, 1 collector, thread local variables
Mark: Handshake(); foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat no_gray = true; foreach x ∈ OBJECTS if x.color == GRAY no_gray = false; foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until no_gray Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
thread local
r
This is not correct yet!
1 mutator, 1 collector, thread local variables
Mark: Handshake(); foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat no_gray = true; foreach x ∈ OBJECTS if x.color == GRAY no_gray = false; foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until no_gray Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
thread local
r
This is not correct yet!
1 mutator, 1 collector, thread local variables
Mark: Handshake(); foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat no_gray = true; foreach x ∈ OBJECTS if x.color == GRAY no_gray = false; foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until no_gray Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
thread local
r
This is not correct yet!
1 mutator, 1 collector, thread local variables
Mark: Handshake(); foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat no_gray = true; foreach x ∈ OBJECTS if x.color == GRAY no_gray = false; foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until no_gray Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
thread local
r
This is not correct yet!
1 mutator, 1 collector, thread local variables
Mark: Handshake(); foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat no_gray = true; foreach x ∈ OBJECTS if x.color == GRAY no_gray = false; foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until no_gray Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
thread local
r
This is not correct yet!
1 mutator, 1 collector, thread local variables
Mark: Handshake(); foreach x ∈ GLOBALS do MarkGray(x) Scan: repeat no_gray = true; foreach x ∈ OBJECTS if x.color == GRAY no_gray = false; foreach f ∈ fields(x) do MarkGray(x.f); x.color = BLACK until no_gray Sweep: foreach x ∈ OBJECTS if x.color == WHITE then FREE(x) Clear: foreach x ∈ OBJECTS x.color = WHITE
MUTATOR [...] Udpate(x1,f1,y1); [...] Cooperate(); [...] Alloc(); [...]
Update(x,f,y) == MarkGray(x.f); MarkGray(y) x.f = y MarkGray(x) == if x.color = WHITE then x.color = GRAY
1 mutator, 1 collector, thread local variables
Mutator N Mutator 1
...
N mutators, 1 collector, thread local variables
Handshake() = statusC = Next(statusC); while not (∀t, status[t] == statusC) skip; Cooperate() = if status[t] ≠ statusC then if ... then foreach r ∈ LOCAL_ROOTS do MarkGray(r); status[t] = statusC
During this busy waiting, some mutators may not be in the same status!
N mutators, 1 collector, thread local variables
Mark: Handshake(); // phaseC = SYNCH1 Handshake(); // phaseC = SYNCH2 stageC = SCANNING; Handshake(); // phaseC = ASYNCH Scan: Scan(); stageC = SWEEPING; Sweep: Sweep(); stageC = RESTING; Clear: < foreach x ∈ OBJECTS do x.color = WHITE >
phases
required per collection cycle
structures are necessary to track grey objects...
N mutators, 1 collector, thread local variables
Mark: Handshake(); // phaseC = SYNCH1 Handshake(); // phaseC = SYNCH2 stageC = SCANNING; Handshake(); // phaseC = ASYNCH Scan: Scan(); stageC = SWEEPING; Sweep: Sweep(); stageC = RESTING; Clear: < foreach x ∈ OBJECTS do x.color = WHITE >
phases
required per collection cycle
structures are necessary to track grey objects...
Lots of complicated thread interactions + Fragile Invariants
N mutators, 1 collector, thread local variables
SYNCH1 SYNCH2 ASYNCH SYNCH1
SYNCH2 ASYNCH
phase[C] phase[m]
collector cycle begins
| {z }
publish roots sweep collection ends
z }| { z
}| {
C-or-M TRACING SWEEPING RESTING
stageC
write barrier
black allocation white allocation
atomic black → white
scan
H a n d s h a k e ( ) ; / / p h a s e C = S Y N C H 1 H a n d s h a k e ( ) ; / / p h a s e C = S Y N C H 2 H a n d s h a k e ( ) ; / / p h a s e C = A S Y N C H S c a n ( ) ; S w e e p ( ) ;
Right level of abstraction :
algorithm’s logic from implementation details
managed langage dyn alloc, well typed GC injected code
with abstract sets, atomic blocks
managed langage dynamic alloc, well typed managed langage dyn alloc, well typed GC injected code executable Low level IR
Intermediate Representation:
cmd := | skip | assume e | x = [y].f | [x].f = e | atomic c | d = cas(o, n, X) | x = alloc rn | free x | free x | x = empty?(y) | x = top(y) | push(x, y) | x.pop() | c1; c2 | c1 c2 | loop { c } | foreach (x in l) do c od
cmd := | skip | assume e | x = [y].f | [x].f = e | atomic c | d = cas(o, n, X) | x = alloc rn | free x | free x | x = empty?(y) | x = top(y) | push(x, y) | x.pop() | c1; c2 | c1 c2 | loop { c } | foreach (x in l) do c od
MarkGray(m, x) == if x.color = WHITE then push(bucket[m], x)
cmd := | skip | assume e | x = [y].f | [x].f = e | atomic c | d = cas(o, n, X) | x = alloc rn | free x | free x | x = empty?(y) | x = top(y) | push(x, y) | x.pop() | c1; c2 | c1 c2 | loop { c } | foreach (x in l) do c od
cmd := | skip | assume e | x = [y].f | [x].f = e | atomic c | d = cas(o, n, X) | x = alloc rn | free x | free x | x = empty?(y) | x = top(y) | push(x, y) | x.pop() | c1; c2 | c1 c2 | loop { c } | foreach (x in l) do c od
Theorem soundness : ∀ ts gs , reachable init_state p (ts ,gs) ⇒ I gs.
trace_grey_reach_white_inv = stage[C]gs = TRACING
forall t, phase[t]gs = ASYNCH
forall r, Reachable_from t gs r -> (exists r0 , Grey gs r0 /\ reachable gs r0 r) \/ Black gs r
SYNCH1 SYNCH2
ASYNCH
SYNCH1 SYNCH2
ASYNCH phase[C]
phase[m]
handshake_inv gs = (forall t, In t TID , (phase[C]gs = phase[t]gs \/ phase[C]gs = phase[t]gs ⊕ 1))
sweeping_no_grey = stage[C] = SWEEPING ⇒ (∀ o, ¬ Grey gs o)
Theorem gc_sound: ∀ gs , reachable_state_mgc gs ⇒ (∀ t r, Reachable_from t gs r ∨ In_bucket gs t r ⇒ ¬ Blue gs r)
Most General GC Client
...
v
lastRead lastWrite
T0 T1 T2
Update(m, o,f, v) == MarkGray(o.f); MarkGray(v);
MarkGray(m, x) == if x.color = WHITE then bucket[m][lastWrite[m]] = x; lastWrite[m]++;
Update(m, o,f, v) == MarkGray(o.f); MarkGray(v);
...
v
lastRead lastWrite
T0
T1 T2
MarkBucket(m) == nr = lastRead[m]; nw = lastWrite[m]; while (nr < nw) do MarkBlack(bucket[m][nr]); nr++;
MarkGray(m, x) == if x.color = WHITE then bucket[m][lastWrite[m]] = x; lastWrite[m]++;
Update(m, o,f, v) == MarkGray(o.f); MarkGray(v);
...
v
lastRead lastWrite
T0
T1 T2
MarkBucket(m) == nr = lastRead[m]; nw = lastWrite[m]; while (nr < nw) do MarkBlack(bucket[m][nr]); nr++;
MarkGray(m, x) == if x.color = WHITE then bucket[m][lastWrite[m]] = x; lastWrite[m]++;
Update(m, o,f, v) == MarkGray(o.f); MarkGray(v);
...
v
lastRead lastWrite
T0
T1 T2
MarkBucket(m) == nr = lastRead[m]; nw = lastWrite[m]; while (nr < nw) do MarkBlack(bucket[m][nr]); nr++;
MarkGray(m, x) == if x.color = WHITE then bucket[m][lastWrite[m]] = x; lastWrite[m]++;
// Cooperate[m] if phase[m] != phase[C] then if phase[C] = ASYNCH then foreach r in Roots[m] do markGrey(m, r);
phase[m] = phase[C];
SYNCH1 SYNCH2 ASYNCH SYNCH1 SYNCH2
ASYNCH
phase[C] phase[m]
// Handshake switch phase[C] { SYNCH1 : phase[C] = SYNCH2; SYNCH2 : phase[C] = ASYNCH; ASYNCH : phase[C] = SYNCH1; } foreach m in Mutators do wait(phase[m] = phase[C]);
// Cooperate[m] if phase[m] != phase[C] then if phase[C] = ASYNCH then foreach r in Roots[m] do markGrey(m, r);
phase[m] = phase[C];
SYNCH1 SYNCH2 ASYNCH SYNCH1 SYNCH2
ASYNCH
phase[C] phase[m]
// Handshake switch phase[C] { SYNCH1 : phase[C] = SYNCH2; SYNCH2 : phase[C] = ASYNCH; ASYNCH : phase[C] = SYNCH1; } foreach m in Mutators do wait(phase[m] = phase[C]);
// Cooperate[m] if phase[m] != phase[C] then if phase[C] = ASYNCH then foreach r in Roots[m] do markGrey(m, r);
phase[m] = phase[C];
SYNCH1 SYNCH2 ASYNCH SYNCH1 SYNCH2
ASYNCH
phase[C] phase[m]
// Handshake switch phase[C] { SYNCH1 : phase[C] = SYNCH2; SYNCH2 : phase[C] = ASYNCH; ASYNCH : phase[C] = SYNCH1; } foreach m in Mutators do wait(phase[m] = phase[C]);
MarkGray(m, x) == if x.color = WHITE then bucket[m][lastWrite[m]] = x; // @local lastWrite[m]++; // release MarkBucket(m) == nr = lastRead[m]; // @private nw = lastWrite[m]; // acquire while (nr < nw) do MarkBlack(bucket[m][nr]); // @local nr++;
Atomicity Refinement for Verified Compilation. Jagannathan et al. [TOPLAS’14]
Publication Idiom
Relies of each thread
managed langage dynamic alloc, well typed GC injected code
with abstract sets, atomic blocks
managed langage dynamic alloc, well typed managed langage dynamic alloc, well typed GC injected code executable Low level IR