4.5 Performance
5.1.1 Entire Compilation and Runtime Flow
5.1.1.1 Translation from Source to VHDL
First the parser converts source files into an Abstract Syntax Tree (AST) and reports syntax and undeclared identifier errors. The control flow in each method and function is trans- formed from nested control-flow structures and expression trees in the AST to a control flow graph (CFG) [81]. The initial control flow graph box in Figure 5.4 shows an example CFG immediately after the transform from an AST. Each node in the CFG is a basic block, which is straight-line code ending in a control flow transfer. This flat structure is easier to perform transforms on than a nested AST structure. Each CFG has a source basic block, where execution starts, and possibly multiple sink basic blocks where execution ends. Ev- ery non-sink ends in a branch or jump to transfer control to another basic block. Sinks end with a return statement in the case of functions and returning methods and a nil statement otherwise.
After type checking, variable declarations and assignments are transformed into static single assignment (SSA) form [80]. In SSA, variables cannot be assigned values after they are declared; each is assigned a value once in its definition statement. This makes the dataflow structure explicit which simplifies transforms and checks on the code. Figure 5.4 shows how assignments in the initial CFG are transformed into definitions with SSA. Mul- tiple assignments to a variable which occur in sequence are transformed into a sequence of definitions of different variables. Each reference to the variable is then updated to the last definition. When a variable which is assigned different values in different paths, a new variable must defined at the point of convergence for any later references to use. The new variable at the point of convergence is defined to be a Φfunction of the variables in the convergent paths. At execution time, the Φfunction selects the variable on the path that was traversed.
A series of canonicalization steps on control and data flow is performed:
1. Case expressions of the form Predicate ? ThenExpr : ElseExpr are trans- formed to basic blocks.
2. Return statements are unified into a single return statement in a unique sink basic block.
3. All function calls are inlined into ancestor methods. This is possible because theNo-
Figure 5.4: The control flow representations used by the compiler after inputting Source Code are: the Initial Control Flow Graph, the Control Flow Graph with Static Single As- signment, Straight Line Code, and Hardware Description Language. Control flow graphs consist of basic blocks with transfer edges. Control out of a branching basic block fol- lows the edge labeled with the Boolean value supplied by the branch’s predicate. Static single assignment hasΦ(phi) functions to select a value based on the input edge control followed. Straight line code uses case expressions to implement Φ functions. Hardware description language substitutes case expressions with multiplexers. The HDL’s wires are labeled with the variables from previous stages that they correspond to.
Recursionconstraint (Section 3.3) disallows recursion.
4. Multiple message send statements on the sameoutfield are unified into a single state- ment, with all send statements moved to the sink basic block. This is possible because theOne-Message-per-Pointerconstraint disallowed calls on the sameoutfield that are on the same control flow graph.
5. All field reads are moved to variable definitions in the source basic block and all field writes are moved to the destination basic block. New variables are introduced when fields are read from after being written to. This will allow object state read to be packed into a single large read at the beginning of operation and write to be packed into a single large write at the end.
6. Tuple expressions are flattened into atomic expressions, tuple types are flattened into atomic types, and tuple references are eliminated.
The last canonicalization step transforms each method’s CFG into straight-line code, which is almost equivalent to the HDL description of the method. The CFG is a DAG, so basic blocks can be sequenced with a topological ordering. Case expressions of the form Predicate ? ThenExpr : ElseExprare reintroduced to replace Φ expressions.
Figure 5.4 shows how the SSA’sΦfunctions are transformed into straight-line code’s case expressions. For eachΦexpression, nested case expressions are constructed whose pred- icates are the predicates used by branches which are post-dominated by theΦ. Variables input to theΦbecome case expressions’ then and else expressions. Nested case expressions are once again flattened into a sequence of statements.
The straight-line code for each method is transformed into HDL directly by replacing each case expression with a two-input multiplexer and ignoring the order of statements.
The statement order can be ignored because statements SSA never change variables. Each variable declaration is typed as a Boolean or signed or unsigned integer parameterized by width so it can be converted into a wire with the correct width. Each arithmetic operation corresponds directly to an HDL arithmetic operation. Figure 5.4 shows how variables are converted to wires and case expressions are converted to multiplexers.
HDL modules described by the compiler includes the operators generated from GRA- PAL methods as well as the entire logic architecture. The compiler uses its own library
for constructing HDL modules. It is more convenient to describe arbitrary, highly pa- rameterized HDL modules with functions and data structures than to rely on externally defined modules in a language like VHDL or Verilog. The Java libraries JHDL [82] and BOOM [83] and Haskell libraries Lava [84] and Kansas Lava [85] take the same approach to supporting highly parameterized structures. Code to print the target VHDL operators can go in a single function in the library, rather than appearing in each part of the compiler that generates HDL. Parameters which depend on the compiled program are pervasive through- out the generated logic. By using a library we can pass these parameters to functions which encapsulate HDL modules, which simplifies the number of changes that need to be made when the meaning of or the set of parameters changes during compiler development. Using ordinary functions to describe complex structures like the network is also more convenient than using VHDL or Verilog functions.