Uploaded by self

main

advertisement
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
Download