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