Files
renderdoc/renderdoc/driver/shaders/spirv/gen_spirv_code.py
T

1411 lines
48 KiB
Python

#!/usr/bin/env python3
import os
import io
import sys
import re
import argparse
import json
import xml.etree.ElementTree as ET
# on msys, use crlf output
nl = None
if sys.platform == 'msys':
nl = "\r\n"
# Get the file, relative to this script's location (same directory)
# that way we're not sensitive to CWD
pathname = os.path.abspath(os.path.dirname(sys.argv[0])) + os.path.sep
with open(pathname + 'spirv.core.grammar.json', mode='r') as f:
spirv = json.load(f)
with open(pathname + 'extinst.glsl.std.450.grammar.json', mode='r') as f:
glsl450 = json.load(f)
with open(pathname + 'extinst.nonsemantic.shader.debuginfo.100.grammar.json', mode='r') as f:
debugInfo = json.load(f)
# open XML registry
registry = ET.parse(pathname + 'spir-v.xml').getroot()
# open the file for write
header = open(pathname + 'spirv_gen.h', mode='w', newline = nl, encoding = "utf-8")
ops_header = open(pathname + 'spirv_op_helpers.h', mode='w', newline = nl, encoding = "utf-8")
cpp = open(pathname + 'spirv_gen.cpp', mode='w', newline = nl, encoding = "utf-8")
###############################################################################
##
## Headers
##
###############################################################################
def prefix_star(line):
if line == '':
return ' *'
else:
return ' * ' + line
def operand_name(name, lowercase_first = True):
name = name.replace('\n', ' ')
# special case a few very awkward names
if re.search(r'member [0-9].*\.\.\.', name, re.RegexFlag.I):
return 'members'
if re.search(r'parameter [0-9].*\.\.\.', name, re.RegexFlag.I):
return 'parameters'
if re.search(r'argument [0-9].*\.\.\.', name, re.RegexFlag.I):
return 'arguments'
if re.search(r'variable, parent.*\.\.\.', name, re.RegexFlag.I):
return 'parents'
name = re.sub(r'<<(.*),(.*)>>', r'\2', name)
name = re.sub(r'[ \'~<>./-]', '', name)
if name.lower() == 'interface':
return 'iface'
if name.lower() == 'default':
return 'def'
if name.lower() == 'asm':
return 'assembly'
if lowercase_first:
return name[0].lower() + name[1:]
else:
return name
copyright = '''
/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2024 Baldur Karlsson
*
* 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 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.
******************************************************************************/
/******************************************************************************
* Generated from Khronos SPIR-V machine-readable JSON grammar.
*
{}
******************************************************************************/
'''.format("\n".join([prefix_star(line.strip()) for line in spirv['copyright']])).strip()
header.write('''{copyright}
#pragma once
// This file is autogenerated with gen_spirv_code.py - any changes will be overwritten next time
// that script is run.
// $ ./gen_spirv_code.py
// We need to disable clang-format since this file is programmatically generated
// clang-format off
#include <stdint.h>
#include "api/replay/apidefs.h"
#include "api/replay/stringise.h"
#undef None
#undef CopyMemory
#undef MemoryBarrier
namespace rdcspv
{{
static const uint32_t MagicNumber = {magic};
static const uint32_t VersionMajor = {major};
static const uint32_t VersionMinor = {minor};
static const uint32_t VersionRevision = {revision};
static const uint32_t VersionPacked = ({major} << 16) | ({minor} << 8);
static const uint32_t OpCodeMask = 0xffff;
static const uint32_t WordCountShift = 16;
static const uint32_t FirstRealWord = 5;
struct Id
{{
constexpr inline Id() : id(0) {{}}
// only allow explicit functions to cast to/from uint32_t
constexpr static inline Id fromWord(uint32_t i) {{ return Id(i); }}
inline uint32_t value() const {{ return id; }}
constexpr inline explicit operator bool() const {{ return id != 0; }}
constexpr inline bool operator==(const Id o) const {{ return id == o.id; }}
constexpr inline bool operator!=(const Id o) const {{ return id != o.id; }}
constexpr inline bool operator<(const Id o) const {{ return id < o.id; }}
constexpr inline bool operator==(const uint32_t o) const {{ return id == o; }}
constexpr inline bool operator!=(const uint32_t o) const {{ return id != o; }}
constexpr inline bool operator<(const uint32_t o) const {{ return id < o; }}
private:
constexpr inline Id(uint32_t i) : id(i) {{}}
uint32_t id;
}};
enum class Generator : uint32_t
{{'''.format(copyright = copyright, magic = spirv['magic_number'], major = spirv['major_version'], minor = spirv['minor_version'], revision = spirv['revision']))
generator_tostr = ''
for gen in registry.findall('ids[@type=\'vendor\']/id[@tool]'):
name = operand_name(gen.attrib['tool'], lowercase_first=False)
tostr = '{} from {} - {}'.format(gen.attrib['tool'], gen.attrib['vendor'], gen.attrib['comment'])
generator_tostr += ' STRINGISE_ENUM_CLASS_NAMED({}, "{}");\n'.format(name, tostr.replace('"', '\\"').replace('\\', '\\\\'))
header.write('\n {} = {},'.format(name, gen.attrib['value']))
header.write('\n};\n\n')
ops_header.write('''{copyright}
#pragma once
// This file is autogenerated with gen_spirv_code.py - any changes will be overwritten next time
// that script is run.
// $ ./gen_spirv_code.py
// We need to disable clang-format since this file is programmatically generated
// clang-format off
#include <functional>
#include <set>
#include <stdint.h>
#include "api/replay/apidefs.h"
#include "api/replay/rdcstr.h"
#include "api/replay/rdcarray.h"
#include "api/replay/stringise.h"
#undef None
#undef CopyMemory
#undef MemoryBarrier
#include "spirv_common.h"
#include "spirv_gen.h"
namespace rdcspv
{{
template<typename Type>
Type DecodeParam(const ConstIter &it, uint32_t &word);
template<>
inline uint32_t DecodeParam(const ConstIter &it, uint32_t &word)
{{
if(word >= it.size()) return 0;
uint32_t ret = it.word(word);
word += 1;
return ret;
}}
template<>
inline Id DecodeParam<Id>(const ConstIter &it, uint32_t &word)
{{
if(word >= it.size()) return Id();
Id ret = Id::fromWord(it.word(word));
word += 1;
return ret;
}}
template<>
inline rdcstr DecodeParam<rdcstr>(const ConstIter &it, uint32_t &word)
{{
if(word >= it.size()) return "";
rdcstr ret = (const char *)&it.word(word);
word += uint32_t(ret.size() / 4) + 1;
return ret;
}}
template<typename Type>
rdcarray<Type> MultiParam(const ConstIter &it, uint32_t &word)
{{
rdcarray<Type> ret;
while(word < it.size())
{{
Type t = DecodeParam<Type>(it, word);
ret.push_back(t);
}}
return ret;
}}
inline void EncodeParam(rdcarray<uint32_t> &words, const rdcstr &str)
{{
size_t i=0, remainingChars = str.size() + 1;
while(remainingChars > 0)
{{
uint32_t word = 0;
for(size_t w=0; w < remainingChars && w < 4; w++)
word |= uint32_t(str[i+w]) << (w*8);
words.push_back(word);
i += 4;
if(remainingChars < 4)
remainingChars = 0;
else
remainingChars -= 4;
}}
}}
'''.format(copyright = copyright))
cpp.write('''{copyright}
// This file is autogenerated with gen_spirv_code.py - any changes will be overwritten next time
// that script is run.
// $ ./gen_spirv_code.py
// We need to disable clang-format since this file is programmatically generated
// clang-format off
#include "spirv_gen.h"
#include "os/os_specific.h"
#include "common/formatting.h"
#include "spirv_op_helpers.h"
'''.format(copyright = copyright))
###############################################################################
##
## Operands (declare enums, stringise, preprocess)
##
###############################################################################
positional_names = [ 'first', 'second', 'third' ]
kinds = {}
for operand_kind in spirv['operand_kinds']:
name = operand_kind['kind']
if 'enumerants' in operand_kind:
operand_kind['has_params'] = any(['parameters' in value for value in operand_kind['enumerants']])
else:
operand_kind['has_params'] = False
kinds[name] = operand_kind
operand_kind['push_words'] = lambda name: 'words.push_back((uint32_t){});'.format(name)
operand_kind['from_words'] = None
operand_kind['is_id'] = False
if operand_kind['category'] == 'ValueEnum':
operand_kind['size'] = 1
operand_kind['def_name'] = name[0].lower() + name[1:]
operand_kind['def_value'] = name + '::Invalid'
operand_kind['type'] = name
decl = ''
stringise = ''
used = []
official_suffixes = ['KHR', 'EXT']
vendor_suffixes = ['AMD', 'NV', 'INTEL', 'ARM', 'QCOM', 'GOOGLE']
for value in operand_kind['enumerants']:
value_name = value['enumerant']
if value_name[0].isdigit():
value_name = '_' + value_name
decl += ' {} = {},\n'.format(value_name, value['value'])
if value['value'] in used:
continue
wait_for_official = False
for off in official_suffixes:
for vend in vendor_suffixes:
if value_name[-len(vend):] == vend:
off_name = value_name[:-len(vend)] + off
for search in operand_kind['enumerants']:
if search['enumerant'] == off_name and search['value']:
wait_for_official = True
if wait_for_official:
continue
used.append(value['value'])
if value_name != value['enumerant']:
stringise += ' STRINGISE_ENUM_CLASS_NAMED({}, "{}");\n'.format(value_name, value['enumerant'])
else:
stringise += ' STRINGISE_ENUM_CLASS({});\n'.format(value_name)
header.write('''enum class {name} : uint32_t
{{
{values}
Max,
Invalid = ~0U,
}};
'''.format(name = name, values = decl.rstrip()))
cpp.write('''template <>
rdcstr DoStringise(const rdcspv::{name} &el)
{{
BEGIN_ENUM_STRINGISE(rdcspv::{name});
{{
{values}
}}
END_ENUM_STRINGISE();
}}
'''.format(name = name, values = stringise.rstrip()))
elif operand_kind['category'] == 'BitEnum':
operand_kind['size'] = 1
operand_kind['def_name'] = name[0].lower() + name[1:]
operand_kind['type'] = name
used = []
none = [v['enumerant'] for v in operand_kind['enumerants'] if 'None' in v['enumerant']]
if len(none) > 0:
none = none[0]
else:
none = 'NoDefValueAvailable'
operand_kind['def_value'] = name + '::' + none
decl = ''
stringise = ''
for value in operand_kind['enumerants']:
decl += ' {} = {},\n'.format(value['enumerant'], value['value'])
if value['value'] in used:
continue
used.append(value['value'])
if value['enumerant'] == none:
stringise += ' STRINGISE_BITFIELD_CLASS_VALUE({});\n\n'.format(none)
else:
stringise += ' STRINGISE_BITFIELD_CLASS_BIT({});\n'.format(value['enumerant'])
header.write('''enum class {name} : uint32_t
{{
{values}
Max,
Invalid = ~0U,
}};
BITMASK_OPERATORS({name});
'''.format(name = name, values = decl.rstrip()))
cpp.write('''template <>
rdcstr DoStringise(const rdcspv::{name} &el)
{{
BEGIN_BITFIELD_STRINGISE(rdcspv::{name});
{{
{values}
}}
END_BITFIELD_STRINGISE();
}}
'''.format(name = name, values = stringise.rstrip()))
# Hardcoded special types that we hardcode behaviour for
elif (operand_kind['kind'] == 'IdRef'):
operand_kind['size'] = 1
operand_kind['def_name'] = 'id'
operand_kind['def_value'] = 'Id()'
operand_kind['type'] = 'Id'
operand_kind['is_id'] = True
operand_kind['push_words'] = lambda name: 'words.push_back({}.value());'.format(name)
operand_kind['from_words'] = lambda name: 'Id::fromWord({})'.format(name)
elif (operand_kind['kind'] == 'IdResultType' or
operand_kind['kind'] == 'IdResult' or
operand_kind['kind'] == 'IdMemorySemantics' or
operand_kind['kind'] == 'IdScope'):
operand_kind['size'] = 1
operand_kind['type'] = name
operand_kind['is_id'] = True
operand_kind['def_name'] = name[2].lower() + name[3:]
operand_kind['def_value'] = name + '()'
operand_kind['push_words'] = lambda name: 'words.push_back({}.value());'.format(name)
operand_kind['from_words'] = lambda name: 'Id::fromWord({})'.format(name)
header.write('using {} = Id;\n\n'.format(name))
# For simplicity, assume literal integers are 32-bit in size
elif (operand_kind['kind'] == 'LiteralInteger'):
operand_kind['size'] = 1
operand_kind['def_name'] = 'num'
operand_kind['def_value'] = '0'
operand_kind['type'] = 'uint32_t'
elif (operand_kind['kind'] == 'LiteralFloat'):
operand_kind['size'] = 1
operand_kind['def_name'] = 'flt'
operand_kind['def_value'] = '0.0f'
operand_kind['type'] = 'float'
elif (operand_kind['kind'] == 'LiteralString'):
operand_kind['size'] = -1000000
operand_kind['type'] = 'rdcstr'
operand_kind['def_name'] = 'str'
operand_kind['def_value'] = '""'
operand_kind['push_words'] = lambda name: 'EncodeParam(words, {});'.format(name)
operand_kind['from_words'] = lambda name: 'DecodeParam({})'.format(name)
elif (operand_kind['kind'] == 'LiteralContextDependentNumber' or
operand_kind['kind'] == 'LiteralExtInstInteger' or
operand_kind['kind'] == 'LiteralSpecConstantOpInteger'):
operand_kind['size'] = None
elif (operand_kind['kind'] == 'PairLiteralIntegerIdRef'):
operand_kind['size'] = None
elif (operand_kind['kind'] == 'PairIdRefLiteralInteger'):
operand_kind['size'] = 2
operand_kind['def_name'] = name[0].lower() + name[1:]
operand_kind['def_value'] = '{Id(), 0}'
operand_kind['type'] = name
operand_kind['push_words'] = lambda name: 'words.push_back({0}.first.value()); words.push_back((uint32_t){0}.second);'.format(name)
ops_header.write('struct {} {{ Id first; uint32_t second; }};\n\n'.format(name))
elif (operand_kind['kind'] == 'PairIdRefIdRef'):
operand_kind['size'] = 2
operand_kind['def_name'] = name[0].lower() + name[1:]
operand_kind['def_value'] = '{Id(), Id()}'
operand_kind['type'] = name
operand_kind['push_words'] = lambda name: 'words.push_back({0}.first.value()); words.push_back({0}.second.value());'.format(name)
ops_header.write('struct {} {{ Id first, second; }};\n\n'.format(name))
continue
else:
raise TypeError("Unexpected operand {} of type {}".format(operand_kind['kind'], operand_kind['category']))
if operand_kind['from_words'] is None:
operand_kind['from_words'] = lambda name,kind=operand_kind: '({}){}'.format(kind['type'], name)
ops_header.write('''
template<>
inline PairIdRefIdRef DecodeParam(const ConstIter &it, uint32_t &word)
{
if(word >= it.size()) return {};
PairIdRefIdRef ret = { Id::fromWord(it.word(word)), Id::fromWord(it.word(word+1)) };
word += 2;
return ret;
}
template<>
inline PairIdRefLiteralInteger DecodeParam(const ConstIter &it, uint32_t &word)
{
if(word >= it.size()) return {};
PairIdRefLiteralInteger ret = { Id::fromWord(it.word(word)), it.word(word+1) };
word += 2;
return ret;
}
''')
tostrs = ''
tostr_decls = ''
# Second pass to declare operand parameter structs in ops helper header
for operand_kind in spirv['operand_kinds']:
name = operand_kind['kind']
if not operand_kind['has_params']:
if operand_kind['category'] == 'ValueEnum':
ops_header.write('inline uint16_t OptionalWordCount(const {0} val) {{ return val != {0}::Invalid ? 1 : 0; }}\n\n'.format(name))
elif operand_kind['category'] == 'BitEnum':
none = [v['enumerant'] for v in operand_kind['enumerants'] if 'None' in v['enumerant']]
if len(none) > 0:
ops_header.write('inline uint16_t OptionalWordCount(const {0} val) {{ return val != {0}::{1} ? 1 : 0; }}\n\n'.format(name, none[0]))
continue
values = ''
set_unset = ''
word_count_cases = ''
decode_cases = ''
encode_cases = ''
constructors = ''
tostr_cases = ''
value_enum = operand_kind['category'] == 'ValueEnum'
bit_enum = operand_kind['category'] == 'BitEnum'
used = []
for value in operand_kind['enumerants']:
params = ''
assign = ''
ret_assign = ''
new_value = value['value'] not in used
used.append(value['value'])
if new_value and bit_enum:
tostr_cases += ' if(el.flags & {0}::{1})\n ret += "{1}"'.format(name, value['enumerant'])
if 'parameters' in value:
# We want plain unions, so don't include strings
if any([param['kind'] == 'LiteralString' for param in value['parameters']]):
continue
if new_value and value_enum:
tostr_cases += ' case {0}::{1}:\n ret += '.format(name, value['enumerant'])
member = ""
param_name = operand_name(value['enumerant'])
size = 0
if new_value:
if value_enum:
decode_cases += ' case {0}::{1}:\n'.format(name, value['enumerant'])
encode_cases += ' case {0}::{1}:\n'.format(name, value['enumerant'])
else:
decode_cases += ' if(ret.flags & {0}::{1})\n {{\n'.format(name, value['enumerant'])
encode_cases += ' if(param.flags & {0}::{1})\n {{\n'.format(name, value['enumerant'])
# if we only have one parameter, add its type to the set
if len(value['parameters']) == 1:
param = value['parameters'][0]
size += kinds[param['kind']]['size']
param_type = kinds[param['kind']]['type']
member = "{} {};\n".format(param_type, param_name)
if value_enum:
values += ' '
if new_value:
decode_cases += ' '
encode_cases += ' '
values += " " + member
params += "{} {}Param".format(param_type, param_name)
assign += " {0} = {0}Param;".format(param_name)
ret_assign += " ret.{0} = {0};\n".format(param_name)
if new_value:
decode_cases += ' ret.{} = {};\n'.format(param_name, kinds[param['kind']]['from_words']('it.word(word)'))
encode_cases += ' {}\n'.format(kinds[param['kind']]['push_words']('param.{}'.format(param_name)))
if kinds[param['kind']]['is_id']:
tostr_cases += ' "(" + idName(el.{}) + ")"'.format(param_name)
else:
tostr_cases += ' "(" + ToStr(el.{}) + ")"'.format(param_name)
# if we have multiple we need a separate struct for this thing
else:
struct_name = param_name[0].upper() + param_name[1:] + 'Params'
member = "{} {};\n".format(struct_name, param_name)
if value_enum:
values += ' '
values += " " + member
struct_values = ''
if new_value:
tostr_cases += ' "("'
for i,param in enumerate(value['parameters']):
subparam_name = positional_names[i]
kind = kinds[param['kind']]
size += kind['size']
if 'name' in param:
subparam_name = operand_name(param['name'])
struct_values += " {} {};\n".format(kind['type'], subparam_name)
if new_value:
if value_enum:
decode_cases += ' '
encode_cases += ' '
decode_cases += ' ret.{}.{} = {};\n'.format(param_name, subparam_name, kinds[param['kind']]['from_words']('it.word(word+{})'.format(i)))
encode_cases += ' {}\n'.format(kinds[param['kind']]['push_words']('param.{}.{}'.format(param_name, subparam_name)))
if kinds[param['kind']]['is_id']:
tostr_cases += ' + idName(el.{}.{}) + '.format(param_name, subparam_name)
else:
tostr_cases += ' + ToStr(el.{}.{}) + '.format(param_name, subparam_name)
assign += " {0}.{1} = {1};".format(param_name, subparam_name)
ret_assign += " ret.{0}.{1} = {0}.{1};\n".format(param_name, subparam_name)
params += "{} {}".format(kind['type'], subparam_name)
if i != len(value['parameters'])-1:
params += ", "
tostr_cases += '", " '
if new_value:
tostr_cases += '")"'
header.write('''struct {struct_name}
{{
{struct_values}
}};
'''.format(struct_name = struct_name, struct_values = struct_values.rstrip()))
if new_value:
if value_enum:
decode_cases += ' word += {};\n'.format(size)
decode_cases += ' break;\n'
encode_cases += ' break;\n'
tostr_cases += '; break;\n'
else:
decode_cases += ' word += {};\n'.format(size)
decode_cases += ' }\n'
encode_cases += ' }\n'
word_count_cases += ' case {}::{}: return {};\n'.format(name, value['enumerant'], size)
constructors += '''template<>\nstruct {name}Param<{name}::{value}>
{{
{member}
{name}Param({params}) {{ {assign} }}
operator {name}AndParamData()
{{
{name}AndParamData ret({name}::{value});
{ret_assign}
return ret;
}}
}};
'''.format(value=value['enumerant'], member=member.rstrip(), name=name, params=params, assign=assign, ret_assign=ret_assign.rstrip())
if new_value and bit_enum:
tostr_cases += ' ", ";\n'
set_unset += ''' void set{flag}({params}) {{ flags |= {name}::{flag};{assign} }}
void unset{flag}() {{ flags &= ~{name}::{flag}; }}
'''.format(flag=value['enumerant'], name=name, params=params, assign=assign)
if constructors != '':
constructors = 'template<{name} val> struct {name}Param;\n\n'.format(name=name) + constructors
# ValueEnums are set up as one or many pairs of enum/params, enum/params, etc. So we declare a struct for the pair
# and declare an array if the op wants many
if value_enum:
tostrs += '''template<>
rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const rdcspv::{name}AndParamData &el)
{{
rdcstr ret = ToStr(el.value);
switch(el.value)
{{
{tostr_cases}
default:
break;
}}
return ret;
}}
'''.format(name=name, tostr_cases=tostr_cases.rstrip())
tostr_decls += '''template<>
rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const rdcspv::{name}AndParamData &el);'''.format(name=name)
header.write('''struct {name}AndParamData
{{
{name}AndParamData({name} v = {name}::Invalid) : value(v) {{}}
{name} value;
union
{{
{values}
}};
operator {name}() const {{ return value; }}
bool operator ==(const {name} v) const {{ return value == v; }}
}};
'''.format(name=name, values=values.rstrip()))
ops_header.write('''{constructors}
template<>
inline {name}AndParamData DecodeParam(const ConstIter &it, uint32_t &word)
{{
if(word >= it.size()) return {name}AndParamData();
{name}AndParamData ret(({name})it.word(word));
word++;
switch(ret.value)
{{
{decode_cases}
default: break;
}}
return ret;
}}
inline void EncodeParam(rdcarray<uint32_t> &words, const {name}AndParamData &param)
{{
words.push_back((uint32_t)param.value);
switch(param.value)
{{
{encode_cases}
default: break;
}}
}}
'''.format(name=name, value_name=operand_name(name), decode_cases=decode_cases.rstrip(),
constructors=constructors, encode_cases=encode_cases.rstrip()))
operand_kind['type'] = '{}AndParamData'.format(name)
# BitEnums are set up with one bitmask, and then a series of parameters, so we declare a struct with an array
elif bit_enum:
tostrs += '''template<>
rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const rdcspv::{name}AndParamDatas &el)
{{
rdcstr ret;
{tostr_cases}
// remove trailing ", "
if(ret.size() > 2)
ret.erase(ret.size()-2, 2);
return ret;
}}
'''.format(name=name, tostr_cases=tostr_cases.rstrip())
tostr_decls += '''template<>
rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const rdcspv::{name}AndParamDatas &el);'''.format(name=name)
header.write('''struct {name}AndParamDatas
{{
{name}AndParamDatas({name} f = {name}::None) : flags(f) {{}}
{name} flags;
{values}
operator {name}() const {{ return flags; }}
bool operator &(const {name} v) const {{ return bool(flags & v); }}
{set_unset}
}};
'''.format(name=name, values=values.rstrip(), set_unset=set_unset.rstrip()))
ops_header.write('''template<>
inline {name}AndParamDatas DecodeParam(const ConstIter &it, uint32_t &word)
{{
if(word >= it.size()) return {name}AndParamDatas();
{name}AndParamDatas ret(({name})it.word(word));
word++;
{decode_cases}
return ret;
}}
inline void EncodeParam(rdcarray<uint32_t> &words, const {name}AndParamDatas &param)
{{
words.push_back((uint32_t)param.flags);
{encode_cases}
}}
'''.format(name=name, decode_cases=decode_cases.rstrip(), encode_cases=encode_cases.rstrip()))
operand_kind['type'] = '{}AndParamDatas'.format(name)
else:
raise TypeError("unexpected operand kind {} with parameters".format(operand_kind['category']))
ops_header.write('''inline uint16_t ExtraWordCount(const {name} {value_name})
{{
switch({value_name})
{{
{word_count_cases}
default: break;
}}
return 0;
}}
'''.format(name = name, value_name = operand_name(name), word_count_cases = word_count_cases.rstrip()))
ops_header.write('''
inline uint16_t ExtraWordCount(const rdcstr &val)
{
return uint16_t(val.size() / 4);
}
inline uint16_t OptionalWordCount(const rdcstr &val)
{
if(val.empty()) return 0;
return uint16_t(val.size() / 4) + 1;
}
inline uint16_t OptionalWordCount(const Id &val)
{
return val != Id() ? 1 : 0;
}
inline uint16_t OptionalWordCount(const PairIdRefLiteralInteger &val)
{
return val.first != Id() ? 2 : 0;
}
inline uint16_t OptionalWordCount(const PairIdRefIdRef &val)
{
return val.first != Id() ? 2 : 0;
}
template<typename Type>
uint16_t MultiWordCount(const rdcarray<Type> &multiParams)
{
uint16_t ret = 0;
for(size_t i=0; i < multiParams.size(); i++)
ret += sizeof(multiParams[i])/sizeof(uint32_t);
return ret;
}
''')
###############################################################################
##
## Opcodes (declare enum / stringise)
##
###############################################################################
# Quickly preprocess, find parameters with duplicated names and disambiguate
for inst in spirv['instructions']:
if 'operands' in inst:
operands = inst['operands']
duplicates = []
for i,A in enumerate(operands):
for j,B in enumerate(operands):
if j <= i:
continue
a = operand_name(A['name'] if 'name' in A else kinds[A['kind']]['def_name'])
b = operand_name(B['name'] if 'name' in B else kinds[B['kind']]['def_name'])
if a == b:
if i not in duplicates:
duplicates.append(i)
if j not in duplicates:
duplicates.append(j)
if len(duplicates) > 0:
for idx,arg in enumerate(duplicates):
A = operands[arg]
operands[arg]['name'] = operand_name(A['name'] if 'name' in A else kinds[A['kind']]['def_name']) + str(idx)
used = []
decl = ''
stringise = ''
op_structs = ''
op_decoder = ''
used_ids = ''
disassemble = ''
for inst in spirv['instructions']:
if inst['class'] == '@exclude':
continue
wait_for_official = False
for off in official_suffixes:
for vend in vendor_suffixes:
if inst['opname'][-len(vend):] == vend:
off_name = inst['opname'][:-len(vend)] + off
for search in spirv['instructions']:
if search['opname'] == off_name and inst['opcode'] == search['opcode']:
wait_for_official = True
if wait_for_official:
continue
decl += ' {} = {},\n'.format(inst['opname'][2:], inst['opcode'])
if inst['opcode'] in used:
continue
stringise += ' STRINGISE_ENUM_CLASS({});\n'.format(inst['opname'][2:])
result = -1
resultCount = 0
resultType = -1
used_ids += ' case rdcspv::Op::{}:\n'.format(inst['opname'][2:])
operands = []
if 'operands' in inst:
operands = inst['operands']
last_operand = operands[-1]
for i,operand in enumerate(operands):
if operand['kind'] == 'IdResult':
result = i+1
resultCount += 1
elif 'name' in operand and operand['name'][0:6] == 'result':
resultCount += 1
if operand['kind'] == 'IdResultType':
resultType = i+1
disassemble += ' case rdcspv::Op::{}:\n'.format(inst['opname'][2:])
disassemble += ' {\n'
if any([kinds[operand['kind']]['size'] is None for operand in operands]):
op_struct = 'struct {}; // has operands with variable sizes\n\n'.format(inst['opname'])
disassemble += ' OpDecoder decoded(it);\n'.format(inst['opname'][2:])
if resultCount == 2:
raise ValueError("Unexpected multiple results without decoded opcode")
elif resultType > 0 and result > 0:
disassemble += ' ret += declName(decoded.resultType, decoded.result) + " = ";\n'
elif resultType > 0 and result == -1:
raise ValueError("Unexpected result type without result")
elif resultType == -1 and result > 0:
disassemble += ' ret += idName(decoded.result) + " = ";\n'
disassemble += ' ret += "{}(...)";\n'.format(inst['opname'][2:])
disassemble += ' break;\n'
disassemble += ' }\n'
else:
params = ''
assign = ''
member_decl = ''
size_name = 'FixedWordSize'
construct_size = 'FixedWordSize'
size = 1 # opcode / wordcount packed
all_size = 1 # size, but with all optionals included
iter_init = ' memcpy(this, it.words(), sizeof(*this));'
complex_type = False
manual_init = ' uint32_t word = 0;(void)word;\n'
manual_init += ' this->op = OpCode;\n'
manual_init += ' this->wordCount = (uint16_t)it.size();\n'
oper_cast = ' operator Operation() const\n {\n rdcarray<uint32_t> words;\n'
has_funcs = ''
used_ids_str = ''
disassemble += ' Op{} decoded(it);\n'.format(inst['opname'][2:])
if resultCount == 2:
disassemble += ' ret += declName(decoded.resultType, decoded.result0) + ", " + idName(decoded.result1) + " = ";\n'
elif resultType > 0 and result > 0:
disassemble += ' ret += declName(decoded.resultType, decoded.result) + " = ";\n'
elif resultType > 0 and result == -1:
raise ValueError("Unexpected result type without result")
elif resultType == -1 and result > 0:
disassemble += ' ret += idName(decoded.result) + " = ";\n'
disassemble += ' ret += rdcstr("{}("_lit)\n'.format(inst['opname'][2:])
disassemble_params = False
if 'operands' in inst:
for i,operand in enumerate(operands):
kind = kinds[operand['kind']]
if kind['has_params'] and not complex_type:
size_name = 'MinWordSize'
construct_size = 'MinWordSize'
complex_type = True
manual_init += ' word = {};\n'.format(all_size)
quantifier = ''
if 'quantifier' in operand:
quantifier = operand['quantifier']
if not complex_type:
size_name = 'MinWordSize'
construct_size = 'MinWordSize'
complex_type = True
if quantifier == '*':
manual_init += ' word = {};\n'.format(all_size)
if kind['is_id']:
if used_ids_str is None or used_ids_str != '':
if used_ids_str is not None:
used_ids += used_ids_str
if quantifier == '*':
used_ids += ' for(; word < size; word++) callback(Id::fromWord(it.word(word)), {0});\n'.format('true' if i+1==result else 'false')
elif quantifier == '?':
used_ids += ' if(word < size) callback(Id::fromWord(it.word(word)), {0});\n'.format('true' if i+1==result else 'false')
else:
used_ids += ' callback(Id::fromWord(it.word(word)), {});\n'.format('true' if i+1==result else 'false')
else:
if quantifier == '*':
used_ids += ' for(size_t i=0; i < size-{0}; i++) callback(Id::fromWord(it.word({0}+i)), {1});\n'.format(all_size, 'true' if i+1==result else 'false')
elif quantifier == '?':
used_ids += ' if({0} < size) callback(Id::fromWord(it.word({0})), {1});\n'.format(all_size, 'true' if i+1==result else 'false')
else:
used_ids += ' callback(Id::fromWord(it.word({})), {});\n'.format(all_size, 'true' if i+1==result else 'false')
if kind['size'] < 0:
size_name = 'MinWordSize'
construct_size = 'MinWordSize'
complex_type = True
manual_init += ' word = {};\n'.format(all_size)
# use DecodeParam<rdcstr> to advance by the number of words when grabbing IDs
# Only used in OpEntryPoint which has a * qualifier after a string (itself already variable length)
used_ids_str += ' word = {};\n'.format(all_size)
used_ids_str += ' (void)DecodeParam<rdcstr>(it, word);\n'
opType,opName = (kind['type'], operand_name(operand['name'] if 'name' in operand else kind['def_name']))
if i+1 != resultType and i+1 != result:
if quantifier == '*':
disassemble += ' + ParamsToStr(idName, decoded.{})\n'.format(opName)
elif quantifier == '?':
disassemble += ' + ({} < size ? ", " + ParamToStr(idName, decoded.{}) : "")\n'.format(all_size, opName)
else:
if opType == 'IdScope':
disassemble += ' + ToStr(Scope(constIntVal(decoded.{})))\n'.format(opName)
elif opType == 'IdMemorySemantics':
disassemble += ' + ToStr(MemorySemantics(constIntVal(decoded.{})))\n'.format(opName)
else:
disassemble += ' + ParamToStr(idName, decoded.{})\n'.format(opName)
if i+1 < len(operands) and ('quantifier' not in operands[i+1] or operands[i+1]['quantifier'] != '?'):
disassemble += ' + ", "\n'
disassemble_params = True
if quantifier == '?':
params += '{} {} = {}, '.format(opType, opName, kind['def_value'])
elif quantifier == '*':
params += 'const rdcarray<{}> &{} = {{}}, '.format(opType, opName)
else:
params += '{} {}, '.format(opType, opName)
if quantifier == '*':
member_decl += ' rdcarray<{}> {};\n'.format(opType, opName)
else:
member_decl += ' {} {};\n'.format(opType, opName)
assign += ' this->{} = {};\n'.format(opName, opName)
if operand['kind'] == 'LiteralString':
if quantifier == '*':
raise ValueError('operand {} in {} is string but has * quantifier'.format(opName, inst['opname']))
manual_init += ' this->{name} = DecodeParam<{type}>(it, word);\n'.format(name = opName, type = opType)
oper_cast += ' EncodeParam(words, {name});\n'.format(name = opName)
if quantifier == '?':
construct_size += ' + OptionalWordCount({})'.format(opName)
has_funcs += ' bool Has{name}() const {{ return wordCount > {idx}; }}\n'.format(idx = all_size, name = opName[0].upper() + opName[1:])
else:
construct_size += ' + ExtraWordCount({})'.format(opName)
elif kind['has_params']:
if quantifier == '*':
raise ValueError('operand {} in {} has * quantifier and params'.format(opName, inst['opname']))
manual_init += ' this->{name} = DecodeParam<{type}>(it, word);\n'.format(name = opName, type = opType)
oper_cast += ' EncodeParam(words, {name});\n'.format(name = opName)
construct_size += ' + ExtraWordCount({})'.format(opName)
elif quantifier == '*':
manual_init += ' this->{name} = MultiParam<{type}>(it, word);\n'.format(name = opName, type = opType)
construct_size += ' + MultiWordCount({})'.format(opName)
oper_cast += ' for(size_t i=0; i < {name}.size(); i++)\n'.format(name = opName)
oper_cast += ' {\n'
oper_cast += ' {push_words}\n'.format(push_words = kind['push_words']('{}[i]'.format(opName)))
oper_cast += ' }\n'
elif quantifier == '?':
manual_init += ' this->{name} = (it.size() > {idx}) ? {value} : {def_value};\n'.format(name = opName, type = opType, idx = all_size, value = kind['from_words']('it.word({})'.format(all_size)), def_value=kind['def_value'])
construct_size += ' + OptionalWordCount({})'.format(opName)
oper_cast += ' if({name} != {def_value}) {push_words}\n'.format(name = opName, def_value=kind['def_value'], push_words = kind['push_words'](opName))
has_funcs += ' bool Has{name}() const {{ return wordCount > {idx}; }}\n'.format(idx = all_size, name = opName[0].upper() + opName[1:])
else:
manual_init += ' this->{name} = {value};\n'.format(name = opName, type = opType, value = kind['from_words']('it.word({})'.format(all_size)))
oper_cast += ' {push_words}\n'.format(push_words = kind['push_words'](opName))
if kind['size'] >= 0 and type(all_size) is int:
all_size += kind['size']
if quantifier == '':
size = all_size
else:
if quantifier == '':
size += 1
all_size = 'error: must not use all_size after variable length entry'
else:
assign = ' // no operands'
member_decl = ' // no operands'
if complex_type:
iter_init = manual_init.rstrip()
oper_cast += ' return Operation(OpCode, words);\n }\n'
else:
oper_cast = ''
if params != '':
params = params[0:-2]
disassemble += ' + ")";\n'
disassemble += ' break;\n'
disassemble += ' }\n'
if has_funcs != '':
has_funcs = '\n\n' + has_funcs
op_struct = '''struct {name}
{{
{name}(const ConstIter &it)
{{
{iter_init}
}}
{name}({params})
: op(Op::{opname})
, wordCount({construct_size})
{{
{assign}
}}
{oper_cast}
static constexpr Op OpCode = Op::{opname};
static constexpr uint16_t {size_name} = {size}U;
Op op;
uint16_t wordCount;
{member_decl}{has_funcs}
}};
'''.format(opname=inst['opname'][2:], name=inst['opname'], params=params, iter_init=iter_init, assign=assign.rstrip(),
member_decl=member_decl.rstrip(), size_name=size_name, construct_size=construct_size,
oper_cast=oper_cast, size=size, has_funcs=has_funcs.rstrip())
op_structs += op_struct
# Sanity check that quantifiers only happen on final operands. Also if there are multiple they are all ?, not *
if 'operands' in inst:
operands = inst['operands']
last_operand = operands[-1]
for operand in operands:
if operand != last_operand and 'quantifier' in operand and ('quantifier' not in last_operand or last_operand['quantifier'] != operand['quantifier'] or operand['quantifier'] != '?'):
raise ValueError('quantifier on operand {} in {} but not on last operand'.format(operand['name'], inst['opname']))
used_ids += ' break;\n'
if result < 0:
result = ' result = Id();'
else:
result = ' result = Id::fromWord(it.word({}));'.format(result)
if resultType < 0:
resultType = ' resultType = Id();'
else:
resultType = ' resultType = Id::fromWord(it.word({}));'.format(resultType)
op_decoder += ' case rdcspv::Op::{}:{}{} break;\n'.format(inst['opname'][2:], result, resultType)
used.append(inst['opcode'])
header.write('''enum class Op : uint16_t
{{
{decl}
Max,
}};
'''.format(decl = decl))
ops_header.write('''
{op_structs}
template<typename T>
inline rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const T &el)
{{
return ToStr(el);
}}
template<>
rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const Id &el);
template<>
rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const rdcstr &el);
template<>
rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const PairIdRefLiteralInteger &el);
template<>
rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const PairIdRefIdRef &el);
{tostr_decls}
template<typename U>
inline rdcstr ParamsToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const rdcarray<U> &ids)
{{
rdcstr ret = "{{";
for(size_t i=0; i < ids.size(); i++)
{{
ret += ParamToStr(idName, ids[i]);
if(i + 1 < ids.size())
ret += ", ";
}}
ret += "}}";
return ret;
}}
extern bool ManualForEachID(const ConstIter &it, const std::function<void(Id,bool)> &callback);
struct OpDecoder
{{
OpDecoder(const ConstIter &it);
static void ForEachID(const ConstIter &it, const std::function<void(Id,bool)> &callback);
static rdcstr Disassemble(const ConstIter &it, const std::function<rdcstr(Id,Id)> &declName, const std::function<rdcstr(rdcspv::Id)> &idName, const std::function<uint32_t(Id)> &constIntVal);
Op op;
uint16_t wordCount;
Id result;
Id resultType;
}};
'''.format(op_structs = op_structs.rstrip(), tostr_decls = tostr_decls))
cpp.write('''template <>
rdcstr DoStringise(const rdcspv::Op &el)
{{
BEGIN_ENUM_STRINGISE(rdcspv::Op);
{{
{stringise}
}}
END_ENUM_STRINGISE();
}}
namespace rdcspv
{{
template<>
rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const Id &el)
{{
return idName(el);
}}
template<>
rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const rdcstr &el)
{{
return "\\"" + el + "\\"";
}}
template<>
rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const PairIdRefLiteralInteger &el)
{{
return StringFormat::Fmt("[%s, %u]", idName(el.first).c_str(), el.second);
}}
template<>
rdcstr ParamToStr(const std::function<rdcstr(rdcspv::Id)> &idName, const PairIdRefIdRef &el)
{{
return StringFormat::Fmt("[%s, %s]", idName(el.first).c_str(), idName(el.second).c_str());
}}
{tostrs}
void OpDecoder::ForEachID(const ConstIter &it, const std::function<void(Id,bool)> &callback)
{{
if (rdcspv::ManualForEachID(it, callback))
return;
size_t size = it.size();
uint32_t word = 0;
(void)word;
switch(it.opcode())
{{
{used_ids}
case Op::Max: break;
}}
}}
rdcstr OpDecoder::Disassemble(const ConstIter &it, const std::function<rdcstr(Id,Id)> &declName, const std::function<rdcstr(rdcspv::Id)> &idName, const std::function<uint32_t(Id)> &constIntVal)
{{
size_t size = it.size();
rdcstr ret;
switch(it.opcode())
{{
{disassemble}
case Op::Max: break;
}}
return ret;
}}
OpDecoder::OpDecoder(const ConstIter &it)
{{
op = it.opcode();
wordCount = (uint16_t)it.size();
switch(op)
{{
{op_decoder}
case Op::Max: break;
}}
}}
}}; // namespace rdcspv
'''.format(stringise = stringise.rstrip(), op_decoder = op_decoder.rstrip(), used_ids = used_ids.rstrip(), disassemble = disassemble.rstrip(), tostrs = tostrs.rstrip()));
###############################################################################
##
## GLSL ext inst set (declare enum)
##
###############################################################################
glsl_decl = ''
glsl_stringise = ''
for inst in glsl450['instructions']:
glsl_decl += ' {} = {},\n'.format(inst['opname'], inst['opcode'])
glsl_stringise += ' STRINGISE_ENUM_CLASS({});\n'.format(inst['opname'])
debug_decl = ''
debug_stringise = ''
for inst in debugInfo['instructions']:
debug_decl += ' {} = {},\n'.format(inst['opname'][5:], inst['opcode'])
debug_stringise += ' STRINGISE_ENUM_CLASS({});\n'.format(inst['opname'][5:])
header.write('''enum class GLSLstd450 : uint32_t
{{
{glsl_decl}
Max,
Invalid = ~0U,
}};
enum class ShaderDbg : uint32_t
{{
{debug_decl}
Max,
Invalid = ~0U,
}};
'''.format(glsl_decl = glsl_decl, debug_decl = debug_decl))
cpp.write('''template <>
rdcstr DoStringise(const rdcspv::GLSLstd450 &el)
{{
BEGIN_ENUM_STRINGISE(rdcspv::GLSLstd450);
{{
{glsl_stringise}
}}
END_ENUM_STRINGISE();
}}
template <>
rdcstr DoStringise(const rdcspv::ShaderDbg &el)
{{
BEGIN_ENUM_STRINGISE(rdcspv::ShaderDbg);
{{
{debug_stringise}
}}
END_ENUM_STRINGISE();
}}
template <>
rdcstr DoStringise(const rdcspv::Generator &el)
{{
BEGIN_ENUM_STRINGISE(rdcspv::Generator);
{{
{generator_tostr}
}}
END_ENUM_STRINGISE();
}}
'''.format(glsl_stringise = glsl_stringise.rstrip(), debug_stringise = debug_stringise.rstrip(), generator_tostr = generator_tostr.rstrip()))
header.write('''
}; // namespace rdcspv
DECLARE_STRINGISE_TYPE(rdcspv::GLSLstd450);
DECLARE_STRINGISE_TYPE(rdcspv::Generator);
''')
for operand_kind in spirv['operand_kinds']:
if operand_kind['category'] == 'ValueEnum' or operand_kind['category'] == 'BitEnum':
header.write('DECLARE_STRINGISE_TYPE(rdcspv::{});\n'.format(operand_kind['kind']))
ops_header.write('''
}; // namespace rdcspv
''')
header.close()
ops_header.close()
cpp.close()