aboutsummaryrefslogtreecommitdiffstats
path: root/lib/compiler/internal_doc/cerl-notes.md
blob: 705fe8f42d8c93f8c21ea93557ddfdfe42f9c2a2 (plain) (blame)
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
Some notes on the cerl modules
==============================

Maps in cerl_clauses:match/3
----------------------------

Not much optimization is done for maps in `cerl_clauses:match/3`.

The reason is that the inliner (`cerl_inline`) was not designed for
data types that depend on variables bound in the enclosing environment
(for example, the keys for maps).  If we attempt to extend the
optimizations for maps similar to the optimizations for the other data
types, the inliner will crash for certain code.  Here is an example of
code that would crash the inliner:

     t() ->
        f(key).

     f(K) ->
        case #{K => value} of
          #{key := V} -> V
        end.

The reason for the crash is that the inliner works in several
passes and calls `cerl_clauses:match/3` twice.  The inliner will
assume that the same result will be returned both times, but
for this example different bindings will be returned.

Here is the rough outline of what happens:

* The first time `cerl_clauses:match/3` will be asked to match the
pattern `#{K := V}` against `#{key => value}`. It cannot say more
than that the pattern *may* match.

* Now the inliner will add the bindings to body of the clause (which
is simply `V`).  In this case, the bindings are still empty, so
nothing is added.

* The inliner will now do some substitutions and renaming.  The
variable `K` will be replaced with `key`.

* The next time `cerl_clauses:match/3` is called, it will be asked to
match the pattern `#{key := V}` against `#{key => value#}`.  In this
case, there will be a match and the bindings can be extended with
`{V,value}`.

* The inliner will see that the entire case can be removed.  Only
the body for the clause needs to be kept.

Thus, after inlining the function `t/0` will look like this:

     t() ->
        V.

The problem here is that the inliner assumed that the bindings from
the first and second call to `cer_clauses:match/3` would be the same.
It used the empty bindings from the first call for the body.

The correct way would be to add the bindings from the second call:

     t() ->
        let V = value in V.

### How to fix this ###

The inliner will need to call `cerl_clauses:match/3` after doing
all substitutions and renaming.  It is not clear to me how difficult
that would be.  I assume that the inliner is written the way it is
for a good reason.  That means that switching the order things are
done would lead to correctness and/or performance problems.

### What must also be done to fix this ###

`cerl_inline:make_template/3` must be extended to create a template
for maps.  That is relatively straightforward.