1# 2# Copyright (C) 2020 Collabora, Ltd. 3# 4# Permission is hereby granted, free of charge, to any person obtaining a 5# copy of this software and associated documentation files (the "Software"), 6# to deal in the Software without restriction, including without limitation 7# the rights to use, copy, modify, merge, publish, distribute, sublicense, 8# and/or sell copies of the Software, and to permit persons to whom the 9# Software is furnished to do so, subject to the following conditions: 10# 11# The above copyright notice and this permission notice (including the next 12# paragraph) shall be included in all copies or substantial portions of the 13# Software. 14# 15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21# IN THE SOFTWARE. 22 23import sys 24import itertools 25from bifrost_isa import parse_instructions, opname_to_c, expand_states 26from mako.template import Template 27 28instructions = parse_instructions(sys.argv[1], include_unused = True) 29 30# Constructs a reserved mask for a derived to cull impossible encodings 31 32def reserved_mask(derived): 33 ((pos, width), opts) = derived 34 reserved = [x is None for x in opts] 35 mask = sum([(y << x) for x, y in enumerate(reserved)]) 36 return (pos, width, mask) 37 38def reserved_masks(op): 39 masks = [reserved_mask(m) for m in op[2].get("derived", [])] 40 return [m for m in masks if m[2] != 0] 41 42# To decode instructions, pattern match based on the rules: 43# 44# 1. Execution unit (FMA or ADD) must line up. 45# 2. All exact bits must match. 46# 3. No fields should be reserved in a legal encoding. 47# 4. Tiebreaker: Longer exact masks (greater unsigned bitwise inverses) win. 48# 49# To implement, filter the execution unit and check for exact bits in 50# descending order of exact mask length. Check for reserved fields per 51# candidate and succeed if it matches. 52# found. 53 54def decode_op(instructions, is_fma): 55 # Filter out the desired execution unit 56 options = [n for n in instructions.keys() if (n[0] == '*') == is_fma] 57 58 # Sort by exact masks, descending 59 MAX_MASK = (1 << (23 if is_fma else 20)) - 1 60 options.sort(key = lambda n: (MAX_MASK ^ instructions[n][2]["exact"][0])) 61 62 # Map to what we need to template 63 mapped = [(opname_to_c(op), instructions[op][2]["exact"], reserved_masks(instructions[op])) for op in options] 64 65 # Generate checks in order 66 template = """void 67bi_disasm_${unit}(FILE *fp, unsigned bits, struct bifrost_regs *srcs, struct bifrost_regs *next_regs, unsigned staging_register, unsigned branch_offset, struct bi_constants *consts, bool last) 68{ 69 fputs(" ", fp); 70 71% for (i, (name, (emask, ebits), derived)) in enumerate(options): 72% if len(derived) > 0: 73 ${"else " if i > 0 else ""}if (unlikely(((bits & ${hex(emask)}) == ${hex(ebits)}) 74% for (pos, width, reserved) in derived: 75 && !(${hex(reserved)} & (1 << _BITS(bits, ${pos}, ${width}))) 76% endfor 77 )) 78% else: 79 ${"else " if i > 0 else ""}if (unlikely(((bits & ${hex(emask)}) == ${hex(ebits)}))) 80% endif 81 bi_disasm_${name}(fp, bits, srcs, next_regs, staging_register, branch_offset, consts, last); 82% endfor 83 else 84 fprintf(fp, "INSTR_INVALID_ENC ${unit} %X", bits); 85 86 fputs("\\n", fp); 87}""" 88 89 return Template(template).render(options = mapped, unit = "fma" if is_fma else "add") 90 91# Decoding emits a series of function calls to e.g. `fma_fadd_v2f16`. We need to 92# emit functions to disassemble a single decoded instruction in a particular 93# state. Sync prototypes to avoid moves when calling. 94 95disasm_op_template = Template("""static void 96bi_disasm_${c_name}(FILE *fp, unsigned bits, struct bifrost_regs *srcs, struct bifrost_regs *next_regs, unsigned staging_register, unsigned branch_offset, struct bi_constants *consts, bool last) 97{ 98 ${body.strip()} 99} 100""") 101 102lut_template_only = Template(""" static const char *${field}[] = { 103 ${", ".join(['"' + x + '"' for x in table])} 104 }; 105""") 106 107# Given a lookup table written logically, generate an accessor 108lut_template = Template(""" static const char *${field}_table[] = { 109 ${", ".join(['"' + x + '"' for x in table])} 110 }; 111 112 const char *${field} = ${field}_table[_BITS(bits, ${pos}, ${width})]; 113""") 114 115# Helpers for decoding follow. pretty_mods applies dot syntax 116 117def pretty_mods(opts, default): 118 return [('.' + (opt or 'reserved') if opt != default else '') for opt in opts] 119 120# Recursively searches for the set of free variables required by an expression 121 122def find_context_keys_expr(expr): 123 if isinstance(expr, list): 124 return set.union(*[find_context_keys_expr(x) for x in expr[1:]]) 125 elif expr[0] == '#': 126 return set() 127 else: 128 return set([expr]) 129 130def find_context_keys(desc, test): 131 keys = set() 132 133 if len(test) > 0: 134 keys |= find_context_keys_expr(test) 135 136 for i, (_, vals) in enumerate(desc.get('derived', [])): 137 for j, val in enumerate(vals): 138 if val is not None: 139 keys |= find_context_keys_expr(val) 140 141 return keys 142 143# Compiles a logic expression to Python expression, ctx -> { T, F } 144 145EVALUATORS = { 146 'and': ' and ', 147 'or': ' or ', 148 'eq': ' == ', 149 'neq': ' != ', 150} 151 152def compile_derived_inner(expr, keys): 153 if expr == []: 154 return 'True' 155 elif expr is None or expr[0] == 'alias': 156 return 'False' 157 elif isinstance(expr, list): 158 args = [compile_derived_inner(arg, keys) for arg in expr[1:]] 159 return '(' + EVALUATORS[expr[0]].join(args) + ')' 160 elif expr[0] == '#': 161 return "'{}'".format(expr[1:]) 162 elif expr == 'ordering': 163 return expr 164 else: 165 return "ctx[{}]".format(keys.index(expr)) 166 167def compile_derived(expr, keys): 168 return eval('lambda ctx, ordering: ' + compile_derived_inner(expr, keys)) 169 170# Generate all possible combinations of values and evaluate the derived values 171# by bruteforce evaluation to generate a forward mapping (values -> deriveds) 172 173def evaluate_forward_derived(vals, ctx, ordering): 174 for j, expr in enumerate(vals): 175 if expr(ctx, ordering): 176 return j 177 178 return None 179 180def evaluate_forward(keys, derivf, testf, ctx, ordering): 181 if not testf(ctx, ordering): 182 return None 183 184 deriv = [] 185 186 for vals in derivf: 187 evaled = evaluate_forward_derived(vals, ctx, ordering) 188 189 if evaled is None: 190 return None 191 192 deriv.append(evaled) 193 194 return deriv 195 196def evaluate_forwards(keys, derivf, testf, mod_vals, ordered): 197 orderings = ["lt", "gt"] if ordered else [None] 198 return [[evaluate_forward(keys, derivf, testf, i, order) for i in itertools.product(*mod_vals)] for order in orderings] 199 200# Invert the forward mapping (values -> deriveds) of finite sets to produce a 201# backwards mapping (deriveds -> values), suitable for disassembly. This is 202# possible since the encoding is unambiguous, so this mapping is a bijection 203# (after reserved/impossible encodings) 204 205def invert_lut(value_size, forward, derived, mod_map, keys, mod_vals): 206 backwards = [None] * (1 << value_size) 207 for (i, deriveds), ctx in zip(enumerate(forward), itertools.product(*mod_vals)): 208 # Skip reserved 209 if deriveds == None: 210 continue 211 212 shift = 0 213 param = 0 214 for j, ((x, width), y) in enumerate(derived): 215 param += (deriveds[j] << shift) 216 shift += width 217 218 assert(param not in backwards) 219 backwards[param] = ctx 220 221 return backwards 222 223# Compute the value of all indirectly specified modifiers by using the 224# backwards mapping (deriveds -> values) as a run-time lookup table. 225 226def build_lut(mnemonic, desc, test): 227 # Construct the system 228 facts = [] 229 230 mod_map = {} 231 232 for ((name, pos, width), default, values) in desc.get('modifiers', []): 233 mod_map[name] = (width, values, pos, default) 234 235 derived = desc.get('derived', []) 236 237 # Find the keys and impose an order 238 key_set = find_context_keys(desc, test) 239 ordered = 'ordering' in key_set 240 key_set.discard('ordering') 241 keys = list(key_set) 242 243 # Evaluate the deriveds for every possible state, forming a (state -> deriveds) map 244 testf = compile_derived(test, keys) 245 derivf = [[compile_derived(expr, keys) for expr in v] for (_, v) in derived] 246 mod_vals = [mod_map[k][1] for k in keys] 247 forward = evaluate_forwards(keys, derivf, testf, mod_vals, ordered) 248 249 # Now invert that map to get a (deriveds -> state) map 250 value_size = sum([width for ((x, width), y) in derived]) 251 backwards = [invert_lut(value_size, f, derived, mod_map, keys, mod_vals) for f in forward] 252 253 # From that map, we can generate LUTs 254 output = "" 255 256 if ordered: 257 output += "bool ordering = (_BITS(bits, {}, 3) > _BITS(bits, {}, 3));\n".format(desc["srcs"][0][0], desc["srcs"][1][0]) 258 259 for j, key in enumerate(keys): 260 # Only generate tables for indirect specifiers 261 if mod_map[key][2] is not None: 262 continue 263 264 idx_parts = [] 265 shift = 0 266 267 for ((pos, width), _) in derived: 268 idx_parts.append("(_BITS(bits, {}, {}) << {})".format(pos, width, shift)) 269 shift += width 270 271 built_idx = (" | ".join(idx_parts)) if len(idx_parts) > 0 else "0" 272 273 default = mod_map[key][3] 274 275 if ordered: 276 for i, order in enumerate(backwards): 277 options = [ctx[j] if ctx is not None and ctx[j] is not None else "reserved" for ctx in order] 278 output += lut_template_only.render(field = key + "_" + str(i), table = pretty_mods(options, default)) 279 280 output += " const char *{} = ordering ? {}_1[{}] : {}_0[{}];\n".format(key, key, built_idx, key, built_idx) 281 else: 282 options = [ctx[j] if ctx is not None and ctx[j] is not None else "reserved" for ctx in backwards[0]] 283 output += lut_template_only.render(field = key + "_table", table = pretty_mods(options, default)) 284 output += " const char *{} = {}_table[{}];\n".format(key, key, built_idx) 285 286 return output 287 288def disasm_mod(mod, skip_mods): 289 if mod[0][0] in skip_mods: 290 return '' 291 else: 292 return ' fputs({}, fp);\n'.format(mod[0][0]) 293 294def disasm_op(name, op): 295 (mnemonic, test, desc) = op 296 is_fma = mnemonic[0] == '*' 297 298 # Modifiers may be either direct (pos is not None) or indirect (pos is 299 # None). If direct, we just do the bit lookup. If indirect, we use a LUT. 300 301 body = "" 302 skip_mods = [] 303 304 body += build_lut(mnemonic, desc, test) 305 306 for ((mod, pos, width), default, opts) in desc.get('modifiers', []): 307 if pos is not None: 308 body += lut_template.render(field = mod, table = pretty_mods(opts, default), pos = pos, width = width) + "\n" 309 310 # Mnemonic, followed by modifiers 311 body += ' fputs("{}", fp);\n'.format(mnemonic) 312 313 srcs = desc.get('srcs', []) 314 315 for mod in desc.get('modifiers', []): 316 # Skip per-source until next block 317 if mod[0][0][-1] in "0123" and int(mod[0][0][-1]) < len(srcs): 318 continue 319 320 body += disasm_mod(mod, skip_mods) 321 322 body += ' fputs(" ", fp);\n' 323 body += ' bi_disasm_dest_{}(fp, next_regs, last);\n'.format('fma' if is_fma else 'add') 324 325 # Next up, each source. Source modifiers are inserterd here 326 327 for i, (pos, mask) in enumerate(srcs): 328 body += ' fputs(", ", fp);\n' 329 body += ' dump_src(fp, _BITS(bits, {}, 3), *srcs, branch_offset, consts, {});\n'.format(pos, "true" if is_fma else "false") 330 331 # Error check if needed 332 if (mask != 0xFF): 333 body += ' if (!({} & (1 << _BITS(bits, {}, 3)))) fputs("(INVALID)", fp);\n'.format(hex(mask), pos, 3) 334 335 # Print modifiers suffixed with this src number (e.g. abs0 for src0) 336 for mod in desc.get('modifiers', []): 337 if mod[0][0][-1] == str(i): 338 body += disasm_mod(mod, skip_mods) 339 340 # And each immediate 341 for (imm, pos, width) in desc.get('immediates', []): 342 body += ' fprintf(fp, ", {}:%u", _BITS(bits, {}, {}));\n'.format(imm, pos, width) 343 344 # Attach a staging register if one is used 345 if desc.get('staging'): 346 body += ' fprintf(fp, ", @r%u", staging_register);\n' 347 348 return disasm_op_template.render(c_name = opname_to_c(name), body = body) 349 350print('#include "util/macros.h"') 351print('#include "disassemble.h"') 352 353states = expand_states(instructions) 354print('#define _BITS(bits, pos, width) (((bits) >> (pos)) & ((1 << (width)) - 1))') 355 356for st in states: 357 print(disasm_op(st, states[st])) 358 359print(decode_op(states, True)) 360print(decode_op(states, False)) 361