// 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 "slang-repro.h" #include "slang-serialize-ir.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 namespace Slang { SlangResult _addLibraryReference(EndToEndCompileRequest* req, Stream* stream); 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 }, { ".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 == "slang-module" || ext == "slang-lib") { 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; } SlangResult parse( int argc, char const* const* argv) { // Copy some state out of the current request, in case we've been called // after some other initialization has been performed. flags = requestImpl->getFrontEndReq()->compileFlags; DiagnosticSink* requestSink = requestImpl->getSink(); CommandLineContext* cmdLineContext = requestImpl->getLinkage()->m_downstreamArgs.getContext(); // Why create a new DiagnosticSink? // We *don't* want the lexer that comes as default (it's for Slang source!) // We may want to set flags that are different // We will need to use a new sourceManager that will just last for this parse and will map locs to // source lines. // // The *problem* is that we still need to communicate to the requestSink in some suitable way. // // 1) We could have some kind of scoping mechanism (and only one sink) // 2) We could have a 'parent' diagnostic sink, that if we set we route output too // 3) We use something like the ISlangWriter to always be the thing output too (this has problems because // some code assumes the diagnostics are accessible as a string) // // The solution used here is to have DiagnosticsSink have a 'parent' that also gets diagnostics reported to. DiagnosticSink parseSink(cmdLineContext->getSourceManager(), nullptr); { parseSink.setFlags(requestSink->getFlags()); // Allow HumaneLoc - it won't display much for command line parsing - just (1): // Leaving allows for diagnostics to be compatible with other Slang diagnostic parsing. //parseSink.resetFlag(DiagnosticSink::Flag::HumaneLoc); parseSink.setFlag(DiagnosticSink::Flag::SourceLocationLine); } // All diagnostics will also be sent to requestSink parseSink.setParentSink(requestSink); DiagnosticSink* sink = &parseSink; // Set up the args CommandLineArgs args(cmdLineContext); // Converts input args into args in 'args'. // Doing so will allocate some SourceLoc space from the CommandLineContext. args.setArgs(argv, argc); { auto linkage = requestImpl->getLinkage(); // Before we do anything else lets strip out all of the downstream arguments. SLANG_RETURN_ON_FAIL(linkage->m_downstreamArgs.stripDownstreamArgs(args, 0, sink)); } CommandLineReader reader(&args, sink); SlangMatrixLayoutMode defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_MODE_UNKNOWN; // The default archive type is zip SlangArchiveType archiveType = SLANG_ARCHIVE_TYPE_ZIP; bool compileStdLib = false; slang::CompileStdLibFlags compileStdLibFlags = 0; bool hasLoadedRepro = false; while (reader.hasArg()) { auto arg = reader.getArgAndAdvance(); const auto& argValue = arg.value; if (argValue[0] == '-') { if(argValue == "-no-mangle" ) { flags |= SLANG_COMPILE_FLAG_NO_MANGLING; } else if (argValue == "-load-stdlib") { CommandLineArg fileName; SLANG_RETURN_ON_FAIL(reader.expectArg(fileName)); // Load the file ScopedAllocation contents; SLANG_RETURN_ON_FAIL(File::readAllBytes(fileName.value, contents)); SLANG_RETURN_ON_FAIL(session->loadStdLib(contents.getData(), contents.getSizeInBytes())); } else if (argValue == "-compile-stdlib") { compileStdLib = true; } else if (argValue == "-archive-type") { CommandLineArg archiveTypeName; SLANG_RETURN_ON_FAIL(reader.expectArg(archiveTypeName)); archiveType = TypeTextUtil::findArchiveType(archiveTypeName.value.getUnownedSlice()); if (archiveType == SLANG_ARCHIVE_TYPE_UNDEFINED) { sink->diagnose(archiveTypeName.loc, Diagnostics::unknownArchiveType, archiveTypeName.value); return SLANG_FAIL; } } else if (argValue == "-save-stdlib") { CommandLineArg fileName; SLANG_RETURN_ON_FAIL(reader.expectArg(fileName)); ComPtr blob; SLANG_RETURN_ON_FAIL(session->saveStdLib(archiveType, blob.writeRef())); SLANG_RETURN_ON_FAIL(File::writeAllBytes(fileName.value, blob->getBufferPointer(), blob->getBufferSize())); } else if (argValue == "-save-stdlib-bin-source") { CommandLineArg fileName; SLANG_RETURN_ON_FAIL(reader.expectArg(fileName)); ComPtr blob; SLANG_RETURN_ON_FAIL(session->saveStdLib(archiveType, blob.writeRef())); StringBuilder builder; StringWriter writer(&builder, 0); SLANG_RETURN_ON_FAIL(HexDumpUtil::dumpSourceBytes((const uint8_t*)blob->getBufferPointer(), blob->getBufferSize(), 16, &writer)); File::writeAllText(fileName.value, builder); } else if (argValue == "-no-codegen") { flags |= SLANG_COMPILE_FLAG_NO_CODEGEN; } else if (argValue == "-dump-intermediates") { compileRequest->setDumpIntermediates(true); } else if (argValue == "-dump-ir-ids") { requestImpl->getFrontEndReq()->m_irDumpOptions.flags |= IRDumpOptions::Flag::DumpDebugIds; requestImpl->getBackEndReq()->m_irDumpOptions.flags |= IRDumpOptions::Flag::DumpDebugIds; } else if (argValue == "-dump-intermediate-prefix") { CommandLineArg prefix; SLANG_RETURN_ON_FAIL(reader.expectArg(prefix)); requestImpl->getBackEndReq()->m_dumpIntermediatePrefix = prefix.value; } else if (argValue == "-output-includes") { requestImpl->getFrontEndReq()->outputIncludes = true; } else if(argValue == "-dump-ir" ) { requestImpl->getFrontEndReq()->shouldDumpIR = true; requestImpl->getBackEndReq()->shouldDumpIR = true; } else if (argValue == "-E" || argValue == "-output-preprocessor") { requestImpl->getFrontEndReq()->outputPreprocessor = true; } else if (argValue == "-dump-ast") { requestImpl->getFrontEndReq()->shouldDumpAST = true; } else if (argValue == "-doc") { // If compiling stdlib is enabled, will write out documentation compileStdLibFlags |= slang::CompileStdLibFlag::WriteDocumentation; // Enable writing out documentation on the req requestImpl->getFrontEndReq()->shouldDocument = true; } else if (argValue == "-dump-repro") { CommandLineArg dumpRepro; SLANG_RETURN_ON_FAIL(reader.expectArg(dumpRepro)); requestImpl->m_dumpRepro = dumpRepro.value; compileRequest->enableReproCapture(); } else if (argValue == "-dump-repro-on-error") { requestImpl->m_dumpReproOnError = true; } else if (argValue == "-extract-repro") { CommandLineArg reproName; SLANG_RETURN_ON_FAIL(reader.expectArg(reproName)); { const Result res = ReproUtil::extractFilesToDirectory(reproName.value, sink); if (SLANG_FAILED(res)) { sink->diagnose(reproName.loc, Diagnostics::unableExtractReproToDirectory, reproName.value); return res; } } } else if (argValue == "-module-name") { CommandLineArg moduleName; SLANG_RETURN_ON_FAIL(reader.expectArg(moduleName)); compileRequest->setDefaultModuleName(moduleName.value.getBuffer()); } else if(argValue == "-load-repro") { CommandLineArg reproName; SLANG_RETURN_ON_FAIL(reader.expectArg(reproName)); List buffer; { const Result res = ReproUtil::loadState(reproName.value, sink, buffer); if (SLANG_FAILED(res)) { sink->diagnose(reproName.loc, Diagnostics::unableToReadFile, reproName.value); return res; } } 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(reproName.value, 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)); hasLoadedRepro = true; } else if (argValue == "-load-repro-directory") { CommandLineArg reproDirectory; SLANG_RETURN_ON_FAIL(reader.expectArg(reproDirectory)); SLANG_RETURN_ON_FAIL(_compileReproDirectory(session, requestImpl, reproDirectory.value, sink)); } else if (argValue == "-repro-file-system") { CommandLineArg reproName; SLANG_RETURN_ON_FAIL(reader.expectArg(reproName)); List buffer; { const Result res = ReproUtil::loadState(reproName.value, sink, buffer); if (SLANG_FAILED(res)) { sink->diagnose(reproName.loc, Diagnostics::unableToReadFile, reproName.value); return res; } } 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 dirFileSystem; String dirPath; if (SLANG_SUCCEEDED(ReproUtil::calcDirectoryPathFromFilename(reproName.value, dirPath))) { SlangPathType pathType; if (SLANG_SUCCEEDED(Path::getPathType(dirPath, &pathType)) && pathType == SLANG_PATH_TYPE_DIRECTORY) { dirFileSystem = new RelativeFileSystem(OSFileSystem::getExtSingleton(), dirPath, true); } } RefPtr cacheFileSystem; SLANG_RETURN_ON_FAIL(ReproUtil::loadFileSystem(base, requestState, dirFileSystem, cacheFileSystem)); // I might want to make the dir file system the fallback file system... cacheFileSystem->setInnerFileSystem(dirFileSystem, cacheFileSystem->getUniqueIdentityMode(), cacheFileSystem->getPathStyle()); // Set as the file system compileRequest->setFileSystem(cacheFileSystem); } else if (argValue == "-serial-ir") { requestImpl->getFrontEndReq()->useSerialIRBottleneck = true; } else if (argValue == "-disable-specialization") { requestImpl->getBackEndReq()->disableSpecialization = true; } else if (argValue == "-disable-dynamic-dispatch") { requestImpl->getBackEndReq()->disableDynamicDispatch = true; } else if (argValue == "-verbose-paths") { requestImpl->getSink()->setFlag(DiagnosticSink::Flag::VerbosePath); } else if (argValue == "-verify-debug-serial-ir") { requestImpl->getFrontEndReq()->verifyDebugSerialization = true; } else if(argValue == "-validate-ir" ) { requestImpl->getFrontEndReq()->shouldValidateIR = true; requestImpl->getBackEndReq()->shouldValidateIR = true; } else if(argValue == "-skip-codegen" ) { requestImpl->m_shouldSkipCodegen = true; } else if(argValue == "-parameter-blocks-use-register-spaces" ) { getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_PARAMETER_BLOCKS_USE_REGISTER_SPACES; } else if (argValue == "-ir-compression") { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); SLANG_RETURN_ON_FAIL(SerialParseUtil::parseCompressionType(name.value.getUnownedSlice(), requestImpl->getLinkage()->serialCompressionType)); } else if (argValue == "-target") { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); const CodeGenTarget format = (CodeGenTarget)TypeTextUtil::findCompileTargetFromName(name.value.getUnownedSlice()); if (format == CodeGenTarget::Unknown) { sink->diagnose(name.loc, Diagnostics::unknownCodeGenerationTarget, name.value); return SLANG_FAIL; } RawTarget rawTarget; rawTarget.format = CodeGenTarget(format); rawTargets.add(rawTarget); } // A "profile" can specify both a general capability level for // a target, and also (as a legacy/compatibility feature) a // specific stage to use for an entry point. else if (argValue == "-profile") { CommandLineArg operand; SLANG_RETURN_ON_FAIL(reader.expectArg(operand)); // A a convenience, the `-profile` option supports an operand that consists // of multiple tokens separated with `+`. The eventual goal is that each // of these tokens will represent a capability that should be assumed to // be present on the target. // List slices; StringUtil::split(operand.value.getUnownedSlice(), '+', slices); Index sliceCount = slices.getCount(); // For now, we will require that the *first* capability in the list is // special, and represents the traditional `Profile` to compile for in // the existing Slang model. // UnownedStringSlice profileName = sliceCount >= 1 ? slices[0] : UnownedTerminatedStringSlice(""); SlangProfileID profileID = Slang::Profile::lookUp(profileName).raw; if( profileID == SLANG_PROFILE_UNKNOWN ) { sink->diagnose(operand.loc, Diagnostics::unknownProfile, profileName); return SLANG_FAIL; } else { auto profile = Profile(profileID); setProfileVersion(getCurrentTarget(), profile.getVersion()); // A `-profile` option that also specifies a stage (e.g., `-profile vs_5_0`) // should be treated like a composite (e.g., `-profile sm_5_0 -stage vertex`) auto stage = profile.getStage(); if(stage != Stage::Unknown) { setStage(getCurrentEntryPoint(), stage); } } // Any additional capability tokens will be assumed to represent `CapabilityAtom`s. // Those atoms will need to be added to the supported capabilities of the target. // for(Index i = 1; i < sliceCount; ++i) { UnownedStringSlice atomName = slices[i]; CapabilityAtom atom = findCapabilityAtom(atomName); if( atom == CapabilityAtom::Invalid ) { sink->diagnose(operand.loc, Diagnostics::unknownProfile, atomName); return SLANG_FAIL; } addCapabilityAtom(getCurrentTarget(), atom); } } else if( argValue == "-capability" ) { // The `-capability` option is similar to `-profile` but does not set the actual profile // for a target (it just adds capabilities). // // TODO: Once profiles are treated as capabilities themselves, it might be possible // to treat `-profile` and `-capability` as aliases, although there might still be // value in only allowing a single `-profile` option per target while still allowing // zero or more `-capability` options. CommandLineArg operand; SLANG_RETURN_ON_FAIL(reader.expectArg(operand)); List slices; StringUtil::split(operand.value.getUnownedSlice(), '+', slices); Index sliceCount = slices.getCount(); for(Index i = 0; i < sliceCount; ++i) { UnownedStringSlice atomName = slices[i]; CapabilityAtom atom = findCapabilityAtom(atomName); if( atom == CapabilityAtom::Invalid ) { sink->diagnose(operand.loc, Diagnostics::unknownProfile, atomName); return SLANG_FAIL; } addCapabilityAtom(getCurrentTarget(), atom); } } else if (argValue == "-stage") { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); Stage stage = findStageByName(name.value); if( stage == Stage::Unknown ) { sink->diagnose(name.loc, Diagnostics::unknownStage, name.value); return SLANG_FAIL; } else { setStage(getCurrentEntryPoint(), stage); } } else if (argValue == "-entry") { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); RawEntryPoint rawEntryPoint; rawEntryPoint.name = name.value; rawEntryPoint.translationUnitIndex = currentTranslationUnitIndex; rawEntryPoints.add(rawEntryPoint); } else if (argValue == "-heterogeneous") { requestImpl->getLinkage()->m_heterogeneous = true; } else if (argValue == "-lang") { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); const SourceLanguage sourceLanguage = (SourceLanguage)TypeTextUtil::findSourceLanguage(name.value.getUnownedSlice()); if (sourceLanguage == SourceLanguage::Unknown) { sink->diagnose(name.loc, Diagnostics::unknownSourceLanguage, name.value); return SLANG_FAIL; } else { while (reader.hasArg() && reader.peekValue().startsWith("-")) { SLANG_RETURN_ON_FAIL(addInputPath(reader.getValueAndAdvance().getBuffer(), sourceLanguage)); } } } else if (argValue == "-pass-through") { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE; if (SLANG_FAILED(TypeTextUtil::findPassThrough(name.value.getUnownedSlice(), passThrough))) { sink->diagnose(name.loc, Diagnostics::unknownPassThroughTarget, name.value); return SLANG_FAIL; } compileRequest->setPassThrough(passThrough); } else if (argValue.getLength() >= 2 && argValue[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 UnownedStringSlice slice = argValue.getUnownedSlice().tail(2); CommandLineArg nextArg; if (slice.getLength() <= 0) { SLANG_RETURN_ON_FAIL(reader.expectArg(nextArg)); slice = nextArg.value.getUnownedSlice(); } // The string that sets up the define can have an `=` between // the name to be defined and its value, so we search for one. const Index equalIndex = slice.indexOf('='); // Now set the preprocessor define if (equalIndex >= 0) { // If we found an `=`, we split the string... compileRequest->addPreprocessorDefine(String(slice.head(equalIndex)).getBuffer(), String(slice.tail(equalIndex + 1)).getBuffer()); } else { // If there was no `=`, then just #define it to an empty string compileRequest->addPreprocessorDefine(String(slice).getBuffer(), ""); } } else if (argValue.getLength() >= 2 && argValue[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) UnownedStringSlice slice = argValue.getUnownedSlice().tail(2); CommandLineArg nextArg; if (slice.getLength() <= 0) { // Need to read another argument from the command line SLANG_RETURN_ON_FAIL(reader.expectArg(nextArg)); slice = nextArg.value.getUnownedSlice(); } compileRequest->addSearchPath(String(slice).getBuffer()); } // // A `-o` option is used to specify a desired output file. else if (argValue == "-o") { CommandLineArg outputPath; SLANG_RETURN_ON_FAIL(reader.expectArg(outputPath)); addOutputPath(outputPath.value.getBuffer()); } else if(argValue == "-matrix-layout-row-major") { defaultMatrixLayoutMode = kMatrixLayoutMode_RowMajor; } else if(argValue == "-matrix-layout-column-major") { defaultMatrixLayoutMode = kMatrixLayoutMode_ColumnMajor; } else if(argValue == "-line-directive-mode") { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); SlangLineDirectiveMode mode = SLANG_LINE_DIRECTIVE_MODE_DEFAULT; if(name.value == "none") { mode = SLANG_LINE_DIRECTIVE_MODE_NONE; } else { sink->diagnose(name.loc, Diagnostics::unknownLineDirectiveMode, name.value); return SLANG_FAIL; } compileRequest->setLineDirectiveMode(mode); } else if( argValue == "-fp-mode" || argValue == "-floating-point-mode" ) { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); FloatingPointMode mode = FloatingPointMode::Default; if(name.value == "fast") { mode = FloatingPointMode::Fast; } else if(name.value == "precise") { mode = FloatingPointMode::Precise; } else { sink->diagnose(name.loc, Diagnostics::unknownFloatingPointMode, name.value); return SLANG_FAIL; } setFloatingPointMode(getCurrentTarget(), mode); } else if( argValue.getLength() >= 2 && argValue[1] == 'O' ) { UnownedStringSlice levelSlice = argValue.getUnownedSlice().tail(2); SlangOptimizationLevel level = SLANG_OPTIMIZATION_LEVEL_DEFAULT; const char c = levelSlice.getLength() == 1 ? levelSlice[0] : 0; switch (c) { case '0': level = SLANG_OPTIMIZATION_LEVEL_NONE; break; case '1': level = SLANG_OPTIMIZATION_LEVEL_DEFAULT; break; case '2': level = SLANG_OPTIMIZATION_LEVEL_HIGH; break; case '3': level = SLANG_OPTIMIZATION_LEVEL_MAXIMAL; break; default: { sink->diagnose(arg.loc, Diagnostics::unknownOptimiziationLevel, arg.value); return SLANG_FAIL; } } compileRequest->setOptimizationLevel(level); } // Note: unlike with `-O` above, we have to consider that other // options might have names that start with `-g` and so cannot // just detect it as a prefix. else if( argValue == "-g" || argValue == "-g2" ) { compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_STANDARD); } else if( argValue == "-g0" ) { compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_NONE); } else if( argValue == "-g1" ) { compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_MINIMAL); } else if( argValue == "-g3" ) { compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_MAXIMAL); } else if( argValue == "-default-image-format-unknown" ) { requestImpl->getBackEndReq()->useUnknownImageFormatAsDefault = true; } else if (argValue == "-obfuscate") { requestImpl->getLinkage()->m_obfuscateCode = true; } else if (argValue == "-file-system") { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); if (name.value == "default") { compileRequest->setFileSystem(nullptr); } else if (name.value == "load-file") { // 'Simple' just implements loadFile interface, so will be wrapped with CacheFileSystem internally compileRequest->setFileSystem(OSFileSystem::getLoadSingleton()); } else if (name.value == "os") { // 'Immutable' implements the ISlangFileSystemExt interface - and will be used directly compileRequest->setFileSystem(OSFileSystem::getExtSingleton()); } else { sink->diagnose(name.loc, Diagnostics::unknownFileSystemOption, name.value); return SLANG_FAIL; } } else if (argValue == "-r") { CommandLineArg referenceModuleName; SLANG_RETURN_ON_FAIL(reader.expectArg(referenceModuleName)); // We need to deserialize and add the modules FileStream fileStream; SLANG_RETURN_ON_FAIL(fileStream.init(referenceModuleName.value, FileMode::Open, FileAccess::Read, FileShare::ReadWrite)); // TODO: probably near an error when we can't open the file? _addLibraryReference(requestImpl, &fileStream); } else if (argValue == "-v") { sink->diagnoseRaw(Severity::Note, session->getBuildTagString()); } else if( argValue == "-emit-spirv-directly" ) { getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY; } else if (argValue == "-default-downstream-compiler") { CommandLineArg sourceLanguageArg, compilerArg; SLANG_RETURN_ON_FAIL(reader.expectArg(sourceLanguageArg)); SLANG_RETURN_ON_FAIL(reader.expectArg(compilerArg)); SlangSourceLanguage sourceLanguage = TypeTextUtil::findSourceLanguage(sourceLanguageArg.value.getUnownedSlice()); if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN) { sink->diagnose(sourceLanguageArg.loc, Diagnostics::unknownSourceLanguage, sourceLanguageArg.value); return SLANG_FAIL; } SlangPassThrough compiler; if (SLANG_FAILED(TypeTextUtil::findPassThrough(compilerArg.value.getUnownedSlice(), compiler))) { sink->diagnose(compilerArg.loc, Diagnostics::unknownPassThroughTarget, compilerArg.value); return SLANG_FAIL; } if (SLANG_FAILED(session->setDefaultDownstreamCompiler(sourceLanguage, compiler))) { sink->diagnose(arg.loc, Diagnostics::unableToSetDefaultDownstreamCompiler, compilerArg.value, sourceLanguageArg.value); return SLANG_FAIL; } } else if (argValue == "--") { // The `--` option causes us to stop trying to parse options, // and treat the rest of the command line as input file names: while (reader.hasArg()) { SLANG_RETURN_ON_FAIL(addInputPath(reader.getValueAndAdvance().getBuffer())); } break; } else { if (argValue.endsWith("-path")) { const Index index = argValue.lastIndexOf('-'); if (index >= 0) { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); UnownedStringSlice passThroughSlice = argValue.getUnownedSlice().head(index).tail(1); // Skip the initial -, up to the last - SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE; if (SLANG_SUCCEEDED(TypeTextUtil::findPassThrough(passThroughSlice, passThrough))) { session->setDownstreamCompilerPath(passThrough, name.value.getBuffer()); continue; } else { sink->diagnose(arg.loc, Diagnostics::unknownDownstreamCompiler, passThroughSlice); return SLANG_FAIL; } } } sink->diagnose(arg.loc, Diagnostics::unknownCommandLineOption, argValue); // TODO: print a usage message return SLANG_FAIL; } } else { SLANG_RETURN_ON_FAIL(addInputPath(argValue.getBuffer())); } } if (compileStdLib) { SLANG_RETURN_ON_FAIL(session->compileStdLib(compileStdLibFlags)); } // TODO(JS): This is a restriction because of how setting of state works for load repro // If a repro has been loaded, then many of the following options will overwrite // what was set up. So for now they are ignored, and only parameters set as part // of the loop work if they are after -load-repro if (hasLoadedRepro) { return SLANG_OK; } compileRequest->setCompileFlags(flags); // As a compatability feature, if the user didn't list any explicit entry // point names, *and* they are compiling a single translation unit, *and* they // have either specified a stage, or we can assume one from the naming // of the translation unit, then we assume they wanted to compile a single // entry point named `main`. // if(rawEntryPoints.getCount() == 0 && rawTranslationUnits.getCount() == 1 && (defaultEntryPoint.stage != Stage::Unknown || rawTranslationUnits[0].impliedStage != Stage::Unknown)) { RawEntryPoint entry; entry.name = "main"; entry.translationUnitIndex = 0; rawEntryPoints.add(entry); } // If the user (manually or implicitly) specified only a single entry point, // then we allow the associated stage to be specified either before or after // the entry point. This means that if there is a stage attached // to the "default" entry point, we should copy it over to the // explicit one. // if( rawEntryPoints.getCount() == 1 ) { if(defaultEntryPoint.stage != Stage::Unknown) { setStage(getCurrentEntryPoint(), defaultEntryPoint.stage); } if(defaultEntryPoint.redundantStageSet) getCurrentEntryPoint()->redundantStageSet = true; if(defaultEntryPoint.conflictingStagesSet) getCurrentEntryPoint()->conflictingStagesSet = true; } else { // If the "default" entry point has had a stage (or // other state, if we add other per-entry-point state) // specified, but there is more than one entry point, // then that state doesn't apply to anything and we // should issue an error to tell the user something // funky is going on. // if( defaultEntryPoint.stage != Stage::Unknown ) { if( rawEntryPoints.getCount() == 0 ) { sink->diagnose(SourceLoc(), Diagnostics::stageSpecificationIgnoredBecauseNoEntryPoints); } else { sink->diagnose(SourceLoc(), Diagnostics::stageSpecificationIgnoredBecauseBeforeAllEntryPoints); } } } // Slang requires that every explicit entry point indicate the translation // unit it comes from. If there is only one translation unit specified, // then implicitly all entry points come from it. // if(translationUnitCount == 1) { for( auto& entryPoint : rawEntryPoints ) { entryPoint.translationUnitIndex = 0; } } else { // Otherwise, we require that all entry points be specified after // the translation unit to which tye belong. 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; } if( anyEntryPointWithoutTranslationUnit ) { sink->diagnose(SourceLoc(), Diagnostics::entryPointsNeedToBeAssociatedWithTranslationUnits); return SLANG_FAIL; } } // Now that entry points are associated with translation units, // we can make one additional pass where if an entry point has // no specified stage, but the nameing of its translation unit // implies a stage, we will use that (a manual `-stage` annotation // will always win out in such a case). // for( auto& rawEntryPoint : rawEntryPoints ) { // Skip entry points that already have a stage. if(rawEntryPoint.stage != Stage::Unknown) continue; // Sanity check: don't process entry points with no associated translation unit. if( rawEntryPoint.translationUnitIndex == -1 ) continue; auto impliedStage = rawTranslationUnits[rawEntryPoint.translationUnitIndex].impliedStage; if(impliedStage != Stage::Unknown) rawEntryPoint.stage = impliedStage; } // Note: it is possible that some entry points still won't have associated // stages at this point, but we don't want to error out here, because // those entry points might get stages later, as part of semantic checking, // if the corresponding function has a `[shader("...")]` attribute. // Now that we've tried to establish stages for entry points, we can // issue diagnostics for cases where stages were set redundantly or // in conflicting ways. // for( auto& rawEntryPoint : rawEntryPoints ) { if( rawEntryPoint.conflictingStagesSet ) { sink->diagnose(SourceLoc(), Diagnostics::conflictingStagesForEntryPoint, rawEntryPoint.name); } else if( rawEntryPoint.redundantStageSet ) { sink->diagnose(SourceLoc(), Diagnostics::sameStageSpecifiedMoreThanOnce, rawEntryPoint.stage, rawEntryPoint.name); } else if( rawEntryPoint.translationUnitIndex != -1 ) { // As a quality-of-life feature, if the file name implies a particular // stage, but the user manually specified something different for // their entry point, give a warning in case they made a mistake. auto& rawTranslationUnit = rawTranslationUnits[rawEntryPoint.translationUnitIndex]; if( rawTranslationUnit.impliedStage != Stage::Unknown && rawEntryPoint.stage != Stage::Unknown && rawTranslationUnit.impliedStage != rawEntryPoint.stage ) { sink->diagnose(SourceLoc(), Diagnostics::explicitStageDoesntMatchImpliedStage, rawEntryPoint.name, rawEntryPoint.stage, rawTranslationUnit.impliedStage); } } } // If the user is requesting code generation via pass-through, // then any entry points they specify need to have a stage set, // because fxc/dxc/glslang don't have a facility for taking // a named entry point and pulling its stage from an attribute. // if(_passThroughRequiresStage(requestImpl->m_passThrough) ) { for( auto& rawEntryPoint : rawEntryPoints ) { if( rawEntryPoint.stage == Stage::Unknown ) { sink->diagnose(SourceLoc(), Diagnostics::noStageSpecifiedInPassThroughMode, rawEntryPoint.name); } } } // We now have inferred enough information to add the // entry points to our compile request. // for( auto& rawEntryPoint : rawEntryPoints ) { if(rawEntryPoint.translationUnitIndex < 0) continue; auto translationUnitID = rawTranslationUnits[rawEntryPoint.translationUnitIndex].translationUnitID; int entryPointID = compileRequest->addEntryPoint( translationUnitID, rawEntryPoint.name.begin(), SlangStage(rawEntryPoint.stage)); rawEntryPoint.entryPointID = entryPointID; } // We are going to build a mapping from target formats to the // target that handles that format. Dictionary mapFormatToTargetIndex; // If there was no explicit `-target` specified, then we will look // at the `-o` options to see what we can infer. // if(rawTargets.getCount() == 0) { for(auto& rawOutput : rawOutputs) { // Some outputs don't imply a target format, and we shouldn't use those for inference. auto impliedFormat = rawOutput.impliedFormat; if( impliedFormat == CodeGenTarget::Unknown ) continue; int targetIndex = 0; if( !mapFormatToTargetIndex.TryGetValue(impliedFormat, targetIndex) ) { targetIndex = (int) rawTargets.getCount(); RawTarget rawTarget; rawTarget.format = impliedFormat; rawTargets.add(rawTarget); mapFormatToTargetIndex[impliedFormat] = targetIndex; } rawOutput.targetIndex = targetIndex; } } else { // If there were explicit targets, then we will use those, but still // build up our mapping. We should object if the same target format // is specified more than once (just because of the ambiguities // it will create). // int targetCount = (int) rawTargets.getCount(); for(int targetIndex = 0; targetIndex < targetCount; ++targetIndex) { auto format = rawTargets[targetIndex].format; if( mapFormatToTargetIndex.ContainsKey(format) ) { sink->diagnose(SourceLoc(), Diagnostics::duplicateTargets, format); } else { mapFormatToTargetIndex[format] = targetIndex; } } } // If we weren't able to infer any targets from output paths (perhaps // because there were no output paths), but there was a profile specified, // then we can try to infer a target from the profile. // if( rawTargets.getCount() == 0 && defaultTarget.profileVersion != ProfileVersion::Unknown && !defaultTarget.conflictingProfilesSet) { // Let's see if the chosen profile allows us to infer // the code gen target format that the user probably meant. // CodeGenTarget inferredFormat = CodeGenTarget::Unknown; auto profileVersion = defaultTarget.profileVersion; switch( Profile(profileVersion).getFamily() ) { default: break; // For GLSL profile versions, we will assume SPIR-V // is the output format the user intended. case ProfileFamily::GLSL: inferredFormat = CodeGenTarget::SPIRV; break; // For DX profile versions, we will assume that the // user wants DXIL for Shader Model 6.0 and up, // and DXBC for all earlier versions. // // Note: There is overlap where both DXBC and DXIL // nominally support SM 5.1, but in general we // expect users to prefer to make a clean break // at SM 6.0. Anybody who cares about the overlap // cases should manually specify `-target dxil`. // case ProfileFamily::DX: if( profileVersion >= ProfileVersion::DX_6_0 ) { inferredFormat = CodeGenTarget::DXIL; } else { inferredFormat = CodeGenTarget::DXBytecode; } break; } if( inferredFormat != CodeGenTarget::Unknown ) { RawTarget rawTarget; rawTarget.format = inferredFormat; rawTargets.add(rawTarget); } } // Similar to the case for entry points, if there is a single target, // then we allow some of its options to come from the "default" // target state. if(rawTargets.getCount() == 1) { if(defaultTarget.profileVersion != ProfileVersion::Unknown) { setProfileVersion(getCurrentTarget(), defaultTarget.profileVersion); } for( auto atom : defaultTarget.capabilityAtoms ) { addCapabilityAtom(getCurrentTarget(), atom); } getCurrentTarget()->targetFlags |= defaultTarget.targetFlags; if( defaultTarget.floatingPointMode != FloatingPointMode::Default ) { setFloatingPointMode(getCurrentTarget(), defaultTarget.floatingPointMode); } } else { // If the "default" target has had a profile (or other state) // specified, but there is != 1 taget, then that state doesn't // apply to anythign and we should give the user an error. // if( defaultTarget.profileVersion != ProfileVersion::Unknown ) { if( rawTargets.getCount() == 0 ) { // This should only happen if there were multiple `-profile` options, // so we didn't try to infer a target, or if the `-profile` option // somehow didn't imply a target. // sink->diagnose(SourceLoc(), Diagnostics::profileSpecificationIgnoredBecauseNoTargets); } else { sink->diagnose(SourceLoc(), Diagnostics::profileSpecificationIgnoredBecauseBeforeAllTargets); } } if( defaultTarget.targetFlags ) { if( rawTargets.getCount() == 0 ) { sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets); } else { sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets); } } if( defaultTarget.floatingPointMode != FloatingPointMode::Default ) { if( rawTargets.getCount() == 0 ) { sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets); } else { sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets); } } } for(auto& rawTarget : rawTargets) { if( rawTarget.conflictingProfilesSet ) { sink->diagnose(SourceLoc(), Diagnostics::conflictingProfilesSpecifiedForTarget, rawTarget.format); } else if( rawTarget.redundantProfileSet ) { sink->diagnose(SourceLoc(), Diagnostics::sameProfileSpecifiedMoreThanOnce, rawTarget.profileVersion, rawTarget.format); } } // TODO: do we need to require that a target must have a profile specified, // or will we continue to allow the profile to be inferred from the target? // We now have enough information to go ahead and declare the targets // through the Slang API: // for(auto& rawTarget : rawTargets) { int targetID = compileRequest->addCodeGenTarget(SlangCompileTarget(rawTarget.format)); rawTarget.targetID = targetID; if( rawTarget.profileVersion != ProfileVersion::Unknown ) { compileRequest->setTargetProfile(targetID, Profile(rawTarget.profileVersion).raw); } for( auto atom : rawTarget.capabilityAtoms ) { requestImpl->addTargetCapability(targetID, SlangCapabilityID(atom)); } if( rawTarget.targetFlags ) { compileRequest->setTargetFlags(targetID, rawTarget.targetFlags); } if( rawTarget.floatingPointMode != FloatingPointMode::Default ) { compileRequest->setTargetFloatingPointMode(targetID, SlangFloatingPointMode(rawTarget.floatingPointMode)); } } if(defaultMatrixLayoutMode != SLANG_MATRIX_LAYOUT_MODE_UNKNOWN) { compileRequest->setMatrixLayoutMode(defaultMatrixLayoutMode); } // Next we need to sort out the output files specified with `-o`, and // figure out which entry point and/or target they apply to. // // If there is only a single entry point, then that is automatically // the entry point that should be associated with all outputs. // if( rawEntryPoints.getCount() == 1 ) { for( auto& rawOutput : rawOutputs ) { rawOutput.entryPointIndex = 0; } } // // Similarly, if there is only one target, then all outputs must // implicitly appertain to that target. // if( rawTargets.getCount() == 1 ) { for( auto& rawOutput : rawOutputs ) { rawOutput.targetIndex = 0; } } // Consider the output files specified via `-o` and try to figure // out how to deal with them. // for(auto& rawOutput : rawOutputs) { // For now, most output formats need to be tightly bound to // both a target and an entry point. // If an output doesn't have a target associated with // it, then search for the target with the matching format. if( rawOutput.targetIndex == -1 ) { auto impliedFormat = rawOutput.impliedFormat; int targetIndex = -1; if(impliedFormat == CodeGenTarget::Unknown) { // If we hit this case, then it means that we need to pick the // target to assocaite with this output based on its implied // format, but the file path doesn't direclty imply a format // (it doesn't have a suffix like `.spv` that tells us what to write). // sink->diagnose(SourceLoc(), Diagnostics::cannotDeduceOutputFormatFromPath, rawOutput.path); } else if( mapFormatToTargetIndex.TryGetValue(rawOutput.impliedFormat, targetIndex) ) { rawOutput.targetIndex = targetIndex; } else { sink->diagnose(SourceLoc(), Diagnostics::cannotMatchOutputFileToTarget, rawOutput.path, rawOutput.impliedFormat); } } // We won't do any searching to match an output file // with an entry point, since the case of a single entry // point was handled above, and the user is expected to // follow the ordering rules when using multiple entry points. if( rawOutput.entryPointIndex == -1 ) { if (rawOutput.targetIndex != -1 ) { auto outputFormat = rawTargets[rawOutput.targetIndex].format; // Here we check whether the given output format supports multiple entry points // When we add targets with support for multiple entry points, // we should update this switch with those new formats switch (outputFormat) { case CodeGenTarget::CPPSource: case CodeGenTarget::PTX: case CodeGenTarget::CUDASource: case CodeGenTarget::HostCallable: case CodeGenTarget::Executable: case CodeGenTarget::SharedLibrary: rawOutput.isWholeProgram = true; break; default: sink->diagnose(SourceLoc(), Diagnostics::cannotMatchOutputFileToEntryPoint, rawOutput.path); break; } } } } // Now that we've diagnosed the output paths, we can add them // to the compile request at the appropriate locations. // // We will consider the output files specified via `-o` and try to figure // out how to deal with them. // for(auto& rawOutput : rawOutputs) { if(rawOutput.targetIndex == -1) continue; auto targetID = rawTargets[rawOutput.targetIndex].targetID; auto target = requestImpl->getLinkage()->targets[targetID]; RefPtr targetInfo; if( !requestImpl->m_targetInfos.TryGetValue(target, targetInfo) ) { targetInfo = new EndToEndCompileRequest::TargetInfo(); requestImpl->m_targetInfos[target] = targetInfo; } if (rawOutput.isWholeProgram) { if (targetInfo->wholeTargetOutputPath != "") { sink->diagnose(SourceLoc(), Diagnostics::duplicateOutputPathsForTarget, target->getTarget()); } else { target->addTargetFlags(SLANG_TARGET_FLAG_GENERATE_WHOLE_PROGRAM); targetInfo->wholeTargetOutputPath = rawOutput.path; } } else { if (rawOutput.entryPointIndex == -1) continue; Int entryPointID = rawEntryPoints[rawOutput.entryPointIndex].entryPointID; auto entryPointReq = requestImpl->getFrontEndReq()->getEntryPointReqs()[entryPointID]; //String outputPath; if (targetInfo->entryPointOutputPaths.ContainsKey(entryPointID)) { sink->diagnose(SourceLoc(), Diagnostics::duplicateOutputPathsForEntryPointAndTarget, entryPointReq->getName(), target->getTarget()); } else { targetInfo->entryPointOutputPaths[entryPointID] = rawOutput.path; } } } return (sink->getErrorCount() == 0) ? SLANG_OK : SLANG_FAIL; } }; SlangResult parseOptions( SlangCompileRequest* inCompileRequest, int argc, char const* const* argv) { Slang::EndToEndCompileRequest* compileRequest = asInternal(inCompileRequest); OptionsParser parser; parser.compileRequest = inCompileRequest; parser.requestImpl = compileRequest; parser.session = asInternal(compileRequest->getSession()); Result res = parser.parse(argc, argv); DiagnosticSink* sink = compileRequest->getSink(); if (sink->getErrorCount() > 0) { // Put the errors in the diagnostic compileRequest->m_diagnosticOutput = sink->outputBuffer.ProduceString(); } return res; } } // namespace Slang