1/* LoongArch opcode support.
2   Copyright (C) 2021-2022 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
28static const struct loongarch_opcode *
29get_loongarch_opcode_by_binfmt (insn_t insn)
30{
31  const struct loongarch_opcode *it;
32  struct loongarch_ase *ase;
33  size_t i;
34  for (ase = loongarch_ASEs; ase->enabled; ase++)
35    {
36      if (!*ase->enabled || (ase->include && !*ase->include)
37	  || (ase->exclude && *ase->exclude))
38	continue;
39
40      if (!ase->opc_htab_inited)
41	{
42	  for (it = ase->opcodes; it->mask; it++)
43	    if (!ase->opc_htab[LARCH_INSN_OPC (it->match)]
44		&& it->macro == NULL)
45	      ase->opc_htab[LARCH_INSN_OPC (it->match)] = it;
46	  for (i = 0; i < 16; i++)
47	    if (!ase->opc_htab[i])
48	      ase->opc_htab[i] = it;
49	  ase->opc_htab_inited = 1;
50	}
51
52      it = ase->opc_htab[LARCH_INSN_OPC (insn)];
53      for (; it->name; it++)
54	if ((insn & it->mask) == it->match && it->mask
55	    && !(it->include && !*it->include)
56	    && !(it->exclude && *it->exclude))
57	  return it;
58    }
59  return NULL;
60}
61
62static const char *const *loongarch_r_disname = NULL;
63static const char *const *loongarch_f_disname = NULL;
64static const char *const *loongarch_c_disname = NULL;
65static const char *const *loongarch_cr_disname = NULL;
66static const char *const *loongarch_v_disname = NULL;
67static const char *const *loongarch_x_disname = NULL;
68
69static void
70set_default_loongarch_dis_options (void)
71{
72  LARCH_opts.ase_ilp32 = 1;
73  LARCH_opts.ase_lp64 = 1;
74  LARCH_opts.ase_sf = 1;
75  LARCH_opts.ase_df = 1;
76  LARCH_opts.ase_lsx = 1;
77  LARCH_opts.ase_lasx = 1;
78
79  loongarch_r_disname = loongarch_r_lp64_name;
80  loongarch_f_disname = loongarch_f_lp64_name;
81  loongarch_c_disname = loongarch_c_normal_name;
82  loongarch_cr_disname = loongarch_cr_normal_name;
83  loongarch_v_disname = loongarch_v_normal_name;
84  loongarch_x_disname = loongarch_x_normal_name;
85}
86
87static int
88parse_loongarch_dis_option (const char *option)
89{
90  if (strcmp (option, "numeric") == 0)
91    {
92      loongarch_r_disname = loongarch_r_normal_name;
93      loongarch_f_disname = loongarch_f_normal_name;
94    }
95  return -1;
96}
97
98static int
99parse_loongarch_dis_options (const char *opts_in)
100{
101  set_default_loongarch_dis_options ();
102
103  if (opts_in == NULL)
104    return 0;
105
106  char *opts, *opt, *opt_end;
107  opts = xmalloc (strlen (opts_in) + 1);
108  strcpy (opts, opts_in);
109
110  for (opt = opt_end = opts; opt_end != NULL; opt = opt_end + 1)
111    {
112      if ((opt_end = strchr (opt, ',')) != NULL)
113	*opt_end = 0;
114      if (parse_loongarch_dis_option (opt) != 0)
115	return -1;
116    }
117  free (opts);
118  return 0;
119}
120
121static int32_t
122dis_one_arg (char esc1, char esc2, const char *bit_field,
123	     const char *arg ATTRIBUTE_UNUSED, void *context)
124{
125  static int need_comma = 0;
126  struct disassemble_info *info = context;
127  insn_t insn = *(insn_t *) info->private_data;
128  int32_t imm, u_imm;
129
130  if (esc1)
131    {
132      if (need_comma)
133	info->fprintf_func (info->stream, ", ");
134      need_comma = 1;
135      imm = loongarch_decode_imm (bit_field, insn, 1);
136      u_imm = loongarch_decode_imm (bit_field, insn, 0);
137    }
138
139  switch (esc1)
140    {
141    case 'r':
142      info->fprintf_func (info->stream, "%s", loongarch_r_disname[u_imm]);
143      break;
144    case 'f':
145      info->fprintf_func (info->stream, "%s", loongarch_f_disname[u_imm]);
146      break;
147    case 'c':
148      switch (esc2)
149	{
150	case 'r':
151	  info->fprintf_func (info->stream, "%s", loongarch_cr_disname[u_imm]);
152	  break;
153	default:
154	  info->fprintf_func (info->stream, "%s", loongarch_c_disname[u_imm]);
155	}
156      break;
157    case 'v':
158      info->fprintf_func (info->stream, "%s", loongarch_v_disname[u_imm]);
159      break;
160    case 'x':
161      info->fprintf_func (info->stream, "%s", loongarch_x_disname[u_imm]);
162      break;
163    case 'u':
164      info->fprintf_func (info->stream, "0x%x", u_imm);
165      break;
166    case 's':
167      if (imm == 0)
168	info->fprintf_func (info->stream, "%d", imm);
169      else
170	info->fprintf_func (info->stream, "%d(0x%x)", imm, u_imm);
171      switch (esc2)
172	{
173	case 'b':
174	  info->insn_type = dis_branch;
175	  info->target += imm;
176	}
177      break;
178    case '\0':
179      need_comma = 0;
180    }
181  return 0;
182}
183
184static void
185disassemble_one (insn_t insn, struct disassemble_info *info)
186{
187  const struct loongarch_opcode *opc = get_loongarch_opcode_by_binfmt (insn);
188
189#ifdef LOONGARCH_DEBUG
190  char have_space[32] = { 0 };
191  insn_t t;
192  int i;
193  const char *t_f = opc ? opc->format : NULL;
194  if (t_f)
195    while (*t_f)
196      {
197	while (('a' <= t_f[0] && t_f[0] <= 'z')
198	       || ('A' <= t_f[0] && t_f[0] <= 'Z')
199	       || t_f[0] == ',')
200	  t_f++;
201	while (1)
202	  {
203	    i = strtol (t_f, &t_f, 10);
204	    have_space[i] = 1;
205	    t_f++; /* ':' */
206	    i += strtol (t_f, &t_f, 10);
207	    have_space[i] = 1;
208	    if (t_f[0] == '|')
209	      t_f++;
210	    else
211	      break;
212	  }
213	if (t_f[0] == '<')
214	  t_f += 2; /* '<' '<' */
215	strtol (t_f, &t_f, 10);
216      }
217
218  have_space[28] = 1;
219  have_space[0] = 0;
220  t = ~((insn_t) -1 >> 1);
221  for (i = 31; 0 <= i; i--)
222    {
223      if (t & insn)
224	info->fprintf_func (info->stream, "1");
225      else
226	info->fprintf_func (info->stream, "0");
227      if (have_space[i])
228	info->fprintf_func (info->stream, " ");
229      t = t >> 1;
230    }
231  info->fprintf_func (info->stream, "\t");
232#endif
233
234  if (!opc)
235    {
236      info->insn_type = dis_noninsn;
237      info->fprintf_func (info->stream, "0x%08x", insn);
238      return;
239    }
240
241  info->insn_type = dis_nonbranch;
242  info->fprintf_func (info->stream, "%-12s", opc->name);
243
244  {
245    char *fake_args = xmalloc (strlen (opc->format) + 1);
246    const char *fake_arg_strs[MAX_ARG_NUM_PLUS_2];
247    strcpy (fake_args, opc->format);
248    if (0 < loongarch_split_args_by_comma (fake_args, fake_arg_strs))
249      info->fprintf_func (info->stream, "\t");
250    info->private_data = &insn;
251    loongarch_foreach_args (opc->format, fake_arg_strs, dis_one_arg, info);
252    free (fake_args);
253  }
254
255  if (info->insn_type == dis_branch || info->insn_type == dis_condbranch
256      /* Someother if we have extra info to print.  */)
257    info->fprintf_func (info->stream, "\t#");
258
259  if (info->insn_type == dis_branch || info->insn_type == dis_condbranch)
260    {
261      info->fprintf_func (info->stream, " ");
262      info->print_address_func (info->target, info);
263    }
264}
265
266int
267print_insn_loongarch (bfd_vma memaddr, struct disassemble_info *info)
268{
269  insn_t insn;
270  int status;
271
272  static int not_init_yet = 1;
273  if (not_init_yet)
274    {
275      parse_loongarch_dis_options (info->disassembler_options);
276      not_init_yet = 0;
277    }
278
279  info->bytes_per_chunk = 4;
280  info->bytes_per_line = 4;
281  info->display_endian = BFD_ENDIAN_LITTLE;
282  info->insn_info_valid = 1;
283  info->target = memaddr;
284
285  if ((status = info->read_memory_func (memaddr, (bfd_byte *) &insn,
286					sizeof (insn), info)) != 0)
287    {
288      info->memory_error_func (status, memaddr, info);
289      return -1; /* loongarch_insn_length (0); */
290    }
291
292  disassemble_one (insn, info);
293
294  return loongarch_insn_length (insn);
295}
296
297void
298print_loongarch_disassembler_options (FILE *stream)
299{
300  fprintf (stream, _("\n\
301The following LoongArch disassembler options are supported for use\n\
302with the -M switch (multiple options should be separated by commas):\n"));
303
304  fprintf (stream, _("\n\
305    numeric       Print numeric register names, rather than ABI names.\n"));
306  fprintf (stream, _("\n"));
307}
308
309int
310loongarch_parse_dis_options (const char *opts_in)
311{
312  return parse_loongarch_dis_options (opts_in);
313}
314
315static void
316my_print_address_func (bfd_vma addr, struct disassemble_info *dinfo)
317{
318  dinfo->fprintf_func (dinfo->stream, "0x%llx", (long long) addr);
319}
320
321void
322loongarch_disassemble_one (int64_t pc, insn_t insn,
323			   int (*fprintf_func) (void *stream,
324						const char *format, ...),
325			   void *stream)
326{
327  static struct disassemble_info my_disinfo =
328  {
329    .print_address_func = my_print_address_func,
330  };
331  static int not_init_yet = 1;
332  if (not_init_yet)
333    {
334      loongarch_parse_dis_options (NULL);
335      not_init_yet = 0;
336    }
337
338  my_disinfo.fprintf_func = fprintf_func;
339  my_disinfo.stream = stream;
340  my_disinfo.target = pc;
341  disassemble_one (insn, &my_disinfo);
342}
343