# Chapter 8 In this chapter: * We move from eager to lazy `Phi` creation in loops. * We add support for `break` and `continue` statements. * We implement a Sea of Nodes Graph Evaluator. Here is the [complete language grammar](docs/08-grammar.md) for this chapter. ## Lazy Phi Creation In [chapter 7](../chapter07/README.md), we created `PhiNode`s for all variables in the loop head eagerly. This approach creates some unnecessary Phis but the redundant Phis get cleaned up later. In this chapter we add support for creating the `PhiNode`s lazily. The main complication with creating `PhiNode`s lazily is that when we create a Phi, it must be created in the innermost loop head scope where it would have been created had we done it eagerly. Since scopes get duped in `if` and `while` statements, we cannot just create Phis in the current scope. It turns out that the easiest way to create the `PhiNode`s lazily is to hook them up to name lookup. But how do we know that a Phi must be created when we access a name? We need a way to flag that a particular name mapping must be converted to a `PhiNode` on lookup. We do this as follows: * In `ScopeNode.dup()`, if the Scope is being duplicated for a loop head, then instead of setting the name's definition to the original node, we set it to the Scope itself. This acts like a sentinel. ```java // For lazy phis on loops we use a sentinel // that will trigger phi creation on update dup.addDef(loop ? this : in(i)); ``` * Subsequently, when a lookup accesses a name, we notice that the def is set to the Scope node, and we create a `PhiNode` at this point. The relevant implementation is in `ScopeNode.update()`. ```java if( old instanceof ScopeNode loop ) { // Lazy Phi! old = loop.in(idx) instanceof PhiNode phi && loop.ctrl()==phi.region() // Loop already has a real Phi, use it ? loop.in(idx) // Set real Phi in the loop head // The phi takes its one input (no backedge yet) from a recursive // lookup, which might have insert a Phi in every loop nest. : loop.setDef(idx,new PhiNode(name,loop.ctrl(),loop.update(name,null,nestingLevel),null).peephole()); setDef(idx,old); } ``` * When we merge scopes, we ensure lazy `PhiNode` creation at the correct inner scope level by invoking lookup by name instead of directly accessing the defs. ```java if( in(i) != that.in(i) ) // No need for redundant Phis // If we are in lazy phi mode we need to a lookup // by name as it will trigger a phi creation setDef(i, new PhiNode(ns[i], r, this.lookup(ns[i]), that.lookup(ns[i])).peephole()); ``` * Finally, in `ScopeNode.endLoop()` we clean up any leftover sentinels, replacing the sentinel with the input from loop head. ```java for( int i=1; i than the loop head // We use _breakScope as a proxy for the loop head scope to obtain the depth while( cur._scopes.size() > _breakScope._scopes.size() ) cur.pop(); // If this is a continue then first time the target is null // So we just use the pruned current scope as the base for the // continue if (toScope == null) return cur; // toScope is either the break scope, or a scope that was created here assert toScope._scopes.size() <= _breakScope._scopes.size(); toScope.mergeScopes(cur); return toScope; } ``` * The continue scope becomes the base scope for the subsequent `continue` statement, thus forming a stack of continue scopes/regions. * After the loop is done, if we find that a continue scope was created within the loop, we must merge the current scope into the continue scope, and make the continue scope the active scope. ```java // Merge the loop bottom into other continue statements if (_continueScope != null) { _continueScope = jumpTo(_continueScope); _scope.kill(); _scope = _continueScope; } ``` ## `break` Statement Implementing `break` is simpler than `continue`, because the exit scope is created before we parse the loop body, and this becomes the initial `break` scope. When we see `break`, we merge the current scope, after pruning any nested lexical scopes, to the current break scope, and the resulting new scope becomes the active break scope. ## Parser Considerations Since a `continue` or `break` targets the immediate surrounding `while` loop, we maintain a stack of the `continue` and `break` scopes. This is done by saving the previous `continue` or `break` scope before parsing a `while` loop and restoring afterward. ## Examples ### Example 1 ```java while(arg < 10) { arg = arg + 1; if (arg == 5) continue; if (arg == 6) break; } return arg; ``` ![Graph1](./docs/08-graph1.svg) ### Example 2 ```java while(arg < 10) { arg = arg + 1; if (arg == 5) continue; if (arg == 6) continue; } return arg; ``` ![Graph1](./docs/08-graph2.svg) ### Example 3 ```java while(arg < 10) { arg = arg + 1; if (arg == 5) break; if (arg == 6) break; } return arg; ``` ![Graph1](./docs/08-graph3.svg) ## Sea of Nodes Graph Evaluator TODO