具有固定/预分配寄存器的表达式的代码生成

面具

我使用这个(见下文)算法(从起飞想法答案)来生成代码树。我的目标是x86 arch,现在我需要处理使用寄存器eax / ebx作为参数的mul / div指令。

我的问题是:

如何修改此设置以加载特定指令的操作数以加载到固定寄存器?例如,对于mul指令加载,左子树和右子树都在eaxebx寄存器上。我当前的实现是:通过当前节点开始作为参数求值,如果它是MULDIV设置regR0R1根据树的一方,分别LEFTRIGHT如果reg为is in_use,则推入reg堆栈并将其标记为开始可用(尚未实施)。当前实现无效,因为它确实assert(r1 != r2)emit_load()函数中声明了(这意味着作为参数传递的两个寄存器都等于r1 = REG_R0r2 = REG_R0

      void gen(AST *ast, RegSet in_use, AST *root) {
            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 if(ast.type == Type_id || ast.type == Type_number) {
                if(node->type == MUL || node->type == DIV) {
                    REG reg;
                    if(node_side == ASTSIDE_LEFT)  reg = REG_R0; 
                    if(node_side == ASTSIDE_RIGHT) reg = REG_R1;
                    if(is_reg_in_use(in_use, reg)) {
                        emit_operation(PUSH, reg);
                    }

                } else {
                  ast->reg = pick_unused_register(in_use);
                  emit_load(ast);
             }
            } else {
                print("gen() error");
                // error
            }
    }

// ershov numbers
void label(AST ast) {
    if(ast == null)
        return;

    label(ast.left);
    label(ast.right);

    if(ast.type == Type_id || ast.type == Type_number)
        ast.n = 1;
    // ast has two childrens
    else if(ast.left not null && ast.right not null) {      
        int l = ast.left.n;
        int r = ast.right.n;

        if(l == r)
            ast.n = 1 + l;
        else
            ast.n = max(1, l, r);
    }
    // ast has one child
    else if(ast.left not null && ast.right is null)
        ast.n = ast.left.n;
    else
        print("label() error!");
}
基因

使用这样的一遍代码生成器,您的选择就受到限制。首先生成3地址代码或其他线性中间表示,然后担心寄存器定向(这是您要完成的工作的名称),这可能会更简单

尽管如此,您想做的事还是有可能的。需要注意的是,您不会获得非常高质量的代码。为了使它更好,您必须丢弃此生成器并重新开始。

您遇到的主要问题是Sethi-Ulman标记不是代码生成算法。这只是选择代码生成顺序一种方式您仍然缺少重要的想法。

有了这些,请注意以下几点:

推动并弹出寄存器以暂时保存它们会使生活变得困难。原因很明显。您只能按LIFO顺序访问保存的值。

如果您在堆栈帧中分配可能是寄存器或存储器位置的“位置”,事情就会变得更加容易。存储器位置有效地扩展了寄存器文件,使其必要时更大。稍微复杂一点是,您需要为每个函数记住该函数的堆栈框架中的位置需要多少个单词,然后回补函数序言以分配该数字。

接下来,实现一个全局操作数堆栈,其中每个堆栈元素都是一个PLACE。PLACE是一个描述符,用于说明已经由已发出的代码计算出的操作数的位置:寄存器或内存以及如何访问它。(为获得更好的代码,您还可以允许PLACE成为用户变量和/或立即数,但是下面描述的PLACE分配器永远不会返回这样的PLACE。此外,允许的PLACE种类越多,则必须使用的情况越多由代码发射器处理,也将在下面进行说明。)

一般原则是“懒惰”。我们等待的时间越晚,发出代码的机会就越多。有了更多的信息,就有可能生成更好的代码。PLACE堆栈可以很好地完成此任务。

代码生成器不变是它发出的代码将结果PLACE留在操作数堆栈的顶部。

您还将需要一个PLACE分配器。这样可以跟踪寄存器和正在使用的存储字。如果所有寄存器和当前字都已经忙,它将分配新的存储字。

PLACE分配器中的寄存器可以具有三种可能的状态:FREE,BUSY,PINNED。需要一个PINNED寄存器来保存一个不能移动的值。(我们将使用它来满足具有特定寄存器要求的指令。)BUSY寄存器是一个必需的寄存器,可以将值按要求移动到其他PLACE。免费寄存器不具有任何价值。

PLACE分配器中的内存是空闲或繁忙。

PLACE分配器至少需要以下入口点:

  1. allocate_register 选择一个空闲寄存器R,使其变为繁忙,然后返回R。如果没有可用的空闲寄存器,则分配一个空闲存储字P,将一个繁忙寄存器R的内容移到那里,然后返回R。
  2. pin_register(R)如下所示:如果R为PINNED,则引发致命错误。如果R忙,则获得一个FREE PLACE P(寄存器或存储字),发出将R的内容移至P的代码,标记R PINNED并返回。如果R是免费的,则将其标记为PINNED并返回。

请注意,当固定或分配寄存器R需要移动其内容时,分配器必须更新操作数堆栈中的相应元素。必须将原来的R更改为P。为此,分配器维护一个映射,将每个寄存器带到描述它的操作数堆栈PLACE中。

完成所有这些之后,用于二进制操作的代码生成器将很简单:

gen_code_for(ast_node) {
  if (ast_node->left_first) {
    gen_code_for(ast_node->left_operand)
    gen_code_for(ast_node->right_operand)
  } else {
    gen_code_for(ast_node->right_operand)
    gen_code_for(ast_node->left_operand)
    swap_stack_top_2()  // get stack top 2 elements in correct order
  }
  emit_code_for(ast_node)
}

代码发射器将像这样工作:

emit_code_for(ast_node) {
  switch (ast_node->kind) {
    case DIV:  // An operation that needs specific registers
      pin_register(EAX) // Might generate some code to make EAX available
      pin_register(EDX) // Might generate some code to make EDX available
      emit_instruction(XOR, EDX, EDX) // clear EDX
      emit_instruction(MOV, EAX, stack(1)) // lhs to EAX
      emit_instruction(DIV, stack(0)) // divide by rhs operand
      pop(2) // remove 2 elements and free their PLACES
      free_place(EDX) // EDX not needed any more.
      mark_busy(EAX)  // EAX now only busy, not pinned.
      push(EAX) // Push result on operand stack
      break;
    case ADD: // An operation that needs no specific register.
      PLACE result = emit_instruction(ADD, stack(1), stack(0))
      pop(2)
      push(result)
      break;
    ... and so on
  }
}

最后,指令发射器必须知道当其操作数具有处理器指令集不支持的类型组合时该怎么做。例如,可能必须将内存PLACE加载到寄存器中。

emit_instruction(op, lhs, [optional] rhs) {
  switch (op) {
    case DIV:
      assert(RAX->state == PINNED && RDX->state == PINNED)
      print_instruction(DIV, lhs)
      return RAX;
    case ADD:
      if (lhs->kind == REGISTER) {
        print_instruction(ADD, lhs, rhs)
        return lhs
      }
      if (rhs->kind == REGISTER) {
        print_instruction(ADD, rhs, lhs)
        return rhs
      }
      // Both operands are MEMORY
      R = allocate_register // Get a register; might emit some code.
      print_instruction(MOV, R, lhs)
      print_instruction(ADD, R, rhs) 
      return R
      ... and so on ...

我一定要讲很多细节。问不清楚的地方。

OP解决的问题

您是对的,我打算stack(n)成为n操作数堆栈顶部的PLACE

语法树的叶子只需将PLACE推入操作数堆栈上的计算值即可满足不变量。

如上所述,您可以为这些操作数创建特殊类型的PLACE(用户标记的内存位置和/或立即值),或者-更简单(如您所建议的那样)-分配一个寄存器并发出将值加载到该寄存器中的代码,然后将寄存器的PLACE推入堆栈。比较简单的方法将导致不必要的加载指令,并消耗比所需更多的寄存器。例如,x = x + 1将生成类似以下的代码:

mov esi, [ebp + x]
mov edi, 1
add esi, edi
mov [ebp + x], esi

在这里,我x用来表示变量的基本指针偏移量。

使用用于变量和文字的PLACE,您可以轻松获得:

mov esi, [ebp + x]
add esi, 1
mov [ebp + x], esi

通过使代码生成器知道PLACE分配作业在何处放置答案,您可以获得

add [ebp + x], 1

或同等

inc [bp + x]

通过PLACE *target在代码生成器中添加一个参数实现此目的,该参数描述了计算表达式值的最终值需要存放的位置。如果当前不编译表达式,则将其设置为NULL。注意,使用target,代码生成器不变:除非 target设置,否则表达式结果的PLACE位于操作数堆栈的顶部在那种情况下,它已经被计算到目标的PLACE中。

这将如何进行x = x + 1过程中ASSIGNMENT情况emit_code_fortarget作为x递归调用自身进行编译时的PLACE x + 1这将责任下放,以将计算的值保存到正确的存储位置,即xow调用emit_code_for情况以递归的方式评估操作数进入堆栈。由于我们具有用于用户变量和立即值的PLACE,因此这些变量被压入堆栈,而根本不生成任何代码。现在发射器必须足够聪明,才能知道,如果它在堆栈上看到一个内存位置L和一个字面常量C,并且目标也是L,那么它就可以发出,并且完成了。ADDemit_code_forx1ADDadd L, C

请记住,每次通过为代码生成器提供更多信息以使其做出这样的决策而使其“更智能”时,由于要处理的情况更多,代码生成器将变得越来越长,越来越复杂。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

vim将正则表达式组的所有匹配项都放入寄存器

来自分类Dev

将表达式转换为通用寄存器操作模型

来自分类Dev

在Vim的表达式寄存器中使用粘贴缓冲区中的值

来自分类Dev

处理器是否使用多个堆栈来将调用堆栈与表达式/寄存器堆栈分开?

来自分类Dev

生成带有预分配代码的工作表按钮

来自分类Dev

如何检查寄存器是否具有偶数值?

来自分类Dev

如何分配存储寄存器的名称?

来自分类Dev

从sympy表达式生成python代码?

来自分类Dev

具有linq表达式的表达式树

来自分类Dev

具有固定增量的范围之间的正则表达式匹配值

来自分类Dev

正则表达式匹配具有固定长度的逗号分隔列表?

来自分类Dev

机器代码生成,内存访问/寄存器操作模式和性能?

来自分类Dev

没有%rbp寄存器的情况下,如何重新分配堆栈帧?

来自分类Dev

为什么分配给寄存器不会导致其他所有问题?

来自分类Dev

没有%rbp寄存器的情况下,如何重新分配堆栈帧?

来自分类Dev

C编程:错误:分配给具有数组类型的表达式

来自分类Dev

数组问题:分配给具有数组类型的表达式

来自分类Dev

C错误分配给具有数组类型的表达式

来自分类Dev

具有自己的操作码的EAX寄存器上的操作有什么意义?

来自分类Dev

具有lambda表达式的事件

来自分类Dev

使TypeScript生成命名寄存器而不是匿名寄存器

来自分类Dev

使TypeScript生成命名寄存器而不是匿名寄存器

来自分类Dev

选择查询仅返回具有特定月份和年份的寄存器

来自分类Dev

具有构造函数的Autofac寄存器类型需要注册构造函数的依赖项

来自分类Dev

当您写入具有相同配置的寄存器时会发生什么?

来自分类Dev

具有启用和异步复位功能的4位寄存器

来自分类Dev

分配结构字段(C)时出现“错误:分配给具有数组类型错误的表达式”

来自分类Dev

错误:将值分配给数组时,分配给具有数组类型的表达式

来自分类Dev

将具有Lambda表达式的Java 8代码转换为Java 6

Related 相关文章

  1. 1

    vim将正则表达式组的所有匹配项都放入寄存器

  2. 2

    将表达式转换为通用寄存器操作模型

  3. 3

    在Vim的表达式寄存器中使用粘贴缓冲区中的值

  4. 4

    处理器是否使用多个堆栈来将调用堆栈与表达式/寄存器堆栈分开?

  5. 5

    生成带有预分配代码的工作表按钮

  6. 6

    如何检查寄存器是否具有偶数值?

  7. 7

    如何分配存储寄存器的名称?

  8. 8

    从sympy表达式生成python代码?

  9. 9

    具有linq表达式的表达式树

  10. 10

    具有固定增量的范围之间的正则表达式匹配值

  11. 11

    正则表达式匹配具有固定长度的逗号分隔列表?

  12. 12

    机器代码生成,内存访问/寄存器操作模式和性能?

  13. 13

    没有%rbp寄存器的情况下,如何重新分配堆栈帧?

  14. 14

    为什么分配给寄存器不会导致其他所有问题?

  15. 15

    没有%rbp寄存器的情况下,如何重新分配堆栈帧?

  16. 16

    C编程:错误:分配给具有数组类型的表达式

  17. 17

    数组问题:分配给具有数组类型的表达式

  18. 18

    C错误分配给具有数组类型的表达式

  19. 19

    具有自己的操作码的EAX寄存器上的操作有什么意义?

  20. 20

    具有lambda表达式的事件

  21. 21

    使TypeScript生成命名寄存器而不是匿名寄存器

  22. 22

    使TypeScript生成命名寄存器而不是匿名寄存器

  23. 23

    选择查询仅返回具有特定月份和年份的寄存器

  24. 24

    具有构造函数的Autofac寄存器类型需要注册构造函数的依赖项

  25. 25

    当您写入具有相同配置的寄存器时会发生什么?

  26. 26

    具有启用和异步复位功能的4位寄存器

  27. 27

    分配结构字段(C)时出现“错误:分配给具有数组类型错误的表达式”

  28. 28

    错误:将值分配给数组时,分配给具有数组类型的表达式

  29. 29

    将具有Lambda表达式的Java 8代码转换为Java 6

热门标签

归档