8

随想录(再谈ucc开源编译器)

 3 years ago
source link: https://feixiaoxing.blog.csdn.net/article/details/111410812
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱: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)
{
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK