/* 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 . */ #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 #include #include #include /* 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, }; const 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 } }; const 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; static struct htab *cfi_r_htab = NULL; static struct htab *cfi_f_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); /* Init cfi registers alias. */ if (!cfi_r_htab) cfi_r_htab = str_htab_create (), str_hash_insert (cfi_r_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); str_hash_insert (cfi_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); str_hash_insert (cfi_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); /* The .cfi directive supports register aliases without the "$" prefix. */ for (i = 0; i < ARRAY_SIZE (loongarch_r_cfi_name); i++) { str_hash_insert (cfi_r_htab, loongarch_r_cfi_name[i], (void *)(i + 1), 0); str_hash_insert (cfi_r_htab, loongarch_r_cfi_name_alias[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); if (!cfi_f_htab) cfi_f_htab = str_htab_create (), str_hash_insert (cfi_f_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); str_hash_insert (cfi_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); str_hash_insert (cfi_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); /* The .cfi directive supports register aliases without the "$" prefix. */ for (i = 0; i < ARRAY_SIZE (loongarch_f_cfi_name); i++) { str_hash_insert (cfi_f_htab, loongarch_f_cfi_name[i], (void *)(i + 1), 0); str_hash_insert (cfi_f_htab, loongarch_f_cfi_name_alias[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; } /* Return 1 if the relocation must be forced, and 0 if the relocation should never be forced. */ int loongarch_force_relocation (struct fix *fixp) { /* Ensure we emit a relocation for every reference to the global offset table. */ switch (fixp->fx_r_type) { case BFD_RELOC_LARCH_GOT_PC_HI20: case BFD_RELOC_LARCH_GOT_PC_LO12: case BFD_RELOC_LARCH_GOT64_PC_LO20: case BFD_RELOC_LARCH_GOT64_PC_HI12: case BFD_RELOC_LARCH_GOT_HI20: case BFD_RELOC_LARCH_GOT_LO12: case BFD_RELOC_LARCH_GOT64_LO20: case BFD_RELOC_LARCH_GOT64_HI12: return 1; default: break; } return generic_force_reloc (fixp); } 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 */); } /* Convert REGNAME to a DWARF register number. */ int tc_loongarch_regname_to_dw2regnum (char *regname) { int reg; /* Look up in the general purpose register table. */ if ((reg = (intptr_t) str_hash_find (cfi_r_htab, regname)) > 0) return reg - 1; /* Look up in the floating point register table. */ if ((reg = (intptr_t) str_hash_find (cfi_f_htab, regname)) > 0) return reg + 31; as_bad (_("unknown register `%s`"), regname); return -1; } /* Derived from tc_parse_to_dw2regnum, but excluding the case where the prefix '%'. */ void tc_loongarch_parse_to_dw2regnum (expressionS *exp) { SKIP_WHITESPACE (); if (is_name_beginner (*input_line_pointer)) { char *name, c; c = get_symbol_name (& name); exp->X_op = O_constant; exp->X_add_number = tc_loongarch_regname_to_dw2regnum (name); restore_line_pointer (c); } else expression_and_evaluate (exp); } 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 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); }