<!--
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2025. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
-->

BEAM SSA Checks
===============

While developing optimizations operating on the BEAM SSA it is often
hard to check that various transforms have the intended effect. For
example, unless a transform produces crashing code, it is hard to
detect that the transform is broken. Likewise missing an optimization
opportunity is also hard to detect.

To simplify the creation of tests on BEAM SSA the compiler has an
internal mode in which it parses and checks assertions on the
structure and content of the produced BEAM SSA code. This is a short
introduction to the syntax and semantics of the SSA checker
functionality.

Syntax
------

SSA checks are embedded in the source code as comments starting with
with one of `%ssa%`, `%%ssa%` or `%%%ssa%`. This is a short
introduction the syntax, for the full syntax please refer to the
`ssa_check_when_clause` production in `erl_parse.yrl`.

SSA checks can be placed inside any Erlang function, for example:

    t0() ->
    %ssa% () when post_ssa_opt ->
    %ssa%   ret(#{}).
      #{}.

will check that `t0/0` returns the literal `#{}`. If we want to check
that a function returns its first formal parameter, we can write:

    t1(A, _B) ->
    %ssa% (X, _) when post_ssa_opt ->
    %ssa%   ret(X).
      A.

Note how we match the first formal parameter using `X`. The reason for
having our own formal parameters for the SSA check, is that we don't
want to introduce new identifiers at the Erlang level to support
SSA-level checks. Consider if `t1/2` had been defined as `t1([A|As],
B)` we would have had to introduce a new identifier for the aggregate
value `[A|As]`.

The full syntax for a SSA check clause is:

    <expected-result>? (<formals>) when <pipeline-location> -> <checks> '.'

where `<expected-result>` can be one of `pass` (the check must
succeed), `fail` and `xfail` (the check must fail). Omitting
`<expected-result>` is parsed as an implicit `pass`.

`<formals>` is a comma-separated list of variables.

`<pipeline-location>` specifies when in the compiler pipeline to run
the checks. For now the only supported value for `<pipeline-location>`
is `post_ssa_opt` which runs the checks after the `ssa_opt` pass.

`<checks>` is a comma-separated list of matches against the BEAM SSA
code. For non-flow-control operations the syntax is:

    <variable> = <operation> ( <arguments> ) <annotation>?

where `<operation>` is the `#b_set.op` field from the internal SSA
representation. BIFs are written as `bif:<atom>`.

`<arguments>` is a comma-separated list of variables or literals.

For flow control operations and labels, the syntax is as follows:

    br(<bool>, <true-label>, <false-label>)

    switch(<value>, <fail-label>, [{<label>,<value>},...])

	ret(<value>)

	label <value>

where `<value>` is a literal or a variable.

A check can also include an assertion on operation annotations. The
assertion is written as a map-like pattern following the argument
list, for example:

    t0() ->
    %ssa% () when post_ssa_opt ->
    %ssa% _ = call(fun return_int/0) { result_type => {t_integer,{17,17}},
    %ssa%                              location => {_,32} },
    %ssa% _ = call(fun return_tuple/0) {
    %ssa%    result_type => {t_tuple,2,true,#{1 => {t_integer,{1,1}},
    %ssa%                                     2 => {t_integer,{2,2}}}}
    %ssa% }.
        X = return_int(),
        Y = return_tuple(),
        {X, Y}.

Semantics
---------

When an SSA assertion is matched against the BEAM SSA for a function,
patterns are applied sequentially. If the current pattern doesn't
match, the checker tries with the next instruction. If the checker
reaches the end of the SSA representation without having matched all
patterns, the check is considered failed otherwise the assertion is
considered successful. When a pattern is matched against an SSA
operation, the values of variables already bound are considered and if
the patterns matches, free variables introduced in the successfully
matched pattern are bound to the values they have in the matched
operation.

Compiler integration
--------------------

The BEAM SSA checker is enabled by the compiler option
`{check_ssa,post_ssa_opt}`. When enabled, a failed SSA assertion will
be reported using the same mechanisms as an ordinary compilation
error.

Limitations
-----------

* Unbound variables are not allowed in the key-part of map literals nor
in annotation assertions.
