1 | // Copyright (c) 2014-2024 The Khronos Group Inc.
|
---|
2 | //
|
---|
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
|
---|
4 | // of this software and/or associated documentation files (the "Materials"),
|
---|
5 | // to deal in the Materials without restriction, including without limitation
|
---|
6 | // the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
---|
7 | // and/or sell copies of the Materials, and to permit persons to whom the
|
---|
8 | // Materials are furnished to do so, subject to the following conditions:
|
---|
9 | //
|
---|
10 | // The above copyright notice and this permission notice shall be included in
|
---|
11 | // all copies or substantial portions of the Materials.
|
---|
12 | //
|
---|
13 | // MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS
|
---|
14 | // STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND
|
---|
15 | // HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/
|
---|
16 | //
|
---|
17 | // THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
---|
18 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
---|
19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
---|
20 | // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
---|
21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
---|
22 | // FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS
|
---|
23 | // IN THE MATERIALS.
|
---|
24 |
|
---|
25 | //
|
---|
26 | // Print headers for SPIR-V in several languages.
|
---|
27 | //
|
---|
28 | // To change the header information, change the C++-built database in doc.*.
|
---|
29 | //
|
---|
30 | // Then, use "spriv -h <language>" - e.g, spriv.{h,hpp,lua,py,etc}:
|
---|
31 | // replace the auto-generated header, or "spirv -H" to generate all
|
---|
32 | // supported language headers to predefined names in the current directory.
|
---|
33 | //
|
---|
34 |
|
---|
35 | #include <string>
|
---|
36 | #include <sstream>
|
---|
37 | #include <fstream>
|
---|
38 | #include <cstring>
|
---|
39 | #include <cstdio>
|
---|
40 | #include <algorithm>
|
---|
41 | #include <memory>
|
---|
42 | #include <cctype>
|
---|
43 | #include <vector>
|
---|
44 | #include <utility>
|
---|
45 | #include <set>
|
---|
46 |
|
---|
47 | #include "jsoncpp/dist/json/json.h"
|
---|
48 |
|
---|
49 | #include "header.h"
|
---|
50 | #include "jsonToSpirv.h"
|
---|
51 |
|
---|
52 | // snprintf and _snprintf are not quite the same, but close enough
|
---|
53 | // for our use.
|
---|
54 | #ifdef _MSC_VER
|
---|
55 | #pragma warning(disable:4996)
|
---|
56 | #define snprintf _snprintf
|
---|
57 | #endif
|
---|
58 |
|
---|
59 | // This file converts SPIR-V definitions to an internal JSON
|
---|
60 | // representation, and then generates language specific
|
---|
61 | // data from that single internal form.
|
---|
62 |
|
---|
63 | // Initially, the internal form is created from C++ data,
|
---|
64 | // though this can be changed to a JSON master in time.
|
---|
65 |
|
---|
66 | namespace {
|
---|
67 | class TPrinter {
|
---|
68 | protected:
|
---|
69 | TPrinter();
|
---|
70 |
|
---|
71 | static const int DocMagicNumber = 0x07230203;
|
---|
72 | static const int DocVersion = 0x00010600;
|
---|
73 | static const int DocRevision = 1;
|
---|
74 | #define DocRevisionString "1"
|
---|
75 | static const std::string DocCopyright;
|
---|
76 | static const std::string DocComment1;
|
---|
77 | static const std::string DocComment2;
|
---|
78 |
|
---|
79 | enum enumStyle_t {
|
---|
80 | enumNoMask,
|
---|
81 | enumCount,
|
---|
82 | enumShift,
|
---|
83 | enumMask,
|
---|
84 | enumHex,
|
---|
85 | };
|
---|
86 |
|
---|
87 | static std::string styleStr(enumStyle_t s) {
|
---|
88 | return s == enumShift ? "Shift" :
|
---|
89 | s == enumMask ? "Mask" : "";
|
---|
90 | }
|
---|
91 |
|
---|
92 | friend std::ostream& operator<<(std::ostream&, const TPrinter&);
|
---|
93 |
|
---|
94 | virtual void printAll(std::ostream&) const;
|
---|
95 | virtual void printComments(std::ostream&) const;
|
---|
96 | virtual void printPrologue(std::ostream&) const { }
|
---|
97 | virtual void printDefs(std::ostream&) const;
|
---|
98 | virtual void printEpilogue(std::ostream&) const { }
|
---|
99 | virtual void printMeta(std::ostream&) const;
|
---|
100 | virtual void printTypes(std::ostream&) const { }
|
---|
101 | virtual void printHasResultType(std::ostream&) const { };
|
---|
102 |
|
---|
103 | virtual std::string escapeComment(const std::string& s) const;
|
---|
104 |
|
---|
105 | // Default printComments() uses these comment strings
|
---|
106 | virtual std::string commentBeg() const { return ""; }
|
---|
107 | virtual std::string commentEnd(bool isLast) const { return ""; }
|
---|
108 | virtual std::string commentBOL() const { return ""; }
|
---|
109 | virtual std::string commentEOL(bool isLast) const { return ""; }
|
---|
110 |
|
---|
111 | typedef std::pair<unsigned, std::string> valpair_t;
|
---|
112 |
|
---|
113 | // for printing enum values
|
---|
114 | virtual std::string enumBeg(const std::string&, enumStyle_t) const { return ""; }
|
---|
115 | virtual std::string enumEnd(const std::string&, enumStyle_t, bool isLast = false) const {
|
---|
116 | return "";
|
---|
117 | }
|
---|
118 | virtual std::string enumFmt(const std::string&, const valpair_t&,
|
---|
119 | enumStyle_t, bool isLast = false) const {
|
---|
120 | return "";
|
---|
121 | }
|
---|
122 | virtual std::string maxEnumFmt(const std::string&, const valpair_t&,
|
---|
123 | enumStyle_t) const {
|
---|
124 | return "";
|
---|
125 | }
|
---|
126 |
|
---|
127 | virtual std::string fmtConstInt(unsigned val, const std::string& name,
|
---|
128 | const char* fmt, bool isLast = false) const {
|
---|
129 | return "";
|
---|
130 | }
|
---|
131 |
|
---|
132 | std::vector<valpair_t> getSortedVals(const Json::Value&) const;
|
---|
133 |
|
---|
134 | virtual std::string indent(int count = 1) const {
|
---|
135 | return std::string(count * 4, ' '); // default indent level = 4
|
---|
136 | }
|
---|
137 |
|
---|
138 | static std::string fmtNum(const char* fmt, unsigned val) {
|
---|
139 | char buff[16]; // ample for 8 hex digits + 0x
|
---|
140 | snprintf(buff, sizeof(buff), fmt, val);
|
---|
141 | buff[sizeof(buff)-1] = '\0'; // MSVC doesn't promise null termination
|
---|
142 | return buff;
|
---|
143 | }
|
---|
144 |
|
---|
145 | static std::string fmtStyleVal(unsigned v, enumStyle_t style);
|
---|
146 |
|
---|
147 | // If the enum value name would start with a sigit, prepend the enum name.
|
---|
148 | // E.g, "3D" -> "Dim3D".
|
---|
149 | static std::string prependIfDigit(const std::string& ename, const std::string& vname) {
|
---|
150 | return (std::isdigit(vname[0]) ? ename : std::string("")) + vname;
|
---|
151 | }
|
---|
152 |
|
---|
153 | void addComment(Json::Value& node, const std::string& str);
|
---|
154 |
|
---|
155 | Json::Value spvRoot; // JSON SPIR-V data
|
---|
156 | };
|
---|
157 |
|
---|
158 | // Format value as mask or value
|
---|
159 | std::string TPrinter::fmtStyleVal(unsigned v, enumStyle_t style)
|
---|
160 | {
|
---|
161 | switch (style) {
|
---|
162 | case enumMask:
|
---|
163 | return fmtNum("0x%08x", 1<<v);
|
---|
164 | case enumHex:
|
---|
165 | return fmtNum("0x%08x", v);
|
---|
166 | default:
|
---|
167 | return std::to_string(v);
|
---|
168 | }
|
---|
169 | }
|
---|
170 |
|
---|
171 | const std::string TPrinter::DocCopyright =
|
---|
172 | R"(Copyright (c) 2014-2024 The Khronos Group Inc.
|
---|
173 |
|
---|
174 | Permission is hereby granted, free of charge, to any person obtaining a copy
|
---|
175 | of this software and/or associated documentation files (the "Materials"),
|
---|
176 | to deal in the Materials without restriction, including without limitation
|
---|
177 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
---|
178 | and/or sell copies of the Materials, and to permit persons to whom the
|
---|
179 | Materials are furnished to do so, subject to the following conditions:
|
---|
180 |
|
---|
181 | The above copyright notice and this permission notice shall be included in
|
---|
182 | all copies or substantial portions of the Materials.
|
---|
183 |
|
---|
184 | MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS
|
---|
185 | STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND
|
---|
186 | HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/
|
---|
187 |
|
---|
188 | THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
---|
189 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
---|
190 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
---|
191 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
---|
192 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
---|
193 | FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS
|
---|
194 | IN THE MATERIALS.
|
---|
195 | )";
|
---|
196 |
|
---|
197 | const std::string TPrinter::DocComment1 =
|
---|
198 | "This header is automatically generated by the same tool that creates\n"
|
---|
199 | "the Binary Section of the SPIR-V specification.\n";
|
---|
200 |
|
---|
201 | const std::string TPrinter::DocComment2 =
|
---|
202 | "Enumeration tokens for SPIR-V, in various styles:\n"
|
---|
203 | " C, C++, C++11, JSON, Lua, Python, C#, D, Beef\n"
|
---|
204 | "\n"
|
---|
205 | "- C will have tokens with a \"Spv\" prefix, e.g.: SpvSourceLanguageGLSL\n"
|
---|
206 | "- C++ will have tokens in the \"spv\" name space, e.g.: spv::SourceLanguageGLSL\n"
|
---|
207 | "- C++11 will use enum classes in the spv namespace, e.g.: spv::SourceLanguage::GLSL\n"
|
---|
208 | "- Lua will use tables, e.g.: spv.SourceLanguage.GLSL\n"
|
---|
209 | "- Python will use dictionaries, e.g.: spv['SourceLanguage']['GLSL']\n"
|
---|
210 | "- C# will use enum classes in the Specification class located in the \"Spv\" namespace,\n"
|
---|
211 | " e.g.: Spv.Specification.SourceLanguage.GLSL\n"
|
---|
212 | "- D will have tokens under the \"spv\" module, e.g: spv.SourceLanguage.GLSL\n"
|
---|
213 | "- Beef will use enum classes in the Specification class located in the \"Spv\" namespace,\n"
|
---|
214 | " e.g.: Spv.Specification.SourceLanguage.GLSL\n"
|
---|
215 | "\n"
|
---|
216 | "Some tokens act like mask values, which can be OR'd together,\n"
|
---|
217 | "while others are mutually exclusive. The mask-like ones have\n"
|
---|
218 | "\"Mask\" in their name, and a parallel enum that has the shift\n"
|
---|
219 | "amount (1 << x) for each corresponding enumerant.\n";
|
---|
220 |
|
---|
221 | // Construct
|
---|
222 | TPrinter::TPrinter()
|
---|
223 | {
|
---|
224 | Json::Value& meta = spvRoot["spv"]["meta"];
|
---|
225 | Json::Value& enums = spvRoot["spv"]["enum"];
|
---|
226 |
|
---|
227 | meta["MagicNumber"] = DocMagicNumber;
|
---|
228 | meta["Version"] = DocVersion;
|
---|
229 | meta["Revision"] = DocRevision;
|
---|
230 | meta["OpCodeMask"] = 0xffff;
|
---|
231 | meta["WordCountShift"] = 16;
|
---|
232 |
|
---|
233 | int commentId = 0;
|
---|
234 | addComment(meta["Comment"][commentId++], DocCopyright);
|
---|
235 | addComment(meta["Comment"][commentId++], DocComment1);
|
---|
236 | addComment(meta["Comment"][commentId++], DocComment2);
|
---|
237 |
|
---|
238 | for (int e = spv::OperandSource; e < spv::OperandOpcode; ++e) {
|
---|
239 | auto& enumSet = spv::OperandClassParams[e];
|
---|
240 | const bool mask = enumSet.bitmask;
|
---|
241 | const std::string enumName = enumSet.codeName;
|
---|
242 |
|
---|
243 | for (auto& enumRow : enumSet) {
|
---|
244 | std::string name = enumRow.name;
|
---|
245 | enums[e - spv::OperandSource]["Values"][name] = enumRow.value;
|
---|
246 | }
|
---|
247 |
|
---|
248 | enums[e - spv::OperandSource]["Type"] = mask ? "Bit" : "Value";
|
---|
249 | enums[e - spv::OperandSource]["Name"] = enumName;
|
---|
250 | }
|
---|
251 |
|
---|
252 | // Instructions are in their own different table
|
---|
253 | {
|
---|
254 | auto& entry = enums[spv::OperandOpcode - spv::OperandSource];
|
---|
255 | for (auto& enumRow : spv::InstructionDesc) {
|
---|
256 | std::string name = enumRow.name;
|
---|
257 | entry["Values"][name] = enumRow.value;
|
---|
258 | }
|
---|
259 | entry["Type"] = "Value";
|
---|
260 | entry["Name"] = "Op";
|
---|
261 | }
|
---|
262 | }
|
---|
263 |
|
---|
264 | // Create comment
|
---|
265 | void TPrinter::addComment(Json::Value& node, const std::string& str)
|
---|
266 | {
|
---|
267 | std::istringstream cstream(str);
|
---|
268 | std::string cline;
|
---|
269 |
|
---|
270 | int line = 0;
|
---|
271 | while (std::getline(cstream, cline)) // fmt each line
|
---|
272 | node[line++] = cline;
|
---|
273 | }
|
---|
274 |
|
---|
275 |
|
---|
276 | // Return a list of values sorted by enum value. The std::vector
|
---|
277 | // returned by value is okay in c++11 due to move semantics.
|
---|
278 | std::vector<TPrinter::valpair_t>
|
---|
279 | TPrinter::getSortedVals(const Json::Value& p) const
|
---|
280 | {
|
---|
281 | std::vector<valpair_t> values;
|
---|
282 |
|
---|
283 | for (auto e = p.begin(); e != p.end(); ++e)
|
---|
284 | values.push_back(valpair_t(e->asUInt(), e.name()));
|
---|
285 |
|
---|
286 | // Use a stable sort because we might have aliases, e.g.
|
---|
287 | // SubgropuBallot (might be in future core) vs. SubgroupBallotKHR.
|
---|
288 | std::stable_sort(values.begin(), values.end());
|
---|
289 |
|
---|
290 | return values;
|
---|
291 | }
|
---|
292 |
|
---|
293 | // Escape comment characters if needed
|
---|
294 | std::string TPrinter::escapeComment(const std::string& s) const { return s; }
|
---|
295 |
|
---|
296 | // Format comments in language specific way
|
---|
297 | void TPrinter::printComments(std::ostream& out) const
|
---|
298 | {
|
---|
299 | const int commentCount = spvRoot["spv"]["meta"]["Comment"].size();
|
---|
300 | int commentNum = 0;
|
---|
301 |
|
---|
302 | for (const auto& comment : spvRoot["spv"]["meta"]["Comment"]) {
|
---|
303 | out << commentBeg();
|
---|
304 |
|
---|
305 | for (int line = 0; line < int(comment.size()); ++line)
|
---|
306 | out << commentBOL() << escapeComment(comment[line].asString()) <<
|
---|
307 | commentEOL((line+1) == comment.size()) << std::endl;
|
---|
308 |
|
---|
309 | out << commentEnd(++commentNum == commentCount) << std::endl;
|
---|
310 | }
|
---|
311 | }
|
---|
312 |
|
---|
313 | // Format header metadata
|
---|
314 | void TPrinter::printMeta(std::ostream& out) const
|
---|
315 | {
|
---|
316 | const Json::Value& meta = spvRoot["spv"]["meta"];
|
---|
317 |
|
---|
318 | const auto print = [&](const char* name, const char* fmt, bool isLast) {
|
---|
319 | out << fmtConstInt(meta[name].asUInt(), name, fmt, isLast);
|
---|
320 | };
|
---|
321 |
|
---|
322 | print("MagicNumber", "0x%08lx", false);
|
---|
323 | print("Version", "0x%08lx", false);
|
---|
324 | print("Revision", "%d", false);
|
---|
325 | print("OpCodeMask", "0x%04x", false);
|
---|
326 | print("WordCountShift", "%d", true);
|
---|
327 | }
|
---|
328 |
|
---|
329 | // Format value definitions in language specific way
|
---|
330 | void TPrinter::printDefs(std::ostream& out) const
|
---|
331 | {
|
---|
332 | const Json::Value& enums = spvRoot["spv"]["enum"];
|
---|
333 |
|
---|
334 | for (auto opClass = enums.begin(); opClass != enums.end(); ++opClass) {
|
---|
335 | const bool isMask = (*opClass)["Type"].asString() == "Bit";
|
---|
336 | const auto opName = (*opClass)["Name"].asString();
|
---|
337 | const auto opPrefix = opName == "Op" ? "" : opName;
|
---|
338 |
|
---|
339 | for (enumStyle_t style = (isMask ? enumShift : enumCount);
|
---|
340 | style <= (isMask ? enumMask : enumCount); style = enumStyle_t(int(style)+1)) {
|
---|
341 |
|
---|
342 | out << enumBeg(opName, style);
|
---|
343 |
|
---|
344 | if (style == enumMask)
|
---|
345 | out << enumFmt(opPrefix, valpair_t(0, "MaskNone"), enumNoMask);
|
---|
346 |
|
---|
347 | const auto sorted = getSortedVals((*opClass)["Values"]);
|
---|
348 |
|
---|
349 | std::string maxEnum = maxEnumFmt(opName, valpair_t(0x7FFFFFFF, "Max"), enumHex);
|
---|
350 |
|
---|
351 | bool printMax = (style != enumMask && maxEnum.size() > 0);
|
---|
352 |
|
---|
353 | for (const auto& v : sorted)
|
---|
354 | out << enumFmt(opPrefix, v, style, !printMax && v.second == sorted.back().second);
|
---|
355 |
|
---|
356 | if (printMax)
|
---|
357 | out << maxEnum;
|
---|
358 |
|
---|
359 | auto nextOpClass = opClass;
|
---|
360 | out << enumEnd(opName, style, ++nextOpClass == enums.end());
|
---|
361 | }
|
---|
362 | }
|
---|
363 | }
|
---|
364 |
|
---|
365 | void TPrinter::printAll(std::ostream& out) const
|
---|
366 | {
|
---|
367 | printComments(out);
|
---|
368 | printPrologue(out);
|
---|
369 | printTypes(out);
|
---|
370 | printMeta(out);
|
---|
371 | printDefs(out);
|
---|
372 | printHasResultType(out);
|
---|
373 | printEpilogue(out);
|
---|
374 | }
|
---|
375 |
|
---|
376 | // Stream entire header to output
|
---|
377 | std::ostream& operator<<(std::ostream& out, const TPrinter &p)
|
---|
378 | {
|
---|
379 | p.printAll(out);
|
---|
380 | return out;
|
---|
381 | }
|
---|
382 |
|
---|
383 | // JSON printer. Rather than use the default printer, we supply our own so
|
---|
384 | // we can control the printing order within various containers.
|
---|
385 | class TPrinterJSON final : public TPrinter {
|
---|
386 | private:
|
---|
387 | void printPrologue(std::ostream& out) const override { out << "{\n" + indent() + "\"spv\":\n" + indent() + "{\n"; }
|
---|
388 | void printEpilogue(std::ostream& out) const override { out << indent() + "}\n}\n"; }
|
---|
389 |
|
---|
390 | std::string escapeComment(const std::string& s) const override {
|
---|
391 | std::string newStr;
|
---|
392 | for (auto c : s) {
|
---|
393 | if (c == '"') {
|
---|
394 | newStr += '\\';
|
---|
395 | newStr += c;
|
---|
396 | } else {
|
---|
397 | newStr += c;
|
---|
398 | }
|
---|
399 | }
|
---|
400 | return newStr;
|
---|
401 | }
|
---|
402 |
|
---|
403 | std::string fmtConstInt(unsigned val, const std::string& name,
|
---|
404 | const char* fmt, bool isLast) const override {
|
---|
405 | return indent(3) + '"' + name + "\": " + fmtNum("%d", val) + (isLast ? "\n" : ",\n");
|
---|
406 | }
|
---|
407 |
|
---|
408 | void printMeta(std::ostream& out) const override
|
---|
409 | {
|
---|
410 | out << indent(2) + "\"meta\":\n" + indent(2) + "{\n";
|
---|
411 | printComments(out);
|
---|
412 | TPrinter::printMeta(out);
|
---|
413 | out << indent(2) + "},\n";
|
---|
414 | }
|
---|
415 |
|
---|
416 | std::string commentBeg() const override { return indent(4) + "[\n"; }
|
---|
417 | std::string commentEnd(bool isLast) const override { return indent(4) + (isLast ? "]" : "],"); }
|
---|
418 | std::string commentBOL() const override { return indent(5) + '"'; }
|
---|
419 | std::string commentEOL(bool isLast) const override { return (isLast ? "\"" : "\","); }
|
---|
420 |
|
---|
421 | void printComments(std::ostream& out) const override
|
---|
422 | {
|
---|
423 | out << indent(3) + "\"Comment\":\n" + indent(3) + "[\n";
|
---|
424 | TPrinter::printComments(out);
|
---|
425 | out << indent(3) + "],\n";
|
---|
426 | }
|
---|
427 |
|
---|
428 | void printDefs(std::ostream& out) const override
|
---|
429 | {
|
---|
430 | out << indent(2) + "\"enum\":\n" + indent(2) + "[\n";
|
---|
431 | TPrinter::printDefs(out);
|
---|
432 | out << indent(2) + "]\n";
|
---|
433 | }
|
---|
434 |
|
---|
435 | void printAll(std::ostream& out) const override
|
---|
436 | {
|
---|
437 | printPrologue(out);
|
---|
438 | printMeta(out);
|
---|
439 | printDefs(out);
|
---|
440 | printEpilogue(out);
|
---|
441 | }
|
---|
442 |
|
---|
443 | std::string enumBeg(const std::string& s, enumStyle_t style) const override {
|
---|
444 | if (style == enumMask)
|
---|
445 | return "";
|
---|
446 | return indent(3) + "{\n" +
|
---|
447 | indent(4) + "\"Name\": \"" + s + "\",\n" +
|
---|
448 | indent(4) + "\"Type\": " + (style == enumShift ? "\"Bit\"" : "\"Value\"") + ",\n" +
|
---|
449 | indent(4) + "\"Values\":\n" +
|
---|
450 | indent(4) + "{\n";
|
---|
451 | }
|
---|
452 |
|
---|
453 | std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
|
---|
454 | if (style == enumMask)
|
---|
455 | return "";
|
---|
456 | return indent(4) + "}\n" +
|
---|
457 | indent(3) + "}" + (isLast ? "" : ",") + "\n";
|
---|
458 | }
|
---|
459 |
|
---|
460 | std::string enumFmt(const std::string& s, const valpair_t& v,
|
---|
461 | enumStyle_t style, bool isLast) const override {
|
---|
462 | if (style == enumMask || style == enumNoMask)
|
---|
463 | return "";
|
---|
464 | return indent(5) + '"' + prependIfDigit(s, v.second) + "\": " + fmtNum("%d", v.first) +
|
---|
465 | (isLast ? "\n" : ",\n");
|
---|
466 | }
|
---|
467 | };
|
---|
468 |
|
---|
469 | // base for C and C++
|
---|
470 | class TPrinterCBase : public TPrinter {
|
---|
471 | protected:
|
---|
472 | virtual void printPrologue(std::ostream& out) const override {
|
---|
473 | out << "#ifndef spirv_" << headerGuardSuffix() << std::endl
|
---|
474 | << "#define spirv_" << headerGuardSuffix() << std::endl
|
---|
475 | << std::endl;
|
---|
476 | }
|
---|
477 |
|
---|
478 | void printMeta(std::ostream& out) const override {
|
---|
479 | out << "#define SPV_VERSION 0x" << std::hex << DocVersion << std::dec << "\n";
|
---|
480 | out << "#define SPV_REVISION " << DocRevision << "\n";
|
---|
481 | out << "\n";
|
---|
482 |
|
---|
483 | return TPrinter::printMeta(out);
|
---|
484 | }
|
---|
485 |
|
---|
486 | virtual void printEpilogue(std::ostream& out) const override {
|
---|
487 | out << "#endif" << std::endl;
|
---|
488 | }
|
---|
489 |
|
---|
490 | virtual void printTypes(std::ostream& out) const override {
|
---|
491 | out << "typedef unsigned int " << pre() << "Id;\n\n";
|
---|
492 | }
|
---|
493 |
|
---|
494 | virtual std::string fmtConstInt(unsigned val, const std::string& name,
|
---|
495 | const char* fmt, bool isLast) const override
|
---|
496 | {
|
---|
497 | return std::string("static const unsigned int ") + pre() + name +
|
---|
498 | " = " + fmtNum(fmt, val) + (isLast ? ";\n\n" : ";\n");
|
---|
499 | }
|
---|
500 |
|
---|
501 | virtual std::string pre() const { return ""; } // C name prefix
|
---|
502 | virtual std::string headerGuardSuffix() const = 0;
|
---|
503 |
|
---|
504 | virtual std::string fmtEnumUse(const std::string& opPrefix, const std::string& name) const { return pre() + name; }
|
---|
505 |
|
---|
506 | virtual void printHasResultType(std::ostream& out) const override
|
---|
507 | {
|
---|
508 | const Json::Value& enums = spvRoot["spv"]["enum"];
|
---|
509 |
|
---|
510 | std::set<unsigned> seenValues;
|
---|
511 |
|
---|
512 | for (auto opClass = enums.begin(); opClass != enums.end(); ++opClass) {
|
---|
513 | const auto opName = (*opClass)["Name"].asString();
|
---|
514 | if (opName != "Op") {
|
---|
515 | continue;
|
---|
516 | }
|
---|
517 |
|
---|
518 | out << "#ifdef SPV_ENABLE_UTILITY_CODE" << std::endl;
|
---|
519 | out << "#ifndef __cplusplus" << std::endl;
|
---|
520 | out << "#include <stdbool.h>" << std::endl;
|
---|
521 | out << "#endif" << std::endl;
|
---|
522 | out << "inline void " << pre() << "HasResultAndType(" << pre() << opName << " opcode, bool *hasResult, bool *hasResultType) {" << std::endl;
|
---|
523 | out << " *hasResult = *hasResultType = false;" << std::endl;
|
---|
524 | out << " switch (opcode) {" << std::endl;
|
---|
525 | out << " default: /* unknown opcode */ break;" << std::endl;
|
---|
526 |
|
---|
527 | for (auto& inst : spv::InstructionDesc) {
|
---|
528 |
|
---|
529 | // Filter out duplicate enum values, which would break the switch statement.
|
---|
530 | // These are probably just extension enums promoted to core.
|
---|
531 | if (seenValues.find(inst.value) != seenValues.end()) {
|
---|
532 | continue;
|
---|
533 | }
|
---|
534 | seenValues.insert(inst.value);
|
---|
535 |
|
---|
536 | std::string name = inst.name;
|
---|
537 | out << " case " << fmtEnumUse("Op", name) << ": *hasResult = " << (inst.hasResult() ? "true" : "false") << "; *hasResultType = " << (inst.hasType() ? "true" : "false") << "; break;" << std::endl;
|
---|
538 | }
|
---|
539 |
|
---|
540 | out << " }" << std::endl;
|
---|
541 | out << "}" << std::endl;
|
---|
542 | out << "#endif /* SPV_ENABLE_UTILITY_CODE */" << std::endl << std::endl;
|
---|
543 | }
|
---|
544 | }
|
---|
545 | };
|
---|
546 |
|
---|
547 | // C printer
|
---|
548 | class TPrinterC final : public TPrinterCBase {
|
---|
549 | private:
|
---|
550 | std::string commentBeg() const override { return "/*\n"; }
|
---|
551 | std::string commentEnd(bool isLast) const override { return "*/\n"; }
|
---|
552 | std::string commentBOL() const override { return "** "; }
|
---|
553 |
|
---|
554 | std::string enumBeg(const std::string& s, enumStyle_t style) const override {
|
---|
555 | return std::string("typedef enum ") + pre() + s + styleStr(style) + "_ {\n";
|
---|
556 | }
|
---|
557 |
|
---|
558 | std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
|
---|
559 | return "} " + pre() + s + styleStr(style) + ";\n\n";
|
---|
560 | }
|
---|
561 |
|
---|
562 | std::string enumFmt(const std::string& s, const valpair_t& v,
|
---|
563 | enumStyle_t style, bool isLast) const override {
|
---|
564 | return indent() + pre() + s + v.second + styleStr(style) + " = " + fmtStyleVal(v.first, style) + ",\n";
|
---|
565 | }
|
---|
566 |
|
---|
567 | std::string maxEnumFmt(const std::string& s, const valpair_t& v,
|
---|
568 | enumStyle_t style) const override {
|
---|
569 | return enumFmt(s, v, style, true);
|
---|
570 | }
|
---|
571 |
|
---|
572 | std::string pre() const override { return "Spv"; } // C name prefix
|
---|
573 | std::string headerGuardSuffix() const override { return "H"; }
|
---|
574 | };
|
---|
575 |
|
---|
576 | // C++ printer
|
---|
577 | class TPrinterCPP : public TPrinterCBase {
|
---|
578 | protected:
|
---|
579 | void printMaskOperators(std::ostream& out, const std::string& specifiers) const {
|
---|
580 | const Json::Value& enums = spvRoot["spv"]["enum"];
|
---|
581 |
|
---|
582 | out << "// Overload bitwise operators for mask bit combining\n\n";
|
---|
583 |
|
---|
584 | for (auto opClass = enums.begin(); opClass != enums.end(); ++opClass) {
|
---|
585 | const bool isMask = (*opClass)["Type"].asString() == "Bit";
|
---|
586 | const auto opName = (*opClass)["Name"].asString();
|
---|
587 |
|
---|
588 | if (isMask) {
|
---|
589 | const auto typeName = opName + styleStr(enumMask);
|
---|
590 |
|
---|
591 | // Overload operator|
|
---|
592 | out << specifiers << " " << typeName << " operator|(" << typeName << " a, " << typeName << " b) { return " <<
|
---|
593 | typeName << "(unsigned(a) | unsigned(b)); }\n";
|
---|
594 | // Overload operator&
|
---|
595 | out << specifiers << " " << typeName << " operator&(" << typeName << " a, " << typeName << " b) { return " <<
|
---|
596 | typeName << "(unsigned(a) & unsigned(b)); }\n";
|
---|
597 | // Overload operator^
|
---|
598 | out << specifiers << " " << typeName << " operator^(" << typeName << " a, " << typeName << " b) { return " <<
|
---|
599 | typeName << "(unsigned(a) ^ unsigned(b)); }\n";
|
---|
600 | // Overload operator~
|
---|
601 | out << specifiers << " " << typeName << " operator~(" << typeName << " a) { return " <<
|
---|
602 | typeName << "(~unsigned(a)); }\n";
|
---|
603 | }
|
---|
604 | }
|
---|
605 | }
|
---|
606 | private:
|
---|
607 | void printPrologue(std::ostream& out) const override {
|
---|
608 | TPrinterCBase::printPrologue(out);
|
---|
609 | out << "namespace spv {\n\n";
|
---|
610 | }
|
---|
611 |
|
---|
612 | void printEpilogue(std::ostream& out) const override {
|
---|
613 | printMaskOperators(out, "inline");
|
---|
614 | out << "\n} // end namespace spv\n\n";
|
---|
615 | out << "#endif // #ifndef spirv_" << headerGuardSuffix() << std::endl;
|
---|
616 | }
|
---|
617 |
|
---|
618 | std::string commentBOL() const override { return "// "; }
|
---|
619 |
|
---|
620 |
|
---|
621 | virtual std::string enumBeg(const std::string& s, enumStyle_t style) const override {
|
---|
622 | return std::string("enum ") + s + styleStr(style) + " {\n";
|
---|
623 | }
|
---|
624 |
|
---|
625 | std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
|
---|
626 | return "};\n\n";
|
---|
627 | }
|
---|
628 |
|
---|
629 | virtual std::string enumFmt(const std::string& s, const valpair_t& v,
|
---|
630 | enumStyle_t style, bool isLast) const override {
|
---|
631 | return indent() + s + v.second + styleStr(style) + " = " + fmtStyleVal(v.first, style) + ",\n";
|
---|
632 | }
|
---|
633 |
|
---|
634 | virtual std::string maxEnumFmt(const std::string& s, const valpair_t& v,
|
---|
635 | enumStyle_t style) const override {
|
---|
636 | return enumFmt(s, v, style, true);
|
---|
637 | }
|
---|
638 |
|
---|
639 | // The C++ and C++11 headers define types with the same name. So they
|
---|
640 | // should use the same header guard.
|
---|
641 | std::string headerGuardSuffix() const override { return "HPP"; }
|
---|
642 |
|
---|
643 | std::string operators;
|
---|
644 | };
|
---|
645 |
|
---|
646 | // C++11 printer (uses enum classes)
|
---|
647 | class TPrinterCPP11 final : public TPrinterCPP {
|
---|
648 | private:
|
---|
649 | void printEpilogue(std::ostream& out) const override {
|
---|
650 | printMaskOperators(out, "constexpr");
|
---|
651 | out << "\n} // end namespace spv\n\n";
|
---|
652 | out << "#endif // #ifndef spirv_" << headerGuardSuffix() << std::endl;
|
---|
653 | }
|
---|
654 | std::string enumBeg(const std::string& s, enumStyle_t style) const override {
|
---|
655 | return std::string("enum class ") + s + styleStr(style) + " : unsigned {\n";
|
---|
656 | }
|
---|
657 |
|
---|
658 | std::string enumFmt(const std::string& s, const valpair_t& v,
|
---|
659 | enumStyle_t style, bool isLast) const override {
|
---|
660 | return indent() + prependIfDigit(s, v.second) + " = " + fmtStyleVal(v.first, style) + ",\n";
|
---|
661 | }
|
---|
662 |
|
---|
663 | std::string maxEnumFmt(const std::string& s, const valpair_t& v,
|
---|
664 | enumStyle_t style) const override {
|
---|
665 | return enumFmt(s, v, style, true);
|
---|
666 | }
|
---|
667 |
|
---|
668 | // Add type prefix for scoped enum
|
---|
669 | virtual std::string fmtEnumUse(const std::string& opPrefix, const std::string& name) const override { return opPrefix + "::" + name; }
|
---|
670 |
|
---|
671 | std::string headerGuardSuffix() const override { return "HPP"; }
|
---|
672 | };
|
---|
673 |
|
---|
674 | // LUA printer
|
---|
675 | class TPrinterLua final : public TPrinter {
|
---|
676 | private:
|
---|
677 | void printPrologue(std::ostream& out) const override { out << "spv = {\n"; }
|
---|
678 |
|
---|
679 | void printEpilogue(std::ostream& out) const override { out << "}\n"; }
|
---|
680 |
|
---|
681 | std::string commentBOL() const override { return "-- "; }
|
---|
682 |
|
---|
683 | std::string enumBeg(const std::string& s, enumStyle_t style) const override {
|
---|
684 | return indent() + s + styleStr(style) + " = {\n";
|
---|
685 | }
|
---|
686 |
|
---|
687 | std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
|
---|
688 | return indent() + "},\n\n";
|
---|
689 | }
|
---|
690 |
|
---|
691 | std::string enumFmt(const std::string& s, const valpair_t& v,
|
---|
692 | enumStyle_t style, bool isLast) const override {
|
---|
693 | return indent(2) + prependIfDigit(s, v.second) + " = " + fmtStyleVal(v.first, style) + ",\n";
|
---|
694 | }
|
---|
695 |
|
---|
696 | virtual std::string fmtConstInt(unsigned val, const std::string& name,
|
---|
697 | const char* fmt, bool isLast) const override
|
---|
698 | {
|
---|
699 | return indent() + name + " = " + fmtNum(fmt, val) + (isLast ? ",\n\n" : ",\n");
|
---|
700 | }
|
---|
701 | };
|
---|
702 |
|
---|
703 | // Python printer
|
---|
704 | class TPrinterPython final : public TPrinter {
|
---|
705 | private:
|
---|
706 | void printPrologue(std::ostream& out) const override { out << "spv = {\n"; }
|
---|
707 |
|
---|
708 | void printEpilogue(std::ostream& out) const override { out << "}\n"; }
|
---|
709 |
|
---|
710 | std::string commentBOL() const override { return "# "; }
|
---|
711 |
|
---|
712 | std::string enumBeg(const std::string& s, enumStyle_t style) const override {
|
---|
713 | return indent() + "'" + s + styleStr(style) + "'" + " : {\n";
|
---|
714 | }
|
---|
715 |
|
---|
716 | std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
|
---|
717 | return indent() + "},\n\n";
|
---|
718 | }
|
---|
719 |
|
---|
720 | std::string enumFmt(const std::string& s, const valpair_t& v,
|
---|
721 | enumStyle_t style, bool isLast) const override {
|
---|
722 | return indent(2) + "'" + prependIfDigit(s, v.second) + "'" + " : " + fmtStyleVal(v.first, style) + ",\n";
|
---|
723 | }
|
---|
724 |
|
---|
725 | std::string fmtConstInt(unsigned val, const std::string& name,
|
---|
726 | const char* fmt, bool isLast) const override
|
---|
727 | {
|
---|
728 | return indent() + "'" + name + "'" + " : " + fmtNum(fmt, val) + (isLast ? ",\n\n" : ",\n");
|
---|
729 | }
|
---|
730 | };
|
---|
731 |
|
---|
732 | // C# printer
|
---|
733 | class TPrinterCSharp final : public TPrinter {
|
---|
734 | private:
|
---|
735 | std::string commentBOL() const override { return "// "; }
|
---|
736 |
|
---|
737 | void printPrologue(std::ostream& out) const override {
|
---|
738 | out << "namespace Spv\n{\n\n";
|
---|
739 | out << indent() << "public static class Specification\n";
|
---|
740 | out << indent() << "{\n";
|
---|
741 | }
|
---|
742 |
|
---|
743 | void printEpilogue(std::ostream& out) const override {
|
---|
744 | out << indent() << "}\n";
|
---|
745 | out << "}\n";
|
---|
746 | }
|
---|
747 |
|
---|
748 | std::string enumBeg(const std::string& s, enumStyle_t style) const override {
|
---|
749 | return indent(2) + "public enum " + s + styleStr(style) + "\n" + indent(2) + "{\n";
|
---|
750 | }
|
---|
751 |
|
---|
752 | std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
|
---|
753 | return indent(2) + "}" + + (isLast ? "\n" : "\n\n");
|
---|
754 | }
|
---|
755 |
|
---|
756 | std::string enumFmt(const std::string& s, const valpair_t& v,
|
---|
757 | enumStyle_t style, bool isLast) const override {
|
---|
758 | return indent(3) + prependIfDigit(s, v.second) + " = " + fmtStyleVal(v.first, style) + ",\n";
|
---|
759 | }
|
---|
760 |
|
---|
761 | std::string fmtConstInt(unsigned val, const std::string& name,
|
---|
762 | const char* fmt, bool isLast) const override {
|
---|
763 | return indent(2) + std::string("public const uint ") + name +
|
---|
764 | " = " + fmtNum(fmt, val) + (isLast ? ";\n\n" : ";\n");
|
---|
765 | }
|
---|
766 | };
|
---|
767 |
|
---|
768 | // D printer
|
---|
769 | class TPrinterD final : public TPrinter {
|
---|
770 | private:
|
---|
771 | std::string commentBeg() const override { return "/+\n"; }
|
---|
772 | std::string commentBOL() const override { return " + "; }
|
---|
773 | std::string commentEnd(bool isLast) const override { return " +/\n"; }
|
---|
774 |
|
---|
775 | void printPrologue(std::ostream& out) const override {
|
---|
776 | out << "module spv;\n\n";
|
---|
777 | }
|
---|
778 |
|
---|
779 | void printEpilogue(std::ostream& out) const override {
|
---|
780 | }
|
---|
781 |
|
---|
782 | std::string enumBeg(const std::string& s, enumStyle_t style) const override {
|
---|
783 | return "enum " + s + styleStr(style) + " : uint\n{\n";
|
---|
784 | }
|
---|
785 |
|
---|
786 | std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
|
---|
787 | return std::string("}\n\n");
|
---|
788 | }
|
---|
789 |
|
---|
790 | std::string enumFmt(const std::string& s, const valpair_t& v,
|
---|
791 | enumStyle_t style, bool isLast) const override {
|
---|
792 | return indent() + prependIfDigit("_", v.second) + " = " + fmtStyleVal(v.first, style) + ",\n";
|
---|
793 | }
|
---|
794 |
|
---|
795 | std::string fmtConstInt(unsigned val, const std::string& name,
|
---|
796 | const char* fmt, bool isLast) const override {
|
---|
797 | return std::string("enum uint ") + name +
|
---|
798 | " = " + fmtNum(fmt, val) + (isLast ? ";\n\n" : ";\n");
|
---|
799 | }
|
---|
800 | };
|
---|
801 |
|
---|
802 | // Beef printer
|
---|
803 | class TPrinterBeef final : public TPrinter {
|
---|
804 | private:
|
---|
805 | std::string commentBOL() const override { return "// "; }
|
---|
806 |
|
---|
807 | void printPrologue(std::ostream& out) const override {
|
---|
808 | out << "namespace Spv\n{\n";
|
---|
809 | out << indent() << "using System;\n\n";
|
---|
810 | out << indent() << "public static class Specification\n";
|
---|
811 | out << indent() << "{\n";
|
---|
812 | }
|
---|
813 |
|
---|
814 | void printEpilogue(std::ostream& out) const override {
|
---|
815 | out << indent() << "}\n";
|
---|
816 | out << "}\n";
|
---|
817 | }
|
---|
818 |
|
---|
819 | std::string enumBeg(const std::string& s, enumStyle_t style) const override {
|
---|
820 | return indent(2) + "[AllowDuplicates, CRepr] public enum " + s + styleStr(style) + "\n" + indent(2) + "{\n";
|
---|
821 | }
|
---|
822 |
|
---|
823 | std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
|
---|
824 | return indent(2) + "}" + +(isLast ? "\n" : "\n\n");
|
---|
825 | }
|
---|
826 |
|
---|
827 | std::string enumFmt(const std::string& s, const valpair_t& v,
|
---|
828 | enumStyle_t style, bool isLast) const override {
|
---|
829 | return indent(3) + prependIfDigit(s, v.second) + " = " + fmtStyleVal(v.first, style) + ",\n";
|
---|
830 | }
|
---|
831 |
|
---|
832 | std::string fmtConstInt(unsigned val, const std::string& name,
|
---|
833 | const char* fmt, bool isLast) const override {
|
---|
834 | return indent(2) + std::string("public const uint32 ") + name +
|
---|
835 | " = " + fmtNum(fmt, val) + (isLast ? ";\n\n" : ";\n");
|
---|
836 | }
|
---|
837 | };
|
---|
838 |
|
---|
839 | } // namespace
|
---|
840 |
|
---|
841 | namespace spv {
|
---|
842 | void PrintAllHeaders()
|
---|
843 | {
|
---|
844 | // TODO: Once MSVC 2012 is no longer a factor, use brace initializers here
|
---|
845 | std::vector<std::pair<TLanguage, std::string>> langInfo;
|
---|
846 |
|
---|
847 | langInfo.push_back(std::make_pair(ELangC, "spirv.h"));
|
---|
848 | langInfo.push_back(std::make_pair(ELangCPP, "spirv.hpp"));
|
---|
849 | langInfo.push_back(std::make_pair(ELangCPP11, "spirv.hpp11"));
|
---|
850 | langInfo.push_back(std::make_pair(ELangJSON, "spirv.json"));
|
---|
851 | langInfo.push_back(std::make_pair(ELangLua, "spirv.lua"));
|
---|
852 | langInfo.push_back(std::make_pair(ELangPython, "spirv.py"));
|
---|
853 | langInfo.push_back(std::make_pair(ELangCSharp, "spirv.cs"));
|
---|
854 | langInfo.push_back(std::make_pair(ELangD, "spv.d"));
|
---|
855 | langInfo.push_back(std::make_pair(ELangBeef, "spirv.bf"));
|
---|
856 |
|
---|
857 | for (const auto& lang : langInfo) {
|
---|
858 | std::ofstream out(lang.second, std::ios::out);
|
---|
859 |
|
---|
860 | if ((out.rdstate() & std::ifstream::failbit)) {
|
---|
861 | std::cerr << "Unable to open file: " << lang.second << std::endl;
|
---|
862 | } else {
|
---|
863 | PrintHeader(lang.first, out);
|
---|
864 | }
|
---|
865 | }
|
---|
866 | }
|
---|
867 |
|
---|
868 | // Print header for given language to given output stream
|
---|
869 | void PrintHeader(TLanguage lang, std::ostream& out)
|
---|
870 | {
|
---|
871 | typedef std::unique_ptr<TPrinter> TPrinterPtr;
|
---|
872 | TPrinterPtr p;
|
---|
873 |
|
---|
874 | switch (lang) {
|
---|
875 | case ELangC: p = TPrinterPtr(new TPrinterC); break;
|
---|
876 | case ELangCPP: p = TPrinterPtr(new TPrinterCPP); break;
|
---|
877 | case ELangCPP11: p = TPrinterPtr(new TPrinterCPP11); break;
|
---|
878 | case ELangJSON: p = TPrinterPtr(new TPrinterJSON); break;
|
---|
879 | case ELangLua: p = TPrinterPtr(new TPrinterLua); break;
|
---|
880 | case ELangPython: p = TPrinterPtr(new TPrinterPython); break;
|
---|
881 | case ELangCSharp: p = TPrinterPtr(new TPrinterCSharp); break;
|
---|
882 | case ELangD: p = TPrinterPtr(new TPrinterD); break;
|
---|
883 | case ELangBeef: p = TPrinterPtr(new TPrinterBeef); break;
|
---|
884 | case ELangAll: PrintAllHeaders(); break;
|
---|
885 | default:
|
---|
886 | std::cerr << "Unknown language." << std::endl;
|
---|
887 | return;
|
---|
888 | }
|
---|
889 |
|
---|
890 | // Print the data in the requested format
|
---|
891 | if (p)
|
---|
892 | out << *p << std::endl;
|
---|
893 |
|
---|
894 | // object is auto-deleted
|
---|
895 | }
|
---|
896 |
|
---|
897 | } // namespace spv
|
---|