📄 cg.c
字号:
/******************************************************************************
*******************************************************************************
文件名: cg;c
本文件对语法分析器生成的中间代码进行处理, 将之转换为VAM汇编代码.
文件修改时间
============
2005年5月6日
*******************************************************************************
******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "vc.h"
/* 本模块用到的常数, 首先, 保留几个寄存器作为特殊用途, R1保存栈指针,
R2保存被调用函数的入口地址, R3是返回地址, R4是返回结果等 */
#define R_ZERO 0 /* 零号寄存器, 恒为零 */
#define R_P 1 /* 栈底指针 */
#define R_CALL 2 /* 被调用函数的入口地址 */
#define R_RET 3 /* 返回地址 */
#define R_RES 4 /* 返回结果 */
#define R_GEN 5 /* 第一个一般用途的寄存器 */
#define R_MAX 16 /* 最大值 */
/* 函数调用时, 系统将生成一个动态栈来保存被调用函数的局部标量以及调用函数的
运行环境, 该动态栈的结构是: 栈底是调用函数栈的入口地址, 称为动态链结.
再预留一个保存函数返回将要执行的第一条指令地址, 在此以后是实参和被调用函
数的局部变量, 动态栈是一个最典型的保存运行活记录的方法 */
#define P_OFF 0 /* 动态链结的偏移量 */
#define PC_OFF 4 /* 指令的偏移量 */
#define VAR_OFF 8 /* 实参的偏移量 */
/* 一下的两个常数供寄存器数据结构使用 */
#define MODIFIED TRUE /* Entries for descriptors */
#define UNMODIFIED FALSE
/* 寄存器数据结构记录在代码生成阶段时当前寄存器的状态, 其中modified标记
寄存器的值在写入内存后是否被修改过 */
struct /* 寄存器数组 */
{
struct symb *name ; /* 寄存器保存的内容 */
int modified ; /* 标志位 */
} rdesc[R_MAX] ;
int tos ; /* 当前栈顶 */
int next_arg ; /* 当前实参 */
/* 以下是代码生成函数的原形, 其函数名为cg_xxx(), xxx是三地址码指令 */
void cg( TAC *tl ) ;
TAC *init_cg( TAC *tl ) ;
void cg_instr( TAC *c ) ;
void cg_bin( char *op,
SYMB *a,
SYMB *b,
SYMB *c ) ;
void cg_copy( SYMB *a,
SYMB *b ) ;
void cg_cond( char *op,
SYMB *a,
int l ) ;
void cg_arg( SYMB *a ) ;
void cg_call( int f,
SYMB *res ) ;
void cg_return( SYMB *a ) ;
void cg_sys( char *fn ) ;
void cg_strings( void ) ;
void cg_str( SYMB *s ) ;
void flush_all( void ) ;
void spill_all( void ) ;
void spill_one( int r ) ;
void load_reg( int r,
SYMB *n ) ;
void clear_desc( int r ) ;
void insert_desc( int r,
SYMB *n,
int mod ) ;
int get_rreg( SYMB *c ) ;
int get_areg( SYMB *b,
int cr ) ;
void cg( TAC *tl )
/* 函数cg()完成对整个三地址码链的翻译, 首先它调用cg_init(), 它将找到三地址码
链的链首, 因为完成语义分析后, 该链指针落在链未.
代码生成的次序是, 1. 拷贝标准头文件(prolog)标准输出; 2. 用循环对三地址码链
进行遍历, 对每条三地址码分别进行翻译; 3. 拷贝库函数到标准输出; 4. 对字符串
生成代码.
注意: 头文件header和库函数lib同编译的源程序在相同的目录. */
{
char filename[80];
TAC *tls = init_cg( tl ) ; /* 三地址码链链首 */
strcpy(filename, LIB_DIR);
strcat(filename, "header");
cg_sys(filename) ; /* 输出头文件 */
for( ; tls != NULL ; tls = tls->next ) /* 生成指令 */
{
printf( "\\ " ) ;
print_instr( tls ) ;
cg_instr( tls ) ;
}
cg_sys( "lib" ) ; /* 输出库函数 */
cg_strings() ; /* 生成字符串代码 */
} /* void cg( TAC *tl ) */
TAC *init_cg( TAC *tl )
/* 对每个寄存器清零; 初始化栈以及返回链首指针 */
{
int r ;
TAC *c ; /* 当前TAC指针 */
TAC *p ; /* 向前TAC指针 */
for( r = 0 ; r < R_MAX ; r++ )
rdesc[r].name = NULL ;
insert_desc( 0, mkconst( 0 ), UNMODIFIED ) ; /* R0设为0 */
tos = VAR_OFF ; /* 栈顶, 即活记录之后的开始地址 */
next_arg = 0 ; /* 当前实参 */
/* 回溯表头并回填反向指针 */
c = NULL ; /* 链尾 */
p = tl ; /* 下一个元素 */
while( p != NULL )
{
p->next = c ; /* 回填next域 */
c = p ; /* 设置下一步运行的环境 */
p = p->prev ;
}
return c ;
} /* TAC *init_cg( TAC *tl ) */
void cg_instr( TAC *c )
/* 对每条TAC指令生成汇编代码 */
{
switch( c->op )
{
case TAC_UNDEF:
error( "cannot translate TAC_UNDEF" ) ;
return ;
case TAC_ADD:
cg_bin( "ADD", c->VA, c->VB, c->VC ) ;
return ;
case TAC_SUB:
cg_bin( "SUB", c->VA, c->VB, c->VC ) ;
return ;
case TAC_MUL:
cg_bin( "MUL", c->VA, c->VB, c->VC ) ;
return ;
case TAC_DIV:
cg_bin( "DIV", c->VA, c->VB, c->VC ) ;
return ;
case TAC_NEG:
cg_bin( "SUB", c->VA, mkconst( 0 ), c->VB ) ;
return ;
case TAC_COPY:
cg_copy( c->VA, c->VB ) ;
return ;
case TAC_GOTO:
cg_cond( "BRA", NULL, c->LA->VA->VAL1 ) ;
return ;
case TAC_IFZ:
cg_cond( "BZE", c->VB, c->LA->VA->VAL1 ) ;
return ;
case TAC_IFNZ:
cg_cond( "BNZ", c->VB, c->LA->VA->VAL1 ) ;
return ;
case TAC_ARG:
cg_arg( c->VA ) ;
return ;
case TAC_CALL:
cg_call( c->LB->VA->VAL1, c->VA ) ;
return ;
case TAC_RETURN:
cg_return( c->VA ) ;
return ;
case TAC_LABEL:
/* 注意在LABEL语句之前, 一定要对寄存器进行刷新, 因为可能有
不是循序执行的代码转移到该LABEL语句 */
flush_all() ;
printf( "L%d:\n", c->VA->VAL1 ) ;
return ;
case TAC_VAR:
/* 在中间代码生成时局部变量没有分配内存, 在目标代码生成时
分配器其距动态链的偏移量, 由于整数用32位表示, 因此新栈顶
要加4 */
c->VA->ADDR2 = tos ;
tos += 4 ;
return ;
case TAC_BEGINFUNC:
/* 执行被调用函数之前, 首先将返回地址(即函数调用语句之后的
第一条执行语句的地址, 该地址在函数调用语句中已装入到寄存
器R_RET中)装入到当前栈的活动记录指定位置PC_OFF(第二条记录)
上 */
tos = VAR_OFF ;
printf( " STI R%d,%d(R%d)\n", R_RET, PC_OFF, R_P ) ;
return ;
case TAC_ENDFUNC:
/* 在函数的结尾之处, 加上一个无条件返回语句, 因为有函数定义中
没有返回语句的可能 */
cg_return( NULL ) ;
return ;
default:
/* TAC代码有误时 */
error( "unknown TAC opcode to translate" ) ;
return ;
}
} /* void cg_instr( TAC *c ) */
void cg_bin( char *op, /* Opcode to use */
SYMB *a, /* Result */
SYMB *b, /* Operands */
SYMB *c )
/* 生成有如下形式的二元运算的汇编代码:
a := b op c
VAM虚拟机的二元运算都将计算结果放在第二个运算量所在的寄存器中
该运算将翻译为: 装载b和c到寄存器中, 计算b op c, 将寄存器对应的变量
修改为a, 注意考虑到代码的优化, 我们对寄存器的结果延迟保存, 使得代码
尽可能地少调用内存, 二直接使用寄存器, 因此在翻译这些TAC语句时, 都没有
将计算结果存入内存, 程序只有在改变运行环境(转移指令的使用)和寄存器资
源枯竭时, 才将有MODIFIED标记的寄存器的值保存到其对应的变量所在的内存中 */
{
int cr = get_rreg( c ) ; /* 获得第一个寄存器保存装载c */
int br = get_areg( b, cr ) ; /* 获得第二个寄存器保存b */
printf( " %s R%d,R%d\n", op, br, cr ) ; /* 计算 b op c */
/* 释放寄存器cr, 并将cr的结果保存到a中 */
clear_desc( cr ) ;
insert_desc( cr, a, MODIFIED ) ;
} /* void cg_bin( char *op,
SYMB *a,
SYMB *b,
SYMB *c ) */
void cg_copy( SYMB *a,
SYMB *b )
/* 生成如下TAC码的汇编代码
a := b
将b装入寄存器中, 并将寄存器的指针修改为a; */
{
int br = get_rreg( b ) ; /* 将b装入寄存器中 */
insert_desc( br, a, MODIFIED ) ; /* 将寄存器的对应的变量修改为a */
} /* void cg_copy( SYMB *a,
SYMB *b ) */
void cg_cond( char *op,
SYMB *a, /* 条件 */
int l ) /* 转移标号 */
/* 转移语句的翻译, 注意转移之前首先要将寄存器的值回填到对应的变量内存中,
将a装入寄存器, 执行转移指令(goto, ifz, ifnz) */
{
spill_all() ; /* 回填寄存器 */
if( a != NULL )
{
int r ;
for( r = R_GEN ; r < R_MAX ; r++ ) /* a是否已在寄存器中 */
if( rdesc[r].name == a )
break ;
if( r < R_MAX ) /* 已在寄存器中 */
/* 如果已在寄存器中重新调用一次, 以刷新标记, 注意
不能用load_reg(), 它将修改寄存器的标记*/
printf( " LDR R%d,R%d\n", r, r ) ;
else
(void)get_rreg( a ) ; /* 装入寄存器 */
}
printf( " %s L%d\n", op, l ) ; /* 转移 */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -