Chapter 5: Intermediate Representations

Chapter Overview

  • Definition: Intermediate Representation (IR) is a central data structure in a compiler that bridges the source and target programs.

  • Importance: The design and properties of IR directly affect the cost and effectiveness of compilation.

  • Types of IR: This chapter focuses on various IR forms used in compilers:

    • Graphical IR

    • Linear IRs

    • Symbol tables

5.1 Introduction

  • Passes in Compilers: Compilers are organized into a series of passes; they derive information about source code during each pass and must convey this information to subsequent passes through an IR.

  • Role of IR: The IR is the definitive form of the program in the compiler during translation, rather than the original source code.

  • Significance of IR Properties: Properties of an IR affect how the compiler reads, writes, and navigates the code.

  • Compiler Uses: Many modern compilers use multiple IRs at different stages of a single compilation.

  • Choosing IR: An appropriate IR must be selected based on:

    • Source language characteristics

    • Target machine properties

    • Application requirements

5.1.1 A Taxonomy of Intermediate Representations

  • Dimensions of IR: IRs can be categorized along three axes:

    • Structural Organization:

    • Graphical IRs: Represent knowledge using graphs (nodes and edges).

      • Example: Parse trees represent the structure of the source code.

    • Linear IRs: Represent sequential operations and resemble assembly code.

      • Example: IL code is a form of linear IR.

    • Hybrid IRs: Combine features of both tree-like and linear representations.

    • Level of Abstraction: IRs can represent operations at varying levels, from high-level abstractions to low-level, machine-like representations.

    • Naming Discipline: Strategies for naming intermediate values can affect optimization and the efficiency of code generation.

5.2 Graphical IRs

  • General Characteristics: Graphical IRs use nodes and edges; they differ in abstraction levels, relationship to code, and graph structure.

5.2.1 Syntax-Related Trees
  • Parse Trees: Include nodes for every grammar symbol in the derivation and reflect the syntactic structure of the program. They provide complete grammatical information, vital for parsing but can be large.

    • Example grammar and parse tree showing operation of a multiplication.

  • Transformations: Transforming the grammar into simpler structures reduces nodes, leading to Abstract Syntax Trees (ASTs), which retain essential meanings but omit extraneous nodes.

5.2.2 Storage Efficiency and Graphical Representations
  • Limitations of Large ASTs: Large trees can consume memory, complicating compilation processes. Efforts to minimize AST size, such as combining fields and reducing node complexity, have proven beneficial.

  • DAGs (Directed Acyclic Graphs): A DAG is a reduced form of an AST that eliminates repeated subexpressions through shared nodes, enhancing storage efficiency.

5.2.3 Control-Flow Graphs (CFG)
  • CFG Definition: Each node represents a basic block, and edges denote control transfers between blocks. They formalize execution paths through code segments and allow analysis for optimization.

    • Example of a CFG for a simple loop structure.

5.2.4 Data-Dependence Graphs
  • Data-Dependence Graph: Encodes the relationships between definitions and uses of values, indicating dependencies and sequencing constraints in operations.

    • Example connections between computational nodes in a code fragment.

5.2.5 Call Graph
  • Call Graph Definition: Represents calling relationships among procedures, critical for interprocedural analysis and optimization.

  • Complexities of Call Graphs: Handling separate compilation and ambiguities in procedure calls can complicate the building of accurate call graphs.

5.3 Linear IRs

  • Linear Representation: Linear IRs depict a sequence of operations, mirroring both source and target code architecture. Common forms include bytecode and three-address code.

    • Stack-Machine Code: A one-address code manipulating operands through stack operations. It is compact but imposes a transitory nature on results.

    • Three-Address Code: A three-argument format for operations (target, operand1, operand2). Highly flexible and compatible with many processors, allowing effective optimization opportunities.

5.4 Mapping Values to Names

  • Naming Disciplines: The effective naming of temporary values has implications for optimization, code generation, and efficiency.

  • Static Single-Assignment Form (SSA): A favored discipline that ensures every variable is assigned once, using φ-functions to merge values from differing paths in control flow.

5.5 Symbol Tables
  • Purpose of Symbol Tables: Used for storing various characteristics of identifiers, such as variable names, types, and memory locations. They manage scope and make name lookup efficient during compilation.

  • Implementation: A hash table structure is often used for quick access, with specific insertion and lookup functions. Nested scopes can also be managed effectively.

5.6 Summary and Perspective

  • Impact of IR Design: The choice of IR influences compiler structure, design efficiency, and the outcome of the compiled program, demanding careful consideration in implementation. Multiple IRs may be suited for different tasks within a single compilation cycle, reflecting the evolving nature of modern software development processes and languages.