编译的全过程,可以从两个方向去看,一是从上往下,另一个是从下往上看。
从上层往下看,也就是从编程语言往指令集、指令集的硬件实现,的方向看,这是一步步具象化的过程,也就是从问题域具象到实现域的过程。
同时,从下往上看的话,就是不断抽象的过程。最底层的肯定是物理规律,具体到半导体这块,就是电气规律,我们要利用这物理规律来让它实现最基本的逻辑运算,也就是通过P-N结所形成的基本结构,引起电流和电压的有规律变化,从而实现最基础的逻辑门,与、或、非,当然,也可以只用NAND这一种逻辑门去实现那三种逻辑门,为了设计的方便,通常我们都用与或非这三种逻辑门。或者今后,利用其他物理规律去实现我们所需要逻辑就有了其他类型的计算机了。那我们有了逻辑门后就可以设计更复杂逻辑电路,去实现加法器、乘法器、控制器、寄存器、解码器等基础部件,然后在这些基础上,通过combinational curcuit 和 sequential curcuit 去实现我们CPU所定义的指令集架构和IO。有了指令集架构后,就自然而然地抽象成基础的汇编语言,也就是二进制机器码抽象成文本形式,然后再进一步抽象成更高级的语言,越往上走,就越接近问题域所要求的抽象模型。
然后现在,我们再重新从上往下看,看看会经历过什么样的具象化的过程。
首先是编程语言的设计。但是我们需要一种怎么样的编程语言,很大程度上是由问题域所决定,也是就是说这门语言是用来针对哪种问题来写解决方案的,解决方案也就是所谓的程序。例如SQL的产生是针对关系数据库的操作。也就是所,问题域的定义,决定了一门编程语言的设计。那么我们用C语言来做例子,C语言的问题域是什么呢?C语言是一门通用语言,英文是 general purpose,那它所提供解决方案的问题域就是通用型计算。那什么是通用型计算?我来定义的话,通用型计算就是能够实现和计算人类可以创造出来的算法。这个计算模型就是 图灵机。每个算法有对一的一个图灵机去实现它。可以实现所有图灵机的图灵机就叫做 Turing Complete. 所以 C语言就是 Turing Complete. 怎么证明?就是用C语言是模拟图灵机的运作,也就是图灵机模拟器。好了,那我们现在清楚了,C语言的抽象计算模型是图灵机,那它的语法设计就是基于这一块来的。打个岔,除了图灵机计算模型外,还有其他的计算模型可以实现通用型计算,例如 Lambda Calculus,其代表编程语言是,Haskell. 回来C语言这块,根据C语言的计算模型,设计好C语言的语法,下面C语言的编译器就会,输入C源代码的字符串到词法分析器(Lexer)产生 Token 串,然后 token串经过 语法分析器(Parse) 产生 AST(Abstract Syntax Tree), 之后 语义分析器 (Semantics) 就会进行 Type check 然后 生成 对应 下一层 中间语言(IR) 的实现。 为什么需要中间语言?当然我们可以直接在语义分析器就直接生成CPU的指令。但是这里面有个问题就是,在不同抽象层之间转义,抽象间隔越大,转义的难度就越大,就好比上楼梯,同样的高度,层级越多,走得越轻松,越少,则每步都比较难。另外还有一个更重要的原因,灵活性。就比如没有中间层,一个编程语言通过一个编译器直接生成对应的一个ISA的机器码。那么如果有A种编程语言和B种ISA,那边要覆盖所有的可能性,就需要有 A乘以B个编译器来实现。如果有了中间语言,就可以使A种编程语言都先转义为中间语言,然后中间语言再分别转义到ISA的机器码,这样只需要 A+B个编译器就能实现。我们叫负责处理编程语言到中间语言的部分叫 编译器的前段(frontend),处理中间语言到ISA的部分叫后端(backend),中间语言叫做(middle end)。同时,因为这样的设计,中间语言可以标准化,这样之后,就可以在中间层去实现通用的优化。这样就可以惠及所有基于这中间层实现的前段和后端。GCC和LLVM都是这种设计,前中后,三段分层设计。好了,回到来,问题域、解决方案、计算模型、C语言、词法分析器、语法分析器、语义分析,现在到了 IR,之后我以LLVM为例,后面IR会经过 Instruction Selection,Instruction Scheduling, Register Allocation, Prologue-Epilogue Insertion 等过程,形成 对应ISA的汇编语言,然后再经 Assembler 形成 object file, 然后再到 linker 形成 executable。此时就可以放到模拟器或真机上运行了。下面就到芯片设计与实现的内容了。ISA就软件和硬件之间的接口。同时,芯片实现的特性也会影响到上层编译器的处理,例如 有多少级的pipeline,多少 functional unit,有没有 out-of-order execution等。
所以,全过程来说,就是从下往上,不断提高抽象度,从上往下,就是不断的具象化。每一层我们都有特定的理论和技术去实现,同时也可以在每一层里发展更先进理论和技术。
同时,基于我们以上的理解,当我们想尽可能地高效处理某一类问题的时候,就可以用专门针对性的解决方案去处理,例如 TPU 之于 人工智能,这样就会要求我们从问题域开始思考,然后解决方案会是什么,它的计算模型是怎样,从而得出需要怎样的编程语言,主要体现在它的类型系统里面,然后目标机器是什么等等。一系列地具象化。