本文通过一个简单子过程嵌套调用的程序来分析SPARC的窗口寄存器和内存堆栈的使用情况。
所用程序(C源程序):
//qiantao.c
int funa(int x)
{return 2*x;}
int funb(int y)
{return 3*funa(y);}
int func(int z)
{return 4*funb(z);}
main()
{int a=1;func(a);}
易知,这段代码实现了计算4!,即1x2x3x4。
工具介绍:
操作系统:Linux(Fedora7)
编译工具:Sparc-elf-gcc 3.4.4
仿真工具:tsim-2.0.10
在分析前,先介绍SPARC的寄存器窗口和堆栈知识:
1、 寄存器窗口
在某特定时刻,SPARC的一条指令可以使用8个global寄存器和由24个寄存器组成的寄存器窗口。后者包括8个ins,8个outs,和8个locals。
窗口的数量从2到32,根据具体的实现而不同。某个具体实现的r寄存器个数这样计算:8个global寄存器+窗口个数x16 。这样的话,寄存器的个数即为40(2个窗口)~520(32个窗口)。
1)寄存器对应关系:
2)窗口重叠:
3)8个窗口的重叠情形:
相邻窗口间 8 个寄存器重叠,即:
当前窗口的 o0~ o7 和下一个窗口的 i0 ~ i7 对应于同一组物理寄存器;
当前窗口的 i0 ~ i7 和上一窗口的 o0 ~ o7 也是对应于同一组物理寄存器;
所有窗口的 local 寄存器绝对独立,这也是其取名为 local 的原因。
结合 ABI 粗略来看,就是当前的输入参数来自于上一个窗口的输出参数,当前的输出参数即是下一个窗口的输入参数!
另外需要特别留意的是,最后一个窗口的输出寄存器组 (o0 ~ o7) 与第一个窗口的输入寄存器组 (i0 ~ i7) 也是重叠的,这样整个寄存器窗口看起来就是一个圆。
4)窗口的管理
为了支持窗口的管理,SPARC 引入了 5 个特权寄存器,一组指令和几个异常来管理窗口。
其中 CWP (Current Window Pointer)指定当前使用的窗口(实际置窗口的编号,从 0 开始),则正常情形下,函数调用时都需转动 CWP,以获取一个新的窗口,则在设计 'CALL' 指令(用于支持函数调用)时,理应让其内含 CWP += 1 操作,一如用于函数返回的 'RETURN' 指令内含 CWP -= 1 操作一样,但 SPARC 设计时,为了支持一些特殊的叶函数能直接使用父函数的窗口,尽可能降低开销,其让所有用于函数调用的指令 'CALL' 和 'JMPL' 都不去影响 CWP,而要子函数自己负责,则非叶子函数的第一条指令就是用'SAVE' 转动 CWP 同时分配栈空间(更新 sp 的值)可以想见,当调用链达到一定的深度后,窗口肯定会不够用,这个时候,处理器就会抛出一个异常,由 OS 来负责保存窗口到栈并释放窗口。
下面是跟窗口有关的几个重要的状态寄存器:
(1)处理器状态寄存器的低5位是CWP
(2)Window Invalid Mask Register(WIM)
WIM由超级用户软件控制,并被硬件使用来确认指令SAVE,RESTORE,RETT会不会造成溢出(上溢和下溢)。
Bit0~Bit7分别对应窗口0~7,当该位值为1时表示对应窗口为无效窗口。注意,%WIM只能有一位为1,即在CPU的8个寄存器窗口中只能存在一个窗口为无效窗口。
每个寄存器窗口都可以划分到以下四个状态之中:Current态、Invalid态、Used态和Unused态。假设CWP值为5,%WIM值为0x01,那么寄存器窗口组可以进行如下划分:W0:Invalid;W1~W4:Unused;W5:Current;W6~W7:Used。
Overflow:当前语句为SAVE指令,并且CWP-1对应的窗口为无效窗口时,发生Overflow Trap。因为在程序调用时每出现一个新的子程序就对应一个SAVE语句,当子程序调用级数大于寄存器窗口数时,就会出现寄存器窗口用完的情况,从而需要进行Overflow Trap的处理,通常是将Used