Will Informatics be able to Justify the Construction of Large Computer Based Systems? Part II. Trusted compiler implementation

The present and the previous article on Realistic Correct Systems Implementation together address correct construction and functioning of large computer based systems. In view of so many annoying and dangerous system misbehaviors we want to ask: Can informaticians righteously be accounted for incorr...

Повний опис

Збережено в:
Бібліографічні деталі
Дата:2015
Автори: Goerigk, W., Langmaack, H.
Формат: Стаття
Мова:Ukrainian
Опубліковано: PROBLEMS IN PROGRAMMING 2015
Теми:
Онлайн доступ:https://pp.isofts.kiev.ua/index.php/ojs1/article/view/8
Теги: Додати тег
Немає тегів, Будьте першим, хто поставить тег для цього запису!
Назва журналу:Problems in programming
Завантажити файл: Pdf

Репозитарії

Problems in programming
id pp_isofts_kiev_ua-article-8
record_format ojs
resource_txt_mv ppisoftskievua/d2/5f28d63083ed7b8263ec021f400ed5d2.pdf
spelling pp_isofts_kiev_ua-article-82018-10-02T13:33:23Z Will Informatics be able to Justify the Construction of Large Computer Based Systems? Part II. Trusted compiler implementation Cпособна ли информатика обосновывать построение больших компьютерных систем? Часть II. Доказательное построение компиляторов Чи здатна інформатика обґрунтовувати побудову великих комп’ютерних систем? Частина II. Доказова побудова компіляторів Goerigk, W. Langmaack, H. The present and the previous article on Realistic Correct Systems Implementation together address correct construction and functioning of large computer based systems. In view of so many annoying and dangerous system misbehaviors we want to ask: Can informaticians righteously be accounted for incorrectness of ystems, will they be able to justify systems to work correctly as intended? We understand the word justification in this sense, i.e. for the design of computer based systems, the formulation of mathematical models of information flows, and the construction of controlling software to be such that the expected system effects, the absence of internal failures, and the robustness towards misuses and malicious external attacks are foreseeable as logical consequences of the models. Since more than 40 years, theoretical informatics, software engineering and compiler construction have made important contributions to correct specification and also to correct high-level implementation of compilers. But the third step, translation — bootstrapping — of high level compiler programs into host machine code by existing host ompilers, is as important. So far there are no realistic recipes to close this gap, although it is known for many years that trust in executable code can dangerously be compromised by Trojan Horses in compiler executables, even if they pass strongest tests. Our article will show how to close this low level gap. We demonstrate the method of rigorous syntactic a-posteriori code inspection, which has been developed by the research group Verifix funded by the Deutsche Forschungsgemeinschaft (DFG).  Много лет теоретическая информатика в части разработки программного обеспечения и построения компиляторов занималась проблемами правильности спецификаций и высокоуровневых реализаций компиляторов. Во второй части статьи рассматривается проблема корректного и безопасного перевода (bootstrapping) программ с языка высокого уровня в коды машины. Показано, как решаются проблемы корректности программ на языках низкого уровня. Продемонстрирован метод строго синтаксического апостериорного анализа, который был разработан исследовательской группой Verifix в университете г. Киля (ФРГ).  Багато років теоретична інформатика вчастині розробки програмного забезпечення і побудови компіляторів займалась проблемами правильності специфікацій і високорівневих реалізацій компіляторів. У другій частині статті розглядається проблема коректного і безпечного перекладу (bootstrapping) програм з мови високого рівня в коди машини. Показано, як вирішуються проблеми коректності програм на мовах низького рівня. Продемонстрований метод строго синтаксичного апостеріор ного аналізу, котрий був розроблений дослідною групою Verifix в університеті м. Киля (ФРН).  PROBLEMS IN PROGRAMMING ПРОБЛЕМЫ ПРОГРАММИРОВАНИЯ ПРОБЛЕМИ ПРОГРАМУВАННЯ 2015-07-01 Article Article application/pdf https://pp.isofts.kiev.ua/index.php/ojs1/article/view/8 PROBLEMS IN PROGRAMMING; No 2 (2003) ПРОБЛЕМЫ ПРОГРАММИРОВАНИЯ; No 2 (2003) ПРОБЛЕМИ ПРОГРАМУВАННЯ; No 2 (2003) 1727-4907 uk https://pp.isofts.kiev.ua/index.php/ojs1/article/view/8/13 Copyright (c) 2015 ПРОБЛЕМИ ПРОГРАМУВАННЯ
institution Problems in programming
baseUrl_str https://pp.isofts.kiev.ua/index.php/ojs1/oai
datestamp_date 2018-10-02T13:33:23Z
collection OJS
language Ukrainian
topic

spellingShingle

Goerigk, W.
Langmaack, H.
Will Informatics be able to Justify the Construction of Large Computer Based Systems? Part II. Trusted compiler implementation
topic_facet





