Skip to content

Commit 7d47178

Browse files
author
doyougnu
committed
lambda lifting: added closure conversion details
1 parent effe924 commit 7d47178

File tree

2 files changed

+52
-4
lines changed

2 files changed

+52
-4
lines changed

src/Optimizations/GHC_opt/lambda_lifting.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,11 @@ How Lambda Lifting Works in GHC
6868
-------------------------------
6969

7070
GHC does have a lambda lifting pass in STG, however lambda lifting is not the
71-
default method GHC uses for handling local functions. GHC uses an alternative
72-
strategy called :term:`Closure Conversion`...
71+
default method GHC uses for handling local functions and free variables.
72+
Instead, GHC uses an alternative strategy called :term:`Closure Conversion`,
73+
which creates more uniformity at the cost of extra heap allocation.
74+
75+
Automated Lambda Lifting in GHC is *Selective* and based on several heuristics:
7376

7477

7578
Observing the Effect of Lambda Lifting

src/glossary.rst

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,50 @@ Glossary
2222

2323
Closure Conversion
2424

25-
Start here tomorrow!
25+
Closure conversion is the default way GHC treats free variables in a
26+
function body. Closure Conversion creates a top level record for the
27+
original function, called the function environment, whose fields are the
28+
free variables of the function. The environment is passed to the function
29+
as an implicit parameter and the free variable call sites are rewritten as
30+
field accesses. Then the function and the record are grouped in a tuple,
31+
i.e., a closure (pair of environment and function) is created causing some
32+
extra heap allocation. Finally the call sites of the original function are
33+
rewritten to pass the environment with the original function. Consider
34+
this example:
35+
36+
.. code-block:: haskell
37+
38+
...
39+
let f = foldl (\acc _ -> acc + x) y xs
40+
in f [1..100]
41+
...
42+
43+
In this example ``x`` and ``y`` are free variables in the function ``f`` .
44+
Closure conversion will capture them and transform this function to:
45+
46+
.. code-block:: haskell
47+
48+
...
49+
-- the function environment
50+
data EnvF = EnvF { x :: Int, y :: Int }
51+
52+
-- the new function
53+
f_cc env xs = foldl (\acc _ -> acc + x env) (y env) xs
54+
55+
-- the closure that replaces the original function in the same scope
56+
let f = (f_cc, EnvF x y)
57+
in (fst f) (snd f) [1..100]
58+
...
59+
60+
Notice closure conversion has *added* an extra ``let`` expression for the
61+
closure and the reference to ``x`` and ``y`` have been replaced with
62+
accesses to ``env`` . The let expression can be a source of extra heap
63+
allocations and is one of the costs of closure conversion. However, the
64+
benefits are uniformity; every function can be treated as a closure.
65+
Closure conversion is often contrasted with Lambda Lifting which is
66+
another strategy to handle free variables that does not incur extra heap
67+
allocation. See :cite:t:`lambdaLifting` and
68+
:cite:t:`selectiveLambdaLifting` for more.
2669

2770
CAF
2871

@@ -38,6 +81,8 @@ Glossary
3881
bar :: (Int, [Int])
3982
bar = ((*) 10 10, [1..])
4083
84+
-- not a lambda, curried functions that can be reduced when given an
85+
-- input are CAFs
4186
baz :: Int -> Int
4287
baz = (*) 3
4388
@@ -46,7 +91,7 @@ Glossary
4691
qux e = e * 3 -- equivalent to baz but is a lambda so not a CAF
4792
4893
quux :: Int -> Int
49-
quux = (*) 10 x -- x is free thus
94+
quux = (*) x -- x is free thus not a CAF
5095
5196
These values are *constant* because they don't bind any variables or have
5297
any free variables. Because they are constant they are floated (see

0 commit comments

Comments
 (0)