假设函数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 = [];
}
答案 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
只需非常
调用该函数后,它“拥有”参数区域和其堆栈帧。只要不超过边界,它可以随心所欲地做任何事情。更多内容如下。
让我们暂时忽略在寄存器中传递参数,为简洁起见,假装我们有push
和pop
指令(例如宏)
来电者会这样做:
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
此调用者的被调用者代码也会被调整。