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