// slang-options.cpp // Implementation of options parsing for `slangc` command line, // and also for API interface that takes command-line argument strings. #include "slang-options.h" #include "../../slang.h" #include "slang-compiler.h" #include "slang-profile.h" #include "../compiler-core/slang-artifact-desc-util.h" #include "../compiler-core/slang-artifact-impl.h" #include "../compiler-core/slang-artifact-representation-impl.h" #include "../compiler-core/slang-name-convention-util.h" #include "slang-repro.h" #include "slang-serialize-ir.h" #include "../core/slang-castable.h" #include "../core/slang-file-system.h" #include "../core/slang-type-text-util.h" #include "../core/slang-hex-dump-util.h" #include "../compiler-core/slang-command-line-args.h" #include "../compiler-core/slang-artifact-desc-util.h" #include "../compiler-core/slang-core-diagnostics.h" #include "../core/slang-char-util.h" #include namespace Slang { SlangResult _addLibraryReference(EndToEndCompileRequest* req, IArtifact* artifact); struct OptionsParser { SlangSession* session = nullptr; SlangCompileRequest* compileRequest = nullptr; Slang::EndToEndCompileRequest* requestImpl = nullptr; // A "translation unit" represents one or more source files // that are processed as a single entity when it comes to // semantic checking. // // For languages like HLSL, GLSL, and C, a translation unit // is usually a single source file (which can then go on // to `#include` other files into the same translation unit). // // For Slang, we support having multiple source files in // a single translation unit, and indeed command-line `slangc` // will always put all the source files into a single translation // unit. // // We track information on the translation units that we // create during options parsing, so that we can assocaite // other entities with these translation units: // struct RawTranslationUnit { // What language is the translation unit using? // // Note: We do not support translation units that mix // languages. // SlangSourceLanguage sourceLanguage; // Certain naming conventions imply a stage for // a file with only a single entry point, and in // those cases we will try to infer the stage from // the file when it is possible. // Stage impliedStage; // We retain the Slang API level translation unit index, // which we will call an "ID" inside the options parsing code. // // This will almost always be the index into the // `rawTranslationUnits` array below, but could conceivably, // be mismatched if we were parsing options for a compile // request that already had some translation unit(s) added // manually. // int translationUnitID; }; List rawTranslationUnits; // If we already have a translation unit for Slang code, then this will give its index. // If not, it will be `-1`. int slangTranslationUnitIndex = -1; // The number of input files that have been specified int inputPathCount = 0; int translationUnitCount = 0; int currentTranslationUnitIndex= -1; // An entry point represents a function to be checked and possibly have // code generated in one of our translation units. An entry point // needs to have an associated stage, which might come via the // `-stage` command line option, or a `[shader("...")]` attribute // in the source code. // struct RawEntryPoint { String name; Stage stage = Stage::Unknown; int translationUnitIndex = -1; int entryPointID = -1; // State for tracking command-line errors bool conflictingStagesSet = false; bool redundantStageSet = false; }; // // We collect the entry points in a "raw" array so that we can // possibly associate them with a stage or translation unit // after the fact. // List rawEntryPoints; // In the case where we have only a single entry point, // the entry point and its options might be specified out // of order, so we will keep a single `RawEntryPoint` around // and use it as the target for any state-setting options // before the first "proper" entry point is specified. RawEntryPoint defaultEntryPoint; SlangCompileFlags flags = 0; struct RawOutput { String path; CodeGenTarget impliedFormat = CodeGenTarget::Unknown; int targetIndex = -1; int entryPointIndex = -1; bool isWholeProgram = false; }; List rawOutputs; struct RawTarget { CodeGenTarget format = CodeGenTarget::Unknown; ProfileVersion profileVersion = ProfileVersion::Unknown; SlangTargetFlags targetFlags = 0; int targetID = -1; FloatingPointMode floatingPointMode = FloatingPointMode::Default; List capabilityAtoms; // State for tracking command-line errors bool conflictingProfilesSet = false; bool redundantProfileSet = false; }; List rawTargets; RawTarget defaultTarget; int addTranslationUnit( SlangSourceLanguage language, Stage impliedStage) { auto translationUnitIndex = rawTranslationUnits.getCount(); auto translationUnitID = compileRequest->addTranslationUnit(language, nullptr); // As a sanity check: the API should be returning the same translation // unit index as we maintain internally. This invariant would only // be broken if we decide to support a mix of translation units specified // via API, and ones specified via command-line arguments. // SLANG_RELEASE_ASSERT(Index(translationUnitID) == translationUnitIndex); RawTranslationUnit rawTranslationUnit; rawTranslationUnit.sourceLanguage = language; rawTranslationUnit.translationUnitID = translationUnitID; rawTranslationUnit.impliedStage = impliedStage; rawTranslationUnits.add(rawTranslationUnit); return int(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( slangTranslationUnitIndex == -1 ) { translationUnitCount++; slangTranslationUnitIndex = addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG, Stage::Unknown); } compileRequest->addTranslationUnitSourceFile(rawTranslationUnits[slangTranslationUnitIndex].translationUnitID, path.begin()); // Set the translation unit to be used by subsequent entry points currentTranslationUnitIndex = slangTranslationUnitIndex; } void addInputForeignShaderPath( String const& path, SlangSourceLanguage language, Stage impliedStage) { translationUnitCount++; currentTranslationUnitIndex = addTranslationUnit(language, impliedStage); compileRequest->addTranslationUnitSourceFile(rawTranslationUnits[currentTranslationUnitIndex].translationUnitID, path.begin()); } static Profile::RawVal findGlslProfileFromPath(const String& path) { struct Entry { const char* ext; Profile::RawVal profileId; }; static const Entry entries[] = { { ".frag", Profile::GLSL_Fragment }, { ".geom", Profile::GLSL_Geometry }, { ".tesc", Profile::GLSL_TessControl }, { ".tese", Profile::GLSL_TessEval }, { ".comp", Profile::GLSL_Compute } }; for (int i = 0; i < SLANG_COUNT_OF(entries); ++i) { const Entry& entry = entries[i]; if (path.endsWith(entry.ext)) { return entry.profileId; } } return Profile::Unknown; } static SlangSourceLanguage findSourceLanguageFromPath(const String& path, Stage& outImpliedStage) { struct Entry { const char* ext; SlangSourceLanguage sourceLanguage; SlangStage impliedStage; }; static const Entry entries[] = { { ".slang", SLANG_SOURCE_LANGUAGE_SLANG, SLANG_STAGE_NONE }, { ".hlsl", SLANG_SOURCE_LANGUAGE_HLSL, SLANG_STAGE_NONE }, { ".fx", SLANG_SOURCE_LANGUAGE_HLSL, SLANG_STAGE_NONE }, { ".glsl", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_NONE }, { ".vert", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_VERTEX }, { ".frag", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_FRAGMENT }, { ".geom", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_GEOMETRY }, { ".tesc", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_HULL }, { ".tese", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_DOMAIN }, { ".comp", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_COMPUTE }, { ".mesh", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_MESH }, { ".task", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_AMPLIFICATION }, { ".c", SLANG_SOURCE_LANGUAGE_C, SLANG_STAGE_NONE }, { ".cpp", SLANG_SOURCE_LANGUAGE_CPP, SLANG_STAGE_NONE }, { ".cu", SLANG_SOURCE_LANGUAGE_CUDA, SLANG_STAGE_NONE } }; for (int i = 0; i < SLANG_COUNT_OF(entries); ++i) { const Entry& entry = entries[i]; if (path.endsWith(entry.ext)) { outImpliedStage = Stage(entry.impliedStage); return entry.sourceLanguage; } } return SLANG_SOURCE_LANGUAGE_UNKNOWN; } SlangResult addInputPath( char const* inPath, SourceLanguage langOverride = SourceLanguage::Unknown) { inputPathCount++; // look at the extension on the file name to determine // how we should handle it. String path = String(inPath); if( path.endsWith(".slang") || langOverride == SourceLanguage::Slang) { // Plain old slang code addInputSlangPath(path); return SLANG_OK; } Stage impliedStage = Stage::Unknown; SlangSourceLanguage sourceLanguage = langOverride == SourceLanguage::Unknown ? findSourceLanguageFromPath(path, impliedStage) : SlangSourceLanguage(langOverride); if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN) { requestImpl->getSink()->diagnose(SourceLoc(), Diagnostics::cannotDeduceSourceLanguage, inPath); return SLANG_FAIL; } addInputForeignShaderPath(path, sourceLanguage, impliedStage); return SLANG_OK; } void addOutputPath( String const& path, CodeGenTarget impliedFormat) { RawOutput rawOutput; rawOutput.path = path; rawOutput.impliedFormat = impliedFormat; rawOutputs.add(rawOutput); } void addOutputPath(char const* inPath) { String path = String(inPath); String ext = Path::getPathExt(path); if (ext == toSlice("slang-module") || ext == toSlice("slang-lib") || ext == toSlice("dir") || ext == toSlice("zip")) { // These extensions don't indicate a artifact container, just that we want to emit IR if (ext == toSlice("slang-module") || ext == toSlice("slang-lib")) { // We want to emit IR requestImpl->m_emitIr = true; } else { // We want to write out in an artfact "container", that can hold multiple artifacts. compileRequest->setOutputContainerFormat(SLANG_CONTAINER_FORMAT_SLANG_MODULE); } requestImpl->m_containerOutputPath = path; } else { const SlangCompileTarget target = TypeTextUtil::findCompileTargetFromExtension(ext.getUnownedSlice()); // If the target is not found the value returned is Unknown. This is okay because // we allow an unknown-format `-o`, assuming we get a target format // from another argument. addOutputPath(path, CodeGenTarget(target)); } } RawEntryPoint* getCurrentEntryPoint() { auto rawEntryPointCount = rawEntryPoints.getCount(); return rawEntryPointCount ? &rawEntryPoints[rawEntryPointCount-1] : &defaultEntryPoint; } void setStage(RawEntryPoint* rawEntryPoint, Stage stage) { if(rawEntryPoint->stage != Stage::Unknown) { rawEntryPoint->redundantStageSet = true; if( stage != rawEntryPoint->stage ) { rawEntryPoint->conflictingStagesSet = true; } } rawEntryPoint->stage = stage; } RawTarget* getCurrentTarget() { auto rawTargetCount = rawTargets.getCount(); return rawTargetCount ? &rawTargets[rawTargetCount-1] : &defaultTarget; } void setProfileVersion(RawTarget* rawTarget, ProfileVersion profileVersion) { if(rawTarget->profileVersion != ProfileVersion::Unknown) { rawTarget->redundantProfileSet = true; if(profileVersion != rawTarget->profileVersion) { rawTarget->conflictingProfilesSet = true; } } rawTarget->profileVersion = profileVersion; } void addCapabilityAtom(RawTarget* rawTarget, CapabilityAtom atom) { rawTarget->capabilityAtoms.add(atom); } void setFloatingPointMode(RawTarget* rawTarget, FloatingPointMode mode) { rawTarget->floatingPointMode = mode; } static bool _passThroughRequiresStage(PassThroughMode passThrough) { switch (passThrough) { case PassThroughMode::Glslang: case PassThroughMode::Dxc: case PassThroughMode::Fxc: { return true; } default: { return false; } } } class ReproPathVisitor : public Slang::Path::Visitor { public: virtual void accept(Slang::Path::Type type, const Slang::UnownedStringSlice& filename) SLANG_OVERRIDE { if (type == Path::Type::File && Path::getPathExt(filename) == "slang-repro") { m_filenames.add(filename); } } Slang::List m_filenames; }; static SlangResult _compileReproDirectory(SlangSession* session, EndToEndCompileRequest* originalRequest, const String& dir, DiagnosticSink* sink) { auto stdOut = originalRequest->getWriter(WriterChannel::StdOutput); ReproPathVisitor visitor; Path::find(dir, nullptr, &visitor); for (auto filename : visitor.m_filenames) { auto path = Path::combine(dir, filename); ComPtr request; SLANG_RETURN_ON_FAIL(session->createCompileRequest(request.writeRef())); auto requestImpl = asInternal(request); List buffer; SLANG_RETURN_ON_FAIL(ReproUtil::loadState(path, sink, buffer)); auto requestState = ReproUtil::getRequest(buffer); MemoryOffsetBase base; base.set(buffer.getBuffer(), buffer.getCount()); // If we can find a directory, that exists, we will set up a file system to load from that directory ComPtr fileSystem; String dirPath; if (SLANG_SUCCEEDED(ReproUtil::calcDirectoryPathFromFilename(path, dirPath))) { SlangPathType pathType; if (SLANG_SUCCEEDED(Path::getPathType(dirPath, &pathType)) && pathType == SLANG_PATH_TYPE_DIRECTORY) { fileSystem = new RelativeFileSystem(OSFileSystem::getExtSingleton(), dirPath); } } SLANG_RETURN_ON_FAIL(ReproUtil::load(base, requestState, fileSystem, requestImpl)); if (stdOut) { StringBuilder buf; buf << filename << "\n"; stdOut->write(buf.getBuffer(), buf.getLength()); } StringBuilder bufs[Index(WriterChannel::CountOf)]; ComPtr writers[Index(WriterChannel::CountOf)]; for (Index i = 0; i < Index(WriterChannel::CountOf); ++i) { writers[i] = new StringWriter(&bufs[0], 0); requestImpl->setWriter(WriterChannel(i), writers[i]); } if (SLANG_FAILED(requestImpl->compile())) { const char failed[] = "FAILED!\n"; stdOut->write(failed, SLANG_COUNT_OF(failed) - 1); const auto& diagnostics = bufs[Index(WriterChannel::Diagnostic)]; stdOut->write(diagnostics.getBuffer(), diagnostics.getLength()); return SLANG_FAIL; } } if (stdOut) { const char end[] = "(END)\n"; stdOut->write(end, SLANG_COUNT_OF(end) - 1); } return SLANG_OK; } static char const* getHelpText() { #ifdef _WIN32 #define EXECUTABLE_EXTENSION ".exe" #else #define EXECUTABLE_EXTENSION "" #endif return "Usage: slangc" EXECUTABLE_EXTENSION " [options...] [--] \n" "\n" "General options:\n" "\n" " -D[=], -D [=]: Insert a preprocessor macro.\n" " -depfile : Save the source file dependency list in a file.\n" " -entry : Specify the name of an entry-point function.\n" " Multiple -entry options may be used in a single invocation.\n" " If no -entry options are given, compiler will use [shader(...)]\n" " attributes to detect entry points.\n" " -h, -help, --help: Print this message.\n" " -I, -I : Add a path to be used in resolving '#include'\n" " and 'import' operations.\n" " -lang : Set the language for the following input files.\n" " Accepted languages are:\n" " c, cpp, c++, cxx, slang, glsl, hlsl, cu, cuda\n" " -matrix-layout-column-major: Set the default matrix layout to column-major.\n" " -matrix-layout-row-major: Set the default matrix layout to row-major.\n" " -module-name : Set the module name to use when compiling multiple\n" " .slang source files into a single module.\n" " -o : Specify a path where generated output should be written.\n" " If no -target or -stage is specified, one may be inferred\n" " from file extension (see File Extensions).\n" " If multiple -target options and a single -entry are present, each -o\n" " associates with the first -target to its left.\n" " Otherwise, if multiple -entry options are present, each -o associates\n" " with the first -entry to its left, and with the -target that matches\n" " the one inferred from .\n" " -profile [+...]: Specify the shader profile for code\n" " generation.\n" " Accepted profiles are:\n" " sm_{4_0,4_1,5_0,5_1,6_0,6_1,6_2,6_3,6_4,6_5,6_6}\n" " glsl_{110,120,130,140,150,330,400,410,420,430,440,450,460}\n" " Additional profiles that include -stage information:\n" " {vs,hs,ds,gs,ps}_\n" " See -capability for information on \n" " When multiple -target options are present, each -profile associates\n" " with the first -target to its left.\n" " -stage : Specify the stage of an entry-point function.\n" " Accepted stages are:\n" " vertex, hull, domain, geometry, fragment, compute,\n" " raygeneration, intersection, anyhit, closesthit, miss, callable\n" " When multiple -entry options are present, each -stage associated with\n" " the first -entry to its left.\n" " May be omitted if entry-point function has a [shader(...)] attribute;\n" " otherwise required for each -entry option.\n" " -target : Specifies the format in which code should be generated.\n" " Accepted formats are:\n" " glsl, hlsl, spirv, spirv-assembly, dxbc,\n" " dxbc-assembly, dxil, dxil-assembly\n" " -v, -version: Display the build version.\n" " -warnings-as-errors all: Treat all warnings as errors.\n" " -warnings-as-errors [,...]: Treat specific warning ids as errors.\n" " -warnings-disable [,...]: Disable specific warning ids.\n" " -W: Enable a warning with the specified id.\n" " -Wno-: Disable a warning with the specified id.\n" " -dump-warning-diagnostics: Dump to output list of warning diagnostic numeric and name ids.\n" " --: Treat the rest of the command line as input files.\n" "\n" "Target code generation options:\n" "\n" " -capability [+...]: Add optional capabilities\n" " to a code generation target. See Capabilities below.\n" " -default-image-format-unknown: Set the format of R/W images with unspecified\n" " format to 'unknown'. Otherwise try to guess the format.\n" " -disable-dynamic-dispatch: Disables generating dynamic dispatch code.\n" " -disable-specialization: Disables generics and specialization pass.\n" " -fp-mode , -floating-point-mode : Set the floating point mode.\n" " Accepted modes are:\n" " precise : Disable optimization that could change the output of floating-\n" " point computations, including around infinities, NaNs, denormalized\n" " values, and negative zero. Prefer the most precise versions of special\n" " functions supported by the target.\n" " fast : Allow optimizations that may change results of floating-point\n" " computations. Prefer the fastest version of special functions supported\n" " by the target.\n" " -g, -g: Include debug information in the generated code, where possible.\n" " N is the amount of information, 0..3, unspecified means 2\n" " -line-directive-mode : Sets how the `#line` directives should be\n" " produced. Available options are:\n" " none : Don't emit `#line` directives at all\n" " source-map : Use source map to track line associations (doen't emit #line)\n" " default : Default behavior\n" " If not specified, default behavior is to use C-style `#line` directives\n" " for HLSL and C/C++ output, and traditional GLSL-style `#line` directives\n" " for GLSL output.\n" " -O: Set the optimization level.\n" " N is the amount of optimization, 0..3, default is 1\n" " -obfuscate: Remove all source file information from outputs.\n" "\n" "Downstream compiler options:\n" "\n" " --path: Specify path to a downstream \n" " executable or library. Accepted compilers are:\n" " fxc (d3dcompiler_47.dll)\n" " dxc (dxcompiler.*)\n" " glslang (slang-glslang.*)\n" " vs = visualstudio (cl.exe)\n" " clang\n" " gcc (g++)\n" " c = cpp = genericcpp\n" " nvrtc\n" " llvm\n" " -default-downstream-compiler : Set a default compiler\n" " for the given language. See -lang for the list of languages.\n" " -X