查看源代码 BEAM SSA 的结构和格式不变性

异常处理

try-catch 表达式转换为 BEAM SSA 具有以下结构

@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

SSA 必须保持以下不变性

  • 所有可能在受保护的代码块中导致异常的代码,都必须具有到落地代码块的显式控制流边。如果除了包含 new_try_tag 的代码块外,没有其他边指向落地代码块,编译器将删除冗余的异常处理程序。
  • 必须按照类、原因和堆栈跟踪的顺序从 landingpad 指令的结果中提取它们。允许省略未使用的元素提取。
  • 落地代码块和最终受保护的代码块都必须以 kill_try_tag 指令结束。尝试在最后一个受保护的代码块和落地代码块之间共享 kill_try_tag 尾声是不太可能成功的。

将旧式 catch 表达式转换为 BEAM SSA 具有以下结构

@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

try-catch 表达式一样,所有可能在受保护的代码块中导致异常的代码,都必须具有到落地代码块的显式控制流边。

重新抛出异常

典型的用户编写的 try-catch 表达式会捕获所有可能的异常类和原因的子集,并将未处理的异常留给调用堆栈中更上一层的处理程序。使用 resume 指令重新抛出异常。resume 必须在程序流中的 kill_try_tag 指令之后。例如,如果 异常处理部分中的示例 仅处理用户 throws,则相关的代码块如下所示

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

函数调用

除非应用以下例外情况之一,否则不在尾调用位置的所有函数调用之后都必须跟随 succeeded:body 指令

  • 可以静态证明该函数调用总是会失败。

  • 该函数调用是到 erlang 模块的,并且可以静态证明总是会成功或失败。

变量命名

BEAM SSA 中的变量名要么是原子,要么是非负整数

atom() | non_neg_integer()

为了生成新的、未使用的变量名,所有编译器转换都维护一个计数器,即 b_functionopt_st 记录中的 cnt 字段,每次创建新的变量或标签时,该计数器都会递增。在以下描述中,cnt 字段的值称为 CntCnt 值保证永远不会与先前定义的变量名冲突。因此,Cnt 的值可以直接用作 SSA 传递中的变量名。

请注意,在 Erlang/OTP 27 之前,规则更加复杂,因为 Cnt 值可能会与其他变量冲突。