Compositional Verification of Termination-Preserving Refinement of Concurrent Programs Hongjin Liang Univ. of Science and Technology of China (USTC) Joint work with Xinyu Feng (USTC) and Zhong Shao (Yale)
Applications of Refinement of Concurrent Programs Correctness of program transformations – Compilers, program optimizations, … T S Compiler Multithreaded Java programs Java bytecode Refinement T ⊑ S : T has no more observable behaviors than S
Applications of Refinement of Concurrent Programs Correctness of program transformations – Compilers, program optimizations, … Correctness of concurrent objects & libraries
Example – Counter Atomic spec. inc(){ local done, tmp; done = false; while (!done) { tmp = cnt; done = cas(cnt, tmp, tmp+1); } INC(){ } atomic block Impl. in concurrent setting inc || while(true) incINC || while(true) INC ⊑ Correctness: impl refines atomic spec in any context e.g.
Applications of Refinement of Concurrent Programs Correctness of program transformations – Compilers, program optimizations, … Correctness of concurrent objects & libraries Correctness of runtime systems & OS kernels – Concurrent garbage collectors, STM algorithms, …
Refinement needs to preserve termination! while (true) skip ⋢ skip Compilers of concurrent programs – Need to preserve termination Concurrent objects & libraries – Must satisfy functionality correctness (e.g. linearizability) & “termination” properties (e.g. lock-freedom) No existing logics can verify such a refinement T ⊑ S !
Our Contributions Rely-Guarantee-based program logic – For termination-preserving refinement – Compositional verification Applied to verify – Linearizability + lock-freedom – Correctness of optimizations of concurrent prog. New simulation as meta-theory
Termination-Preserving Refinement T ⊑ S : More Examples while (true) skipskip ⋢ while (true) skip; print(1); while (true) skip; ⋢ We should avoid T looping infinitely without progress cannot print out 1always print out 1 complete some abstract tasks of S
Verifying T ⊑ S : How to avoid T looping infinitely without progress Assign tokens for loops – Consume a token for each iteration € One token to pay for one iteration local i = 0; while (i < 2) i++; print(1); ⊑ € i = 0 i = 1i = 2
Verifying T ⊑ S : How to avoid T looping infinitely without progress Assign tokens for loops – Consume a token for each iteration local i = 0; while (true) i++; print(1); ⋢ Never enough tokens!
How to compositionally verify T ⊑ S in concurrent settings (Compositionality) T1 || T2 ⊑ S1 || S2 T1S1 ⊑ T2 S2 ⊑ In particular, termination-preservation of individual threads is not compositional
Challenge : termination-preservation of individual threads is not compositional print(1); || print(1); S1 || S2 : while (i==0) { i++; } print(1); || while (i==0) { i--; } print(1); T1 || T2 : Both T1 & T2 terminate, but T1 || T2 may not terminate
Challenge : termination-preservation of individual threads is not compositional Consider NO environment Need to take env interference into account! How will env affect termination-preservation? Which effects are acceptable? How to change tokens for acceptable env effects? To compositionally verify T ⊑ S in concurrent settings, … Env may delay progress of the current thread. Is it always bad?
Env delays progress of current thread – Sometimes acceptable inc(){ 1 local done, tmp; 2 done = false; 3 while (!done) { 4 tmp = cnt; 5 done = cas(cnt, tmp, tmp+1); 6 } } while(true) inc; || INC(){ } Take a snapshot Compare and swap May never progress (complete abstract task INC) since its cas fails infinitely often
Env delays progress of current thread – Sometimes acceptable inc(){ 1 local done, tmp; 2 done = false; 3 while (!done) { 4 tmp = cnt; 5 done = cas(cnt, tmp, tmp+1); 6 } } while(true) inc; || INC(){ } Because: The failure of one thread must be caused by the success of another. The system as a whole progresses. Should allow current thread to loop more (i.e. reset tokens) if env progresses
Our Ideas Prove termination-preservation of individual threads under env interference – Assign tokens for loops – Consume a token for each iteration Avoid looping infinitely without progress – Could reset tokens when env progresses The system as a whole progresses. So the current thread can loop more.
How tokens change : Example of counter inc(){ 1 local done, tmp; 2 done = false; 3 while (!done) { 4 tmp = cnt; 5 done = cas(cnt, tmp, tmp+1); 6 } } cnt 0 inc || inc || inc ⊑ INC || INC || INC € €€ 1 succeededfailed pay for the next iteration Idea: if env progresses, reset the token.
Detailed Thread-Local Proof of Counter inc(){ 1 local done, tmp; 2 done = false; 3 while (!done) { 4 tmp = cnt; 5 done = cas(cnt, tmp, tmp+1); 6 } } INC(){ } { cnt = CNT } { cnt = CNT rem(skip) } rem(INC) } abstract tasks to be finished Embed hi-level code in assertions
inc(){ 1 local done, tmp; 2 done = false; 3 while (!done) { 4 tmp = cnt; 5 done = cas(cnt, tmp, tmp+1); 6 } } { cnt = CNT rem(INC) } Assign tokens for loops } € { cnt = CNT rem(INC) } pay for one iteration { cnt = CNT rem(INC) ( cnt = tmp )} cnt ≠ tmp { cnt = CNT+1 (done rem(INC)) … } env’s progress – reset tokens ) } € execute hi-level code { cnt = CNT (done rem(skip)) … } ( done rem(INC) ) } € pay for the next iteration
NOT a total correctness logic! We ensure low-level preserves termination of high-level – For loops: ensure progress before consuming all tokens Not ensure termination of low-level/high-level code – For loops: not ensure termination before consuming all tokens INC_LOOP(){ while (true) { ; } inc_loop(){ local tmp; while (true) { tmp = cnt; cas(cnt, tmp, tmp+1); } Example – loop a counter ⊑
INC_LOOP(){ while (true) { INC; } inc_loop(){ 1 local tmp; 2 while (true) { 3 tmp = cnt; 4 cas(cnt, tmp, tmp+1); 5 } } { cnt = CNT rem(INC_LOOP) } } € { cnt = CNT rem(INC_LOOP) } { cnt = CNT rem(INC_LOOP) … } pay for the next iteration env’s progress – reset tokens { cnt = CNT rem(INC_LOOP) (cnt = tmp cnt ≠ tmp )} € { cnt = CNT+1 rem(INC_LOOP) … } if cas successful € reset tokens since the thrd progresses rem(INC; INC_LOOP) …} execute hi-level code Need one token only
Summary of Our Informal Ideas Prove termination-preservation of individual threads under env interference – Assign tokens for loops – Consume a token for each iteration – Could reset tokens when env progresses – Could reset tokens when current thread progresses
Our Logic for Verifying T ⊑ S Judgment – R, G: rely/guarantee to describe env interference … and extended with effects on termination-preservation (i.e. whether current thrd is allowed to reset tokens) R, G {p rem(S) } T {q rem(skip)}
{p rem(S)} T {q rem(skip)} R, G {p} (T, S) {q}R, G If we can find R, G and q such that then we have: T ⊑ p S {p} (T, S) {q}R, G
Compositionality Rules {p} (T1, S1) {r}R, G{r} (T2, S2) {q}R, G {p} (T1;T2, S1;S2) {q}R, G Just like standard Rely/Guarantee rules, e.g. {p1} (T1, S1) {q1}R1, G1 {p2} (T2, S2) {q2}R2, G2 {p1 p2} (T1ǁT2, S1ǁS2) {q1 q2}R1 R2, G1 G2 G2 R1 G1 R2
Soundness Theorem If we can find R, G and q such that then we have: T ⊑ p S {p} (T, S) {q}R, G
Applications Linearizability & lock-freedom – Counters and its variants – Treiber stack – Michael-Scott lock-free queue – DGLM lock-free queue Correctness of non-atomic objects – Synchronous queue (used in Java 6) Equivalence of optimized algo & original one – Counters and its variants – TAS lock and TTAS lock
Conclusion Logic for termination-preserving refinement of concurrent programs – Use tokens for termination-preservation Reset tokens when current thrd or env progresses New rely/guarantee conditions for env interference – Compositionality – Meta-theory: a new simulation
BACKUP SLIDES
Examples that we cannot verify Linearizability & lock-freedom – Helping HSY elimination-backoff stack – Speculation RDCSS algorithm Technically, we use a forward simulation as meta-theory – Forward-backward simulation?
Lock-freedom vs. Obstruction-freedom vs. Wait-freedom How is progress of current thread affected by env? Lock-freedom – Can be affected only if env progresses Obstruction-freedom – Can be affected whatever env does – But when the thread executes in isolation, it must progress Wait-freedom – Cannot be affected
Modify our logic to verify obstruction-freedom and wait-freedom Our logic: distinguish whether env progresses – Could reset tokens when env progresses – Lock-freedom Obstruction-freedom – Could reset tokens whenever env does – But when no env, the thread must progress Wait-freedom – Never reset tokens Should also modify definition of T ⊑ S for obstruction-freedom and wait-freedom
Lock-Based Algorithms Refinement needs to take fairness into account inc(){ lock(l); cnt++; unlock(l); } INC(){ } inc || inc may not terminate in an unfair execution
Hoffmann et al.’s logic for lock-freedom Not prove termination-preservation Need to know the number of threads – Token transfer cnt 0 € € € 1 succeededfailed €€€€ €€ loop: pay one token for one iteration loop: pay one token for one iteration cas: token transfer [LICS’13]