Coverage Report

Created: 2026-09-14 08:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/binutils-gdb/opcodes/loongarch-dis.c
Line
Count
Source
1
/* LoongArch opcode support.
2
   Copyright (C) 2021-2026 Free Software Foundation, Inc.
3
   Contributed by Loongson Ltd.
4
5
   This file is part of the GNU opcodes library.
6
7
   This library is free software; you can redistribute it and/or modify
8
   it under the terms of the GNU General Public License as published by
9
   the Free Software Foundation; either version 3, or (at your option)
10
   any later version.
11
12
   It is distributed in the hope that it will be useful, but WITHOUT
13
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14
   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
15
   License for more details.
16
17
   You should have received a copy of the GNU General Public License
18
   along with this program; see the file COPYING3.  If not,
19
   see <http://www.gnu.org/licenses/>.  */
20
21
#include "sysdep.h"
22
#include "disassemble.h"
23
#include "opintl.h"
24
#include "opcode/loongarch.h"
25
#include "libiberty.h"
26
#include <stdlib.h>
27
28
static bool loongarch_dis_show_aliases = true;
29
static bool loongarch_dis_show_annotate = false;
30
static const char *const *loongarch_r_disname = NULL;
31
static const char *const *loongarch_f_disname = NULL;
32
static const char *const *loongarch_fc_disname = NULL;
33
static const char *const *loongarch_c_disname = NULL;
34
static const char *const *loongarch_cr_disname = NULL;
35
static const char *const *loongarch_v_disname = NULL;
36
static const char *const *loongarch_x_disname = NULL;
37
38
static const struct loongarch_opcode *
39
get_loongarch_opcode_by_binfmt (insn_t insn)
40
94.0k
{
41
94.0k
  const struct loongarch_opcode *it;
42
94.0k
  struct loongarch_ase *ase;
43
94.0k
  size_t i;
44
1.29M
  for (ase = loongarch_ASEs; ase->enabled; ase++)
45
1.24M
    {
46
1.24M
      if (!*ase->enabled || (ase->include && !*ase->include)
47
1.24M
    || (ase->exclude && *ase->exclude))
48
0
  continue;
49
50
1.24M
      if (!ase->opc_htab_inited)
51
36
  {
52
3.52k
    for (it = ase->opcodes; it->mask; it++)
53
3.49k
      if (!ase->opc_htab[LARCH_INSN_OPC (it->match)]
54
44
    && it->macro == NULL
55
44
    && (!(it->pinfo & INSN_DIS_ALIAS)
56
8
        || loongarch_dis_show_aliases))
57
44
        ase->opc_htab[LARCH_INSN_OPC (it->match)] = it;
58
612
    for (i = 0; i < 16; i++)
59
576
      if (!ase->opc_htab[i])
60
532
        ase->opc_htab[i] = it;
61
36
    ase->opc_htab_inited = 1;
62
36
  }
63
64
1.24M
      it = ase->opc_htab[LARCH_INSN_OPC (insn)];
65
53.8M
      for (; it->name; it++)
66
52.6M
  if ((insn & it->mask) == it->match && it->mask
67
44.4k
      && !(it->include && !*it->include)
68
44.4k
      && !(it->exclude && *it->exclude))
69
44.4k
    {
70
      /* ud ui5 need rd==rj. We should continue searching
71
         for the next `it` if rd != rj. Furthermore, we need
72
         `it->pinfo` to ensure that only the `it` in loongarch
73
         alias_opcodes[] is skipped.  */
74
44.4k
      if (LARCH_INSN_AMSWAP_W (insn)
75
136
    && (LARCH_GET_RD (insn) != LARCH_GET_RJ (insn))
76
58
    && (it->pinfo & INSN_DIS_ALIAS))
77
4
        continue;
78
44.4k
      return it;
79
44.4k
    }
80
1.24M
    }
81
49.5k
  return NULL;
82
94.0k
}
83
84
static void
85
set_default_loongarch_dis_options (void)
86
2
{
87
2
  LARCH_opts.ase_ilp32 = 1;
88
2
  LARCH_opts.ase_lp64 = 1;
89
2
  LARCH_opts.ase_sf = 1;
90
2
  LARCH_opts.ase_df = 1;
91
2
  LARCH_opts.ase_lsx = 1;
92
2
  LARCH_opts.ase_lasx = 1;
93
2
  LARCH_opts.ase_lvz = 1;
94
2
  LARCH_opts.ase_lbt = 1;
95
96
2
  loongarch_r_disname = loongarch_r_alias;
97
2
  loongarch_f_disname = loongarch_f_alias;
98
2
  loongarch_fc_disname = loongarch_fc_normal_name;
99
2
  loongarch_c_disname = loongarch_c_normal_name;
100
2
  loongarch_cr_disname = loongarch_cr_normal_name;
101
2
  loongarch_v_disname = loongarch_v_normal_name;
102
2
  loongarch_x_disname = loongarch_x_normal_name;
103
2
}
104
105
static int
106
parse_loongarch_dis_option (const char *option)
107
0
{
108
0
  if (strcmp (option, "no-aliases") == 0)
109
0
    {
110
0
      loongarch_dis_show_aliases = false;
111
0
      return 0;
112
0
    }
113
114
0
  if (strcmp (option, "numeric") == 0)
115
0
    {
116
0
      loongarch_r_disname = loongarch_r_normal_name;
117
0
      loongarch_f_disname = loongarch_f_normal_name;
118
0
      return 0;
119
0
    }
120
121
0
  if (strcmp (option, "annotate") == 0)
122
0
    {
123
0
      loongarch_dis_show_annotate = true;
124
0
      return 0;
125
0
    }
126
127
0
  return -1;
128
0
}
129
130
static int
131
parse_loongarch_dis_options (const char *opts_in)
132
2
{
133
2
  set_default_loongarch_dis_options ();
134
135
2
  if (opts_in == NULL)
136
2
    return 0;
137
138
0
  char *opts, *opt, *opt_end;
139
0
  opts = xmalloc (strlen (opts_in) + 1);
140
0
  strcpy (opts, opts_in);
141
142
0
  for (opt = opt_end = opts; opt_end != NULL; opt = opt_end + 1)
143
0
    {
144
0
      if ((opt_end = strchr (opt, ',')) != NULL)
145
0
  *opt_end = 0;
146
0
      if (parse_loongarch_dis_option (opt) != 0)
147
0
  return -1;
148
0
    }
149
0
  free (opts);
150
0
  return 0;
151
0
}
152
153
static int32_t
154
dis_one_arg (char esc1, char esc2, const char *bit_field,
155
       const char *arg ATTRIBUTE_UNUSED, void *context)
156
175k
{
157
175k
  static int need_comma = 0;
158
175k
  struct disassemble_info *info = context;
159
175k
  insn_t insn = *(insn_t *) info->private_data;
160
175k
  int32_t imm, u_imm;
161
175k
  enum disassembler_style style;
162
175k
  bool is_ud_2nd_arg = false;
163
164
175k
  if (LARCH_INSN_AMSWAP_W (insn)
165
515
      && (LARCH_GET_RD (insn) == LARCH_GET_RJ (insn))
166
299
      && (LARCH_GET_RK (insn) == 1)
167
39
      && loongarch_dis_show_aliases
168
39
      && need_comma)
169
26
    is_ud_2nd_arg = true;
170
171
175k
  if (esc1)
172
130k
    {
173
      /* The "ud ui5" does not nedd a comma.  */
174
130k
      if (need_comma && !is_ud_2nd_arg)
175
86.2k
  info->fprintf_styled_func (info->stream, dis_style_text, ", ");
176
130k
      need_comma = 1;
177
130k
      imm = loongarch_decode_imm (bit_field, insn, 1);
178
130k
      u_imm = loongarch_decode_imm (bit_field, insn, 0);
179
130k
    }
180
181
175k
  switch (esc1)
182
175k
    {
183
77.9k
    case 'r':
184
77.9k
      switch (esc2)
185
77.9k
  {
186
26
    case 'u':
187
      /* The "ud ui5" only needs to print one parameter.  */
188
26
      if (is_ud_2nd_arg)
189
13
        break;
190
13
      info->fprintf_styled_func (info->stream, dis_style_immediate, "0x%x", u_imm);
191
13
      break;
192
77.9k
    default:
193
77.9k
      info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_r_disname[u_imm]);
194
77.9k
  }
195
77.9k
      break;
196
77.9k
    case 'f':
197
9.89k
      switch (esc2)
198
9.89k
  {
199
248
  case 'c':
200
248
    info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_fc_disname[u_imm]);
201
248
    break;
202
9.64k
  default:
203
9.64k
    info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_f_disname[u_imm]);
