blob: cb805d3b35cb2f9a314810e8dee3366fde42421d [file] [log] [blame]
/* Subroutines for gcc2 for pdp11.
Copyright (C) 1994-2015 Free Software Foundation, Inc.
Contributed by Michael K. Gschwind (mike@vlsivie.tuwien.ac.at).
This file is part of GCC.
GCC 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 3, or (at your option)
any later version.
GCC 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 GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "tm.h"
#include "rtl.h"
#include "regs.h"
#include "hard-reg-set.h"
#include "insn-config.h"
#include "conditions.h"
#include "hashtab.h"
#include "hash-set.h"
#include "vec.h"
#include "machmode.h"
#include "input.h"
#include "function.h"
#include "output.h"
#include "insn-attr.h"
#include "flags.h"
#include "recog.h"
#include "symtab.h"
#include "wide-int.h"
#include "inchash.h"
#include "tree.h"
#include "stor-layout.h"
#include "varasm.h"
#include "calls.h"
#include "statistics.h"
#include "double-int.h"
#include "real.h"
#include "fixed-value.h"
#include "alias.h"
#include "expmed.h"
#include "dojump.h"
#include "explow.h"
#include "emit-rtl.h"
#include "stmt.h"
#include "expr.h"
#include "diagnostic-core.h"
#include "tm_p.h"
#include "target.h"
#include "target-def.h"
#include "dominance.h"
#include "cfg.h"
#include "cfgrtl.h"
#include "cfganal.h"
#include "lcm.h"
#include "cfgbuild.h"
#include "cfgcleanup.h"
#include "predict.h"
#include "basic-block.h"
#include "df.h"
#include "opts.h"
#include "dbxout.h"
#include "builtins.h"
/* this is the current value returned by the macro FIRST_PARM_OFFSET
defined in tm.h */
int current_first_parm_offset;
/* Routines to encode/decode pdp11 floats */
static void encode_pdp11_f (const struct real_format *fmt,
long *, const REAL_VALUE_TYPE *);
static void decode_pdp11_f (const struct real_format *,
REAL_VALUE_TYPE *, const long *);
static void encode_pdp11_d (const struct real_format *fmt,
long *, const REAL_VALUE_TYPE *);
static void decode_pdp11_d (const struct real_format *,
REAL_VALUE_TYPE *, const long *);
/* These two are taken from the corresponding vax descriptors
in real.c, changing only the encode/decode routine pointers. */
const struct real_format pdp11_f_format =
{
encode_pdp11_f,
decode_pdp11_f,
2,
24,
24,
-127,
127,
15,
15,
false,
false,
false,
false,
false,
false,
false,
false,
"pdp11_f"
};
const struct real_format pdp11_d_format =
{
encode_pdp11_d,
decode_pdp11_d,
2,
56,
56,
-127,
127,
15,
15,
false,
false,
false,
false,
false,
false,
false,
false,
"pdp11_d"
};
static void
encode_pdp11_f (const struct real_format *fmt ATTRIBUTE_UNUSED, long *buf,
const REAL_VALUE_TYPE *r)
{
(*vax_f_format.encode) (fmt, buf, r);
buf[0] = ((buf[0] >> 16) & 0xffff) | ((buf[0] & 0xffff) << 16);
}
static void
decode_pdp11_f (const struct real_format *fmt ATTRIBUTE_UNUSED,
REAL_VALUE_TYPE *r, const long *buf)
{
long tbuf;
tbuf = ((buf[0] >> 16) & 0xffff) | ((buf[0] & 0xffff) << 16);
(*vax_f_format.decode) (fmt, r, &tbuf);
}
static void
encode_pdp11_d (const struct real_format *fmt ATTRIBUTE_UNUSED, long *buf,
const REAL_VALUE_TYPE *r)
{
(*vax_d_format.encode) (fmt, buf, r);
buf[0] = ((buf[0] >> 16) & 0xffff) | ((buf[0] & 0xffff) << 16);
buf[1] = ((buf[1] >> 16) & 0xffff) | ((buf[1] & 0xffff) << 16);
}
static void
decode_pdp11_d (const struct real_format *fmt ATTRIBUTE_UNUSED,
REAL_VALUE_TYPE *r, const long *buf)
{
long tbuf[2];
tbuf[0] = ((buf[0] >> 16) & 0xffff) | ((buf[0] & 0xffff) << 16);
tbuf[1] = ((buf[1] >> 16) & 0xffff) | ((buf[1] & 0xffff) << 16);
(*vax_d_format.decode) (fmt, r, tbuf);
}
/* This is where the condition code register lives. */
/* rtx cc0_reg_rtx; - no longer needed? */
static const char *singlemove_string (rtx *);
static bool pdp11_assemble_integer (rtx, unsigned int, int);
static bool pdp11_rtx_costs (rtx, int, int, int, int *, bool);
static bool pdp11_return_in_memory (const_tree, const_tree);
static rtx pdp11_function_value (const_tree, const_tree, bool);
static rtx pdp11_libcall_value (machine_mode, const_rtx);
static bool pdp11_function_value_regno_p (const unsigned int);
static void pdp11_trampoline_init (rtx, tree, rtx);
static rtx pdp11_function_arg (cumulative_args_t, machine_mode,
const_tree, bool);
static void pdp11_function_arg_advance (cumulative_args_t,
machine_mode, const_tree, bool);
static void pdp11_conditional_register_usage (void);
static bool pdp11_legitimate_constant_p (machine_mode, rtx);
static bool pdp11_scalar_mode_supported_p (machine_mode);
/* Initialize the GCC target structure. */
#undef TARGET_ASM_BYTE_OP
#define TARGET_ASM_BYTE_OP NULL
#undef TARGET_ASM_ALIGNED_HI_OP
#define TARGET_ASM_ALIGNED_HI_OP NULL
#undef TARGET_ASM_ALIGNED_SI_OP
#define TARGET_ASM_ALIGNED_SI_OP NULL
#undef TARGET_ASM_INTEGER
#define TARGET_ASM_INTEGER pdp11_assemble_integer
#undef TARGET_ASM_OPEN_PAREN
#define TARGET_ASM_OPEN_PAREN "["
#undef TARGET_ASM_CLOSE_PAREN
#define TARGET_ASM_CLOSE_PAREN "]"
#undef TARGET_RTX_COSTS
#define TARGET_RTX_COSTS pdp11_rtx_costs
#undef TARGET_FUNCTION_ARG
#define TARGET_FUNCTION_ARG pdp11_function_arg
#undef TARGET_FUNCTION_ARG_ADVANCE
#define TARGET_FUNCTION_ARG_ADVANCE pdp11_function_arg_advance
#undef TARGET_RETURN_IN_MEMORY
#define TARGET_RETURN_IN_MEMORY pdp11_return_in_memory
#undef TARGET_FUNCTION_VALUE
#define TARGET_FUNCTION_VALUE pdp11_function_value
#undef TARGET_LIBCALL_VALUE
#define TARGET_LIBCALL_VALUE pdp11_libcall_value
#undef TARGET_FUNCTION_VALUE_REGNO_P
#define TARGET_FUNCTION_VALUE_REGNO_P pdp11_function_value_regno_p
#undef TARGET_TRAMPOLINE_INIT
#define TARGET_TRAMPOLINE_INIT pdp11_trampoline_init
#undef TARGET_SECONDARY_RELOAD
#define TARGET_SECONDARY_RELOAD pdp11_secondary_reload
#undef TARGET_REGISTER_MOVE_COST
#define TARGET_REGISTER_MOVE_COST pdp11_register_move_cost
#undef TARGET_PREFERRED_RELOAD_CLASS
#define TARGET_PREFERRED_RELOAD_CLASS pdp11_preferred_reload_class
#undef TARGET_PREFERRED_OUTPUT_RELOAD_CLASS
#define TARGET_PREFERRED_OUTPUT_RELOAD_CLASS pdp11_preferred_output_reload_class
#undef TARGET_LEGITIMATE_ADDRESS_P
#define TARGET_LEGITIMATE_ADDRESS_P pdp11_legitimate_address_p
#undef TARGET_CONDITIONAL_REGISTER_USAGE
#define TARGET_CONDITIONAL_REGISTER_USAGE pdp11_conditional_register_usage
#undef TARGET_ASM_FUNCTION_SECTION
#define TARGET_ASM_FUNCTION_SECTION pdp11_function_section
#undef TARGET_PRINT_OPERAND
#define TARGET_PRINT_OPERAND pdp11_asm_print_operand
#undef TARGET_PRINT_OPERAND_PUNCT_VALID_P
#define TARGET_PRINT_OPERAND_PUNCT_VALID_P pdp11_asm_print_operand_punct_valid_p
#undef TARGET_LEGITIMATE_CONSTANT_P
#define TARGET_LEGITIMATE_CONSTANT_P pdp11_legitimate_constant_p
#undef TARGET_SCALAR_MODE_SUPPORTED_P
#define TARGET_SCALAR_MODE_SUPPORTED_P pdp11_scalar_mode_supported_p
/* A helper function to determine if REGNO should be saved in the
current function's stack frame. */
static inline bool
pdp11_saved_regno (unsigned regno)
{
return !call_used_regs[regno] && df_regs_ever_live_p (regno);
}
/* Expand the function prologue. */
void
pdp11_expand_prologue (void)
{
HOST_WIDE_INT fsize = get_frame_size ();
unsigned regno;
rtx x, via_ac = NULL;
/* If we are outputting code for main, the switch FPU to the
right mode if TARGET_FPU. */
if (MAIN_NAME_P (DECL_NAME (current_function_decl)) && TARGET_FPU)
{
emit_insn (gen_setd ());
emit_insn (gen_seti ());
}
if (frame_pointer_needed)
{
x = gen_rtx_PRE_DEC (Pmode, stack_pointer_rtx);
x = gen_frame_mem (Pmode, x);
emit_move_insn (x, hard_frame_pointer_rtx);
emit_move_insn (hard_frame_pointer_rtx, stack_pointer_rtx);
}
/* Make frame. */
if (fsize)
{
emit_insn (gen_addhi3 (stack_pointer_rtx, stack_pointer_rtx,
GEN_INT (-fsize)));
/* Prevent frame references via the frame pointer from being
scheduled before the frame is allocated. */
if (frame_pointer_needed)
emit_insn (gen_blockage ());
}
/* Save CPU registers. */
for (regno = R0_REGNUM; regno <= PC_REGNUM; regno++)
if (pdp11_saved_regno (regno)
&& (regno != HARD_FRAME_POINTER_REGNUM || !frame_pointer_needed))
{
x = gen_rtx_PRE_DEC (Pmode, stack_pointer_rtx);
x = gen_frame_mem (Pmode, x);
emit_move_insn (x, gen_rtx_REG (Pmode, regno));
}
/* Save FPU registers. */
for (regno = AC0_REGNUM; regno <= AC3_REGNUM; regno++)
if (pdp11_saved_regno (regno))
{
x = gen_rtx_PRE_DEC (Pmode, stack_pointer_rtx);
x = gen_frame_mem (DFmode, x);
via_ac = gen_rtx_REG (DFmode, regno);
emit_move_insn (x, via_ac);
}
/* ??? Maybe make ac4, ac5 call used regs?? */
for (regno = AC4_REGNUM; regno <= AC5_REGNUM; regno++)
if (pdp11_saved_regno (regno))
{
gcc_assert (via_ac != NULL);
emit_move_insn (via_ac, gen_rtx_REG (DFmode, regno));
x = gen_rtx_PRE_DEC (Pmode, stack_pointer_rtx);
x = gen_frame_mem (DFmode, x);
emit_move_insn (x, via_ac);
}
}
/* 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. */
/* Maybe we can make leaf functions faster by switching to the
second register file - this way we don't have to save regs!
leaf functions are ~ 50% of all functions (dynamically!)
set/clear bit 11 (dec. 2048) of status word for switching register files -
but how can we do this? the pdp11/45 manual says bit may only
be set (p.24), but not cleared!
switching to kernel is probably more expensive, so we'll leave it
like this and not use the second set of registers...
maybe as option if you want to generate code for kernel mode? */
void
pdp11_expand_epilogue (void)
{
HOST_WIDE_INT fsize = get_frame_size ();
unsigned regno;
rtx x, reg, via_ac = NULL;
if (pdp11_saved_regno (AC4_REGNUM) || pdp11_saved_regno (AC5_REGNUM))
{
/* Find a temporary with which to restore AC4/5. */
for (regno = AC0_REGNUM; regno <= AC3_REGNUM; regno++)
if (pdp11_saved_regno (regno))
{
via_ac = gen_rtx_REG (DFmode, regno);
break;
}
}
/* If possible, restore registers via pops. */
if (!frame_pointer_needed || crtl->sp_is_unchanging)
{
/* Restore registers via pops. */
for (regno = AC5_REGNUM; regno >= AC0_REGNUM; regno--)
if (pdp11_saved_regno (regno))
{
x = gen_rtx_POST_INC (Pmode, stack_pointer_rtx);
x = gen_frame_mem (DFmode, x);
reg = gen_rtx_REG (DFmode, regno);
if (LOAD_FPU_REG_P (regno))
emit_move_insn (reg, x);
else
{
emit_move_insn (via_ac, x);
emit_move_insn (reg, via_ac);
}
}
for (regno = PC_REGNUM; regno >= R0_REGNUM + 2; regno--)
if (pdp11_saved_regno (regno)
&& (regno != HARD_FRAME_POINTER_REGNUM || !frame_pointer_needed))
{
x = gen_rtx_POST_INC (Pmode, stack_pointer_rtx);
x = gen_frame_mem (Pmode, x);
emit_move_insn (gen_rtx_REG (Pmode, regno), x);
}
}
else
{
/* Restore registers via moves. */
/* ??? If more than a few registers need to be restored, it's smaller
to generate a pointer through which we can emit pops. Consider
that moves cost 2*NREG words and pops cost NREG+3 words. This
means that the crossover is NREG=3.
Possible registers to use are:
(1) The first call-saved general register. This register will
be restored with the last pop.
(2) R1, if it's not used as a return register.
(3) FP itself. This option may result in +4 words, since we
may need two add imm,rn instructions instead of just one.
This also has the downside that we're not representing
the unwind info in any way, so during the epilogue the
debugger may get lost. */
HOST_WIDE_INT ofs = -pdp11_sp_frame_offset ();
for (regno = AC5_REGNUM; regno >= AC0_REGNUM; regno--)
if (pdp11_saved_regno (regno))
{
x = plus_constant (Pmode, hard_frame_pointer_rtx, ofs);
x = gen_frame_mem (DFmode, x);
reg = gen_rtx_REG (DFmode, regno);
if (LOAD_FPU_REG_P (regno))
emit_move_insn (reg, x);
else
{
emit_move_insn (via_ac, x);
emit_move_insn (reg, via_ac);
}
ofs += 8;
}
for (regno = PC_REGNUM; regno >= R0_REGNUM + 2; regno--)
if (pdp11_saved_regno (regno)
&& (regno != HARD_FRAME_POINTER_REGNUM || !frame_pointer_needed))
{
x = plus_constant (Pmode, hard_frame_pointer_rtx, ofs);
x = gen_frame_mem (Pmode, x);
emit_move_insn (gen_rtx_REG (Pmode, regno), x);
ofs += 2;
}
}
/* Deallocate the stack frame. */
if (fsize)
{
/* Prevent frame references via any pointer from being
scheduled after the frame is deallocated. */
emit_insn (gen_blockage ());
if (frame_pointer_needed)
{
/* We can deallocate the frame with a single move. */
emit_move_insn (stack_pointer_rtx, hard_frame_pointer_rtx);
}
else
emit_insn (gen_addhi3 (stack_pointer_rtx, stack_pointer_rtx,
GEN_INT (fsize)));
}
if (frame_pointer_needed)
{
x = gen_rtx_POST_INC (Pmode, stack_pointer_rtx);
x = gen_frame_mem (Pmode, x);
emit_move_insn (hard_frame_pointer_rtx, x);
}
emit_jump_insn (gen_return ());
}
/* Return the best assembler insn template
for moving operands[1] into operands[0] as a fullword. */
static const char *
singlemove_string (rtx *operands)
{
if (operands[1] != const0_rtx)
return "mov %1,%0";
return "clr %0";
}
/* Expand multi-word operands (SImode or DImode) into the 2 or 4
corresponding HImode operands. The number of operands is given
as the third argument, and the required order of the parts as
the fourth argument. */
bool
pdp11_expand_operands (rtx *operands, rtx exops[][2], int opcount,
pdp11_action *action, pdp11_partorder order)
{
int words, op, w, i, sh;
pdp11_partorder useorder;
bool sameoff = false;
enum { REGOP, OFFSOP, MEMOP, PUSHOP, POPOP, CNSTOP, RNDOP } optype;
REAL_VALUE_TYPE r;
long sval[2];
words = GET_MODE_BITSIZE (GET_MODE (operands[0])) / 16;
/* If either piece order is accepted and one is pre-decrement
while the other is post-increment, set order to be high order
word first. That will force the pre-decrement to be turned
into a pointer adjust, then offset addressing.
Otherwise, if either operand uses pre-decrement, that means
the order is low order first.
Otherwise, if both operands are registers and destination is
higher than source and they overlap, do low order word (highest
register number) first. */
useorder = either;
if (opcount == 2)
{
if (!REG_P (operands[0]) && !REG_P (operands[1]) &&
!(CONSTANT_P (operands[1]) ||
GET_CODE (operands[1]) == CONST_DOUBLE) &&
((GET_CODE (XEXP (operands[0], 0)) == POST_INC &&
GET_CODE (XEXP (operands[1], 0)) == PRE_DEC) ||
(GET_CODE (XEXP (operands[0], 0)) == PRE_DEC &&
GET_CODE (XEXP (operands[1], 0)) == POST_INC)))
useorder = big;
else if ((!REG_P (operands[0]) &&
GET_CODE (XEXP (operands[0], 0)) == PRE_DEC) ||
(!REG_P (operands[1]) &&
!(CONSTANT_P (operands[1]) ||
GET_CODE (operands[1]) == CONST_DOUBLE) &&
GET_CODE (XEXP (operands[1], 0)) == PRE_DEC))
useorder = little;
else if (REG_P (operands[0]) && REG_P (operands[1]) &&
REGNO (operands[0]) > REGNO (operands[1]) &&
REGNO (operands[0]) < REGNO (operands[1]) + words)
useorder = little;
/* Check for source == offset from register and dest == push of
the same register. In that case, we have to use the same
offset (the one for the low order word) for all words, because
the push increases the offset to each source word.
In theory there are other cases like this, for example dest == pop,
but those don't occur in real life so ignore those. */
if (GET_CODE (operands[0]) == MEM
&& GET_CODE (XEXP (operands[0], 0)) == PRE_DEC
&& REGNO (XEXP (XEXP (operands[0], 0), 0)) == STACK_POINTER_REGNUM
&& reg_overlap_mentioned_p (stack_pointer_rtx, operands[1]))
sameoff = true;
}
/* If the caller didn't specify order, use the one we computed,
or high word first if we don't care either. If the caller did
specify, verify we don't have a problem with that order.
(If it matters to the caller, constraints need to be used to
ensure this case doesn't occur). */
if (order == either)
order = (useorder == either) ? big : useorder;
else
gcc_assert (useorder == either || useorder == order);
for (op = 0; op < opcount; op++)
{
/* First classify the operand. */
if (REG_P (operands[op]))
optype = REGOP;
else if (CONSTANT_P (operands[op])
|| GET_CODE (operands[op]) == CONST_DOUBLE)
optype = CNSTOP;
else if (GET_CODE (XEXP (operands[op], 0)) == POST_INC)
optype = POPOP;
else if (GET_CODE (XEXP (operands[op], 0)) == PRE_DEC)
optype = PUSHOP;
else if (!reload_in_progress || offsettable_memref_p (operands[op]))
optype = OFFSOP;
else if (GET_CODE (operands[op]) == MEM)
optype = MEMOP;
else
optype = RNDOP;
/* Check for the cases that the operand constraints are not
supposed to allow to happen. Return failure for such cases. */
if (optype == RNDOP)
return false;
if (action != NULL)
action[op] = no_action;
/* If the operand uses pre-decrement addressing but we
want to get the parts high order first,
decrement the former register explicitly
and change the operand into ordinary indexing. */
if (optype == PUSHOP && order == big)
{
gcc_assert (action != NULL);
action[op] = dec_before;
operands[op] = gen_rtx_MEM (GET_MODE (operands[op]),
XEXP (XEXP (operands[op], 0), 0));
optype = OFFSOP;
}
/* If the operand uses post-increment mode but we want
to get the parts low order first, change the operand
into ordinary indexing and remember to increment
the register explicitly when we're done. */
else if (optype == POPOP && order == little)
{
gcc_assert (action != NULL);
action[op] = inc_after;
operands[op] = gen_rtx_MEM (GET_MODE (operands[op]),
XEXP (XEXP (operands[op], 0), 0));
optype = OFFSOP;
}
if (GET_CODE (operands[op]) == CONST_DOUBLE)
{
REAL_VALUE_FROM_CONST_DOUBLE (r, operands[op]);
REAL_VALUE_TO_TARGET_DOUBLE (r, sval);
}
for (i = 0; i < words; i++)
{
if (order == big)
w = i;
else if (sameoff)
w = words - 1;
else
w = words - 1 - i;
/* Set the output operand to be word "w" of the input. */
if (optype == REGOP)
exops[i][op] = gen_rtx_REG (HImode, REGNO (operands[op]) + w);
else if (optype == OFFSOP)
exops[i][op] = adjust_address (operands[op], HImode, w * 2);
else if (optype == CNSTOP)
{
if (GET_CODE (operands[op]) == CONST_DOUBLE)
{
sh = 16 - (w & 1) * 16;
exops[i][op] = gen_rtx_CONST_INT (HImode, (sval[w / 2] >> sh) & 0xffff);
}
else
{
sh = ((words - 1 - w) * 16);
exops[i][op] = gen_rtx_CONST_INT (HImode, trunc_int_for_mode (INTVAL(operands[op]) >> sh, HImode));
}
}
else
exops[i][op] = operands[op];
}
}
return true;
}
/* Output assembler code to perform a multiple-word move insn
with operands OPERANDS. This moves 2 or 4 words depending
on the machine mode of the operands. */
const char *
output_move_multiple (rtx *operands)
{
rtx exops[4][2];
pdp11_action action[2];
int i, words;
words = GET_MODE_BITSIZE (GET_MODE (operands[0])) / 16;
pdp11_expand_operands (operands, exops, 2, action, either);
/* Check for explicit decrement before. */
if (action[0] == dec_before)
{
operands[0] = XEXP (operands[0], 0);
output_asm_insn ("sub $4,%0", operands);
}
if (action[1] == dec_before)
{
operands[1] = XEXP (operands[1], 0);
output_asm_insn ("sub $4,%1", operands);
}
/* Do the words. */
for (i = 0; i < words; i++)
output_asm_insn (singlemove_string (exops[i]), exops[i]);
/* Check for increment after. */
if (action[0] == inc_after)
{
operands[0] = XEXP (operands[0], 0);
output_asm_insn ("add $4,%0", operands);
}
if (action[1] == inc_after)
{
operands[1] = XEXP (operands[1], 0);
output_asm_insn ("add $4,%1", operands);
}
return "";
}
/* Output an ascii string. */
void
output_ascii (FILE *file, const char *p, int size)
{
int i;
/* This used to output .byte "string", which doesn't work with the UNIX
assembler and I think not with DEC ones either. */
fprintf (file, "\t.byte ");
for (i = 0; i < size; i++)
{
register int c = p[i];
if (c < 0)
c += 256;
fprintf (file, "%#o", c);
if (i < size - 1)
putc (',', file);
}
putc ('\n', file);
}
void
pdp11_asm_output_var (FILE *file, const char *name, int size,
int align, bool global)
{
if (align > 8)
fprintf (file, "\n\t.even\n");
if (global)
{
fprintf (file, ".globl ");
assemble_name (file, name);
}
fprintf (file, "\n");
assemble_name (file, name);
fprintf (file, ": .=.+ %#ho\n", (unsigned short)size);
}
static void
pdp11_asm_print_operand (FILE *file, rtx x, int code)
{
REAL_VALUE_TYPE r;
long sval[2];
if (code == '#')
fprintf (file, "#");
else if (code == '@')
{
if (TARGET_UNIX_ASM)
fprintf (file, "*");
else
fprintf (file, "@");
}
else if (GET_CODE (x) == REG)
fprintf (file, "%s", reg_names[REGNO (x)]);
else if (GET_CODE (x) == MEM)
output_address (XEXP (x, 0));
else if (GET_CODE (x) == CONST_DOUBLE && GET_MODE (x) != SImode)
{
REAL_VALUE_FROM_CONST_DOUBLE (r, x);
REAL_VALUE_TO_TARGET_DOUBLE (r, sval);
fprintf (file, "$%#lo", sval[0] >> 16);
}
else
{
putc ('$', file);
output_addr_const_pdp11 (file, x);
}
}
static bool
pdp11_asm_print_operand_punct_valid_p (unsigned char c)
{
return (c == '#' || c == '@');
}
void
print_operand_address (FILE *file, register rtx addr)
{
register rtx breg;
rtx offset;
int again = 0;
retry:
switch (GET_CODE (addr))
{
case MEM:
if (TARGET_UNIX_ASM)
fprintf (file, "*");
else
fprintf (file, "@");
addr = XEXP (addr, 0);
again = 1;
goto retry;
case REG:
fprintf (file, "(%s)", reg_names[REGNO (addr)]);
break;
case PRE_MODIFY:
case PRE_DEC:
fprintf (file, "-(%s)", reg_names[REGNO (XEXP (addr, 0))]);
break;
case POST_MODIFY:
case POST_INC:
fprintf (file, "(%s)+", reg_names[REGNO (XEXP (addr, 0))]);
break;
case PLUS:
breg = 0;
offset = 0;
if (CONSTANT_ADDRESS_P (XEXP (addr, 0))
|| GET_CODE (XEXP (addr, 0)) == MEM)
{
offset = XEXP (addr, 0);
addr = XEXP (addr, 1);
}
else if (CONSTANT_ADDRESS_P (XEXP (addr, 1))
|| GET_CODE (XEXP (addr, 1)) == MEM)
{
offset = XEXP (addr, 1);
addr = XEXP (addr, 0);
}
if (GET_CODE (addr) != PLUS)
;
else if (GET_CODE (XEXP (addr, 0)) == REG)
{
breg = XEXP (addr, 0);
addr = XEXP (addr, 1);
}
else if (GET_CODE (XEXP (addr, 1)) == REG)
{
breg = XEXP (addr, 1);
addr = XEXP (addr, 0);
}
if (GET_CODE (addr) == REG)
{
gcc_assert (breg == 0);
breg = addr;
addr = 0;
}
if (offset != 0)
{
gcc_assert (addr == 0);
addr = offset;
}
if (addr != 0)
output_addr_const_pdp11 (file, addr);
if (breg != 0)
{
gcc_assert (GET_CODE (breg) == REG);
fprintf (file, "(%s)", reg_names[REGNO (breg)]);
}
break;
default:
if (!again && GET_CODE (addr) == CONST_INT)
{
/* Absolute (integer number) address. */
if (!TARGET_UNIX_ASM)
fprintf (file, "@$");
}
output_addr_const_pdp11 (file, addr);
}
}
/* Target hook to assemble integer objects. We need to use the
pdp-specific version of output_addr_const. */
static bool
pdp11_assemble_integer (rtx x, unsigned int size, int aligned_p)
{
if (aligned_p)
switch (size)
{
case 1:
fprintf (asm_out_file, "\t.byte\t");
output_addr_const_pdp11 (asm_out_file, GEN_INT (INTVAL (x) & 0xff));
;
fprintf (asm_out_file, " /* char */\n");
return true;
case 2:
fprintf (asm_out_file, TARGET_UNIX_ASM ? "\t" : "\t.word\t");
output_addr_const_pdp11 (asm_out_file, x);
fprintf (asm_out_file, " /* short */\n");
return true;
}
return default_assemble_integer (x, size, aligned_p);
}
/* register move costs, indexed by regs */
static const int move_costs[N_REG_CLASSES][N_REG_CLASSES] =
{
/* NO MUL GEN LFPU NLFPU FPU ALL */
/* NO */ { 0, 0, 0, 0, 0, 0, 0},
/* MUL */ { 0, 2, 2, 22, 22, 22, 22},
/* GEN */ { 0, 2, 2, 22, 22, 22, 22},
/* LFPU */ { 0, 22, 22, 2, 2, 2, 22},
/* NLFPU */ { 0, 22, 22, 2, 10, 10, 22},
/* FPU */ { 0, 22, 22, 2, 10, 10, 22},
/* ALL */ { 0, 22, 22, 22, 22, 22, 22}
} ;
/* -- note that some moves are tremendously expensive,
because they require lots of tricks! do we have to
charge the costs incurred by secondary reload class
-- as we do here with 10 -- or not ? */
static int
pdp11_register_move_cost (machine_mode mode ATTRIBUTE_UNUSED,
reg_class_t c1, reg_class_t c2)
{
return move_costs[(int)c1][(int)c2];
}
static bool
pdp11_rtx_costs (rtx x, int code, int outer_code ATTRIBUTE_UNUSED,
int opno ATTRIBUTE_UNUSED, int *total,
bool speed ATTRIBUTE_UNUSED)
{
switch (code)
{
case CONST_INT:
if (INTVAL (x) == 0 || INTVAL (x) == -1 || INTVAL (x) == 1)
{
*total = 0;
return true;
}
/* FALLTHRU */
case CONST:
case LABEL_REF:
case SYMBOL_REF:
/* Twice as expensive as REG. */
*total = 2;
return true;
case CONST_DOUBLE:
/* Twice (or 4 times) as expensive as 16 bit. */
*total = 4;
return true;
case MULT:
/* ??? There is something wrong in MULT because MULT is not
as cheap as total = 2 even if we can shift! */
/* If optimizing for size make mult etc cheap, but not 1, so when
in doubt the faster insn is chosen. */
if (optimize_size)
*total = COSTS_N_INSNS (2);
else
*total = COSTS_N_INSNS (11);
return false;
case DIV:
if (optimize_size)
*total = COSTS_N_INSNS (2);
else
*total = COSTS_N_INSNS (25);
return false;
case MOD:
if (optimize_size)
*total = COSTS_N_INSNS (2);
else
*total = COSTS_N_INSNS (26);
return false;
case ABS:
/* Equivalent to length, so same for optimize_size. */
*total = COSTS_N_INSNS (3);
return false;
case ZERO_EXTEND:
/* Only used for qi->hi. */
*total = COSTS_N_INSNS (1);
return false;
case SIGN_EXTEND:
if (GET_MODE (x) == HImode)
*total = COSTS_N_INSNS (1);
else if (GET_MODE (x) == SImode)
*total = COSTS_N_INSNS (6);
else
*total = COSTS_N_INSNS (2);
return false;
case ASHIFT:
case LSHIFTRT:
case ASHIFTRT:
if (optimize_size)
*total = COSTS_N_INSNS (1);
else if (GET_MODE (x) == QImode)
{
if (GET_CODE (XEXP (x, 1)) != CONST_INT)
*total = COSTS_N_INSNS (8); /* worst case */
else
*total = COSTS_N_INSNS (INTVAL (XEXP (x, 1)));
}
else if (GET_MODE (x) == HImode)
{
if (GET_CODE (XEXP (x, 1)) == CONST_INT)
{
if (abs (INTVAL (XEXP (x, 1))) == 1)
*total = COSTS_N_INSNS (1);
else
*total = COSTS_N_INSNS (2.5 + 0.5 * INTVAL (XEXP (x, 1)));
}
else
*total = COSTS_N_INSNS (10); /* worst case */
}
else if (GET_MODE (x) == SImode)
{
if (GET_CODE (XEXP (x, 1)) == CONST_INT)
*total = COSTS_N_INSNS (2.5 + 0.5 * INTVAL (XEXP (x, 1)));
else /* worst case */
*total = COSTS_N_INSNS (18);
}
return false;
default:
return false;
}
}
const char *
output_jump (enum rtx_code code, int inv, int length)
{
static int x = 0;
static char buf[1000];
const char *pos, *neg;
if (cc_prev_status.flags & CC_NO_OVERFLOW)
{
switch (code)
{
case GTU: code = GT; break;
case LTU: code = LT; break;
case GEU: code = GE; break;
case LEU: code = LE; break;
default: ;
}
}
switch (code)
{
case EQ: pos = "beq", neg = "bne"; break;
case NE: pos = "bne", neg = "beq"; break;
case GT: pos = "bgt", neg = "ble"; break;
case GTU: pos = "bhi", neg = "blos"; break;
case LT: pos = "blt", neg = "bge"; break;
case LTU: pos = "blo", neg = "bhis"; break;
case GE: pos = "bge", neg = "blt"; break;
case GEU: pos = "bhis", neg = "blo"; break;
case LE: pos = "ble", neg = "bgt"; break;
case LEU: pos = "blos", neg = "bhi"; break;
default: gcc_unreachable ();
}
#if 0
/* currently we don't need this, because the tstdf and cmpdf
copy the condition code immediately, and other float operations are not
yet recognized as changing the FCC - if so, then the length-cost of all
jump insns increases by one, because we have to potentially copy the
FCC! */
if (cc_status.flags & CC_IN_FPU)
output_asm_insn("cfcc", NULL);
#endif
switch (length)
{
case 2:
sprintf(buf, "%s %%l1", inv ? neg : pos);
return buf;
case 6:
sprintf(buf, "%s JMP_%d\n\tjmp %%l1\nJMP_%d:", inv ? pos : neg, x, x);
x++;
return buf;
default:
gcc_unreachable ();
}
}
void
notice_update_cc_on_set(rtx exp, rtx insn ATTRIBUTE_UNUSED)
{
if (GET_CODE (SET_DEST (exp)) == CC0)
{
cc_status.flags = 0;
cc_status.value1 = SET_DEST (exp);
cc_status.value2 = SET_SRC (exp);
}
else if (GET_CODE (SET_SRC (exp)) == CALL)
{
CC_STATUS_INIT;
}
else if (SET_DEST(exp) == pc_rtx)
{
/* jump */
}
else if (GET_MODE (SET_DEST(exp)) == HImode
|| GET_MODE (SET_DEST(exp)) == QImode)
{
cc_status.flags = GET_CODE (SET_SRC(exp)) == MINUS ? 0 : CC_NO_OVERFLOW;
cc_status.value1 = SET_SRC (exp);
cc_status.value2 = SET_DEST (exp);
if (cc_status.value1 && GET_CODE (cc_status.value1) == REG
&& cc_status.value2
&& reg_overlap_mentioned_p (cc_status.value1, cc_status.value2))
cc_status.value2 = 0;
if (cc_status.value1 && GET_CODE (cc_status.value1) == MEM
&& cc_status.value2
&& GET_CODE (cc_status.value2) == MEM)
cc_status.value2 = 0;
}
else
{
CC_STATUS_INIT;
}
}
int
simple_memory_operand(rtx op, machine_mode mode ATTRIBUTE_UNUSED)
{
rtx addr;
/* Eliminate non-memory operations */
if (GET_CODE (op) != MEM)
return FALSE;
#if 0
/* dword operations really put out 2 instructions, so eliminate them. */
if (GET_MODE_SIZE (GET_MODE (op)) > (HAVE_64BIT_P () ? 8 : 4))
return FALSE;
#endif
/* Decode the address now. */
indirection:
addr = XEXP (op, 0);
switch (GET_CODE (addr))
{
case REG:
/* (R0) - no extra cost */
return 1;
case PRE_DEC:
case POST_INC:
/* -(R0), (R0)+ - cheap! */
return 0;
case MEM:
/* cheap - is encoded in addressing mode info!
-- except for @(R0), which has to be @0(R0) !!! */
if (GET_CODE (XEXP (addr, 0)) == REG)
return 0;
op=addr;
goto indirection;
case CONST_INT:
case LABEL_REF:
case CONST:
case SYMBOL_REF:
/* @#address - extra cost */
return 0;
case PLUS:
/* X(R0) - extra cost */
return 0;
default:
break;
}
return FALSE;
}
/*
* output a block move:
*
* operands[0] ... to
* operands[1] ... from
* operands[2] ... length
* operands[3] ... alignment
* operands[4] ... scratch register
*/
const char *
output_block_move(rtx *operands)
{
static int count = 0;
char buf[200];
int unroll;
int lastbyte = 0;
/* Move of zero bytes is a NOP. */
if (operands[2] == const0_rtx)
return "";
/* Look for moves by small constant byte counts, those we'll
expand to straight line code. */
if (CONSTANT_P (operands[2]))
{
if (INTVAL (operands[2]) < 16
&& (!optimize_size || INTVAL (operands[2]) < 5)
&& INTVAL (operands[3]) == 1)
{
register int i;
for (i = 1; i <= INTVAL (operands[2]); i++)
output_asm_insn("movb (%1)+, (%0)+", operands);
return "";
}
else if (INTVAL(operands[2]) < 32
&& (!optimize_size || INTVAL (operands[2]) < 9)
&& INTVAL (operands[3]) >= 2)
{
register int i;
for (i = 1; i <= INTVAL (operands[2]) / 2; i++)
output_asm_insn ("mov (%1)+, (%0)+", operands);
if (INTVAL (operands[2]) & 1)
output_asm_insn ("movb (%1), (%0)", operands);
return "";
}
}
/* Ideally we'd look for moves that are multiples of 4 or 8
bytes and handle those by unrolling the move loop. That
makes for a lot of code if done at run time, but it's ok
for constant counts. Also, for variable counts we have
to worry about odd byte count with even aligned pointers.
On 11/40 and up we handle that case; on older machines
we don't and just use byte-wise moves all the time. */
if (CONSTANT_P (operands[2]) )
{
if (INTVAL (operands[3]) < 2)
unroll = 0;
else
{
lastbyte = INTVAL (operands[2]) & 1;
if (optimize_size || INTVAL (operands[2]) & 2)
unroll = 1;
else if (INTVAL (operands[2]) & 4)
unroll = 2;
else
unroll = 3;
}
/* Loop count is byte count scaled by unroll. */
operands[2] = GEN_INT (INTVAL (operands[2]) >> unroll);
output_asm_insn ("mov %2, %4", operands);
}
else
{
/* Variable byte count; use the input register
as the scratch. */
operands[4] = operands[2];
/* Decide whether to move by words, and check
the byte count for zero. */
if (TARGET_40_PLUS && INTVAL (operands[3]) > 1)
{
unroll = 1;
output_asm_insn ("asr %4", operands);
}
else
{
unroll = 0;
output_asm_insn ("tst %4", operands);
}
sprintf (buf, "beq movestrhi%d", count + 1);
output_asm_insn (buf, NULL);
}
/* Output the loop label. */
sprintf (buf, "\nmovestrhi%d:", count);
output_asm_insn (buf, NULL);
/* Output the appropriate move instructions. */
switch (unroll)
{
case 0:
output_asm_insn ("movb (%1)+, (%0)+", operands);
break;
case 1:
output_asm_insn ("mov (%1)+, (%0)+", operands);
break;
case 2:
output_asm_insn ("mov (%1)+, (%0)+", operands);
output_asm_insn ("mov (%1)+, (%0)+", operands);
break;
default:
output_asm_insn ("mov (%1)+, (%0)+", operands);
output_asm_insn ("mov (%1)+, (%0)+", operands);
output_asm_insn ("mov (%1)+, (%0)+", operands);
output_asm_insn ("mov (%1)+, (%0)+", operands);
break;
}
/* Output the decrement and test. */
if (TARGET_40_PLUS)
{
sprintf (buf, "sob %%4, movestrhi%d", count);
output_asm_insn (buf, operands);
}
else
{
output_asm_insn ("dec %4", operands);
sprintf (buf, "bgt movestrhi%d", count);
output_asm_insn (buf, NULL);
}
count ++;
/* If constant odd byte count, move the last byte. */
if (lastbyte)
output_asm_insn ("movb (%1), (%0)", operands);
else if (!CONSTANT_P (operands[2]))
{
/* Output the destination label for the zero byte count check. */
sprintf (buf, "\nmovestrhi%d:", count);
output_asm_insn (buf, NULL);
count++;
/* If we did word moves, check for trailing last byte. */
if (unroll)
{
sprintf (buf, "bcc movestrhi%d", count);
output_asm_insn (buf, NULL);
output_asm_insn ("movb (%1), (%0)", operands);
sprintf (buf, "\nmovestrhi%d:", count);
output_asm_insn (buf, NULL);
count++;
}
}
return "";
}
/* This function checks whether a real value can be encoded as
a literal, i.e., addressing mode 27. In that mode, real values
are one word values, so the remaining 48 bits have to be zero. */
int
legitimate_const_double_p (rtx address)
{
REAL_VALUE_TYPE r;
long sval[2];
REAL_VALUE_FROM_CONST_DOUBLE (r, address);
REAL_VALUE_TO_TARGET_DOUBLE (r, sval);
if ((sval[0] & 0xffff) == 0 && sval[1] == 0)
return 1;
return 0;
}
/* Implement CANNOT_CHANGE_MODE_CLASS. */
bool
pdp11_cannot_change_mode_class (machine_mode from,
machine_mode to,
enum reg_class rclass)
{
/* Also, FPU registers contain a whole float value and the parts of
it are not separately accessible.
So we disallow all mode changes involving FPRs. */
if (FLOAT_MODE_P (from) != FLOAT_MODE_P (to))
return true;
return reg_classes_intersect_p (FPU_REGS, rclass);
}
/* TARGET_PREFERRED_RELOAD_CLASS
Given an rtx X being reloaded into a reg required to be
in class CLASS, return the class of reg to actually use.
In general this is just CLASS; but on some machines
in some cases it is preferable to use a more restrictive class.
loading is easier into LOAD_FPU_REGS than FPU_REGS! */
static reg_class_t
pdp11_preferred_reload_class (rtx x, reg_class_t rclass)
{
if (rclass == FPU_REGS)
return LOAD_FPU_REGS;
if (rclass == ALL_REGS)
{
if (FLOAT_MODE_P (GET_MODE (x)))
return LOAD_FPU_REGS;
else
return GENERAL_REGS;
}
return rclass;
}
/* TARGET_PREFERRED_OUTPUT_RELOAD_CLASS
Given an rtx X being reloaded into a reg required to be
in class CLASS, return the class of reg to actually use.
In general this is just CLASS; but on some machines
in some cases it is preferable to use a more restrictive class.
loading is easier into LOAD_FPU_REGS than FPU_REGS! */
static reg_class_t
pdp11_preferred_output_reload_class (rtx x, reg_class_t rclass)
{
if (rclass == FPU_REGS)
return LOAD_FPU_REGS;
if (rclass == ALL_REGS)
{
if (FLOAT_MODE_P (GET_MODE (x)))
return LOAD_FPU_REGS;
else
return GENERAL_REGS;
}
return rclass;
}
/* TARGET_SECONDARY_RELOAD.
FPU registers AC4 and AC5 (class NO_LOAD_FPU_REGS) require an
intermediate register (AC0-AC3: LOAD_FPU_REGS). Everything else
can be loade/stored directly. */
static reg_class_t
pdp11_secondary_reload (bool in_p ATTRIBUTE_UNUSED,
rtx x,
reg_class_t reload_class,
machine_mode reload_mode ATTRIBUTE_UNUSED,
secondary_reload_info *sri ATTRIBUTE_UNUSED)
{
if (reload_class != NO_LOAD_FPU_REGS || GET_CODE (x) != REG ||
REGNO_REG_CLASS (REGNO (x)) == LOAD_FPU_REGS)
return NO_REGS;
return LOAD_FPU_REGS;
}
/* Target routine to check if register to register move requires memory.
The answer is yes if we're going between general register and FPU
registers. The mode doesn't matter in making this check.
*/
bool
pdp11_secondary_memory_needed (reg_class_t c1, reg_class_t c2,
machine_mode mode ATTRIBUTE_UNUSED)
{
int fromfloat = (c1 == LOAD_FPU_REGS || c1 == NO_LOAD_FPU_REGS ||
c1 == FPU_REGS);
int tofloat = (c2 == LOAD_FPU_REGS || c2 == NO_LOAD_FPU_REGS ||
c2 == FPU_REGS);
return (fromfloat != tofloat);
}
/* TARGET_LEGITIMATE_ADDRESS_P recognizes an RTL expression
that is a valid memory address for an instruction.
The MODE argument is the machine mode for the MEM expression
that wants to use this address.
*/
static bool
pdp11_legitimate_address_p (machine_mode mode,
rtx operand, bool strict)
{
rtx xfoob;
/* accept @#address */
if (CONSTANT_ADDRESS_P (operand))
return true;
switch (GET_CODE (operand))
{
case REG:
/* accept (R0) */
return !strict || REGNO_OK_FOR_BASE_P (REGNO (operand));
case PLUS:
/* accept X(R0) */
return GET_CODE (XEXP (operand, 0)) == REG
&& (!strict || REGNO_OK_FOR_BASE_P (REGNO (XEXP (operand, 0))))
&& CONSTANT_ADDRESS_P (XEXP (operand, 1));
case PRE_DEC:
/* accept -(R0) */
return GET_CODE (XEXP (operand, 0)) == REG
&& (!strict || REGNO_OK_FOR_BASE_P (REGNO (XEXP (operand, 0))));
case POST_INC:
/* accept (R0)+ */
return GET_CODE (XEXP (operand, 0)) == REG
&& (!strict || REGNO_OK_FOR_BASE_P (REGNO (XEXP (operand, 0))));
case PRE_MODIFY:
/* accept -(SP) -- which uses PRE_MODIFY for byte mode */
return GET_CODE (XEXP (operand, 0)) == REG
&& REGNO (XEXP (operand, 0)) == STACK_POINTER_REGNUM
&& GET_CODE ((xfoob = XEXP (operand, 1))) == PLUS
&& GET_CODE (XEXP (xfoob, 0)) == REG
&& REGNO (XEXP (xfoob, 0)) == STACK_POINTER_REGNUM
&& CONSTANT_P (XEXP (xfoob, 1))
&& INTVAL (XEXP (xfoob,1)) == -2;
case POST_MODIFY:
/* accept (SP)+ -- which uses POST_MODIFY for byte mode */
return GET_CODE (XEXP (operand, 0)) == REG
&& REGNO (XEXP (operand, 0)) == STACK_POINTER_REGNUM
&& GET_CODE ((xfoob = XEXP (operand, 1))) == PLUS
&& GET_CODE (XEXP (xfoob, 0)) == REG
&& REGNO (XEXP (xfoob, 0)) == STACK_POINTER_REGNUM
&& CONSTANT_P (XEXP (xfoob, 1))
&& INTVAL (XEXP (xfoob,1)) == 2;
case MEM:
/* handle another level of indirection ! */
xfoob = XEXP (operand, 0);
/* (MEM:xx (MEM:xx ())) is not valid for SI, DI and currently
also forbidden for float, because we have to handle this
in output_move_double and/or output_move_quad() - we could
do it, but currently it's not worth it!!!
now that DFmode cannot go into CPU register file,
maybe I should allow float ...
but then I have to handle memory-to-memory moves in movdf ?? */
if (GET_MODE_BITSIZE(mode) > 16)
return false;
/* accept @address */
if (CONSTANT_ADDRESS_P (xfoob))
return true;
switch (GET_CODE (xfoob))
{
case REG:
/* accept @(R0) - which is @0(R0) */
return !strict || REGNO_OK_FOR_BASE_P(REGNO (xfoob));
case PLUS:
/* accept @X(R0) */
return GET_CODE (XEXP (xfoob, 0)) == REG
&& (!strict || REGNO_OK_FOR_BASE_P (REGNO (XEXP (xfoob, 0))))
&& CONSTANT_ADDRESS_P (XEXP (xfoob, 1));
case PRE_DEC:
/* accept @-(R0) */
return GET_CODE (XEXP (xfoob, 0)) == REG
&& (!strict || REGNO_OK_FOR_BASE_P (REGNO (XEXP (xfoob, 0))));
case POST_INC:
/* accept @(R0)+ */
return GET_CODE (XEXP (xfoob, 0)) == REG
&& (!strict || REGNO_OK_FOR_BASE_P (REGNO (XEXP (xfoob, 0))));
default:
/* anything else is invalid */
return false;
}
default:
/* anything else is invalid */
return false;
}
}
/* Return the class number of the smallest class containing
reg number REGNO. */
enum reg_class
pdp11_regno_reg_class (int regno)
{
if (regno == FRAME_POINTER_REGNUM || regno == ARG_POINTER_REGNUM)
return GENERAL_REGS;
else if (regno > AC3_REGNUM)
return NO_LOAD_FPU_REGS;
else if (regno >= AC0_REGNUM)
return LOAD_FPU_REGS;
else if (regno & 1)
return MUL_REGS;
else
return GENERAL_REGS;
}
int
pdp11_sp_frame_offset (void)
{
int offset = 0, regno;
offset = get_frame_size();
for (regno = 0; regno <= PC_REGNUM; regno++)
if (pdp11_saved_regno (regno))
offset += 2;
for (regno = AC0_REGNUM; regno <= AC5_REGNUM; regno++)
if (pdp11_saved_regno (regno))
offset += 8;
return offset;
}
/* Return the offset between two registers, one to be eliminated, and the other
its replacement, at the start of a routine. */
int
pdp11_initial_elimination_offset (int from, int to)
{
int spoff;
if (from == ARG_POINTER_REGNUM && to == HARD_FRAME_POINTER_REGNUM)
return 4;
else if (from == FRAME_POINTER_REGNUM
&& to == HARD_FRAME_POINTER_REGNUM)
return 0;
else
{
gcc_assert (to == STACK_POINTER_REGNUM);
/* Get the size of the register save area. */
spoff = pdp11_sp_frame_offset ();
if (from == FRAME_POINTER_REGNUM)
return spoff;
gcc_assert (from == ARG_POINTER_REGNUM);
/* If there is a frame pointer, that is saved too. */
if (frame_pointer_needed)
spoff += 2;
/* Account for the saved PC in the function call. */
return spoff + 2;
}
}
/* A copy of output_addr_const modified for pdp11 expression syntax.
output_addr_const also gets called for %cDIGIT and %nDIGIT, which we don't
use, and for debugging output, which we don't support with this port either.
So this copy should get called whenever needed.
*/
void
output_addr_const_pdp11 (FILE *file, rtx x)
{
char buf[256];
int i;
restart:
switch (GET_CODE (x))
{
case PC:
gcc_assert (flag_pic);
putc ('.', file);
break;
case SYMBOL_REF:
assemble_name (file, XSTR (x, 0));
break;
case LABEL_REF:
ASM_GENERATE_INTERNAL_LABEL (buf, "L", CODE_LABEL_NUMBER (XEXP (x, 0)));
assemble_name (file, buf);
break;
case CODE_LABEL:
ASM_GENERATE_INTERNAL_LABEL (buf, "L", CODE_LABEL_NUMBER (x));
assemble_name (file, buf);
break;
case CONST_INT:
i = INTVAL (x);
if (i < 0)
{
i = -i;
fprintf (file, "-");
}
fprintf (file, "%#o", i & 0xffff);
break;
case CONST:
/* This used to output parentheses around the expression,
but that does not work on the 386 (either ATT or BSD assembler). */
output_addr_const_pdp11 (file, XEXP (x, 0));
break;
case CONST_DOUBLE:
if (GET_MODE (x) == VOIDmode)
{
/* We can use %o if the number is one word and positive. */
gcc_assert (!CONST_DOUBLE_HIGH (x));
fprintf (file, "%#ho", (unsigned short) CONST_DOUBLE_LOW (x));
}
else
/* We can't handle floating point constants;
PRINT_OPERAND must handle them. */
output_operand_lossage ("floating constant misused");
break;
case PLUS:
/* Some assemblers need integer constants to appear last (e.g. masm). */
if (GET_CODE (XEXP (x, 0)) == CONST_INT)
{
output_addr_const_pdp11 (file, XEXP (x, 1));
if (INTVAL (XEXP (x, 0)) >= 0)
fprintf (file, "+");
output_addr_const_pdp11 (file, XEXP (x, 0));
}
else
{
output_addr_const_pdp11 (file, XEXP (x, 0));
if (INTVAL (XEXP (x, 1)) >= 0)
fprintf (file, "+");
output_addr_const_pdp11 (file, XEXP (x, 1));
}
break;
case MINUS:
/* Avoid outputting things like x-x or x+5-x,
since some assemblers can't handle that. */
x = simplify_subtraction (x);
if (GET_CODE (x) != MINUS)
goto restart;
output_addr_const_pdp11 (file, XEXP (x, 0));
if (GET_CODE (XEXP (x, 1)) != CONST_INT
|| INTVAL (XEXP (x, 1)) >= 0)
fprintf (file, "-");
output_addr_const_pdp11 (file, XEXP (x, 1));
break;
case ZERO_EXTEND:
case SIGN_EXTEND:
output_addr_const_pdp11 (file, XEXP (x, 0));
break;
default:
output_operand_lossage ("invalid expression as operand");
}
}
/* Worker function for TARGET_RETURN_IN_MEMORY. */
static bool
pdp11_return_in_memory (const_tree type, const_tree fntype ATTRIBUTE_UNUSED)
{
/* Integers 32 bits and under, and scalar floats (if FPU), are returned
in registers. The rest go into memory. */
return (TYPE_MODE (type) == DImode
|| (FLOAT_MODE_P (TYPE_MODE (type)) && ! TARGET_AC0)
|| TREE_CODE (type) == VECTOR_TYPE
|| COMPLEX_MODE_P (TYPE_MODE (type)));
}
/* Worker function for TARGET_FUNCTION_VALUE.
On the pdp11 the value is found in R0 (or ac0??? not without FPU!!!! ) */
static rtx
pdp11_function_value (const_tree valtype,
const_tree fntype_or_decl ATTRIBUTE_UNUSED,
bool outgoing ATTRIBUTE_UNUSED)
{
return gen_rtx_REG (TYPE_MODE (valtype),
BASE_RETURN_VALUE_REG(TYPE_MODE(valtype)));
}
/* Worker function for TARGET_LIBCALL_VALUE. */
static rtx
pdp11_libcall_value (machine_mode mode,
const_rtx fun ATTRIBUTE_UNUSED)
{
return gen_rtx_REG (mode, BASE_RETURN_VALUE_REG(mode));
}
/* Worker function for TARGET_FUNCTION_VALUE_REGNO_P.
On the pdp, the first "output" reg is the only register thus used.
maybe ac0 ? - as option someday! */
static bool
pdp11_function_value_regno_p (const unsigned int regno)
{
return (regno == RETVAL_REGNUM) || (TARGET_AC0 && (regno == AC0_REGNUM));
}
/* Worker function for TARGET_TRAMPOLINE_INIT.
trampoline - how should i do it in separate i+d ?
have some allocate_trampoline magic???
the following should work for shared I/D:
MOV #STATIC, $4 01270Y 0x0000 <- STATIC; Y = STATIC_CHAIN_REGNUM
JMP @#FUNCTION 000137 0x0000 <- FUNCTION
*/
static void
pdp11_trampoline_init (rtx m_tramp, tree fndecl, rtx chain_value)
{
rtx fnaddr = XEXP (DECL_RTL (fndecl), 0);
rtx mem;
gcc_assert (!TARGET_SPLIT);
mem = adjust_address (m_tramp, HImode, 0);
emit_move_insn (mem, GEN_INT (012700+STATIC_CHAIN_REGNUM));
mem = adjust_address (m_tramp, HImode, 2);
emit_move_insn (mem, chain_value);
mem = adjust_address (m_tramp, HImode, 4);
emit_move_insn (mem, GEN_INT (000137));
emit_move_insn (mem, fnaddr);
}
/* Worker function for TARGET_FUNCTION_ARG.
Determine where to put an argument to a function.
Value is zero to push the argument on the stack,
or a hard register in which to store the argument.
MODE is the argument's machine mode.
TYPE is the data type of the argument (as a tree).
This is null for libcalls where that information may
not be available.
CUM is a variable of type CUMULATIVE_ARGS which gives info about
the preceding args and about the function being called.
NAMED is nonzero if this argument is a named parameter
(otherwise it is an extra parameter matching an ellipsis). */
static rtx
pdp11_function_arg (cumulative_args_t cum ATTRIBUTE_UNUSED,
machine_mode mode ATTRIBUTE_UNUSED,
const_tree type ATTRIBUTE_UNUSED,
bool named ATTRIBUTE_UNUSED)
{
return NULL_RTX;
}
/* Worker function for TARGET_FUNCTION_ARG_ADVANCE.
Update the data in CUM to advance over an argument of mode MODE and
data type TYPE. (TYPE is null for libcalls where that information
may not be available.) */
static void
pdp11_function_arg_advance (cumulative_args_t cum_v, machine_mode mode,
const_tree type, bool named ATTRIBUTE_UNUSED)
{
CUMULATIVE_ARGS *cum = get_cumulative_args (cum_v);
*cum += (mode != BLKmode
? GET_MODE_SIZE (mode)
: int_size_in_bytes (type));
}
/* Make sure everything's fine if we *don't* have an FPU.
This assumes that putting a register in fixed_regs will keep the
compiler's mitts completely off it. We don't bother to zero it out
of register classes. Also fix incompatible register naming with
the UNIX assembler. */
static void
pdp11_conditional_register_usage (void)
{
int i;
HARD_REG_SET x;
if (!TARGET_FPU)
{
COPY_HARD_REG_SET (x, reg_class_contents[(int)FPU_REGS]);
for (i = 0; i < FIRST_PSEUDO_REGISTER; i++ )
if (TEST_HARD_REG_BIT (x, i))
fixed_regs[i] = call_used_regs[i] = 1;
}
if (TARGET_AC0)
call_used_regs[AC0_REGNUM] = 1;
if (TARGET_UNIX_ASM)
{
/* Change names of FPU registers for the UNIX assembler. */
reg_names[8] = "fr0";
reg_names[9] = "fr1";
reg_names[10] = "fr2";
reg_names[11] = "fr3";
reg_names[12] = "fr4";
reg_names[13] = "fr5";
}
}
static section *
pdp11_function_section (tree decl ATTRIBUTE_UNUSED,
enum node_frequency freq ATTRIBUTE_UNUSED,
bool startup ATTRIBUTE_UNUSED,
bool exit ATTRIBUTE_UNUSED)
{
return NULL;
}
/* Implement TARGET_LEGITIMATE_CONSTANT_P. */
static bool
pdp11_legitimate_constant_p (machine_mode mode ATTRIBUTE_UNUSED, rtx x)
{
return GET_CODE (x) != CONST_DOUBLE || legitimate_const_double_p (x);
}
/* Implement TARGET_SCALAR_MODE_SUPPORTED_P. */
static bool
pdp11_scalar_mode_supported_p (machine_mode mode)
{
/* Support SFmode even with -mfloat64. */
if (mode == SFmode)
return true;
return default_scalar_mode_supported_p (mode);
}
struct gcc_target targetm = TARGET_INITIALIZER;