format Article
author Goerigk, W.
Langmaack, H.
author_facet Goerigk, W.
Langmaack, H.
author_sort Goerigk, W.
title Will Informatics be able to Justify the Construction of Large Computer Based Systems? Part II. Trusted compiler implementation
title_short Will Informatics be able to Justify the Construction of Large Computer Based Systems? Part II. Trusted compiler implementation
title_full Will Informatics be able to Justify the Construction of Large Computer Based Systems? Part II. Trusted compiler implementation
title_fullStr Will Informatics be able to Justify the Construction of Large Computer Based Systems? Part II. Trusted compiler implementation
title_full_unstemmed Will Informatics be able to Justify the Construction of Large Computer Based Systems? Part II. Trusted compiler implementation
title_sort will informatics be able to justify the construction of large computer based systems? part ii. trusted compiler implementation
title_alt Cпособна ли информатика обосновывать построение больших компьютерных систем? Часть II. Доказательное построение компиляторов
Чи здатна інформатика обґрунтовувати побудову великих комп’ютерних систем? Частина II. Доказова побудова компіляторів
description The present and the previous article on Realistic Correct Systems Implementation together address correct construction and functioning of large computer based systems. In view of so many annoying and dangerous system misbehaviors we want to ask: Can informaticians righteously be accounted for incorrectness of ystems, will they be able to justify systems to work correctly as intended? We understand the word justification in this sense, i.e. for the design of computer based systems, the formulation of mathematical models of information flows, and the construction of controlling software to be such that the expected system effects, the absence of internal failures, and the robustness towards misuses and malicious external attacks are foreseeable as logical consequences of the models. Since more than 40 years, theoretical informatics, software engineering and compiler construction have made important contributions to correct specification and also to correct high-level implementation of compilers. But the third step, translation — bootstrapping — of high level compiler programs into host machine code by existing host ompilers, is as important. So far there are no realistic recipes to close this gap, although it is known for many years that trust in executable code can dangerously be compromised by Trojan Horses in compiler executables, even if they pass strongest tests. Our article will show how to close this low level gap. We demonstrate the method of rigorous syntactic a-posteriori code inspection, which has been developed by the research group Verifix funded by the Deutsche Forschungsgemeinschaft (DFG). 
publisher PROBLEMS IN PROGRAMMING
publishDate 2015
url https://pp.isofts.kiev.ua/index.php/ojs1/article/view/8
work_keys_str_mv AT goerigkw willinformaticsbeabletojustifytheconstructionoflargecomputerbasedsystemspartiitrustedcompilerimplementation
AT langmaackh willinformaticsbeabletojustifytheconstructionoflargecomputerbasedsystemspartiitrustedcompilerimplementation
AT goerigkw cposobnaliinformatikaobosnovyvatʹpostroeniebolʹšihkompʹûternyhsistemčastʹiidokazatelʹnoepostroeniekompilâtorov
AT langmaackh cposobnaliinformatikaobosnovyvatʹpostroeniebolʹšihkompʹûternyhsistemčastʹiidokazatelʹnoepostroeniekompilâtorov
AT goerigkw čizdatnaínformatikaobgruntovuvatipobudovuvelikihkompûternihsistemčastinaiidokazovapobudovakompílâtorív
AT langmaackh čizdatnaínformatikaobgruntovuvatipobudovuvelikihkompûternihsistemčastinaiidokazovapobudovakompílâtorív
first_indexed 2025-07-17T10:09:05Z
last_indexed 2025-07-17T10:09:05Z
_version_ 1850413018894041088
fulltext Теоретические и методологические основы программирования © Wolfgang Goerigk, Hans Langmaack, 2003 ISSN 1727-4907. Проблемы программирования. 2003. № 2 3 UDC 681.3.51 Wolfgang Goerigk, Hans Langmaack WILL INFORMATICS BE ABLE TO JUSTIFY THE CONSTRUCTION OF LARGE COMPUTER BASED SYSTEMS? PART II. TRUSTED COMPILER IMPLEMENTATION1 The present and the previous article on Realistic Correct Systems Implementation to- gether address correct construction and functioning of large computer based systems. In view of so many annoying and dangerous system misbehaviors we want to ask: Can infor- maticians righteously be accounted for incorrectness of systems, will they be able to justify systems to work correctly as intended? We understand the word justification in this sense, i.e. for the design of computer based systems, the formulation of mathematical models of information flows, and the construction of controlling software to be such that the ex- pected system effects, the absence of internal failures, and the robustness towards misuses and malicious external attacks are foreseeable as logical consequences of the models. Since more than 40 years, theoretical informatics, software engineering and compiler construction have made important contributions to correct specification and also to correct high-level implementation of compilers. But the third step, translation — bootstrapping — of high level compiler programs into host machine code by existing host compilers, is as important. So far there are no realistic recipes to close this gap, although it is known for many years that trust in executable code can dangerously be compromised by Trojan Horses in compiler executables, even if they pass strongest tests. Our article will show how to close this low level gap. We demonstrate the method of rigorous syntactic a-posteriori code inspection, which has been developed by the research group Verifix funded by the Deutsche Forschungsgemeinschaft (DFG). 1Supported by Deutsche Forschungsgemeinschaft (DFG) under the grants La426/15 and /16. 1. Realistic Correct Systems Implementation As an introduction to the present article, which focuses on trusted compiler implementation, we want to summarize and repeat some of the important defini- tions from the previous article [Goe- rigk/Langmaack01c] on Realistic Correct Systems Implementation. Compiler construction is crucial to the construction of computer based sys- tems, and correct realistic compilers are necessary for a convincing construction process of correct software/hardware sys- tems. Correct realistic compilation is nec- essary, can be achieved and it establishes a trusted execution basis for further soft- ware development. Most faults and bugs are indeed found in software design and high-level software engineering, however, we find considerably many compiler bugs as well [GccBugList], and one or the other turns out to be critical. Unless the implementation and integration gap of software systems is closed with mathe- matical rigor, the risk of incorrect code generation will always seriously disturb the recognition of and the trust in certain- ties which are guaranteed by mature software engineering and formal methods. The rigid nature of matter educates hardware technologists to be extremely sensitive towards hardware failures; they are felt as sensations. So system faults are mostly and increasingly caused by soft- ware. This observation is crucial. Software engineers are permitted to assume hard- ware to work correctly, and to exploit this assumption for equally sensitive rig- orous low level implementation verifica- tion of software. We will demonstrate this in a non-self-evident way in particular for compilers, because they are crucial to low level implementation correctness of application software. At the upper end, software engineers rely on the machine independent semantics for their high- level languages, but they have to be aware of what kind of implementation correctness makes sense and can realisti- cally be guaranteed in their application domain. Теоретические и методологические основы программирования 4 Correct realistic compilation is the major topic of the German joint project Verifix on Correct Compilers of the uni- versities of Karlsruhe, Kiel and Ulm [Go- erigk97d, Goos/Zimmermann99]. The goal is to develop repeatable engineering me- thods for the construction and correct implementation of compiler programs for realistic, industrially applicable imperative and object-oriented source programming languages and real world target and host processors, which generate efficient code that compares to unverified compilers. A number of key ideas and achie- vements make this possible and help to modularize the entire effort into suffi- ciently small steps: an appropriate realis- tic notion of correct implementation and compiler correctness [Goerigk/Langma- ack01c, Mueller-Olm/Wolf99], the reuse of approved compiler architecture and construction and compiler generator techniques [Goos/Zimmermann99], the method of a-posteriori program result checking [Goerigk+98], mechanical proof support [Dold+02], and finally, a trusted initial compiler executable for a realistic imperative high-level system programming language [Goerigk/Hoffmann98b, Goe- rigk/Hoffmann97]. The initial compiler and in particular its low-level machine implementation verification is the topic of the present paper. The compiler serves as a sound bootstrapping and implementa- tion basis and lifts proof obligations from machine code level to much more ab- stract source code. It is proved to at most generate correct implementations of well- formed source programs; it may fail, e.g. due to resource errors, and if the source program is not well formed, then the tar- get code may cause unacceptable errors and compute unpredictable results. But if the source program is well-formed, and if the compiler succeeds, then the target code is a correct implementation of the source program. The compiler is imple- mented and runs on a concrete physical processor. In the companion article [Goe- rigk/Langmaack01c] on Realistic Correct Systems Implementation we gave an ex- tended motivation also for the impact of the question raised in the title of the two articles. Furthermore we modularized the entire compiler verification effort in three steps and developed a mathematical the- ory which realistically and rigorously al- lows to express compiling specification correctness and high-level and low-level compiler implementation correctness com- positionally by commutative diagrams. The present article focuses on the realistic method for low-level compiler implementation verification by bootstrap- ping and syntactical a-posteriori code in- spection, which we have developed in order to provide, for the first time, a rig- orously verified and fully trusted initial compiler executable. We want to summarize the impor- tant definitions and observations from the previous article [Goerigk/Langmaack01c]. We will repeat the definition of correct implementation between source and tar- get programs, and the modularization of the entire proof effort in three steps. For details, however, we want to refer to [Go- erigk/Langmaack01c]. 1.1. Compiling Specification and Compiler Implementation. Compiling (specification) verification involves se- mantics. It is the first step necessary to modularize the entire correctness proof. The second step is correct compiler im- plementation, i.e. the correct transforma- tion of the compiling specification to ma- chine code of the compiler host proces- sor. Proving its correctness is called com- piler implementation verification. Correct compiler source programs are imple- mented by bootstrapping. In practice we can further modularize compiler imple- mentation in high-level compiler imple- mentation and low-level compiler ma- chine implementation. High-level compiler implementation corresponds to programming. Likewise, high-level compiler implementation veri- fication is program verification. Machine level compiler implementation correctness can be established by a trusted initial compiler, or by syntactical a-posteriori result checking (as in Section 4). Three tasks are necessary in order to present a convincing correctness proof Теоретические и методологические основы программирования 5 of a compiler executable on a compiler host machine. SL and TL are abstract source and target languages with conc- rete representation domains indicated by primes. HL is the compiler host language, and HML is the compiler host machine language. The compiler program τHL is written in HL and compiled into executa- ble code τHML written in HML. [[τHL]] HL and [[τHML]] HML denote the respective semantical relations. We will later simplify the situa- tion and identify source and host lan- guages as well as target and host machine language. Using this notation, we can modularize the problem in three tasks: 1. Specification of a compiling re- lation C between SL and TL, and compil- ing (specification) verification w.r.t. an appropriate semantics relation between language semantics [[٠]] SL, [[٠]] TL. 2. Implementation of a correspon- ding compiler program τHL in HL, and high level compiler implementation veri- fication (w.r.t. C and to program repre- sentations φSL SL‘ and φTL TL‘). 3. Implementation of a correspon- ding compiler executable τHML in HML, and low level compiler implementation verification (with respect to [[τHL]] HL and program representations φSL‘ SL‘‘ and φTL‘ TL‘‘ ). The last two tasks establish imple- mentation correctness relations between compiling specification, compiler source program and compiler executable. Every task will be formalized by a correspond- ing commutative diagram (Figure 1). Fig 1. Three tasks for correct compiler specification and implementation In Figure 1, A ⇀ B denotes the domain of relations and A → B of (partial) functions from A to B. The semantical mappings [[٠]] SL and [[٠]] TL are functions from source language SL resp. target language TL to their semantical domains SemSL and SemTL (i.e. the semantics of a well-formed program is uniquely deter- mined). Compiling specifications C typi- cally allow for more than only one target program, and also compiler program se- mantics might be non-deterministic. Data and program representations, which map abstract programs to concrete pro- gram representations and to their string representations, are in general relations, anyway. 1.2. Preservation of Relative Cor- rectness. In this article we focus on com- pilers and transformational programs and formalize operational program semantics by error strict relations f ⊆ Di × Do be- tween input and output domains, which are extended by individually associated disjoint non-empty error sets Ω = A ⊎ U of acceptable and unacceptable errors. One particular error ⊥ models infinite computations. If fs is a source and ft a target pro- gram semantics, and if ρi and ρo are data representation relations (strongly error strict in both directions), then we define ft to be a correct implementation of fs relative to ρi and ρo and associated error sets, iff Теоретические и методологические основы программирования 6 ( ρi ; ft ) (d) ⊆ ( fs ; ρo) (d) ∪ Ao holds for all d ∈ Di s with fs (d) ⊆ Do s ∪ Ao s. This exactly captures the intuitive re- quirements: d ∈ Di s is called admissible, if it does not cause an unacceptable error outcome, i.e. if fs (d) ∩ Uo s = ∅. Non- admissible inputs may cause unpredict- able or arbitrary results. But if d is admis- sible, then we can trust in results com- puted by target program semantics: Ei- ther the result is correct (representation of a corresponding source program out- put) or the target program execution aborts (observably) with an acceptable (resource) error. Fig 2. Correct Implementation The symbol ⊑ denotes this refine- ment relation, and '';'' denotes (diagram- matic) relational composition. ρi ; ft ⊒ fs ; ρo (or ft ⊒ fs for short) means that ft correctly implements or refines or is better than fs. Note that ⊑ depicts (basically) a relati- onal inclusion in opposite direction. De- tails can be found in [Goerigk/Langma- ack01c]. Our definition is a relational ver- sion of relative correctness preservation [Mueller-Olm/Wolf99, Wolf00] and can be understood as a deliberate generaliza- tion of partial respectively total correct- ness preservation. It is both horizontally and vertically transitive (compositional). 1.3. Precise View at the Three Steps. We have to refer to [Goerigk/Lan- gmaack01c] for details, but we want to repeat the diagram which precisely de- fines the proof obligations necessary in order to work through a complete com- piler correctness proof. We will focus on the lowest diagram of Figure 3 in this ar- ticle, and it relates the compiler machine executable semantically to the high-level compiler implementation. They work se- mantically on different input and output data domains of HL and HML which are related by program representation rela- tions [Goerigk/Langmaack01c]. The dif- ference is indicated by primes and dou- ble-primes in Figure 3. We now know precisely every conjecture we have to prove in order to implement an SL to TL- compiler correctly as an executable pro- gram on a host processor HM. Every data set, program set and semantics space, every program seman- tics, data representation, program repre- sentation, semantics function, compiling specification, compiler semantics and se- mantics relation has to be appropriately extended by unacceptable and acceptable error elements. The commutative dia- grams of Figure 2 and 3 precisely express Fig 3. This diagram is again, but now precisely, illustrating the three steps for correct compiler implementation as of Figure 1 Теоретические и методологические основы программирования 7 that C is a correct compiling specifica- tion, and that τHL respectively τHML are correct compiler programs. Programs and their representations have equal semantics. But we should ex- plicitly note that in diagram 3 the com- piler program τHML is not a representation of τHL. These two programs have in gen- eral different semantics, but the former is a correct implementation of the latter. 1.4. T-Diagram Notation. McKee- man's so-called T-diagrams allow to illus- trate the effects of iterated compiler ap- plications in an intuitive way. We use them as shorthand notations for particu- lar diagrams as of Figure 4. Fig 4. The situation which we will abbreviate by McKeeman's T-diagrams Recall that ℂ is the natural correct compiling specification from SL to TL. Well-formed programs and their (syntac- tical) φ-representations have equal se- mantics, and τHL‘ ∈ φHL HL‘(τHL) is a well- formed HL'-program compiling syntactical SL' programs to syntactical TL'-programs. HL' is the domain of perhaps more con- crete syntactical representations of HL- programs. In this situation we use the T- diagram (Figure 5) Fig 5. McKeeman's T-diagram as a short- hand for the above situation as an abbreviation for the diagram in Figure 4. However, we have to keep in mind that the concrete situation is a bit more involved, that the T-diagrams do not explicitly express crucial differences between various program representations. We need to distinguish programs, pro- gram semantics and (syntactical) program representations in order to suffice re- quirements from practice. We cannot put practitioners short by elegant but too nebulous idealizations. 2. Related Work on Compiler Implementation Verification An extended discussion of related work in the field of compiler verification has been presented in our previous arti- cle [Goerigk/Langmaack01c]. Recall that we find intensive work on steps 1 and 2, i.e. on compiling specification and on high level compiler implementation veri- fication, although often under unrealistic assumptions so that the results have to be handled with care. Step 3, however, has nearly totally been neglected so far. If the phrase ''compiler verification'' is used, then most of the authors mean step 1. There is virtually no work on full com- piler verification. Therefore, the ProCoS project (1989—1995) has made a clear distinction between compiling verification (step 1) and compiler implementation veri- fication (steps 2 and 3) [Langmaack97b]. Compiling verification is part of theoretical informatics and program se- mantics, and high level compiler imple- mentation verification (step 2) is a field within software engineering. However, compiler implementation correctness then further depends on existing correct initial host compilers, which are not available up to now. Hackers might have intruded Trojan horses [Thompson84, Goerigk99b] via unverified start up compilers (cf. the introduction to the first article [Go- erigk/Langmaack01c] and the following Section 4). This in view, we are finally left on human checkability of mechanical proof protocols (a-posteriori-proof check- ing) and initial compiler machine code. Literature on low and machine level compiler implementation verifica- tion (step 3) is by far too sparse. There are only demands by some researchers like Ken Thompson [Thompson84], L.M. Chirica and D.F. Martin [Chirica/ Теоретические и методологические основы программирования 8 Martin86] and J S. Moore [Moore88, Moore96], no convincing realistic meth- ods. Here is the most serious logical gap in full compiler verification. Unfortu- nately, a majority of software users and engineers — as opposed to mathemati- cians and hardware engineers — do not feel logical gaps to be relevant unless they have been confronted with a counter-example. So we need A. convincing realistic methods for low level implementation verification (step 3) B. striking counter-examples (fail- ures) in case only step 3 has been left out. Let us first step into B and hence go on with an initial discurs on the po- tential risk of omitting the low level ma- chine code verification step for compilers. 3. The Risk of Neglecting Machine Level Verification Ken Thompson, inventor of the op- erating system Unix, stated in his Turing Award Lecture ''Reflections on Trusting Trust'' [Thompson84]: ''You can't trust code that you did not totally create yourself. (Especially code from companies that employ people like me.) No amount of source-level verification or scrutiny will protect you from using untrusted code.'' He underpinned his statement by sketching the construction of an execu- table binary machine code version of a C- compiler which was wrong, although his version successfully passed N. Wirth's strong compiler or bootstrap test [Wirth77], which is well-known to be ex- tremely hard to deceive, and although we may even assume that the corresponding C-version of his compiler has been verified. Let us assume there exists a binary version τ0 of a C-compiler running on a machine M0, and a (different) C-compiler τ1 written in C generating code for a ma- chine M1. A two step bootstrapping proc- ess of τ1 on M0 generates a version τ3 (of τ1), which is now formulated in binary M1-machine code. If we assume τ0 and τ1 correct, and the machine M0 to work cor- rectly, then τ3 is correct as well [Lang- maack97a, Goerigk96b, Hoffmann98b]. By a third bootstrapping step of τ1, we may compile τ1 to a new M1-binary τ4 using the compiler τ3 on machine M1. If we assume the machine M1 to work cor- rectly as well, then τ4 and τ3 are identical [Goerigk99a], at least if we assume every involved program deterministic. The third bootstrapping step (and checking identity of τ3 and τ4) is N. Wirth's so called strong compiler or bootstrap test. It is employed for safety reasons, if at least one of the four above correctness assumptions can- not be guaranteed. Fig 6. Wirth's strong compiler test. Note: Even if M0 and M1 are the same machine, τ0 and τ2 need not necessarily generate identi- cal code. τ0 and τ2 are two different compil- ers. In general, we know nothing about τ0's target code. But let us come back to Ken Thompson's scenario: He manipulated τ0, constructed a malicious τ̄ 0 that finally produced a wrong τ̄ 3, although he fol- lowed the entire above bootstrapping process and τ̄3 passed the strong com- piler test. Although the compiler source program τ1 remains correct (unchanged), and even if the machines M0 and M1 work correctly, τ̄3 incorrectly translates at least one C-source program, in his case the Unix login command. He has introduced a Trojan Horse in τ0, which is a very hid- den error hard to detect by tests. The manipulation of τ0 can even be steered so that τ̄3 generates incorrect target code for exactly two C-source programs [Goe- Теоретические и методологические основы программирования 9 rigk99b] — one of which must be the compiler source program τ1 itself, be- cause otherwise, due to the strong com- piler test, τ̄3 would be correct. In any case, the crucial insight is that all this might happen even if τ1 is verified on source level [Goerigk99b, Go- erigk00b]. Moreover, if the user would try to convince her/himself of correctness by a test suite, as this is common practice today, she/he would have to find at least one of the two incorrectly compiled pro- grams among those billions of (and theo- retically infinitely many) test candidates. We easily imagine that program validation by test is heavily overcharged in case of compilers if their generation employs unverified auxiliary software, like τ0 in our case. We should well real- ize the security impact of all this. Virtu- ally every realistic software generation basically depends on running non-veri- fied auxiliary software. Since computers nowadays are usually connected to the world wide net, the software is more or less open to hacker attacks, and might already have been manipulated from out- side. Fighting security attacks causes much harder problems than avoiding un- intentional bugs (safety). Safety, in a sense, relies on statistically distributed bugs by constructors' mistakes or materi- als' faults, whereas for security we have to be aware of subversive intent. Note that one possible procedure in order to uncover the malicious Trojan Horse is to perform the compiler boot- strap and hence use the compiler itself as a test case. But note: this test case would have to be performed very carefully, which means that we have to run the generated compiler τ̄ 3, apply it to τ1 and to compare the result τ4 with the ex- pected verified result that is given as part of the test suite. We doubt, that any ex- isting compiler binary (like τ4) has ever been verified in this sense yet. Actually, this is one of the essential tasks of the present paper. Unless we verify τ3 to be a correct implementation of (the verified) τ1, any of the compiler executables τ0, τ2, τ3, τ4, any further bootstrapped compiler implemen- tation, and any source program compiled by one of these programs might eventu- ally cause disastrous, even catastrophic effects. We think that this well serves as a striking counter example. 4. Realistic Method for Low Level Compiler Generation The question we are going to an- swer now is how we attack task A of step 3. A first idea might be to apply software engineering philosophy as for high level implementation verification, i.e. to step- wise refine the high level implementation down to executable binary machine code HML using verified transformation rules. Then, as in the 60´s, we would have de- veloped the entire machine code written compiler by hand, although nowadays supported by using the computer as an efficient typewriter and maybe for check- ing correct application of the implemen- tation rules. However, as we have seen before (section 4), we ought to have doubts trusting implementations of auto- matic checking routines as long as there are no trusted, i.e. correctly implemented initial compiler executables available. Even if trained in mathematical rigor, no software scientist would ever manually construct or, more importantly, certify, a real world compiler executable in ma- chine code. Therefore, we follow a different approach, the Verifix-idea, to generate even initial compiler implementations by an approved method, thereby incorporat- ing the necessary verifications. We pur- sue N. Wirth's idea to bootstrap the compiler and add a sufficient variant of the strong bootstrap test [Goerigk/Hoff- mann98]. We identify source and high level implementation language SL = HL and take a proper sublanguage ComLisp [Go- erigk/Hoffmann96] of ANSI-Common- Lisp = SL+. We further identify target and low level implementation language TL = HML and take the binary machine code of a concrete processor M, e.g. DEC α or INMOS-Transputer-T400. An existing ANSI-Common-Lisp system with Теоретические и методологические основы программирования 10 compiler τ0 is running on a workstation M0 with machine code TL0 = HML0, so that our initial two bootstrapping steps to- gether are a cross-compilation to TL-code using the workstation as host machine. Fig 7. Steps 1 and 2 for an initial full correct compiler implementation According to step 1 and 2 (section 1.1) we develop a correct SL to TL- compiling specification C and correctly implement it as a compiler program τSL, now in SL = HL itself. High level syntactical programs in SL' and TL' are SL-data, i.e. s-expression sequences. Abstract syntactical programs in SL and TL are associated to SL', TL' by bijections φSL SL‘ and φTL TL‘. We formulate C such that it will relate target programs πTL to well-formed source programs πSL at most, i.e. for which source semantics [[πSL]] SL are defined. We construct τSL such that, if applied to well-formed source programs in SL', τSL, or more pre- cisely [[τSL]] SL, executed on an imagined SL-machine, will either terminate suc- cessfully or abort with an acceptable er- ror due to target machine resource ex- haustion. τSL has a pre-condition that re- stricts its inputs to representations of well-formed source programs. That is to say, τSL might not check for well formed- ness; but then, [[τSL]] SL has to have an un- acceptable error outcome, perhaps speci- fied non-deterministically, if applied to SL-data that do not represent well-formed SL-programs (see the discussion on ℂ in [Goerigk/Langmaack01c]). We will also need a version τ''SL which works on machine representations of s-expressions, for instance on character sequences (strings, cf. Figure 8), which form a particular subset of all s-ex- pression sequences. Then, φSL‘‘ SL‘‘ = φTL‘‘ TL‘‘ are the known string representations for s-expressions, and their inverses are sin- gle-valued functions (a string denotes at most one s-expression sequence). Note: τ''SL is to be a correct implementation of τSL. The operator read-sequence (used in τSL in order to read s-expression se- quences) is programmed in τ''SL as a function procedure which reads character sequences (using operators read-char and perhaps peek-char) and returns a list of s- expressions. Analogously the output is programmed using character output, i.e. the operator write-char. τ''SL‘‘ denotes a string version representing τ''SL, i.e. (τ''SL, τ''SL‘‘) ∈ φSL SL‘‘. Fig 8. A correct compiler τ''SL mapping character sequences in SL'' to character sequences in TL'' Fig 9. Bootstrapping the initial compiler. A sufficient variant of Wirth's strong compiler test (a-posteriori-syntactical code inspection) guarantees fully correct implementation Let us now (unrealistically) assume for a moment, that τ0, M0's (the work- Теоретические и методологические основы программирования 11 station's) Lisp-compiler, has been cor- rectly developed and correctly imple- mented in TL0. Then a threefold boot- strapping (Figure 9) of τSL (often of a byte-sequence or string representation) eventually generates τ̄ TL, provided that the three compiler executions of τ0, τ2, and τTL successfully terminate with regu- lar outputs τ2, τTL, and τ̄ TL, respectively. Since τSL and τ0 are assumed correct, and whenever the data abstractions ρi SL TL -1 , ρo SL TL -1 , ρi SL + TL0 -1 , and ρo SL + TL0 -1 are single- valued functions, we have the following bootstrapping theorem [Langmaack97a, Goerigk99a, Goerigk00a]: Theorem 4.1. (Bootstrapping theo- rem) The compilers τ2, τTL, and τ̄ TL are correct, even correctly implemented on hardware processor M0 resp. M. They are all correct refinements of C and τSL. In our case ρi SL TL -1 , ρi SL + TL0 -1 , ρo SL TL -1 and ρo SL + TL0 -1 are single-valued functions; actually, they are just the same as φSL‘‘ SL‘‘ -1 , φSL‘‘+ SL‘‘+ -1 , φTL‘ TL‘‘ -1 , and φTL’ 0 TL ‘‘0 -1 , respectively. We prove Theorem 4.1 by successive ap- plication of a bootstrapping lemma: Let τ0 and τ1 1 be two correct compilers and let us apply τ0 to τ1: Fig 10. Bootstrapping an initial compiler im- plementation τ2 What can we expect in case of a successful compilation with a regular re- sult τ2 respectively a representation τ2‘? The T-diagrams represent two commuta- tive diagrams (Figure 11 for τ0, and Fig- ure 12 for τ1). 1 We use τ1 here instead of τSL, because the follow- ing argument works for any correct compiler source program. Moreover, τ0 is correct, and the result of applying τ0 to τ1‘ is the regular HL-datum τ2‘ ∈ Do HLΩ = TL´Ω. Thus, [[τ1‘]] SL‘ ⊑[[τ2‘]] TL‘,which means ρi ; [[τ2‘]] TL‘ ⊒ [[τ1‘]] SL‘ ; ρo. Fig 11. Extended view at Figure 10 (part 1 for τ0) Fig 12. Extended view at Figure 10 (part 2 for τ1) Hence, also the diagram in Figure 13 is commutative and, thus, vertical composi- tion of the diagrams in Figure 12 and in Figure 13 finally yields the following bootstrapping lemma: Lemma 4.2. (Bootstrapping lemma) If (ρi -1 ; [[٠]] SL´) and (ρo -1 ; [[٠]] TL´ ) are rea- sonable semantics functions (e.g. if ρi -1, ρo -1 are partial functions like in our case us- ing Lisp s-expression sequences), then Di TL and Do TL may be conceived to be con- cretizations SL´´ and TL´´ of the pro- gramming languages SL, SL´ and TL, TL´, and in case of regular termination of τ0´ applied to τ1´, [[τ2´]] TL‘ = [[τ2]] TL is a cor- rect implementation of [[τ1‘]] SL‘ = [[τ1]] SL and by vertical composition also of C (Figure Теоретические и методологические основы программирования 12 12, perhaps different from ℂ). Thus, τ2‘ and τ2 are correct compiler programs with the T-diagram shown in Figure 14. Fig 14. If τ0 and τ1 are correct compilers, then so is τ2 Let us start a minor detour and come back to the discussion in the first article [Goerigk/Langmaack01c]: The proof of the bootstrapping lemma shows, that the (compiler generating) compiler τ0‘ needs not be a regularly terminating program for all well-formed input pro- grams in SL' in order to be useful. τ0‘, ap- plied to a well-formed program πSL‘ in SL', even to a well-formed compiler like π1‘, might run into an acceptable error in ATL‘ = Ao HL, perhaps due to a resource violation like a memory overflow. If τ0‘ terminates regularly, however, then the associated regular result τ2‘ in TL' is again a well-formed and correct SL to TL-compiler. In other words: Since we cannot expect τ0‘ to yield regular results for all (arbitrarily large) source program representations, τ0‘ will in general be cor- rect in the sense of preservation of rela- tive (partial) program correctness, but not in the sense of preservation of regular (to- tal) correctness [Goerigk/Langmaack01c]. Thus, the expected error behaviors of SL -programs and in particular of τ1 and hence also of τ2, on the one hand, might be of quite a different nature than, on the other hand, of SL and hence in particular of τ0. SL might even be a process programming language such that we expect SL-programs and their imple- mentations in TL to be regularly (totally) correct. If τ1 preserves regular (total) pro- gram correctness, then so will τ2, i.e. the result of applying τ0 to τ1 [Goerigk/Lang- maack01c]. That is to say: A partial correctness preserving compiler τ0 may well generate a total correctness preserving compiler executable τ2 from a corresponding total correctness preserving compiler source program τ1. We hence have just given the proof, that there is no need for trusted compilers to be correct in the sense of preservation of total correctness. We do not depend on a guarantee of well-defi- nedness while bootstrapping compilers. This ends our detour and we come back in particular to the strong compiler (bootstrapping) test. Since τSL, restricted to well-formed SL-programs, is determi- nistic, we also have the following strong compiler test theorem (variants can also be found in [Langmaack97a, Goerigk99a, Goerigk00a]). Whereas the proof of the bootstrapping theorem (4.1) is a simple application of the bootstrapping lemma, the proof of the strong compiler test theorem requires a more explicit exploi- tation of the bootstrapping lemma. Theorem 4.3. (Strong compiler test theorem) The compilers τTL and τ̄ TL are equal. Proof. Let τSL and τ0 play the roles of τ1 and τ0 in the lemma. We take a rep- resentation τSL´ of (the well-formed ab- stract s-expression) τSL. τSL´ is also a rep- Fig 13. Commutative diagram corresponding to [[τ1‘]] SL‘ ⊑ [[τ2‘]] TL‘ Теоретические и методологические основы программирования 13 resentation of the abstract SL+-program τSL. SL and SL+ have the same input and output data domains (of concrete s- expression sequences). τSL and τSL´ have the same semantics [[τSL]] SL = [[τSL]] SL+ = = [[τSL´]] SL´ = [[τSL´]] SL+´. Let τ2´ be the concrete result of successfully applying τ0 to τSL´ on host machine M0. Due to the proof of the bootstrapping lemma we have the follow- ing commutative diagram: Recall that we introduced lan- guages SL'' and TL'' as reasonable repre- sentations of SL, SL' and of TL, TL'. SL and SL' resp. TL and TL' are isomorphic, and the inverses of ρi SL + TL0 and ρo SL + TL0 are single valued functions. Let τTL´´ be the concrete successful result of compiling τSL´ by τ2´ again on the host machine M0. Again, due to the proof of the bootstrapping lemma we have Let τ̄ TL´´ be the concrete successful result of compiling τSL´ by τTL´´, now on machine M. Since τSL is assumed deter- ministic (which actually can be guaran- teed by construction), τTL´´ and τ̄ TL´´ are representations of one and the same con- crete s-expression sequence τTL´ = τ̄ TL´ in Do SL+ = Do SL = TL' and of τTL = τ̄ TL in the ab- stract language TL. We can not prove equality of τTL´´ and τ̄ TL´´. Equality does in general not hold, because the code of τTL´´ and of τ̄ TL´´ has been generated by two perhaps dif- ferent code generation mechanisms of τ2ґ on M0 resp. of τTL´´ on M, i.e. by two dif- ferent compilers on two different ma- chines. The code τTL´´ is influenced by runtime-system code (in particular by print-routines) which τ0, e.g. the existing SL+ = ANSI-Common Lisp compiler run- ning on M0, attaches as part of τ2´´ (which generates τTL´´), whereas the code of τ̄ TL´´ is influenced by runtime-system code which our compiler executable (τ 2´) attaches as part of τTL´´ (which generates τ̄ TL´´). Let us for instance assume τTL´´ and τ̄ TL´´ to be character sequence (string) representations of τTL resp. τ̄ TL. Since there are different correct string repre- sentations of one and the same s- expression, the M0-print routine of τ2´ might print τTL´´ differently to how τTL´´ prints τ̄ TL´´. But nevertheless, both are correct printed representations of the equal s-expressions τTL´ resp. τ̄ TL´. However, we could add a further bootstrapping step comparing the string results of τTL and τ̄ TL. Since we would then use the same print routines, and be- cause our programs are deterministic, we would finally get equal string representa- Теоретические и методологические основы программирования 14 tions τ̄ TL´´ = ¯̄τ TL´´, if the bootstrap succeeds. In order to see this, we exploit that τTL, τTL´´, τ̄ TL, τ̄ TL´´ are semantically equal and that their string representations represent one and the same s-expression. Here is another important remark: The loading mechanism on machine M must not de- pend on different binary (character se- quence) representations τTL´´ resp. τ̄ TL´´ of τTL = τ̄ TL. The loader has to load the same abstract M-machine program in both cases. In any case, we could also start the entire bootstrapping process by applying τ0 on M0 to τ''SL´´. Recall that the latter is a version of our compiler source program τSL which does not use the standard op- erators read and print of the runtime- system of the implementing compiler to read and print s-expressions, but com- prises its own reading and printing rou- tines (implemented by character in- put/output-routines read-char, peek-char, and write-char). Hence, it transforms character sequences. In that case, both programs are guaranteed to deterministi- cally compute the same sequence of characters, even on different machines. Like in the proof of Theorem 4.3 we could then show equality of the character sequences τ''TL´´ = τ̄ ''TL´´, because their rep- resentations by φTL‘ TL‘‘ are single-valued. But note that this is again only true if we compare the printed character sequences. On binary level, they might for instance be represented in different character codings on M0 and M, say in 8bit-ASCII or in 16bit-Unicode. Let us end this section with the fol- lowing remark: We are well aware that the previous observations are tedious, cumbersome and by far not obvious. But on the other hand, we have too often seen compilers generating wrong code just because they have been bootstrapped or cross-compiled in a quick-and-dirty process, forgetting about potential code representation problems like for instance byte order, alignment or even different character codes used on different ma- chines. Fortunately, our rigorous mathe- matical treatment of code generation and compiler bootstrapping enables to un- cover any of these tedious problems and to talk about them precisely. 5. Source Level Verification is not Sufficient Note, that all this has been proved under the unrealistic assumption that τ0 is correct. But there is no guarantee in the situation we are aiming at: We want to construct and generate an initial (first) correctly implemented compiler executa- ble. Nevertheless, we can use τ0 and τ2 as intelligent (and efficient) tools, and they will often succeed to produce the correct result. We just have to assure this fact. This is the key idea of our approach to low level compiler implementation verifi- cation. However, the successful bootstrap test (τTL = τ̄ TL) does not help. It is well- known that it might succeed for incorrect compiler source programs τSL. Just con- sider a source language construct, which is incorrectly compiled but not used in the compiler itself. Our situation is more delicate: τSL is a verified compiler source program. Unfortunately, we can prove [Goerigk99a, Goerigk99b, Goerigk00b] even in this case: Although τ̄ TL is successfully gener- ated from the verified τSL by threefold bootstrapping and passes the strong compiler test, τTL is not necessarily cor- rect. Ken Thompson's Trojan Horse, originally hidden in τ0, might have sur- vived so that we find it both in τTL and in τ̄ TL (and also in τ2). In [Goerigk99b, Goerigk00b], we prove this fact mechanically using M. Ka- ufmann's and J Moore's logic and theo- rem prover ACL2 [Kaufmann/Moore94]. Ken Thompson's Trojan Horse can be ex- pressed in high level language, even in the clean and abstract Boyer/Moore-logic of first order total recursive functions. We need no ugly machine code to construct such a malicious program part. The situa- tion is even more delicate if we consider preceeding or subsequent compilation phases: If only one phase is corrupted, the only chance to uncover that error is Теоретические и методологические основы программирования 15 to rigorously check the target code of ex- actly that phase, while the compiler ex- ecutable τTL (or τ2) compiles τSL. No other test will help, unless the user by accident runs τTL on exactly that one additional incorrectly compiled source program that eventually causes catastrophic effects (and waits for the catastrophe to happen). 6. Realistic Method for Low Level Compiler Verification Let us now drop our assumption that τ0 is correct. Also note that, in gen- eral, programs are non-deterministic. By twofold bootstrapping of τ''SL resp. τ''SL´´ on machine M0 we generate an output string s which is supposed to be a string representation of τ''TL. This is according to the first two steps of Figure 9. Since τ0 and hence τ2 might be incorrect, we have to make sure with mathematical rigor, that s is indeed a representation of τ''TL. Our method of low level compiler implementation verification is as follows: Let τ''TL be a program written in TL such that τ''SL C τ''TL holds, i.e. let τ''SL, τ''TL ful- fill the compiling specification which was verified in step 1 (Figure 7). Hence, τ''SL's and τ''TL's semantics are related by ⊑. Fig 15. Correct implementation of correct compiler source programs. The extended se- mantics, defined on Di SL Ω resp. Di TL Ω, carries an unacceptable error outcome in Ui SL resp. Ui TL indicating the cases where inputs do not represent well-formed SL- or TL-programs φSL‘‘ SL‘‘ , φ TL‘‘ TL‘‘ are the natural 1-1- mappings on character sequences. How ρi SL TL, ρo SL TL are precisely defined depends on which primitive standard input-output- routines are actually used in SL and TL. Since ρi SL TL -1 = ρo SL TL -1 is single-va- lued, representations of well-formed pro- grams have unique semantics in Sem SL resp. Sem TL. Due to vertical composability (cf. [Goerigk/Langmaack01c]), τ''TL is a correct SL'' to TL''- compiler correctly implemented in TL. Figure 15 accom- plishes Figure 7 and 8 and yields Figure 1 in our special situation. Any of our data including pro- grams are representable by s-expressions. Thus, we may assume that any of our source and target languages L have s-expression-syntaxes, i.e. syntactical pro- grams are s-expression sequences. Input and output data domains Di L, Do L are sets of s-expression sequences as well. Note that characters are particular s-expressi- ons, and hence character sequences (strings) are particular s-expression sequ- ences. Not every syntactical program (s- expression) is well-formed. In fact, the set of well-formed programs is exactly the domain of definition of the semantics function [[٠]] L ∈ Sem L. Compiling specifications C are of- ten defined by syntactical rules (e.g. by term rewriting), whereas correctness of τ''TL is a semantical matter. That is to say: We have a reduction of the correctness problem from semantics to syntax. The previous paragraphs sketch the proof of the following theorem: Theorem 4.4. (Semantics to Syntax Reduction) If s is a string, if τ''TL = = (φTL TL‘‘ ; φ TL‘‘ TL‘‘) -1(s) and if syntactical che- cking of τ''SL C τ''TL is successful, then τ''TL is a well-formed SL'' to TL'' compiler cor- rectly implemented in TL. It is not in all cases necessary to completely verify an algorithm before- hand in order to trust a computed result. In the proposed process for correct com- piler construction, we verify τSL resp. τ''SL a-priori (steps 1 and 2), but verification of τTL resp. τ''TL (step 3) is an a-posteriori syntactical result checking. It allows for using unverified supporting software, e.g. compilers τ0 and τ2 on machine M0. They are used as intelligent but not necessarily correct type writers. Checking guarantees to find any error in τ''TL, even intended errors like viruses or Trojan horses as of section 4. Теоретические и методологические основы программирования 16 The idea of a-posteriori result checking is old. We can find applications e.g. in high school mathematics, like checking division or linear equation solv- ing by (matrix-vector) multiplication. The idea has found its way to algorithms the- ory [Blum+89], trusted compilation [Lang- maack97b, Langmaack97c, Goerigk/Hoff- mann98, Pnueli+98, Heberle+98, Gaul- +99, Cimatti+97], and systems verifica- tion [Goerigk+98, Bartsch/Goerigk00d] in general. 6.1. Realistic Syntactical a-poste- riori Code Inspection. However, since we know that realistic compiling specifica- tions and compilers are of tangible size, we might ask if syntactical a-posteriori code checking is realistically manage- able. A first idea might be to look for machine support, i.e. to write checking algorithms and programs. But we should be aware that in this way we might well run into circulos vitiosos. We burden our- selves with new specification and (high and machine level) implementation cor- rectness problems for checking algo- rithms and programs. If we want to im- plement an initial compiler fully correctly on a machine, there is no way around some hand checking. Remark: We do not condemn the use of programmed computers for proving and proof checking. If a software engineer believes in auxiliary software to be suffi- ciently trustful, she or he is allowed to use it in order to gain more reliable software production. However, the software engi- neer should then make clear which parts of the auxiliary software he/she has used and hence relies on although still not be- ing rigorously verified modulo hardware correctness. We have shown techniques how to maliciously harm auxiliary soft- ware. It is most important for the IT- community to demonstrate sound and re- alistic means how to stop such ever last- ing circulos vitiosos. Since source code verification may succeed, and since man- ual machine code verification hardly ever will do, we strongly believe that providing trustworthy compiler executables is the most promising sound basis. Verifix has introduced [Goerigk/ Hoffmann98, Hoffmann98b] three inter- mediate languages between SL (Com- Lisp) and TL (INMOS Transputer- or DEC α-code). Because it is necessary to finally produce a convincing complete rigorous proof document, these lan- guages have particularly been chosen in order to isolate crucial compilation steps and to enable code inspection by target to source code comparison. Essential characteristics and advantages for code inspection are: • Languages Li are close to their preceeding languages Li-1 so that only few crucial translation steps are necessary per pass. • Translation uses standard tech- niques, does only moderately expand and is local in the sense that it does not reor- der corresponding thick code pieces. • We avoid optimization; every transition remains well recognizable and locally checkable w.r.t. CLi Li+1 by juxta- posing corresponding code. • Every language has a procedure or subroutine concept; source and target programs are modularized by correspond- ing subroutines. These characteristics will be re- flected by our checking (i.e. compiling specification and code inspection) rules in section 6.2 below. Source, intermediate and target languages are: 1. High level source language is ComLisp = SL. Programs consist of non- nested mutually recursive function pro- cedures with call-by-value parameter passing. Variables are simple, and data are Lisp-s-expressions. Denotational, op- erational copy rule resp. stack semantics are well-known [Loeckx/Sieber87, Niel- son/Nielson92]. 2. Stack Intermediate Language SIL. Programs consist of non-nested mu- tually recursive parameterless procedures. Data remain s-expressions. Operators are postfixed (reverse Polish notation), paren- theses are dropped, and variables are represented by frame-pointer based stack locations (usually very small relative ad- dresses) intended to implement proce- Теоретические и методологические основы программирования 17 dure and operator parameters. Opera- tional stack semantics is straight forward and easily comparable to the operational SL-semantics. 3. C-like intermediate language Cint similar to Java's virtual machine lan- guage. All variables are of type integer, contents are either immediate or referen- ces into two linear stack resp. heap ar- rays. The stack is intended to implement SIL's stack, and the heap to refine non- atomic Lisp-s-expressions (SIL-data). Every SIL-program can be implemented in Cint with equivalent semantics. 4. Assembly language TA. Instruc- tions are machine dependent, e.g. Trans- puter or DEC α. Symbolic addresses are avoided; subroutines are called using unique numbers, variables have small relative addresses, branches stay within subroutine bodies and are instruction counter relative. 5. Machine code TL. Binary or hexadecimal notation of byte contents with more or less implicit prescription of how to load registers and memory of the target machine. The implicit prescription is materialized by a small boot program [Goerigk/Hoffmann97]. Only TA and TL are machine dependent. Semantics of a TL-program πTL is given by execution of the machine M, after the instruction counter has been loaded with the start address of the main part of πTL. Memory cells and registers not explicitly mentioned in the loading process are assumed to contain arbitrary data, i.e. πTL might behave non-deter- ministically, although each instruction works deterministically. πTL might in ge- neral even overwrite itself. However, the programs we generate will not. In case we prove preservation of partial correct- ness, they will instead stop with an error message like ''stack overflow'', ''heap overflow'', ''return stack overflow'' or ''arithmetic overflow'', or due to operator undefinedness. This is guaranteed by com- piling specification verification (step 1). 6.2. A Closer Look into a-posteriori Code Inspection. In the following we re- fer to our concrete compiler implementa- tion from SL = ComLisp to binary Tran- sputer machine code TL [Goerigk/Hoff- mann98, Goerigk/Hoffmann97, Goerigk/ Hoffmann98b]. The compiler proceeds in four separate phases. Each phase is cor- rectly implemented in ComLisp and gen- erates an external string representation of the intermediate and target programs. Checking the entire transformation of a Lisp-program directly into binary Transputer-machine code is unrealistic. We would have to check, that the hexa- decimal representation of the code for e.g. a function definition like (DEFUN f (x y) (+ (* x y) 3)) which compiles into the Transputer- machine code (33 z 4a 75 e0 73 75 e1 73 fa d3 75 52 d5 75 74 f9 a2 21 f0 73 58 71 f9 a2 21 f0 73 30 73 e4 73 31 73 e5 73 32 73 e6 73 33 73 e7 44 70 21 3e f6 43 73 e6 43 73 e7 44 70 21 3c f6 73 34 73 e0 73 35 73 e1 75 60 5e d5 75 31 d3 75 30 f6) is a correct one. Without any further structure of the target code we would not be able to do this conscientiously. The vertical decomposition into intermediate languages gives the necessary structure. We will show this using the concrete output of our compiler implementation for the above function. It has no higher control structures (no loops nor condi- tionals). This slightly simplifies the pres- entation here because we will not have to check relative jump distances for the as- sembly code. 6.2.1. Checking the Front End. The first two compilation steps are ma- chine independent. We start with our original ComLisp-function and compile it to SIL. This essentially is the transfor- Теоретические и методологические основы программирования 18 mation of expressions into postfix form. The body is compiled to (x y * 3 +), augmented by relative positions of vari- ables and intermediate results. The last statement copies the result to the result stack position 0. (DEFUN f (x y) (DEFUN F (+ (* x (_COPY 0 2) y (_COPY 1 3) ) (* 2) 3 (_COPYC 3 3) ) (+ 2) ) (_COPY 2 0)) Let us cite the compiling specifica- tion rules which are necessary to check that this particular source to target code transformation has been computed ac- cording to the specification. Note that the software engineer does not even have to understand the code semantically in order to check this step1. The purely syn- tactical (but semantically verified) com- piling specification defines checking rules. An average educated software en- gineer will not be overtaxed and can obey them in an informal but neverthe- less clear, succinct and rigorous mathe- matical proof style. 1. CLdef [[ (DEFUN p ( p 1 ... p k ) f 1 ... f m )]] γ ⊇def ⊇def (DEFUN p CLprogn [[ f 1 ... f m ]] ρ, γ, k (_COPY k 0)) where ρ( p i ) = i-1 for each i = 1, ..., k 2. CLprogn [[ f 1 ... f m ]] ρ, γ, k ⊇def CLform [[ f 1 ]] ρ, γ, k ... CLform [[ f m ]] ρ, γ, k where m ≥ 1 3. CLform[[(p f 1 ... f n )]] ρ, γ, k ⊇def CLform [[ f 1 ]] ρ, γ, k ... CLform [[ f n ]] ρ, γ, k+n-1 (p k) 4. CLform [[c ]] ρ, γ, k ⊇def (_COPYC c k) where c is a constant integer, charac- ter, string or symbol NIL or T 1 Nevertheless, we will sometimes give comments on the semantics in order to make this presenta- tion more intuitive and readable. 5. CLform [[v ]] ρ, γ, k ⊇def (_COPY ρ(v) k) where v is a local variable or formal parameter with ρ(v) a defined natural number We present the compiling specifi- cation rules in the style of a conditional term rewrite system (for details see [Goe- rigk/Hoffmann97, Hoffmann98b]). Ground terms are s-expressions or s-expression sequences from the syntactical domains of source and target language, i.e. of ComLisp and SIL: <program>, <decla- rations>, <form>, <fname>, <ident>, <operator>, <symbol>, <integer>, <cha- racter>, <string> resp. <program>SIL, <declarations>SIL, <form>SIL. Ground terms are augmented by rewrite vari- ables2 and unary rewrite operators like CLdef [[٠]], CLprogn [[٠]] or CLform [[٠]] with pa- rameters ρ, γ, k. Actually, ρ contains rela- tive addresses for local variables and pa- rameters, γ maps global variables to ''ab- solute'' addresses, and k is the relative result position corresponding to the structural depth of source expressions. We just presented those specification rules necessary for our example. The system of all conditional term rewrite rules together defines multivalued (non-deterministic) operations associated to each rewrite operator, and we under- stand the single rules above to specify that the left hand side ground term set contains the right hand side set of ground terms by definition (⊇def). This is an inclusion by definition, because there might be other rules which apply to the same left hand side pattern. The simple structure of these rules guarantees a simple checking process because of their compositionality, order preservation, at most linear expansion and because rewrite operator applications are not nested and procedure boundaries are preserved. 2 We use the prefix rewrite in order to distinguish rewrite variables from those ranging over program fragments and rewrite operators from program operators. Теоретические и методологические основы программирования 19 The next step is data refinement of dynamically typed Lisp-data to a linear memory architecture. Relative addresses are multiplied by 2 (tag and value field) and copied in pairs. We have to focus on single SIL statements and compare them with pairs of target statements: In order to copy the content of x from relative position 0 to 2, the target code has to copy the tag field from 0 to 4 and the value field from 1 to 5. Operator calls now become subrou- tine calls into the runtime system – com- piling specification verification proves that the runtime system procedures are correct operation refinements of the SIL-operators. (DEFUN F (DEFUN F (8) (_COPY 0 2) (_SETLOCAL (_LOCAL 0) 4) (_SETLOCAL (_LOCAL 1) 5) (_COPY 1 3) (_SETLOCAL (_LOCAL 2) 6) (_SETLOCAL (_LOCAL 3) 7) (* 2) (* 4) (_COPYC 3 3) (_SETLOCAL 3 6) (_SETLOCAL 3 7) (+ 2) (+ 4) (_COPY 2 0)) (_SETLOCAL (_LOCAL 4) 0) (_SETLOCAL (_LOCAL 5) 1)) Again, we cite the corresponding conditional term rewrite rules from the compiling specification from SIL to Cint (ξ is a compiletime environment mapping program constants such as symbols, strings, and lists to linear heap represen- tations and addresses): 1. CSdef [[(DEFUN p f 1 ... f m )]] ξ ⊇def ⊇def (DEFUN p (s) CSform [[ f 1 ]] ξ ... CSform [[ f m ]] ξ ) where s is the maximal stack frame length needed by f1, . . . ,fm 2. CSform [[(_COPY i j)]] ξ ⊇def ⊇def (_SETLOCAL (_LOCAL 2i) 2j) (_SETLOCAL (_LOCAL 2i+1) 2j+1) 3. CSform [[( p i )]] ξ ⊇def (p 2i) 4. CSform [[(_COPYC n i)]] ξ ⊇def ⊇def (_SETLOCAL τ 2i) (_SETLOCAL n 2i+1) where τ is the number tag 3 and n is an integer This completes checking the ma- chine independent front end. The next two steps are machine dependent. The final step generates the machine code above. 6.2.2. Checking the Back End. The first back end phase transforms con- trol structure into linear assembly code with relative jumps. The generated sub- routine body consists of procedure entry code, the main part and procedure exit code, three parts which are structured in three lists in the TA-code. Entry and exit code share the same pattern for every procedure. This phase also handles re- source restrictions of the concrete 32-bit machine. But this is a semantical issue not to be checked here. In order to check the main part, we have to compare single Cint-instructions with small groups of up to four or five assembly instructions. For instance, the instruction (SETLOCAL (LOCAL 0) 4) (the second line in the Cint-definition) is compiled to the instruction sequence LDL 3 LDNL 0 LDL 3 STNL 4 (first line of the TA-main part below). It first loads the frame pointer, then the content of relative position 0, which af- ter loading the frame pointer again is finally stored into relative position 4. (DEFUN F (8) (_DEFCODE F 51 (LDL 5 STNL 0 LDL 3 LDL 5 STNL 1 LDL 3 OPR 10 STL 3 LDL 5 LDNLP 2 STL 5 LDL 5 LDL 4 OPR 9 CJ 2 OPR 16 LDL 3 LDNLP 8 LDL 1 OPR 9 CJ 2 OPR 16) (_SETLOCAL (_LOCAL 0) 4) (LDL 3 LDNL 0 LDL 3 STNL 4 (_SETLOCAL (_LOCAL 1) 5) LDL 3 LDNL 1 LDL 3 STNL 5 (_SETLOCAL (_LOCAL 2) 6) LDL 3 LDNL 2 LDL 3 STNL 6 (_SETLOCAL (_LOCAL 3) 7) LDL 3 LDNL 3 LDL 3 STNL 7 Теоретические и методологические основы программирования 20 (* 4) LDC 4 LDL 0 LDNL 30 OPR 6 (_SETLOCAL 3 6) LDC 3 LDL 3 STNL 6 (_SETLOCAL 3 7) LDC 3 LDL 3 STNL 7 (+ 4) LDC 4 LDL 0 LDNL 28 OPR 6 (_SETLOCAL (_LOCAL 4) 0) LDL 3 LDNL 4 LDL 3 STNL 0 (_SETLOCAL (_LOCAL 5) 1) LDL 3 LDNL 5 LDL 3 STNL 1) (LDL 5 LDNLP -2 STL 5 LDL 5 LDNL 1 STL 3 LDL 5 LDNL 0 ) OPR 6)) The involved rules of the compil- ing specification1 from Cint to TA are the following: 1. CCdef [[(DEFUN f (σ)s 1 ... s n )]] ϕ ⊇def ⊇def (DEFCODE f ψ ( f ) (entrycode (σ)) (CCstmt [[s 1]] ϕ ,σ ... CCstmt [[ s n ]] ϕ ,σ) (exitcode) where ϕ = <ψ, | stack |, | heap |> and ψ is a subroutine numbering 2. CCstmt [[( f i)]] ϕ ,σ ⊇def ⊇def LDC i LDL start LDNL ψ ( f ) OPR 6 where 0 ≤ i < σ 3. CCstmt [[(_SETLOCAL e i )]] ϕ ,σ ⊇def ⊇def CCexpr [[e]] ϕ ,σ LDL base STNL i where 0 ≤ i < σ 4. CCstmt [[(_LOCAL i)]] ϕ ,σ ⊇def ⊇def LDL base LDNL i where 0 ≤ i < σ In the first rule, σ is the stack frame length of f, and we used σ (see also the above code) to stress that the procedure entry code is nearly constant, i.e. only parameterized by the number σ. |stack| and |heap| denote the initial stack and heap size. In the second rule, i is the relative address of the return value posi- tion, start contains the jump table start address, ψ(f) denotes the (constant) jump table position of f 's start address, and OPR 6 is the GCALL operation, i.e. the subroutine jump. In the third and fourth rule, base contains the ''absolute'' stack frame base address and i is the relative address of the variable to be assigned to respectively loaded from. Finally, it is easy to check that the TA-mnemonics and decimal operands have been transformed correctly to in- struction byte sequences (cf. Figure 16). Note, that in order to understand the code semantically, we additionally would have to know the semantics for instance of the Transputer-operations called by OPR instructions. We need not know this information to perform syntactical check- ing; the correctness of the code follows from compiling verification, whereas we have to check the code for compliance with the specification. The TL-module number #x33 = 51 below is a code module (indicated by the character z) that has a length of #x4a = 74 bytes. (_DEFCODE F 51 (33 z 4a (LDL 5 STNL 0 LDL 3 LDL 5 75 e0 73 75 STNL 1 LDL 3 OPR 10 STL 3 e1 73 fa d3 LDL 5 LDNLP 2 STL 5 LDL 5 75 52 d5 75 LDL 4 OPR 9 CJ 2 OPR 16 LDL 3 74 f9 a2 21 f0 73 LDNLP 8 LDL 1 OPR 9 CJ 2 58 71 f9 a2 1 The mechanical correctness proof of the corre- sponding compiling specification [Dold+02] us- ing the PVS theorem prover unvealed a tedious bug in the procedure initialization code. Although easy to repair, we prefer to leave the bug in the code and make this remark instead. The OPR 10 (WSUB, word subscript) in the third line of the code above is an address calculation which in- deed does not check for overflow. Finding this bug is due to the mechanized compiling verifica- tion by using incorruptible and inexhaustible theorem prover support. Since it is a relative ad- dress calculation, it would hardly show up in any practical test run. Теоретические и методологические основы программирования 21 OPR 16) 21 f0 (LDL 3 LDNL 0 LDL 3 STNL 4 73 30 73 e4 LDL 3 LDNL 1 LDL 3 STNL 5 73 31 73 e5 LDL 3 LDNL 2 LDL 3 STNL 6 73 32 73 e6 LDL 3 LDNL 3 LDL 3 STNL 7 73 33 73 e7 LDC 4 LDL 0 LDNL 30 OPR 6 44 70 21 3e f6 LDC 3 LDL 3 STNL 6 43 73 e6 LDC 3 LDL 3 STNL 7 43 73 e7 LDC 4 LDL 0 LDNL 28 OPR 6 44 70 21 3c f6 LDL 3 LDNL 4 LDL 3 STNL 0 73 34 73 e0 LDL 3 LDNL 5 LDL 3 STNL 1) 73 35 73 e1 (LDL 5 LDNLP -2 STL 5 LDL 5 75 60 5e d5 75 LDNL 1 STL 3 LDL 5 LDNL 0 31 d3 75 30 OPR 6))) f6) The involved compiling rules from TA to TL are: 1. CAdef [[(DEFCODE f i b )]] ⊇def ⊇def (i#x z |c|#x c) where c = CAbody [[b]] and i#x, |c|#x de- note hexadecimal representations of i,|c| 2. CAbody [[ op 1 e 1 ...( ...) ...( ... op n e n ) ]] ⊇def ⊇def CAopr [[ op 1 e 1 ]] ... CAopr [[ op n e n ]] 3. CAopr [[ op e ]] ⊇def prefix(assembleop(op, e) In the second rule, CAbody ignores the list (parentheses) structure, and in the third rule we apply two auxiliary func- tions: assembleop translates the 16 basic transputer instruction mnemonics to hexadecimal digits '0' up to 'f' according to the table in Figure 16, and prefix gen- erates the pfix/nfix-chains necessary to load the value of e, which is in particu- lar very easy for small non-negative numbers between 0 and 15 (representable by a four bit nibble). That is to say: In order to check the final code generation step, we only need to know the 16 instruction mne- monics, their mapping to instruction code nibbles, and the pfix/nfix-chains necessary to load large operands. So for instance LDC 4 is transformed to 44 which loads the constant 4 into Areg, whereas LDNL 28 is compiled into the pfix-chain 21 3c, which will execute LDNL on 16 × 1 + 12 = 28. 6.2.3. The Complete Proof Struc- ture. This ends the more detailed look into the characteristics of our technique Fig 16. Transputer-architecture and direct function codes. The Transputer state consists of the registers Areg, Breg, and Creg, which form a mini stack with top Areg, the operand reg- ister Oreg, the instruction pointer (program counter) Iptr, the workspace pointer Wptr, vari- ous flags like the ErrorFlag, some more registers and the memory Mem. The registers con- tain Word valued quantities. The memory is byte or word addressable Теоретические и методологические основы программирования 22 of a-posteriori code inspection by com- paring corresponding code parts of the respective source and target programs and checking them to be in conformance with the compiling specification rules. In order to come back to the over- all proof structure, note that program fragments like those of the previous sec- tion have been generated for the entire compiler using four unsafe initial com- piler implementations produced by τ0 on machine M0 for both front-end and both back-end phases. The following large diagram (Fig- ure 17) shows all four sub-compiling specifications CLi-1 Li, all four hand-written sub-compiler implementations τ''i,SL = τ''i,L1 and all 16 = 4 × 4 sub-compilers τ''i,Lj generated and printed out by bootstrap- ping. The specifications are verified by compiling verification (step 1), the τ''i,SL by high level implementation verification (step 2) and the τ''i,Lj for j > 1 by low level implementation verification (step 3), ac- tually by checking τ''i,Lj-1 CLj-1 Lj τ''i,Lj which is exactly what we sketched in the previous section and which we proved to be sufficient due to the semantics to syn- tax reduction Theorem 4.4. However, it is not necessary to per- form all those cumbersome checkings. In particular, unpleasant low level ma- chine) code inspections below the diago- nal are redundant: Since CTA TL and τ''4,SL are correct (steps 1 and 2), and since τ''4,TL is checked to be correctly compiled to TL, τ''4,TL is a fully correct TA to TL- compiler executable. We can use it to correctly compile τ''3,TA to a correct τ'''3,TL, which guarantees τ'''3,TL to be a fully cor- rect Cint to TA-compiler executable, and so forth. That is to say: We load the correct compiler τ''4,TL (actually τ''4,TL´´) into ma- chine M using the boot program. Cor- rectness of that program means and hence guarantees that it follows the ex- plicit and implicit loading prescriptions of τ''4,TL´´. Then we start the loaded com- piler in M and let M read τ''3,TA´´. If M terminates successfully (regularly), then due to Bootstrapping Theorem 4.1 the output is a correct compiler τ'''3,TL (actu- ally τ'''3,TL´´) as well, not necessarily iden- tical to τ''3,TL, but according to CTA TL. We can now concatenate τ'''3,TL ; τ''4,TL (actually τ'''3,TL´´ ; τ''4,TL´´) and obtain a correct compiler executable from Cint'' to TL'' due to composability of commutative diagrams (cf. [Goerigk/Langmaack01c]). If we proceed, this process will finally generate the desired correct compiler ex- ecutable from SL'' to TL''. Fig 17. Special bootstrapping with four compiler phases. ϕ'' L =def (ϕ L L´ ; ϕ L´ L´´) Теоретические и методологические основы программирования 23 τ'''3,TL´´ = τ'''1,TL´´ ; τ'''2,TL´´ ; τ'''3,TL´´ ; τ''4,TL´´ Here we exploit Verifix's hardware correctness assumption for verified low level compiler implementation (step 3). Note that we have implicitly introduced a (syntactical) concatenation operator '';'' for sequential programs, which corre- sponds to particular sequential composi- tion. Its semantics is obvious. Any of our languages allows for the syntactical con- catenation of programs. The compilers τ''i,SL contain a parser for s-expression sequences, namely the implementation of read-sequence. It is part of the runtime system, and so far, it has to be checked down to the diago- nal, in particular and unfortunately also as part of τ''4,TL resp. τ''3,TA in machine code. However, there is a remarkable chance to reduce the a-posteriori code inspection work load considerably: Since the print-routines are checked down to machine code TL as well, we may in principle check the (considerably larger) read-routines by (trusted) printing of their results, i.e. we may look at the parser read-sequence as an additional ini- tial compiler phase τ''0,SL. Then we only need its correct implementation as a high level SL-program and no further low level implementation verification. In that case, however, unchecked code runs initially, and we need further precaution1, namely to validate the intermediate machine state after running the code of read- sequence. We can exploit the processor's memory protection mechanism and add a few validations (runtime-checkings) to sufficiently guarantee the relevant part of the intermediate state not to be cor- rupted. In later work we shall report on this and give the necessary proofs. 7. Conclusions At the end of our exposй we would like to answer the question raised in the title of this essay: Will informatics be able to justify the construction of large computer based systems? We will trace 1 Unfortunately, it seems again not to be sufficient to compare the initially loaded and the generated code to be identical (cf. section 4). again the main lines of thought which finally lead us to a rather confident an- swer: Yes. In fact, internal misbehaviors and intended external violations of com- puter based systems need not last for- ever, i.e. safety and security might re- cover. However, this will not work out unless software production and informat- ics science carefully enough solve the following problem: At the end it is the executable binary real world processor code, and not only high level specifica- tions and programs, which has to be guaranteed to behave correctly as re- quired. Realistic software production em- ploys and relies on compilers for high level languages, like for instance C, C++, Ada, Java or Common Lisp. C is very close to machine level, but in our context it must be seen as a high level language with very critical and decisive compilation steps towards real processor code. Of course, many constructors of realistic commercial and industrial com- pilers are doing a quite good job. They care about correct compiling specification and correct compiler implementation by high level systems programs or by so- phisticated rule sets for term or graph rewrite systems. But there has been bluntly no industry oriented research nor development of techniques to implement high level written compilers such that their executable binary host machine ver- sions are guaranteed to generate (at most) correct target machine code (sec- tion 2). Again and again, the reason for trouble is the use of unverified auxiliary software like tools and in particular com- pilers, the use of so-called software of un- certain pedigree (SOUP, [HSE01]). There are many examples, for instance incorrect implementations of theorem provers or of cryptographic protocols. Actually, even if we (unrealistically would) assume that compiler constructors have been verifying their compiler programs perfectly on source level, compiler implementations have been and are still produced by a now over 40 years lasting unsafe boot- Теоретические и методологические основы программирования 24 strapping process using unverified com- piler implementations to generate unveri- fied compiler implementations. In contrast to mathematicians, and also to hardware engineers, software en- gineers are often not so much impressed by logical gaps, especially not by those evoked by unverified compilers which passed Wirth's strong compiler test. Software constructors like to transfer such logical gaps into Wittgenstein's do- main of logical scepticism, arguing that no way of reasoning will ever lead to- wards convincing solutions (section 2). But unverified compilers are well outside Wittgenstein's domain and they bear real risks (as shown by Ken Thomp- son [Thompson84] and later by ourselves [Goerigk99a, Goerigk99b, Goerigk00b]). Meanwhile, since computers use to be connected to world wide networks, we can unfortunately not give any guarantee for any of our programs used (section 4). Actually, rumors say that a computer does not survive un-hacked for more than about eight hours continuous connection to the internet. The risk increases, and computer science will be accounted for providing solutions. The Verifix-project offers industry oriented methods to solve the founda- tional problem of trusted program im- plementation, namely to produce realistic initial compilers for diligently chosen but nevertheless realistic programming lan- guages, compilers that run correctly and hence trustworthily on real host proces- sors and generate correct and hence trustworthy binary code for real target processors (cf. [Goerigk/Langmaack01c] and sections 4 to 6). Our particular tech- nique for low level compiler implementa- tion verification is new. It is a sophisti- cated diagonal method of so-called syn- tactical a posteriori-code inspection, a variant of rigorous a-posteriori-result checking (section 6). Once we have got an initial cor- rectly implemented compiler executable, we may safely (mechanically) bootstrap further correct compiler implementations for instance for more comfortable lan- guages (Bootstrapping lemma and theo- rem, section 4). Even if the bootstrapping compiler preserves partial correctness, it is perfectly able to generate correct com- piler executables which preserve total correctness. In fact, this is a very impor- tant point for software engineers and process programmers interested in trusted implementation of safety critical embedded real-time systems. It is by no means necessary, that the language SL we have chosen for trusted compiler bootstrapping is a per- fect systems programming language. Compilers for languages with more elaborated data types, nested and even higher order procedures and/or func- tions, object-orientation and inheritance etc. can safely be bootstrapped. SL and its initial correct compiler implementa- tion are chosen both to be useful tools and to conscientiously provide a high level proof documentation for correct low level binary code generation. Informati- cians can check their proof documenta- tion rigorously even without deep mathematical education. Moreover, if we assume hardware to work as described in the instruction manuals, then a lot of un- pleasant low level checking is even re- dundant due to our diagonal technique (section 6). The usual procedure, i.e. to incre- mentally step up in a hierarchy of ab- stractions by constructing and/or imple- menting higher level (programming or specification) languages safely (correctly) on the lower level, does not work for ini- tial correct compiler executables. The reason is, that machines, their physics, net lists and even their machine lan- guages are too low level in order to ade- quately express and to conscientiously reason about their program behaviors semantically. Our procedure to drive cor- rectness down towards the real physical machine is, and has to be, of a character- istically different nature, namely to ex- press and to verify realistic correct com- pilation semantically on the upper level first, and then to bridge the gap towards the real machine ''in a big step''. Fortu- nately, it turns out that our techniques still allow to exploit the modularization in Теоретические и методологические основы программирования 25 appropriate steps of concretization. In fact, our intermediate languages and the four compiler phases are carefully chosen exactly in order to make this possible. Theoretical basis of compiler cor- rectness is the notion of correct relative implementation which Verifix has devel- oped and which is much more flexible than classical correct implementation (cf. [Goerigk/Langmaack01c] and section 1.1). Software engineering theory stresses preservation of total program correctness, but E. Börger is right in his remark (Bop- pard, Germany, 1998): ''In the past, the role of regular termination has been ex- aggerated in software engineering.'' Pro- grams need not be proved never to fail in order to be useful. They might end in ac- ceptable errors, and we are ''sensible enough'' to give a guarantee that such errors will be signaled and hence detec- ted while programs are executed. On the other hand, if errors cannot be detected, for instance for undecidability reasons, they are unacceptable and thus the user has to avoid (circumvent) them by choos- ing appropriate inputs. We want to admit that this is kind of sophisticated. How- ever, realistic software engineering requi- rements are sophisticated trade-offs be- tween a lot of inherently different wishes including for instance efficiency as well. The important point is that our results allow, for the first time in this critical area, to mathematically rigorously formu- late and to formalize and prove such real- istic requirements to be guaranteed. It is this line of thoughts that closes a 40 years old gap of low level compiler implementation verification and that makes us confident that informatics eventually will enable to justify the con- struction of large computer based sys- tems. In particular, Verifix also demon- strates techniques to incrementally trans- fer other so far unverified software into verified software. Acknowledgments We would like to thank our colleagues in the Verifix project for many fruitful discussions, in particular Axel Dold, Thilo Gaul, Gerhard Goos, Andreas Heberle, Friedrich von Henke, Ulrich Hoffmann, Vincent Via- lard, Wolf Zimmermann. Special thanks to Markus Müller-Olm and Andreas Wolf for their contributions to the notion of relative program correctness and its pre- servation. [Bartsch/Goerigk00d] R. Bartsch and W. Goerigk. Mechanical a-posteriori Verification of Results: A Case Study for a Safety Critical AI System. In AAAI Workshop on Model Based Validation of Intelligence MBVI'2001, Stanford, CA, U.S.A., 2001. [Blum+89] M. Blum, M. Luby, and R. Rubinfeld. Program result checking against adaptive pro- grams and in cryptographic settings. In DIMACS Workshop on Distributed Computing and Cryp- thography, 1989. [Cimatti+97] A. Cimatti, F. Giunchiglia, P. Pec- chiari, B. Pietra, J. Profeta, D. Romano, and P. Traverso. A Provably Correct Embedded Verifier for the Certification of Safety Critical Software. In Proceedings of CAV '97 Conference on Computer Aided Verification, Haifa, Israel, June 1997. [Chirica/Martin86] L. M. Chirica and D. F. Mar- tin. Toward Compiler Implementation Correctness Proofs. ACM Transactions on Programming Lan- guages and Systems, 8(2):185-214, April 1986. [Dold+02] A. Dold, W. Goerigk, F. W. von Henke, and V. Vialard. A Mechanically Verified Compiling Specification for a Realistic Compiler. Technical Report UIB 2002-03, University of Ulm, 2002. [GccBugList] Gcc Project. The Gcc Bugs Archive. http://gcc.gnu.org/ml/gcc-bugs/. [Goerigk96b] W. Goerigk. An Exercise in Pro- gram Verification: The ACL2 Correctness Proof of a Simple Theorem Prover Executable. Technical Report Verifix/CAU/2.4, CAU Kiel, 1996. [Goerigk97d] W. Goerigk. Towards Rigorous Compiler Implementation Verification. In R. Berghammer and F. Simon, editors, Proc. of the 1997 Workshop on Programming Languages and Fundamentals of Programming, pages 118 — 126, Avendorf, Germany, November 1997. [Goerigk+98] W. Goerigk, Th. Gaul, and W. Zimmermann. Correct Programs without Proof? On Checker-Based Program Verification. In R. Berghammer and Y. Lakhnech, editors, Tool Support for System Specification, Development, and Verification, Advances in Computing Science, pages 108 — 122, Springer Verlag Wien, New York, 1998. [Goerigk/Hoffmann96] W. Goerigk and U. Hoff- mann. The Compiler Implementation Language ComLisp. Technical Report Verifix/CAU/1.7, CAU Kiel, June 1996. Теоретические и методологические основы программирования 26 [Goerigk/Hoffmann97] W. Goerigk and U. Hoff- mann. The Compiling Specification from ComLisp to Executable Machine Code. Technical Report Nr. 9713, Institut für Informatik, CAU, Kiel, De- cember 1998. [Goerigk/Hoffmann98b] W. Goerigk and U. Hoff- mann. Compiling ComLisp to Executable Ma- chine Code: Compiler Construction. Technical Report Nr. 9812, Institut für Informatik, CAU Kiel, October 1998. [Goerigk/Hoffmann98] W. Goerigk and U. Hoff- mann. Rigorous Compiler Implementation Cor- rectness: How to Prove the Real Thing Correct. In D. Hutter, W. Stephan, P. Traverso, and M. Ull- mann, editors, Applied Formal Methods - FM- Trends 98, volume 1641 of LNCS, pages 122 — 136, Springer Verlag, 1998. [Goerigk/Langmaack01c] W. Goerigk and H. Langmaack. Will Informatics be able to Justify the Construction of Large Computer Based Sys- tems? Part I: Realistic Correct Systems Implemen- tation. International Journal on Problems of Pro- gramming, 2003. Forthcoming. [Goerigk99a] W. Goerigk. On Trojan Horses in Compiler Implementations. In F. Saglietti and W. Goerigk, editors, Proc. des Workshops Sicher- heit und Zuverlдssigkeit softwarebasierter Systeme, ISTec Report ISTec-A-367, ISBN 3-00-004872-3, Garching, August 1999. [Goerigk99b] W. Goerigk. Compiler Verification Revisited. In M. Kaufmann, P. Manolios, and J S. Moore, editors, Computer Aided Reasoning: ACL2 Case Studies. Kluwer Academic Publishers, 2000. [Goerigk00a] W. Goerigk. Trusted Program Exe- cution. Habilitation thesis. Techn. Faculty, Chris- tian-Albrechts-Universität zu Kiel, May 2000. To be published. [Goerigk00b] W. Goerigk. Proving Preservation of Partial Correctness with ACL2: A Mechanical Compiler Source Level Correctness Proof. In M. Kaufmann and J S. Moore, editors, Proceeding of the ACL2'2000 Workshop, University of Texas, Austin, Texas, U.S.A., October 2000. [Goos/Zimmermann99] G. Goos and W. Zimmer- mann. Verification of Compilers. In E.-R. Olderog and B. Steffen, editors, Correct System Design, volume 1710 of LNCS, pages 201 — 230. Springer Verlag, 1999. [Gaul+99] Th. Gaul, W. Zimmermann, and W. Go- erigk. Construction of Verified Software Systems with Program-Checking: An Application To Com- piler Back-Ends. In A. Pnueli and P. Traverso, edi- tors, Proc. FLoC'99 International Workshop on Run- time Result Verification, Trento, Italy, 1999. [Heberle+98]A. Heberle, Th. Gaul, W. Goerigk, G. Goos, and W. Zimmermann. Construction of Verified Compiler Front-Ends with Program- Checking. In Proceedings of PSI '99: Andrei Er- shov Third International Conference on Perspec- tives Of System Informatics, volume 1755 of Lec- ture Notes in Computer Science, Novosibirsk, Rus- sia, 1999. Springer Verlag. [Hoffmann98b] U. Hoffmann. Compiler Implemen- tation Verification through Rigorous Syntactical Code Inspection. PhD thesis, Technische Fakultät der Christian-Albrechts-Universität zu Kiel, Tech- nical Report 9814, 1998. [HSE01] C. Jones, R.E. Bloomfield, P.K.D. Froo- me, and P.G. Bishop. Methods for assessing the safety integrity of safety-related software of un- certain pedigree (SOUP). Contract Research Re- port 337/2001, Health and Safety Executive, Ade- lard, London, UK, 2001. [Kaufmann/Moore94] M. Kaufmann and J S. Moore. Design Goals of ACL2. Technical Re- port 101, Computational Logic, Inc., August 1994. [Langmaack97a] H. Langmaack. Softwareengi- neering zur Zertifizierung von Systemen: Spezifi- kations-, Implementierungs-, Übersetzerkorrek- theit. Informationstechnik und Technische Infor- matik it+ti, 39(3):41—47, 1997. [Langmaack97b] H. Langmaack. The ProCoS Ap- proach to Correct Systems. Real Time Systems, 13:251—273, Kluwer Academic Publishers, 1997. [Langmaack97c] H. Langmaack. Contribution to Goodenough's and Gerhart's Theory of Software Testing and Verification: Relation between Strong Compiler Test and Compiler Implementation Verification. Foundations of Computer Science: Potential-Theory-Cognition. LNCS 1337, pages 321—335, Springer Verlag, 1997. [Loeckx/Sieber87] Jacques Loeckx and Kurt Sieber. The Foundations of Program Verification (Second edition). John Wiley and Sons, New York, N.Y., 1987. [Moore88] J S. Moore. Piton: A verified assembly level language. Techn. Report 22, Comp. Logic Inc, Austin, Texas, 1988. [Moore96] J S. Moore. Piton: A Mechanically Verified Assembly-Level Language. Kluwer Aca- demic Press, Dordrecht, The Netherlands, 1996. [Mueller-Olm/Wolf99] M. Müller-Olm and A. Wolf. On Excusable and Inexcusable Failures: Towards an Adequate Notion of Translation Cor- rectness. In J. M. Wing, J. Woodcock, and J.Davies, editors, Proceedings of Formal Methods FM'99, volume 1709 of LNCS, pages 1107—1127, Toulouse, France, 1999. Springer Verlag. Теоретические и методологические основы программирования 27 [Nielson/Nielson92] H. R. Nielson and F. Nielson. Semantics with Applications: A Formal Introduc- tion. John Wiley & Sons, Chichester, 1992. [Pnueli+98] A. Pnueli, M. Siegel, and E. Singer- man. Translation Validation. In Proc. 4th Interna- tional Conference on Tools and Algorithms for the Construction and Analysis of Systems, Lisbon, Por- tugal, March 1998. [Thompson84] K. Thompson. Reflections on Trusting Trust. Communications of the ACM, 27(8):761—763, 1984. Also in ACM Turing Award Lectures: The First Twenty Years 1965-1985, ACM Press, 1987, and in Computers Under Attack: In- truders, Worms, and Viruses, ACM Press, 1990. [Wirth77] N. Wirth. Compilerbau, eine Ein- führung. B.G. Teubner, Stuttgart, 1977. [Wolf00] A. Wolf. Weakest Relative Precondition Semantics - Balancing Approved Theory and Real- istic Translation Verification. PhD thesis, Tech- nische Fakultät der Christian-Albrechts- Universität, Report No. 2013, Kiel, February 2001. Date received 20.03.03 About authors Prof. Dr. Wolfgang Goerigk Professor of Institute of Informatics and Applied Mathematics Prof. Dr. Hans Langmaack Professor of Institute of Informatics and Applied Mathematics Institut für Informatik und Praktische Mathematik Christian-Albrechts-Universität zu Kiel, Kiel, Germany Email: wg@informatik.uni-kiel.de, hl@informatik.uni-kiel.de