How a multi-clause function chooses a clause, read from its bytecode.
A multi-clause function raises FunctionClauseError by jumping to its
own func_info label, so the tests whose failure edge points there are
clause selection, and a function with no such edge accepts every
argument. The atoms those tests compare a register against are the
values the function discriminates on. Four extractors kept private
copies of these readings; this is the one.
Summary
Functions
Every {:f, label} operand of an instruction.
The literal atoms the function compares register against — in
is_eq_exact tests, is_tagged_tuple tests (the tag) and select_val
tables — or against any register when :any. Over-approximated on
purpose: every comparison anywhere in the body counts, and consumers
ask which values are NOT handled.
Where execution continues once register has been established equal to
atom: the instruction after an is_eq_exact against it, or the label
select_val pairs with it. Resolved to instruction indices via
labels (label → index).
Where execution begins: the instruction after func_info, not index
zero, which is the failure landing pad.
The label of the function's func_info instruction, or nil.
Label → instruction index for the function.
Whether some clause accepts every argument: nothing branches to the
func_info label. Guards fall out correctly, since a guarded catch-all
compiles to a test whose failure branch is that label.
Whether some clause accepts every value of register — the message
argument of a callback — whatever it demands of the other arguments.
Functions
@spec branch_targets(tuple()) :: [non_neg_integer()]
Every {:f, label} operand of an instruction.
@spec compared_atoms([tuple()], {:x, non_neg_integer()} | :any) :: [atom()]
The literal atoms the function compares register against — in
is_eq_exact tests, is_tagged_tuple tests (the tag) and select_val
tables — or against any register when :any. Over-approximated on
purpose: every comparison anywhere in the body counts, and consumers
ask which values are NOT handled.
@spec continuations_after([tuple()], {:x, non_neg_integer()}, atom(), %{ required(non_neg_integer()) => non_neg_integer() }) :: [non_neg_integer()]
Where execution continues once register has been established equal to
atom: the instruction after an is_eq_exact against it, or the label
select_val pairs with it. Resolved to instruction indices via
labels (label → index).
@spec entry_index([tuple()]) :: non_neg_integer()
Where execution begins: the instruction after func_info, not index
zero, which is the failure landing pad.
@spec func_info_label([tuple()]) :: non_neg_integer() | nil
The label of the function's func_info instruction, or nil.
@spec labels([tuple()]) :: %{required(non_neg_integer()) => non_neg_integer()}
Label → instruction index for the function.
Whether some clause accepts every argument: nothing branches to the
func_info label. Guards fall out correctly, since a guarded catch-all
compiles to a test whose failure branch is that label.
@spec total_on?( [tuple()], {:x, non_neg_integer()} ) :: boolean()
Whether some clause accepts every value of register — the message
argument of a callback — whatever it demands of the other arguments.
handle_info(msg, {stack, continuation}) is a catch-all for messages
even though its head tests the state; total?/1 would say no, since
the state pattern can branch to func_info. The scan walks the clause
heads in order: a clause begins at the entry or at the fail label of a
head test, and it is total on register if its head tests nothing
read from register (or a register copied or projected from it)
before the body starts. Guards on the message count as tests; guards
on the state do not.