前言

在前两篇文章中,我们讲了:

  • 语法分析:把代码变成语法树
  • 语义分析:检查代码是否有意义

现在,编译器已经理解了代码的结构和含义。接下来要做什么?

把代码翻译成更接近机器的形式

这就像翻译一本书:

  • 原文:高级编程语言(Python、Java)
  • 译文:机器语言(0和1)
  • 中间代码:介于两者之间的"草稿"

今天,我们深入讲解中间代码生成


一、为什么需要中间代码?

1.1 直接翻译的问题

假设你要把一本中文书翻译成10种语言:

flowchart LR
    A[中文] --> B[英语]
    A --> C[法语]
    A --> D[德语]
    A --> E[日语]
    A --> F[...]
    
    style A fill:#e1f5ff

需要 10 个翻译官。

但如果先翻译成世界语(国际辅助语言):

flowchart LR
    A[中文] --> B[世界语<br/>中间语言]
    B --> C[英语]
    B --> D[法语]
    B --> E[德语]
    B --> F[日语]
    B --> G[...]
    
    style A fill:#e1f5ff
    style B fill:#fff4e1

只需要 1 + 10 = 11 个翻译步骤,而不是 10 个直接翻译。

1.2 编译器中的中间代码

编译器面临同样的问题:

源语言:Python, Java, C++, JavaScript, ...
目标机器:x86, ARM, RISC-V, MIPS, ...

如果直接翻译:需要 M × N 种编译器。

使用中间代码

flowchart LR
    A[Python] --> D[中间代码 IR]
    B[Java] --> D
    C[C++] --> D
    
    D --> E[x86]
    D --> F[ARM]
    D --> G[RISC-V]
    
    style D fill:#fff4e1

只需要 M + N 种转换。

1.3 中间代码的好处

好处 说明
可移植性 一次编写,到处运行
优化机会 在中间层做优化,适用所有平台
简化编译器 前端只管生成IR,后端只管翻译IR
模块化 前端和后端独立开发

二、中间代码的形式

中间代码有多种形式,我们讲解最常见的几种。

2.1 三地址码(Three-Address Code)

什么是三地址码?

三地址码:每条指令最多包含三个地址(两个操作数 + 一个结果)。

就像简单的计算器指令:

t1 = a + b    # 两个操作数 (a, b) + 一个结果 (t1)
t2 = t1 * c   # 两个操作数 (t1, c) + 一个结果 (t2)
x = t2        # 一个操作数 (t2) + 一个结果 (x)

为什么叫"三地址"?

每条指令涉及最多三个地址(变量或临时变量):

x = y op z
↑   ↑    ↑
1   2    3

三地址码的指令类型

指令类型 形式 例子
赋值 x = y x = a
二元运算 x = y op z x = a + b
一元运算 x = op y x = -a
无条件跳转 goto L goto L1
条件跳转 if x goto L if x > 0 goto L1
参数传递 param x param a
函数调用 call f, n call foo, 2
返回 return x return t1
数组访问 x = y[i] x = arr[i]
指针操作 x = *y x = *p

示例:从源代码到三地址码

源代码

a = 1 + 2 * 3

三地址码

t1 = 2 * 3
t2 = 1 + t1
a = t2

图解

graph TD
    A["源代码: a = 1 + 2 * 3"] --> B[语法树]
    B --> C["三地址码"]
    
    C --> D["t1 = 2 * 3"]
    C --> E["t2 = 1 + t1"]
    C --> F["a = t2"]
    
    style A fill:#e1f5ff
    style C fill:#fff4e1
    style D fill:#e8f5e9
    style E fill:#e8f5e9
    style F fill:#e8f5e9

2.2 静态单赋值(SSA)

什么是SSA?

SSA(Static Single Assignment):每个变量只被赋值一次

普通代码

x = 1
x = 2
y = x

SSA形式

x1 = 1
x2 = 2
y1 = x2

每次赋值都产生一个新版本

为什么需要SSA?

简化优化

普通形式:
x = 1
x = 2      # x 的值变了
y = x      # y = ?

SSA形式:
x1 = 1
x2 = 2     # x2 是新版本
y1 = x2    # y1 肯定等于 2

