回到backend_init,如果源代码要进行尺寸优化或者优化级别高于–O2,flag_caller_saves将是true。那么init_caller_save被调用。在这里我们关注所有被函数调用使用的物理寄存器,并且它们没有被排除在跨函数调用外(由regclass.c中的fixed_regs,call_used_regs等所指定)。
注意到对于这个评估,它也是在由创建伪函数上下文一节中所创建的伪函数上下文中执行。
111 void
112 init_caller_save (void) in caller-save.c
113 {
114 rtx addr_reg;
115 int offset;
116 rtx address;
117 int i, j;
118 enum machine_mode mode;
119 rtx savepat, restpat;
120 rtx test_reg, test_mem;
121 rtx saveinsn, restinsn;
122
123 /* First find all the registers that we need to deal with and all
124 the modes that they can have. If we can't find a mode to use,
125 we can't have the register live over calls. */
126
127 for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
128 {
129 if (call_used_regs[i] && ! call_fixed_regs[i])
130 {
131 for (j = 1; j <= MOVE_MAX_WORDS; j++)
132 {
133 regno_save_mode[i][j] = HARD_REGNO_CALLER_SAVE_MODE (i, j,
134 VOIDmode);
135 if (regno_save_mode[i][j] == VOIDmode && j == 1)
136 {
137 call_fixed_regs[i] = 1;
138 SET_HARD_REG_BIT (call_fixed_reg_set, i);
139 }
140 }
141 }
142 else
143 regno_save_mode[i][1] = VOIDmode;
144 }
在131行,MOVE_MAX_WORDS具有定义如下。
47 #define MOVE_MAX_WORDS (MOVE_MAX / UNITS_PER_WORD) in caller-save.c
MOVE_MAX,对于x86机器,被定义为16,它是我们在一条合理的快速指令中,能在内存间所能移动的最大字节数。
1137 #define HARD_REGNO_CALLER_SAVE_MODE(REGNO, NREGS, MODE)/ in i386.h
1138 (CC_REGNO_P (REGNO) ? VOIDmode /
1139 : (MODE) == VOIDmode && (NREGS) != 1 ? VOIDmode /
1140 : (MODE) == VOIDmode ? choose_hard_reg_mode ((REGNO), (NREGS), false)/
1141 : (MODE) == HImode && !TARGET_PARTIAL_REG_STALL ? SImode /
1142 : (MODE) == QImode && (REGNO) >= 4 && !TARGET_64BIT ? SImode /
1143 : (MODE))
HARD_REGNO_CALLER_SAVE_MODE尝试找出要求NREGS个连续的从REGNO编号开始的寄存器的模式,如果没有这样的模式,将返回VOIDmode。这个结果将被保存在regno_save_mode[REGNO][NREGS]。看到127行,循环控制变量j的初始值是1,因此regno_save_mode[REGNO][0]永远不会用到,一直为VOIDmode。
根据init_reg_sets_1及表6:x86机器的寄存器分组,call_fixed_regs[REGNO]的值,如果是1,表示对应的寄存器有固定的用途,或者为函数调用机制所使用的寄存器,它不能在跨越多个调用的时段内保存数值,即便通过暂存和恢复的手段。并且回忆call_fixed_regs是call_used_regs的子集,call_used_regs还包括了调用间被破坏的寄存器,这些寄存器通过暂存和恢复的手段可以跨越调用。
在为不同数据尺寸收集了有效模式后,init_caller_save继续检查,那些我们可以不需要辅助寄存器(scratching register)或其他复杂性,而保存及恢复一个寄存器的条件。
init_caller_save (continue)
146 /* The following code tries to approximate the conditions under which
147 we can easily save and restore a register without scratch registers or
148 other complexities. It will usually work, except under conditions where
149 the validity of an insn operand is dependent on the address offset.
150 No such cases are currently known.
151
152 We first find a typical offset from some BASE_REG_CLASS register.
153 This address is chosen by finding the first register in the class
154 and by finding the smallest power of two that is a valid offset from
155 that register in every mode we will use to save registers. */
156
157 for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
158 if (TEST_HARD_REG_BIT
159 (reg_class_contents
160 [(int) MODE_BASE_REG_CLASS (regno_save_mode [i][1])], i))
161 break;
162
163 if (i == FIRST_PSEUDO_REGISTER)
164 abort ();
165
166 addr_reg = gen_rtx_REG (Pmode, i);
167
168 for (offset = 1 << (HOST_BITS_PER_INT / 2); offset; offset >>= 1)
169 {
170 address = gen_rtx_PLUS (Pmode, addr_reg, GEN_INT (offset));
171
172 for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
173 if (regno_save_mode[i][1] != VOIDmode
174 && ! strict_memory_address_p (regno_save_mode[i][1], address))
175 break;
176
177 if (i == FIRST_PSEUDO_REGISTER)
178 break;
179 }
180
181 /* If we didn't find a valid address, we must use register indirect. */
182 if (offset == 0)
183 address = addr_reg;
MODE_BASE_REG_CLASS(mode),对于x86机器,是GENERAL_REGS,当用作reg_class_contents的索引时,将返回代表eax,ebx,ecx,edx,esi,edi,ebp,esp,r8 ~ r15的位图。因此对于157行的FOR循环,i = 0 (eax) 将退出循环。这个寄存器在166行用于保存地址,然后在170行,进一步创建如下图中的地址。
如我们在什么所见,regno_save_mode[REGNO][1] ,如果不是VOIDmode,则表示了编号为REGNO的寄存器所能支持的模式。而strict_memory_address_p则检查这个模式是否能支持地址。实际上strict_memory_address_p调用GO_IF_LEGITIMATE_ADDRESS,而这个宏则调用legitimate_address_p。
图31:地址表达式的rtx对象
浏览legitimate_address_p,已知在上图中,base指向REG,disp指向const_int,该函数将检查保存base的寄存器(eax)及disp是否有效。对于上图中所展示的地址, strict_memory_address_p将返回true。注意到在strict_memory_address_p中,在regno_save_mode[REGNO][1]中的模式在处理中没有用到,仅地址表达式被评估,因此在172行的FOR循环中,寄存器0 ~ FIRST_PSEUDO_REGISTER被成功处理。
init_caller_save (continue)
185 /* Next we try to form an insn to save and restore the register. We
186 see if such an insn is recognized and meets its constraints.
187
188 To avoid lots of unnecessary RTL allocation, we construct all the RTL
189 once, then modify the memory and register operands in-place. */
190
191 test_reg = gen_rtx_REG (VOIDmode, 0);
192 test_mem = gen_rtx_MEM (VOIDmode, address);
193 savepat = gen_rtx_SET (VOIDmode, test_mem, test_reg);
194 restpat = gen_rtx_SET (VOIDmode, test_reg, test_mem);
195
196 saveinsn = gen_rtx_INSN (VOIDmode, 0, 0, 0, 0, 0, savepat, -1, 0, 0);
197 restinsn = gen_rtx_INSN (VOIDmode, 0, 0, 0, 0, 0, restpat, -1, 0, 0);
198
199 for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
200 for (mode = 0 ; mode < MAX_MACHINE_MODE; mode++)
201 if (HARD_REGNO_MODE_OK (i, mode))
202 {
203 int ok;
204
205 /* Update the register number and modes of the register
206 and memory operand. */
207 REGNO (test_reg) = i;
208 PUT_MODE (test_reg, mode);
209 PUT_MODE (test_mem, mode);
210
211 /* Force re-recognition of the modified insns. */
212 INSN_CODE (saveinsn) = -1;
213 INSN_CODE (restinsn) = -1;
214
215 reg_save_code[i][mode] = recog_memoized (saveinsn);
216 reg_restore_code[i][mode] = recog_memoized (restinsn);
217
218 /* Now extract both insns and see if we can meet their
219 constraints. */
220 ok = (reg_save_code[i][mode] != -1
221 && reg_restore_code[i][mode] != -1);
222 if (ok)
223 {
224 extract_insn (saveinsn);
225 ok = constrain_operands (1);
226 extract_insn (restinsn);
227 ok &= constrain_operands (1);
228 }
229
230 if (! ok)
231 {
232 reg_save_code[i][mode] = -1;
233 reg_restore_code[i][mode] = -1;
234 }
235 }
236 else
237 {
238 reg_save_code[i][mode] = -1;
239 reg_restore_code[i][mode] = -1;
240 }
241
242 for (i = 0; i < FIRST_PSEUDO_REGISTER; i++)
243 for (j = 1; j <= MOVE_MAX_WORDS; j++)
244 if (reg_save_code [i][regno_save_mode[i][j]] == -1)
245 {
246 regno_save_mode[i][j] = VOIDmode;
247 if (j == 1)
248 {
249 call_fixed_regs[i] = 1;
250 SET_HARD_REG_BIT (call_fixed_reg_set, i);
251 }
252 }
253 }
接下来,只要内存地址是有效的,如果寄存器可以通过一个简单的SET后端指令(见193及194行)保存符合其最大机器模式的数据,那么该寄存器可用于调用者保存(caller-save)。我们将这些指令的INSN_CODE记录在reg_save_code[REGNO][mode]中(recog_memoized如果能识别该指令,将返回其INSN_CODE)。另外,reg_restore_code[REGNO][mode] 有可能不能被识别,因为在我们产生这些指令时,这些地址可能不是有效的。
为此,需要生成评估所用的rtx对象。 上面,在196及197行,gen_rtx_INSN创建了如下2个INSN对象。
图32:寄存器->内存,初始rtx对象
图33:内存->寄存器,初始rtx对象
在199行的FOR循环,遍历了所有可用的寄存器及机器模式,检查将寄存器保存入内存,及从内存恢复寄存器是否可行。从207到213行,重置了寄存器编号,机器模式,及指令码。注意到在212及213行,指令码被设置为-1,在recog_memoized中,在215及216行,这个值将触发对recog的调用。这个函数如果能识别该INSN对象,将返回指令码;否则返回-1。
如果指定的寄存器及机器模式被recog所识别,那么在224~227行通过extract_insn及 constrain_operands检查该INSN对象的有效性。接着在242行,进一步根据reg_save_code[REGNO][1]更新call_fixed_regs。
在此,后端的初始化已经完成,现在需要清除伪函数上下文。
6861 void
6862 expand_dummy_function_end (void) in function.c
6863 {
6864 /* End any sequences that failed to be closed due to syntax errors. */
6865 while (in_sequence_p ())
6866 end_sequence ();
6867
6868 /* Outside function body, can't compute type's actual size
6869 until next function's body starts. */
6870
6871 free_after_parsing (cfun);
6872 free_after_compilation (cfun);
6873 cfun = 0;
6874 }
这里free_after_parsing不做任何事情,而free_after_compilation将重置cfun的大部分域。