// Copyright (c) 2014-2020 The Khronos Group Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and/or associated documentation files (the "Materials"), // to deal in the Materials without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Materials, and to permit persons to whom the // Materials are furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Materials. // // MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS // STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND // HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ // // THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS // IN THE MATERIALS. // // Print headers for SPIR-V in several languages. // // To change the header information, change the C++-built database in doc.*. // // Then, use "spriv -h " - e.g, spriv.{h,hpp,lua,py,etc}: // replace the auto-generated header, or "spirv -H" to generate all // supported language headers to predefined names in the current directory. // #include #include #include #include #include #include #include #include #include #include #include #include "jsoncpp/dist/json/json.h" #include "header.h" #include "jsonToSpirv.h" // snprintf and _snprintf are not quite the same, but close enough // for our use. #ifdef _MSC_VER #pragma warning(disable:4996) #define snprintf _snprintf #endif // This file converts SPIR-V definitions to an internal JSON // representation, and then generates language specific // data from that single internal form. // Initially, the internal form is created from C++ data, // though this can be changed to a JSON master in time. namespace { class TPrinter { protected: TPrinter(); static const int DocMagicNumber = 0x07230203; static const int DocVersion = 0x00010600; static const int DocRevision = 1; #define DocRevisionString "1" static const std::string DocCopyright; static const std::string DocComment1; static const std::string DocComment2; enum enumStyle_t { enumNoMask, enumCount, enumShift, enumMask, enumHex, }; static std::string styleStr(enumStyle_t s) { return s == enumShift ? "Shift" : s == enumMask ? "Mask" : ""; } friend std::ostream& operator<<(std::ostream&, const TPrinter&); virtual void printAll(std::ostream&) const; virtual void printComments(std::ostream&) const; virtual void printPrologue(std::ostream&) const { } virtual void printDefs(std::ostream&) const; virtual void printEpilogue(std::ostream&) const { } virtual void printMeta(std::ostream&) const; virtual void printTypes(std::ostream&) const { } virtual void printHasResultType(std::ostream&) const { }; virtual std::string escapeComment(const std::string& s) const; // Default printComments() uses these comment strings virtual std::string commentBeg() const { return ""; } virtual std::string commentEnd(bool isLast) const { return ""; } virtual std::string commentBOL() const { return ""; } virtual std::string commentEOL(bool isLast) const { return ""; } typedef std::pair valpair_t; // for printing enum values virtual std::string enumBeg(const std::string&, enumStyle_t) const { return ""; } virtual std::string enumEnd(const std::string&, enumStyle_t, bool isLast = false) const { return ""; } virtual std::string enumFmt(const std::string&, const valpair_t&, enumStyle_t, bool isLast = false) const { return ""; } virtual std::string maxEnumFmt(const std::string&, const valpair_t&, enumStyle_t) const { return ""; } virtual std::string fmtConstInt(unsigned val, const std::string& name, const char* fmt, bool isLast = false) const { return ""; } std::vector getSortedVals(const Json::Value&) const; virtual std::string indent(int count = 1) const { return std::string(count * 4, ' '); // default indent level = 4 } static std::string fmtNum(const char* fmt, unsigned val) { char buff[16]; // ample for 8 hex digits + 0x snprintf(buff, sizeof(buff), fmt, val); buff[sizeof(buff)-1] = '\0'; // MSVC doesn't promise null termination return buff; } static std::string fmtStyleVal(unsigned v, enumStyle_t style); // If the enum value name would start with a sigit, prepend the enum name. // E.g, "3D" -> "Dim3D". static std::string prependIfDigit(const std::string& ename, const std::string& vname) { return (std::isdigit(vname[0]) ? ename : std::string("")) + vname; } void addComment(Json::Value& node, const std::string& str); Json::Value spvRoot; // JSON SPIR-V data }; // Format value as mask or value std::string TPrinter::fmtStyleVal(unsigned v, enumStyle_t style) { switch (style) { case enumMask: return fmtNum("0x%08x", 1< TPrinter::getSortedVals(const Json::Value& p) const { std::vector values; for (auto e = p.begin(); e != p.end(); ++e) values.push_back(valpair_t(e->asUInt(), e.name())); // Use a stable sort because we might have aliases, e.g. // SubgropuBallot (might be in future core) vs. SubgroupBallotKHR. std::stable_sort(values.begin(), values.end()); return values; } // Escape comment characters if needed std::string TPrinter::escapeComment(const std::string& s) const { return s; } // Format comments in language specific way void TPrinter::printComments(std::ostream& out) const { const int commentCount = spvRoot["spv"]["meta"]["Comment"].size(); int commentNum = 0; for (const auto& comment : spvRoot["spv"]["meta"]["Comment"]) { out << commentBeg(); for (int line = 0; line < int(comment.size()); ++line) out << commentBOL() << escapeComment(comment[line].asString()) << commentEOL((line+1) == comment.size()) << std::endl; out << commentEnd(++commentNum == commentCount) << std::endl; } } // Format header metadata void TPrinter::printMeta(std::ostream& out) const { const Json::Value& meta = spvRoot["spv"]["meta"]; const auto print = [&](const char* name, const char* fmt, bool isLast) { out << fmtConstInt(meta[name].asUInt(), name, fmt, isLast); }; print("MagicNumber", "0x%08lx", false); print("Version", "0x%08lx", false); print("Revision", "%d", false); print("OpCodeMask", "0x%04x", false); print("WordCountShift", "%d", true); } // Format value definitions in language specific way void TPrinter::printDefs(std::ostream& out) const { const Json::Value& enums = spvRoot["spv"]["enum"]; for (auto opClass = enums.begin(); opClass != enums.end(); ++opClass) { const bool isMask = (*opClass)["Type"].asString() == "Bit"; const auto opName = (*opClass)["Name"].asString(); const auto opPrefix = opName == "Op" ? "" : opName; for (enumStyle_t style = (isMask ? enumShift : enumCount); style <= (isMask ? enumMask : enumCount); style = enumStyle_t(int(style)+1)) { out << enumBeg(opName, style); if (style == enumMask) out << enumFmt(opPrefix, valpair_t(0, "MaskNone"), enumNoMask); const auto sorted = getSortedVals((*opClass)["Values"]); std::string maxEnum = maxEnumFmt(opName, valpair_t(0x7FFFFFFF, "Max"), enumHex); bool printMax = (style != enumMask && maxEnum.size() > 0); for (const auto& v : sorted) out << enumFmt(opPrefix, v, style, !printMax && v.second == sorted.back().second); if (printMax) out << maxEnum; auto nextOpClass = opClass; out << enumEnd(opName, style, ++nextOpClass == enums.end()); } } } void TPrinter::printAll(std::ostream& out) const { printComments(out); printPrologue(out); printTypes(out); printMeta(out); printDefs(out); printHasResultType(out); printEpilogue(out); } // Stream entire header to output std::ostream& operator<<(std::ostream& out, const TPrinter &p) { p.printAll(out); return out; } // JSON printer. Rather than use the default printer, we supply our own so // we can control the printing order within various containers. class TPrinterJSON final : public TPrinter { private: void printPrologue(std::ostream& out) const override { out << "{\n" + indent() + "\"spv\":\n" + indent() + "{\n"; } void printEpilogue(std::ostream& out) const override { out << indent() + "}\n}\n"; } std::string escapeComment(const std::string& s) const override { std::string newStr; for (auto c : s) { if (c == '"') { newStr += '\\'; newStr += c; } else { newStr += c; } } return newStr; } std::string fmtConstInt(unsigned val, const std::string& name, const char* fmt, bool isLast) const override { return indent(3) + '"' + name + "\": " + fmtNum("%d", val) + (isLast ? "\n" : ",\n"); } void printMeta(std::ostream& out) const override { out << indent(2) + "\"meta\":\n" + indent(2) + "{\n"; printComments(out); TPrinter::printMeta(out); out << indent(2) + "},\n"; } std::string commentBeg() const override { return indent(4) + "[\n"; } std::string commentEnd(bool isLast) const override { return indent(4) + (isLast ? "]" : "],"); } std::string commentBOL() const override { return indent(5) + '"'; } std::string commentEOL(bool isLast) const override { return (isLast ? "\"" : "\","); } void printComments(std::ostream& out) const override { out << indent(3) + "\"Comment\":\n" + indent(3) + "[\n"; TPrinter::printComments(out); out << indent(3) + "],\n"; } void printDefs(std::ostream& out) const override { out << indent(2) + "\"enum\":\n" + indent(2) + "[\n"; TPrinter::printDefs(out); out << indent(2) + "]\n"; } void printAll(std::ostream& out) const override { printPrologue(out); printMeta(out); printDefs(out); printEpilogue(out); } std::string enumBeg(const std::string& s, enumStyle_t style) const override { if (style == enumMask) return ""; return indent(3) + "{\n" + indent(4) + "\"Name\": \"" + s + "\",\n" + indent(4) + "\"Type\": " + (style == enumShift ? "\"Bit\"" : "\"Value\"") + ",\n" + indent(4) + "\"Values\":\n" + indent(4) + "{\n"; } std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override { if (style == enumMask) return ""; return indent(4) + "}\n" + indent(3) + "}" + (isLast ? "" : ",") + "\n"; } std::string enumFmt(const std::string& s, const valpair_t& v, enumStyle_t style, bool isLast) const override { if (style == enumMask || style == enumNoMask) return ""; return indent(5) + '"' + prependIfDigit(s, v.second) + "\": " + fmtNum("%d", v.first) + (isLast ? "\n" : ",\n"); } }; // base for C and C++ class TPrinterCBase : public TPrinter { protected: virtual void printPrologue(std::ostream& out) const override { out << "#ifndef spirv_" << headerGuardSuffix() << std::endl << "#define spirv_" << headerGuardSuffix() << std::endl << std::endl; } void printMeta(std::ostream& out) const override { out << "#define SPV_VERSION 0x" << std::hex << DocVersion << std::dec << "\n"; out << "#define SPV_REVISION " << DocRevision << "\n"; out << "\n"; return TPrinter::printMeta(out); } virtual void printEpilogue(std::ostream& out) const override { out << "#endif" << std::endl; } virtual void printTypes(std::ostream& out) const override { out << "typedef unsigned int " << pre() << "Id;\n\n"; } virtual std::string fmtConstInt(unsigned val, const std::string& name, const char* fmt, bool isLast) const override { return std::string("static const unsigned int ") + pre() + name + " = " + fmtNum(fmt, val) + (isLast ? ";\n\n" : ";\n"); } virtual std::string pre() const { return ""; } // C name prefix virtual std::string headerGuardSuffix() const = 0; virtual std::string fmtEnumUse(const std::string& opPrefix, const std::string& name) const { return pre() + name; } virtual void printHasResultType(std::ostream& out) const override { const Json::Value& enums = spvRoot["spv"]["enum"]; std::set seenValues; for (auto opClass = enums.begin(); opClass != enums.end(); ++opClass) { const auto opName = (*opClass)["Name"].asString(); if (opName != "Op") { continue; } out << "#ifdef SPV_ENABLE_UTILITY_CODE" << std::endl; out << "#ifndef __cplusplus" << std::endl; out << "#include " << std::endl; out << "#endif" << std::endl; out << "inline void " << pre() << "HasResultAndType(" << pre() << opName << " opcode, bool *hasResult, bool *hasResultType) {" << std::endl; out << " *hasResult = *hasResultType = false;" << std::endl; out << " switch (opcode) {" << std::endl; out << " default: /* unknown opcode */ break;" << std::endl; for (auto& inst : spv::InstructionDesc) { // Filter out duplicate enum values, which would break the switch statement. // These are probably just extension enums promoted to core. if (seenValues.find(inst.value) != seenValues.end()) { continue; } seenValues.insert(inst.value); std::string name = inst.name; out << " case " << fmtEnumUse("Op", name) << ": *hasResult = " << (inst.hasResult() ? "true" : "false") << "; *hasResultType = " << (inst.hasType() ? "true" : "false") << "; break;" << std::endl; } out << " }" << std::endl; out << "}" << std::endl; out << "#endif /* SPV_ENABLE_UTILITY_CODE */" << std::endl << std::endl; } } }; // C printer class TPrinterC final : public TPrinterCBase { private: std::string commentBeg() const override { return "/*\n"; } std::string commentEnd(bool isLast) const override { return "*/\n"; } std::string commentBOL() const override { return "** "; } std::string enumBeg(const std::string& s, enumStyle_t style) const override { return std::string("typedef enum ") + pre() + s + styleStr(style) + "_ {\n"; } std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override { return "} " + pre() + s + styleStr(style) + ";\n\n"; } std::string enumFmt(const std::string& s, const valpair_t& v, enumStyle_t style, bool isLast) const override { return indent() + pre() + s + v.second + styleStr(style) + " = " + fmtStyleVal(v.first, style) + ",\n"; } std::string maxEnumFmt(const std::string& s, const valpair_t& v, enumStyle_t style) const override { return enumFmt(s, v, style, true); } std::string pre() const override { return "Spv"; } // C name prefix std::string headerGuardSuffix() const override { return "H"; } }; // C++ printer class TPrinterCPP : public TPrinterCBase { private: void printPrologue(std::ostream& out) const override { TPrinterCBase::printPrologue(out); out << "namespace spv {\n\n"; } void printEpilogue(std::ostream& out) const override { const Json::Value& enums = spvRoot["spv"]["enum"]; out << "// Overload bitwise operators for mask bit combining\n\n"; for (auto opClass = enums.begin(); opClass != enums.end(); ++opClass) { const bool isMask = (*opClass)["Type"].asString() == "Bit"; const auto opName = (*opClass)["Name"].asString(); if (isMask) { const auto typeName = opName + styleStr(enumMask); // Overload operator| out << "inline " << typeName << " operator|(" << typeName << " a, " << typeName << " b) { return " << typeName << "(unsigned(a) | unsigned(b)); }\n"; // Overload operator& out << "inline " << typeName << " operator&(" << typeName << " a, " << typeName << " b) { return " << typeName << "(unsigned(a) & unsigned(b)); }\n"; // Overload operator^ out << "inline " << typeName << " operator^(" << typeName << " a, " << typeName << " b) { return " << typeName << "(unsigned(a) ^ unsigned(b)); }\n"; // Overload operator~ out << "inline " << typeName << " operator~(" << typeName << " a) { return " << typeName << "(~unsigned(a)); }\n"; } } out << "\n} // end namespace spv\n\n"; out << "#endif // #ifndef spirv_" << headerGuardSuffix() << std::endl; } std::string commentBOL() const override { return "// "; } virtual std::string enumBeg(const std::string& s, enumStyle_t style) const override { return std::string("enum ") + s + styleStr(style) + " {\n"; } std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override { return "};\n\n"; } virtual std::string enumFmt(const std::string& s, const valpair_t& v, enumStyle_t style, bool isLast) const override { return indent() + s + v.second + styleStr(style) + " = " + fmtStyleVal(v.first, style) + ",\n"; } virtual std::string maxEnumFmt(const std::string& s, const valpair_t& v, enumStyle_t style) const override { return enumFmt(s, v, style, true); } // The C++ and C++11 headers define types with the same name. So they // should use the same header guard. std::string headerGuardSuffix() const override { return "HPP"; } std::string operators; }; // C++11 printer (uses enum classes) class TPrinterCPP11 final : public TPrinterCPP { private: std::string enumBeg(const std::string& s, enumStyle_t style) const override { return std::string("enum class ") + s + styleStr(style) + " : unsigned {\n"; } std::string enumFmt(const std::string& s, const valpair_t& v, enumStyle_t style, bool isLast) const override { return indent() + prependIfDigit(s, v.second) + " = " + fmtStyleVal(v.first, style) + ",\n"; } std::string maxEnumFmt(const std::string& s, const valpair_t& v, enumStyle_t style) const override { return enumFmt(s, v, style, true); } // Add type prefix for scoped enum virtual std::string fmtEnumUse(const std::string& opPrefix, const std::string& name) const override { return opPrefix + "::" + name; } std::string headerGuardSuffix() const override { return "HPP"; } }; // LUA printer class TPrinterLua final : public TPrinter { private: void printPrologue(std::ostream& out) const override { out << "spv = {\n"; } void printEpilogue(std::ostream& out) const override { out << "}\n"; } std::string commentBOL() const override { return "-- "; } std::string enumBeg(const std::string& s, enumStyle_t style) const override { return indent() + s + styleStr(style) + " = {\n"; } std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override { return indent() + "},\n\n"; } std::string enumFmt(const std::string& s, const valpair_t& v, enumStyle_t style, bool isLast) const override { return indent(2) + prependIfDigit(s, v.second) + " = " + fmtStyleVal(v.first, style) + ",\n"; } virtual std::string fmtConstInt(unsigned val, const std::string& name, const char* fmt, bool isLast) const override { return indent() + name + " = " + fmtNum(fmt, val) + (isLast ? ",\n\n" : ",\n"); } }; // Python printer class TPrinterPython final : public TPrinter { private: void printPrologue(std::ostream& out) const override { out << "spv = {\n"; } void printEpilogue(std::ostream& out) const override { out << "}\n"; } std::string commentBOL() const override { return "# "; } std::string enumBeg(const std::string& s, enumStyle_t style) const override { return indent() + "'" + s + styleStr(style) + "'" + " : {\n"; } std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override { return indent() + "},\n\n"; } std::string enumFmt(const std::string& s, const valpair_t& v, enumStyle_t style, bool isLast) const override { return indent(2) + "'" + prependIfDigit(s, v.second) + "'" + " : " + fmtStyleVal(v.first, style) + ",\n"; } std::string fmtConstInt(unsigned val, const std::string& name, const char* fmt, bool isLast) const override { return indent() + "'" + name + "'" + " : " + fmtNum(fmt, val) + (isLast ? ",\n\n" : ",\n"); } }; // C# printer class TPrinterCSharp final : public TPrinter { private: std::string commentBOL() const override { return "// "; } void printPrologue(std::ostream& out) const override { out << "namespace Spv\n{\n\n"; out << indent() << "public static class Specification\n"; out << indent() << "{\n"; } void printEpilogue(std::ostream& out) const override { out << indent() << "}\n"; out << "}\n"; } std::string enumBeg(const std::string& s, enumStyle_t style) const override { return indent(2) + "public enum " + s + styleStr(style) + "\n" + indent(2) + "{\n"; } std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override { return indent(2) + "}" + + (isLast ? "\n" : "\n\n"); } std::string enumFmt(const std::string& s, const valpair_t& v, enumStyle_t style, bool isLast) const override { return indent(3) + prependIfDigit(s, v.second) + " = " + fmtStyleVal(v.first, style) + ",\n"; } std::string fmtConstInt(unsigned val, const std::string& name, const char* fmt, bool isLast) const override { return indent(2) + std::string("public const uint ") + name + " = " + fmtNum(fmt, val) + (isLast ? ";\n\n" : ";\n"); } }; // D printer class TPrinterD final : public TPrinter { private: std::string commentBeg() const override { return "/+\n"; } std::string commentBOL() const override { return " + "; } std::string commentEnd(bool isLast) const override { return " +/\n"; } void printPrologue(std::ostream& out) const override { out << "module spv;\n\n"; } void printEpilogue(std::ostream& out) const override { } std::string enumBeg(const std::string& s, enumStyle_t style) const override { return "enum " + s + styleStr(style) + " : uint\n{\n"; } std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override { return std::string("}\n\n"); } std::string enumFmt(const std::string& s, const valpair_t& v, enumStyle_t style, bool isLast) const override { return indent() + prependIfDigit("_", v.second) + " = " + fmtStyleVal(v.first, style) + ",\n"; } std::string fmtConstInt(unsigned val, const std::string& name, const char* fmt, bool isLast) const override { return std::string("enum uint ") + name + " = " + fmtNum(fmt, val) + (isLast ? ";\n\n" : ";\n"); } }; // Beef printer class TPrinterBeef final : public TPrinter { private: std::string commentBOL() const override { return "// "; } void printPrologue(std::ostream& out) const override { out << "namespace Spv\n{\n"; out << indent() << "using System;\n\n"; out << indent() << "public static class Specification\n"; out << indent() << "{\n"; } void printEpilogue(std::ostream& out) const override { out << indent() << "}\n"; out << "}\n"; } std::string enumBeg(const std::string& s, enumStyle_t style) const override { return indent(2) + "[AllowDuplicates, CRepr] public enum " + s + styleStr(style) + "\n" + indent(2) + "{\n"; } std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override { return indent(2) + "}" + +(isLast ? "\n" : "\n\n"); } std::string enumFmt(const std::string& s, const valpair_t& v, enumStyle_t style, bool isLast) const override { return indent(3) + prependIfDigit(s, v.second) + " = " + fmtStyleVal(v.first, style) + ",\n"; } std::string fmtConstInt(unsigned val, const std::string& name, const char* fmt, bool isLast) const override { return indent(2) + std::string("public const uint32 ") + name + " = " + fmtNum(fmt, val) + (isLast ? ";\n\n" : ";\n"); } }; } // namespace namespace spv { void PrintAllHeaders() { // TODO: Once MSVC 2012 is no longer a factor, use brace initializers here std::vector> langInfo; langInfo.push_back(std::make_pair(ELangC, "spirv.h")); langInfo.push_back(std::make_pair(ELangCPP, "spirv.hpp")); langInfo.push_back(std::make_pair(ELangCPP11, "spirv.hpp11")); langInfo.push_back(std::make_pair(ELangJSON, "spirv.json")); langInfo.push_back(std::make_pair(ELangLua, "spirv.lua")); langInfo.push_back(std::make_pair(ELangPython, "spirv.py")); langInfo.push_back(std::make_pair(ELangCSharp, "spirv.cs")); langInfo.push_back(std::make_pair(ELangD, "spv.d")); langInfo.push_back(std::make_pair(ELangBeef, "spirv.bf")); for (const auto& lang : langInfo) { std::ofstream out(lang.second, std::ios::out); if ((out.rdstate() & std::ifstream::failbit)) { std::cerr << "Unable to open file: " << lang.second << std::endl; } else { PrintHeader(lang.first, out); } } } // Print header for given language to given output stream void PrintHeader(TLanguage lang, std::ostream& out) { typedef std::unique_ptr TPrinterPtr; TPrinterPtr p; switch (lang) { case ELangC: p = TPrinterPtr(new TPrinterC); break; case ELangCPP: p = TPrinterPtr(new TPrinterCPP); break; case ELangCPP11: p = TPrinterPtr(new TPrinterCPP11); break; case ELangJSON: p = TPrinterPtr(new TPrinterJSON); break; case ELangLua: p = TPrinterPtr(new TPrinterLua); break; case ELangPython: p = TPrinterPtr(new TPrinterPython); break; case ELangCSharp: p = TPrinterPtr(new TPrinterCSharp); break; case ELangD: p = TPrinterPtr(new TPrinterD); break; case ELangBeef: p = TPrinterPtr(new TPrinterBeef); break; case ELangAll: PrintAllHeaders(); break; default: std::cerr << "Unknown language." << std::endl; return; } // Print the data in the requested format if (p) out << *p << std::endl; // object is auto-deleted } } // namespace spv