【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
之前写过一篇ucc的文章,也就是这一篇。这篇文章对ucc的流程说了挺多,但是怎么把ucc移植到新的cpu上面,却没有说很多,后来自己又看了一下代码,发现还是有不少新的收获。
文章目录
1、emit.c
emit.c文件是真正的后端入口,所有的汇编文件的整理、组织部分都是这里完成的。当然这部分只是框架的内容,告诉我们一个大概,全局变量怎么放,字符串怎么放、函数怎么放等等。涉及到具体的内容还要依赖于具体的cpu体系文件。
2、x86linux.c & x86win32.c
因为ucc只是涉及到了x86的部分,所以这里只谈x86。当然,同时因为在linux和windows平台上面,两者的汇编文件格式有所差别,所以这里有两个翻译文件。上面变量怎么放、字符串怎么放,emit.c就会调用这两个文件里面的对应函数。
3、x86.c
如果说是普通的格式,那么x86linux.c & x86win32.c都可以完成,但是如果是具体函数的翻译,那么只能在x86.c里面完成了。当然,涉及到具体的PutASMCode指令部分,还是需要调用x86linux.c & x86win32.c这两个文件的。因为编译的过程比较复杂,特别是对于堆栈的计算、函数的压栈和出栈部分,这部分需要仔细研究和推敲。
4、x86linux.tpl & x86linux.tpl
这是两个翻译模块,分别被包含在上面x86linux.c & x86win32.c文件中。分离出来,只是为了让结构更清晰一点。这部分和编译器生成的中间代码,几乎是一一对应的。
5、opcode.h
这部分是比较容易忽视的部分,其实这部分最重要。因为其实它就包含在x86.c文件。本身描述的也是中间代码格式,后端翻译的本质就是将中间代码,按照模板翻译成汇编,就是这么简单。
6、reg.c
寄存器分配是几乎所有体系结构都会遇到的问题,但是ucc处理的方法比较简单,就是尽量把临时变量保存到寄存器,实在保存不了,只好压栈。这样效率不一定高,但是不会出错。
7、其他体系应该怎么移植
如果要移植到其他的汇编语言,比如mips,那么也需要成立这么几个文件,即mips.c、mips_linux.c、mips_linux.tpl文件,同时mips_linux.tpl包含在mips_linux.c中,opcode.h包含在mips.c中,mips.c调用mips_linux.c的PutASMCode函数,按照中间代码的类型生成每一个emit函数,比如EmitMove。mips_linux.tpl需要像x86linux.tpl一样,对opcode.h中的每一个中间格式进行翻译。有兴趣的同学可以试一试。
8、github链接
ucc本身还是非常不错的代码,常看常新,github上也有对应的链接。代码内容也非常适合用来学习和研究。
9、编译和链接
ucc的本身完整编译依赖于gcc生成预处理文件、asm文件生成obj文件、obj文件生成exe文件,这就是ucc的高明之处。因为ucc自己只实现了ucl,也就是c文件到asm文件的这部分,而这部分的工作确是非常有意义的。一方面对于学生来说,可以知道编译原理的基本思路,另外一方面,对于某些行业、比如安全关键行业,完全可以做一些私人化的定制,这是很有意义的。我们可以看一下,完整ucc运行时,依赖哪些命令,
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "ucc.h"
#define _P_WAIT 0
#define UCCDIR "/home/iron/bin/"
/**
ucc -E -v hello.c -I../ -DREAL=double -o hello.ii
For $1/$2/$3 ,see BuildCommand() for detail.
$1 -I../ -DREAL=double, command-line options
$2 hello.c, input file
$3 hello.ii, output file
*/
char *CPPProg[] =
{
"/usr/bin/gcc", "-m32", "-U__GNUC__", "-D_POSIX_SOURCE", "-D__STRICT_ANSI__",
"-Dunix", "-Di386", "-Dlinux", "-D__unix__", "-D__i386__", "-D__linux__",
"-D__signed__=signed", "-D_UCC", "-I" UCCDIR "include", "$1", "-E", "$2", "-o", "$3", 0
};
/**
ucc -S -v hello.c --dump-ast -o hello.asm
------->
/home/iron/bin/ucl -o hello.asm --dump-ast hello.i
$1 --dump-ast, some command-line options
$2 hello.i, input file
$3 hello.asm, output file
*/
char *CCProg[] =
{
UCCDIR "ucl", "-o", "$3", "$1", "$2", 0
};
char *ASProg[] =
{
"/usr/bin/gcc", "-m32", "-c", "-o", "$3", "$1", "$2", 0
};
char *LDProg[] =
{
"/usr/bin/gcc", "-m32", "-o", "$3", "$1", "$2", UCCDIR "assert.o", "-lc", "-lm", 0
};
char *ExtNames[] = { ".c", ".i", ".s", ".o", ".a;.so", 0 };
int Execute(char **cmd)
{
int pid, n, status;
pid = fork();
if (pid == -1)
{
fprintf(stderr, "no more processes\n");
return 100;
}
else if (pid == 0)
{
execv(cmd[0], cmd);
perror(cmd[0]);
fflush(stdout);
exit(100);
}
/**
wait(): on success, returns the process ID of the terminated child; on
error, -1 is returned.
*/
while ((n = wait(&status)) != pid && n != -1)
;
if (n == -1)
status = -1;
if (status & 0xff)
{
fprintf(stderr, "fatal error in %s\n", cmd[0]);
status |= 0x100;
}
return (status >> 8) & 0xff;
}
void SetupToolChain(void)
{
}