-
Notifications
You must be signed in to change notification settings - Fork 0
/
BP_Kvapil_Ondřej_2020.tex
1969 lines (1747 loc) · 106 KB
/
BP_Kvapil_Ondřej_2020.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
% vim: set tabstop=4 shiftwidth=4
% Neovim setup:
% let g:vimtex_compiler_latexrun = {
% \ 'build_dir' : '',
% \ 'options' : [
% \ '-verbose-cmds',
% \ '-xelatex',
% \ '-shell-escape',
% \ '-interaction=nonstopmode',
% \ '-synctex=1',
% \ '-file-line-error',
% \ '--latex-args="-shell-escape"',
% \ ],
% \}
% options:
% thesis=B bachelor's thesis
% thesis=M master's thesis
% czech thesis in Czech language
% english thesis in English language
% hidelinks remove colour boxes around hyperlinks
\documentclass[thesis=B,english]{FITthesis}[2019/12/23]
\usepackage[utf8]{inputenc} % LaTeX source encoded as UTF-8
% \usepackage[latin2]{inputenc} % LaTeX source encoded as ISO-8859-2
% \usepackage[cp1250]{inputenc} % LaTeX source encoded as Windows-1250
% \usepackage{subfig} %subfigures
% \usepackage{amsmath} %advanced maths
% \usepackage{amssymb} %additional math symbols
\usepackage{dirtree} %directory tree visualisation
\usepackage{morewrites}
\usepackage{xcolor}
\usepackage{blindtext}
\usepackage{footnote}
\usepackage{tabularx}
\usepackage{ragged2e}
\usepackage{booktabs}
\usepackage{microtype}
\usepackage[numbers]{natbib}
\usepackage[htt]{hyphenat}
\usepackage{xparse,minted}
% taken from
% https://tex.stackexchange.com/questions/161124/how-to-make-a-minted-code-listing-centered-on-a-page
\RecustomVerbatimEnvironment{Verbatim}{BVerbatim}{}
\usepackage{tikz}
\usepackage{tcolorbox}
\tcbuselibrary{breakable}
\tcbuselibrary{fitting}
\tcbuselibrary{minted}
\usetikzlibrary{
shapes.multipart, arrows, positioning
}
% custom commands
\newcommand{\eg}{\emph{e.g.}\xspace}
\newcommand{\ie}{\emph{i.e.}\xspace}
\newcommand{\todo}[1]{\textcolor{red}{\textbf{[[#1]]}}}
\newcommand{\blind}[1][1]{\textcolor{gray}{\Blindtext[#1][1]}}
\newcommand{\citationNeeded}{\textcolor{red}{\textbf{[citation needed]}}}
\newcommand{\hackage}[1]{\texttt{#1}}
\newcommand{\hsSignature}[1]{\hsCode{#1}}
\newcommand{\hsPat}[1]{\texttt{#1}}
\newcommand{\hsType}[1]{\texttt{#1}}
\newcommand{\hsIdent}[1]{\texttt{#1}}
\newcommand{\hsModule}[1]{\texttt{#1}}
\newcommand{\hsTC}[1]{\texttt{#1}}
\newcommand{\hsCode}[1]{\mintinline[
breakbytokenanywhere,breaklines,escapeinside=&&,mathescape=true
]{haskell}{#1}}
% tabularx customisation
\newcolumntype{L}{>{\RaggedRight\arraybackslash}X}
% list of acronyms
\usepackage[acronym,nonumberlist,toc,numberedsection=autolabel]{glossaries}
\iflanguage{czech}{\renewcommand*{\acronymname}{Seznam pou{\v z}it{\' y}ch zkratek}}{}
\makeglossaries
% \newacronym{CVUT}{{\v C}VUT}{{\v C}esk{\' e} vysok{\' e} u{\v c}en{\' i} technick{\' e} v Praze}
% \newacronym{FIT}{FIT}{Fakulta informa{\v c}n{\' i}ch technologi{\' i}}
\newacronym{ghc}{GHC}{Glasgow Haskell Compiler}
\newacronym{ghci}{GHCi}{\acrshort{ghc} interpreter}
\newacronym{rts}{RTS}{Runtime System}
\newacronym{stm}{STM}{Software Transactional Memory}
\newacronym{ui}{UI}{User Interface}
\newacronym{ffi}{FFI}{Foreign Function Interface}
\newacronym{th}{TH}{Template Haskell}
\newacronym{os}{OS}{Operating System}
\newacronym{llvm}{LLVM}{Low-Level Virtual Machine}
\newacronym{syb}{SYB}{Scrap Your Boilerplate}
\newacronym{adt}{ADT}{Algebraic Data Type}
\newacronym{gadt}{GADT}{Generalised Algebraic Data Type}
\newacronym{repl}{REPL}{Read-Eval-Print Loop}
\newacronym{ast}{AST}{Abstract Syntax Tree}
\newacronym{ir}{IR}{Intermediate Representation}
\newacronym{csv}{CSV}{Comma-Separated Values}
\newacronym{api}{API}{Application Programming Interface}
\newacronym{rhs}{RHS}{Right-Hand Side}
\newacronym{hpc}{HPC}{Haskell Program Coverage}
\newacronym{stg}{STG}{Spineless Tagless G-machine}
\newacronym{gnu}{GNU}{GNU's Not Unix, a Unix-like operating system}
\newacronym{hls}{HLS}{Haskell Language Server}
\newacronym{bco}{BCO}{Byte Code Object}
\newacronym{lsp}{LSP}{Language Server Protocol}
\newacronym{tso}{TSO}{Thread State Object}
\newacronym{whnf}{WHNF}{Weak Head Normal Form}
% % % % % % % % % % % % % % % % % % % % % % % % % % % % % %
% EDIT THIS
% % % % % % % % % % % % % % % % % % % % % % % % % % % % % %
\department{Programming Research Laboratory}
\title{Haskell Dynamic Tracing}
\authorGN{Ondřej} %author's given name/names
\authorFN{Kvapil} %author's surname
\author{Ondřej Kvapil} %author's name without academic degrees
\authorWithDegrees{Ondřej Kvapil} %author's name with academic degrees
\supervisor{Ing. Filip Křikava, Ph.D.}
\acknowledgements{
I would like to thank Ing. Filip Křikava, Ph.D. and professor Jan Vitek for
not only making this work possible, but for being open, friendly, and
supportive throughout the whole process. Special thanks goes to Artem
Pelenitsyn and Aviral Goel who provided me with advice and direction in my
work. Additional thanks goes to Benedict Allen, Abigail Magalhães de
Alcantara, and Jonathan Coates, who have had a greater influence on my love
of programming languages than they might think, and to my friends and
family for an endless supply of entertainment.
}
\abstractEN{
Haskell is one of the most well-known instances of a programming language
that uses non-strict semantics. On the one hand, this brings the
convenience of infinite data structures, user-defined control flow, and the
possibility to avoid unnecessary computation. On the other hand, these
benefits are hampered by the runtime overhead and hard-to-predict the
behaviour of call-by-need. This begs the question: \emph{Is laziness worth
it?} To answer this question, we need to understand how laziness is used in
the wild. To this end, we develop a tool for dynamic analysis used to trace
the evaluation of function parameters. It is implemented as a compiler
plugin for the \acrlong{ghc}.
}
\abstractCS{
Haskell je jeden z nejznámějších jazyků s non-strict sémantikou. Na jednu
stranu přináší tato sémantika pohodlí nekonečných datových struktur,
řídících konstrukcí definovaných uživatelem a možnost vyhnout se
nepotřebným výpočtům. Na stranu druhou jsou tyto výhody postiženy daní na
výkonu za běhu programu a těžko předvídatelným chováním call-by-need.
Nabízí se otázka: \emph{Vyplatí se líná evaluace?} K zodpovězení této
otázky musíme porozumět tomu, jak je lenost využívána v praxi. K tomuto
účelu jsme vyvinuli nástroj pro dynamickou analýzu použitelný k trasování
evaluace funkčních parametrů. Je implementován jako zásuvný modul
kompilátoru \acrlong{ghc}.
}
\placeForDeclarationOfAuthenticity{Prague}
\keywordsCS{Haskell, dynamické trasování, líné vyhodnocování, zásuvné moduly
kompilátorů, generické programování, GHC}
\keywordsEN{Haskell, dynamic tracing, lazy evaluation, compiler plugins,
generic programming, GHC}
\declarationOfAuthenticityOption{4} %select as appropriate, according to the desired license (integer 1-6)
% \website{http://site.example/thesis} %optional thesis URL
\begin{document}
\setsecnumdepth{part}
\chapter{Introduction} \label{sec:intro}
Conventional programming languages of all paradigms use -- almost equivocally
-- eager evaluation strategies. Non-strict semantics has far-reaching
implications on the design of a language~\cite{haskell-is-pure} and comes with
both benefits in expressiveness and implementation challenges.
The non-strict semantics of the Haskell language were a guiding principle which
influenced or directly determined many of the decisions made at its inception
over thirty years ago~\cite{history-of-haskell}. Lazy evaluation is a
potentially powerful implementation strategy for non-strict languages, freeing
the programmer to focus on what a program means rather than on how it is
computed. Laziness naturally accommodates user-defined control flow and
evaluates only the required subset of a given program in a demand-driven
manner. However, the implementation of non-strict features via laziness in
\acrshort{ghc} brings many pitfalls which Haskell programmers need to deal
with. Automatic avoidance of unnecessary thunk allocations is
conservative~\cite{cmtary-demand-analysis}: if \acrshort{ghc} is unable to
prove the strictness of a function in an argument by static strictness
analysis, the function will remain lazy, possibly leading to pathological
memory behaviour at runtime.
Haskell code is pure.\footnote{
Unless it uses unsafe facilities of the language.
} Functions in Haskell correspond closely to mathematical functions: they are
deterministic and free of side-effects. Haskell programs include pure
descriptions of effectful computations built in a compositional way via the
\hsType{IO} monad. Each program exports a top-level definition called
\hsIdent{main}. Invoking the program begins the demand-driven evaluation of its
\hsIdent{main} definition by the runtime. Haskell's strong static type system
provides a compile-time distinction between pure and effectful code and ensures
the two cannot be mixed in an impure way.
\begin{listing}[h]
\centering
\begin{minted}[autogobble]{haskell}
length [] = 0
length (_:xs) = 1 + length xs
\end{minted}
\caption{A naive implementation of list length.}
\label{lst:length}
\end{listing}
When the runtime evaluates an expression, it does so to the least extent possible.
For example, take the list \hsCode{ys = fact 5 : fact 6 : fact 7 : []} of three
values of the factorial function.\footnote{
The empty list is spelled \hsCode{[]} and the cons cell is written infix as
\hsCode{:}.
} Evaluation of the function applications is delayed by storing the necessary
data in runtime structures called \textit{thunks}. When we apply the function
\hsIdent{length} (defined in Listing \ref{lst:length}) to \hsIdent{ys} and
force the value of the application, \eg by printing it to standard output, the
function pattern-matches on the \hsIdent{ys} value. Case analysis requires the
scrutinee to be in \textit{\acrfull{whnf}}. An expression is said to be in weak
head normal form if it has been evaluated to the outermost data constructor
(such as \hsCode{:} or \hsCode{[]}) or lambda abstraction.
The list \hsIdent{ys} is in \acrshort{whnf} already, it is an evaluated cons
cell with a thunk at the head and another evaluated cons cell at the tail.
Since \hsIdent{length} only counts the cons cells in a list and does not need
to evaluate their elements, the application of \hsIdent{length} to \hsIdent{ys}
will leave the thunks untouched and finish in linear time.
\begin{listing}[h]
\centering
\begin{minted}[autogobble]{haskell}
-- snd is non-strict in the first
-- component of the pair
snd :: (Int, Int) -> Int
snd (x, y) = y
-- purity and laziness: foo reduces to 3,
-- complexComputation is not evaluated
foo = snd (complexComputation, 3)
-- non-strict semantics can prevent
-- runtime errors
foo' = snd (error "oops!", 3)
-- computations are shared,
-- even across threads
bar = let x = complexComputation
in x `par` f x
\end{minted}
\caption[Example lazy expressions.]{Example expressions where the semantics
of Haskell notably differ from that of strict languages.}
\label{lst:let-x}
\end{listing}
Other examples can be seen in Listing \ref{lst:let-x}. The binding
\hsIdent{foo} evaluates efficiently to the integer~3, but not until its value
is required for the evaluation of another computation. The \hsIdent{foo'}
example is more interesting. Some programs which would crash or diverge in
strict languages cleanly terminate in Haskell.
The Haskell Prelude, a collection of commonly used definitions imported into
every module, includes the special function \hsSignature{seq :: a -> b -> b},
which evaluates its first argument to \acrshort{whnf} and returns its second
argument.\footnote{Although not necessarily in this order.} Since evaluation to
\acrshort{whnf} happens automatically, one may wonder what is the purpose of
this function. Its usefulness becomes apparent when the user starts dealing
with programs in which performance is critical or with longer-running
applications. In these situations, the problems of laziness tend to surface.
Thunks which are never forced by the program but are still reachable in the
object graph waste memory. Haskell is a garbage collected language and similar
memory leaks slow the garbage collector down, adding a negative impact on
runtime performance. The problems with laziness are well-known and difficult to
debug. The user may be tempted to add calls to \hsIdent{seq} or other utilities
to force evaluation and avoid thunk build-up.
This fight against the semantics is detrimental to the developer experience of
the language. The question arises whether the benefits of laziness outweigh the
toll it takes on the programmer. This work focuses on laying the empirical
groundwork to help answer this question.
\setsecnumdepth{all}
\chapter{State-of-the-art} \label{sec:state-of-the-art}
Although there are many functional languages of the ML family which enjoy
widespread use (F\#, OCaml, SML), Haskell is the only non-strict language among
them. The \acrfull{ghc} implements Haskell's non-strict semantics by lazy
evaluation facilitated mainly by a runtime data structure called a
\textit{thunk}, which represents delayed computations.
Laziness leads to many issues with runtime behaviour of Haskell programs,
although it is an efficient implementation of non-strict semantics as required
by the Haskell spec~\cite{haskell2010}. The accumulation of thunks at runtime is
a frequent cause of pathological memory behaviour and unpredictable
performance. There is a number of libraries and tools which aim to help the
Haskell programmer inspect the runtime state of the Haskell heap, force the
evaluation of thunks known to be forced by the program at a later point anyway,
and avoid their creation altogether for certain expressions.
We open this chapter with an overview of \acrshort{ghc}. We then follow with a
survey of several debuggers and solutions for the inspection and management of
thunks. We found no existing tools that would directly capture enough
information to be suitable for dynamic tracing and strictness analysis, but two
came close (\nameref{sec:hat} and \nameref{sec:ghc-heap-view}).
\section{The Glasgow Haskell Compiler} \label{sec:ghc}
\acrshort{ghc} is the most widespread Haskell distribution. Its plethora of
language extensions~\cite{ghc-language-extensions}, which range from simple
syntactical utilities to complex type system add-ons, lets the programmer
customise the set of features provided by the language. We briefly discuss the
internal organisation of the project and in the process explain those basics of
the Haskell language that are needed for the later chapters.
\subsection{Architectural overview}
Although a thorough and authoritative -- if a little dated -- description of
the architecture of the compiler is available in the aptly named, freely
accessible Architecture of Open Source Applications \cite{arch-ghc}, we include
a summary of the key points relevant to our work as well as to the discussed
technicalities. \acrshort{ghc} is an optimising compiler for the Haskell
language. The project consists of three major components: (1) the compiler
itself, (2) the boot libraries (a collection of core libraries \acrshort{ghc}
itself depends on), and (3) the \acrlong{rts} (\acrshort{rts}, a large library
of C code linked into every compiled program). \acrshort{rts} provides
low-overhead runtime support for facilities abstracted away by Haskell code
such as garbage collection, exception handling, or concurrency primitives.
The compiler turns Haskell source code into object and interface
files.\footnote{
These describe high-level information about a compiled module, including
data type definitions and in\-line\-able functions.
} The process is organised in a pipeline that consists of the following phases:
\begin{description}
\item[Parsing] constructs abstract syntax trees. Lexical and syntactical
errors are reported here.
\item[Renaming] resolves identifiers into fully qualified names. Undefined
references are reported here. The renaming phase re\-associates
operator applications in the \acrshort{ast} formed during parsing. This
is because Haskell allows specifying the precedence and associativity
of infix operators, but their properties are only available after their
references have been resolved.
\item[Typechecking] verifies the program's type-correctness. Type checking
annotates all binders in the program with type signatures. Type errors
are reported here.
\item[Desugaring] converts Haskell surface syntax to the much smaller
intermediate language, Core.
\item[Simplification] performs optimisations on the Core language,
including demand analysis, \hsCode{let} floating, dead-code
elimination, common subexpression elimination, constructor
specialisation, and others.
\item[Conversion to \acrshort{stg}] translates Core to the language of the
\acrlong{stg}, suitable for code generation.
\item[Code generation] produces machine code or \acrshort{llvm} bitcode
for further processing by the \acrshort{llvm} toolchain.
\end{description}
\subsubsection*{Compiler front end}
Phases of the pipeline from parsing to desugaring form the compiler front end.
It starts out with a textual representation and gradually transforms it into
increasingly structured data before passing it on to the back end. During this
process, the front end identifies and reports all errors in the user's code.
As the program flows through the pipeline, its invariants gradually change. The
codebase reflects this by passing different data types from phase to phase. For
example, the type of binders changes from \hsType{Name}s, which represent
fully-qualified names, to \hsType{Id}s, which are annotated with type
information. The types of the nodes of the surface syntax tree are indexed by
an uninhabited type \hsType{GhcPass}, which is itself indexed by the
\hsType{Pass} data type (i.e. \hsType{GhcPass} has kind \hsType{Pass -> *}),
lifted to the type level. Together, the \hsType{GhcPass} types represent the
various phases of the compiler front end, from parsing to renaming to type
checking. The type level distinction between phases complicates the type
signatures of almost all functions in the pipeline, but the choice comes with
important benefits. First, indexing \acrshort{ast} types by the
\hsType{GhcPass} types provides a compile-time guarantee that nodes from
different phases cannot be mixed unintentionally. Second, the phase type
parameter allows one to use the Tress that Grow pattern~\cite{trees-that-grow},
that enables easy extensions of both sum and product types at various phases.
\begin{tcolorbox}[parbox=false, breakable, title=Trees that Grow]
Let us take a small detour to explain the concept of the design pattern,
invented specifically to add extensibility to \acrshort{ghc}'s abstract syntax
data types. The basic algorithm for making a data type extensible is as
follows:
\begin{enumerate}
\item Index the data type of choice by a type parameter $\xi$, called the
\textit{extension descriptor},
\begin{center}
\hsCode{data D = ...} $\rightarrow$ \hsCode{data D &$\xi$& = ...}
\end{center}
\item add one new constructor $\mathrm{Extra}$ to the data type,
\begin{center}
\hsCode{data D &$\xi$& = C&$_1$& ... | ... | C&$_n$& ...} \\
$\downarrow$ \\
\hsCode{data D &$\xi$& = C&$_1$& ... | ... | C&$_n$& ... | Extra}
\end{center}
\item create a \textit{type family} $X_{\mathrm{Con}}$ -- a function from
types to types -- for all constructors,
\begin{center}
\hsCode{type family X&$_{C_1}$& &$\quad\xi$&} \\
$\vdots$ \\
\hsCode{type family X&$_{C_n}$& &$\quad\xi$&} \\
\hsCode{type family X&$_{\mathrm{Extra}}$& &$\xi$&}
\end{center}
\item add one field of type $X_{\mathrm{Con}}~\xi$ to every constructor.
\begin{center}
\hsCode{data D &$\xi$& = C&$_1$& ... | ... | Extra} \\
$\downarrow$ \\
\hsCode{data D &$\xi$& = C&$_1$& (X&$_{C_1}$& &$\xi
$&) ... | ... | Extra (X&$_{\mathrm{Extra}}$& &$\xi$&)}
\end{center}
\end{enumerate}
This small refactoring enables the programmer to both restrict the use of
certain constructors and introduce new constructors depending on the extension
descriptor $\xi$. The programmer can apply these modifications by manipulating
the definitions of the type families rather than the original data type itself.
It also lets the programmer add new fields to the existing constructors, again
depending on the particular type $\xi$.
To define the original data type without extensions in terms of its extensible
variant, it suffices to fix the extension descriptor to some type, \eg to
\hsType{Void}, and omit any equations for the type families. Doing so leaves
the type level applications of the shape $X_{\mathrm{Con}}~\mathrm{Void}$
irreducible and thus isomorphic to any empty type, with the only valid value
being $\bot$ (such as \hsIdent{undefined}). In effect, the extension fields of
constructors cannot be pattern-matched against, because they have no
constructors. The extension constructor $\mathrm{Extra}$ can still be matched,
but cannot hold any data. It can be hidden completely by not exporting it from
the module of definition.
For extensions, it suffices to add type family instances -- the analogy of
function equations for type functions -- which resolve a particular assignment
of the extension descriptor to the desired type of the extension.
As presented, the Trees that Grow transformation leaves much to be desired from
a usage perspective: we have to pass a \hsIdent{void} or \hsIdent{undefined}
for unused extension fields during construction, these extensions also clutter
the pattern matches, and matching on both constructors and multiple fields
added via extensions is clunky at best. These grievances can be solved by the
use of a convenient syntactical feature of Haskell called \textit{pattern
synonyms}\cite{pattern-synonyms}. These let the programmer abstract over
patterns and so define reusable interfaces to the data types extended via the
Trees that Grow transformation, hiding the structural complexity of the
underlying flexible data type.
For an in-depth description of the design pattern, its generalisations to
multiple type parameters, existentials, \acrshortpl{gadt}, hierarchies of
extension descriptors, as well as relations to generic programming,
typeclasses, and for many other practically useful details, we
recommend~\cite{trees-that-grow}, which introduces the idea.
Although the Trees that Grow pattern is not used universally throughout the
\acrshort{ghc} project, its concepts play an important role in many of the core
data types.
\end{tcolorbox}
\subsubsection*{Compiler back end}
The back end of the compiler starts with the desugaring phase, which translates
the resolved and type checked surface syntax into an \acrfull{ir} called Core.
The Haskell language contains many redundancies and shorthands designed to make
the syntax more user-friendly. The \acrshort{ast} data types contains hundreds
of constructors. In contrast, Core only has about 10 syntactical forms.
Essentially, it is a variant of System F extended with type equality
coercions~\cite{system-fc}.
Although Core is typed, the compiler only type checks Core programs if the user
explicitly asks for it. Core types exist mostly to validate the compiler's
internal consistency -- desugaring a Haskell program that passed typechecking
into incorrectly typed Core would be a compiler bug.
Most of the optimisation passes \acrshort{ghc} performs are local
semantics-pre\-serv\-ing transformations of Core (Core-to-Core passes) which
are applied in many iterations during invocations of the
simplifier~\cite{cmtary-core2core}. These include \eg constant folding,
inlining, or fusion of nested case expressions. The local rewritings improve
intermediate code between applications of heavier optimisations, such as
specialisation (to eliminate overloading), demand analysis, \hsCode{let}
floating, and others.
The optimised Core is transformed to a slightly different representation which
corresponds to programs of an abstract graph reduction machine, the
\acrfull{stg} (\cite{stg-classic}, later revised in \cite{stg2}). It is
translated again to the low-level imperative language \texttt{Cmm}, a dialect
of C$--$, before entering one of the final stages of the code generation phase.
A successful run of the compiler typically terminates in \acrshort{ghc}'s
built-in native code back end, but the \texttt{Cmm} representation can be
translated to \acrshort{llvm} bitcode and additionally processed by the
\acrshort{llvm} pipeline.
A notable divergence in the compiler is the bytecode compilation pipeline.
Bytecode is executed by the \acrshort{rts} interpreter, the backbone of
\acrshort{ghc}'s interactive interface (\acrshort{ghci}). \acrshort{ghci}
includes a debugger which can pause and resume the evaluation of an interpreted
Haskell program and print the runtime values of local bindings. We will discuss
\acrshort{ghci} in greater detail in Chapter~\ref{sec:analysis-design}. The
bytecode pipeline does not involve optimisations, the conversion to
\acrshort{stg}, or any later passes. Instead, the separate generator translates
Core directly to bytecode instructions, although this is about to change in
\acrshort{ghc} 9.2 \cite{mr-ghci-stg-unboxed}.
\subsubsection*{Compiler plugins}
Both the front end and the back end of the compiler can be modified or extended
in a modular way using \textit{compiler plugins}, which come in two main
flavours:\footnote{
There are other types of plugins as well, including typechecker, hole fit,
front end, and DynFlags plugins, but these are not that relevant to our
work.
} Core plugins on the back end and source plugins on the front
end~\cite{ghc-source-plugins}. The former act on the Core language and are best
suited for optimisations, while high level analyses, language extensions and
code generation are better handled by the latter. We will discuss source
plugins in more detail in Chapter~\ref{sec:analysis-design}.
\subsubsection*{\acrlong{rts}}
The runtime system consists of about 50,000 lines of C and C$--$ code. It
implements all the functionality Haskell programs require that is not compiled
into the programs themselves, much of which involves low-level interactions
with abstractions provided by the operating system. The major components of the
\acrshort{rts} are the following:
\begin{itemize}
\item A user-space scheduler which multiplexes lightweight Haskell threads
onto heavy \acrshort{os} threads,
\item a storage manager, including a block allocation layer, which
abstracts over memory management, and a parallel generational garbage
collector,
\item primitives for exception handling, concurrency, and built-in
operations,
\item a bytecode interpreter and a dynamic linker for \acrshort{ghci}, and
\item support for \acrfull{stm}.
\end{itemize}
The scheduler is at the heart of the \acrshort{rts}. Haskell threads yield to
the scheduler when their assigned slice of execution time expires, when they
run out of heap or stack space, or when they need to switch between machine
code execution and bytecode interpretation. Any foreign calls into or out of
Haskell need to pass through the scheduler as well.
The storage manager defines the data structures which represent Haskell values
at runtime. Since the understanding of these representations is crucial for the
understanding of the implementation of laziness in \acrshort{ghc} and the
trade-offs involved, we will discuss the relevant parts of the storage manager
here.
\begin{figure}[h]
\centering
\begin{tikzpicture}[
memory slab/.style={
rectangle, draw,
},
]
\node[
memory slab, rectangle split, rectangle split parts = 2,
rectangle split horizontal, align = center
] (obj) at (0, 0) {
Header
\nodepart[text width = 70pt]{second} Payload
};
\node[
memory slab, rectangle split, rectangle split parts = 2,
align = center
] (header) at (1, -2) {
Info table
\nodepart{second} Entry code
};
% for some reason, just (a) |- (b) doesn't work, even though (a) -| (b) works fine...
\draw[->] (obj.one south) -- (obj.one south |- header.one split west) -- (header.one split west);
\end{tikzpicture}
\caption{The memory layout of a generic closure.}
\label{fig:closure-layout}
\end{figure}
\textit{Closures} (the runtime objects of programs compiled with
\acrshort{ghc}) share the same basic representation shown in Figure
\ref{fig:closure-layout}. The \textit{header} contains primarily a pointer to
the metadata of a closure, though it also includes a profiling header if
profiling is enabled. The \textit{payload} of a closure usually contains data
not known at compile time. The \textit{info table} identifies the type of the
closure (data constructor, function, thunk, \ldots). It informs the garbage
collector about the pointer\-hood of the payload. The \textit{entry code} is
the code executed when \textit{entering}, \ie evaluating the closure. For
example, the entry code of functions represents the body of the function.
The \acrshort{stg} uses a number of registers, a heap, and a stack which stores
function arguments and continuations. Closures can also reside statically in
the compiled object code of a Haskell program. During execution, any heap
allocations are preceded by a \textit{heap check}, which invokes garbage
collection if not enough space is left on the heap. Similarly, when code needs
to push values onto the stack, it performs a \textit{stack check} and grows the
stack if necessary.
All the dynamic allocations are managed by the garbage collector, including
stack frames and lightweight threads.
There are over 60 different types of closures. Here is a summary of the most
important ones:
\begin{description}
\item[Function closures] represent Haskell functions. When entered,
functions assume that all their arguments are present at the top of the
stack. This is known as the \textit{eval/apply} evaluation
model~\cite{eval-apply}.
The payload of a function closure carries pointers to the free
variables of the function's body.
\item[Thunks] represent unevaluated expressions. When entered, the
corresponding expression is evaluated and the closure is replaced with
an indirection to the resulting value. This ensures that thunks are not
evaluated multiple times, as subsequent attempts at evaluation will
instead enter the indirection which will simply return the existing
value.
\item[Indirections] are proxies to other closures. Their payload is simply
a single pointer to the target object. To reduce the overhead of
sharing, indirections are removed by the garbage collector and never
outlive the youngest generation.
\item[Black holes] are thunks under evaluation, with a layout identical to
that of indirections. Since thunks are shared across threads, a thread
entering a black hole blocks until it is overwritten with an
indirection to the evaluated object.
\item[Data constructors] carry their arguments (fields) as payload, ordered
such that pointers come first. Their entry code returns immediately to
the topmost stack frame (a constructor itself is always evaluated,
although its arguments may not be).
\item[Thread state objects] represent lightweight Haskell threads,
including their stacks. Since a \acrshort{tso} is simply a closure, it
is managed by the garbage collector, just like any other heap object.
The garbage collector sends exceptions to blocked threads which become
unreachable.
\end{description}
\subsection{Strictness features}
\acrshort{ghc} is not only used for production ready Haskell, but also serves
as an incubator of new language features -- including those directly related to
managing the amount of laziness in a program. These allow programmers to aid
the compiler in optimising by avoiding unnecessary non-strictness where its
static analysis does not suffice.
A simple and robust method of preventing undesired laziness is the
language extension \texttt{BangPatterns}, which introduces a new pattern syntax
\hsPat{!pat} for forcing an expression to \acrshort{whnf} before
pattern-matching it against \hsPat{pat}. For short functions and clear
algorithms which do not benefit from pervasive laziness it is often very easy
to simply annotate certain patterns in the program with exclamation marks and
observe a reduction in memory consumption.
The language extension shares the exclamation mark syntax with the Haskell 2010
strictness flags feature~\cite{haskell2010-strictness-flags}. While
\texttt{BangPatterns} add optional strictness to pattern matching, strictness
flags do the same for data types. Unfortunately, proper use of this flexibility
hinges on the programmer's knowledge of how is the particular piece of code
going to be used. While it is good practice to request the early evaluation of
values which will have to be forced anyway, sprinkling strictness annotations
throughout library code in an attempt to prevent space leaks may lead to the
unintentional sacrifice of the benefits of laziness, even preventing some usage
patterns in subtle ways. Additionally, since these strict evaluation facilities
only force thunks to \acrshort{whnf}, the evaluated objects may still retain
large delayed expressions. The ability to excise thunks from a Haskell value
completely was the core motivation for the development of the \hackage{deepseq}
library.
The lack of programmer insight into how a piece of code is used in a program
and what strictness properties it has is a major developer experience
issue~\cite{memory-profiling-blog-post, anatomy-of-thunk-leak-blog-post,
nothunks-blog-post, haskell-space-leaks, detecting-space-leaks-blog-post}.
Some of the discussed debugging tools help ameliorate the problem, but
\acrshort{ghc} itself includes features especially suited to doing so. The
compiler supports two profiling modes, cost-centre profiling and
``ticky-ticky'' profiling, which the \acrshort{ghc} User's Guide dedicates a
chapter to~\cite{ghc-profiling}. While the ``ticky-ticky'' mode is only of
interest to \acrshort{ghc} developers, the cost-centre profiling functionality
is an easy-to-use tool for understanding the time and space behaviour of
Haskell programs. All it requires of the programmer is a recompilation of the
modules of interest with a few specific compiler options.
Cost-centre profiling assigns the so-called ``cost-centres'' to certain
sections of code. The \acrshort{rts} records any time spent and allocations
performed during the evaluation of code associated with a cost-centre. These
recordings are summarised by a time and allocation profiling report, which the
profiled program generates. The report indicates the time and space
requirements of each cost centre in proportion to the entire program.
\acrshort{ghc} is able to introduce cost centres automatically by adding them
to all non-in\-lined bindings, but the user also has the option to annotate
terms with a pragma to fine-tune the placement of cost centres.
\acrshort{ghc}'s implementation of profiling can shed some light on the use of
call-by-need in a Haskell program. The compiler can also provide certain deeper
insights about the program's strictness, although it presents them in a
substantially less user-friendly manner. In particular, \acrshort{ghc} can
output the translation of surface syntax to its internal language, Core. Being
a fairly small $\lambda$ calculus, Core has a clearer semantics including a
strict pattern-matching operator \hsCode{case e of arms...}, which indicates
obviously strict subexpressions. Furthermore, the Core output features
\textit{demand signatures}, inferred by \acrshort{ghc}'s demand
analysis~\cite{cmtary-demand-analysis}, which classify binders depending on how
strict they are in their arguments and to what extent do they use the
components of arguments of product types. The results of demand analysis are
crucial for subsequent optimisation. Understanding the demand signatures of a
program can equip the programmer with the information necessary to determine
which patterns would most benefit from the \texttt{BangPatterns} extension,
which data types could be annotated with strictness flags, and which parts of
the program should be refactored in other ways in order to improve the native
code generated by the compiler.
The \acrshort{ghc}-provided tooling outlined above -- particularly the option
to dump Core code during compilation and analyse demand signatures -- is rather
obscure. It is reasonable to expect the average Haskell programmer to only
reach for the profiling tools in a time of dire need, \eg when writing
high-performance code or dealing with unacceptable space leaks. It is further
reasonable not to expect the average Haskell programmer to know the internals
of the compiler well enough to ask it for the Core representation of their
program, or indeed to be aware at all of the existence of demand signatures,
which are only described in the \acrshort{ghc} Commentary.\footnote{
The commentary is intended for \acrshort{ghc} developers and is hosted on a
GitLab instance (online), unlike the User's Guide which is bundled with the
\acrshort{ghc} distribution and revised for every release.
} Perhaps it would be interesting to include the strictness information
inferred by the compiler in interfaces programmers often interact with, such as
the various widgets provided by the \acrfull{hls}~\cite{gh-hls}, but to our
knowledge no such tool exists at the time of writing.
In theory, the \acrlong{ghc}'s optimisations are advanced enough to compile the
majority of Haskell code fairly efficiently, without space leaks or allocation
slow-downs, while enabling the greater flexibility, code reuse, and abstraction
of a non-strict language. However, inefficiencies introduced to support
unnecessary laziness which are small enough not to cause substantial problems
could hide in the compiled program. It is a part of the motivation behind this
thesis to lay the groundwork necessary for their detection.
\section{Existing tools} \label{sec:existing-tools}
Apart from functionality implemented in the compiler itself, the Haskell
environment includes a number of practical solutions to help with debugging. A
few of these deal specifically with the issues with laziness that Haskell
programmers have to face.
\subsection*{Hoed} \label{sec:hoed}
Hoed~\cite{gh-hoed} is a tracer and debugger for Haskell. Unlike the built-in
debugger of \acrshort{ghci}, Hoed is implemented as a regular Haskell library.
Users of Hoed manually annotate functions of interest to make the tracer
capture relevant information during execution. The annotations are simply calls
to the provided debugging function \hsIdent{observe} with a signature similar
to that of the \hsIdent{trace} function from the \hsModule{Debug.Trace} module
of Haskell's standard library. Both \hsIdent{trace} and \hsIdent{observe}
circumvent the guarantees of the type system and are in fact impure.
\hsIdent{observe} has type \hsType{Observable a => Text -> a -> a}, its
\hsType{Text} argument has to equal the name of the function being annotated.
The \hsType{Observable} constraint on \hsType{a} is used by Hoed internally,
the typeclass has a default implementation. The resulting trace of the
debugging session is exposed via a web-based interface, to which the users
connect with a regular web browser. Hoed's traces include information about
which functions have been called during the execution of the annotated program
and what were their arguments. It only collects information about annotated
functions.
Hoed features several tools to help users analyse problems with their code and
find the culprits of test failures. One of these is \textit{algorithmic
debugging}, an interactive trace browser which uses an algorithm similar to
binary search to locate the deepest incorrect function in the recorded call
tree. It does so by asking the user questions about whether certain evaluations
were correct, working its way gradually deeper into the tree. The ``algorithmic
debugger'' ultimately reports the faults it located.
While Hoed's approach to debugging is certainly interesting and quite distinct
in comparison to debuggers in other languages, it lacks any kind of awareness
of the low-level details of non-strictness. Hoed is thus intended for use with
property testers like QuickCheck~\cite{quickcheck-paper}, and not as a tool for
the identification and resolution of language implementation -dependent issues,
such as memory leaks.
\subsection*{\hackage{nothunks}} \label{sec:nothunks}
\hackage{nothunks} is a recently released Haskell package which helps in writing
thunk-free code. It defines a new typeclass, \hsTC{NoThunks}, along with
instances for common Haskell types. Any type with a \hsTC{NoThunks} instance
can be inspected for unexpected thunks. The library also implements a number of
alternatives to common functions from the Prelude. These re\-implementations
check for unexpected thunks introduced during execution, throwing an exception
whenever a thunk is detected.
The exceptions of \hackage{nothunks} contain helpful information about the
context of the thunk which the library function detected, guiding the
programmer in locating the unexpectedly lazy code or data structure. The
library also allows various relaxations to the strictness of its inspection
policy, such as the \hsType{OnlyCheckWhnf} and \hsType{AllowThunk}
\hsCode{newtype}s. Thanks to \hsModule{GHC.Generics}\cite{ghc-generics},
\hackage{nothunks} also offers the convenient \hsCode{deriving (Generic,
NoThunks)} syntax to add instances of the necessary typeclasses for custom data
structures automatically.
The \hackage{nothunks} package can greatly help fix serious memory leaks caused
by thunk accumulation. However, it is intended primarily for the complete
removal of thunks from the runtime state of a program, and does not help with
careful strictness analysis.
\subsection*{Hat} \label{sec:hat}
The Haskell Tracer Hat~\cite{proj-hat} is a source-level tracer. It works by
compiling Haskell source files to annotated -- but still textual -- Haskell
source files. After this source-to-source translation, the user compiles the
annotated source code and runs it to produce a Hat trace.
The trace is a rich recording which contains high-level information about each
reduction the program performed. Hat comes with a number of utilities for
exploring the trace files, including some forms of forward and backward
debugging, filtering utilities which show all arguments passed to top-level
functions, virtual stack traces, and even an interactive tool for locating
errors in a program, similar to one of the features of \nameref{sec:hoed}.
Hat was initially developed for the \texttt{nhc} Haskell
compiler~\cite{hat-history}. It centred around the idea of using a single, rich
trace of a program's execution to support several different kinds of debugging.
Despite its advanced features, it did not seem to attract many
users~\cite{hat-history}, possibly due to feature disparities between the
supported syntax and new language extensions.
Hat's source-to-source translation makes it portable between different
compilers. The project uses the \hackage{haskell-src-exts} package to parse the
language, rather than relying \eg on the \acrshort{ghc} \acrshort{api}. While
Hat cannot directly answer questions about the strictness of debugged
functions, its approach to rewriting the source language is interesting.
Tracing necessarily leads to runtime overhead, but the code produced by Hat is
subject to compiler optimisations. Hat therefore does not need to worry about
low-level details of the optimiser and how it reorders, splits and combines
expressions. The connection between the tracing code and the original source is
maintained thanks to the semantics-preserving nature of optimisations.
\subsection*{\hackage{htrace}} \label{sec:htrace}
\hackage{htrace} \cite{hkg-htrace} is a simple package which exports a single
function: \hsCode{htrace :: String -> a -> a}. As the name and function
signature suggest, this function mirrors the behaviour of the standard
\hsIdent{trace}, except that \hsIdent{htrace} hierarchically indents the
tracing messages based on the current call depth. It works simply by
manipulating a global mutable variable and hiding this fact from the user with
\hsIdent{unsafePerformIO}.
Although very simple and oblivious to any laziness implementation details, this
approach is still useful for debugging purposes. The indented tracing messages
suggest the depth to which various thunks are evaluated at different points of
the program's operation.
\subsection*{\hackage{ghc-heap-view}} \label{sec:ghc-heap-view}
\hackage{ghc-heap-view} is a Haskell package which enables advanced
introspection of the Haskell heap from within pure Haskell code. It relies on
the \hackage{ghc-heap} library which comes bundled with \acrshort{ghc}.
The library's notable high-level features include a function which attempts to
recreate readable Haskell source code from a runtime value, using \hsCode{let}
bindings to express sharing. There are also tree and graph data structures for
heap mapping and a high-level algebraic data type for all Haskell closures,
complete with their info tables.
\subsection*{Haskell Program Coverage} \label{sec:hpc}
\acrlong{hpc}\cite{hpc-paper} is (unsurprisingly) a code coverage tool for
Haskell. Unlike the other tools in this section, \acrshort{hpc} is not directly
related to laziness control and debugging. Similarly to Hat, \acrshort{hpc} has
a source-to-source mode of operation but additionally offers tight integration
with \acrshort{ghc} and comes bundled with modern releases of the compiler. It
supports all \acrshort{ghc} language extensions.
\acrshort{hpc} allows easy instrumentation of arbitrarily complex Haskell
programs without source annotations. It wraps subexpressions in the program
with an unsafe side-effecting function which records its evaluation by mutating
a module-wide array of integer counters. The final state of the per-module
arrays forms the \acrshort{hpc} trace. This architecture is wired into the
\acrshort{ghc} compiler pipeline in all the major data structures (the surface
syntax, Core language, and \acrshort{stg}), which makes it both robust and
per\-for\-mant. The tool comes bundled with utilities for displaying the
original source code with colourful mark-up, highlighting interesting
subexpressions based on the information extracted from the trace. Notably,
\acrshort{hpc} supports traces of the boolean values of pattern guards, which
are added to the visualisation.
\acrshort{hpc}'s feature set can be of tremendous help to the Haskell
programmer, especially when combined with tools like
QuickCheck~\cite{quickcheck-paper}. However, its traces are tuned specifically
for code coverage and do not contain enough information to be useful for any
kind of dynamic strictness analysis. While the \acrshort{hpc} traces are
sufficiently granular, the subexpression counters lack necessary information
about their execution context and timing.
\subsection*{Summary} \label{sec:summary}
Table \ref{tbl:thunk-manager-comparison} summarizes the surveyed tooling. The
\textit{Memory awareness} column suggests to what extent is the particular
package or program aware of runtime representations. Independence of the
structures underlying Haskell values leads to better portability and a clean
interaction with regular Haskell code. On the other hand, more low-level
approaches such as \nameref{sec:ghc-heap-view} give a much clearer view of the
runtime state.
\begin{table}\noindent
\begin{minipage}{\textwidth} % support for footnotes within a tabular environment
\centering
\small
\begin{tabularx}{\textwidth}{|| l *{3}{L} >{\raggedright\arraybackslash}X ||}
\hline
Tool
& Source changes
& Order of evaluation
& Thunks
& Memory awareness
\\ \hline \hline
\nameref{sec:hoed}
& Required % source changes
& Recorded % order of evaluation
& Transparent % thunks
& None % memory awareness
\\ \hline
\nameref{sec:nothunks}
& Required % source changes
& Ignored % order of evaluation
& Detected % thunks
& Limited % memory awareness
\\ \hline
\nameref{sec:hat}
& Unnecessary % source changes
& Recorded % order of evaluation
& Transparent % thunks
& None % memory awareness
\\ \hline
\nameref{sec:htrace}
& Required % source changes
& Illustrated % order of evaluation
& Transparent % thunks
& None % memory awareness
\\ \hline
\nameref{sec:ghc-heap-view}
& Unnecessary % source changes
& Ignored % order of evaluation
& Reified % thunks
& Full % memory awareness
\\ \hline
\end{tabularx}
\caption{An overview of existing solutions to thunk discovery and laziness
debugging.}
\label{tbl:thunk-manager-comparison}
\end{minipage}
\end{table}
Despite Haskell users' considerable interest in avoiding the implicit delaying
of computations which the language is notorious for, there are no records of a
large-scale study of the use of laziness in practice akin to
\cite{emp-study-laziness-r}. The tool with a feature set closest to what is
necessary for a comprehensive analysis of the practical use of laziness is
likely \hackage{ghc-heap-view}, which allows the user to interactively inspect
the heap objects and look inside thunks using \acrshort{ghci}. However, the
package primarily provides a rich library interface. It does not implement a
tracing mode, which would facilitate collection of laziness-relevant
information during the execution of entire programs.
\chapter{Analysis and design} \label{sec:analysis-design}
The goal of this work is to design and implement a tool suitable for
understanding how is laziness used in real-life Haskell programs. To analyse
the practical implications of \acrshort{ghc}'s implementation of non-strict
semantics, we have to understand the strictness properties of functions. For
example, some arguments may be evaluated if and only if others are. Our tool
must capture these dependencies and usage patterns, as they may uncover both
use cases where laziness is essential and places where it could be safely
avoided, even though static analysis cannot determine so. In this chapter, we
focus on the task of dynamic tracing and evaluate two possible solutions to the
problem.
\section{Approach} \label{sec:approach}
Dynamically inferring the strictness properties of functions requires a peek
under the hood of Haskell's runtime machinery. Typical Haskell code is
oblivious to the underlying representation of the values it manipulates, as
reification of the heap objects underneath the abstractions would weaken
equational reasoning and parametricity.
Once we have the power to inspect the runtime representations of values, we
need to use it to determine the strictness of functions. A function \hsIdent{f}
is strict in an argument \hsIdent{a} if \hsIdent{a} has to be evaluated
whenever \hsCode{f a} is evaluated.
There is a number of possible approaches to this problem. As discussed in
Chapter~\ref{sec:state-of-the-art}, related projects which we could build on
already exist, at various levels of abstraction. At the lowest level, we could
modify the \acrshort{rts} and extract information about heap objects there. We
could also modify the compiler in various ways, since it already includes
support for \acrshort{hpc} and profiling, which is similar to the tracing we
would like to implement. Another option is to follow the path of