| /* tc-loongarch.c -- Assemble for the LoongArch ISA |
| |
| Copyright (C) 2021-2024 Free Software Foundation, Inc. |
| Contributed by Loongson Ltd. |
| |
| This file is part of GAS. |
| |
| 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 of the license, 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 this program; see the file COPYING3. If not, |
| see <http://www.gnu.org/licenses/>. */ |
| |
| #include "as.h" |
| #include "subsegs.h" |
| #include "dw2gencfi.h" |
| #include "loongarch-lex.h" |
| #include "elf/loongarch.h" |
| #include "opcode/loongarch.h" |
| #include "obj-elf.h" |
| #include "bfd/elfxx-loongarch.h" |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <assert.h> |
| |
| /* All information about an instruction during assemble. */ |
| struct loongarch_cl_insn |
| { |
| /* First split string. */ |
| const char *name; |
| const char *arg_strs[MAX_ARG_NUM_PLUS_2]; |
| size_t arg_num; |
| |
| /* Second analyze name_str and each actual args string to match the insn |
| in 'loongarch-opc.c'. And actual args may need be relocated. |
| We get length of insn. If 'insn_length == 0 && insn_mo->macro != NULL', |
| it's a macro insntruction and we call 'md_assemble' recursively |
| after expanding it. */ |
| int match_now; |
| int all_match; |
| |
| const struct loongarch_opcode *insn; |
| size_t insn_length; |
| |
| offsetT args[MAX_ARG_NUM_PLUS_2]; |
| struct reloc_info reloc_info[MAX_RELOC_NUMBER_A_INSN]; |
| size_t reloc_num; |
| |
| /* For relax reserved. We not support relax now. |
| 'insn_length < relax_max_length' means need to relax. |
| And 'insn_length == relax_max_length' means no need to relax. */ |
| size_t relax_max_length; |
| relax_substateT subtype; |
| |
| /* Then we get the binary representation of insn |
| and write it in to section. */ |
| insn_t insn_bin; |
| |
| /* The frag that contains the instruction. */ |
| struct frag *frag; |
| /* The offset into FRAG of the first instruction byte. */ |
| long where; |
| /* The relocs associated with the instruction, if any. */ |
| fixS *fixp[MAX_RELOC_NUMBER_A_INSN]; |
| /* Represents macros or instructions expanded from macro. |
| For la.local -> la.pcrel or la.pcrel -> pcalau12i + addi.d, la.pcrel, |
| pcalau12i and addi.d are expanded from macro. |
| The first bit represents expanded from one register macro (e.g. |
| la.local $t0, symbol) and emit R_LARCH_RELAX relocations. |
| The second bit represents expanded from two registers macro (e.g. |
| la.local $t0, $t1, symbol) and not emit R_LARCH_RELAX relocations. |
| |
| The macros or instructions expanded from macros do not output register |
| deprecated warning. */ |
| unsigned int expand_from_macro; |
| }; |
| |
| #ifndef DEFAULT_ARCH |
| #define DEFAULT_ARCH "loongarch64" |
| #endif |
| |
| /* This array holds the chars that always start a comment. If the |
| pre-processor is disabled, these aren't very useful. */ |
| const char comment_chars[] = "#"; |
| |
| /* This array holds the chars that only start a comment at the beginning of |
| a line. If the line seems to have the form '# 123 filename' |
| .line and .file directives will appear in the pre-processed output. */ |
| /* Note that input_file.c hand checks for '#' at the beginning of the |
| first line of the input file. This is because the compiler outputs |
| #NO_APP at the beginning of its output. */ |
| /* Also note that C style comments are always supported. */ |
| const char line_comment_chars[] = "#"; |
| |
| /* This array holds machine specific line separator characters. */ |
| const char line_separator_chars[] = ";"; |
| |
| /* Chars that can be used to separate mant from exp in floating point nums. */ |
| const char EXP_CHARS[] = "eE"; |
| |
| /* Chars that mean this number is a floating point constant. */ |
| /* As in 0f12.456. */ |
| /* or 0d1.2345e12. */ |
| const char FLT_CHARS[] = "rRsSfFdDxXpP"; |
| |
| const char *md_shortopts = "O::g::G:"; |
| |
| static const char default_arch[] = DEFAULT_ARCH; |
| |
| static bool call36 = 0; |
| |
| /* The lowest 4-bit is the bytes of instructions. */ |
| #define RELAX_BRANCH_16 0xc0000014 |
| #define RELAX_BRANCH_21 0xc0000024 |
| #define RELAX_BRANCH_26 0xc0000048 |
| |
| #define RELAX_BRANCH(x) \ |
| (((x) & 0xf0000000) == 0xc0000000) |
| #define RELAX_BRANCH_ENCODE(x) \ |
| (BFD_RELOC_LARCH_B16 == (x) ? RELAX_BRANCH_16 : RELAX_BRANCH_21) |
| |
| #define ALIGN_MAX_ADDEND(n, max) ((max << 8) | n) |
| #define ALIGN_MAX_NOP_BYTES(addend) ((1 << (addend & 0xff)) - 4) |
| #define FRAG_AT_START_OF_SECTION(frag) \ |
| (0 == frag->fr_address && 0 == frag->fr_fix) |
| |
| enum options |
| { |
| OPTION_IGNORE = OPTION_MD_BASE, |
| |
| OPTION_ABI, |
| OPTION_FLOAT_ABI, |
| OPTION_FLOAT_ISA, |
| |
| OPTION_LA_LOCAL_WITH_ABS, |
| OPTION_LA_GLOBAL_WITH_PCREL, |
| OPTION_LA_GLOBAL_WITH_ABS, |
| |
| OPTION_RELAX, |
| OPTION_NO_RELAX, |
| |
| OPTION_THIN_ADD_SUB, |
| OPTION_IGNORE_START_ALIGN, |
| |
| OPTION_END_OF_ENUM, |
| }; |
| |
| struct option md_longopts[] = |
| { |
| { "mabi", required_argument, NULL, OPTION_ABI }, |
| |
| { "mfpu", required_argument, NULL, OPTION_FLOAT_ISA }, |
| |
| { "mla-local-with-abs", no_argument, NULL, OPTION_LA_LOCAL_WITH_ABS }, |
| { "mla-global-with-pcrel", no_argument, NULL, OPTION_LA_GLOBAL_WITH_PCREL }, |
| { "mla-global-with-abs", no_argument, NULL, OPTION_LA_GLOBAL_WITH_ABS }, |
| |
| { "mrelax", no_argument, NULL, OPTION_RELAX }, |
| { "mno-relax", no_argument, NULL, OPTION_NO_RELAX }, |
| { "mthin-add-sub", no_argument, NULL, OPTION_THIN_ADD_SUB}, |
| { "mignore-start-align", no_argument, NULL, OPTION_IGNORE_START_ALIGN}, |
| |
| { NULL, no_argument, NULL, 0 } |
| }; |
| |
| size_t md_longopts_size = sizeof (md_longopts); |
| |
| int |
| md_parse_option (int c, const char *arg) |
| { |
| int ret = 1; |
| char lp64[256] = ""; |
| char ilp32[256] = ""; |
| unsigned char *suf = (unsigned char *)arg; |
| |
| lp64['s'] = lp64['S'] = EF_LOONGARCH_ABI_SOFT_FLOAT; |
| lp64['f'] = lp64['F'] = EF_LOONGARCH_ABI_SINGLE_FLOAT; |
| lp64['d'] = lp64['D'] = EF_LOONGARCH_ABI_DOUBLE_FLOAT; |
| |
| ilp32['s'] = ilp32['S'] = EF_LOONGARCH_ABI_SOFT_FLOAT; |
| ilp32['f'] = ilp32['F'] = EF_LOONGARCH_ABI_SINGLE_FLOAT; |
| ilp32['d'] = ilp32['D'] = EF_LOONGARCH_ABI_DOUBLE_FLOAT; |
| |
| switch (c) |
| { |
| case OPTION_ABI: |
| if (strncasecmp (arg, "lp64", 4) == 0 && lp64[suf[4]] != 0) |
| { |
| LARCH_opts.ase_ilp32 = 1; |
| LARCH_opts.ase_lp64 = 1; |
| LARCH_opts.ase_lsx = 1; |
| LARCH_opts.ase_lasx = 1; |
| LARCH_opts.ase_lvz = 1; |
| LARCH_opts.ase_lbt = 1; |
| LARCH_opts.ase_abi = lp64[suf[4]]; |
| } |
| else if (strncasecmp (arg, "ilp32", 5) == 0 && ilp32[suf[5]] != 0) |
| { |
| LARCH_opts.ase_abi = ilp32[suf[5]]; |
| LARCH_opts.ase_ilp32 = 1; |
| } |
| else |
| ret = 0; |
| break; |
| |
| case OPTION_FLOAT_ISA: |
| if (strcasecmp (arg, "soft") == 0) |
| LARCH_opts.ase_nf = 1; |
| else if (strcasecmp (arg, "single") == 0) |
| LARCH_opts.ase_sf = 1; |
| else if (strcasecmp (arg, "double") == 0) |
| { |
| LARCH_opts.ase_sf = 1; |
| LARCH_opts.ase_df = 1; |
| } |
| else |
| ret = 0; |
| break; |
| |
| case OPTION_LA_LOCAL_WITH_ABS: |
| LARCH_opts.ase_labs = 1; |
| break; |
| |
| case OPTION_LA_GLOBAL_WITH_PCREL: |
| LARCH_opts.ase_gpcr = 1; |
| break; |
| |
| case OPTION_LA_GLOBAL_WITH_ABS: |
| LARCH_opts.ase_gabs = 1; |
| break; |
| |
| case OPTION_RELAX: |
| LARCH_opts.relax = 1; |
| break; |
| |
| case OPTION_NO_RELAX: |
| LARCH_opts.relax = 0; |
| break; |
| |
| case OPTION_THIN_ADD_SUB: |
| LARCH_opts.thin_add_sub = 1; |
| break; |
| |
| case OPTION_IGNORE_START_ALIGN: |
| LARCH_opts.ignore_start_align = 1; |
| break; |
| |
| case OPTION_IGNORE: |
| break; |
| |
| default: |
| ret = 0; |
| break; |
| } |
| return ret; |
| } |
| |
| static const char *const *r_abi_names = NULL; |
| static const char *const *f_abi_names = NULL; |
| static struct htab *r_htab = NULL; |
| static struct htab *r_deprecated_htab = NULL; |
| static struct htab *f_htab = NULL; |
| static struct htab *f_deprecated_htab = NULL; |
| static struct htab *fc_htab = NULL; |
| static struct htab *fcn_htab = NULL; |
| static struct htab *c_htab = NULL; |
| static struct htab *cr_htab = NULL; |
| static struct htab *v_htab = NULL; |
| static struct htab *x_htab = NULL; |
| |
| void |
| loongarch_after_parse_args () |
| { |
| /* Set default ABI/ISA LP64D. */ |
| if (!LARCH_opts.ase_ilp32) |
| { |
| if (strcmp (default_arch, "loongarch64") == 0) |
| { |
| LARCH_opts.ase_abi = EF_LOONGARCH_ABI_DOUBLE_FLOAT; |
| LARCH_opts.ase_ilp32 = 1; |
| LARCH_opts.ase_lp64 = 1; |
| LARCH_opts.ase_lsx = 1; |
| LARCH_opts.ase_lasx = 1; |
| LARCH_opts.ase_lvz = 1; |
| LARCH_opts.ase_lbt = 1; |
| } |
| else if (strcmp (default_arch, "loongarch32") == 0) |
| { |
| LARCH_opts.ase_abi = EF_LOONGARCH_ABI_DOUBLE_FLOAT; |
| LARCH_opts.ase_ilp32 = 1; |
| } |
| else |
| as_bad ("unknown default architecture `%s'", default_arch); |
| } |
| |
| LARCH_opts.ase_abi |= EF_LOONGARCH_OBJABI_V1; |
| /* Set default ISA double-float. */ |
| if (!LARCH_opts.ase_nf |
| && !LARCH_opts.ase_sf |
| && !LARCH_opts.ase_df) |
| { |
| LARCH_opts.ase_sf = 1; |
| LARCH_opts.ase_df = 1; |
| } |
| |
| size_t i; |
| |
| assert(LARCH_opts.ase_ilp32); |
| |
| /* Init ilp32/lp64 registers names. */ |
| if (!r_htab) |
| r_htab = str_htab_create (), str_hash_insert (r_htab, "", 0, 0); |
| if (!r_deprecated_htab) |
| r_deprecated_htab = str_htab_create (), |
| str_hash_insert (r_deprecated_htab, "", 0, 0); |
| |
| r_abi_names = loongarch_r_normal_name; |
| for (i = 0; i < ARRAY_SIZE (loongarch_r_normal_name); i++) |
| str_hash_insert (r_htab, loongarch_r_normal_name[i], (void *) (i + 1), 0); |
| |
| /* Init ilp32/lp64 registers alias. */ |
| r_abi_names = loongarch_r_alias; |
| for (i = 0; i < ARRAY_SIZE (loongarch_r_alias); i++) |
| str_hash_insert (r_htab, loongarch_r_alias[i], (void *) (i + 1), 0); |
| |
| for (i = 0; i < ARRAY_SIZE (loongarch_r_alias_1); i++) |
| str_hash_insert (r_htab, loongarch_r_alias_1[i], (void *) (i + 1), 0); |
| |
| for (i = 0; i < ARRAY_SIZE (loongarch_r_alias_deprecated); i++) |
| str_hash_insert (r_deprecated_htab, loongarch_r_alias_deprecated[i], |
| (void *) (i + 1), 0); |
| |
| if (!cr_htab) |
| cr_htab = str_htab_create (), str_hash_insert (cr_htab, "", 0, 0); |
| |
| for (i = 0; i < ARRAY_SIZE (loongarch_cr_normal_name); i++) |
| str_hash_insert (cr_htab, loongarch_cr_normal_name[i], (void *) (i + 1), 0); |
| |
| /* Init single/double float registers names. */ |
| if (LARCH_opts.ase_sf || LARCH_opts.ase_df) |
| { |
| if (!f_htab) |
| f_htab = str_htab_create (), str_hash_insert (f_htab, "", 0, 0); |
| if (!f_deprecated_htab) |
| f_deprecated_htab = str_htab_create (), |
| str_hash_insert (f_deprecated_htab, "", 0, 0); |
| |
| f_abi_names = loongarch_f_normal_name; |
| for (i = 0; i < ARRAY_SIZE (loongarch_f_normal_name); i++) |
| str_hash_insert (f_htab, loongarch_f_normal_name[i], (void *) (i + 1), |
| 0); |
| |
| /* Init float-ilp32/lp64 registers alias. */ |
| f_abi_names = loongarch_f_alias; |
| for (i = 0; i < ARRAY_SIZE (loongarch_f_alias); i++) |
| str_hash_insert (f_htab, loongarch_f_alias[i], |
| (void *) (i + 1), 0); |
| for (i = 0; i < ARRAY_SIZE (loongarch_f_alias_deprecated); i++) |
| str_hash_insert (f_deprecated_htab, loongarch_f_alias_deprecated[i], |
| (void *) (i + 1), 0); |
| |
| if (!fc_htab) |
| fc_htab = str_htab_create (), str_hash_insert (fc_htab, "", 0, 0); |
| |
| for (i = 0; i < ARRAY_SIZE (loongarch_fc_normal_name); i++) |
| str_hash_insert (fc_htab, loongarch_fc_normal_name[i], (void *) (i + 1), |
| 0); |
| |
| if (!fcn_htab) |
| fcn_htab = str_htab_create (), str_hash_insert (fcn_htab, "", 0, 0); |
| |
| for (i = 0; i < ARRAY_SIZE (loongarch_fc_numeric_name); i++) |
| str_hash_insert (fcn_htab, loongarch_fc_numeric_name[i], (void *) (i + 1), |
| 0); |
| |
| if (!c_htab) |
| c_htab = str_htab_create (), str_hash_insert (c_htab, "", 0, 0); |
| |
| for (i = 0; i < ARRAY_SIZE (loongarch_c_normal_name); i++) |
| str_hash_insert (c_htab, loongarch_c_normal_name[i], (void *) (i + 1), |
| 0); |
| |
| } |
| |
| /* Init lsx registers names. */ |
| if (LARCH_opts.ase_lsx) |
| { |
| if (!v_htab) |
| v_htab = str_htab_create (), str_hash_insert (v_htab, "", 0, 0); |
| for (i = 0; i < ARRAY_SIZE (loongarch_v_normal_name); i++) |
| str_hash_insert (v_htab, loongarch_v_normal_name[i], (void *) (i + 1), |
| 0); |
| } |
| |
| /* Init lasx registers names. */ |
| if (LARCH_opts.ase_lasx) |
| { |
| if (!x_htab) |
| x_htab = str_htab_create (), str_hash_insert (x_htab, "", 0, 0); |
| for (i = 0; i < ARRAY_SIZE (loongarch_x_normal_name); i++) |
| str_hash_insert (x_htab, loongarch_x_normal_name[i], (void *) (i + 1), |
| 0); |
| } |
| |
| } |
| |
| const char * |
| loongarch_target_format () |
| { |
| return LARCH_opts.ase_lp64 ? "elf64-loongarch" : "elf32-loongarch"; |
| } |
| |
| typedef struct |
| { |
| unsigned int sec_id; |
| symbolS *s; |
| } align_sec_sym; |
| |
| static htab_t align_hash; |
| |
| static hashval_t |
| align_sec_sym_hash (const void *entry) |
| { |
| const align_sec_sym *e = entry; |
| return (hashval_t) (e->sec_id); |
| } |
| |
| static int |
| align_sec_sym_eq (const void *entry1, const void *entry2) |
| { |
| const align_sec_sym *e1 = entry1, *e2 = entry2; |
| return e1->sec_id == e2->sec_id; |
| } |
| |
| /* Make align symbol be in same section with alignment directive. |
| If the symbol is only created at the first time to handle alignment |
| directive. This means that all other sections may use this symbol. |
| If the section of this symbol is discarded, there may be problems. */ |
| |
| static symbolS *get_align_symbol (segT sec) |
| { |
| align_sec_sym search = { sec->id, NULL }; |
| align_sec_sym *pentry = htab_find (align_hash, &search); |
| if (pentry) |
| return pentry->s; |
| |
| /* If we not find the symbol in this section. Create and insert it. */ |
| symbolS *s = (symbolS *)local_symbol_make (".Lla-relax-align", sec, |
| &zero_address_frag, 0); |
| align_sec_sym entry = { sec->id, s }; |
| align_sec_sym **slot = (align_sec_sym **) htab_find_slot (align_hash, |
| &entry, INSERT); |
| if (slot == NULL) |
| return NULL; |
| *slot = (align_sec_sym *) xmalloc (sizeof (align_sec_sym)); |
| if (*slot == NULL) |
| return NULL; |
| **slot = entry; |
| return entry.s; |
| } |
| |
| void |
| md_begin () |
| { |
| const struct loongarch_opcode *it; |
| struct loongarch_ase *ase; |
| for (ase = loongarch_ASEs; ase->enabled; ase++) |
| for (it = ase->opcodes; it->name; it++) |
| { |
| if (loongarch_check_format (it->format) != 0) |
| as_fatal (_("insn name: %s\tformat: %s\tsyntax error"), |
| it->name, it->format); |
| if (it->mask == 0 && it->macro == 0) |
| as_fatal (_("insn name: %s\nformat: %s\nwe want macro but " |
| "macro is NULL"), |
| it->name, it->format); |
| if (it->macro |
| && loongarch_check_macro (it->format, it->macro) != 0) |
| as_fatal (_("insn name: %s\nformat: %s\nmacro: %s\tsyntax error"), |
| it->name, it->format, it->macro); |
| } |
| |
| align_hash = htab_create (10, align_sec_sym_hash, align_sec_sym_eq, free); |
| |
| /* FIXME: expressionS use 'offsetT' as constant, |
| * we want this is 64-bit type. */ |
| assert (8 <= sizeof (offsetT)); |
| } |
| |
| /* Called just before the assembler exits. */ |
| |
| void |
| loongarch_md_end (void) |
| { |
| htab_delete (align_hash); |
| } |
| |
| unsigned long |
| loongarch_mach (void) |
| { |
| return LARCH_opts.ase_lp64 ? bfd_mach_loongarch64 : bfd_mach_loongarch32; |
| } |
| |
| static const expressionS const_0 = { .X_op = O_constant, .X_add_number = 0 }; |
| |
| /* Handle the .dtprelword and .dtpreldword pseudo-ops. They generate |
| a 32-bit or 64-bit DTP-relative relocation (BYTES says which) for |
| use in DWARF debug information. */ |
| |
| static void |
| s_dtprel (int bytes) |
| { |
| expressionS ex; |
| char *p; |
| |
| expression (&ex); |
| |
| if (ex.X_op != O_symbol) |
| { |
| as_bad (_("Unsupported use of %s"), |
| (bytes == 8 ? ".dtpreldword" : ".dtprelword")); |
| ignore_rest_of_line (); |
| } |
| |
| p = frag_more (bytes); |
| md_number_to_chars (p, 0, bytes); |
| fix_new_exp (frag_now, p - frag_now->fr_literal, bytes, &ex, FALSE, |
| (bytes == 8 |
| ? BFD_RELOC_LARCH_TLS_DTPREL64 |
| : BFD_RELOC_LARCH_TLS_DTPREL32)); |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| struct LARCH_option_stack |
| { |
| struct LARCH_option_stack *next; |
| struct loongarch_ASEs_option options; |
| }; |
| |
| static struct LARCH_option_stack *LARCH_opts_stack = NULL; |
| |
| /* Handle the .option pseudo-op. |
| The alignment of .align is done by R_LARCH_ALIGN at link time. |
| If the .align directive is within the range controlled by |
| .option norelax, that is, relax is turned off, R_LARCH_ALIGN |
| cannot be generated, which may cause ld to be unable to handle |
| the alignment. */ |
| static void |
| s_loongarch_option (int x ATTRIBUTE_UNUSED) |
| { |
| char *name = input_line_pointer, ch; |
| while (!is_end_of_line[(unsigned char) *input_line_pointer]) |
| ++input_line_pointer; |
| ch = *input_line_pointer; |
| *input_line_pointer = '\0'; |
| |
| if (strcmp (name, "relax") == 0) |
| LARCH_opts.relax = 1; |
| else if (strcmp (name, "norelax") == 0) |
| LARCH_opts.relax = 0; |
| else if (strcmp (name, "push") == 0) |
| { |
| struct LARCH_option_stack *s; |
| |
| s = XNEW (struct LARCH_option_stack); |
| s->next = LARCH_opts_stack; |
| s->options = LARCH_opts; |
| LARCH_opts_stack = s; |
| } |
| else if (strcmp (name, "pop") == 0) |
| { |
| struct LARCH_option_stack *s; |
| |
| s = LARCH_opts_stack; |
| if (s == NULL) |
| as_bad (_(".option pop with no .option push")); |
| else |
| { |
| LARCH_opts_stack = s->next; |
| LARCH_opts = s->options; |
| free (s); |
| } |
| } |
| else |
| { |
| as_warn (_("unrecognized .option directive: %s"), name); |
| } |
| *input_line_pointer = ch; |
| demand_empty_rest_of_line (); |
| } |
| |
| static const pseudo_typeS loongarch_pseudo_table[] = |
| { |
| { "dword", cons, 8 }, |
| { "word", cons, 4 }, |
| { "half", cons, 2 }, |
| { "dtprelword", s_dtprel, 4 }, |
| { "dtpreldword", s_dtprel, 8 }, |
| { "option", s_loongarch_option, 0}, |
| { NULL, NULL, 0 }, |
| }; |
| |
| void |
| loongarch_pop_insert (void) |
| { |
| pop_insert (loongarch_pseudo_table); |
| } |
| |
| #define INTERNAL_LABEL_SPECIAL 10 |
| static unsigned long internal_label_count[INTERNAL_LABEL_SPECIAL] = { 0 }; |
| |
| static const char * |
| loongarch_internal_label_name (unsigned long label, int augend) |
| { |
| static char symbol_name_build[24]; |
| unsigned long want_label; |
| char *p; |
| |
| want_label = internal_label_count[label] + augend; |
| |
| p = symbol_name_build; |
| #ifdef LOCAL_LABEL_PREFIX |
| *p++ = LOCAL_LABEL_PREFIX; |
| #endif |
| *p++ = 'L'; |
| for (; label; label /= 10) |
| *p++ = label % 10 + '0'; |
| /* Make sure internal label never belong to normal label namespace. */ |
| *p++ = ':'; |
| for (; want_label; want_label /= 10) |
| *p++ = want_label % 10 + '0'; |
| *p++ = '\0'; |
| return symbol_name_build; |
| } |
| |
| static void |
| setup_internal_label_here (unsigned long label) |
| { |
| assert (label < INTERNAL_LABEL_SPECIAL); |
| internal_label_count[label]++; |
| colon (loongarch_internal_label_name (label, 0)); |
| } |
| |
| void |
| get_internal_label (expressionS *label_expr, unsigned long label, |
| int augend /* 0 for previous, 1 for next. */) |
| { |
| assert (label < INTERNAL_LABEL_SPECIAL); |
| as_fatal (_("internal error: we have no internal label yet")); |
| label_expr->X_op = O_symbol; |
| label_expr->X_add_symbol = |
| symbol_find_or_make (loongarch_internal_label_name (label, augend)); |
| label_expr->X_add_number = 0; |
| } |
| |
| static int |
| is_internal_label (const char *c_str) |
| { |
| do |
| { |
| if (*c_str != ':') |
| break; |
| c_str++; |
| if (!('0' <= *c_str && *c_str <= '9')) |
| break; |
| while ('0' <= *c_str && *c_str <= '9') |
| c_str++; |
| if (*c_str != 'b' && *c_str != 'f') |
| break; |
| c_str++; |
| return *c_str == '\0'; |
| } |
| while (0); |
| return 0; |
| } |
| |
| static int |
| is_label (const char *c_str) |
| { |
| if (is_internal_label (c_str)) |
| return 1; |
| else if ('0' <= *c_str && *c_str <= '9') |
| { |
| /* [0-9]+[bf] */ |
| while ('0' <= *c_str && *c_str <= '9') |
| c_str++; |
| return *c_str == 'b' || *c_str == 'f'; |
| } |
| else if (is_name_beginner (*c_str)) |
| { |
| /* [a-zA-Z\._\$][0-9a-zA-Z\._\$]* */ |
| c_str++; |
| while (is_part_of_name (*c_str)) |
| c_str++; |
| return *c_str == '\0'; |
| } |
| else |
| return 0; |
| } |
| |
| static int |
| is_label_with_addend (const char *c_str) |
| { |
| if (is_internal_label (c_str)) |
| return 1; |
| else if ('0' <= *c_str && *c_str <= '9') |
| { |
| /* [0-9]+[bf] */ |
| while ('0' <= *c_str && *c_str <= '9') |
| c_str++; |
| if (*c_str == 'b' || *c_str == 'f') |
| c_str++; |
| else |
| return 0; |
| return *c_str == '\0' |
| || ((*c_str == '-' || *c_str == '+') |
| && is_unsigned (c_str + 1)); |
| } |
| else if (is_name_beginner (*c_str)) |
| { |
| /* [a-zA-Z\._\$][0-9a-zA-Z\._\$]* */ |
| c_str++; |
| while (is_part_of_name (*c_str)) |
| c_str++; |
| return *c_str == '\0' |
| || ((*c_str == '-' || *c_str == '+') |
| && is_unsigned (c_str + 1)); |
| } |
| else |
| return 0; |
| } |
| |
| static int32_t |
| loongarch_args_parser_can_match_arg_helper (char esc_ch1, char esc_ch2, |
| const char *bit_field, |
| const char *arg, void *context) |
| { |
| struct loongarch_cl_insn *ip = context; |
| offsetT imm, ret = 0; |
| size_t reloc_num_we_have = MAX_RELOC_NUMBER_A_INSN - ip->reloc_num; |
| size_t reloc_num = 0; |
| |
| if (!ip->match_now) |
| return 0; |
| |
| switch (esc_ch1) |
| { |
| case 'l': |
| switch (esc_ch2) |
| { |
| default: |
| ip->match_now = is_label (arg); |
| if (!ip->match_now && is_label_with_addend (arg)) |
| as_fatal (_("This label shouldn't be with addend.")); |
| break; |
| case 'a': |
| ip->match_now = is_label_with_addend (arg); |
| break; |
| } |
| break; |
| /* This is used for TLS, where the fourth operand is %le_add_r, |
| to get a relocation applied to an add instruction, for relaxation to use. |
| Two conditions, ip->match_now and reloc_num, are used to check tls insn |
| to prevent cases like add.d $a0,$a0,$a0,8. */ |
| case 't': |
| ip->match_now = loongarch_parse_expr (arg, ip->reloc_info + ip->reloc_num, |
| reloc_num_we_have, &reloc_num, &imm) == 0; |
| |
| if (!ip->match_now) |
| break; |
| |
| bfd_reloc_code_real_type tls_reloc_type = BFD_RELOC_LARCH_TLS_LE_ADD_R; |
| |
| if (reloc_num |
| && (ip->reloc_info[ip->reloc_num].type == tls_reloc_type)) |
| { |
| ip->reloc_num += reloc_num; |
| ip->reloc_info[ip->reloc_num].type = BFD_RELOC_LARCH_RELAX; |
| ip->reloc_info[ip->reloc_num].value = const_0; |
| ip->reloc_num++; |
| } |
| else |
| ip->match_now = 0; |
| break; |
| case 's': |
| case 'u': |
| ip->match_now = |
| loongarch_parse_expr (arg, ip->reloc_info + ip->reloc_num, |
| reloc_num_we_have, &reloc_num, &imm) == 0; |
| |
| if (!ip->match_now) |
| break; |
| |
| ret = imm; |
| if (reloc_num) |
| { |
| bfd_reloc_code_real_type reloc_type = BFD_RELOC_NONE; |
| reloc_num_we_have -= reloc_num; |
| if (reloc_num_we_have == 0) |
| as_fatal (_("expr too huge") /* Want one more reloc. */); |
| if (esc_ch1 == 'u') |
| { |
| if (strncmp (bit_field, "10:12", strlen ("10:12")) == 0) |
| reloc_type = BFD_RELOC_LARCH_SOP_POP_32_U_10_12; |
| } |
| else if (esc_ch1 == 's') |
| { |
| if (strncmp (bit_field, "10:16<<2", strlen ("10:16<<2")) == 0) |
| reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_10_16_S2; |
| else if (strncmp (bit_field, "0:5|10:16<<2", |
| strlen ("0:5|10:16<<2")) == 0) |
| reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_0_5_10_16_S2; |
| else if (strncmp (bit_field, "0:10|10:16<<2", |
| strlen ("0:10|10:16<<2")) == 0) |
| reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_0_10_10_16_S2; |
| else if (strncmp (bit_field, "10:12", strlen ("10:12")) == 0) |
| reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_10_12; |
| else if (strncmp (bit_field, "5:20", strlen ("5:20")) == 0) |
| reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_5_20; |
| else if (strncmp (bit_field, "10:16", strlen ("10:16")) == 0) |
| reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_10_16; |
| else if (strncmp (bit_field, "10:5", strlen ("10:5")) == 0) |
| reloc_type = BFD_RELOC_LARCH_SOP_POP_32_S_10_5; |
| } |
| if (reloc_type == BFD_RELOC_NONE) |
| as_fatal ( |
| _("not support reloc bit-field\nfmt: %c%c %s\nargs: %s"), |
| esc_ch1, esc_ch2, bit_field, arg); |
| |
| if (ip->reloc_info[0].type >= BFD_RELOC_LARCH_B16 |
| && ip->reloc_info[0].type <= BFD_RELOC_LARCH_TLS_DESC_PCREL20_S2) |
| { |
| /* As we compact stack-relocs, it is no need for pop operation. |
| But break out until here in order to check the imm field. |
| May be reloc_num > 1 if implement relax? */ |
| ip->reloc_num += reloc_num; |
| reloc_type = ip->reloc_info[0].type; |
| |
| if (LARCH_opts.relax |
| && (BFD_RELOC_LARCH_TLS_LE_HI20_R == reloc_type |
| || BFD_RELOC_LARCH_TLS_LE_LO12_R == reloc_type |
| || BFD_RELOC_LARCH_TLS_LE_HI20 == reloc_type |
| || BFD_RELOC_LARCH_TLS_LE_LO12 == reloc_type |
| || BFD_RELOC_LARCH_TLS_LE64_LO20 == reloc_type |
| || BFD_RELOC_LARCH_TLS_LE64_HI12 == reloc_type |
| || BFD_RELOC_LARCH_CALL36 == reloc_type)) |
| { |
| ip->reloc_info[ip->reloc_num].type = BFD_RELOC_LARCH_RELAX; |
| ip->reloc_info[ip->reloc_num].value = const_0; |
| ip->reloc_num++; |
| } |
| |
| /* Only one register macros (used in normal code model) |
| emit R_LARCH_RELAX. |
| LARCH_opts.ase_labs and LARCH_opts.ase_gabs are used |
| to generate the code model of absolute addresses, and |
| we do not relax this code model. */ |
| if (LARCH_opts.relax && (ip->expand_from_macro & 1) |
| && !(LARCH_opts.ase_labs | LARCH_opts.ase_gabs) |
| && (BFD_RELOC_LARCH_PCALA_HI20 == reloc_type |
| || BFD_RELOC_LARCH_PCALA_LO12 == reloc_type |
| || BFD_RELOC_LARCH_GOT_PC_HI20 == reloc_type |
| || BFD_RELOC_LARCH_GOT_PC_LO12 == reloc_type |
| || BFD_RELOC_LARCH_TLS_LD_PC_HI20 == reloc_type |
| || BFD_RELOC_LARCH_TLS_GD_PC_HI20 == reloc_type |
| || BFD_RELOC_LARCH_TLS_DESC_PC_HI20 == reloc_type |
| || BFD_RELOC_LARCH_TLS_DESC_PC_LO12 == reloc_type |
| || BFD_RELOC_LARCH_TLS_DESC_LD == reloc_type |
| || BFD_RELOC_LARCH_TLS_DESC_CALL == reloc_type |
| || BFD_RELOC_LARCH_TLS_IE_PC_HI20 == reloc_type |
| || BFD_RELOC_LARCH_TLS_IE_PC_LO12 == reloc_type)) |
| { |
| ip->reloc_info[ip->reloc_num].type = BFD_RELOC_LARCH_RELAX; |
| ip->reloc_info[ip->reloc_num].value = const_0; |
| ip->reloc_num++; |
| } |
| break; |
| } |
| reloc_num++; |
| ip->reloc_num += reloc_num; |
| ip->reloc_info[ip->reloc_num - 1].type = reloc_type; |
| ip->reloc_info[ip->reloc_num - 1].value = const_0; |
| } |
| break; |
| case 'r': |
| imm = (intptr_t) str_hash_find (r_htab, arg); |
| ip->match_now = 0 < imm; |
| ret = imm - 1; |
| if (ip->match_now) |
| break; |
| /* Handle potential usage of deprecated register aliases. */ |
| imm = (intptr_t) str_hash_find (r_deprecated_htab, arg); |
| ip->match_now = 0 < imm; |
| ret = imm - 1; |
| /* !ip->expand_from_macro: avoiding duplicate output warnings, |
| only the first macro output warning. */ |
| if (ip->match_now && !ip->expand_from_macro) |
| as_warn (_("register alias %s is deprecated, use %s instead"), |
| arg, r_abi_names[ret]); |
| break; |
| case 'f': |
| switch (esc_ch2) |
| { |
| case 'c': |
| imm = (intptr_t) str_hash_find (fc_htab, arg); |
| if (0 >= imm) |
| { |
| imm = (intptr_t) str_hash_find (fcn_htab, arg); |
| } |
| break; |
| default: |
| imm = (intptr_t) str_hash_find (f_htab, arg); |
| } |
| ip->match_now = 0 < imm; |
| ret = imm - 1; |
| if (ip->match_now && !ip->expand_from_macro) |
| break; |
| /* Handle potential usage of deprecated register aliases. */ |
| imm = (intptr_t) str_hash_find (f_deprecated_htab, arg); |
| ip->match_now = 0 < imm; |
| ret = imm - 1; |
| if (ip->match_now) |
| as_warn (_("register alias %s is deprecated, use %s instead"), |
| arg, f_abi_names[ret]); |
| break; |
| case 'c': |
| switch (esc_ch2) |
| { |
| case 'r': |
| imm = (intptr_t) str_hash_find (cr_htab, arg); |
| break; |
| default: |
| imm = (intptr_t) str_hash_find (c_htab, arg); |
| } |
| ip->match_now = 0 < imm; |
| ret = imm - 1; |
| break; |
| case 'v': |
| imm = (intptr_t) str_hash_find (v_htab, arg); |
| ip->match_now = 0 < imm; |
| ret = imm - 1; |
| break; |
| case 'x': |
| imm = (intptr_t) str_hash_find (x_htab, arg); |
| ip->match_now = 0 < imm; |
| ret = imm - 1; |
| break; |
| case '\0': |
| ip->all_match = ip->match_now; |
| ip->insn_length = |
| ip->insn->mask ? loongarch_insn_length (ip->insn->match) : 0; |
| /* FIXME: now we have no relax insn. */ |
| ip->relax_max_length = ip->insn_length; |
| break; |
| default: |
| as_fatal (_("unknown escape")); |
| } |
| |
| do |
| { |
| /* Check imm overflow. */ |
| int bit_width, bits_needed_s, bits_needed_u; |
| char *t; |
| |
| if (!ip->match_now) |
| break; |
| |
| if (0 < reloc_num) |
| break; |
| |
| bit_width = loongarch_get_bit_field_width (bit_field, &t); |
| |
| if (bit_width == -1) |
| /* No specify bit width. */ |
| break; |
| |
| imm = ret; |
| if (t[0] == '<' && t[1] == '<') |
| { |
| int i = strtol (t += 2, &t, 10), j; |
| for (j = i; 0 < j; j--, imm >>= 1) |
| if (imm & 1) |
| as_fatal (_("require imm low %d bit is 0."), i); |
| } |
| |
| if (*t == '+') |
| imm -= strtol (t, &t, 10); |
| |
| bits_needed_s = loongarch_bits_imm_needed (imm, 1); |
| bits_needed_u = loongarch_bits_imm_needed (imm, 0); |
| |
| if ((esc_ch1 == 's' && bit_width < bits_needed_s) |
| || (esc_ch1 != 's' && bit_width < bits_needed_u)) |
| /* How to do after we detect overflow. */ |
| as_fatal (_("Immediate overflow.\n" |
| "format: %c%c%s\n" |
| "arg: %s"), |
| esc_ch1, esc_ch2, bit_field, arg); |
| } |
| while (0); |
| |
| if (esc_ch1 != '\0') |
| { |
| ip->args[ip->arg_num] = ret; |
| ip->arg_num++; |
| } |
| return ret; |
| } |
| |
| static void |
| get_loongarch_opcode (struct loongarch_cl_insn *insn) |
| { |
| const struct loongarch_opcode *it; |
| struct loongarch_ase *ase; |
| for (ase = loongarch_ASEs; ase->enabled; ase++) |
| { |
| if (!*ase->enabled || (ase->include && !*ase->include) |
| || (ase->exclude && *ase->exclude)) |
| continue; |
| |
| if (!ase->name_hash_entry) |
| { |
| ase->name_hash_entry = str_htab_create (); |
| for (it = ase->opcodes; it->name; it++) |
| { |
| if ((!it->include || (it->include && *it->include)) |
| && (!it->exclude || (it->exclude && !(*it->exclude))) |
| && !(it->pinfo & INSN_DIS_ALIAS)) |
| str_hash_insert (ase->name_hash_entry, it->name, |
| (void *) it, 0); |
| } |
| } |
| |
| if ((it = str_hash_find (ase->name_hash_entry, insn->name)) == NULL) |
| continue; |
| |
| do |
| { |
| insn->insn = it; |
| insn->match_now = 1; |
| insn->all_match = 0; |
| insn->arg_num = 0; |
| insn->reloc_num = 0; |
| insn->insn_bin = (loongarch_foreach_args |
| (it->format, insn->arg_strs, |
| loongarch_args_parser_can_match_arg_helper, |
| insn)); |
| if (insn->all_match && !(it->include && !*it->include) |
| && !(it->exclude && *it->exclude)) |
| { |
| insn->insn_bin |= it->match; |
| return; |
| } |
| it++; |
| } |
| while (it->name && strcasecmp (it->name, insn->name) == 0); |
| } |
| } |
| |
| static int |
| check_this_insn_before_appending (struct loongarch_cl_insn *ip) |
| { |
| int ret = 0; |
| |
| if (strncmp (ip->name, "la.abs", 6) == 0) |
| { |
| ip->reloc_info[ip->reloc_num].type = BFD_RELOC_LARCH_MARK_LA; |
| ip->reloc_info[ip->reloc_num].value = const_0; |
| ip->reloc_num++; |
| } |
| /* check all atomic memory insns */ |
| else if (ip->insn->mask == LARCH_MK_ATOMIC_MEM |
| && LARCH_INSN_ATOMIC_MEM(ip->insn_bin)) |
| { |
| /* For AMO insn amswap.[wd], amadd.[wd], etc. */ |
| if (ip->args[0] != 0 |
| && (ip->args[0] == ip->args[1] || ip->args[0] == ip->args[2])) |
| as_bad (_("atomic memory operations insns require rd != rj" |
| " && rd != rk when rd isn't r0")); |
| } |
| else if ((ip->insn->mask == LARCH_MK_BSTRINS_W |
| /* bstr(ins|pick).w rd, rj, msbw, lsbw */ |
| && (LARCH_INSN_BSTRINS_W(ip->insn_bin) |
| || LARCH_INSN_BSTRPICK_W(ip->insn_bin))) |
| || (ip->insn->mask == LARCH_MK_BSTRINS_D |
| /* bstr(ins|pick).d rd, rj, msbd, lsbd */ |
| && (LARCH_INSN_BSTRINS_D(ip->insn_bin) |
| || LARCH_INSN_BSTRPICK_D(ip->insn_bin)))) |
| { |
| /* For bstr(ins|pick).[wd]. */ |
| if (ip->args[2] < ip->args[3]) |
| as_bad (_("bstr(ins|pick).[wd] require msbd >= lsbd")); |
| } |
| else if (ip->insn->mask != 0 |
| && (LARCH_INSN_CSRXCHG(ip->insn_bin) |
| || LARCH_INSN_GCSRXCHG(ip->insn_bin)) |
| && (LARCH_GET_RJ(ip->insn_bin) == 0 |
| || LARCH_GET_RJ(ip->insn_bin) == 1) |
| /* csrxchg rd, rj, csr_num */ |
| && (strcmp ("csrxchg", ip->name) == 0 |
| || strcmp ("gcsrxchg", ip->name) == 0)) |
| as_bad (_("g?csrxchg require rj != r0 && rj != r1")); |
| |
| return ret; |
| } |
| |
| static void |
| install_insn (const struct loongarch_cl_insn *insn) |
| { |
| char *f = insn->frag->fr_literal + insn->where; |
| if (0 < insn->insn_length) |
| md_number_to_chars (f, insn->insn_bin, insn->insn_length); |
| } |
| |
| static void |
| move_insn (struct loongarch_cl_insn *insn, fragS *frag, long where) |
| { |
| size_t i; |
| insn->frag = frag; |
| insn->where = where; |
| for (i = 0; i < insn->reloc_num; i++) |
| { |
| if (insn->fixp[i]) |
| { |
| insn->fixp[i]->fx_frag = frag; |
| insn->fixp[i]->fx_where = where; |
| } |
| } |
| install_insn (insn); |
| } |
| |
| /* Add INSN to the end of the output. */ |
| static void |
| append_fixed_insn (struct loongarch_cl_insn *insn) |
| { |
| /* Ensure the jirl is emitted to the same frag as the pcaddu18i. */ |
| if (BFD_RELOC_LARCH_CALL36 == insn->reloc_info[0].type) |
| frag_grow (8); |
| |
| char *f = frag_more (insn->insn_length); |
| move_insn (insn, frag_now, f - frag_now->fr_literal); |
| |
| if (call36) |
| { |
| if (strcmp (insn->name, "jirl") == 0) |
| { |
| /* See comment at end of append_fixp_and_insn. */ |
| frag_wane (frag_now); |
| frag_new (0); |
| } |
| call36 = 0; |
| } |
| |
| if (BFD_RELOC_LARCH_CALL36 == insn->reloc_info[0].type) |
| call36 = 1; |
| } |
| |
| /* Add instructions based on the worst-case scenario firstly. */ |
| static void |
| append_relaxed_branch_insn (struct loongarch_cl_insn *insn, int max_chars, |
| int var, relax_substateT subtype, symbolS *symbol, offsetT offset) |
| { |
| frag_grow (max_chars); |
| move_insn (insn, frag_now, frag_more (0) - frag_now->fr_literal); |
| frag_var (rs_machine_dependent, max_chars, var, |
| subtype, symbol, offset, NULL); |
| } |
| |
| static void |
| append_fixp_and_insn (struct loongarch_cl_insn *ip) |
| { |
| reloc_howto_type *howto; |
| bfd_reloc_code_real_type r_type; |
| struct reloc_info *reloc_info = ip->reloc_info; |
| size_t i; |
| |
| dwarf2_emit_insn (0); |
| |
| for (i = 0; i < ip->reloc_num; i++) |
| { |
| r_type = reloc_info[i].type; |
| |
| if (r_type != BFD_RELOC_UNUSED) |
| { |
| |
| gas_assert (&(reloc_info[i].value)); |
| if (BFD_RELOC_LARCH_B16 == r_type || BFD_RELOC_LARCH_B21 == r_type) |
| { |
| int min_bytes = 4; /* One branch instruction. */ |
| unsigned max_bytes = 8; /* Branch and jump instructions. */ |
| |
| if (now_seg == absolute_section) |
| { |
| as_bad (_("relaxable branches not supported in absolute section")); |
| return; |
| } |
| |
| append_relaxed_branch_insn (ip, max_bytes, min_bytes, |
| RELAX_BRANCH_ENCODE (r_type), |
| reloc_info[i].value.X_add_symbol, |
| reloc_info[i].value.X_add_number); |
| return; |
| } |
| else |
| { |
| howto = bfd_reloc_type_lookup (stdoutput, r_type); |
| if (howto == NULL) |
| as_fatal (_("no HOWTO loong relocation number %d"), r_type); |
| |
| ip->fixp[i] = fix_new_exp (ip->frag, ip->where, |
| bfd_get_reloc_size (howto), |
| &reloc_info[i].value, FALSE, r_type); |
| } |
| /* Allow LoongArch 64 to use 64-bit addends. */ |
| if (LARCH_opts.ase_lp64) |
| ip->fixp[i]->fx_no_overflow = 1; |
| } |
| } |
| |
| if (ip->insn_length < ip->relax_max_length) |
| as_fatal (_("Internal error: not support relax now")); |
| else |
| append_fixed_insn (ip); |
| |
| /* We need to start a new frag after any instruction that can be |
| optimized away or compressed by the linker during relaxation, to prevent |
| the assembler from computing static offsets across such an instruction. |
| |
| This is necessary to get correct .eh_frame FDE DW_CFA_advance_loc info. |
| If one cfi_insn_data's two symbols are not in the same frag, it will |
| generate ADD and SUB relocations pairs to calculate DW_CFA_advance_loc. |
| (gas/dw2gencfi.c: output_cfi_insn: |
| if (symbol_get_frag (to) == symbol_get_frag (from))) |
| |
| For macro instructions, only the first instruction expanded from macro |
| need to start a new frag. |
| Since the relocations of the normal code model and the extreme code model |
| of the old LE instruction sequence are the same, it is impossible to |
| distinguish which code model it is based on relocation alone, so the |
| extreme code model has to be relaxed. */ |
| if (LARCH_opts.relax |
| && (BFD_RELOC_LARCH_PCALA_HI20 == reloc_info[0].type |
| || BFD_RELOC_LARCH_GOT_PC_HI20 == reloc_info[0].type |
| || BFD_RELOC_LARCH_TLS_LE_HI20_R == reloc_info[0].type |
| || BFD_RELOC_LARCH_TLS_LE_ADD_R == reloc_info[0].type |
| || BFD_RELOC_LARCH_TLS_LD_PC_HI20 == reloc_info[0].type |
| || BFD_RELOC_LARCH_TLS_GD_PC_HI20 == reloc_info[0].type |
| || BFD_RELOC_LARCH_TLS_DESC_PC_HI20 == reloc_info[0].type |
| || BFD_RELOC_LARCH_TLS_IE_PC_HI20 == reloc_info[0].type |
| || BFD_RELOC_LARCH_TLS_LE_HI20 == reloc_info[0].type |
| || BFD_RELOC_LARCH_TLS_LE_LO12 == reloc_info[0].type |
| || BFD_RELOC_LARCH_TLS_LE64_LO20 == reloc_info[0].type |
| || BFD_RELOC_LARCH_TLS_LE64_HI12 == reloc_info[0].type)) |
| { |
| frag_wane (frag_now); |
| frag_new (0); |
| } |
| } |
| |
| /* Ask helper for returning a malloced c_str or NULL. */ |
| static char * |
| assember_macro_helper (const char *const args[], void *context_ptr) |
| { |
| struct loongarch_cl_insn *insn = context_ptr; |
| char *ret = NULL; |
| if ( strcmp (insn->name, "li.w") == 0 || strcmp (insn->name, "li.d") == 0) |
| { |
| char args_buf[50], insns_buf[200]; |
| const char *arg_strs[6]; |
| uint32_t hi32, lo32; |
| |
| /* We pay attention to sign extend beacause it is chance of reduce insn. |
| The exception is 12-bit and hi-12-bit unsigned, |
| we need a 'ori' or a 'lu52i.d' accordingly. */ |
| char all0_bit_vec, sign_bit_vec, allf_bit_vec, paritial_is_sext_of_prev; |
| |
| lo32 = insn->args[1] & 0xffffffff; |
| hi32 = insn->args[1] >> 32; |
| |
| if (strcmp (insn->name, "li.w") == 0) |
| { |
| if (hi32 != 0 && hi32 != 0xffffffff) |
| as_fatal (_("li overflow: hi32:0x%x lo32:0x%x"), hi32, lo32); |
| hi32 = lo32 & 0x80000000 ? 0xffffffff : 0; |
| } |
| |
| if (strcmp (insn->name, "li.d") == 0 && !LARCH_opts.ase_lp64) |
| as_fatal (_("we can't li.d on 32bit-arch")); |
| |
| snprintf (args_buf, sizeof (args_buf), "0x%x,0x%x,0x%x,0x%x,%s", |
| (hi32 >> 20) & 0xfff, hi32 & 0xfffff, (lo32 >> 12) & 0xfffff, |
| lo32 & 0xfff, args[0]); |
| loongarch_split_args_by_comma (args_buf, arg_strs); |
| |
| all0_bit_vec = |
| ((((hi32 & 0xfff00000) == 0) << 3) | (((hi32 & 0x000fffff) == 0) << 2) |
| | (((lo32 & 0xfffff000) == 0) << 1) | ((lo32 & 0x00000fff) == 0)); |
| sign_bit_vec = |
| ((((hi32 & 0x80000000) != 0) << 3) | (((hi32 & 0x00080000) != 0) << 2) |
| | (((lo32 & 0x80000000) != 0) << 1) | ((lo32 & 0x00000800) != 0)); |
| allf_bit_vec = |
| ((((hi32 & 0xfff00000) == 0xfff00000) << 3) |
| | (((hi32 & 0x000fffff) == 0x000fffff) << 2) |
| | (((lo32 & 0xfffff000) == 0xfffff000) << 1) |
| | ((lo32 & 0x00000fff) == 0x00000fff)); |
| paritial_is_sext_of_prev = |
| (all0_bit_vec ^ allf_bit_vec) & (all0_bit_vec ^ (sign_bit_vec << 1)); |
| |
| static const char *const li_32bit[] = |
| { |
| "lu12i.w %5,%3&0x80000?%3-0x100000:%3;ori %5,%5,%4;", |
| "lu12i.w %5,%3&0x80000?%3-0x100000:%3;", |
| "addi.w %5,$r0,%4&0x800?%4-0x1000:%4;", |
| "or %5,$r0,$r0;", |
| }; |
| static const char *const li_hi_32bit[] = |
| { |
| "lu32i.d %5,%2&0x80000?%2-0x100000:%2;" |
| "lu52i.d %5,%5,%1&0x800?%1-0x1000:%1;", |
| "lu52i.d %5,%5,%1&0x800?%1-0x1000:%1;", |
| "lu32i.d %5,%2&0x80000?%2-0x100000:%2;", |
| "", |
| }; |
| do |
| { |
| insns_buf[0] = '\0'; |
| if (paritial_is_sext_of_prev == 0x7) |
| { |
| strcat (insns_buf, "lu52i.d %5,$r0,%1&0x800?%1-0x1000:%1;"); |
| break; |
| } |
| if ((all0_bit_vec & 0x3) == 0x2) |
| strcat (insns_buf, "ori %5,$r0,%4;"); |
| else |
| strcat (insns_buf, li_32bit[paritial_is_sext_of_prev & 0x3]); |
| strcat (insns_buf, li_hi_32bit[paritial_is_sext_of_prev >> 2]); |
| } |
| while (0); |
| |
| ret = loongarch_expand_macro (insns_buf, arg_strs, NULL, NULL, |
| sizeof (args_buf)); |
| } |
| |
| return ret; |
| } |
| |
| /* Accept instructions separated by ';' |
| * assuming 'not starting with space and not ending with space' or pass in |
| * empty c_str. */ |
| static void |
| loongarch_assemble_INSNs (char *str, unsigned int expand_from_macro) |
| { |
| char *rest; |
| size_t len_str = strlen(str); |
| |
| for (rest = str; *rest != ';' && *rest != '\0'; rest++); |
| if (*rest == ';') |
| *rest++ = '\0'; |
| |
| if (*str == ':') |
| { |
| str++; |
| setup_internal_label_here (strtol (str, &str, 10)); |
| str++; |
| } |
| |
| do |
| { |
| if (*str == '\0') |
| break; |
| |
| struct loongarch_cl_insn the_one = { 0 }; |
| the_one.name = str; |
| the_one.expand_from_macro = expand_from_macro; |
| |
| for (; *str && *str != ' '; str++) |
| ; |
| if (*str == ' ') |
| *str++ = '\0'; |
| |
| loongarch_split_args_by_comma (str, the_one.arg_strs); |
| get_loongarch_opcode (&the_one); |
| |
| if (!the_one.all_match) |
| { |
| char *ss = loongarch_cat_splited_strs (the_one.arg_strs); |
| as_bad (_("no match insn: %s\t%s"), the_one.name, ss ? ss : ""); |
| free(ss); |
| return; |
| } |
| |
| if (check_this_insn_before_appending (&the_one) != 0) |
| break; |
| |
| append_fixp_and_insn (&the_one); |
| |
| /* Expanding macro instructions. */ |
| if (the_one.insn_length == 0 && the_one.insn->macro) |
| { |
| unsigned int new_expand_from_macro = 0; |
| if (2 == the_one.arg_num) |
| new_expand_from_macro |= 1; |
| else if (3 == the_one.arg_num) |
| new_expand_from_macro |= 2; |
| |
| char *c_str = loongarch_expand_macro (the_one.insn->macro, |
| the_one.arg_strs, |
| assember_macro_helper, |
| &the_one, len_str); |
| /* The first instruction expanded from macro. */ |
| loongarch_assemble_INSNs (c_str, new_expand_from_macro); |
| free (c_str); |
| } |
| } |
| while (0); |
| |
| /* The rest instructions expanded from macro, split by semicolon(;), |
| assembly one by one. */ |
| if (*rest != '\0') |
| loongarch_assemble_INSNs (rest, expand_from_macro); |
| } |
| |
| void |
| md_assemble (char *str) |
| { |
| loongarch_assemble_INSNs (str, 0); |
| } |
| |
| const char * |
| md_atof (int type, char *litP, int *sizeP) |
| { |
| return ieee_md_atof (type, litP, sizeP, FALSE); |
| } |
| |
| void |
| md_number_to_chars (char *buf, valueT val, int n) |
| { |
| number_to_chars_littleendian (buf, val, n); |
| } |
| |
| /* The location from which a PC relative jump should be calculated, |
| given a PC relative reloc. */ |
| long |
| md_pcrel_from (fixS *fixP ATTRIBUTE_UNUSED) |
| { |
| return 0; |
| } |
| |
| static void fix_reloc_insn (fixS *fixP, bfd_vma reloc_val, char *buf) |
| { |
| reloc_howto_type *howto; |
| insn_t insn; |
| howto = bfd_reloc_type_lookup (stdoutput, fixP->fx_r_type); |
| |
| insn = bfd_getl32 (buf); |
| |
| if (!loongarch_adjust_reloc_bitsfield (NULL, howto, &reloc_val)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, "Reloc overflow"); |
| |
| insn = (insn & (insn_t)howto->src_mask) |
| | ((insn & (~(insn_t)howto->dst_mask)) | reloc_val); |
| |
| bfd_putl32 (insn, buf); |
| } |
| |
| void |
| md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED) |
| { |
| static int64_t stack_top; |
| static int last_reloc_is_sop_push_pcrel_1 = 0; |
| int last_reloc_is_sop_push_pcrel = last_reloc_is_sop_push_pcrel_1; |
| segT sub_segment; |
| last_reloc_is_sop_push_pcrel_1 = 0; |
| |
| char *buf = fixP->fx_frag->fr_literal + fixP->fx_where; |
| switch (fixP->fx_r_type) |
| { |
| case BFD_RELOC_LARCH_SOP_PUSH_TLS_TPREL: |
| case BFD_RELOC_LARCH_SOP_PUSH_TLS_GD: |
| case BFD_RELOC_LARCH_SOP_PUSH_TLS_GOT: |
| case BFD_RELOC_LARCH_TLS_LE_HI20: |
| case BFD_RELOC_LARCH_TLS_LE_LO12: |
| case BFD_RELOC_LARCH_TLS_LE64_LO20: |
| case BFD_RELOC_LARCH_TLS_LE64_HI12: |
| case BFD_RELOC_LARCH_TLS_IE_PC_HI20: |
| case BFD_RELOC_LARCH_TLS_IE_PC_LO12: |
| case BFD_RELOC_LARCH_TLS_IE64_PC_LO20: |
| case BFD_RELOC_LARCH_TLS_IE64_PC_HI12: |
| case BFD_RELOC_LARCH_TLS_IE_HI20: |
| case BFD_RELOC_LARCH_TLS_IE_LO12: |
| case BFD_RELOC_LARCH_TLS_IE64_LO20: |
| case BFD_RELOC_LARCH_TLS_IE64_HI12: |
| case BFD_RELOC_LARCH_TLS_LD_PC_HI20: |
| case BFD_RELOC_LARCH_TLS_LD_HI20: |
| case BFD_RELOC_LARCH_TLS_GD_PC_HI20: |
| case BFD_RELOC_LARCH_TLS_GD_HI20: |
| case BFD_RELOC_LARCH_TLS_DESC_PC_HI20: |
| case BFD_RELOC_LARCH_TLS_DESC_PC_LO12: |
| case BFD_RELOC_LARCH_TLS_DESC64_PC_LO20: |
| case BFD_RELOC_LARCH_TLS_DESC64_PC_HI12: |
| case BFD_RELOC_LARCH_TLS_DESC_HI20: |
| case BFD_RELOC_LARCH_TLS_DESC_LO12: |
| case BFD_RELOC_LARCH_TLS_DESC64_LO20: |
| case BFD_RELOC_LARCH_TLS_DESC64_HI12: |
| case BFD_RELOC_LARCH_TLS_LE_ADD_R: |
| case BFD_RELOC_LARCH_TLS_LE_HI20_R: |
| case BFD_RELOC_LARCH_TLS_LE_LO12_R: |
| /* Add tls lo (got_lo reloc type). */ |
| if (fixP->fx_addsy == NULL) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("Relocation against a constant")); |
| S_SET_THREAD_LOCAL (fixP->fx_addsy); |
| break; |
| |
| case BFD_RELOC_LARCH_SOP_PUSH_PCREL: |
| if (fixP->fx_addsy == NULL) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("Relocation against a constant")); |
| |
| last_reloc_is_sop_push_pcrel_1 = 1; |
| if (S_GET_SEGMENT (fixP->fx_addsy) == seg) |
| stack_top = (S_GET_VALUE (fixP->fx_addsy) + fixP->fx_offset |
| - (fixP->fx_where + fixP->fx_frag->fr_address)); |
| else |
| stack_top = 0; |
| break; |
| |
| case BFD_RELOC_LARCH_TLS_DESC_LD: |
| case BFD_RELOC_LARCH_TLS_DESC_CALL: |
| break; |
| |
| case BFD_RELOC_LARCH_SOP_POP_32_S_10_5: |
| case BFD_RELOC_LARCH_SOP_POP_32_S_10_12: |
| case BFD_RELOC_LARCH_SOP_POP_32_U_10_12: |
| case BFD_RELOC_LARCH_SOP_POP_32_S_10_16: |
| case BFD_RELOC_LARCH_SOP_POP_32_S_10_16_S2: |
| case BFD_RELOC_LARCH_SOP_POP_32_S_5_20: |
| case BFD_RELOC_LARCH_SOP_POP_32_U: |
| case BFD_RELOC_LARCH_SOP_POP_32_S_0_5_10_16_S2: |
| case BFD_RELOC_LARCH_SOP_POP_32_S_0_10_10_16_S2: |
| if (!last_reloc_is_sop_push_pcrel) |
| break; |
| |
| fix_reloc_insn (fixP, (bfd_vma)stack_top, buf); |
| break; |
| |
| /* LARCH only has R_LARCH_64/32, not has R_LARCH_24/16/8. |
| For BFD_RELOC_64/32, if fx_addsy and fx_subsy not null, wer need |
| generate BFD_RELOC_LARCH_ADD64/32 and BFD_RELOC_LARCH_SUB64/32 here. |
| Then will parse howto table bfd_reloc_code_real_type to generate |
| R_LARCH_ADD64/32 and R_LARCH_SUB64/32 reloc at tc_gen_reloc function. |
| If only fx_addsy not null, skip here directly, then generate |
| R_LARCH_64/32. |
| |
| For BFD_RELOC_24/16/8, if fx_addsy and fx_subsy not null, wer need |
| generate BFD_RELOC_LARCH_ADD24/16/8 and BFD_RELOC_LARCH_SUB24/16/8 here. |
| Then will parse howto table bfd_reloc_code_real_type to generate |
| R_LARCH_ADD24/16/8 and R_LARCH_SUB24/16/8 reloc at tc_gen_reloc |
| function. If only fx_addsy not null, we generate |
| BFD_RELOC_LARCH_ADD24/16/8 only, then generate R_LARCH_24/16/8. |
| To avoid R_LARCH_ADDxx add extra value, we write 0 first |
| (use md_number_to_chars (buf, 0, fixP->fx_size)). */ |
| case BFD_RELOC_64: |
| case BFD_RELOC_32: |
| if (fixP->fx_pcrel) |
| { |
| switch (fixP->fx_r_type) |
| { |
| case BFD_RELOC_64: |
| fixP->fx_r_type = BFD_RELOC_LARCH_64_PCREL; |
| break; |
| case BFD_RELOC_32: |
| fixP->fx_r_type = BFD_RELOC_LARCH_32_PCREL; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* If symbol in .eh_frame the address may be adjusted, and contents of |
| .eh_frame will be adjusted, so use pc-relative relocation for FDE |
| initial location. |
| The Option of mthin-add-sub does not affect the generation of |
| R_LARCH_32_PCREL relocation in .eh_frame. */ |
| if (fixP->fx_r_type == BFD_RELOC_32 |
| && fixP->fx_addsy && fixP->fx_subsy |
| && (sub_segment = S_GET_SEGMENT (fixP->fx_subsy)) |
| && strcmp (sub_segment->name, ".eh_frame") == 0 |
| && S_GET_VALUE (fixP->fx_subsy) |
| == fixP->fx_frag->fr_address + fixP->fx_where) |
| { |
| fixP->fx_r_type = BFD_RELOC_LARCH_32_PCREL; |
| fixP->fx_subsy = NULL; |
| break; |
| } |
| |
| if (fixP->fx_addsy && fixP->fx_subsy) |
| { |
| fixP->fx_next = xmemdup (fixP, sizeof (*fixP), sizeof (*fixP)); |
| fixP->fx_next->fx_addsy = fixP->fx_subsy; |
| fixP->fx_next->fx_subsy = NULL; |
| fixP->fx_next->fx_offset = 0; |
| fixP->fx_subsy = NULL; |
| |
| switch (fixP->fx_r_type) |
| { |
| case BFD_RELOC_64: |
| fixP->fx_r_type = BFD_RELOC_LARCH_ADD64; |
| fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB64; |
| break; |
| case BFD_RELOC_32: |
| fixP->fx_r_type = BFD_RELOC_LARCH_ADD32; |
| fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB32; |
| break; |
| default: |
| break; |
| } |
| |
| md_number_to_chars (buf, 0, fixP->fx_size); |
| } |
| |
| if (fixP->fx_addsy == NULL) |
| { |
| fixP->fx_done = 1; |
| md_number_to_chars (buf, *valP, fixP->fx_size); |
| } |
| break; |
| |
| case BFD_RELOC_24: |
| case BFD_RELOC_16: |
| case BFD_RELOC_8: |
| if (fixP->fx_addsy) |
| { |
| fixP->fx_next = xmemdup (fixP, sizeof (*fixP), sizeof (*fixP)); |
| fixP->fx_next->fx_addsy = fixP->fx_subsy; |
| fixP->fx_next->fx_subsy = NULL; |
| fixP->fx_next->fx_offset = 0; |
| fixP->fx_subsy = NULL; |
| |
| switch (fixP->fx_r_type) |
| { |
| case BFD_RELOC_24: |
| fixP->fx_r_type = BFD_RELOC_LARCH_ADD24; |
| fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB24; |
| break; |
| case BFD_RELOC_16: |
| fixP->fx_r_type = BFD_RELOC_LARCH_ADD16; |
| fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB16; |
| break; |
| case BFD_RELOC_8: |
| fixP->fx_r_type = BFD_RELOC_LARCH_ADD8; |
| fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB8; |
| break; |
| default: |
| break; |
| } |
| |
| md_number_to_chars (buf, 0, fixP->fx_size); |
| |
| if (fixP->fx_next->fx_addsy == NULL) |
| fixP->fx_next->fx_done = 1; |
| } |
| |
| if (fixP->fx_addsy == NULL) |
| { |
| fixP->fx_done = 1; |
| md_number_to_chars (buf, *valP, fixP->fx_size); |
| } |
| break; |
| |
| case BFD_RELOC_LARCH_CFA: |
| if (fixP->fx_addsy && fixP->fx_subsy) |
| { |
| fixP->fx_next = xmemdup (fixP, sizeof (*fixP), sizeof (*fixP)); |
| fixP->fx_next->fx_addsy = fixP->fx_subsy; |
| fixP->fx_next->fx_subsy = NULL; |
| fixP->fx_next->fx_offset = 0; |
| fixP->fx_subsy = NULL; |
| |
| unsigned int subtype; |
| offsetT loc; |
| subtype = bfd_get_8 (NULL, &((fragS *) |
| (fixP->fx_frag->fr_opcode))->fr_literal[fixP->fx_where]); |
| loc = fixP->fx_frag->fr_fix - (subtype & 7); |
| switch (subtype) |
| { |
| case DW_CFA_advance_loc1: |
| fixP->fx_where = loc + 1; |
| fixP->fx_next->fx_where = loc + 1; |
| fixP->fx_r_type = BFD_RELOC_LARCH_ADD8; |
| fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB8; |
| md_number_to_chars (buf+1, 0, fixP->fx_size); |
| break; |
| |
| case DW_CFA_advance_loc2: |
| fixP->fx_size = 2; |
| fixP->fx_next->fx_size = 2; |
| fixP->fx_where = loc + 1; |
| fixP->fx_next->fx_where = loc + 1; |
| fixP->fx_r_type = BFD_RELOC_LARCH_ADD16; |
| fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB16; |
| md_number_to_chars (buf+1, 0, fixP->fx_size); |
| break; |
| |
| case DW_CFA_advance_loc4: |
| fixP->fx_size = 4; |
| fixP->fx_next->fx_size = 4; |
| fixP->fx_where = loc; |
| fixP->fx_next->fx_where = loc; |
| fixP->fx_r_type = BFD_RELOC_LARCH_ADD32; |
| fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB32; |
| md_number_to_chars (buf+1, 0, fixP->fx_size); |
| break; |
| |
| default: |
| if (subtype < 0x80 && (subtype & 0x40)) |
| { |
| /* DW_CFA_advance_loc. */ |
| fixP->fx_frag = (fragS *) fixP->fx_frag->fr_opcode; |
| fixP->fx_next->fx_frag = fixP->fx_frag; |
| fixP->fx_r_type = BFD_RELOC_LARCH_ADD6; |
| fixP->fx_next->fx_r_type = BFD_RELOC_LARCH_SUB6; |
| md_number_to_chars (buf, 0x40, fixP->fx_size); |
| } |
| else |
| as_fatal (_("internal: bad CFA value #%d"), subtype); |
| break; |
| } |
| } |
| break; |
| |
| case BFD_RELOC_LARCH_B16: |
| case BFD_RELOC_LARCH_B21: |
| case BFD_RELOC_LARCH_B26: |
| if (fixP->fx_addsy == NULL) |
| { |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _ ("Relocation against a constant.")); |
| } |
| if (S_GET_SEGMENT (fixP->fx_addsy) == seg |
| && !S_FORCE_RELOC (fixP->fx_addsy, 1)) |
| { |
| int64_t sym_addend = S_GET_VALUE (fixP->fx_addsy) + fixP->fx_offset; |
| int64_t pc = fixP->fx_where + fixP->fx_frag->fr_address; |
| fix_reloc_insn (fixP, sym_addend - pc, buf); |
| |
| /* If relax, symbol value may change at link time, so reloc need to |
| be saved. */ |
| if (!LARCH_opts.relax) |
| fixP->fx_done = 1; |
| } |
| break; |
| |
| /* Because ADD_ULEB128/SUB_ULEB128 always occur in pairs. |
| So just deal with one is ok. |
| case BFD_RELOC_LARCH_ADD_ULEB128: */ |
| case BFD_RELOC_LARCH_SUB_ULEB128: |
| { |
| unsigned int len = 0; |
| len = loongarch_get_uleb128_length ((bfd_byte *)buf); |
| bfd_byte *endp = (bfd_byte*) buf + len -1; |
| /* Clean the uleb128 value to 0. Do not reduce the length. */ |
| memset (buf, 0x80, len - 1); |
| *endp = 0; |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| |
| /* Estimate the size of a frag before relaxing. */ |
| |
| int |
| md_estimate_size_before_relax (fragS *fragp, asection *sec) |
| { |
| /* align pseudo instunctions. */ |
| if (rs_align_code == fragp->fr_subtype) |
| { |
| offsetT nop_bytes; |
| if (NULL == fragp->fr_symbol) |
| nop_bytes = fragp->fr_offset; |
| else |
| nop_bytes = ALIGN_MAX_NOP_BYTES (fragp->fr_offset); |
| |
| /* Normally, nop_bytes should be >= 4. */ |
| gas_assert (nop_bytes > 0); |
| |
| if (FRAG_AT_START_OF_SECTION (fragp) |
| && 0 == ((1 << sec->alignment_power) % (nop_bytes + 4))) |
| return (fragp->fr_var = 0); |
| else |
| return (fragp->fr_var = nop_bytes); |
| } |
| |
| /* branch instructions and other instructions. |
| branch instructions may become 8 bytes after relaxing. */ |
| return (fragp->fr_var = 4); |
| } |
| |
| /* Translate internal representation of relocation info to BFD target |
| format. */ |
| arelent * |
| tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS *fixp) |
| { |
| arelent *reloc = (arelent *) xmalloc (sizeof (arelent)); |
| |
| reloc->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *)); |
| *reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy); |
| reloc->address = fixp->fx_frag->fr_address + fixp->fx_where; |
| reloc->addend = fixp->fx_offset; |
| |
| reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type); |
| if (reloc->howto == NULL) |
| { |
| as_bad_where (fixp->fx_file, fixp->fx_line, |
| _("cannot represent %s relocation in object file"), |
| bfd_get_reloc_code_name (fixp->fx_r_type)); |
| return NULL; |
| } |
| |
| return reloc; |
| } |
| |
| /* Standard calling conventions leave the CFA at SP on entry. */ |
| void |
| loongarch_cfi_frame_initial_instructions (void) |
| { |
| cfi_add_CFA_def_cfa_register (3 /* $sp */); |
| } |
| |
| void |
| loongarch_pre_output_hook (void) |
| { |
| const frchainS *frch; |
| segT s; |
| |
| if (!LARCH_opts.relax) |
| return; |
| |
| /* Save the current segment info. */ |
| segT seg = now_seg; |
| subsegT subseg = now_subseg; |
| |
| for (s = stdoutput->sections; s; s = s->next) |
| for (frch = seg_info (s)->frchainP; frch; frch = frch->frch_next) |
| { |
| fragS *frag; |
| |
| for (frag = frch->frch_root; frag; frag = frag->fr_next) |
| { |
| if (frag->fr_type == rs_cfa) |
| { |
| expressionS exp; |
| expressionS *symval; |
| |
| symval = symbol_get_value_expression (frag->fr_symbol); |
| exp.X_op = O_subtract; |
| exp.X_add_symbol = symval->X_add_symbol; |
| exp.X_add_number = 0; |
| exp.X_op_symbol = symval->X_op_symbol; |
| |
| /* We must set the segment before creating a frag after all |
| frag chains have been chained together. */ |
| subseg_set (s, frch->frch_subseg); |
| |
| fix_new_exp (frag, (int) frag->fr_offset, 1, &exp, 0, |
| BFD_RELOC_LARCH_CFA); |
| } |
| } |
| } |
| |
| /* Restore the original segment info. */ |
| subseg_set (seg, subseg); |
| } |
| |
| void |
| tc_loongarch_parse_to_dw2regnum (expressionS *exp) |
| { |
| expression_and_evaluate (exp); |
| } |
| |
| void |
| md_show_usage (FILE *stream) |
| { |
| fprintf (stream, _("LARCH options:\n")); |
| /* FIXME */ |
| fprintf (stream, _("\ |
| -mthin-add-sub Convert a pair of R_LARCH_ADD32/64 and R_LARCH_SUB32/64 to\n\ |
| R_LARCH_32/64_PCREL as much as possible\n\ |
| The option does not affect the generation of R_LARCH_32_PCREL\n\ |
| relocations in .eh_frame\n\ |
| -mignore-start-align Ignore .align if it is at the start of a section. This option\n\ |
| can't be used when partial linking (ld -r).\n")); |
| } |
| |
| static void |
| loongarch_make_nops (char *buf, bfd_vma bytes) |
| { |
| bfd_vma i = 0; |
| |
| /* Fill with 4-byte NOPs. */ |
| for ( ; i < bytes; i += 4) |
| number_to_chars_littleendian (buf + i, LARCH_NOP, 4); |
| } |
| |
| /* Called from md_do_align. Used to create an alignment frag in a |
| code section by emitting a worst-case NOP sequence that the linker |
| will later relax to the correct number of NOPs. We can't compute |
| the correct alignment now because of other linker relaxations. */ |
| |
| bool |
| loongarch_frag_align_code (int n, int max) |
| { |
| char *nops; |
| expressionS ex; |
| symbolS *s = NULL; |
| |
| /* When not relaxing, loongarch_handle_align handles code alignment. */ |
| if (!LARCH_opts.relax) |
| return false; |
| |
| bfd_vma align_bytes = (bfd_vma) 1 << n; |
| bfd_vma worst_case_bytes = align_bytes - 4; |
| bfd_vma addend = worst_case_bytes; |
| bool align_max = max > 0 && (bfd_vma) max < worst_case_bytes; |
| |
| /* If we are moving to a smaller alignment than the instruction size, then no |
| alignment is required. */ |
| if (align_bytes <= 4) |
| return true; |
| |
| /* If max <= 0, ignore max. |
| If max >= worst_case_bytes, max has no effect. |
| Similar to gas/write.c relax_segment function rs_align_code case: |
| if (fragP->fr_subtype != 0 && offset > fragP->fr_subtype). */ |
| if (align_max) |
| { |
| s = get_align_symbol (now_seg); |
| if (!s) |
| as_fatal (_("internal error: cannot get align symbol")); |
| addend = ALIGN_MAX_ADDEND (n, max); |
| } |
| |
| if (LARCH_opts.ignore_start_align) |
| { |
| frag_grow (worst_case_bytes); |
| /* Use relaxable frag for .align. |
| If .align at the start of section, do nothing. Section alignment can |
| ensure correct alignment. |
| If .align is not at the start of a section, reserve NOP instructions |
| and R_LARCH_ALIGN relocation. */ |
| nops = frag_var (rs_machine_dependent, worst_case_bytes, worst_case_bytes, |
| rs_align_code, s, addend, NULL); |
| } |
| else |
| { |
| nops = frag_more (worst_case_bytes); |
| if (align_max) |
| { |
| ex.X_add_symbol = s; |
| ex.X_op = O_symbol; |
| } |
| else |
| ex.X_op = O_constant; |
| |
| ex.X_add_number = addend; |
| |
| fix_new_exp (frag_now, nops - frag_now->fr_literal, 0, |
| &ex, false, BFD_RELOC_LARCH_ALIGN); |
| } |
| |
| /* Default write NOP for aligned bytes. */ |
| loongarch_make_nops (nops, worst_case_bytes); |
| |
| /* We need to start a new frag after the alignment which may be removed by |
| the linker, to prevent the assembler from computing static offsets. |
| This is necessary to get correct EH info. */ |
| frag_wane (frag_now); |
| frag_new (0); |
| |
| return true; |
| } |
| |
| /* Fill in an rs_align_code fragment. We want to fill 'andi $r0,$r0,0'. */ |
| void |
| loongarch_handle_align (fragS *fragp) |
| { |
| /* char nop_opcode; */ |
| char *p; |
| int bytes, size, excess; |
| valueT opcode; |
| |
| if (fragp->fr_type != rs_align_code) |
| return; |
| |
| struct loongarch_cl_insn nop = |
| { .name = "andi", .arg_strs = { "$r0", "$r0", "0", NULL } }; |
| |
| get_loongarch_opcode (&nop); |
| gas_assert (nop.all_match); |
| |
| p = fragp->fr_literal + fragp->fr_fix; |
| opcode = nop.insn_bin; |
| size = 4; |
| |
| bytes = fragp->fr_next->fr_address - fragp->fr_address - fragp->fr_fix; |
| excess = bytes % size; |
| |
| gas_assert (excess < 4); |
| fragp->fr_fix += excess; |
| |
| while (excess-- != 0) |
| *p++ = 0; |
| |
| md_number_to_chars (p, opcode, size); |
| fragp->fr_var = size; |
| } |
| |
| /* Scan uleb128 subtraction expressions and insert fixups for them. |
| e.g., .uleb128 .L1 - .L0 |
| Because relaxation may change the value of the subtraction, we |
| must resolve them at link-time. */ |
| |
| static void |
| loongarch_insert_uleb128_fixes (bfd *abfd ATTRIBUTE_UNUSED, |
| asection *sec, void *xxx ATTRIBUTE_UNUSED) |
| { |
| segment_info_type *seginfo = seg_info (sec); |
| struct frag *fragP; |
| |
| subseg_set (sec, 0); |
| |
| for (fragP = seginfo->frchainP->frch_root; |
| fragP; fragP = fragP->fr_next) |
| { |
| expressionS *exp, *exp_dup; |
| |
| if (fragP->fr_type != rs_leb128 || fragP->fr_symbol == NULL) |
| continue; |
| |
| exp = symbol_get_value_expression (fragP->fr_symbol); |
| |
| if (exp->X_op != O_subtract) |
| continue; |
| |
| /* FIXME: Skip for .sleb128. */ |
| if (fragP->fr_subtype != 0) |
| continue; |
| |
| exp_dup = xmemdup (exp, sizeof (*exp), sizeof (*exp)); |
| exp_dup->X_op = O_symbol; |
| exp_dup->X_op_symbol = NULL; |
| |
| exp_dup->X_add_symbol = exp->X_add_symbol; |
| fix_new_exp (fragP, fragP->fr_fix, 0, |
| exp_dup, 0, BFD_RELOC_LARCH_ADD_ULEB128); |
| |
| /* From binutils/testsuite/binutils-all/dw5.S |
| section .debug_rnglists |
| .uleb128 .Letext0-.Ltext0 Range length (*.LLRL2) |
| Offset Info Type Symbol's Value Symbol's Name + Addend |
| 0000000000000015 0000000200000079 R_LARCH_ADD_ULEB128 0000000000000000 .text + 2 |
| 0000000000000015 000000020000007a R_LARCH_SUB_ULEB128 0000000000000000 .text + 0. */ |
| |
| /* Only the ADD_ULEB128 has X_add_number (Addend)? */ |
| exp_dup->X_add_number = 0; |
| exp_dup->X_add_symbol = exp->X_op_symbol; |
| fix_new_exp (fragP, fragP->fr_fix, 0, |
| exp_dup, 0, BFD_RELOC_LARCH_SUB_ULEB128); |
| } |
| } |
| |
| void |
| loongarch_md_finish (void) |
| { |
| /* Insert relocations for uleb128 directives, so the values can be recomputed |
| at link time. */ |
| if (LARCH_opts.relax) |
| bfd_map_over_sections (stdoutput, loongarch_insert_uleb128_fixes, NULL); |
| } |
| |
| void |
| loongarch_elf_final_processing (void) |
| { |
| elf_elfheader (stdoutput)->e_flags = LARCH_opts.ase_abi; |
| } |
| |
| /* Compute the length of a branch sequence, and adjust the stored length |
| accordingly. If FRAGP is NULL, the worst-case length is returned. */ |
| static unsigned |
| loongarch_relaxed_branch_length (fragS *fragp, asection *sec, int update) |
| { |
| int length = 4; |
| |
| if (!fragp) |
| return 8; |
| |
| if (fragp->fr_symbol != NULL |
| && S_IS_DEFINED (fragp->fr_symbol) |
| && !S_IS_WEAK (fragp->fr_symbol) |
| && sec == S_GET_SEGMENT (fragp->fr_symbol)) |
| { |
| offsetT val = S_GET_VALUE (fragp->fr_symbol) + fragp->fr_offset; |
| |
| val -= fragp->fr_address + fragp->fr_fix; |
| |
| if (RELAX_BRANCH_16 == fragp->fr_subtype |
| && OUT_OF_RANGE (val, 16, 2)) |
| { |
| length = 8; |
| if (update) |
| fragp->fr_subtype = RELAX_BRANCH_26; |
| } |
| |
| if (RELAX_BRANCH_21 == fragp->fr_subtype |
| && OUT_OF_RANGE (val, 21, 2)) |
| { |
| length = 8; |
| if (update) |
| fragp->fr_subtype = RELAX_BRANCH_26; |
| } |
| |
| if (RELAX_BRANCH_26 == fragp->fr_subtype) |
| length = 8; |
| } |
| |
| return length; |
| } |
| |
| int |
| loongarch_relax_frag (asection *sec, fragS *fragp, |
| long stretch ATTRIBUTE_UNUSED) |
| { |
| if (RELAX_BRANCH (fragp->fr_subtype)) |
| { |
| offsetT old_var = fragp->fr_var; |
| fragp->fr_var = loongarch_relaxed_branch_length (fragp, sec, true); |
| return fragp->fr_var - old_var; |
| } |
| else if (rs_align_code == fragp->fr_subtype) |
| { |
| offsetT nop_bytes; |
| if (NULL == fragp->fr_symbol) |
| nop_bytes = fragp->fr_offset; |
| else |
| nop_bytes = ALIGN_MAX_NOP_BYTES (fragp->fr_offset); |
| |
| /* Normally, nop_bytes should be >= 4. */ |
| gas_assert (nop_bytes > 0); |
| |
| offsetT old_var = fragp->fr_var; |
| /* If .align at the start of a section, do nothing. Section alignment |
| * can ensure correct alignment. */ |
| if (FRAG_AT_START_OF_SECTION (fragp) |
| && 0 == ((1 << sec->alignment_power) % (nop_bytes + 4))) |
| fragp->fr_var = 0; |
| else |
| fragp->fr_var = nop_bytes; |
| return fragp->fr_var - old_var; |
| } |
| return 0; |
| } |
| |
| /* Expand far branches to multi-instruction sequences. |
| Branch instructions: |
| beq, bne, blt, bgt, bltz, bgtz, ble, bge, blez, bgez |
| bltu, bgtu, bleu, bgeu |
| beqz, bnez, bceqz, bcnez. */ |
| |
| static void |
| loongarch_convert_frag_branch (fragS *fragp) |
| { |
| bfd_byte *buf; |
| expressionS exp; |
| fixS *fixp; |
| insn_t insn; |
| |
| buf = (bfd_byte *)fragp->fr_literal + fragp->fr_fix; |
| |
| exp.X_op = O_symbol; |
| exp.X_add_symbol = fragp->fr_symbol; |
| exp.X_add_number = fragp->fr_offset; |
| |
| gas_assert ((fragp->fr_subtype & 0xf) == fragp->fr_var); |
| |
| /* blt $t0, $t1, .L1 |
| nop |
| change to: |
| bge $t0, $t1, .L2 |
| b .L1 |
| .L2: |
| nop */ |
| switch (fragp->fr_subtype) |
| { |
| case RELAX_BRANCH_26: |
| insn = bfd_getl32 (buf); |
| /* Invert the branch condition. */ |
| if (LARCH_INSN_FLOAT_BRANCH(insn)) |
| insn ^= LARCH_FLOAT_BRANCH_INVERT_BIT; |
| else |
| insn ^= LARCH_BRANCH_INVERT_BIT; |
| insn |= ENCODE_BRANCH16_IMM (8); /* Set target to PC + 8. */ |
| bfd_putl32 (insn, buf); |
| buf += 4; |
| |
| /* Add the B instruction and jump to the original target. */ |
| bfd_putl32 (LARCH_B, buf); |
| fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal, |
| 4, &exp, false, BFD_RELOC_LARCH_B26); |
| buf += 4; |
| break; |
| case RELAX_BRANCH_21: |
| fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal, |
| 4, &exp, false, BFD_RELOC_LARCH_B21); |
| buf += 4; |
| break; |
| case RELAX_BRANCH_16: |
| fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal, |
| 4, &exp, false, BFD_RELOC_LARCH_B16); |
| buf += 4; |
| break; |
| |
| default: |
| abort(); |
| } |
| |
| fixp->fx_file = fragp->fr_file; |
| fixp->fx_line = fragp->fr_line; |
| |
| gas_assert (buf == (bfd_byte *)fragp->fr_literal |
| + fragp->fr_fix + fragp->fr_var); |
| |
| fragp->fr_fix += fragp->fr_var; |
| } |
| |
| /* Relax .align frag. */ |
| |
| static void |
| loongarch_convert_frag_align (fragS *fragp, asection *sec) |
| { |
| bfd_byte *buf = (bfd_byte *)fragp->fr_literal + fragp->fr_fix; |
| |
| offsetT nop_bytes; |
| if (NULL == fragp->fr_symbol) |
| nop_bytes = fragp->fr_offset; |
| else |
| nop_bytes = ALIGN_MAX_NOP_BYTES (fragp->fr_offset); |
| |
| /* Normally, nop_bytes should be >= 4. */ |
| gas_assert (nop_bytes > 0); |
| |
| if (!(FRAG_AT_START_OF_SECTION (fragp) |
| && 0 == ((1 << sec->alignment_power) % (nop_bytes + 4)))) |
| { |
| expressionS exp; |
| exp.X_op = O_symbol; |
| exp.X_add_symbol = fragp->fr_symbol; |
| exp.X_add_number = fragp->fr_offset; |
| |
| fixS *fixp = fix_new_exp (fragp, buf - (bfd_byte *)fragp->fr_literal, |
| nop_bytes, &exp, false, BFD_RELOC_LARCH_ALIGN); |
| fixp->fx_file = fragp->fr_file; |
| fixp->fx_line = fragp->fr_line; |
| |
| buf += nop_bytes; |
| } |
| |
| gas_assert (buf == (bfd_byte *)fragp->fr_literal |
| + fragp->fr_fix + fragp->fr_var); |
| |
| fragp->fr_fix += fragp->fr_var; |
| } |
| |
| /* Relax a machine dependent frag. */ |
| |
| void |
| md_convert_frag (bfd *abfd ATTRIBUTE_UNUSED, segT asec, fragS *fragp) |
| { |
| gas_assert (RELAX_BRANCH (fragp->fr_subtype) |
| || rs_align_code == fragp->fr_subtype); |
| if (RELAX_BRANCH (fragp->fr_subtype)) |
| loongarch_convert_frag_branch (fragp); |
| else if (rs_align_code == fragp->fr_subtype) |
| loongarch_convert_frag_align (fragp, asec); |
| } |