O0 与调试
保存计算、常量表达式、存储空间和调试响应信息的规则。
Wave Foundation
保护模型
O0 保留原始 typed IR 的计算、变量和控制流,以供调试之用。它还保留未使用的结果和结构上无法访问的块。验证诊断故障IR,并且不删除块或简化操作。
生成机器语言所需的转换在单独的子子IR中执行。即使在转换后,也必须保持与原始ID的对应关系。 typed IR 守恒并不意味着所有 IR 操作和机器指令都一一对应。
转换未由 O0 执行
| 转换 | O0 操作 |
|---|---|
| 内联 | 维护调用和函数边界 |
| 转换 Tail-call | 维护一般调用/返回结构 |
| 删除死代码 | 维护未使用的计算和无法访问的块 |
| 折叠运行时常量 | 保持原来的操作 |
| 结合常见的计算 | 保持单独的计算 |
| 局部变量存储空间的复用 | 维护各个存储空间 |
| 省略帧指针 | 维护帧指针 |
| 自动合并字符串 | 维护单独的字符串对象 |
例如,将两个常量相加的运行时操作即使不使用结果,仍然保持加法。条件不变的分支也保持原来的控制流结构。
编译时常量
编译时常量声明保留 typed 初始化表达式和计算结果。这与常规运行时指令中的常量折叠不同。
初始化表达式为 1 + 2 的声明留下加法表达式和结果 3。此示例说明了表达式的含义,而不是特定源语言的声明性语法。结果可以在构造静态数据时使用,并且不会用运行时添加替换初始化表达式。
还必须识别未使用和无法访问的声明。即使同名的声明相互混淆,也必须用名称·声明ID·引用分隔。验证会拒绝具有无效引用、依赖循环、无效类型或与原始表达式不匹配的存储结果。
无法访问源语句
return·break·continue之后的句子也保持在不连接的块中。由于此语句,您不应更改前面的terminator或创建新的执行路径。它还可以诊断无法到达的句子中的无效表达式。
该保存规则允许检查原始程序结构。这并不意味着terminator之后的语句将实际执行。
保留 IR 示例
以下是通过验证程序的模块的当前打印机输出。一起显示未使用的计算、编译时声明和未链接的块。
module {
format_version 2
semantics_version 1
target "x86_64-whale-linux"
datalayout { ptr=64, endian=little }
declare @f0 "preserved": whale () -> void, linkage internal
fn @preserved() -> void, id @f0 {
entry:
%v0: i32 = const i32 1
%v1: i32 = const i32 2
%v2: i32 = add i32 %v0, %v1
%v3: i32 = const_decl "count" add(i32 1, i32 2) => const i32 3
ret void
unreachable.cont:
%v4: i32 = const i32 4
%v5: i32 = const i32 5
%v6: i32 = add i32 %v4, %v5
ret void
}
}
即使没有用,%v2 仍然是 add。 %v3是一个单独的const_decl,它将表达式add(i32 1, i32 2)和评估结果3一起保留。由于unreachable.cont没有传入边,我们可以检查%v6的添加,而无需在ret void之后添加执行路径。
验证保留这些指令和块。此示例显示 IR 配置/验证/输出,并不意味着提供 native 执行或 DWARF 输出。
源和堆栈信息
调试接口使用DWARF 5中的函数信息、源代码行、默认局部变量和调用帧信息。即使转换为子表达式,也必须保持函数/局部变量信息与原始IR标识符之间的对应关系。
AMD64 配置文件保留帧指针,并且不使用 red zone。调用帧信息用于堆栈检查,并不意味着支持异常unwinding。 trap 终止执行而不保证析构函数或 unwinding。
有关DWARF输出和native执行的可用性,请参阅工具链概述。 O1 及以上优化的行为超出了本 O0 参考的范围。