| /* tc-pj.c -- Assemble code for Pico Java |
| Copyright (C) 1999-2021 Free Software Foundation, Inc. |
| |
| This file is part of GAS, the GNU Assembler. |
| |
| GAS 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. |
| |
| GAS 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 GAS; see the file COPYING. If not, write to |
| the Free Software Foundation, 51 Franklin Street - Fifth Floor, |
| Boston, MA 02110-1301, USA. */ |
| |
| /* Contributed by Steve Chamberlain of Transmeta <sac@pobox.com>. */ |
| |
| #include "as.h" |
| #include "safe-ctype.h" |
| #include "opcode/pj.h" |
| |
| extern const pj_opc_info_t pj_opc_info[512]; |
| |
| const char comment_chars[] = "!/"; |
| const char line_separator_chars[] = ";"; |
| const char line_comment_chars[] = "/!#"; |
| |
| static int pending_reloc; |
| static htab_t opcode_hash_control; |
| |
| static void |
| little (int ignore ATTRIBUTE_UNUSED) |
| { |
| target_big_endian = 0; |
| } |
| |
| static void |
| big (int ignore ATTRIBUTE_UNUSED) |
| { |
| target_big_endian = 1; |
| } |
| |
| const pseudo_typeS md_pseudo_table[] = |
| { |
| {"ml", little, 0}, |
| {"mb", big, 0}, |
| {0, 0, 0} |
| }; |
| |
| const char FLT_CHARS[] = "rRsSfFdDxXpP"; |
| const char EXP_CHARS[] = "eE"; |
| |
| void |
| md_operand (expressionS *op) |
| { |
| if (startswith (input_line_pointer, "%hi16")) |
| { |
| if (pending_reloc) |
| as_bad (_("confusing relocation expressions")); |
| pending_reloc = BFD_RELOC_PJ_CODE_HI16; |
| input_line_pointer += 5; |
| expression (op); |
| } |
| |
| if (startswith (input_line_pointer, "%lo16")) |
| { |
| if (pending_reloc) |
| as_bad (_("confusing relocation expressions")); |
| pending_reloc = BFD_RELOC_PJ_CODE_LO16; |
| input_line_pointer += 5; |
| expression (op); |
| } |
| } |
| |
| /* Parse an expression and then restore the input line pointer. */ |
| |
| static char * |
| parse_exp_save_ilp (char *s, expressionS *op) |
| { |
| char *save = input_line_pointer; |
| |
| input_line_pointer = s; |
| expression (op); |
| s = input_line_pointer; |
| input_line_pointer = save; |
| return s; |
| } |
| |
| /* This is called by emit_expr via TC_CONS_FIX_NEW when creating a |
| reloc for a cons. We could use the definition there, except that |
| we want to handle magic pending reloc expressions specially. */ |
| |
| void |
| pj_cons_fix_new_pj (fragS *frag, int where, int nbytes, expressionS *exp, |
| bfd_reloc_code_real_type r ATTRIBUTE_UNUSED) |
| { |
| static int rv[5][2] = |
| { { 0, 0 }, |
| { BFD_RELOC_8, BFD_RELOC_8 }, |
| { BFD_RELOC_PJ_CODE_DIR16, BFD_RELOC_16 }, |
| { 0, 0 }, |
| { BFD_RELOC_PJ_CODE_DIR32, BFD_RELOC_32 }}; |
| |
| fix_new_exp (frag, where, nbytes, exp, 0, |
| pending_reloc ? pending_reloc |
| : rv[nbytes][(now_seg->flags & SEC_CODE) ? 0 : 1]); |
| |
| pending_reloc = 0; |
| } |
| |
| /* Turn a reloc description character from the pj-opc.h table into |
| code which BFD can handle. */ |
| |
| static int |
| c_to_r (int x) |
| { |
| switch (x) |
| { |
| case O_R8: |
| return BFD_RELOC_8_PCREL; |
| case O_U8: |
| case O_8: |
| return BFD_RELOC_8; |
| case O_R16: |
| return BFD_RELOC_PJ_CODE_REL16; |
| case O_U16: |
| case O_16: |
| return BFD_RELOC_PJ_CODE_DIR16; |
| case O_R32: |
| return BFD_RELOC_PJ_CODE_REL32; |
| case O_32: |
| return BFD_RELOC_PJ_CODE_DIR32; |
| } |
| abort (); |
| return 0; |
| } |
| |
| /* Handler for the ipush fake opcode, |
| turns ipush <foo> into sipush lo16<foo>, sethi hi16<foo>. */ |
| |
| static void |
| ipush_code (pj_opc_info_t *opcode ATTRIBUTE_UNUSED, char *str) |
| { |
| char *b = frag_more (6); |
| expressionS arg; |
| |
| b[0] = 0x11; |
| b[3] = 0xed; |
| parse_exp_save_ilp (str + 1, &arg); |
| if (pending_reloc) |
| { |
| as_bad (_("can't have relocation for ipush")); |
| pending_reloc = 0; |
| } |
| |
| fix_new_exp (frag_now, b - frag_now->fr_literal + 1, 2, |
| &arg, 0, BFD_RELOC_PJ_CODE_DIR16); |
| fix_new_exp (frag_now, b - frag_now->fr_literal + 4, 2, |
| &arg, 0, BFD_RELOC_PJ_CODE_HI16); |
| } |
| |
| /* Insert names into the opcode table which are really mini macros, |
| not opcodes. The fakeness is indicated with an opcode of -1. */ |
| |
| static void |
| fake_opcode (const char *name, |
| void (*func) (struct pj_opc_info_t *, char *)) |
| { |
| pj_opc_info_t * fake = XNEW (pj_opc_info_t); |
| |
| fake->opcode = -1; |
| fake->opcode_next = -1; |
| fake->u.func = func; |
| str_hash_insert (opcode_hash_control, name, fake, 0); |
| } |
| |
| /* Enter another entry into the opcode hash table so the same opcode |
| can have another name. */ |
| |
| static void |
| alias (const char *new_name, const char *old) |
| { |
| str_hash_insert (opcode_hash_control, new_name, |
| str_hash_find (opcode_hash_control, old), 0); |
| } |
| |
| /* This function is called once, at assembler startup time. It sets |
| up the hash table with all the opcodes in it, and also initializes |
| some aliases for compatibility with other assemblers. */ |
| |
| void |
| md_begin (void) |
| { |
| const pj_opc_info_t *opcode; |
| opcode_hash_control = str_htab_create (); |
| |
| /* Insert names into hash table. */ |
| for (opcode = pj_opc_info; opcode->u.name; opcode++) |
| str_hash_insert (opcode_hash_control, opcode->u.name, opcode, 0); |
| |
| /* Insert the only fake opcode. */ |
| fake_opcode ("ipush", ipush_code); |
| |
| /* Add some aliases for opcode names. */ |
| alias ("ifeq_s", "ifeq"); |
| alias ("ifne_s", "ifne"); |
| alias ("if_icmpge_s", "if_icmpge"); |
| alias ("if_icmpne_s", "if_icmpne"); |
| alias ("if_icmpeq_s", "if_icmpeq"); |
| alias ("if_icmpgt_s", "if_icmpgt"); |
| alias ("goto_s", "goto"); |
| |
| bfd_set_arch_mach (stdoutput, TARGET_ARCH, 0); |
| } |
| |
| /* This is the guts of the machine-dependent assembler. STR points to |
| a machine dependent instruction. This function is supposed to emit |
| the frags/bytes it assembles to. */ |
| |
| void |
| md_assemble (char *str) |
| { |
| char *op_start; |
| char *op_end; |
| |
| pj_opc_info_t *opcode; |
| char *output; |
| int idx = 0; |
| char pend; |
| |
| int nlen = 0; |
| |
| /* Drop leading whitespace. */ |
| while (*str == ' ') |
| str++; |
| |
| /* Find the op code end. */ |
| op_start = str; |
| for (op_end = str; |
| *op_end && !is_end_of_line[*op_end & 0xff] && *op_end != ' '; |
| op_end++) |
| nlen++; |
| |
| pend = *op_end; |
| *op_end = 0; |
| |
| if (nlen == 0) |
| as_bad (_("can't find opcode ")); |
| |
| opcode = (pj_opc_info_t *) str_hash_find (opcode_hash_control, op_start); |
| *op_end = pend; |
| |
| if (opcode == NULL) |
| { |
| as_bad (_("unknown opcode %s"), op_start); |
| return; |
| } |
| |
| dwarf2_emit_insn (0); |
| if (opcode->opcode == -1) |
| { |
| /* It's a fake opcode. Dig out the args and pretend that was |
| what we were passed. */ |
| (*opcode->u.func) (opcode, op_end); |
| } |
| else |
| { |
| unsigned int an; |
| |
| output = frag_more (opcode->len); |
| output[idx++] = opcode->opcode; |
| |
| if (opcode->opcode_next != -1) |
| output[idx++] = opcode->opcode_next; |
| |
| for (an = 0; an < ARRAY_SIZE (opcode->arg) && opcode->arg[an]; an++) |
| { |
| expressionS arg; |
| |
| if (*op_end == ',' && an != 0) |
| op_end++; |
| |
| if (*op_end == 0) |
| as_bad (_("expected expression")); |
| |
| op_end = parse_exp_save_ilp (op_end, &arg); |
| |
| fix_new_exp (frag_now, |
| output - frag_now->fr_literal + idx, |
| ASIZE (opcode->arg[an]), |
| &arg, |
| PCREL (opcode->arg[an]), |
| pending_reloc ? pending_reloc : c_to_r (opcode->arg[an])); |
| |
| idx += ASIZE (opcode->arg[an]); |
| pending_reloc = 0; |
| } |
| |
| while (ISSPACE (*op_end)) |
| op_end++; |
| |
| if (*op_end != 0) |
| as_warn (_("extra stuff on line ignored")); |
| |
| } |
| |
| if (pending_reloc) |
| as_bad (_("Something forgot to clean up\n")); |
| } |
| |
| const char * |
| md_atof (int type, char *litP, int *sizeP) |
| { |
| return ieee_md_atof (type, litP, sizeP, target_big_endian); |
| } |
| |
| const char *md_shortopts = ""; |
| |
| struct option md_longopts[] = |
| { |
| #define OPTION_LITTLE (OPTION_MD_BASE) |
| #define OPTION_BIG (OPTION_LITTLE + 1) |
| |
| {"little", no_argument, NULL, OPTION_LITTLE}, |
| {"big", no_argument, NULL, OPTION_BIG}, |
| {NULL, no_argument, NULL, 0} |
| }; |
| size_t md_longopts_size = sizeof (md_longopts); |
| |
| int |
| md_parse_option (int c, const char *arg ATTRIBUTE_UNUSED) |
| { |
| switch (c) |
| { |
| case OPTION_LITTLE: |
| little (0); |
| break; |
| case OPTION_BIG: |
| big (0); |
| break; |
| default: |
| return 0; |
| } |
| return 1; |
| } |
| |
| void |
| md_show_usage (FILE *stream) |
| { |
| fprintf (stream, _("\ |
| PJ options:\n\ |
| -little generate little endian code\n\ |
| -big generate big endian code\n")); |
| } |
| |
| /* Apply a fixup to the object file. */ |
| |
| void |
| md_apply_fix (fixS *fixP, valueT * valP, segT seg ATTRIBUTE_UNUSED) |
| { |
| char *buf = fixP->fx_where + fixP->fx_frag->fr_literal; |
| long val = *valP; |
| long max, min; |
| |
| max = min = 0; |
| switch (fixP->fx_r_type) |
| { |
| case BFD_RELOC_VTABLE_INHERIT: |
| case BFD_RELOC_VTABLE_ENTRY: |
| fixP->fx_done = 0; |
| return; |
| |
| case BFD_RELOC_PJ_CODE_REL16: |
| if (val < -0x8000 || val >= 0x7fff) |
| as_bad_where (fixP->fx_file, fixP->fx_line, _("pcrel too far")); |
| buf[0] |= (val >> 8) & 0xff; |
| buf[1] = val & 0xff; |
| break; |
| |
| case BFD_RELOC_PJ_CODE_HI16: |
| *buf++ = val >> 24; |
| *buf++ = val >> 16; |
| fixP->fx_addnumber = val & 0xffff; |
| break; |
| |
| case BFD_RELOC_PJ_CODE_DIR16: |
| case BFD_RELOC_PJ_CODE_LO16: |
| *buf++ = val >> 8; |
| *buf++ = val >> 0; |
| |
| max = 0xffff; |
| min = -0xffff; |
| break; |
| |
| case BFD_RELOC_8: |
| max = 0xff; |
| min = -0xff; |
| *buf++ = val; |
| break; |
| |
| case BFD_RELOC_PJ_CODE_DIR32: |
| case BFD_RELOC_PJ_CODE_REL32: |
| *buf++ = val >> 24; |
| *buf++ = val >> 16; |
| *buf++ = val >> 8; |
| *buf++ = val >> 0; |
| break; |
| |
| case BFD_RELOC_32: |
| if (target_big_endian) |
| { |
| *buf++ = val >> 24; |
| *buf++ = val >> 16; |
| *buf++ = val >> 8; |
| *buf++ = val >> 0; |
| } |
| else |
| { |
| *buf++ = val >> 0; |
| *buf++ = val >> 8; |
| *buf++ = val >> 16; |
| *buf++ = val >> 24; |
| } |
| break; |
| |
| case BFD_RELOC_16: |
| if (target_big_endian) |
| { |
| *buf++ = val >> 8; |
| *buf++ = val >> 0; |
| } |
| else |
| { |
| *buf++ = val >> 0; |
| *buf++ = val >> 8; |
| } |
| break; |
| |
| default: |
| abort (); |
| } |
| |
| if (max != 0 && (val < min || val > max)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, _("offset out of range")); |
| |
| if (fixP->fx_addsy == NULL && fixP->fx_pcrel == 0) |
| fixP->fx_done = 1; |
| } |
| |
| /* Put number into target byte order. Always put values in an |
| executable section into big endian order. */ |
| |
| void |
| md_number_to_chars (char *ptr, valueT use, int nbytes) |
| { |
| if (target_big_endian || now_seg->flags & SEC_CODE) |
| number_to_chars_bigendian (ptr, use, nbytes); |
| else |
| number_to_chars_littleendian (ptr, use, nbytes); |
| } |
| |
| /* Translate internal representation of relocation info to BFD target |
| format. */ |
| |
| arelent * |
| tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS *fixp) |
| { |
| arelent *rel; |
| bfd_reloc_code_real_type r_type; |
| |
| rel = XNEW (arelent); |
| rel->sym_ptr_ptr = XNEW (asymbol *); |
| *rel->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy); |
| rel->address = fixp->fx_frag->fr_address + fixp->fx_where; |
| |
| r_type = fixp->fx_r_type; |
| rel->addend = fixp->fx_addnumber; |
| rel->howto = bfd_reloc_type_lookup (stdoutput, r_type); |
| |
| if (rel->howto == NULL) |
| { |
| as_bad_where (fixp->fx_file, fixp->fx_line, |
| _("Cannot represent relocation type %s"), |
| bfd_get_reloc_code_name (r_type)); |
| /* Set howto to a garbage value so that we can keep going. */ |
| rel->howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_32); |
| gas_assert (rel->howto != NULL); |
| } |
| |
| return rel; |
| } |