pan/va: Add ISA.xml parser and support code
authorAlyssa Rosenzweig <alyssa@collabora.com>
Fri, 16 Jul 2021 14:49:35 +0000 (10:49 -0400)
committerMarge Bot <eric+marge@anholt.net>
Tue, 27 Jul 2021 20:20:32 +0000 (20:20 +0000)
This Python module parses the ISA.xml file added in the previous
commits, extracts all the useful information, and combines it with
extra annotations. In total, it provides a programmatic way to interface
with the Valhall instruction set.

   from valhall import instructions, enums

Signed-off-by: Alyssa Rosenzweig <alyssa@collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/12025>

src/panfrost/bifrost/valhall/valhall.py [new file with mode: 0644]

diff --git a/src/panfrost/bifrost/valhall/valhall.py b/src/panfrost/bifrost/valhall/valhall.py
new file mode 100644 (file)
index 0000000..b1e4f06
--- /dev/null
@@ -0,0 +1,362 @@
+#encoding=utf-8
+
+# Copyright (C) 2016 Intel Corporation
+# Copyright (C) 2016 Broadcom
+# 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.
+
+import os
+import textwrap
+import xml.etree.ElementTree as ET
+import sys
+
+tree = ET.parse(os.path.join(os.path.dirname(__file__), 'ISA.xml'))
+root = tree.getroot()
+
+# All instructions in the ISA
+instructions = []
+
+# All immediates in the ISA
+ilut = root.findall('lut')[0]
+assert(ilut.attrib['name'] == "Immediates")
+immediates = [int(imm.text, base=0) for imm in ilut.findall('constant')]
+enums = {}
+
+def xmlbool(s):
+    assert(s.lower() in ["false", "true"])
+    return False if s.lower() == "false" else True
+
+class EnumValue:
+    def __init__(self, value, default):
+        self.value = value
+        self.default = default
+
+class Enum:
+    def __init__(self, name, values):
+        self.name = name
+        self.values = values
+        self.bare_values = [x.value for x in values]
+
+        defaults = [x.value for x in values if x.default]
+        if len(defaults) > 0:
+            assert(len(defaults) == 1)
+            self.default = defaults[0]
+
+def build_enum(el):
+    values = []
+
+    for child in el:
+        if child.tag == 'value':
+            is_default = child.attrib.get('default', False)
+            values.append(EnumValue(child.text, is_default))
+        elif child.tag == 'reserved':
+            values.append(EnumValue("reserved", False))
+
+    return Enum(el.attrib['name'], values)
+
+class Modifier:
+    def __init__(self, name, start, size, implied = False):
+        self.name = name
+        self.start = start
+        self.size = size
+        self.implied = implied
+
+        if size == 1:
+            self.bare_values = ['', name]
+            self.default = 0
+        else:
+            enum = enums[name]
+            self.bare_values = [x.value for x in enum.values]
+            defaults = [x for x in enum.values if x.default]
+            assert(len(defaults) <= 1)
+            
+            if len(defaults) > 0:
+                self.default = self.bare_values.index(defaults[0].value)
+            else:
+                self.default = None
+
+def Flag(name, start):
+    return Modifier(name, start, 1)
+
+# Model a single instruction
+class Source:
+    def __init__(self, index, size, is_float = False, swizzle = False, widen = False, lanes = False, lane = None, absneg = False, notted = False, name = ""):
+        self.is_float = is_float or absneg
+        self.size = size
+        self.absneg = absneg
+        self.notted = notted
+        self.swizzle = swizzle
+        self.widen = widen
+        self.lanes = lanes
+        self.lane = lane
+        self.name = name
+
+        self.offset = {}
+        self.bits = {}
+        if absneg:
+            self.offset['neg'] = 32 + 2 + ((2 - index) * 2)
+            self.offset['abs'] = 33 + 2 + ((2 - index) * 2)
+            self.bits['neg'] = 1
+            self.bits['abs'] = 1
+        if notted:
+            self.offset['not'] = 35
+            self.bits['not'] = 1
+        if widen or lanes:
+            self.offset['widen'] = 26 if index == 1 else 36
+            self.bits['widen'] = 4 # XXX: too much?
+        if lane:
+            self.offset['lane'] = self.lane
+            self.bits['lane'] = 2 if size in (8, 32) else 1
+        if swizzle:
+            assert(size in [16, 32])
+            self.offset['swizzle'] = 24 + ((2 - index) * 2)
+            self.bits['swizzle'] = 2
+
+class Dest:
+    def __init__(self, name = ""):
+        self.name = name
+
+class Staging:
+    def __init__(self, read = False, write = False, index = 0, count = 0, flags = True, name = ""):
+        self.name = name
+        self.read = read
+        self.write = write
+        self.count = count
+        self.flags = flags
+
+        # For compatibility
+        self.absneg = False
+        self.swizzle = False
+        self.notted = False
+        self.widen = False
+        self.lanes = False
+        self.lane = False
+        self.size = 32
+
+        assert(index < 2)
+        self.start = 40 if index == 0 else 16
+
+        if not flags:
+            self.encoded_flags = 0
+        elif index > 0:
+            self.encoded_flags = 0xC0
+        else:
+            self.encoded_flags = (0x80 if write else 0) | (0x40 if read else 0)
+
+class Immediate:
+    def __init__(self, name, start, size, signed):
+        self.name = name
+        self.start = start
+        self.size = size
+        self.signed = signed
+
+class Instruction:
+    def __init__(self, name, opcode, opcode2, srcs = [], dests = [], immediates = [], modifiers = [], staging = None):
+        self.name = name
+        self.srcs = srcs
+        self.dests = dests
+        self.opcode = opcode
+        self.opcode2 = opcode2 or 0
+        self.immediates = immediates
+        self.modifiers = modifiers
+        self.staging = staging
+
+        self.secondary_shift = max(len(self.srcs) * 8, 16)
+        self.secondary_mask = 0xF if opcode2 is not None else 0x0
+        if "left" in [x.name for x in self.modifiers]:
+            self.secondary_mask |= 0x100
+        if len(srcs) == 3 and (srcs[1].widen or srcs[1].lanes):
+            self.secondary_mask &= ~0xC # conflicts
+        if opcode == 0x90:
+            # XXX: XMLify this, but disambiguates sign of conversions
+            self.secondary_mask |= 0x10
+        if name.startswith("LOAD.i") or name.startswith("STORE.i"):
+            self.secondary_shift = 27 # Alias with memory_size
+            self.secondary_mask = 0x7
+
+        assert(len(dests) == 0 or not staging)
+        assert(not opcode2 or (opcode2 & self.secondary_mask) == opcode2)
+
+    def __str__(self):
+        return self.name
+
+# Build a single source from XML
+def build_source(el, i, size):
+    lane = el.get('lane', None)
+    if lane == "true":
+        lane = 38 if i == 0 else 36
+    elif lane is not None:
+        lane = int(lane)
+
+    return Source(i, int(el.get('size', size)),
+            absneg = el.get('absneg', False),
+            is_float = el.get('float', False),
+            swizzle = el.get('swizzle', False),
+            widen = el.get('widen', False),
+            lanes = el.get('lanes', False),
+            lane = lane,
+            notted = el.get('not', False),
+            name = el.text or "")
+
+def build_imm(el):
+    return Immediate(el.attrib['name'], int(el.attrib['start']),
+            int(el.attrib['size']), bool(el.attrib.get('signed', False)))
+
+def build_staging(i, el):
+    r = xmlbool(el.attrib.get('read', 'false'))
+    w = xmlbool(el.attrib.get('write', 'false'))
+    count = int(el.attrib.get('count', '0'))
+    flags = xmlbool(el.attrib.get('flags', 'true'))
+
+    return Staging(r, w, i, count, flags, el.text or '')
+
+def build_modifier(el):
+    name = el.attrib['name']
+    start = int(el.attrib['start'])
+    size = int(el.attrib['size'])
+    implied = xmlbool(el.get('implied', 'false'))
+
+    return Modifier(name, start, size, implied)
+
+# Build a single instruction from XML and group based overrides
+def build_instr(el, overrides = {}):
+    # Get overridables
+    name = overrides.get('name') or el.attrib.get('name')
+    opcode = overrides.get('opcode') or el.attrib.get('opcode')
+    opcode2 = overrides.get('opcode2') or el.attrib.get('opcode2')
+    opcode = int(opcode, base=0)
+    opcode2 = int(opcode2, base=0) if opcode2 else None
+
+    # Get explicit sources/dests
+    tsize = typesize(name)
+    sources = [build_source(src, i, tsize) for i, src in enumerate(el.findall('src'))]
+    dests = [Dest(dest.text or '') for dest in el.findall('dest')]
+
+    # Get implicit ones
+    sources = sources + ([Source(i, int(tsize)) for i in range(int(el.attrib.get('srcs', 0)))])
+    dests = dests + ([Dest()] * int(el.attrib.get('dests', 0)))
+
+    # Get staging registers
+    staging = [build_staging(i, el) for i, el in enumerate(el.findall('sr'))]
+
+    # Get immediates
+    imms = [build_imm(imm) for imm in el.findall('imm')]
+
+    modifiers = []
+    for mod in el:
+        if mod.tag in MODIFIERS:
+            modifiers.append(MODIFIERS[mod.tag])
+        elif mod.tag =='mod':
+            modifiers.append(build_modifier(mod))
+
+    instr = Instruction(name, opcode, opcode2, srcs = sources, dests = dests, immediates = imms, modifiers = modifiers, staging = staging)
+
+    instructions.append(instr)
+
+# Build all the instructions in a group by duplicating the group itself with
+# overrides for each distinct instruction
+def build_group(el):
+    for ins in el.findall('ins'):
+        build_instr(el, overrides = {
+            'name': ins.attrib['name'],
+            'opcode': ins.attrib.get('opcode'),
+            'opcode2': ins.attrib.get('opcode2'),
+        })
+
+def to_alphanum(name):
+    substitutions = {
+        ' ': '_',
+        '/': '_',
+        '[': '',
+        ']': '',
+        '(': '',
+        ')': '',
+        '-': '_',
+        ':': '',
+        '.': '',
+        ',': '',
+        '=': '',
+        '>': '',
+        '#': '',
+        '&': '',
+        '*': '',
+        '"': '',
+        '+': '',
+        '\'': '',
+    }
+
+    for i, j in substitutions.items():
+        name = name.replace(i, j)
+
+    return name
+
+def safe_name(name):
+    name = to_alphanum(name)
+    if not name[0].isalpha():
+        name = '_' + name
+
+    return name.lower()
+
+# 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
+
+for child in root.findall('enum'):
+    enums[safe_name(child.attrib['name'])] = build_enum(child)
+
+MODIFIERS = {
+    "inactive_result": Modifier("inactive_result", 22, 4),
+    "store_segment": Modifier("store_segment", 24, 2),
+    "regfmt": Modifier("register_format", 24, 3),
+    "vecsize": Modifier("vector_size", 28, 2),
+
+    "slot": Modifier("slot", 30, 3),
+    "roundmode": Modifier("round_mode", 30, 2),
+    "result_type": Modifier("result_type", 30, 2),
+    "saturate": Flag("saturate", 30),
+    "not_result": Flag("not_result", 30),
+
+    "lane_op": Modifier("lane_operation", 32, 2),
+    "cmp": Modifier("condition", 32, 3),
+    "clamp": Modifier("clamp", 32, 2),
+    "sr_count": Modifier("staging_register_count", 33, 3, implied = True),
+
+    "subgroup": Modifier("subgroup_size", 36, 2),
+}
+
+# Parse the ISA
+for child in root:
+    if child.tag == 'group':
+        build_group(child)
+    elif child.tag == 'ins':
+        build_instr(child)
+
+instruction_dict = { ins.name: ins for ins in instructions }