1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2023 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6 7from generator import OutputGenerator, write 8from spec_tools.attributes import ExternSyncEntry 9from spec_tools.util import getElemName 10 11import pdb 12 13def makeLink(link, altlink = None): 14 """Create an asciidoctor link, optionally with altlink text 15 if provided""" 16 17 if altlink is not None: 18 return '<<{},{}>>'.format(link, altlink) 19 else: 20 return '<<{}>>'.format(link) 21 22class SpirvCapabilityOutputGenerator(OutputGenerator): 23 """SpirvCapabilityOutputGenerator - subclass of OutputGenerator. 24 Generates AsciiDoc includes of the SPIR-V capabilities table for the 25 features chapter of the API specification. 26 27 ---- methods ---- 28 SpirvCapabilityOutputGenerator(errFile, warnFile, diagFile) - args as for 29 OutputGenerator. Defines additional internal state. 30 ---- methods overriding base class ---- 31 genCmd(cmdinfo)""" 32 33 def __init__(self, *args, **kwargs): 34 super().__init__(*args, **kwargs) 35 36 def beginFile(self, genOpts): 37 OutputGenerator.beginFile(self, genOpts) 38 39 # Accumulate SPIR-V capability and feature information 40 self.spirv = [] 41 42 def getCondition(self, enable, parent): 43 """Return a strings which is the condition under which an 44 enable is supported. 45 46 - enable - ElementTree corresponding to an <enable> XML tag for a 47 SPIR-V capability or extension 48 - parent - Parent <spirvcapability> or <spirvenable> ElementTree, 49 used for error reporting""" 50 51 if enable.get('version'): 52 return enable.get('version') 53 elif enable.get('extension'): 54 return enable.get('extension') 55 elif enable.get('struct') or enable.get('property'): 56 return enable.get('requires') 57 else: 58 self.logMsg('error', f"<{parent.tag} name=\"{parent.get('name')}\"> is missing a required attribute for an <enable>") 59 return '' 60 61 def getConditions(self, enables): 62 """Return a sorted list of strings which are conditions under which 63 one or more of the enables is supported. 64 65 - enables - ElementTree corresponding to a <spirvcapability> or 66 <spirvextension> XML tag""" 67 68 conditions = set() 69 for enable in enables.findall('enable'): 70 condition = self.getCondition(enable, parent=enables) 71 if condition != None: 72 conditions.add(condition) 73 return sorted(conditions) 74 75 def endFile(self): 76 captable = [] 77 exttable = [] 78 79 # How to "indent" a pseudo-column for better use of space. 80 # {captableindent} is defined in appendices/spirvenv.adoc 81 indent = '{captableindent}' 82 83 for elem in self.spirv: 84 conditions = self.getConditions(elem) 85 86 # Combine all conditions for enables and surround the row with 87 # them 88 if len(conditions) > 0: 89 condition_string = ','.join(conditions) 90 prefix = [ 'ifdef::{}[]'.format(condition_string) ] 91 suffix = [ 'endif::{}[]'.format(condition_string) ] 92 else: 93 prefix = [] 94 suffix = [] 95 96 body = [] 97 98 # Generate an anchor for each capability 99 if elem.tag == 'spirvcapability': 100 anchor = '[[spirvenv-capabilities-table-{}]]'.format( 101 elem.get('name')) 102 else: 103 # <spirvextension> entries do not get anchors 104 anchor = '' 105 106 # First "cell" in a table row, and a break for the other "cells" 107 body.append('| {}code:{} +'.format(anchor, elem.get('name'))) 108 109 # Iterate over each enable emitting a formatting tag for it 110 # Protect the term if there is a version or extension 111 # requirement, and if there are multiple enables (otherwise, 112 # the ifdef protecting the entire row will suffice). 113 114 enables = [e for e in elem.findall('enable')] 115 116 remaining = len(enables) 117 for subelem in enables: 118 remaining -= 1 119 120 # Sentinel value 121 linktext = None 122 if subelem.get('version'): 123 version = subelem.get('version') 124 125 # Convert API enum to anchor for version appendices (versions-m.n) 126 # version must be the spec conditional macro VK_VERSION_m_n, not 127 # the API version macro VK_API_VERSION_m_n. 128 enable = version 129 link = 'versions-' + version[-3:].replace('_', '.') 130 altlink = version 131 132 linktext = makeLink(link, altlink) 133 elif subelem.get('extension'): 134 extension = subelem.get('extension') 135 136 enable = extension 137 link = extension 138 altlink = None 139 140 # This uses the extension name macro, rather than 141 # asciidoc markup 142 linktext = '`apiext:{}`'.format(extension) 143 elif subelem.get('struct'): 144 struct = subelem.get('struct') 145 feature = subelem.get('feature') 146 requires = subelem.get('requires') 147 alias = subelem.get('alias') 148 149 link_name = feature 150 # For cases, like bufferDeviceAddressEXT where need manual help 151 if alias: 152 link_name = alias 153 exceptions = { 154 'VkPhysicalDeviceCooperativeMatrixFeaturesNV::cooperativeMatrix': 'cooperativeMatrix-NV', 155 } 156 if struct + '::' + feature in exceptions: 157 link_name = exceptions[struct + '::' + feature] 158 159 enable = requires 160 link = 'features-' + link_name 161 altlink = 'sname:{}::pname:{}'.format(struct, feature) 162 163 linktext = makeLink(link, altlink) 164 else: 165 property = subelem.get('property') 166 member = subelem.get('member') 167 requires = subelem.get('requires') 168 value = subelem.get('value') 169 170 enable = requires 171 # Properties should have a "feature" prefix 172 link = 'limits-' + member 173 # Display the property value by itself if it is not a boolean (matches original table) 174 # DenormPreserve is an example where it makes sense to just show the 175 # member value as it is just a boolean and the name implies "true" 176 # GroupNonUniformVote is an example where the whole name is too long 177 # better to just display the value 178 if value == "VK_TRUE": 179 altlink = 'sname:{}::pname:{}'.format(property, member) 180 else: 181 altlink = '{}'.format(value) 182 183 linktext = makeLink(link, altlink) 184 185 # If there are no more enables, do not continue the last line 186 if remaining > 0: 187 continuation = ' +' 188 else: 189 continuation = '' 190 191 # condition_string != enable is a small optimization 192 if enable is not None and condition_string != enable: 193 body.append('ifdef::{}[]'.format(enable)) 194 body.append('{} {}{}'.format(indent, linktext, continuation)) 195 if enable is not None and condition_string != enable: 196 body.append('endif::{}[]'.format(enable)) 197 198 if elem.tag == 'spirvcapability': 199 captable += prefix + body + suffix 200 else: 201 exttable += prefix + body + suffix 202 203 # Generate the asciidoc include files 204 self.writeBlock('captable.adoc', captable) 205 self.writeBlock('exttable.adoc', exttable) 206 207 # Finish processing in superclass 208 OutputGenerator.endFile(self) 209 210 def writeBlock(self, basename, contents): 211 """Generate an include file. 212 213 - directory - subdirectory to put file in 214 - basename - base name of the file 215 - contents - contents of the file (Asciidoc boilerplate aside)""" 216 217 filename = self.genOpts.directory + '/' + basename 218 self.logMsg('diag', '# Generating include file:', filename) 219 with open(filename, 'w', encoding='utf-8') as fp: 220 write(self.genOpts.conventions.warning_comment, file=fp) 221 222 if len(contents) > 0: 223 for str in contents: 224 write(str, file=fp) 225 else: 226 self.logMsg('diag', '# No contents for:', filename) 227 228 def genSpirv(self, capinfo, name, alias): 229 """Generate SPIR-V capabilities 230 231 capinfo - dictionary entry for an XML <spirvcapability> or 232 <spirvextension> element 233 name - name attribute of capinfo.elem""" 234 235 OutputGenerator.genSpirv(self, capinfo, name, alias) 236 237 # Just accumulate each element, process in endFile 238 self.spirv.append(capinfo.elem) 239