+static void
+emit_insn (struct arc_insn *insn)
+{
+ if (insn->relax)
+ emit_insn1 (insn);
+ else
+ emit_insn0 (insn, NULL, FALSE);
+}
+
+/* Check whether a symbol involves a register. */
+
+static bfd_boolean
+contains_register (symbolS *sym)
+{
+ if (sym)
+ {
+ expressionS *ex = symbol_get_value_expression (sym);
+
+ return ((O_register == ex->X_op)
+ && !contains_register (ex->X_add_symbol)
+ && !contains_register (ex->X_op_symbol));
+ }
+
+ return FALSE;
+}
+
+/* Returns the register number within a symbol. */
+
+static int
+get_register (symbolS *sym)
+{
+ if (!contains_register (sym))
+ return -1;
+
+ expressionS *ex = symbol_get_value_expression (sym);
+ return regno (ex->X_add_number);
+}
+
+/* Return true if a RELOC is generic. A generic reloc is PC-rel of a
+ simple ME relocation (e.g. RELOC_ARC_32_ME, BFD_RELOC_ARC_PC32. */
+
+static bfd_boolean
+generic_reloc_p (extended_bfd_reloc_code_real_type reloc)
+{
+ if (!reloc)
+ return FALSE;
+
+ switch (reloc)
+ {
+ case BFD_RELOC_ARC_SDA_LDST:
+ case BFD_RELOC_ARC_SDA_LDST1:
+ case BFD_RELOC_ARC_SDA_LDST2:
+ case BFD_RELOC_ARC_SDA16_LD:
+ case BFD_RELOC_ARC_SDA16_LD1:
+ case BFD_RELOC_ARC_SDA16_LD2:
+ case BFD_RELOC_ARC_SDA16_ST2:
+ case BFD_RELOC_ARC_SDA32_ME:
+ return FALSE;
+ default:
+ return TRUE;
+ }
+}
+
+/* Allocates a tok entry. */
+
+static int
+allocate_tok (expressionS *tok, int ntok, int cidx)
+{
+ if (ntok > MAX_INSN_ARGS - 2)
+ return 0; /* No space left. */
+
+ if (cidx > ntok)
+ return 0; /* Incorect args. */
+
+ memcpy (&tok[ntok+1], &tok[ntok], sizeof (*tok));
+
+ if (cidx == ntok)
+ return 1; /* Success. */
+ return allocate_tok (tok, ntok - 1, cidx);
+}
+
+/* Check if an particular ARC feature is enabled. */
+
+static bfd_boolean
+check_cpu_feature (insn_subclass_t sc)
+{
+ if (!(arc_features & ARC_CD)
+ && is_code_density_p (sc))
+ return FALSE;
+
+ if (!(arc_features & ARC_SPFP)
+ && is_spfp_p (sc))
+ return FALSE;
+
+ if (!(arc_features & ARC_DPFP)
+ && is_dpfp_p (sc))
+ return FALSE;
+
+ if (!(arc_features & ARC_FPUDA)
+ && is_fpuda_p (sc))
+ return FALSE;
+
+ return TRUE;
+}
+
+/* Search forward through all variants of an opcode looking for a
+ syntax match. */
+
+static const struct arc_opcode *
+find_opcode_match (const struct arc_opcode_hash_entry *entry,
+ expressionS *tok,
+ int *pntok,
+ struct arc_flags *first_pflag,
+ int nflgs,
+ int *pcpumatch)
+{
+ const struct arc_opcode *opcode;
+ struct arc_opcode_hash_entry_iterator iter;
+ int ntok = *pntok;
+ int got_cpu_match = 0;
+ expressionS bktok[MAX_INSN_ARGS];
+ int bkntok;
+ expressionS emptyE;
+
+ arc_opcode_hash_entry_iterator_init (&iter);
+ memset (&emptyE, 0, sizeof (emptyE));
+ memcpy (bktok, tok, MAX_INSN_ARGS * sizeof (*tok));
+ bkntok = ntok;
+
+ for (opcode = arc_opcode_hash_entry_iterator_next (entry, &iter);
+ opcode != NULL;
+ opcode = arc_opcode_hash_entry_iterator_next (entry, &iter))
+ {
+ const unsigned char *opidx;
+ const unsigned char *flgidx;
+ int tokidx = 0, lnflg, i;
+ const expressionS *t = &emptyE;
+
+ pr_debug ("%s:%d: find_opcode_match: trying opcode 0x%08X ",
+ frag_now->fr_file, frag_now->fr_line, opcode->opcode);
+
+ /* Don't match opcodes that don't exist on this
+ architecture. */
+ if (!(opcode->cpu & arc_target))
+ goto match_failed;
+
+ if (!check_cpu_feature (opcode->subclass))
+ goto match_failed;
+
+ got_cpu_match = 1;
+ pr_debug ("cpu ");
+
+ /* Check the operands. */
+ for (opidx = opcode->operands; *opidx; ++opidx)
+ {
+ const struct arc_operand *operand = &arc_operands[*opidx];
+
+ /* Only take input from real operands. */
+ if ((operand->flags & ARC_OPERAND_FAKE)
+ && !(operand->flags & ARC_OPERAND_BRAKET))
+ continue;
+
+ /* When we expect input, make sure we have it. */
+ if (tokidx >= ntok)
+ goto match_failed;
+
+ /* Match operand type with expression type. */
+ switch (operand->flags & ARC_OPERAND_TYPECHECK_MASK)
+ {
+ case ARC_OPERAND_IR:
+ /* Check to be a register. */
+ if ((tok[tokidx].X_op != O_register
+ || !is_ir_num (tok[tokidx].X_add_number))
+ && !(operand->flags & ARC_OPERAND_IGNORE))
+ goto match_failed;
+
+ /* If expect duplicate, make sure it is duplicate. */
+ if (operand->flags & ARC_OPERAND_DUPLICATE)
+ {
+ /* Check for duplicate. */
+ if (t->X_op != O_register
+ || !is_ir_num (t->X_add_number)
+ || (regno (t->X_add_number) !=
+ regno (tok[tokidx].X_add_number)))
+ goto match_failed;
+ }
+
+ /* Special handling? */
+ if (operand->insert)
+ {
+ const char *errmsg = NULL;
+ (*operand->insert)(0,
+ regno (tok[tokidx].X_add_number),
+ &errmsg);
+ if (errmsg)
+ {
+ if (operand->flags & ARC_OPERAND_IGNORE)
+ {
+ /* Missing argument, create one. */
+ if (!allocate_tok (tok, ntok - 1, tokidx))
+ goto match_failed;
+
+ tok[tokidx].X_op = O_absent;
+ ++ntok;
+ }
+ else
+ goto match_failed;
+ }
+ }
+
+ t = &tok[tokidx];
+ break;
+
+ case ARC_OPERAND_BRAKET:
+ /* Check if bracket is also in opcode table as
+ operand. */
+ if (tok[tokidx].X_op != O_bracket)
+ goto match_failed;
+ break;
+
+ case ARC_OPERAND_LIMM:
+ case ARC_OPERAND_SIGNED:
+ case ARC_OPERAND_UNSIGNED:
+ switch (tok[tokidx].X_op)
+ {
+ case O_illegal:
+ case O_absent:
+ case O_register:
+ goto match_failed;
+
+ case O_bracket:
+ /* Got an (too) early bracket, check if it is an
+ ignored operand. N.B. This procedure works only
+ when bracket is the last operand! */
+ if (!(operand->flags & ARC_OPERAND_IGNORE))
+ goto match_failed;
+ /* Insert the missing operand. */
+ if (!allocate_tok (tok, ntok - 1, tokidx))
+ goto match_failed;
+
+ tok[tokidx].X_op = O_absent;
+ ++ntok;
+ break;
+
+ case O_symbol:
+ {
+ const char *p;
+ const struct arc_aux_reg *auxr;
+
+ if (opcode->class != AUXREG)
+ goto de_fault;
+ p = S_GET_NAME (tok[tokidx].X_add_symbol);
+
+ auxr = hash_find (arc_aux_hash, p);
+ if (auxr)
+ {
+ /* We modify the token array here, safe in the
+ knowledge, that if this was the wrong
+ choice then the original contents will be
+ restored from BKTOK. */
+ tok[tokidx].X_op = O_constant;
+ tok[tokidx].X_add_number = auxr->address;
+ ARC_SET_FLAG (tok[tokidx].X_add_symbol, ARC_FLAG_AUX);
+ }
+
+ if (tok[tokidx].X_op != O_constant)
+ goto de_fault;
+ }
+ /* Fall-through */
+ case O_constant:
+ /* Check the range. */
+ if (operand->bits != 32
+ && !(operand->flags & ARC_OPERAND_NCHK))
+ {
+ offsetT min, max, val;
+ val = tok[tokidx].X_add_number;
+
+ if (operand->flags & ARC_OPERAND_SIGNED)
+ {
+ max = (1 << (operand->bits - 1)) - 1;
+ min = -(1 << (operand->bits - 1));
+ }
+ else
+ {
+ max = (1 << operand->bits) - 1;
+ min = 0;
+ }
+
+ if (val < min || val > max)
+ goto match_failed;
+
+ /* Check alignmets. */
+ if ((operand->flags & ARC_OPERAND_ALIGNED32)
+ && (val & 0x03))
+ goto match_failed;
+
+ if ((operand->flags & ARC_OPERAND_ALIGNED16)
+ && (val & 0x01))
+ goto match_failed;
+ }
+ else if (operand->flags & ARC_OPERAND_NCHK)
+ {
+ if (operand->insert)
+ {
+ const char *errmsg = NULL;
+ (*operand->insert)(0,
+ tok[tokidx].X_add_number,
+ &errmsg);
+ if (errmsg)
+ goto match_failed;
+ }
+ else
+ goto match_failed;
+ }
+ break;
+
+ case O_subtract:
+ /* Check if it is register range. */
+ if ((tok[tokidx].X_add_number == 0)
+ && contains_register (tok[tokidx].X_add_symbol)
+ && contains_register (tok[tokidx].X_op_symbol))
+ {
+ int regs;
+
+ regs = get_register (tok[tokidx].X_add_symbol);
+ regs <<= 16;
+ regs |= get_register (tok[tokidx].X_op_symbol);
+ if (operand->insert)
+ {
+ const char *errmsg = NULL;
+ (*operand->insert)(0,
+ regs,
+ &errmsg);
+ if (errmsg)
+ goto match_failed;
+ }
+ else
+ goto match_failed;
+ break;
+ }
+ default:
+ de_fault:
+ if (operand->default_reloc == 0)
+ goto match_failed; /* The operand needs relocation. */
+
+ /* Relocs requiring long immediate. FIXME! make it
+ generic and move it to a function. */
+ switch (tok[tokidx].X_md)
+ {
+ case O_gotoff:
+ case O_gotpc:
+ case O_pcl:
+ case O_tpoff:
+ case O_dtpoff:
+ case O_tlsgd:
+ case O_tlsie:
+ if (!(operand->flags & ARC_OPERAND_LIMM))
+ goto match_failed;
+ case O_absent:
+ if (!generic_reloc_p (operand->default_reloc))
+ goto match_failed;
+ default:
+ break;
+ }
+ break;
+ }
+ /* If expect duplicate, make sure it is duplicate. */
+ if (operand->flags & ARC_OPERAND_DUPLICATE)
+ {
+ if (t->X_op == O_illegal
+ || t->X_op == O_absent
+ || t->X_op == O_register
+ || (t->X_add_number != tok[tokidx].X_add_number))
+ goto match_failed;
+ }
+ t = &tok[tokidx];
+ break;
+
+ default:
+ /* Everything else should have been fake. */
+ abort ();
+ }
+
+ ++tokidx;
+ }
+ pr_debug ("opr ");
+
+ /* Setup ready for flag parsing. */
+ lnflg = nflgs;
+ for (i = 0; i < nflgs; i++)
+ first_pflag[i].flgp = NULL;
+
+ /* Check the flags. Iterate over the valid flag classes. */
+ for (flgidx = opcode->flags; *flgidx; ++flgidx)
+ {
+ /* Get a valid flag class. */
+ const struct arc_flag_class *cl_flags = &arc_flag_classes[*flgidx];
+ const unsigned *flgopridx;
+ int cl_matches = 0;
+ struct arc_flags *pflag = NULL;
+
+ /* Check for extension conditional codes. */
+ if (ext_condcode.arc_ext_condcode
+ && cl_flags->class & F_CLASS_EXTEND)
+ {
+ struct arc_flag_operand *pf = ext_condcode.arc_ext_condcode;
+ while (pf->name)
+ {
+ pflag = first_pflag;
+ for (i = 0; i < nflgs; i++, pflag++)
+ {
+ if (!strcmp (pf->name, pflag->name))
+ {
+ if (pflag->flgp != NULL)
+ goto match_failed;
+ /* Found it. */
+ cl_matches++;
+ pflag->flgp = pf;
+ lnflg--;
+ break;
+ }
+ }
+ pf++;
+ }
+ }
+
+ for (flgopridx = cl_flags->flags; *flgopridx; ++flgopridx)
+ {
+ const struct arc_flag_operand *flg_operand;
+
+ pflag = first_pflag;
+ flg_operand = &arc_flag_operands[*flgopridx];
+ for (i = 0; i < nflgs; i++, pflag++)
+ {
+ /* Match against the parsed flags. */
+ if (!strcmp (flg_operand->name, pflag->name))
+ {
+ if (pflag->flgp != NULL)
+ goto match_failed;
+ cl_matches++;
+ pflag->flgp = (struct arc_flag_operand *) flg_operand;
+ lnflg--;
+ break; /* goto next flag class and parsed flag. */
+ }
+ }
+ }
+
+ if ((cl_flags->class & F_CLASS_REQUIRED) && cl_matches == 0)
+ goto match_failed;
+ if ((cl_flags->class & F_CLASS_OPTIONAL) && cl_matches > 1)
+ goto match_failed;
+ }
+ /* Did I check all the parsed flags? */
+ if (lnflg)
+ goto match_failed;
+
+ pr_debug ("flg");
+ /* Possible match -- did we use all of our input? */
+ if (tokidx == ntok)
+ {
+ *pntok = ntok;
+ pr_debug ("\n");
+ return opcode;
+ }
+
+ match_failed:;
+ pr_debug ("\n");
+ /* Restore the original parameters. */
+ memcpy (tok, bktok, MAX_INSN_ARGS * sizeof (*tok));
+ ntok = bkntok;
+ }
+
+ if (*pcpumatch)
+ *pcpumatch = got_cpu_match;
+
+ return NULL;
+}
+
+/* Swap operand tokens. */
+
+static void
+swap_operand (expressionS *operand_array,
+ unsigned source,
+ unsigned destination)
+{
+ expressionS cpy_operand;
+ expressionS *src_operand;
+ expressionS *dst_operand;
+ size_t size;
+
+ if (source == destination)
+ return;
+
+ src_operand = &operand_array[source];
+ dst_operand = &operand_array[destination];
+ size = sizeof (expressionS);
+
+ /* Make copy of operand to swap with and swap. */
+ memcpy (&cpy_operand, dst_operand, size);
+ memcpy (dst_operand, src_operand, size);
+ memcpy (src_operand, &cpy_operand, size);
+}
+
+/* Check if *op matches *tok type.
+ Returns FALSE if they don't match, TRUE if they match. */
+
+static bfd_boolean
+pseudo_operand_match (const expressionS *tok,
+ const struct arc_operand_operation *op)
+{
+ offsetT min, max, val;
+ bfd_boolean ret;
+ const struct arc_operand *operand_real = &arc_operands[op->operand_idx];
+
+ ret = FALSE;
+ switch (tok->X_op)
+ {
+ case O_constant:
+ if (operand_real->bits == 32 && (operand_real->flags & ARC_OPERAND_LIMM))
+ ret = 1;
+ else if (!(operand_real->flags & ARC_OPERAND_IR))
+ {
+ val = tok->X_add_number + op->count;
+ if (operand_real->flags & ARC_OPERAND_SIGNED)
+ {
+ max = (1 << (operand_real->bits - 1)) - 1;
+ min = -(1 << (operand_real->bits - 1));
+ }
+ else
+ {
+ max = (1 << operand_real->bits) - 1;
+ min = 0;
+ }
+ if (min <= val && val <= max)
+ ret = TRUE;
+ }
+ break;
+
+ case O_symbol:
+ /* Handle all symbols as long immediates or signed 9. */
+ if (operand_real->flags & ARC_OPERAND_LIMM ||
+ ((operand_real->flags & ARC_OPERAND_SIGNED) && operand_real->bits == 9))
+ ret = TRUE;
+ break;
+
+ case O_register:
+ if (operand_real->flags & ARC_OPERAND_IR)
+ ret = TRUE;
+ break;
+
+ case O_bracket:
+ if (operand_real->flags & ARC_OPERAND_BRAKET)
+ ret = TRUE;
+ break;
+
+ default:
+ /* Unknown. */
+ break;
+ }
+ return ret;
+}
+
+/* Find pseudo instruction in array. */
+
+static const struct arc_pseudo_insn *
+find_pseudo_insn (const char *opname,
+ int ntok,
+ const expressionS *tok)
+{
+ const struct arc_pseudo_insn *pseudo_insn = NULL;
+ const struct arc_operand_operation *op;
+ unsigned int i;
+ int j;
+
+ for (i = 0; i < arc_num_pseudo_insn; ++i)
+ {
+ pseudo_insn = &arc_pseudo_insns[i];
+ if (strcmp (pseudo_insn->mnemonic_p, opname) == 0)
+ {
+ op = pseudo_insn->operand;
+ for (j = 0; j < ntok; ++j)
+ if (!pseudo_operand_match (&tok[j], &op[j]))
+ break;
+
+ /* Found the right instruction. */
+ if (j == ntok)
+ return pseudo_insn;
+ }
+ }
+ return NULL;
+}
+
+/* Assumes the expressionS *tok is of sufficient size. */
+
+static const struct arc_opcode_hash_entry *
+find_special_case_pseudo (const char *opname,
+ int *ntok,
+ expressionS *tok,
+ int *nflgs,
+ struct arc_flags *pflags)
+{
+ const struct arc_pseudo_insn *pseudo_insn = NULL;
+ const struct arc_operand_operation *operand_pseudo;
+ const struct arc_operand *operand_real;
+ unsigned i;
+ char construct_operand[MAX_CONSTR_STR];
+
+ /* Find whether opname is in pseudo instruction array. */
+ pseudo_insn = find_pseudo_insn (opname, *ntok, tok);
+
+ if (pseudo_insn == NULL)
+ return NULL;
+
+ /* Handle flag, Limited to one flag at the moment. */
+ if (pseudo_insn->flag_r != NULL)
+ *nflgs += tokenize_flags (pseudo_insn->flag_r, &pflags[*nflgs],
+ MAX_INSN_FLGS - *nflgs);
+
+ /* Handle operand operations. */
+ for (i = 0; i < pseudo_insn->operand_cnt; ++i)
+ {
+ operand_pseudo = &pseudo_insn->operand[i];
+ operand_real = &arc_operands[operand_pseudo->operand_idx];
+
+ if (operand_real->flags & ARC_OPERAND_BRAKET &&
+ !operand_pseudo->needs_insert)
+ continue;
+
+ /* Has to be inserted (i.e. this token does not exist yet). */
+ if (operand_pseudo->needs_insert)
+ {
+ if (operand_real->flags & ARC_OPERAND_BRAKET)
+ {
+ tok[i].X_op = O_bracket;
+ ++(*ntok);
+ continue;
+ }
+
+ /* Check if operand is a register or constant and handle it
+ by type. */
+ if (operand_real->flags & ARC_OPERAND_IR)
+ snprintf (construct_operand, MAX_CONSTR_STR, "r%d",
+ operand_pseudo->count);
+ else
+ snprintf (construct_operand, MAX_CONSTR_STR, "%d",
+ operand_pseudo->count);
+
+ tokenize_arguments (construct_operand, &tok[i], 1);
+ ++(*ntok);
+ }
+
+ else if (operand_pseudo->count)
+ {
+ /* Operand number has to be adjusted accordingly (by operand
+ type). */
+ switch (tok[i].X_op)
+ {
+ case O_constant:
+ tok[i].X_add_number += operand_pseudo->count;
+ break;
+
+ case O_symbol:
+ break;
+
+ default:
+ /* Ignored. */
+ break;
+ }
+ }
+ }
+
+ /* Swap operands if necessary. Only supports one swap at the
+ moment. */
+ for (i = 0; i < pseudo_insn->operand_cnt; ++i)
+ {
+ operand_pseudo = &pseudo_insn->operand[i];
+
+ if (operand_pseudo->swap_operand_idx == i)
+ continue;
+
+ swap_operand (tok, i, operand_pseudo->swap_operand_idx);
+
+ /* Prevent a swap back later by breaking out. */
+ break;
+ }
+
+ return arc_find_opcode (pseudo_insn->mnemonic_r);
+}
+
+static const struct arc_opcode_hash_entry *
+find_special_case_flag (const char *opname,
+ int *nflgs,
+ struct arc_flags *pflags)
+{
+ unsigned int i;
+ const char *flagnm;
+ unsigned flag_idx, flag_arr_idx;
+ size_t flaglen, oplen;
+ const struct arc_flag_special *arc_flag_special_opcode;
+ const struct arc_opcode_hash_entry *entry;
+
+ /* Search for special case instruction. */
+ for (i = 0; i < arc_num_flag_special; i++)
+ {
+ arc_flag_special_opcode = &arc_flag_special_cases[i];
+ oplen = strlen (arc_flag_special_opcode->name);
+
+ if (strncmp (opname, arc_flag_special_opcode->name, oplen) != 0)
+ continue;
+
+ /* Found a potential special case instruction, now test for
+ flags. */
+ for (flag_arr_idx = 0;; ++flag_arr_idx)
+ {
+ flag_idx = arc_flag_special_opcode->flags[flag_arr_idx];
+ if (flag_idx == 0)
+ break; /* End of array, nothing found. */
+
+ flagnm = arc_flag_operands[flag_idx].name;
+ flaglen = strlen (flagnm);
+ if (strcmp (opname + oplen, flagnm) == 0)
+ {
+ entry = arc_find_opcode (arc_flag_special_opcode->name);
+
+ if (*nflgs + 1 > MAX_INSN_FLGS)
+ break;
+ memcpy (pflags[*nflgs].name, flagnm, flaglen);
+ pflags[*nflgs].name[flaglen] = '\0';
+ (*nflgs)++;
+ return entry;
+ }
+ }
+ }
+ return NULL;
+}
+
+/* Used to find special case opcode. */
+
+static const struct arc_opcode_hash_entry *
+find_special_case (const char *opname,
+ int *nflgs,
+ struct arc_flags *pflags,
+ expressionS *tok,
+ int *ntok)
+{
+ const struct arc_opcode_hash_entry *entry;
+
+ entry = find_special_case_pseudo (opname, ntok, tok, nflgs, pflags);
+
+ if (entry == NULL)
+ entry = find_special_case_flag (opname, nflgs, pflags);
+
+ return entry;
+}
+
+/* Given an opcode name, pre-tockenized set of argumenst and the
+ opcode flags, take it all the way through emission. */
+
+static void
+assemble_tokens (const char *opname,
+ expressionS *tok,
+ int ntok,
+ struct arc_flags *pflags,
+ int nflgs)
+{
+ bfd_boolean found_something = FALSE;
+ const struct arc_opcode_hash_entry *entry;
+ int cpumatch = 1;
+
+ /* Search opcodes. */
+ entry = arc_find_opcode (opname);
+
+ /* Couldn't find opcode conventional way, try special cases. */
+ if (entry == NULL)
+ entry = find_special_case (opname, &nflgs, pflags, tok, &ntok);
+
+ if (entry != NULL)
+ {
+ const struct arc_opcode *opcode;
+
+ pr_debug ("%s:%d: assemble_tokens: %s\n",
+ frag_now->fr_file, frag_now->fr_line, opname);
+ found_something = TRUE;
+ opcode = find_opcode_match (entry, tok, &ntok, pflags,
+ nflgs, &cpumatch);
+ if (opcode != NULL)
+ {
+ struct arc_insn insn;
+
+ assemble_insn (opcode, tok, ntok, pflags, nflgs, &insn);
+ emit_insn (&insn);
+ return;
+ }
+ }
+
+ if (found_something)
+ {
+ if (cpumatch)
+ as_bad (_("inappropriate arguments for opcode '%s'"), opname);
+ else
+ as_bad (_("opcode '%s' not supported for target %s"), opname,
+ arc_target_name);
+ }
+ else
+ as_bad (_("unknown opcode '%s'"), opname);
+}
+
+/* The public interface to the instruction assembler. */
+
+void
+md_assemble (char *str)
+{
+ char *opname;
+ expressionS tok[MAX_INSN_ARGS];
+ int ntok, nflg;
+ size_t opnamelen;
+ struct arc_flags flags[MAX_INSN_FLGS];
+
+ /* Split off the opcode. */
+ opnamelen = strspn (str, "abcdefghijklmnopqrstuvwxyz_0123468");
+ opname = xmemdup0 (str, opnamelen);
+
+ /* Signalize we are assmbling the instructions. */
+ assembling_insn = TRUE;
+
+ /* Tokenize the flags. */
+ if ((nflg = tokenize_flags (str + opnamelen, flags, MAX_INSN_FLGS)) == -1)
+ {
+ as_bad (_("syntax error"));
+ return;
+ }
+
+ /* Scan up to the end of the mnemonic which must end in space or end
+ of string. */
+ str += opnamelen;
+ for (; *str != '\0'; str++)
+ if (*str == ' ')
+ break;
+
+ /* Tokenize the rest of the line. */
+ if ((ntok = tokenize_arguments (str, tok, MAX_INSN_ARGS)) < 0)
+ {
+ as_bad (_("syntax error"));
+ return;
+ }
+
+ /* Finish it off. */
+ assemble_tokens (opname, tok, ntok, flags, nflg);
+ assembling_insn = FALSE;
+}
+
+/* Callback to insert a register into the hash table. */
+
+static void
+declare_register (const char *name, int number)
+{
+ const char *err;
+ symbolS *regS = symbol_create (name, reg_section,
+ number, &zero_address_frag);
+
+ err = hash_insert (arc_reg_hash, S_GET_NAME (regS), (void *) regS);
+ if (err)
+ as_fatal (_("Inserting \"%s\" into register table failed: %s"),
+ name, err);
+}
+
+/* Construct symbols for each of the general registers. */
+
+static void
+declare_register_set (void)
+{
+ int i;
+ for (i = 0; i < 64; ++i)
+ {
+ char name[7];
+
+ sprintf (name, "r%d", i);
+ declare_register (name, i);
+ if ((i & 0x01) == 0)
+ {
+ sprintf (name, "r%dr%d", i, i+1);
+ declare_register (name, i);
+ }
+ }
+}
+
+/* Port-specific assembler initialization. This function is called
+ once, at assembler startup time. */
+
+void
+md_begin (void)
+{
+ const struct arc_opcode *opcode = arc_opcodes;
+
+ if (!mach_type_specified_p)
+ arc_select_cpu ("arc700");
+
+ /* The endianness can be chosen "at the factory". */
+ target_big_endian = byte_order == BIG_ENDIAN;
+
+ if (!bfd_set_arch_mach (stdoutput, bfd_arch_arc, arc_mach_type))
+ as_warn (_("could not set architecture and machine"));
+
+ /* Set elf header flags. */
+ bfd_set_private_flags (stdoutput, arc_eflag);
+
+ /* Set up a hash table for the instructions. */
+ arc_opcode_hash = hash_new ();
+ if (arc_opcode_hash == NULL)
+ as_fatal (_("Virtual memory exhausted"));
+
+ /* Initialize the hash table with the insns. */
+ do
+ {
+ const char *name = opcode->name;
+
+ arc_insert_opcode (opcode);
+
+ while (++opcode && opcode->name
+ && (opcode->name == name
+ || !strcmp (opcode->name, name)))
+ continue;
+ }while (opcode->name);
+
+ /* Register declaration. */
+ arc_reg_hash = hash_new ();
+ if (arc_reg_hash == NULL)
+ as_fatal (_("Virtual memory exhausted"));
+
+ declare_register_set ();
+ declare_register ("gp", 26);
+ declare_register ("fp", 27);
+ declare_register ("sp", 28);
+ declare_register ("ilink", 29);
+ declare_register ("ilink1", 29);
+ declare_register ("ilink2", 30);
+ declare_register ("blink", 31);
+
+ declare_register ("mlo", 57);
+ declare_register ("mmid", 58);
+ declare_register ("mhi", 59);
+
+ declare_register ("acc1", 56);
+ declare_register ("acc2", 57);
+
+ declare_register ("lp_count", 60);
+ declare_register ("pcl", 63);
+
+ /* Initialize the last instructions. */
+ memset (&arc_last_insns[0], 0, sizeof (arc_last_insns));
+
+ /* Aux register declaration. */
+ arc_aux_hash = hash_new ();
+ if (arc_aux_hash == NULL)
+ as_fatal (_("Virtual memory exhausted"));
+
+ const struct arc_aux_reg *auxr = &arc_aux_regs[0];
+ unsigned int i;
+ for (i = 0; i < arc_num_aux_regs; i++, auxr++)
+ {
+ const char *retval;
+
+ if (!(auxr->cpu & arc_target))
+ continue;
+
+ if ((auxr->subclass != NONE)
+ && !check_cpu_feature (auxr->subclass))
+ continue;
+
+ retval = hash_insert (arc_aux_hash, auxr->name, (void *) auxr);
+ if (retval)
+ as_fatal (_("internal error: can't hash aux register '%s': %s"),
+ auxr->name, retval);
+ }
+}
+
+/* Write a value out to the object file, using the appropriate
+ endianness. */
+
+void
+md_number_to_chars (char *buf,
+ valueT val,
+ int n)
+{
+ if (target_big_endian)
+ number_to_chars_bigendian (buf, val, n);
+ else
+ number_to_chars_littleendian (buf, val, n);
+}
+
+/* Round up a section size to the appropriate boundary. */
+
+valueT
+md_section_align (segT segment,
+ valueT size)
+{
+ int align = bfd_get_section_alignment (stdoutput, segment);
+
+ return ((size + (1 << align) - 1) & (-((valueT) 1 << align)));
+}
+
+/* The location from which a PC relative jump should be calculated,
+ given a PC relative reloc. */
+
+long
+md_pcrel_from_section (fixS *fixP,
+ segT sec)
+{
+ offsetT base = fixP->fx_where + fixP->fx_frag->fr_address;
+
+ pr_debug ("pcrel_from_section, fx_offset = %d\n", (int) fixP->fx_offset);
+
+ if (fixP->fx_addsy != (symbolS *) NULL
+ && (!S_IS_DEFINED (fixP->fx_addsy)
+ || S_GET_SEGMENT (fixP->fx_addsy) != sec))
+ {
+ pr_debug ("Unknown pcrel symbol: %s\n", S_GET_NAME (fixP->fx_addsy));
+
+ /* The symbol is undefined (or is defined but not in this section).
+ Let the linker figure it out. */
+ return 0;
+ }
+
+ if ((int) fixP->fx_r_type < 0)
+ {
+ /* These are the "internal" relocations. Align them to
+ 32 bit boundary (PCL), for the moment. */
+ base &= ~3;
+ }
+ else
+ {
+ switch (fixP->fx_r_type)
+ {
+ case BFD_RELOC_ARC_PC32:
+ /* The hardware calculates relative to the start of the
+ insn, but this relocation is relative to location of the
+ LIMM, compensate. The base always needs to be
+ substracted by 4 as we do not support this type of PCrel
+ relocation for short instructions. */
+ base -= 4;
+ /* Fall through. */
+ case BFD_RELOC_ARC_PLT32:
+ case BFD_RELOC_ARC_S25H_PCREL_PLT:
+ case BFD_RELOC_ARC_S21H_PCREL_PLT:
+ case BFD_RELOC_ARC_S25W_PCREL_PLT:
+ case BFD_RELOC_ARC_S21W_PCREL_PLT:
+
+ case BFD_RELOC_ARC_S21H_PCREL:
+ case BFD_RELOC_ARC_S25H_PCREL:
+ case BFD_RELOC_ARC_S13_PCREL:
+ case BFD_RELOC_ARC_S21W_PCREL:
+ case BFD_RELOC_ARC_S25W_PCREL:
+ base &= ~3;
+ break;
+ default:
+ as_bad_where (fixP->fx_file, fixP->fx_line,
+ _("unhandled reloc %s in md_pcrel_from_section"),
+ bfd_get_reloc_code_name (fixP->fx_r_type));
+ break;
+ }
+ }
+
+ pr_debug ("pcrel from %x + %lx = %x, symbol: %s (%x)\n",
+ fixP->fx_frag->fr_address, fixP->fx_where, base,
+ fixP->fx_addsy ? S_GET_NAME (fixP->fx_addsy) : "(null)",
+ fixP->fx_addsy ? S_GET_VALUE (fixP->fx_addsy) : 0);
+
+ return base;
+}
+
+/* Given a BFD relocation find the coresponding operand. */
+
+static const struct arc_operand *
+find_operand_for_reloc (extended_bfd_reloc_code_real_type reloc)
+{
+ unsigned i;
+
+ for (i = 0; i < arc_num_operands; i++)
+ if (arc_operands[i].default_reloc == reloc)
+ return &arc_operands[i];
+ return NULL;
+}
+
+/* Insert an operand value into an instruction. */
+
+static unsigned
+insert_operand (unsigned insn,
+ const struct arc_operand *operand,
+ offsetT val,
+ const char *file,
+ unsigned line)
+{
+ offsetT min = 0, max = 0;
+
+ if (operand->bits != 32
+ && !(operand->flags & ARC_OPERAND_NCHK)
+ && !(operand->flags & ARC_OPERAND_FAKE))
+ {
+ if (operand->flags & ARC_OPERAND_SIGNED)
+ {
+ max = (1 << (operand->bits - 1)) - 1;
+ min = -(1 << (operand->bits - 1));
+ }
+ else
+ {
+ max = (1 << operand->bits) - 1;
+ min = 0;
+ }
+
+ if (val < min || val > max)
+ as_bad_value_out_of_range (_("operand"),
+ val, min, max, file, line);
+ }
+
+ pr_debug ("insert field: %ld <= %ld <= %ld in 0x%08x\n",
+ min, val, max, insn);
+
+ if ((operand->flags & ARC_OPERAND_ALIGNED32)
+ && (val & 0x03))
+ as_bad_where (file, line,
+ _("Unaligned operand. Needs to be 32bit aligned"));
+
+ if ((operand->flags & ARC_OPERAND_ALIGNED16)
+ && (val & 0x01))
+ as_bad_where (file, line,
+ _("Unaligned operand. Needs to be 16bit aligned"));
+
+ if (operand->insert)
+ {
+ const char *errmsg = NULL;
+
+ insn = (*operand->insert) (insn, val, &errmsg);
+ if (errmsg)
+ as_warn_where (file, line, "%s", errmsg);
+ }
+ else
+ {
+ if (operand->flags & ARC_OPERAND_TRUNCATE)
+ {
+ if (operand->flags & ARC_OPERAND_ALIGNED32)
+ val >>= 2;
+ if (operand->flags & ARC_OPERAND_ALIGNED16)
+ val >>= 1;
+ }
+ insn |= ((val & ((1 << operand->bits) - 1)) << operand->shift);
+ }
+ return insn;
+}
+
+/* Apply a fixup to the object code. At this point all symbol values
+ should be fully resolved, and we attempt to completely resolve the
+ reloc. If we can not do that, we determine the correct reloc code
+ and put it back in the fixup. To indicate that a fixup has been
+ eliminated, set fixP->fx_done. */
+
+void
+md_apply_fix (fixS *fixP,
+ valueT *valP,
+ segT seg)
+{
+ char * const fixpos = fixP->fx_frag->fr_literal + fixP->fx_where;
+ valueT value = *valP;
+ unsigned insn = 0;
+ symbolS *fx_addsy, *fx_subsy;
+ offsetT fx_offset;
+ segT add_symbol_segment = absolute_section;
+ segT sub_symbol_segment = absolute_section;
+ const struct arc_operand *operand = NULL;
+ extended_bfd_reloc_code_real_type reloc;
+
+ pr_debug ("%s:%u: apply_fix: r_type=%d (%s) value=0x%lX offset=0x%lX\n",
+ fixP->fx_file, fixP->fx_line, fixP->fx_r_type,
+ ((int) fixP->fx_r_type < 0) ? "Internal":
+ bfd_get_reloc_code_name (fixP->fx_r_type), value,
+ fixP->fx_offset);
+
+ fx_addsy = fixP->fx_addsy;
+ fx_subsy = fixP->fx_subsy;
+ fx_offset = 0;
+
+ if (fx_addsy)
+ {
+ add_symbol_segment = S_GET_SEGMENT (fx_addsy);
+ }
+
+ if (fx_subsy
+ && fixP->fx_r_type != BFD_RELOC_ARC_TLS_DTPOFF
+ && fixP->fx_r_type != BFD_RELOC_ARC_TLS_DTPOFF_S9
+ && fixP->fx_r_type != BFD_RELOC_ARC_TLS_GD_LD)
+ {
+ resolve_symbol_value (fx_subsy);
+ sub_symbol_segment = S_GET_SEGMENT (fx_subsy);
+
+ if (sub_symbol_segment == absolute_section)
+ {
+ /* The symbol is really a constant. */
+ fx_offset -= S_GET_VALUE (fx_subsy);
+ fx_subsy = NULL;
+ }
+ else
+ {
+ as_bad_where (fixP->fx_file, fixP->fx_line,
+ _("can't resolve `%s' {%s section} - `%s' {%s section}"),
+ fx_addsy ? S_GET_NAME (fx_addsy) : "0",
+ segment_name (add_symbol_segment),
+ S_GET_NAME (fx_subsy),
+ segment_name (sub_symbol_segment));
+ return;
+ }
+ }
+
+ if (fx_addsy
+ && !S_IS_WEAK (fx_addsy))
+ {
+ if (add_symbol_segment == seg
+ && fixP->fx_pcrel)
+ {
+ value += S_GET_VALUE (fx_addsy);
+ value -= md_pcrel_from_section (fixP, seg);
+ fx_addsy = NULL;
+ fixP->fx_pcrel = FALSE;
+ }
+ else if (add_symbol_segment == absolute_section)
+ {
+ value = fixP->fx_offset;
+ fx_offset += S_GET_VALUE (fixP->fx_addsy);
+ fx_addsy = NULL;
+ fixP->fx_pcrel = FALSE;
+ }
+ }
+
+ if (!fx_addsy)
+ fixP->fx_done = TRUE;
+
+ if (fixP->fx_pcrel)
+ {
+ if (fx_addsy
+ && ((S_IS_DEFINED (fx_addsy)
+ && S_GET_SEGMENT (fx_addsy) != seg)
+ || S_IS_WEAK (fx_addsy)))
+ value += md_pcrel_from_section (fixP, seg);
+
+ switch (fixP->fx_r_type)
+ {
+ case BFD_RELOC_ARC_32_ME:
+ /* This is a pc-relative value in a LIMM. Adjust it to the
+ address of the instruction not to the address of the
+ LIMM. Note: it is not anylonger valid this afirmation as
+ the linker consider ARC_PC32 a fixup to entire 64 bit
+ insn. */
+ fixP->fx_offset += fixP->fx_frag->fr_address;
+ /* Fall through. */
+ case BFD_RELOC_32:
+ fixP->fx_r_type = BFD_RELOC_ARC_PC32;
+ /* Fall through. */
+ case BFD_RELOC_ARC_PC32:
+ /* fixP->fx_offset += fixP->fx_where - fixP->fx_dot_value; */
+ break;
+ default:
+ if ((int) fixP->fx_r_type < 0)
+ as_fatal (_("PC relative relocation not allowed for (internal) type %d"),
+ fixP->fx_r_type);
+ break;
+ }
+ }
+
+ pr_debug ("%s:%u: apply_fix: r_type=%d (%s) value=0x%lX offset=0x%lX\n",
+ fixP->fx_file, fixP->fx_line, fixP->fx_r_type,
+ ((int) fixP->fx_r_type < 0) ? "Internal":
+ bfd_get_reloc_code_name (fixP->fx_r_type), value,
+ fixP->fx_offset);
+
+
+ /* Now check for TLS relocations. */
+ reloc = fixP->fx_r_type;
+ switch (reloc)
+ {
+ case BFD_RELOC_ARC_TLS_DTPOFF:
+ case BFD_RELOC_ARC_TLS_LE_32:
+ if (fixP->fx_done)
+ break;
+ /* Fall through. */
+ case BFD_RELOC_ARC_TLS_GD_GOT:
+ case BFD_RELOC_ARC_TLS_IE_GOT:
+ S_SET_THREAD_LOCAL (fixP->fx_addsy);
+ break;
+
+ case BFD_RELOC_ARC_TLS_GD_LD:
+ gas_assert (!fixP->fx_offset);
+ if (fixP->fx_subsy)
+ fixP->fx_offset
+ = (S_GET_VALUE (fixP->fx_subsy)
+ - fixP->fx_frag->fr_address- fixP->fx_where);
+ fixP->fx_subsy = NULL;
+ /* Fall through. */
+ case BFD_RELOC_ARC_TLS_GD_CALL:
+ /* These two relocs are there just to allow ld to change the tls
+ model for this symbol, by patching the code. The offset -
+ and scale, if any - will be installed by the linker. */
+ S_SET_THREAD_LOCAL (fixP->fx_addsy);
+ break;
+
+ case BFD_RELOC_ARC_TLS_LE_S9:
+ case BFD_RELOC_ARC_TLS_DTPOFF_S9:
+ as_bad (_("TLS_*_S9 relocs are not supported yet"));
+ break;
+
+ default:
+ break;
+ }
+
+ if (!fixP->fx_done)
+ {
+ return;
+ }
+
+ /* Addjust the value if we have a constant. */
+ value += fx_offset;
+
+ /* For hosts with longs bigger than 32-bits make sure that the top
+ bits of a 32-bit negative value read in by the parser are set,
+ so that the correct comparisons are made. */
+ if (value & 0x80000000)
+ value |= (-1L << 31);
+
+ reloc = fixP->fx_r_type;
+ switch (reloc)
+ {
+ case BFD_RELOC_8:
+ case BFD_RELOC_16:
+ case BFD_RELOC_24:
+ case BFD_RELOC_32:
+ case BFD_RELOC_64:
+ case BFD_RELOC_ARC_32_PCREL:
+ md_number_to_chars (fixpos, value, fixP->fx_size);
+ return;
+
+ case BFD_RELOC_ARC_GOTPC32:
+ /* I cannot fix an GOTPC relocation because I need to relax it
+ from ld rx,[pcl,@sym@gotpc] to add rx,pcl,@sym@gotpc. */
+ as_bad (_("Unsupported operation on reloc"));
+ return;
+
+ case BFD_RELOC_ARC_TLS_DTPOFF:
+ case BFD_RELOC_ARC_TLS_LE_32:
+ gas_assert (!fixP->fx_addsy);
+ gas_assert (!fixP->fx_subsy);
+
+ case BFD_RELOC_ARC_GOTOFF:
+ case BFD_RELOC_ARC_32_ME:
+ case BFD_RELOC_ARC_PC32:
+ md_number_to_chars_midend (fixpos, value, fixP->fx_size);
+ return;
+
+ case BFD_RELOC_ARC_PLT32:
+ md_number_to_chars_midend (fixpos, value, fixP->fx_size);
+ return;
+
+ case BFD_RELOC_ARC_S25H_PCREL_PLT:
+ reloc = BFD_RELOC_ARC_S25W_PCREL;
+ goto solve_plt;
+
+ case BFD_RELOC_ARC_S21H_PCREL_PLT:
+ reloc = BFD_RELOC_ARC_S21H_PCREL;
+ goto solve_plt;
+
+ case BFD_RELOC_ARC_S25W_PCREL_PLT:
+ reloc = BFD_RELOC_ARC_S25W_PCREL;
+ goto solve_plt;
+
+ case BFD_RELOC_ARC_S21W_PCREL_PLT:
+ reloc = BFD_RELOC_ARC_S21W_PCREL;
+
+ case BFD_RELOC_ARC_S25W_PCREL:
+ case BFD_RELOC_ARC_S21W_PCREL:
+ case BFD_RELOC_ARC_S21H_PCREL:
+ case BFD_RELOC_ARC_S25H_PCREL:
+ case BFD_RELOC_ARC_S13_PCREL:
+ solve_plt:
+ operand = find_operand_for_reloc (reloc);
+ gas_assert (operand);
+ break;
+
+ default:
+ {
+ if ((int) fixP->fx_r_type >= 0)
+ as_fatal (_("unhandled relocation type %s"),
+ bfd_get_reloc_code_name (fixP->fx_r_type));
+
+ /* The rest of these fixups needs to be completely resolved as
+ constants. */
+ if (fixP->fx_addsy != 0
+ && S_GET_SEGMENT (fixP->fx_addsy) != absolute_section)
+ as_bad_where (fixP->fx_file, fixP->fx_line,
+ _("non-absolute expression in constant field"));
+
+ gas_assert (-(int) fixP->fx_r_type < (int) arc_num_operands);
+ operand = &arc_operands[-(int) fixP->fx_r_type];
+ break;
+ }
+ }
+
+ if (target_big_endian)
+ {
+ switch (fixP->fx_size)
+ {
+ case 4:
+ insn = bfd_getb32 (fixpos);
+ break;
+ case 2:
+ insn = bfd_getb16 (fixpos);
+ break;
+ default:
+ as_bad_where (fixP->fx_file, fixP->fx_line,
+ _("unknown fixup size"));
+ }
+ }
+ else
+ {
+ insn = 0;
+ switch (fixP->fx_size)
+ {
+ case 4:
+ insn = bfd_getl16 (fixpos) << 16 | bfd_getl16 (fixpos + 2);
+ break;
+ case 2:
+ insn = bfd_getl16 (fixpos);
+ break;
+ default:
+ as_bad_where (fixP->fx_file, fixP->fx_line,
+ _("unknown fixup size"));
+ }
+ }
+
+ insn = insert_operand (insn, operand, (offsetT) value,
+ fixP->fx_file, fixP->fx_line);
+
+ md_number_to_chars_midend (fixpos, insn, fixP->fx_size);
+}
+
+/* Prepare machine-dependent frags for relaxation.
+
+ Called just before relaxation starts. Any symbol that is now undefined
+ will not become defined.
+
+ Return the correct fr_subtype in the frag.
+
+ Return the initial "guess for fr_var" to caller. The guess for fr_var
+ is *actually* the growth beyond fr_fix. Whatever we do to grow fr_fix
+ or fr_var contributes to our returned value.
+
+ Although it may not be explicit in the frag, pretend
+ fr_var starts with a value. */