Argus.Extractor.Dispatch (Panoptes v0.13.0)

Copy Markdown View Source

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

branch_targets(instr)

@spec branch_targets(tuple()) :: [non_neg_integer()]

Every {:f, label} operand of an instruction.

compared_atoms(instrs, register)

@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.

continuations_after(instrs, register, atom, labels)

@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).

entry_index(instrs)

@spec entry_index([tuple()]) :: non_neg_integer()

Where execution begins: the instruction after func_info, not index zero, which is the failure landing pad.

func_info_label(instrs)

@spec func_info_label([tuple()]) :: non_neg_integer() | nil

The label of the function's func_info instruction, or nil.

labels(instrs)

@spec labels([tuple()]) :: %{required(non_neg_integer()) => non_neg_integer()}

Label → instruction index for the function.

total?(instrs)

@spec total?([tuple()]) :: boolean()

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.

total_on?(instrs, register)

@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.