summaryrefslogtreecommitdiff
path: root/lib/compiler/internal_doc/beam_ssa.md
blob: d32431ecff6969e4e15b9830e013590c7a3f0f67 (plain)
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
Invariants on the Structure and Format of BEAM SSA
==================================================

Exception Handling
------------------

The translation of a `try`-`catch` expression into BEAM SSA has the
following structure:

    @tag = new_try_tag `try`
	br @tag, ^protected_block0, ^landing_pad_block

	protected_block0:
	  @success0 = ... % Something that could raise an exception
	  br @success0, ^protected_block1, ^landing_pad_block

	...

	protected_blockN:
	  % The end of the protected code
	  @ignored0 = kill_try_tag @tag
      br ^after_try_catch

	landing_pad_block:
	  @aggregate = landingpad try, @tag
	  @class  = extract @aggregate, `0` % The error class
	  @reason = extract @aggregate, `1` % The reason
	  @stk    = extract @aggregate, `2` % The stack trace
	  @ignored1 = kill_try_tag @tag
	  %% Pattern matching on @class, @reason, and @stk is done here
	  %% to send control to the appropriate catch clause
      br ^after_try_catch

    after_try_catch:
      % Normal execution continues

The following invariants must hold for the SSA:

 * All code that can cause an exception in one of the protected blocks
   must have explicit control flow edges to the landing pad block. If
   there are no edges to the landing pad block except from the block
   containing the `new_try_tag`, the compiler will remove the
   redundant exception handler.
 * The extraction of the class, reason and stack trace from the result
   of the `landingpad` instruction must be done in that
   order. Omitting the extraction of elements which are unused is
   allowed.
 * Both the landing pad block and the final protected block must end
   with a `kill_try_tag` instruction. Trying to share the
   `kill_try_tag` epilogue between the last protected block and the
   landing pad is unlikely to work.

The translation of an old-style `catch` expression into BEAM SSA has
the following structure:

    @tag = new_try_tag `try`
	br @tag, ^protected_block0, ^landing_pad_block

	protected_block0:
	  @success0 = ... % Something that could raise an exception
	  br @success0, ^protected_block1, ^landing_pad_block

	...

	protected_blockN:
	  % The end of the protected code
	  @successful_result = .... % The result of a successful computation
	  br ^common_end_of_catch

	landing_pad_block:
	   @aggregate = landingpad catch, @tag
	   @catched_val = extract @ssa_agg, `0`
	   br ^common_end_of_catch

	common_end_of_catch:
	  @tmp = phi { @catched_val, ^landing_pad_block },
	             { @successful_result, ^protected_blockN }
	  @result_of_catch_expr = catch_end @tag, @tmp

Just as for a `try`-`catch` expression all code that can cause an
exception in one of the protected blocks must have explicit control
flow edges to the landing pad block.

Exception Re-issuing
--------------------

A typical user-written `try`-`catch` expression will catch a subset of
all possible exception classes and reasons and leave unhandled
exceptions to a handler further up the call stack. Re-issuing an
exception is done with the `resume` instruction. The `resume` must
come after the `kill_try_tag` instruction in the program flow. For
example, if the [example in the Exception Handling Section](#exception-handling)
was to only handle user `throws`, the relevant blocks would look like this:

	landing_pad_block:
	  @aggregate = landingpad `try`, @tag
	  @class  = extract @aggregate, `0` % The error class
	  @reason = extract @aggregate, `1` % The reason
	  @stk    = extract @aggregate, `2` % The stack trace
	  @ignored1 = kill_try_tag @tag
	  @is_throw = bif:'=:=' @class, `throw`
      br @is_throw ^first_block_of_throw_handler, ^reissue

	first_block_of_throw_handler:
	  %% Handle the user-defined throw

	reissue:
	  @tmp = resume @stk, @reason
	  ret @tmp

Function Calls
--------------

All function calls not in a tail call position must be followed by a
succeeded:body-instruction unless one of the following exceptions
apply:

* The function call can statically be proven to always fail.

* The function call is to the `erlang`-module and can statically be
  proven to always succeed or fail.

Variable Naming
---------------

A variable name in BEAM SSA is either an atom, a non-negative integer
or a tuple: `atom() | non_neg_integer() | {atom() | non_neg_integer(),
non_neg_integer()}`. In order to generate fresh unused variable names,
all compiler transforms maintain a counter, the `cnt`-field in the
`opt_st`-record, which is incremented each time a new variable or
label is created. In the following description the value of the
`cnt`-field is called `Cnt`.

Due to peculiarities in the BEAM SSA code generator, a compiler
transformation unfortunately cannot just use the `cnt`-value directly
as a fresh name. There are three basic strategies for creating fresh
variable names which can by used by a compiler pass:

1) A name can be derived from an existing name of the form `V ::
  atom() | non_neg_integer()` by selecting an atom, which is unique to
  the compiler pass, to form a new name `{A, V}`. The same `A` cannot
  be used by strategy 3) below.

2) A name can be derived from an existing name of the form `V ::
  non_neg_integer()` by combining it with the `cnt`-field into `{V,
  Cnt}`.

3) A fresh name can be created by selecting an atom `A`, which is
  unique to the compiler pass, to form the new name `{A, Cnt}`. The
  same `A` cannot be used by strategy 1) above.