Dependency Graph
A dependency graph shows which attributes must be computed before which. Build one for a single arithmetic expression, read off a valid evaluation order with a topological sort, and see why our example is a DAG with no cycles.
Definition
Dependency graph. Given an annotated parse tree, the dependency graph has one node per attribute instance (one attribute at one tree node, e.g. the
valon a specificT), and a directed edgeb → cwhenever the semantic rule that computescusesbas an input.
A valid evaluation order is any topological sort of this graph: an ordering in which every node appears before all nodes that depend on it. Such an order exists if and only if the graph is acyclic — a DAG (directed acyclic graph).
If the graph has a cycle, some attribute would have to be computed before itself, so no valid order exists and the SDD is rejected for that tree.
The Example
We use one running grammar for arithmetic with a single synthesized attribute, val.
Input: ( 3 * 5 + 2 ) * 2 n (here n is the end marker / newline).
How each rule becomes edges. Read the right-hand side of every rule: each attribute it uses points into the attribute being defined.
Because every val is synthesized (computed from children), every edge points from a child attribute up toward its parent.
Solution — Building the Graph
Build the dependency graph for the val instances of ( 3 * 5 + 2 ) * 2. The leaves carry digit.lexval; edges flow upward to the root, where print consumes the top E.val.
Every edge points upward (child → parent), so the graph has no cycle — it is a DAG.
A valid topological order. Reading the DAG bottom-up gives one evaluation order in which each attribute is ready before it is used:
Because all edges point up, this single bottom-up pass evaluates every attribute with its inputs already computed. The final print outputs 34.
Cycles — Circular SDDs
Our running example is acyclic, but not every SDD is. A cycle means an attribute depends on itself (directly or transitively), so no topological order — and no evaluation order — exists.
A small contrived pair of rules shows it:
A.s needs B.i, but B.i needs A.s — a 2-node cycle. There is no topological order, so the SDD is unevaluable for that tree and must be rejected.
In contrast, the val grammar above has only child → parent edges, so it can never form such a loop.
S-Attributed and L-Attributed SDDs
Two restricted classes are guaranteed acyclic, so the compiler never has to build or sort a dependency graph — a fixed traversal always works.
| Class | Definition | Safe traversal |
|---|---|---|
| S-attributed | every attribute is synthesized (child → parent only) | post-order / bottom-up; fits LR parsers |
| L-attributed | each inherited attribute depends only on the parent and on left siblings | one depth-first, left-to-right pass; fits LL parsers |
Our running val grammar is S-attributed — every attribute is synthesized — which is exactly why the single bottom-up pass above just works.
Key Points
- A dependency graph has one node per attribute instance and an edge b → c when the rule for c uses b.
- A valid evaluation order is a topological sort; it exists iff the graph is a DAG.
- For
( 3 * 5 + 2 ) * 2, allvaledges point upward, so one bottom-up pass evaluates everything and prints 34. - A cycle (e.g.
A.s ↔ B.i) makes an SDD unevaluable. - S-attributed SDDs (like ours) are always acyclic and evaluate in post-order.