• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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