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): 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 49 if enable.get('version'): 50 # Turn VK_API_VERSION_1_0 -> VK_VERSION_1_0 51 return enable.get('version').replace('API_', '') 52 elif enable.get('extension'): 53 return enable.get('extension') 54 elif enable.get('struct') or enable.get('property'): 55 return enable.get('requires') 56 else: 57 self.logMsg('error', 'Unrecognized SPIR-V enable') 58 return '' 59 60 def getConditions(self, enables): 61 """Return a sorted list of strings which are conditions under which 62 one or more of the enables is supported. 63 64 - enables - ElementTree corresponding to a <spirvcapability> or 65 <spirvextension> XML tag""" 66 67 conditions = set() 68 for enable in enables.findall('enable'): 69 condition = self.getCondition(enable) 70 if condition != None: 71 conditions.add(condition) 72 return sorted(conditions) 73 74 def endFile(self): 75 captable = [] 76 exttable = [] 77 78 # How to "indent" a pseudo-column for better use of space. 79 # {captableindent} is defined in appendices/spirvenv.txt 80 indent = '{captableindent}' 81 82 for elem in self.spirv: 83 conditions = self.getConditions(elem) 84 85 # Combine all conditions for enables and surround the row with 86 # them 87 if len(conditions) > 0: 88 condition_string = ','.join(conditions) 89 prefix = [ 'ifdef::{}[]'.format(condition_string) ] 90 suffix = [ 'endif::{}[]'.format(condition_string) ] 91 else: 92 prefix = [] 93 suffix = [] 94 95 body = [] 96 97 # Generate an anchor for each capability 98 if elem.tag == 'spirvcapability': 99 anchor = '[[spirvenv-capabilities-table-{}]]'.format( 100 elem.get('name')) 101 else: 102 # <spirvextension> entries don't get anchors 103 anchor = '' 104 105 # First "cell" in a table row, and a break for the other "cells" 106 body.append('| {}code:{} +'.format(anchor, elem.get('name'))) 107 108 # Iterate over each enable emitting a formatting tag for it 109 # Protect the term if there is a version or extension 110 # requirement, and if there are multiple enables (otherwise, 111 # the ifdef protecting the entire row will suffice). 112 113 enables = [e for e in elem.findall('enable')] 114 115 remaining = len(enables) 116 for subelem in enables: 117 remaining -= 1 118 119 # Sentinel value 120 linktext = None 121 if subelem.get('version'): 122 version = subelem.get('version') 123 124 # Convert API enum VK_API_VERSION_m_n to conditional 125 # used for spec builds (VK_VERSION_m_n) 126 enable = version.replace('API_', '') 127 # Convert API enum to anchor for version appendices (versions-m.n) 128 link = 'versions-' + version[-3:].replace('_', '.') 129 altlink = version 130 131 linktext = makeLink(link, altlink) 132 elif subelem.get('extension'): 133 extension = subelem.get('extension') 134 135 enable = extension 136 link = extension 137 altlink = None 138 139 # This uses the extension name macro, rather than 140 # asciidoc markup 141 linktext = '`apiext:{}`'.format(extension) 142 elif subelem.get('struct'): 143 struct = subelem.get('struct') 144 feature = subelem.get('feature') 145 requires = subelem.get('requires') 146 alias = subelem.get('alias') 147 148 link_name = feature 149 # For cases, like bufferDeviceAddressEXT where need manual help 150 if alias: 151 link_name = alias 152 153 enable = requires 154 link = 'features-' + link_name 155 altlink = 'sname:{}::pname:{}'.format(struct, feature) 156 157 linktext = makeLink(link, altlink) 158 else: 159 property = subelem.get('property') 160 member = subelem.get('member') 161 requires = subelem.get('requires') 162 value = subelem.get('value') 163 164 enable = requires 165 # Properties should have a "feature" prefix 166 link = 'limits-' + member 167 # Display the property value by itself if it is not a boolean (matches original table) 168 # DenormPreserve is an example where it makes sense to just show the 169 # member value as it is just a boolean and the name implies "true" 170 # GroupNonUniformVote is an example where the whole name is too long 171 # better to just display the value 172 if value == "VK_TRUE": 173 altlink = 'sname:{}::pname:{}'.format(property, member) 174 else: 175 altlink = '{}'.format(value) 176 177 linktext = makeLink(link, altlink) 178 179 # If there are no more enables, don't continue the last line 180 if remaining > 0: 181 continuation = ' +' 182 else: 183 continuation = '' 184 185 # condition_string != enable is a small optimization 186 if enable is not None and condition_string != enable: 187 body.append('ifdef::{}[]'.format(enable)) 188 body.append('{} {}{}'.format(indent, linktext, continuation)) 189 if enable is not None and condition_string != enable: 190 body.append('endif::{}[]'.format(enable)) 191 192 if elem.tag == 'spirvcapability': 193 captable += prefix + body + suffix 194 else: 195 exttable += prefix + body + suffix 196 197 # Generate the asciidoc include files 198 self.writeBlock('captable.txt', captable) 199 self.writeBlock('exttable.txt', exttable) 200 201 # Finish processing in superclass 202 OutputGenerator.endFile(self) 203 204 def writeBlock(self, basename, contents): 205 """Generate an include file. 206 207 - directory - subdirectory to put file in 208 - basename - base name of the file 209 - contents - contents of the file (Asciidoc boilerplate aside)""" 210 211 filename = self.genOpts.directory + '/' + basename 212 self.logMsg('diag', '# Generating include file:', filename) 213 with open(filename, 'w', encoding='utf-8') as fp: 214 write(self.genOpts.conventions.warning_comment, file=fp) 215 216 if len(contents) > 0: 217 for str in contents: 218 write(str, file=fp) 219 else: 220 self.logMsg('diag', '# No contents for:', filename) 221 222 def paramIsArray(self, param): 223 """Check if the parameter passed in is a pointer to an array.""" 224 return param.get('len') is not None 225 226 def paramIsPointer(self, param): 227 """Check if the parameter passed in is a pointer.""" 228 tail = param.find('type').tail 229 return tail is not None and '*' in tail 230 231 def makeThreadSafetyBlocks(self, cmd, paramtext): 232 # See also makeThreadSafetyBlock in validitygenerator.py - similar but not entirely identical 233 protoname = cmd.find('proto/name').text 234 235 # Find and add any parameters that are thread unsafe 236 explicitexternsyncparams = cmd.findall(paramtext + "[@externsync]") 237 if explicitexternsyncparams is not None: 238 for param in explicitexternsyncparams: 239 self.makeThreadSafetyForParam(protoname, param) 240 241 # Find and add any "implicit" parameters that are thread unsafe 242 implicitexternsyncparams = cmd.find('implicitexternsyncparams') 243 if implicitexternsyncparams is not None: 244 for elem in implicitexternsyncparams: 245 entry = ValidityEntry() 246 entry += elem.text 247 entry += ' in ' 248 entry += self.makeFLink(protoname) 249 self.threadsafety['implicit'] += entry 250 251 # Add a VU for any command requiring host synchronization. 252 # This could be further parameterized, if a future non-Vulkan API 253 # requires it. 254 if self.genOpts.conventions.is_externsync_command(protoname): 255 entry = ValidityEntry() 256 entry += 'The sname:VkCommandPool that pname:commandBuffer was allocated from, in ' 257 entry += self.makeFLink(protoname) 258 self.threadsafety['implicit'] += entry 259 260 def makeThreadSafetyForParam(self, protoname, param): 261 """Create thread safety validity for a single param of a command.""" 262 externsyncattribs = ExternSyncEntry.parse_externsync_from_param(param) 263 param_name = getElemName(param) 264 265 for attrib in externsyncattribs: 266 entry = ValidityEntry() 267 is_array = False 268 if attrib.entirely_extern_sync: 269 # "true" or "true_with_children" 270 if self.paramIsArray(param): 271 entry += 'Each element of the ' 272 is_array = True 273 elif self.paramIsPointer(param): 274 entry += 'The object referenced by the ' 275 else: 276 entry += 'The ' 277 278 entry += self.makeParameterName(param_name) 279 entry += ' parameter' 280 281 if attrib.children_extern_sync: 282 entry += ', and any child handles,' 283 284 else: 285 # parameter/member reference 286 readable = attrib.get_human_readable(make_param_name=self.makeParameterName) 287 is_array = (' element of ' in readable) 288 entry += readable 289 290 entry += ' in ' 291 entry += self.makeFLink(protoname) 292 293 if is_array: 294 self.threadsafety['parameterlists'] += entry 295 else: 296 self.threadsafety['parameters'] += entry 297 298 def genSpirv(self, capinfo, name, alias): 299 """Generate SPIR-V capabilities 300 301 capinfo - dictionary entry for an XML <spirvcapability> or 302 <spirvextension> element 303 name - name attribute of capinfo.elem""" 304 305 OutputGenerator.genSpirv(self, capinfo, name, alias) 306 307 # Just accumulate each element, process in endFile 308 self.spirv.append(capinfo.elem) 309