在SSA中,定义-使用链非常清晰。

φ函数:处理分支

当控制流汇合时,需要φ函数选择值:

源代码

if condition:
    x = 1
else:
    x = 2
y = x  # x 的值来自哪个分支?

SSA形式

if condition:
    x1 = 1
else:
    x2 = 2
x3 = φ(x1, x2)  # 选择:如果来自then块,选x1;否则选x2
y1 = x3

图解

flowchart TD
    A[condition?] -->|true| B[x1 = 1]
    A -->|false| C[x2 = 2]
    B --> D["x3 = φ(x1, x2)"]
    C --> D
    D --> E[y1 = x3]
    
    style A fill:#e1f5ff
    style D fill:#fff4e1

2.3 其他中间表示

形式 特点 使用场景
三地址码 简单、易理解 教学入门
SSA 单赋值、易优化 现代编译器(LLVM、GCC)
栈式虚拟机 基于栈操作 JVM、.NET
CFG 控制流图 优化分析
数据流图 数据依赖关系 并行优化

三、如何生成中间代码?

3.1 语法树遍历

从语法树生成三地址码:

graph TD
    A[语法树] --> B[遍历树节点]
    B --> C[为每个运算生成临时变量]
    C --> D[生成三地址指令]
    D --> E[中间代码]
    
    style A fill:#e1f5ff
    style E fill:#d4edda

3.2 实现示例

让我们实现一个三地址码生成器:

from dataclasses import dataclass
from typing import List, Union

@dataclass
class Instruction:
    """三地址码指令"""
    op: str           # 操作符
    result: str       # 结果
    arg1: str = None  # 操作数1
    arg2: str = None  # 操作数2
    
    def __str__(self):
        if self.op == 'assign':
            return f"{self.result} = {self.arg1}"
        elif self.op in ['+', '-', '*', '/']:
            return f"{self.result} = {self.arg1} {self.op} {self.arg2}"
        elif self.op == 'uminus':
            return f"{self.result} = -{self.arg1}"
        elif self.op == 'param':
            return f"param {self.result}"
        elif self.op == 'call':
            return f"{self.result} = call {self.arg1}, {self.arg2}"
        elif self.op == 'return':
            return f"return {self.result}"
        else:
            return f"{self.op} {self.result} {self.arg1} {self.arg2}"

class ThreeAddressCodeGenerator:
    """三地址码生成器"""
    
    def __init__(self):
        self.instructions: List[Instruction] = []
        self.temp_count = 0
        self.label_count = 0
    
    def new_temp(self) -> str:
        """生成新的临时变量"""
        self.temp_count += 1
        return f"t{self.temp_count}"
    
    def new_label(self) -> str:
        """生成新的标签"""
        self.label_count += 1
        return f"L{self.label_count}"
    
    def emit(self, op: str, result: str, arg1: str = None, arg2: str = None):
        """生成一条指令"""
        instr = Instruction(op, result, arg1, arg2)
        self.instructions.append(instr)
    
    def generate(self, node) -> str:
        """从AST节点生成三地址码,返回结果的地址"""
        
        if isinstance(node, dict):
            node_type = node.get('type')
            
            # 数字常量
            if node_type == 'number':
                temp = self.new_temp()
                self.emit('assign', temp, str(node['value']))
                return temp
            
            # 变量
            elif node_type == 'variable':
                return node['name']
            
            # 二元运算
            elif node_type == 'binary':
                left_addr = self.generate(node['left'])
                right_addr = self.generate(node['right'])
                result_addr = self.new_temp()
                self.emit(node['operator'], result_addr, left_addr, right_addr)
                return result_addr
            
            # 一元运算
            elif node_type == 'unary':
                operand_addr = self.generate(node['operand'])
                result_addr = self.new_temp()
                self.emit('uminus', result_addr, operand_addr)
                return result_addr
            
            # 赋值
            elif node_type == 'assignment':
                value_addr = self.generate(node['value'])
                self.emit('assign', node['variable'], value_addr)
                return node['variable']
            
            # if 语句
            elif node_type == 'if':
                return self.generate_if(node)
            
            # while 语句
            elif node_type == 'while':
                return self.generate_while(node)
            
            # 代码块
            elif node_type == 'block':
                for stmt in node['statements']:
                    self.generate(stmt)
                return None
        
        return str(node)
    
    def generate_if(self, node) -> None:
        """生成 if 语句的三地址码"""
        else_label = self.new_label()
        end_label = self.new_label()
        
        # 计算条件
        cond_addr = self.generate(node['condition'])
        
        # 条件跳转
        self.emit('ifFalse', cond_addr, else_label)
        
        # then 块
        self.generate(node['then_branch'])
        self.emit('goto', end_label)
        
        # else 块
        self.emit('label', else_label)
        if 'else_branch' in node:
            self.generate(node['else_branch'])
        
        # 结束标签
        self.emit('label', end_label)
    
    def generate_while(self, node) -> None:
        """生成 while 语句的三地址码"""
        start_label = self.new_label()
        end_label = self.new_label()
        
        # 开始标签
        self.emit('label', start_label)
        
        # 计算条件
        cond_addr = self.generate(node['condition'])
        
        # 条件跳转
        self.emit('ifFalse', cond_addr, end_label)
        
        # 循环体
        self.generate(node['body'])
        
        # 跳回开始
        self.emit('goto', start_label)
        
        # 结束标签
        self.emit('label', end_label)
    
    def print_code(self):
        """打印生成的三地址码"""
        print("=== 三地址码 ===")
        for instr in self.instructions:
            print(f"  {instr}")

