summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2023-05-22 17:22:22 -0400
committerGitHub <noreply@github.com>2023-05-22 17:22:22 -0400
commit972a931452c3f06a23a4f67ccfb655851df53fa4 (patch)
tree309a75c5239af163e47bc0e123d023683c1ec74a
parent33e15236c6fd8623bc516a194ca65e8810f1f855 (diff)
Source embedding for output (#2889)
* #include an absolute path didn't work - because paths were taken to always be relative. * Fix typo. * Add options for source embedding. * Small improvements. * Working with tests. * Add check for supported language types for embedding. * Try and remove assume warning. * Fix warning on MacOSX. * Some extra checking around Style::Text. * Some small improvements to docs/handling for headers extensions. * Fix md issue. * Small fixes around zeroing partial last element. * Another small fix.... * Small improvement in hex conversion. * Add an assert for unsignedness.
-rw-r--r--build/visual-studio/compiler-core/compiler-core.vcxproj2
-rw-r--r--build/visual-studio/compiler-core/compiler-core.vcxproj.filters6
-rw-r--r--docs/command-line-slangc-reference.md45
-rw-r--r--premake5.lua2
-rw-r--r--source/compiler-core/slang-artifact-desc-util.cpp2
-rw-r--r--source/compiler-core/slang-artifact-desc-util.h3
-rw-r--r--source/compiler-core/slang-dxc-compiler.cpp2
-rw-r--r--source/compiler-core/slang-fxc-compiler.cpp2
-rw-r--r--source/compiler-core/slang-glslang-compiler.cpp2
-rw-r--r--source/compiler-core/slang-nvrtc-compiler.cpp2
-rw-r--r--source/compiler-core/slang-source-embed-util.cpp399
-rw-r--r--source/compiler-core/slang-source-embed-util.h59
-rw-r--r--source/slang/slang-artifact-output-util.cpp4
-rw-r--r--source/slang/slang-compiler.cpp77
-rwxr-xr-xsource/slang/slang-compiler.h10
-rw-r--r--source/slang/slang-diagnostic-defs.h2
-rw-r--r--source/slang/slang-options.cpp46
-rw-r--r--tests/feature/source-embed/source-embed-1.slang44
-rw-r--r--tests/feature/source-embed/source-embed-2.slang45
-rw-r--r--tests/feature/source-embed/source-embed-3.slang45
-rw-r--r--tests/feature/source-embed/source-embed-4.slang45
21 files changed, 815 insertions, 29 deletions
diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj b/build/visual-studio/compiler-core/compiler-core.vcxproj
index 30650946b..7cfa0fa16 100644
--- a/build/visual-studio/compiler-core/compiler-core.vcxproj
+++ b/build/visual-studio/compiler-core/compiler-core.vcxproj
@@ -307,6 +307,7 @@
<ClInclude Include="..\..\..\source\compiler-core\slang-name.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-nvrtc-compiler.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-slice-allocator.h" />
+ <ClInclude Include="..\..\..\source\compiler-core\slang-source-embed-util.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-source-loc.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-source-map.h" />
<ClInclude Include="..\..\..\source\compiler-core\slang-test-server-protocol.h" />
@@ -352,6 +353,7 @@
<ClCompile Include="..\..\..\source\compiler-core\slang-name.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-nvrtc-compiler.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-slice-allocator.cpp" />
+ <ClCompile Include="..\..\..\source\compiler-core\slang-source-embed-util.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-source-loc.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-source-map.cpp" />
<ClCompile Include="..\..\..\source\compiler-core\slang-test-server-protocol.cpp" />
diff --git a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters
index a24b7511d..57fd7525f 100644
--- a/build/visual-studio/compiler-core/compiler-core.vcxproj.filters
+++ b/build/visual-studio/compiler-core/compiler-core.vcxproj.filters
@@ -135,6 +135,9 @@
<ClInclude Include="..\..\..\source\compiler-core\slang-slice-allocator.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\compiler-core\slang-source-embed-util.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\compiler-core\slang-source-loc.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -266,6 +269,9 @@
<ClCompile Include="..\..\..\source\compiler-core\slang-slice-allocator.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\compiler-core\slang-source-embed-util.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\compiler-core\slang-source-loc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/docs/command-line-slangc-reference.md b/docs/command-line-slangc-reference.md
index 777d586d1..16d5b52a2 100644
--- a/docs/command-line-slangc-reference.md
+++ b/docs/command-line-slangc-reference.md
@@ -29,6 +29,7 @@ slangc -help-style markdown -h
* [optimization-level](#optimization-level)
* [debug-level](#debug-level)
* [file-system-type](#file-system-type)
+* [source-embed-style](#source-embed-style)
* [target](#target)
* [stage](#stage)
* [vulkan-shift](#vulkan-shift)
@@ -237,6 +238,36 @@ Treat the rest of the command line as input files.
Reports the time spent in the downstream compiler.
+<a id="source-embed-style-1"></a>
+## -source-embed-style
+
+**-source-embed-style &lt;[source-embed-style](#source-embed-style)&gt;**
+
+If source embedding is enabled, defines the style used. When enabled (with any style other than `none`), will write compile results into embeddable source for the target language. If no output file is specified, the output is written to stdout. If an output file is specified it is written either to that file directly (if it is appropriate for the target language), or it will be output to the filename with an appropriate extension.
+
+
+
+Note for C/C++ with u16/u32/u64 types it is necessary to have "#include &lt;stdint.h&gt;" before the generated file.
+
+
+
+
+<a id="source-embed-name"></a>
+## -source-embed-name
+
+**-source-embed-name &lt;name&gt;**
+
+The name used as the basis for variables output for source embedding.
+
+
+<a id="source-embed-language"></a>
+## -source-embed-language
+
+**-source-embed-language &lt;[language](#language)&gt;**
+
+The language to be used for source embedding. Defaults to C/C++. Currently only C/C++ are supported
+
+
<a id="Target"></a>
# Target
@@ -719,6 +750,20 @@ File System Type
* `load-file` : Just implements loadFile interface, so will be wrapped with CacheFileSystem internally.
* `os` : Use the OS based file system directly (without file system caching)
+<a id="source-embed-style"></a>
+# source-embed-style
+
+Source Embed Style
+
+* `none` : No source level embedding
+* `default` : The default embedding for the type to be embedded
+* `text` : Embed as text. May change line endings. If output isn't text will use 'default'. Size will *not* contain terminating 0.
+* `binary-text` : Embed as text assuming contents is binary.
+* `u8` : Embed as unsigned bytes.
+* `u16` : Embed as uint16_t.
+* `u32` : Embed as uint32_t.
+* `u64` : Embed as uint64_t.
+
<a id="target"></a>
# target
diff --git a/premake5.lua b/premake5.lua
index 44a0da437..20bb6b364 100644
--- a/premake5.lua
+++ b/premake5.lua
@@ -375,7 +375,7 @@ workspace "slang"
filter { "toolset:gcc*", "language:C++" }
buildoptions { "-Wno-class-memaccess" }
filter { "toolset:clang or gcc*", "language:C++" }
- buildoptions { "-Wno-reorder", "-Wno-invalid-offsetof" }
+ buildoptions { "-Wno-reorder", "-Wno-invalid-offsetof", "-Wno-assume" }
-- When compiling the debug configuration, we want to turn
-- optimization off, make sure debug symbols are output,
diff --git a/source/compiler-core/slang-artifact-desc-util.cpp b/source/compiler-core/slang-artifact-desc-util.cpp
index 18558eab9..559a1b4ca 100644
--- a/source/compiler-core/slang-artifact-desc-util.cpp
+++ b/source/compiler-core/slang-artifact-desc-util.cpp
@@ -899,7 +899,7 @@ SlangResult ArtifactDescUtil::appendDefaultExtension(const ArtifactDesc& desc, S
}
}
-/* static */bool ArtifactDescUtil::isDissassembly(const ArtifactDesc& from, const ArtifactDesc& to)
+/* static */bool ArtifactDescUtil::isDisassembly(const ArtifactDesc& from, const ArtifactDesc& to)
{
// From must be a binary like type
if (!isDerivedFrom(from.kind, ArtifactKind::CompileBinary))
diff --git a/source/compiler-core/slang-artifact-desc-util.h b/source/compiler-core/slang-artifact-desc-util.h
index 1e8b853e6..879732bb1 100644
--- a/source/compiler-core/slang-artifact-desc-util.h
+++ b/source/compiler-core/slang-artifact-desc-util.h
@@ -100,11 +100,12 @@ struct ArtifactDescUtil
static bool isDescDerivedFrom(const ArtifactDesc& desc, const ArtifactDesc& from);
/// True if `to` is disassembly of `from`
- static bool isDissassembly(const ArtifactDesc& from, const ArtifactDesc& to);
+ static bool isDisassembly(const ArtifactDesc& from, const ArtifactDesc& to);
/// Append the desc as text to out
static void appendText(const ArtifactDesc& desc, StringBuilder& out);
+ /// Given an artifact desc return a description as a string
static String getText(const ArtifactDesc& desc);
};
diff --git a/source/compiler-core/slang-dxc-compiler.cpp b/source/compiler-core/slang-dxc-compiler.cpp
index 832c5809c..9428d0d26 100644
--- a/source/compiler-core/slang-dxc-compiler.cpp
+++ b/source/compiler-core/slang-dxc-compiler.cpp
@@ -701,7 +701,7 @@ SlangResult DXCDownstreamCompiler::compile(const CompileOptions& inOptions, IArt
bool DXCDownstreamCompiler::canConvert(const ArtifactDesc& from, const ArtifactDesc& to)
{
- return ArtifactDescUtil::isDissassembly(from, to) && from.payload == ArtifactPayload::DXIL;
+ return ArtifactDescUtil::isDisassembly(from, to) && from.payload == ArtifactPayload::DXIL;
}
SlangResult DXCDownstreamCompiler::convert(IArtifact* from, const ArtifactDesc& to, IArtifact** outArtifact)
diff --git a/source/compiler-core/slang-fxc-compiler.cpp b/source/compiler-core/slang-fxc-compiler.cpp
index b3b952796..ed92cc3f8 100644
--- a/source/compiler-core/slang-fxc-compiler.cpp
+++ b/source/compiler-core/slang-fxc-compiler.cpp
@@ -335,7 +335,7 @@ SlangResult FXCDownstreamCompiler::compile(const CompileOptions& inOptions, IArt
bool FXCDownstreamCompiler::canConvert(const ArtifactDesc& from, const ArtifactDesc& to)
{
// Can only disassemble blobs that are DXBC
- return ArtifactDescUtil::isDissassembly(from, to) && from.payload == ArtifactPayload::DXBC;
+ return ArtifactDescUtil::isDisassembly(from, to) && from.payload == ArtifactPayload::DXBC;
}
SlangResult FXCDownstreamCompiler::convert(IArtifact* from, const ArtifactDesc& to, IArtifact** outArtifact)
diff --git a/source/compiler-core/slang-glslang-compiler.cpp b/source/compiler-core/slang-glslang-compiler.cpp
index c1053692f..3c5f7a2b8 100644
--- a/source/compiler-core/slang-glslang-compiler.cpp
+++ b/source/compiler-core/slang-glslang-compiler.cpp
@@ -251,7 +251,7 @@ SlangResult GlslangDownstreamCompiler::compile(const CompileOptions& inOptions,
bool GlslangDownstreamCompiler::canConvert(const ArtifactDesc& from, const ArtifactDesc& to)
{
// Can only disassemble blobs that are SPIR-V
- return ArtifactDescUtil::isDissassembly(from, to) && from.payload == ArtifactPayload::SPIRV;
+ return ArtifactDescUtil::isDisassembly(from, to) && from.payload == ArtifactPayload::SPIRV;
}
SlangResult GlslangDownstreamCompiler::convert(IArtifact* from, const ArtifactDesc& to, IArtifact** outArtifact)
diff --git a/source/compiler-core/slang-nvrtc-compiler.cpp b/source/compiler-core/slang-nvrtc-compiler.cpp
index cc8a72dce..e2f3e678c 100644
--- a/source/compiler-core/slang-nvrtc-compiler.cpp
+++ b/source/compiler-core/slang-nvrtc-compiler.cpp
@@ -1036,7 +1036,7 @@ SlangResult NVRTCDownstreamCompiler::compile(const DownstreamCompileOptions& inO
bool NVRTCDownstreamCompiler::canConvert(const ArtifactDesc& from, const ArtifactDesc& to)
{
- return ArtifactDescUtil::isDissassembly(from, to) || ArtifactDescUtil::isDissassembly(to, from);
+ return ArtifactDescUtil::isDisassembly(from, to) || ArtifactDescUtil::isDisassembly(to, from);
}
SlangResult NVRTCDownstreamCompiler::convert(IArtifact* from, const ArtifactDesc& to, IArtifact** outArtifact)
diff --git a/source/compiler-core/slang-source-embed-util.cpp b/source/compiler-core/slang-source-embed-util.cpp
new file mode 100644
index 000000000..ec0c005cf
--- /dev/null
+++ b/source/compiler-core/slang-source-embed-util.cpp
@@ -0,0 +1,399 @@
+#include "slang-source-embed-util.h"
+
+// Artifact
+#include "../compiler-core/slang-artifact-desc-util.h"
+#include "../compiler-core/slang-artifact-util.h"
+
+#include "../core/slang-string-util.h"
+#include "../core/slang-char-util.h"
+
+#include "../core/slang-string-escape-util.h"
+
+#include "../core/slang-blob.h"
+#include "../core/slang-io.h"
+
+namespace Slang
+{
+
+namespace { // anonymous
+typedef SourceEmbedUtil::Style Style;
+} // anonymous
+
+static const NamesDescriptionValue kSourceEmbedStyleInfos[] =
+{
+ { ValueInt(Style::None), "none", "No source level embedding" },
+ { ValueInt(Style::Default), "default", "The default embedding for the type to be embedded"},
+ { ValueInt(Style::Text), "text", "Embed as text. May change line endings. If output isn't text will use 'default'. Size will *not* contain terminating 0." },
+ { ValueInt(Style::BinaryText), "binary-text", "Embed as text assuming contents is binary. "},
+ { ValueInt(Style::U8), "u8", "Embed as unsigned bytes."},
+ { ValueInt(Style::U16), "u16", "Embed as uint16_t."},
+ { ValueInt(Style::U32), "u32", "Embed as uint32_t."},
+ { ValueInt(Style::U64), "u64", "Embed as uint64_t."},
+};
+
+/* static */ConstArrayView<NamesDescriptionValue> SourceEmbedUtil::getStyleInfos()
+{
+ return makeConstArrayView(kSourceEmbedStyleInfos);
+}
+
+/* static */ bool SourceEmbedUtil::isSupported(SlangSourceLanguage lang)
+{
+ return lang == SLANG_SOURCE_LANGUAGE_CPP || lang == SLANG_SOURCE_LANGUAGE_C;
+}
+
+static bool _isHeaderExtension(const UnownedStringSlice& in)
+{
+ // Some "typical" header extensions
+ return in == toSlice("h") ||
+ in == toSlice("hpp") ||
+ in == toSlice("hxx") ||
+ in == toSlice("h++") ||
+ in == toSlice("hh");
+}
+
+/* static */String SourceEmbedUtil::getPath(const String& path, const Options& options)
+{
+ if (!isSupported(options.language))
+ {
+ return String();
+ }
+
+ if (!path.getLength())
+ {
+ return path;
+ }
+
+ const auto ext = Path::getPathExt(path);
+
+ if (_isHeaderExtension(ext.getUnownedSlice()))
+ {
+ return path;
+ }
+
+ // Assume it's a header, and just use the .h extension
+ StringBuilder buf;
+ buf << path << toSlice(".h");
+ return buf;
+}
+
+/* static */SourceEmbedUtil::Style SourceEmbedUtil::getDefaultStyle(const ArtifactDesc& desc)
+{
+ if (ArtifactDescUtil::isText(desc))
+ {
+ return Style::Text;
+ }
+
+ if (isDerivedFrom(desc.kind, ArtifactKind::CompileBinary))
+ {
+ // SPIR-V is encoded as U32
+ if (isDerivedFrom(desc.payload, ArtifactPayload::SPIRV))
+ {
+ return Style::U32;
+ }
+ }
+
+ // When in doube encode as U8 bytes.
+ // The problem is on some compilers there are limits on how long a U8 based binary can be.
+ return Style::U8;
+}
+
+// True if we need to copy into a buffer. Necessary if there is an alignement
+// issue or if there is a partial entry
+static bool _needsCopy(const uint8_t* cur, Count bytesPerElement, Count bytesPerLine)
+{
+ return ((size_t(bytesPerLine) | size_t(cur)) & size_t(bytesPerElement - 1)) != 0;
+}
+
+// NOTE! Assumes T is an unsigned type. Behavior will be incorrect if it is not.
+template <typename T>
+static void _appendHex(const T* in, ArrayView<char> elementWork, char* dst, size_t bytesForLine, StringBuilder& out)
+{
+ // Check that T is unsigned
+ SLANG_COMPILE_TIME_ASSERT((T(~T(0))) > T(0));
+
+ // Make sure dst seems plausible
+ SLANG_ASSERT(dst >= elementWork.begin() && dst <= elementWork.end());
+ // Check the alignment
+ SLANG_ASSERT((size_t(in) & (sizeof(T) - 1)) == 0);
+
+ // Calculate the amount of elements for this line.
+ const size_t elementsCount = (bytesForLine + sizeof(T) - 1) / sizeof(T);
+
+ // The amount of hex digits needed, is 2 per byte
+ const Count numHexDigits = sizeof(T) * 2;
+
+ // Shift to get top nybble
+ const Index shift = (numHexDigits - 1) * 4;
+
+ for (size_t i = 0; i < elementsCount; ++i)
+ {
+ T value = in[i];
+
+ for (Index j = 0; j < numHexDigits; j++, value <<= 4)
+ {
+ dst[j] = CharUtil::getHexChar(Index(value >> shift) & 0xf);
+ }
+
+ out.append(elementWork.getBuffer(), elementWork.getCount());
+ }
+}
+
+static SlangResult _append(const SourceEmbedUtil::Options& options, ConstArrayView<uint8_t> data, StringBuilder& buf)
+{
+ const uint8_t* cur = data.begin();
+
+ const auto prefix = toSlice("0x");
+ const auto suffix = toSlice(", ");
+ UnownedStringSlice literalSuffix;
+
+ UnownedStringSlice elementType;
+
+ Count bytesPerElement;
+
+ switch (options.style)
+ {
+ case Style::U8:
+ {
+ elementType = toSlice("unsigned char");
+ bytesPerElement = 1;
+ break;
+ }
+ case Style::U16:
+ {
+ elementType = toSlice("uint16_t");
+ bytesPerElement = 2;
+ break;
+ }
+ case Style::U32:
+ {
+ elementType = toSlice("uint32_t");
+ bytesPerElement = 4;
+ break;
+ }
+ case Style::U64:
+ {
+ elementType = toSlice("uint64_t");
+ bytesPerElement = 8;
+ // On testing on GCC/CLANG/Recent VS, there is no warning/error without suffix, so
+ // will leave off for now.
+ // literalSuffix = toSlice("ULL");
+ break;
+ }
+ default: return SLANG_FAIL;
+ }
+
+ // Output the variable
+
+ buf << "const " << elementType << " " << options.variableName << "[] = \n";
+ buf << "{\n";
+
+ // Work out the element work
+ char work[80];
+ Count elementSizeInChars;
+ {
+ StringBuilder workBuf;
+ workBuf << prefix;
+ workBuf.appendRepeatedChar('N', 2 * bytesPerElement);
+ workBuf << literalSuffix;
+ workBuf << suffix;
+
+ elementSizeInChars = workBuf.getLength();
+ ::memcpy(work, workBuf.getBuffer(), elementSizeInChars);
+ }
+
+ auto workView = makeArrayView(work, elementSizeInChars);
+ char* dstChars = work + prefix.getLength();
+
+ Count elementsPerLine = (options.lineLength - options.indent.getLength()) / elementSizeInChars;
+ elementsPerLine = (elementsPerLine <= 0) ? 1 : elementsPerLine;
+
+ // Maximum bytes output per line
+ const size_t bytesPerLine = elementsPerLine * bytesPerElement;
+
+ List<uint64_t> alignedElements;
+ alignedElements.setCount(Count((bytesPerLine / sizeof(uint64_t)) + 2));
+ uint8_t* alignedDst = (uint8_t*)alignedElements.getBuffer();
+
+ size_t bytesRemaining = data.getCount();
+
+ while (bytesRemaining > 0)
+ {
+ const size_t bytesForLine = bytesRemaining > bytesPerLine ? bytesPerLine : bytesRemaining;
+ bytesRemaining -= bytesForLine;
+
+ const uint8_t* lineBytes = cur;
+ cur += bytesForLine;
+
+ // We copy if we want alignment of if we hit a partial at the end
+ if (_needsCopy(lineBytes, bytesPerElement, bytesForLine))
+ {
+ // Make sure the last element is zeroed, before copying
+ // Needed if the last element is partial.
+ alignedElements[Index(bytesForLine / sizeof(uint64_t))] = 0;
+
+ // Copy the bytes over
+ ::memcpy(alignedDst, lineBytes, bytesForLine);
+
+ // Use the aligned buffer for the line
+ lineBytes = alignedDst;
+ }
+
+ buf << options.indent;
+
+ switch (bytesPerElement)
+ {
+ case 1: _appendHex<uint8_t>(lineBytes, workView, dstChars, bytesForLine, buf); break;
+ case 2: _appendHex<uint16_t>((const uint16_t*)lineBytes, workView, dstChars, bytesForLine, buf); break;
+ case 4: _appendHex<uint32_t>((const uint32_t*)lineBytes, workView, dstChars, bytesForLine, buf); break;
+ case 8: _appendHex<uint64_t>((const uint64_t*)lineBytes, workView, dstChars, bytesForLine, buf); break;
+ }
+
+ buf << "\n";
+ }
+
+ buf << "};\n\n";
+
+ return SLANG_OK;
+}
+
+/* static */SlangResult SourceEmbedUtil::createEmbedded(IArtifact* artifact, const Options& inOptions, ComPtr<IArtifact>& outArtifact)
+{
+ if (!isSupported(inOptions.language))
+ {
+ return SLANG_E_NOT_IMPLEMENTED;
+ }
+
+ ComPtr<ISlangBlob> blob;
+ SLANG_RETURN_ON_FAIL(artifact->loadBlob(ArtifactKeep::No, blob.writeRef()));
+
+ const auto desc = artifact->getDesc();
+
+ Options options(inOptions);
+
+ // If the style is text, but the artifact *isn't* a text type, we'll
+ // use 'default' for the type
+ if (options.style == Style::Text &&
+ !ArtifactDescUtil::isText(desc))
+ {
+ options.style = Style::Default;
+ }
+
+ if (options.style == Style::Default)
+ {
+ options.style = getDefaultStyle(desc);
+ }
+
+ // If there is no style there is nothing to do
+ if (options.style == Style::None)
+ {
+ return SLANG_OK;
+ }
+
+ if (options.variableName.getLength() <= 0)
+ {
+ options.variableName = "data";
+ }
+
+ StringBuilder buf;
+
+ ConstArrayView<uint8_t> data((const uint8_t*)blob->getBufferPointer(), blob->getBufferSize());
+
+ size_t totalSizeInBytes = data.getCount();
+
+ switch (options.style)
+ {
+ case Style::Text:
+ {
+ totalSizeInBytes = 0;
+
+ auto handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::Cpp);
+
+ buf << "const char " << options.variableName << "[] = \n";
+
+ // Split into lines
+ // We dont worry about splitting lines in this impl...
+ UnownedStringSlice text((const char*)data.begin(), data.getCount());
+
+ for (auto line : LineParser(text))
+ {
+ buf << options.indent;
+ buf << "\"";
+
+ handler->appendEscaped(line, buf);
+
+ // Work out the total size, taking into account we may encode line endings and \0 differently
+ // The +1 is for \n
+ totalSizeInBytes += line.getLength() + 1;
+
+ buf << "\\n\"\n";
+ }
+
+ buf << ";\n";
+ break;
+ }
+ case Style::BinaryText:
+ {
+ auto handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::Cpp);
+
+ buf << "const char " << options.variableName << "[] = \n";
+
+ // We could encode everything and then split
+ // but if we do that we probably want to not split across an escaped character,
+ // although that may be handled correctly.
+
+ // The other way to this is incrementally, so that's what we will do here
+ UnownedStringSlice text((const char*)data.begin(), data.getCount());
+
+ auto cur = text.begin();
+ auto end = text.end();
+
+ while (cur < end)
+ {
+ const auto startOffset = buf.getLength();
+
+ buf << options.indent;
+ buf << "\"";
+
+ do
+ {
+ handler->appendEscaped(UnownedStringSlice(cur, 1), buf);
+ cur++;
+ }
+ while (buf.getLength() - startOffset < options.lineLength - 1);
+
+ buf << "\"\n";
+ }
+
+ buf << ";\n";
+ break;
+ }
+ case Style::U8:
+ case Style::U16:
+ case Style::U32:
+ case Style::U64:
+ {
+ SLANG_RETURN_ON_FAIL(_append(options, data, buf));
+ break;
+ }
+ default:
+ {
+ return SLANG_E_NOT_IMPLEMENTED;
+ }
+ }
+
+ buf << "const size_t " << options.variableName << "_sizeInBytes = " << uint64_t(totalSizeInBytes) << ";\n\n";
+
+ // Make into an artifact
+ ArtifactPayload payload = options.language == SLANG_SOURCE_LANGUAGE_C ? ArtifactPayload::C : ArtifactPayload::Cpp;
+ auto dstDesc = ArtifactDesc::make(ArtifactKind::Source, payload);
+
+ auto dstArtifact = ArtifactUtil::createArtifact(dstDesc);
+
+ auto dstBlob = StringBlob::moveCreate(buf);
+ dstArtifact->addRepresentationUnknown(dstBlob);
+
+ outArtifact = dstArtifact;
+ return SLANG_OK;
+}
+
+} // namespace Slang
diff --git a/source/compiler-core/slang-source-embed-util.h b/source/compiler-core/slang-source-embed-util.h
new file mode 100644
index 000000000..70389cfe1
--- /dev/null
+++ b/source/compiler-core/slang-source-embed-util.h
@@ -0,0 +1,59 @@
+#ifndef SLANG_SOURCE_EMBED_UTIL_H
+#define SLANG_SOURCE_EMBED_UTIL_H
+
+#include "../core/slang-basic.h"
+
+#include "slang-artifact.h"
+#include "slang-diagnostic-sink.h"
+
+#include "../../slang-com-ptr.h"
+
+#include "../core/slang-name-value.h"
+
+namespace Slang
+{
+
+
+struct SourceEmbedUtil
+{
+ enum class Style : uint32_t
+ {
+ None, ///< No embedding
+ Default, ///< Default embedding for the type
+ Text, ///< Embed as text. May change line endings. If output isn't text will use 'default'. Size will *not* contain terminating 0
+ BinaryText, ///< Embed as text assuming contents is binary.
+ U8, ///< Embed as unsigned bytes
+ U16, ///< Embed as uint16_t
+ U32, ///< Embed as uint32_t
+ U64, ///< Embed as uint64_t
+ CountOf,
+ };
+
+ struct Options
+ {
+ Style style = Style::Default; ///< Style of embedding
+ Count lineLength = 120; ///< The line length, lines can be larger for some styles, but will aim to keep within range
+ SlangSourceLanguage language = SLANG_SOURCE_LANGUAGE_C; ///< The language to output for
+ String variableName; ///< The name to give the variable
+ String indent = " "; ///< Indenting
+ };
+
+ /// Get the style infos
+ static ConstArrayView<NamesDescriptionValue> getStyleInfos();
+
+ /// Given an artifact and
+ static SlangResult createEmbedded(IArtifact* artifact, const Options& options, ComPtr<IArtifact>& outArtifact);
+
+ /// Returns the default style for the desc
+ static Style getDefaultStyle(const ArtifactDesc& desc);
+
+ /// Returns true if supports the specified language for embedding
+ static bool isSupported(SlangSourceLanguage lang);
+
+ /// Given the path return the output path. If no path is available return the empty string
+ static String getPath(const String& path, const Options& options);
+};
+
+}
+
+#endif
diff --git a/source/slang/slang-artifact-output-util.cpp b/source/slang/slang-artifact-output-util.cpp
index 80c11a466..32aff9f43 100644
--- a/source/slang/slang-artifact-output-util.cpp
+++ b/source/slang/slang-artifact-output-util.cpp
@@ -25,7 +25,7 @@ namespace Slang
assemblyDesc.kind = ArtifactKind::Assembly;
// Check it seems like a plausbile disassembly
- if (!ArtifactDescUtil::isDissassembly(desc, assemblyDesc))
+ if (!ArtifactDescUtil::isDisassembly(desc, assemblyDesc))
{
if (sink)
{
@@ -77,7 +77,7 @@ SlangResult ArtifactOutputUtil::maybeDisassemble(Session* session, IArtifact* ar
toDesc.kind = ArtifactKind::Assembly;
// If this likes a playsible disassebly conversion
- if (ArtifactDescUtil::isDissassembly(desc, toDesc))
+ if (ArtifactDescUtil::isDisassembly(desc, toDesc))
{
ComPtr<IArtifact> disassemblyArtifact;
diff --git a/source/slang/slang-compiler.cpp b/source/slang/slang-compiler.cpp
index 4ca76c53a..8426b7eec 100644
--- a/source/slang/slang-compiler.cpp
+++ b/source/slang/slang-compiler.cpp
@@ -1648,24 +1648,52 @@ namespace Slang
return String();
}
+ SlangResult EndToEndCompileRequest::_writeArtifact(const String& path, IArtifact* artifact)
+ {
+ if (path.getLength() > 0)
+ {
+ SLANG_RETURN_ON_FAIL(ArtifactOutputUtil::writeToFile(artifact, getSink(), path));
+ }
+ else if (m_containerFormat == ContainerFormat::None)
+ {
+ // If we aren't writing to a container and we didn't write to a file, we can output to standard output
+ writeArtifactToStandardOutput(artifact, getSink());
+ }
+ return SLANG_OK;
+ }
+
SlangResult EndToEndCompileRequest::_maybeWriteArtifact(const String& path, IArtifact* artifact)
{
// We don't have to do anything if there is no artifact
- if (artifact)
+ if (!artifact)
{
- if (path.getLength())
- {
- SLANG_RETURN_ON_FAIL(ArtifactOutputUtil::writeToFile(artifact, getSink(), path));
- return SLANG_OK;
- }
+ return SLANG_OK;
+ }
+
+ // If embedding is enabled...
+ if (m_sourceEmbedStyle != SourceEmbedUtil::Style::None)
+ {
+ SourceEmbedUtil::Options options;
- // If we aren't writing to a container and we didn't write to a file, we can output to
- if (m_containerFormat == ContainerFormat::None)
+ options.style = m_sourceEmbedStyle;
+ options.variableName = m_sourceEmbedName;
+ options.language = (SlangSourceLanguage)m_sourceEmbedLanguage;
+
+ ComPtr<IArtifact> embeddedArtifact;
+ SLANG_RETURN_ON_FAIL(SourceEmbedUtil::createEmbedded(artifact, options, embeddedArtifact));
+
+ if (!embeddedArtifact)
{
- writeArtifactToStandardOutput(artifact, getSink());
+ return SLANG_FAIL;
}
+ SLANG_RETURN_ON_FAIL(_writeArtifact(SourceEmbedUtil::getPath(path, options), embeddedArtifact));
+ return SLANG_OK;
}
-
+ else
+ {
+ SLANG_RETURN_ON_FAIL(_writeArtifact(path, artifact));
+ }
+
return SLANG_OK;
}
@@ -2118,14 +2146,16 @@ namespace Slang
}
}
+
void EndToEndCompileRequest::generateOutput()
{
generateOutput(getSpecializedGlobalAndEntryPointsComponentType());
-
+
// If we are in command-line mode, we might be expected to actually
// write output to one or more files here.
- if (m_isCommandLineCompile)
+ if (m_isCommandLineCompile &&
+ m_containerFormat == ContainerFormat::None)
{
auto linkage = getLinkage();
auto program = getSpecializedGlobalAndEntryPointsComponentType();
@@ -2136,23 +2166,27 @@ namespace Slang
if (targetReq->isWholeProgramRequest())
{
- const auto path = _getWholeProgramPath(targetReq);
- const auto artifact = targetProgram->getExistingWholeProgramResult();
+ if (const auto artifact = targetProgram->getExistingWholeProgramResult())
+ {
+ const auto path = _getWholeProgramPath(targetReq);
- _maybeWriteArtifact(path, artifact);
+ _maybeWriteArtifact(path, artifact);
+ }
}
else
{
Index entryPointCount = program->getEntryPointCount();
for (Index ee = 0; ee < entryPointCount; ++ee)
{
- const auto path = _getEntryPointPath(targetReq, ee);
- const auto artifact = targetProgram->getExistingEntryPointResult(ee);
-
- _maybeWriteArtifact(path, artifact);
+ if (const auto artifact = targetProgram->getExistingEntryPointResult(ee))
+ {
+ const auto path = _getEntryPointPath(targetReq, ee);
+
+ _maybeWriteArtifact(path, artifact);
+ }
}
}
- }
+ }
}
// Maybe create the container
@@ -2161,6 +2195,9 @@ namespace Slang
// If it's a command line compile we may need to write the container to a file
if (m_isCommandLineCompile)
{
+ // TODO(JS):
+ // We could write the container into a source embedded format potentially
+
maybeWriteContainer(m_containerOutputPath);
_writeDependencyFile(this);
diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h
index 1386b37e8..4d3df5433 100755
--- a/source/slang/slang-compiler.h
+++ b/source/slang/slang-compiler.h
@@ -12,6 +12,8 @@
#include "../compiler-core/slang-include-system.h"
#include "../compiler-core/slang-command-line-args.h"
+#include "../compiler-core/slang-source-embed-util.h"
+
#include "../core/slang-std-writers.h"
#include "../core/slang-command-options.h"
@@ -2667,6 +2669,13 @@ namespace Slang
// Should we just pass the input to another compiler?
PassThroughMode m_passThrough = PassThroughMode::None;
+ /// If output should be source embedded, define the style of the embedding
+ SourceEmbedUtil::Style m_sourceEmbedStyle = SourceEmbedUtil::Style::None;
+ /// The language to be used for source embedding
+ SourceLanguage m_sourceEmbedLanguage = SourceLanguage::C;
+ /// Source embed variable name. Note may be used as a basis for names if multiple items written
+ String m_sourceEmbedName;
+
/// Source code for the specialization arguments to use for the global specialization parameters of the program.
List<String> m_globalSpecializationArgStrings;
@@ -2802,6 +2811,7 @@ namespace Slang
/// Maybe write the artifact to the path (if set), or stdout (if there is no container or path)
SlangResult _maybeWriteArtifact(const String& path, IArtifact* artifact);
+ SlangResult _writeArtifact(const String& path, IArtifact* artifact);
ISlangUnknown* getInterface(const Guid& guid);
diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h
index 25c86fa05..722f25843 100644
--- a/source/slang/slang-diagnostic-defs.h
+++ b/source/slang/slang-diagnostic-defs.h
@@ -98,6 +98,8 @@ DIAGNOSTIC( 43, Error, targetFlagsIgnoredBecauseBeforeAllTargets, "when using
DIAGNOSTIC( 50, Error, duplicateTargets, "the target '$0' has been specified more than once")
+DIAGNOSTIC( 51, Error, unhandledLanguageForSourceEmbedding, "unhandled source language for source embedding")
+
DIAGNOSTIC( 60, Error, cannotDeduceOutputFormatFromPath, "cannot infer an output format from the output path '$0'")
DIAGNOSTIC( 61, Error, cannotMatchOutputFileToTarget, "no specified '-target' option matches the output path '$0', which implies the '$1' format")
diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp
index f69e0ec3a..09c46b7ef 100644
--- a/source/slang/slang-options.cpp
+++ b/source/slang/slang-options.cpp
@@ -29,6 +29,7 @@
#include "../compiler-core/slang-command-line-args.h"
#include "../compiler-core/slang-artifact-desc-util.h"
#include "../compiler-core/slang-core-diagnostics.h"
+#include "../compiler-core/slang-source-embed-util.h"
#include "../core/slang-string-slice-pool.h"
#include "../core/slang-char-util.h"
@@ -72,6 +73,10 @@ enum class OptionKind
EmitIr,
ReportDownstreamTime,
+ SourceEmbedStyle,
+ SourceEmbedName,
+ SourceEmbedLanguage,
+
// Target
Capability,
@@ -167,6 +172,7 @@ enum class ValueCategory
DebugLevel,
FileSystemType,
VulkanShift,
+ SourceEmbedStyle,
CountOf,
};
@@ -184,6 +190,8 @@ SLANG_GET_VALUE_CATEGORY(FileSystemType, TypeTextUtil::FileSystemType)
SLANG_GET_VALUE_CATEGORY(HelpStyle, CommandOptionsWriter::Style)
SLANG_GET_VALUE_CATEGORY(OptimizationLevel, SlangOptimizationLevel)
SLANG_GET_VALUE_CATEGORY(VulkanShift, HLSLToVulkanLayoutOptions::Kind)
+SLANG_GET_VALUE_CATEGORY(SourceEmbedStyle, SourceEmbedUtil::Style)
+SLANG_GET_VALUE_CATEGORY(Language, SourceLanguage)
} // anonymous
@@ -242,6 +250,9 @@ void initCommandOptions(CommandOptions& options)
options.addCategory(CategoryKind::Value, "file-system-type", "File System Type", UserValue(ValueCategory::FileSystemType));
options.addValues(TypeTextUtil::getFileSystemTypeInfos());
+
+ options.addCategory(CategoryKind::Value, "source-embed-style", "Source Embed Style", UserValue(ValueCategory::SourceEmbedStyle));
+ options.addValues(SourceEmbedUtil::getStyleInfos());
}
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! target !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
@@ -416,6 +427,17 @@ void initCommandOptions(CommandOptions& options)
{ OptionKind::DumpWarningDiagnostics, "-dump-warning-diagnostics", nullptr, "Dump to output list of warning diagnostic numeric and name ids." },
{ OptionKind::InputFilesRemain, "--", nullptr, "Treat the rest of the command line as input files."},
{ OptionKind::ReportDownstreamTime, "-report-downstream-time", nullptr, "Reports the time spent in the downstream compiler." },
+ { OptionKind::SourceEmbedStyle, "-source-embed-style", "-source-embed-style <source-embed-style>",
+ "If source embedding is enabled, defines the style used. When enabled (with any style other than `none`), "
+ "will write compile results into embeddable source for the target language. "
+ "If no output file is specified, the output is written to stdout. If an output file is specified "
+ "it is written either to that file directly (if it is appropriate for the target language), "
+ "or it will be output to the filename with an appropriate extension.\n\n"
+ "Note for C/C++ with u16/u32/u64 types it is necessary to have \"#include <stdint.h>\" before the generated file.\n" },
+ { OptionKind::SourceEmbedName, "-source-embed-name", "-source-embed-name <name>",
+ "The name used as the basis for variables output for source embedding."},
+ { OptionKind::SourceEmbedLanguage, "-source-embed-language", "-source-embed-language <language>",
+ "The language to be used for source embedding. Defaults to C/C++. Currently only C/C++ are supported"},
};
_addOptions(makeConstArrayView(generalOpts), options);
@@ -2287,6 +2309,30 @@ SlangResult OptionsParser::_parse(
}
break;
}
+ case OptionKind::SourceEmbedStyle:
+ {
+ SLANG_RETURN_ON_FAIL(_expectValue(m_requestImpl->m_sourceEmbedStyle));
+ break;
+ }
+ case OptionKind::SourceEmbedName:
+ {
+ CommandLineArg name;
+ SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));
+ m_requestImpl->m_sourceEmbedName = name.value;
+ break;
+ }
+ case OptionKind::SourceEmbedLanguage:
+ {
+ SLANG_RETURN_ON_FAIL(_expectValue(m_requestImpl->m_sourceEmbedLanguage));
+
+ if (!SourceEmbedUtil::isSupported((SlangSourceLanguage)m_requestImpl->m_sourceEmbedLanguage))
+ {
+ m_sink->diagnose(arg.loc, Diagnostics::unhandledLanguageForSourceEmbedding);
+ return SLANG_FAIL;
+ }
+
+ break;
+ }
default:
{
// Hmmm, we looked up and produced a valid enum, but it wasn't handled in the switch...
diff --git a/tests/feature/source-embed/source-embed-1.slang b/tests/feature/source-embed/source-embed-1.slang
new file mode 100644
index 000000000..75b360a23
--- /dev/null
+++ b/tests/feature/source-embed/source-embed-1.slang
@@ -0,0 +1,44 @@
+//TEST:SIMPLE(filecheck=CHECK):-target spirv -entry computeMain -profile cs_6_3 -line-directive-mode none -source-embed-style default
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name outputBuffer
+RWStructuredBuffer<int> outputBuffer;
+
+// CHECK: const uint32_t data[] =
+// CHECK: 0x07230203, 0x00010000,
+// CHECK: const size_t data_sizeInBytes =
+
+int calcThing(int offset)
+{
+ int total = 0;
+ int another[2] = { 1, 2};
+
+ for (int k = 0; k < 20; ++k)
+ {
+ for (int i = 0; i < 17; ++i)
+ {
+ another[i & 1] += k + i;
+ }
+
+ total += another[k & 1];
+
+ if ((k + 7) % 5 == 4)
+ {
+ return 1;
+ }
+ }
+
+ if (total > 4)
+ {
+ total = -total;
+ }
+
+ return total;
+}
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ int index = int(dispatchThreadID.x);
+
+ outputBuffer[index] = calcThing(index);
+}
diff --git a/tests/feature/source-embed/source-embed-2.slang b/tests/feature/source-embed/source-embed-2.slang
new file mode 100644
index 000000000..a4e4a75be
--- /dev/null
+++ b/tests/feature/source-embed/source-embed-2.slang
@@ -0,0 +1,45 @@
+//TEST:SIMPLE(filecheck=CHECK):-target spirv -entry computeMain -profile cs_6_3 -line-directive-mode none -source-embed-style binary-text -source-embed-name hello
+
+// CHECK: const char hello[] =
+// CHECK: const size_t hello_sizeInBytes =
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name outputBuffer
+RWStructuredBuffer<int> outputBuffer;
+
+// Test loop handling, with more complex structure
+
+int calcThing(int offset)
+{
+ int total = 0;
+ int another[2] = { 1, 2};
+
+ for (int k = 0; k < 20; ++k)
+ {
+ for (int i = 0; i < 17; ++i)
+ {
+ another[i & 1] += k + i;
+ }
+
+ total += another[k & 1];
+
+ if ((k + 7) % 5 == 4)
+ {
+ return 1;
+ }
+ }
+
+ if (total > 4)
+ {
+ total = -total;
+ }
+
+ return total;
+}
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ int index = int(dispatchThreadID.x);
+
+ outputBuffer[index] = calcThing(index);
+}
diff --git a/tests/feature/source-embed/source-embed-3.slang b/tests/feature/source-embed/source-embed-3.slang
new file mode 100644
index 000000000..9dc7cf324
--- /dev/null
+++ b/tests/feature/source-embed/source-embed-3.slang
@@ -0,0 +1,45 @@
+//TEST:SIMPLE(filecheck=CHECK):-target dxil -entry computeMain -profile cs_6_3 -line-directive-mode none -source-embed-style u64 -source-embed-name hello64
+
+// CHECK: const uint64_t hello64[] =
+// CHECK: const size_t hello64_sizeInBytes =
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name outputBuffer
+RWStructuredBuffer<int> outputBuffer;
+
+// Test loop handling, with more complex structure
+
+int calcThing(int offset)
+{
+ int total = 0;
+ int another[2] = { 1, 2};
+
+ for (int k = 0; k < 20; ++k)
+ {
+ for (int i = 0; i < 17; ++i)
+ {
+ another[i & 1] += k + i;
+ }
+
+ total += another[k & 1];
+
+ if ((k + 7) % 5 == 4)
+ {
+ return 1;
+ }
+ }
+
+ if (total > 4)
+ {
+ total = -total;
+ }
+
+ return total;
+}
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ int index = int(dispatchThreadID.x);
+
+ outputBuffer[index] = calcThing(index);
+}
diff --git a/tests/feature/source-embed/source-embed-4.slang b/tests/feature/source-embed/source-embed-4.slang
new file mode 100644
index 000000000..d7324042e
--- /dev/null
+++ b/tests/feature/source-embed/source-embed-4.slang
@@ -0,0 +1,45 @@
+//TEST:SIMPLE(filecheck=CHECK):-target glsl -entry computeMain -profile cs_6_3 -line-directive-mode none -source-embed-style default -source-embed-name z
+
+// CHECK: const char z[] =
+// CHECK: const size_t z_sizeInBytes =
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name outputBuffer
+RWStructuredBuffer<int> outputBuffer;
+
+// Test loop handling, with more complex structure
+
+int calcThing(int offset)
+{
+ int total = 0;
+ int another[2] = { 1, 2};
+
+ for (int k = 0; k < 20; ++k)
+ {
+ for (int i = 0; i < 17; ++i)
+ {
+ another[i & 1] += k + i;
+ }
+
+ total += another[k & 1];
+
+ if ((k + 7) % 5 == 4)
+ {
+ return 1;
+ }
+ }
+
+ if (total > 4)
+ {
+ total = -total;
+ }
+
+ return total;
+}
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ int index = int(dispatchThreadID.x);
+
+ outputBuffer[index] = calcThing(index);
+}