Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
R
riscv-gcc-1
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
lvzhengyang
riscv-gcc-1
Commits
235d6e4f
Commit
235d6e4f
authored
Jan 23, 1992
by
Richard Stallman
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Initial revision
From-SVN: r233
parent
09da0d1c
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
2106 additions
and
0 deletions
+2106
-0
gcc/config/i860/i860.c
+2106
-0
No files found.
gcc/config/i860/i860.c
0 → 100644
View file @
235d6e4f
/* Subroutines for insn-output.c for Intel 860
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
Derived from sparc.c.
Written by Richard Stallman (rms@ai.mit.edu).
Hacked substantially by Ron Guilmette (rfg@ncd.com) to cater
to the whims of the System V Release 4 assembler.
This file is part of GNU CC.
GNU CC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
GNU CC is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU CC; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
#include "config.h"
#include "flags.h"
#include "rtl.h"
#include "regs.h"
#include "hard-reg-set.h"
#include "real.h"
#include "insn-config.h"
#include "conditions.h"
#include "insn-flags.h"
#include "output.h"
#include "recog.h"
#include "insn-attr.h"
#include <stdio.h>
static
rtx
find_addr_reg
();
#ifndef I860_REG_PREFIX
#define I860_REG_PREFIX ""
#endif
char
*
i860_reg_prefix
=
I860_REG_PREFIX
;
/* Save information from a "cmpxx" operation until the branch is emitted. */
rtx
i860_compare_op0
,
i860_compare_op1
;
/* Return non-zero if this pattern, can be evaluated safely, even if it
was not asked for. */
int
safe_insn_src_p
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
/* Just experimenting. */
/* No floating point src is safe if it contains an arithmetic
operation, since that operation may trap. */
switch
(
GET_CODE
(
op
))
{
case
CONST_INT
:
case
LABEL_REF
:
case
SYMBOL_REF
:
case
CONST
:
return
1
;
case
REG
:
return
1
;
case
MEM
:
return
CONSTANT_ADDRESS_P
(
XEXP
(
op
,
0
));
/* We never need to negate or complement constants. */
case
NEG
:
return
(
mode
!=
SFmode
&&
mode
!=
DFmode
);
case
NOT
:
case
ZERO_EXTEND
:
return
1
;
case
EQ
:
case
NE
:
case
LT
:
case
GT
:
case
LE
:
case
GE
:
case
LTU
:
case
GTU
:
case
LEU
:
case
GEU
:
case
MINUS
:
case
PLUS
:
return
(
mode
!=
SFmode
&&
mode
!=
DFmode
);
case
AND
:
case
IOR
:
case
XOR
:
case
LSHIFT
:
case
ASHIFT
:
case
ASHIFTRT
:
case
LSHIFTRT
:
if
((
GET_CODE
(
XEXP
(
op
,
0
))
==
CONST_INT
&&
!
SMALL_INT
(
XEXP
(
op
,
0
)))
||
(
GET_CODE
(
XEXP
(
op
,
1
))
==
CONST_INT
&&
!
SMALL_INT
(
XEXP
(
op
,
1
))))
return
0
;
return
1
;
default
:
return
0
;
}
}
/* Return 1 if REG is clobbered in IN.
Return 2 if REG is used in IN.
Return 3 if REG is both used and clobbered in IN.
Return 0 if neither. */
static
int
reg_clobbered_p
(
reg
,
in
)
rtx
reg
;
rtx
in
;
{
register
enum
rtx_code
code
;
if
(
in
==
0
)
return
0
;
code
=
GET_CODE
(
in
);
if
(
code
==
SET
||
code
==
CLOBBER
)
{
rtx
dest
=
SET_DEST
(
in
);
int
set
=
0
;
int
used
=
0
;
while
(
GET_CODE
(
dest
)
==
STRICT_LOW_PART
||
GET_CODE
(
dest
)
==
SUBREG
||
GET_CODE
(
dest
)
==
SIGN_EXTRACT
||
GET_CODE
(
dest
)
==
ZERO_EXTRACT
)
dest
=
XEXP
(
dest
,
0
);
if
(
dest
==
reg
)
set
=
1
;
else
if
(
GET_CODE
(
dest
)
==
REG
&&
refers_to_regno_p
(
REGNO
(
reg
),
REGNO
(
reg
)
+
HARD_REGNO_NREGS
(
reg
,
GET_MODE
(
reg
)),
SET_DEST
(
in
),
0
))
{
set
=
1
;
/* Anything that sets just part of the register
is considered using as well as setting it.
But note that a straight SUBREG of a single-word value
clobbers the entire value. */
if
(
dest
!=
SET_DEST
(
in
)
&&
!
(
GET_CODE
(
SET_DEST
(
in
))
==
SUBREG
||
UNITS_PER_WORD
>=
GET_MODE_SIZE
(
GET_MODE
(
dest
))))
used
=
1
;
}
if
(
code
==
SET
)
{
if
(
set
)
used
=
refers_to_regno_p
(
REGNO
(
reg
),
REGNO
(
reg
)
+
HARD_REGNO_NREGS
(
reg
,
GET_MODE
(
reg
)),
SET_SRC
(
in
),
0
);
else
used
=
refers_to_regno_p
(
REGNO
(
reg
),
REGNO
(
reg
)
+
HARD_REGNO_NREGS
(
reg
,
GET_MODE
(
reg
)),
in
,
0
);
}
return
set
+
used
*
2
;
}
if
(
refers_to_regno_p
(
REGNO
(
reg
),
REGNO
(
reg
)
+
HARD_REGNO_NREGS
(
reg
,
GET_MODE
(
reg
)),
in
,
0
))
return
2
;
return
0
;
}
/* Return non-zero if OP can be written to without screwing up
GCC's model of what's going on. It is assumed that this operand
appears in the dest position of a SET insn in a conditional
branch's delay slot. AFTER is the label to start looking from. */
int
operand_clobbered_before_used_after
(
op
,
after
)
rtx
op
;
rtx
after
;
{
/* Just experimenting. */
if
(
GET_CODE
(
op
)
==
CC0
)
return
1
;
if
(
GET_CODE
(
op
)
==
REG
)
{
rtx
insn
;
if
(
op
==
stack_pointer_rtx
)
return
0
;
/* Scan forward from the label, to see if the value of OP
is clobbered before the first use. */
for
(
insn
=
NEXT_INSN
(
after
);
insn
;
insn
=
NEXT_INSN
(
insn
))
{
if
(
GET_CODE
(
insn
)
==
NOTE
)
continue
;
if
(
GET_CODE
(
insn
)
==
INSN
||
GET_CODE
(
insn
)
==
JUMP_INSN
||
GET_CODE
(
insn
)
==
CALL_INSN
)
{
switch
(
reg_clobbered_p
(
op
,
PATTERN
(
insn
)))
{
default
:
return
0
;
case
1
:
return
1
;
case
0
:
break
;
}
}
/* If we reach another label without clobbering OP,
then we cannot safely write it here. */
else
if
(
GET_CODE
(
insn
)
==
CODE_LABEL
)
return
0
;
if
(
GET_CODE
(
insn
)
==
JUMP_INSN
)
{
if
(
condjump_p
(
insn
))
return
0
;
/* This is a jump insn which has already
been mangled. We can't tell what it does. */
if
(
GET_CODE
(
PATTERN
(
insn
))
==
PARALLEL
)
return
0
;
if
(
!
JUMP_LABEL
(
insn
))
return
0
;
/* Keep following jumps. */
insn
=
JUMP_LABEL
(
insn
);
}
}
return
1
;
}
/* In both of these cases, the first insn executed
for this op will be a orh whatever%h,%?r0,%?r31,
which is tolerable. */
if
(
GET_CODE
(
op
)
==
MEM
)
return
(
CONSTANT_ADDRESS_P
(
XEXP
(
op
,
0
)));
return
0
;
}
/* Return non-zero if this pattern, as a source to a "SET",
is known to yield an instruction of unit size. */
int
single_insn_src_p
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
switch
(
GET_CODE
(
op
))
{
case
CONST_INT
:
/* This is not always a single insn src, technically,
but output_delayed_branch knows how to deal with it. */
return
1
;
case
SYMBOL_REF
:
case
CONST
:
/* This is not a single insn src, technically,
but output_delayed_branch knows how to deal with it. */
return
1
;
case
REG
:
return
1
;
case
MEM
:
return
1
;
/* We never need to negate or complement constants. */
case
NEG
:
return
(
mode
!=
DFmode
);
case
NOT
:
case
ZERO_EXTEND
:
return
1
;
case
PLUS
:
case
MINUS
:
/* Detect cases that require multiple instructions. */
if
(
CONSTANT_P
(
XEXP
(
op
,
1
))
&&
!
(
GET_CODE
(
XEXP
(
op
,
1
))
==
CONST_INT
&&
SMALL_INT
(
XEXP
(
op
,
1
))))
return
0
;
case
EQ
:
case
NE
:
case
LT
:
case
GT
:
case
LE
:
case
GE
:
case
LTU
:
case
GTU
:
case
LEU
:
case
GEU
:
/* Not doing floating point, since they probably
take longer than the branch slot they might fill. */
return
(
mode
!=
SFmode
&&
mode
!=
DFmode
);
case
AND
:
if
(
GET_CODE
(
XEXP
(
op
,
1
))
==
NOT
)
{
rtx
arg
=
XEXP
(
XEXP
(
op
,
1
),
0
);
if
(
CONSTANT_P
(
arg
)
&&
!
(
GET_CODE
(
arg
)
==
CONST_INT
&&
(
SMALL_INT
(
arg
)
||
INTVAL
(
arg
)
&
0xffff
==
0
)))
return
0
;
}
case
IOR
:
case
XOR
:
/* Both small and round numbers take one instruction;
others take two. */
if
(
CONSTANT_P
(
XEXP
(
op
,
1
))
&&
!
(
GET_CODE
(
XEXP
(
op
,
1
))
==
CONST_INT
&&
(
SMALL_INT
(
XEXP
(
op
,
1
))
||
INTVAL
(
XEXP
(
op
,
1
))
&
0xffff
==
0
)))
return
0
;
case
LSHIFT
:
case
ASHIFT
:
case
ASHIFTRT
:
case
LSHIFTRT
:
return
1
;
case
SUBREG
:
if
(
SUBREG_WORD
(
op
)
!=
0
)
return
0
;
return
single_insn_src_p
(
SUBREG_REG
(
op
),
mode
);
/* Not doing floating point, since they probably
take longer than the branch slot they might fill. */
case
FLOAT_EXTEND
:
case
FLOAT_TRUNCATE
:
case
FLOAT
:
case
FIX
:
case
UNSIGNED_FLOAT
:
case
UNSIGNED_FIX
:
return
0
;
default
:
return
0
;
}
}
/* Nonzero only if this *really* is a single insn operand. */
int
strict_single_insn_op_p
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
if
(
mode
==
VOIDmode
)
mode
=
GET_MODE
(
op
);
switch
(
GET_CODE
(
op
))
{
case
CC0
:
return
1
;
case
CONST_INT
:
if
(
SMALL_INT
(
op
))
return
1
;
/* We can put this set insn into delay slot, because this is one
insn; `orh'. */
if
((
INTVAL
(
op
)
&
0xffff
)
==
0
)
return
1
;
return
0
;
case
SYMBOL_REF
:
return
0
;
case
REG
:
#if 0
/* This loses when moving an freg to a general reg. */
return HARD_REGNO_NREGS (REGNO (op), mode) == 1;
#endif
return
(
mode
!=
DFmode
&&
mode
!=
DImode
);
case
MEM
:
if
(
!
CONSTANT_ADDRESS_P
(
XEXP
(
op
,
0
)))
return
(
mode
!=
DFmode
&&
mode
!=
DImode
);
return
0
;
/* We never need to negate or complement constants. */
case
NEG
:
return
(
mode
!=
DFmode
);
case
NOT
:
case
ZERO_EXTEND
:
return
1
;
case
PLUS
:
case
MINUS
:
/* Detect cases that require multiple instructions. */
if
(
CONSTANT_P
(
XEXP
(
op
,
1
))
&&
!
(
GET_CODE
(
XEXP
(
op
,
1
))
==
CONST_INT
&&
SMALL_INT
(
XEXP
(
op
,
1
))))
return
0
;
case
EQ
:
case
NE
:
case
LT
:
case
GT
:
case
LE
:
case
GE
:
case
LTU
:
case
GTU
:
case
LEU
:
case
GEU
:
return
1
;
case
AND
:
if
(
GET_CODE
(
XEXP
(
op
,
1
))
==
NOT
)
{
rtx
arg
=
XEXP
(
XEXP
(
op
,
1
),
0
);
if
(
CONSTANT_P
(
arg
)
&&
!
(
GET_CODE
(
arg
)
==
CONST_INT
&&
(
SMALL_INT
(
arg
)
||
INTVAL
(
arg
)
&
0xffff
==
0
)))
return
0
;
}
case
IOR
:
case
XOR
:
/* Both small and round numbers take one instruction;
others take two. */
if
(
CONSTANT_P
(
XEXP
(
op
,
1
))
&&
!
(
GET_CODE
(
XEXP
(
op
,
1
))
==
CONST_INT
&&
(
SMALL_INT
(
XEXP
(
op
,
1
))
||
INTVAL
(
XEXP
(
op
,
1
))
&
0xffff
==
0
)))
return
0
;
case
LSHIFT
:
case
ASHIFT
:
case
ASHIFTRT
:
case
LSHIFTRT
:
return
1
;
case
SUBREG
:
if
(
SUBREG_WORD
(
op
)
!=
0
)
return
0
;
return
strict_single_insn_op_p
(
SUBREG_REG
(
op
),
mode
);
case
SIGN_EXTEND
:
if
(
GET_CODE
(
XEXP
(
op
,
0
))
==
MEM
&&
!
CONSTANT_ADDRESS_P
(
XEXP
(
XEXP
(
op
,
0
),
0
)))
return
1
;
return
0
;
/* Not doing floating point, since they probably
take longer than the branch slot they might fill. */
case
FLOAT_EXTEND
:
case
FLOAT_TRUNCATE
:
case
FLOAT
:
case
FIX
:
case
UNSIGNED_FLOAT
:
case
UNSIGNED_FIX
:
return
0
;
default
:
return
0
;
}
}
/* Return truth value of whether OP is a relational operator. */
int
relop
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
switch
(
GET_CODE
(
op
))
{
case
EQ
:
case
NE
:
case
GT
:
case
GE
:
case
LT
:
case
LE
:
case
GTU
:
case
GEU
:
case
LTU
:
case
LEU
:
return
1
;
}
return
0
;
}
/* Return non-zero only if OP is a register of mode MODE,
or const0_rtx. */
int
reg_or_0_operand
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
return
(
op
==
const0_rtx
||
register_operand
(
op
,
mode
)
||
op
==
CONST0_RTX
(
mode
));
}
/* Return truth value of whether OP can be used as an operands in a three
address add/subtract insn (such as add %o1,7,%l2) of mode MODE. */
int
arith_operand
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
return
(
register_operand
(
op
,
mode
)
||
(
GET_CODE
(
op
)
==
CONST_INT
&&
SMALL_INT
(
op
)));
}
/* Return 1 if OP is a valid first operand for a logical insn of mode MODE. */
int
logic_operand
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
return
(
register_operand
(
op
,
mode
)
||
(
GET_CODE
(
op
)
==
CONST_INT
&&
LOGIC_INT
(
op
)));
}
/* Return 1 if OP is a valid first operand for a shift insn of mode MODE. */
int
shift_operand
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
return
(
register_operand
(
op
,
mode
)
||
(
GET_CODE
(
op
)
==
CONST_INT
));
}
/* Return 1 if OP is a valid first operand for either a logical insn
or an add insn of mode MODE. */
int
compare_operand
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
return
(
register_operand
(
op
,
mode
)
||
(
GET_CODE
(
op
)
==
CONST_INT
&&
SMALL_INT
(
op
)
&&
LOGIC_INT
(
op
)));
}
/* Return truth value of whether OP can be used as the 5-bit immediate
operand of a bte or btne insn. */
int
bte_operand
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
return
(
register_operand
(
op
,
mode
)
||
(
GET_CODE
(
op
)
==
CONST_INT
&&
(
unsigned
)
INTVAL
(
op
)
<
0x20
));
}
/* Return 1 if OP is an indexed memory reference of mode MODE. */
int
indexed_operand
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
return
(
GET_CODE
(
op
)
==
MEM
&&
GET_MODE
(
op
)
==
mode
&&
GET_CODE
(
XEXP
(
op
,
0
))
==
PLUS
&&
GET_MODE
(
XEXP
(
op
,
0
))
==
SImode
&&
register_operand
(
XEXP
(
XEXP
(
op
,
0
),
0
),
SImode
)
&&
register_operand
(
XEXP
(
XEXP
(
op
,
0
),
1
),
SImode
));
}
/* Return 1 if OP is a suitable source operand for a load insn
with mode MODE. */
int
load_operand
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
return
(
memory_operand
(
op
,
mode
)
||
indexed_operand
(
op
,
mode
));
}
/* Return truth value of whether OP is a integer which fits the
range constraining immediate operands in add/subtract insns. */
int
small_int
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
return
(
GET_CODE
(
op
)
==
CONST_INT
&&
SMALL_INT
(
op
));
}
/* Return truth value of whether OP is a integer which fits the
range constraining immediate operands in logic insns. */
int
logic_int
(
op
,
mode
)
rtx
op
;
enum
machine_mode
mode
;
{
return
(
GET_CODE
(
op
)
==
CONST_INT
&&
LOGIC_INT
(
op
));
}
/* Return the best assembler insn template
for moving operands[1] into operands[0] as a fullword. */
static
char
*
singlemove_string
(
operands
)
rtx
*
operands
;
{
if
(
GET_CODE
(
operands
[
0
])
==
MEM
)
{
if
(
GET_CODE
(
operands
[
1
])
!=
MEM
)
if
(
CONSTANT_ADDRESS_P
(
XEXP
(
operands
[
0
],
0
)))
{
if
(
!
((
cc_prev_status
.
flags
&
CC_KNOW_HI_R31
)
&&
(
cc_prev_status
.
flags
&
CC_HI_R31_ADJ
)
&&
cc_prev_status
.
mdep
==
XEXP
(
operands
[
0
],
0
)))
{
CC_STATUS_INIT
;
output_asm_insn
(
"orh %h0,%?r0,%?r31"
,
operands
);
}
cc_status
.
flags
|=
CC_KNOW_HI_R31
|
CC_HI_R31_ADJ
;
cc_status
.
mdep
=
XEXP
(
operands
[
0
],
0
);
return
"st.l %r1,%L0(%?r31)"
;
}
else
return
"st.l %r1,%0"
;
else
abort
();
#if 0
{
rtx xoperands[2];
cc_status.flags &= ~CC_F0_IS_0;
xoperands[0] = gen_rtx (REG, SFmode, 32);
xoperands[1] = operands[1];
output_asm_insn (singlemove_string (xoperands), xoperands);
xoperands[1] = xoperands[0];
xoperands[0] = operands[0];
output_asm_insn (singlemove_string (xoperands), xoperands);
return "";
}
#endif
}
if
(
GET_CODE
(
operands
[
1
])
==
MEM
)
{
if
(
CONSTANT_ADDRESS_P
(
XEXP
(
operands
[
1
],
0
)))
{
if
(
!
((
cc_prev_status
.
flags
&
CC_KNOW_HI_R31
)
&&
(
cc_prev_status
.
flags
&
CC_HI_R31_ADJ
)
&&
cc_prev_status
.
mdep
==
XEXP
(
operands
[
1
],
0
)))
{
CC_STATUS_INIT
;
output_asm_insn
(
"orh %h1,%?r0,%?r31"
,
operands
);
}
cc_status
.
flags
|=
CC_KNOW_HI_R31
|
CC_HI_R31_ADJ
;
cc_status
.
mdep
=
XEXP
(
operands
[
1
],
0
);
return
"ld.l %L1(%?r31),%0"
;
}
return
"ld.l %m1,%0"
;
}
if
(
GET_CODE
(
operands
[
1
])
==
CONST_INT
)
{
if
((
INTVAL
(
operands
[
1
])
&
0xffff0000
)
==
0
)
return
"or %L1,%?r0,%0"
;
if
((
INTVAL
(
operands
[
1
])
&
0x0000ffff
)
==
0
)
return
"orh %H1,%?r0,%0"
;
if
(
operands
[
1
]
==
const0_rtx
)
return
"mov %?r0,%0"
;
}
return
"mov %1,%0"
;
}
/* Output assembler code to perform a doubleword move insn
with operands OPERANDS. */
char
*
output_move_double
(
operands
)
rtx
*
operands
;
{
enum
{
REGOP
,
OFFSOP
,
MEMOP
,
PUSHOP
,
POPOP
,
CNSTOP
,
RNDOP
}
optype0
,
optype1
;
rtx
latehalf
[
2
];
rtx
addreg0
=
0
,
addreg1
=
0
;
/* First classify both operands. */
if
(
REG_P
(
operands
[
0
]))
optype0
=
REGOP
;
else
if
(
offsettable_memref_p
(
operands
[
0
]))
optype0
=
OFFSOP
;
else
if
(
GET_CODE
(
operands
[
0
])
==
MEM
)
optype0
=
MEMOP
;
else
optype0
=
RNDOP
;
if
(
REG_P
(
operands
[
1
]))
optype1
=
REGOP
;
else
if
(
CONSTANT_P
(
operands
[
1
]))
optype1
=
CNSTOP
;
else
if
(
offsettable_memref_p
(
operands
[
1
]))
optype1
=
OFFSOP
;
else
if
(
GET_CODE
(
operands
[
1
])
==
MEM
)
optype1
=
MEMOP
;
else
optype1
=
RNDOP
;
/* Check for the cases that the operand constraints are not
supposed to allow to happen. Abort if we get one,
because generating code for these cases is painful. */
if
(
optype0
==
RNDOP
||
optype1
==
RNDOP
)
abort
();
/* If an operand is an unoffsettable memory ref, find a register
we can increment temporarily to make it refer to the second word. */
if
(
optype0
==
MEMOP
)
addreg0
=
find_addr_reg
(
XEXP
(
operands
[
0
],
0
));
if
(
optype1
==
MEMOP
)
addreg1
=
find_addr_reg
(
XEXP
(
operands
[
1
],
0
));
/* ??? Perhaps in some cases move double words
if there is a spare pair of floating regs. */
/* Ok, we can do one word at a time.
Normally we do the low-numbered word first,
but if either operand is autodecrementing then we
do the high-numbered word first.
In either case, set up in LATEHALF the operands to use
for the high-numbered word and in some cases alter the
operands in OPERANDS to be suitable for the low-numbered word. */
if
(
optype0
==
REGOP
)
latehalf
[
0
]
=
gen_rtx
(
REG
,
SImode
,
REGNO
(
operands
[
0
])
+
1
);
else
if
(
optype0
==
OFFSOP
)
latehalf
[
0
]
=
adj_offsettable_operand
(
operands
[
0
],
4
);
else
latehalf
[
0
]
=
operands
[
0
];
if
(
optype1
==
REGOP
)
latehalf
[
1
]
=
gen_rtx
(
REG
,
SImode
,
REGNO
(
operands
[
1
])
+
1
);
else
if
(
optype1
==
OFFSOP
)
latehalf
[
1
]
=
adj_offsettable_operand
(
operands
[
1
],
4
);
else
if
(
optype1
==
CNSTOP
)
{
if
(
GET_CODE
(
operands
[
1
])
==
CONST_DOUBLE
)
split_double
(
operands
[
1
],
&
operands
[
1
],
&
latehalf
[
1
]);
else
if
(
CONSTANT_P
(
operands
[
1
]))
latehalf
[
1
]
=
const0_rtx
;
}
else
latehalf
[
1
]
=
operands
[
1
];
/* If the first move would clobber the source of the second one,
do them in the other order.
RMS says "This happens only for registers;
such overlap can't happen in memory unless the user explicitly
sets it up, and that is an undefined circumstance."
but it happens on the sparc when loading parameter registers,
so I am going to define that circumstance, and make it work
as expected. */
if
(
optype0
==
REGOP
&&
optype1
==
REGOP
&&
REGNO
(
operands
[
0
])
==
REGNO
(
latehalf
[
1
]))
{
CC_STATUS_PARTIAL_INIT
;
/* Make any unoffsettable addresses point at high-numbered word. */
if
(
addreg0
)
output_asm_insn
(
"adds 0x4,%0,%0"
,
&
addreg0
);
if
(
addreg1
)
output_asm_insn
(
"adds 0x4,%0,%0"
,
&
addreg1
);
/* Do that word. */
output_asm_insn
(
singlemove_string
(
latehalf
),
latehalf
);
/* Undo the adds we just did. */
if
(
addreg0
)
output_asm_insn
(
"adds -0x4,%0,%0"
,
&
addreg0
);
if
(
addreg1
)
output_asm_insn
(
"adds -0x4,%0,%0"
,
&
addreg1
);
/* Do low-numbered word. */
return
singlemove_string
(
operands
);
}
else
if
(
optype0
==
REGOP
&&
optype1
!=
REGOP
&&
reg_overlap_mentioned_p
(
operands
[
0
],
operands
[
1
]))
{
/* Do the late half first. */
output_asm_insn
(
singlemove_string
(
latehalf
),
latehalf
);
/* Then clobber. */
return
singlemove_string
(
operands
);
}
/* Normal case: do the two words, low-numbered first. */
output_asm_insn
(
singlemove_string
(
operands
),
operands
);
CC_STATUS_PARTIAL_INIT
;
/* Make any unoffsettable addresses point at high-numbered word. */
if
(
addreg0
)
output_asm_insn
(
"adds 0x4,%0,%0"
,
&
addreg0
);
if
(
addreg1
)
output_asm_insn
(
"adds 0x4,%0,%0"
,
&
addreg1
);
/* Do that word. */
output_asm_insn
(
singlemove_string
(
latehalf
),
latehalf
);
/* Undo the adds we just did. */
if
(
addreg0
)
output_asm_insn
(
"adds -0x4,%0,%0"
,
&
addreg0
);
if
(
addreg1
)
output_asm_insn
(
"adds -0x4,%0,%0"
,
&
addreg1
);
return
""
;
}
char
*
output_fp_move_double
(
operands
)
rtx
*
operands
;
{
/* If the source operand is any sort of zero, use f0 instead. */
if
(
operands
[
1
]
==
CONST0_RTX
(
GET_MODE
(
operands
[
1
])))
operands
[
1
]
=
gen_rtx
(
REG
,
DFmode
,
F0_REGNUM
);
if
(
FP_REG_P
(
operands
[
0
]))
{
if
(
FP_REG_P
(
operands
[
1
]))
return
"fmov.dd %1,%0"
;
if
(
GET_CODE
(
operands
[
1
])
==
REG
)
{
output_asm_insn
(
"ixfr %1,%0"
,
operands
);
operands
[
0
]
=
gen_rtx
(
REG
,
VOIDmode
,
REGNO
(
operands
[
0
])
+
1
);
operands
[
1
]
=
gen_rtx
(
REG
,
VOIDmode
,
REGNO
(
operands
[
1
])
+
1
);
return
"ixfr %1,%0"
;
}
if
(
operands
[
1
]
==
CONST0_RTX
(
DFmode
))
return
"fmov.dd f0,%0"
;
if
(
CONSTANT_ADDRESS_P
(
XEXP
(
operands
[
1
],
0
)))
{
if
(
!
((
cc_prev_status
.
flags
&
CC_KNOW_HI_R31
)
&&
(
cc_prev_status
.
flags
&
CC_HI_R31_ADJ
)
&&
cc_prev_status
.
mdep
==
XEXP
(
operands
[
1
],
0
)))
{
CC_STATUS_INIT
;
output_asm_insn
(
"orh %h1,%?r0,%?r31"
,
operands
);
}
cc_status
.
flags
|=
CC_KNOW_HI_R31
|
CC_HI_R31_ADJ
;
cc_status
.
mdep
=
XEXP
(
operands
[
1
],
0
);
return
"fld.d %L1(%?r31),%0"
;
}
return
"fld.d %1,%0"
;
}
else
if
(
FP_REG_P
(
operands
[
1
]))
{
if
(
GET_CODE
(
operands
[
0
])
==
REG
)
{
output_asm_insn
(
"fxfr %1,%0"
,
operands
);
operands
[
0
]
=
gen_rtx
(
REG
,
VOIDmode
,
REGNO
(
operands
[
0
])
+
1
);
operands
[
1
]
=
gen_rtx
(
REG
,
VOIDmode
,
REGNO
(
operands
[
1
])
+
1
);
return
"fxfr %1,%0"
;
}
if
(
CONSTANT_ADDRESS_P
(
XEXP
(
operands
[
0
],
0
)))
{
if
(
!
((
cc_prev_status
.
flags
&
CC_KNOW_HI_R31
)
&&
(
cc_prev_status
.
flags
&
CC_HI_R31_ADJ
)
&&
cc_prev_status
.
mdep
==
XEXP
(
operands
[
0
],
0
)))
{
CC_STATUS_INIT
;
output_asm_insn
(
"orh %h0,%?r0,%?r31"
,
operands
);
}
cc_status
.
flags
|=
CC_KNOW_HI_R31
|
CC_HI_R31_ADJ
;
cc_status
.
mdep
=
XEXP
(
operands
[
0
],
0
);
return
"fst.d %1,%L0(%?r31)"
;
}
return
"fst.d %1,%0"
;
}
else
abort
();
/* NOTREACHED */
return
NULL
;
}
/* Return a REG that occurs in ADDR with coefficient 1.
ADDR can be effectively incremented by incrementing REG. */
static
rtx
find_addr_reg
(
addr
)
rtx
addr
;
{
while
(
GET_CODE
(
addr
)
==
PLUS
)
{
if
(
GET_CODE
(
XEXP
(
addr
,
0
))
==
REG
)
addr
=
XEXP
(
addr
,
0
);
else
if
(
GET_CODE
(
XEXP
(
addr
,
1
))
==
REG
)
addr
=
XEXP
(
addr
,
1
);
else
if
(
CONSTANT_P
(
XEXP
(
addr
,
0
)))
addr
=
XEXP
(
addr
,
1
);
else
if
(
CONSTANT_P
(
XEXP
(
addr
,
1
)))
addr
=
XEXP
(
addr
,
0
);
else
abort
();
}
if
(
GET_CODE
(
addr
)
==
REG
)
return
addr
;
abort
();
/* NOTREACHED */
return
NULL
;
}
/* Return a template for a load instruction with mode MODE and
arguments from the string ARGS.
This string is in static storage. */
static
char
*
load_opcode
(
mode
,
args
,
reg
)
enum
machine_mode
mode
;
char
*
args
;
rtx
reg
;
{
static
char
buf
[
30
];
char
*
opcode
;
switch
(
mode
)
{
case
QImode
:
opcode
=
"ld.b"
;
break
;
case
HImode
:
opcode
=
"ld.s"
;
break
;
case
SImode
:
case
SFmode
:
if
(
FP_REG_P
(
reg
))
opcode
=
"fld.l"
;
else
opcode
=
"ld.l"
;
break
;
case
DImode
:
if
(
!
FP_REG_P
(
reg
))
abort
();
case
DFmode
:
opcode
=
"fld.d"
;
break
;
default
:
abort
();
}
sprintf
(
buf
,
"%s %s"
,
opcode
,
args
);
return
buf
;
}
/* Return a template for a store instruction with mode MODE and
arguments from the string ARGS.
This string is in static storage. */
static
char
*
store_opcode
(
mode
,
args
,
reg
)
enum
machine_mode
mode
;
char
*
args
;
rtx
reg
;
{
static
char
buf
[
30
];
char
*
opcode
;
switch
(
mode
)
{
case
QImode
:
opcode
=
"st.b"
;
break
;
case
HImode
:
opcode
=
"st.s"
;
break
;
case
SImode
:
case
SFmode
:
if
(
FP_REG_P
(
reg
))
opcode
=
"fst.l"
;
else
opcode
=
"st.l"
;
break
;
case
DImode
:
if
(
!
FP_REG_P
(
reg
))
abort
();
case
DFmode
:
opcode
=
"fst.d"
;
break
;
default
:
abort
();
}
sprintf
(
buf
,
"%s %s"
,
opcode
,
args
);
return
buf
;
}
/* Output a store-in-memory whose operands are OPERANDS[0,1].
OPERANDS[0] is a MEM, and OPERANDS[1] is a reg or zero.
This function returns a template for an insn.
This is in static storage.
It may also output some insns directly.
It may alter the values of operands[0] and operands[1]. */
char
*
output_store
(
operands
)
rtx
*
operands
;
{
enum
machine_mode
mode
=
GET_MODE
(
operands
[
0
]);
rtx
address
=
XEXP
(
operands
[
0
],
0
);
char
*
string
;
cc_status
.
flags
|=
CC_KNOW_HI_R31
|
CC_HI_R31_ADJ
;
cc_status
.
mdep
=
address
;
if
(
!
((
cc_prev_status
.
flags
&
CC_KNOW_HI_R31
)
&&
(
cc_prev_status
.
flags
&
CC_HI_R31_ADJ
)
&&
address
==
cc_prev_status
.
mdep
))
{
CC_STATUS_INIT
;
output_asm_insn
(
"orh %h0,%?r0,%?r31"
,
operands
);
cc_prev_status
.
mdep
=
address
;
}
/* Store zero in two parts when appropriate. */
if
(
mode
==
DFmode
&&
operands
[
1
]
==
CONST0_RTX
(
DFmode
))
return
store_opcode
(
DFmode
,
"%r1,%L0(%?r31)"
,
operands
[
1
]);
/* Code below isn't smart enough to move a doubleword in two parts,
so use output_move_double to do that in the cases that require it. */
if
((
mode
==
DImode
||
mode
==
DFmode
)
&&
!
FP_REG_P
(
operands
[
1
]))
return
output_move_double
(
operands
);
return
store_opcode
(
mode
,
"%r1,%L0(%?r31)"
,
operands
[
1
]);
}
/* Output a load-from-memory whose operands are OPERANDS[0,1].
OPERANDS[0] is a reg, and OPERANDS[1] is a mem.
This function returns a template for an insn.
This is in static storage.
It may also output some insns directly.
It may alter the values of operands[0] and operands[1]. */
char
*
output_load
(
operands
)
rtx
*
operands
;
{
enum
machine_mode
mode
=
GET_MODE
(
operands
[
0
]);
rtx
address
=
XEXP
(
operands
[
1
],
0
);
/* We don't bother trying to see if we know %hi(address).
This is because we are doing a load, and if we know the
%hi value, we probably also know that value in memory. */
cc_status
.
flags
|=
CC_KNOW_HI_R31
|
CC_HI_R31_ADJ
;
cc_status
.
mdep
=
address
;
if
(
!
((
cc_prev_status
.
flags
&
CC_KNOW_HI_R31
)
&&
(
cc_prev_status
.
flags
&
CC_HI_R31_ADJ
)
&&
address
==
cc_prev_status
.
mdep
&&
cc_prev_status
.
mdep
==
cc_status
.
mdep
))
{
CC_STATUS_INIT
;
output_asm_insn
(
"orh %h1,%?r0,%?r31"
,
operands
);
cc_prev_status
.
mdep
=
address
;
}
/* Code below isn't smart enough to move a doubleword in two parts,
so use output_move_double to do that in the cases that require it. */
if
((
mode
==
DImode
||
mode
==
DFmode
)
&&
!
FP_REG_P
(
operands
[
0
]))
return
output_move_double
(
operands
);
return
load_opcode
(
mode
,
"%L1(%?r31),%0"
,
operands
[
0
]);
}
#if 0
/* Load the address specified by OPERANDS[3] into the register
specified by OPERANDS[0].
OPERANDS[3] may be the result of a sum, hence it could either be:
(1) CONST
(2) REG
(2) REG + CONST_INT
(3) REG + REG + CONST_INT
(4) REG + REG (special case of 3).
Note that (3) is not a legitimate address.
All cases are handled here. */
void
output_load_address (operands)
rtx *operands;
{
rtx base, offset;
if (CONSTANT_P (operands[3]))
{
output_asm_insn ("mov %3,%0", operands);
return;
}
if (REG_P (operands[3]))
{
if (REGNO (operands[0]) != REGNO (operands[3]))
output_asm_insn ("shl %?r0,%3,%0", operands);
return;
}
if (GET_CODE (operands[3]) != PLUS)
abort ();
base = XEXP (operands[3], 0);
offset = XEXP (operands[3], 1);
if (GET_CODE (base) == CONST_INT)
{
rtx tmp = base;
base = offset;
offset = tmp;
}
if (GET_CODE (offset) != CONST_INT)
{
/* Operand is (PLUS (REG) (REG)). */
base = operands[3];
offset = const0_rtx;
}
if (REG_P (base))
{
operands[6] = base;
operands[7] = offset;
CC_STATUS_PARTIAL_INIT;
if (SMALL_INT (offset))
output_asm_insn ("adds %7,%6,%0", operands);
else
output_asm_insn ("mov %7,%0\n\tadds %0,%6,%0", operands);
}
else if (GET_CODE (base) == PLUS)
{
operands[6] = XEXP (base, 0);
operands[7] = XEXP (base, 1);
operands[8] = offset;
CC_STATUS_PARTIAL_INIT;
if (SMALL_INT (offset))
output_asm_insn ("adds %6,%7,%0\n\tadds %8,%0,%0", operands);
else
output_asm_insn ("mov %8,%0\n\tadds %0,%6,%0\n\tadds %0,%7,%0", operands);
}
else
abort ();
}
#endif
/* Output code to place a size count SIZE in register REG.
Because block moves are pipelined, we don't include the
first element in the transfer of SIZE to REG.
For this, we subtract ALIGN. (Actually, I think it is not
right to subtract on this machine, so right now we don't.) */
static
void
output_size_for_block_move
(
size
,
reg
,
align
)
rtx
size
,
reg
,
align
;
{
rtx
xoperands
[
3
];
xoperands
[
0
]
=
reg
;
xoperands
[
1
]
=
size
;
xoperands
[
2
]
=
align
;
#if 1
cc_status
.
flags
&=
~
CC_KNOW_HI_R31
;
output_asm_insn
(
"mov %1,%0"
,
xoperands
);
#else
if
(
GET_CODE
(
size
)
==
REG
)
output_asm_insn
(
"sub %2,%1,%0"
,
xoperands
);
else
{
xoperands
[
1
]
=
gen_rtx
(
CONST_INT
,
VOIDmode
,
INTVAL
(
size
)
-
INTVAL
(
align
));
cc_status
.
flags
&=
~
CC_KNOW_HI_R31
;
output_asm_insn
(
"mov %1,%0"
,
xoperands
);
}
#endif
}
/* Emit code to perform a block move.
OPERANDS[0] is the destination.
OPERANDS[1] is the source.
OPERANDS[2] is the size.
OPERANDS[3] is the known safe alignment.
OPERANDS[4..6] are pseudos we can safely clobber as temps. */
char
*
output_block_move
(
operands
)
rtx
*
operands
;
{
/* A vector for our computed operands. Note that load_output_address
makes use of (and can clobber) up to the 8th element of this vector. */
rtx
xoperands
[
10
];
rtx
zoperands
[
10
];
static
int
movstrsi_label
=
0
;
int
i
,
j
;
rtx
temp1
=
operands
[
4
];
rtx
alignrtx
=
operands
[
3
];
int
align
=
INTVAL
(
alignrtx
);
int
chunk_size
;
xoperands
[
0
]
=
operands
[
0
];
xoperands
[
1
]
=
operands
[
1
];
xoperands
[
2
]
=
temp1
;
/* We can't move more than four bytes at a time
because we have only one register to move them through. */
if
(
align
>
4
)
{
align
=
4
;
alignrtx
=
gen_rtx
(
CONST_INT
,
VOIDmode
,
4
);
}
/* Recognize special cases of block moves. These occur
when GNU C++ is forced to treat something as BLKmode
to keep it in memory, when its mode could be represented
with something smaller.
We cannot do this for global variables, since we don't know
what pages they don't cross. Sigh. */
if
(
GET_CODE
(
operands
[
2
])
==
CONST_INT
&&
!
CONSTANT_ADDRESS_P
(
operands
[
0
])
&&
!
CONSTANT_ADDRESS_P
(
operands
[
1
]))
{
int
size
=
INTVAL
(
operands
[
2
]);
rtx
op0
=
xoperands
[
0
];
rtx
op1
=
xoperands
[
1
];
if
((
align
&
3
)
==
0
&&
(
size
&
3
)
==
0
&&
(
size
>>
2
)
<=
16
)
{
if
(
memory_address_p
(
SImode
,
plus_constant
(
op0
,
size
))
&&
memory_address_p
(
SImode
,
plus_constant
(
op1
,
size
)))
{
cc_status
.
flags
&=
~
CC_KNOW_HI_R31
;
for
(
i
=
(
size
>>
2
)
-
1
;
i
>=
0
;
i
--
)
{
xoperands
[
0
]
=
plus_constant
(
op0
,
i
*
4
);
xoperands
[
1
]
=
plus_constant
(
op1
,
i
*
4
);
output_asm_insn
(
"ld.l %a1,%?r31
\n\t
st.l %?r31,%a0"
,
xoperands
);
}
return
""
;
}
}
else
if
((
align
&
1
)
==
0
&&
(
size
&
1
)
==
0
&&
(
size
>>
1
)
<=
16
)
{
if
(
memory_address_p
(
HImode
,
plus_constant
(
op0
,
size
))
&&
memory_address_p
(
HImode
,
plus_constant
(
op1
,
size
)))
{
cc_status
.
flags
&=
~
CC_KNOW_HI_R31
;
for
(
i
=
(
size
>>
1
)
-
1
;
i
>=
0
;
i
--
)
{
xoperands
[
0
]
=
plus_constant
(
op0
,
i
*
2
);
xoperands
[
1
]
=
plus_constant
(
op1
,
i
*
2
);
output_asm_insn
(
"ld.s %a1,%?r31
\n\t
st.s %?r31,%a0"
,
xoperands
);
}
return
""
;
}
}
else
if
(
size
<=
16
)
{
if
(
memory_address_p
(
QImode
,
plus_constant
(
op0
,
size
))
&&
memory_address_p
(
QImode
,
plus_constant
(
op1
,
size
)))
{
cc_status
.
flags
&=
~
CC_KNOW_HI_R31
;
for
(
i
=
size
-
1
;
i
>=
0
;
i
--
)
{
xoperands
[
0
]
=
plus_constant
(
op0
,
i
);
xoperands
[
1
]
=
plus_constant
(
op1
,
i
);
output_asm_insn
(
"ld.b %a1,%?r31
\n\t
st.b %?r31,%a0"
,
xoperands
);
}
return
""
;
}
}
}
/* Since we clobber untold things, nix the condition codes. */
CC_STATUS_INIT
;
/* This is the size of the transfer.
Either use the register which already contains the size,
or use a free register (used by no operands). */
output_size_for_block_move
(
operands
[
2
],
operands
[
4
],
alignrtx
);
#if 0
/* Also emit code to decrement the size value by ALIGN. */
zoperands[0] = operands[0];
zoperands[3] = plus_constant (operands[0], align);
output_load_address (zoperands);
#endif
/* Generate number for unique label. */
xoperands
[
3
]
=
gen_rtx
(
CONST_INT
,
VOIDmode
,
movstrsi_label
++
);
/* Calculate the size of the chunks we will be trying to move first. */
#if 0
if ((align & 3) == 0)
chunk_size = 4;
else if ((align & 1) == 0)
chunk_size = 2;
else
#endif
chunk_size
=
1
;
/* Copy the increment (negative) to a register for bla insn. */
xoperands
[
4
]
=
gen_rtx
(
CONST_INT
,
VOIDmode
,
-
chunk_size
);
xoperands
[
5
]
=
operands
[
5
];
output_asm_insn
(
"adds %4,%?r0,%5"
,
xoperands
);
/* Predecrement the loop counter. This happens again also in the `bla'
instruction which precceds the loop, but we need to have it done
two times before we enter the loop because of the bizzare semantics
of the bla instruction. */
output_asm_insn
(
"adds %5,%2,%2"
,
xoperands
);
/* Check for the case where the original count was less than or equal to
zero. Avoid going through the loop at all if the original count was
indeed less than or equal to zero. Note that we treat the count as
if it were a signed 32-bit quantity here, rather than an unsigned one,
even though we really shouldn't. We have to do this because of the
semantics of the `ble' instruction, which assume that the count is
a signed 32-bit value. Anyway, in practice it won't matter because
nobody is going to try to do a memcpy() of more than half of the
entire address space (i.e. 2 gigabytes) anyway. */
output_asm_insn
(
"bc .Le%3"
,
xoperands
);
/* Make available a register which is a temporary. */
xoperands
[
6
]
=
operands
[
6
];
/* Now the actual loop.
In xoperands, elements 1 and 0 are the input and output vectors.
Element 2 is the loop index. Element 5 is the increment. */
output_asm_insn
(
"subs %1,%5,%1"
,
xoperands
);
output_asm_insn
(
"bla %5,%2,.Lm%3"
,
xoperands
);
output_asm_insn
(
"adds %0,%2,%6"
,
xoperands
);
output_asm_insn
(
"
\n
.Lm%3:"
,
xoperands
);
/* Label for bla above. */
output_asm_insn
(
"
\n
.Ls%3:"
,
xoperands
);
/* Loop start label. */
output_asm_insn
(
"adds %5,%6,%6"
,
xoperands
);
/* NOTE: The code here which is supposed to handle the cases where the
sources and destinations are known to start on a 4 or 2 byte boundary
are currently broken. They fail to do anything about the overflow
bytes which might still need to be copied even after we have copied
some number of words or halfwords. Thus, for now we use the lowest
common denominator, i.e. the code which just copies some number of
totally unaligned individual bytes. (See the calculation of
chunk_size above. */
if
(
chunk_size
==
4
)
{
output_asm_insn
(
"ld.l %2(%1),%?r31"
,
xoperands
);
output_asm_insn
(
"bla %5,%2,.Ls%3"
,
xoperands
);
output_asm_insn
(
"st.l %?r31,8(%6)"
,
xoperands
);
}
else
if
(
chunk_size
==
2
)
{
output_asm_insn
(
"ld.s %2(%1),%?r31"
,
xoperands
);
output_asm_insn
(
"bla %5,%2,.Ls%3"
,
xoperands
);
output_asm_insn
(
"st.s %?r31,4(%6)"
,
xoperands
);
}
else
/* chunk_size == 1 */
{
output_asm_insn
(
"ld.b %2(%1),%?r31"
,
xoperands
);
output_asm_insn
(
"bla %5,%2,.Ls%3"
,
xoperands
);
output_asm_insn
(
"st.b %?r31,2(%6)"
,
xoperands
);
}
output_asm_insn
(
"
\n
.Le%3:"
,
xoperands
);
/* Here if count <= 0. */
return
""
;
}
/* Output a delayed branch insn with the delay insn in its
branch slot. The delayed branch insn template is in TEMPLATE,
with operands OPERANDS. The insn in its delay slot is INSN.
As a special case, since we know that all memory transfers are via
ld/st insns, if we see a (MEM (SYMBOL_REF ...)) we divide the memory
reference around the branch as
orh ha%x,%?r0,%?r31
b ...
ld/st l%x(%?r31),...
As another special case, we handle loading (SYMBOL_REF ...) and
other large constants around branches as well:
orh h%x,%?r0,%0
b ...
or l%x,%0,%1
*/
char
*
output_delayed_branch
(
template
,
operands
,
insn
)
char
*
template
;
rtx
*
operands
;
rtx
insn
;
{
rtx
src
=
XVECEXP
(
PATTERN
(
insn
),
0
,
1
);
rtx
dest
=
XVECEXP
(
PATTERN
(
insn
),
0
,
0
);
/* See if we are doing some branch together with setting some register
to some 32-bit value which does (or may) have some of the high-order
16 bits set. If so, we need to set the register in two stages. One
stage must be done before the branch, and the other one can be done
in the delay slot. */
if
(
(
GET_CODE
(
src
)
==
CONST_INT
&&
((
unsigned
)
INTVAL
(
src
)
&
(
unsigned
)
0xffff0000
)
!=
(
unsigned
)
0
)
||
(
GET_CODE
(
src
)
==
SYMBOL_REF
)
||
(
GET_CODE
(
src
)
==
LABEL_REF
)
||
(
GET_CODE
(
src
)
==
CONST
))
{
rtx
xoperands
[
2
];
xoperands
[
0
]
=
dest
;
xoperands
[
1
]
=
src
;
CC_STATUS_PARTIAL_INIT
;
/* Output the `orh' insn. */
output_asm_insn
(
"orh %H1,%?r0,%0"
,
xoperands
);
/* Output the branch instruction next. */
output_asm_insn
(
template
,
operands
);
/* Now output the `or' insn. */
output_asm_insn
(
"or %L1,%0,%0"
,
xoperands
);
}
else
if
((
GET_CODE
(
src
)
==
MEM
&&
CONSTANT_ADDRESS_P
(
XEXP
(
src
,
0
)))
||
(
GET_CODE
(
dest
)
==
MEM
&&
CONSTANT_ADDRESS_P
(
XEXP
(
dest
,
0
))))
{
rtx
xoperands
[
2
];
char
*
split_template
;
xoperands
[
0
]
=
dest
;
xoperands
[
1
]
=
src
;
/* Output the `orh' insn. */
if
(
GET_CODE
(
src
)
==
MEM
)
{
if
(
!
((
cc_prev_status
.
flags
&
CC_KNOW_HI_R31
)
&&
(
cc_prev_status
.
flags
&
CC_HI_R31_ADJ
)
&&
cc_prev_status
.
mdep
==
XEXP
(
operands
[
1
],
0
)))
{
CC_STATUS_INIT
;
output_asm_insn
(
"orh %h1,%?r0,%?r31"
,
xoperands
);
}
split_template
=
load_opcode
(
GET_MODE
(
dest
),
"%L1(%?r31),%0"
,
dest
);
}
else
{
if
(
!
((
cc_prev_status
.
flags
&
CC_KNOW_HI_R31
)
&&
(
cc_prev_status
.
flags
&
CC_HI_R31_ADJ
)
&&
cc_prev_status
.
mdep
==
XEXP
(
operands
[
0
],
0
)))
{
CC_STATUS_INIT
;
output_asm_insn
(
"orh %h0,%?r0,%?r31"
,
xoperands
);
}
split_template
=
store_opcode
(
GET_MODE
(
dest
),
"%r1,%L0(%?r31)"
,
src
);
}
/* Output the branch instruction next. */
output_asm_insn
(
template
,
operands
);
/* Now output the load or store.
No need to do a CC_STATUS_INIT, because we are branching anyway. */
output_asm_insn
(
split_template
,
xoperands
);
}
else
{
int
insn_code_number
;
rtx
pat
=
gen_rtx
(
SET
,
VOIDmode
,
dest
,
src
);
rtx
delay_insn
=
gen_rtx
(
INSN
,
VOIDmode
,
0
,
0
,
0
,
pat
,
-
1
,
0
,
0
);
int
i
;
/* Output the branch instruction first. */
output_asm_insn
(
template
,
operands
);
/* Now recognize the insn which we put in its delay slot.
We must do this after outputing the branch insn,
since operands may just be a pointer to `recog_operand'. */
INSN_CODE
(
delay_insn
)
=
insn_code_number
=
recog
(
pat
,
delay_insn
);
if
(
insn_code_number
==
-
1
)
abort
();
for
(
i
=
0
;
i
<
insn_n_operands
[
insn_code_number
];
i
++
)
{
if
(
GET_CODE
(
recog_operand
[
i
])
==
SUBREG
)
recog_operand
[
i
]
=
alter_subreg
(
recog_operand
[
i
]);
}
insn_extract
(
delay_insn
);
if
(
!
constrain_operands
(
insn_code_number
,
1
))
fatal_insn_not_found
(
delay_insn
);
template
=
insn_template
[
insn_code_number
];
if
(
template
==
0
)
template
=
(
*
insn_outfun
[
insn_code_number
])
(
recog_operand
,
delay_insn
);
output_asm_insn
(
template
,
recog_operand
);
}
CC_STATUS_INIT
;
return
""
;
}
/* Output a newly constructed insn DELAY_INSN. */
char
*
output_delay_insn
(
delay_insn
)
rtx
delay_insn
;
{
char
*
template
;
int
insn_code_number
;
int
i
;
/* Now recognize the insn which we put in its delay slot.
We must do this after outputing the branch insn,
since operands may just be a pointer to `recog_operand'. */
insn_code_number
=
recog_memoized
(
delay_insn
);
if
(
insn_code_number
==
-
1
)
abort
();
/* Extract the operands of this delay insn. */
INSN_CODE
(
delay_insn
)
=
insn_code_number
;
insn_extract
(
delay_insn
);
/* It is possible that this insn has not been properly scaned by final
yet. If this insn's operands don't appear in the peephole's
actual operands, then they won't be fixed up by final, so we
make sure they get fixed up here. -- This is a kludge. */
for
(
i
=
0
;
i
<
insn_n_operands
[
insn_code_number
];
i
++
)
{
if
(
GET_CODE
(
recog_operand
[
i
])
==
SUBREG
)
recog_operand
[
i
]
=
alter_subreg
(
recog_operand
[
i
]);
}
#ifdef REGISTER_CONSTRAINTS
if
(
!
constrain_operands
(
insn_code_number
))
abort
();
#endif
cc_prev_status
=
cc_status
;
/* Update `cc_status' for this instruction.
The instruction's output routine may change it further.
If the output routine for a jump insn needs to depend
on the cc status, it should look at cc_prev_status. */
NOTICE_UPDATE_CC
(
PATTERN
(
delay_insn
),
delay_insn
);
/* Now get the template for what this insn would
have been, without the branch. */
template
=
insn_template
[
insn_code_number
];
if
(
template
==
0
)
template
=
(
*
insn_outfun
[
insn_code_number
])
(
recog_operand
,
delay_insn
);
output_asm_insn
(
template
,
recog_operand
);
return
""
;
}
/* Special routine to convert an SFmode value represented as a
CONST_DOUBLE into its equivalent unsigned long bit pattern.
We convert the value from a double precision floating-point
value to single precision first, and thence to a bit-wise
equivalent unsigned long value. This routine is used when
generating an immediate move of an SFmode value directly
into a general register because the svr4 assembler doesn't
grok floating literals in instruction operand contexts. */
unsigned
long
sfmode_constant_to_ulong
(
x
)
rtx
x
;
{
union
{
double
d
;
unsigned
long
i
[
2
];
}
u
;
union
{
float
f
;
unsigned
long
i
;
}
u2
;
if
(
GET_CODE
(
x
)
!=
CONST_DOUBLE
||
GET_MODE
(
x
)
!=
SFmode
)
abort
();
#ifndef HOST_WORDS_BIG_ENDIAN
u
.
i
[
0
]
=
CONST_DOUBLE_LOW
(
x
);
u
.
i
[
1
]
=
CONST_DOUBLE_HIGH
(
x
);
#else
u
.
i
[
0
]
=
CONST_DOUBLE_HIGH
(
x
);
u
.
i
[
1
]
=
CONST_DOUBLE_LOW
(
x
);
#endif
u2
.
f
=
u
.
d
;
return
u2
.
i
;
}
/* This function generates the assembly code for function entry.
The macro FUNCTION_PROLOGUE in i860.h is defined to call this function.
ASM_FILE is a stdio stream to output the code to.
SIZE is an int: how many units of temporary storage to allocate.
Refer to the array `regs_ever_live' to determine which registers
to save; `regs_ever_live[I]' is nonzero if register number I
is ever used in the function. This macro is responsible for
knowing which registers should not be saved even if used.
NOTE: `frame_lower_bytes' is the count of bytes which will lie
between the new `fp' value and the new `sp' value after the
prologue is done. `frame_upper_bytes' is the count of bytes
that will lie between the new `fp' and the *old* `sp' value
after the new `fp' is setup (in the prologue). The upper
part of each frame always includes at least 2 words (8 bytes)
to hold the saved frame pointer and the saved return address.
The svr4 ABI for the i860 now requires that the values of the
stack pointer and frame pointer registers be kept aligned to
16-byte boundaries at all times. We obey that restriction here.
The svr4 ABI for the i860 is entirely vague when it comes to specifying
exactly where the "preserved" registers should be saved. The native
svr4 C compiler I now have doesn't help to clarify the requirements
very much because it is plainly out-of-date and non-ABI-compliant
(in at least one important way, i.e. how it generates function
epilogues).
The native svr4 C compiler saves the "preserved" registers (i.e.
r4-r15 and f2-f7) in the lower part of a frame (i.e. at negative
offsets from the frame pointer).
Previous versions of GCC also saved the "preserved" registers in the
"nagative" part of the frame, but they saved them using positive
offsets from the (adjusted) stack pointer (after it had been adjusted
to allocate space for the new frame). That's just plain wrong
because if the current function calls alloca(), the stack pointer
will get moved, and it will be impossible to restore the registers
properly again after that.
Both compilers handled parameter registers (i.e. r16-r27 and f8-f15)
by copying their values either into various "preserved" registers or
into stack slots in the lower part of the current frame (as seemed
appropriate, depending upon subsequent usage of these values).
Here we want to save the preserved registers at some offset from the
frame pointer register so as to avoid any possible problems arising
from calls to alloca(). We can either save them at small positive
offsets from the frame pointer, or at small negative offsets from
the frame pointer. If we save them at small negative offsets from
the frame pointer (i.e. in the lower part of the frame) then we
must tell the rest of GCC (via STARTING_FRAME_OFFSET) exactly how
many bytes of space we plan to use in the lower part of the frame
for this purpose. Since other parts of the compiler reference the
value of STARTING_FRAME_OFFSET long before final() calls this function,
we would have to go ahead and assume the worst-case storage requirements
for saving all of the "preserved" registers (and use that number, i.e.
`80', to define STARTING_FRAME_OFFSET) if we wanted to save them in
the lower part of the frame. That could potentially be very wasteful,
and that wastefulness could really hamper people compiling for embedded
i860 targets with very tight limits on stack space. Thus, we choose
here to save the preserved registers in the upper part of the
frame, so that we can decide at the very last minute how much (or how
little) space we must allocate for this purpose.
To satisfy the needs of the svr4 ABI "tdesc" scheme, preserved
registers must always be saved so that the saved values of registers
with higher numbers are at higher addresses. We obey that restriction
here.
There are two somewhat different ways that you can generate prologues
here... i.e. pedantically ABI-compliant, and the "other" way. The
"other" way is more consistant with what is currently generated by the
"native" svr4 C compiler for the i860. That's important if you want
to use the current (as of 8/91) incarnation of svr4 SDB for the i860.
The SVR4 SDB for the i860 insists on having function prologues be
non-ABI-compliant!
To get fully ABI-compliant prologues, define I860_STRICT_ABI_PROLOGUES
in the i860svr4.h file. (By default this is *not* defined).
The differences between the ABI-compliant and non-ABI-compliant prologues
are that (a) the ABI version seems to require the use of *signed*
(rather than unsigned) adds and subtracts, and (b) the ordering of
the various steps (e.g. saving preserved registers, saving the
return address, setting up the new frame pointer value) is different.
For strict ABI compliance, it seems to be the case that the very last
thing that is supposed to happen in the prologue is getting the frame
pointer set to its new value (but only after everything else has
already been properly setup). We do that here, but only if the symbol
I860_STRICT_ABI_PROLOGUES is defined.
*/
#ifndef STACK_ALIGNMENT
#define STACK_ALIGNMENT 16
#endif
extern
char
call_used_regs
[];
extern
int
leaf_function_p
();
char
*
current_function_original_name
;
static
int
must_preserve_r1
;
static
unsigned
must_preserve_bytes
;
void
function_prologue
(
asm_file
,
local_bytes
)
register
FILE
*
asm_file
;
register
unsigned
local_bytes
;
{
register
unsigned
frame_lower_bytes
;
register
unsigned
frame_upper_bytes
;
register
unsigned
total_fsize
;
register
unsigned
preserved_reg_bytes
=
0
;
register
unsigned
i
;
register
unsigned
preserved_so_far
=
0
;
must_preserve_r1
=
(
optimize
<
2
||
!
leaf_function_p
());
must_preserve_bytes
=
4
+
(
must_preserve_r1
?
4
:
0
);
/* Count registers that need preserving. Ignore r0. It never needs
preserving. */
for
(
i
=
1
;
i
<
FIRST_PSEUDO_REGISTER
;
i
++
)
{
if
(
regs_ever_live
[
i
]
&&
!
call_used_regs
[
i
])
preserved_reg_bytes
+=
4
;
}
/* Round-up the frame_lower_bytes so that it's a multiple of 16. */
frame_lower_bytes
=
(
local_bytes
+
STACK_ALIGNMENT
-
1
)
&
-
STACK_ALIGNMENT
;
/* The upper part of each frame will contain the saved fp,
the saved r1, and stack slots for all of the other "preserved"
registers that we find we will need to save & restore. */
frame_upper_bytes
=
must_preserve_bytes
+
preserved_reg_bytes
;
/* Round-up the frame_upper_bytes so that it's a multiple of 16. */
frame_upper_bytes
=
(
frame_upper_bytes
+
STACK_ALIGNMENT
-
1
)
&
-
STACK_ALIGNMENT
;
total_fsize
=
frame_upper_bytes
+
frame_lower_bytes
;
#ifndef I860_STRICT_ABI_PROLOGUES
/* There are two kinds of function prologues.
You use the "small" version if the total frame size is
small enough so that it can fit into an immediate 16-bit
value in one instruction. Otherwise, you use the "large"
version of the function prologue. */
if
(
total_fsize
>
0x7fff
)
{
/* Adjust the stack pointer. The ABI sez to do this using `adds',
but the native C compiler on svr4 uses `addu'. */
fprintf
(
asm_file
,
"
\t
addu -%d,%ssp,%ssp
\n
"
,
frame_upper_bytes
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Save the old frame pointer. */
fprintf
(
asm_file
,
"
\t
st.l %sfp,0(%ssp)
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Setup the new frame pointer. The ABI sez to do this after
preserving registers (using adds), but that's not what the
native C compiler on svr4 does. */
fprintf
(
asm_file
,
"
\t
addu 0,%ssp,%sfp
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Get the value of frame_lower_bytes into r31. */
fprintf
(
asm_file
,
"
\t
orh %d,%sr0,%sr31
\n
"
,
frame_lower_bytes
>>
16
,
i860_reg_prefix
,
i860_reg_prefix
);
fprintf
(
asm_file
,
"
\t
or %d,%sr31,%sr31
\n
"
,
frame_lower_bytes
&
0xffff
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Now re-adjust the stack pointer using the value in r31.
The ABI sez to do this with `subs' but SDB may prefer `subu'. */
fprintf
(
asm_file
,
"
\t
subu %ssp,%sr31,%ssp
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Preserve registers. The ABI sez to do this before setting
up the new frame pointer, but that's not what the native
C compiler on svr4 does. */
for
(
i
=
1
;
i
<
32
;
i
++
)
if
(
regs_ever_live
[
i
]
&&
!
call_used_regs
[
i
])
fprintf
(
asm_file
,
"
\t
st.l %s%s,%d(%sfp)
\n
"
,
i860_reg_prefix
,
reg_names
[
i
],
must_preserve_bytes
+
(
4
*
preserved_so_far
++
),
i860_reg_prefix
);
for
(
i
=
32
;
i
<
64
;
i
++
)
if
(
regs_ever_live
[
i
]
&&
!
call_used_regs
[
i
])
fprintf
(
asm_file
,
"
\t
fst.l %s%s,%d(%sfp)
\n
"
,
i860_reg_prefix
,
reg_names
[
i
],
must_preserve_bytes
+
(
4
*
preserved_so_far
++
),
i860_reg_prefix
);
/* Save the return address. */
if
(
must_preserve_r1
)
fprintf
(
asm_file
,
"
\t
st.l %sr1,4(%sfp)
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
);
}
else
{
/* Adjust the stack pointer. The ABI sez to do this using `adds',
but the native C compiler on svr4 uses `addu'. */
fprintf
(
asm_file
,
"
\t
addu -%d,%ssp,%ssp
\n
"
,
total_fsize
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Save the old frame pointer. */
fprintf
(
asm_file
,
"
\t
st.l %sfp,%d(%ssp)
\n
"
,
i860_reg_prefix
,
frame_lower_bytes
,
i860_reg_prefix
);
/* Setup the new frame pointer. The ABI sez to do this after
preserving registers and after saving the return address,
(and its saz to do this using adds), but that's not what the
native C compiler on svr4 does. */
fprintf
(
asm_file
,
"
\t
addu %d,%ssp,%sfp
\n
"
,
frame_lower_bytes
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Preserve registers. The ABI sez to do this before setting
up the new frame pointer, but that's not what the native
compiler on svr4 does. */
for
(
i
=
1
;
i
<
32
;
i
++
)
if
(
regs_ever_live
[
i
]
&&
!
call_used_regs
[
i
])
fprintf
(
asm_file
,
"
\t
st.l %s%s,%d(%sfp)
\n
"
,
i860_reg_prefix
,
reg_names
[
i
],
must_preserve_bytes
+
(
4
*
preserved_so_far
++
),
i860_reg_prefix
);
for
(
i
=
32
;
i
<
64
;
i
++
)
if
(
regs_ever_live
[
i
]
&&
!
call_used_regs
[
i
])
fprintf
(
asm_file
,
"
\t
fst.l %s%s,%d(%sfp)
\n
"
,
i860_reg_prefix
,
reg_names
[
i
],
must_preserve_bytes
+
(
4
*
preserved_so_far
++
),
i860_reg_prefix
);
/* Save the return address. The ABI sez to do this earlier,
and also via an offset from %sp, but the native C compiler
on svr4 does it later (i.e. now) and uses an offset from
%fp. */
if
(
must_preserve_r1
)
fprintf
(
asm_file
,
"
\t
st.l %sr1,4(%sfp)
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
);
}
#else
/* defined(I860_STRICT_ABI_PROLOGUES) */
/* There are two kinds of function prologues.
You use the "small" version if the total frame size is
small enough so that it can fit into an immediate 16-bit
value in one instruction. Otherwise, you use the "large"
version of the function prologue. */
if
(
total_fsize
>
0x7fff
)
{
/* Adjust the stack pointer (thereby allocating a new frame). */
fprintf
(
asm_file
,
"
\t
adds -%d,%ssp,%ssp
\n
"
,
frame_upper_bytes
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Save the caller's frame pointer. */
fprintf
(
asm_file
,
"
\t
st.l %sfp,0(%ssp)
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Save return address. */
if
(
must_preserve_r1
)
fprintf
(
asm_file
,
"
\t
st.l %sr1,4(%ssp)
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Get the value of frame_lower_bytes into r31 for later use. */
fprintf
(
asm_file
,
"
\t
orh %d,%sr0,%sr31
\n
"
,
frame_lower_bytes
>>
16
,
i860_reg_prefix
,
i860_reg_prefix
);
fprintf
(
asm_file
,
"
\t
or %d,%sr31,%sr31
\n
"
,
frame_lower_bytes
&
0xffff
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Now re-adjust the stack pointer using the value in r31. */
fprintf
(
asm_file
,
"
\t
subs %ssp,%sr31,%ssp
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Pre-compute value to be used as the new frame pointer. */
fprintf
(
asm_file
,
"
\t
adds %ssp,%sr31,%sr31
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Preserve registers. */
for
(
i
=
1
;
i
<
32
;
i
++
)
if
(
regs_ever_live
[
i
]
&&
!
call_used_regs
[
i
])
fprintf
(
asm_file
,
"
\t
st.l %s%s,%d(%sr31)
\n
"
,
i860_reg_prefix
,
reg_names
[
i
],
must_preserve_bytes
+
(
4
*
preserved_so_far
++
),
i860_reg_prefix
);
for
(
i
=
32
;
i
<
64
;
i
++
)
if
(
regs_ever_live
[
i
]
&&
!
call_used_regs
[
i
])
fprintf
(
asm_file
,
"
\t
fst.l %s%s,%d(%sr31)
\n
"
,
i860_reg_prefix
,
reg_names
[
i
],
must_preserve_bytes
+
(
4
*
preserved_so_far
++
),
i860_reg_prefix
);
/* Actually set the new value of the frame pointer. */
fprintf
(
asm_file
,
"
\t
mov %sr31,%sfp
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
);
}
else
{
/* Adjust the stack pointer. */
fprintf
(
asm_file
,
"
\t
adds -%d,%ssp,%ssp
\n
"
,
total_fsize
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Save the caller's frame pointer. */
fprintf
(
asm_file
,
"
\t
st.l %sfp,%d(%ssp)
\n
"
,
i860_reg_prefix
,
frame_lower_bytes
,
i860_reg_prefix
);
/* Save the return address. */
if
(
must_preserve_r1
)
fprintf
(
asm_file
,
"
\t
st.l %sr1,%d(%ssp)
\n
"
,
i860_reg_prefix
,
frame_lower_bytes
+
4
,
i860_reg_prefix
);
/* Preserve registers. */
for
(
i
=
1
;
i
<
32
;
i
++
)
if
(
regs_ever_live
[
i
]
&&
!
call_used_regs
[
i
])
fprintf
(
asm_file
,
"
\t
st.l %s%s,%d(%ssp)
\n
"
,
i860_reg_prefix
,
reg_names
[
i
],
frame_lower_bytes
+
must_preserve_bytes
+
(
4
*
preserved_so_far
++
),
i860_reg_prefix
);
for
(
i
=
32
;
i
<
64
;
i
++
)
if
(
regs_ever_live
[
i
]
&&
!
call_used_regs
[
i
])
fprintf
(
asm_file
,
"
\t
fst.l %s%s,%d(%ssp)
\n
"
,
i860_reg_prefix
,
reg_names
[
i
],
frame_lower_bytes
+
must_preserve_bytes
+
(
4
*
preserved_so_far
++
),
i860_reg_prefix
);
/* Setup the new frame pointer. */
fprintf
(
asm_file
,
"
\t
adds %d,%ssp,%sfp
\n
"
,
frame_lower_bytes
,
i860_reg_prefix
,
i860_reg_prefix
);
}
#endif
/* defined(I860_STRICT_ABI_PROLOGUES) */
#ifdef ASM_OUTPUT_PROLOGUE_SUFFIX
ASM_OUTPUT_PROLOGUE_SUFFIX
(
asm_file
);
#endif
/* defined(ASM_OUTPUT_PROLOGUE_SUFFIX) */
}
/* This function generates the assembly code for function exit.
The macro FUNCTION_EPILOGUE in i860.h is defined to call this function.
ASM_FILE is a stdio stream to output the code to.
SIZE is an int: how many units of temporary storage to allocate.
The function epilogue should not depend on the current stack pointer!
It should use the frame pointer only. This is mandatory because
of alloca; we also take advantage of it to omit stack adjustments
before returning.
Note that when we go to restore the preserved register values we must
not try to address their slots by using offsets from the stack pointer.
That's because the stack pointer may have been moved during the function
execution due to a call to alloca(). Rather, we must restore all
preserved registers via offsets from the frame pointer value.
Note also that when the current frame is being "popped" (by adjusting
the value of the stack pointer) on function exit, we must (for the
sake of alloca) set the new value of the stack pointer based upon
the current value of the frame pointer. We can't just add what we
believe to be the (static) frame size to the stack pointer because
if we did that, and alloca() had been called during this function,
we would end up returning *without* having fully deallocated all of
the space grabbed by alloca. If that happened, and a function
containing one or more alloca() calls was called over and over again,
then the stack would grow without limit!
Finally note that the epilogues generated here are completely ABI
compliant. They go out of their way to insure that the value in
the frame pointer register is never less than the value in the stack
pointer register. It's not clear why this relationship needs to be
maintained at all times, but maintaining it only costs one extra
instruction, so what the hell.
*/
void
function_epilogue
(
asm_file
,
local_bytes
)
register
FILE
*
asm_file
;
register
unsigned
local_bytes
;
{
register
unsigned
frame_upper_bytes
;
register
unsigned
preserved_reg_bytes
=
0
;
register
unsigned
i
;
register
unsigned
restored_so_far
=
0
;
/* Count the number of registers that were preserved in the prologue.
Ignore r0. It is never preserved. */
for
(
i
=
1
;
i
<
FIRST_PSEUDO_REGISTER
;
i
++
)
{
if
(
regs_ever_live
[
i
]
&&
!
call_used_regs
[
i
])
preserved_reg_bytes
+=
4
;
}
/* The upper part of each frame will contain only saved fp,
the saved r1, and stack slots for all of the other "preserved"
registers that we find we will need to save & restore. */
frame_upper_bytes
=
must_preserve_bytes
+
preserved_reg_bytes
;
/* Round-up frame_upper_bytes so that t is a multiple of 16. */
frame_upper_bytes
=
(
frame_upper_bytes
+
STACK_ALIGNMENT
-
1
)
&
-
STACK_ALIGNMENT
;
/* Restore all of the "preserved" registers that need restoring. */
for
(
i
=
1
;
i
<
32
;
i
++
)
if
(
regs_ever_live
[
i
]
&&
!
call_used_regs
[
i
])
fprintf
(
asm_file
,
"
\t
ld.l %d(%sfp),%s%s
\n
"
,
must_preserve_bytes
+
(
4
*
restored_so_far
++
),
i860_reg_prefix
,
i860_reg_prefix
,
reg_names
[
i
]);
for
(
i
=
32
;
i
<
64
;
i
++
)
if
(
regs_ever_live
[
i
]
&&
!
call_used_regs
[
i
])
fprintf
(
asm_file
,
"
\t
fld.l %d(%sfp),%s%s
\n
"
,
must_preserve_bytes
+
(
4
*
restored_so_far
++
),
i860_reg_prefix
,
i860_reg_prefix
,
reg_names
[
i
]);
/* Get the value we plan to use to restore the stack pointer into r31. */
fprintf
(
asm_file
,
"
\t
adds %d,%sfp,%sr31
\n
"
,
frame_upper_bytes
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Restore the return address and the old frame pointer. */
if
(
must_preserve_r1
)
fprintf
(
asm_file
,
"
\t
ld.l 4(%sfp),%sr1
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
);
fprintf
(
asm_file
,
"
\t
ld.l 0(%sfp),%sfp
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
);
/* Return and restore the old stack pointer value. */
fprintf
(
asm_file
,
"
\t
bri %sr1
\n\t
mov %sr31,%ssp
\n
"
,
i860_reg_prefix
,
i860_reg_prefix
,
i860_reg_prefix
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment