LCC编译器的源程序分析(45)函数代码入口和出口的代码生成

由于 C 语言可以动态地分配局部变量,因此它的运行环境都是基于栈式的分配来实现的,所以在函数的入口就会生成一段分配栈的代码,如下:
#002 [section .text]
#003 $main:
#004 push ebx
#005 push esi
#006 push edi
#007  push ebp
#008 mov ebp, esp
#009 sub esp, 16
2 行是 NASM 汇编的代码段开始。
3 行是函数的名称,在 NASM 是一个过程标号。
4 行是保存 ebx 寄存器。
5 行是保存 esi 寄存器。
6 行是保存 edi 寄存器。
7 行是保存 ebp 寄存器,它是活动帧的指针。
8 行是保存上一个栈指针。
9 行是保留空间给局部变量和临时变量使用。
 
接着下就去分析 LCC 是怎么样生成这段代码的,生成代码段的函数 swtoseg ,它是在函数 funcdefn 里如下调用:
#210 
#211  swtoseg(CODE);
#212  
 
由传入的参数 CODE 可以知道这是生成代码。 swtoseg 函数代码如下:
#001 void swtoseg(int seg)
#002 {
#003  if (curseg != seg)
#004         (*IR->segment)(seg);
#005  curseg = seg;
#006 }
3 行判断当前段是否跟生成段相同,如果不相同就需要生成段代码。
4 行是调用后端接口 IR->segment 来实现生成代码段。
5 行是保存当前代码段。
 
接着下来,查看接口 IR->segment 的代码如下:
#001 static void segment(int n)
#002 {
#003  if (n == cseg)
#004         return;
#005 #if 0
#006  if (cseg == CODE || cseg == LIT)
#007         print("_TEXT ends/n");
#008  else if (cseg == DATA || cseg == BSS)
#009         print("_DATA ends/n");
#010  cseg = n;
#011  if (cseg == CODE || cseg == LIT)
#012         print("_TEXT segment/n");
#013  else if (cseg == DATA || cseg == BSS)
#014         print("_DATA segment/n");
#015 #else
#016  cseg = n;
#017  if (cseg == CODE)
#018         print("[section .text]/n");
#019  else if (cseg == DATA || cseg == LIT)
#020         print("[section .data]/n");
#021  else if (cseg == BSS)
#022         print("[section .bss]/n");
#023 #endif
#024 }
 
3 行也是判断是否重复生成相同的段代码,这是写高质量代码的需要,让代码达到最大的可靠性。
16 行是保存当前生成的代码段。
17 行是输出代码段。
20 行是输出数据段。
22 行是输出全局初始化变量段。
 
接着下来就是生成活动帧入口代码,如下:
#001 static void function(Symbol f, Symbol caller[], Symbol callee[], int n) {
#002  int i;
#003 
#004  print("%s:/n", f->x.name);
#005  print("push ebx/n");
#006  print("push esi/n");
#007  print("push edi/n");
#008  print("push ebp/n");
#009  print("mov ebp, esp/n");
4 行是生成函数的过程标号。
5 行到第 9 行就是生成前面介绍的入口代码。
 
#010  usedmask[0] = usedmask[1] = 0;
#011  freemask[0] = freemask[1] = ~(unsigned)0;
#012  offset = 16 + 4;
#013  for (i = 0; callee[i]; i++) {
#014         Symbol p = callee[i];
#015         Symbol q = caller[i];
#016         assert(q);
#017         p->x.offset = q->x.offset = offset;
#018         p->x.name = q->x.name = stringf("%d", p->x.offset);
#019         p->sclass = q->sclass = AUTO;
#020         offset += roundup(q->type->size, 4);
#021  }
#022  assert(caller[i] == 0);
#023  offset = maxoffset = 0;
#024  gencode(caller, callee);
#025  framesize = roundup(maxoffset, 4);
#026  if (framesize >= 4096)
#027 #if 0
#028         print("mov eax, %d/ncall __chkstk/n", framesize);
#029 #else
#030         print("mov eax, %d/ncall $_chkstk/n", framesize);
#031 #endif
#032  else if (framesize > 0)
#033         print("sub esp, %d/n", framesize);
10 行到第 25 行是计算局部变量、参数变量等使用栈的 framesize 大小,然后在第 33 行里输出到文件里。
24 行的函数是生成把 DAG 生成汇编代码。
 
 
#034  emitcode();
#035  print("mov esp, ebp/n");
#036  print("pop ebp/n");
#037  print("pop edi/n");
#038  print("pop esi/n");
#039  print("pop ebx/n");
#040  print("ret/n");
#041  if (framesize >= 4096) {
#042 #if 0
#043         int oldseg = cseg;
#044         segment(0);
#045         print("extrn __chkstk:near/n");
#046         segment(oldseg);
#047 #else
#048         print("[extern $_chkstk]/n");
#049 #endif
#050  }
#051 }
34 行是根据模式匹配来发送编译生成的汇编代码。
35 行到第 40 行是生成退出活动帧的代码。
41 行是判断是否需要声明调用分配内存块的代码。
 
通过上面这段代码就可以了解怎么样生成一个函数的代码。这里主要介绍生成函数的名称,函数的入口和出口等代码,其它方面的内容后面再接着介绍。
 
 

你可能感兴趣的:(代码生成)