Reaching definitions over Layer-1 facts: which instruction's register write feeds which instruction's register read.
For every use row, the analysis finds the def rows that can have
produced the value being read — true def→use edges that respect register
reuse, which simple register-name matching cannot.
Control flow
The successor relation comes from the explicit control-transfer facts:
next (fallthrough), jump, branch (the fail edge), and
select_branch, with labels resolved through label_at. This is
deliberately not Argus.Cfg's block-edge relation:
- Exception edges (
try_starthandlers) are not followed. At the handler the VM materializes the exception class/reason/stacktrace inx0–x2without anydeffact, so following the edge would attribute pre-trywrites of those registers to handler reads — a false flow. Handler code instead flows from its own writes (its reads of the VM-materialized registers resolve to nothing, honestly). bif_call/bs_startfail edges are not followed; like the genericbranchfallthrough, the fail path is reached through the facts that carry it explicitly.
Algorithm
Per function (edges never cross functions): instructions chain into maximal straight-line blocks, each block gets a gen/kill summary, the summaries propagate over the block graph to a fixpoint (classic iterative reaching definitions), and the per-instruction edges fall out of one local walk per block. This is observationally identical to the per-instruction fixpoint, just proportional to blocks rather than instructions.
Summary
Functions
Compute the def→use edge set from typed facts
(Argus.Pipeline.extract/2 with format: :typed).
Types
@type edge() :: {Argus.InstrId.t(), Argus.InstrId.t()}
A def→use edge: the writing instruction feeds the reading one.
Functions
@spec def_use_edges(Argus.Facts.t()) :: MapSet.t(edge())
Compute the def→use edge set from typed facts
(Argus.Pipeline.extract/2 with format: :typed).
One module at a time
Functions are grouped by InstrId.fa/1, which is {name, arity} and
carries no module. Pass facts for a single module. Over a merged
multi-module fact set every init/1 in the program lands in one group
and unrelated control-flow graphs are spliced together, which silently
loses real edges and invents others — measured at 25,409 edges instead
of 45,319 on one project.
Every caller does this correctly today (Planchette.Flow.build/1 and
Gloss.Adapters.dataflow/1 are both per module, as is the derivation in
Argus.Pipeline), so this documents a precondition that was being met
by convention rather than fixing a live defect.