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