DEPARTMENT OF COMPUTER SCIENCE Blues: a programming language for safe parallel computation Joshua S. Gawley A dissertation submitted to the University of Bristol in accordance with the requirements of the degree of Master of Engineering in the Faculty of Engineering. Thursday 2nd May, 2024 Abstract Parallel and distributed computing have become more and more prominent over the past twenty years due to the advent of multicore processors. However, parallel programming can often be challenging, as the programmer has to contend with new kinds of bugs such as data races, deadlocks, and spinlocks. Based off Jonathan Moody’s work on defining a dual context lambda calculus for distributed computing, we design and implement a new programming language called Blues that statically guarantees that concurrency bugs such as data races, deadlocks, and spinlocks. We write an interpreter consisting of about 2500 lines of code in Rust, documenting our implementation details, and we show that the interpreter exhibits the correct type checking and evaluation behaviour. We then benchmark our interpreter and discuss the merits of the implementation in light of those results. We conclude with a short literature review discussing other related type systems. i Declaration I declare that the work in this dissertation was carried out in accordance with the requirements of the University’s Regulations and Code of Practice for Taught Programmes and that it has not been submitted for any other academic award. Except where indicated by specific reference in the text, this work is my own work. Work done in collaboration with, or with the assistance of others, is indicated as such. I have identified all material in this dissertation which is not my own work through appropriate referencing and acknowledgement. Where I have quoted or otherwise incorporated material which is the work of others, I have included the source in the references. Any views expressed in the dissertation, other than referenced material, are those of the author. Joshua S. Gawley, Thursday 2nd May, 2024 ii Contents 1 Introduction 1 2 Background 2.1 Modal logic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Modal types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3 The dual context lambda calculus . . . . . . . . . . . . . . . . . . . . . . . . 2.4 Note on terminology . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 2 3 3 6 3 The Blues language 3.1 Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Statics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Dynamics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4 Example programs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 7 7 9 11 4 Implementation 4.1 Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.1.1 What kind of interpreter? . . . . . . . . . . . . . . . . . . . . . . . . 4.1.2 High-level interpreter design . . . . . . . . . . . . . . . . . . . . . . 4.2 Language choice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3 Basic structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Rust traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.4 Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5 Type checking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5.1 Type checking diagnostics . . . . . . . . . . . . . . . . . . . . . . . . 4.6 Evaluating expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6.1 Evaluation error handling . . . . . . . . . . . . . . . . . . . . . . . . 4.7 Putting it all together . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 13 13 13 14 14 17 18 21 27 31 34 35 5 Benchmarks 5.1 Flame graphs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Restricting parallelism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 39 43 6 Evaluation 6.1 Related work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Future work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 47 47 iii 7 Conclusion 49 iv List of Figures 2.1 2.2 2.3 Definition of Moody’s dual context calculus . . . . . . . . . . . . . . . . . . Typing rules for the dual context calculus . . . . . . . . . . . . . . . . . . . . Evaluation rules for the dual context calculus . . . . . . . . . . . . . . . . . . 4 5 6 3.1 Formal grammar of Blues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Typing rules for the blues language . . . . . . . . . . . . . . . . . . . . . . . 3.3 Evaluation rules of the Blues languages . . . . . . . . . . . . . . . . . . . . . 3.4 fib.b7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.5 fac.b7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6 Examples of serial programs . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.7 Example program outputs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.8 parfib.b7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.9 parfac.b7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.10 Examples of parallel programs . . . . . . . . . . . . . . . . . . . . . . . . . . 8 9 10 11 11 11 11 12 12 12 4.1 Definition of Term . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Definition of Term . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3 Definition of Abstraction . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.4 Definition of Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.5 Definition of Value . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6 Definition of Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.7 Implementing PartialEq on Type . . . . . . . . . . . . . . . . . . . . . . . . 4.8 Pest grammar for types of the Blues language . . . . . . . . . . . . . . . . . 4.9 Rust code accompanying Listing 4.8 . . . . . . . . . . . . . . . . . . . . . . . 4.10 Definition of the Context struct . . . . . . . . . . . . . . . . . . . . . . . . . 4.11 Definition of Context::type_of . . . . . . . . . . . . . . . . . . . . . . . . . 4.12 Type checking let, mlet, and let box . . . . . . . . . . . . . . . . . . . . . . . 4.13 Definition of Context::bind_pattern . . . . . . . . . . . . . . . . . . . . . 4.14 Definition of WhichContext . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.15 Definition of Context::has_local_deps . . . . . . . . . . . . . . . . . . . . 4.16 Program which tries to compare an Int with a Bool . . . . . . . . . . . . . . 4.17 Output of running the program in Figure 4.16 . . . . . . . . . . . . . . . . . 4.18 Type checking error types . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.19 Traits for error handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.20 Implementing various conversion traits on PatternError . . . . . . . . . . 15 15 16 16 17 17 18 19 20 22 22 23 24 25 26 27 27 28 29 29 v 4.21 Implementing Reportable on PatternError . . . . . . . . . . . . . . . . . 4.22 Definition of Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.23 Definition of Environment::eval . . . . . . . . . . . . . . . . . . . . . . . . 4.24 Evaluating let and mlet expressions . . . . . . . . . . . . . . . . . . . . . . . 4.25 Definition of Context::bind_and_eval . . . . . . . . . . . . . . . . . . . . 4.26 Definition of Context::bind_pattern . . . . . . . . . . . . . . . . . . . . . 4.27 Evaluating let box terms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.28 Evaluating fix and mfix terms . . . . . . . . . . . . . . . . . . . . . . . . . . 4.29 Definition of main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.30 Definition of eval . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.31 Definition of type_check . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 31 31 32 32 33 34 35 36 37 38 5.1 Benchmarking fac.b7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Benchmarking parfac.b7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Benchmarking fib.b7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.4 Benchmarking parfac.b7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5 Benchmarking factorial and Fibonacci functions . . . . . . . . . . . . . . . . 5.6 Flame graph after running fib.b7 . . . . . . . . . . . . . . . . . . . . . . . . . 5.7 Flame graph after running parfib.b7 . . . . . . . . . . . . . . . . . . . . . . . 5.8 parfib_improved.b7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.9 Benchmarking parfib_improved.b7 . . . . . . . . . . . . . . . . . . . . . . . . 5.10 Flame graph after running fib.b7 . . . . . . . . . . . . . . . . . . . . . . . . . 40 40 40 40 40 41 42 43 43 45 vi Ethics statement This project did not require ethical review, as determined by my supervisor, Alex Kavvos. vii 1 Introduction Parallel and distributed computing have become more and more prominent over the past twenty years due to the advent of multicore processors [1]. However, parallel programming can often be challenging, as the programmer has to contend with new kinds of bugs such as data races, deadlocks, and spinlocks [2]. Hence, an area of interest for programming language research has been developing type systems which encode some notation of ‘safe’ parallel computation so as to reduce or eliminate the possibility of these bugs. Jonathan Moody proposed one such theory in 2005, where he used modal logic to represent mobility and locality [3]. He introduced a dual context lambda calculus whose type system statically guarantees that concurrency bugs like those above do not occur. Moody also defined a distributed abstract machine representing the concurrent execution of the calculus across multiple processes at different locations. Furthermore, in 2023, Joseph Eastoe proved that Moody’s proposed dual context calculus and distributed abstract machine are computationally equivalent [4]. The aim of this dissertation is to build on this work and provide a minimal implementation of Moody’s work and then test its real world viability. We present a programming language called Blues1 which implements (in part) Moody’s work. We then write and benchmark some example programs, seeing if Moody’s means of encoding parallel computation results in performant code. We conclude with a short literature review of other related type systems. 1 The author originally named the language ‘Moody’, but realizing the inevitable ambiguity this would cause, he changed the name to ‘Blues’ in reference to the English rock band ‘The Moody Blues’. 1 2 Background We now provide some basic background into the theory underlying this dissertation, roughly following Eastoe [4]. 2.1 Modal logic Modal logic extends propositional logic by introducing the notions of necessity and possibility [5]. Given a proposition π΄, we can express “it is necessary that π΄ is true” by β‘π΄, and similarly we can express “it is possible that π΄ is true” by ♦π΄. The truth value of a modal formula is given relative to a particular world 1 ; we say that β‘π΄ is true at a world π€ if and only if π΄ is true in every world accessible from π€, and similarly “♦π΄ is true at a world π€ if and only if π΄ is true in some world accessible from π€”. We could have that a formula is true in some worlds but not in others; for example, take π΄ to be “It is raining today”, π€ to be Bristol, and π£ to be Canterbury. It could be the case that it is raining in Bristol today but not in Canterbury (as it happens this is not an uncommon occurrence), so π΄ would be true in the world π€ but false in the world π£. A natural question at this point is “How do we define which worlds are accessible from a given world π€?”. We do this by defining an accessibility relation π which we can endow with certain properties, thereby generating new modal logic systems. Three examples of such systems are K, S4, and S5. K can be thought of as “the smallest modal logic system where we can deduce every valid modal formula”; S4 adds the additional requirement that the accessibility relation π be reflexive and transitive; and S5 additionally requires that π be symmetric (so in S5, the accessibility relation is an equivalence relation on worlds). Note therefore that every formula that is deducible in K is deducible in S4, and every formula that is deducible in S4 is deducible in S5 (but not the converse). 1 The semantics we use is called ‘Kripke semantics’ and is the standard semantics used for modal logic systems. 2 2.2 Modal types 2.2 Modal types The Curry-Howard correspondence describes the relationship between propositional logic2 and the simply-typed lambda calculus: propositions correspond to types and the proofs of these propositions correspond to programs that output a value of a particular type [6]. This relationship has proven exceptionally fruitful in the history of programming languages research as advances in formal logic can be immediately leveraged to create new type systems. One such advance came from Davies and Pfenning [7], who extended the Curry-Howard correspondence from propositional logic to S4 and, using this extension, proposed a lambda calculus using modal types to express staged computation3 . This calculus was innovative in the sense that it used two typing contexts, an ‘ordinary’ context and a ‘modal’ context; terms of type A would reside in the ‘ordinary’ context whilst terms of type β‘π΄ would reside in the ‘modal’ context. In this type system, each world corresponds to a stage of computation, and terms of type β‘π΄ can be executed in future stages of computation. Building on this work, Moody proposed a very similar type system which allows for safe distributed computation [3]. In this type system, each world corresponds to a site of computation, and terms of type β‘π΄ can be executed at any site of computation. Moody then gave an operational semantics formalizing this interpretation and proved that the type system satisfies progress and preservation, and thus satisfies type safety. We can interpret these ‘sites’ of computation as threads and thus arrive at a type system which we can use to write multithreaded programs that are statically guaranteed to not just thread safe, but also bug free in the sense described in Chapter 1. 2.3 The dual context lambda calculus We now present a modified version of Moody’s dual context lambda calculus that represents the theoretical basis of the Blues language, following Eastoe, Pierce, and Moody [3], [4], [9]. Note that the calculus presented here represents a subset of the functionality we implement in the Blues language; we provide the full typing and evaluation rules in the appendix. The definition of terms, types, and values are given in Figure 2.1. The typing judgement is of the form Δ; Γ β’ π‘ βΆ π΄ 2 Strictly speaking, we use intuitionistic logic, where logical sentences are only considered ’true’ if there exists a proof of its truth. 3 Staged computation is a computation that proceeds as a sequence of multiple stages, in which each stage produces the code for the next stage [8]. Example applications include run-time code generation. 3 2.3 The dual context lambda calculus where Δ, Γ are the local and global contexts respectively and π‘ is a term of type π΄4 . We assume that no variables are duplicated across contexts; if π₯ appears in the local context Γ, it cannot also appear in the global context Δ, and vice versa. We also assume that all new variables are different to all previous variables; if a new variable is the same as an existing variable, we can just πΌ-rename the new variable to preserve uniqueness. π‘1 , π‘ 2 βΆβΆ= π₯ | ππ₯ βΆ π . π‘1 | (π‘1 ) π‘2 | let π₯ = π‘1 in π‘2 | fix π‘1 | mfix π‘1 | box π‘1 | let box π₯ = π‘1 in π‘2 variables function function application let binding fixed point of M mobile fixed point of M box let-box binding Grammar 2.1: Terms Δ, Γ βΆβΆ= ∅ | Δ; Γ, π₯ βΆ π | Δ, π’ βΆ π ; Γ empty context local context extension mobile context extension Grammar 2.2: Contexts π βΆβΆ= π | ππ₯ βΆ π .π | box π π numeric value function value mobile value Grammar 2.3: Values βΆβΆ= | | Int π →π β‘π numbers function type mobile type Grammar 2.4: Types Fig. 2.1: Definition of Moody’s dual context calculus The typing rules are given in Figure 2.2. A variable can derive its type from either the local context using Var or the mobile context using MVar. We can “mobilize” terms of type π that only depend on the mobile context Γ using Box; we then obtain a term of type β‘π. Let and MLet are very similar; first, we evaluate π‘1 and then bind the resultant value to π₯ in the term π‘2 . The difference is that in a Let term, π₯ is added to the local context, whereas in MLet, π₯ is added to the global context. Fix provide the ability to recurse over functions taking local parameters, and MFix is the analogous rule for recursing over functions which take 4 We use Pierce’s convention for terms instead of Moody’s and Eastoe’s, so π‘1 , π‘2 , etc. are terms. 4 2.3 The dual context lambda calculus mobile parameters [10]. LetBox allows us to “demobilize” terms of type β‘π and use them in computation. Int π∈β€ Var MVar Abs Γβ’π₯βΆπ Δ; Γ β’ π’ βΆ π Γ β’ ππ₯ βΆ π΄ . π βΆ π΄ → π΅ π₯βΆπ ∈Γ Γ β’ π βΆ Int App Γ β’ π‘1 βΆ π΄ → π΅ π’βΆπ ∈Δ Let Γ β’ π‘2 βΆ π΄ Γ β’ π‘1 βΆ π΄ Γ β’ π‘1 (π‘2 ) βΆ π΅ MLet Δ; ⋅ β’ π‘1 βΆ π΄ Γ, π₯ βΆ π΄ β’ π βΆ π΅ Γ, π₯ βΆ π΄ β’ π‘2 βΆ π΅ Γ β’ let π₯ = π‘1 in π‘2 Δ, π₯ βΆ π΄ β’ π‘2 βΆ π΅ Δ β’ mlet π₯ = π‘1 in π‘2 Fix Δ; Γ π₯ βΆ π β’ π‘ βΆ π Δ; Γ β’ fix (π₯ βΆ π ). π‘ βΆ π MFix Box Δ; Γ β’ mfix (π’ βΆ π ).π‘ βΆ π Δ; Γ β’ box π βΆ β‘π΄ Δ, π’ βΆ π ; ⋅ β’ π‘ βΆ π LetBox Δ; ⋅ β’ π βΆ π΄ Δ; Γ β’ π‘1 βΆ β‘π΄ Δ, π’ βΆ π΄; Γ β’ π‘2 βΆ πΆ Δ; Γ β’ let box π’ = π‘1 in π‘2 βΆ πΆ Fig. 2.2: Typing rules for the dual context calculus The evaluation rules of the calculus are given in Figure 2.3. Like Eastoe, we present the evaluation rules in terms of evaluation contexts in the manner of Felleisen and Hieb [11] to avoid an excess of evaluation rules. πΈ βΆβΆ= | | | | | | | [] πΈ(π ) π (πΈ) let π₯ = πΈ in π‘2 mlet π₯ = πΈ in π‘2 fix πΈ mfix πΈ let box π’ = πΈ in π‘2 Grammar 2.5: Evaluation contexts for the dual context calculus 5 2.4 Note on terminology Val-Num Val-Abs Val-Abs D-Beta π val ππ₯ βΆ π .π‘1 βΆ π΄ val box π‘ πval (ππ₯ βΆ π .π‘1 βΆ π΄)(π‘2 ) βΌ π‘1 [π‘2 /π₯] π∈β€ D-LetBeta D-MLetBeta let π₯ = π‘1 in π‘2 βΌ π‘2 [π‘1 /π₯] mlet π₯ = π‘1 in π‘2 βΌ π‘2 [π‘1 /π₯] D-BoxBeta D-FixBeta let box π’ = π‘1 in π‘2 βΌ π‘2 [π‘1 /π₯] fix (ππ₯ βΆ π .π‘1 βΆ π΄) βΌ π‘1 [fix (ππ₯ βΆ π .π‘1 βΆ π΄)/π₯] D-MFixBeta D-Eval mfix (ππ₯ βΆ π .π‘1 βΆ π΄) βΌ π‘1 [mfix (ππ₯ βΆ π .π‘1 βΆ π΄)/π₯] πΈ(π) βΌ πΈ(π ′ ) π β¦ π′ Fig. 2.3: Evaluation rules for the dual context calculus The evaluation rules are easy to spell out; integers, functions, and mobile terms are values by the various Val-* rules. The various D-*Beta rules lay out that using let, mlet, fix, and let box terms result in a substitution. let box is special in that the evaluation of π‘2 occurs in parallel, as we will discuss in Section 4.6. 2.4 Note on terminology In the literature, the type β‘π΄ is referred to variously as ‘modal’, ‘box’, and ‘mobile’. For clarity’s sake, in this dissertation we refer to β‘π΄ as ‘mobile’; this is more descriptive than ‘modal’ and will avoid confusion with similarly named constructs such as Rust’s Box<T> construct later. We call the typing context Γ ‘local’, and the typing context Δ ‘global’ or sometimes ‘mobile’. Code will be referred to as ‘mobile’, and non-mobile code that can be made mobile (ie, does not depend on values in the local context) will be referred to as ‘mobilizable’. 6 3 The Blues language We now discuss the design of the Blues programming language, which is the main object of interest in this dissertation. 3.1 Syntax A Blues program is a sequence of term declarations and type declarations. π βΆβΆ= π; π term declaration | π;π type declaration | HALT end of program Grammar 3.1: Structure of a Blues program We give the formal definitions of terms, types, and values below in Figure 3.1. For the most part, the language is based off the simply-typed lambda calculus as detailed in Pierce [9], with a minimal mobile fragment via Moody and Eastoe [3], [4]. We include a number of simple extensions from Pierce; such as base types Int, Bool, and Unit; and tuple and variant types. We also provide built-in support for various arithmetic operators and boolean operators, as well as pattern matching on tuples and variants. The author believes that these extensions are compatible with the mobile fragment as-is; however, we do not provide a proof of this conjecture. 3.2 Statics We present an abridged version of the typing rules of the Blues language in Figure 3.2. These are derived from the dual context calculus seen in Section 2.3, with extensions from Pierce [9]. 7 3.2 Statics π‘1 , π‘ 2 βΆβΆ= | | | | | | | | | | | | | | | | | | | | true false π π₯ ππ₯ βΆ π .π‘ (π‘1 π‘2 ) if π‘1 then π‘2 else π‘3 op π‘ π‘1 op π‘2 π‘ op π‘ as π let π = π‘1 in π‘2 mlet π = π‘1 in π‘2 (π‘ππ∈1…π ) π‘.π β¨π = π‘β© as π match π with β¨ ππ = π‘π β© ⇒ πππ∈1…π fix π‘ mfix π‘ box π‘ let box π = box π‘1 in π‘2 constant true constant false numbers variables function function application conditional prefix operator infix operator postfix operator ascription let binding mobile let binding tuple tuple projection tagging match fixed point of M mobile fixed point of M box let-box binding Grammar 3.2: Terms π£ βΆβΆ= | | | | | | true false unit π ππ₯ βΆ π .π‘ (π£ π∈1…π ) β¨ππ βΆ π£ππ∈1…π β© π true value false value unit value numeric value function value tuple value value of variants βΆβΆ= | | | | | | Grammar 3.3: Values π Int Bool Unit π →π (π π∈1…π ) β¨ππ βΆ πππ∈1…π β© β‘π numbers booleans unit type function type tuple type type of variants mobile type Grammar 3.4: Types βΆβΆ= π₯ variable pattern π∈1…π | (ππ ) tuple pattern | _ wildcard pattern Grammar 3.5: Patterns Fig. 3.1: Formal grammar of Blues 8 3.3 Dynamics True False Unit Var Γ β’ π βΆ Int true βΆ Bool false βΆ Bool unit βΆ Unit Γβ’π₯βΆπ MVar Abs Γβ’π βΆπ΄→π΅ Γβ’π βΆπ΄ Δ; Γ β’ π’ βΆ π Γ β’ ππ₯ βΆ π΄ . π βΆ π΄ → π΅ Int π∈β€ π’βΆπ ∈Δ Let Γ β’ π‘1 βΆ π΄ App Γ, π₯ βΆ π΄ β’ π βΆ π΅ Γ β’ π(π ) βΆ π΅ MLet Γ, π₯ βΆ π΄ β’ π‘2 βΆ π΅ Δ; ⋅ β’ π‘1 βΆ π΄ Γ β’ let π₯ = π‘1 in π‘2 Fix Δ; Γ π₯ βΆ π β’ π‘ βΆ π Proj MFix Tuple Δ; Γ β’ mfix (π’ βΆ π ).π‘ βΆ π Γ β’ (π‘ππ∈1…π ) βΆ (πππ∈1…π ) If Γ β’ π‘1 βΆ (πππ∈1…π ) π₯1 βΆ Bool Γ β’ π‘1 .π βΆ ππ Δ, π₯ βΆ π΄ β’ π‘2 βΆ π΅ Δ β’ mlet π₯ = π‘1 in π‘2 Δ, π’ βΆ π ; ⋅ β’ π‘ βΆ π Δ; Γ β’ fix (π₯ βΆ π ). π‘ βΆ π π₯βΆπ ∈Γ π‘2 βΆ π for each π π‘3 βΆ π if π₯1 then π‘2 else π‘3 βΆ π Box Δ; ⋅ β’ π βΆ π΄ Δ; Γ β’ box π βΆ β‘π΄ LetBox Δ; Γ β’ π‘1 βΆ β‘π΄ Γ β’ π‘π βΆ π π Ascribe Γβ’π₯βΆπ Γ β’ π₯ as π βΆ π Δ, π’ βΆ π΄; Γ β’ π‘2 βΆ πΆ Δ; Γ β’ let box π’ = π‘1 in π‘2 βΆ πΆ Fig. 3.2: Typing rules for the blues language 3.3 Dynamics We present an abridged version of the evaluation rules of the Blues language, again derived from Section 2.3 and Pierce. As previously, we present the rules in terms of evaluation contexts, defined in Grammar 2.5 9 3.3 Dynamics πΈ βΆβΆ= | | | | | | | | | | | [] πΈ(π ) π (πΈ) let π = πΈ in π‘2 mlet π = πΈ in π‘2 fix πΈ mfix πΈ if πΈ then π‘1 else π‘2 let box π’ = πΈ in π‘2 πΈ as π πΈ.π π∈1…π−1 (π£π π∈π+1…π , πΈ, π£π ) Grammar 3.6: Evaluation contexts for the Blues language Val-Num Val-Abs Val-Abs D-Beta π val ππ₯ βΆ π .π‘1 βΆ π΄ val box π‘ πval (ππ₯ βΆ π .π‘1 βΆ π΄)(π‘2 ) βΌ π‘1 [π‘2 /π₯] π∈β€ D-LetBeta D-MLetBeta let π₯ = π‘1 in π‘2 βΌ π‘2 [π‘1 /π₯] mlet π₯ = π‘1 in π‘2 βΌ π‘2 [π‘1 /π₯] D-BoxBeta D-FixBeta let box π’ = π‘1 in π‘2 βΌ π‘2 [π‘1 /π₯] fix (ππ₯ βΆ π .π‘1 βΆ π΄) βΌ π‘1 [fix (ππ₯ βΆ π .π‘1 βΆ π΄)/π₯] D-MFixBeta D-ProjTuple mfix (ππ₯ βΆ π .π‘1 βΆ π΄) βΌ π‘1 [mfix (ππ₯ βΆ π .π‘1 βΆ π΄)/π₯] (π£ππ∈1…π ).π βΌ π£π D-Eval π β¦ π′ πΈ(π) βΌ πΈ(π ′ ) Fig. 3.3: Evaluation rules of the Blues languages 10 3.4 Example programs 3.4 Example programs We now look at a number of example programs in the Blues language. let fac = fix fac: Int -> Int => n: Int => if (n < 2) then 1 else n * fac (n - 1) let fib = fix fib: Int -> Int => n: Int => if (n < 2) then n else fib (n - 1) + fib (n - 2) let main = _: () => let x = fac 25 in x let main = fib 25 Fig. 3.5: fac.b7 Fig. 3.4: fib.b7 Fig. 3.6: Examples of serial programs In Figure 3.6, we see programs that calculate the 25th Fibonacci and factorial numbers. In Figure 3.5, we define fac to be the fixed point of another function also called fac, which then calculates the factorial number as normal. A slight quirk of the Blues language is how main is handled. If main is a variable, then the value of the expression binded to main alongside its type will be printed to console. If main is a function, then the last value calculated within the function will be printed to console. In Figure 3.7, we see the results of running the programs in Figure 3.6. $ b7 fib.b7 75025: Int $ b7 fac.b7 2432902008176640000 Fig. 3.7: Example program outputs Figure 3.10 shows parallel versions of the programs seen in Figure 3.6. 11 3.4 Example programs let parfac = mfix fac: []Int -> Int => n: []Int => let box u = n in if (u < 2) then u else let box v = box (fac (box (u - 1))) in (u * v) let main = parfac (box 25) Fig. 3.8: parfib.b7 let parfib = mfix fib: []Int -> Int => n: []Int => let box u = n in if (u < 2) then u else let box a = box (fib (box (u - 1))) in let box b = box (fib (box (u - 2))) in (a + b) let main = parfib (box 25) Fig. 3.9: parfac.b7 Fig. 3.10: Examples of parallel programs We go through Figure 3.9. First, as we want to use let box to evaluate in parallel, parfac takes a mobile integer π βΆ β‘Int as input rather than a plain integer. We then use let box on π to “demobilize” π so we can use the standard arithmetic and comparison operators; π’ is the resulting variable. The rest of the function is very similar to fac, except for the recursive case we use another let box to evaluate in parallel. We have to use box on π’ because π’ is of type Int, being the demobilized dual of π, and similarly we have to use box on the output of fac because fac returns a term of type Int rather than β‘Int. 12 4 Implementation 4.1 Overview 4.1.1 What kind of interpreter? As discussed in Section 2.3, Moody proposed both a dual context lambda calculus and a distributed abstract machine, which Eastoe showed to be computationally equivalent. Hence, we can choose which one we shall implement. Both options map well to well-trodden implementation options; after we parse program code into an abstract syntax tree (AST), we can either implement the dual context calculus by walking the AST or we can implement the distributed abstract machine by means of a bytecode interpreter. A tree-walk interpreter is easier to implement as we can translate the typing and evaluation rules directly into code using pattern matching (if our implementation language supports it), whereas we would have to decide on a bytecode format for our distributed abstract machine and deal with concerns such as distributed garbage collection. However, a bytecode interpreter would likely be more performant than a tree-walk interpreter and would additionally allow more scope for optimization in the future [12]. Due to time constraints, we decided to go down the tree-walking route for this implementation, but future work could certainly include implementing the distributed abstract machine and comparing its performance to our present implementation. 4.1.2 High-level interpreter design We follow Nystrome in separating our implementation into parsing and then evaluating via walking the AST [12]. However, since Blues is statically typed, we insert an additional type checking phase in between the parsing and evaluation stages. 13 4.2 Language choice 4.2 Language choice We now consider the choice of programming we use to implement the interpreter. Our requirements for our implementation language are that it supports parallel programming in a relatively straightforward fashion and that it supports sum types and pattern matching on those types (this will allow us to write code that mirrors the formal language definition found in Chapter 3. After some initial research, two languages stood out as viable candidates: Rust and Scala. Scala [13] is a multi-paradigm programming language supporting both object-oriented and functional programming which typically runs on the Java Virtual Machine (JVM). Scala has an expressive type system, supporting algebraic date types [14], and also supports parallel programming via either Java threads or Scala futures. Rust [15] is a systems programming language that aims to overcome the tradeoff between highlevel safety guarantees and low-level control over resource management [16]. Rust enforces memory and thread safety through leveraging its powerful type system and ownership model; for example, the ownership model of Rust ensures that data can only be accessed by one thread [17]. In addition, Rust supports ‘fearless concurrency’ [18]; its standard library includes support for native threads and libraries such as Rayon, Tokio, and Crossbeam [19]–[21] offer support for parallel processing, asynchronous programming, and concurrency primitives respectively. Whilst both languages support the features we need, we note that Rust is a compiled language unlike Scala, and the author has previous experience with programming in Rust but none in Scala. Given the challenges of this project, we decided to implement the Blues language using Rust as the implementation language. 4.3 Basic structures We only look at important extracts of code in this dissertation; the full source code can be found at https://github.com/joshuagawley/blues. The definition of Program and Declaration (Figure 4.1) follow the formal definitions in Grammar 3.1. The main type for the abstract syntax tree is Term (Figure 4.2), which represents a node of the AST and mirrors the abstract definition of a term found in Grammar 3.2. 14 4.3 Basic structures pub struct Program(pub Vec<Declaration>); #[derive(Clone, Debug)] pub enum Declaration { Term(Pattern, Term), Type(String, Type), } Fig. 4.1: Definition of Term #[derive(Clone, Debug)] pub enum Term { Abstraction(Abstraction, Span), Application(Arc<Term>, Arc<Term>, Span), Ascription(Arc<Term>, Type, Span), Bool(bool, Span), Box(Arc<Term>, Span), Fix(Arc<Term>, Span), MFix(Arc<Term>, Span), If(Arc<Term>, Arc<Term>, Arc<Term>, Span), Int(i64, Span), Infix(Arc<Term>, Infix, Arc<Term>, Span), Let(Pattern, Arc<Term>, Arc<Term>, Span), MLet(Pattern, Arc<Term>, Arc<Term>, Span), LetBox(Pattern, Arc<Term>, Arc<Term>, Span), Match(Arc<Term>, IndexMap<String, (Pattern, Term)>, Span), #[allow(dead_code)] Postfix(Arc<Term>, Postfix, Span), Prefix(Prefix, Arc<Term>, Span), Tuple(Vec<Term>, Span), TupleProjection(Arc<Term>, usize, Span), Unit(Span), Variable(String, Span), Variant(String, Arc<Term>, Span), } Fig. 4.2: Definition of Term Term is declared as a Rust enum; this is how we declare sum types in Rust. Every variant contains a Span; this is a struct that stores the location in the source file of a certain term or type for more informative error messages later in the type checker. In addition, since Term is a recursive type; most variants contain one or more Arc<Term>, which are references to other instances of Term. Note that when defining a match term, we use an IndexMap rather than a HashMap; IndexMap preserves the order in which elements are inserted [22] so when we come to evaluating match statements, we can use a for loop to iterate over each case whilst still retaining correctness. 15 4.3 Basic structures Recursive fields of a type in Rust must be pointer types as otherwise the type would have infinite size [23]. There are multiple types of smart pointer1 types, such as Box<T>, Rc<T>, and Arc<T>, which are owned types which allocate a value of type T on the heap. Box<T> provides unique ownership of a value of type T; this means that calling clone() on an instance of Box<T> causes a full copy of the enclosed object. Alternatively, Rc<T> uses reference counting to keep track of how many references exist of an object, so calling clone() merely returns a reference to the enclosed object rather than performing a memory allocation. However, Rc<T> is not thread-safe as its implementation of reference counting uses non-atomic operations, and thus Rc<T> is unsuitable for use in our implementation. Arc<T>, like Rc<T> is a type which uses atomic reference counting and thus is thread-safe. We use Arc<T> in in our definition of Term because in preliminary versions of the interpreter, we found that Box<T> gave very poor performance due to clones causing deep copies and thus a lot of memory allocations, whose frequency is reduced by using reference counting. Abstractions are both terms and values, so we define abstractions as a separate type. #[derive(Clone, Debug)] pub struct Abstraction { pub param: Pattern, pub param_type: Type, pub body: Arc<Term>, } Fig. 4.3: Definition of Abstraction Type is defined similarly to Term, again mirroring the formal definition found in Grammar 3.4. #[derive(Clone, Debug)] pub enum Type { Abstraction(Span, Box<Type>, Box<Type>), Bool(Span), Int(Span), Tuple(Span, Vec<Type>), Variable(Span, String), // IndexMap preserves the order in which (String, Type) pairs are inserted Variant(Span, IndexMap<String, Type>), Unit(Span), Mobile(Span, Box<Type>), } Fig. 4.4: Definition of Type 1 ‘Smart pointer’ types simulate raw pointer types but offer additional features such as automatically handing allocations and deallocations, reference counting, etc. [24]. 16 4.3 Basic structures We use Box<T> in this recursive type definition rather than Arc<T> here because types are only constructed for type checking purposes and not checked during evaluation, so we do not need either shared ownership or multithreaded support (although Box<T> is thread safe). Also note that we use IndexMap rather than HashMap for variant types for the same reason as when we used IndexMap when defining match terms in Figure 4.2. The definitions of Value and Pattern again mirrors the definitions seen in Grammars 3.3 and 3.5. #[derive(Clone, Debug)] pub enum Value { Abstraction(Abstraction, Environment), Bool(bool), Int(i64), Tuple(Vec<Value>), Unit, Variant { label: String, value: Box<Value> }, } #[derive(Clone, Debug)] pub enum Pattern { Tuple(Span, Vec<Pattern>), Variable(Span, String), Wildcard(Span), } Fig. 4.5: Definition of Value Fig. 4.6: Definition of Pattern Note that for function types, we associate an Environment, which in this case stores the values of variables already evaluated (see Section 4.6 for more details). 4.3.1 Rust traits We implement a number of traits on these types; traits are roughly analogous to typeclasses in Haskell in that a trait specifies behaviour shared by multiple types [25]. For example, implementing the Clone trait on Term allows us to make a deep copy of a value of type Term using arbitrary operations. Some traits offer similar functionality but are meant for different purposes; for example, both Debug and Display produce a String representation of a type. However, Debug is meant for programmer-facing output and can be derived (as seen above), whereas Display is meant for user-facing input and thus cannot be derived; the programmer has to provide a custom implementation. We generally prefer to use derive macros when possible as this reduces boilerplate, but at times we implement traits ourselves to ensure correct behaviour. For example, in Figure 4.7, we see our custom implementation of PartialEq on Type where we disregard spans; a derived implementation of PartialEq on Type would check additionally 17 4.4 Parsing that spans are equivalent which is clearly not correct. impl PartialEq for Type { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Abstraction(_, l1, l2), Self::Abstraction(_, r1, r2)) => { l1 == r1 && l2 == r2 } (Self::Bool(_), Self::Bool(_)) => true, (Self::Int(_), Self::Int(_)) => true, (Self::Unit(_), Self::Unit(_)) => true, (Self::Tuple(_, l1), Self::Tuple(_, r1)) => l1 == r1, (Self::Variable(_, l1), right) => match l1.as_str() { "Int" => right.is_int(), "Bool" => right.is_bool(), "Unit" => right.is_tuple(), _ => match right { Self::Variable(_, r1) => l1 == r1, _ => false, }, }, (left, Self::Variable(_, r1)) => match r1.as_str() { "Int" => left.is_int(), "Bool" => left.is_bool(), "Unit" => left.is_tuple(), _ => match left { Self::Variable(_, l1) => l1 == r1, _ => false, }, }, (Self::Variant(_, l1), Self::Variant(_, r1)) => l1 == r1, (Self::Mobile(_, l1), Self::Mobile(_, r1)) => l1 == r1, _ => false, } } } Fig. 4.7: Implementing PartialEq on Type 4.4 Parsing After attempting to write a parser by hand, we eventually decided to use a parser generator. There are multiple parser generators available for Rust such as LALRPOP [26] and Pest [27], but we decided to use Pest due to its ease of use: it does not require a custom build step (unlike LALRPOP), the grammar syntax corresponds directly to BNF, and the code written to turn the Pest grammar representation into a strongly typed abstract syntax tree is very readable. 18 4.4 Parsing Parsers implemented using Pest consist of a grammar file alongside Rust code that converts the grammar into an abstract syntax tree, as shown in Listings 4.8 and 4.9. The grammar provided to Pest is a parsing expression grammar (PEG), which is a formal grammar that looks similar to a context free grammar (CFG). However, unlike CFGs, PEGs use a prioritized choice operator; each pattern is tested in left-to-right order until a successful match is found. This eliminates any ambiguity from the parser. Because PEGs are completely unambiguous, we can parse PEGs using recursive descent parsers. The kind of parser that Pest provides for this is a Pratt parser, which associates each operator with a certain binding power [28]. A higher binding power corresponds with a higher precedence, so for example, multiplication should have a higher binding power than addition (as multiplication comes before addition in the order of operations). The Blues parser is made up of two sub-parsers, one for parsing terms and one for parsing types. This gives us the ability to easily extend the language through adding new type constructors and new kinds of terms in the future (for example, extending the language to support recursive types). We look at an abridged version of the type parser of the Blues language in Figures 4.8 and 4.9. type = { type_prefix? ~ primary_type ~ (type_infix ~ type_prefix? ~ primary_type)* } type_infix = _{ type_arrow } type_prefix = _{ box_type } type_arrow = { "->" } box_type = { "[]" } primary_type = _{ list_type | tuple_type | variant_type | ident } field_types = _{ field_type ~ ("," ~ field_type)* } field_type = { ident ~ ":" ~ type } list_type = { "[" ~ type ~ "]" } tuple_type = { "(" ~ types? ~ ")" } types = _{ type ~ ("," ~ type)* } variant_type = { "<" ~ field_types? ~ ">" } [...] ident = @{ keyword? ~ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_" | "'")* } keyword = { "_" | "as" | "box" | "else" | "false" | "fix" | "if" | "in" | "let" | "match" | "mfix" | "mlet" | "then" | "true" } Fig. 4.8: Pest grammar for types of the Blues language The top-most rule is type. A type consists of an optional type_prefix (? denotes an optional match), followed by a primary_type, then followed by zero or more subexpressions which consist of a type infix, then an optional type prefix, then a ‘primary type’ again (subexpressions are denoted in parentheses and * denotes that an expression should be matched zero or more times). We note that this construction of type is mandated by our use of a PrattParser [29]. 19 4.4 Parsing #[derive(Parser)] #[grammar = "parser/grammar.pest"] pub struct Parser { source_name: String, type_parser: PrattParser<Rule>, term_parser: PrattParser<Rule>, } impl Parser { fn parse_type(&self, pair: Pair<Rule>) -> Type { self.type_parser .map_primary(|pair| { let span = self.span_from(&pair); match pair.as_rule() { Rule::tuple_type => { [...] } Rule::variant_type => { [...] } Rule::ident => { [...] } _ => unreachable!("{pair}"), } }) .map_prefix(|op, right| { let span = right.span().join(&self.span_from(&op)).unwrap(); match op.as_rule() { Rule::box_type => Type::Mobile(span, Box::new(right)), _ => unreachable!("{op}"), } }) .map_infix(|left, op, right| { let span = left.span().join(&self.span_from(&op)).unwrap(); match op.as_rule() { Rule::type_arrow => { Type::Abstraction(span, Box::new(left), Box::new(right)) } _ => unreachable!(), } }) .parse(pair.into_inner()) } } impl Default for Parser { fn default() -> Self { let type_parser = PrattParser::new() .op(Op::infix(Rule::type_arrow, Assoc::Right)) .op(Op::prefix(Rule::box_type)); let term_parser = [...]; Self { source_name: "".to_owned(), type_parser, term_parser, } } } Fig. 4.9: Rust code accompanying Listing 4.8 20 4.5 Type checking Some rules such as type_prefix and type_infix have underscores before their definition. This indicates that these rules are silent; token pairs are not created for these rules during parsing, and they are not error reported. Using these silent rules allows us to easily add new infix and prefix operatiors for types in the future without changing the structure of the parser. Figure 4.9 shows the Rust code that converts Pest Rules into our definition of Type. The functions parse_primary, parse_prefix, and parse_infix take a closure as input. Closures are similar to functions but unlike functions, closures can capture values from the scope in which they’re defined [30]. In the case of parse_primary, parse_prefix, and parse_infix, the closures we pass map the Pest representation of types to our custom representation of Type. For example, the closure we pass into parse_prefix first gets the span of the whole type expression by joining the spans of the parsed prefix operator and the parsed primary type. Then, we pattern match on the Rule of the prefix operator; since we define one prefix operator on types, ‘[]’, we only check for the ‘mobile’ rule. Since Rules can be arbitrarily defined and so the compiler can’t determine that Rule::mobile_type is the only prefix operator we could parse, we use the unreachable!() macro in the fallthrough case to cause the program to panic in this case. Recall that Pratt parsers associate a binding power with each operator. The binding power of operators in a Pest PrattParser is determined by the order in which operators are added to a PrattParser; operators added later have a higher binding power. In the case of our type parser, we add the ‘mobile’ type prefix operator after the ‘type arrow’ infix operator, signifying that [] binds more closely than -> (so we parse “[]T -> T” as ([]T) -> T instead of [](T -> T)). The term parser uses the same patterns and strategy as the type parser; for reasons of brevity we omit a discussion of the term parser. Interested parties may view the source code for the term parser (and the entire type parser) here2 . 4.5 Type checking The type checker is the largest component of the interpreter, comprising around a quarter of the source lines of code (SLOC). Type-checking of each term is done using a Context, containing three maps of strings to types. types keeps track of user-supplied type definitions, and local_bindings and mobile_bindings keep tracks of variables in the local and mobile contexts respectively. 2 https://github.com/joshuagawley/blues/src/parser 21 4.5 Type checking #[derive(Clone, Default, Debug)] pub struct Context { types: HashMap<String, Type>, local_bindings: HashMap<String, Type>, mobile_bindings: HashMap<String, Type>, } Fig. 4.10: Definition of the Context struct The central type checking function is type_of, which takes a term as input and returns MaybeType (see Subsection 4.5.1). The type_of function is a mammoth match statement which implemented inverted versions of the typing rules seen in Grammar 3.4. impl Context { pub fn type_of(&mut self, term: &Term) -> MaybeType { match term { [...] } } } Fig. 4.11: Definition of Context::type_of We now look at some interesting cases: Figure 4.12 shows how we type check let, mlet, and let box terms. These three cases share the same structure; we resolve the type of value (the term we are going to substitute into body), then we make a copy of the current context, bind pattern to value in the new context, and then finally resolve the type of body. MLet and Let differ in the context we bind the pattern in (we bind to the local context in a let term whereas we bind to the mobile context in an mlet term). With LetBox, we check that the resolved type of value is indeed mobile and return an error diagostic if it is not, then we get the inner type of the resolved value type (this is the type after “demobilizing” value) and proceed identically to MLet. Note that since get_inner_type returns &Type, ie a reference to a Type, we have to change some of the code compared to the MLet case, notably cloning the resolved type of the body. 22 4.5 Type checking impl Context { pub fn type_of(&mut self, term: &Term) -> MaybeType { match term { [...] Term::MLet(pattern, value, body, _) => { let value_type = self.resolve_type_of(value)?; let mut context = self.clone(); context.bind_pattern(Mobile, pattern, term, &value_type)?; context.resolve_type_of(body) } Term::Let(pattern, value, body, _) => { let value_type = self.resolve_type_of(value)?; let mut context = self.clone(); context.bind_pattern(Local, pattern, term, &value_type)?; context.resolve_type_of(body) } Term::LetBox(pattern, value, body, span) => { let mobile_value_type = self.resolve_type_of(value)?; if !matches!(mobile_value_type, Type::Mobile(..)) { return Err(vec![TypeError::ExpectedModal( term.span().start(), span.clone(), mobile_value_type, ) .into()]); } let value_type = mobile_value_type.get_inner_type(); let mut context = self.clone(); context.bind_pattern(Mobile, pattern, term, value_type)?; Ok(context.resolve_type_of(body)?.clone()) } [...] } } } Fig. 4.12: Type checking let, mlet, and let box To handle both binding variable patterns and tuple patterns, we pull out the binding logic into a separate function bind_pattern (see Figure 4.13). For variable patterns, bind_pattern will just call insert() to create a binding in the requested context. For tuple patterns, bind_pattern checks that the type we want to bind is a tuple of the same length as the tuple pattern, and then we bind each resulting variable pattern using a recursive bind_pattern 23 4.5 Type checking impl Context { pub fn bind_pattern( &mut self, which_context: WhichContext, pattern: &Pattern, term: &Term, raw_type: &Type, ) -> Result<(), Errors> { let r#type = self.resolve(raw_type.clone())?; match (pattern, r#type) { (Pattern::Tuple(span, patterns), Type::Tuple(_, types)) => { if patterns.len() != types.len() { return Err(vec![PatternError::MissingElements { span: span.clone(), actual: patterns.len(), expected: types.len(), } .into()]); } for (pattern, r#type) in patterns.iter().zip(types.into_iter()) { self.bind_pattern(which_context, pattern, term, &r#type)?; } } (Pattern::Tuple(span, _), other_type) => { return Err(vec![PatternError::Incompatible { span: span.clone(), actual: pattern.clone(), expected: other_type, } .into()]); } (Pattern::Variable(_, name), value) => { self.insert(which_context, name.clone(), value) } (Pattern::Wildcard(_), _) => {} } Ok(()) } } Fig. 4.13: Definition of Context::bind_pattern For reasons of clarity, we use an auxiliary type called WhichContext which we pass into bind_pattern (and in turn insert) to determine which context we ought to bind a pattern in. 24 4.5 Type checking #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum WhichContext { Local, Mobile, } Fig. 4.14: Definition of WhichContext In the typing rules, we often need to check that an expression doesn’t have dependencies from the local context. Hence, we have a helper function has_local_deps which checks if a function has local dependencies. In the majority of cases, we simply check if subexpressions have local dependencies; the base cases are variables, base types, and fix and let. Base types, local fixed-points, and let binding are simple; we return false on base types since they have no dependencies; we also return true on let terms and fix since these constructions always bind to expressions to the local context. For variables, we check if the variable exists in the local_bindings map. In all other cases, we check if the subexpressions have local dependencies (this is especially important for mlet and mfix). 25 4.5 Type checking impl Context { fn has_local_deps(&self, term: &Term) -> bool { match term { Term::Variable(name, _) => self.get_local(name).is_some(), Term::Abstraction( Abstraction { param: _, param_type, body, }, _, ) => matches!(param_type, Type::Modal(..)) && self.has_local_deps(body), Term::Application(_, arg, _) => self.has_local_deps(arg), Term::Ascription(term, _, _) => self.has_local_deps(term), Term::Bool(..) => false, Term::Box(term, _) => self.has_local_deps(term), Term::Fix(..) => true, Term::MFix(term, _) => self.has_local_deps(term), Term::If(_guard, if_true, if_false, _) => { self.has_local_deps(if_true) && self.has_local_deps(if_false) } Term::Int(..) => false, Term::Infix(left, _, right, _) => { self.has_local_deps(left) && self.has_local_deps(right) } Term::MLet(_, value, body, _) => self.has_local_deps(value) && self.has_local_deps(body), Term::Let(..) => true, Term::LetBox(_, value, body, _) => { self.has_local_deps(value) && self.has_local_deps(body) } Term::Match(value, arms, _) => { self.has_local_deps(value) && arms.values().all(|(_, term)| self.has_local_deps(term)) } Term::Postfix(term, _, _) => self.has_local_deps(term), Term::Prefix(_, term, _) => self.has_local_deps(term), Term::Tuple(terms, _) => terms.iter().all(|term| self.has_local_deps(term)), Term::TupleProjection(tuple, _, _) => self.has_local_deps(tuple), Term::Unit(..) => false, Term::Variant(_, arms, _) => self.has_local_deps(arms), } } Fig. 4.15: Definition of Context::has_local_deps 26 4.5 Type checking 4.5.1 Type checking diagnostics Although an ancillary part of the interpreter, it is important that we give users helpful error messages at least at the type checking phase. For example, consider the following example program which tries to compare 42 to true. let x = 42 let y = true let main = if x < y then 0 else 1 Fig. 4.16: Program which tries to compare an Int with a Bool When we run this program, we get the following output on the console. Fig. 4.17: Output of running the program in Figure 4.16 We now explain how this is implemented. First, error handling in Rust is handled by the Result<T, E> type which is analogous to Maybe T in Haskell; Result<T, E> has two variants: Ok<T> representing success and containing a value of type T, and Err<E> representing failure and containing a value of type E [31]. As with any Rust enum, we can pattern match on Result<T, E> to handle the separate cases manually; however, we often use error propagation to let the callee handle the error case, using the ? operator Doing this iteratively can cause the error case to ‘bubble’ up to the entrypoint function main. Our entrypoint function main has return type anyhow::Result<()>; the success type of anyhow::Result<()> is the unit type () (so main returns nothing), and the error type is arbitrary. If we end up ‘returning’ an error type from main, then an error message will be printed with the backtrace of all the errors that caused the program to terminate. We define two kinds of type checking errors, one for patterns and one for types, as seen in Figure 4.18. 27 4.5 Type checking #[derive(Clone, Debug)] pub enum PatternError { Incompatible { span: Span, actual: Pattern, expected: Type, }, MissingElements { span: Span, actual: usize, expected: usize, }, } #[derive(Clone, Debug)] pub enum TypeError { BoxedExprHasLocalDeps(usize, Span, String), Mismatch { offset: usize, span: Span, expected: Type, actual: Type, }, VariableDefinedInBothContexts(usize, Span, String), MissingVariants(usize, Span, Vec<(String, Type)>), UndefinedVariable(usize, Span, String), UnknownField(usize, Span, String, Type), UnknownIndex { offset: usize, span: Span, index: usize, tuple_type: Type, }, ExpectedModal(usize, Span, Type), UnknownType(usize, Span, String), } Fig. 4.18: Type checking error types Each variant of TypeError contains an offset; this denotes the exact location of the type error within the expression containing the type error. We don’t need an additional offset field for variants of PatternError because the offset in a pattern error is simply the beginning of the problematic pattern. We then define some more error handling infrastructure in Figure 4.19. We define a new trait Reportable with a build_report method which allows us to write an Ariadne report, which represents a type diagnostic that is ready to be printed. We also define offset and span methods so we can refer to these when building our diagnostics. We then define Error as a type alias for Box<dyn Reportable>, which represents a reference to a type that implements 28 4.5 Type checking Reportable; this is an example of dynamic dispatch. We define Errors to be a Vector of Errors and then finally, MaybeType is a type alias for Result<Type, Errors>, either holding a Type or a list of error messages ready to be printed. use crate::parser::Span; use crate::syntax::r#type::Type; use ariadne::Report; use core::fmt::Debug; pub type Error = Box<dyn Reportable>; pub type Errors = Vec<Error>; pub type MaybeType = Result<Type, Errors>; pub trait Reportable: Debug { fn build_report(&self) -> Report<Span>; fn offset(&self) -> usize; fn span(&self) -> &Span; } Fig. 4.19: Traits for error handling Note that Reportable is a supertrait of the Debug trait; this means that any type that implements Reportable must also implement Debug. We do this because some methods of Result<T, E> such as unwrap() require the error type E to implement the Debug trait. impl From<PatternError> for super::Error { fn from(err: PatternError) -> Self { Box::new(err) } } impl From<PatternError> for super::Errors { fn from(err: PatternError) -> Self { vec![err.into()] } } impl<T> From<PatternError> for Result<T, super::Errors> { fn from(err: PatternError) -> Self { Err(err.into()) } } Fig. 4.20: Implementing various conversion traits on PatternError Figure 4.21 shows the implementation of Reportable on PatternError and Figure 4.20 shows implementations of various conversion traits on PatternError 29 4.5 Type checking impl Reportable for PatternError { fn build_report(&self) -> Report<Span> { let report = Report::<Span>::build( ReportKind::Error, self.span().source(), self.offset(), ); match self { Self::Incompatible { span, actual: _, expected, } => report.with_message("mismatched types").with_label( Label::new(span.clone()) .with_message(format!( "expected {}", expected.to_string().fg(Color::Green) )) .with_color(Color::Red), ), Self::MissingElements { span, actual, expected, } => report.with_message("mismatched types").with_label( Label::new(span.clone()) .with_message(format!( "expected a tuple with {} elements, found one with {} elements", expected.to_string().fg(Color::Green), actual.to_string().fg(Color::Red), )) .with_color(Color::Red), ), } .finish() } fn offset(&self) -> usize { match self { Self::Incompatible { span, .. } | Self::MissingElements { span, .. } => span.start(), } } fn span(&self) -> &Span { match self { Self::Incompatible { span, .. } | Self::MissingElements { span, .. } => span, } } } Fig. 4.21: Implementing Reportable on PatternError 30 4.6 Evaluating expressions 4.6 Evaluating expressions After type checking, the next step is evaluating expressions. As elsewhere, we only look at the implementation of mobile fragment, the full evaluation source code can be Evaluation takes place within the context of an Environment, which contains a map of strings to values (ie, the results of fully evaluated expressions). #[derive(Clone, Debug)] pub struct Environment { values: HashMap<String, Value>, } Fig. 4.22: Definition of Environment The majority of the evaluation logic takes place in the eval method of the Environment, which takes a Term as input and returns either a Value or an error message. We also pass our global thread pool so we can use it when evaluating let box expressions. impl Environment { pub fn eval(&mut self, threadpool: &ThreadPool, term: &Term) -> anyhow::Result<Value> { match term { [...] } } } Fig. 4.23: Definition of Environment::eval The majority of the expression evaluating code is located in the eval method of the Environment struct, which takes a Term as input and outputs either a Value or an error (an error from this method would be a runtime error). The way we handle errors during the evaluation stage is different from the type checking stage. Instead of defining our own custom error type, we use anyhow::Result<Value> as the return type; if any errors occur during evaluation, the interpreter will stop evaluating and print the error that occurred to console. We use anyhow::Result<Value> rather than std::result::Result because anyhow’s version is thread safe and there is a convenient As elsewhere, we use pattern matching on the input term to decide how we proceed. As most variants of the Term enumeration contain references to other terms, we normally have to 31 4.6 Evaluating expressions evaluate any child terms before evaluating the parent term, this is done via recursion for the most part. For example, we see the cases for evaluating let and mlet expressions in Figure 4.24 (note that the evaluation rules in Section 3.3 mean that let and mlet behave identically at evaluation). impl Environment { pub fn eval(&mut self, threadpool: &Threadpool, term: &Term) -> anyhow::Result<Value> { match term { [...] Term::Let(abs, span) | Term::MLet(abs, span) => { let value = self.eval(thread_pool, value)?; self.bind_and_eval(thread_pool, pattern, value, body) } [...] } } } Fig. 4.24: Evaluating let and mlet expressions We evaluate the value that we substitute into the expression (returning an error message if this evaluation fails), and then we call bind_and_eval, whose definition we see in Figure 4.25. impl Environment { [...] fn bind_and_eval( &mut self, thread_pool: &ThreadPool, pattern: &Pattern, value: Value, term: &Term, ) -> anyhow::Result<Value> { let mut env = self.clone(); env.bind_pattern(pattern, value)?; env.eval(thread_pool, term) } [...] } Fig. 4.25: Definition of Context::bind_and_eval We use bind_pattern to bind the pattern to the value given and as we see in Figure 4.26, Environment::bind_pattern has a very similar definition to Context::bind_pattern. 32 4.6 Evaluating expressions impl Environment { [...] pub fn bind_pattern( &mut self, pattern: &Pattern, value: Value, ) -> anyhow::Result<()> { match (pattern, value) { (Pattern::Tuple(_, patterns), Value::Tuple(values)) => { if patterns.len() != patterns.len() { return Err(anyhow!("Tuple pattern insufficient.")); } for (pattern, value) in patterns.iter().zip(values.into_iter()) { self.bind_pattern(pattern, value)?; } } (Pattern::Tuple(_, _), _) => { return Err(anyhow!("Tuple pattern incompatible with value.")); } (Pattern::Variable(_, name), value) => { self.insert(name.clone(), value); } (Pattern::Wildcard(_), _) => {} } Ok(()) } [...] } Fig. 4.26: Definition of Context::bind_pattern Figure 4.27 shows the code for evaluating let box expressions. We cannot spawn new threads using directly inside the eval function as it may be that the rest of the term is evaluated before the computation in the spawned thread is finished. Instead, we handle the parallel computation within a separate function: eval_parallel. In most cases, we evaluate the term passed into eval_parallel, executing this within f the thread pool. However, if the term is just an Int, Bool, or Unit, we just return the value of the term. This limits further how many times we spawn new threads, which helps increase performance. We see how we evaluate fix and mfix in Figure 4.28. The distinction between fix and mfix exists only at the type checking level; they have identical behaviour at evaluation. Given our input abstraction abs, we generate a new Abstraction called abs which takes an empty 33 4.6 Evaluating expressions use rayon::ThreadPool; [...] pub fn eval_parallel( thread_pool: &ThreadPool, mut env: Environment, body: Arc<Term>, ) -> anyhow::Result<Value> { match body.as_ref() { Term::Int(i, _) => Ok(Value::Int(*i)), Term::Bool(b, _) => Ok(Value::Bool(*b)), Term::Unit(_) => Ok(Value::Unit), _ => thread_pool.install(|| env.eval(thread_pool, &body)), } } [...] impl Environment { pub fn eval(&mut self, threadpool: &ThreadPool, term: &Term) -> anyhow::Result<Value> { match term { [...] Term::LetBox(pattern, value, body, _) => { let value = self.eval(thread_pool, value)?; let mut env = self.clone(); env.bind_pattern(pattern, value)?; eval_parallel(thread_pool, env, body.clone()) } [...] } } } Fig. 4.27: Evaluating let box terms tuple as input (this is how we represent a function which doesn’t take input values), and whose body consists of just the input abstraction. We then create a new application where we apply our new abstraction to our input abstraction, giving us our fixed point, which we then evaluate. 4.6.1 Evaluation error handling Unlike the type checking diagnostics we discussed in Subsection 4.5.1, we take a more simple approach to error handling during the evaluation phase. 34 4.7 Putting it all together impl Environment { pub fn eval(&mut self, threadpool: &Threadpool, term: &Term) -> anyhow::Result<Value> { match term { [...] Term::Fix(abs, span) | Term::MFix(abs, span) => { let arg = Term::Abstraction( Abstraction { param: Pattern::Variable(span.clone(), "v".to_owned()), param_type: Type::Tuple(span.clone(), vec![]), body: Arc::new(Term::Application( Arc::new(term.clone()), Arc::new(Term::Variable("v".to_owned(), Span::default())), span.clone(), )), }, Span::default(), ); let fixed = Term::Application(abs.clone(), Arc::new(arg), span.clone()); self.eval(thread_pool, &fixed) } [...] } } } Fig. 4.28: Evaluating fix and mfix terms In Figure 4.23, we see that the eval method returns values of type anyhow::Result<Value>. This is reminiscent of our discussion in Subsection 4.5.1, where we explained that the main function returns anyhow::Result<()>. The Anyhow library also supplies the anyhow! macro, which takes a format string and produces an anyhow::Error, which is the error type of anyhow::Result<T> [32]. Since evaluation errors are typically small issues, we use these instead of more complex solutions such as those we saw in Subsection 4.5.1. For example, in Figure 4.26, we use anyhow! to generate error messages if we try to bind a tuple pattern to a non-tuple pattern, or a tuple pattern to a tuple value which is not of the same length. 4.7 Putting it all together We bring all of our work thus far together in the entrypoint function main, shown in Figure 4.29. 35 4.7 Putting it all together fn main() -> anyhow::Result<()> { let Some(path) = std::env::args().nth(1) else { return Err(anyhow!("Usage: b7 <file>")); }; // Parsing let source = fs::read_to_string(&path)?; let parser = Parser::new(path.clone()); let Program(decls) = parser.parse_source(&source)?; // Prelude let (mut context, mut env) = make_context(); // Type checking for decl in &decls { type_check(decl, &mut context, &source, &path)?; } // Evaluating let thread_pool = rayon::ThreadPoolBuilder::new().build()?; for decl in &decls { eval(decl, &mut env, &thread_pool)? } let main_type = context.get("main", 0, Span::default()).unwrap(); let main = env.get("main")?; match (main, main_type) { (Value::Abstraction(abs, env), Type::Abstraction(_, param_type, _)) if param_type.is_tuple() => { let span = Span::default(); let value = env.clone().eval( &thread_pool, &Term::Application( Arc::new(Term::Abstraction(abs.clone(), span.clone())), Arc::new(Term::Tuple(vec![], span.clone())), span.clone(), ), )?; println!("{value}") } (value, r#type) => println!("{value}: {type}"), } Ok(()) } Fig. 4.29: Definition of main 36 4.7 Putting it all together We pass through the following stages: we read in a source file based on a user-supplied path, then we parse the program, prepare our typing context and evaluation environment, then we type check, then evaluate, and then finally print the value of main (if declared), as discussed in Section 3.4. We now look at how we type check and evaluate declarations in Figures 4.31 and 4.30 respectively. We first find the raw type of the term; if we encounter an error we print any type diagnostics generated and terminate the program. We then fully resolve the type of the term, with a similar procedure for handling errors. We then bind the pattern and the type together in the local context, except when dealing with an mlet expression, where we instead bind in the global context. To evaluate declarations, we check that the declaration is a term (we don’t evaluate type declarations); then we evaluate the term and bind the resultant value with the pattern (or print an error if evaluation fails). fn eval(decl: &Declaration, env: &mut Environment, thread_pool: &ThreadPool) -> anyhow::Result<()> { if let Declaration::Term(pattern, term) = decl { let value = env.eval(thread_pool, term)?; env.bind_pattern(pattern, value)?; } Ok(()) } Fig. 4.30: Definition of eval 37 4.7 Putting it all together fn type_check(decl: &Declaration, context: &mut Context, source: &str, path: &str) -> anyhow::Result<()> { match decl { Declaration::Term(pattern, term) => { let raw_type = context.type_of(term); let Ok(raw_type) = raw_type else { let reports = raw_type.unwrap_err(); for report in reports { report .build_report() .eprint((path.to_owned(), Source::from(&source)))?; } std::process::exit(1) }; let r#type = context.resolve(raw_type); let Ok(r#type) = r#type else { let reports = r#type.unwrap_err(); for report in reports { report .build_report() .eprint((path.to_owned(), Source::from(&source)))?; } std::process::exit(1) }; let which_context = match term { Term::MLet(..) => WhichContext::Mobile, _ => WhichContext::Local, }; context .bind_pattern(which_context, pattern, term, &r#type) .unwrap(); } Declaration::Type(name, r#type) => { context.insert_type(name.clone(), r#type.clone()) } } Ok(()) } Fig. 4.31: Definition of type_check 38 5 Benchmarks We now test the performance of our interpreter by comparing how long the programs in Figures 3.6 and 3.10 take to run. We benchmark by running our programs with the time command, which will print how long the interpreter takes to run as well as other statistics such as CPU utilization. Figure 5.5 are the results of running the programs using an interpreter compiled in release mode1 on a 2020 MacBook Pro with an M1 processor, plugged in2 . We can see that the serial and parallel implementations of calculating factorial had almost identical runtimes but relatively, the parallel implementation was around twice as fast as the serial implementation. The parallel implementation also utilized more CPU (115% vs 80%). In contrast, the parallel implementation of Fibonacci took 49% longer to run compared to the serial implementation and both implementations had identical CPU utilization. We would expect in principle that the parallel implementation of Fibonacci is faster than the serial implementation so this merits some investigation. 5.1 Flame graphs Software profilers such as DTrace [33] are useful tools for analyzing which components of a program are most resource-intensive. However, their output can often be verbose and thus it can be difficult to identify where improvements could be made. 1 2 Compiling in ‘release’ mode enables most optimizations. The M1 processor has eight cores, four of which are ‘efficiency’ cores which have the best performance when the computer is plugged in 39 5.1 Flame graphs $ time b7 fac.b7 7034535277573963776 b7 fac.b7 0.00s user 0.00s system 80% cpu 0.007 total Fig. 5.1: Benchmarking fac.b7 $ time b7 parfac.b7 7034535277573963776: Int b7 parfac.b7 0.00s user 0.00s system 115% cpu 0.004 total Fig. 5.2: Benchmarking parfac.b7 $ time b7 fib.b7 75025: Int b7 fib.b7 0.64s user 0.00s system 99% cpu 0.645 total Fig. 5.3: Benchmarking fib.b7 $ time b7 parfib.b7 75025: Int b7 parfib.b7 0.95s user 0.00s system 99% cpu 0.960 total Fig. 5.4: Benchmarking parfac.b7 Fig. 5.5: Benchmarking factorial and Fibonacci functions Brendan Gregg introduced flame graphs as a visual means of analyzing code paths for the purpose of software profiling and optimization [34]. Stack traces are represented as columns of boxes, where each box represents a function (or strictly speaking, a stack frame). The width of each box shows the frequency at which that function is present in stack traces, so functions with wider boxes are more prominent in the stack traces than those with narrow boxes. The y-axis shows the stack depth, ordered from root at the bottom to leaf at the top. Hence, we can read off code flow by going from the bottom to the top of the flame graph. Figure 5.6 shows the flame graph of running the serial Fibonacci program fib.b7. The graph is dominated by Environment::eval calls, indicating that the interpreter spends the majority of its time evaluating expressions. This is not particularly surprising given that we have implemented a tree-walk interpreter as discussed in Subsection 4.1.1. We now look at the flame graph after running parfib.b7 in Figure 5.7. 40 5.1 Flame graphs Flame Graph b7`.. b7.. b7`b.. b7`b7:.. b7`b7::.. b7`.. b7`b7::eval::en.. b7`b7::.. b7`b7:.. b7`b7::eval::envi.. b7`b7::eva.. b7`b7::eval::environment::.. b7`b7::eva.. b7`b7::eval::environment::.. b7`b7::eva.. b7`b7::eval::environment::E.. b7`b7::eva.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval.. b7`b7::eval::environment::E.. b7`b7::eval::environment::Environment::ev.. b7`b7::eval::environment::Environment::bi.. b7`b.. b7`b.. b.. b7`.. b7`b7::.. b.. b7`.. b7`b7:.. b7`b7.. b.. b7`b7:.. b7`b7::eva.. b7`b7::eval::.. b7`b7.. b.. b7`b7::ev.. b7`b7::eva.. b.. b7`b7::eval::.. b7`b7.. b.. b.. b7`b7::ev.. b7`b7::eva.. b.. b7`b7::eval::en.. b7`b7:.. b.. b7`b7::eval::e.. b7`b7::eval.. b.. b7`b7::eval::en.. b7`b7::eva.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eva.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eva.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eva.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eva.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eva.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eva.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eva.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eva.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eva.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eva.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eva.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval:.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval:.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval:.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval:.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval:.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::environment::Environ.. b7`b7::eval::e.. b7`b7::eval::en.. b7`b7::eval::environment::Environ.. b7`b7::eval::environment::Enviro.. b7`b7::eval::environment::Environment::eval Search b.. b.. b.. b.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. b.. b7`.. Fig. 5.6: Flame graph after running fib.b7 41 b.. b.. b.. b7`b.. b.. b.. b7`b7.. b.. b7.. b.. b7`b7::e.. b7`.. b7.. b.. b7`b7::e.. b7`.. b7.. b7`.. b7`b7::ev.. b.. b7`.. b7.. b7`.. b7`b7::eva.. b7.. b7`.. b7.. b7`.. b7`b7::eval::env.. b7`.. b7.. b7`.. b7`b7::eval::envi.. b7`.. b7.. b7`.. b7`b7::eval::envir.. b7`.. b7.. b7`.. b7`b7::eval::envir.. b7`.. b7.. b7`.. b7`b7::eval::envir.. b7`.. b7.. b7`.. b7`b7::eval::enviro.. b7`.. b7.. b7`.. b7`b7::eval::environme.. b7`.. b7.. b7`.. b7`b7::eval::environme.. b7`.. b7.. b7`.. b7`b7::eval::environmen.. b7`.. b7.. b7`.. b7`b7::eval::environmen.. b7`.. b7.. b7`.. b7`b7::eval::environmen.. b7`.. b7.. b7`.. b7`b7::eval::environment.. b7`.. b7.. b7`.. b7`b7::eval::environment::.. b7`.. b7.. b7`.. b7`b7::eval::environment::E.. b7`.. b7.. b7`.. b7`b7::eval::environment::E.. b7`.. b7.. b7`.. b7`b7::eval::environment::E.. b7`.. b7.. b7`.. b7`b7::eval::environment::E.. b7`.. b7.. b7`.. b7`b7::eval::environment::E.. b7`.. b7.. b7`.. b7`b7::eval::environment::E.. b7`.. b7.. b7`.. b7`b7::eval::environment::E.. b7`.. b7.. b7`.. b7`b7::eval::environment::E.. b7`.. b7.. b7`.. b7`b7::eval::environment::E.. b7`.. b7.. b7`.. b7`b7::eval::environment::E.. b7`.. b7.. b7`.. b7`b7::eval::environment::E.. b7`.. b7.. b7`.. b7`b7::eval::environment::E.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::eval::environment::En.. b7`.. b7.. b7`.. b7`b7::main b7`.. b7.. b7`.. b7`std::sys_common::backtrace.. b7`.. b7.. b7`.. b7`std::rt::lang_start::_{{cl.. b7`.. b7.. b7`.. b7`std::rt::lang_start_intern.. b7`.. b7.. b7`.. b7`main b7`.. b7.. b7`.. dyld`start 5.1 Flame graphs Flame Graph b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b.. b7.. b.. b.. b.. b.. b.. b.. b7.. b7.. b.. b.. b7`.. b.. b.. b7.. b7.. b7.. b.. b.. b7`.. b.. b.. b7.. b7.. b7.. b7.. b.. b.. b7`.. b.. b7`.. b7.. b7.. b7.. b7.. b.. b.. b7`.. b.. b7`.. b7.. b7.. b7.. b7.. b.. b.. b.. b7`.. b.. b.. b7`.. b7.. b7.. b7.. b7.. b.. b.. b7.. b.. b7`.. b.. b.. b7`.. b7.. b7.. b7.. b7.. b7.. b.. b.. b7.. b7.. b.. b7`.. b7.. b7.. b.. b7`.. b7.. b7.. b7.. b7.. b7.. b7`b7.. b.. b7.. b7.. b.. b7`.. b7.. b7.. b.. b7`.. b7.. b7.. b7`b7.. b7.. b7.. b7.. b.. b7`b7.. b.. b7.. b7.. b.. b7`b7:.. b7.. b7.. b.. b7`.. b7`b.. b7.. b7.. b7`b7.. b7.. b7.. b7.. b.. b7`b7.. b.. b7.. b7.. b.. b7`b7:.. b7.. b7.. b.. b7`.. b7`b.. b7.. b7.. b7`b7.. b7.. b7`b7.. b7.. b.. b7`b7.. b.. b7.. b7.. b.. b7`b7:.. b7.. b7.. b.. b7`b7.. b7`b.. b7.. b7.. b7`b7.. b7.. b7`b7.. b7.. b.. b7`b7.. b.. b7.. b7.. b.. b7`b7:.. b7.. b7.. b.. b7`b7.. b7`b.. b7.. b7.. b7`b7.. b7.. b7`b7.. b7.. b.. b7`b7.. b.. b7.. b7.. b.. b7.. b.. b7`b7:.. b7.. b7.. b.. b7`b7.. b7`b.. b7.. b7.. b7`b7.. b7.. b7`b7.. b7.. b.. b7`b7.. b.. b7`b7.. b7.. b.. b7.. b.. b7`b7:.. b7.. b7.. b.. b7`b7.. b7`b.. b7.. b7.. b7`b7.. b7`b7.. b7`b7.. b7.. b.. b7`b7.. b.. b7`b7.. b7`b7.. b7.. b.. b7`b7:.. b7.. b7`b7.. b7`b7.. b7`b.. b7.. b7.. b7`b7.. b7`b7.. b7`b7.. b7.. b.. b7`b7::e.. b7`b7.. b7`b7.. b7.. b.. b7`b7:.. b7.. b7`b7.. b7`b7.. b7`b.. b7.. b7.. b7`b7::e.. b7`b7.. b7`b7.. b7.. b7`b.. b7`b7::e.. b7`b7.. b7`b7.. b7.. b.. b7`b7::eva.. b7`b7.. b7`b7.. b7`b.. b7`b7:.. b7`b7::e.. b7`b7.. b7`b7.. b7.. b7`b.. b7`b7::e.. b7`b7.. b7`b7.. b7.. b.. b7`b7::eva.. b7`b7.. b7`b7.. b7`b.. b7`b7:.. b7`b7::e.. b7`b7.. b7`b7::ev.. b7`b.. b7`b7::e.. b7`b7.. b7`b7.. b7.. b.. b7`b7::eva.. b7`b7.. b7`b7::eval:.. b7`b7:.. b7`b7::e.. b7`b7.. b7`b7::ev.. b7`b.. b7`b7::e.. b7`b7.. b7`b7.. b7.. b.. b7`b7::eva.. b7`b7.. b7`b7::eval:.. b7`b7:.. b7`b7::e.. b7`b7.. b7`b7::ev.. b7`b.. b7`b7::e.. b7`b7.. b7`b7.. b7.. b7`b.. b7`b7::eva.. b7`b7.. b7`b7::eval:.. b7`b7:.. b7`b7::e.. b7`b7.. b7`b7::ev.. b7`b.. b7`b7::e.. b7`b7.. b7`b7.. b7.. b7`b.. b7`b7::eva.. b7`b7.. b7`b7::eval:.. b7`b7:.. b7`b7::e.. b7`b7.. b7`b7::ev.. b7`b.. b7`b7::e.. b7`b7.. b7`b7::eva.. b7`b.. b7`b7::eva.. b7`b7.. b7`b7::eval:.. b7`b7:.. b7`b7::e.. b7`b7.. b7`b7::ev.. b7`b.. b7`b7::eval::env.. b7`b7::eva.. b7`b.. b7`b7::eva.. b7`b7.. b7`b7::eval:.. b7`b7:.. b7`b7::eval::en.. b7`b7::ev.. b7`b.. b7`b7::eval::env.. b7`b7::eva.. b7`b.. b7`b7::eval::envir.. b7`b7::eval:.. b7`b7:.. b7`b7::eval::en.. b7`b7::ev.. b7`b.. b7`b7::eval::env.. b7`b7::eva.. b7`b.. b7`b7::eval::envir.. b7`b7::eval:.. b7`b7:.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::env.. b7`b7::eva.. b7`b.. b7`b7::eval::envir.. b7`b7::eval::environ.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::env.. b7`b7::eva.. b7`b.. b7`b7::eval::envir.. b7`b7::eval::environ.. b7`b7::eval::en.. b7`b7::eval::en.. b7`b7::eval::env.. b7`b7::eva.. b7`b.. b7`b7::eval::envir.. b7`b7::eval::environ.. b7`b7::eval::environment::Enviro.. b7`b7::eval::env.. b7`b7::eva.. b7`b.. b7`b7::eval::environment::Environment::e.. b7`b7::eval::environment::Enviro.. b7`b7::eval::environment::Environm.. b7`b7::eval::environment::Environment::b.. b7`b7::eval::environment::Environment::eval Search b.. b.. b.. b7.. b.. b.. b7.. b.. b7.. b.. b7.. b.. b7.. b.. b7.. b.. b7.. b.. b7.. b.. b7.. b.. b7.. b.. b7.. b.. b7.. b7`.. b.. b7.. b.. b.. b7.. b7`.. b.. b7.. b7.. b7.. b.. b7`b.. b7`.. b.. b7.. b7.. b7.. b7.. b.. b7`b.. b7`.. b.. b7`b.. b7.. b7.. b7.. b.. b7`b.. b7`.. b.. b.. b7`b.. b7.. b7.. b7.. b.. b7`b.. b7`.. b.. b.. b7`b.. b7.. b7.. b7.. b.. b7`b.. b7`.. b.. b.. b7`b.. b7.. b7.. b7.. b.. b7`b.. b7`.. b.. b.. b7`b.. b7.. b7.. b7.. b.. b7`b.. b7`.. b7`.. b.. b7`b.. b7.. b7.. b7.. b.. b7`b.. b7`.. b7`.. b.. b7`b.. b7.. b7`b7.. b7.. b.. b7`b7::eva.. b7`.. b.. b7`b.. b7.. b7`b7.. b7`b7:.. b7`b7::eva.. b7`.. b.. b7`b7::ev.. b7`b7.. b7`b7:.. b7`b7::eva.. b7`.. b7`b.. b7`b7::ev.. b7`b7.. b7`b7:.. b7`b7::eva.. b7`.. b7`b.. b7`b7::ev.. b7`b7.. b7`b7:.. b7`b7::eva.. b7`.. b7`b.. b7`b7::ev.. b7`b7.. b7`b7:.. b7`b7::eva.. b7`.. b7`b.. b7`b7::ev.. b7`b7.. b7`b7:.. b7`b7::eva.. b7`.. b7`b.. b7`b7::ev.. b7`b7.. b7`b7:.. b7`b7::eva.. b7`.. b7`b.. b7`b7::ev.. b7`b7.. b7`b7::eval::environment.. b7`b.. b7`b7::ev.. b7`b7.. b7`b7::eval::environment.. b7`b7::eval::environmen.. b7`b7::eval::environment.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. Fig. 5.7: Flame graph after running parfib.b7 42 5.2 Restricting parallelism After taking some time to process the initial shock of seeing this monstrosity, we note based on the bottommost functions that the parallel implementation of Fibonacci has the same problem as the serial implementation; evaluating expressions turns out to be massively recursive and hence woefully inefficient. This is confounded by introducing parallelism via let box, since in parfib.b7, a huge amount of time is spent waiting for other threads to finish intermediate computations. A potential solution is to reduce the amount of parallelism we use in our parallel Fibonacci implementation. 5.2 Restricting parallelism Reducing parallelism can be a useful way of improving performance of parallel programs if communication overheads and context switches represent a significant proportion of execution time [2]. The only other operations performed frequently in our implementation of parallel Fibonacci are integer addition and function calls, which don’t cause significant overhead, so reducing the amount of parallelism we use in parfib may result in increased performance. Figure 5.8 shows a different implementation of parfib that doesn’t spawn new threads to calculate the recursive Fibonacci calls. let parfib = mfix fib: []Int -> Int => n: []Int => let box u = n in if (u < 2) then u else fib (box (u - 1)) + fib (box (u - 2)) let main = parfib (box 25) Fig. 5.8: parfib_improved.b7 Figure 5.9 shows the time statistics of running parfib_improved.b7. $ time b7 parfib_improved.b7 75025: Int b7 parfib_improved.b7 0.80s user 0.01s system 99% cpu 0.812 total Fig. 5.9: Benchmarking parfib_improved.b7 43 5.2 Restricting parallelism By comparison to Figures 5.3 and 5.4, we see that parfib_improved is more performant than the original parallel Fibonacci implementation, but still slower than the serial implementation. Figure 5.10 shows the flame graph after running parfib_improved.b7. Whilst still denser than Figure 5.6, we can see by the number of columns that there is substantially less computation occuring in the improved version of parfib compared to our original implementation. However, this may be because of our implementation. In our implementation, we spawn a thread to evaluate a term but then immediately join with the main thread (threads are joined implicitly after calling ThreadPool::execute). However, this poses a problem with nested let box terms as seen in our implementation of parfib in Figure 3.8; because we spawn a new thread every time we find a let box term, we keep spawning more and more threads the more we nest let box terms. The proper way to do this would be to figure out how nested the let box terms are, spawn threads to evaluate them, and then join all of these threads together all at once. However, in our implementation, we only keep track of what current term we are evaluating and not the parent terms, so implementating this improvement would require a major refactor of the interpreter. 44 5.2 Restricting parallelism Flame Graph l.. b7`b7.. b.. b7.. b7`b.. b7`b7::.. b7`b7::eval:.. b7`.. b7`b7::eval::envi.. b7`b7::eval::.. b7`b7::e.. b7`b7::eval::envir.. b7`b7::eval::envi.. b7`b7::eval::environment::Env.. b7`b7::eval::envi.. b7`b7::eval::environment::Env.. b7`b7::eval::envi.. b7`b7::eval::environment::Envi.. b7`b7::eval::envi.. b7`b7::eval::environment::Envi.. b7`b7::eval::envi.. b7`b7::eval::environment::Envi.. b7`b7::eval::envi.. b7`b7::eval::environment::eval.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::eval.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::eval.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::eval.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::eval.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::eval.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::eval.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::eval.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::eval.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::eval.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::eval.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::Envi.. b7`b7::eval::envir.. b7`b7::eval::environment::eval.. b7`b7::eval::environment::Environment::eval b7`b7::eval::environment::Environment::bind_and_eval b7`b7::e.. b7`b.. b7`b.. b7`b7::eval:.. b.. b7`b7::e.. b7`b.. b7`b7::eval::env.. b7`b7::eval::envir.. b7`b7::eval::envi.. b7`b7::eval::envir.. b.. b7`b7::eval::envi.. b7`b7::eval::environme.. b7`b7::eval::envi.. b7`b7::eval::environme.. b7`b7::eval::envi.. b7`b7::eval::environme.. b7`b7::eval::envi.. b7`b7::eval::environme.. b7`b7::eval::envi.. b7`b7::eval::environme.. b7`b7::eval::envi.. b7`b7::eval::environme.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::envi.. b7`b7::eval::environmen.. b7`b7::eval::environment::Environment::eval b7`b7::eval::environment::Environment::bind.. b7`b7::eval::environment::Environment::eval Search b7`b7.. b7.. b7`b7::eval.. b7.. b7.. b7`b7::eval.. b7.. b7.. b7`b7::eval.. b7.. b7`.. b7`b7::eval.. b7`.. b7`b.. b7`b7::eval.. b7`.. b7`b.. b7`b7::eval.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::.. b7`b7::ev.. b7`b7::eval::environment:.. Fig. 5.10: Flame graph after running fib.b7 45 b7.. b.. b7.. b7`.. b7`.. b7`b7.. b7`b7::ev.. b7`b7::e.. b7`b7::eva.. b7`b7::ev.. b7`b7::eval::environme.. b7`b7::eval::environme.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b7`b7::eval::environmen.. b.. b.. b7.. b.. b7`.. b7.. b7`.. b7`b.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. b7`b7::eval.. 6 Evaluation We now evaluate the project as a whole. Our aim was to provide a minimal implementation of Moody’s type system and test its real world viability. We are confident that we succeeded in this aim; our interpreter successfully parses, type checks, and evaluates the example programs detailed in Section 3.4. We also implemented helpful type diagnostics that give the user more information about where type checking errors occur (Subsection 4.5.1). One of the major benefits of the Blues language is the type-level separation between local and mobile code; as with memory safety in Rust, being able to statically guarantee that computations can run safely in parallel is hugely beneficial to the programmer and allows them to focus more on aspects such as correctness. In addition, the fact that the implementation details of parallelism are abstracted away from the programmer reduces cognitive overhead and makes it easier to reason about the program. However, this abstraction can be a double-edged sword in a parallel context in the sense that we don’t have as much control over how computations are evaluated. Often, this control can be important in maximizing the performance of a parallel algorithm, and could also be frustrating for programmers with experience in implementing parallel algorithms who are used to having more control over their implementation. The most negative aspect of the interpreter is the poor performance of the parallel programs. As discussed, this is likely due to poor design decisions such as choosing to implement a tree-walk interpreter, which then cascaded and meant we were forced into suboptimal solutions for problems such as how to handle spawning threads when encountering a let box term. Implementing the distributed abstract machine would have allowed us to implement evaluation in a way that was less dependent on the syntactic structure of a Blues program and more in line with how Rust (and other imperative languages) handle multithreaded programming. Given all the above, it is difficult to draw any kind of proper judgment as to the real world utility of Moody’s type system or the Blues language. Further work is probably necessary both in refining the implementation and implementing parallel algorithms in Blues. 46 6.1 Related work 6.1 Related work We now look briefly at other examples from the literature of type systems for distributed operations. As Eastoe [4] has already investigated distributed calculi, we instead choose to briefly discuss other related type systems. Proposed by Murphy, ML5 [35] is perhaps the most similar dual context calculus to the one we considered in Section 2.3. ML5 is based on S51 rather than S4, the logic that Moody used; hence the accessibility relation is also symmetric and so every world can access every other world. Like Moody’s calculus, in ML5, a term of type β‘π΄ can be evaluated at any site of computation. However, unlike Moody’s calculus, worlds are explicitly used in the judgements and types to explicitly define locations. PriML is an extension to Standard ML which facilitates multithreaded programming with priorities [36]. Like Moody, PriML utilizes a modal type system based on S4 but PriML explicitly interpret the assymetry of the accessibility relation of S4 in terms of thread priorities. This is leveraged to statically guarantee that low-priority threads do not delay high-priority threads. Place-based systems, like the type system we implemented, interpret computation as being located at a particular “place” and use a type system to enforce locality of resource access. An example of which is via Chandra et al. [37], who used place types to define several common distributed datatypes such as distributed hash tables and distributed binary trees. Unlike other type systems we have discussed, place-based systems are not based on logic. 6.2 Future work There are a number of paths which future work can take. For example, Moody’s original type system not only supported parallel computation via the type β‘π΄, but also computation in some definite, perhaps remote location via ♦π΄, generic side effects via the type ,π΄, and mutable references ref π΄ via store effects [3]. We could extend the type system to support various forms of polymorphism; for example, adding parametric polymorphism would facilitate support for important data structures such as lists and trees. A more major project would be to reimplement the Blues language using the distributed abstract machine mentioned in Subsection 4.1.1. This would be likely be a more performant solution, especially as we could avoid the massive recursion that lead to poor performance as seen in Section 5.5. This would be sound in the case of implementing the subset of the type system already in the Blues language, but further work extending Eastoe [4] would 1 See Section 2.1. 47 6.2 Future work be needed to prove that the dual context calculus and the distributed abstract machine are computationally equivalent with respect to ♦π΄, βπ΄, and ref π΄. 48 7 Conclusion To conclude, parallel and distributed computing have become more and more prominent over the past twenty years due to the advent of multicore processors. However, parallel programming can often be challenging, as the programmer has to contend with new kinds of bugs such as data races, deadlocks, and spinlocks. In response to this challenge, we designed a new programming language called Blues based on Moody’s type system, which statically guarantees that concurrency bugs like those above do not occur. We wrote an interpreter consisting of about 2500 lines of Rust code that implements the Blues language, explained the rationale behind our implementation decisions, and showed that our implementation exhibits the correct type checking and evaluation behaviour according to the specification. Our benchmarks showed that our implementation was not as performant as expected, and we reasoned that this was due to faults with our implementation. We then offered some thoughts on future work and also gave a short review of related type systems. 49 Bibliography [1] J. L. Hennessy and D. A. Patterson, Computer architecture : a quantitative approach, Sixth edition. Cambridge, MA: Morgan Kaufmann Publishers, 2019, isbn: 978-0-12-811905-1 0-12-811905-5. [2] T. Rauber and G. Rünger, Parallel Programming. Springer Cham, 4th Apr. 2023, 554 pp., isbn: 9783-031-28924-8. [Online]. Available: https://link.springer.com/book/10.1007/978-3-031-28924-8 (visited on 27/04/2024). [3] J. Moody, ‘Logical mobility and locality types’, in Logic Based Program Synthesis and Transformation, S. Etalle, Ed., ser. Lecture Notes in Computer Science, Berlin, Heidelberg: Springer, 2005, pp. 69–84, isbn: 978-3-540-31683-1. doi: 10.1007/11506676_5. [4] J. Eastoe, ‘Modal types for distributed computing’, Master’s thesis, University of Bristol, 11th May 2023. [5] J. Garson, ‘Modal logic’, in The Stanford Encyclopedia of Philosophy, E. N. Zalta and U. Nodelman, Eds., Spring 2024, Metaphysics Research Lab, Stanford University, 2024. [Online]. Available: https://plato.stanford.edu/archives/spr2024/entries/logic-modal/ (visited on 25/04/2024). [6] W. A. Howard, ‘The formulae-as-types notion of construction’, 1969. [Online]. Available: https: //www.semanticscholar.org/paper/The-formulae-as-types-notion-of-construction-Howard/ 1c28e7160184454808b5559faaa6570a1dc63f7c (visited on 26/04/2024). [7] R. Davies and F. Pfenning, ‘A modal analysis of staged computation’, Journal of the ACM, vol. 48, no. 3, pp. 555–604, 1st May 2001, issn: 0004-5411. doi: 10.1145/382780.382785. [Online]. Available: https://dl.acm.org/doi/10.1145/382780.382785 (visited on 20/02/2024). [8] K. Hinsen, ‘Staged computation: The technique you did not know you were using’, Computing in Science & Engineering, vol. 22, no. 4, pp. 99–103, Jul. 2020, Conference Name: Computing in Science & Engineering, issn: 1558-366X. doi: 10.1109/MCSE.2020.2985508. [Online]. Available: https://ieeexplore.ieee.org/document/9121605 (visited on 27/04/2024). [9] B. C. Pierce, Types and programming languages. Cambridge, Mass: MIT Press, 2002, 623 pp., isbn: 978-0-262-16209-8. [10] J. Moody, ‘Modal logic as a basis for distributed computation’, Carnegie Mellon University, Technical Report CMU-CS-03-194, Oct. 2003. [11] M. Felleisen and R. Hieb, ‘The revised report on the syntactic theories of sequential control and state’, Theoretical Computer Science, vol. 103, no. 2, pp. 235–271, 14th Sep. 1992, issn: 0304-3975. doi: 10.1016/0304-3975(92)90014-7. [Online]. Available: https://www.sciencedirect.com/science/ article/pii/0304397592900147 (visited on 30/04/2024). [12] Robert Nystrome, Crafting Interpreters. Genever Benning, 28th Jul. 2021, 639 pp., isbn: 978-09905829-3-9. [Online]. Available: https://craftinginterpreters.com/. 50 Bibliography [13] ‘The scala programming language’. (), [Online]. Available: https://www.scala-lang.org/ (visited on 08/04/2024). [14] ‘Algebraic data types’, Scala 3 Reference. (), [Online]. Available: https://docs.scala-lang.org/ scala3/reference/enums/adts.html (visited on 27/04/2024). [15] ‘Rust programming language’. (), [Online]. Available: https://www.rust-lang.org/ (visited on 08/04/2024). [16] R. Jung, J.-H. Jourdan, R. Krebbers and D. Dreyer, ‘RustBelt: Securing the foundations of the rust programming language’, Proceedings of the ACM on Programming Languages, vol. 2, 66:1–66:34, POPL 27th Dec. 2017. doi: 10.1145/3158154. [Online]. Available: https://dl.acm.org/doi/10.1145/ 3158154 (visited on 08/04/2024). [17] V. Besozzi, ‘PPL: Structured parallel programming meets rust’, in 2024 32nd Euromicro International Conference on Parallel, Distributed and Network-Based Processing (PDP), ISSN: 23775750, Mar. 2024, pp. 78–87. doi: 10.1109/PDP62718.2024.00019. [Online]. Available: https: //ieeexplore.ieee.org/document/10495565 (visited on 30/04/2024). [18] Z. Yu, L. Song and Y. Zhang, Fearless concurrency? understanding concurrent programming safety in real-world rust software, 5th Feb. 2019. doi: 10.48550/arXiv.1902.01906. arXiv: 1902.01906[cs]. [Online]. Available: http://arxiv.org/abs/1902.01906 (visited on 30/04/2024). [19] Josh Stone and Niko Matsakis. ‘The rayon library’. (24th Mar. 2024), [Online]. Available: https: //crates.io/crates/rayon (visited on 30/04/2024). [20] The Tokio team. ‘The tokio library’. (28th Mar. 2024), [Online]. Available: https://crates.io/crates/ tokio (visited on 30/04/2024). [21] The Crossbeam team. ‘The crossbeam library’. (8th Jan. 2024), [Online]. Available: https://crates. io/crates/crossbeam (visited on 30/04/2024). [22] Josh Stone. ‘The IndexMap library’. (23rd Mar. 2024), [Online]. Available: https://crates.io/crates/ indexmap (visited on 01/05/2024). [23] ‘Types’, The Rust Reference. (), [Online]. Available: https://doc.rust-lang.org/reference/types. html#recursive-types (visited on 27/04/2024). [24] Steve Klabnik and Carol Nichols, ‘Smart pointers’, in The Rust Programming Language, 2nd edition, No Scratch Press, Dec. 2022, isbn: 978-1-71850-310-6. [Online]. Available: https://doc. rust-lang.org/book/ch15-00-smart-pointers.html. [25] Steve Klabnik and Carol Nichols, ‘Traits: Defining shared behavior’, in The Rust Programming Language, 2nd edition, No Scratch Press, Dec. 2022, isbn: 978-1-71850-310-6. [Online]. Available: https://doc.rust-lang.org/book/ch10-02-traits.html. [26] The LALRPOP team. ‘LALRPOP’. (10th Sep. 2015), [Online]. Available: https://lalrpop.github.io/ lalrpop/ (visited on 08/04/2024). [27] The Pest team. ‘The pest library’. (2nd Apr. 2024), [Online]. Available: https://crates.io/crates/pest (visited on 01/05/2024). [28] V. R. Pratt, ‘Top down operator precedence’, in Proceedings of the 1st annual ACM SIGACTSIGPLAN symposium on Principles of programming languages, ser. POPL ’73, New York, NY, USA: Association for Computing Machinery, 1st Oct. 1973, pp. 41–51, isbn: 978-1-4503-7349-4. doi: 10.1145/512927.512931. [Online]. Available: https://dl.acm.org/doi/10.1145/512927.512931 (visited on 01/05/2024). 51 Bibliography [29] The Pest team. ‘PrattParser in pest::pratt_parser - rust’. (), [Online]. Available: https://docs.rs/ pest/latest/pest/pratt_parser/struct.PrattParser.html (visited on 01/05/2024). [30] Steve Klabnik and Carol Nichols, ‘Closures: Anonymous functions that capture their environment’, in The Rust Programming Language, 2nd edition, Dec. 2022, isbn: 978-1-71850-310-6. [Online]. Available: https://doc.rust-lang.org/book/ch13-01-closures.html. [31] Steve Klabnik and Carol Nichols, ‘Recoverable errors with result’, in The Rust Programming Language, 2nd edition, No Scratch Press, Dec. 2022, isbn: 978-1-71850-310-6. [Online]. Available: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#recoverable-errorswith-result. [32] David Tolnoy. ‘The anyhow library’. (10th Apr. 2024), [Online]. Available: https://crates.io/ crates/anyhow (visited on 29/04/2024). [33] ‘Dtrace.org’. (), [Online]. Available: https://dtrace.org/ (visited on 29/04/2024). [34] B. Gregg, ‘The flame graph: This visualization of software execution is a new necessity for performance profiling and debugging.’, Queue, vol. 14, no. 2, pp. 91–110, 1st Mar. 2016, issn: 1542-7730. doi: 10.1145/2927299.2927301. [Online]. Available: https://dl.acm.org/doi/10.1145/ 2927299.2927301 (visited on 29/04/2024). [35] T. Murphy VII, K. Crary and R. Harper, ‘Type-safe distributed programming with ML5’, in Trustworthy Global Computing, G. Barthe and C. Fournet, Eds., Berlin, Heidelberg: Springer, 2008, pp. 108–123, isbn: 978-3-540-78663-4. doi: 10.1007/978-3-540-78663-4_9. [36] S. K. Muller, U. A. Acar and R. Harper, ‘Competitive parallelism: Getting your priorities right’, Proceedings of the ACM on Programming Languages, vol. 2, 95:1–95:30, ICFP 30th Jul. 2018. doi: 10.1145/3236790. [Online]. Available: https://dl.acm.org/doi/10.1145/3236790 (visited on 12/04/2024). [37] S. Chandra, V. Saraswat, V. Sarkar and R. Bodik, ‘Type inference for locality analysis of distributed data structures’, in Proceedings of the 13th ACM SIGPLAN Symposium on Principles and practice of parallel programming, ser. PPoPP ’08, New York, NY, USA: Association for Computing Machinery, 20th Feb. 2008, pp. 11–22, isbn: 978-1-59593-795-7. doi: 10.1145/1345206.1345211. [Online]. Available: https://dl.acm.org/doi/10.1145/1345206.1345211 (visited on 30/04/2024). 52