Skip to content

Commit 0953dda

Browse files
committed
add a spirv prober to test extension and capability combinations
1 parent bc0cb22 commit 0953dda

6 files changed

Lines changed: 392 additions & 0 deletions

File tree

samples/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ add_subdirectory( images )
5656
add_subdirectory( opengl )
5757
add_subdirectory( python )
5858
add_subdirectory( vulkan )
59+
add_subdirectory( spirv )
5960
add_subdirectory( svm )
6061
add_subdirectory( usm )
6162

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright (c) 2026 Ben Ashbaugh
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
find_package(Python3 COMPONENTS Interpreter QUIET)
6+
7+
add_custom_command(
8+
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/spirv_probes_generated.def
9+
COMMENT "Generating spirv_probes_generated.def..."
10+
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generate_spirv_probes.py
11+
--grammar "${SPIRV-Headers_SOURCE_DIR}/include/spirv/unified1/spirv.core.grammar.json"
12+
--output "${CMAKE_CURRENT_BINARY_DIR}/spirv_probes_generated.def"
13+
DEPENDS generate_spirv_probes.py "${SPIRV-Headers_SOURCE_DIR}/include/spirv/unified1/spirv.core.grammar.json"
14+
USES_TERMINAL
15+
VERBATIM)
16+
17+
add_opencl_sample(
18+
TEST
19+
NUMBER 01
20+
TARGET spirvprobe
21+
VERSION 300
22+
CATEGORY spirv
23+
SOURCES main.cpp ${CMAKE_CURRENT_BINARY_DIR}/spirv_probes_generated.def
24+
INCLUDES ${SPIRV-Headers_SOURCE_DIR}/include ${CMAKE_CURRENT_BINARY_DIR}
25+
LIBS ${SPIRV_TOOLS_LDFLAGS})
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env bash
2+
set -u
3+
4+
dir="spirv_dumps"
5+
tool="./spirvkernelfromfile"
6+
options="-p1"
7+
8+
if [[ ! -d "$dir" ]]; then
9+
echo "Error: directory not found: $dir"
10+
exit 1
11+
fi
12+
13+
if [[ ! -x "$tool" ]]; then
14+
echo "Error: tool not found or not executable: $tool"
15+
exit 1
16+
fi
17+
18+
shopt -s nullglob
19+
files=("$dir"/*.spv)
20+
21+
if (( ${#files[@]} == 0 )); then
22+
echo "No .spv files found in $dir"
23+
exit 0
24+
fi
25+
26+
for file in "${files[@]}"; do
27+
name="$(basename "$file")"
28+
29+
printf 'Running: %q %q --file=%q\n' "$tool" "$options" "$file"
30+
"$tool" "$options" --file="$file" > NUL 2>&1
31+
rc=$?
32+
33+
if (( rc == 0 )); then
34+
echo "SUCCESS: $name"
35+
else
36+
echo "ERROR (exit code $rc): $name"
37+
fi
38+
done
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env python3
2+
3+
# Copyright (c) 2026 Ben Ashbaugh
4+
#
5+
# SPDX-License-Identifier: MIT
6+
7+
import argparse
8+
import json
9+
10+
header_text = """\
11+
// This file is generated from the SPIR-V JSON grammar file.
12+
// Please do not edit it directly!
13+
"""
14+
15+
def main():
16+
parser = argparse.ArgumentParser(description='Generate SPIR-V extension and capability probes')
17+
18+
parser.add_argument('--grammar', metavar='<path>',
19+
type=str, required=True,
20+
help='input JSON grammar file')
21+
parser.add_argument('--output', metavar='<path>',
22+
type=str, required=False,
23+
help='output file path (default: stdout)')
24+
args = parser.parse_args()
25+
26+
extensions = {}
27+
with open(args.grammar) as json_file:
28+
grammar_json = json.loads(json_file.read())
29+
for operand_kind in grammar_json['operand_kinds']:
30+
if not 'enumerants' in operand_kind:
31+
continue
32+
for enum in operand_kind['enumerants']:
33+
if not 'extensions' in enum:
34+
continue
35+
for extension in enum['extensions']:
36+
extensions.setdefault(extension, [])
37+
if operand_kind['kind'] == 'Capability':
38+
name = enum['enumerant']
39+
extensions[extension].append(name)
40+
41+
output = []
42+
output.append(header_text)
43+
for extension in sorted(extensions.keys()):
44+
output.append('')
45+
output.append('// {}:'.format(extension))
46+
output.append('SPIRV_PROBE_EXTENSION( {} )'.format(extension))
47+
for cap in sorted(extensions[extension]):
48+
output.append('SPIRV_PROBE_EXTENSION_CAPABILITY( {}, {} )'.format(extension, cap))
49+
50+
if args.output:
51+
with open(args.output, 'w') as output_file:
52+
output_file.write('\n'.join(output))
53+
else:
54+
print('\n'.join(output))
55+
56+
if __name__ == '__main__':
57+
main()
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/*
2+
// Copyright (c) 2026 Ben Ashbaugh
3+
//
4+
// SPDX-License-Identifier: MIT
5+
*/
6+
7+
#include <popl/popl.hpp>
8+
9+
#include <CL/opencl.hpp>
10+
11+
#define SPV_ENABLE_UTILITY_CODE
12+
#include <spirv/unified1/spirv.hpp>
13+
#include <spirv-tools/libspirv.hpp>
14+
15+
#include "util.hpp"
16+
17+
#include <fstream>
18+
#include <map>
19+
#include <set>
20+
#include <string>
21+
#include <vector>
22+
23+
constexpr const char* cProlog = R"SPV(
24+
OpCapability Addresses
25+
OpCapability Linkage
26+
OpCapability Kernel
27+
)SPV";
28+
29+
constexpr const char* cEpilog = R"SPV(
30+
OpMemoryModel Physical64 OpenCL
31+
OpEntryPoint Kernel %kernel "empty"
32+
%void = OpTypeVoid
33+
%kernel_sig = OpTypeFunction %void
34+
%kernel = OpFunction %void None %kernel_sig
35+
%entry = OpLabel
36+
OpReturn
37+
OpFunctionEnd
38+
)SPV";
39+
40+
int main(int argc, char** argv)
41+
{
42+
int platformIndex = 0;
43+
int deviceIndex = 0;
44+
45+
bool build = false;
46+
47+
{
48+
popl::OptionParser op("Supported Options");
49+
op.add<popl::Value<int>>("p", "platform", "Platform Index", platformIndex, &platformIndex);
50+
op.add<popl::Value<int>>("d", "device", "Device Index", deviceIndex, &deviceIndex);
51+
op.add<popl::Switch>("b", "build", "Build the programs vs. dumping files", &build);
52+
53+
bool printUsage = false;
54+
try {
55+
op.parse(argc, argv);
56+
} catch (std::exception& e) {
57+
fprintf(stderr, "Error: %s\n\n", e.what());
58+
printUsage = true;
59+
}
60+
61+
if (printUsage || !op.unknown_options().empty() || !op.non_option_args().empty()) {
62+
fprintf(stderr,
63+
"Usage: spirvprobe [options]\n"
64+
"%s", op.help().c_str());
65+
return -1;
66+
}
67+
}
68+
69+
std::vector<cl::Platform> platforms;
70+
cl::Platform::get(&platforms);
71+
72+
if (!checkPlatformIndex(platforms, platformIndex)) {
73+
return -1;
74+
}
75+
76+
cl::Platform& platform = platforms[platformIndex];
77+
printf("Running on platform: %s\n",
78+
platform.getInfo<CL_PLATFORM_NAME>().c_str() );
79+
80+
std::vector<cl::Device> devices;
81+
platform.getDevices(CL_DEVICE_TYPE_ALL, &devices);
82+
83+
cl::Device& device = devices[deviceIndex];
84+
printf("Running on device: %s\n",
85+
device.getInfo<CL_DEVICE_NAME>().c_str() );
86+
87+
cl::Context context{device};
88+
89+
cl_version spvVersion = 0;
90+
auto ilVersions = device.getInfo<CL_DEVICE_ILS_WITH_VERSION>();
91+
for (const auto& ilVersion : ilVersions) {
92+
if (std::string(ilVersion.name) == std::string("SPIR-V") &&
93+
ilVersion.version > spvVersion) {
94+
spvVersion = ilVersion.version;
95+
}
96+
}
97+
98+
if (spvVersion == 0) {
99+
printf("No supported SPIR-V versions were found, exiting.\n");
100+
return -1;
101+
}
102+
103+
const int spvVersionMajor = CL_VERSION_MAJOR(spvVersion);
104+
const int spvVersionMinor = CL_VERSION_MINOR(spvVersion);
105+
printf("Highest supported SPIR-V version is: %d.%d\n",
106+
spvVersionMajor,
107+
spvVersionMinor);
108+
109+
const cl_uint addressBits = device.getInfo<CL_DEVICE_ADDRESS_BITS>();
110+
if (addressBits != 64) {
111+
printf("This test requires 64-bit addresses, but CL_DEVICE_ADDRESS_BITS returned %u.\n",
112+
addressBits);
113+
return -1;
114+
}
115+
116+
spv_target_env env = SPV_ENV_UNIVERSAL_1_0;
117+
if (spvVersionMajor > 1 || spvVersionMinor >= 6) {
118+
env = SPV_ENV_UNIVERSAL_1_6;
119+
} else if (spvVersionMinor == 5) {
120+
env = SPV_ENV_UNIVERSAL_1_5;
121+
} else if (spvVersionMinor == 4) {
122+
env = SPV_ENV_UNIVERSAL_1_4;
123+
} else if (spvVersionMinor == 3) {
124+
env = SPV_ENV_UNIVERSAL_1_3;
125+
} else if (spvVersionMinor == 2) {
126+
env = SPV_ENV_UNIVERSAL_1_2;
127+
} else if (spvVersionMinor == 1) {
128+
env = SPV_ENV_UNIVERSAL_1_1;
129+
}
130+
131+
printf("Collecting test probes...\n");
132+
133+
typedef std::map<std::string, std::vector<spv::Capability>> CProbeMap;
134+
CProbeMap probes;
135+
#define SPIRV_PROBE_EXTENSION(_e) \
136+
if (probes.count(#_e) == 0) { probes[#_e] = {}; }
137+
#define SPIRV_PROBE_EXTENSION_CAPABILITY(_e, _c) \
138+
probes[#_e].push_back(spv::Capability##_c);
139+
#include "spirv_probes_generated.def"
140+
#undef SPIRV_PROBE_EXTENSION
141+
#undef SPIRV_PROBE_EXTENSION_CAPABILITY
142+
143+
std::set<std::string> skipExtensions;
144+
skipExtensions.insert("SPV_ARM_graph"); // No OpGraphEntryPointARM instruction was found but the GraphARM capability is declared.
145+
skipExtensions.insert("SPV_KHR_vulkan_memory_model"); // VulkanMemoryModelKHR capability must only be specified if the VulkanKHR memory model is used.
146+
skipExtensions.insert("SPV_NV_bindless_texture"); // Missing required OpSamplerImageAddressingModeNV instruction.
147+
148+
printf("Starting testing!\n\n");
149+
150+
auto DisMessagePrinter =
151+
[](spv_message_level_t, const char *, const spv_position_t &,
152+
const char *message) -> void { fprintf(stderr, "spirv error: %s\n", message); };
153+
154+
CProbeMap::iterator i = probes.begin();
155+
while (i != probes.end()) {
156+
const auto& extension = (*i).first;
157+
const auto& capabilities = (*i).second;
158+
159+
++i;
160+
161+
if (skipExtensions.count(extension)) {
162+
printf("Skipped extension %s.\n", extension.c_str());
163+
continue;
164+
}
165+
166+
for (auto capability : capabilities) {
167+
printf("Testing extension %s with capability %s (%d)... ",
168+
extension.c_str(),
169+
spv::CapabilityToString(capability),
170+
static_cast<int>(capability));
171+
fflush(stdout);
172+
173+
std::vector<uint32_t> spirvBinary;
174+
175+
spvtools::SpirvTools tools(env);
176+
tools.SetMessageConsumer(DisMessagePrinter);
177+
178+
std::string spirv_text;
179+
spirv_text += cProlog;
180+
spirv_text += " OpCapability ";
181+
spirv_text += spv::CapabilityToString(capability);
182+
spirv_text += "\n";
183+
spirv_text += " OpExtension \"";
184+
spirv_text += extension;
185+
spirv_text += "\"\n";
186+
spirv_text += cEpilog;
187+
188+
if (!tools.Assemble(spirv_text, &spirvBinary))
189+
{
190+
printf("failed to assemble!\n");
191+
continue;
192+
}
193+
194+
if (!tools.Validate(spirvBinary.data(), spirvBinary.size())) {
195+
printf("failed to validate!\n");
196+
continue;
197+
}
198+
199+
if (build) {
200+
cl::Program program{
201+
clCreateProgramWithIL(
202+
context(),
203+
spirvBinary.data(),
204+
spirvBinary.size() * sizeof(uint32_t),
205+
nullptr)};
206+
cl_int errorCode = program.build();
207+
if (errorCode != CL_SUCCESS) {
208+
printf("failed to build!\n");
209+
printf("%s\n", program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(device).c_str());
210+
continue;
211+
}
212+
} else {
213+
std::string filename("./spirv_dumps/");
214+
filename += extension;
215+
filename += "_";
216+
filename += spv::CapabilityToString(capability);
217+
filename += ".spv";
218+
219+
std::ofstream os;
220+
os.open(filename, std::ios::out | std::ios::binary);
221+
if (os.good()) {
222+
os.write(
223+
(const char*)spirvBinary.data(),
224+
spirvBinary.size() * sizeof(uint32_t));
225+
os.close();
226+
} else {
227+
printf("Failed to dump SPIR-V to file %s!\n", filename.c_str());
228+
}
229+
}
230+
231+
printf("success.\n");
232+
}
233+
}
234+
235+
return 0;
236+
}

0 commit comments

Comments
 (0)