# 测试:生成 a = 1 + 2 * 3 的三地址码
print("示例1:简单表达式\n")
ast1 = {
    'type': 'assignment',
    'variable': 'a',
    'value': {
        'type': 'binary',
        'operator': '+',
        'left': {'type': 'number', 'value': 1},
        'right': {
            'type': 'binary',
            'operator': '*',
            'left': {'type': 'number', 'value': 2},
            'right': {'type': 'number', 'value': 3}
        }
    }
}

gen1 = ThreeAddressCodeGenerator()
gen1.generate(ast1)
gen1.print_code()

# 测试:生成 if 语句
print("\n示例2:if 语句\n")
ast2 = {
    'type': 'if',
    'condition': {'type': 'variable', 'name': 'x'},
    'then_branch': {
        'type': 'assignment',
        'variable': 'y',
        'value': {'type': 'number', 'value': 1}
    },
    'else_branch': {
        'type': 'assignment',
        'variable': 'y',
        'value': {'type': 'number', 'value': 2}
    }
}

gen2 = ThreeAddressCodeGenerator()
gen2.generate(ast2)
gen2.print_code()

# 测试:生成 while 语句
print("\n示例3:while 语句\n")
ast3 = {
    'type': 'while',
    'condition': {
        'type': 'binary',
        'operator': '<',
        'left': {'type': 'variable', 'name': 'i'},
        'right': {'type': 'number', 'value': 10}
    },
    'body': {
        'type': 'block',
        'statements': [
            {
                'type': 'assignment',
                'variable': 'i',
                'value': {
                    'type': 'binary',
                    'operator': '+',
                    'left': {'type': 'variable', 'name': 'i'},
                    'right': {'type': 'number', 'value': 1}
                }
            }
        ]
    }
}

gen3 = ThreeAddressCodeGenerator()
gen3.generate(ast3)
gen3.print_code()

3.3 输出示例

示例1:简单表达式

=== 三地址码 ===
  t1 = 2 * 3
  t2 = 1 + t1
  a = t2

示例2:if 语句

=== 三地址码 ===
  ifFalse x L1
  y = 1
  goto L2
  label L1
  y = 2
  label L2

示例3:while 语句

=== 三地址码 ===
  label L1
  t1 = i < 10
  ifFalse t1 L2
  t2 = i + 1
  i = t2
  goto L1
  label L2

四、中间代码优化

中间代码生成后,可以做各种优化。

4.1 常量折叠

优化前

t1 = 2 * 3
t2 = 1 + t1

优化后

t1 = 6
t2 = 7

实现

