1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2021 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.txt 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 154 enable = requires 155 link = 'features-' + link_name 156 altlink = 'sname:{}::pname:{}'.format(struct, feature) 157 158 linktext = makeLink(link, altlink) 159 else: 160 property = subelem.get('property') 161 member = subelem.get('member') 162 requires = subelem.get('requires') 163 value = subelem.get('value') 164 165 enable = requires 166 # Properties should have a "feature" prefix 167 link = 'limits-' + member 168 # Display the property value by itself if it is not a boolean (matches original table) 169 # DenormPreserve is an example where it makes sense to just show the 170 # member value as it is just a boolean and the name implies "true" 171 # GroupNonUniformVote is an example where the whole name is too long 172 # better to just display the value 173 if value == "VK_TRUE": 174 altlink = 'sname:{}::pname:{}'.format(property, member) 175 else: 176 altlink = '{}'.format(value) 177 178 linktext = makeLink(link, altlink) 179 180 # If there are no more enables, do not continue the last line 181 if remaining > 0: 182 continuation = ' +' 183 else: 184 continuation = '' 185 186 # condition_string != enable is a small optimization 187 if enable is not None and condition_string != enable: 188 body.append('ifdef::{}[]'.format(enable)) 189 body.append('{} {}{}'.format(indent, linktext, continuation)) 190 if enable is not None and condition_string != enable: 191 body.append('endif::{}[]'.format(enable)) 192 193 if elem.tag == 'spirvcapability': 194 captable += prefix + body + suffix 195 else: 196 exttable += prefix + body + suffix 197 198 # Generate the asciidoc include files 199 self.writeBlock('captable.txt', captable) 200 self.writeBlock('exttable.txt', exttable) 201 202 # Finish processing in superclass 203 OutputGenerator.endFile(self) 204 205 def writeBlock(self, basename, contents): 206 """Generate an include file. 207 208 - directory - subdirectory to put file in 209 - basename - base name of the file 210 - contents - contents of the file (Asciidoc boilerplate aside)""" 211 212 filename = self.genOpts.directory + '/' + basename 213 self.logMsg('diag', '# Generating include file:', filename) 214 with open(filename, 'w', encoding='utf-8') as fp: 215 write(self.genOpts.conventions.warning_comment, file=fp) 216 217 if len(contents) > 0: 218 for str in contents: 219 write(str, file=fp) 220 else: 221 self.logMsg('diag', '# No contents for:', filename) 222 223 def paramIsArray(self, param): 224 """Check if the parameter passed in is a pointer to an array.""" 225 return param.get('len') is not None 226 227 def paramIsPointer(self, param): 228 """Check if the parameter passed in is a pointer.""" 229 tail = param.find('type').tail 230 return tail is not None and '*' in tail 231 232 def makeThreadSafetyBlocks(self, cmd, paramtext): 233 # See also makeThreadSafetyBlock in validitygenerator.py - similar but not entirely identical 234 protoname = cmd.find('proto/name').text 235 236 # Find and add any parameters that are thread unsafe 237 explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]") 238 if explicitexternsyncparams is not None: 239 for param in explicitexternsyncparams: 240 self.makeThreadSafetyForParam(protoname, param) 241 242 # Find and add any "implicit" parameters that are thread unsafe 243 implicitexternsyncparams = cmd.find('implicitexternsyncparams') 244 if implicitexternsyncparams is not None: 245 for elem in implicitexternsyncparams: 246 entry = ValidityEntry() 247 entry += elem.text 248 entry += ' in ' 249 entry += self.makeFLink(protoname) 250 self.threadsafety['implicit'] += entry 251 252 # Add a VU for any command requiring host synchronization. 253 # This could be further parameterized, if a future non-Vulkan API 254 # requires it. 255 if self.genOpts.conventions.is_externsync_command(protoname): 256 entry = ValidityEntry() 257 entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from, in ' 258 entry += self.makeFLink(protoname) 259 self.threadsafety['implicit'] += entry 260 261 def makeThreadSafetyForParam(self, protoname, param): 262 """Create thread safety validity for a single param of a command.""" 263 externsyncattribs = ExternSyncEntry.parse_externsync_from_param(param) 264 param_name = getElemName(param) 265 266 for attrib in externsyncattribs: 267 entry = ValidityEntry() 268 is_array = False 269 if attrib.entirely_extern_sync: 270 # "true" or "true_with_children" 271 if self.paramIsArray(param): 272 entry += 'Each element of the ' 273 is_array = True 274 elif self.paramIsPointer(param): 275 entry += 'The object referenced by the ' 276 else: 277 entry += 'The ' 278 279 entry += self.makeParameterName(param_name) 280 entry += ' parameter' 281 282 if attrib.children_extern_sync: 283 entry += ', and any child handles,' 284 285 else: 286 # parameter/member reference 287 readable = attrib.get_human_readable(make_param_name=self.makeParameterName) 288 is_array = (' element of ' in readable) 289 entry += readable 290 291 entry += ' in ' 292 entry += self.makeFLink(protoname) 293 294 if is_array: 295 self.threadsafety['parameterlists'] += entry 296 else: 297 self.threadsafety['parameters'] += entry 298 299 def genSpirv(self, capinfo, name, alias): 300 """Generate SPIR-V capabilities 301 302 capinfo - dictionary entry for an XML <spirvcapability> or 303 <spirvextension> element 304 name - name attribute of capinfo.elem""" 305 306 OutputGenerator.genSpirv(self, capinfo, name, alias) 307 308 # Just accumulate each element, process in endFile 309 self.spirv.append(capinfo.elem) 310