
Provably Correct Peephole Optimizations with Alive Nuno P. Lopes David Menendez Santosh Nagarakatte John Regehr Microsoft Research, UK Rutgers University, USA University of Utah, USA [email protected] fdavemm,[email protected] [email protected] Abstract (compiler verification) or a proof that a particular compilation was Compilers should not miscompile. Our work addresses problems correct (translation validation). For example, CompCert [17] uses in developing peephole optimizations that perform local rewriting a hybrid of the two approaches. Unfortunately, creating CompCert to improve the efficiency of LLVM code. These optimizations are required several person-years of proof engineering and the resulting individually difficult to get right, particularly in the presence of un- tool does not provide a good value proposition for many real-world defined behavior; taken together they represent a persistent source use cases: it implements a subset of C, optimizes only lightly, and of bugs. This paper presents Alive, a domain-specific language for does not yet support x86-64 or the increasingly important vector writing optimizations and for automatically either proving them cor- extensions to x86 and ARM. In contrast, production compilers rect or else generating counterexamples. Furthermore, Alive can are constantly improved to support new language standards and be automatically translated into C++ code that is suitable for inclu- to obtain the best possible performance on emerging architectures. sion in an LLVM optimization pass. Alive is based on an attempt This paper presents Alive: a new language and tool for devel- to balance usability and formal methods; for example, it captures— oping correct LLVM optimizations. Alive aims for a design point but largely hides—the detailed semantics of three different kinds that is both practical and formal; it allows compiler writers to spec- of undefined behavior in LLVM. We have translated more than 300 ify peephole optimizations for LLVM’s intermediate representation LLVM optimizations into Alive and, in the process, found that eight (IR), it automatically proves them correct with the help of satisfia- of them were wrong. bility modulo theory (SMT) solvers (or provides a counterexample), and it automatically generates C++ code that is similar to hand- Categories and Subject Descriptors D.2.4 [Programming Lan- written peephole optimizations such as those found in LLVM’s in- guages]: Software/Program Verification; D.3.4 [Programming struction combiner (InstCombine) pass. InstCombine transforma- Languages]: Processors—Compilers; F.3.1 [Logics and Meanings tions perform numerous algebraic simplifications that improve effi- of Programs]: Specifying and Verifying and Reasoning about Pro- ciency, enable other optimizations, and canonicalize LLVM code. grams Alive is inspired by previous research on domain specific lan- guages for easing compiler development such as Gospel [36], Keywords Compiler Verification, Peephole Optimization, Alive Rhodium [16], PEC [13], Broadway [9], and Lola [21]. Alive’s main contribution to the state of the art is providing a usable formal meth- 1. Introduction ods tool based on the semantics of LLVM IR, with support for auto- Compiler optimizations should be efficient, effective, and correct— mated correctness proofs in the presence of LLVM’s three kinds of but meeting all of these goals is difficult. In practice, whereas effi- undefined behavior, and with support for code generation. ciency and effectiveness are relatively easy to quantify, correctness While testing LLVM using Csmith, we found InstCombine to is not. Incorrect compiler optimizations can remain latent for long be the single buggiest file [37]; it was subsequently split into multi- periods of time; the resulting problems are subtle and difficult to ple files totaling about 15,600 SLOC. A similar pass, InstSimplify, diagnose since the incorrectness is introduced at a level of abstrac- contains about 2,500 more SLOC. An example InstCombine trans- (x ⊕ −1) + C (C − 1) − x tion lower than the one where software developers typically work. formation takes and turns it into where x ⊕ C Although mainstream compilers use well-known algorithms, bugs is a variable, is exclusive or, and is an arbitrary constant x C 3333 arise due to misunderstandings of the semantics, incomplete reason- as wide as . If is , the LLVM input to this InstCombine ing about boundary conditions, and errors in the implementation of transformation would look like this: the algorithms. %1 = xor i32 %x, -1 Random testing [15, 23, 37] is one approach to improve the %2 = add i32 %1, 3333 correctness of compilers; it has been shown to be effective, but of course testing misses bugs. A stronger form of insurance against and the optimized code: compiler bugs can be provided by a proof that the compiler is correct %2 = sub i32 3332, %x The LLVM code specifying this transformation1 is 160 bytes of C++, excluding comments. In Alive it is: %1 = xor %x, -1 %2 = add %1, C => %2 = sub C-1, %x 1 http://llvm.org/viewvc/llvm-project/llvm/tags/ RELEASE_350/final/lib/Transforms/InstCombine/ [Copyright notice will appear here once ’preprint’ option is removed.] InstCombineAddSub.cpp?view=markup#l1148 1 2015/4/27 Pre: C1 & C2 == 0 && MaskedValueIsZero(%V, ∼C1) prog :: = pre nl stmt =) stmt %t0 = or %B, %V stmt :: = stmt nl stmt j reg = inst j reg = op j %t1 = and %t0, C1 store op; op j unreachable %t2 = and %B, C2 inst :: = binop attr op; op j conv op j %R = or %t1, %t2 select op; op; op j icmp cond; op; op j => alloca typ; constant j bitcast op j %R = and %t0, (C1 | C2) inttoptr op j ptrtoint op j getelementptr op;:::; op j load op cond :: = eq j ne j ugt j uge j ult j Figure 2. An example illustrating many of Alive’s features. ((B _ ule j sgt j sge j slt j sle V ) ^ C1) _ (B ^ C2) can be transformed to (B _ V ) ^ typ :: = isz j typ∗ j [ sz × typ ] (C1 _ C2) when C1 ^ C2 = 0 and when the predicate binop :: = add j sub j mul j udiv j sdiv j MaskedV alueIsZero(V; :C1) is true, indicating that an LLVM urem j srem j shl j lshr j ashr j dataflow analysis has concluded that V ^ :C1 = 0. %B and %V and j or j xor are input variables. C1 and C2 are constants. %t0, %t1, and %t2 attr :: = nsw j nuw j exact are temporaries. This transformation is rooted at %R. op :: = reg j constant j undef conv :: = zext j sext j trunc exact instruction attributes that weaken the behavior of integer Figure 1. Partial Alive syntax. Types include arbitrary bitwidth instructions by adding undefined behaviors. integers, pointers typ∗, and arrays [sz × typ] that have a statically- known size sz. Scoping. The source and target templates must have a common root variable that is the root of the respective graphs. The remaining variables are either inputs to the transformation or else temporary The Alive specification is less than one third the size of the variables produced by instructions in the source or target template. LLVM C++ code. Moreover, it was designed to resemble—both Inputs are visible throughout the source and target templates. Tem- syntactically and semantically—the LLVM transformation that it poraries defined in the source template are in scope for the precondi- describes. The Alive code is, we claim, much easier to understand, tion, the target, and the remaining part of the source from the point in addition to being verifiable by the Alive tool chain. This trans- of definition. Temporaries declared in the target are in scope for the formation illustrates two forms of abstraction supported by Alive: remainder of the target. To help catch errors, every temporary in abstraction over choice of a compile-time constant and abstraction the source template must be used in a later source instruction or be over bitwidth. overwritten in the target, and all target instructions must be used in So far Alive has helped us discover eight previously unknown a later target instruction or overwrite a source instruction. bugs in the LLVM InstCombine transformations. Furthermore, we Constant expressions. To allow algebraic simplifications and con- have prevented dozens of bugs from getting into LLVM by mon- stant folding, Alive includes a language for constant expressions. A itoring the various InstCombine patches as they were committed constant expression may be a literal, an abstract constant (e.g., C in to the LLVM subversion repository. Several LLVM developers are the example on the previous page), or a unary or binary operator currently using the Alive prototype to check their InstCombine trans- 2 applied to one or two constant expressions. The operators include formations. Alive is open source. signed and unsigned arithmetic operators and bitwise logical opera- tors. Alive also supports functions on constant expressions. Built-in 2. The Alive Language functions include type conversions and mathematical and bitvector utilities (e.g., abs(), umax(), width()). We designed Alive to resemble the LLVM intermediate represen- tation (IR) because our user base—the LLVM developers—is al- 2.2 Type System ready familiar with it. Alive’s most important features include its Alive supports a subset of LLVM’s types. The types in Alive abstraction over choice of constants, over the bitwidths of operands are a union of integer types (I = fi1; i2; i3;:::g for bitwidth (Sections 2.2 and 3.2), and over LLVM’s instruction attributes that 1,2,3,:::), pointer types (P = ft∗ j t 2 T g), array types (A = control undefined behavior (Sections 2.4 and 3.4). f[n x t] j n 2 N ^ t 2 T g), and the void type. LLVM also defines the set of first-class types (FC = I[P), which are the types that 2.1 Syntax can be the result of an instruction.
Details
-
File Typepdf
-
Upload Time-
-
Content LanguagesEnglish
-
Upload UserAnonymous/Not logged-in
-
File Pages11 Page
-
File Size-