Sethi-Ullman 实际上只是一个调度算法,而不是一个寄存器分配算法,所以它只是告诉你order在其中执行操作以最小化所需寄存器的数量;它没有告诉你which寄存器使用在哪里。
因此,您需要将其与寄存器分配策略结合起来——通常只是一个贪婪的分配器。接下来的问题是,如果寄存器用完该怎么办——是内联插入溢出,还是中止并执行其他操作?
为了与您的调度和指令生成内联进行简单的贪婪分配(您似乎正在使用简单的gen
递归过程),您需要跟踪在任何给定时间正在使用哪些寄存器。最简单的方法是添加一个额外的in_use
gen 函数的参数:
typedef unsigned RegSet; /* using a simple bitmask for a set -- assuming that
* unsigned is big enough to have a bit per register */
void gen(AST *ast, RegSet in_use) {
if(ast->left != 0 && ast->right != 0) {
if (ast->left->n >= ast->right->n) {
gen(ast->left, in_use);
gen(ast->right, in_use | (1 << ast->left->reg));
} else {
gen(ast->right, in_use);
gen(ast->left, in_use | (1 << ast->right->reg)); }
ast->reg = ast->left->reg
emit_operation(ast->type, ast->left->reg, ast->right->reg);
} else if(ast->type == Type_id || ast->type == Type_number) {
ast->reg = pick_unused_register(in_use);
emit_load(ast);
} else ....
请注意,您需要单独的递归过程来计算n
对于每个节点(Sethi-Ullman 本质上需要两次遍历,第一次遍历计算自下而上n
第二次遍历使用自上而下的值)。
现在上面的代码根本不处理寄存器耗尽的问题。为此,您需要插入一些溢出物。一种方法是在进行递归调用之前检测所有寄存器是否都在使用中,然后溢出,然后恢复:
void gen(AST *ast, RegSet in_use) {
if(ast->left != 0 && ast->right != 0) {
Reg spill = NoRegister; /* no spill yet */
AST *do1st, *do2nd; /* what order to generate children */
if (ast->left->n >= ast->right->n) {
do1st = ast->left;
do2nd = ast->right;
} else {
do1st = ast->right;
do2nd = ast->left; }
gen(do1st, in_use);
in_use |= 1 << do1st->reg;
if (all_used(in_use)) {
spill = pick_register_other_than(do1st->reg);
in_use &= ~(1 << spill);
emit_operation(PUSH, spill); }
gen(do2nd, in_use);
ast->reg = ast->left->reg
emit_operation(ast->type, ast->left->reg, ast->right->reg);
if (spill != NoRegister)
emit_operation(POP, spill);
} else ...
当然,事实证明这并不是非常有效——通常最好早点溢出并稍后再填充,但只有当你知道你的寄存器即将用完时。