unsigned immisreg : 1; /* .imm field is a second register. */
unsigned isscalar : 1; /* Operand is a (Neon) scalar. */
unsigned immisalign : 1; /* Immediate is an alignment specifier. */
+ unsigned immisfloat : 1; /* Immediate was parsed as a float. */
/* Note: we abuse "regisimm" to mean "is Neon register" in VMOV
instructions. This allows us to disambiguate ARM <-> vector insns. */
unsigned regisimm : 1; /* 64-bit immediate, reg forms high 32 bits. */
/* Parse an 8-bit "quarter-precision" floating point number of the form:
0baBbbbbbc defgh000 00000000 00000000.
- The minus-zero case needs special handling, since it can't be encoded in the
- "quarter-precision" float format, but can nonetheless be loaded as an integer
- constant. */
+ The zero and minus-zero cases need special handling, since they can't be
+ encoded in the "quarter-precision" float format, but can nonetheless be
+ loaded as integer constants. */
static unsigned
parse_qfloat_immediate (char **ccp, int *immed)
{
char *str = *ccp;
+ char *fpnum;
LITTLENUM_TYPE words[MAX_LITTLENUMS];
+ int found_fpchar = 0;
skip_past_char (&str, '#');
+ /* We must not accidentally parse an integer as a floating-point number. Make
+ sure that the value we parse is not an integer by checking for special
+ characters '.' or 'e'.
+ FIXME: This is a horrible hack, but doing better is tricky because type
+ information isn't in a very usable state at parse time. */
+ fpnum = str;
+ skip_whitespace (fpnum);
+
+ if (strncmp (fpnum, "0x", 2) == 0)
+ return FAIL;
+ else
+ {
+ for (; *fpnum != '\0' && *fpnum != ' ' && *fpnum != '\n'; fpnum++)
+ if (*fpnum == '.' || *fpnum == 'e' || *fpnum == 'E')
+ {
+ found_fpchar = 1;
+ break;
+ }
+
+ if (!found_fpchar)
+ return FAIL;
+ }
+
if ((str = atof_ieee (str, 's', words)) != NULL)
{
unsigned fpword = 0;
fpword |= words[i];
}
- if (is_quarter_float (fpword) || fpword == 0x80000000)
+ if (is_quarter_float (fpword) || (fpword & 0x7fffffff) == 0)
*immed = fpword;
else
return FAIL;
Case 3: VMOV<c><q>.<dt> <Dd>, #<float-imm>
Case 10: VMOV.F32 <Sd>, #<imm>
Case 11: VMOV.F64 <Dd>, #<imm> */
- ;
+ inst.operands[i].immisfloat = 1;
else if (parse_big_immediate (&ptr, i) == SUCCESS)
/* Case 2: VMOV<c><q>.<dt> <Qd>, #<imm>
Case 3: VMOV<c><q>.<dt> <Dd>, #<imm> */
OP_oRR, /* ARM register */
OP_oRRnpc, /* ARM register, not the PC */
+ OP_oRRw, /* ARM register, not r15, optional trailing ! */
OP_oRND, /* Optional Neon double precision register */
OP_oRNQ, /* Optional Neon quad precision register */
OP_oRNDQ, /* Optional Neon double or quad precision register */
backtrack_index = i;
}
- if (i > 0)
+ if (i > 0 && (i > 1 || inst.operands[0].present))
po_char_or_fail (',');
switch (upat[i])
break;
case OP_RRw:
+ case OP_oRRw:
po_reg_or_fail (REG_TYPE_RN);
if (skip_past_char (&str, '!') == SUCCESS)
inst.operands[i].writeback = 1;
case OP_RRnpc:
case OP_RRnpcb:
case OP_RRw:
+ case OP_oRRw:
case OP_RRnpc_I0:
if (inst.operands[i].isreg && inst.operands[i].reg == REG_PC)
inst.error = BAD_PC;
static void
do_mlas (void)
{
- /* This restriction does not apply to mls (nor to mla in v6, but
- that's hard to detect at present). */
+ /* This restriction does not apply to mls (nor to mla in v6 or later). */
if (inst.operands[0].reg == inst.operands[1].reg
+ && !ARM_CPU_HAS_FEATURE (selected_cpu, arm_ext_v6)
&& !(inst.instruction & 0x00400000))
- as_tsktsk (_("rd and rm should be different in mla"));
+ as_tsktsk (_("Rd and Rm should be different in mla"));
inst.instruction |= inst.operands[0].reg << 16;
inst.instruction |= inst.operands[1].reg;
inst.instruction |= inst.operands[2].reg << 8;
inst.instruction |= inst.operands[3].reg << 12;
-
}
static void
inst.instruction |= inst.operands[1].reg;
inst.instruction |= inst.operands[2].reg << 8;
- if (inst.operands[0].reg == inst.operands[1].reg)
- as_tsktsk (_("rd and rm should be different in mul"));
+ if (inst.operands[0].reg == inst.operands[1].reg
+ && !ARM_CPU_HAS_FEATURE (selected_cpu, arm_ext_v6))
+ as_tsktsk (_("Rd and Rm should be different in mul"));
}
/* Long Multiply Parser
inst.instruction |= inst.operands[2].reg << 8;
}
-/* ARM V6 srs (argument parse). */
+/* ARM V6 srs (argument parse). The variable fields in the encoding are
+ the same for both ARM and Thumb-2. */
static void
do_srs (void)
{
- inst.instruction |= inst.operands[0].imm;
- if (inst.operands[0].writeback)
+ int reg;
+
+ if (inst.operands[0].present)
+ {
+ reg = inst.operands[0].reg;
+ constraint (reg != 13, _("SRS base register must be r13"));
+ }
+ else
+ reg = 13;
+
+ inst.instruction |= reg << 16;
+ inst.instruction |= inst.operands[1].imm;
+ if (inst.operands[0].writeback || inst.operands[1].writeback)
inst.instruction |= WRITE_BACK;
}
inst.instruction |= cond << 4;
}
+/* Helper function used for both push/pop and ldm/stm. */
+static void
+encode_thumb2_ldmstm (int base, unsigned mask, bfd_boolean writeback)
+{
+ bfd_boolean load;
+
+ load = (inst.instruction & (1 << 20)) != 0;
+
+ if (mask & (1 << 13))
+ inst.error = _("SP not allowed in register list");
+ if (load)
+ {
+ if (mask & (1 << 14)
+ && mask & (1 << 15))
+ inst.error = _("LR and PC should not both be in register list");
+
+ if ((mask & (1 << base)) != 0
+ && writeback)
+ as_warn (_("base register should not be in register list "
+ "when written back"));
+ }
+ else
+ {
+ if (mask & (1 << 15))
+ inst.error = _("PC not allowed in register list");
+
+ if (mask & (1 << base))
+ as_warn (_("value stored for r%d is UNPREDICTABLE"), base);
+ }
+
+ if ((mask & (mask - 1)) == 0)
+ {
+ /* Single register transfers implemented as str/ldr. */
+ if (writeback)
+ {
+ if (inst.instruction & (1 << 23))
+ inst.instruction = 0x00000b04; /* ia! -> [base], #4 */
+ else
+ inst.instruction = 0x00000d04; /* db! -> [base, #-4]! */
+ }
+ else
+ {
+ if (inst.instruction & (1 << 23))
+ inst.instruction = 0x00800000; /* ia -> [base] */
+ else
+ inst.instruction = 0x00000c04; /* db -> [base, #-4] */
+ }
+
+ inst.instruction |= 0xf8400000;
+ if (load)
+ inst.instruction |= 0x00100000;
+
+ mask = ffs(mask) - 1;
+ mask <<= 12;
+ }
+ else if (writeback)
+ inst.instruction |= WRITE_BACK;
+
+ inst.instruction |= mask;
+ inst.instruction |= base << 16;
+}
+
static void
do_t_ldmstm (void)
{
if (unified_syntax)
{
+ bfd_boolean narrow;
+ unsigned mask;
+
+ narrow = FALSE;
/* See if we can use a 16-bit instruction. */
if (inst.instruction < 0xffff /* not ldmdb/stmdb */
&& inst.size_req != 4
- && inst.operands[0].reg <= 7
- && !(inst.operands[1].imm & ~0xff)
- && (inst.instruction == T_MNEM_stmia
- ? inst.operands[0].writeback
- : (inst.operands[0].writeback
- == !(inst.operands[1].imm & (1 << inst.operands[0].reg)))))
+ && !(inst.operands[1].imm & ~0xff))
{
- if (inst.instruction == T_MNEM_stmia
- && (inst.operands[1].imm & (1 << inst.operands[0].reg))
- && (inst.operands[1].imm & ((1 << inst.operands[0].reg) - 1)))
- as_warn (_("value stored for r%d is UNPREDICTABLE"),
- inst.operands[0].reg);
+ mask = 1 << inst.operands[0].reg;
- inst.instruction = THUMB_OP16 (inst.instruction);
- inst.instruction |= inst.operands[0].reg << 8;
- inst.instruction |= inst.operands[1].imm;
- }
- else
- {
- if (inst.operands[1].imm & (1 << 13))
- as_warn (_("SP should not be in register list"));
- if (inst.instruction == T_MNEM_stmia)
+ if (inst.operands[0].reg <= 7
+ && (inst.instruction == T_MNEM_stmia
+ ? inst.operands[0].writeback
+ : (inst.operands[0].writeback
+ == !(inst.operands[1].imm & mask))))
{
- if (inst.operands[1].imm & (1 << 15))
- as_warn (_("PC should not be in register list"));
- if (inst.operands[1].imm & (1 << inst.operands[0].reg))
+ if (inst.instruction == T_MNEM_stmia
+ && (inst.operands[1].imm & mask)
+ && (inst.operands[1].imm & (mask - 1)))
as_warn (_("value stored for r%d is UNPREDICTABLE"),
inst.operands[0].reg);
+
+ inst.instruction = THUMB_OP16 (inst.instruction);
+ inst.instruction |= inst.operands[0].reg << 8;
+ inst.instruction |= inst.operands[1].imm;
+ narrow = TRUE;
}
- else
+ else if (inst.operands[0] .reg == REG_SP
+ && inst.operands[0].writeback)
{
- if (inst.operands[1].imm & (1 << 14)
- && inst.operands[1].imm & (1 << 15))
- as_warn (_("LR and PC should not both be in register list"));
- if ((inst.operands[1].imm & (1 << inst.operands[0].reg))
- && inst.operands[0].writeback)
- as_warn (_("base register should not be in register list "
- "when written back"));
+ inst.instruction = THUMB_OP16 (inst.instruction == T_MNEM_stmia
+ ? T_MNEM_push : T_MNEM_pop);
+ inst.instruction |= inst.operands[1].imm;
+ narrow = TRUE;
}
+ }
+
+ if (!narrow)
+ {
if (inst.instruction < 0xffff)
inst.instruction = THUMB_OP32 (inst.instruction);
- inst.instruction |= inst.operands[0].reg << 16;
- inst.instruction |= inst.operands[1].imm;
- if (inst.operands[0].writeback)
- inst.instruction |= WRITE_BACK;
+
+ encode_thumb2_ldmstm(inst.operands[0].reg, inst.operands[1].imm,
+ inst.operands[0].writeback);
}
}
else
{
constraint (inst.operands[0].reg > 7
|| (inst.operands[1].imm & ~0xff), BAD_HIREG);
+ constraint (inst.instruction != T_MNEM_ldmia
+ && inst.instruction != T_MNEM_stmia,
+ _("Thumb-2 instruction only valid in unified syntax"));
if (inst.instruction == T_MNEM_stmia)
{
if (!inst.operands[0].writeback)
mask = inst.operands[0].imm;
if ((mask & ~0xff) == 0)
- inst.instruction = THUMB_OP16 (inst.instruction);
+ inst.instruction = THUMB_OP16 (inst.instruction) | mask;
else if ((inst.instruction == T_MNEM_push
&& (mask & ~0xff) == 1 << REG_LR)
|| (inst.instruction == T_MNEM_pop
{
inst.instruction = THUMB_OP16 (inst.instruction);
inst.instruction |= THUMB_PP_PC_LR;
- mask &= 0xff;
+ inst.instruction |= mask & 0xff;
}
else if (unified_syntax)
{
- if (mask & (1 << 13))
- inst.error = _("SP not allowed in register list");
- if (inst.instruction == T_MNEM_push)
- {
- if (mask & (1 << 15))
- inst.error = _("PC not allowed in register list");
- }
- else
- {
- if (mask & (1 << 14)
- && mask & (1 << 15))
- inst.error = _("LR and PC should not both be in register list");
- }
- if ((mask & (mask - 1)) == 0)
- {
- /* Single register push/pop implemented as str/ldr. */
- if (inst.instruction == T_MNEM_push)
- inst.instruction = 0xf84d0d04; /* str reg, [sp, #-4]! */
- else
- inst.instruction = 0xf85d0b04; /* ldr reg, [sp], #4 */
- mask = ffs(mask) - 1;
- mask <<= 12;
- }
- else
- inst.instruction = THUMB_OP32 (inst.instruction);
+ inst.instruction = THUMB_OP32 (inst.instruction);
+ encode_thumb2_ldmstm(13, mask, TRUE);
}
else
{
inst.error = _("invalid register list to push/pop instruction");
return;
}
-
- inst.instruction |= mask;
}
static void
inst.instruction |= Rs << 16;
if (!inst.operands[2].isreg)
{
- inst.instruction = (inst.instruction & 0xe1ffffff) | 0x10000000;
- inst.reloc.type = BFD_RELOC_ARM_T32_IMMEDIATE;
+ bfd_boolean narrow;
+
+ if ((inst.instruction & 0x00100000) != 0)
+ narrow = (current_it_mask == 0);
+ else
+ narrow = (current_it_mask != 0);
+
+ if (Rd > 7 || Rs > 7)
+ narrow = FALSE;
+
+ if (inst.size_req == 4 || !unified_syntax)
+ narrow = FALSE;
+
+ if (inst.reloc.exp.X_op != O_constant
+ || inst.reloc.exp.X_add_number != 0)
+ narrow = FALSE;
+
+ /* Turn rsb #0 into 16-bit neg. We should probably do this via
+ relaxation, but it doesn't seem worth the hassle. */
+ if (narrow)
+ {
+ inst.reloc.type = BFD_RELOC_UNUSED;
+ inst.instruction = THUMB_OP16 (T_MNEM_negs);
+ inst.instruction |= Rs << 3;
+ inst.instruction |= Rd;
+ }
+ else
+ {
+ inst.instruction = (inst.instruction & 0xe1ffffff) | 0x10000000;
+ inst.reloc.type = BFD_RELOC_ARM_T32_IMMEDIATE;
+ }
}
else
encode_thumb32_shifted_operand (2);
try smaller element sizes. */
static int
-neon_cmode_for_move_imm (unsigned immlo, unsigned immhi, unsigned *immbits,
- int *op, int size, enum neon_el_type type)
+neon_cmode_for_move_imm (unsigned immlo, unsigned immhi, int float_p,
+ unsigned *immbits, int *op, int size,
+ enum neon_el_type type)
{
+ /* Only permit float immediates (including 0.0/-0.0) if the operand type is
+ float. */
+ if (type == NT_float && !float_p)
+ return FAIL;
+
if (type == NT_float && is_quarter_float (immlo) && immhi == 0)
{
if (size != 32 || *op == 1)
struct neon_type_el et = neon_check_type (2, rs,
N_I8 | N_I16 | N_I32 | N_I64 | N_F32 | N_KEY, N_EQK);
unsigned immlo, immhi = 0, immbits;
- int op, cmode;
+ int op, cmode, float_p;
constraint (et.type == NT_invtype,
_("operand size must be specified for immediate VMOV"));
constraint (et.size < 32 && (immlo & ~((1 << et.size) - 1)) != 0,
_("immediate has bits set outside the operand size"));
- if ((cmode = neon_cmode_for_move_imm (immlo, immhi, &immbits, &op,
+ float_p = inst.operands[1].immisfloat;
+
+ if ((cmode = neon_cmode_for_move_imm (immlo, immhi, float_p, &immbits, &op,
et.size, et.type)) == FAIL)
{
/* Invert relevant bits only. */
with one or the other; those cases are caught by
neon_cmode_for_move_imm. */
op = !op;
- if ((cmode = neon_cmode_for_move_imm (immlo, immhi, &immbits, &op,
- et.size, et.type)) == FAIL)
+ if ((cmode = neon_cmode_for_move_imm (immlo, immhi, float_p, &immbits,
+ &op, et.size, et.type)) == FAIL)
{
first_error (_("immediate out of range"));
return;
struct neon_type_el et = neon_check_type (3, rs,
N_EQK, N_EQK, N_8 | N_16 | N_32 | N_64 | N_KEY);
unsigned imm = (inst.operands[3].imm * et.size) / 8;
+ constraint (imm >= (neon_quad (rs) ? 16 : 8), _("shift out of range"));
inst.instruction |= LOW4 (inst.operands[0].reg) << 12;
inst.instruction |= HI1 (inst.operands[0].reg) << 22;
inst.instruction |= LOW4 (inst.operands[1].reg) << 16;
return;
}
+ if (!ARM_CPU_HAS_FEATURE (variant, arm_ext_v6t2) && !inst.size_req)
+ {
+ /* Implicit require narrow instructions on Thumb-1. This avoids
+ relaxation accidentally introducing Thumb-2 instructions. */
+ if (opcode->tencode != do_t_blx && opcode->tencode != do_t_branch23)
+ inst.size_req = 2;
+ }
+
/* Check conditional suffixes. */
if (current_it_mask)
{
return;
}
}
+
+ /* Something has gone badly wrong if we try to relax a fixed size
+ instruction. */
+ assert (inst.size_req == 0 || !inst.relax);
+
ARM_MERGE_FEATURE_SETS (thumb_arch_used, thumb_arch_used,
*opcode->tvariant);
/* Many Thumb-2 instructions also have Thumb-1 variants, so explicitly
tCE(push, 92d0000, push, 1, (REGLST), push_pop, t_push_pop),
tCE(pop, 8bd0000, pop, 1, (REGLST), push_pop, t_push_pop),
+ /* These may simplify to neg. */
+ TCE(rsb, 0600000, ebc00000, 3, (RR, oRR, SH), arit, t_rsb),
+ TC3(rsbs, 0700000, ebd00000, 3, (RR, oRR, SH), arit, t_rsb),
+
#undef THUMB_VARIANT
#define THUMB_VARIANT &arm_ext_v6
TCE(cpy, 1a00000, 4600, 2, (RR, RR), rd_rm, t_cpy),
/* V1 instructions with no Thumb analogue prior to V6T2. */
#undef THUMB_VARIANT
#define THUMB_VARIANT &arm_ext_v6t2
- TCE(rsb, 0600000, ebc00000, 3, (RR, oRR, SH), arit, t_rsb),
- TC3(rsbs, 0700000, ebd00000, 3, (RR, oRR, SH), arit, t_rsb),
TCE(teq, 1300000, ea900f00, 2, (RR, SH), cmp, t_mvn_tst),
TC3w(teqs, 1300000, ea900f00, 2, (RR, SH), cmp, t_mvn_tst),
CL(teqp, 130f000, 2, (RR, SH), cmp),
TCE(smuadx, 700f030, fb20f010, 3, (RRnpc, RRnpc, RRnpc), smul, t_simd),
TCE(smusd, 700f050, fb40f000, 3, (RRnpc, RRnpc, RRnpc), smul, t_simd),
TCE(smusdx, 700f070, fb40f010, 3, (RRnpc, RRnpc, RRnpc), smul, t_simd),
- TUF(srsia, 8cd0500, e980c000, 1, (I31w), srs, srs),
- UF(srsib, 9cd0500, 1, (I31w), srs),
- UF(srsda, 84d0500, 1, (I31w), srs),
- TUF(srsdb, 94d0500, e800c000, 1, (I31w), srs, srs),
+ TUF(srsia, 8c00500, e980c000, 2, (oRRw, I31w), srs, srs),
+ UF(srsib, 9c00500, 2, (oRRw, I31w), srs),
+ UF(srsda, 8400500, 2, (oRRw, I31w), srs),
+ TUF(srsdb, 9400500, e800c000, 2, (oRRw, I31w), srs, srs),
TCE(ssat16, 6a00f30, f3200000, 3, (RRnpc, I16, RRnpc), ssat16, t_ssat16),
TCE(strex, 1800f90, e8400000, 3, (RRnpc, RRnpc, ADDR), strex, t_strex),
TCE(umaal, 0400090, fbe00060, 4, (RRnpc, RRnpc, RRnpc, RRnpc),smlal, t_mlal),
nUF(vmull, vmull, 3, (RNQ, RND, RND_RNSC), neon_vmull),
/* Extract. Size 8. */
- NUF(vext, 0b00000, 4, (RNDQ, oRNDQ, RNDQ, I7), neon_ext),
- NUF(vextq, 0b00000, 4, (RNQ, oRNQ, RNQ, I7), neon_ext),
+ NUF(vext, 0b00000, 4, (RNDQ, oRNDQ, RNDQ, I15), neon_ext),
+ NUF(vextq, 0b00000, 4, (RNQ, oRNQ, RNQ, I15), neon_ext),
/* Two registers, miscellaneous. */
/* Reverse. Sizes 8 16 32 (must be < size in opcode). */
as_bad_where (fixP->fx_file, fixP->fx_line,
_("invalid literal constant: pool needs to be closer"));
else
- as_bad (_("bad immediate value for half-word offset (%ld)"),
+ as_bad (_("bad immediate value for 8-bit offset (%ld)"),
(long) value);
break;
}
case BFD_RELOC_THUMB_PCREL_BRANCH7: /* CBZ */
/* CBZ can only branch forward. */
- if (value & ~0x7e)
- as_bad_where (fixP->fx_file, fixP->fx_line,
- _("branch out of range"));
- if (fixP->fx_done || !seg->use_rela_p)
+ /* Attempts to use CBZ to branch to the next instruction
+ (which, strictly speaking, are prohibited) will be turned into
+ no-ops.
+
+ FIXME: It may be better to remove the instruction completely and
+ perform relaxation. */
+ if (value == -2)
{
newval = md_chars_to_number (buf, THUMB_SIZE);
- newval |= ((value & 0x3e) << 2) | ((value & 0x40) << 3);
+ newval = 0xbf00; /* NOP encoding T1 */
md_number_to_chars (buf, newval, THUMB_SIZE);
}
+ else
+ {
+ if (value & ~0x7e)
+ as_bad_where (fixP->fx_file, fixP->fx_line,
+ _("branch out of range"));
+
+ if (fixP->fx_done || !seg->use_rela_p)
+ {
+ newval = md_chars_to_number (buf, THUMB_SIZE);
+ newval |= ((value & 0x3e) << 2) | ((value & 0x40) << 3);
+ md_number_to_chars (buf, newval, THUMB_SIZE);
+ }
+ }
break;
case BFD_RELOC_THUMB_PCREL_BRANCH9: /* Conditional branch. */