1#!/usr/bin/env python3
2#
3# Copyright 2019 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Generates the api_gen.h and api_gen.cpp.
18"""
19
20import os
21import generator_common as gencom
22
23# Functions intercepted at vulkan::api level.
24_INTERCEPTED_COMMANDS = [
25    'vkCreateDevice',
26    'vkDestroyDevice',
27    'vkDestroyInstance',
28    'vkEnumerateDeviceExtensionProperties',
29    'vkEnumerateDeviceLayerProperties',
30]
31
32
33def gen_h():
34  """Generates the api_gen.h file.
35  """
36  genfile = os.path.join(os.path.dirname(__file__),
37                         '..', 'libvulkan', 'api_gen.h')
38
39  with open(genfile, 'w') as f:
40    instance_dispatch_table_entries = []
41    device_dispatch_table_entries = []
42
43    for cmd in gencom.command_list:
44      if cmd not in gencom.alias_dict:
45        if gencom.is_instance_dispatch_table_entry(cmd):
46          instance_dispatch_table_entries.append(
47              'PFN_' + cmd + ' ' + gencom.base_name(cmd) + ';')
48        elif gencom.is_device_dispatch_table_entry(cmd):
49          device_dispatch_table_entries.append(
50              'PFN_' + cmd + ' ' + gencom.base_name(cmd) + ';')
51
52    f.write(gencom.copyright_and_warning(2016))
53
54    f.write("""\
55#ifndef LIBVULKAN_API_GEN_H
56#define LIBVULKAN_API_GEN_H
57
58#include <vulkan/vulkan.h>
59
60#include <bitset>
61
62#include "driver_gen.h"
63
64namespace vulkan {
65namespace api {
66
67struct InstanceDispatchTable {
68    // clang-format off\n""")
69
70    for entry in instance_dispatch_table_entries:
71      f.write(gencom.indent(1) + entry + '\n')
72
73    f.write("""\
74    // clang-format on
75};
76
77struct DeviceDispatchTable {
78    // clang-format off\n""")
79
80    for entry in device_dispatch_table_entries:
81      f.write(gencom.indent(1) + entry + '\n')
82
83    f.write("""\
84    // clang-format on
85};
86
87bool InitDispatchTable(
88    VkInstance instance,
89    PFN_vkGetInstanceProcAddr get_proc,
90    const std::bitset<driver::ProcHook::EXTENSION_COUNT>& extensions);
91bool InitDispatchTable(
92    VkDevice dev,
93    PFN_vkGetDeviceProcAddr get_proc,
94    const std::bitset<driver::ProcHook::EXTENSION_COUNT>& extensions);
95
96}  // namespace api
97}  // namespace vulkan
98
99#endif  // LIBVULKAN_API_GEN_H\n""")
100
101    f.close()
102  gencom.run_clang_format(genfile)
103
104
105def _define_extension_stub(cmd, f):
106  """Emits a stub for an exported extension function.
107
108  Args:
109    cmd: Vulkan function name.
110    f: Output file handle.
111  """
112  if (cmd in gencom.extension_dict and gencom.is_function_exported(cmd)):
113    ext_name = gencom.extension_dict[cmd]
114    ret = gencom.return_type_dict[cmd]
115    params = gencom.param_dict[cmd]
116    first_param = params[0][0] + params[0][1]
117    tail_params = ', '.join([i[0][:-1] for i in params[1:]])
118
119    f.write('VKAPI_ATTR ' + ret + ' disabled' + gencom.base_name(cmd) +
120            '(' + first_param + ', ' + tail_params + ') {\n')
121
122    f.write(gencom.indent(1) + 'driver::Logger(' + params[0][1] +
123            ').Err(' + params[0][1] + ', \"' + ext_name +
124            ' not enabled. Exported ' + cmd + ' not executed.\");\n')
125
126    if gencom.return_type_dict[cmd] != 'void':
127      f.write(gencom.indent(1) + 'return VK_SUCCESS;\n')
128
129    f.write('}\n\n')
130
131
132def _is_intercepted(cmd):
133  """Returns true if a function is intercepted by vulkan::api.
134
135  Args:
136    cmd: Vulkan function name.
137  """
138  if gencom.is_function_supported(cmd):
139    if gencom.is_globally_dispatched(cmd) or cmd in _INTERCEPTED_COMMANDS:
140      return True
141  return False
142
143
144def _intercept_instance_proc_addr(f):
145  """Emits code for vkGetInstanceProcAddr for function interception.
146
147  Args:
148    f: Output file handle.
149  """
150  f.write("""\
151    // global functions
152    if (instance == VK_NULL_HANDLE) {\n""")
153
154  for cmd in gencom.command_list:
155    # vkGetInstanceProcAddr(nullptr, "vkGetInstanceProcAddr") is effectively
156    # globally dispatched
157    if gencom.is_globally_dispatched(cmd) or cmd == 'vkGetInstanceProcAddr':
158      f.write(gencom.indent(2) +
159              'if (strcmp(pName, \"' + cmd +
160              '\") == 0) return reinterpret_cast<PFN_vkVoidFunction>(' +
161              gencom.base_name(cmd) + ');\n')
162
163  f.write("""
164        ALOGE("invalid vkGetInstanceProcAddr(VK_NULL_HANDLE, \\\"%s\\\") call", pName);
165        return nullptr;
166    }
167
168    static const struct Hook {
169        const char* name;
170        PFN_vkVoidFunction proc;
171    } hooks[] = {\n""")
172
173  sorted_command_list = sorted(gencom.command_list)
174  for cmd in sorted_command_list:
175    if gencom.is_function_exported(cmd):
176      if gencom.is_globally_dispatched(cmd):
177        f.write(gencom.indent(2) + '{ \"' + cmd + '\", nullptr },\n')
178      elif (_is_intercepted(cmd) or
179            cmd == 'vkGetInstanceProcAddr' or
180            gencom.is_device_dispatched(cmd)):
181        f.write(gencom.indent(2) + '{ \"' + cmd +
182                '\", reinterpret_cast<PFN_vkVoidFunction>(' +
183                gencom.base_name(cmd) + ') },\n')
184
185  f.write("""\
186    };
187    // clang-format on
188    constexpr size_t count = sizeof(hooks) / sizeof(hooks[0]);
189    auto hook = std::lower_bound(
190        hooks, hooks + count, pName,
191        [](const Hook& h, const char* n) { return strcmp(h.name, n) < 0; });
192    if (hook < hooks + count && strcmp(hook->name, pName) == 0) {
193        if (!hook->proc) {
194            vulkan::driver::Logger(instance).Err(
195                instance, "invalid vkGetInstanceProcAddr(%p, \\\"%s\\\") call",
196                instance, pName);
197        }
198        return hook->proc;
199    }
200    // clang-format off\n\n""")
201
202
203def _intercept_device_proc_addr(f):
204  """Emits code for vkGetDeviceProcAddr for function interception.
205
206  Args:
207    f: Output file handle.
208  """
209  f.write("""\
210    if (device == VK_NULL_HANDLE) {
211        ALOGE("invalid vkGetDeviceProcAddr(VK_NULL_HANDLE, ...) call");
212        return nullptr;
213    }
214
215    static const char* const known_non_device_names[] = {\n""")
216
217  sorted_command_list = sorted(gencom.command_list)
218  for cmd in sorted_command_list:
219    if gencom.is_function_supported(cmd):
220      if not gencom.is_device_dispatched(cmd):
221        f.write(gencom.indent(2) + '\"' + cmd + '\",\n')
222
223  f.write("""\
224    };
225    // clang-format on
226    constexpr size_t count =
227        sizeof(known_non_device_names) / sizeof(known_non_device_names[0]);
228    if (!pName ||
229        std::binary_search(
230            known_non_device_names, known_non_device_names + count, pName,
231            [](const char* a, const char* b) { return (strcmp(a, b) < 0); })) {
232        vulkan::driver::Logger(device).Err(
233            device, "invalid vkGetDeviceProcAddr(%p, \\\"%s\\\") call", device,
234            (pName) ? pName : "(null)");
235        return nullptr;
236    }
237    // clang-format off\n\n""")
238
239  for cmd in gencom.command_list:
240    if gencom.is_device_dispatched(cmd):
241      if _is_intercepted(cmd) or cmd == 'vkGetDeviceProcAddr':
242        f.write(gencom.indent(1) + 'if (strcmp(pName, "' + cmd +
243                '") == 0) return reinterpret_cast<PFN_vkVoidFunction>(' +
244                gencom.base_name(cmd) + ');\n')
245  f.write('\n')
246
247
248def _api_dispatch(cmd, f):
249  """Emits code to dispatch a function.
250
251  Args:
252    cmd: Vulkan function name.
253    f: Output file handle.
254  """
255  assert not _is_intercepted(cmd)
256
257  f.write(gencom.indent(1))
258  if gencom.return_type_dict[cmd] != 'void':
259    f.write('return ')
260
261  param_list = gencom.param_dict[cmd]
262  handle = param_list[0][1]
263  f.write('GetData(' + handle + ').dispatch.' + gencom.base_name(cmd) +
264          '(' + ', '.join(i[1] for i in param_list) + ');\n')
265
266
267def gen_cpp():
268  """Generates the api_gen.cpp file.
269  """
270  genfile = os.path.join(os.path.dirname(__file__),
271                         '..', 'libvulkan', 'api_gen.cpp')
272
273  with open(genfile, 'w') as f:
274    f.write(gencom.copyright_and_warning(2016))
275
276    f.write("""\
277#include <log/log.h>
278#include <string.h>
279
280#include <algorithm>
281
282// to catch mismatches between vulkan.h and this file
283#undef VK_NO_PROTOTYPES
284#include "api.h"
285
286namespace vulkan {
287namespace api {
288
289#define UNLIKELY(expr) __builtin_expect((expr), 0)
290
291#define INIT_PROC(required, obj, proc)                                 \\
292    do {                                                               \\
293        data.dispatch.proc =                                           \\
294            reinterpret_cast<PFN_vk##proc>(get_proc(obj, "vk" #proc)); \\
295        if (UNLIKELY(required && !data.dispatch.proc)) {               \\
296            ALOGE("missing " #obj " proc: vk" #proc);                  \\
297            success = false;                                           \\
298        }                                                              \\
299    } while (0)
300
301// Exported extension functions may be invoked even when their extensions
302// are disabled.  Dispatch to stubs when that happens.
303#define INIT_PROC_EXT(ext, required, obj, proc)  \\
304    do {                                         \\
305        if (extensions[driver::ProcHook::ext])   \\
306            INIT_PROC(required, obj, proc);      \\
307        else                                     \\
308            data.dispatch.proc = disabled##proc; \\
309    } while (0)
310
311namespace {
312
313// clang-format off\n\n""")
314
315    for cmd in gencom.command_list:
316      _define_extension_stub(cmd, f)
317
318    f.write("""\
319// clang-format on
320
321}  // namespace
322
323bool InitDispatchTable(
324    VkInstance instance,
325    PFN_vkGetInstanceProcAddr get_proc,
326    const std::bitset<driver::ProcHook::EXTENSION_COUNT>& extensions) {
327    auto& data = GetData(instance);
328    bool success = true;
329
330    // clang-format off\n""")
331
332    for cmd in gencom.command_list:
333      if gencom.is_instance_dispatch_table_entry(cmd):
334        gencom.init_proc(cmd, f)
335
336    f.write("""\
337    // clang-format on
338
339    return success;
340}
341
342bool InitDispatchTable(
343    VkDevice dev,
344    PFN_vkGetDeviceProcAddr get_proc,
345    const std::bitset<driver::ProcHook::EXTENSION_COUNT>& extensions) {
346    auto& data = GetData(dev);
347    bool success = true;
348
349    // clang-format off\n""")
350
351    for cmd in gencom.command_list:
352      if gencom.is_device_dispatch_table_entry(cmd):
353        gencom.init_proc(cmd, f)
354
355    f.write("""\
356    // clang-format on
357
358    return success;
359}
360
361// clang-format off
362
363namespace {
364
365// forward declarations needed by GetInstanceProcAddr and GetDeviceProcAddr
366""")
367
368    for cmd in gencom.command_list:
369      if gencom.is_function_exported(cmd) and not _is_intercepted(cmd):
370        param_list = [''.join(i) for i in gencom.param_dict[cmd]]
371        f.write('VKAPI_ATTR ' + gencom.return_type_dict[cmd] + ' ' +
372                gencom.base_name(cmd) + '(' + ', '.join(param_list) + ');\n')
373
374    f.write('\n')
375    for cmd in gencom.command_list:
376      if gencom.is_function_exported(cmd) and not _is_intercepted(cmd):
377        param_list = [''.join(i) for i in gencom.param_dict[cmd]]
378        f.write('VKAPI_ATTR ' + gencom.return_type_dict[cmd] + ' ' +
379                gencom.base_name(cmd) + '(' + ', '.join(param_list) + ') {\n')
380        if cmd == 'vkGetInstanceProcAddr':
381          _intercept_instance_proc_addr(f)
382        elif cmd == 'vkGetDeviceProcAddr':
383          _intercept_device_proc_addr(f)
384        _api_dispatch(cmd, f)
385        f.write('}\n\n')
386
387    f.write("""
388}  // anonymous namespace
389
390// clang-format on
391
392}  // namespace api
393}  // namespace vulkan
394
395// clang-format off\n\n""")
396
397    for cmd in gencom.command_list:
398      if gencom.is_function_exported(cmd):
399        param_list = [''.join(i) for i in gencom.param_dict[cmd]]
400        f.write('__attribute__((visibility("default")))\n')
401        f.write('VKAPI_ATTR ' + gencom.return_type_dict[cmd] + ' ' +
402                cmd + '(' + ', '.join(param_list) + ') {\n')
403        f.write(gencom.indent(1))
404        if gencom.return_type_dict[cmd] != 'void':
405          f.write('return ')
406        param_list = gencom.param_dict[cmd]
407        f.write('vulkan::api::' + gencom.base_name(cmd) +
408                '(' + ', '.join(i[1] for i in param_list) + ');\n}\n\n')
409
410    f.write('// clang-format on\n')
411    f.close()
412  gencom.run_clang_format(genfile)
413