class ConstantFolder:
    """常量折叠优化"""
    
    def __init__(self, instructions):
        self.instructions = instructions
    
    def optimize(self):
        """执行常量折叠"""
        optimized = []
        constants = {}  # 记录已知的常量值
        
        for instr in self.instructions:
            if instr.op == 'assign' and instr.arg1.isdigit():
                # 记录常量
                constants[instr.result] = int(instr.arg1)
                optimized.append(instr)
            
            elif instr.op in ['+', '-', '*', '/']:
                left_val = constants.get(instr.arg1)
                right_val = constants.get(instr.arg2)
                
                if left_val is not None and right_val is not None:
                    # 可以计算
                    if instr.op == '+':
                        result = left_val + right_val
                    elif instr.op == '-':
                        result = left_val - right_val
                    elif instr.op == '*':
                        result = left_val * right_val
                    elif instr.op == '/':
                        result = left_val // right_val
                    
                    # 替换为常量赋值
                    new_instr = Instruction('assign', instr.result, str(result))
                    constants[instr.result] = result
                    optimized.append(new_instr)
                    print(f"✅ 常量折叠: {instr.arg1} {instr.op} {instr.arg2}{result}")
                else:
                    optimized.append(instr)
            
            else:
                optimized.append(instr)
        
        return optimized

4.2 死代码消除

优化前

t1 = 2 * 3    # t1 从未被使用
t2 = 1 + 5
a = t2

优化后

t2 = 6
a = t2

4.3 公共子表达式消除

优化前

t1 = a + b
t2 = a + b    # 重复计算
t3 = t1 * t2

优化后

t1 = a + b
t2 = t1       # 复用
t3 = t1 * t2

五、实战:完整的中间代码生成器

让我们实现一个完整的系统:

from typing import List, Dict, Set

@dataclass
class BasicBlock:
    """基本块:连续执行的指令序列"""
    label: str
    instructions: List[Instruction]
    predecessors: Set[str]
    successors: Set[str]

class IntermediateCodeSystem:
    """完整的中间代码系统"""
    
    def __init__(self):
        self.generator = ThreeAddressCodeGenerator()
        self.blocks: Dict[str, BasicBlock] = {}
    
    def generate_from_ast(self, ast):
        """从AST生成中间代码"""
        print("📊 第一步:生成三地址码")
        self.generator.generate(ast)
        self.generator.print_code()
        
        print("\n🔧 第二步:常量折叠优化")
        self.optimize_constants()
        
        print("\n🗑️ 第三步:死代码消除")
        self.eliminate_dead_code()
        
        print("\n📦 第四步:构建基本块")
        self.build_basic_blocks()
    
    def optimize_constants(self):
        """常量折叠"""
        folder = ConstantFolder(self.generator.instructions)
        self.generator.instructions = folder.optimize()
        self.generator.print_code()
    
    def eliminate_dead_code(self):
        """死代码消除"""
        # 找出所有被使用的变量
        used_vars = set()
        
        # 第一遍:收集所有被使用的变量
        for instr in self.generator.instructions:
            if instr.arg1:
                used_vars.add(instr.arg1)
            if instr.arg2:
                used_vars.add(instr.arg2)
        
        # 第二遍:移除未被使用的赋值
        optimized = []
        for instr in self.generator.instructions:
            if instr.op == 'assign':
                # 检查结果是否被使用
                if instr.result in used_vars or not instr.result.startswith('t'):
                    optimized.append(instr)
                else:
                    print(f"❌ 移除死代码: {instr}")
            else:
                optimized.append(instr)
        
        self.generator.instructions = optimized
        self.generator.print_code()
    
    def build_basic_blocks(self):
        """构建基本块"""
        # 简化版:按标签分割基本块
        current_block = []
        current_label = "entry"
        
        for instr in self.generator.instructions:
            if instr.op == 'label':
                # 新基本块开始
                if current_block:
                    self.blocks[current_label] = BasicBlock(
                        label=current_label,
                        instructions=current_block,
                        predecessors=set(),
                        successors=set()
                    )
                current_label = instr.result
                current_block = []
            else:
                current_block.append(instr)
        
        # 最后一个块
        if current_block:
            self.blocks[current_label] = BasicBlock(
                label=current_label,
                instructions=current_block,
                predecessors=set(),
                successors=set()
            )
        
        # 打印基本块
        for label, block in self.blocks.items():
            print(f"\n基本块 [{label}]:")
            for instr in block.instructions:
                print(f"  {instr}")
    
    def visualize_cfg(self):
        """可视化控制流图"""
        print("\n📊 控制流图(CFG):")
        print("```mermaid")
        print("graph TD")
        for label, block in self.blocks.items():
            instr_str = '<br/>'.join([str(i) for i in block.instructions[:3]])
            if len(block.instructions) > 3:
                instr_str += '<br/>...'
            print(f"    {label}[\"{label}<br/>{instr_str}\"]")
        print("```")

