pan/va: Add ISA.xml parser and support code

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>
This commit is contained in:
Alyssa Rosenzweig 2021-07-16 10:49:35 -04:00 committed by Marge Bot
parent 316486dd9f
commit 02e378b628
1 changed files with 362 additions and 0 deletions

View File

@ -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 }