blob: f6291909660b7da607e98cf72c5835090438f43d [file] [log] [blame]
% formal/formal.tex
% SPDX-License-Identifier: CC-BY-SA-3.0
\QuickQuizChapter{chp:Formal Verification}{Formal Verification}
%
\Epigraph{Beware of bugs in the above code; I have only proved it correct,
not tried it.}{\emph{Donald Knuth}}
\OriginallyPublished{Chapter}{chp:Formal Verification}{Formal Verification}{Linux Weekly News}{PaulEMcKenney2007QRCUspin,PaulEMcKenney2008dynticksRCU,PaulEMcKenney2011ppcmem}
Parallel algorithms can be hard to write, and even harder to debug.
Testing, though essential, is insufficient, as fatal race conditions
can have extremely low probabilities of occurrence.
Proofs of correctness can be valuable, but in the end are just as
prone to human error as is the original algorithm.
In addition, a proof of correctness cannot be expected to find errors
in your assumptions, shortcomings in the requirements,
misunderstandings of the underlying software or hardware primitives,
or errors that you did not think to construct a proof for.
This means that formal methods can never replace testing, however,
formal methods are nevertheless a valuable addition to your validation toolbox.
It would be very helpful to have a tool that could somehow locate
all race conditions.
A number of such tools exist, for example,
Section~\ref{sec:formal:General-Purpose State-Space Search} provides an
introduction to the general-purpose state-space search tools Promela and Spin,
Section~\ref{sec:formal:Special-Purpose State-Space Search}
similarly introduces the special-purpose ppcmem and cppmem tools,
Section~\ref{sec:formal:Axiomatic Approaches}
looks at an example axiomatic approach,
Section~\ref{sec:formal:SAT Solvers}
briefly overviews SAT solvers,
and finally
Section~\ref{sec:formal:Summary}
sums up use of formal-verification tools for verifying parallel algorithms.
\input{formal/spinhint}
\input{formal/dyntickrcu}
\input{formal/ppcmem}
\input{formal/axiomatic}
\input{formal/sat}
\section{Summary}
\label{sec:formal:Summary}
The formal-verification techniques described in this chapter
are very powerful tools for validating small
parallel algorithms, but they should not be the only tools in your toolbox.
Despite decades of focus on formal verification, testing remains the
validation workhorse for large parallel software
systems~\cite{JonathanCorbet2006lockdep,DaveJones2011Trinity}.
It is nevertheless quite possible that this will not always be the case.
To see this, consider that there are more than one billion instances
of the Linux kernel as of 2013.
Suppose that the Linux kernel has a bug that manifests on average every million
years of runtime.
As noted at the end of the preceding chapter,
this bug will be appearing three times \emph{per day} across the installed
base.
But the fact remains that most formal validation techniques can be used
only on very small code bases.
So what is a concurrency coder to do?
One approach is to think in terms of finding the first bug, the first
relevant bug, the last relevant bug, and the last bug.
The first bug is normally found via inspection or compiler diagnostics.
Although the increasingly sophisticated diagnostics provided by modern
compilers might be considered to be a lightweight sort of formal
verification, it is not common to think of them in those terms.
This is in part due to an odd practitioner prejudice which says
``If I am using it, it cannot be formal verification'' on the one
hand, and the large difference in sophistication between compiler
diagnostics and verification research on the other.
Although the first relevant bug might be located via inspection or
compiler diagnostics, it is not unusual for these two steps to find
only typos and false positives.
Either way, the bulk of the relevant bugs, that is, those bugs that
might actually be encountered in production, will often be found via testing.
When testing is driven by anticipated or real use cases, it is not
uncommon for the last relevant bug to be located by testing.
This situation might motivate a complete rejection of formal verification,
however, irrelevant bugs have a bad habit of suddenly becoming relevant
at the least convenient moment possible, courtesy of black-hat attacks.
For security-critical software, which appears to be a continually
increasing fraction of the total, there can thus be strong motivation
to find and fix the last bug.
Testing is demonstrably unable to find the last bug, so there is a
possible role for formal verification.
That is, there is such a role if and only if formal verification
proves capable of growing into it.
As this chapter has shown, current formal verification systems are
extremely limited.
Another approach is to consider that
formal verification is often much harder to use than is testing.
This is of course in part a cultural statement, and there is every reason
to hope that formal verification will be perceived to be easier as more
people become familiar with it.
That said, very simple test harnesses can find significant bugs in arbitrarily
large software systems.
In contrast, the effort required to apply formal verification seems to
increase dramatically as the system size increases.
I have nevertheless made occasional use of formal verification
for more than 20 years, playing to formal verification's strengths,
namely design-time verification of small complex portions of the overarching
software construct.
The larger overarching software construct is of course validated by testing.
\QuickQuiz{}
In light of the full verification of the L4 microkernel,
isn't this limited view of formal verification just a little
bit obsolete?
\QuickQuizAnswer{
Unfortunately, no.
The first full verification of the L4 microkernel was a tour de force,
with a large number of Ph.D.~students hand-verifying code at a
very slow per-student rate.
This level of effort could not be applied to most software projects
because the rate of change is just too great.
Furthermore, although the L4 microkernel is a large software
artifact from the viewpoint of formal verification, it is tiny
compared to a great number of projects, including LLVM,
gcc, the Linux kernel, Hadoop, MongoDB, and a great many others.
Although formal verification is finally starting to show some
promise, including more-recent L4 verifications involving greater
levels of automation, it currently has no chance of completely
displacing testing in the foreseeable future.
And although I would dearly love to be proven wrong on this point,
please note that such a proof will be in the form of a real tool
that verifies real software, not in the form of a large body of
rousing rhetoric.
} \QuickQuizEnd
One final approach is to consider the following two definitions and the
consequence that they imply:
\begin{description}
\item[Definition:] Bug-free programs are trivial programs.
\item[Definition:] Reliable programs have no known bugs.
\item[Consequence:] Any non-trivial reliable program contains at least
one as-yet-unknown bug.
\end{description}
From this viewpoint, any advances in validation and verification can
have but two effects: (1)~An increase in the number of trivial programs or
(2)~A decrease in the number of reliable programs.
Of course, the human race's increasing reliance on multicore systems and
software provides extreme motivation for a very sharp increase in the
number of trivial programs!
However, if your code is so complex that you find yourself
relying too heavily on formal-verification
tools, you should carefully rethink your design, especially if your
formal-verification tools require your code to be hand-translated
to a special-purpose language.
For example, a complex implementation of the dynticks interface for
preemptible RCU that was presented in
Section~\ref{sec:formal:Promela Parable: dynticks and Preemptible RCU}
turned out to
have a much simpler alternative implementation, as discussed in
Section~\ref{sec:formal:Simplicity Avoids Formal Verification}.
All else being equal, a simpler implementation is much better than
a mechanical proof for a complex implementation!
And the open challenge to those working on formal verification techniques
and systems is to prove this summary wrong!