debug Swift compiler

简介

作为工程师,我们几乎花费了70%时间来调试,剩下的20%来思考架构和团队协作,只有10%的时间来用写码.

Debugging is like being the detective in a crime movie where you are also the murderer.
Filipe Fortes via Twitter

下面我们会对如何调试表一起和编译器的输出指令进行说明。

在遇到一些方法分派的问题时,因为编译器实现方式不同,导致调用结果不符合预期的"bug",都可以通过对编译中间结果的查看来追踪问题.下面我们除了讲解几种调试方式,还会针对Swift常见的几个"bug",从编译器的角度进行说明.

基础工具

通常调试一个crash追踪或者构建log的编译器问题的第一步是通过命令行重新运行编译器。
utils/dev-scripts内的split-cmdline脚本将命令行拆分成了多个部分。这样用于理解和编辑比较长的指令。

打印中间语言

调试编译器最重要的环节就是检查IR。 以下是如何在编译器的主流程中输出IR的指令:

  1. 语法分析:打印语法分析后的AST:lisp
swiftc -dump-ast -O file.swift
  1. SILGen:在SILGen之后立刻打印SIL
swiftc -emit-silgen -O file.swift

what is dmangle??

  1. 强制SIL检测:打印强制检测后的SIL
swiftc -emit-sil -Onone file.swift

其他指令

  1. SIL性能检查:打印SIL通道优化完成之后的SIL
swiftc -emit-sil -O file.swift
  1. IRGen:在IR生成之后打印LLVM IR
swiftc -emit-ir -Xfrontend -disable-llvm-optzns -O file.swift
  1. LLVM检测:打印LLVM检测之后的LLVM IR
swiftc -emit-ir -O file.swift
  1. 生成代码:打印最终生成的代码
swiftc -S -O file.swift

编译器会在打印完对应阶段后终止。所以如果想打印SIL和LLVM IR,需要运行编译器两次。

调试类型检查

允许打印

可以通过以下参数来允许类型检查的打印-Xfrontend -debug-constraints。使用该指令会打印类型检查器内部的状态,打印已经解决的约束,展示最终的检查结果:

---Constraint solving for the expression at [test.swift:3:10 - line:3:10]---
---Initial constraints for the given expression---
(integer_literal_expr type='$T0' location=test.swift:3:10 range=[test.swift:3:10 - line:3:10] value=0)
Score: 0 0 0 0 0 0 0 0 0 0 0 0 0
Contextual Type: Int
Type Variables:
  #0 = $T0 [inout allowed]

Active Constraints:

Inactive Constraints:
  $T0 literal conforms to ExpressibleByIntegerLiteral [[locator@0x7ffa3a865a00 [IntegerLiteral@test.swift:3:10]]];
  $T0 conv Int [[locator@0x7ffa3a865a00 [IntegerLiteral@test.swift:3:10]]];
($T0 literal=3 bindings=(subtypes of) (default from ExpressibleByIntegerLiteral) Int)
Active bindings: $T0 := Int
(trying $T0 := Int
  (found solution 0 0 0 0 0 0 0 0 0 0 0 0 0)
)
---Solution---
Fixed score: 0 0 0 0 0 0 0 0 0 0 0 0 0
Type variables:
  $T0 as Int

Overload choices:

Constraint restrictions:

Disjunction choices:

Conformances:
  At locator@0x7ffa3a865a00 [IntegerLiteral@test.swift:3:10]
(normal_conformance type=Int protocol=ExpressibleByIntegerLiteral lazy
  (normal_conformance type=Int protocol=_ExpressibleByBuiltinIntegerLiteral lazy))
(found solution 0 0 0 0 0 0 0 0 0 0 0 0 0)
---Type-checked expression---
(call_expr implicit type='Int' location=test.swift:3:10 range=[test.swift:3:10 - line:3:10] arg_labels=_builtinIntegerLiteral:
  (constructor_ref_call_expr implicit type='(_MaxBuiltinIntegerType) -> Int' location=test.swift:3:10 range=[test.swift:3:10 - line:3:10]
    (declref_expr implicit type='(Int.Type) -> (_MaxBuiltinIntegerType) -> Int' location=test.swift:3:10 range=[test.swift:3:10 - line:3:10] decl=Swift.(file).Int.init(_builtinIntegerLiteral:) function_ref=single)
    (type_expr implicit type='Int.Type' location=test.swift:3:10 range=[test.swift:3:10 - line:3:10] typerepr='Int'))
  (tuple_expr implicit type='(_builtinIntegerLiteral: Int2048)' location=test.swift:3:10 range=[test.swift:3:10 - line:3:10] names=_builtinIntegerLiteral
    (integer_literal_expr type='Int2048' location=test.swift:3:10 range=[test.swift:3:10 - line:3:10] value=0)))

