/src/binutils-gdb/opcodes/cgen-dis.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* CGEN generic disassembler support code. |
2 | | Copyright (C) 1996-2025 Free Software Foundation, Inc. |
3 | | |
4 | | This file is part of libopcodes. |
5 | | |
6 | | This library is free software; you can redistribute it and/or modify |
7 | | it under the terms of the GNU General Public License as published by |
8 | | the Free Software Foundation; either version 3, or (at your option) |
9 | | any later version. |
10 | | |
11 | | It is distributed in the hope that it will be useful, but WITHOUT |
12 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
13 | | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
14 | | License for more details. |
15 | | |
16 | | You should have received a copy of the GNU General Public License along |
17 | | with this program; if not, write to the Free Software Foundation, Inc., |
18 | | 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ |
19 | | |
20 | | #include "sysdep.h" |
21 | | #include <stdio.h> |
22 | | #include "ansidecl.h" |
23 | | #include "libiberty.h" |
24 | | #include "bfd.h" |
25 | | #include "symcat.h" |
26 | | #include "opcode/cgen.h" |
27 | | #include "disassemble.h" |
28 | | |
29 | | static CGEN_INSN_LIST * hash_insn_array (CGEN_CPU_DESC, const CGEN_INSN *, int, int, CGEN_INSN_LIST **, CGEN_INSN_LIST *); |
30 | | static CGEN_INSN_LIST * hash_insn_list (CGEN_CPU_DESC, const CGEN_INSN_LIST *, CGEN_INSN_LIST **, CGEN_INSN_LIST *); |
31 | | static void build_dis_hash_table (CGEN_CPU_DESC); |
32 | | static int count_decodable_bits (const CGEN_INSN *); |
33 | | static void add_insn_to_hash_chain (CGEN_INSN_LIST *, |
34 | | const CGEN_INSN *, |
35 | | CGEN_INSN_LIST **, |
36 | | unsigned int); |
37 | | |
38 | | /* Return the number of decodable bits in this insn. */ |
39 | | static int |
40 | | count_decodable_bits (const CGEN_INSN *insn) |
41 | 93.5M | { |
42 | 93.5M | CGEN_INSN_LGUINT mask = CGEN_INSN_BASE_MASK (insn); |
43 | 93.5M | #if GCC_VERSION >= 3004 |
44 | 93.5M | return __builtin_popcount (mask); |
45 | | #else |
46 | | int bits = 0; |
47 | | unsigned m; |
48 | | |
49 | | for (m = 1; m != 0; m <<= 1) |
50 | | { |
51 | | if (mask & m) |
52 | | ++bits; |
53 | | } |
54 | | return bits; |
55 | | #endif |
56 | 93.5M | } |
57 | | |
58 | | /* Add an instruction to the hash chain. */ |
59 | | static void |
60 | | add_insn_to_hash_chain (CGEN_INSN_LIST *hentbuf, |
61 | | const CGEN_INSN *insn, |
62 | | CGEN_INSN_LIST **htable, |
63 | | unsigned int hash) |
64 | 51.7k | { |
65 | 51.7k | CGEN_INSN_LIST *current_buf; |
66 | 51.7k | CGEN_INSN_LIST *previous_buf; |
67 | 51.7k | int insn_decodable_bits; |
68 | | |
69 | | /* Add insns sorted by the number of decodable bits, in decreasing order. |
70 | | This ensures that any insn which is a special case of another will be |
71 | | checked first. */ |
72 | 51.7k | insn_decodable_bits = count_decodable_bits (insn); |
73 | 51.7k | previous_buf = NULL; |
74 | 93.5M | for (current_buf = htable[hash]; current_buf != NULL; |
75 | 93.4M | current_buf = current_buf->next) |
76 | 93.5M | { |
77 | 93.5M | int current_decodable_bits = count_decodable_bits (current_buf->insn); |
78 | 93.5M | if (insn_decodable_bits >= current_decodable_bits) |
79 | 49.6k | break; |
80 | 93.4M | previous_buf = current_buf; |
81 | 93.4M | } |
82 | | |
83 | | /* Now insert the new insn. */ |
84 | 51.7k | hentbuf->insn = insn; |
85 | 51.7k | hentbuf->next = current_buf; |
86 | 51.7k | if (previous_buf == NULL) |
87 | 9.65k | htable[hash] = hentbuf; |
88 | 42.1k | else |
89 | 42.1k | previous_buf->next = hentbuf; |
90 | 51.7k | } |
91 | | |
92 | | /* Subroutine of build_dis_hash_table to add INSNS to the hash table. |
93 | | |
94 | | COUNT is the number of elements in INSNS. |
95 | | ENTSIZE is sizeof (CGEN_IBASE) for the target. |
96 | | ??? No longer used but leave in for now. |
97 | | HTABLE points to the hash table. |
98 | | HENTBUF is a pointer to sufficiently large buffer of hash entries. |
99 | | The result is a pointer to the next entry to use. |
100 | | |
101 | | The table is scanned backwards as additions are made to the front of the |
102 | | list and we want earlier ones to be preferred. */ |
103 | | |
104 | | static CGEN_INSN_LIST * |
105 | | hash_insn_array (CGEN_CPU_DESC cd, |
106 | | const CGEN_INSN * insns, |
107 | | int count, |
108 | | int entsize ATTRIBUTE_UNUSED, |
109 | | CGEN_INSN_LIST ** htable, |
110 | | CGEN_INSN_LIST * hentbuf) |
111 | 98 | { |
112 | 98 | int big_p = CGEN_CPU_INSN_ENDIAN (cd) == CGEN_ENDIAN_BIG; |
113 | 98 | int i; |
114 | | |
115 | 52.9k | for (i = count - 1; i >= 0; --i, ++hentbuf) |
116 | 52.8k | { |
117 | 52.8k | unsigned int hash; |
118 | 52.8k | char buf [8]; |
119 | 52.8k | unsigned long value; |
120 | 52.8k | const CGEN_INSN *insn = &insns[i]; |
121 | 52.8k | size_t size; |
122 | | |
123 | 52.8k | if (! (* cd->dis_hash_p) (insn)) |
124 | 1.04k | continue; |
125 | | |
126 | | /* We don't know whether the target uses the buffer or the base insn |
127 | | to hash on, so set both up. */ |
128 | | |
129 | 51.7k | value = CGEN_INSN_BASE_VALUE (insn); |
130 | 51.7k | size = CGEN_INSN_MASK_BITSIZE (insn); |
131 | 51.7k | OPCODES_ASSERT (size <= sizeof (buf) * 8); |
132 | 51.7k | bfd_put_bits ((bfd_vma) value, buf, size, big_p); |
133 | 51.7k | hash = (* cd->dis_hash) (buf, value); |
134 | 51.7k | add_insn_to_hash_chain (hentbuf, insn, htable, hash); |
135 | 51.7k | } |
136 | | |
137 | 98 | return hentbuf; |
138 | 98 | } |
139 | | |
140 | | /* Subroutine of build_dis_hash_table to add INSNS to the hash table. |
141 | | This function is identical to hash_insn_array except the insns are |
142 | | in a list. */ |
143 | | |
144 | | static CGEN_INSN_LIST * |
145 | | hash_insn_list (CGEN_CPU_DESC cd, |
146 | | const CGEN_INSN_LIST *insns, |
147 | | CGEN_INSN_LIST **htable, |
148 | | CGEN_INSN_LIST *hentbuf) |
149 | 98 | { |
150 | 98 | int big_p = CGEN_CPU_INSN_ENDIAN (cd) == CGEN_ENDIAN_BIG; |
151 | 98 | const CGEN_INSN_LIST *ilist; |
152 | | |
153 | 98 | for (ilist = insns; ilist != NULL; ilist = ilist->next, ++ hentbuf) |
154 | 0 | { |
155 | 0 | unsigned int hash; |
156 | 0 | char buf[8]; |
157 | 0 | unsigned long value; |
158 | 0 | size_t size; |
159 | |
|
160 | 0 | if (! (* cd->dis_hash_p) (ilist->insn)) |
161 | 0 | continue; |
162 | | |
163 | | /* We don't know whether the target uses the buffer or the base insn |
164 | | to hash on, so set both up. */ |
165 | | |
166 | 0 | value = CGEN_INSN_BASE_VALUE (ilist->insn); |
167 | 0 | size = CGEN_INSN_MASK_BITSIZE (ilist->insn); |
168 | 0 | OPCODES_ASSERT (size <= sizeof (buf) * 8); |
169 | 0 | bfd_put_bits ((bfd_vma) value, buf, size, big_p); |
170 | 0 | hash = (* cd->dis_hash) (buf, value); |
171 | 0 | add_insn_to_hash_chain (hentbuf, ilist->insn, htable, hash); |
172 | 0 | } |
173 | | |
174 | 98 | return hentbuf; |
175 | 98 | } |
176 | | |
177 | | /* Build the disassembler instruction hash table. */ |
178 | | |
179 | | static void |
180 | | build_dis_hash_table (CGEN_CPU_DESC cd) |
181 | 49 | { |
182 | 49 | int count = cgen_insn_count (cd) + cgen_macro_insn_count (cd); |
183 | 49 | CGEN_INSN_TABLE *insn_table = & cd->insn_table; |
184 | 49 | CGEN_INSN_TABLE *macro_insn_table = & cd->macro_insn_table; |
185 | 49 | unsigned int hash_size = cd->dis_hash_size; |
186 | 49 | CGEN_INSN_LIST *hash_entry_buf; |
187 | 49 | CGEN_INSN_LIST **dis_hash_table; |
188 | 49 | CGEN_INSN_LIST *dis_hash_table_entries; |
189 | | |
190 | | /* The space allocated for the hash table consists of two parts: |
191 | | the hash table and the hash lists. */ |
192 | | |
193 | 49 | dis_hash_table = (CGEN_INSN_LIST **) |
194 | 49 | xmalloc (hash_size * sizeof (CGEN_INSN_LIST *)); |
195 | 49 | memset (dis_hash_table, 0, hash_size * sizeof (CGEN_INSN_LIST *)); |
196 | 49 | dis_hash_table_entries = hash_entry_buf = (CGEN_INSN_LIST *) |
197 | 49 | xmalloc (count * sizeof (CGEN_INSN_LIST)); |
198 | | |
199 | | /* Add compiled in insns. |
200 | | Don't include the first one as it is a reserved entry. */ |
201 | | /* ??? It was the end of all hash chains, and also the special |
202 | | "invalid insn" marker. May be able to do it differently now. */ |
203 | | |
204 | 49 | hash_entry_buf = hash_insn_array (cd, |
205 | 49 | insn_table->init_entries + 1, |
206 | 49 | insn_table->num_init_entries - 1, |
207 | 49 | insn_table->entry_size, |
208 | 49 | dis_hash_table, hash_entry_buf); |
209 | | |
210 | | /* Add compiled in macro-insns. */ |
211 | | |
212 | 49 | hash_entry_buf = hash_insn_array (cd, macro_insn_table->init_entries, |
213 | 49 | macro_insn_table->num_init_entries, |
214 | 49 | macro_insn_table->entry_size, |
215 | 49 | dis_hash_table, hash_entry_buf); |
216 | | |
217 | | /* Add runtime added insns. |
218 | | Later added insns will be preferred over earlier ones. */ |
219 | | |
220 | 49 | hash_entry_buf = hash_insn_list (cd, insn_table->new_entries, |
221 | 49 | dis_hash_table, hash_entry_buf); |
222 | | |
223 | | /* Add runtime added macro-insns. */ |
224 | | |
225 | 49 | hash_insn_list (cd, macro_insn_table->new_entries, |
226 | 49 | dis_hash_table, hash_entry_buf); |
227 | | |
228 | 49 | cd->dis_hash_table = dis_hash_table; |
229 | 49 | cd->dis_hash_table_entries = dis_hash_table_entries; |
230 | 49 | } |
231 | | |
232 | | /* Return the first entry in the hash list for INSN. */ |
233 | | |
234 | | CGEN_INSN_LIST * |
235 | | cgen_dis_lookup_insn (CGEN_CPU_DESC cd, const char * buf, CGEN_INSN_INT value) |
236 | 2.09M | { |
237 | 2.09M | unsigned int hash; |
238 | | |
239 | 2.09M | if (cd->dis_hash_table == NULL) |
240 | 49 | build_dis_hash_table (cd); |
241 | | |
242 | 2.09M | hash = (* cd->dis_hash) (buf, value); |
243 | | |
244 | 2.09M | return cd->dis_hash_table[hash]; |
245 | 2.09M | } |