Tiger编译器到MIPS程序集:参数传递函数调用

时间:2016-05-18 02:43:52

标签: compiler-construction mips

假设函数f调用函数g(a0, a1, a2, a3, a4, a5)

a0, a2, a4是转义变量(它们被推到帧上,因此它们可以被内部函数访问),a1, a3, a5不是(所以它们可以传递给MIPS的输入寄存器。

在Appel的编译器书中,每当调用一个函数时都会创建一个框架。由于有三个转义变量,新堆栈将在帧指针上方有三个字大小的单元格。 但我怎么想知道哪些输入参数应该在代码生成阶段进入哪个单元?

我的解决方法:

在输入寄存器中传递前四个参数,其余部分在堆栈中传递。

当创建帧g时,执行视图移位:复制所有输入寄存器并将内存堆栈到局部变量。但我认为函数参数应该是与帧指针的已知偏移量。所以我不确定这是否是一种解决方法。

f调用g时,f无法了解g的帧布局。那么如何在代码生成阶段实现函数调用呢?

UPDATE:我认为我的问题应该改为:调用者将a0,a1,a2,a3传递给输入寄存器,其余的传递给堆栈。然而,被调用者在编译时决定a0,a2,a4应该被推入堆栈,因为它们会逃脱。那么在实际执行函数体之前,被调用者如何才能进行正确的视图移位?

在我的编译器的类型检查阶段,每当它看到函数声明时。它将尝试确定是否有任何形式参数转义(它们是否将由任何内部函数访问)。如果它逃脱,它将在堆栈中,或者它将在寄存器中。 OCaml中的示例代码:

  type access = 
    (* whether it will be in frame or in a register *)
    | In_frame of offset
    | In_reg of Temp.temp

当需要创建新帧时,函数名称和布尔变量列表将传递到new_frame函数:

  let gen_offset () = 
    let res = !loc * word_size in
    loc := !loc + 1; res


  let new_frame name escapes  = 
    loc := 0 ;
    let fs = List.map escapes
      ~f:(fun t -> if t then (In_frame (gen_offset ())) else In_reg (Temp.new_temp ()))
    in
    let l = List.length fs in
    {
      name    = name;
      length  = l;
      formals = fs;
      locals  = [];
    }

1 个答案:

答案 0 :(得分:1)

此过程中有两个堆栈区域,它们是 distinct 。对我而言,似乎你把两者合二为一,这可能是混乱的根源。

(1)调用者放置参数的区域。这是传递参数的地方,它与被调用者的堆栈帧无关(例如,这是f在调用g之前设置的内容)

(2)被叫者的堆栈帧。这是被调用者(例如g)为其功能范围的自动变量设置空间的地方。来电者触摸此区域。换句话说,f 没有布局知识,允许也没有。见下文。

如果被调用者是尾函数,那么他甚至不需要设置堆栈帧 甚至可能没有一个。 Callee可能是纯寄存器函数,只使用a0-a3,v0 / v1,t0-t9,因此根本不需要堆栈帧。

在设置$fp注册表的某些功能时,许多编译器可以优化$fp的使用,并仅使用偏移量$sp。如果某个函数具有VLA,例如$fp或使用int dim = foo(); int arr[dim];

,则设置alloca只需非常

调用该函数后,它“拥有”参数区域其堆栈帧。只要不超过边界,它可以随心所欲地做任何事情。更多内容如下。

让我们暂时忽略在寄存器中传递参数,为简洁起见,假装我们有pushpop指令(例如宏)

来电者会这样做:

push    a5
push    a4
push    a3
push    a2
push    a1
push    a0

jal     g

addiu   $sp,$sp,24

推送设置参数区域。那是不是被调用者(即g)堆栈帧的一部分。

请注意,在调用之后,调用者将使用addiu删除参数区域。它假定没有关于任何剩余的值。

这就是我所说的“拥有”它的意思。 Callee可以填充它想要的任何数据(例如,在使用a5之后,被调用者可以自由地用其他东西覆盖参数区域中的单元格。)

被调用者将自己设置其堆栈帧。请注意,我可能会在偏移量上出现一个错误,但您会明白这一点:

push    $fp
move    $fp,$sp
subiu   $sp,$sp,8       # establish space for callee vars: c0 and c1

lw      $t0,4($fp)      # get a0
lw      $t1,8($fp)      # get a1
# ...

lw      $t6,-0($fp)     # get c0
lw      $t7,-4($fp)     # get c1

# do stuff ...

move    $sp,$fp
pop     $fp
jr      $ra

调用者将任何内容填充到被调用者的堆栈帧中[因为它尚未设置]。它将参数放在参数区域中,高于被调用者堆栈帧。

现在,对于真正的mips ABI,前四个参数在寄存器$a0-$a3中传递,参数a4和a5在堆栈上被推送。这是“解决方法”的第一个部分,但“视图转换”内容:

push    a5
push    a4
move    $a3,a3
move    $a2,a2
move    $a1,a1
move    $a0,a0

jal     g

addiu   $sp,$sp,8

此调用者的被调用者代码也会被调整。