在使用整体的Swift的REPL(read-eval-print loop: 读取-求值-输出 循环),可以输出每个表达式的结果,这个结果和通过:constraints debug on指令允许约束打印的结果相同:

$ swift -frontend -repl -enable-objc-interop -module-name REPL
***  You are running Swift's integrated REPL,  ***
***  intended for compiler and stdlib          ***
***  development and testing purposes only.    ***
***  The full REPL is built as part of LLDB.   ***
***  Type ':help' for assistance.              ***
(swift) :constraints debug on

捕获第一个错误的

在修改类型检查器时,会引发一系列的连锁错误。因为Swift不会抛出错误,所以开发者需要对类型检查器足够理解,通过判断觉得如何停止调试器。比起这样,开发者还可以使用条件-Xllvm -swift-diagnostics-assert-on-error=1来唤起DiagnosticsEngine诊断引擎抛出第一个错误,用于向开发者提供捕获的信息。

调试SIL的级别

SIL输出指令选项

SILPassManager提供了有效的选项用于输出各阶段的SIL
-Xllvm -sil-print-all选项用于输出全部检测之后的整个SIL组件。尽管打印结果只是检测后被修改的函数,但是输出非常巨大.
可以通过函数名过滤输出信息:-Xllvm -sil-print-only-function/s或指定区段-Xllvm -sil-print-before/after/around.
更多信息可以参考PassManager.cpp.

在LLDB中输出SIL和其他数据

使用LLDB调试Swift编译器,可以发挥非常强大的检测编译数据(比如SIL)能力.遵循LLVM dump()协议,很多SIL类(包括AST类)提供了dump()方法.可以通过LLDB的expression --,print或者p指令来调用dump()方法.
例如,检测SIL结构体

(lldb) p Inst->dump()
%12 = struct_extract %10 : $UnsafeMutablePointer<X>, #UnsafeMutablePointer._rawValue // user: %13

输出检测后的整个方法

(lldb) p getFunction()->dump()

SIL组件和方法都可能非常大,所以将结果输出到一个文件内更为方便

(lldb) p getFunction()->dump("myfunction.sil")

也可以输出方法的CFG(control flow graph: 控制流程图)

(lldb) p Func->viewCFG()

这个指令会打开一个包含函数CFG的预览窗口.

在SIL层调试和推断

如果想要SIL调试需要同时增加 front-end option -gsil 和 -g :

swiftc -g -Xfrontend -gsil -O test.swift -o a.out

以上指令会在优化之后将SIL写入一个文件,并且生成相关的调试信息.在调试器内看到的是SIL代码而非Swift源码.详情可以查看SILDebugInfoGenerator.

如果想调试Swift标准库,可以使用 build-script-impl的选项--build-sil-debugging-stdlib.

ViewCFG: 基于CFG打印的正则表达式

ViewCFG(./utils/viewcfg)是一个脚本,作用是解析文本的CFG然后通过.dot格式展示.解析是通过正则表达式完成的. ViewCFG具有以下能力

  1. 解析SIL和LLVM IR
  2. 解析block和函数,不需要知晓上下文信息.(类型或者声明等信息)

使用断点

LLDB具有强大的断点能力.下面通过LLDB在命令行的使用示例来说明.
有时我们在查看SIL输出的函数时,想要知道函数是在编译器哪里创造的.这时就可以SILFunction结构体设置一个断点.

(lldb) br set -c 'hasName("_TFC3nix1Xd")' -f SILFunction.cpp -l 91

如果想知道插入了哪些优化,移除或者移动了哪些说明,可以使用在SILInstruction.cpp内的ilist_traits<SILInstruction>::addNodeToListilist_traits<SILInstruction>::removeNodeFromList来设置断点.以下示例是在strong_retain说明被移除时设置断点的指令

(lldb) br set -c 'I->getKind() == ValueKind::StrongRetainInst' -f SILInstruction.cpp -l 63

还可以测试是在哪个方法内发生的

(lldb) br set -c 'I->getKind() == ValueKind::StrongRetainInst &&
           I->getFunction()->hasName("_TFC3nix1Xd")'
           -f SILInstruction.cpp -l 63
introduction to sIL
default auguments
sil summary

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容