| # |
| # Copyright (C) 2020 Collabora, Ltd. |
| # |
| # Permission is hereby granted, free of charge, to any person obtaining a |
| # copy of this software and associated documentation files (the "Software"), |
| # to deal in the Software without restriction, including without limitation |
| # the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| # and/or sell copies of the Software, and to permit persons to whom the |
| # Software is furnished to do so, subject to the following conditions: |
| # |
| # The above copyright notice and this permission notice (including the next |
| # paragraph) shall be included in all copies or substantial portions of the |
| # Software. |
| # |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| # IN THE SOFTWARE. |
| |
| # Useful for autogeneration |
| COPYRIGHT = """/* |
| * Copyright (C) 2020 Collabora, Ltd. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
| * and/or sell copies of the Software, and to permit persons to whom the |
| * Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| /* Autogenerated file, do not edit */ |
| |
| """ |
| |
| # Parse instruction set XML into a normalized form for processing |
| |
| import xml.etree.ElementTree as ET |
| import copy |
| import itertools |
| from collections import OrderedDict |
| |
| def parse_cond(cond, aliased = False): |
| if cond.tag == 'reserved': |
| return None |
| |
| if cond.attrib.get('alias', False) and not aliased: |
| return ['alias', parse_cond(cond, True)] |
| |
| if 'left' in cond.attrib: |
| return [cond.tag, cond.attrib['left'], cond.attrib['right']] |
| else: |
| return [cond.tag] + [parse_cond(x) for x in cond.findall('*')] |
| |
| def parse_exact(obj): |
| return [int(obj.attrib['mask'], 0), int(obj.attrib['exact'], 0)] |
| |
| def parse_derived(obj): |
| out = [] |
| |
| for deriv in obj.findall('derived'): |
| loc = [int(deriv.attrib['start']), int(deriv.attrib['size'])] |
| count = 1 << loc[1] |
| |
| opts = [parse_cond(d) for d in deriv.findall('*')] |
| default = [None] * count |
| opts_fit = (opts + default)[0:count] |
| |
| out.append([loc, opts_fit]) |
| |
| return out |
| |
| def parse_modifiers(obj, include_pseudo): |
| out = [] |
| |
| for mod in obj.findall('mod'): |
| if mod.attrib.get('pseudo', False) and not include_pseudo: |
| continue |
| |
| name = mod.attrib['name'] |
| start = mod.attrib.get('start', None) |
| size = int(mod.attrib['size']) |
| |
| if start is not None: |
| start = int(start) |
| |
| opts = [x.text if x.tag == 'opt' else x.tag for x in mod.findall('*')] |
| |
| if len(opts) == 0: |
| assert('opt' in mod.attrib) |
| opts = ['none', mod.attrib['opt']] |
| |
| # Find suitable default |
| default = mod.attrib.get('default', 'none' if 'none' in opts else None) |
| |
| # Pad out as reserved |
| count = (1 << size) |
| opts = (opts + (['reserved'] * count))[0:count] |
| out.append([[name, start, size], default, opts]) |
| |
| return out |
| |
| def parse_copy(enc, existing): |
| for node in enc.findall('copy'): |
| name = node.get('name') |
| for ex in existing: |
| if ex[0][0] == name: |
| ex[0][1] = node.get('start') |
| |
| def parse_instruction(ins, include_pseudo): |
| common = { |
| 'srcs': [], |
| 'modifiers': [], |
| 'immediates': [], |
| 'swaps': [], |
| 'derived': [], |
| 'staging': ins.attrib.get('staging', '').split('=')[0], |
| 'staging_count': ins.attrib.get('staging', '=0').split('=')[1], |
| 'dests': int(ins.attrib.get('dests', '1')), |
| 'variable_dests': ins.attrib.get('variable_dests', False), |
| 'variable_srcs': ins.attrib.get('variable_srcs', False), |
| 'unused': ins.attrib.get('unused', False), |
| 'pseudo': ins.attrib.get('pseudo', False), |
| 'message': ins.attrib.get('message', 'none'), |
| 'last': ins.attrib.get('last', False), |
| 'table': ins.attrib.get('table', False), |
| } |
| |
| if 'exact' in ins.attrib: |
| common['exact'] = parse_exact(ins) |
| |
| for src in ins.findall('src'): |
| if src.attrib.get('pseudo', False) and not include_pseudo: |
| continue |
| |
| mask = int(src.attrib['mask'], 0) if ('mask' in src.attrib) else 0xFF |
| common['srcs'].append([int(src.attrib['start'], 0), mask]) |
| |
| for imm in ins.findall('immediate'): |
| if imm.attrib.get('pseudo', False) and not include_pseudo: |
| continue |
| |
| start = int(imm.attrib['start']) if 'start' in imm.attrib else None |
| common['immediates'].append([imm.attrib['name'], start, int(imm.attrib['size'])]) |
| |
| common['derived'] = parse_derived(ins) |
| common['modifiers'] = parse_modifiers(ins, include_pseudo) |
| |
| for swap in ins.findall('swap'): |
| lr = [int(swap.get('left')), int(swap.get('right'))] |
| cond = parse_cond(swap.findall('*')[0]) |
| rewrites = {} |
| |
| for rw in swap.findall('rewrite'): |
| mp = {} |
| |
| for m in rw.findall('map'): |
| mp[m.attrib['from']] = m.attrib['to'] |
| |
| rewrites[rw.attrib['name']] = mp |
| |
| common['swaps'].append([lr, cond, rewrites]) |
| |
| encodings = ins.findall('encoding') |
| variants = [] |
| |
| if len(encodings) == 0: |
| variants = [[None, common]] |
| else: |
| for enc in encodings: |
| variant = copy.deepcopy(common) |
| assert(len(variant['derived']) == 0) |
| |
| variant['exact'] = parse_exact(enc) |
| variant['derived'] = parse_derived(enc) |
| parse_copy(enc, variant['modifiers']) |
| |
| cond = parse_cond(enc.findall('*')[0]) |
| variants.append([cond, variant]) |
| |
| return variants |
| |
| def parse_instructions(xml, include_unused = False, include_pseudo = False): |
| final = {} |
| instructions = ET.parse(xml).getroot().findall('ins') |
| |
| for ins in instructions: |
| parsed = parse_instruction(ins, include_pseudo) |
| |
| # Some instructions are for useful disassembly only and can be stripped |
| # out of the compiler, particularly useful for release builds |
| if parsed[0][1]["unused"] and not include_unused: |
| continue |
| |
| # On the other hand, some instructions are only for the IR, not disassembly |
| if parsed[0][1]["pseudo"] and not include_pseudo: |
| continue |
| |
| final[ins.attrib['name']] = parsed |
| |
| return final |
| |
| # Expand out an opcode name to something C-escaped |
| |
| def opname_to_c(name): |
| return name.lower().replace('*', 'fma_').replace('+', 'add_').replace('.', '_') |
| |
| # Expand out distinct states to distrinct instructions, with a placeholder |
| # condition for instructions with a single state |
| |
| def expand_states(instructions): |
| out = {} |
| |
| for ins in instructions: |
| c = instructions[ins] |
| |
| for ((test, desc), i) in zip(c, range(len(c))): |
| # Construct a name for the state |
| name = ins + (('.' + str(i)) if len(c) > 1 else '') |
| |
| out[name] = (ins, test if test is not None else [], desc) |
| |
| return out |
| |
| # Drop keys used for packing to simplify IR representation, so we can check for |
| # equivalence easier |
| |
| def simplify_to_ir(ins): |
| return { |
| 'staging': ins['staging'], |
| 'srcs': len(ins['srcs']), |
| 'dests': ins['dests'], |
| 'variable_dests': ins['variable_dests'], |
| 'variable_srcs': ins['variable_srcs'], |
| 'modifiers': [[m[0][0], m[2]] for m in ins['modifiers']], |
| 'immediates': [m[0] for m in ins['immediates']] |
| } |
| |
| # Converstions to integers default to rounding-to-zero |
| # All other opcodes default to rounding to nearest even |
| def default_round_to_zero(name): |
| # 8-bit int to float is exact |
| subs = ['_TO_U', '_TO_S', '_TO_V2U', '_TO_V2S', '_TO_V4U', '_TO_V4S'] |
| return any([x in name for x in subs]) |
| |
| def combine_ir_variants(instructions, key): |
| seen = [op for op in instructions.keys() if op[1:] == key] |
| variant_objs = [[simplify_to_ir(Q[1]) for Q in instructions[x]] for x in seen] |
| variants = sum(variant_objs, []) |
| |
| # Accumulate modifiers across variants |
| modifiers = {} |
| |
| for s in variants[0:]: |
| # Check consistency |
| assert(s['srcs'] == variants[0]['srcs']) |
| assert(s['dests'] == variants[0]['dests']) |
| assert(s['immediates'] == variants[0]['immediates']) |
| assert(s['staging'] == variants[0]['staging']) |
| |
| for name, opts in s['modifiers']: |
| if name not in modifiers: |
| modifiers[name] = copy.deepcopy(opts) |
| else: |
| modifiers[name] += opts |
| |
| # Great, we've checked srcs/immediates are consistent and we've summed over |
| # modifiers |
| return { |
| 'key': key, |
| 'srcs': variants[0]['srcs'], |
| 'dests': variants[0]['dests'], |
| 'variable_dests': variants[0]['variable_dests'], |
| 'variable_srcs': variants[0]['variable_srcs'], |
| 'staging': variants[0]['staging'], |
| 'immediates': sorted(variants[0]['immediates']), |
| 'modifiers': modifiers, |
| 'v': len(variants), |
| 'ir': variants, |
| 'rtz': default_round_to_zero(key) |
| } |
| |
| # Partition instructions to mnemonics, considering units and variants |
| # equivalent. |
| |
| def partition_mnemonics(instructions): |
| key_func = lambda x: x[1:] |
| sorted_instrs = sorted(instructions.keys(), key = key_func) |
| partitions = itertools.groupby(sorted_instrs, key_func) |
| return { k: combine_ir_variants(instructions, k) for k, v in partitions } |
| |
| # Generate modifier lists, by accumulating all the possible modifiers, and |
| # deduplicating thus assigning canonical enum values. We don't try _too_ hard |
| # to be clever, but by preserving as much of the original orderings as |
| # possible, later instruction encoding is simplified a bit. Probably a micro |
| # optimization but we have to pick _some_ ordering, might as well choose the |
| # most convenient. |
| # |
| # THIS MUST BE DETERMINISTIC |
| |
| def order_modifiers(ir_instructions): |
| out = {} |
| |
| # modifier name -> (list of option strings) |
| modifier_lists = {} |
| |
| for ins in sorted(ir_instructions): |
| modifiers = ir_instructions[ins]["modifiers"] |
| |
| for name in modifiers: |
| name_ = name[0:-1] if name[-1] in "0123" else name |
| |
| if name_ not in modifier_lists: |
| modifier_lists[name_] = copy.deepcopy(modifiers[name]) |
| else: |
| modifier_lists[name_] += modifiers[name] |
| |
| for mod in modifier_lists: |
| lst = list(OrderedDict.fromkeys(modifier_lists[mod])) |
| |
| # Ensure none is false for booleans so the builder makes sense |
| if len(lst) == 2 and lst[1] == "none": |
| lst.reverse() |
| elif mod == "table": |
| # We really need a zero sentinel to materialize DTSEL |
| assert(lst[2] == "none") |
| lst[2] = lst[0] |
| lst[0] = "none" |
| |
| out[mod] = lst |
| |
| return out |
| |
| # Count sources for a simplified (IR) instruction, including a source for a |
| # staging register if necessary |
| def src_count(op): |
| staging = 1 if (op["staging"] in ["r", "rw"]) else 0 |
| return op["srcs"] + staging |
| |
| # Parses out the size part of an opocde name |
| def typesize(opcode): |
| if opcode[-3:] == '128': |
| return 128 |
| if opcode[-2:] == '48': |
| return 48 |
| elif opcode[-1] == '8': |
| return 8 |
| else: |
| try: |
| return int(opcode[-2:]) |
| except: |
| return 32 |