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行是判断是否需要声明调用分配内存块的代码。
通过上面这段代码就可以了解怎么样生成一个函数的代码。这里主要介绍生成函数的名称,函数的入口和出口等代码,其它方面的内容后面再接着介绍。