summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2023-03-24 09:56:59 -0400
committerGitHub <noreply@github.com>2023-03-24 09:56:59 -0400
commite794de0d63e6de9be564c971fd40486ecf631293 (patch)
tree903bd4100fd9c259d68f2893c61a36880e182f14
parent03c10833beb331e234554808c2a80d3cadecc7c0 (diff)
Obfuscated source map writing (#2727)
* #include an absolute path didn't work - because paths were taken to always be relative. * WIP produce obfuscated source map and write when container is specified. * Make the sourcemap generated name stable.
-rw-r--r--source/compiler-core/slang-artifact-desc-util.cpp20
-rw-r--r--source/compiler-core/slang-source-map.cpp50
-rw-r--r--source/compiler-core/slang-source-map.h14
-rw-r--r--source/slang/slang-compiler.cpp175
-rwxr-xr-xsource/slang/slang-compiler.h7
-rw-r--r--source/slang/slang-ir-obfuscate-loc.cpp28
-rw-r--r--source/slang/slang.cpp24
-rw-r--r--tests/serialization/obfuscated-serialized-module-test.slang35
-rw-r--r--tests/serialization/obfuscated-serialized-module-test.slang.1.expected.txt4
-rw-r--r--tests/serialization/obfuscated-serialized-module-test.slang.2.expected.txt4
-rw-r--r--tests/serialization/obfuscated-serialized-module.slang15
11 files changed, 307 insertions, 69 deletions
diff --git a/source/compiler-core/slang-artifact-desc-util.cpp b/source/compiler-core/slang-artifact-desc-util.cpp
index 08b7c8412..ca7dcb70f 100644
--- a/source/compiler-core/slang-artifact-desc-util.cpp
+++ b/source/compiler-core/slang-artifact-desc-util.cpp
@@ -710,6 +710,26 @@ SlangResult ArtifactDescUtil::appendDefaultExtension(const ArtifactDesc& desc, S
// Don't know the extension for that
return SLANG_E_NOT_FOUND;
}
+ case ArtifactKind::Json:
+ {
+ auto ext = _getPayloadExtension(desc.payload);
+ if (ext.begin() != nullptr)
+ {
+ // TODO(JS):
+ // Do we need to alter the extension or the name if it's an
+ // obfuscated map?
+ //if (isDerivedFrom(desc.style, ArtifactStyle::Obfuscated))
+ //{
+ //}
+
+ out << ext;
+ return SLANG_OK;
+ }
+
+ // Not really what kind of json, so just use 'generic' json extension
+ out << "json";
+ return SLANG_OK;
+ }
default: break;
}
diff --git a/source/compiler-core/slang-source-map.cpp b/source/compiler-core/slang-source-map.cpp
index 830314008..1dd0fe8a5 100644
--- a/source/compiler-core/slang-source-map.cpp
+++ b/source/compiler-core/slang-source-map.cpp
@@ -201,7 +201,7 @@ void SourceMap::clear()
void SourceMap::advanceToLine(Index nextLineIndex)
{
- const Count currentLineIndex = getGeneratedLineCount();
+ const Count currentLineIndex = getGeneratedLineCount() - 1;
SLANG_ASSERT(nextLineIndex >= currentLineIndex);
@@ -215,24 +215,13 @@ void SourceMap::advanceToLine(Index nextLineIndex)
// For all the new entries they will need to point to the end
m_lineStarts.setCount(nextLineIndex + 1);
- Index* starts = m_lineStarts.getBuffer() + currentLineIndex;
- const Count startsCount = nextLineIndex + 1 - currentLineIndex;
-
- for (Index i = 0; i < startsCount; ++i)
+ Index* starts = m_lineStarts.getBuffer();
+ for (Index i = currentLineIndex + 1; i < nextLineIndex + 1; ++i)
{
starts[i] = lastEntryIndex;
}
}
-void SourceMap::addEntry(const Entry& entry)
-{
- m_lineEntries.add(entry);
- ++m_lineStarts.getLast();
-
- // Check things seem normal...
- SLANG_ASSERT(m_lineStarts.getLast() == m_lineEntries.getCount());
-}
-
Index SourceMap::getNameIndex(const UnownedStringSlice& slice)
{
StringSlicePool::Handle handle;
@@ -284,33 +273,30 @@ SlangResult SourceMap::decode(JSONContainer* container, JSONValue root, Diagnost
clear();
// Let's try and decode the JSON into native types to make this easier...
-
RttiTypeFuncsMap typeMap = JSONNativeUtil::getTypeFuncsMap();
// Convert to native
- JSONSourceMap src;
+ JSONSourceMap native;
{
JSONToNativeConverter converter(container, &typeMap, sink);
// Convert to the native type
- SLANG_RETURN_ON_FAIL(converter.convert(root, GetRttiInfo<JSONSourceMap>::get(), &src));
+ SLANG_RETURN_ON_FAIL(converter.convert(root, GetRttiInfo<JSONSourceMap>::get(), &native));
}
- m_slicePool.clear();
-
- m_file = src.file;
- m_sourceRoot = src.sourceRoot;
+ m_file = native.file;
+ m_sourceRoot = native.sourceRoot;
- const Count sourcesCount = src.sources.getCount();
+ const Count sourcesCount = native.sources.getCount();
// These should all be unique, but for simplicity, we build a table
m_sources.setCount(sourcesCount);
for (Index i = 0; i < sourcesCount; ++i)
{
- m_sources[i] = m_slicePool.add(src.sources[i]);
+ m_sources[i] = m_slicePool.add(native.sources[i]);
}
- Count sourcesContentCount = src.sourcesContent.getCount();
+ Count sourcesContentCount = native.sourcesContent.getCount();
sourcesContentCount = std::min(sourcesContentCount, sourcesCount);
m_sourcesContent.setCount(sourcesContentCount);
@@ -321,7 +307,7 @@ SlangResult SourceMap::decode(JSONContainer* container, JSONValue root, Diagnost
for (Index i = 0; i < sourcesContentCount; ++i)
{
- auto value = src.sourcesContent[i];
+ auto value = native.sourcesContent[i];
if (value.type != JSONValue::Type::Null)
{
@@ -334,7 +320,7 @@ SlangResult SourceMap::decode(JSONContainer* container, JSONValue root, Diagnost
}
List<UnownedStringSlice> lines;
- StringUtil::split(src.mappings, ';', lines);
+ StringUtil::split(native.mappings, ';', lines);
List<UnownedStringSlice> segments;
@@ -378,10 +364,10 @@ SlangResult SourceMap::decode(JSONContainer* container, JSONValue root, Diagnost
// It can be 4 or 5 parts
if (segment.getLength())
{
- /* If present, an zero-based index into the “sources” list. This field is a base 64 VLQ relative to the previous occurrence of this field, unless this is the first occurrence of this field, in which case the whole value is represented.
- If present, the zero-based starting line in the original source represented. This field is a base 64 VLQ relative to the previous occurrence of this field, unless this is the first occurrence of this field, in which case the whole value is represented. Always present if there is a source field.
- If present, the zero-based starting column of the line in the source represented. This field is a base 64 VLQ relative to the previous occurrence of this field, unless this is the first occurrence of this field, in which case the whole value is represented. Always present if there is a source field.
- */
+ /* If present, an zero-based index into the "sources" list. This field is a base 64 VLQ relative to the previous occurrence of this field, unless this is the first occurrence of this field, in which case the whole value is represented.
+ If present, the zero-based starting line in the original source represented. This field is a base 64 VLQ relative to the previous occurrence of this field, unless this is the first occurrence of this field, in which case the whole value is represented. Always present if there is a source field.
+ If present, the zero-based starting column of the line in the source represented. This field is a base 64 VLQ relative to the previous occurrence of this field, unless this is the first occurrence of this field, in which case the whole value is represented. Always present if there is a source field.
+ */
Index sourceFileDelta;
Index sourceLineDelta;
@@ -402,7 +388,9 @@ SlangResult SourceMap::decode(JSONContainer* container, JSONValue root, Diagnost
// 5 parts
if (segment.getLength() > 0)
{
- /* If present, the zero - based index into the “names” list associated with this segment.This field is a base 64 VLQ relative to the previous occurrence of this field, unless this is the first occurrence of this field, in which case the whole value is represented.
+ /* If present, the zero - based index into the "names" list associated with this segment.
+ This field is a base 64 VLQ relative to the previous occurrence of this field, unless this is the first occurrence
+ of this field, in which case the whole value is represented.
*/
Index nameDelta;
diff --git a/source/compiler-core/slang-source-map.h b/source/compiler-core/slang-source-map.h
index 739cf1992..accd2473f 100644
--- a/source/compiler-core/slang-source-map.h
+++ b/source/compiler-core/slang-source-map.h
@@ -43,7 +43,7 @@ struct SourceMap : public RefObject
SlangResult encode(JSONContainer* container, DiagnosticSink* sink, JSONValue& outValue);
/// Get the total number of generated lines
- Count getGeneratedLineCount() const { return m_lineStarts.getCount() - 1; }
+ Count getGeneratedLineCount() const { return m_lineStarts.getCount(); }
/// Get the entries on the line
SLANG_FORCE_INLINE ConstArrayView<Entry> getEntriesForLine(Index generatedLine) const;
@@ -53,7 +53,7 @@ struct SourceMap : public RefObject
void advanceToLine(Index lineIndex);
/// Add an entry to the current line
- void addEntry(const Entry& entry);
+ void addEntry(const Entry& entry) { m_lineEntries.add(entry); }
/// Given the slice returns the index
Index getSourceFileIndex(const UnownedStringSlice& slice);
@@ -91,18 +91,20 @@ struct SourceMap : public RefObject
// -------------------------------------------------------------
SLANG_FORCE_INLINE ConstArrayView<SourceMap::Entry> SourceMap::getEntriesForLine(Index generatedLine) const
{
+ SLANG_ASSERT(generatedLine >= 0 && generatedLine < m_lineStarts.getCount());
+
const Index start = m_lineStarts[generatedLine];
- const Index end = m_lineStarts[generatedLine + 1];
-
+
+ const Index end = (generatedLine + 1 >= m_lineStarts.getCount()) ? m_lineEntries.getCount() : m_lineStarts[generatedLine + 1];
+
const auto entries = m_lineEntries.begin();
- SLANG_ASSERT(start >= 0 && start < m_lineEntries.getCount());
+ SLANG_ASSERT(start >= 0 && start <= m_lineEntries.getCount());
SLANG_ASSERT(end >= start && end >= 0 && end <= m_lineEntries.getCount());
return ConstArrayView<SourceMap::Entry>(entries + start, end - start);
}
-
} // namespace Slang
#endif // SLANG_COMPILER_CORE_SOURCE_MAP_H
diff --git a/source/slang/slang-compiler.cpp b/source/slang/slang-compiler.cpp
index f2ebefb9d..8cbe12ef0 100644
--- a/source/slang/slang-compiler.cpp
+++ b/source/slang/slang-compiler.cpp
@@ -1844,46 +1844,185 @@ namespace Slang
return SLANG_OK;
}
- SlangResult EndToEndCompileRequest::maybeCreateContainer()
+ SlangResult EndToEndCompileRequest::_createContainer()
{
switch (m_containerFormat)
{
- case ContainerFormat::SlangModule:
+ case ContainerFormat::SlangModule:
+ {
+
+ OwnedMemoryStream stream(FileAccess::Write);
+ SlangResult res = writeContainerToStream(&stream);
+ if (SLANG_FAILED(res))
{
- m_containerBlob.setNull();
+ getSink()->diagnose(SourceLoc(), Diagnostics::unableToCreateModuleContainer);
+ return res;
+ }
+
+ // Need to turn into a blob
+ List<uint8_t> blobData;
+ stream.swapContents(blobData);
+
+ auto containerBlob = ListBlob::moveCreate(blobData);
+
+ m_containerArtifact = Artifact::create(ArtifactDesc::make(Artifact::Kind::CompileBinary, ArtifactPayload::SlangIR, ArtifactStyle::Unknown));
+
+ m_containerArtifact->addRepresentationUnknown(containerBlob);
+
+ return res;
+ }
+ default: break;
+ }
+ return SLANG_OK;
+ }
+
+ SlangResult EndToEndCompileRequest::_completeContainer()
+ {
+ SLANG_ASSERT(m_containerArtifact);
+ if (!m_containerArtifact)
+ {
+ return SLANG_FAIL;
+ }
+
+ auto frontEndReq = getFrontEndReq();
- OwnedMemoryStream stream(FileAccess::Write);
- SlangResult res = writeContainerToStream(&stream);
- if (SLANG_FAILED(res))
+ auto sourceManager = frontEndReq->getSourceManager();
+ auto sink = getSink();
+
+ for (auto translationUnit : frontEndReq->translationUnits)
+ {
+ // Hmmm do I have to therefore add a map for all translation units(!)
+ // I guess this is okay in so far as an association can always be looked up by name
+
+ auto sourceMap = translationUnit->getModule()->getIRModule()->getObfuscatedSourceMap();
+
+ if (sourceMap)
+ {
+ // Write it out
+ String json;
{
- getSink()->diagnose(SourceLoc(), Diagnostics::unableToCreateModuleContainer);
- return res;
+ RefPtr<JSONContainer> jsonContainer(new JSONContainer(sourceManager));
+
+ JSONValue jsonValue;
+
+ SLANG_RETURN_ON_FAIL(sourceMap->encode(jsonContainer, sink, jsonValue));
+
+ // Convert into a string
+ JSONWriter writer(JSONWriter::IndentationStyle::Allman);
+ jsonContainer->traverseRecursively(jsonValue, &writer);
+
+ json = writer.getBuilder();
}
- // Need to turn into a blob
- List<uint8_t> blobData;
- stream.swapContents(blobData);
+ auto jsonSourceMapBlob = StringBlob::moveCreate(json);
- m_containerBlob = ListBlob::moveCreate(blobData);
+ auto artifactDesc = ArtifactDesc::make(ArtifactKind::Json, ArtifactPayload::SourceMap, ArtifactStyle::Obfuscated);
- return res;
+ // Create the source map artifact
+ auto sourceMapArtifact = Artifact::create(artifactDesc, sourceMap->m_file.getUnownedSlice());
+ sourceMapArtifact->addRepresentationUnknown(jsonSourceMapBlob);
+
+ // Associate with the container
+ m_containerArtifact->addAssociated(sourceMapArtifact);
}
- default: break;
}
+
+ return SLANG_OK;
+ }
+
+ SlangResult EndToEndCompileRequest::maybeCreateContainer()
+ {
+ m_containerArtifact.setNull();
+
+ if (m_containerFormat == ContainerFormat::None)
+ {
+ return SLANG_OK;
+ }
+
+ // Create the container
+ SLANG_RETURN_ON_FAIL(_createContainer());
+
+ // Associate any other stuff with the container artifact
+ SLANG_RETURN_ON_FAIL(_completeContainer());
+
return SLANG_OK;
}
SlangResult EndToEndCompileRequest::maybeWriteContainer(const String& fileName)
{
// If there is no container, or filename, don't write anything
- if (fileName.getLength() == 0 || !m_containerBlob)
+ if (fileName.getLength() == 0 || !m_containerArtifact)
{
return SLANG_OK;
}
- FileStream stream;
- SLANG_RETURN_ON_FAIL(stream.init(fileName, FileMode::Create, FileAccess::Write, FileShare::ReadWrite));
- SLANG_RETURN_ON_FAIL(stream.write(m_containerBlob->getBufferPointer(), m_containerBlob->getBufferSize()));
+ ComPtr<ISlangBlob> containerBlob;
+ SLANG_RETURN_ON_FAIL(m_containerArtifact->loadBlob(ArtifactKeep::Yes, containerBlob.writeRef()));
+
+ {
+ FileStream stream;
+ SLANG_RETURN_ON_FAIL(stream.init(fileName, FileMode::Create, FileAccess::Write, FileShare::ReadWrite));
+ SLANG_RETURN_ON_FAIL(stream.write(containerBlob->getBufferPointer(), containerBlob->getBufferSize()));
+ }
+
+ auto parentPath = Path::getParentDirectory(fileName);
+
+ // Lets look to see if we have any maps
+ {
+ Index nameCount = 0;
+
+ auto associated = m_containerArtifact->getAssociated();
+ const Count count = associated->getCount();
+ for (Index i = 0; i < count; ++i)
+ {
+ IArtifact* artifact = as<IArtifact>(associated->getAt(i));
+ auto desc = artifact->getDesc();
+
+ if (isDerivedFrom(desc.payload, ArtifactPayload::SourceMap))
+ {
+ StringBuilder artifactFilename;
+
+ // Dump out
+ const char* artifactName = artifact->getName();
+ if (artifactName && artifactName[0] != 0)
+ {
+ SLANG_RETURN_ON_FAIL(ArtifactUtil::calcName(artifact, UnownedStringSlice(artifactName), artifactFilename));
+ }
+ else
+ {
+ // Perhaps we can generate the name from the output basename
+ StringBuilder baseName;
+
+ baseName << Path::getFileNameWithoutExt(m_containerOutputPath);
+
+ if (nameCount != 0)
+ {
+ baseName.appendChar('-');
+ baseName.append(nameCount);
+ }
+
+ SLANG_RETURN_ON_FAIL(ArtifactUtil::calcName(artifact, baseName.getUnownedSlice(), artifactFilename));
+
+ nameCount ++;
+ }
+
+ ComPtr<ISlangBlob> blob;
+ SLANG_RETURN_ON_FAIL(artifact->loadBlob(ArtifactKeep::No, blob.writeRef()));
+
+ // Try to write it out
+ {
+ // Work out the path to the artifact
+ auto artifactPath = Path::combine(parentPath, artifactFilename);
+
+ // Write out the map
+ FileStream stream;
+ SLANG_RETURN_ON_FAIL(stream.init(artifactPath, FileMode::Create, FileAccess::Write, FileShare::ReadWrite));
+ SLANG_RETURN_ON_FAIL(stream.write(blob->getBufferPointer(), blob->getBufferSize()));
+ }
+ }
+ }
+ }
+
return SLANG_OK;
}
diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h
index fcf62d826..b287b21eb 100755
--- a/source/slang/slang-compiler.h
+++ b/source/slang/slang-compiler.h
@@ -2625,9 +2625,9 @@ namespace Slang
// If it's set to a format, the container blob will be calculated during compile
ContainerFormat m_containerFormat = ContainerFormat::None;
- /// Where the container blob is stored. This is calculated as part of compile if m_containerFormat is set to
+ /// Where the container is stored. This is calculated as part of compile if m_containerFormat is set to
/// a supported format.
- ComPtr<ISlangBlob> m_containerBlob;
+ ComPtr<IArtifact> m_containerArtifact;
// Path to output container to
String m_containerOutputPath;
@@ -2775,6 +2775,9 @@ namespace Slang
void init();
+ SlangResult _createContainer();
+ SlangResult _completeContainer();
+
Session* m_session = nullptr;
RefPtr<Linkage> m_linkage;
DiagnosticSink m_sink;
diff --git a/source/slang/slang-ir-obfuscate-loc.cpp b/source/slang/slang-ir-obfuscate-loc.cpp
index b3f5d5cd3..79e855633 100644
--- a/source/slang/slang-ir-obfuscate-loc.cpp
+++ b/source/slang/slang-ir-obfuscate-loc.cpp
@@ -72,13 +72,14 @@ SlangResult obfuscateModuleLocs(IRModule* module, SourceManager* sourceManager)
List<LocPair> locPairs;
+ // We want the hash to be stable. One problem is the source locs depend on their order of inclusion.
+ // To work around this we are going
{
+ SourceView* sourceView = nullptr;
+
SourceLoc curLoc;
for (const auto& instWithLoc : instWithLocs)
{
- hash = combineHash(hash, getHashCode(instWithLoc.inst));
- hash = combineHash(hash, getHashCode(instWithLoc.loc.getRaw()));
-
if (instWithLoc.loc != curLoc)
{
LocPair locPair;
@@ -87,6 +88,21 @@ SlangResult obfuscateModuleLocs(IRModule* module, SourceManager* sourceManager)
// This is the current loc
curLoc = instWithLoc.loc;
+
+ // If the loc isn't in the view, lookup the view it is in
+ if (sourceView == nullptr ||
+ !sourceView->getRange().contains(curLoc))
+ {
+ sourceView = sourceManager->findSourceViewRecursively(curLoc);
+ SLANG_ASSERT(sourceView);
+
+ // Combine the name
+ hash = combineHash(hash, getHashCode(sourceView->getViewPathInfo().getName().getUnownedSlice()));
+ }
+ SLANG_ASSERT(sourceView);
+
+ // We combine the *offset* which is stable
+ hash = combineHash(hash, getHashCode(sourceView->getRange().getOffset(curLoc)));
}
}
}
@@ -128,6 +144,12 @@ SlangResult obfuscateModuleLocs(IRModule* module, SourceManager* sourceManager)
SourceFile* obfuscatedFile = sourceManager->createSourceFileWithSize(obfusctatedPathInfo, uniqueLocCount);
+ // We have only one line for all locs, just set up that way...
+ {
+ const uint32_t offsets[2] = { 0, uint32_t(uniqueLocCount) };
+ obfuscatedFile->setLineBreakOffsets(offsets, SLANG_COUNT_OF(offsets));
+ }
+
// Create the view we are going to use from the obfusctated "file".
SourceView* obfuscatedView = sourceManager->createSourceView(obfuscatedFile, nullptr, SourceLoc());
diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp
index bd19670d9..79f9e56ba 100644
--- a/source/slang/slang.cpp
+++ b/source/slang/slang.cpp
@@ -2647,6 +2647,7 @@ SlangResult EndToEndCompileRequest::executeActionsInner()
m_specializedEntryPoints = getFrontEndReq()->getUnspecializedEntryPoints();
SLANG_RETURN_ON_FAIL(maybeCreateContainer());
+
SLANG_RETURN_ON_FAIL(maybeWriteContainer(m_containerOutputPath));
return SLANG_OK;
@@ -5231,10 +5232,14 @@ char const* EndToEndCompileRequest::getEntryPointSource(int entryPointIndex)
void const* EndToEndCompileRequest::getCompileRequestCode(size_t* outSize)
{
- if (m_containerBlob)
+ if (m_containerArtifact)
{
- *outSize = m_containerBlob->getBufferSize();
- return m_containerBlob->getBufferPointer();
+ ComPtr<ISlangBlob> containerBlob;
+ if (SLANG_SUCCEEDED(m_containerArtifact->loadBlob(ArtifactKeep::Yes, containerBlob.writeRef())))
+ {
+ *outSize = containerBlob->getBufferSize();
+ return containerBlob->getBufferPointer();
+ }
}
// Container blob does not have any contents
@@ -5244,14 +5249,15 @@ void const* EndToEndCompileRequest::getCompileRequestCode(size_t* outSize)
SlangResult EndToEndCompileRequest::getContainerCode(ISlangBlob** outBlob)
{
- ISlangBlob* containerBlob = m_containerBlob;
- if (containerBlob)
+ if (m_containerArtifact)
{
- containerBlob->addRef();
- *outBlob = containerBlob;
- return SLANG_OK;
+ ComPtr<ISlangBlob> containerBlob;
+ if (SLANG_SUCCEEDED(m_containerArtifact->loadBlob(ArtifactKeep::Yes, containerBlob.writeRef())))
+ {
+ *outBlob = containerBlob.detach();
+ return SLANG_OK;
+ }
}
-
return SLANG_FAIL;
}
diff --git a/tests/serialization/obfuscated-serialized-module-test.slang b/tests/serialization/obfuscated-serialized-module-test.slang
new file mode 100644
index 000000000..55a68f6c2
--- /dev/null
+++ b/tests/serialization/obfuscated-serialized-module-test.slang
@@ -0,0 +1,35 @@
+// obfuscated-serialized-module-test.slang
+
+// A test to try out the basics of module
+// serialization, obfuscation and source maps.
+
+//TEST:COMPILE: tests/serialization/serialized-module.slang -o tests/serialization/obfuscated-serialized-module.slang-module -obfuscate -source-map
+//TEST:COMPARE_COMPUTE_EX:-slang -compute -Xslang... -r tests/serialization/obfuscated-serialized-module.slang-module -obfuscate -source-map -X. -shaderobj
+
+//import obfuscated_serialized_module;
+
+// This is fragile - needs match the definition in obfuscated_serialized_module
+struct Thing
+{
+ int a;
+ int b;
+};
+
+// TODO: need to get the name mangling to line up!
+int foo(Thing thing);
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0 ], stride=4):out,name outputBuffer
+RWStructuredBuffer<int> outputBuffer;
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ Thing thing;
+
+ int index = (int)dispatchThreadID.x;
+
+ thing.a = index;
+ thing.b = -index;
+
+ outputBuffer[index] = foo(thing);
+}
diff --git a/tests/serialization/obfuscated-serialized-module-test.slang.1.expected.txt b/tests/serialization/obfuscated-serialized-module-test.slang.1.expected.txt
new file mode 100644
index 000000000..bc856dafa
--- /dev/null
+++ b/tests/serialization/obfuscated-serialized-module-test.slang.1.expected.txt
@@ -0,0 +1,4 @@
+0
+1
+2
+3
diff --git a/tests/serialization/obfuscated-serialized-module-test.slang.2.expected.txt b/tests/serialization/obfuscated-serialized-module-test.slang.2.expected.txt
new file mode 100644
index 000000000..bc856dafa
--- /dev/null
+++ b/tests/serialization/obfuscated-serialized-module-test.slang.2.expected.txt
@@ -0,0 +1,4 @@
+0
+1
+2
+3
diff --git a/tests/serialization/obfuscated-serialized-module.slang b/tests/serialization/obfuscated-serialized-module.slang
new file mode 100644
index 000000000..17ddfa662
--- /dev/null
+++ b/tests/serialization/obfuscated-serialized-module.slang
@@ -0,0 +1,15 @@
+//TEST_IGNORE_FILE:
+
+// obfuscated-serialized-module.slang
+
+struct Thing
+{
+ int a;
+ int b;
+};
+
+int foo(Thing thing)
+{
+ return (thing.a + thing.b) - thing.b;
+}
+