204
9.89k
  }
205
9.89k
      break;
206
9.89k
    case 'c':
207
939
      switch (esc2)
208
939
  {
209
353
  case 'r':
210
353
    info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_cr_disname[u_imm]);
211
353
    break;
212
586
  default:
213
586
    info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_c_disname[u_imm]);
214
939
  }
215
939
      break;
216
2.87k
    case 'v':
217
2.87k
      info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_v_disname[u_imm]);
218
2.87k
      break;
219
5.51k
    case 'x':
220
5.51k
      info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_x_disname[u_imm]);
221
5.51k
      break;
222
13.2k
    case 'u':
223
13.2k
      style = esc2 == 'o' ? dis_style_address_offset : dis_style_immediate;
224
13.2k
      info->fprintf_styled_func (info->stream, style, "0x%x", u_imm);
225
13.2k
      break;
226
20.2k
    case 's':
227
20.2k
      switch (esc2)
228
20.2k
  {
229
8.61k
  case 'b':
230
14.2k
  case 'o':
231
    /* Both represent address offsets.  */
232
14.2k
    style = dis_style_address_offset;
233
14.2k
    break;
234
6.02k
  default:
235
6.02k
    style = dis_style_immediate;
236
6.02k
    break;
237
20.2k
  }
238
20.2k
      info->fprintf_styled_func (info->stream, style, "%d", imm);
239
20.2k
      switch (esc2)
240
20.2k
  {
241
8.61k
  case 'b':
242
8.61k
    info->insn_type = dis_branch;
243
8.61k
    info->target += imm;
244
20.2k
  }
245
20.2k
      break;
246
44.4k
    case '\0':
247
44.4k
      need_comma = 0;
248
175k
    }
249
175k
  return 0;
