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:
new_try_tag, the compiler will remove the redundant exception handler. landingpad instruction must be done in that order. Omitting the extraction of elements which are unused is allowed. 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.
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 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
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.
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.
© 2010–2023 Ericsson AB
Licensed under the Apache License, Version 2.0.