• 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):
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