250
175k
}
251
252
static void
253
disassemble_annotate_one (insn_t insn, struct disassemble_info *info)
254
0
{
255
0
  if (LARCH_INSN_DBAR (insn))
256
0
    {
257
0
      if ((insn & 0x700) == 0x700)
258
0
  {
259
0
    info->fprintf_styled_func (info->stream, dis_style_comment_start, "\t# SA-RAR");
260
0
    return;
261
0
  }
262
263
      /* Currently, for reserved hint values such as 0xf, 0x1f and 0x7000, even
264
   though execution behavior matches hint=0 on most hardware, their exact
265
   semantics are undefined.
266
   We adopt a conservative approach and skip annotation for these values.  */
267
0
      if (!(insn & 0x7fe0))
268
0
  {
269
0
    unsigned int hint = LARCH_GET_RD (insn);
270
271
    /* If hint value is 0xf or 0x1f, its no specific semantics.  */
272
0
    if ((hint & 0xf) == 0xf)
273
0
      return;
274
275
    /* dbar imm[4:0] annotate layout:
276
       CRW|RW
277
       │││ ││
278
       │││ │└─ bit 0
279
       │││ └── bit 1
280
       ││└──── bit 2
281
       │└───── bit 3
282
       └────── bit 4
283
       Bit 0: false denotes W, true denotes null, W declare store after this
284
       dbar are constrained by the barrier.
285
       Bit 1: false denotes R, true denotes null, R declare load after this
286
       dbar are constrained by the barrier.
287
       Bit 2: false denotes W, true denotes null, W declare store before this
288
       dbar are constrained by the barrier.
289
       Bit 3: false denotes R, true denotes null, R declare load before this
290
       dbar are constrained by the barrier.
291
       Bit 4: false denotes null, true denotes C, C declare only cached
292
       load\store are constrained by the barrier.
293
       For exmaple, immediate value 0x10 is annotated as CRW|RW that only
294
       cached load and store operations before and after this dbar are
295
       constrained by the barrier.  */
296
0
    char annotate[7] = {' ', ' ', ' ', '|', ' ', ' ', '\0'};
297
0
    if ((hint & (1u << 0)) == 0)
298
0
      annotate[5] = 'W';
299
0
    if ((hint & (1u << 1)) == 0)
300
0
      annotate[4] = 'R';
301
0
    if ((hint & (1u << 2)) == 0)
302
0
      annotate[2] = 'W';
303
0
    if ((hint & (1u << 3)) == 0)
304
0
      annotate[1] = 'R';
305
0
    if ((hint & (1u << 4)) != 0)
306
0
      annotate[0] = 'C';
307
308
0
    info->fprintf_styled_func (info->stream, dis_style_comment_start, "\t# ");
309
0
    info->fprintf_styled_func (info->stream, dis_style_sub_mnemonic, "%s", annotate);
310
0
    return;
311
0
  }
312
0
    }
313
314
0
  return;
315
0
}
316
317
static void
318
disassemble_one (insn_t insn, struct disassemble_info *info)
319
94.0k
{
320
94.0k
  const struct loongarch_opcode *opc = get_loongarch_opcode_by_binfmt (insn);
321
322
#ifdef LOONGARCH_DEBUG
323
  char have_space[32] = { 0 };
324
  insn_t t;
325
  int i;
326
  const char *t_f = opc ? opc->format : NULL;
327
  if (t_f)
328
    while (*t_f)
329
      {
330
  while (('a' <= t_f[0] && t_f[0] <= 'z')
331
         || ('A' <= t_f[0] && t_f[0] <= 'Z')
332
         || t_f[0] == ',')
333
    t_f++;
334
  while (1)
335
    {
336
      i = strtol (t_f, &t_f, 10);
337
      have_space[i] = 1;
338
      t_f++; /* ':' */
339
      i += strtol (t_f, &t_f, 10);
340
      have_space[i] = 1;
341
      if (t_f[0] == '|')
342
        t_f++;
343
      else
344
        break;
345
    }
346
  if (t_f[0] == '<')
347
    t_f += 2; /* '<' '<' */
348
  strtol (t_f, &t_f, 10);
349
      }
350
351
  have_space[28] = 1;
352
  have_space[0] = 0;
353
  t = ~((insn_t) -1 >> 1);
354
  for (i = 31; 0 <= i; i--)
355
    {
356
      if (t & insn)
357
  info->fprintf_styled_func (info->stream, dis_style_text, "1");
358
      else
359
  info->fprintf_styled_func (info->stream, dis_style_text, "0");
360
      if (have_space[i])
361
  info->fprintf_styled_func (info->stream, dis_style_text, " ");
362
      t = t >> 1;
363
    }
364
  info->fprintf_styled_func (info->stream, dis_style_text, "\t");
365
#endif
366
367
94.0k
  if (!opc)
368
49.5k
    {
369
49.5k
      info->insn_type = dis_noninsn;
370
49.5k
      info->fprintf_styled_func (info->stream, dis_style_assembler_directive, ".word\t\t");
371
49.5k
      info->fprintf_styled_func (info->stream, dis_style_immediate, "0x%08x", insn);
372
49.5k
      return;
373
49.5k
    }
374
375
44.4k
  info->insn_type = dis_nonbranch;
376
44.4k
  if (opc->format == NULL || opc->format[0] == '\0')
377
59
    info->fprintf_styled_func (info->stream, dis_style_mnemonic,
378
59
        "%s", opc->name);
379
44.3k
  else
380
44.3k
    info->fprintf_styled_func (info->stream, dis_style_mnemonic,
381
44.3k
        "%-12s", opc->name);
382
383
44.4k
  {
384
44.4k
    char *fake_args = xmalloc (strlen (opc->format) + 1);
385
44.4k
    const char *fake_arg_strs[MAX_ARG_NUM_PLUS_2];
386
44.4k
    strcpy (fake_args, opc->format);
387
44.4k
    if (0 < loongarch_split_args_by_comma (fake_args, fake_arg_strs))
388
44.3k
      info->fprintf_styled_func (info->stream, dis_style_text, "\t");
389
44.4k
    info->private_data = &insn;
390
44.4k
    loongarch_foreach_args (opc->format, fake_arg_strs, dis_one_arg, info);
391
44.4k
    free (fake_args);
392
44.4k
  }
393
394
44.4k
  if (loongarch_dis_show_annotate)
395
0
    disassemble_annotate_one (insn, info);
396
397
44.4k
  if (info->insn_type == dis_branch || info->insn_type == dis_condbranch)
398
8.61k
    {
399
8.61k
      info->fprintf_styled_func (info->stream, dis_style_comment_start, "\t# ");
400
8.61k
      info->print_address_func (info->target, info);
401
8.61k
    }
402
44.4k
}
403
404
int
405
print_insn_loongarch (bfd_vma memaddr, struct disassemble_info *info)
406
94.1k
{
407
94.1k
  insn_t insn;
408
94.1k
  int status;
409
410
94.1k
  static int not_init_yet = 1;
411
94.1k
  if (not_init_yet)
412
2
    {
413
2
      parse_loongarch_dis_options (info->disassembler_options);
414
2
      not_init_yet = 0;
415
2
    }
416
417
94.1k
  info->bytes_per_chunk = 4;
418
94.1k
  info->bytes_per_line = 4;
419
94.1k
  info->display_endian = BFD_ENDIAN_LITTLE;
420
94.1k
  info->insn_info_valid = 1;
421
94.1k
  info->target = memaddr;
422
423
94.1k
  if ((status = info->read_memory_func (memaddr, (bfd_byte *) &insn,
424
94.1k
          sizeof (insn), info)) != 0)
425
139
    {
426
139
      info->memory_error_func (status, memaddr, info);
427
139
      return -1; /* loongarch_insn_length (0); */
428
139
    }
429
430
94.0k
  disassemble_one (insn, info);
431
432
94.0k
  return loongarch_insn_length (insn);
433
94.1k
}
434
435
void
436
print_loongarch_disassembler_options (FILE *stream)
437
0
{
438
0
  fprintf (stream, _("\n\
439
0
The following LoongArch disassembler options are supported for use\n\
440
0
with the -M switch (multiple options should be separated by commas):\n"));
441
442
0
  fprintf (stream, _("\n\
443
0
    no-aliases    Use canonical instruction forms.\n"));
444
0
  fprintf (stream, _("\n\
445
0
    numeric       Print numeric register names, rather than ABI names.\n"));
446
0
  fprintf (stream, _("\n\
447
0
    annotate      Print LoongArch instruction annotations.\n"));
448
0
  fprintf (stream, _("\
449
0
      dbar annotate layout:\n\
450
0
      'C': only cached load and store operations are constrained by the barrier;\n\
451
0
      '|' left‑side 'RW': all load and store before the dbar are constrained by the barrier;\n\
452
0
      '|' right‑side 'RW': all load and store after the dbar are constrained by the barrier.\n"));
453
  fprintf (stream, _("\n"));
454
0
}