# 完整示例
print("=" * 60)
print("完整示例:编译表达式计算程序")
print("=" * 60)

program = {
    'type': 'block',
    'statements': [
        # a = 10
        {
            'type': 'assignment',
            'variable': 'a',
            'value': {'type': 'number', 'value': 10}
        },
        # b = 20
        {
            'type': 'assignment',
            'variable': 'b',
            'value': {'type': 'number', 'value': 20}
        },
        # c = a + b * 2
        {
            'type': 'assignment',
            'variable': 'c',
            'value': {
                'type': 'binary',
                'operator': '+',
                'left': {'type': 'variable', 'name': 'a'},
                'right': {
                    'type': 'binary',
                    'operator': '*',
                    'left': {'type': 'variable', 'name': 'b'},
                    'right': {'type': 'number', 'value': 2}
                }
            }
        }
    ]
}

system = IntermediateCodeSystem()
system.generate_from_ast(program)

六、中间代码与目标代码

6.1 从中间代码到机器码

中间代码最终要翻译成机器码:

flowchart LR
    A[中间代码 IR] --> B[指令选择]
    B --> C[寄存器分配]
    C --> D[指令调度]
    D --> E[目标代码]
    
    style A fill:#fff4e1
    style E fill:#d4edda

6.2 示例:三地址码到x86汇编

三地址码

t1 = a + b
t2 = t1 * c
x = t2

x86汇编

mov eax, [a]      ; 加载 a
add eax, [b]      ; eax = a + b
imul eax, [c]     ; eax = (a + b) * c
mov [x], eax      ; 存储到 x

6.3 寄存器分配

问题:寄存器有限,如何高效使用?

三地址码:        寄存器分配后:
t1 = a + b        R1 = a + b
t2 = t1 * c       R1 = R1 * c    (复用 R1)
t3 = t2 - d       R2 = d         (需要新寄存器)
                   R1 = R1 - R2

七、实际编译器中的中间代码

7.1 LLVM IR

LLVM 使用自己的中间表示:

; 源代码: int add(int a, int b) { return a + b; }

define i32 @add(i32 %a, i32 %b) {
entry:
  %sum = add i32 %a, %b
  ret i32 %sum
}

7.2 Java 字节码

Java 编译成字节码:

; 源代码: int add(int a, int b) { return a + b; }

iload_0    ; 加载第一个参数
iload_1    ; 加载第二个参数
iadd       ; 相加
ireturn    ; 返回

7.3 GCC GIMPLE

GCC 使用 GIMPLE:

; 源代码: a = b + c * d;

t1 = c * d;
a = b + t1;

八、总结

8.1 核心概念

  1. 中间代码:介于源代码和机器码之间的表示
  2. 三地址码:每条指令最多三个地址
  3. SSA:每个变量只赋值一次
  4. 优化:常量折叠、死代码消除等

8.2 编译流程

flowchart TD
    A[源代码] --> B[词法分析]
    B --> C[语法分析]
    C --> D[语义分析]
    D --> E[中间代码生成]
    E --> F[中间代码优化]
    F --> G[目标代码生成]
    G --> H[机器码]
    
    style E fill:#fff4e1
    style F fill:#fff4e1

8.3 为什么重要?

  • 可移植性:支持多平台
  • 优化机会:在中间层做优化
  • 模块化:前端后端分离
  • 简化编译器:复杂度降低

扩展阅读


中间代码是编译器的"通用语言",让代码能在不同平台上运行。