编译器自举和移植

来源:互联网 发布:理工大学网络教育 编辑:程序博客网 时间:2024/04/28 03:40

编译器自举和移植


有个著名的问题:Mommy, where do compilers comefrom?要解决这个问题,首先来看看T-Diagram。可以将编译器用一个T形图来表示:

 ---------
| S     T |
 ---   ---  
   | I |
    ---
其中,S表示Souce Language,T表示Target Language,I表示ImplementationLanguage。
根据这个图,可以得到两种组合:
1、 由I实现的将S翻译成A的编译器和将A翻译成T的编译器联合起来工作,可以实现S到T的编译。这不是很有意思的组合。
 ------------------
| S     A | A     T |
 ---   ------   ---   
   | I |    | I |
    ---      ---
2、由I实现的将S翻译成T的编译器(compiler C1),由H实现的将I翻译成K的编译器的组合(compilerC2)。这个组合比较有意思:可以用C2去编译C1(将其实现语言从I翻译成K),这样就得到由K实现的将S翻译成T的编译器(compilerC3):
 ---------                ---------
| S     T |              | S     T |
 ---   ----------  --->   ---   ---
   | I | I     K |          | K |
    -------   ---            ---
          | H |
           ---
这便是我们所感兴趣的:编译器的编译器。
而一个程序要在某台机器上运行时,其实现语言必须与相应的机器语言匹配。例如在x86平台上,能执行的程序只能是x86机器语言:
------------------
\    Program     /
 \              /
  -------------
  |     X86    |
  -------------
  |     X86    |
  -------------
让我们将T形图和上图联系起来看,以C为例(假定已有x86的C编译器):
--------        --------       ---------
\ Hello /        \ Hello /       \ Hello /
 -----------------------        -------
 | C   | C    x86| x86  |  --->  | x86  |
 ---------    ----------        -------
          |x86|                 | x86  |
          ----                  -------
上图表示用C写的Hello程序可以在C“机器”上运行,然而这不是我们想要的——在x86机器上运行。于是使用实现语言为x86的编译器(其可执行代码)编译形成x86语言,这样就可以运行了。
根据上面的第2条(编译器的编译器)以及程序的执行,我们可以得出,要想得到在M机器上运行的S语言编译器,可以使用S语言来编写(用M语言也可以,然而太麻烦)。但还是需要用M语言写一个具有很小功能的S编译器,然后用其去编译加入了更多功能的编译器,然后用编译出来的更多功能的编译器去编译,这样就得到了最终需要的编译器,这便是自举。
 ---------                ---------
| S     M |              | S     M |
 ---   ----------  --->   ---   ---
   | S | S     M |          | M |
    -------   ---            ---
          | M |(微型S编译器)
           ---
用S编写S的编译器还有一个好处,那就是方便移植,并且能产生交叉编译器。假定要编译K机器语言的S编译器,只需要将目标语言替换成K,然后用已有的S语言编译器来编译:
 ---------           ---------
| S     K |         | S     K |
 ---   ---------- -> ---   ---
   | S | S     M |     | M |(运行在M机器上的编译器,即交叉编译器)
    -------   ---       ---
          | M |
           ---
有了交叉编译器,就可以编译在K上运行的S编译器了:
 ---------           ---------
| S     K |         | S     K |
 ---   ---------- -> ---   ---
   | S | S     K |     | K |
    -------   ---       ---
          | M |

           ---

PS

这个知识实在是简单又基础,主要是觉得很有意思才在这里说说。整个GCC(编译器及交叉编译器)就是用自举和移植产生的,对此RMS有详细的说明。——当然,也有很多编译器不是这样写出来的——用另一种语言编写的。可是,“Mom,where does the compiler needed come from?”
实在不愿意贴图了,就用字符代替了,懒:)

参考

Compiler Construction Principles and Practice, by Kenneth C.Louden
"Mommy, where do compilers come from?", by anonymous.
Copyleft (C) 2007, 2008 raof01.
 早期的CPU全部是CISC架构,它的设计目的是要用最少的机器语言指令来完成所需的计算任务。比如对于乘法运算,在CISC架构的CPU上,您可能需要这样一条指令:MULADDRA, ADDRB就可以将ADDRA和ADDRB中的数相乘并将结果储存在ADDRA中。将ADDRA,ADDRB中的数据读入寄存器,相乘和将结果写回内存的操作全部依赖于CPU中设计的逻辑来实现。这种架构会增加CPU结构的复杂性和对CPU工艺的要求,但对于编译器的开发十分有利。比如上面的例子,C程序中的a*=b就可以直接编译为一条乘法指令。今天只有Intel及其兼容CPU还在使用CISC架构。

   RISC架构要求软件来指定各个操作步骤。上面的例子如果要在RISC架构上实现,将ADDRA,ADDRB中的数据读入寄存器,相乘和将结果写回内存的操作都必须由软件来实现,比如:MOV A, ADDRA; MOV B,ADDRB; MUL A, B; STR ADDRA,A。这种架构可以降低CPU的复杂性以及允许在同样的工艺水平下生产出功能更强大的CPU,但对于编译器的设计有更高的要求。


原创粉丝点击