diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2019-07-17 10:26:37 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-07-17 10:26:37 -0400 |
| commit | 749634a2a6e03acf435c39f78b933a01b90a7440 (patch) | |
| tree | 950203d3fc29610428b0ca03eb756911b9b11f47 | |
| parent | f52f5cd4a7b5b71617b949fc62a78abe8c4822b3 (diff) | |
Slang -> C++ -> SharedLibrary -> Test (#999)
* WIP: Adding support for C/C++ compilation to slang API.
* Removed BackEndType in test harness -> use SlangPassThrough to identify backends
Only require stage for targets that require it.
Detection of all different backends.
* Windows/Unix create temporary filename.
* WIP: Output CPU binaries.
* Added a pass-through c/c++ test.
* Compile C++/C and store in temporary file.
* Read the binary back into memory.
* Set debug info and optimization flags for C/C++.
Make the CPPCompiler debug/optimization levels match slangs.
* Handling of include paths and math precision.
* Dumping c++/c source and exe/shared library.
* Put hex dump into own util.
* End to end pass through c compilation test.
* WIP: Simple execute test working on Linux/Unix.
* Fix typo on linux.
* WIP: To compile slang to cpp shared library. Report backend compiler errors.
* Compiles slang -> cpp and loads as shared library.
* Fix problem on c-cross-compile test because prelude is now included with <> quotes.
* Run slang generated cpp code - using hard coded data.
* Added cpp-execute-simple, and test output.
* Fix warning that broke win32 build.
* Fix compilation problem on osx.
36 files changed, 1786 insertions, 210 deletions
@@ -514,6 +514,8 @@ extern "C" SLANG_DXIL_ASM, SLANG_C_SOURCE, ///< The C language SLANG_CPP_SOURCE, ///< The C++ language + SLANG_EXECUTABLE, ///< Executable (for hosting CPU/OS) + SLANG_SHARED_LIBRARY, ///< A shared library/Dll (for hosting CPU/OS) }; /* A "container format" describes the way that the outputs @@ -537,6 +539,11 @@ extern "C" SLANG_PASS_THROUGH_FXC, SLANG_PASS_THROUGH_DXC, SLANG_PASS_THROUGH_GLSLANG, + SLANG_PASS_THROUGH_CLANG, ///< Clang C/C++ compiler + SLANG_PASS_THROUGH_VISUAL_STUDIO, ///< Visual studio C/C++ compiler + SLANG_PASS_THROUGH_GCC, ///< GCC C/C++ compiler + SLANG_PASS_THROUGH_GENERIC_C_CPP, ///< Generic C or C++ compiler, which is decided by the source type + SLANG_PASS_THROUGH_COUNT_OF, }; /*! @@ -601,6 +608,8 @@ extern "C" SLANG_SOURCE_LANGUAGE_SLANG, SLANG_SOURCE_LANGUAGE_HLSL, SLANG_SOURCE_LANGUAGE_GLSL, + SLANG_SOURCE_LANGUAGE_C, + SLANG_SOURCE_LANGUAGE_CPP, }; typedef unsigned int SlangProfileID; @@ -859,7 +868,7 @@ extern "C" typedef void(*SlangFuncPtr)(void); - /** An interface that can be used to encapsulate access to a shared library. An implementaion + /** An interface that can be used to encapsulate access to a shared library. An implementation does not have to implement the library as a shared library. */ struct ISlangSharedLibrary: public ISlangUnknown diff --git a/source/core/core.vcxproj b/source/core/core.vcxproj index 9238cf058..a5a6c1b24 100644 --- a/source/core/core.vcxproj +++ b/source/core/core.vcxproj @@ -182,6 +182,7 @@ <ClInclude Include="slang-free-list.h" /> <ClInclude Include="slang-gcc-compiler-util.h" /> <ClInclude Include="slang-hash.h" /> + <ClInclude Include="slang-hex-dump-util.h" /> <ClInclude Include="slang-io.h" /> <ClInclude Include="slang-list.h" /> <ClInclude Include="slang-math.h" /> @@ -213,6 +214,7 @@ <ClCompile Include="slang-cpp-compiler.cpp" /> <ClCompile Include="slang-free-list.cpp" /> <ClCompile Include="slang-gcc-compiler-util.cpp" /> + <ClCompile Include="slang-hex-dump-util.cpp" /> <ClCompile Include="slang-io.cpp" /> <ClCompile Include="slang-memory-arena.cpp" /> <ClCompile Include="slang-object-scope-manager.cpp" /> diff --git a/source/core/core.vcxproj.filters b/source/core/core.vcxproj.filters index 3b3e429ba..fa4101506 100644 --- a/source/core/core.vcxproj.filters +++ b/source/core/core.vcxproj.filters @@ -45,6 +45,9 @@ <ClInclude Include="slang-hash.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="slang-hex-dump-util.h"> + <Filter>Header Files</Filter> + </ClInclude> <ClInclude Include="slang-io.h"> <Filter>Header Files</Filter> </ClInclude> @@ -134,6 +137,9 @@ <ClCompile Include="slang-gcc-compiler-util.cpp"> <Filter>Source Files</Filter> </ClCompile> + <ClCompile Include="slang-hex-dump-util.cpp"> + <Filter>Source Files</Filter> + </ClCompile> <ClCompile Include="slang-io.cpp"> <Filter>Source Files</Filter> </ClCompile> diff --git a/source/core/slang-cpp-compiler.cpp b/source/core/slang-cpp-compiler.cpp index 0ed0a1ba8..54138ab27 100644 --- a/source/core/slang-cpp-compiler.cpp +++ b/source/core/slang-cpp-compiler.cpp @@ -19,6 +19,17 @@ namespace Slang { +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CPPCompiler::Desc !!!!!!!!!!!!!!!!!!!!!!*/ + +void CPPCompiler::Desc::appendAsText(StringBuilder& out) const +{ + out << getCompilerTypeAsText(type); + out << " "; + out << majorVersion; + out << "."; + out << minorVersion; +} + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CPPCompiler::OutputMessage !!!!!!!!!!!!!!!!!!!!!!*/ /* static */UnownedStringSlice CPPCompiler::OutputMessage::getTypeText(OutputMessage::Type type) @@ -33,6 +44,21 @@ namespace Slang } } +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CPPCompiler !!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ + +/* static */UnownedStringSlice CPPCompiler::getCompilerTypeAsText(CompilerType type) +{ + switch (type) + { + default: + case CompilerType::Unknown: return UnownedStringSlice::fromLiteral("Unknown"); + case CompilerType::VisualStudio:return UnownedStringSlice::fromLiteral("Visual Studio"); + case CompilerType::GCC: return UnownedStringSlice::fromLiteral("GCC"); + case CompilerType::Clang: return UnownedStringSlice::fromLiteral("Clang"); + case CompilerType::SNC: return UnownedStringSlice::fromLiteral("SNC"); + case CompilerType::GHS: return UnownedStringSlice::fromLiteral("GHS"); + } +} /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CPPCompiler::Output !!!!!!!!!!!!!!!!!!!!!!*/ @@ -167,6 +193,11 @@ SlangResult GenericCPPCompiler::compile(const CompileOptions& options, Output& o return m_parseOutputFunc(exeRes, outOutput); } +SlangResult GenericCPPCompiler::calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath) +{ + return m_calcModuleFilePathFunc(options, outPath); +} + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CPPCompilerUtil !!!!!!!!!!!!!!!!!!!!!!*/ static CPPCompiler::Desc _calcCompiledWithDesc() @@ -308,7 +339,7 @@ static void _addGCCFamilyCompiler(const String& exeName, CPPCompilerSet* compile CPPCompiler::Desc desc; if (SLANG_SUCCEEDED(GCCCompilerUtil::calcVersion(exeName, desc))) { - RefPtr<CPPCompiler> compiler(new GenericCPPCompiler(desc, exeName, &GCCCompilerUtil::calcArgs, &GCCCompilerUtil::parseOutput)); + RefPtr<CPPCompiler> compiler(new GenericCPPCompiler(desc, exeName, &GCCCompilerUtil::calcArgs, &GCCCompilerUtil::parseOutput, GCCCompilerUtil::calcModuleFilePath)); compilerSet->addCompiler(compiler); } } @@ -377,6 +408,19 @@ void CPPCompilerSet::getCompilers(List<CPPCompiler*>& outCompilers) const outCompilers.addRange((CPPCompiler*const*)m_compilers.begin(), m_compilers.getCount()); } +bool CPPCompilerSet::hasCompiler(CPPCompiler::CompilerType compilerType) const +{ + for (CPPCompiler* compiler : m_compilers) + { + const auto& desc = compiler->getDesc(); + if (desc.type == compilerType) + { + return true; + } + } + return false; +} + void CPPCompilerSet::addCompiler(CPPCompiler* compiler) { const Index index = _findIndex(compiler->getDesc()); diff --git a/source/core/slang-cpp-compiler.h b/source/core/slang-cpp-compiler.h index 60b534556..851975d72 100644 --- a/source/core/slang-cpp-compiler.h +++ b/source/core/slang-cpp-compiler.h @@ -40,6 +40,8 @@ public: /// Get the version as a value Int getVersionValue() const { return majorVersion * 100 + minorVersion; } + void appendAsText(StringBuilder& out) const; + /// Ctor Desc(CompilerType inType = CompilerType::Unknown, Int inMajorVersion = 0, Int inMinorVersion = 0):type(inType), majorVersion(inMajorVersion), minorVersion(inMinorVersion) {} @@ -50,16 +52,26 @@ public: enum class OptimizationLevel { - Normal, ///< Normal optimization - Debug, ///< General has no optimizations + None, ///< Don't optimize at all. + Default, ///< Default optimization level: balance code quality and compilation time. + High, ///< Optimize aggressively. + Maximal, ///< Include optimizations that may take a very long time, or may involve severe space-vs-speed tradeoffs }; - enum DebugInfoType + enum class DebugInfoType + { + None, ///< Don't emit debug information at all. + Minimal, ///< Emit as little debug information as possible, while still supporting stack traces. + Standard, ///< Emit whatever is the standard level of debug information for each target. + Maximal, ///< Emit as much debug information as possible for each target. + }; + enum class FloatingPointMode { - None, ///< Binary has no debug information - Maximum, ///< Has maximum debug information - Normal, ///< Has normal debug information + Default, + Fast, + Precise, }; + enum TargetType { Executable, ///< Produce an executable @@ -84,10 +96,11 @@ public: }; }; - OptimizationLevel optimizationLevel = OptimizationLevel::Debug; - DebugInfoType debugInfoType = DebugInfoType::Normal; + OptimizationLevel optimizationLevel = OptimizationLevel::Default; + DebugInfoType debugInfoType = DebugInfoType::Standard; TargetType targetType = TargetType::Executable; SourceType sourceType = SourceType::CPP; + FloatingPointMode floatingPointMode = FloatingPointMode::Default; Flags flags = Flag::EnableExceptionHandling; @@ -163,6 +176,11 @@ public: const Desc& getDesc() const { return m_desc; } /// Compile using the specified options. The result is in resOut virtual SlangResult compile(const CompileOptions& options, Output& outOutput) = 0; + /// Given the compilation options and the module name, determines the actual file name used for output + virtual SlangResult calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath) = 0; + + /// Return the compiler type as name + static UnownedStringSlice getCompilerTypeAsText(CompilerType type); protected: @@ -180,26 +198,31 @@ public: typedef void(*CalcArgsFunc)(const CPPCompiler::CompileOptions& options, CommandLine& cmdLine); typedef SlangResult(*ParseOutputFunc)(const ExecuteResult& exeResult, Output& output); + typedef SlangResult(*CalcModuleFilePathFunc)(const CPPCompiler::CompileOptions& options, StringBuilder& outPath); virtual SlangResult compile(const CompileOptions& options, Output& outOutput) SLANG_OVERRIDE; + virtual SlangResult calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath) SLANG_OVERRIDE; - GenericCPPCompiler(const Desc& desc, const String& exeName, CalcArgsFunc calcArgsFunc, ParseOutputFunc parseOutputFunc) : + GenericCPPCompiler(const Desc& desc, const String& exeName, CalcArgsFunc calcArgsFunc, ParseOutputFunc parseOutputFunc, CalcModuleFilePathFunc calcModuleFilePathFunc) : Super(desc), m_calcArgsFunc(calcArgsFunc), - m_parseOutputFunc(parseOutputFunc) + m_parseOutputFunc(parseOutputFunc), + m_calcModuleFilePathFunc(calcModuleFilePathFunc) { m_cmdLine.setExecutableFilename(exeName); } - GenericCPPCompiler(const Desc& desc, const CommandLine& cmdLine, CalcArgsFunc calcArgsFunc, ParseOutputFunc parseOutputFunc) : + GenericCPPCompiler(const Desc& desc, const CommandLine& cmdLine, CalcArgsFunc calcArgsFunc, ParseOutputFunc parseOutputFunc, CalcModuleFilePathFunc calcModuleFilePathFunc) : Super(desc), m_cmdLine(cmdLine), m_calcArgsFunc(calcArgsFunc), - m_parseOutputFunc(parseOutputFunc) + m_parseOutputFunc(parseOutputFunc), + m_calcModuleFilePathFunc(calcModuleFilePathFunc) {} CalcArgsFunc m_calcArgsFunc; ParseOutputFunc m_parseOutputFunc; + CalcModuleFilePathFunc m_calcModuleFilePathFunc; CommandLine m_cmdLine; }; @@ -225,6 +248,9 @@ public: /// Set the default compiler void setDefaultCompiler(CPPCompiler* compiler) { m_defaultCompiler = compiler; } + /// True if has a compiler of the specified type + bool hasCompiler(CPPCompiler::CompilerType compilerType) const; + protected: Index _findIndex(const CPPCompiler::Desc& desc) const; @@ -263,6 +289,9 @@ struct CPPCompilerUtil /// Given a set, registers compilers found through standard means and determines a reasonable default compiler if possible static SlangResult initializeSet(CPPCompilerSet* set); + + + }; diff --git a/source/core/slang-gcc-compiler-util.cpp b/source/core/slang-gcc-compiler-util.cpp index d210fa829..69d68e608 100644 --- a/source/core/slang-gcc-compiler-util.cpp +++ b/source/core/slang-gcc-compiler-util.cpp @@ -335,6 +335,34 @@ static SlangResult _parseGCCFamilyLine(const UnownedStringSlice& line, LineParse return SLANG_OK; } +/* static */ SlangResult GCCCompilerUtil::calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath) +{ + outPath.Clear(); + + switch (options.targetType) + { + case TargetType::SharedLibrary: + { + outPath << SharedLibrary::calcPlatformPath(options.modulePath.getUnownedSlice()); + return SLANG_OK; + } + case TargetType::Executable: + { + outPath << options.modulePath; + outPath << ProcessUtil::getExecutableSuffix(); + return SLANG_OK; + } + case TargetType::Object: + { + // Will be .o for typical gcc targets + outPath << options.modulePath << ".o"; + return SLANG_OK; + } + } + + return SLANG_FAIL; +} + /* static */void GCCCompilerUtil::calcArgs(const CompileOptions& options, CommandLine& cmdLine) { cmdLine.addArg("-fvisibility=hidden"); @@ -349,17 +377,27 @@ static SlangResult _parseGCCFamilyLine(const UnownedStringSlice& line, LineParse switch (options.optimizationLevel) { - case OptimizationLevel::Debug: + case OptimizationLevel::None: { // No optimization cmdLine.addArg("-O0"); break; } - case OptimizationLevel::Normal: + case OptimizationLevel::Default: { cmdLine.addArg("-Os"); break; } + case OptimizationLevel::High: + { + cmdLine.addArg("-O2"); + break; + } + case OptimizationLevel::Maximal: + { + cmdLine.addArg("-O4"); + break; + } default: break; } @@ -368,6 +406,29 @@ static SlangResult _parseGCCFamilyLine(const UnownedStringSlice& line, LineParse cmdLine.addArg("-g"); } + switch (options.floatingPointMode) + { + case FloatingPointMode::Default: break; + case FloatingPointMode::Precise: + { + //cmdLine.addArg("-fno-unsafe-math-optimizations"); + break; + } + case FloatingPointMode::Fast: + { + // We could enable SSE with -mfpmath=sse + // But that would only make sense on a x64/x86 type processor and only if that feature is present (it is on all x64) + cmdLine.addArg("-ffast-math"); + break; + } + } + + StringBuilder moduleFilePath; + calcModuleFilePath(options, moduleFilePath); + + cmdLine.addArg("-o"); + cmdLine.addArg(moduleFilePath); + switch (options.targetType) { case TargetType::SharedLibrary: @@ -376,22 +437,10 @@ static SlangResult _parseGCCFamilyLine(const UnownedStringSlice& line, LineParse cmdLine.addArg("-shared"); // Position independent cmdLine.addArg("-fPIC"); - - String sharedLibraryPath = SharedLibrary::calcPlatformPath(options.modulePath.getUnownedSlice()); - - cmdLine.addArg("-o"); - cmdLine.addArg(sharedLibraryPath); break; } case TargetType::Executable: { - cmdLine.addArg("-o"); - - StringBuilder builder; - builder << options.modulePath; - builder << ProcessUtil::getExecutableSuffix(); - - cmdLine.addArg(options.modulePath); break; } case TargetType::Object: diff --git a/source/core/slang-gcc-compiler-util.h b/source/core/slang-gcc-compiler-util.h index 29070fc61..a72080acc 100644 --- a/source/core/slang-gcc-compiler-util.h +++ b/source/core/slang-gcc-compiler-util.h @@ -14,6 +14,7 @@ struct GCCCompilerUtil typedef CPPCompiler::TargetType TargetType; typedef CPPCompiler::DebugInfoType DebugInfoType; typedef CPPCompiler::SourceType SourceType; + typedef CPPCompiler::FloatingPointMode FloatingPointMode; /// Extracts version number into desc from text (assumes gcc/clang -v layout with a line with version) static SlangResult parseVersion(const UnownedStringSlice& text, const UnownedStringSlice& prefix, CPPCompiler::Desc& outDesc); @@ -26,6 +27,10 @@ struct GCCCompilerUtil /// Parse ExecuteResult into Output static SlangResult parseOutput(const ExecuteResult& exeRes, CPPCompiler::Output& outOutput); + + /// Calculate the output module filename + static SlangResult calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath); + }; } diff --git a/source/core/slang-hex-dump-util.cpp b/source/core/slang-hex-dump-util.cpp new file mode 100644 index 000000000..908f6c1b6 --- /dev/null +++ b/source/core/slang-hex-dump-util.cpp @@ -0,0 +1,183 @@ +// slang-hex-dump-util.cpp +#include "slang-hex-dump-util.h" + +#include "slang-common.h" +#include "slang-string-util.h" +#include "slang-writer.h" + +#include "../../slang-com-helper.h" + +namespace Slang +{ + +static const UnownedStringSlice s_start = UnownedStringSlice::fromLiteral("--START--"); +static const UnownedStringSlice s_end = UnownedStringSlice::fromLiteral("--END--"); + +/* static */SlangResult HexDumpUtil::dumpWithMarkers(const List<uint8_t>& data, int maxBytesPerLine, ISlangWriter* writer) +{ + WriterHelper helper(writer); + SLANG_RETURN_ON_FAIL(helper.write(s_start.begin(), s_start.size())); + SLANG_RETURN_ON_FAIL(helper.print(" (%zu)\n", size_t(data.getCount()))); + + SLANG_RETURN_ON_FAIL(dump(data, maxBytesPerLine, writer)); + + SLANG_RETURN_ON_FAIL(helper.write(s_end.begin(), s_end.size())); + SLANG_RETURN_ON_FAIL(helper.put("\n")); + return SLANG_OK; +} + +/* static */SlangResult HexDumpUtil::dump(const List<uint8_t>& data, int maxBytesPerLine, ISlangWriter* writer) +{ + int maxCharsPerLine = 2 * maxBytesPerLine + 1 + maxBytesPerLine + 1; + + const uint8_t* cur = data.begin(); + const uint8_t* end = data.end(); + + static char s_hex[] = "0123456789abcdef"; + + while (cur < end) + { + size_t count = size_t(end - cur); + count = (count > size_t(maxBytesPerLine)) ? size_t(maxBytesPerLine) : count; + + char* startDst = writer->beginAppendBuffer(maxCharsPerLine); + char* dst = startDst; + + for (size_t i = 0; i < count; ++i) + { + uint8_t byte = cur[i]; + *dst++ = s_hex[byte >> 4]; + *dst++ = s_hex[byte & 0xf]; + } + + *dst++ = ' '; + + for (size_t i = 0; i < count; ++i) + { + char c = char(cur[i]); + + if ((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= 32 && (c & 0x80) == 0)) + { + } + else + { + c = '.'; + } + *dst++ = c; + } + + *dst++ = '\n'; + SLANG_ASSERT(dst <= startDst + maxCharsPerLine); + + SLANG_RETURN_ON_FAIL(writer->endAppendBuffer(startDst, size_t(dst - startDst))); + + cur += count; + } + + return SLANG_OK; +} + +static int _parseHexDigit(char c) +{ + if (c >= '0' && c <= '9') + { + return c -'0'; + } + else if (c >= 'a' && c <= 'f') + { + return c - 'a' + 10; + } + else if (c >= 'A' && c <= 'F') + { + return c - 'A' + 10; + } + return -1; +} + +/* static */SlangResult HexDumpUtil::parse(const UnownedStringSlice& lines, List<uint8_t>& outBytes) +{ + outBytes.clear(); + + bool inHex = false; + + LineParser lineParser(lines); + for (const auto& line : lineParser) + { + if (!inHex) + { + if (line.startsWith(s_start)) + { + inHex = true; + continue; + } + } + else + { + if (line.startsWith(s_end)) + { + break; + } + } + + const char* cur = line.begin(); + const char* end = line.end(); + + while(cur + 2 <= end) + { + const char c = cur[0]; + if (c == ' ' || c== '\n' || c == '\r' || c == '\t') + { + // Skip to next line + break; + } + + const int hi = _parseHexDigit(c); + const int lo = _parseHexDigit(cur[1]); + cur += 2; + + if (hi < 0 || lo < 0) + { + return SLANG_FAIL; + } + outBytes.add(uint8_t((hi << 4) | lo)); + } + } + + return SLANG_OK; +} + +/* static */SlangResult HexDumpUtil::parseWithMarkers(const UnownedStringSlice& lines, List<uint8_t>& outBytes) +{ + UnownedStringSlice remaining(lines), line; + + while(StringUtil::extractLine(remaining, line)) + { + if (line.startsWith(s_start)) + { + // Extract next line + if (!StringUtil::extractLine(remaining, line)) + { + return SLANG_FAIL; + } + // It's the start line + UnownedStringSlice startLine = line; + + // Look for the ending line + do + { + if (line.startsWith(s_end)) + { + return parse(UnownedStringSlice(startLine.begin(), line.begin()), outBytes); + } + } + while ( StringUtil::extractLine(remaining, line)); + } + } + + return SLANG_FAIL; +} + +} diff --git a/source/core/slang-hex-dump-util.h b/source/core/slang-hex-dump-util.h new file mode 100644 index 000000000..580f5a120 --- /dev/null +++ b/source/core/slang-hex-dump-util.h @@ -0,0 +1,28 @@ +#ifndef SLANG_HEX_DUMP_UTIL_H +#define SLANG_HEX_DUMP_UTIL_H + +#include "slang-common.h" +#include "slang-string.h" + +#include "slang-list.h" +#include "../../slang.h" + +namespace Slang +{ + +struct HexDumpUtil +{ + /// Dump data to writer, with lines starting with hex data + static SlangResult dump(const List<uint8_t>& data, int numBytesPerLine, ISlangWriter* writer); + + static SlangResult dumpWithMarkers(const List<uint8_t>& data, int numBytesPerLine, ISlangWriter* writer); + + /// Parses lines formatted by dump, back into bytes + static SlangResult parse(const UnownedStringSlice& lines, List<uint8_t>& outBytes); + + static SlangResult parseWithMarkers(const UnownedStringSlice& lines, List<uint8_t>& outBytes); +}; + +} + +#endif diff --git a/source/core/slang-io.cpp b/source/core/slang-io.cpp index 6cdd75c74..caa45c8f4 100644 --- a/source/core/slang-io.cpp +++ b/source/core/slang-io.cpp @@ -17,7 +17,7 @@ # include <Windows.h> #endif -#if defined(__linux__) || defined(__CYGWIN__) +#if defined(__linux__) || defined(__CYGWIN__) || SLANG_APPLE_FAMILY # include <unistd.h> #endif @@ -51,6 +51,99 @@ namespace Slang #endif } + +#ifdef _WIN32 + /* static */SlangResult File::generateTemporary(const UnownedStringSlice& inPrefix, Slang::String& outFileName) + { + // https://docs.microsoft.com/en-us/windows/win32/fileio/creating-and-using-a-temporary-file + + String tempPath; + { + int count = MAX_PATH + 1; + while (true) + { + char* chars = tempPath.prepareForAppend(count); + // Gets the temp path env string (no guarantee it's a valid path). + // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppatha + DWORD ret = ::GetTempPathA(count - 1, chars); + if (ret == 0) + { + return SLANG_FAIL; + } + if (ret > DWORD(count - 1)) + { + count = ret + 1; + continue; + } + tempPath.appendInPlace(chars, count); + break; + } + } + + if (!File::exists(tempPath)) + { + return SLANG_FAIL; + } + + const String prefix(inPrefix); + String tempFileName; + + { + int count = MAX_PATH + 1; + char* chars = tempFileName.prepareForAppend(count); + + // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettempfilenamea + // Generates a temporary file name. + DWORD ret = ::GetTempFileNameA(tempPath.getBuffer(), prefix.getBuffer(), 0, chars); + + if (ret == 0) + { + return SLANG_FAIL; + } + tempFileName.appendInPlace(chars, ::strlen(chars)); + } + + outFileName = tempFileName; + return SLANG_OK; + } +#else + /* static */SlangResult File::generateTemporary(const UnownedStringSlice& inPrefix, Slang::String& outFileName) + { + StringBuilder builder; + builder << "/tmp/" << inPrefix << "-XXXXXX"; + + List<char> buffer; + buffer.setCount(builder.getLength() + 1); + ::memcpy(buffer.getBuffer(), builder.getBuffer(), builder.getLength()); + buffer[builder.getLength()] = 0; + + int handle = mkstemp(buffer.getBuffer()); + if (handle == -1) + { + return SLANG_FAIL; + } + + // Close the handle.. + close(handle); + + outFileName = buffer.getBuffer(); + return SLANG_OK; + } +#endif + + /* static */SlangResult File::makeExecutable(const String& fileName) + { +#ifdef _WIN32 + SLANG_UNUSED(fileName); + // As long as file extension is executable, it can be executed + return SLANG_OK; +#else + const int ret = ::chmod(fileName.getBuffer(), S_IXUSR); + return (ret == 0) ? SLANG_OK : SLANG_FAIL; +#endif + } + + bool File::exists(const String& fileName) { #ifdef _WIN32 @@ -585,5 +678,6 @@ namespace Slang writer.Write(text); } + } diff --git a/source/core/slang-io.h b/source/core/slang-io.h index da63e9c20..e2935afe4 100644 --- a/source/core/slang-io.h +++ b/source/core/slang-io.h @@ -16,6 +16,10 @@ namespace Slang static Slang::List<unsigned char> readAllBytes(const Slang::String& fileName); static void writeAllText(const Slang::String& fileName, const Slang::String& text); static SlangResult remove(const String& fileName); + + static SlangResult makeExecutable(const String& fileName); + + static SlangResult generateTemporary(const UnownedStringSlice& prefix, Slang::String& outFileName); }; class Path @@ -85,6 +89,26 @@ namespace Slang /// @return The first element of the path, or empty static UnownedStringSlice getFirstElement(const UnownedStringSlice& path); }; + + // Helper class to clean up temporary files on dtor + struct TemporaryFileSet + { + void add(const String& path) + { + if (m_paths.indexOf(path) < 0) + { + m_paths.add(path); + } + } + ~TemporaryFileSet() + { + for (const auto& path : m_paths) + { + File::remove(path); + } + } + List<String> m_paths; + }; } #endif diff --git a/source/core/slang-string.cpp b/source/core/slang-string.cpp index 8b2a35b3f..22049f82d 100644 --- a/source/core/slang-string.cpp +++ b/source/core/slang-string.cpp @@ -361,6 +361,12 @@ namespace Slang append(&chr, &chr + 1); } + + void String::appendChar(char chr) + { + append(&chr, &chr + 1); + } + void String::append(String const& str) { if (!m_buffer) diff --git a/source/core/slang-string.h b/source/core/slang-string.h index cb2731cee..357eb726e 100644 --- a/source/core/slang-string.h +++ b/source/core/slang-string.h @@ -443,6 +443,9 @@ namespace Slang void append(StringSlice const& slice); void append(UnownedStringSlice const& slice); + /// Append a character (to remove ambiguity with other integral types) + void appendChar(char chr); + String(int32_t val, int radix = 10) { append(val, radix); diff --git a/source/core/slang-visual-studio-compiler-util.cpp b/source/core/slang-visual-studio-compiler-util.cpp index 20d655e78..765edc349 100644 --- a/source/core/slang-visual-studio-compiler-util.cpp +++ b/source/core/slang-visual-studio-compiler-util.cpp @@ -10,6 +10,33 @@ namespace Slang { +/* static */ SlangResult VisualStudioCompilerUtil::calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath) +{ + outPath.Clear(); + + switch (options.targetType) + { + case TargetType::SharedLibrary: + { + outPath << options.modulePath << ".dll"; + return SLANG_OK; + } + case TargetType::Executable: + { + outPath << options.modulePath << ".exe"; + return SLANG_OK; + } + case TargetType::Object: + { + outPath << options.modulePath << ".obj"; + return SLANG_OK; + } + default: break; + } + + return SLANG_FAIL; +} + /* static */void VisualStudioCompilerUtil::calcArgs(const CompileOptions& options, CommandLine& cmdLine) { // https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically?view=vs-2019 @@ -30,30 +57,72 @@ namespace Slang } } + switch (options.debugInfoType) + { + default: + { + // Multithreaded statically linked runtime library + cmdLine.addArg("/MD"); + break; + } + case DebugInfoType::Maximal: + { + // Multithreaded statically linked *debug* runtime library + cmdLine.addArg("/MDd"); + break; + } + } + + // /Fd - followed by name of the pdb file + if (options.debugInfoType != DebugInfoType::None) + { + cmdLine.addPrefixPathArg("/Fd", options.modulePath, ".pdb"); + } + switch (options.optimizationLevel) { - case OptimizationLevel::Debug: + case OptimizationLevel::None: { // No optimization - cmdLine.addArg("/Od"); - - cmdLine.addArg("/MDd"); + cmdLine.addArg("/Od"); + break; + } + case OptimizationLevel::Default: + { break; } - case OptimizationLevel::Normal: + case OptimizationLevel::High: { cmdLine.addArg("/O2"); - // Multithreaded DLL - cmdLine.addArg("/MD"); + break; + } + case OptimizationLevel::Maximal: + { + cmdLine.addArg("/Ox"); break; } default: break; } - // /Fd - followed by name of the pdb file - if (options.debugInfoType != DebugInfoType::None) + switch (options.floatingPointMode) { - cmdLine.addPrefixPathArg("/Fd", options.modulePath, ".pdb"); + case FloatingPointMode::Default: break; + case FloatingPointMode::Precise: + { + // precise is default behavior, VS also has 'strict' + // + // ```/fp:strict has behavior similar to /fp:precise, that is, the compiler preserves the source ordering and rounding properties of floating-point code when + // it generates and optimizes object code for the target machine, and observes the standard when handling special values. In addition, the program may safely + // access or modify the floating-point environment at runtime.``` + + cmdLine.addArg("/fp:precise"); + break; + } + case FloatingPointMode::Fast: + { + cmdLine.addArg("/fp:fast"); + break; + } } switch (options.targetType) @@ -61,7 +130,7 @@ namespace Slang case TargetType::SharedLibrary: { // Create dynamic link library - if (options.optimizationLevel == OptimizationLevel::Debug) + if (options.debugInfoType == DebugInfoType::None) { cmdLine.addArg("/LDd"); } diff --git a/source/core/slang-visual-studio-compiler-util.h b/source/core/slang-visual-studio-compiler-util.h index 86845b5af..cc34b49f5 100644 --- a/source/core/slang-visual-studio-compiler-util.h +++ b/source/core/slang-visual-studio-compiler-util.h @@ -14,11 +14,15 @@ struct VisualStudioCompilerUtil typedef CPPCompiler::DebugInfoType DebugInfoType; typedef CPPCompiler::SourceType SourceType; typedef CPPCompiler::OutputMessage OutputMessage; + typedef CPPCompiler::FloatingPointMode FloatingPointMode; /// Calculate Visual Studio family compilers cmdLine arguments from options static void calcArgs(const CompileOptions& options, CommandLine& cmdLine); /// Parse Visual Studio exeRes into CPPCompiler::Output static SlangResult parseOutput(const ExecuteResult& exeRes, CPPCompiler::Output& outOutput); + + static SlangResult calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath); + }; } diff --git a/source/core/slang-writer.cpp b/source/core/slang-writer.cpp index 5b643fff8..2f694aeec 100644 --- a/source/core/slang-writer.cpp +++ b/source/core/slang-writer.cpp @@ -1,3 +1,5 @@ +#define _CRT_SECURE_NO_WARNINGS + #include "slang-writer.h" #include "slang-platform.h" @@ -166,6 +168,21 @@ SlangResult FileWriter::setMode(SlangWriterMode mode) return SLANG_FAIL; } +/* static */SlangResult FileWriter::create(const char* filePath, const char* writeOptions, WriterFlags flags, ComPtr<ISlangWriter>& outWriter) +{ + flags &= ~WriterFlag::IsUnowned; + + FILE* file = fopen(filePath, writeOptions); + if (!file) + { + return SLANG_E_CANNOT_OPEN; + } + + outWriter = new FileWriter(file, flags); + return SLANG_OK; +} + + /* !!!!!!!!!!!!!!!!!!!!!!!!! StringWriter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/ SLANG_NO_THROW char* SLANG_MCALL StringWriter::beginAppendBuffer(size_t maxNumChars) diff --git a/source/core/slang-writer.h b/source/core/slang-writer.h index cd5737b3d..759644fde 100644 --- a/source/core/slang-writer.h +++ b/source/core/slang-writer.h @@ -4,6 +4,8 @@ #include "slang-string.h" #include "../../slang-com-helper.h" +#include "../../slang-com-ptr.h" + #include "slang-list.h" namespace Slang @@ -118,6 +120,12 @@ public: m_file(file) {} + /// + static SlangResult create(const char* filePath, const char* writeOptions, WriterFlags flags, ComPtr<ISlangWriter>& outWriter); + + static SlangResult createBinary(const char* filePath, WriterFlags flags, ComPtr<ISlangWriter>& outWriter) { return create(filePath, "wb", flags, outWriter); } + static SlangResult createText(const char* filePath, WriterFlags flags, ComPtr<ISlangWriter>& outWriter) { return create(filePath, "w", flags, outWriter); } + /// Dtor ~FileWriter(); diff --git a/source/core/windows/slang-win-visual-studio-util.cpp b/source/core/windows/slang-win-visual-studio-util.cpp index 7e683ee55..a406e168f 100644 --- a/source/core/windows/slang-win-visual-studio-util.cpp +++ b/source/core/windows/slang-win-visual-studio-util.cpp @@ -293,7 +293,7 @@ static SlangResult _find(int versionIndex, WinVisualStudioUtil::VersionPath& out CommandLine cmdLine; calcExecuteCompilerArgs(versionPath, cmdLine); - RefPtr<GenericCPPCompiler> compiler = new GenericCPPCompiler(desc, cmdLine, &VisualStudioCompilerUtil::calcArgs, &VisualStudioCompilerUtil::parseOutput); + RefPtr<GenericCPPCompiler> compiler = new GenericCPPCompiler(desc, cmdLine, &VisualStudioCompilerUtil::calcArgs, &VisualStudioCompilerUtil::parseOutput, &VisualStudioCompilerUtil::calcModuleFilePath); set->addCompiler(compiler); } } diff --git a/source/slang/slang-check.cpp b/source/slang/slang-check.cpp index 83b75b964..a8900986b 100644 --- a/source/slang/slang-check.cpp +++ b/source/slang/slang-check.cpp @@ -381,6 +381,17 @@ namespace Slang return func; } + CPPCompilerSet* Session::requireCPPCompilerSet() + { + if (cppCompilerSet == nullptr) + { + cppCompilerSet = new CPPCompilerSet; + CPPCompilerUtil::initializeSet(cppCompilerSet); + } + SLANG_ASSERT(cppCompilerSet); + return cppCompilerSet; + } + enum class CheckingPhase { diff --git a/source/slang/slang-compiler.cpp b/source/slang/slang-compiler.cpp index ff2facc26..64afef136 100644 --- a/source/slang/slang-compiler.cpp +++ b/source/slang/slang-compiler.cpp @@ -4,6 +4,7 @@ #include "../core/slang-platform.h" #include "../core/slang-io.h" #include "../core/slang-string-util.h" +#include "../core/slang-hex-dump-util.h" #include "slang-compiler.h" #include "slang-lexer.h" @@ -84,7 +85,9 @@ namespace Slang x("dxil", DXIL) \ x("dxil-asm,dxil-assembly", DXILAssembly) \ x("c", CSource) \ - x("cpp", CPPSource) + x("cpp", CPPSource) \ + x("exe,executable", Executable) \ + x("sharedlib,sharedlibrary,dll", SharedLibrary) #define SLANG_CODE_GEN_INFO(names, e) \ { CodeGenTarget::e, UnownedStringSlice::fromLiteral(names) }, @@ -369,6 +372,22 @@ namespace Slang return Stage::Unknown; } + static UnownedStringSlice _getPassThroughAsText(PassThroughMode mode) + { + switch (mode) + { + case PassThroughMode::None: return UnownedStringSlice::fromLiteral("None"); + case PassThroughMode::Dxc: return UnownedStringSlice::fromLiteral("Dxc"); + case PassThroughMode::Fxc: return UnownedStringSlice::fromLiteral("Fxc"); + case PassThroughMode::Glslang: return UnownedStringSlice::fromLiteral("Glslang"); + case PassThroughMode::Clang: return UnownedStringSlice::fromLiteral("Clang"); + case PassThroughMode::VisualStudio: return UnownedStringSlice::fromLiteral("VisualStudio"); + case PassThroughMode::Gcc: return UnownedStringSlice::fromLiteral("GCC"); + case PassThroughMode::GenericCCpp: return UnownedStringSlice::fromLiteral("Generic C/C++ Compiler"); + default: return UnownedStringSlice::fromLiteral("Unknown"); + } + } + SlangResult checkExternalCompilerSupport(Session* session, PassThroughMode passThrough) { switch (passThrough) @@ -378,7 +397,7 @@ namespace Slang // If no pass through -> that will always work! return SLANG_OK; } - case PassThroughMode::dxc: + case PassThroughMode::Dxc: { #if SLANG_ENABLE_DXIL_SUPPORT // Must have dxc @@ -386,7 +405,7 @@ namespace Slang #endif break; } - case PassThroughMode::fxc: + case PassThroughMode::Fxc: { #if SLANG_ENABLE_DXBC_SUPPORT // Must have fxc @@ -394,13 +413,32 @@ namespace Slang #endif break; } - case PassThroughMode::glslang: + case PassThroughMode::Glslang: { #if SLANG_ENABLE_GLSLANG_SUPPORT return session->getOrLoadSharedLibrary(Slang::SharedLibraryType::Glslang, nullptr) ? SLANG_OK : SLANG_E_NOT_FOUND; #endif break; } + case PassThroughMode::Clang: + { + return session->requireCPPCompilerSet()->hasCompiler(CPPCompiler::CompilerType::Clang) ? SLANG_OK: SLANG_E_NOT_FOUND; + } + case PassThroughMode::VisualStudio: + { + return session->requireCPPCompilerSet()->hasCompiler(CPPCompiler::CompilerType::VisualStudio) ? SLANG_OK: SLANG_E_NOT_FOUND; + } + case PassThroughMode::Gcc: + { + return session->requireCPPCompilerSet()->hasCompiler(CPPCompiler::CompilerType::GCC) ? SLANG_OK: SLANG_E_NOT_FOUND; + } + case PassThroughMode::GenericCCpp: + { + List<CPPCompiler::Desc> descs; + session->requireCPPCompilerSet()->getCompilerDescs(descs); + + return descs.getCount() ? SLANG_OK: SLANG_E_NOT_FOUND; + } } return SLANG_E_NOT_IMPLEMENTED; } @@ -428,17 +466,17 @@ namespace Slang case CodeGenTarget::SPIRVAssembly: case CodeGenTarget::SPIRV: { - return PassThroughMode::glslang; + return PassThroughMode::Glslang; } case CodeGenTarget::DXBytecode: case CodeGenTarget::DXBytecodeAssembly: { - return PassThroughMode::fxc; + return PassThroughMode::Fxc; } case CodeGenTarget::DXIL: case CodeGenTarget::DXILAssembly: { - return PassThroughMode::dxc; + return PassThroughMode::Dxc; } case CodeGenTarget::CPPSource: case CodeGenTarget::CSource: @@ -446,6 +484,12 @@ namespace Slang // Don't need an external compiler to output C and C++ code return PassThroughMode::None; } + case CodeGenTarget::SharedLibrary: + case CodeGenTarget::Executable: + { + // We need some C/C++ compiler + return PassThroughMode::GenericCCpp; + } default: break; } @@ -485,6 +529,27 @@ namespace Slang return translationUnit; } + static void _appendEscapedPath(const UnownedStringSlice& path, StringBuilder& outBuilder) + { + for (auto c : path) + { + // TODO(JS): Probably want more sophisticated handling... + if (c == '\\') + { + outBuilder.appendChar(c); + } + outBuilder.appendChar(c); + } + } + + static void _appendCodeWithPath(const UnownedStringSlice& filePath, const UnownedStringSlice& fileContent, StringBuilder& outCodeBuilder) + { + outCodeBuilder << "#line 1 \""; + _appendEscapedPath(filePath, outCodeBuilder); + outCodeBuilder << "\"\n"; + outCodeBuilder << fileContent << "\n"; + } + String emitHLSLForEntryPoint( BackEndCompileRequest* compileRequest, EntryPoint* entryPoint, @@ -503,26 +568,7 @@ namespace Slang StringBuilder codeBuilder; for(auto sourceFile : translationUnit->getSourceFiles()) { - codeBuilder << "#line 1 \""; - - const String& path = sourceFile->getPathInfo().foundPath; - - for(auto c : path) - { - char buffer[] = { c, 0 }; - switch(c) - { - default: - codeBuilder << buffer; - break; - - case '\\': - codeBuilder << "\\\\"; - } - } - codeBuilder << "\"\n"; - - codeBuilder << sourceFile->getContent() << "\n"; + _appendCodeWithPath(sourceFile->getPathInfo().foundPath.getUnownedSlice(), sourceFile->getContent(), codeBuilder); } return codeBuilder.ProduceString(); @@ -537,6 +583,35 @@ namespace Slang } } + String emitCPPForEntryPoint( + BackEndCompileRequest* compileRequest, + EntryPoint* entryPoint, + Int entryPointIndex, + TargetRequest* targetReq, + EndToEndCompileRequest* endToEndReq) + { + if (auto translationUnit = findPassThroughTranslationUnit(endToEndReq, entryPointIndex)) + { + // Generate a string that includes the content of + // the source file(s), along with a line directive + // to ensure that we get reasonable messages + // from the downstream compiler when in pass-through + // mode. + + StringBuilder codeBuilder; + for (auto sourceFile : translationUnit->getSourceFiles()) + { + _appendCodeWithPath(sourceFile->getPathInfo().foundPath.getUnownedSlice(), sourceFile->getContent(), codeBuilder); + } + + return codeBuilder.ProduceString(); + } + else + { + return emitEntryPoint(compileRequest, entryPoint, CodeGenTarget::CPPSource, targetReq); + } + } + String emitGLSLForEntryPoint( BackEndCompileRequest* compileRequest, EntryPoint* entryPoint, @@ -711,10 +786,10 @@ namespace Slang if(!translationUnitRequest) return "slang-generated"; - auto sink = endToEndReq->getSink(); - const auto& sourceFiles = translationUnitRequest->getSourceFiles(); + auto sink = endToEndReq->getSink(); + const Index numSourceFiles = sourceFiles.getCount(); switch (numSourceFiles) @@ -1028,6 +1103,381 @@ SlangResult dissassembleDXILUsingDXC( return SLANG_OK; } + SlangResult emitCPUBinaryForEntryPoint( + BackEndCompileRequest* slangRequest, + EntryPoint* entryPoint, + Int entryPointIndex, + TargetRequest* targetReq, + EndToEndCompileRequest* endToEndReq, + List<uint8_t>& binOut) + { + auto sink = slangRequest->getSink(); + + const String originalSourcePath = calcSourcePathForEntryPoint(endToEndReq, entryPointIndex); + + binOut.clear(); + + CPPCompilerSet* compilerSet = slangRequest->getSession()->requireCPPCompilerSet(); + + // Determine compiler to use + CPPCompiler* compiler = nullptr; + switch (endToEndReq->passThrough) + { + case PassThroughMode::None: + case PassThroughMode::GenericCCpp: + { + // If there is no pass through... still need a compiler + compiler = compilerSet->getDefaultCompiler(); + break; + } + case PassThroughMode::Clang: + { + compiler = CPPCompilerUtil::findCompiler(compilerSet, CPPCompilerUtil::MatchType::Newest, CPPCompiler::Desc(CPPCompiler::CompilerType::Clang)); + break; + } + case PassThroughMode::VisualStudio: + { + compiler = CPPCompilerUtil::findCompiler(compilerSet, CPPCompilerUtil::MatchType::Newest, CPPCompiler::Desc(CPPCompiler::CompilerType::VisualStudio)); + break; + } + case PassThroughMode::Gcc: + { + compiler = CPPCompilerUtil::findCompiler(compilerSet, CPPCompilerUtil::MatchType::Newest, CPPCompiler::Desc(CPPCompiler::CompilerType::GCC)); + break; + } + } + + if (!compiler) + { + if (endToEndReq->passThrough != PassThroughMode::None) + { + sink->diagnose(SourceLoc(), Diagnostics::passThroughCompilerNotFound, _getPassThroughAsText(endToEndReq->passThrough)); + } + else + { + sink->diagnose(SourceLoc(), Diagnostics::cppCompilerNotFound); + } + return SLANG_FAIL; + } + + TemporaryFileSet temporaryFileSet; + + bool useOriginalFile = false; + + String compileSourcePath; + String sourceContents; + + String rawSource; + + SourceLanguage rawSourceLanguage = SourceLanguage::Unknown; + + Dictionary<String, String> preprocessorDefinitions; + List<String> includePaths; + + /* This is more convoluted than the other scenarios, because when we invoke C/C++ compiler we would ideally like + to use the original file. We want to do this because we want includes relative to the source file to work, and + for that to work most easily we want to use the original file, if there is one */ + if (auto translationUnit = findPassThroughTranslationUnit(endToEndReq, entryPointIndex)) + { + // If it's pass through we accumulate the preprocessor definitions. + for (auto& define : translationUnit->compileRequest->preprocessorDefinitions) + { + preprocessorDefinitions.Add(define.Key, define.Value); + } + for (auto& define : translationUnit->preprocessorDefinitions) + { + preprocessorDefinitions.Add(define.Key, define.Value); + } + + { + /* TODO(JS): Not totally clear what options should be set here. If we are using the pass through - then using say the defines/includes + all makes total sense. If we are generating C++ code from slang, then should we really be using these values -> aren't they what is + being set for the *slang* source, not for the C++ generated code. That being the case it implies that there needs to be a mechanism + (if there isn't already) to specify such information on a particular pass/pass through etc. + + On invoking DXC for example include paths do not appear to be set at all (even with pass-through). + */ + + auto linkage = targetReq->getLinkage(); + + // Add all the search paths + + const auto searchDirectories = linkage->getSearchDirectories(); + const SearchDirectoryList* searchList = &searchDirectories; + while (searchList) + { + for (const auto& searchDirectory : searchList->searchDirectories) + { + includePaths.add(searchDirectory.path); + } + searchList = searchList->parent; + } + } + + // We are just passing thru, so it's whatever it originally was + rawSourceLanguage = translationUnit->sourceLanguage; + + const auto& sourceFiles = translationUnit->getSourceFiles(); + if (sourceFiles.getCount() == 1) + { + const SourceFile* sourceFile = sourceFiles[0]; + const PathInfo& pathInfo = sourceFile->getPathInfo(); + if (pathInfo.type == PathInfo::Type::FoundPath || pathInfo.type == PathInfo::Type::Normal) + { + compileSourcePath = pathInfo.foundPath; + // We can see if we can load it + if (File::exists(compileSourcePath)) + { + // Here we look for the file on the regular file system (as opposed to using the + // ISlangFileSystem. This is unfortunate but necessary - because when we call out + // to the CPP compiler all it is able to (currently) see are files on the file system. + // + // Note that it could be coincidence that the filesystem has a file that's identical in + // contents/name. That being the case though, any includes wouldn't work for a generated + // file either from some specialized ISlangFileSystem, so this is probably as good as it gets + // until we can integrate directly to a C/C++ compiler through say a shared library where we can control + // file system access. + try + { + String readContents = File::readAllText(compileSourcePath); + // We should see if they are the same + useOriginalFile = (sourceFile->getContent() == readContents.getUnownedSlice()); + } + catch (const Slang::IOException&) + { + } + } + } + } + + if (!useOriginalFile) + { + StringBuilder codeBuilder; + for (auto sourceFile : translationUnit->getSourceFiles()) + { + _appendCodeWithPath(sourceFile->getPathInfo().foundPath.getUnownedSlice(), sourceFile->getContent(), codeBuilder); + } + sourceContents = codeBuilder.ProduceString(); + } + } + else + { + rawSource = emitCPPForEntryPoint( + slangRequest, + entryPoint, + entryPointIndex, + targetReq, + endToEndReq); + + maybeDumpIntermediate(slangRequest, rawSource.getBuffer(), CodeGenTarget::CPPSource); + + rawSourceLanguage = SourceLanguage::CPP; + } + + List<String> tempFiles; + + if (!useOriginalFile) + { + SLANG_RETURN_ON_FAIL(File::generateTemporary(UnownedStringSlice::fromLiteral("slang-generated"), compileSourcePath)); + + // Make the temporary filename have the appropriate extension. + // NOTE: Strictly speaking that may introduce a temp filename clash, but in practice is extraordinary unlikely + if (rawSourceLanguage == SourceLanguage::C) + { + compileSourcePath.append(".c"); + } + else + { + compileSourcePath.append(".cpp"); + } + + // Delete this path at end of execution + temporaryFileSet.add(compileSourcePath); + + try + { + File::writeAllText(compileSourcePath, rawSource); + } + catch (...) + { + sink->diagnose(SourceLoc(), Diagnostics::unableToWriteFile, compileSourcePath); + return SLANG_FAIL; + } + } + + CPPCompiler::CompileOptions options; + + // Generate a path a temporary filename for output module + String modulePath; + SLANG_RETURN_ON_FAIL(File::generateTemporary(UnownedStringSlice::fromLiteral("slang-generated"), modulePath)); + + options.modulePath = modulePath; + options.sourceFiles.add(compileSourcePath); + + // Set what kind of target we should build + switch (targetReq->target) + { + case CodeGenTarget::SharedLibrary: + { + options.targetType = CPPCompiler::TargetType::SharedLibrary; + break; + } + case CodeGenTarget::Executable: + { + options.targetType = CPPCompiler::TargetType::Executable; + break; + } + default: break; + } + + String moduleFilePath; + + { + StringBuilder builder; + SLANG_RETURN_ON_FAIL(compiler->calcModuleFilePath(options, builder)); + moduleFilePath = builder.ProduceString(); + } + + // Add so it's deleted at the end + temporaryFileSet.add(moduleFilePath); + + // Need to configure for the compilation + + { + auto linkage = targetReq->getLinkage(); + + switch (linkage->optimizationLevel) + { + case OptimizationLevel::None: options.optimizationLevel = CPPCompiler::OptimizationLevel::None; break; + case OptimizationLevel::Default: options.optimizationLevel = CPPCompiler::OptimizationLevel::Default; break; + case OptimizationLevel::High: options.optimizationLevel = CPPCompiler::OptimizationLevel::High; break; + case OptimizationLevel::Maximal: options.optimizationLevel = CPPCompiler::OptimizationLevel::Maximal; break; + default: SLANG_ASSERT(!"Unhandled optimization level"); break; + } + + switch (linkage->debugInfoLevel) + { + case DebugInfoLevel::None: options.debugInfoType = CPPCompiler::DebugInfoType::None; break; + case DebugInfoLevel::Minimal: options.debugInfoType = CPPCompiler::DebugInfoType::Minimal; break; + + case DebugInfoLevel::Standard: options.debugInfoType = CPPCompiler::DebugInfoType::Standard; break; + case DebugInfoLevel::Maximal: options.debugInfoType = CPPCompiler::DebugInfoType::Maximal; break; + default: SLANG_ASSERT(!"Unhandled debug level"); break; + } + + switch( targetReq->floatingPointMode ) + { + case FloatingPointMode::Default: options.floatingPointMode = CPPCompiler::FloatingPointMode::Default; break; + case FloatingPointMode::Precise: options.floatingPointMode = CPPCompiler::FloatingPointMode::Precise; break; + case FloatingPointMode::Fast: options.floatingPointMode = CPPCompiler::FloatingPointMode::Fast; break; + default: SLANG_ASSERT(!"Unhanlde floating point mode"); + } + + // Add all the search paths (as calculated earlier - they will only be set if this is a pass through else will be empty) + options.includePaths = includePaths; + + // Add the specified defines (as calculated earlier - they will only be set if this is a pass through else will be empty) + { + for(auto& def : preprocessorDefinitions) + { + CPPCompiler::Define define; + define.nameWithSig = def.Key; + define.value = def.Value; + + options.defines.add(define); + } + } + } + + // TODO(JS): HACK! We need to include the prelude from somewhere, but where? The generated output + // is sitting in some temp directory. + // So here, we search all the 'sourceFiles', and try their paths for plausibility, and take the first + { + auto frontEndReq = endToEndReq->getFrontEndReq(); + auto entryPointReq = frontEndReq->getEntryPointReq(entryPointIndex); + auto translationUnit = entryPointReq->getTranslationUnit(); + + for (SourceFile* sourceFile : translationUnit->m_sourceFiles) + { + const auto& pathInfo = sourceFile->getPathInfo(); + + if (pathInfo.type == PathInfo::Type::FoundPath || + pathInfo.type == PathInfo::Type::Normal) + { + String originalSourceDirectory = Path::getParentDirectory(pathInfo.foundPath); + + if (originalSourceDirectory.getLength() && File::exists(originalSourceDirectory)) + { + // We can't use this path directly, so make canonical so it is absolute + StringBuilder canonicalPath; + if (SLANG_SUCCEEDED(Path::getCanonical(originalSourceDirectory, canonicalPath))) + { + options.includePaths.add(canonicalPath.ProduceString()); + break; + } + } + } + } + } + + // Compile + CPPCompiler::Output output; + SLANG_RETURN_ON_FAIL(compiler->compile(options, output)); + + { + StringBuilder compilerText; + compiler->getDesc().appendAsText(compilerText); + + StringBuilder builder; + + typedef CPPCompiler::OutputMessage OutputMessage; + + for (const auto& msg : output.messages) + { + builder.Clear(); + + builder << msg.filePath << "(" << msg.fileLine <<"): "; + + if (msg.stage == OutputMessage::Stage::Link) + { + builder << "link "; + } + + switch (msg.type) + { + case OutputMessage::Type::Error: builder << "error"; break; + case OutputMessage::Type::Unknown: builder << "warning"; break; + case OutputMessage::Type::Info: builder << "info"; break; + default: break; + } + + builder << " " << msg.code << ": " << msg.text; + + reportExternalCompileError(compilerText.getBuffer(), SLANG_OK, builder.getUnownedSlice(), sink); + } + } + + // If any errors are emitted, then we are done + if (output.has(CPPCompiler::OutputMessage::Type::Error)) + { + return SLANG_FAIL; + } + + // Read the binary + try + { + // Read the contents of the binary + binOut = File::readAllBytes(moduleFilePath); + } + catch (const Slang::IOException&) + { + sink->diagnose(SourceLoc(), Diagnostics::unableToReadFile, moduleFilePath); + return SLANG_FAIL; + } + + return SLANG_OK; + } + SlangResult emitSPIRVForEntryPoint( BackEndCompileRequest* slangRequest, EntryPoint* entryPoint, @@ -1106,6 +1556,23 @@ SlangResult dissassembleDXILUsingDXC( switch (target) { + case CodeGenTarget::SharedLibrary: + case CodeGenTarget::Executable: + { + List<uint8_t> code; + if (SLANG_SUCCEEDED(emitCPUBinaryForEntryPoint( + compileRequest, + entryPoint, + entryPointIndex, + targetReq, + endToEndReq, + code))) + { + maybeDumpIntermediate(compileRequest, code.getBuffer(), code.getCount(), target); + result = CompileResult(code); + } + } + break; case CodeGenTarget::HLSL: { String code = emitHLSLForEntryPoint( @@ -1444,6 +1911,11 @@ SlangResult dissassembleDXILUsingDXC( } break; + case CodeGenTarget::SharedLibrary: + case CodeGenTarget::Executable: + HexDumpUtil::dumpWithMarkers(data, 24, writer); + break; + default: SLANG_UNEXPECTED("unhandled output format"); return; @@ -1753,6 +2225,22 @@ SlangResult dissassembleDXILUsingDXC( } break; #endif + + case CodeGenTarget::CSource: + dumpIntermediateText(compileRequest, data, size, ".c"); + break; + case CodeGenTarget::CPPSource: + dumpIntermediateText(compileRequest, data, size, ".cpp"); + break; + + case CodeGenTarget::Executable: + // What these should be called is target specific, but just use these exts to make clear for now + // for now + dumpIntermediateBinary(compileRequest, data, size, ".exe"); + break; + case CodeGenTarget::SharedLibrary: + dumpIntermediateBinary(compileRequest, data, size, ".shared-lib"); + break; } } diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 2b2c35845..b7f62ae5b 100644 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -4,6 +4,8 @@ #include "../core/slang-basic.h" #include "../core/slang-shared-library.h" +#include "../core/slang-cpp-compiler.h" + #include "../../slang-com-ptr.h" #include "slang-diagnostics.h" @@ -57,6 +59,8 @@ namespace Slang DXILAssembly = SLANG_DXIL_ASM, CSource = SLANG_C_SOURCE, CPPSource = SLANG_CPP_SOURCE, + Executable = SLANG_EXECUTABLE, + SharedLibrary = SLANG_SHARED_LIBRARY, }; CodeGenTarget calcCodeGenTargetFromName(const UnownedStringSlice& name); @@ -417,10 +421,14 @@ namespace Slang enum class PassThroughMode : SlangPassThrough { - None = SLANG_PASS_THROUGH_NONE, // don't pass through: use Slang compiler - fxc = SLANG_PASS_THROUGH_FXC, // pass through HLSL to `D3DCompile` API - dxc = SLANG_PASS_THROUGH_DXC, // pass through HLSL to `IDxcCompiler` API - glslang = SLANG_PASS_THROUGH_GLSLANG, // pass through GLSL to `glslang` library + None = SLANG_PASS_THROUGH_NONE, ///< don't pass through: use Slang compiler + Fxc = SLANG_PASS_THROUGH_FXC, ///< pass through HLSL to `D3DCompile` API + Dxc = SLANG_PASS_THROUGH_DXC, ///< pass through HLSL to `IDxcCompiler` API + Glslang = SLANG_PASS_THROUGH_GLSLANG, ///< pass through GLSL to `glslang` library + Clang = SLANG_PASS_THROUGH_CLANG, ///< Pass through clang compiler + Gcc = SLANG_PASS_THROUGH_GCC, ///< Gcc compiler + VisualStudio = SLANG_PASS_THROUGH_VISUAL_STUDIO, ///< Visual studio compiler + GenericCCpp = SLANG_PASS_THROUGH_GENERIC_C_CPP, ///< Generic C/C++ compiler }; class SourceFile; @@ -1493,6 +1501,8 @@ namespace Slang RefPtr<Type> stringType; RefPtr<Type> enumTypeType; + RefPtr<CPPCompilerSet> cppCompilerSet; ///< Information about available C/C++ compilers. null unless information is requested (because slow) + ComPtr<ISlangSharedLibraryLoader> sharedLibraryLoader; ///< The shared library loader (never null) ComPtr<ISlangSharedLibrary> sharedLibraries[int(SharedLibraryType::CountOf)]; ///< The loaded shared libraries SlangFuncPtr sharedLibraryFunctions[int(SharedLibraryFuncType::CountOf)]; @@ -1567,6 +1577,9 @@ namespace Slang SlangFuncPtr getSharedLibraryFunc(SharedLibraryFuncType type, DiagnosticSink* sink); + /// Finds out what compilers are present and caches the result + CPPCompilerSet* requireCPPCompilerSet(); + Session(); void addBuiltinSource( diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index fc9cac3f3..04b7560d2 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -478,6 +478,12 @@ DIAGNOSTIC(52000, Error, multiLevelBreakUnsupported, "control flow appears to re DIAGNOSTIC(52001, Warning, dxilNotFound, "dxil shared library not found, so 'dxc' output cannot be signed! Shader code will not be runnable in non-development environments."); +DIAGNOSTIC(52002, Error, passThroughCompilerNotFound, "Could not find a suitable pass-through compiler for '$0'."); +DIAGNOSTIC(52003, Error, cppCompilerNotFound, "Could not find a suitable C/C++ compiler."); + +DIAGNOSTIC(52004, Error, unableToWriteFile, "Unable to write file '$0'"); +DIAGNOSTIC(52005, Error, unableToReadFile, "Unable to read file '$0'"); + // 99999 - Internal compiler errors, and not-yet-classified diagnostics. DIAGNOSTIC(99999, Internal, unimplemented, "unimplemented feature in Slang compiler: $0") diff --git a/source/slang/slang-emit-cpp.cpp b/source/slang/slang-emit-cpp.cpp index e89268106..354a2d856 100644 --- a/source/slang/slang-emit-cpp.cpp +++ b/source/slang/slang-emit-cpp.cpp @@ -1399,7 +1399,7 @@ void CPPSourceEmitter::emitEntryPointAttributesImpl(IRFunc* irFunc, EntryPointLa default: break; } - m_writer->emit("SLANG_EXPORT\n"); + m_writer->emit("SLANG_PRELUDE_EXPORT\n"); } void CPPSourceEmitter::emitVectorTypeNameImpl(IRType* elementType, IRIntegerValue elementCount) @@ -1613,7 +1613,8 @@ void CPPSourceEmitter::emitPreprocessorDirectivesImpl() SourceWriter* writer = getSourceWriter(); writer->emit("\n"); - writer->emit("#include \"slang-cpp-prelude.h\"\n\n"); + + writer->emit("#include <slang-cpp-prelude.h>\n\n"); // Emit the type definitions for (const auto& keyValue : m_typeNameMap) diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index 4b3a9f8f0..81fb468eb 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -36,6 +36,26 @@ SlangResult tryReadCommandLineArgument(DiagnosticSink* sink, char const* option, return SLANG_OK; } +#define SLANG_PASS_THROUGH_TYPES(x) \ + x(none, NONE) \ + x(fxc, FXC) \ + x(dxc, DXC) \ + x(glslang, GLSLANG) \ + x(vs, VISUAL_STUDIO) \ + x(visualstudio, VISUAL_STUDIO) \ + x(clang, CLANG) \ + x(gcc, GCC) \ + x(c, GENERIC_C_CPP) \ + x(cpp, GENERIC_C_CPP) + +static SlangResult _parsePassThrough(const UnownedStringSlice& name, SlangPassThrough& outPassThrough) +{ +#define SLANG_PASS_THROUGH_TYPE_CHECK(x, y) \ + if (name == #x) { outPassThrough = SLANG_PASS_THROUGH_##y; return SLANG_OK; } + SLANG_PASS_THROUGH_TYPES(SLANG_PASS_THROUGH_TYPE_CHECK) + return SLANG_FAIL; +} + struct OptionsParser { SlangSession* session = nullptr; @@ -278,6 +298,9 @@ struct OptionsParser { ".tesc", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_HULL }, { ".tese", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_DOMAIN }, { ".comp", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_COMPUTE }, + + { ".c", SLANG_SOURCE_LANGUAGE_C, SLANG_STAGE_NONE }, + { ".cpp", SLANG_SOURCE_LANGUAGE_CPP, SLANG_STAGE_NONE }, }; for (int i = 0; i < SLANG_COUNT_OF(entries); ++i) @@ -360,8 +383,12 @@ struct OptionsParser CASE(".spv", SPIRV); CASE(".spv.asm", SPIRV_ASM); - CASE(".c", C_SOURCE); - CASE(".cpp", CPP_SOURCE); + CASE(".c", C_SOURCE); + CASE(".cpp", CPP_SOURCE); + + CASE(".exe", EXECUTABLE); + CASE(".dll", SHARED_LIBRARY); + CASE(".so", SHARED_LIBRARY); #undef CASE @@ -422,6 +449,23 @@ struct OptionsParser rawTarget->floatingPointMode = mode; } + static bool _passThroughRequiresStage(PassThroughMode passThrough) + { + switch (passThrough) + { + case PassThroughMode::Glslang: + case PassThroughMode::Dxc: + case PassThroughMode::Fxc: + { + return true; + } + default: + { + return false; + } + } + } + SlangResult parse( int argc, char const* const* argv) @@ -561,18 +605,13 @@ struct OptionsParser SLANG_RETURN_ON_FAIL(tryReadCommandLineArgument(sink, arg, &argCursor, argEnd, name)); SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE; - if (name == "fxc") { passThrough = SLANG_PASS_THROUGH_FXC; } - else if (name == "dxc") { passThrough = SLANG_PASS_THROUGH_DXC; } - else if (name == "glslang") { passThrough = SLANG_PASS_THROUGH_GLSLANG; } - else + if (SLANG_FAILED(_parsePassThrough(name.getUnownedSlice(), passThrough))) { sink->diagnose(SourceLoc(), Diagnostics::unknownPassThroughTarget, name); return SLANG_FAIL; } - spSetPassThrough( - compileRequest, - passThrough); + spSetPassThrough(compileRequest, passThrough); } else if (argStr == "-dxc-path") { @@ -938,7 +977,7 @@ struct OptionsParser // because fxc/dxc/glslang don't have a facility for taking // a named entry point and pulling its stage from an attribute. // - if( requestImpl->passThrough != PassThroughMode::None ) + if(_passThroughRequiresStage(requestImpl->passThrough) ) { for( auto& rawEntryPoint : rawEntryPoints ) { diff --git a/source/slang/slang-profile-defs.h b/source/slang/slang-profile-defs.h index 238621084..50e1750ac 100644 --- a/source/slang/slang-profile-defs.h +++ b/source/slang/slang-profile-defs.h @@ -3,7 +3,7 @@ // Define all the various language "profiles" we want to support. #ifndef LANGUAGE -#define LANGUAGE(TAG, NAME) /* emptry */ +#define LANGUAGE(TAG, NAME) /* empty */ #endif #ifndef LANGUAGE_ALIAS @@ -47,6 +47,8 @@ LANGUAGE(GLSL_ES, glsl_es) LANGUAGE(GLSL_VK, glsl_vk) LANGUAGE(SPIRV, spirv) LANGUAGE(SPIRV_GL, spirv_gl) +LANGUAGE(C, c) +LANGUAGE(CPP, cpp) LANGUAGE_ALIAS(GLSL, glsl_gl) LANGUAGE_ALIAS(SPIRV, spirv_vk) diff --git a/source/slang/slang-profile.h b/source/slang/slang-profile.h index de471282d..f26b48866 100644 --- a/source/slang/slang-profile.h +++ b/source/slang/slang-profile.h @@ -13,6 +13,8 @@ namespace Slang Slang = SLANG_SOURCE_LANGUAGE_SLANG, HLSL = SLANG_SOURCE_LANGUAGE_HLSL, GLSL = SLANG_SOURCE_LANGUAGE_GLSL, + C = SLANG_SOURCE_LANGUAGE_C, + CPP = SLANG_SOURCE_LANGUAGE_CPP, }; // TODO(tfoley): This should merge with the above... diff --git a/source/slang/slang-type-layout.cpp b/source/slang/slang-type-layout.cpp index c3dbb4966..9b6571372 100644 --- a/source/slang/slang-type-layout.cpp +++ b/source/slang/slang-type-layout.cpp @@ -815,6 +815,8 @@ LayoutRulesFamilyImpl* getDefaultLayoutRulesFamilyForTarget(TargetRequest* targe return &kGLSLLayoutRulesFamilyImpl; + case CodeGenTarget::Executable: + case CodeGenTarget::SharedLibrary: case CodeGenTarget::CPPSource: case CodeGenTarget::CSource: { diff --git a/tests/cpp-compiler/simple-c-compile.c b/tests/cpp-compiler/simple-c-compile.c new file mode 100644 index 000000000..32b5561f1 --- /dev/null +++ b/tests/cpp-compiler/simple-c-compile.c @@ -0,0 +1,10 @@ +//TEST:SIMPLE: -entry main -target exe -pass-through c + +#include <stdlib.h> +#include <stdio.h> + +int main(int argc, char** argv) +{ + printf("Hello World!\n"); + return 0; +} diff --git a/tests/cpp-compiler/simple-c-compile.c.expected b/tests/cpp-compiler/simple-c-compile.c.expected new file mode 100644 index 000000000..0e042b53b --- /dev/null +++ b/tests/cpp-compiler/simple-c-compile.c.expected @@ -0,0 +1,6 @@ +result code = 0 +standard error = { +} +standard output = { +Hello World! +} diff --git a/tests/cross-compile/cpp-execute-simple.slang b/tests/cross-compile/cpp-execute-simple.slang new file mode 100644 index 000000000..74a3ec634 --- /dev/null +++ b/tests/cross-compile/cpp-execute-simple.slang @@ -0,0 +1,12 @@ +//TEST:CPU_EXECUTE: -profile cs_5_0 -entry computeMain -target sharedlib + +[numthreads(4, 1, 1)] +void computeMain( + uint3 dispatchThreadID : SV_DispatchThreadID, +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out + RWStructuredBuffer<int> outputBuffer) +{ + uint tid = dispatchThreadID.x; + + outputBuffer[tid] = int(tid * tid); +}
\ No newline at end of file diff --git a/tests/cross-compile/cpp-execute-simple.slang.expected b/tests/cross-compile/cpp-execute-simple.slang.expected new file mode 100644 index 000000000..b84777e95 --- /dev/null +++ b/tests/cross-compile/cpp-execute-simple.slang.expected @@ -0,0 +1 @@ +0, 1, 4, 9 diff --git a/tests/cross-compile/cpp-execute.slang b/tests/cross-compile/cpp-execute.slang new file mode 100644 index 000000000..1c90c8dd2 --- /dev/null +++ b/tests/cross-compile/cpp-execute.slang @@ -0,0 +1,106 @@ +//TEST:CPU_EXECUTE: -profile cs_5_0 -entry computeMain -target sharedlib + +enum Color +{ + Red, + Green = 2, + Blue, +} + +int test(int val) +{ + Color c = Color.Red; + + if(val > 1) + { + c = Color.Green; + } + + if(c == Color.Red) + { + if(val & 1) + { + c = Color.Blue; + } + } + + switch(c) + { + case Color.Red: + val = 1; + break; + + case Color.Green: + val = 2; + break; + + case Color.Blue: + val = 3; + break; + + default: + val = -1; + break; + } + + return (val << 4) + int(c); +} + +float sum(float a[3]) +{ + float total = a[0]; + for (int i = 1; i < 3; ++i) + { + total += a[i]; + } + return total; +} + +struct Thing +{ + int a; + float b; +}; + +[numthreads(4, 1, 1)] +void computeMain( + uint3 dispatchThreadID : SV_DispatchThreadID, +//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out + RWStructuredBuffer<int> outputBuffer) +{ + uint tid = dispatchThreadID.x; + + Thing thing = { 10, -1.0 }; + + float array[3] = { thing.a, 2, 3}; + + float anotherArray[] = { 1, 2, 5 }; + + array[0] += anotherArray[1]; + + matrix<float, 2, 3> mat = { { sum(array), 1, 2 }, { 3, 4, 5} }; + vector<float, 2> vec = { float(tid + 1), float(tid + 2) }; + + vector<float, 3> vec2 = max(sin(mul(vec, mat)), float3(1, 2, -1)); + vector<float, 3> vec3 = mul(vec, mat); + + float3 vec4 = lerp(vec2, vec3, float3(tid * (1.0f / 4), 1, 1)); + + float3 crossVec = normalize(cross(vec4, vec4)); + + vec2.x = fmod(crossVec.y, crossVec.x); + + vec2 = fmod(vec2, crossVec); + + vec2 += (-vec2.zyx) * 2 + crossVec * length(crossVec) + reflect(vec4, normalize(crossVec)); + + vector<bool, 3> z = vec2 > 0; + + int val = (int(tid) + (any(z) ? 1 : 0) + (all(z) ? 2 : 0)) % 100; + + val = asint(asfloat(asuint(asfloat(val)))); + + val = test(val); + + outputBuffer[tid] = val + int(dot(vec2, vec4)); +}
\ No newline at end of file diff --git a/tests/cross-compile/cpp-execute.slang.expected b/tests/cross-compile/cpp-execute.slang.expected new file mode 100644 index 000000000..65e3ed534 --- /dev/null +++ b/tests/cross-compile/cpp-execute.slang.expected @@ -0,0 +1 @@ +-2147483632, -2147483597, -2147483614, -2147483614 diff --git a/tests/cross-compile/slang-cpp-prelude.h b/tests/cross-compile/slang-cpp-prelude.h index f60fc8518..f2635eb2c 100644 --- a/tests/cross-compile/slang-cpp-prelude.h +++ b/tests/cross-compile/slang-cpp-prelude.h @@ -1,3 +1,5 @@ +#ifndef SLANG_CPP_PRELUDE_H +#define SLANG_CPP_PRELUDE_H #include <inttypes.h> #include <math.h> @@ -6,24 +8,32 @@ #include <assert.h> #include <stdlib.h> -#ifndef M_PI -# define M_PI 3.14159265358979323846 +#ifndef SLANG_FORCE_INLINE +# define SLANG_FORCE_INLINE inline +#endif + +#ifndef SLANG_PRELUDE_PI +# define SLANG_PRELUDE_PI 3.14159265358979323846 #endif #if defined(_MSC_VER) -# define SLANG_SHARED_LIB_EXPORT __declspec(dllexport) +# define SLANG_PRELUDE_SHARED_LIB_EXPORT __declspec(dllexport) #else -# define SLANG_SHARED_LIB_EXPORT __attribute__((__visibility__("default"))) -//# define SLANG_SHARED_LIB_EXPORT __attribute__ ((dllexport)) __attribute__((__visibility__("default"))) +# define SLANG_PRELUDE_SHARED_LIB_EXPORT __attribute__((__visibility__("default"))) +//# define SLANG_PRELUDE_SHARED_LIB_EXPORT __attribute__ ((dllexport)) __attribute__((__visibility__("default"))) #endif #ifdef __cplusplus -# define SLANG_EXTERN_C extern "C" +# define SLANG_PRELUDE_EXTERN_C extern "C" #else -# define SLANG_EXTERN_C +# define SLANG_PRELUDE_EXTERN_C #endif -#define SLANG_EXPORT SLANG_EXTERN_C SLANG_SHARED_LIB_EXPORT +#define SLANG_PRELUDE_EXPORT SLANG_PRELUDE_EXTERN_C SLANG_PRELUDE_SHARED_LIB_EXPORT + +#ifdef SLANG_PRELUDE_NAMESPACE +namespace SLANG_PRELUDE_NAMESPACE { +#endif template <typename T, size_t SIZE> struct FixedArray @@ -43,6 +53,39 @@ struct RWStructuredBuffer size_t count; }; +template <typename T, int COUNT> +struct Vector; + +template <typename T> +struct Vector<T, 1> +{ + T x; +}; + +template <typename T> +struct Vector<T, 2> +{ + T x, y; +}; + +template <typename T> +struct Vector<T, 3> +{ + T x, y, z; +}; + +template <typename T> +struct Vector<T, 4> +{ + T x, y, z, w; +}; + +template <typename T, int ROWS, int COLS> +struct Matrix +{ + Vector<T, COLS> rows[ROWS]; +}; + // ----------------------------- F32 ----------------------------------------- union Union32 @@ -53,70 +96,125 @@ union Union32 }; // Helpers -float F32_calcSafeRadians(float radians) +SLANG_FORCE_INLINE float F32_calcSafeRadians(float radians) { - float a = radians * (1.0f / M_PI); + float a = radians * (1.0f / float(SLANG_PRELUDE_PI)); a = (a < 0.0f) ? (::ceilf(a) - a) : (a - ::floorf(a)); - return (a * M_PI); + return (a * float(SLANG_PRELUDE_PI)); } // Unary -float F32_ceil(float f) { return ::ceilf(f); } -float F32_floor(float f) { return ::floorf(f); } -float F32_sin(float f) { return ::sinf(F32_calcSafeRadians(f)); } -float F32_cos(float f) { return ::cosf(F32_calcSafeRadians(f)); } -float F32_tan(float f) { return ::tanf(f); } -float F32_asin(float f) { return ::asinf(f); } -float F32_acos(float f) { return ::acosf(f); } -float F32_atan(float f) { return ::atanf(f); } -float F32_log2(float f) { return ::log2f(f); } -float F32_exp2(float f) { return ::exp2f(f); } -float F32_exp(float f) { return ::expf(f); } -float F32_abs(float f) { return ::fabsf(f); } -float F32_trunc(float f) { return ::truncf(f); } -float F32_sqrt(float f) { return ::sqrtf(f); } -float F32_rsqrt(float f) { return 1.0f / F32_sqrt(f); } -float F32_rcp(float f) { return 1.0f / f; } -float F32_sign(float f) { return ( f == 0.0f) ? f : (( f < 0.0f) ? -1.0f : 1.0f); } -float F32_saturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } -float F32_frac(float f) { return f - F32_floor(f); } -float F32_radians(float f) { return f * 0.01745329222f; } +SLANG_FORCE_INLINE float F32_ceil(float f) { return ::ceilf(f); } +SLANG_FORCE_INLINE float F32_floor(float f) { return ::floorf(f); } +SLANG_FORCE_INLINE float F32_sin(float f) { return ::sinf(F32_calcSafeRadians(f)); } +SLANG_FORCE_INLINE float F32_cos(float f) { return ::cosf(F32_calcSafeRadians(f)); } +SLANG_FORCE_INLINE float F32_tan(float f) { return ::tanf(f); } +SLANG_FORCE_INLINE float F32_asin(float f) { return ::asinf(f); } +SLANG_FORCE_INLINE float F32_acos(float f) { return ::acosf(f); } +SLANG_FORCE_INLINE float F32_atan(float f) { return ::atanf(f); } +SLANG_FORCE_INLINE float F32_log2(float f) { return ::log2f(f); } +SLANG_FORCE_INLINE float F32_exp2(float f) { return ::exp2f(f); } +SLANG_FORCE_INLINE float F32_exp(float f) { return ::expf(f); } +SLANG_FORCE_INLINE float F32_abs(float f) { return ::fabsf(f); } +SLANG_FORCE_INLINE float F32_trunc(float f) { return ::truncf(f); } +SLANG_FORCE_INLINE float F32_sqrt(float f) { return ::sqrtf(f); } +SLANG_FORCE_INLINE float F32_rsqrt(float f) { return 1.0f / F32_sqrt(f); } +SLANG_FORCE_INLINE float F32_rcp(float f) { return 1.0f / f; } +SLANG_FORCE_INLINE float F32_sign(float f) { return ( f == 0.0f) ? f : (( f < 0.0f) ? -1.0f : 1.0f); } +SLANG_FORCE_INLINE float F32_saturate(float f) { return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f; } +SLANG_FORCE_INLINE float F32_frac(float f) { return f - F32_floor(f); } +SLANG_FORCE_INLINE float F32_radians(float f) { return f * 0.01745329222f; } // Binary -float F32_min(float a, float b) { return a < b ? a : b; } -float F32_max(float a, float b) { return a > b ? a : b; } -float F32_pow(float a, float b) { return ::powf(a, b); } -float F32_fmod(float a, float b) { return ::fmodf(a, b); } -float F32_step(float a, float b) { return float(a >= b); } -float F32_atan2(float a, float b) { return float(atan2(a, b)); } +SLANG_FORCE_INLINE float F32_min(float a, float b) { return a < b ? a : b; } +SLANG_FORCE_INLINE float F32_max(float a, float b) { return a > b ? a : b; } +SLANG_FORCE_INLINE float F32_pow(float a, float b) { return ::powf(a, b); } +SLANG_FORCE_INLINE float F32_fmod(float a, float b) { return ::fmodf(a, b); } +SLANG_FORCE_INLINE float F32_step(float a, float b) { return float(a >= b); } +SLANG_FORCE_INLINE float F32_atan2(float a, float b) { return float(atan2(a, b)); } // Ternary -float F32_smoothstep(float min, float max, float x) { return x < min ? min : ((x > max) ? max : x / (max - min)); } -float F32_lerp(float x, float y, float s) { return x + s * (y - x); } -float F32_clamp(float x, float min, float max) { return ( x < min) ? min : ((x > max) ? max : x); } -void F32_sincos(float f, float& outSin, float& outCos) { outSin = F32_sin(f); outCos = F32_cos(f); } +SLANG_FORCE_INLINE float F32_smoothstep(float min, float max, float x) { return x < min ? min : ((x > max) ? max : x / (max - min)); } +SLANG_FORCE_INLINE float F32_lerp(float x, float y, float s) { return x + s * (y - x); } +SLANG_FORCE_INLINE float F32_clamp(float x, float min, float max) { return ( x < min) ? min : ((x > max) ? max : x); } +SLANG_FORCE_INLINE void F32_sincos(float f, float& outSin, float& outCos) { outSin = F32_sin(f); outCos = F32_cos(f); } + +SLANG_FORCE_INLINE uint32_t F32_asuint(float f) { Union32 u; u.f = f; return u.u; } +SLANG_FORCE_INLINE int32_t F32_asint(float f) { Union32 u; u.f = f; return u.i; } -uint32_t F32_asuint(float f) { Union32 u; u.f = f; return u.u; } -int32_t F32_asint(float f) { Union32 u; u.f = f; return u.i; } +// ----------------------------- F64 ----------------------------------------- + +SLANG_FORCE_INLINE double F64_calcSafeRadians(double radians) +{ + double a = radians * (1.0 / SLANG_PRELUDE_PI); + a = (a < 0.0) ? (::ceil(a) - a) : (a - ::floor(a)); + return (a * SLANG_PRELUDE_PI); +} + +// Unary +SLANG_FORCE_INLINE double F64_ceil(double f) { return ::ceil(f); } +SLANG_FORCE_INLINE double F64_floor(double f) { return ::floor(f); } +SLANG_FORCE_INLINE double F64_sin(double f) { return ::sin(F64_calcSafeRadians(f)); } +SLANG_FORCE_INLINE double F64_cos(double f) { return ::cos(F64_calcSafeRadians(f)); } +SLANG_FORCE_INLINE double F64_tan(double f) { return ::tan(f); } +SLANG_FORCE_INLINE double F64_asin(double f) { return ::asin(f); } +SLANG_FORCE_INLINE double F64_acos(double f) { return ::acos(f); } +SLANG_FORCE_INLINE double F64_atan(double f) { return ::atan(f); } +SLANG_FORCE_INLINE double F64_log2(double f) { return ::log2(f); } +SLANG_FORCE_INLINE double F64_exp2(double f) { return ::exp2(f); } +SLANG_FORCE_INLINE double F64_exp(double f) { return ::exp(f); } +SLANG_FORCE_INLINE double F64_abs(double f) { return ::fabs(f); } +SLANG_FORCE_INLINE double F64_trunc(double f) { return ::trunc(f); } +SLANG_FORCE_INLINE double F64_sqrt(double f) { return ::sqrt(f); } +SLANG_FORCE_INLINE double F64_rsqrt(double f) { return 1.0 / F64_sqrt(f); } +SLANG_FORCE_INLINE double F64_rcp(double f) { return 1.0 / f; } +SLANG_FORCE_INLINE double F64_sign(double f) { return (f == 0.0) ? f : ((f < 0.0) ? -1.0 : 1.0); } +SLANG_FORCE_INLINE double F64_saturate(double f) { return (f < 0.0) ? 0.0 : (f > 1.0) ? 1.0 : f; } +SLANG_FORCE_INLINE double F64_frac(double f) { return f - F64_floor(f); } +SLANG_FORCE_INLINE double F64_radians(double f) { return f * 0.01745329222; } + +// Binary +SLANG_FORCE_INLINE double F64_min(double a, double b) { return a < b ? a : b; } +SLANG_FORCE_INLINE double F64_max(double a, double b) { return a > b ? a : b; } +SLANG_FORCE_INLINE double F64_pow(double a, double b) { return ::pow(a, b); } +SLANG_FORCE_INLINE double F64_fmod(double a, double b) { return ::fmod(a, b); } +SLANG_FORCE_INLINE double F64_step(double a, double b) { return double(a >= b); } +SLANG_FORCE_INLINE double F64_atan2(double a, double b) { return atan2(a, b); } + +// Ternary +SLANG_FORCE_INLINE double F64_smoothstep(double min, double max, double x) { return x < min ? min : ((x > max) ? max : x / (max - min)); } +SLANG_FORCE_INLINE double F64_lerp(double x, double y, double s) { return x + s * (y - x); } +SLANG_FORCE_INLINE double F64_clamp(double x, double min, double max) { return (x < min) ? min : ((x > max) ? max : x); } +SLANG_FORCE_INLINE void F64_sincos(double f, double& outSin, double& outCos) { outSin = F64_sin(f); outCos = F64_cos(f); } + +// TODO! +//uint32_t F64_asuint(float f); +//int32_t F64_asint(float f); // ----------------------------- I32 ----------------------------------------- -int32_t I32_abs(int32_t f) { return (f < 0) ? -f : f; } +SLANG_FORCE_INLINE int32_t I32_abs(int32_t f) { return (f < 0) ? -f : f; } -int32_t I32_min(int32_t a, int32_t b) { return a < b ? a : b; } -int32_t I32_max(int32_t a, int32_t b) { return a > b ? a : b; } +SLANG_FORCE_INLINE int32_t I32_min(int32_t a, int32_t b) { return a < b ? a : b; } +SLANG_FORCE_INLINE int32_t I32_max(int32_t a, int32_t b) { return a > b ? a : b; } -int32_t I32_clamp(int32_t x, int32_t min, int32_t max) { return ( x < min) ? min : ((x > max) ? max : x); } +SLANG_FORCE_INLINE int32_t I32_clamp(int32_t x, int32_t min, int32_t max) { return ( x < min) ? min : ((x > max) ? max : x); } -float I32_asfloat(int32_t x) { Union32 u; u.i = x; return u.f; } +SLANG_FORCE_INLINE float I32_asfloat(int32_t x) { Union32 u; u.i = x; return u.f; } // ----------------------------- U32 ----------------------------------------- -uint32_t U32_abs(uint32_t f) { return f; } +SLANG_FORCE_INLINE uint32_t U32_abs(uint32_t f) { return f; } -uint32_t U32_min(uint32_t a, uint32_t b) { return a < b ? a : b; } -uint32_t U32_max(uint32_t a, uint32_t b) { return a > b ? a : b; } +SLANG_FORCE_INLINE uint32_t U32_min(uint32_t a, uint32_t b) { return a < b ? a : b; } +SLANG_FORCE_INLINE uint32_t U32_max(uint32_t a, uint32_t b) { return a > b ? a : b; } -uint32_t U32_clamp(uint32_t x, uint32_t min, uint32_t max) { return ( x < min) ? min : ((x > max) ? max : x); } +SLANG_FORCE_INLINE uint32_t U32_clamp(uint32_t x, uint32_t min, uint32_t max) { return ( x < min) ? min : ((x > max) ? max : x); } -float U32_asfloat(uint32_t x) { Union32 u; u.u = x; return u.f; } +SLANG_FORCE_INLINE float U32_asfloat(uint32_t x) { Union32 u; u.u = x; return u.f; } + +#ifdef SLANG_PRELUDE_NAMESPACE +} +#endif + +#endif diff --git a/tools/slang-test/slang-test-main.cpp b/tools/slang-test/slang-test-main.cpp index b6c29b423..5e7a3d6f3 100644 --- a/tools/slang-test/slang-test-main.cpp +++ b/tools/slang-test/slang-test-main.cpp @@ -3,6 +3,7 @@ #include "../../source/core/slang-io.h" #include "../../source/core/slang-token-reader.h" #include "../../source/core/slang-std-writers.h" +#include "../../source/core/slang-hex-dump-util.h" #include "../../slang-com-helper.h" @@ -30,6 +31,8 @@ using namespace Slang; #include <stdlib.h> #include <stdarg.h> +#define SLANG_PRELUDE_NAMESPACE CPPPrelude +#include "../../tests/cross-compile/slang-cpp-prelude.h" // Options for a particular test struct TestOptions @@ -490,24 +493,41 @@ static bool _hasOption(const List<String>& args, const String& argName) return args.indexOf(argName) != Index(-1); } -static BackendType _toBackendType(const UnownedStringSlice& slice) +static SlangPassThrough _toPassThroughType(const UnownedStringSlice& slice) { if (slice == "dxc") { - return BackendType::Dxc; + return SLANG_PASS_THROUGH_DXC; } else if (slice == "fxc") { - return BackendType::Fxc; + return SLANG_PASS_THROUGH_FXC; } else if (slice == "glslang") { - return BackendType::Glslang; + return SLANG_PASS_THROUGH_GLSLANG; } - return BackendType::Unknown; + else if (slice == "c" || slice == "cpp") + { + return SLANG_PASS_THROUGH_GENERIC_C_CPP; + } + else if (slice == "clang") + { + return SLANG_PASS_THROUGH_CLANG; + } + else if (slice == "gcc") + { + return SLANG_PASS_THROUGH_GCC; + } + else if (slice == "vs" || slice == "visualstudio") + { + return SLANG_PASS_THROUGH_VISUAL_STUDIO; + } + + return SLANG_PASS_THROUGH_NONE; } -static BackendFlags _getBackendFlagsForTarget(SlangCompileTarget target) +static PassThroughFlags _getPassThroughFlagsForTarget(SlangCompileTarget target) { switch (target) { @@ -523,18 +543,25 @@ static BackendFlags _getBackendFlagsForTarget(SlangCompileTarget target) case SLANG_DXBC: case SLANG_DXBC_ASM: { - return BackendFlag::Fxc; + return PassThroughFlag::Fxc; } case SLANG_SPIRV: case SLANG_SPIRV_ASM: { - return BackendFlag::Glslang; + return PassThroughFlag::Glslang; } case SLANG_DXIL: case SLANG_DXIL_ASM: { - return BackendFlag::Dxc; + return PassThroughFlag::Dxc; + } + + case SLANG_EXECUTABLE: + case SLANG_SHARED_LIBRARY: + { + return PassThroughFlag::Generic_C_CPP; } + default: { SLANG_ASSERT(!"Unknown type"); @@ -543,17 +570,6 @@ static BackendFlags _getBackendFlagsForTarget(SlangCompileTarget target) } } -static BackendType _toBackendTypeFromPassThroughType(SlangPassThrough passThru) -{ - switch (passThru) - { - case SLANG_PASS_THROUGH_DXC: return BackendType::Dxc; - case SLANG_PASS_THROUGH_FXC: return BackendType::Fxc; - case SLANG_PASS_THROUGH_GLSLANG: return BackendType::Glslang; - default: return BackendType::Unknown; - } -} - static SlangCompileTarget _getCompileTarget(const UnownedStringSlice& name) { #define CASE(NAME, TARGET) if(name == NAME) return SLANG_##TARGET; @@ -571,6 +587,9 @@ static SlangCompileTarget _getCompileTarget(const UnownedStringSlice& name) CASE("dxil-asm", DXIL_ASM) CASE("c", C_SOURCE) CASE("cpp", CPP_SOURCE) + CASE("exe", EXECUTABLE) + CASE("sharedlib", SHARED_LIBRARY) + CASE("dll", SHARED_LIBRARY) #undef CASE return SLANG_TARGET_UNKNOWN; @@ -680,11 +699,11 @@ static SlangResult _extractRenderTestRequirements(const CommandLine& cmdLine, Te if (passThru == SLANG_PASS_THROUGH_NONE) { // Work out backends needed based on the target - ioRequirements->addUsedBackends(_getBackendFlagsForTarget(target)); + ioRequirements->addUsedBackends(_getPassThroughFlagsForTarget(target)); } else { - ioRequirements->addUsed(_toBackendTypeFromPassThroughType(passThru)); + ioRequirements->addUsed(passThru); } // Add the render api used @@ -701,7 +720,7 @@ static SlangResult _extractSlangCTestRequirements(const CommandLine& cmdLine, Te String passThrough; if (SLANG_SUCCEEDED(_extractArg(cmdLine, "-pass-through", passThrough))) { - ioRequirements->addUsed(_toBackendType(passThrough.getUnownedSlice())); + ioRequirements->addUsed(_toPassThroughType(passThrough.getUnownedSlice())); } } @@ -711,7 +730,7 @@ static SlangResult _extractSlangCTestRequirements(const CommandLine& cmdLine, Te if (SLANG_SUCCEEDED(_extractArg(cmdLine, "-target", targetName))) { const SlangCompileTarget target = _getCompileTarget(targetName.getUnownedSlice()); - ioRequirements->addUsedBackends(_getBackendFlagsForTarget(target)); + ioRequirements->addUsedBackends(_getPassThroughFlagsForTarget(target)); } } return SLANG_OK; @@ -921,17 +940,48 @@ TestResult asTestResult(ToolReturnCode code) } \ } +static SlangResult _executeBinary(const UnownedStringSlice& hexDump, ExecuteResult& outExeRes) +{ + // We need to extract the binary + List<uint8_t> data; + SLANG_RETURN_ON_FAIL(HexDumpUtil::parseWithMarkers(hexDump, data)); + + // Need to write this off to a temporary file + String fileName; + SLANG_RETURN_ON_FAIL(File::generateTemporary(UnownedStringSlice("slang-test"), fileName)); + + fileName.append(ProcessUtil::getExecutableSuffix()); + + TemporaryFileSet temporaryFileSet; + temporaryFileSet.add(fileName); + + { + ComPtr<ISlangWriter> writer; + SLANG_RETURN_ON_FAIL(FileWriter::createBinary(fileName.getBuffer(), 0, writer)); + + SLANG_RETURN_ON_FAIL(writer->write((const char*)data.getBuffer(), data.getCount())); + } + + // Make executable... (for linux/unix like targets) + SLANG_RETURN_ON_FAIL(File::makeExecutable(fileName)); + + // Execute it + CommandLine cmdLine; + cmdLine.m_executable = fileName; + cmdLine.m_executableType = CommandLine::ExecutableType::Path; + return ProcessUtil::execute(cmdLine, outExeRes); +} + + TestResult runSimpleTest(TestContext* context, TestInput& input) { // need to execute the stand-alone Slang compiler on the file, and compare its output to what we expect - - auto filePath999 = input.filePath; auto outputStem = input.outputStem; CommandLine cmdLine; _initSlangCompiler(context, cmdLine); - cmdLine.addArg(filePath999); + cmdLine.addArg(input.filePath); for( auto arg : input.testOptions->args ) { @@ -946,6 +996,28 @@ TestResult runSimpleTest(TestContext* context, TestInput& input) return TestResult::Pass; } + // See what kind of target it is + SlangCompileTarget target = SLANG_TARGET_UNKNOWN; + { + const auto& args = input.testOptions->args; + const Index targetIndex = args.indexOf("-target"); + if (targetIndex != Index(-1) && targetIndex + 1 < args.getCount()) + { + target = _getCompileTarget(args[targetIndex + 1].getUnownedSlice()); + } + } + + // If it's executable we run it and use it's output + if (target == SLANG_EXECUTABLE) + { + ExecuteResult runExeRes; + if (SLANG_FAILED(_executeBinary(exeRes.standardOutput.getUnownedSlice(), runExeRes))) + { + return TestResult::Fail; + } + exeRes = runExeRes; + } + String actualOutput = getOutput(exeRes); String expectedOutputPath = outputStem + ".expected"; @@ -988,6 +1060,136 @@ TestResult runSimpleTest(TestContext* context, TestInput& input) return result; } +static SlangResult _loadAsSharedLibrary(const UnownedStringSlice& hexDump, TemporaryFileSet& inOutTemporaryFileSet, SharedLibrary::Handle& outSharedLibrary) +{ + // We need to extract the binary + List<uint8_t> data; + SLANG_RETURN_ON_FAIL(HexDumpUtil::parseWithMarkers(hexDump, data)); + + // Need to write this off to a temporary file + String fileName; + SLANG_RETURN_ON_FAIL(File::generateTemporary(UnownedStringSlice("slang-test"), fileName)); + + // Need to work out the dll name + String sharedLibraryName = SharedLibrary::calcPlatformPath(fileName.getUnownedSlice()); + inOutTemporaryFileSet.add(sharedLibraryName); + + { + ComPtr<ISlangWriter> writer; + SLANG_RETURN_ON_FAIL(FileWriter::createBinary(sharedLibraryName.getBuffer(), 0, writer)); + SLANG_RETURN_ON_FAIL(writer->write((const char*)data.getBuffer(), data.getCount())); + } + + // Make executable... (for linux/unix like targets) + //SLANG_RETURN_ON_FAIL(File::makeExecutable(fileName)); + + return SharedLibrary::loadWithPlatformPath(sharedLibraryName.getBuffer(), outSharedLibrary); +} + +static void _writeBuffer(const CPPPrelude::RWStructuredBuffer<int32_t>& in, StringBuilder& out) +{ + for (size_t i = 0; i < in.count; ++i) + { + if (i > 0) + { + out << ", "; + } + out << in[i]; + } + out << "\n"; +} + +TestResult runCPUExecuteTest(TestContext* context, TestInput& input) +{ + auto outputStem = input.outputStem; + + CommandLine cmdLine; + _initSlangCompiler(context, cmdLine); + + cmdLine.addArg(input.filePath); + + for (auto arg : input.testOptions->args) + { + cmdLine.addArg(arg); + } + + ExecuteResult exeRes; + TEST_RETURN_ON_DONE(spawnAndWait(context, outputStem, input.spawnType, cmdLine, exeRes)); + + if (context->isCollectingRequirements()) + { + return TestResult::Pass; + } + + TemporaryFileSet temporaryFileSet; + SharedLibrary::Handle sharedLibrary = SharedLibrary::Handle(0); + if (SLANG_FAILED(_loadAsSharedLibrary(exeRes.standardOutput.getUnownedSlice(), temporaryFileSet, sharedLibrary))) + { + return TestResult::Fail; + } + + StringBuilder actualOutput; + + // TODO(JS): For moment just assume function name/data/paramters + { + SharedLibrary::FuncPtr func = SharedLibrary::findFuncByName(sharedLibrary, "computeMain"); + if (!func) + { + SharedLibrary::unload(sharedLibrary); + return TestResult::Fail; + } + + typedef void (*Func)(CPPPrelude::Vector<uint32_t,3> threadID, CPPPrelude::RWStructuredBuffer<int32_t> buffer); + + Func runFunc = Func(func); + int32_t data[4] = { 0, 0, 0, 0}; + CPPPrelude::RWStructuredBuffer<int32_t> buffer{data, 4}; + + for (Int i = 0; i < 4; ++i) + { + CPPPrelude::Vector<uint32_t, 3> threadID{ uint32_t(i), 0, 0}; + runFunc(threadID, buffer); + } + + SharedLibrary::unload(sharedLibrary); + + // Write the data + _writeBuffer(buffer, actualOutput); + } + + String expectedOutputPath = outputStem + ".expected"; + String expectedOutput; + try + { + expectedOutput = Slang::File::readAllText(expectedOutputPath); + } + catch (Slang::IOException) + { + } + + TestResult result = TestResult::Pass; + + // Otherwise we compare to the expected output + if (actualOutput != expectedOutput) + { + context->reporter->dumpOutputDifference(expectedOutput, actualOutput); + result = TestResult::Fail; + } + + // If the test failed, then we write the actual output to a file + // so that we can easily diff it from the command line and + // diagnose the problem. + if (result == TestResult::Fail) + { + String actualOutputPath = outputStem + ".actual"; + Slang::File::writeAllText(actualOutputPath, actualOutput); + + context->reporter->dumpOutputDifference(expectedOutput, actualOutput); + } + + return result; +} + TestResult runSimpleCompareCommandLineTest(TestContext* context, TestInput& input) { TestInput workInput(input); @@ -1158,6 +1360,8 @@ static TestResult runCPPCompilerCompile(TestContext* context, TestInput& input) CPPCompiler::CompileOptions options; options.sourceType = (targetExt == "c") ? CPPCompiler::SourceType::C : CPPCompiler::SourceType::CPP; + options.includePaths.add("tests/cross-compile"); + // Create a filename to write this out to String cppSource = modulePath + "." + targetExt; Slang::File::writeAllText(cppSource, actualOutput); @@ -2217,7 +2421,8 @@ static const TestCommandInfo s_testCommandInfos[] = { "CROSS_COMPILE", &runCrossCompilerTest}, { "CPP_COMPILER_EXECUTE", &runCPPCompilerExecute}, { "CPP_COMPILER_SHARED_LIBRARY", &runCPPCompilerSharedLibrary}, - { "CPP_COMPILER_COMPILE", &runCPPCompilerCompile} + { "CPP_COMPILER_COMPILE", &runCPPCompilerCompile}, + { "CPU_EXECUTE", &runCPUExecuteTest}, }; TestResult runTest( @@ -2624,44 +2829,42 @@ SlangResult innerMain(int argc, char** argv) auto unixCatagory = categorySet.add("unix", fullTestCategory); #endif + // An un-categorized test will always belong to the `full` category + categorySet.defaultCategory = fullTestCategory; + + TestCategory* fxcCategory = nullptr; TestCategory* dxcCategory = nullptr; TestCategory* glslangCategory = nullptr; - // Might be better if we had an API on slang so we could get what 'pass-through's are available - // This works whilst these targets imply the pass-through/backends + // Work out what backends/pass-thrus are available { SlangSession* session = context.getSession(); - if (SLANG_SUCCEEDED(spSessionCheckPassThroughSupport(session, SLANG_PASS_THROUGH_FXC))) + + for (int i = 0; i < SLANG_PASS_THROUGH_COUNT_OF; ++i) + { + SlangPassThrough passThru = SlangPassThrough(i); + + if (SLANG_SUCCEEDED(spSessionCheckPassThroughSupport(session, passThru))) + { + context.availableBackendFlags |= PassThroughFlags(1) << int(i); + } + } + + if (context.availableBackendFlags & PassThroughFlag::Fxc) { fxcCategory = categorySet.add("fxc", fullTestCategory); } - if (SLANG_SUCCEEDED(spSessionCheckPassThroughSupport(session, SLANG_PASS_THROUGH_GLSLANG))) + if (context.availableBackendFlags & PassThroughFlag::Glslang) { glslangCategory = categorySet.add("glslang", fullTestCategory); } - if (SLANG_SUCCEEDED(spSessionCheckPassThroughSupport(session, SLANG_PASS_THROUGH_DXC))) + if (context.availableBackendFlags & PassThroughFlag::Dxc) { dxcCategory = categorySet.add("dxc", fullTestCategory); } } - // An un-categorized test will always belong to the `full` category - categorySet.defaultCategory = fullTestCategory; - - // Work out what backends are available - { - SlangSession* session = context.getSession(); - const SlangPassThrough passThrus[] = { SLANG_PASS_THROUGH_DXC, SLANG_PASS_THROUGH_FXC, SLANG_PASS_THROUGH_GLSLANG }; - for (auto passThru: passThrus) - { - if (SLANG_SUCCEEDED(spSessionCheckPassThroughSupport(session, passThru))) - { - context.availableBackendFlags |= BackendFlags(1) << int(_toBackendTypeFromPassThroughType(passThru)); - } - } - } - // Working out what renderApis is worked on on demand through // _getAvailableRenderApiFlags() diff --git a/tools/slang-test/test-context.h b/tools/slang-test/test-context.h index 895cb9c06..a66047687 100644 --- a/tools/slang-test/test-context.h +++ b/tools/slang-test/test-context.h @@ -13,23 +13,18 @@ #include "options.h" -enum class BackendType +typedef uint32_t PassThroughFlags; +struct PassThroughFlag { - Unknown = -1, - Dxc, - Fxc, - Glslang, - CountOf, -}; - -typedef uint32_t BackendFlags; -struct BackendFlag -{ - enum Enum : uint32_t + enum Enum : PassThroughFlags { - Dxc = 1 << int(BackendType::Dxc), - Fxc = 1 << int(BackendType::Fxc), - Glslang = 1 << int(BackendType::Glslang), + Dxc = 1 << int(SLANG_PASS_THROUGH_DXC), + Fxc = 1 << int(SLANG_PASS_THROUGH_FXC), + Glslang = 1 << int(SLANG_PASS_THROUGH_GLSLANG), + VisualStudio = 1 << int(SLANG_PASS_THROUGH_VISUAL_STUDIO), + GCC = 1 << int(SLANG_PASS_THROUGH_GCC), + Clang = 1 << int(SLANG_PASS_THROUGH_CLANG), + Generic_C_CPP = 1 << int(SLANG_PASS_THROUGH_GENERIC_C_CPP), }; }; @@ -37,11 +32,11 @@ struct BackendFlag /// back-end availability struct TestRequirements { - TestRequirements& addUsed(BackendType type) + TestRequirements& addUsed(SlangPassThrough type) { - if (type != BackendType::Unknown) + if (type != SLANG_PASS_THROUGH_NONE) { - usedBackendFlags |= BackendFlags(1) << int(type); + usedBackendFlags |= PassThroughFlags(1) << int(type); } return *this; } @@ -54,7 +49,7 @@ struct TestRequirements } return *this; } - TestRequirements& addUsedBackends(BackendFlags flags) + TestRequirements& addUsedBackends(PassThroughFlags flags) { usedBackendFlags |= flags; return *this; @@ -71,7 +66,7 @@ struct TestRequirements } Slang::RenderApiType explicitRenderApi = Slang::RenderApiType::Unknown; ///< The render api explicitly specified - BackendFlags usedBackendFlags = 0; ///< Used backends + PassThroughFlags usedBackendFlags = 0; ///< Used backends Slang::RenderApiFlags usedRenderApiFlags = 0; ///< Used render api flags (some might be implied) }; @@ -111,7 +106,7 @@ class TestContext /// If set then tests are not run, but their requirements are set TestRequirements* testRequirements = nullptr; - BackendFlags availableBackendFlags = 0; + PassThroughFlags availableBackendFlags = 0; Slang::RenderApiFlags availableRenderApiFlags = 0; bool isAvailableRenderApiFlagsValid = false; |
