diff --git a/mcpp.toml b/mcpp.toml index 21a469d..84a1a4c 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -93,6 +93,7 @@ members = [ "tests/examples/libdrm", "tests/examples/libffi", "tests/examples/expat", + "tests/examples/spirv-headers", "tests/examples/wayland", "tests/examples/wayland-egl", "tests/examples/wayland-cursor", diff --git a/pkgs/c/compat.spirv-headers.lua b/pkgs/c/compat.spirv-headers.lua new file mode 100644 index 0000000..4b00311 --- /dev/null +++ b/pkgs/c/compat.spirv-headers.lua @@ -0,0 +1,82 @@ +-- compat.spirv-headers — Khronos SPIR-V headers (the `spirv/unified1` tree). +-- +-- Header-only, the same shape as `compat.vulkan-headers`: expose `include/` and +-- carry a trivial anchor TU so the package still produces a buildable lib +-- target. +-- +-- WHO NEEDS IT. A program that only submits SPIR-V to a driver does not: the +-- module is opaque data and `vulkan_core.h` describes the API that takes it. +-- A program that READS a SPIR-V module needs the opcode and enum definitions, +-- and llama.cpp's Vulkan backend is one -- it inspects the modules its own +-- shader generator produced. Splitting this out of `compat.vulkan-headers` +-- follows Khronos: they are separate repositories, and a renderer that never +-- decodes SPIR-V should not acquire the decoder's headers. +-- +-- THE INCLUDE PATH IS PART OF THE CONTRACT. Consumers spell it +-- ``, which is the Khronos layout and what +-- `include/` yields directly. Distributions that flatten it to +-- `` exist and consumers probe for both with +-- `__has_include`; this package provides the first, so the probe's first arm +-- wins and no consumer needs a second spelling. +-- +-- Versioning follows the Vulkan SDK release the tag belongs to +-- (`vulkan-sdk-1.4.357.0` → `1.4.357.0`), which is how Khronos ties the +-- SPIR-V, Vulkan header and loader repositories together. +-- +-- LICENCE IS A SET. The bulk is MIT; upstream's own `LICENSE` calls out files +-- under CC-BY-4.0 and ships `LICENSES/` carrying both texts. Naming only the +-- headline would be a statement about the package that its own tree +-- contradicts. +package = { + spec = "1", + namespace = "compat", + name = "spirv-headers", + description = "Khronos SPIR-V headers (spirv/unified1)", + licenses = {"MIT", "CC-BY-4.0"}, + repo = "https://github.com/KhronosGroup/SPIRV-Headers", + type = "package", + + xpm = { + linux = { + ["1.4.357.0"] = { + url = { + GLOBAL = "https://github.com/KhronosGroup/SPIRV-Headers/archive/refs/tags/vulkan-sdk-1.4.357.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/spirv-headers/releases/download/1.4.357.0/spirv-headers-1.4.357.0.tar.gz", + }, + sha256 = "4d703067a7e06331ccb37bdfed3f9b7879cc61969a2689ae95c95db34a47ff07", + }, + }, + macosx = { + ["1.4.357.0"] = { + url = { + GLOBAL = "https://github.com/KhronosGroup/SPIRV-Headers/archive/refs/tags/vulkan-sdk-1.4.357.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/spirv-headers/releases/download/1.4.357.0/spirv-headers-1.4.357.0.tar.gz", + }, + sha256 = "4d703067a7e06331ccb37bdfed3f9b7879cc61969a2689ae95c95db34a47ff07", + }, + }, + windows = { + ["1.4.357.0"] = { + url = { + GLOBAL = "https://github.com/KhronosGroup/SPIRV-Headers/archive/refs/tags/vulkan-sdk-1.4.357.0.tar.gz", + CN = "https://gitcode.com/mcpp-res/spirv-headers/releases/download/1.4.357.0/spirv-headers-1.4.357.0.tar.gz", + }, + sha256 = "4d703067a7e06331ccb37bdfed3f9b7879cc61969a2689ae95c95db34a47ff07", + }, + }, + }, + + mcpp = { + language = "c++23", + import_std = false, + c_standard = "c11", + include_dirs = { "*/include" }, + generated_files = { + ["mcpp_generated/spirv_headers_anchor.c"] = + "int mcpp_compat_spirv_headers_anchor(void) { return 0; }\n", + }, + sources = { "mcpp_generated/spirv_headers_anchor.c" }, + targets = { ["spirv-headers"] = { kind = "lib" } }, + deps = {}, + }, +} diff --git a/tests/examples/spirv-headers/mcpp.toml b/tests/examples/spirv-headers/mcpp.toml new file mode 100644 index 0000000..2f1b569 --- /dev/null +++ b/tests/examples/spirv-headers/mcpp.toml @@ -0,0 +1,6 @@ +[package] +name = "spirv-headers-tests" +version = "0.1.0" + +[dependencies.compat] +spirv-headers = "1.4.357.0" diff --git a/tests/examples/spirv-headers/tests/decode.cpp b/tests/examples/spirv-headers/tests/decode.cpp new file mode 100644 index 0000000..919e3f4 --- /dev/null +++ b/tests/examples/spirv-headers/tests/decode.cpp @@ -0,0 +1,55 @@ +// Behavioral test -- verify compat.spirv-headers delivers the Khronos include +// layout and definitions a SPIR-V READER needs. +// +// The criterion is deliberately not "the header compiles". A header-only +// package that resolves and exposes the wrong include root compiles nothing +// and fails at the consumer, so this test spells the include the way a +// consumer spells it and then decodes a module built by hand, which is the +// only thing that can tell a real header tree from an empty one. +#include + +import std; + +int main() { + // A minimal, well-formed SPIR-V module header: magic, version, generator, + // bound, schema. Written as words rather than read from a file so the test + // depends on nothing but the package under test. + const std::uint32_t module_words[] = { + spv::MagicNumber, + spv::Version, + 0u, // generator + 1u, // bound + 0u, // schema + }; + + if (module_words[0] != 0x07230203u) { + std::println("spv::MagicNumber is {:#x}, expected 0x07230203", module_words[0]); + return 1; + } + + // The enums a decoder switches on. Their numeric values are part of the + // SPIR-V specification, so a header that renamed or renumbered them would + // be a different specification rather than a newer package. + if (static_cast(spv::OpCapability) != 17u) { + std::println("spv::OpCapability is {}, expected 17", + static_cast(spv::OpCapability)); + return 2; + } + if (static_cast(spv::ExecutionModelGLCompute) != 5u) { + std::println("spv::ExecutionModelGLCompute is {}, expected 5", + static_cast(spv::ExecutionModelGLCompute)); + return 3; + } + + // The version word packs major and minor into the middle two bytes. A + // consumer that gates on a SPIR-V version reads it exactly this way. + const std::uint32_t major = (module_words[1] >> 16) & 0xffu; + const std::uint32_t minor = (module_words[1] >> 8) & 0xffu; + if (major != 1u) { + std::println("spv::Version reports major {}, expected 1", major); + return 4; + } + + std::println("compat.spirv-headers: SPIR-V {}.{} definitions present", major, minor); + return 0; +}