summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Foley <tfoley@nvidia.com>2017-06-19 08:47:23 -0700
committerTim Foley <tfoley@nvidia.com>2017-06-19 09:56:42 -0700
commit634522da69b14b38c15b14d6b717b1289812e9bb (patch)
tree04f502283ed818f661e368b3d8d1ba5ce875d1d2
parentcafed774d99f95bce6f182599913f3417dc68a3a (diff)
Allow for automatic importing of Slang code
The basic idea of this change is that user code can just write: #include "foo.h" and then if `foo.h` gets found in a list of registered directories for "auto-import," then it actually gets interpreted as if the user had writte, more or less: __import foo; That is, the code in `foo.h` will be treated as Slang, and will be fully parsed and checked (no matter what the source language had been), and the scoping rules will be those of `__import` instead of `#include`. This is a really big hammer, and I could imagine it smashing fingers if used poorly. I'm not sure this feature will pan out, but we need to try things to know. One big piece of that that I'll likely keep in either case is an overhaul of command-line options parsing for `slangc`. In particular, this logic has been moved into the core `slang` library (so that users can just pass options in via the API), and it is all done on UTF-8 strings rather than wide strings (which was always going to be Windows-specific).
-rw-r--r--slang.h22
-rw-r--r--source/core/slang-io.h5
-rw-r--r--source/core/slang-string.h10
-rw-r--r--source/slang/compiler.h22
-rw-r--r--source/slang/options.cpp566
-rw-r--r--source/slang/parser.cpp14
-rw-r--r--source/slang/preprocessor.cpp88
-rw-r--r--source/slang/preprocessor.h24
-rw-r--r--source/slang/slang.cpp172
-rw-r--r--source/slang/slang.vcxproj1
-rw-r--r--source/slang/slang.vcxproj.filters1
-rw-r--r--source/slang/token-defs.h2
-rw-r--r--source/slangc/main.cpp600
-rw-r--r--tests/render/auto-import.hlsl147
-rw-r--r--tests/render/auto-import.slang.h21
-rw-r--r--tools/render-test/options.cpp16
-rw-r--r--tools/render-test/options.h9
-rw-r--r--tools/render-test/slang-support.cpp2
18 files changed, 1083 insertions, 639 deletions
diff --git a/slang.h b/slang.h
index 11943c7d4..5fcbef694 100644
--- a/slang.h
+++ b/slang.h
@@ -194,8 +194,8 @@ extern "C"
void const* userData);
/*!
- @brief Add a path in which source files are being search. When the programmer specifies @code using <file_name> @endcode in code, the compiler searches the file
- in all search pathes in order.
+ @brief Add a path to use when searching for referenced files.
+ This will be used for both `#include` directives and also for explicit `__import` declarations.
@param ctx The compilation context.
@param searchDir The additional search directory.
*/
@@ -204,6 +204,16 @@ extern "C"
const char* searchDir);
/*!
+ @brief Add a path to use when searching for referenced files, that automatically treats `#include` as `__import`
+ This behaves just like `spAddSearchPath()` except that any `#include` file found through this path
+ will be treated as if it was referenced with `__import`.
+ @param ctx The compilation context.
+ @param searchDir The additional search directory.
+ */
+ SLANG_API void spAddAutoImportPath(
+ SlangCompileRequest* request,
+ const char* searchDir);
+ /*!
@brief Add a macro definition to be used during preprocessing.
@param key The name of the macro to define.
@param value The value of the macro to define.
@@ -213,6 +223,13 @@ extern "C"
const char* key,
const char* value);
+ /*!
+ @brief Set options using arguments as if specified via command line.
+ */
+ SLANG_API int spProcessCommandLineArguments(
+ SlangCompileRequest* request,
+ char const* const* args,
+ int argCount);
/** Add a distinct translation unit to the compilation request
@@ -890,6 +907,7 @@ namespace slang
#include "source/slang/diagnostics.cpp"
#include "source/slang/emit.cpp"
#include "source/slang/lexer.cpp"
+#include "source/slang/options.cpp"
#include "source/slang/parameter-binding.cpp"
#include "source/slang/parser.cpp"
#include "source/slang/preprocessor.cpp"
diff --git a/source/core/slang-io.h b/source/core/slang-io.h
index 869fad873..2f140c3ad 100644
--- a/source/core/slang-io.h
+++ b/source/core/slang-io.h
@@ -20,11 +20,8 @@ namespace Slang
class Path
{
public:
-#ifdef _WIN32
- static const char PathDelimiter = '\\';
-#else
static const char PathDelimiter = '/';
-#endif
+
static String TruncateExt(const String & path);
static String ReplaceExt(const String & path, const char * newExt);
static String GetFileName(const String & path);
diff --git a/source/core/slang-string.h b/source/core/slang-string.h
index 80eb00605..448b351aa 100644
--- a/source/core/slang-string.h
+++ b/source/core/slang-string.h
@@ -141,6 +141,16 @@ namespace Slang
memcpy(buffer.Ptr(), str, length + 1);
}
}
+ String(const char* textBegin, char const* textEnd)
+ {
+ if (textBegin != textEnd)
+ {
+ length = (int)(textEnd - textBegin);
+ buffer = new char[length + 1];
+ memcpy(buffer.Ptr(), textBegin, length + 1);
+ buffer.Ptr()[length] = 0;
+ }
+ }
String(char chr)
{
if (chr)
diff --git a/source/slang/compiler.h b/source/slang/compiler.h
index f34be794b..38640fd0f 100644
--- a/source/slang/compiler.h
+++ b/source/slang/compiler.h
@@ -91,6 +91,26 @@ namespace Slang
Dictionary<String, String> preprocessorDefinitions;
};
+
+ struct SearchDirectory
+ {
+ enum Kind
+ {
+ Default,
+ AutoImport,
+ };
+
+ SearchDirectory() = default;
+ SearchDirectory(SearchDirectory const& other) = default;
+ SearchDirectory(String const& path, Kind kind)
+ : path(path)
+ , kind(kind)
+ {}
+
+ String path;
+ Kind kind;
+ };
+
class CompileOptions
{
public:
@@ -98,7 +118,7 @@ namespace Slang
CodeGenTarget Target = CodeGenTarget::Unknown;
// Directories to search for `#include` files or `import`ed modules
- List<String> SearchDirectories;
+ List<SearchDirectory> searchDirectories;
// Definitions to provide during preprocessing
Dictionary<String, String> preprocessorDefinitions;
diff --git a/source/slang/options.cpp b/source/slang/options.cpp
new file mode 100644
index 000000000..b3f0629c6
--- /dev/null
+++ b/source/slang/options.cpp
@@ -0,0 +1,566 @@
+// options.cpp
+
+// Implementation of options parsing for `slangc` command line,
+// and also for API interface that takes command-line argument strings.
+
+#include "../../slang.h"
+
+#include "profile.h"
+
+#include <assert.h>
+
+namespace Slang {
+
+char const* tryReadCommandLineArgumentRaw(char const* option, char const* const**ioCursor, char const* const*end)
+{
+ char const* const*& cursor = *ioCursor;
+ if (cursor == end)
+ {
+ fprintf(stderr, "expected an argument for command-line option '%s'", option);
+ exit(1);
+ }
+ else
+ {
+ return *cursor++;
+ }
+}
+
+String tryReadCommandLineArgument(char const* option, char const* const**ioCursor, char const* const*end)
+{
+ return String(tryReadCommandLineArgumentRaw(option, ioCursor, end));
+}
+
+
+
+
+
+struct OptionsParser
+{
+ SlangSession* session = nullptr;
+ SlangCompileRequest* compileRequest = nullptr;
+
+ struct RawTranslationUnit
+ {
+ SlangSourceLanguage sourceLanguage;
+ SlangProfileID implicitProfile;
+ int translationUnitIndex;
+ };
+
+ // Collect translation units so that we can futz with them later
+ List<RawTranslationUnit> rawTranslationUnits;
+
+ struct RawEntryPoint
+ {
+ String name;
+ SlangProfileID profileID = SLANG_PROFILE_UNKNOWN;
+ int translationUnitIndex = -1;
+ };
+
+ // Collect entry point names, so that we can associate them
+ // with entry points later...
+ List<RawEntryPoint> rawEntryPoints;
+
+ // The number of input files that have been specified
+ int inputPathCount = 0;
+
+ // If we already have a translation unit for Slang code, then this will give its index.
+ // If not, it will be `-1`.
+ int slangTranslationUnit = -1;
+
+ int translationUnitCount = 0;
+ int currentTranslationUnitIndex = -1;
+
+ SlangProfileID currentProfileID = SLANG_PROFILE_UNKNOWN;
+
+ SlangCompileFlags flags = 0;
+
+ int addTranslationUnit(
+ SlangSourceLanguage language,
+ SlangProfileID implicitProfile = SLANG_PROFILE_UNKNOWN)
+ {
+ auto translationUnitIndex = spAddTranslationUnit(compileRequest, language, nullptr);
+
+ assert(translationUnitIndex == rawTranslationUnits.Count());
+
+ RawTranslationUnit rawTranslationUnit;
+ rawTranslationUnit.sourceLanguage = language;
+ rawTranslationUnit.implicitProfile = implicitProfile;
+ rawTranslationUnit.translationUnitIndex = translationUnitIndex;
+
+ rawTranslationUnits.Add(rawTranslationUnit);
+
+ return translationUnitIndex;
+ }
+
+ void addInputSlangPath(
+ String const& path)
+ {
+ // All of the input .slang files will be grouped into a single logical translation unit,
+ // which we create lazily when the first .slang file is encountered.
+ if( slangTranslationUnit == -1 )
+ {
+ translationUnitCount++;
+ slangTranslationUnit = addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG);
+ }
+
+ spAddTranslationUnitSourceFile(
+ compileRequest,
+ slangTranslationUnit,
+ path.begin());
+
+ // Set the translation unit to be used by subsequent entry points
+ currentTranslationUnitIndex = slangTranslationUnit;
+ }
+
+ void addInputForeignShaderPath(
+ String const& path,
+ SlangSourceLanguage language,
+ SlangProfileID implicitProfile = SLANG_PROFILE_UNKNOWN)
+ {
+ translationUnitCount++;
+ currentTranslationUnitIndex = addTranslationUnit(language, implicitProfile);
+
+ spAddTranslationUnitSourceFile(
+ compileRequest,
+ currentTranslationUnitIndex,
+ path.begin());
+ }
+
+ void addInputPath(
+ char const* inPath)
+ {
+ inputPathCount++;
+
+ // look at the extension on the file name to determine
+ // how we should handle it.
+ String path = String(inPath);
+
+ if( path.EndsWith(".slang") )
+ {
+ // Plain old slang code
+ addInputSlangPath(path);
+ }
+#define CASE(EXT, LANG) \
+ else if(path.EndsWith(EXT)) do { addInputForeignShaderPath(path, SLANG_SOURCE_LANGUAGE_##LANG); } while(0)
+
+ CASE(".hlsl", HLSL);
+ CASE(".fx", HLSL);
+
+ CASE(".glsl", GLSL);
+#undef CASE
+
+#define CASE(EXT, LANG, PROFILE) \
+ else if(path.EndsWith(EXT)) do { addInputForeignShaderPath(path, SLANG_SOURCE_LANGUAGE_##LANG, SlangProfileID(Slang::Profile::PROFILE)); } while(0)
+ // TODO: need a way to pass along stage/profile and entry-point info for these cases...
+ CASE(".vert", GLSL, GLSL_Vertex);
+ CASE(".frag", GLSL, GLSL_Fragment);
+ CASE(".geom", GLSL, GLSL_Geometry);
+ CASE(".tesc", GLSL, GLSL_TessControl);
+ CASE(".tese", GLSL, GLSL_TessEval);
+ CASE(".comp", GLSL, GLSL_Compute);
+
+#undef CASE
+
+ else
+ {
+ fprintf(stderr, "error: can't deduce language for input file '%s'\n", inPath);
+ exit(1);
+ }
+ }
+
+ int parse(
+ int argc,
+ char const* const* argv)
+ {
+ char const* const* argCursor = &argv[0];
+ char const* const* argEnd = &argv[argc];
+ while (argCursor != argEnd)
+ {
+ char const* arg = *argCursor++;
+ if (arg[0] == '-')
+ {
+ String argStr = String(arg);
+
+ // The argument looks like an option, so try to parse it.
+// if (argStr == "-outdir")
+// outputDir = tryReadCommandLineArgument(arg, &argCursor, argEnd);
+// if (argStr == "-out")
+// options.outputName = tryReadCommandLineArgument(arg, &argCursor, argEnd);
+// else if (argStr == "-symbo")
+// options.SymbolToCompile = tryReadCommandLineArgument(arg, &argCursor, argEnd);
+ //else
+ if (argStr == "-no-checking")
+ flags |= SLANG_COMPILE_FLAG_NO_CHECKING;
+ else if (argStr == "-backend" || argStr == "-target")
+ {
+ String name = tryReadCommandLineArgument(arg, &argCursor, argEnd);
+ SlangCompileTarget target = SLANG_TARGET_UNKNOWN;
+
+ if (name == "glsl")
+ {
+ target = SLANG_GLSL;
+ }
+ else if (name == "glsl_vk")
+ {
+ target = SLANG_GLSL_VULKAN;
+ }
+// else if (name == "glsl_vk_onedesc")
+// {
+// options.Target = CodeGenTarget::GLSL_Vulkan_OneDesc;
+// }
+ else if (name == "hlsl")
+ {
+ target = SLANG_HLSL;
+ }
+ else if (name == "spriv")
+ {
+ target = SLANG_SPIRV;
+ }
+ else if (name == "dxbc")
+ {
+ target = SLANG_DXBC;
+ }
+ else if (name == "dxbc-assembly")
+ {
+ target = SLANG_DXBC_ASM;
+ }
+ #define CASE(NAME, TARGET) \
+ else if(name == #NAME) do { target = SLANG_##TARGET; } while(0)
+
+ CASE(spirv, SPIRV);
+ CASE(spirv-assembly, SPIRV_ASM);
+
+ #undef CASE
+
+ else if (name == "reflection-json")
+ {
+ target = SLANG_REFLECTION_JSON;
+ }
+ else
+ {
+ fprintf(stderr, "unknown code generation target '%S'\n", name.ToWString());
+ exit(1);
+ }
+
+ spSetCodeGenTarget(compileRequest, target);
+ }
+ // A "profile" specifies both a specific target stage and a general level
+ // of capability required by the program.
+ else if (argStr == "-profile")
+ {
+ String name = tryReadCommandLineArgument(arg, &argCursor, argEnd);
+
+ SlangProfileID profileID = spFindProfile(session, name.begin());
+ if( profileID == SLANG_PROFILE_UNKNOWN )
+ {
+ fprintf(stderr, "unknown profile '%s'\n", name.begin());
+ }
+ else
+ {
+ currentProfileID = profileID;
+ }
+ }
+ else if (argStr == "-entry")
+ {
+ String name = tryReadCommandLineArgument(arg, &argCursor, argEnd);
+
+ RawEntryPoint entry;
+ entry.name = name;
+ entry.translationUnitIndex = currentTranslationUnitIndex;
+
+ // TODO(tfoley): Allow user to fold a specification of a profile into the entry-point name,
+ // for the case where they might be compiling multiple entry points in one invocation...
+ //
+ // For now, just use the last profile set on the command-line to specify this
+
+ entry.profileID = currentProfileID;
+
+ rawEntryPoints.Add(entry);
+ }
+#if 0
+ else if (argStr == "-stage")
+ {
+ String name = tryReadCommandLineArgument(arg, &argCursor, argEnd);
+ StageTarget stage = StageTarget::Unknown;
+ if (name == "vertex") { stage = StageTarget::VertexShader; }
+ else if (name == "fragment") { stage = StageTarget::FragmentShader; }
+ else if (name == "hull") { stage = StageTarget::HullShader; }
+ else if (name == "domain") { stage = StageTarget::DomainShader; }
+ else if (name == "compute") { stage = StageTarget::ComputeShader; }
+ else
+ {
+ fprintf(stderr, "unknown stage '%S'\n", name.ToWString());
+ }
+ options.stage = stage;
+ }
+#endif
+ else if (argStr == "-pass-through")
+ {
+ String name = tryReadCommandLineArgument(arg, &argCursor, argEnd);
+ SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE;
+ if (name == "fxc") { passThrough = SLANG_PASS_THROUGH_FXC; }
+ else if (name == "glslang") { passThrough = SLANG_PASS_THROUGH_GLSLANG; }
+ else
+ {
+ fprintf(stderr, "unknown pass-through target '%S'\n", name.ToWString());
+ exit(1);
+ }
+
+ spSetPassThrough(
+ compileRequest,
+ passThrough);
+ }
+// else if (argStr == "-genchoice")
+// options.Mode = CompilerMode::GenerateChoice;
+ else if (argStr[1] == 'D')
+ {
+ // The value to be defined might be part of the same option, as in:
+ // -DFOO
+ // or it might come separately, as in:
+ // -D FOO
+ char const* defineStr = arg + 2;
+ if (defineStr[0] == 0)
+ {
+ // Need to read another argument from the command line
+ defineStr = tryReadCommandLineArgumentRaw(arg, &argCursor, argEnd);
+ }
+ // The string that sets up the define can have an `=` between
+ // the name to be defined and its value, so we search for one.
+ char const* eqPos = nullptr;
+ for(char const* dd = defineStr; *dd; ++dd)
+ {
+ if (*dd == '=')
+ {
+ eqPos = dd;
+ break;
+ }
+ }
+
+ // Now set the preprocessor define
+ //
+ if (eqPos)
+ {
+ // If we found an `=`, we split the string...
+
+ spAddPreprocessorDefine(
+ compileRequest,
+ String(defineStr, eqPos).begin(),
+ String(eqPos+1).begin());
+ }
+ else
+ {
+ // If there was no `=`, then just #define it to an empty string
+
+ spAddPreprocessorDefine(
+ compileRequest,
+ String(defineStr).begin(),
+ "");
+ }
+ }
+ else if (argStr[1] == 'I')
+ {
+ // The value to be defined might be part of the same option, as in:
+ // -IFOO
+ // or it might come separately, as in:
+ // -I FOO
+ // (see handling of `-D` above)
+ char const* includeDirStr = arg + 2;
+ if (includeDirStr[0] == 0)
+ {
+ // Need to read another argument from the command line
+ includeDirStr = tryReadCommandLineArgumentRaw(arg, &argCursor, argEnd);
+ }
+
+ spAddSearchPath(
+ compileRequest,
+ String(includeDirStr).begin());
+ }
+ else if (argStr == "-auto-import-dir")
+ {
+ char const* importDirStr = tryReadCommandLineArgumentRaw(arg, &argCursor, argEnd);
+
+ spAddAutoImportPath(
+ compileRequest,
+ String(importDirStr).begin());
+ }
+ else if (argStr == "--")
+ {
+ // The `--` option causes us to stop trying to parse options,
+ // and treat the rest of the command line as input file names:
+ while (argCursor != argEnd)
+ {
+ addInputPath(*argCursor++);
+ }
+ break;
+ }
+ else
+ {
+ fprintf(stderr, "unknown command-line option '%S'\n", argStr.ToWString());
+ // TODO: print a usage message
+ exit(1);
+ }
+ }
+ else
+ {
+ addInputPath(arg);
+ }
+ }
+
+ spSetCompileFlags(compileRequest, flags);
+
+ // TODO(tfoley): This kind of validation needs to wait until
+ // after all options have been specified for API usage
+#if 0
+ if (inputPathCount == 0)
+ {
+ fprintf(stderr, "error: no input file specified\n");
+ exit(1);
+ }
+
+ // No point in moving forward if there is nothing to compile
+ if( translationUnitCount == 0 )
+ {
+ fprintf(stderr, "error: no compilation requested\n");
+ exit(1);
+ }
+#endif
+
+ // If the user didn't list any explicit entry points, then we can
+ // try to infer one from the type of input file
+ if(rawEntryPoints.Count() == 0)
+ {
+ for(auto rawTranslationUnit : rawTranslationUnits)
+ {
+ // Dont' add implicit entry points when compiling from Slang files,
+ // since Slang doesn't require entry points to be named on the
+ // command line.
+ if(rawTranslationUnit.sourceLanguage == SLANG_SOURCE_LANGUAGE_SLANG )
+ continue;
+
+ // Use a default entry point name
+ char const* entryPointName = "main";
+
+ // Try to determine a profile
+ SlangProfileID entryPointProfile = SLANG_PROFILE_UNKNOWN;
+
+ // If a profile was specified on the command line, then we use it
+ if(currentProfileID != SLANG_PROFILE_UNKNOWN)
+ {
+ entryPointProfile = currentProfileID;
+ }
+ // Otherwise, check if the translation unit implied a profile
+ // (e.g., a `*.vert` file implies the `GLSL_Vertex` profile)
+ else if(rawTranslationUnit.implicitProfile != SLANG_PROFILE_UNKNOWN)
+ {
+ entryPointProfile = rawTranslationUnit.implicitProfile;
+ }
+
+ RawEntryPoint entry;
+ entry.name = entryPointName;
+ entry.translationUnitIndex = rawTranslationUnit.translationUnitIndex;
+ entry.profileID = entryPointProfile;
+ rawEntryPoints.Add(entry);
+ }
+ }
+
+ // For any entry points that were given without an explicit profile, we can now apply
+ // the profile that was given to them.
+ if( rawEntryPoints.Count() != 0 )
+ {
+ bool anyEntryPointWithoutProfile = false;
+ for( auto& entryPoint : rawEntryPoints )
+ {
+ // Skip entry points that are already associated with a translation unit...
+ if( entryPoint.profileID != SLANG_PROFILE_UNKNOWN )
+ continue;
+
+ anyEntryPointWithoutProfile = true;
+ break;
+ }
+
+ if( anyEntryPointWithoutProfile )
+ {
+ fprintf(stderr, "error: no profile specified; use the '-profile <profile name>' option");
+ exit(1);
+ }
+ // TODO: issue an error if we have multiple `-profile` options *and*
+ // there are entry points that didn't get a profile.
+ else
+ {
+ for( auto& e : rawEntryPoints )
+ {
+ if( e.profileID == SLANG_PROFILE_UNKNOWN )
+ {
+ e.profileID = currentProfileID;
+ }
+ }
+ }
+ }
+
+ // Next, we want to make sure that entry points get attached to the appropriate translation
+ // unit that will provide them.
+ {
+ bool anyEntryPointWithoutTranslationUnit = false;
+ for( auto& entryPoint : rawEntryPoints )
+ {
+ // Skip entry points that are already associated with a translation unit...
+ if( entryPoint.translationUnitIndex != -1 )
+ continue;
+
+ anyEntryPointWithoutTranslationUnit = true;
+ entryPoint.translationUnitIndex = 0;
+ }
+
+ if( anyEntryPointWithoutTranslationUnit && translationUnitCount != 1 )
+ {
+ fprintf(stderr, "error: when using multiple translation units, entry points must be specified after their translation unit file(s)");
+ exit(1);
+ }
+
+ // Now place all those entry points where they belong
+ for( auto& entryPoint : rawEntryPoints )
+ {
+ spAddTranslationUnitEntryPoint(
+ compileRequest,
+ entryPoint.translationUnitIndex,
+ entryPoint.name.begin(),
+ entryPoint.profileID);
+ }
+ }
+
+#if 0
+ // Automatically derive an output directory based on the first file specified.
+ //
+ // TODO: require manual specification if there are multiple input files, in different directories
+ String fileName = options.translationUnits[0].sourceFilePaths[0];
+ if (outputDir.Length() == 0)
+ {
+ outputDir = Path::GetDirectoryName(fileName);
+ }
+#endif
+
+ return 0;
+ }
+};
+
+
+int parseOptions(
+ SlangCompileRequest* compileRequest,
+ int argc,
+ char const* const* argv)
+{
+ OptionsParser parser;
+ parser.compileRequest = compileRequest;
+ return parser.parse(argc, argv);
+}
+
+
+} // namespace Slang
+
+SLANG_API int spProcessCommandLineArguments(
+ SlangCompileRequest* request,
+ char const* const* args,
+ int argCount)
+{
+ return Slang::parseOptions(request, argCount, args);
+}
diff --git a/source/slang/parser.cpp b/source/slang/parser.cpp
index b8997b1d7..fb66fbbcf 100644
--- a/source/slang/parser.cpp
+++ b/source/slang/parser.cpp
@@ -802,6 +802,18 @@ namespace Slang
return decl;
}
+ static RefPtr<Decl> parseAutoImportDecl(
+ Parser* parser)
+ {
+ Token importToken = parser->ReadToken(TokenType::AutoImport);
+
+ auto decl = new ImportDecl();
+ decl->nameToken = importToken;
+ decl->scope = parser->currentScope;
+
+ return decl;
+ }
+
static Token ParseDeclName(
Parser* parser)
{
@@ -2159,6 +2171,8 @@ parser->ReadToken(TokenType::Comma);
decl = parseModifierDecl(parser);
else if(parser->LookAheadToken("__import"))
decl = parseImportDecl(parser);
+ else if(parser->LookAheadToken(TokenType::AutoImport))
+ decl = parseAutoImportDecl(parser);
else if (AdvanceIf(parser, TokenType::Semicolon))
{
decl = new EmptyDecl();
diff --git a/source/slang/preprocessor.cpp b/source/slang/preprocessor.cpp
index b8a42d46d..6e0906d9e 100644
--- a/source/slang/preprocessor.cpp
+++ b/source/slang/preprocessor.cpp
@@ -16,7 +16,16 @@
// idioms for using the preprocessor, found in shader code in the wild.
-namespace Slang{
+namespace Slang {
+
+// Forward declaration for code provided in `slang.cpp`
+//
+// TODO: Need an appropriate header for this.
+String autoImportModule(
+ CompileRequest* request,
+ String const& path,
+ String const& source,
+ CodePosition const& loc);
// State of a preprocessor conditional, which can change when
// we encounter directives like `#elif` or `#endif`
@@ -170,6 +179,9 @@ struct Preprocessor
// Syntax for the program we are trying to parse
ProgramSyntaxNode* syntax;
+
+ // The over-arching compile request taht is invoking us
+ CompileRequest* compileRequest;
};
// Convenience routine to access the diagnostic sink
@@ -1475,7 +1487,6 @@ static void HandleIncludeDirective(PreprocessorDirectiveContext* context)
String foundPath;
String foundSource;
-
IncludeHandler* includeHandler = context->preprocessor->includeHandler;
if (!includeHandler)
{
@@ -1483,10 +1494,17 @@ static void HandleIncludeDirective(PreprocessorDirectiveContext* context)
GetSink(context)->diagnose(pathToken.Position, Diagnostics::noIncludeHandlerSpecified);
return;
}
- if (!includeHandler->TryToFindIncludeFile(path, pathIncludedFrom, &foundPath, &foundSource))
+ auto includeResult = includeHandler->TryToFindIncludeFile(path, pathIncludedFrom, &foundPath, &foundSource);
+
+ switch (includeResult)
{
+ case IncludeResult::NotFound:
+ case IncludeResult::Error:
GetSink(context)->diagnose(pathToken.Position, Diagnostics::includeFailed, path);
return;
+
+ case IncludeResult::FoundIncludeFile:
+ break;
}
// Do all checking related to the end of this directive before we push a new stream,
@@ -1494,12 +1512,50 @@ static void HandleIncludeDirective(PreprocessorDirectiveContext* context)
// a switch of input stream
expectEndOfDirective(context);
- // Push the new file onto our stack of input streams
- // TODO(tfoley): check if we have made our include stack too deep
- PreprocessorInputStream* inputStream = CreateInputStreamForSource(context->preprocessor, foundSource, foundPath);
- inputStream->parent = context->preprocessor->inputStream;
- context->preprocessor->inputStream = inputStream;
-}
+ switch( includeResult )
+ {
+ case IncludeResult::FoundIncludeFile:
+ {
+ // Push the new file onto our stack of input streams
+ // TODO(tfoley): check if we have made our include stack too deep
+ PreprocessorInputStream* inputStream = CreateInputStreamForSource(context->preprocessor, foundSource, foundPath);
+ inputStream->parent = context->preprocessor->inputStream;
+ context->preprocessor->inputStream = inputStream;
+ }
+ break;
+
+ case IncludeResult::FoundAutoImportFile:
+ {
+ //
+
+ String autoImportName = autoImportModule(
+ context->preprocessor->compileRequest,
+ foundPath,
+ foundSource,
+ GetDirectiveLoc(context));
+
+ SourceTextInputStream* inputStream = new SourceTextInputStream();
+
+ Token token;
+ token.Type = TokenType::AutoImport;
+ token.Position = GetDirectiveLoc(context);
+ token.flags = 0;
+ token.Content = autoImportName;
+
+ inputStream->lexedTokens.mTokens.Add(token);
+
+ token.Type = TokenType::EndOfFile;
+ token.flags = TokenFlag::AfterWhitespace | TokenFlag::AtStartOfLine;
+ inputStream->lexedTokens.mTokens.Add(token);
+
+ inputStream->tokenReader = TokenReader(inputStream->lexedTokens);
+
+ inputStream->parent = context->preprocessor->inputStream;
+ context->preprocessor->inputStream = inputStream;
+ }
+ break;
+ }
+ }
// Handle a `#define` directive
static void HandleDefineDirective(PreprocessorDirectiveContext* context)
@@ -2007,16 +2063,18 @@ static TokenList ReadAllTokens(
}
TokenList preprocessSource(
- String const& source,
- String const& fileName,
- DiagnosticSink* sink,
- IncludeHandler* includeHandler,
- Dictionary<String, String> defines,
- ProgramSyntaxNode* syntax)
+ String const& source,
+ String const& fileName,
+ DiagnosticSink* sink,
+ IncludeHandler* includeHandler,
+ Dictionary<String, String> defines,
+ ProgramSyntaxNode* syntax,
+ CompileRequest* compileRequest)
{
Preprocessor preprocessor;
InitializePreprocessor(&preprocessor, sink);
preprocessor.syntax = syntax;
+ preprocessor.compileRequest = compileRequest;
preprocessor.includeHandler = includeHandler;
for (auto p : defines)
diff --git a/source/slang/preprocessor.h b/source/slang/preprocessor.h
index a3c5336f5..f16bc3929 100644
--- a/source/slang/preprocessor.h
+++ b/source/slang/preprocessor.h
@@ -7,14 +7,23 @@
namespace Slang {
+struct CompileRequest;
class DiagnosticSink;
class ProgramSyntaxNode;
+enum class IncludeResult
+{
+ Error,
+ NotFound,
+ FoundIncludeFile,
+ FoundAutoImportFile,
+};
+
// Callback interface for the preprocessor to use when looking
// for files in `#include` directives.
struct IncludeHandler
{
- virtual bool TryToFindIncludeFile(
+ virtual IncludeResult TryToFindIncludeFile(
String const& pathToInclude,
String const& pathIncludedFrom,
String* outFoundPath,
@@ -23,12 +32,13 @@ struct IncludeHandler
// Take a string of source code and preprocess it into a list of tokens.
TokenList preprocessSource(
- String const& source,
- String const& fileName,
- DiagnosticSink* sink,
- IncludeHandler* includeHandler,
- Dictionary<String, String> defines,
- ProgramSyntaxNode* syntax);
+ String const& source,
+ String const& fileName,
+ DiagnosticSink* sink,
+ IncludeHandler* includeHandler,
+ Dictionary<String, String> defines,
+ ProgramSyntaxNode* syntax,
+ CompileRequest* compileRequest);
} // namespace Slang
diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp
index ce48f9032..5ec530592 100644
--- a/source/slang/slang.cpp
+++ b/source/slang/slang.cpp
@@ -130,9 +130,9 @@ struct CompileRequest
{
CompileRequest* request;
- List<String> searchDirs;
+ List<SearchDirectory> searchDirs;
- virtual bool TryToFindIncludeFile(
+ virtual IncludeResult TryToFindIncludeFile(
String const& pathToInclude,
String const& pathIncludedFrom,
String* outFoundPath,
@@ -146,12 +146,26 @@ struct CompileRequest
request->mDependencyFilePaths.Add(path);
- return true;
+ // HACK(tfoley): We might have found the file in the same directory,
+ // but what if this is also inside an auto-import path?
+ for (auto & dir : searchDirs)
+ {
+ // Only consider auto-import paths
+ if(dir.kind != SearchDirectory::Kind::AutoImport)
+ continue;
+
+ String otherPath = Path::Combine(dir.path, pathToInclude);
+
+ if(otherPath == path)
+ return IncludeResult::FoundAutoImportFile;
+ }
+
+ return IncludeResult::FoundIncludeFile;
}
for (auto & dir : searchDirs)
{
- path = Path::Combine(dir, pathToInclude);
+ path = Path::Combine(dir.path, pathToInclude);
if (File::Exists(path))
{
*outFoundPath = path;
@@ -159,10 +173,17 @@ struct CompileRequest
request->mDependencyFilePaths.Add(path);
- return true;
+ switch( dir.kind )
+ {
+ case SearchDirectory::Kind::Default:
+ return IncludeResult::FoundIncludeFile;
+
+ case SearchDirectory::Kind::AutoImport:
+ return IncludeResult::FoundAutoImportFile;
+ }
}
}
- return false;
+ return IncludeResult::NotFound;
}
};
@@ -205,9 +226,9 @@ struct CompileRequest
{
auto sourceFilePath = sourceFile->path;
- auto searchDirs = options.SearchDirectories;
+ auto searchDirs = options.searchDirectories;
searchDirs.Reverse();
- searchDirs.Add(Path::GetDirectoryName(sourceFilePath));
+ searchDirs.Add(SearchDirectory(Path::GetDirectoryName(sourceFilePath), SearchDirectory::Kind::Default));
searchDirs.Reverse();
includeHandler.searchDirs = searchDirs;
@@ -219,7 +240,8 @@ struct CompileRequest
mResult.GetErrorWriter(),
&includeHandler,
preprocessorDefinitions,
- translationUnitSyntax.Ptr());
+ translationUnitSyntax.Ptr(),
+ this);
parseSourceFile(
translationUnitSyntax.Ptr(),
@@ -448,6 +470,63 @@ struct CompileRequest
Dictionary<String, RefPtr<ProgramSyntaxNode>> loadedModules;
+ RefPtr<ProgramSyntaxNode> loadModule(
+ String const& name,
+ String const& path,
+ String const& source,
+ CodePosition const& loc)
+ {
+ // now we need to try compiling it, etc.
+
+ // We don't want to use the same options that the user specified
+ // for loading modules on-demand. In particular, we always want
+ // semantic checking to be enabled.
+ CompileOptions moduleOptions;
+ moduleOptions.searchDirectories = Options.searchDirectories;
+ moduleOptions.profile = Options.profile;
+
+ RefPtr<SourceFile> sourceFile = new SourceFile();
+ sourceFile->path = path;
+ sourceFile->content = source;
+
+ TranslationUnitOptions translationUnitOptions;
+ translationUnitOptions.sourceFiles.Add(sourceFile);
+
+ CompileUnit translationUnit = parseTranslationUnit(translationUnitOptions, moduleOptions);
+
+ // TODO: handle errors
+
+ checkTranslationUnit(translationUnit, moduleOptions);
+
+ // Skip code generation
+
+ //
+
+ RefPtr<ProgramSyntaxNode> moduleDecl = translationUnit.SyntaxNode;
+
+ loadedModules.Add(name, moduleDecl);
+
+ return moduleDecl;
+
+ }
+
+ String autoImportModule(
+ String const& path,
+ String const& source,
+ CodePosition const& loc)
+ {
+ // TODO: may want to have some kind of canonicalization step here
+ String name = path;
+
+ // Have we already loaded a module matching this name?
+ if (loadedModules.TryGetValue(name))
+ return name;
+
+ loadModule(name, path, source, loc);
+
+ return name;
+ }
+
RefPtr<ProgramSyntaxNode> findOrImportModule(
String const& name,
CodePosition const& loc)
@@ -486,47 +565,30 @@ struct CompileRequest
String foundPath;
String foundSource;
- bool found = includeHandler.TryToFindIncludeFile(fileName, pathIncludedFrom, &foundPath, &foundSource);
- if (!found)
+ IncludeResult includeResult = includeHandler.TryToFindIncludeFile(fileName, pathIncludedFrom, &foundPath, &foundSource);
+ switch( includeResult )
{
- this->mSink.diagnose(loc, Diagnostics::cannotFindFile, fileName);
+ case IncludeResult::NotFound:
+ case IncludeResult::Error:
+ {
+ this->mSink.diagnose(loc, Diagnostics::cannotFindFile, fileName);
+
+ loadedModules[name] = nullptr;
+ return nullptr;
+ }
+ break;
- loadedModules[name] = nullptr;
- return nullptr;
+ default:
+ break;
}
// We've found a file that we can load for the given module, so
- // now we need to try compiling it, etc.
-
- // We don't want to use the same options that the user specified
- // for loading modules on-demand. In particular, we always want
- // semantic checking to be enabled.
- CompileOptions moduleOptions;
- moduleOptions.SearchDirectories = Options.SearchDirectories;
- moduleOptions.profile = Options.profile;
-
- RefPtr<SourceFile> sourceFile = new SourceFile();
- sourceFile->path = foundPath;
- sourceFile->content = foundSource;
-
- TranslationUnitOptions translationUnitOptions;
- translationUnitOptions.sourceFiles.Add(sourceFile);
-
- CompileUnit translationUnit = parseTranslationUnit(translationUnitOptions, moduleOptions);
-
- // TODO: handle errors
-
- checkTranslationUnit(translationUnit, moduleOptions);
-
- // Skip code generation
-
- //
-
- moduleDecl = translationUnit.SyntaxNode;
-
- loadedModules.Add(name, moduleDecl);
-
- return moduleDecl;
+ // go ahead and perform the module-load action
+ return loadModule(
+ name,
+ foundPath,
+ foundSource,
+ loc);
}
};
@@ -539,6 +601,15 @@ RefPtr<ProgramSyntaxNode> findOrImportModule(
return request->findOrImportModule(name, loc);
}
+String autoImportModule(
+ CompileRequest* request,
+ String const& path,
+ String const& source,
+ CodePosition const& loc)
+{
+ return request->autoImportModule(path, source, loc);
+}
+
void Session::addBuiltinSource(
RefPtr<Scope> const& scope,
String const& path,
@@ -687,9 +758,16 @@ SLANG_API void spSetDiagnosticCallback(
SLANG_API void spAddSearchPath(
SlangCompileRequest* request,
- const char* searchDir)
+ const char* path)
+{
+ REQ(request)->Options.searchDirectories.Add(Slang::SearchDirectory(path, Slang::SearchDirectory::Kind::Default));
+}
+
+SLANG_API void spAddAutoImportPath(
+ SlangCompileRequest* request,
+ const char* path)
{
- REQ(request)->Options.SearchDirectories.Add(searchDir);
+ REQ(request)->Options.searchDirectories.Add(Slang::SearchDirectory(path, Slang::SearchDirectory::Kind::AutoImport));
}
SLANG_API void spAddPreprocessorDefine(
diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj
index 7f5e0e32e..4a3313b07 100644
--- a/source/slang/slang.vcxproj
+++ b/source/slang/slang.vcxproj
@@ -193,6 +193,7 @@
<ClCompile Include="emit.cpp" />
<ClCompile Include="lexer.cpp" />
<ClCompile Include="lookup.cpp" />
+ <ClCompile Include="options.cpp" />
<ClCompile Include="parameter-binding.cpp" />
<ClCompile Include="parser.cpp" />
<ClCompile Include="preprocessor.cpp" />
diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters
index 31c332295..7eee0643d 100644
--- a/source/slang/slang.vcxproj.filters
+++ b/source/slang/slang.vcxproj.filters
@@ -43,5 +43,6 @@
<ClCompile Include="syntax.cpp" />
<ClCompile Include="token.cpp" />
<ClCompile Include="type-layout.cpp" />
+ <ClCompile Include="options.cpp" />
</ItemGroup>
</Project> \ No newline at end of file
diff --git a/source/slang/token-defs.h b/source/slang/token-defs.h
index f29574bbb..f7800de55 100644
--- a/source/slang/token-defs.h
+++ b/source/slang/token-defs.h
@@ -30,6 +30,8 @@ TOKEN(NewLine, "newline")
TOKEN(LineComment, "line comment")
TOKEN(BlockComment, "block comment")
+TOKEN(AutoImport, "automatic import directive")
+
#define PUNCTUATION(id, text) \
TOKEN(id, "'" text "'")
diff --git a/source/slangc/main.cpp b/source/slangc/main.cpp
index ead286434..06f004531 100644
--- a/source/slangc/main.cpp
+++ b/source/slangc/main.cpp
@@ -8,536 +8,7 @@ using namespace Slang;
#include <assert.h>
-// Currently only used for looking up `Profile::` values that aren't
-// exported by the public API
-#include "../slang/profile.h"
-
// Try to read an argument for a command-line option.
-wchar_t const* tryReadCommandLineArgumentRaw(wchar_t const* option, wchar_t***ioCursor, wchar_t**end)
-{
- wchar_t**& cursor = *ioCursor;
- if (cursor == end)
- {
- fprintf(stderr, "expected an argument for command-line option '%S'", option);
- exit(1);
- }
- else
- {
- return *cursor++;
- }
-}
-
-String tryReadCommandLineArgument(wchar_t const* option, wchar_t***ioCursor, wchar_t**end)
-{
- return String::FromWString(tryReadCommandLineArgumentRaw(option, ioCursor, end));
-}
-
-struct OptionsParser
-{
- SlangSession* session = nullptr;
- SlangCompileRequest* compileRequest = nullptr;
-
- struct RawTranslationUnit
- {
- SlangSourceLanguage sourceLanguage;
- SlangProfileID implicitProfile;
- int translationUnitIndex;
- };
-
- // Collect translation units so that we can futz with them later
- List<RawTranslationUnit> rawTranslationUnits;
-
- struct RawEntryPoint
- {
- String name;
- SlangProfileID profileID = SLANG_PROFILE_UNKNOWN;
- int translationUnitIndex = -1;
- };
-
- // Collect entry point names, so that we can associate them
- // with entry points later...
- List<RawEntryPoint> rawEntryPoints;
-
- // The number of input files that have been specified
- int inputPathCount = 0;
-
- // If we already have a translation unit for Slang code, then this will give its index.
- // If not, it will be `-1`.
- int slangTranslationUnit = -1;
-
- int translationUnitCount = 0;
- int currentTranslationUnitIndex = -1;
-
- SlangProfileID currentProfileID = SLANG_PROFILE_UNKNOWN;
-
- SlangCompileFlags flags = 0;
-
- int addTranslationUnit(
- SlangSourceLanguage language,
- SlangProfileID implicitProfile = SLANG_PROFILE_UNKNOWN)
- {
- auto translationUnitIndex = spAddTranslationUnit(compileRequest, language, nullptr);
-
- assert(translationUnitIndex == rawTranslationUnits.Count());
-
- RawTranslationUnit rawTranslationUnit;
- rawTranslationUnit.sourceLanguage = language;
- rawTranslationUnit.implicitProfile = implicitProfile;
- rawTranslationUnit.translationUnitIndex = translationUnitIndex;
-
- rawTranslationUnits.Add(rawTranslationUnit);
-
- return translationUnitIndex;
- }
-
- void addInputSlangPath(
- String const& path)
- {
- // All of the input .slang files will be grouped into a single logical translation unit,
- // which we create lazily when the first .slang file is encountered.
- if( slangTranslationUnit == -1 )
- {
- translationUnitCount++;
- slangTranslationUnit = addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG);
- }
-
- spAddTranslationUnitSourceFile(
- compileRequest,
- slangTranslationUnit,
- path.begin());
-
- // Set the translation unit to be used by subsequent entry points
- currentTranslationUnitIndex = slangTranslationUnit;
- }
-
- void addInputForeignShaderPath(
- String const& path,
- SlangSourceLanguage language,
- SlangProfileID implicitProfile = SLANG_PROFILE_UNKNOWN)
- {
- translationUnitCount++;
- currentTranslationUnitIndex = addTranslationUnit(language, implicitProfile);
-
- spAddTranslationUnitSourceFile(
- compileRequest,
- currentTranslationUnitIndex,
- path.begin());
- }
-
- void addInputPath(
- wchar_t const* inPath)
- {
- inputPathCount++;
-
- // look at the extension on the file name to determine
- // how we should handle it.
- String path = String::FromWString(inPath);
-
- if( path.EndsWith(".slang") )
- {
- // Plain old slang code
- addInputSlangPath(path);
- }
-#define CASE(EXT, LANG) \
- else if(path.EndsWith(EXT)) do { addInputForeignShaderPath(path, SLANG_SOURCE_LANGUAGE_##LANG); } while(0)
-
- CASE(".hlsl", HLSL);
- CASE(".fx", HLSL);
-
- CASE(".glsl", GLSL);
-#undef CASE
-
-#define CASE(EXT, LANG, PROFILE) \
- else if(path.EndsWith(EXT)) do { addInputForeignShaderPath(path, SLANG_SOURCE_LANGUAGE_##LANG, SlangProfileID(Slang::Profile::PROFILE)); } while(0)
- // TODO: need a way to pass along stage/profile and entry-point info for these cases...
- CASE(".vert", GLSL, GLSL_Vertex);
- CASE(".frag", GLSL, GLSL_Fragment);
- CASE(".geom", GLSL, GLSL_Geometry);
- CASE(".tesc", GLSL, GLSL_TessControl);
- CASE(".tese", GLSL, GLSL_TessEval);
- CASE(".comp", GLSL, GLSL_Compute);
-
-#undef CASE
-
- else
- {
- fprintf(stderr, "error: can't deduce language for input file '%S'\n", inPath);
- exit(1);
- }
- }
-
- void parse(
- int argc,
- wchar_t** argv)
- {
- wchar_t** argCursor = &argv[1];
- wchar_t** argEnd = &argv[argc];
- while (argCursor != argEnd)
- {
- wchar_t const* arg = *argCursor++;
- if (arg[0] == '-')
- {
- String argStr = String::FromWString(arg);
-
- // The argument looks like an option, so try to parse it.
-// if (argStr == "-outdir")
-// outputDir = tryReadCommandLineArgument(arg, &argCursor, argEnd);
-// if (argStr == "-out")
-// options.outputName = tryReadCommandLineArgument(arg, &argCursor, argEnd);
-// else if (argStr == "-symbo")
-// options.SymbolToCompile = tryReadCommandLineArgument(arg, &argCursor, argEnd);
- //else
- if (argStr == "-no-checking")
- flags |= SLANG_COMPILE_FLAG_NO_CHECKING;
- else if (argStr == "-backend" || argStr == "-target")
- {
- String name = tryReadCommandLineArgument(arg, &argCursor, argEnd);
- SlangCompileTarget target = SLANG_TARGET_UNKNOWN;
-
- if (name == "glsl")
- {
- target = SLANG_GLSL;
- }
- else if (name == "glsl_vk")
- {
- target = SLANG_GLSL_VULKAN;
- }
-// else if (name == "glsl_vk_onedesc")
-// {
-// options.Target = CodeGenTarget::GLSL_Vulkan_OneDesc;
-// }
- else if (name == "hlsl")
- {
- target = SLANG_HLSL;
- }
- else if (name == "spriv")
- {
- target = SLANG_SPIRV;
- }
- else if (name == "dxbc")
- {
- target = SLANG_DXBC;
- }
- else if (name == "dxbc-assembly")
- {
- target = SLANG_DXBC_ASM;
- }
- #define CASE(NAME, TARGET) \
- else if(name == #NAME) do { target = SLANG_##TARGET; } while(0)
-
- CASE(spirv, SPIRV);
- CASE(spirv-assembly, SPIRV_ASM);
-
- #undef CASE
-
- else if (name == "reflection-json")
- {
- target = SLANG_REFLECTION_JSON;
- }
- else
- {
- fprintf(stderr, "unknown code generation target '%S'\n", name.ToWString());
- exit(1);
- }
-
- spSetCodeGenTarget(compileRequest, target);
- }
- // A "profile" specifies both a specific target stage and a general level
- // of capability required by the program.
- else if (argStr == "-profile")
- {
- String name = tryReadCommandLineArgument(arg, &argCursor, argEnd);
-
- SlangProfileID profileID = spFindProfile(session, name.begin());
- if( profileID == SLANG_PROFILE_UNKNOWN )
- {
- fprintf(stderr, "unknown profile '%s'\n", name.begin());
- }
- else
- {
- currentProfileID = profileID;
- }
- }
- else if (argStr == "-entry")
- {
- String name = tryReadCommandLineArgument(arg, &argCursor, argEnd);
-
- RawEntryPoint entry;
- entry.name = name;
- entry.translationUnitIndex = currentTranslationUnitIndex;
-
- // TODO(tfoley): Allow user to fold a specification of a profile into the entry-point name,
- // for the case where they might be compiling multiple entry points in one invocation...
- //
- // For now, just use the last profile set on the command-line to specify this
-
- entry.profileID = currentProfileID;
-
- rawEntryPoints.Add(entry);
- }
-#if 0
- else if (argStr == "-stage")
- {
- String name = tryReadCommandLineArgument(arg, &argCursor, argEnd);
- StageTarget stage = StageTarget::Unknown;
- if (name == "vertex") { stage = StageTarget::VertexShader; }
- else if (name == "fragment") { stage = StageTarget::FragmentShader; }
- else if (name == "hull") { stage = StageTarget::HullShader; }
- else if (name == "domain") { stage = StageTarget::DomainShader; }
- else if (name == "compute") { stage = StageTarget::ComputeShader; }
- else
- {
- fprintf(stderr, "unknown stage '%S'\n", name.ToWString());
- }
- options.stage = stage;
- }
-#endif
- else if (argStr == "-pass-through")
- {
- String name = tryReadCommandLineArgument(arg, &argCursor, argEnd);
- SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE;
- if (name == "fxc") { passThrough = SLANG_PASS_THROUGH_FXC; }
- else if (name == "glslang") { passThrough = SLANG_PASS_THROUGH_GLSLANG; }
- else
- {
- fprintf(stderr, "unknown pass-through target '%S'\n", name.ToWString());
- exit(1);
- }
-
- spSetPassThrough(
- compileRequest,
- passThrough);
- }
-// else if (argStr == "-genchoice")
-// options.Mode = CompilerMode::GenerateChoice;
- else if (argStr[1] == 'D')
- {
- // The value to be defined might be part of the same option, as in:
- // -DFOO
- // or it might come separately, as in:
- // -D FOO
- wchar_t const* defineStr = arg + 2;
- if (defineStr[0] == 0)
- {
- // Need to read another argument from the command line
- defineStr = tryReadCommandLineArgumentRaw(arg, &argCursor, argEnd);
- }
- // The string that sets up the define can have an `=` between
- // the name to be defined and its value, so we search for one.
- wchar_t const* eqPos = nullptr;
- for(wchar_t const* dd = defineStr; *dd; ++dd)
- {
- if (*dd == '=')
- {
- eqPos = dd;
- break;
- }
- }
-
- // Now set the preprocessor define
- //
- if (eqPos)
- {
- // If we found an `=`, we split the string...
-
- spAddPreprocessorDefine(
- compileRequest,
- String::FromWString(defineStr, eqPos).begin(),
- String::FromWString(eqPos+1).begin());
- }
- else
- {
- // If there was no `=`, then just #define it to an empty string
-
- spAddPreprocessorDefine(
- compileRequest,
- String::FromWString(defineStr).begin(),
- "");
- }
- }
- else if (argStr[1] == 'I')
- {
- // The value to be defined might be part of the same option, as in:
- // -IFOO
- // or it might come separately, as in:
- // -I FOO
- // (see handling of `-D` above)
- wchar_t const* includeDirStr = arg + 2;
- if (includeDirStr[0] == 0)
- {
- // Need to read another argument from the command line
- includeDirStr = tryReadCommandLineArgumentRaw(arg, &argCursor, argEnd);
- }
-
- spAddSearchPath(
- compileRequest,
- String::FromWString(includeDirStr).begin());
- }
- else if (argStr == "--")
- {
- // The `--` option causes us to stop trying to parse options,
- // and treat the rest of the command line as input file names:
- while (argCursor != argEnd)
- {
- addInputPath(*argCursor++);
- }
- break;
- }
- else
- {
- fprintf(stderr, "unknown command-line option '%S'\n", argStr.ToWString());
- // TODO: print a usage message
- exit(1);
- }
- }
- else
- {
- addInputPath(arg);
- }
- }
-
- spSetCompileFlags(compileRequest, flags);
-
- if (inputPathCount == 0)
- {
- fprintf(stderr, "error: no input file specified\n");
- exit(1);
- }
-
- // No point in moving forward if there is nothing to compile
- if( translationUnitCount == 0 )
- {
- fprintf(stderr, "error: no compilation requested\n");
- exit(1);
- }
-
- // If the user didn't list any explicit entry points, then we can
- // try to infer one from the type of input file
- if(rawEntryPoints.Count() == 0)
- {
- for(auto rawTranslationUnit : rawTranslationUnits)
- {
- // Dont' add implicit entry points when compiling from Slang files,
- // since Slang doesn't require entry points to be named on the
- // command line.
- if(rawTranslationUnit.sourceLanguage == SLANG_SOURCE_LANGUAGE_SLANG )
- continue;
-
- // Use a default entry point name
- char const* entryPointName = "main";
-
- // Try to determine a profile
- SlangProfileID entryPointProfile = SLANG_PROFILE_UNKNOWN;
-
- // If a profile was specified on the command line, then we use it
- if(currentProfileID != SLANG_PROFILE_UNKNOWN)
- {
- entryPointProfile = currentProfileID;
- }
- // Otherwise, check if the translation unit implied a profile
- // (e.g., a `*.vert` file implies the `GLSL_Vertex` profile)
- else if(rawTranslationUnit.implicitProfile != SLANG_PROFILE_UNKNOWN)
- {
- entryPointProfile = rawTranslationUnit.implicitProfile;
- }
-
- RawEntryPoint entry;
- entry.name = entryPointName;
- entry.translationUnitIndex = rawTranslationUnit.translationUnitIndex;
- entry.profileID = entryPointProfile;
- rawEntryPoints.Add(entry);
- }
- }
-
- // For any entry points that were given without an explicit profile, we can now apply
- // the profile that was given to them.
- if( rawEntryPoints.Count() != 0 )
- {
- bool anyEntryPointWithoutProfile = false;
- for( auto& entryPoint : rawEntryPoints )
- {
- // Skip entry points that are already associated with a translation unit...
- if( entryPoint.profileID != SLANG_PROFILE_UNKNOWN )
- continue;
-
- anyEntryPointWithoutProfile = true;
- break;
- }
-
- if( anyEntryPointWithoutProfile )
- {
- fprintf(stderr, "error: no profile specified; use the '-profile <profile name>' option");
- exit(1);
- }
- // TODO: issue an error if we have multiple `-profile` options *and*
- // there are entry points that didn't get a profile.
- else
- {
- for( auto& e : rawEntryPoints )
- {
- if( e.profileID == SLANG_PROFILE_UNKNOWN )
- {
- e.profileID = currentProfileID;
- }
- }
- }
- }
-
- // Next, we want to make sure that entry points get attached to the appropriate translation
- // unit that will provide them.
- {
- bool anyEntryPointWithoutTranslationUnit = false;
- for( auto& entryPoint : rawEntryPoints )
- {
- // Skip entry points that are already associated with a translation unit...
- if( entryPoint.translationUnitIndex != -1 )
- continue;
-
- anyEntryPointWithoutTranslationUnit = true;
- entryPoint.translationUnitIndex = 0;
- }
-
- if( anyEntryPointWithoutTranslationUnit && translationUnitCount != 1 )
- {
- fprintf(stderr, "error: when using multiple translation units, entry points must be specified after their translation unit file(s)");
- exit(1);
- }
-
- // Now place all those entry points where they belong
- for( auto& entryPoint : rawEntryPoints )
- {
- spAddTranslationUnitEntryPoint(
- compileRequest,
- entryPoint.translationUnitIndex,
- entryPoint.name.begin(),
- entryPoint.profileID);
- }
- }
-
-#if 0
- // Automatically derive an output directory based on the first file specified.
- //
- // TODO: require manual specification if there are multiple input files, in different directories
- String fileName = options.translationUnits[0].sourceFilePaths[0];
- if (outputDir.Length() == 0)
- {
- outputDir = Path::GetDirectoryName(fileName);
- }
-#endif
- }
-
-};
-
-
-void parseOptions(
- SlangCompileRequest* compileRequest,
- int argc,
- wchar_t** argv)
-{
- OptionsParser parser;
- parser.compileRequest = compileRequest;
- parser.parse(argc, argv);
-}
static void diagnosticCallback(
char const* message,
@@ -547,14 +18,28 @@ static void diagnosticCallback(
fflush(stderr);
}
-int wmain(int argc, wchar_t* argv[])
+#ifdef _WIN32
+#define MAIN slangc_main
+#else
+#define MAIN main
+#endif
+
+int MAIN(int argc, char** argv)
{
// Parse any command-line options
SlangSession* session = spCreateSession(nullptr);
SlangCompileRequest* compileRequest = spCreateCompileRequest(session);
- parseOptions(compileRequest, argc, argv);
+ char const* appName = "slangc";
+ if(argc > 0) appName = argv[0];
+
+ int err = spProcessCommandLineArguments(compileRequest, &argv[1], argc - 1);
+ if( err )
+ {
+ // TODO: print usage message
+ exit(1);
+ }
spSetDiagnosticCallback(
compileRequest,
@@ -604,40 +89,29 @@ int wmain(int argc, wchar_t* argv[])
}
#endif
-#if 0
- int returnValue = -1;
- {
+#ifdef _MSC_VER
+ _CrtDumpMemoryLeaks();
+#endif
+ return 0;
+}
+#ifdef _WIN32
+int wmain(int argc, wchar_t** argv)
+{
+ // Conver the wide-character Unicode arguments to UTF-8,
+ // since that is what Slang expects on the API side.
- auto sourceDir = Path::GetDirectoryName(fileName);
- CompileResult result;
- try
- {
- auto files = SlangLib::CompileShaderSourceFromFile(result, fileName, options);
- for (auto & f : files)
- {
- try
- {
- f.SaveToFile(Path::Combine(outputDir, f.MetaData.ShaderName + ".cse"));
- }
- catch (Exception &)
- {
- result.GetErrorWriter()->diagnose(CodePosition(0, 0, 0, ""), Diagnostics::cannotWriteOutputFile, Path::Combine(outputDir, f.MetaData.ShaderName + ".cse"));
- }
- }
- }
- catch (Exception & e)
- {
- printf("internal compiler error: %S\n", e.Message.ToWString());
- }
- result.PrintDiagnostics();
- if (result.GetErrorCount() == 0)
- returnValue = 0;
+ List<String> args;
+ for(int ii = 0; ii < argc; ++ii)
+ {
+ args.Add(String::FromWString(argv[ii]));
+ }
+ List<char const*> argBuffers;
+ for(int ii = 0; ii < argc; ++ii)
+ {
+ argBuffers.Add(args[ii].Buffer());
}
-#endif
-#ifdef _MSC_VER
- _CrtDumpMemoryLeaks();
+ return MAIN(argc, (char**) &argBuffers[0]);
+}
#endif
- return 0;
-} \ No newline at end of file
diff --git a/tests/render/auto-import.hlsl b/tests/render/auto-import.hlsl
new file mode 100644
index 000000000..588ebc612
--- /dev/null
+++ b/tests/render/auto-import.hlsl
@@ -0,0 +1,147 @@
+//TEST(smoke,render):COMPARE_HLSL_GLSL_RENDER: -xslang -auto-import-dir -xslang tests/render/
+
+// This is a basic test case for cross-compilation behavior.
+//
+// We will define distinct HLSL and GLSL entry points,
+// but the two will share a dependency on a file of
+// pure Spire code that provides the actual shading logic.
+
+
+// Pull in Spire code depdendency using extended syntax:
+#include "auto-import.slang.h"
+
+#if defined(__HLSL__)
+
+cbuffer Uniforms
+{
+ float4x4 modelViewProjection;
+};
+
+struct AssembledVertex
+{
+ float3 position;
+ float3 color;
+};
+
+struct CoarseVertex
+{
+ float3 color;
+};
+
+struct Fragment
+{
+ float4 color;
+};
+
+// Vertex Shader
+
+struct VertexStageInput
+{
+ AssembledVertex assembledVertex : A;
+};
+
+struct VertexStageOutput
+{
+ CoarseVertex coarseVertex : CoarseVertex;
+ float4 sv_position : SV_Position;
+};
+
+VertexStageOutput vertexMain(VertexStageInput input)
+{
+ VertexStageOutput output;
+
+ float3 position = input.assembledVertex.position;
+ float3 color = input.assembledVertex.color;
+
+ output.coarseVertex.color = color;
+ output.sv_position = mul(modelViewProjection, float4(position, 1.0));
+
+ return output;
+
+}
+
+// Fragment Shader
+
+struct FragmentStageInput
+{
+ CoarseVertex coarseVertex : CoarseVertex;
+};
+
+struct FragmentStageOutput
+{
+ Fragment fragment : SV_Target;
+};
+
+FragmentStageOutput fragmentMain(FragmentStageInput input)
+{
+ FragmentStageOutput output;
+
+ float3 color = input.coarseVertex.color;
+
+ color = transformColor(color);
+
+ output.fragment.color = float4(color, 1.0);
+
+ return output;
+}
+
+#elif defined(__GLSL__)
+
+#version 420
+
+uniform Uniforms
+{
+ mat4x4 modelViewProjection;
+};
+
+#define ASSEMBLED_VERTEX(QUAL) \
+ /* */
+
+#define V2F(QUAL) \
+ QUAL vec3 coarse_color; \
+ /* */
+
+// Vertex Shader
+
+#ifdef __GLSL_VERTEX__
+
+layout(location = 0)
+in vec3 assembled_position;
+
+layout(location = 1)
+in vec3 assembled_color;
+
+V2F(out)
+
+void main()
+{
+ vec3 position = assembled_position;
+ vec3 color = assembled_color;
+
+ coarse_color = color;
+// gl_Position = modelViewProjection * vec4(position, 1.0);
+ gl_Position = vec4(position, 1.0) * modelViewProjection;
+}
+
+#endif
+
+#ifdef __GLSL_FRAGMENT__
+
+V2F(in)
+
+layout(location = 0)
+out vec4 fragment_color;
+
+void main()
+{
+ vec3 color = coarse_color;
+
+ color = transformColor(color);
+
+ fragment_color = vec4(color, 1.0);
+}
+
+
+#endif
+
+#endif
diff --git a/tests/render/auto-import.slang.h b/tests/render/auto-import.slang.h
new file mode 100644
index 000000000..d53005688
--- /dev/null
+++ b/tests/render/auto-import.slang.h
@@ -0,0 +1,21 @@
+//TEST_IGNORE_FILE:
+
+// This file implements the "library" code
+// that both the HLSL and GLSL shaders share.
+//
+// This code is written in Slang (more or less
+// just HLSL), and will be translated as needed
+// for each of the targets.
+
+float3 transformColor(float3 color)
+{
+ float3 result;
+
+ result.x = sin(20.0 * (color.x + color.y));
+ result.y = saturate(cos(color.z * 30.0));
+ result.z = sin(color.x * color.y * color.z * 100.0);
+
+ result = 0.5 * (result + 1);
+
+ return result;
+} \ No newline at end of file
diff --git a/tools/render-test/options.cpp b/tools/render-test/options.cpp
index a486c8b15..53f88b7a9 100644
--- a/tools/render-test/options.cpp
+++ b/tools/render-test/options.cpp
@@ -76,6 +76,22 @@ void parseOptions(int* argc, char** argv)
{
gOptions.mode = Mode::GLSLCrossCompile;
}
+ else if( strcmp(arg, "-xslang") == 0 )
+ {
+ // This is an option that we want to pass along to Slang
+
+ if( argCursor == argEnd )
+ {
+ fprintf(stderr, "expected argument for '%s' option\n", arg);
+ exit(1);
+ }
+ if( gOptions.slangArgCount == kMaxSlangArgs )
+ {
+ fprintf(stderr, "maximum number of '%s' options exceeded (%d)\n", arg, kMaxSlangArgs);
+ exit(1);
+ }
+ gOptions.slangArgs[gOptions.slangArgCount++] = *argCursor++;
+ }
else
{
fprintf(stderr, "unknown option '%s'\n", arg);
diff --git a/tools/render-test/options.h b/tools/render-test/options.h
index 0731a5dc9..fbc615d56 100644
--- a/tools/render-test/options.h
+++ b/tools/render-test/options.h
@@ -25,12 +25,21 @@ enum class Mode
GLSLCrossCompile,
};
+enum
+{
+ // maximum number of command-line arguments to pass along to slang
+ kMaxSlangArgs = 16,
+};
+
struct Options
{
char const* appName = "render-test";
char const* sourcePath = nullptr;
char const* outputPath = nullptr;
Mode mode = Mode::Slang;
+
+ char const* slangArgs[kMaxSlangArgs];
+ int slangArgCount = 0;
};
extern Options gOptions;
diff --git a/tools/render-test/slang-support.cpp b/tools/render-test/slang-support.cpp
index bec651e87..8db62ebef 100644
--- a/tools/render-test/slang-support.cpp
+++ b/tools/render-test/slang-support.cpp
@@ -35,6 +35,8 @@ struct SlangShaderCompilerWrapper : public ShaderCompiler
}
spAddPreprocessorDefine(slangRequest, langDefine, "1");
+ spProcessCommandLineArguments(slangRequest, &gOptions.slangArgs[0], gOptions.slangArgCount);
+
int vertexTranslationUnit = 0;
int fragmentTranslationUnit = 0;
if( sourceLanguage == SLANG_SOURCE_LANGUAGE_GLSL )