1\documentclass{article} 2\usepackage[english]{babel} 3\usepackage{listings} 4\usepackage{fullpage} 5\usepackage{color} 6 7%include polycode.fmt 8 9%if false 10 Error: DSL for error definition 11 12 Copyright (c) 2009, 2011 ETH Zurich. 13 All rights reserved. 14 15 This file is distributed under the terms in the attached LICENSE file. 16 If you do not find this file, copies can be found by writing to: 17 ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group. 18%endif 19 20\title{Filet-O-Fish Tutorial:\\ 21 The Fugu Error Definition Language} 22\author{\Large{Pierre-Evariste \sc{Dagand}}} 23\date{} 24 25\begin{document} 26 27\lstset{basicstyle=\ttfamily, 28 columns=fullflexible, 29 language=C} 30 31\maketitle 32 33 34 35\textcolor{red}{Warning: the comments below are out-of-sync with some 36aspects of the code. Some will claim that this is a clear proof that 37literate programming is useless. Others will note that in non-literate 38code, I would not have written this disclaimer and the code would be a 39complete mess. Anyway, feel free to improve the literate story: we 40have variable-size blocks, error codes are not generated randomly 41anymore, we use a threshold to differentiate success to failure 42cases.} 43 44 45%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 46\section*{Introduction} 47%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 48 49 50 51%% <- Present the goal of the tutorial 52 53In this tutorial, we aim at illustrating the usage of Filet-O-Fish 54(FoF). Therefore, we will implement a small Domain-Specific Language 55(DSL), using FoF in the back-end. However, we will also cover a 56broader topic: how to design a new DSL taking advantage of FoF 57particularities. In particular, this DSL will be an Error Definition 58Language (EDL) called Fugu. Its functionalities are described in 59Section~\ref{sec:edl}. 60 61%% -> Step-by-step, real life design 62 63Hence, we will adopt a step-by-step approach. While covering the 64various phases of development, we will try to devise a more 65``principled'' approach, which could serve for future development. 66 67%% -> From C code to high-level language 68 69One of our partis pris is that such work should start from mature C 70code. FoF is meant as a safe meta-language, allowing the DSL designer 71to, first, abstract over C and, then, manipulate these high-level 72constructions in a powerful environment, such as Haskell or a theorem 73prover. 74 75%% -> With a focus on the back-end 76 77For the sake of brevity, we will focus our presentation on the DSL 78back-end. Hence, we will not write a parser for Fugu: the syntax of 79this small language will be \emph{embedded} in Haskell, by the means 80of some \emph{combinators}. We will take care of avoiding any 81confusion between all those languages. 82 83%% <- Outline 84%% -> Follow real development 85%% <- Intents of the Error Definition language 86%% <- Presentation of the C sample 87%% <- Designing the Syntax 88%% <- Backend 89 90This tutorial is organized as follow. In a first Section, we specify 91our requirements concerning Fugu. In a second Section, we consider 92define a small class of errors and describe how we would like to 93compile them down. From there, we carry a step-by-step implementation 94of Fugu in Section~\ref{sec:backend}. 95 96 97 98%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 99\section{An Error Definition System} 100\label{sec:edl} 101%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 102 103 104%% <- Replacing errno 105%% <- Overloaded meaning (several causes, one code) 106 107The need for an Error Definition System arises from the deficiencies of 108the traditional scheme, namely the \texttt{errno.h}, or similar, 109file. In this file, developers typically aggregate the error codes of 110all errors that could potentially occur in the system. In order to 111scale, they quickly ``overload'' the meaning of these error codes: 112instead of defining distinct error codes, they use a single code which 113has multiple causes. For example, the {\sc Einval} error of FreeBSD 114signals that ``some invalid argument was supplied: for example, 115specifying an undefined signal to a signal function or a kill system 116call''. In parallel, they also abuse the return values of 117functions. For instance, a function returning a {\sc Null} pointer is 118typically signalling an error. However, this does not provide much 119information to the developer facing such case. 120 121%% <- Artificially limited namespace 122 123Although this might have been historically relevant, there is no 124incentive, today, to limit the error code space to 255. Using 16 bits 125would allow us to define 65535 error codes. In such a huge space, we 126would be freed from the burden of overloading error codes and 127functions return values: when it is relevant, the developer should be 128able to define new error codes. Hence, he could handle errors more 129precisely, in the code as well as during the debugging process. 130 131%% <- Flat namespace 132 133However, by imposing a flat name-space, the \texttt{errno.h} approach 134reduces the benefit of defining more precise error codes. When an 135error occurs, the developer is not able to relate it to a subsystem: 136it is simply defined as one error among thousands of others. In a 137sense, the error is more precise but still lacks some context. Being 138able to give a context to an error would be a step forward, notably 139during debugging. 140 141%% <- Absence of error-tracking 142 143Finally, it is often the case that an error in one function must be 144reported to several functions in the call-stack. Whereas the depth of 145the call-stack is generally low, it is extremely inconvenient to 146define case-specific error codes at each level (the number of cases 147growing exponentially with the depth). Hence, we generally give up 148accuracy and report a global, overloaded error code. 149 150%% <- Design goals 151%% <- One class of errors per "component" 152 153For these reasons, we provide the developer with the power of 154defining classes of errors per ``component''. The notion of 155\emph{component} is purposely kept fuzzy: it is up to the developer 156to define its components, on a case-by-case basis. For example, it 157could be a single function or a class of related functions. 158 159%% <- Call-trace 160 161By using a 16 bits space, we are able get rid of the artificial 162limitation of the state-space. But we can also take advantage of this 163sub-word quantity in our 64 bits machines. Indeed, in one machine 164word, we are able to handle 4 error codes. Hence, we can build the 165call-trace by \emph{pushing} an error code in the previous one, and so 166on along the call-path. 167 168%% <- Automated, human-readable error reporting 169 170By relying on a tool to generate C code for us, we can also get rid of 171a lot of boilerplate code. For example, when defining an error code, 172we label it with a descriptive message as well as a short 173acronym. Then, given an error, we should be able to report it to the 174user, in a meaningful way. 175 176%% <- Sensibility to wild writes 177 178Finally, in the event of wild write -- such as a buffer overflow -- on 179the error code variable, we would like the error code to become 180meaningless, instead of signaling an unrelated error. Therefore, we 181should use random numbers to identify errors. Hence, the common 182consequences of a wild write, such as overwriting with 0 or with a 183character, could be detected more easily. 184 185 186 187%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 188\section{C Sample Code} 189%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 190 191%% <- Why starting with a C sample 192 193 194%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 195\subsection{Starting from C} 196%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 197 198%% <- Ability for anyone (non-FoF user) to discuss 199%% and modify the model 200%% <- Importance of user's feed-back 201 202First of all, we would like to stress the importance of starting from 203a mature C code. The first and foremost reason is the ability to 204\emph{discuss} this draft with the final users of the DSL: whereas not 205everyone is familiar with Haskell and FoF, C is the \emph{lingua 206franca} for everyone. Thanks to this support, users are given the 207power to influence the design of the DSL, without having to handle its 208machinery. Moreover, this C file is an inexpensive support for testing 209and debugging: we can quickly modify its behavior without dealing 210with the compilation machinery. 211 212%% <- FoF should not be meant as a programming language per se 213%% <- Give a meaning 214 215Although disturbing at first, FoF should \emph{not} be meant as a 216programming language. First and foremost, FoF gives a meaning, the 217semantics, to your DSL language. The fact that it can be compiled to C 218comes as a bonus. 219 220%% <- C code serves as a weak specification of the intention 221%% <- Informal alternative semantics 222 223Finally, a ``good'' DSL should be defined by an alternative semantics, 224which would allow the DSL designer to check the correctness of the 225compiler. Having a C sample of a particular instance can play this 226role, for this test-case. Although it does not help in obtaining 227formal guarantees, it is a first step toward a seemingly correct 228compiler. 229 230 231%% <- The Errors 232%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 233\subsection{Our Test-Case} 234\label{sec:example} 235%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 236 237In the following, we will define the error codes for two components: 238``system'' and ``pci''. The ``\verb!system_err!'' class of errors will 239contain the following cases of success: 240\begin{description} 241 \item[\verb!SYS_OK!] No error 242 \item[\verb!SYS_ILIA!] Mafioso Pasta (private ETH joke, on SOSP lunches) 243\end{description} 244 245And the following cases of error: 246\begin{description} 247 \item[\verb!SYS_FAIL!] Failed 248 \item[\verb!SYS_TEM!] Kernel Hacker 249 \item[\verb!SYS_IPHUS!] Analphabet Greek 250\end{description} 251 252The ``\verb!pci_err!'' class of errors is more serious, with the 253following cases of success: 254\begin{description} 255 \item[\verb!PCI_OK!] No PCI error 256 \item[\verb!PCI_GET_CAP!] That is my cap 257\end{description} 258 259And this case of error: 260\begin{description} 261 \item[\verb!PCI_CAP_NOK!] Lost my cap 262\end{description} 263 264In the next Section, we review the C code \emph{we would like to 265generate} from this or a similar description. Bear in mind that this 266code is the result of a long and contradictory discussion with the 267final users. 268 269%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 270\subsection{The C Sample} 271%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 272 273%% <- Includes 274%%%%%%%%%%%%%%%%%%%% 275\subsubsection{Includes} 276%%%%%%%%%%%%%%%%%%%% 277 278 279First of all, we will need, more or less, the following includes: 280 281\begin{lstlisting} 282 #include <stdlib.h> 283 #include <stdio.h> 284 #include <assert.h> 285 #include <stdbool.h> 286 #include <stdint.h> 287 #include <stdarg.h> 288\end{lstlisting} 289 290 291 292%% <- Typedef 293%%%%%%%%%%%%%%%%%%%% 294\subsubsection{Type Definitions} 295%%%%%%%%%%%%%%%%%%%% 296 297We define an error value as 64 bits machine word. This encompass both 298the current error code and, potentially, 3 others stacked error codes. 299 300\begin{lstlisting} 301typedef uint64_t errval_t; 302\end{lstlisting} 303 304%% <- Enumeration 305%%%%%%%%%%%%%%%%%%%% 306\subsubsection{Error codes} 307%%%%%%%%%%%%%%%%%%%% 308 309We define the error codes in an enumeration. Each code is identified 310by a random number between 0 and 65535. The success codes are 311identified by an odd number whereas failure codes are identified by an 312even number. This will come handy when we define a global 313\verb!err_is_ok! and \verb!err_is_fail! functions. 314 315\begin{lstlisting} 316enum err_code { 317 SYS_OK = 11, 318 SYS_FAIL = 84, 319 SYS_ILIA = 93, 320 SYS_TEM = 2, 321 SYS_IPHUS = 34, 322 PCI_OK = 41, 323 PCI_CAP_NOK = 28, 324 PCI_GET_CAP = 16 325}; 326\end{lstlisting} 327 328 329%% <- Labels 330%%%%%%%%%%%%%%%%%%%% 331\subsubsection{Labels} 332%%%%%%%%%%%%%%%%%%%% 333 334Then, we statically define the previous error descriptions, acronyms, 335and domains. This way, we will be able to provide a descriptive 336notification to the user in case of error. 337 338\begin{lstlisting} 339static char *err_domains[] = { 340 "System_err", "Pci_err", "Undefined" 341}; 342static char *err_codes[] = { 343 "SYS_OK", "SYS_FAIL", "SYS_ILIA", 344 "SYS_TEM", "SYS_IPHUS", "PCI_OK", 345 "PCI_CAP_NOK", "PCI_GET_CAP", 346 "UNDEFINED" 347}; 348static char *err_msgs[] = { 349 "No error", "Failed", 350 "Mafioso Pasta", 351 "Kernel Hacker", 352 "Analphabet Greek", 353 "No PCI error", 354 "Lost my cap", 355 "That is my cap", 356 "Undefined error", 357}; 358\end{lstlisting} 359 360 361%% <- Pop and push Error number 362%%%%%%%%%%%%%%%%%%%% 363\subsubsection{Getting and Stacking Errors} 364%%%%%%%%%%%%%%%%%%%% 365 366Given an \verb!errval_t!, we will need to extract the latest error 367code. This is done by accessing the lowest 16 bits. 368 369\begin{lstlisting} 370static inline enum err_code err_no(errval_t err){ 371 return (err & ((1 << 16) - 1)); 372} 373\end{lstlisting} 374 375Similarly, we can stack up an error code in an existing error value: 376we simply have to shift-left the stack and write the current error 377code in the lowest 16 bits. 378 379\begin{lstlisting} 380static inline errval_t err_push(errval_t err, enum err_code code){ 381 return (err << 16) || code; 382} 383\end{lstlisting} 384 385 386 387%% <- Testing for success or failure 388%%%%%%%%%%%%%%%%%%%% 389\subsubsection{Success and Failure} 390%%%%%%%%%%%%%%%%%%%% 391 392We will frequently need to test an error code for failure or 393success. Hence, we provide two function which are both efficient and 394apply to any error code. 395 396\begin{lstlisting} 397static inline bool err_is_ok(errval_t err){ 398 return err_no(err) % 2; 399} 400static inline bool err_is_fail(errval_t err){ 401 return 1 - (err_no(err) % 2); 402} 403\end{lstlisting} 404 405 406%% <- Error-specific tests 407%%%%%%%%%%%%%%%%%%%% 408\subsubsection{Error-specific Tests} 409%%%%%%%%%%%%%%%%%%%% 410 411Then, we might need to test some error codes against a specific 412error. This is simply achieved by the following functions. 413 414\begin{lstlisting} 415static inline bool err_is_sys_fail(errval_t e){ 416 return err_no(e) == SYS_FAIL; 417} 418... 419static inline bool err_is_pci_ok(errval_t e){ 420 return err_no(e) == SYS_OK; 421} 422... 423\end{lstlisting} 424 425 426%% <- Asserting success or failure 427%%%%%%%%%%%%%%%%%%%% 428\subsubsection{Asserting Success and Failure} 429%%%%%%%%%%%%%%%%%%%% 430 431Similarly, during the debugging phase, we might need to assert the 432correctness or incorrectness of a result. Hence, the following 433assertions. 434 435\begin{lstlisting} 436static inline void err_assert_ok(errval_t err){ 437 assert(err_is_ok(err)); 438} 439static inline void err_assert_fail(errval_t err){ 440 assert(err_is_fail(err)); 441} 442\end{lstlisting} 443 444 445%% <- Printers on errors 446%%%%%%%%%%%%%%%%%%%% 447\subsubsection{Printing Errors} 448%%%%%%%%%%%%%%%%%%%% 449 450 451 452\begin{lstlisting} 453static inline char *err_getstring(errval_t err) { 454 455 switch (err_no(err)) { 456 case SYS_OK: { 457 return err_msgs[0]; 458 break; 459 } 460 ... 461 case PCI_OK: { 462 return err_msgs[5]; 463 break; 464 } 465 ... 466 default: { 467 return err_msgs[8]; 468 break; 469 } 470 } 471 return NULL; 472} 473 474static inline char *err_getcode(errval_t err ) { 475 476 switch (err_no(err)) { 477 case SYS_OK: { 478 return err_codes[0]; 479 break; 480 } 481 ... 482 case PCI_OK: { 483 return err_codes[5]; 484 break; 485 } 486 ... 487 default: { 488 return err_codes[8]; 489 break; 490 } 491 } 492 return NULL; 493} 494 495static inline char *err_getdomain(errval_t err ) { 496 497 switch (err_no(err)) { 498 case SYS_OK: { 499 return err_domains[0]; 500 break; 501 } 502 ... 503 case PCI_OK: { 504 return err_domains[1]; 505 break; 506 } 507 ... 508 default: { 509 return err_domains[2]; 510 break; 511 } 512 } 513 return NULL; 514} 515\end{lstlisting} 516 517 518%% <- Printing the call trace 519%%%%%%%%%%%%%%%%%%%% 520\subsubsection{Printing the Call-trace} 521%%%%%%%%%%%%%%%%%%%% 522 523Finally, mostly during debugging, we would like to inspect the 524call-trace leading to an error. This is achieved by the following 525code. First, it verifies that the stack contains an error. Then, it 526\emph{pop}s error codes from it and prints their domain, description, 527and acronym. 528 529\begin{lstlisting} 530static inline void err_print_calltrace(errval_t err){ 531 532 if (err_is_fail(err)){ 533 534 enum err_code x; 535 while( (x = err_no(err)) != 0 ){ 536 printf("Failure: (%12s) %20s [%s]\n", 537 err_getdomain(x), 538 err_getstring(x), 539 err_getcode(x)); 540 err = err >> 16; 541 } 542 } 543} 544\end{lstlisting} 545 546 547This concludes our draft implementation of an error management 548system. Now, we are aware of our needs, what the user needs to specify 549and what constructs should we abstract away. Let us start implementing 550Fugu, now. 551 552 553 554%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 555\section{Fugu: the Error Definition Language} 556\label{sec:backend} 557%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 558 559 560%if false 561 562> module FuguBackend where 563 564> import System.IO 565> import System.Random 566> import Data.Char 567> import Data.Maybe 568> import Data.List 569> import Data.Monoid 570> import qualified Data.Bits as B 571 572> import Debug.Trace 573 574> import Semantics 575> import Constructs 576> import PureExpressions 577> import Expressions 578 579> import Constructs.Conditionals 580> import Constructs.References 581> import Constructs.Functions 582> import Constructs.Structures 583> import Constructs.Arrays 584> import Constructs.Enumerations 585> import Constructs.Typedef 586> import Constructs.Strings 587 588> import Libc.Assert 589> import Libc.Printf 590 591> import Compile 592 593%endif 594 595In the following Sections, we describe the implementation of Fugu. As 596usual with me, you are reading a literate code: the Haskell compiler 597processes this very same file. Therefore, you are reading the real 598Fugu's code. In Section \ref{sec:syntax}, we define Fugu's AST and a 599set of combinators, to be able to embed Fugu's language into 600Haskell. In Section \ref{sec:fof_backend}, we implement the back-end 601using Filet-O-Fish. 602 603 604 605%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 606\subsection{Fugu Embedded Syntax} 607\label{sec:syntax} 608%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 609 610 611 612%%%%%%%%%%%%%%%%%%%% 613\subsubsection{The Abstract-Syntax Tree} 614%%%%%%%%%%%%%%%%%%%% 615 616First, we define the Abstract-Syntax Tree (AST). An error definition 617file is a list of error classes: 618 619> type Errors = [ ErrorClass ] 620 621Where an error class is identified by a string, its name, and a list 622of error definitions: 623 624> data ErrorClass = ErrorClass String [ ErrorField ] 625 626An error definition is composed by a description and a short 627acronym. We also specify an error status, either a failure case or a 628success case. 629 630> data ErrorField = ErrorField ErrorStatus String String 631> 632> data ErrorStatus = Failure 633> | Success 634> | DefaultSuccess 635> deriving Eq 636 637 638 639%%%%%%%%%%%%%%%%%%%% 640\subsubsection{Enbedding Fugu in Haskell} 641%%%%%%%%%%%%%%%%%%%% 642 643Instead of designing a parser, possibly using Parsec, we chose to 644embed the Fugu language inside Haskell. This means that we are going 645to define some Haskell functions, operating on the AST. By combining 646these functions, we will be able to build a complete AST that can be 647processed by Fugu's back-end. 648 649First of all, we can build an |ErrorField| out of the error acronym 650and description, using one of these two combinators: 651 652> success, failure :: String -> String -> ErrorField 653> success acronym description = ErrorField Success acronym description 654> failure acronym description = ErrorField Failure acronym description 655 656Given a list of such fields and a global name for them, we can then 657build an |ErrorClass|: 658 659> errors :: String -> [ ErrorField ] -> ErrorClass 660> errors className errors = ErrorClass className errors 661 662And, finally, given a list of such classes, we get a valid error 663definition ``file'': 664 665> define :: [ErrorClass] -> Errors 666> define errors = errors 667 668 669 670%%%%%%%%%%%%%%%%%%%% 671\subsubsection{Our Example in Fugu} 672%%%%%%%%%%%%%%%%%%%% 673 674 675If we translate our informal description of Section~\ref{sec:example}, 676this leads to the following code: 677 678> testE = define [ 679> errors "system_err" [ 680> success "SYS_ERR_OK" "No error", 681> failure "SYS_ERR_FAIL" "Failed", 682> success "SYS_ERR_ILIA" "Mafioso Pasta", 683> failure "SYS_ERR_TEM" "Kernel Hacker", 684> failure "SYS_ERR_IPHUS" "Analphabet Greek" 685> ], 686> errors "pci_err" [ 687> success "PCI_ERR_OK" "No PCI error", 688> failure "PCI_ERR_NOK" "Lost my cap", 689> success "PCI_ERR_CAP" "That is my cap" 690> ] 691> ] 692 693 694%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 695\subsection{Filet-O-Fish Back-end} 696\label{sec:fof_backend} 697%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 698 699At this point, there is no way we can postpone the implementation of 700the compiler back-end. So, \emph{Ave, Caesar, Morituri Te Salutant}. 701 702 703%% <- Typedefs 704%%%%%%%%%%%%%%%%%%%% 705\subsubsection{Defining Type Aliases} 706%%%%%%%%%%%%%%%%%%%% 707 708 709We start with some type aliasing. In its basic form, FoF only knows 710\emph{primitive} types such as integers, floats, and user-defined 711structures, unions, and arrays. Therefore, if we are to use the 712standard |bool| or |char|, we have to inform FoF of their existence as 713well as to which primitive type they match to. 714 715> boolT :: TypeExpr 716> boolT = typedef uint64T "bool" 717 718Similarly, we can define our own aliases: 719 720> err_codeT :: TypeExpr 721> err_codeT = enumT "err_code" [] -- not right, [] is populated 722> err_code :: Int -> PureExpr 723> err_code = uint16 . toInteger 724> 725> errval_tT :: TypeExpr 726> errval_tT = typedef uintptrT "errval_t" 727 728 729%% <- Defining general functions 730%%%%%%%%%%%%%%%%%%%% 731\subsubsection{Defining General Functions} 732%%%%%%%%%%%%%%%%%%%% 733 734Implementing the |err_no|, |err_push|, |err_is_ok|, and |err_is_fail| 735consists in a straightforward translation from C to FoF. Let us give a 736closer look at |err_no|, for instance. 737 738The body of |err_no| consists in doing some bit twiddling on the error 739stack and returning this value: 740 741> err_no_int :: Integer -> [PureExpr] -> FoFCode PureExpr 742> err_no_int bitBlock (err : []) = 743> returnc $ cast err_codeT $ (err .&. ((uint64 1 .<<. uint64 bitBlock) .-. uint64 1)) 744 745We build a function out of the function body thanks to the |def| 746operator. Along with the function body, we have to pass the return 747type as well as the parameters types: 748 749> err_noF :: Integer -> FoFCode PureExpr 750> err_noF bitBlock = def [Static, Inline] "err_no" (err_no_int bitBlock) 751> err_codeT 752> [(errval_tT, Just "errval")] 753 754And we are done: |err_noF| is corresponds to the C |err_no| 755function. We will see later on how it can be called. 756 757Similarly, we define |err_pushF|. This time, we declare the function 758body in a @where@ clause. Note, however, that we have to provide the 759type of |err_push_int| as it cannot be inferred by the Haskell 760compiler. 761 762 763> err_pushF :: Integer -> FoFCode PureExpr 764> err_pushF bitBlock = def [Static, Inline] "err_push" err_push_int errval_tT [(errval_tT, Just "errval"), 765> (err_codeT, Just "errcode")] 766> where err_push_int (err : code : []) = 767> returnc ((err .<<. uint64 bitBlock) .|. 768> (cast errval_tT $ uint64 (((toInteger 1) `B.shiftL` (fromInteger bitBlock)) - 1) .&. code)) 769 770And the same goes for |err_is_okF| and |err_is_failF|: 771 772> err_is_okF :: Integer -> Int -> FoFCode PureExpr 773> err_is_okF bitBlock indexFailures = def [Static, Inline] "err_is_ok" err_is_ok_int boolT [(errval_tT, Just "errval")] 774> where err_is_ok_int (err : []) = 775> do 776> err_no <- err_noF bitBlock 777> err_no_e <- call err_no [err] 778> returnc (err_no_e .<. uint64 (toInteger indexFailures)) 779> 780> err_is_failF :: Integer -> Int -> FoFCode PureExpr 781> err_is_failF bitBlock indexFailures = def [Static, Inline] "err_is_fail" err_is_fail_int boolT [(errval_tT, Just "errval")] 782> where err_is_fail_int (err : []) = 783> do 784> err_no <- err_noF bitBlock 785> err_no_e <- call err_no [err] 786> returnc (err_no_e .>=. uint64 (toInteger indexFailures)) 787 788%% <- Defining parameterized functions 789%%%%%%%%%%%%%%%%%%%% 790\subsubsection{Defining Parameterized Functions} 791%%%%%%%%%%%%%%%%%%%% 792 793An example is |err_get_array|, which is used to build the 794|err_getdomain|, |err_getstring|, and |err_getcode| functions. Hence, 795|err_get_array| takes as first argument the postfix of the |err_get*| 796target. It is also provided a reference to the array that contains the 797descriptive strings. And the last argument consists of a list of pairs 798(error code, array index). 799 800The body of the |err_get_array| is a small generalization over our C 801examples: it calls |err_no| on the argument and switch over it. For 802each error code, it reads in the provided array, at the corresponding 803index. The default case simply consists in reading at latest position 804in the array, where ``undefined'' or its variant is written. 805 806> err_get_array :: Integer -> String -> PureExpr -> [(Int, PureExpr)] -> 807> PureExpr -> FoFCode PureExpr 808> err_get_array bitBlock name array codes defaultCode = 809> def [] ("err_get" ++ name) err_getstring_int (ptrT charT) [(errval_tT, Just "errval")] 810> where err_getstring_int (err : []) = 811> do 812> err_no <- err_noF bitBlock 813> err_no_e <- call err_no [err] 814> switch err_no_e 815> [ (uint64 $ toInteger code, (return_array array index)) 816> | (code, index) <- codes ] 817> (return_array array defaultCode) 818> returnc (uint64 0) 819> return_array array index = 820> do 821> x <- readArray array index 822> returnc $ cast (ptrT charT) x 823 824The last function is |err_print_calltrace|. Although longer, this 825function does not involve technical aspects of FoF. We can observe the 826use of |if| and |while| operators. As well as the definition and usage 827of \emph{references}. We also make use of the |printf| operator. 828 829> err_print_calltraceF :: Integer -> Int -> PureExpr -> PureExpr -> PureExpr -> FoFCode PureExpr 830> err_print_calltraceF bitBlock indexFailures err_getdomain err_getstring err_getcode = 831> def [] "err_print_calltrace" err_print_calltrace_int voidT [(errval_tT, Just "errval")] 832> where err_print_calltrace_int (err : []) = 833> do 834> err_no <- err_noF bitBlock 835> err_is_fail <- err_is_failF bitBlock indexFailures 836> 837> err_ref <- newRef err 838> 839> ifc (do 840> is_fail_e <- call err_is_fail [err] 841> return is_fail_e) 842> (do 843> x <- newRef $ uint16 0 844> iterations <- newRef $ uint16 0 845> 846> while (do 847> err_ref_val <- readRef err_ref 848> err_ref_no <- call err_no [err_ref_val] 849> writeRef x err_ref_no 850> return $ (err_ref_no .!=. (uint16 0)) .&. (iterations .<. ((sizeof(errval_tT) .*. (uint64 8)) ./. (uint64 bitBlock)))) 851> (do 852> err_ref_val <- readRef err_ref 853> err_ref_dom <- call err_getdomain [err_ref_val] 854> err_ref_str <- call err_getstring [err_ref_val] 855> err_ref_code <- call err_getcode [err_ref_val] 856> printf "Failure: (%15s) %20s [%10s]\\n" 857> [err_ref_dom, err_ref_str, err_ref_code] 858> writeRef iterations (iterations .+. (uint64 1)) 859> writeRef err_ref (err_ref_val .>>. (uint64 bitBlock)))) 860> (do return void) 861 862 863%% <- Defining data-structures 864%%%%%%%%%%%%%%%%%%%% 865\subsubsection{Defining Data-Structures} 866%%%%%%%%%%%%%%%%%%%% 867 868The following codes involves more Haskell-specific data-processing of 869the AST, and less FoF-specific manipulations. However, this 870quasi-absence of FoF is also a good sign: FoF succeeds in being 871non-intrusive while being able to provide strong guarantees on the 872compiler's correctness. 873 874%%%%%%%% 875\paragraph{Compiling the Labels} 876%%%%%%%% 877 878The first step here is to extract the labels, ie. the error codes and 879descriptions, from the Error Definition. Once extracted, we turn them 880into FoF strings, thanks to |makeStrings|. 881 882> err_strings :: Errors -> ([String], [String], [String]) 883> err_strings errors = 884> (domains ++ ["Undefined"], 885> errcodes ++ ["UNDEFINED"], 886> errdescs ++ ["Undefined"]) 887> where domains = [ domain | ErrorClass domain _ <- errors ] 888> (errcodes,errdescs) = unzip $ concat [ [ (code, descr) 889> | ErrorField _ code descr <- fields ] 890> | ErrorClass _ fields <- errors ] 891 892Using |err_strings|, the following function builds the 3 static arrays 893|err_domains|, |err_codes|, and |err_msgs|. Remember that 894|err_strings| returns a triple of lists of strings. We instantiate 895each of these strings as static arrays, hence obtaining a list of 896arrays. Finally, we instantiate these list of arrays as static 897arrays. Hence, we obtain 3 static arrays, each containing some static 898arrays: the various strings. 899 900> newStringIndex :: String -> (Int, String) -> 901> FoFCode PureExpr 902> newStringIndex name (i, x) = newStringN (name ++ show i) x 903 904> err_arrays :: Errors -> FoFCode (PureExpr, PureExpr, PureExpr) 905> err_arrays errors = 906> do 907> let (domains, errcodes, errdescs) = err_strings errors 908> errdomains_str <- sequence $ map (newStringIndex "errdomains_") $ zip [1..] domains 909> errcodes_str <- sequence $ map (newStringIndex "errcodes_") $ zip [1..] errcodes 910> errdescs_str <- sequence $ map (newStringIndex "errdescs_") $ zip [1..] errdescs 911> errdomains_arr <- newStaticArrayN "err_domains" errdomains_str 912> errcodes_arr <- newStaticArrayN "errcodes" errcodes_str 913> errdescs_arr <- newStaticArrayN "errdescs" errdescs_str 914> return (errdomains_arr, errcodes_arr, errdescs_arr) 915 916At this point, we have compiled the labels. 917 918 919%%%%%%%% 920\paragraph{Compiling the Enumeration} 921%%%%%%%% 922 923 924Before compiling the enumeration, we need to compute two lists of 925random, unique, odd and even numbers. This is achieved by the 926following function, which implementation is hidden for the sake of 927brevity. It takes a random number generator, the number $n$ of desired 928integers, and returns a pair composed by $n$ even numbers and $n$ 929odd numbers. 930 931> mkRandomUnique :: StdGen -> Int -> ([Int], [Int]) 932 933%if false 934 935> mkRandomUnique gen i = mkRandomUniqueInt gen i i [] [] 936> where mkRandomUniqueInt gen 0 0 accEven accOdd = (accEven, accOdd) 937> mkRandomUniqueInt gen i j accEven accOdd = 938> let (rand, gen') = next gen in 939> let rand' = rand `mod` 65536 in 940> if rand' `mod` 2 == 0 then 941> if rand' `elem` accEven then 942> mkRandomUniqueInt gen' i j accEven accOdd 943> else 944> if i == 0 then 945> mkRandomUniqueInt gen' i j accEven accOdd 946> else 947> mkRandomUniqueInt gen' (i-1) j (rand' : accEven) accOdd 948> else 949> if rand' `elem` accOdd then 950> mkRandomUniqueInt gen' i j accEven accOdd 951> else 952> if j == 0 then 953> mkRandomUniqueInt gen' i j accEven accOdd 954> else 955> mkRandomUniqueInt gen' i (j-1) accEven (rand' : accOdd) 956 957%endif 958 959Using these lists of even and odd numbers, we assign an identifier to 960each error and return this mapping as a list of triples, containing 961the domain, the acronym, and the unique identifier. Depending whether 962the error indicates a success or a failure, we pick the identifier, 963respectively, among the odd or even numbers. 964 965> mkEnum :: Errors -> (Int, Int, [(String, String, Int)]) 966> mkEnum errors = (succCode', 967> succCode' + failCode, 968> reverse $ codes) 969> where errorTypes = concat [ [ (typ, name,dom) 970> | ErrorField typ name _ <- fields ] 971> | ErrorClass dom fields <- errors ] 972> (codes, 973> succCode', -- note that |succCode'| is used below. Lazyness. 974> failCode) = foldl filterCode ([], 1, 0) errorTypes 975> filterCode (codes, succCode, failCode) 976> (typ, name, dom) 977> | typ == DefaultSuccess = 978> let s = (dom, name, 0) in 979> (s : codes, succCode, failCode) 980> | typ == Success = 981> let s = (dom, name, succCode) in 982> (s : codes, succCode + 1, failCode) 983> | typ == Failure = 984> let s = (dom, name, succCode' + failCode) in 985> (s : codes, succCode, failCode + 1) 986 987 988 989%%%%%%%%%%%%%%%%%%%% 990\subsubsection{Putting Things Together} 991%%%%%%%%%%%%%%%%%%%% 992 993 994The back-end now consists in a straightforward recollection of the 995various, previously developed pieces. Its type is the following: 996 997> backendHeader :: StdGen -> Errors -> FoFCode PureExpr 998 999Meaning that it takes a random number generation, an Error AST and it 1000generates a Semantics. We can notice which components of FoF are used 1001here: Enumerations, Assert, References, Functions, Static arrays, 1002Conditionals, Type definitions, and Printf. 1003 1004The back-end executes the following actions: 1005 1006> backendHeader gen errors = 1007> do 1008 1009Compile the type-definitions: 1010 1011> alias errval_tT 1012> 1013> aliasE "<stdbool.h>" boolT 1014 1015Compile the enumeration 1016 1017> newEnum "err_code" enumErrCodes "" 1018 1019Compile the functions: 1020 1021> err_no <- err_noF bitBlock 1022> err_push <- err_pushF bitBlock 1023> err_is_ok <- err_is_okF bitBlock numberOfSuccess 1024> err_is_fail <- err_is_failF bitBlock numberOfSuccess 1025 1026And we are done. 1027 1028> return void 1029 1030%if false 1031 1032> where errorCodes = concat [ [ code | ErrorField _ code _ <- fields ] 1033> | ErrorClass _ fields <- errors ] 1034> noCodes = length errorCodes 1035> (evenNumbers, oddNumbers) = mkRandomUnique gen noCodes 1036> (numberOfSuccess, numberOfCodes, labeledErrCodes) = mkEnum errors 1037> enumErrCodes = map (\(_,x1,x2) -> (x1,x2)) labeledErrCodes 1038> idErrCodes = map (\(_,_,x) -> x) labeledErrCodes 1039> mapIdDescription = zip idErrCodes (map uint64 [1..]) 1040> mapIdAcronym = mapIdDescription 1041> mapIdDomain = zip idErrCodes (map domainIndex labeledErrCodes) 1042> domains = [ dom | ErrorClass dom _ <- errors ] 1043> domainIndex (dom,_,_) = uint64 $ toInteger $ fromJust $ dom `elemIndex` [ dom | ErrorClass dom _ <- errors ] 1044> noDomains = length domains 1045> bitBlock = (floor $ logBase 2 $ (fromInteger . toInteger) numberOfCodes) + 1 1046 1047%endif 1048 1049> backendCode :: StdGen -> Errors -> FoFCode PureExpr 1050 1051> backendCode gen errors = 1052> do 1053 1054Compute the labels arrays: 1055 1056> (err_domains, 1057> err_codes, 1058> err_descs) <- err_arrays errors 1059 1060Compile the array accessors: 1061 1062> err_getstring <- err_get_array bitBlock "string" err_descs mapIdDescription (err_code noCodes) 1063> err_getcode <- err_get_array bitBlock "code" err_codes mapIdAcronym (err_code noCodes) 1064> err_getdomain <- err_get_array bitBlock "domain" err_domains mapIdDomain (err_code noDomains) 1065 1066Finally, compile the call-trace printer: 1067 1068> err_print_calltrace <- err_print_calltraceF bitBlock numberOfSuccess err_getdomain err_getstring err_getcode 1069 1070And we are done. 1071 1072> return void 1073 1074%if false 1075 1076> where errorCodes = concat [ [ code | ErrorField _ code _ <- fields ] 1077> | ErrorClass _ fields <- errors ] 1078> noCodes = length errorCodes 1079> (evenNumbers, oddNumbers) = mkRandomUnique gen noCodes 1080> (numberOfSuccess, numberOfCodes, labeledErrCodes) = mkEnum errors 1081> enumErrCodes = map (\(_,x1,x2) -> (x1,x2)) labeledErrCodes 1082> idErrCodes = map (\(_,_,x) -> x) labeledErrCodes 1083> mapIdDescription = zip idErrCodes (map uint64 [0..]) 1084> mapIdAcronym = mapIdDescription 1085> mapIdDomain = zip idErrCodes (map domainIndex labeledErrCodes) 1086> domains = [ dom | ErrorClass dom _ <- errors ] 1087> domainIndex (dom,_,_) = uint64 $ toInteger $ fromJust $ dom `elemIndex` [ dom | ErrorClass dom _ <- errors ] 1088> noDomains = length domains 1089> bitBlock = (floor $ logBase 2 $ (fromInteger . toInteger) numberOfCodes) + 1 1090 1091%endif 1092 1093%%%%%%%%%%%%%%%% 1094\subsubsection{Compiling the Test-Case} 1095%%%%%%%%%%%%%%%% 1096 1097 1098At this stage, we just have to open a file, call FoF's |compile| 1099function on the back-end applied to the test-case, and we are done! 1100 1101%if false 1102 1103> {- 1104 1105%endif 1106 1107> main :: IO () 1108> main = do 1109> let gen = mkStdGen 1 1110> fileH <- openFile "test_error.h" WriteMode 1111> hPutStrLn fileH $ show $ fst $ compile (backendHeader gen testE) emptyBinding 1112> hClose fileH 1113 1114%if false 1115 1116> -} 1117 1118%endif 1119 1120Thanks for your attention! 1121 1122 1123 1124\end{document} 1125