]>
Commit | Line | Data |
---|---|---|
cce8749e CH |
1 | /* Output routines for GCC for ARM/RISCiX. |
2 | Copyright (C) 1991 Free Software Foundation, Inc. | |
3 | Contributed by Pieter `Tiggr' Schoenmakers (rcpieter@win.tue.nl) | |
4 | and Martin Simmons (@harleqn.co.uk). | |
5 | ||
6 | This file is part of GNU CC. | |
7 | ||
8 | GNU CC is free software; you can redistribute it and/or modify | |
9 | it under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation; either version 2, or (at your option) | |
11 | any later version. | |
12 | ||
13 | GNU CC is distributed in the hope that it will be useful, | |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | GNU General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with GNU CC; see the file COPYING. If not, write to | |
20 | the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ | |
21 | ||
22 | #include <stdio.h> | |
23 | #include <assert.h> | |
24 | #include "config.h" | |
25 | #include "rtl.h" | |
26 | #include "regs.h" | |
27 | #include "hard-reg-set.h" | |
28 | #include "real.h" | |
29 | #include "insn-config.h" | |
30 | #include "conditions.h" | |
31 | #include "insn-flags.h" | |
32 | #include "output.h" | |
33 | #include "insn-attr.h" | |
34 | #include "flags.h" | |
35 | ||
36 | /* The maximum number of insns skipped which will be conditionalised if | |
37 | possible. */ | |
38 | #define MAX_INSNS_SKIPPED 5 | |
39 | ||
40 | /* Some function declarations. */ | |
cce8749e CH |
41 | extern FILE *asm_out_file; |
42 | extern char *output_multi_immediate (); | |
43 | extern char *arm_output_asm_insn (); | |
44 | extern void arm_increase_location (); | |
45 | ||
46 | /* In case of a PRE_INC, POST_INC, PRE_DEC, POST_DEC memory reference, we | |
47 | must report the mode of the memory reference from PRINT_OPERAND to | |
48 | PRINT_OPERAND_ADDRESS. */ | |
49 | int output_memory_reference_mode; | |
50 | ||
51 | /* Nonzero if the prologue must setup `fp'. */ | |
52 | int current_function_anonymous_args; | |
53 | ||
54 | /* Location counter of .text segment. */ | |
55 | int arm_text_location = 0; | |
56 | ||
57 | /* A hash table is used to store text segment labels and their associated | |
58 | offset from the start of the text segment. */ | |
59 | struct label_offset | |
60 | { | |
61 | char *name; | |
62 | int offset; | |
63 | struct label_offset *cdr; | |
64 | }; | |
65 | ||
66 | #define LABEL_HASH_SIZE 257 | |
67 | ||
68 | static struct label_offset *offset_table[LABEL_HASH_SIZE]; | |
69 | ||
70 | /* For an explanation of these variables, see final_prescan_insn below. */ | |
71 | int arm_ccfsm_state; | |
72 | int arm_current_cc; | |
73 | rtx arm_target_insn; | |
74 | int arm_target_label; | |
75 | char *arm_condition_codes[]; | |
76 | \f | |
77 | /* Return the number of mov instructions needed to get the constant VALUE into | |
78 | a register. */ | |
79 | ||
80 | int | |
81 | arm_const_nmoves (value) | |
82 | register int value; | |
83 | { | |
84 | register int i; | |
85 | ||
86 | if (value == 0) | |
87 | return (1); | |
88 | for (i = 0; value; i++, value &= ~0xff) | |
89 | while ((value & 3) == 0) | |
90 | value = (value >> 2) | ((value & 3) << 30); | |
91 | return (i); | |
92 | } /* arm_const_nmoves */ | |
93 | ||
94 | ||
95 | /* Return TRUE if int I is a valid immediate ARM constant. */ | |
96 | ||
97 | int | |
98 | const_ok_for_arm (i) | |
99 | int i; | |
100 | { | |
101 | unsigned int mask = ~0xFF; | |
102 | ||
103 | do | |
104 | { | |
105 | if ((i & mask) == 0) | |
106 | return(TRUE); | |
107 | mask = (mask << 2) | (mask >> (32 - 2)); | |
108 | } while (mask != ~0xFF); | |
109 | ||
110 | return (FALSE); | |
111 | } /* const_ok_for_arm */ | |
112 | ||
113 | /* Return TRUE if rtx X is a valid immediate FPU constant. */ | |
114 | ||
115 | int | |
116 | const_double_rtx_ok_for_fpu (x) | |
117 | rtx x; | |
118 | { | |
119 | double d; | |
120 | union real_extract u; | |
121 | u.i[0] = CONST_DOUBLE_LOW(x); | |
122 | u.i[1] = CONST_DOUBLE_HIGH(x); | |
123 | d = u.d; | |
124 | ||
125 | return (d == 0.0 || d == 1.0 || d == 2.0 || d == 3.0 | |
126 | || d == 4.0 || d == 5.0 || d == 0.5 || d == 10.0); | |
127 | } /* const_double_rtx_ok_for_fpu */ | |
128 | \f | |
129 | /* Predicates for `match_operand' and `match_operator'. */ | |
130 | ||
131 | /* Return TRUE for valid operands for the rhs of an ARM instruction. */ | |
132 | ||
133 | int | |
134 | arm_rhs_operand (op, mode) | |
135 | rtx op; | |
136 | enum machine_mode mode; | |
137 | { | |
138 | return (register_operand (op, mode) | |
139 | || (GET_CODE (op) == CONST_INT && const_ok_for_arm (INTVAL (op)))); | |
140 | } /* arm_rhs_operand */ | |
141 | ||
142 | /* Return TRUE for valid operands for the rhs of an FPU instruction. */ | |
143 | ||
144 | int | |
145 | fpu_rhs_operand (op, mode) | |
146 | rtx op; | |
147 | enum machine_mode mode; | |
148 | { | |
149 | if (register_operand (op, mode)) | |
150 | return(TRUE); | |
151 | else if (GET_CODE (op) == CONST_DOUBLE) | |
152 | return (const_double_rtx_ok_for_fpu (op)); | |
153 | else return (FALSE); | |
154 | } /* fpu_rhs_operand */ | |
155 | ||
156 | /* Return nonzero if OP is a constant power of two. */ | |
157 | ||
158 | int | |
159 | power_of_two_operand (op, mode) | |
160 | rtx op; | |
161 | enum machine_mode mode; | |
162 | { | |
163 | if (GET_CODE (op) == CONST_INT) | |
164 | { | |
165 | int value = INTVAL(op); | |
166 | return (value != 0 && (value & (value-1)) == 0); | |
167 | } | |
168 | return (FALSE); | |
169 | } /* power_of_two_operand */ | |
170 | ||
171 | /* Return TRUE for a valid operand of a DImode operation. | |
172 | Either: REG, CONST_DOUBLE or MEM(offsettable). | |
173 | Note that this disallows MEM(REG+REG). */ | |
174 | ||
175 | int | |
176 | di_operand (op, mode) | |
177 | rtx op; | |
178 | enum machine_mode mode; | |
179 | { | |
180 | if (register_operand (op, mode)) | |
181 | return (TRUE); | |
182 | ||
183 | switch (GET_CODE (op)) | |
184 | { | |
185 | case CONST_DOUBLE: | |
186 | case CONST_INT: | |
187 | return (TRUE); | |
188 | case MEM: | |
189 | return (memory_address_p (DImode, XEXP (op, 0)) | |
190 | && offsettable_address_p (FALSE, DImode, XEXP (op, 0))); | |
191 | default: | |
192 | return (FALSE); | |
193 | } | |
194 | } /* di_operand */ | |
195 | ||
196 | /* Return TRUE for valid index operands. */ | |
197 | ||
198 | int | |
199 | index_operand (op, mode) | |
200 | rtx op; | |
201 | enum machine_mode mode; | |
202 | { | |
203 | return (register_operand(op, mode) | |
204 | || (immediate_operand (op, mode) && abs (INTVAL (op)) < 4096)); | |
205 | } /* index_operand */ | |
206 | ||
207 | /* Return TRUE for arithmetic operators which can be combined with a multiply | |
208 | (shift). */ | |
209 | ||
210 | int | |
211 | shiftable_operator (x, mode) | |
212 | rtx x; | |
213 | enum machine_mode mode; | |
214 | { | |
215 | if (GET_MODE (x) != mode) | |
216 | return FALSE; | |
217 | else | |
218 | { | |
219 | enum rtx_code code = GET_CODE (x); | |
220 | ||
221 | return (code == PLUS || code == MINUS | |
222 | || code == IOR || code == XOR || code == AND); | |
223 | } | |
224 | } /* shiftable_operator */ | |
225 | ||
226 | /* Return TRUE for shift operators. */ | |
227 | ||
228 | int | |
229 | shift_operator (x, mode) | |
230 | rtx x; | |
231 | enum machine_mode mode; | |
232 | { | |
233 | if (GET_MODE (x) != mode) | |
234 | return FALSE; | |
235 | else | |
236 | { | |
237 | enum rtx_code code = GET_CODE (x); | |
238 | ||
239 | return (code == ASHIFT || code == LSHIFT | |
240 | || code == ASHIFTRT || code == LSHIFTRT); | |
241 | } | |
242 | } /* shift_operator */ | |
243 | \f | |
244 | /* Routines to output assembly language. */ | |
245 | ||
246 | /* Output the operands of a LDM/STM instruction to STREAM. | |
247 | MASK is the ARM register set mask of which only bits 0-15 are important. | |
248 | INSTR is the possibly suffixed base register. HAT unequals zero if a hat | |
249 | must follow the register list. */ | |
250 | ||
251 | void | |
252 | print_multi_reg (stream, instr, mask, hat) | |
253 | FILE *stream; | |
254 | char *instr; | |
255 | int mask, hat; | |
256 | { | |
257 | int i; | |
258 | int not_first = FALSE; | |
259 | ||
260 | fprintf (stream, "\t%s, {", instr); | |
261 | for (i = 0; i < 16; i++) | |
262 | if (mask & (1 << i)) | |
263 | { | |
264 | if (not_first) | |
265 | fprintf (stream, ", "); | |
266 | fprintf (stream, "%s", reg_names[i]); | |
267 | not_first = TRUE; | |
268 | } | |
269 | fprintf (stream, "}%s\n", hat ? "^" : ""); | |
270 | } /* print_multi_reg */ | |
271 | ||
272 | /* Output a 'call' insn. */ | |
273 | ||
274 | char * | |
275 | output_call (operands) | |
276 | rtx operands[]; | |
277 | { | |
278 | operands[0] = XEXP (operands[0], 0); | |
279 | ||
280 | /* Handle calls to lr using ip (which may be clobbered in subr anyway). */ | |
281 | ||
282 | if (REGNO (operands[0]) == 14) | |
283 | { | |
284 | operands[0] = gen_rtx (REG, SImode, 12); | |
285 | arm_output_asm_insn ("mov\t%0, lr", operands); | |
286 | } | |
287 | arm_output_asm_insn ("mov\tlr, pc", operands); | |
288 | arm_output_asm_insn ("mov\tpc, %0", operands); | |
289 | return (""); | |
290 | } /* output_call */ | |
291 | ||
292 | /* Output a move from arm registers to an fpu registers. | |
293 | OPERANDS[0] is an fpu register. | |
294 | OPERANDS[1] is the first registers of an arm register pair. */ | |
295 | ||
296 | char * | |
297 | output_mov_double_fpu_from_arm (operands) | |
298 | rtx operands[]; | |
299 | { | |
300 | int arm_reg0 = REGNO (operands[1]); | |
301 | rtx ops[2]; | |
302 | ||
303 | if (arm_reg0 == 12) | |
304 | abort(); | |
305 | ops[0] = gen_rtx (REG, SImode, arm_reg0); | |
306 | ops[1] = gen_rtx (REG, SImode, 1 + arm_reg0); | |
307 | arm_output_asm_insn ("stmfd\tsp!, {%0, %1}", ops); | |
308 | arm_output_asm_insn ("ldfd\t%0, [sp], #8", operands); | |
309 | return (""); | |
310 | } /* output_mov_double_fpu_from_arm */ | |
311 | ||
312 | /* Output a move from an fpu register to arm registers. | |
313 | OPERANDS[0] is the first registers of an arm register pair. | |
314 | OPERANDS[1] is an fpu register. */ | |
315 | ||
316 | char * | |
317 | output_mov_double_arm_from_fpu (operands) | |
318 | rtx operands[]; | |
319 | { | |
320 | int arm_reg0 = REGNO (operands[0]); | |
321 | rtx ops[2]; | |
322 | ||
323 | if (arm_reg0 == 12) | |
324 | abort(); | |
325 | ops[0] = gen_rtx (REG, SImode, arm_reg0); | |
326 | ops[1] = gen_rtx (REG, SImode, 1 + arm_reg0); | |
327 | arm_output_asm_insn ("stfd\t%1, [sp, #-8]!", operands); | |
328 | arm_output_asm_insn ("ldmfd\tsp!, {%0, %1}", ops); | |
329 | return(""); | |
330 | } /* output_mov_double_arm_from_fpu */ | |
331 | ||
332 | /* Output a move between double words. | |
333 | It must be REG<-REG, REG<-CONST_DOUBLE, REG<-CONST_INT, REG<-MEM | |
334 | or MEM<-REG and all MEMs must be offsettable addresses. */ | |
335 | ||
336 | char * | |
337 | output_move_double (operands) | |
338 | rtx operands[]; | |
339 | { | |
340 | enum rtx_code code0 = GET_CODE (operands[0]); | |
341 | enum rtx_code code1 = GET_CODE (operands[1]); | |
342 | rtx otherops[2]; | |
343 | ||
344 | if (code0 == REG) | |
345 | { | |
346 | int reg0 = REGNO (operands[0]); | |
347 | ||
348 | otherops[0] = gen_rtx (REG, SImode, 1 + reg0); | |
349 | if (code1 == REG) | |
350 | { | |
351 | int reg1 = REGNO (operands[1]); | |
352 | if (reg1 == 12) | |
353 | abort(); | |
354 | otherops[1] = gen_rtx (REG, SImode, 1 + reg1); | |
355 | ||
356 | /* Ensure the second source is not overwritten */ | |
357 | if (reg0 == 1 + reg1) | |
358 | { | |
359 | arm_output_asm_insn("mov\t%0, %1", otherops); | |
360 | arm_output_asm_insn("mov\t%0, %1", operands); | |
361 | } | |
362 | else | |
363 | { | |
364 | arm_output_asm_insn("mov\t%0, %1", operands); | |
365 | arm_output_asm_insn("mov\t%0, %1", otherops); | |
366 | } | |
367 | } | |
368 | else if (code1 == CONST_DOUBLE) | |
369 | { | |
370 | otherops[1] = gen_rtx (CONST_INT, VOIDmode, | |
371 | CONST_DOUBLE_HIGH (operands[1])); | |
372 | operands[1] = gen_rtx (CONST_INT, VOIDmode, | |
373 | CONST_DOUBLE_LOW (operands[1])); | |
374 | arm_output_asm_insn ("mov\t%0, %1", operands); | |
375 | arm_output_asm_insn ("mov\t%0, %1", otherops); | |
376 | } | |
377 | else if (code1 == CONST_INT) | |
378 | { | |
379 | otherops[1] = const0_rtx; | |
380 | arm_output_asm_insn ("mov\t%0, %1", operands); | |
381 | arm_output_asm_insn ("mov\t%0, %1", otherops); | |
382 | } | |
383 | else if (code1 == MEM) | |
384 | { | |
385 | if (GET_CODE (XEXP (operands[1], 0)) == REG) | |
386 | { | |
387 | /* Handle the simple case where address is [r, #0] more | |
388 | efficient. */ | |
389 | operands[1] = XEXP (operands[1], 0); | |
390 | arm_output_asm_insn ("ldmia\t%1, %M0", operands); | |
391 | } | |
392 | else | |
393 | { | |
394 | otherops[1] = adj_offsettable_operand (operands[1], 4); | |
395 | /* Take care of overlapping base/data reg. */ | |
396 | if (reg_mentioned_p (operands[0], operands[1])) | |
397 | { | |
398 | arm_output_asm_insn ("ldr\t%0, %1", otherops); | |
399 | arm_output_asm_insn ("ldr\t%0, %1", operands); | |
400 | } | |
401 | else | |
402 | { | |
403 | arm_output_asm_insn ("ldr\t%0, %1", operands); | |
404 | arm_output_asm_insn ("ldr\t%0, %1", otherops); | |
405 | } | |
406 | } | |
407 | } | |
408 | else abort(); /* Constraints should prevent this */ | |
409 | } | |
410 | else if (code0 == MEM && code1 == REG) | |
411 | { | |
412 | if (REGNO (operands[1]) == 12) | |
413 | abort(); | |
414 | ||
415 | if (GET_CODE (XEXP (operands[0], 0)) == REG) | |
416 | { | |
417 | operands[0] = XEXP (operands[0], 0); | |
418 | arm_output_asm_insn ("stmia\t%0, %M1", operands); | |
419 | } | |
420 | else | |
421 | { | |
422 | otherops[0] = adj_offsettable_operand (operands[0], 4); | |
423 | otherops[1] = gen_rtx (REG, SImode, 1 + REGNO (operands[1])); | |
424 | arm_output_asm_insn ("str\t%1, %0", operands); | |
425 | arm_output_asm_insn ("str\t%1, %0", otherops); | |
426 | } | |
427 | } | |
428 | else abort(); /* Constraints should prevent this */ | |
429 | ||
430 | return(""); | |
431 | } /* output_move_double */ | |
432 | ||
433 | ||
434 | /* Output an arbitrary MOV reg, #n. | |
435 | OPERANDS[0] is a register. OPERANDS[1] is a const_int. */ | |
436 | ||
437 | char * | |
438 | output_mov_immediate (operands) | |
439 | rtx operands[2]; | |
440 | { | |
441 | int n = INTVAL (operands[1]); | |
442 | int n_ones = 0; | |
443 | int i; | |
444 | ||
445 | /* Try to use one MOV */ | |
446 | ||
447 | if (const_ok_for_arm (n)) | |
448 | return (arm_output_asm_insn ("mov\t%0, %1", operands)); | |
449 | ||
450 | /* Try to use one MVN */ | |
451 | ||
452 | if (const_ok_for_arm(~n)) | |
453 | { | |
454 | operands[1] = gen_rtx (CONST_INT, VOIDmode, ~n); | |
455 | return (arm_output_asm_insn ("mvn\t%0, %1", operands)); | |
456 | } | |
457 | ||
458 | /* If all else fails, make it out of ORRs or BICs as appropriate. */ | |
459 | ||
460 | for (i=0; i < 32; i++) | |
461 | if (n & 1 << i) | |
462 | n_ones++; | |
463 | ||
464 | if (n_ones > 16) /* Shorter to use MVN with BIC in this case. */ | |
465 | output_multi_immediate(operands, "mvn\t%0, %1", "bic\t%0, %0, %1", 1, ~n); | |
466 | else | |
467 | output_multi_immediate(operands, "mov\t%0, %1", "orr\t%0, %0, %1", 1, n); | |
468 | return(""); | |
469 | } /* output_mov_immediate */ | |
470 | ||
471 | ||
472 | /* Output an ADD r, s, #n where n may be too big for one instruction. If | |
473 | adding zero to one register, output nothing. */ | |
474 | ||
475 | char * | |
476 | output_add_immediate (operands) | |
477 | rtx operands[3]; | |
478 | { | |
479 | int n = INTVAL (operands[2]); | |
480 | ||
481 | if (n != 0 || REGNO (operands[0]) != REGNO (operands[1])) | |
482 | { | |
483 | if (n < 0) | |
484 | output_multi_immediate (operands, | |
485 | "sub\t%0, %1, %2", "sub\t%0, %0, %2", 2, -n); | |
486 | else | |
487 | output_multi_immediate (operands, | |
488 | "add\t%0, %1, %2", "add\t%0, %0, %2", 2, n); | |
489 | } | |
490 | return(""); | |
491 | } /* output_add_immediate */ | |
492 | ||
493 | ||
494 | /* Output a multiple immediate operation. | |
495 | OPERANDS is the vector of operands referred to in the output patterns. | |
496 | INSTR1 is the output pattern to use for the first constant. | |
497 | INSTR2 is the output pattern to use for subsequent constants. | |
498 | IMMED_OP is the index of the constant slot in OPERANDS. | |
499 | N is the constant value. */ | |
500 | ||
501 | char * | |
502 | output_multi_immediate (operands, instr1, instr2, immed_op, n) | |
503 | rtx operands[]; | |
504 | char *instr1, *instr2; | |
505 | int immed_op, n; | |
506 | { | |
507 | if (n == 0) | |
508 | { | |
509 | operands[immed_op] = const0_rtx; | |
510 | arm_output_asm_insn (instr1, operands); /* Quick and easy output */ | |
511 | } | |
512 | else | |
513 | { | |
514 | int i; | |
515 | char *instr = instr1; | |
516 | ||
517 | /* Note that n is never zero here (which would give no output) */ | |
518 | ||
519 | for (i = 0; i < 32; i += 2) | |
520 | { | |
521 | if (n & (3 << i)) | |
522 | { | |
523 | operands[immed_op] = gen_rtx (CONST_INT, VOIDmode, | |
524 | n & (255 << i)); | |
525 | arm_output_asm_insn (instr, operands); | |
526 | instr = instr2; | |
527 | i += 6; | |
528 | } | |
529 | } | |
530 | } | |
531 | return (""); | |
532 | } /* output_multi_immediate */ | |
533 | ||
534 | ||
535 | /* Return the appropriate ARM instruction for the operation code. | |
536 | The returned result should not be overwritten. OP is the rtx of the | |
537 | operation. SHIFT_FIRST_ARG is TRUE if the first argument of the operator | |
538 | was shifted. */ | |
539 | ||
540 | char * | |
541 | arithmetic_instr (op, shift_first_arg) | |
542 | rtx op; | |
543 | { | |
544 | switch (GET_CODE(op)) | |
545 | { | |
546 | case PLUS: | |
547 | return ("add"); | |
548 | case MINUS: | |
549 | if (shift_first_arg) | |
550 | return ("rsb"); | |
551 | else | |
552 | return ("sub"); | |
553 | case IOR: | |
554 | return ("orr"); | |
555 | case XOR: | |
556 | return ("eor"); | |
557 | case AND: | |
558 | return ("and"); | |
559 | default: | |
560 | abort(); | |
561 | } | |
562 | return (""); /* stupid cc */ | |
563 | } /* arithmetic_instr */ | |
564 | ||
565 | ||
566 | /* Ensure valid constant shifts and return the appropriate shift mnemonic | |
567 | for the operation code. The returned result should not be overwritten. | |
568 | OP is the rtx code of the shift. | |
569 | SHIFT_PTR points to the shift size operand. */ | |
570 | ||
571 | char * | |
572 | shift_instr (op, shift_ptr) | |
573 | enum rtx_code op; | |
574 | rtx *shift_ptr; | |
575 | { | |
576 | int min_shift = 0; | |
577 | int max_shift = 31; | |
578 | char *mnem; | |
579 | ||
580 | switch (op) | |
581 | { | |
582 | case ASHIFT: | |
583 | mnem = "asl"; | |
584 | break; | |
585 | case LSHIFT: | |
586 | mnem = "lsl"; | |
587 | break; | |
588 | case ASHIFTRT: | |
589 | mnem = "asr"; | |
590 | max_shift = 32; | |
591 | break; | |
592 | case LSHIFTRT: | |
593 | mnem = "lsr"; | |
594 | max_shift = 32; | |
595 | break; | |
596 | default: | |
597 | abort(); | |
598 | } | |
599 | ||
600 | if (GET_CODE (*shift_ptr) == CONST_INT) | |
601 | { | |
602 | int shift = INTVAL (*shift_ptr); | |
603 | ||
604 | if (shift < min_shift) | |
605 | *shift_ptr = gen_rtx (CONST_INT, VOIDmode, 0); | |
606 | else if (shift > max_shift) | |
607 | *shift_ptr = gen_rtx (CONST_INT, VOIDmode, max_shift); | |
608 | } | |
609 | return (mnem); | |
610 | } /* shift_instr */ | |
611 | ||
612 | ||
613 | /* Obtain the shift from the POWER of two. */ | |
614 | ||
615 | int | |
616 | int_log2 (power) | |
617 | unsigned int power; | |
618 | { | |
619 | int shift = 0; | |
620 | ||
621 | while (((1 << shift) & power) == 0) | |
622 | { | |
623 | if (shift > 31) | |
624 | abort(); | |
625 | shift++; | |
626 | } | |
627 | return (shift); | |
628 | } /* int_log2 */ | |
629 | ||
630 | ||
631 | /* Output an arithmetic instruction which may set the condition code. | |
632 | OPERANDS[0] is the destination register. | |
633 | OPERANDS[1] is the arithmetic operator expression. | |
634 | OPERANDS[2] is the left hand argument. | |
635 | OPERANDS[3] is the right hand argument. | |
636 | CONST_FIRST_ARG is TRUE if the first argument of the operator was constant. | |
637 | SET_COND is TRUE when the condition code should be set. */ | |
638 | ||
639 | char * | |
640 | output_arithmetic (operands, const_first_arg, set_cond) | |
641 | rtx operands[4]; | |
642 | int const_first_arg; | |
643 | int set_cond; | |
644 | { | |
645 | char mnemonic[80]; | |
646 | char *instr = arithmetic_instr (operands[1], const_first_arg); | |
647 | ||
648 | sprintf (mnemonic, "%s%s\t%%0, %%2, %%3", instr, set_cond ? "s" : ""); | |
649 | return (arm_output_asm_insn (mnemonic, operands)); | |
650 | } /* output_arithmetic */ | |
651 | ||
652 | ||
653 | /* Output an arithmetic instruction with a shift. | |
654 | OPERANDS[0] is the destination register. | |
655 | OPERANDS[1] is the arithmetic operator expression. | |
656 | OPERANDS[2] is the unshifted register. | |
657 | OPERANDS[3] is the shift operator expression. | |
658 | OPERANDS[4] is the shifted register. | |
659 | OPERANDS[5] is the shift constant or register. | |
660 | SHIFT_FIRST_ARG is TRUE if the first argument of the operator was shifted. | |
661 | SET_COND is TRUE when the condition code should be set. */ | |
662 | ||
663 | char * | |
664 | output_arithmetic_with_shift (operands, shift_first_arg, set_cond) | |
665 | rtx operands[6]; | |
666 | int shift_first_arg; | |
667 | int set_cond; | |
668 | { | |
669 | char mnemonic[80]; | |
670 | char *instr = arithmetic_instr (operands[1], shift_first_arg); | |
671 | char *condbit = set_cond ? "s" : ""; | |
672 | char *shift = shift_instr (GET_CODE (operands[3]), &operands[5]); | |
673 | ||
674 | sprintf (mnemonic, "%s%s\t%%0, %%2, %%4, %s %%5", instr, condbit, shift); | |
675 | return (arm_output_asm_insn (mnemonic, operands)); | |
676 | } /* output_arithmetic_with_shift */ | |
677 | ||
678 | ||
679 | /* Output an arithmetic instruction with a power of two multiplication. | |
680 | OPERANDS[0] is the destination register. | |
681 | OPERANDS[1] is the arithmetic operator expression. | |
682 | OPERANDS[2] is the unmultiplied register. | |
683 | OPERANDS[3] is the multiplied register. | |
684 | OPERANDS[4] is the constant multiple (power of two). | |
685 | SHIFT_FIRST_ARG is TRUE if the first arg of the operator was multiplied. */ | |
686 | ||
687 | char * | |
688 | output_arithmetic_with_immediate_multiply (operands, shift_first_arg) | |
689 | rtx operands[5]; | |
690 | int shift_first_arg; | |
691 | { | |
692 | char mnemonic[80]; | |
693 | char *instr = arithmetic_instr (operands[1], shift_first_arg); | |
694 | int shift = int_log2 (INTVAL (operands[4])); | |
695 | ||
696 | sprintf (mnemonic, "%s\t%%0, %%2, %%3, asl#%d", instr, shift); | |
697 | return (arm_output_asm_insn (mnemonic, operands)); | |
698 | } /* output_arithmetic_with_immediate_multiply */ | |
699 | ||
700 | ||
701 | /* Output a move with a shift. | |
702 | OP is the shift rtx code. | |
703 | OPERANDS[0] = destination register. | |
704 | OPERANDS[1] = source register. | |
705 | OPERANDS[2] = shift constant or register. */ | |
706 | ||
707 | char * | |
708 | output_shifted_move (op, operands) | |
709 | enum rtx_code op; | |
710 | rtx operands[2]; | |
711 | { | |
712 | char mnemonic[80]; | |
713 | ||
714 | if (GET_CODE (operands[2]) == CONST_INT && INTVAL (operands[2]) == 0) | |
715 | sprintf (mnemonic, "mov\t%%0, %%1"); | |
716 | else | |
717 | sprintf (mnemonic, "mov\t%%0, %%1, %s %%2", | |
718 | shift_instr (op, &operands[2])); | |
719 | return (arm_output_asm_insn (mnemonic, operands)); | |
720 | } /* output_shifted_move */ | |
721 | ||
722 | ||
723 | /* Output a .ascii pseudo-op, keeping track of lengths. This is because | |
724 | /bin/as is horribly restrictive. */ | |
725 | ||
726 | void | |
727 | output_ascii_pseudo_op (stream, p, len) | |
728 | FILE *stream; | |
729 | char *p; | |
730 | int len; | |
731 | { | |
732 | int i; | |
733 | int len_so_far = 1000; | |
734 | int chars_so_far = 0; | |
735 | ||
736 | for (i = 0; i < len; i++) | |
737 | { | |
738 | register int c = p[i]; | |
739 | ||
740 | if (len_so_far > 50) | |
741 | { | |
742 | if (chars_so_far) | |
743 | fputs ("\"\n", stream); | |
744 | fputs ("\t.ascii\t\"", stream); | |
745 | len_so_far = 0; | |
746 | arm_increase_location (chars_so_far); | |
747 | chars_so_far = 0; | |
748 | } | |
749 | ||
750 | if (c == '\"' || c == '\\') | |
751 | { | |
752 | putc('\\', stream); | |
753 | len_so_far++; | |
754 | } | |
755 | if (c >= ' ' && c < 0177) | |
756 | { | |
757 | putc (c, stream); | |
758 | len_so_far++; | |
759 | } | |
760 | else | |
761 | { | |
762 | fprintf (stream, "\\%03o", c); | |
763 | len_so_far +=4; | |
764 | } | |
765 | chars_so_far++; | |
766 | } | |
767 | fputs ("\"\n", stream); | |
768 | arm_increase_location (chars_so_far); | |
769 | } /* output_ascii_pseudo_op */ | |
770 | \f | |
771 | void | |
772 | output_prologue (f, frame_size) | |
773 | FILE *f; | |
774 | int frame_size; | |
775 | { | |
776 | ||
777 | int reg, live_regs_mask = 0, code_size = 0; | |
778 | rtx operands[3]; | |
779 | ||
780 | /* Nonzero if the `fp' (argument pointer) register is needed. */ | |
781 | int fp_needed = 0; | |
782 | ||
783 | /* Nonzero if we must stuff some register arguments onto the stack as if | |
784 | they were passed there. */ | |
785 | int store_arg_regs = 0; | |
786 | ||
787 | fprintf (f, "\t@ args = %d, pretend = %d, frame = %d\n", | |
788 | current_function_args_size, current_function_pretend_args_size, frame_size); | |
789 | fprintf (f, "\t@ frame_pointer_needed = %d, current_function_anonymous_args = %d\n", | |
790 | frame_pointer_needed, current_function_anonymous_args); | |
791 | ||
792 | if (current_function_pretend_args_size || current_function_args_size | |
793 | || frame_pointer_needed || current_function_anonymous_args || TARGET_APCS) | |
794 | fp_needed = 1; | |
795 | ||
796 | if (current_function_anonymous_args && current_function_pretend_args_size) | |
797 | store_arg_regs = 1; | |
798 | ||
799 | for (reg = 4; reg < 10; reg++) | |
800 | if (regs_ever_live[reg]) | |
801 | live_regs_mask |= (1 << reg); | |
802 | ||
803 | if (fp_needed) | |
804 | { | |
805 | live_regs_mask |= 0xD800; | |
806 | /* The following statement is probably redundant now | |
807 | because the frame pointer is recorded in regs_ever_live. */ | |
808 | if (frame_pointer_needed) | |
809 | live_regs_mask |= (1 << FRAME_POINTER_REGNUM); | |
810 | fputs ("\tmov\tip, sp\n", f); | |
811 | code_size += 4; | |
812 | } | |
813 | else if (regs_ever_live[14]) | |
814 | live_regs_mask |= 0x4000; | |
815 | ||
816 | /* If CURRENT_FUNCTION_PRETEND_ARGS_SIZE, adjust the stack pointer to make | |
817 | room. If also STORE_ARG_REGS store the argument registers involved in | |
818 | the created slot (this is for stdarg and varargs). */ | |
819 | if (current_function_pretend_args_size) | |
820 | { | |
821 | if (store_arg_regs) | |
822 | { | |
823 | int arg_size, mask = 0; | |
824 | ||
825 | assert (current_function_pretend_args_size <= 16); | |
826 | for (reg = 3, arg_size = current_function_pretend_args_size; | |
827 | arg_size > 0; reg--, arg_size -= 4) | |
828 | mask |= (1 << reg); | |
829 | print_multi_reg (f, "stmfd\tsp!", mask, FALSE); | |
830 | } | |
831 | else | |
832 | { | |
833 | operands[0] = operands[1] = stack_pointer_rtx; | |
834 | operands[2] = gen_rtx (CONST_INT, VOIDmode, | |
835 | -current_function_pretend_args_size); | |
836 | output_add_immediate (operands); | |
837 | } | |
838 | } | |
839 | ||
840 | if (live_regs_mask) | |
841 | { | |
842 | print_multi_reg (f, "stmfd\tsp!", live_regs_mask, FALSE); | |
843 | code_size += 4; | |
844 | } | |
845 | ||
846 | for (reg = 23; reg > 19; reg--) | |
847 | if (regs_ever_live[reg]) | |
848 | { | |
849 | fprintf (f, "\tstfe\t%s, [sp, #-12]!\n", reg_names[reg]); | |
850 | code_size += 4; | |
851 | } | |
852 | ||
853 | if (fp_needed) | |
854 | { | |
855 | /* Make `fp' point to saved value of `pc'. */ | |
856 | ||
857 | operands[0] = arg_pointer_rtx; | |
858 | operands[1] = gen_rtx (REG, SImode, 12); | |
859 | operands[2] = gen_rtx (CONST_INT, VOIDmode, | |
860 | - (4 + current_function_pretend_args_size)); | |
861 | output_add_immediate (operands); | |
862 | } | |
863 | ||
864 | if (frame_pointer_needed) | |
865 | { | |
866 | fprintf (f, "\tmov\trfp, sp\n"); | |
867 | code_size += 4; | |
868 | } | |
869 | ||
870 | if (frame_size) | |
871 | { | |
872 | operands[0] = operands[1] = stack_pointer_rtx; | |
873 | operands[2] = gen_rtx (CONST_INT, VOIDmode, -frame_size); | |
874 | output_add_immediate (operands); | |
875 | } | |
876 | ||
877 | arm_increase_location (code_size); | |
878 | } /* output_prologue */ | |
879 | ||
880 | ||
881 | void | |
882 | output_epilogue (f, frame_size) | |
883 | FILE *f; | |
884 | int frame_size; | |
885 | { | |
886 | int reg, live_regs_mask = 0, code_size = 0, fp_needed = 0; | |
887 | rtx operands[3]; | |
888 | ||
889 | if (current_function_pretend_args_size || current_function_args_size | |
890 | || frame_pointer_needed || current_function_anonymous_args || TARGET_APCS) | |
891 | fp_needed = 1; | |
892 | ||
893 | for (reg = 4; reg < 10; reg++) | |
894 | if (regs_ever_live[reg]) | |
895 | live_regs_mask |= (1 << reg); | |
896 | ||
897 | if (fp_needed) | |
898 | { | |
899 | live_regs_mask |= 0xA800; | |
900 | if (frame_pointer_needed) | |
901 | live_regs_mask |= (1 << FRAME_POINTER_REGNUM); | |
902 | } | |
903 | else if (regs_ever_live[14]) | |
904 | live_regs_mask |= 0x4000; | |
905 | ||
906 | for (reg = 20; reg < 24; reg++) | |
907 | if (regs_ever_live[reg]) | |
908 | { | |
909 | fprintf (f, "\tldfe\t%s, [%s], #12\n", reg_names[reg], | |
910 | frame_pointer_needed ? "rfp" : "sp"); | |
911 | code_size += 4; | |
912 | } | |
913 | ||
914 | if (fp_needed) | |
915 | { | |
916 | print_multi_reg (f, "ldmea\tfp", live_regs_mask, TRUE); | |
917 | code_size += 4; | |
918 | } | |
919 | else | |
920 | { | |
921 | if (current_function_pretend_args_size == 0 && regs_ever_live[14]) | |
922 | { | |
923 | print_multi_reg (f, "ldmfd\tsp!", | |
924 | (live_regs_mask & ~0x4000) | 0x8000, TRUE); | |
925 | code_size += 4; | |
926 | } | |
927 | else | |
928 | { | |
929 | if (live_regs_mask) | |
930 | { | |
931 | print_multi_reg (f, "ldmfd\tsp!", live_regs_mask, FALSE); | |
932 | code_size += 4; | |
933 | } | |
934 | if (current_function_pretend_args_size) | |
935 | { | |
936 | operands[0] = operands[1] = stack_pointer_rtx; | |
937 | operands[2] = gen_rtx (CONST_INT, VOIDmode, | |
938 | current_function_pretend_args_size); | |
939 | output_add_immediate (operands); | |
940 | } | |
941 | fputs ("\tmovs\tpc, lr\n", f); | |
942 | code_size += 4; | |
943 | } | |
944 | } | |
945 | arm_increase_location (code_size); | |
946 | current_function_anonymous_args = 0; | |
947 | } /* output_epilogue */ | |
948 | \f | |
949 | /* Increase the `arm_text_location' by AMOUNT if we're in the text | |
950 | segment. */ | |
951 | ||
952 | void | |
953 | arm_increase_location (amount) | |
954 | int amount; | |
955 | { | |
956 | if (in_text_section ()) | |
957 | arm_text_location += amount; | |
958 | } /* arm_increase_location */ | |
959 | ||
960 | ||
961 | /* Like output_asm_insn (), but also increases the arm_text_location (if in | |
962 | the .text segment, of course, even though this will always be true). | |
963 | Returns the empty string. */ | |
964 | ||
965 | char * | |
966 | arm_output_asm_insn (template, operands) | |
967 | char *template; | |
968 | rtx *operands; | |
969 | { | |
970 | extern FILE *asm_out_file; | |
971 | ||
972 | output_asm_insn (template, operands); | |
973 | if (in_text_section ()) | |
974 | arm_text_location += 4; | |
975 | fflush (asm_out_file); | |
976 | return (""); | |
977 | } /* arm_output_asm_insn */ | |
978 | ||
979 | ||
980 | /* Output a label definition. If this label is within the .text segment, it | |
981 | is stored in OFFSET_TABLE, to be used when building `llc' instructions. | |
982 | Maybe GCC remembers names not starting with a `*' for a long time, but this | |
983 | is a minority anyway, so we just make a copy. Do not store the leading `*' | |
984 | if the name starts with one. */ | |
985 | ||
986 | void | |
987 | arm_asm_output_label (stream, name) | |
988 | FILE *stream; | |
989 | char *name; | |
990 | { | |
991 | char *real_name, *s; | |
992 | struct label_offset *cur; | |
993 | int hash = 0; | |
994 | ||
995 | assemble_name (stream, name); | |
996 | fputs (":\n", stream); | |
997 | if (! in_text_section ()) | |
998 | return; | |
999 | ||
1000 | if (name[0] == '*') | |
1001 | { | |
1002 | real_name = xmalloc (1 + strlen (&name[1])); | |
1003 | strcpy (real_name, &name[1]); | |
1004 | } | |
1005 | else | |
1006 | { | |
1007 | real_name = xmalloc (2 + strlen (name)); | |
1008 | strcpy (real_name, "_"); | |
1009 | strcat (real_name, name); | |
1010 | } | |
1011 | for (s = real_name; *s; s++) | |
1012 | hash += *s; | |
1013 | hash = hash % LABEL_HASH_SIZE; | |
838928c2 | 1014 | cur = (struct label_offset *) xmalloc (sizeof (struct label_offset)); |
cce8749e CH |
1015 | cur->name = real_name; |
1016 | cur->offset = arm_text_location; | |
1017 | cur->cdr = offset_table[hash]; | |
1018 | offset_table[hash] = cur; | |
1019 | } /* arm_asm_output_label */ | |
1020 | ||
1021 | ||
1022 | /* Output the instructions needed to perform what Martin's /bin/as called | |
1023 | llc: load an SImode thing from the function's constant pool. | |
1024 | ||
1025 | XXX This could be enhanced in that we do not really need a pointer in the | |
1026 | constant pool pointing to the real thing. If we can address this pointer, | |
1027 | we can also address what it is pointing at, in fact, anything in the text | |
1028 | segment which has been defined already within this .s file. */ | |
1029 | ||
1030 | char * | |
1031 | arm_output_llc (operands) | |
1032 | rtx *operands; | |
1033 | { | |
1034 | char *s, *name = XSTR (XEXP (operands[1], 0), 0); | |
1035 | struct label_offset *he; | |
1036 | int hash = 0, conditional = (arm_ccfsm_state == 3 || arm_ccfsm_state == 4); | |
1037 | ||
1038 | if (*name != '*') | |
1039 | abort (); | |
1040 | ||
1041 | for (s = &name[1]; *s; s++) | |
1042 | hash += *s; | |
1043 | hash = hash % LABEL_HASH_SIZE; | |
1044 | he = offset_table[hash]; | |
1045 | while (he && strcmp (he->name, &name[1])) | |
1046 | he = he->cdr; | |
1047 | ||
1048 | if (!he) | |
1049 | abort (); | |
1050 | ||
1051 | if (arm_text_location + 8 - he->offset < 4095) | |
1052 | { | |
1053 | fprintf (asm_out_file, "\tldr%s\t%s, [pc, #%s - . - 8]\n", | |
1054 | conditional ? arm_condition_codes[arm_current_cc] : "", | |
1055 | reg_names[REGNO (operands[0])], &name[1]); | |
1056 | arm_increase_location (4); | |
1057 | return (""); | |
1058 | } | |
1059 | else | |
1060 | { | |
1061 | int offset = - (arm_text_location + 8 - he->offset); | |
1062 | char *reg_name = reg_names[REGNO (operands[0])]; | |
1063 | ||
1064 | /* ??? This is a hack, assuming the constant pool never is more than | |
1065 | (1 + 255) * 4096 == 1Meg away from the PC. */ | |
1066 | ||
1067 | if (offset > 1000000) | |
1068 | abort (); | |
1069 | ||
1070 | fprintf (asm_out_file, "\tsub%s\t%s, pc, #(8 + . - %s) & ~4095\n", | |
1071 | conditional ? arm_condition_codes[arm_current_cc] : "", | |
1072 | reg_name, &name[1]); | |
1073 | fprintf (asm_out_file, "\tldr%s\t%s, [%s, #- ((4 + . - %s) & 4095)]\n", | |
1074 | conditional ? arm_condition_codes[arm_current_cc] : "", | |
1075 | reg_name, reg_name, &name[1]); | |
1076 | arm_increase_location (8); | |
1077 | } | |
1078 | return (""); | |
1079 | } /* arm_output_llc */ | |
1080 | ||
1081 | ||
1082 | /* Output code resembling an .lcomm directive. /bin/as doesn't have this | |
1083 | directive hence this hack, which works by reserving some `.space' in the | |
1084 | bss segment directly. | |
1085 | ||
1086 | XXX This is a severe hack, which is guaranteed NOT to work since it doesn't | |
1087 | define STATIC COMMON space but merely STATIC BSS space. */ | |
1088 | ||
1089 | void | |
1090 | output_lcomm_directive (stream, name, size, rounded) | |
1091 | FILE *stream; | |
1092 | char *name; | |
1093 | int size, rounded; | |
1094 | { | |
1095 | fputs ("\n\t.bss\t@ .lcomm\n", stream); | |
1096 | assemble_name (stream, name); | |
1097 | fprintf (stream, ":\t.space\t%d\n", rounded); | |
1098 | if (in_text_section ()) | |
1099 | fputs ("\n\t.text\n", stream); | |
1100 | else | |
1101 | fputs ("\n\t.data\n", stream); | |
1102 | } /* output_lcomm_directive */ | |
1103 | \f | |
1104 | /* A finite state machine takes care of noticing whether or not instructions | |
1105 | can be conditionally executed, and thus decrease execution time and code | |
1106 | size by deleting branch instructions. The fsm is controlled by | |
1107 | final_prescan_insn, and controls the actions of ASM_OUTPUT_OPCODE. */ | |
1108 | ||
1109 | /* The state of the fsm controlling condition codes are: | |
1110 | 0: normal, do nothing special | |
1111 | 1: make ASM_OUTPUT_OPCODE not output this instruction | |
1112 | 2: make ASM_OUTPUT_OPCODE not output this instruction | |
1113 | 3: make instructions conditional | |
1114 | 4: make instructions conditional | |
1115 | ||
1116 | State transitions (state->state by whom under condition): | |
1117 | 0 -> 1 final_prescan_insn if the `target' is a label | |
1118 | 0 -> 2 final_prescan_insn if the `target' is an unconditional branch | |
1119 | 1 -> 3 ASM_OUTPUT_OPCODE after not having output the conditional branch | |
1120 | 2 -> 4 ASM_OUTPUT_OPCODE after not having output the conditional branch | |
1121 | 3 -> 0 ASM_OUTPUT_INTERNAL_LABEL if the `target' label is reached | |
1122 | (the target label has CODE_LABEL_NUMBER equal to arm_target_label). | |
1123 | 4 -> 0 final_prescan_insn if the `target' unconditional branch is reached | |
1124 | (the target insn is arm_target_insn). | |
1125 | ||
1126 | XXX In case the `target' is an unconditional branch, this conditionalising | |
1127 | of the instructions always reduces code size, but not always execution | |
1128 | time. But then, I want to reduce the code size to somewhere near what | |
1129 | /bin/cc produces. */ | |
1130 | ||
1131 | /* The condition codes of the ARM, and the inverse function. */ | |
1132 | char *arm_condition_codes[] = | |
1133 | { | |
1134 | "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", | |
1135 | "hi", "ls", "ge", "lt", "gt", "le", "al", "nv" | |
1136 | }; | |
1137 | ||
1138 | #define ARM_INVERSE_CONDITION_CODE(X) ((X) ^ 1) | |
1139 | ||
1140 | /* Returns the index of the ARM condition code string in | |
1141 | `arm_condition_codes'. COMPARISON should be an rtx like | |
1142 | `(eq (...) (...))'. */ | |
1143 | ||
1144 | int | |
1145 | get_arm_condition_code (comparison) | |
1146 | rtx comparison; | |
1147 | { | |
1148 | switch (GET_CODE (comparison)) | |
1149 | { | |
1150 | case NE: return (1); | |
1151 | case EQ: return (0); | |
1152 | case GE: return (10); | |
1153 | case GT: return (12); | |
1154 | case LE: return (13); | |
1155 | case LT: return (11); | |
1156 | case GEU: return (2); | |
1157 | case GTU: return (8); | |
1158 | case LEU: return (9); | |
1159 | case LTU: return (3); | |
1160 | default: abort (); | |
1161 | } | |
1162 | /*NOTREACHED*/ | |
1163 | return (42); | |
1164 | } /* get_arm_condition_code */ | |
1165 | ||
1166 | ||
1167 | void | |
1168 | final_prescan_insn (insn, opvec, noperands) | |
1169 | rtx insn; | |
1170 | rtx *opvec; | |
1171 | int noperands; | |
1172 | { | |
1173 | /* BODY will hold the body of INSN. */ | |
1174 | register rtx body = PATTERN (insn); | |
1175 | ||
1176 | /* This will be 1 if trying to repeat the trick, and things need to be | |
1177 | reversed if it appears to fail. */ | |
1178 | int reverse = 0; | |
1179 | ||
1180 | /* START_INSN will hold the insn from where we start looking. This is the | |
1181 | first insn after the following code_label if REVERSE is true. */ | |
1182 | rtx start_insn = insn; | |
1183 | ||
1184 | /* If in state 4, check if the target branch is reached, in order to | |
1185 | change back to state 0. */ | |
1186 | if (arm_ccfsm_state == 4) | |
1187 | { | |
1188 | if (insn == arm_target_insn) | |
1189 | arm_ccfsm_state = 0; | |
1190 | return; | |
1191 | } | |
1192 | ||
1193 | /* If in state 3, it is possible to repeat the trick, if this insn is an | |
1194 | unconditional branch to a label, and immediately following this branch | |
1195 | is the previous target label which is only used once, and the label this | |
1196 | branch jumps to is not too far off. */ | |
1197 | if (arm_ccfsm_state == 3) | |
1198 | { | |
1199 | if (simplejump_p (insn)) | |
1200 | { | |
1201 | start_insn = next_nonnote_insn (start_insn); | |
1202 | if (GET_CODE (start_insn) == BARRIER) | |
1203 | { | |
1204 | /* XXX Isn't this always a barrier? */ | |
1205 | start_insn = next_nonnote_insn (start_insn); | |
1206 | } | |
1207 | if (GET_CODE (start_insn) == CODE_LABEL | |
1208 | && CODE_LABEL_NUMBER (start_insn) == arm_target_label | |
1209 | && LABEL_NUSES (start_insn) == 1) | |
1210 | reverse = TRUE; | |
1211 | else | |
1212 | return; | |
1213 | } | |
1214 | else | |
1215 | return; | |
1216 | } | |
1217 | ||
1218 | if (arm_ccfsm_state != 0 && !reverse) | |
1219 | abort (); | |
1220 | if (GET_CODE (insn) != JUMP_INSN) | |
1221 | return; | |
1222 | ||
1223 | if (reverse | |
1224 | || (GET_CODE (body) == SET && GET_CODE (SET_DEST (body)) == PC | |
1225 | && GET_CODE (SET_SRC (body)) == IF_THEN_ELSE)) | |
1226 | { | |
1227 | int insns_skipped = 0, fail = FALSE, succeed = FALSE; | |
1228 | /* Flag which part of the IF_THEN_ELSE is the LABEL_REF. */ | |
1229 | int then_not_else = TRUE; | |
1230 | rtx this_insn = start_insn, label; | |
1231 | ||
1232 | /* Register the insn jumped to. */ | |
1233 | if (reverse) | |
1234 | label = XEXP (SET_SRC (body), 0); | |
1235 | else if (GET_CODE (XEXP (SET_SRC (body), 1)) == LABEL_REF) | |
1236 | label = XEXP (XEXP (SET_SRC (body), 1), 0); | |
1237 | else if (GET_CODE (XEXP (SET_SRC (body), 2)) == LABEL_REF) | |
1238 | { | |
1239 | label = XEXP (XEXP (SET_SRC (body), 2), 0); | |
1240 | then_not_else = FALSE; | |
1241 | } | |
1242 | else | |
1243 | abort (); | |
1244 | ||
1245 | /* See how many insns this branch skips, and what kind of insns. If all | |
1246 | insns are okay, and the label or unconditional branch to the same | |
1247 | label is not too far away, succeed. */ | |
1248 | for (insns_skipped = 0; | |
1249 | !fail && !succeed && insns_skipped < MAX_INSNS_SKIPPED; | |
1250 | insns_skipped++) | |
1251 | { | |
1252 | rtx scanbody; | |
1253 | ||
1254 | this_insn = next_nonnote_insn (this_insn); | |
1255 | if (!this_insn) | |
1256 | break; | |
1257 | ||
1258 | scanbody = PATTERN (this_insn); | |
1259 | ||
1260 | switch (GET_CODE (this_insn)) | |
1261 | { | |
1262 | case CODE_LABEL: | |
1263 | /* Succeed if it is the target label, otherwise fail since | |
1264 | control falls in from somewhere else. */ | |
1265 | if (this_insn == label) | |
1266 | { | |
1267 | arm_ccfsm_state = 1; | |
1268 | succeed = TRUE; | |
1269 | } | |
1270 | else | |
1271 | fail = TRUE; | |
1272 | break; | |
1273 | ||
1274 | case BARRIER: /* XXX Is this case necessary? */ | |
1275 | /* Succeed if the following insn is the target label. | |
1276 | Otherwise fail. */ | |
1277 | this_insn = next_nonnote_insn (this_insn); | |
1278 | if (this_insn == label) | |
1279 | { | |
1280 | arm_ccfsm_state = 1; | |
1281 | succeed = TRUE; | |
1282 | } | |
1283 | else | |
1284 | fail = TRUE; | |
1285 | break; | |
1286 | ||
1287 | case JUMP_INSN: | |
1288 | /* If this is an unconditional branch to the same label, succeed. | |
1289 | If it is to another label, do nothing. If it is conditional, | |
1290 | fail. */ | |
1291 | /* XXX Probably, the test for the SET and the PC are unnecessary. */ | |
1292 | ||
1293 | if (GET_CODE (scanbody) == SET && GET_CODE (SET_DEST (scanbody)) == PC) | |
1294 | { | |
1295 | if (GET_CODE (SET_SRC (scanbody)) == LABEL_REF | |
1296 | && XEXP (SET_SRC (scanbody), 0) == label && !reverse) | |
1297 | { | |
1298 | arm_ccfsm_state = 2; | |
1299 | succeed = TRUE; | |
1300 | } | |
1301 | else if (GET_CODE (SET_SRC (scanbody)) == IF_THEN_ELSE) | |
1302 | fail = TRUE; | |
1303 | } | |
1304 | break; | |
1305 | ||
1306 | case INSN: | |
1307 | /* Instructions affecting the condition codes make it fail. */ | |
1308 | if (sets_cc0_p (scanbody)) | |
1309 | fail = TRUE; | |
1310 | break; | |
1311 | ||
1312 | default: | |
1313 | break; | |
1314 | } | |
1315 | } | |
1316 | if (succeed) | |
1317 | { | |
1318 | if (arm_ccfsm_state == 1 || reverse) | |
1319 | arm_target_label = CODE_LABEL_NUMBER (label); | |
1320 | else if (arm_ccfsm_state == 2) | |
1321 | arm_target_insn = this_insn; | |
1322 | else | |
1323 | abort (); | |
1324 | ||
1325 | /* If REVERSE is true, ARM_CURRENT_CC needs to be inverted from what | |
1326 | it was. */ | |
1327 | if (!reverse) | |
1328 | arm_current_cc = get_arm_condition_code (XEXP (SET_SRC (body), 0)); | |
1329 | if (reverse || then_not_else) | |
1330 | arm_current_cc = ARM_INVERSE_CONDITION_CODE (arm_current_cc); | |
1331 | } | |
1332 | } | |
1333 | } /* final_prescan_insn */ | |
1334 | ||
1335 | /* EOF */ |