xref: /aosp_15_r20/external/mesa3d/src/panfrost/compiler/bifrost_isa.py (revision 6104692788411f58d303aa86923a9ff6ecaded22)
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
23# Useful for autogeneration
24COPYRIGHT = """/*
25 * Copyright (C) 2020 Collabora, Ltd.
26 *
27 * Permission is hereby granted, free of charge, to any person obtaining a
28 * copy of this software and associated documentation files (the "Software"),
29 * to deal in the Software without restriction, including without limitation
30 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
31 * and/or sell copies of the Software, and to permit persons to whom the
32 * Software is furnished to do so, subject to the following conditions:
33 *
34 * The above copyright notice and this permission notice (including the next
35 * paragraph) shall be included in all copies or substantial portions of the
36 * Software.
37 *
38 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
41 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44 * SOFTWARE.
45 */
46
47/* Autogenerated file, do not edit */
48
49"""
50
51# Parse instruction set XML into a normalized form for processing
52
53import xml.etree.ElementTree as ET
54import copy
55import itertools
56from collections import OrderedDict
57
58def parse_cond(cond, aliased = False):
59    if cond.tag == 'reserved':
60        return None
61
62    if cond.attrib.get('alias', False) and not aliased:
63        return ['alias', parse_cond(cond, True)]
64
65    if 'left' in cond.attrib:
66        return [cond.tag, cond.attrib['left'], cond.attrib['right']]
67    else:
68        return [cond.tag] + [parse_cond(x) for x in cond.findall('*')]
69
70def parse_exact(obj):
71    return [int(obj.attrib['mask'], 0), int(obj.attrib['exact'], 0)]
72
73def parse_derived(obj):
74    out = []
75
76    for deriv in obj.findall('derived'):
77        loc = [int(deriv.attrib['start']), int(deriv.attrib['size'])]
78        count = 1 << loc[1]
79
80        opts = [parse_cond(d) for d in deriv.findall('*')]
81        default = [None] * count
82        opts_fit = (opts + default)[0:count]
83
84        out.append([loc, opts_fit])
85
86    return out
87
88def parse_modifiers(obj, include_pseudo):
89    out = []
90
91    for mod in obj.findall('mod'):
92        if mod.attrib.get('pseudo', False) and not include_pseudo:
93            continue
94
95        name = mod.attrib['name']
96        start = mod.attrib.get('start', None)
97        size = int(mod.attrib['size'])
98
99        if start is not None:
100            start = int(start)
101
102        opts = [x.text if x.tag == 'opt' else x.tag for x in mod.findall('*')]
103
104        if len(opts) == 0:
105            if 'opt' in mod.attrib:
106                opts = ['none', mod.attrib['opt']]
107
108        # Find suitable default
109        default = mod.attrib.get('default', 'none' if 'none' in opts else None)
110
111        # Pad out as reserved
112        count = (1 << size)
113        opts = (opts + (['reserved'] * count))[0:count]
114        out.append([[name, start, size], default, opts])
115
116    return out
117
118def parse_copy(enc, existing):
119    for node in enc.findall('copy'):
120        name = node.get('name')
121        for ex in existing:
122            if ex[0][0] == name:
123                ex[0][1] = node.get('start')
124
125mod_names = {
126    'cmp'     : [['cmpf', None, None], None, ['eq', 'gt', 'ge', 'ne', 'lt', 'le', 'gtlt', 'total']],
127    # FIXME: Valhall can accept any integer comparision, but the old IR generator only generated
128    #   gt and ge, and left out lt and le. For now follow the old way so we can compare generated files,
129    #   but we should switch over to the proper way once everything is working
130#    'cmpfi'    : [['cmpf', None, None], None, ['eq', 'ne', 'gt', 'ge', 'lt', 'le']],
131    'cmpfi'     : [['cmpf', None, None], None, ['eq', 'ne', 'gt', 'ge']],
132    'eq'        : [['cmpf', None, None], 'ne', ['eq', 'ne']],
133    'dimension' : [['dimension', None, None], None, ['1d', '2d', '3d', 'cube']],
134    'fetch_component' : [['fetch_component', None, None], None, ['gather4_r', 'gather4_g', 'gather4_b', 'gather4_a']],
135    'lod_mode': [['va_lod_mode', None, None], 'zero_lod', ['zero_lod', 'computed_lod', 'explicit', 'computed_bias', 'grdesc']],
136    'regfmt'  : [['register_format', None, None], None, ['f16', 'f32', 's32', 'u32', 's16', 'u16', 'f64', 'i64', 'auto']],
137    'result_type' : [['result_type', None, None], None, ['i1', 'f1', 'm1']],
138    'sample'  : [['sample', None, None], 'none', ['center', 'centroid', 'sample', 'explicit', 'none']],
139    'update'  : [['update', None, None], None, ['store', 'retrieve', 'conditional', 'clobber']],
140    'vecsize' : [['vecsize', None, None], 'none', ['none', 'v2', 'v3', 'v4']],
141    'source_format' : [['source_format', None, None], None, ['flat32', 'flat16', 'f32', 'f16']],
142
143    'array_enable': [['array_enable', None, None], 'none', ['none', 'array_enable']],
144    'integer_coordinates': [['integer_coordinates', None, None], 'none', ['none', 'integer_coordinates']],
145    'shadow'      : [['shadow', None, None], 'none', ['none', 'shadow']],
146    'skip'        : [['skip', None, None], 'none', ['none', 'skip']],
147    'texel_offset': [['texel_offset', None, None], 'none', ['none', 'texel_offset']],
148    'wide_indices': [['wide_indices', None, None], 'none', ['none', 'wide_indices']],
149    'write_mask' : [['write_mask', None, None], 'none', ['none', 'r', 'g', 'rg', 'b', 'rb', 'gb', 'rgb', 'a', 'ra', 'ga', 'rga', 'ba', 'rba', 'gba', 'rgba']]
150    }
151
152def parse_instruction(ins, include_pseudo):
153    common = {
154            'srcs': [],
155            'modifiers': [],
156            'immediates': [],
157            'swaps': [],
158            'derived': [],
159            'staging': ins.attrib.get('staging', '').split('=')[0],
160            'staging_count': ins.attrib.get('staging', '=0').split('=')[1],
161            'dests': int(ins.attrib.get('dests', '1')),
162            'variable_dests': ins.attrib.get('variable_dests', False),
163            'variable_srcs': ins.attrib.get('variable_srcs', False),
164            'unused': ins.attrib.get('unused', False),
165            'pseudo': ins.attrib.get('pseudo', False),
166            'message': ins.attrib.get('message', 'none'),
167            'last': ins.attrib.get('last', False),
168            'table': ins.attrib.get('table', False),
169    }
170
171    if 'exact' in ins.attrib:
172        common['exact'] = parse_exact(ins)
173
174    extra_modifiers=[]
175    src_num = 0
176    for src in ins.findall('src'):
177        if src.attrib.get('pseudo', False) and not include_pseudo:
178            continue
179
180        mask = int(src.attrib['mask'], 0) if ('mask' in src.attrib) else 0xFF
181        if src.attrib.get('start') is not None:
182            common['srcs'].append([int(src.attrib['start'], 0), mask])
183        else:
184            common['srcs'].append([src_num*3, mask])
185        if src.attrib.get('absneg', False):
186            extra_modifiers.append([['neg'+str(src_num), '0', '1'], 'neg',['none', 'neg']])
187            extra_modifiers.append([['abs'+str(src_num), '0', '1'], 'abs',['none', 'abs']])
188        src_num += 1
189
190    for imm in ins.findall('immediate'):
191        if imm.attrib.get('pseudo', False) and not include_pseudo:
192            continue
193
194        start = int(imm.attrib['start']) if 'start' in imm.attrib else None
195        common['immediates'].append([imm.attrib['name'], start, int(imm.attrib['size'])])
196
197    # FIXME valhall ISA.xml uses <imm/> instead of <immediate/>
198    for imm in ins.findall('imm'):
199        if imm.attrib.get('pseudo', False) and not include_pseudo:
200            continue
201
202        base_name = imm.attrib['name']
203        name = imm.attrib.get('ir_name', base_name)
204        if not name:
205            continue
206
207        start = int(imm.attrib['start']) if 'start' in imm.attrib else None
208        common['immediates'].append([name, start, int(imm.attrib['size'])])
209
210    staging_read = False
211    staging_write = False
212    for sr in ins.findall('sr'):
213        if sr.attrib.get('read', False):
214            staging_read = True
215        if sr.attrib.get('write', False):
216            staging_write = True
217
218    if staging_read:
219        common['staging'] = 'r'
220    if staging_write:
221        common['staging'] += 'w'
222    for sr in ins.findall('sr_count'):
223        size = sr.attrib.get('count', 'sr_count')
224        common['staging_count'] = size
225
226    for m in ins.findall('*'):
227        name = m.tag
228        if name == 'cmp':
229            if m.attrib.get('int_only', False):
230                name = 'cmpfi'
231            elif m.attrib.get('eqne_only', False):
232                name = 'cmpfeq'
233        if name == 'va_mod':
234            name = m.attrib.get('name', '')
235        if name in mod_names:
236            extra_modifiers.append(mod_names[name])
237
238    common['derived'] = parse_derived(ins)
239    common['modifiers'] = parse_modifiers(ins, include_pseudo) + extra_modifiers
240
241    for swap in ins.findall('swap'):
242        lr = [int(swap.get('left')), int(swap.get('right'))]
243        cond = parse_cond(swap.findall('*')[0])
244        rewrites = {}
245
246        for rw in swap.findall('rewrite'):
247            mp = {}
248
249            for m in rw.findall('map'):
250                mp[m.attrib['from']] = m.attrib['to']
251
252            rewrites[rw.attrib['name']] = mp
253
254        common['swaps'].append([lr, cond, rewrites])
255
256    encodings = ins.findall('encoding')
257    variants = []
258
259    if len(encodings) == 0:
260        variants = [[None, common]]
261    else:
262        for enc in encodings:
263            variant = copy.deepcopy(common)
264            assert(len(variant['derived']) == 0)
265
266            variant['exact'] = parse_exact(enc)
267            variant['derived'] = parse_derived(enc)
268            parse_copy(enc, variant['modifiers'])
269
270            cond = parse_cond(enc.findall('*')[0])
271            variants.append([cond, variant])
272
273    return variants
274
275def ins_name(ins, group = False):
276    # a historical artifact: the first character of the name should contain
277    # a single character tag to indicate the unit: '+' for add, '*' for fma
278    # bifrost has only those two units, valhall has others but for those
279    # it doesn't matter (pretend they are '+')
280    if not group:
281        group = ins
282
283    base_name = ins.attrib['name']
284    if group.attrib['unit'] == 'fma':
285        tagged_name = '*' + base_name
286    else:
287        tagged_name = '+' + base_name
288    return tagged_name
289
290def parse_instructions(xml, include_unused = False, include_pseudo = False):
291    final = {}
292
293    # look at groups of instructions
294    groups = ET.parse(xml).getroot().findall('group')
295    for gr in groups:
296        if gr.attrib.get('unused', False) and not include_unused:
297            continue
298        if gr.attrib.get('pseudo', False) and not include_pseudo:
299            continue
300        group_base = parse_instruction(gr, include_pseudo)
301        for ins in gr.findall('ins'):
302            parsed = copy.deepcopy(group_base)
303            tagged_name = ins_name(ins, gr)
304            final[tagged_name] = parsed
305
306    # now look at individual instructions
307    instructions = ET.parse(xml).getroot().findall('ins')
308
309    for ins in instructions:
310        parsed = parse_instruction(ins, include_pseudo)
311
312        # Some instructions are for useful disassembly only and can be stripped
313        # out of the compiler, particularly useful for release builds
314        if parsed[0][1]["unused"] and not include_unused:
315            continue
316
317        # On the other hand, some instructions are only for the IR, not disassembly
318        if parsed[0][1]["pseudo"] and not include_pseudo:
319            continue
320
321        tagged_name = ins_name(ins)
322        final[tagged_name] = parsed
323
324    return final
325
326# Expand out an opcode name to something C-escaped
327
328def opname_to_c(name):
329    return name.lower().replace('*', 'fma_').replace('+', 'add_').replace('.', '_')
330
331# Expand out distinct states to distrinct instructions, with a placeholder
332# condition for instructions with a single state
333
334def expand_states(instructions):
335    out = {}
336
337    for ins in instructions:
338        c = instructions[ins]
339
340        for ((test, desc), i) in zip(c, range(len(c))):
341            # Construct a name for the state
342            name = ins + (('.' + str(i)) if len(c) > 1 else '')
343
344            out[name] = (ins, test if test is not None else [], desc)
345
346    return out
347
348# Drop keys used for packing to simplify IR representation, so we can check for
349# equivalence easier
350
351def simplify_to_ir(ins):
352    return {
353            'staging': ins['staging'],
354            'srcs': len(ins['srcs']),
355            'dests': ins['dests'],
356            'variable_dests': ins['variable_dests'],
357            'variable_srcs': ins['variable_srcs'],
358            'modifiers': [[m[0][0], m[2]] for m in ins['modifiers']],
359            'immediates': [m[0] for m in ins['immediates']]
360        }
361
362# Converstions to integers default to rounding-to-zero
363# All other opcodes default to rounding to nearest even
364def default_round_to_zero(name):
365    # 8-bit int to float is exact
366    subs = ['_TO_U', '_TO_S', '_TO_V2U', '_TO_V2S', '_TO_V4U', '_TO_V4S']
367    return any([x in name for x in subs])
368
369def combine_ir_variants(instructions, key):
370    seen = [op for op in instructions.keys() if op[1:] == key]
371    variant_objs = [[simplify_to_ir(Q[1]) for Q in instructions[x]] for x in seen]
372    variants = sum(variant_objs, [])
373
374    # Accumulate modifiers across variants
375    modifiers = {}
376
377    for s in variants[0:]:
378        # Check consistency
379        assert(s['srcs'] == variants[0]['srcs'])
380        assert(s['dests'] == variants[0]['dests'])
381        assert(s['immediates'] == variants[0]['immediates'])
382        assert(s['staging'] == variants[0]['staging'])
383
384        for name, opts in s['modifiers']:
385            if name not in modifiers:
386                modifiers[name] = copy.deepcopy(opts)
387            else:
388                modifiers[name] += opts
389
390    # Great, we've checked srcs/immediates are consistent and we've summed over
391    # modifiers
392    return {
393            'key': key,
394            'srcs': variants[0]['srcs'],
395            'dests': variants[0]['dests'],
396            'variable_dests': variants[0]['variable_dests'],
397            'variable_srcs': variants[0]['variable_srcs'],
398            'staging': variants[0]['staging'],
399            'immediates': sorted(variants[0]['immediates']),
400            'modifiers': modifiers,
401            'v': len(variants),
402            'ir': variants,
403            'rtz': default_round_to_zero(key)
404        }
405
406# Partition instructions to mnemonics, considering units and variants
407# equivalent.
408
409def partition_mnemonics(instructions):
410    key_func = lambda x: x[1:]
411    sorted_instrs = sorted(instructions.keys(), key = key_func)
412    partitions = itertools.groupby(sorted_instrs, key_func)
413    return { k: combine_ir_variants(instructions, k) for k, v in partitions }
414
415# Generate modifier lists, by accumulating all the possible modifiers, and
416# deduplicating thus assigning canonical enum values. We don't try _too_ hard
417# to be clever, but by preserving as much of the original orderings as
418# possible, later instruction encoding is simplified a bit.  Probably a micro
419# optimization but we have to pick _some_ ordering, might as well choose the
420# most convenient.
421#
422# THIS MUST BE DETERMINISTIC
423
424def order_modifiers(ir_instructions):
425    out = {}
426
427    # modifier name -> (list of option strings)
428    modifier_lists = {}
429
430    for ins in sorted(ir_instructions):
431        modifiers = ir_instructions[ins]["modifiers"]
432
433        for name in modifiers:
434            name_ = name[0:-1] if name[-1] in "0123" else name
435
436            if name_ not in modifier_lists:
437                modifier_lists[name_] = copy.deepcopy(modifiers[name])
438            else:
439                modifier_lists[name_] += modifiers[name]
440
441    for mod in modifier_lists:
442        lst = list(OrderedDict.fromkeys(modifier_lists[mod]))
443
444        # Ensure none is false for booleans so the builder makes sense
445        if len(lst) == 2 and lst[1] == "none":
446            lst.reverse()
447        elif mod == "table":
448            # We really need a zero sentinel to materialize DTSEL
449            assert(lst[2] == "none")
450            lst[2] = lst[0]
451            lst[0] = "none"
452
453        out[mod] = lst
454
455    return out
456
457# Count sources for a simplified (IR) instruction, including a source for a
458# staging register if necessary
459def src_count(op):
460    staging = 1 if (op["staging"] in ["r", "rw"]) else 0
461    return op["srcs"] + staging
462
463# Parses out the size part of an opocde name
464def typesize(opcode):
465    if opcode[-3:] == '128':
466        return 128
467    if opcode[-2:] == '48':
468        return 48
469    elif opcode[-1] == '8':
470        return 8
471    else:
472        try:
473            return int(opcode[-2:])
474        except:
475            return 32
476