https://github.com/shader-slang/slang
Tip revision: 5902acdabc4445a65741a7a6a3a95f223e301059 authored by Yong He on 23 January 2024, 07:19:40 UTC
[LSP] Fetch configs directly from didConfigurationChanged message. (#3478)
[LSP] Fetch configs directly from didConfigurationChanged message. (#3478)
Tip revision: 5902acd
slang-options.cpp
// 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 "slang-hlsl-to-vulkan-layout-options.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 "../compiler-core/slang-source-embed-util.h"
#include "../core/slang-string-slice-pool.h"
#include "../core/slang-char-util.h"
#include "../core/slang-name-value.h"
#include "../core/slang-command-options-writer.h"
#include <assert.h>
namespace Slang {
namespace { // anonymous
// All of the options are given an unique enum
enum class OptionKind
{
// General
MacroDefine,
DepFile,
EntryPointName,
Help,
HelpStyle,
Include,
Language,
MatrixLayoutColumn,
MatrixLayoutRow,
ModuleName,
Output,
Profile,
Stage,
Target,
Version,
WarningsAsErrors,
DisableWarnings,
EnableWarning,
DisableWarning,
DumpWarningDiagnostics,
InputFilesRemain,
EmitIr,
ReportDownstreamTime,
ReportPerfBenchmark,
SourceEmbedStyle,
SourceEmbedName,
SourceEmbedLanguage,
// Target
Capability,
DefaultImageFormatUnknown,
DisableDynamicDispatch,
DisableSpecialization,
FloatingPointMode,
DebugInformation,
LineDirectiveMode,
Optimization,
Obfuscate,
VulkanBindShift,
VulkanBindGlobals,
VulkanInvertY,
VulkanUseEntryPointName,
VulkanUseGLLayout,
VulkanEmitReflection,
GLSLForceScalarLayout,
EnableEffectAnnotations,
EmitSpirvViaGLSL,
EmitSpirvDirectly,
SPIRVCoreGrammarJSON,
// Downstream
CompilerPath,
DefaultDownstreamCompiler,
DownstreamArgs,
PassThrough,
// Repro
DumpRepro,
DumpReproOnError,
ExtractRepro,
LoadRepro,
LoadReproDirectory,
ReproFallbackDirectory,
// Debugging
DumpAst,
DumpIntermediatePrefix,
DumpIntermediates,
DumpIr,
DumpIrIds,
PreprocessorOutput,
NoCodeGen,
OutputIncludes,
ReproFileSystem,
SerialIr,
SkipCodeGen,
ValidateIr,
VerbosePaths,
VerifyDebugSerialIr,
// Experimental
FileSystem,
Heterogeneous,
NoMangle,
AllowGLSL,
// Internal
ArchiveType,
CompileStdLib,
Doc,
IrCompression,
LoadStdLib,
ReferenceModule,
SaveStdLib,
SaveStdLibBinSource,
TrackLiveness,
// Deprecated
ParameterBlocksUseRegisterSpaces,
CountOf,
};
struct Option
{
OptionKind optionKind;
const char* name;
const char* usage = nullptr;
const char* description = nullptr;
};
enum class ValueCategory
{
Compiler,
Target,
Language,
FloatingPointMode,
ArchiveType,
Stage,
LineDirectiveMode,
DebugInfoFormat,
HelpStyle,
OptimizationLevel,
DebugLevel,
FileSystemType,
VulkanShift,
SourceEmbedStyle,
CountOf,
};
template <typename T>
struct GetValueCategory;
#define SLANG_GET_VALUE_CATEGORY(cat, type) template <> struct GetValueCategory<type> { enum { Value = Index(ValueCategory::cat) }; };
SLANG_GET_VALUE_CATEGORY(Compiler, SlangPassThrough)
SLANG_GET_VALUE_CATEGORY(ArchiveType, SlangArchiveType)
SLANG_GET_VALUE_CATEGORY(LineDirectiveMode, SlangLineDirectiveMode)
SLANG_GET_VALUE_CATEGORY(FloatingPointMode, FloatingPointMode)
SLANG_GET_VALUE_CATEGORY(FileSystemType, TypeTextUtil::FileSystemType)
SLANG_GET_VALUE_CATEGORY(HelpStyle, CommandOptionsWriter::Style)
SLANG_GET_VALUE_CATEGORY(OptimizationLevel, SlangOptimizationLevel)
SLANG_GET_VALUE_CATEGORY(VulkanShift, HLSLToVulkanLayoutOptions::Kind)
SLANG_GET_VALUE_CATEGORY(SourceEmbedStyle, SourceEmbedUtil::Style)
SLANG_GET_VALUE_CATEGORY(Language, SourceLanguage)
} // anonymous
static void _addOptions(const ConstArrayView<Option>& options, CommandOptions& cmdOptions)
{
for (auto& opt : options)
{
cmdOptions.add(opt.name, opt.usage, opt.description, CommandOptions::UserValue(opt.optionKind));
}
}
void initCommandOptions(CommandOptions& options)
{
typedef CommandOptions::Flag::Enum Flag;
typedef CommandOptions::CategoryKind CategoryKind;
typedef CommandOptions::UserValue UserValue;
// Add all the option categories
options.addCategory(CategoryKind::Option, "General", "General options");
options.addCategory(CategoryKind::Option, "Target", "Target code generation options");
options.addCategory(CategoryKind::Option, "Downstream", "Downstream compiler options");
options.addCategory(CategoryKind::Option, "Debugging", "Compiler debugging/instrumentation options");
options.addCategory(CategoryKind::Option, "Repro", "Slang repro system related");
options.addCategory(CategoryKind::Option, "Experimental", "Experimental options (use at your own risk)");
options.addCategory(CategoryKind::Option, "Internal", "Internal-use options (use at your own risk)");
options.addCategory(CategoryKind::Option, "Deprecated", "Deprecated options (allowed but ignored; may be removed in future)");
// Do the easy ones
{
options.addCategory(CategoryKind::Value, "compiler", "Downstream Compilers (aka Pass through)", UserValue(ValueCategory::Compiler));
options.addValues(TypeTextUtil::getCompilerInfos());
options.addCategory(CategoryKind::Value, "language", "Language", UserValue(ValueCategory::Language));
options.addValues(TypeTextUtil::getLanguageInfos());
options.addCategory(CategoryKind::Value, "archive-type", "Archive Type", UserValue(ValueCategory::ArchiveType));
options.addValues(TypeTextUtil::getArchiveTypeInfos());
options.addCategory(CategoryKind::Value, "line-directive-mode", "Line Directive Mode", UserValue(ValueCategory::LineDirectiveMode));
options.addValues(TypeTextUtil::getLineDirectiveInfos());
options.addCategory(CategoryKind::Value, "debug-info-format", "Debug Info Format", UserValue(ValueCategory::DebugInfoFormat));
options.addValues(TypeTextUtil::getDebugInfoFormatInfos());
options.addCategory(CategoryKind::Value, "fp-mode", "Floating Point Mode", UserValue(ValueCategory::FloatingPointMode));
options.addValues(TypeTextUtil::getFloatingPointModeInfos());
options.addCategory(CategoryKind::Value, "help-style", "Help Style", UserValue(ValueCategory::HelpStyle));
options.addValues(CommandOptionsWriter::getStyleInfos());
options.addCategory(CategoryKind::Value, "optimization-level", "Optimization Level", UserValue(ValueCategory::OptimizationLevel));
options.addValues(TypeTextUtil::getOptimizationLevelInfos());
options.addCategory(CategoryKind::Value, "debug-level", "Debug Level", UserValue(ValueCategory::DebugLevel));
options.addValues(TypeTextUtil::getDebugLevelInfos());
options.addCategory(CategoryKind::Value, "file-system-type", "File System Type", UserValue(ValueCategory::FileSystemType));
options.addValues(TypeTextUtil::getFileSystemTypeInfos());
options.addCategory(CategoryKind::Value, "source-embed-style", "Source Embed Style", UserValue(ValueCategory::SourceEmbedStyle));
options.addValues(SourceEmbedUtil::getStyleInfos());
}
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! target !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
{
options.addCategory(CategoryKind::Value, "target", "Target", UserValue(ValueCategory::Target));
for (auto opt : TypeTextUtil::getCompileTargetInfos())
{
options.addValue(opt.names, opt.description, UserValue(opt.target));
}
}
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stage !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
{
options.addCategory(CategoryKind::Value, "stage", "Stage", UserValue(ValueCategory::Stage));
List<NameValue> opts;
for (auto& info: getStageInfos())
{
opts.add({ValueInt(info.stage), info.name });
}
options.addValuesWithAliases(opts.getArrayView());
}
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! vulkan-shift !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
{
options.addCategory(CategoryKind::Value, "vulkan-shift", "Vulkan Shift", UserValue(ValueCategory::VulkanShift));
options.addValues(HLSLToVulkanLayoutOptions::getKindInfos());
}
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! capabilities !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
{
options.addCategory(CategoryKind::Value, "capability",
"A capability describes an optional feature that a target may or "
"may not support. When a -capability is specified, the compiler "
"may assume that the target supports that capability, and generate "
"code accordingly.");
List<UnownedStringSlice> names;
getCapabilityNames(names);
// We'll just add to keep the list more simple...
options.addValue("spirv_1_{ 0,1,2,3,4,5 }", "minimum supported SPIR - V version");
for (auto name : names)
{
if (name.startsWith("__") ||
name.startsWith("spirv_1_") ||
name.startsWith("_"))
{
continue;
}
else if (name.startsWith("GL_") || name.startsWith("SPV_") || name.startsWith("GLSL_"))
{
// We'll assume it is an extension..
StringBuilder buf;
buf << "enables the " << name << " extension";
options.addValue(name, buf.getUnownedSlice());
}
else
{
options.addValue(name);
}
}
}
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! extension !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
{
options.addCategory(CategoryKind::Value, "file-extension",
"A <language>, <format>, and/or <stage> may be inferred from the "
"extension of an input or -o path");
// TODO(JS): It's concevable that these are enumerated via some other system
// rather than just being listed here
const CommandOptions::ValuePair pairs[] =
{
{"hlsl,fx", "hlsl"},
{"dxbc", nullptr},
{"dxbc-asm", "dxbc-assembly"},
{"dxil", nullptr},
{"dxil-asm", "dxil-assembly"},
{"glsl", nullptr},
{"vert", "glsl (vertex)"},
{"frag", "glsl (fragment)"},
{"geom", "glsl (geoemtry)"},
{"tesc", "glsl (hull)"},
{"tese", "glsl (domain)"},
{"comp", "glsl (compute)"},
{"slang", nullptr},
{"spv", "SPIR-V"},
{"spv-asm", "SPIR-V assembly"},
{"c", nullptr},
{"cpp,c++,cxx", "C++"},
{"exe", "executable"},
{"dll,so", "sharedlibrary/dll"},
{"cu", "CUDA"},
{"ptx", "PTX"},
{"obj,o", "object-code"},
{"zip", "container"},
{"slang-module,slang-library", "Slang Module/Library"},
{"dir", "Container as a directory"},
};
options.addValues(pairs, SLANG_COUNT_OF(pairs));
}
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! General !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
options.setCategory("General");
const Option generalOpts[] =
{
{ OptionKind::MacroDefine, "-D?...", "-D<name>[=<value>], -D <name>[=<value>]",
"Insert a preprocessor macro.\n"
"The space between - D and <name> is optional. If no <value> is specified, Slang will define the macro with an empty value." },
{ OptionKind::DepFile, "-depfile", "-depfile <path>", "Save the source file dependency list in a file." },
{ OptionKind::EntryPointName, "-entry", "-entry <name>",
"Specify the name of an entry-point function.\n"
"When compiling from a single file, this defaults to main if you specify a stage using -stage.\n"
"Multiple -entry options may be used in a single invocation. "
"When they do, the file associated with the entry point will be the first one found when searching to the left in the command line.\n"
"If no -entry options are given, compiler will use [shader(...)] "
"attributes to detect entry points."},
{ OptionKind::EmitIr, "-emit-ir", nullptr, "Emit IR typically as a '.slang-module' when outputting to a container." },
{ OptionKind::Help, "-h,-help,--help", "-h or -h <help-category>", "Print this message, or help in specified category." },
{ OptionKind::HelpStyle, "-help-style", "-help-style <help-style>", "Help formatting style" },
{ OptionKind::Include, "-I?...", "-I<path>, -I <path>",
"Add a path to be used in resolving '#include' "
"and 'import' operations."},
{ OptionKind::Language, "-lang", "-lang <language>", "Set the language for the following input files."},
{ OptionKind::MatrixLayoutColumn, "-matrix-layout-column-major", nullptr, "Set the default matrix layout to column-major."},
{ OptionKind::MatrixLayoutRow,"-matrix-layout-row-major", nullptr, "Set the default matrix layout to row-major."},
{ OptionKind::ModuleName, "-module-name", "-module-name <name>",
"Set the module name to use when compiling multiple .slang source files into a single module."},
{ OptionKind::Output, "-o", "-o <path>",
"Specify a path where generated output should be written.\n"
"If no -target or -stage is specified, one may be inferred "
"from file extension (see <file-extension>). "
"If multiple -target options and a single -entry are present, each -o "
"associates with the first -target to its left. "
"Otherwise, if multiple -entry options are present, each -o associates "
"with the first -entry to its left, and with the -target that matches "
"the one inferred from <path>."},
{ OptionKind::Profile, "-profile", "-profile <profile>[+<capability>...]",
"Specify the shader profile for code 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}_<version>\n"
"See -capability for information on <capability>\n"
"When multiple -target options are present, each -profile associates "
"with the first -target to its left."},
{ OptionKind::Stage, "-stage", "-stage <stage>",
"Specify the stage of an entry-point function.\n"
"When multiple -entry options are present, each -stage associated with "
"the first -entry to its left.\n"
"May be omitted if entry-point function has a [shader(...)] attribute; "
"otherwise required for each -entry option."},
{ OptionKind::Target, "-target", "-target <target>", "Specifies the format in which code should be generated."},
{ OptionKind::Version, "-v,-version", nullptr,
"Display the build version. This is the contents of git describe --tags.\n"
"It is typically only set from automated builds(such as distros available on github).A user build will by default be 'unknown'."},
{ OptionKind::WarningsAsErrors, "-warnings-as-errors", "-warnings-as-errors all or -warnings-as-errors <id>[,<id>...]",
"all - Treat all warnings as errors.\n"
"<id>[,<id>...]: Treat specific warning ids as errors.\n"},
{ OptionKind::DisableWarnings, "-warnings-disable", "-warnings-disable <id>[,<id>...]", "Disable specific warning ids."},
{ OptionKind::EnableWarning, "-W...", "-W<id>", "Enable a warning with the specified id."},
{ OptionKind::DisableWarning, "-Wno-...", "-Wno-<id>", "Disable warning with <id>"},
{ OptionKind::DumpWarningDiagnostics, "-dump-warning-diagnostics", nullptr, "Dump to output list of warning diagnostic numeric and name ids." },
{ OptionKind::InputFilesRemain, "--", nullptr, "Treat the rest of the command line as input files."},
{ OptionKind::ReportDownstreamTime, "-report-downstream-time", nullptr, "Reports the time spent in the downstream compiler." },
{ OptionKind::ReportPerfBenchmark, "-report-perf-benchmark", nullptr, "Reports compiler performance benchmark results." },
{ OptionKind::SourceEmbedStyle, "-source-embed-style", "-source-embed-style <source-embed-style>",
"If source embedding is enabled, defines the style used. When enabled (with any style other than `none`), "
"will write compile results into embeddable source for the target language. "
"If no output file is specified, the output is written to stdout. If an output file is specified "
"it is written either to that file directly (if it is appropriate for the target language), "
"or it will be output to the filename with an appropriate extension.\n\n"
"Note for C/C++ with u16/u32/u64 types it is necessary to have \"#include <stdint.h>\" before the generated file.\n" },
{ OptionKind::SourceEmbedName, "-source-embed-name", "-source-embed-name <name>",
"The name used as the basis for variables output for source embedding."},
{ OptionKind::SourceEmbedLanguage, "-source-embed-language", "-source-embed-language <language>",
"The language to be used for source embedding. Defaults to C/C++. Currently only C/C++ are supported"},
};
_addOptions(makeConstArrayView(generalOpts), options);
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Target !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
options.setCategory("Target");
StringBuilder vkShiftNames;
{
for (auto nameSlice : NameValueUtil::getNames(NameValueUtil::NameKind::All, HLSLToVulkanLayoutOptions::getKindInfos()))
{
// -fvk-{b|s|t|u}-shift
vkShiftNames << "-fvk-" << nameSlice << "-shift,";
}
// remove last ,
vkShiftNames.reduceLength(vkShiftNames.getLength() - 1);
}
const Option targetOpts[] =
{
{ OptionKind::Capability, "-capability", "-capability <capability>[+<capability>...]",
"Add optional capabilities to a code generation target. See Capabilities below."},
{ OptionKind::DefaultImageFormatUnknown, "-default-image-format-unknown", nullptr,
"Set the format of R/W images with unspecified format to 'unknown'. Otherwise try to guess the format."},
{ OptionKind::DisableDynamicDispatch, "-disable-dynamic-dispatch", nullptr, "Disables generating dynamic dispatch code." },
{ OptionKind::DisableSpecialization, "-disable-specialization", nullptr, "Disables generics and specialization pass." },
{ OptionKind::FloatingPointMode, "-fp-mode,-floating-point-mode", "-fp-mode <fp-mode>, -floating-point-mode <fp-mode>",
"Control floating point optimizations"},
{ OptionKind::DebugInformation, "-g...", "-g, -g<debug-info-format>, -g<debug-level>",
"Include debug information in the generated code, where possible.\n"
"<debug-level> is the amount of information, 0..3, unspecified means 2\n"
"<debug-info-format> specifies a debugging info format\n"
"It is valid to have multiple -g options, such as a <debug-level> and a <debug-info-format>" },
{ OptionKind::LineDirectiveMode, "-line-directive-mode", "-line-directive-mode <line-directive-mode>",
"Sets how the `#line` directives should be produced. Available options are:\n"
"If not specified, default behavior is to use C-style `#line` directives "
"for HLSL and C/C++ output, and traditional GLSL-style `#line` directives "
"for GLSL output." },
{ OptionKind::Optimization, "-O...", "-O<optimization-level>", "Set the optimization level."},
{ OptionKind::Obfuscate, "-obfuscate", nullptr, "Remove all source file information from outputs." },
{ OptionKind::GLSLForceScalarLayout,
"-force-glsl-scalar-layout", nullptr,
"Force using scalar block layout for uniform and shader storage buffers in GLSL output."},
{ OptionKind::VulkanBindShift, vkShiftNames.getBuffer(), "-fvk-<vulkan-shift>-shift <N> <space>",
"For example '-fvk-b-shift <N> <space>' shifts by N the inferred binding numbers for all resources in 'b' registers of space <space>. "
"For a resource attached with :register(bX, <space>) but not [vk::binding(...)], "
"sets its Vulkan descriptor set to <space> and binding number to X + N. If you need to shift the "
"inferred binding numbers for more than one space, provide more than one such option. "
"If more than one such option is provided for the same space, the last one takes effect. "
"If you need to shift the inferred binding numbers for all sets, use 'all' as <space>. "
"\n"
"* [DXC description](https://github.com/Microsoft/DirectXShaderCompiler/blob/main/docs/SPIR-V.rst#implicit-binding-number-assignment)\n"
"* [GLSL wiki](https://github.com/KhronosGroup/glslang/wiki/HLSL-FAQ#auto-mapped-binding-numbers)\n" },
{ OptionKind::VulkanBindGlobals, "-fvk-bind-globals", "-fvk-bind-globals <N> <descriptor-set>",
"Places the $Globals cbuffer at descriptor set <descriptor-set> and binding <N>."},
{ OptionKind::VulkanInvertY, "-fvk-invert-y", nullptr, "Negates (additively inverts) SV_Position.y before writing to stage output."},
{ OptionKind::VulkanUseEntryPointName, "-fvk-use-entrypoint-name", nullptr, "Uses the entrypoint name from the source instead of 'main' in the spirv output."},
{ OptionKind::VulkanUseGLLayout, "-fvk-use-gl-layout", nullptr, "Use std430 layout instead of D3D buffer layout for raw buffer load/stores."},
{ OptionKind::VulkanEmitReflection, "-fspv-reflect", nullptr, "Include reflection decorations in the resulting SPIRV for shader parameters."},
{ OptionKind::EnableEffectAnnotations,
"-enable-effect-annotations", nullptr,
"Enables support for legacy effect annotation syntax."},
#if defined(SLANG_CONFIG_DEFAULT_SPIRV_DIRECT)
{ OptionKind::EmitSpirvViaGLSL, "-emit-spirv-via-glsl", nullptr,
"Generate SPIR-V output by compiling generated GLSL with glslang" },
{ OptionKind::EmitSpirvDirectly, "-emit-spirv-directly", nullptr,
"Generate SPIR-V output direclty (default)" },
#else
{ OptionKind::EmitSpirvViaGLSL, "-emit-spirv-via-glsl", nullptr,
"Generate SPIR-V output by compiling generated GLSL with glslang (default)" },
{ OptionKind::EmitSpirvDirectly, "-emit-spirv-directly", nullptr,
"Generate SPIR-V output direclty rather than by compiling generated GLSL with glslang" },
{ OptionKind::SPIRVCoreGrammarJSON, "-spirv-core-grammar", nullptr,
"A path to a specific spirv.core.grammar.json to use when generating SPIR-V output" },
#endif
};
_addOptions(makeConstArrayView(targetOpts), options);
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Downstream !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
options.setCategory("Downstream");
{
auto namesList = NameValueUtil::getNames(NameValueUtil::NameKind::First, TypeTextUtil::getCompilerInfos());
StringBuilder names;
for (auto name : namesList)
{
names << "-" << name << "-path,";
}
// remove last ,
names.reduceLength(names.getLength() - 1);
options.add(names.getBuffer(), "-<compiler>-path <path>",
"Specify path to a downstream <compiler> "
"executable or library.\n",
UserValue(OptionKind::CompilerPath));
}
const Option downstreamOpts[] =
{
{ OptionKind::DefaultDownstreamCompiler, "-default-downstream-compiler", "-default-downstream-compiler <language> <compiler>",
"Set a default compiler for the given language. See -lang for the list of languages." },
{ OptionKind::DownstreamArgs, "-X...", "-X<compiler> <option> -X<compiler>... <options> -X.",
"Pass arguments to downstream <compiler>. Just -X<compiler> passes just the next argument "
"to the downstream compiler. -X<compiler>... options -X. will pass *all* of the options "
"inbetween the opening -X and -X. to the downstream compiler."},
{ OptionKind::PassThrough, "-pass-through", "-pass-through <compiler>",
"Pass the input through mostly unmodified to the "
"existing compiler <compiler>.\n"
"These are intended for debugging/testing purposes, when you want to be able to see what these existing compilers do with the \"same\" input and options"},
};
_addOptions(makeConstArrayView(downstreamOpts), options);
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Repro !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
options.setCategory("Repro");
const Option reproOpts[] =
{
{ OptionKind::DumpReproOnError, "-dump-repro-on-error", nullptr, "Dump `.slang-repro` file on any compilation error." },
{ OptionKind::ExtractRepro, "-extract-repro", "-extract-repro <name>", "Extract the repro files into a folder." },
{ OptionKind::LoadReproDirectory, "-load-repro-directory", "-load-repro-directory <path>", "Use repro along specified path" },
{ OptionKind::LoadRepro, "-load-repro", "-load-repro <name>", "Load repro"},
{ OptionKind::ReproFileSystem, "-repro-file-system", "-repro-file-system <name>", "Use a repro as a file system" },
{ OptionKind::DumpRepro, "-dump-repro", nullptr, "Dump a `.slang-repro` file that can be used to reproduce "
"a compilation on another machine.\n"},
{ OptionKind::ReproFallbackDirectory, "-repro-fallback-directory <path>",
"Specify a directory to use if a file isn't found in a repro. Should be specified *before* any repro usage such as `load-repro`. \n"
"There are two *special* directories: \n\n"
" * 'none:' indicates no fallback, so if the file isn't found in the repro compliation will fail\n"
" * 'default:' is the default (which is the OS file system)"}
};
_addOptions(makeConstArrayView(reproOpts), options);
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Debugging !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
options.setCategory("Debugging");
const Option debuggingOpts[] =
{
{ OptionKind::DumpAst, "-dump-ast", nullptr, "Dump the AST to a .slang-ast file next to the input." },
{ OptionKind::DumpIntermediatePrefix, "-dump-intermediate-prefix", "-dump-intermediate-prefix <prefix>",
"File name prefix for -dump-intermediates outputs, default is 'slang-dump-'"},
{ OptionKind::DumpIntermediates, "-dump-intermediates", nullptr, "Dump intermediate outputs for debugging." },
{ OptionKind::DumpIr, "-dump-ir", nullptr, "Dump the IR for debugging." },
{ OptionKind::DumpIrIds, "-dump-ir-ids", nullptr, "Dump the IDs with -dump-ir (debug builds only)" },
{ OptionKind::PreprocessorOutput, "-E,-output-preprocessor", nullptr, "Output the preprocessing result and exit." },
{ OptionKind::NoCodeGen, "-no-codegen", nullptr, "Skip the code generation step, just check the code and generate layout." },
{ OptionKind::OutputIncludes, "-output-includes", nullptr, "Print the hierarchy of the processed source files." },
{ OptionKind::SerialIr, "-serial-ir", nullptr, "Serialize the IR between front-end and back-end." },
{ OptionKind::SkipCodeGen, "-skip-codegen", nullptr, "Skip the code generation phase." },
{ OptionKind::ValidateIr, "-validate-ir", nullptr, "Validate the IR between the phases." },
{ OptionKind::VerbosePaths, "-verbose-paths", nullptr, "When displaying diagnostic output aim to display more detailed path information. "
"In practice this is typically the complete 'canonical' path to the source file used." },
{ OptionKind::VerifyDebugSerialIr, "-verify-debug-serial-ir", nullptr, "Verify IR in the front-end." }
};
_addOptions(makeConstArrayView(debuggingOpts), options);
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Experimental !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
options.setCategory("Experimental");
const Option experimentalOpts[] =
{
{ OptionKind::FileSystem, "-file-system", "-file-system <file-system-type>",
"Set the filesystem hook to use for a compile request."},
{ OptionKind::Heterogeneous, "-heterogeneous", nullptr, "Output heterogeneity-related code." },
{ OptionKind::NoMangle, "-no-mangle", nullptr, "Do as little mangling of names as possible." },
{ OptionKind::AllowGLSL, "-allow-glsl", nullptr, "Enable GLSL as an input language." },
};
_addOptions(makeConstArrayView(experimentalOpts), options);
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Internal !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
options.setCategory("Internal");
const Option internalOpts[] =
{
{ OptionKind::ArchiveType, "-archive-type", "-archive-type <archive-type>", "Set the archive type for -save-stdlib. Default is zip." },
{ OptionKind::CompileStdLib, "-compile-stdlib", nullptr,
"Compile the StdLib from embedded sources. "
"Will return a failure if there is already a StdLib available."},
{ OptionKind::Doc, "-doc", nullptr, "Write documentation for -compile-stdlib" },
{ OptionKind::IrCompression,"-ir-compression", "-ir-compression <type>",
"Set compression for IR and AST outputs.\n"
"Accepted compression types: none, lite"},
{ OptionKind::LoadStdLib, "-load-stdlib", "-load-stdlib <filename>", "Load the StdLib from file." },
{ OptionKind::ReferenceModule, "-r", "-r <name>", "reference module <name>" },
{ OptionKind::SaveStdLib, "-save-stdlib", "-save-stdlib <filename>", "Save the StdLib modules to an archive file." },
{ OptionKind::SaveStdLibBinSource, "-save-stdlib-bin-source","-save-stdlib-bin-source <filename>", "Same as -save-stdlib but output "
"the data as a C array.\n"},
{ OptionKind::TrackLiveness, "-track-liveness", nullptr, "Enable liveness tracking. Places SLANG_LIVE_START, and SLANG_LIVE_END in output source to indicate value liveness." },
};
_addOptions(makeConstArrayView(internalOpts), options);
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Deprecated !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
options.setCategory("Deprecated");
const Option deprecatedOpts[] =
{
{ OptionKind::ParameterBlocksUseRegisterSpaces, "-parameter-blocks-use-register-spaces", nullptr, "Parameter blocks will use register spaces" },
};
_addOptions(makeConstArrayView(deprecatedOpts), options);
// We can now check that the whole range is available. If this fails it means there
// is an enum in the list that hasn't been setup as an option!
SLANG_ASSERT(options.hasContiguousUserValueRange(CommandOptions::LookupKind::Option, UserValue(0), UserValue(OptionKind::CountOf)));
SLANG_ASSERT(options.hasContiguousUserValueRange(CommandOptions::LookupKind::Category, UserValue(0), UserValue(ValueCategory::CountOf)));
}
SlangResult _addLibraryReference(EndToEndCompileRequest* req, IArtifact* artifact);
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<String> m_filenames;
};
struct OptionsParser
{
// 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;
};
// 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;
};
struct RawOutput
{
String path;
CodeGenTarget impliedFormat = CodeGenTarget::Unknown;
int targetIndex = -1;
int entryPointIndex = -1;
bool isWholeProgram = false;
};
struct RawTarget
{
CodeGenTarget format = CodeGenTarget::Unknown;
ProfileVersion profileVersion = ProfileVersion::Unknown;
SlangTargetFlags targetFlags = kDefaultTargetFlags;
int targetID = -1;
FloatingPointMode floatingPointMode = FloatingPointMode::Default;
bool forceGLSLScalarLayout = false;
List<CapabilityName> capabilityAtoms;
// State for tracking command-line errors
bool conflictingProfilesSet = false;
bool redundantProfileSet = false;
};
int addTranslationUnit(SlangSourceLanguage language, Stage impliedStage);
void addInputSlangPath(String const& path);
void addInputForeignShaderPath(
String const& path,
SlangSourceLanguage language,
Stage impliedStage);
static Profile::RawVal findGlslProfileFromPath(const String& path);
static SlangSourceLanguage findSourceLanguageFromPath(const String& path, Stage& outImpliedStage);
SlangResult addInputPath(char const* inPath, SourceLanguage langOverride = SourceLanguage::Unknown);
void addOutputPath(String const& path, CodeGenTarget impliedFormat);
void addOutputPath(char const* inPath);
RawEntryPoint* getCurrentEntryPoint();
void setStage(RawEntryPoint* rawEntryPoint, Stage stage);
RawTarget* getCurrentTarget();
void setProfileVersion(RawTarget* rawTarget, ProfileVersion profileVersion);
void addCapabilityAtom(RawTarget* rawTarget, CapabilityName atom);
void setFloatingPointMode(RawTarget* rawTarget, FloatingPointMode mode);
SlangResult parse(
SlangCompileRequest* compileRequest,
int argc,
char const* const* argv);
SlangResult _parse(
int argc,
char const* const* argv);
static bool _passThroughRequiresStage(PassThroughMode passThrough);
SlangResult _compileReproDirectory(SlangSession* session, EndToEndCompileRequest* originalRequest, const String& dir);
// Pass Severity::Disabled to allow any original severity
SlangResult _overrideDiagnostics(const UnownedStringSlice& identifierList, Severity originalSeverity, Severity overrideSeverity);
// Pass Severity::Disabled to allow any original severity
SlangResult _overrideDiagnostic(const UnownedStringSlice& identifier, Severity originalSeverity, Severity overrideSeverity);
SlangResult _dumpDiagnostics(Severity originalSeverity);
template <typename T>
SlangResult _getValue(const CommandLineArg& arg, const UnownedStringSlice& name, T& ioValue)
{
CommandOptions::UserValue value;
SLANG_RETURN_ON_FAIL(_getValue(ValueCategory(GetValueCategory<T>::Value), arg, name, value));
ioValue = T(value);
return SLANG_OK;
}
SlangResult _getValue(ValueCategory valueCategory, const CommandLineArg& arg, const UnownedStringSlice& name, CommandOptions::UserValue& outValue);
SlangResult _getValue(ValueCategory valueCategory, const CommandLineArg& arg, CommandOptions::UserValue& outValue);
SlangResult _getValue(const ConstArrayView<ValueCategory>& valueCategories, const CommandLineArg& arg, const UnownedStringSlice& name, ValueCategory& outCat, CommandOptions::UserValue& outValue);
SlangResult _expectValue(ValueCategory valueCategory, CommandOptions::UserValue& outValue);
SlangResult _expectInt(const CommandLineArg& arg, Int& outInt);
template <typename T>
SlangResult _expectValue(T& ioValue)
{
CommandOptions::UserValue value;
SLANG_RETURN_ON_FAIL(_expectValue(ValueCategory(GetValueCategory<T>::Value), value));
ioValue = T(value);
return SLANG_OK;
}
void _appendUsageTitle(StringBuilder& out);
void _appendMinimalUsage(StringBuilder& out);
void _outputMinimalUsage();
SlangResult _parseReferenceModule(const CommandLineArg& arg);
SlangResult _parseReproFileSystem(const CommandLineArg& arg);
SlangResult _parseLoadRepro(const CommandLineArg& arg);
SlangResult _parseDebugInformation(const CommandLineArg& arg);
SlangResult _parseProfile(const CommandLineArg& arg);
SlangResult _parseHelp(const CommandLineArg& arg);
SlangSession* m_session = nullptr;
SlangCompileRequest* m_compileRequest = nullptr;
Slang::EndToEndCompileRequest* m_requestImpl = nullptr;
List<RawTarget> m_rawTargets;
RawTarget m_defaultTarget;
//
// 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<RawEntryPoint> m_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 m_defaultEntryPoint;
SlangCompileFlags m_flags = 0;
RefPtr<HLSLToVulkanLayoutOptions> m_hlslToVulkanLayoutOptions;
List<RawTranslationUnit> m_rawTranslationUnits;
// If we already have a translation unit for Slang code, then this will give its index.
// If not, it will be `-1`.
int m_slangTranslationUnitIndex = -1;
// The number of input files that have been specified
int m_inputPathCount = 0;
int m_translationUnitCount = 0;
int m_currentTranslationUnitIndex = -1;
List<RawOutput> m_rawOutputs;
DiagnosticSink m_parseSink;
DiagnosticSink* m_sink = nullptr;
FrontEndCompileRequest* m_frontEndReq = nullptr;
SlangMatrixLayoutMode m_defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_MODE_UNKNOWN;
// The default archive type is zip
SlangArchiveType m_archiveType = SLANG_ARCHIVE_TYPE_ZIP;
bool m_compileStdLib = false;
slang::CompileStdLibFlags m_compileStdLibFlags = 0;
bool m_hasLoadedRepro = false;
String m_spirvCoreGrammarJSONPath;
bool m_allowGLSLInput = false;
CommandLineReader m_reader;
CommandOptionsWriter::Style m_helpStyle = CommandOptionsWriter::Style::Text;
CommandOptions* m_cmdOptions = nullptr;
CommandLineContext* m_cmdLineContext = nullptr;
};
int OptionsParser::addTranslationUnit(
SlangSourceLanguage language,
Stage impliedStage)
{
auto translationUnitIndex = m_rawTranslationUnits.getCount();
auto translationUnitID = m_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;
m_rawTranslationUnits.add(rawTranslationUnit);
return int(translationUnitIndex);
}
void OptionsParser::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 (m_slangTranslationUnitIndex == -1)
{
m_translationUnitCount++;
m_slangTranslationUnitIndex = addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG, Stage::Unknown);
}
m_compileRequest->addTranslationUnitSourceFile(m_rawTranslationUnits[m_slangTranslationUnitIndex].translationUnitID, path.begin());
// Set the translation unit to be used by subsequent entry points
m_currentTranslationUnitIndex = m_slangTranslationUnitIndex;
}
void OptionsParser::addInputForeignShaderPath(
String const& path,
SlangSourceLanguage language,
Stage impliedStage)
{
m_translationUnitCount++;
m_currentTranslationUnitIndex = addTranslationUnit(language, impliedStage);
m_compileRequest->addTranslationUnitSourceFile(m_rawTranslationUnits[m_currentTranslationUnitIndex].translationUnitID, path.begin());
}
/* static */Profile::RawVal OptionsParser::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 (Index 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 OptionsParser::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 (Index 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 OptionsParser::addInputPath(char const* inPath, SourceLanguage langOverride )
{
m_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)
{
m_requestImpl->getSink()->diagnose(SourceLoc(), Diagnostics::cannotDeduceSourceLanguage, inPath);
return SLANG_FAIL;
}
addInputForeignShaderPath(path, sourceLanguage, impliedStage);
return SLANG_OK;
}
void OptionsParser::addOutputPath(
String const& path,
CodeGenTarget impliedFormat)
{
RawOutput rawOutput;
rawOutput.path = path;
rawOutput.impliedFormat = impliedFormat;
m_rawOutputs.add(rawOutput);
}
void OptionsParser::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
m_requestImpl->m_emitIr = true;
}
else
{
// We want to write out in an artfact "container", that can hold multiple artifacts.
m_compileRequest->setOutputContainerFormat(SLANG_CONTAINER_FORMAT_SLANG_MODULE);
}
m_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));
}
}
OptionsParser::RawEntryPoint* OptionsParser::getCurrentEntryPoint()
{
auto rawEntryPointCount = m_rawEntryPoints.getCount();
return rawEntryPointCount ? &m_rawEntryPoints[rawEntryPointCount - 1] : &m_defaultEntryPoint;
}
void OptionsParser::setStage(RawEntryPoint* rawEntryPoint, Stage stage)
{
if (rawEntryPoint->stage != Stage::Unknown)
{
rawEntryPoint->redundantStageSet = true;
if (stage != rawEntryPoint->stage)
{
rawEntryPoint->conflictingStagesSet = true;
}
}
rawEntryPoint->stage = stage;
}
OptionsParser::RawTarget* OptionsParser::getCurrentTarget()
{
auto rawTargetCount = m_rawTargets.getCount();
return rawTargetCount ? &m_rawTargets[rawTargetCount - 1] : &m_defaultTarget;
}
void OptionsParser::setProfileVersion(RawTarget* rawTarget, ProfileVersion profileVersion)
{
if (rawTarget->profileVersion != ProfileVersion::Unknown)
{
rawTarget->redundantProfileSet = true;
if (profileVersion != rawTarget->profileVersion)
{
rawTarget->conflictingProfilesSet = true;
}
}
rawTarget->profileVersion = profileVersion;
}
void OptionsParser::addCapabilityAtom(RawTarget* rawTarget, CapabilityName atom)
{
rawTarget->capabilityAtoms.add(atom);
}
void OptionsParser::setFloatingPointMode(RawTarget* rawTarget, FloatingPointMode mode)
{
rawTarget->floatingPointMode = mode;
}
/* static */bool OptionsParser::_passThroughRequiresStage(PassThroughMode passThrough)
{
switch (passThrough)
{
case PassThroughMode::Glslang:
case PassThroughMode::Dxc:
case PassThroughMode::Fxc:
{
return true;
}
default:
{
return false;
}
}
}
static SlangResult _loadRepro(const String& path, DiagnosticSink* sink, EndToEndCompileRequest* request)
{
List<uint8_t> 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<ISlangFileSystem> optionalFileSystem;
String dirPath;
if (SLANG_SUCCEEDED(ReproUtil::calcDirectoryPathFromFilename(path, dirPath)))
{
SlangPathType pathType;
if (SLANG_SUCCEEDED(Path::getPathType(dirPath, &pathType)) && pathType == SLANG_PATH_TYPE_DIRECTORY)
{
optionalFileSystem = new RelativeFileSystem(OSFileSystem::getExtSingleton(), dirPath);
}
}
SLANG_RETURN_ON_FAIL(ReproUtil::load(base, requestState, optionalFileSystem, request));
return SLANG_OK;
}
SlangResult OptionsParser::_compileReproDirectory(SlangSession* session, EndToEndCompileRequest* originalRequest, const String& dir)
{
auto stdOut = originalRequest->getWriter(WriterChannel::StdOutput);
ReproPathVisitor visitor;
Path::find(dir, nullptr, &visitor);
for (auto filename : visitor.m_filenames)
{
// Create a fresh request
ComPtr<slang::ICompileRequest> request;
SLANG_RETURN_ON_FAIL(session->createCompileRequest(request.writeRef()));
auto requestImpl = asInternal(request);
// Copy over the fallback file system
requestImpl->m_reproFallbackFileSystem = originalRequest->m_reproFallbackFileSystem;
// Load the repro into it
auto path = Path::combine(dir, filename);
if (SLANG_FAILED(_loadRepro(path, m_sink, requestImpl)))
{
if (stdOut)
{
StringBuilder buf;
buf << filename << " - Failed to load!\n";
}
continue;
}
if (stdOut)
{
StringBuilder buf;
buf << filename << "\n";
stdOut->write(buf.getBuffer(), buf.getLength());
}
StringBuilder bufs[Index(WriterChannel::CountOf)];
ComPtr<ISlangWriter> 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 OptionsParser::_overrideDiagnostics(const UnownedStringSlice& identifierList, Severity originalSeverity, Severity overrideSeverity)
{
List<UnownedStringSlice> slices;
StringUtil::split(identifierList, ',', slices);
for (const auto& slice : slices)
{
SLANG_RETURN_ON_FAIL(_overrideDiagnostic(slice, originalSeverity, overrideSeverity));
}
return SLANG_OK;
}
SlangResult OptionsParser::_overrideDiagnostic(const UnownedStringSlice& identifier, Severity originalSeverity, Severity overrideSeverity)
{
auto diagnosticsLookup = getDiagnosticsLookup();
const DiagnosticInfo* diagnostic = nullptr;
Int diagnosticId = -1;
// If it starts with a digit we assume it a number
if (identifier.getLength() > 0 && (CharUtil::isDigit(identifier[0]) || identifier[0] == '-'))
{
if (SLANG_FAILED(StringUtil::parseInt(identifier, diagnosticId)))
{
m_sink->diagnose(SourceLoc(), Diagnostics::unknownDiagnosticName, identifier);
return SLANG_FAIL;
}
// If we use numbers, we don't worry if we can't find a diagnostic
// and silently ignore. This was the previous behavior, and perhaps
// provides a way to safely disable warnings, without worrying about
// the version of the compiler.
diagnostic = diagnosticsLookup->getDiagnosticById(diagnosticId);
}
else
{
diagnostic = diagnosticsLookup->findDiagnosticByName(identifier);
if (!diagnostic)
{
m_sink->diagnose(SourceLoc(), Diagnostics::unknownDiagnosticName, identifier);
return SLANG_FAIL;
}
diagnosticId = diagnostic->id;
}
// If we are only allowing certain original severities check it's the right type
if (diagnostic && originalSeverity != Severity::Disable && diagnostic->severity != originalSeverity)
{
// Strictly speaking the diagnostic name is known, but it's not the right severity
// to be converted from, so it is an 'unknown name' in the context of severity...
// Or perhaps we want another diagnostic
m_sink->diagnose(SourceLoc(), Diagnostics::unknownDiagnosticName, identifier);
return SLANG_FAIL;
}
// Override the diagnostic severity in the sink
m_requestImpl->getSink()->overrideDiagnosticSeverity(int(diagnosticId), overrideSeverity, diagnostic);
return SLANG_OK;
}
SlangResult OptionsParser::_dumpDiagnostics(Severity originalSeverity)
{
// Get the diagnostics and dump them
auto diagnosticsLookup = getDiagnosticsLookup();
StringBuilder buf;
for (const auto& diagnostic : diagnosticsLookup->getDiagnostics())
{
if (originalSeverity != Severity::Disable &&
diagnostic->severity != originalSeverity)
{
continue;
}
buf.clear();
buf << diagnostic->id << " : ";
NameConventionUtil::convert(NameStyle::Camel, UnownedStringSlice(diagnostic->name), NameConvention::LowerKabab, buf);
buf << "\n";
m_sink->diagnoseRaw(Severity::Note, buf.getUnownedSlice());
}
return SLANG_OK;
}
void OptionsParser::_appendUsageTitle(StringBuilder& out)
{
out << "Usage: slangc [options...] [--] <input files>\n\n";
}
void OptionsParser::_outputMinimalUsage()
{
// Output usage info
StringBuilder buf;
_appendMinimalUsage(buf);
m_sink->diagnoseRaw(Severity::Note, buf.getUnownedSlice());
}
void OptionsParser::_appendMinimalUsage(StringBuilder& out)
{
_appendUsageTitle(out);
out << "For help: slangc -h\n";
}
SlangResult OptionsParser::_getValue(ValueCategory valueCategory, const CommandLineArg& arg, const UnownedStringSlice& name, CommandOptions::UserValue& outValue)
{
const auto optionIndex = m_cmdOptions->findOptionByCategoryUserValue(CommandOptions::UserValue(valueCategory), name);
if (optionIndex < 0)
{
const auto categoryIndex = m_cmdOptions->findCategoryByUserValue(CommandOptions::UserValue(valueCategory));
SLANG_ASSERT(categoryIndex >= 0);
if (categoryIndex < 0)
{
return SLANG_FAIL;
}
List<UnownedStringSlice> names;
m_cmdOptions->getCategoryOptionNames(categoryIndex, names);
StringBuilder buf;
StringUtil::join(names.getBuffer(), names.getCount(), toSlice(", "), buf);
m_sink->diagnose(arg.loc, Diagnostics::unknownCommandLineValue, buf);
return SLANG_FAIL;
}
outValue = m_cmdOptions->getOptionAt(optionIndex).userValue;
return SLANG_OK;
}
SlangResult OptionsParser::_getValue(ValueCategory valueCategory, const CommandLineArg& arg, CommandOptions::UserValue& outValue)
{
return _getValue(valueCategory, arg, arg.value.getUnownedSlice(), outValue);
}
SlangResult OptionsParser::_getValue(const ConstArrayView<ValueCategory>& valueCategories, const CommandLineArg& arg, const UnownedStringSlice& name, ValueCategory& outCat, CommandOptions::UserValue& outValue)
{
auto& cmdOptions = asInternal(m_session)->m_commandOptions;
for (auto valueCategory : valueCategories)
{
const auto optionIndex = cmdOptions.findOptionByCategoryUserValue(CommandOptions::UserValue(valueCategory), name);
if (optionIndex >= 0)
{
outCat = valueCategory;
outValue = cmdOptions.getOptionAt(optionIndex).userValue;
return SLANG_OK;
}
}
List<UnownedStringSlice> names;
for (auto valueCategory : valueCategories)
{
const auto categoryIndex = cmdOptions.findCategoryByUserValue(CommandOptions::UserValue(valueCategory));
SLANG_ASSERT(categoryIndex >= 0);
if (categoryIndex < 0)
{
return SLANG_FAIL;
}
cmdOptions.appendCategoryOptionNames(categoryIndex, names);
}
StringBuilder buf;
StringUtil::join(names.getBuffer(), names.getCount(), toSlice(", "), buf);
m_sink->diagnose(arg.loc, Diagnostics::unknownCommandLineValue, buf);
return SLANG_FAIL;
}
SlangResult OptionsParser::_expectValue(ValueCategory valueCategory, CommandOptions::UserValue& outValue)
{
CommandLineArg arg;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(arg));
SLANG_RETURN_ON_FAIL(_getValue(valueCategory, arg, outValue));
return SLANG_OK;
}
SlangResult OptionsParser::_expectInt(const CommandLineArg& initArg, Int& outInt)
{
SLANG_UNUSED(initArg);
CommandLineArg arg;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(arg));
if (SLANG_FAILED(StringUtil::parseInt(arg.value.getUnownedSlice(), outInt)))
{
m_sink->diagnose(arg.loc, Diagnostics::expectingAnInteger);
return SLANG_FAIL;
}
return SLANG_OK;
}
SlangResult OptionsParser::_parseReferenceModule(const CommandLineArg& arg)
{
SLANG_UNUSED(arg);
CommandLineArg referenceModuleName;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(referenceModuleName));
const auto path = referenceModuleName.value;
auto desc = ArtifactDescUtil::getDescFromPath(path.getUnownedSlice());
if (desc.kind == ArtifactKind::Unknown)
{
m_sink->diagnose(referenceModuleName.loc, Diagnostics::unknownLibraryKind, Path::getPathExt(path));
return SLANG_FAIL;
}
// If it's a GPU binary, then we'll assume it's a library
if (ArtifactDescUtil::isGpuUsable(desc))
{
desc.kind = ArtifactKind::Library;
}
// If its a zip we'll *assume* its a zip holding compilation results
if (desc.kind == ArtifactKind::Zip)
{
desc.payload = ArtifactPayload::CompileResults;
}
if (!ArtifactDescUtil::isLinkable(desc))
{
m_sink->diagnose(referenceModuleName.loc, Diagnostics::kindNotLinkable, Path::getPathExt(path));
return SLANG_FAIL;
}
const String name = ArtifactDescUtil::getBaseNameFromPath(desc, path.getUnownedSlice());
// Create the artifact
auto artifact = Artifact::create(desc, name.getUnownedSlice());
// There is a problem here if I want to reference a library that is a 'system' library or is not directly a file
// In that case the path shouldn't be set and the name should completely define the library.
// Seeing as on all targets the baseName doesn't have an extension, and all library types do
// if the name doesn't have an extension we can assume there is no path to it.
ComPtr<IOSFileArtifactRepresentation> fileRep;
if (Path::getPathExt(path).getLength() <= 0)
{
// If there is no extension *assume* it is the name of a system level library
fileRep = new OSFileArtifactRepresentation(IOSFileArtifactRepresentation::Kind::NameOnly, path.getUnownedSlice(), nullptr);
}
else
{
fileRep = new OSFileArtifactRepresentation(IOSFileArtifactRepresentation::Kind::Reference, path.getUnownedSlice(), nullptr);
if (!fileRep->exists())
{
m_sink->diagnose(referenceModuleName.loc, Diagnostics::libraryDoesNotExist, path);
return SLANG_FAIL;
}
}
artifact->addRepresentation(fileRep);
SLANG_RETURN_ON_FAIL(_addLibraryReference(m_requestImpl, artifact));
return SLANG_OK;
}
SlangResult OptionsParser::_parseReproFileSystem(const CommandLineArg& arg)
{
SLANG_UNUSED(arg);
CommandLineArg reproName;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproName));
List<uint8_t> buffer;
{
const Result res = ReproUtil::loadState(reproName.value, m_sink, buffer);
if (SLANG_FAILED(res))
{
m_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<ISlangFileSystem> 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);
}
}
ComPtr<ISlangFileSystemExt> fileSystem;
SLANG_RETURN_ON_FAIL(ReproUtil::loadFileSystem(base, requestState, dirFileSystem, fileSystem));
auto cacheFileSystem = as<CacheFileSystem>(fileSystem);
SLANG_ASSERT(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
m_compileRequest->setFileSystem(fileSystem);
return SLANG_OK;
}
SlangResult OptionsParser::_parseHelp(const CommandLineArg& arg)
{
SLANG_UNUSED(arg);
Index categoryIndex = -1;
if (m_reader.hasArg())
{
auto catArg = m_reader.getArgAndAdvance();
categoryIndex = m_cmdOptions->findCategoryByCaseInsensitiveName(catArg.value.getUnownedSlice());
if (categoryIndex < 0)
{
m_sink->diagnose(catArg.loc, Diagnostics::unknownHelpCategory);
return SLANG_FAIL;
}
}
CommandOptionsWriter::Options writerOptions;
writerOptions.style = m_helpStyle;
auto writer = CommandOptionsWriter::create(writerOptions);
auto& buf = writer->getBuilder();
if (categoryIndex < 0)
{
// If it's the text style we can inject usage at the top
if (m_helpStyle == CommandOptionsWriter::Style::Text)
{
_appendUsageTitle(buf);
}
else
{
// NOTE! We need this preamble because if we have links,
// we have to make sure the first thing in markdown *isn't* <>
buf << "# Slang Command Line Options\n\n";
buf << "*Usage:*\n";
buf << "```\n";
buf << "slangc [options...] [--] <input files>\n\n";
buf << "# For help\n";
buf << "slangc -h\n\n";
buf << "# To generate this file\n";
buf << "slangc -help-style markdown -h\n";
buf << "```\n";
}
writer->appendDescription(m_cmdOptions);
}
else
{
writer->appendDescriptionForCategory(m_cmdOptions, categoryIndex);
}
m_sink->diagnoseRaw(Severity::Note, buf.getBuffer());
return SLANG_OK;
}
SlangResult OptionsParser::_parseLoadRepro(const CommandLineArg& arg)
{
SLANG_UNUSED(arg);
CommandLineArg reproName;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproName));
if (SLANG_FAILED(_loadRepro(reproName.value, m_sink, m_requestImpl)))
{
m_sink->diagnose(reproName.loc, Diagnostics::unableToReadFile, reproName.value);
return SLANG_FAIL;
}
m_hasLoadedRepro = true;
return SLANG_OK;
}
SlangResult OptionsParser::_parseDebugInformation(const CommandLineArg& arg)
{
auto name = arg.value.getUnownedSlice().tail(2);
// 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.
if (name.getLength() == 0)
{
// The default is standard
m_compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_STANDARD);
}
else
{
CommandOptions::UserValue value;
ValueCategory valueCat;
ValueCategory valueCats[] = { ValueCategory::DebugLevel, ValueCategory::DebugInfoFormat };
SLANG_RETURN_ON_FAIL(_getValue(makeConstArrayView(valueCats), arg, name, valueCat, value));
if (valueCat == ValueCategory::DebugLevel)
{
const auto level = (SlangDebugInfoLevel)value;
m_compileRequest->setDebugInfoLevel(level);
}
else
{
const auto debugFormat = (SlangDebugInfoFormat)value;
m_compileRequest->setDebugInfoFormat(debugFormat);
}
}
return SLANG_OK;
}
SlangResult OptionsParser::_parseProfile(const CommandLineArg& arg)
{
SLANG_UNUSED(arg);
// 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.
CommandLineArg operand;
SLANG_RETURN_ON_FAIL(m_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<UnownedStringSlice> 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 = SlangProfileID(Slang::Profile::lookUp(profileName).raw);
if (profileID == SLANG_PROFILE_UNKNOWN)
{
m_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];
CapabilityName atom = findCapabilityName(atomName);
if (atom == CapabilityName::Invalid)
{
m_sink->diagnose(operand.loc, Diagnostics::unknownProfile, atomName);
return SLANG_FAIL;
}
addCapabilityAtom(getCurrentTarget(), atom);
}
return SLANG_OK;
}
SlangResult OptionsParser::_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.
m_flags = m_requestImpl->getFrontEndReq()->compileFlags;
// Set up the args
CommandLineArgs args(m_cmdLineContext);
// Converts input args into args in 'args'.
// Doing so will allocate some SourceLoc space from the CommandLineContext.
args.setArgs(argv, argc);
{
auto linkage = m_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, m_sink));
}
m_reader.init(&args, m_sink);
while (m_reader.hasArg())
{
auto arg = m_reader.getArgAndAdvance();
const auto& argValue = arg.value;
// If it's not an option we assume it's a path
if (argValue[0] != '-')
{
SLANG_RETURN_ON_FAIL(addInputPath(argValue.getBuffer()));
continue;
}
const Index optionIndex = m_cmdOptions->findOptionByName(argValue.getUnownedSlice());
if (optionIndex < 0)
{
m_sink->diagnose(arg.loc, Diagnostics::unknownCommandLineOption, argValue);
_outputMinimalUsage();
return SLANG_FAIL;
}
const auto optionKind = OptionKind(m_cmdOptions->getOptionAt(optionIndex).userValue);
switch (optionKind)
{
case OptionKind::NoMangle: m_flags |= SLANG_COMPILE_FLAG_NO_MANGLING; break;
case OptionKind::AllowGLSL: m_allowGLSLInput = true; break;
case OptionKind::EmitIr: m_requestImpl->m_emitIr = true; break;
case OptionKind::LoadStdLib:
{
CommandLineArg fileName;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName));
// Load the file
ScopedAllocation contents;
SLANG_RETURN_ON_FAIL(File::readAllBytes(fileName.value, contents));
SLANG_RETURN_ON_FAIL(m_session->loadStdLib(contents.getData(), contents.getSizeInBytes()));
break;
}
case OptionKind::CompileStdLib: m_compileStdLib = true; break;
case OptionKind::ArchiveType: SLANG_RETURN_ON_FAIL(_expectValue(m_archiveType)); break;
case OptionKind::SaveStdLib:
{
CommandLineArg fileName;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName));
ComPtr<ISlangBlob> blob;
SLANG_RETURN_ON_FAIL(m_session->saveStdLib(m_archiveType, blob.writeRef()));
SLANG_RETURN_ON_FAIL(File::writeAllBytes(fileName.value, blob->getBufferPointer(), blob->getBufferSize()));
break;
}
case OptionKind::SaveStdLibBinSource:
{
CommandLineArg fileName;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(fileName));
ComPtr<ISlangBlob> blob;
SLANG_RETURN_ON_FAIL(m_session->saveStdLib(m_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);
break;
}
case OptionKind::NoCodeGen: m_flags |= SLANG_COMPILE_FLAG_NO_CODEGEN; break;
case OptionKind::DumpIntermediates: m_compileRequest->setDumpIntermediates(true); break;
case OptionKind::DumpIrIds:
{
m_frontEndReq->m_irDumpOptions.flags |= IRDumpOptions::Flag::DumpDebugIds;
break;
}
case OptionKind::DumpIntermediatePrefix:
{
CommandLineArg prefix;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(prefix));
m_requestImpl->m_dumpIntermediatePrefix = prefix.value;
break;
}
case OptionKind::OutputIncludes: m_frontEndReq->outputIncludes = true; break;
case OptionKind::DumpIr: m_frontEndReq->shouldDumpIR = true; break;
case OptionKind::PreprocessorOutput: m_frontEndReq->outputPreprocessor = true; break;
case OptionKind::DumpAst: m_frontEndReq->shouldDumpAST = true; break;
case OptionKind::Doc:
{
// If compiling stdlib is enabled, will write out documentation
m_compileStdLibFlags |= slang::CompileStdLibFlag::WriteDocumentation;
// Enable writing out documentation on the req
m_frontEndReq->shouldDocument = true;
break;
}
case OptionKind::DumpRepro:
{
CommandLineArg dumpRepro;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(dumpRepro));
m_requestImpl->m_dumpRepro = dumpRepro.value;
m_compileRequest->enableReproCapture();
break;
}
case OptionKind::DumpReproOnError: m_requestImpl->m_dumpReproOnError = true; break;
case OptionKind::ExtractRepro:
{
CommandLineArg reproName;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproName));
{
const Result res = ReproUtil::extractFilesToDirectory(reproName.value, m_sink);
if (SLANG_FAILED(res))
{
m_sink->diagnose(reproName.loc, Diagnostics::unableExtractReproToDirectory, reproName.value);
return res;
}
}
break;
}
case OptionKind::ReportDownstreamTime:
{
m_compileRequest->setReportDownstreamTime(true);
break;
}
case OptionKind::ReportPerfBenchmark:
{
m_compileRequest->setReportPerfBenchmark(true);
break;
}
case OptionKind::ModuleName:
{
CommandLineArg moduleName;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(moduleName));
m_compileRequest->setDefaultModuleName(moduleName.value.getBuffer());
break;
}
case OptionKind::LoadRepro: SLANG_RETURN_ON_FAIL(_parseLoadRepro(arg)); break;
case OptionKind::LoadReproDirectory:
{
CommandLineArg reproDirectory;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproDirectory));
SLANG_RETURN_ON_FAIL(_compileReproDirectory(m_session, m_requestImpl, reproDirectory.value));
break;
}
case OptionKind::ReproFallbackDirectory:
{
CommandLineArg reproDirectory;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(reproDirectory));
if (reproDirectory.value == toSlice("default:"))
{
// The default is to use the OS file system
m_requestImpl->m_reproFallbackFileSystem = OSFileSystem::getExtSingleton();
}
else if (reproDirectory.value == toSlice("none:"))
{
// None, means that there isn't a fallback
m_requestImpl->m_reproFallbackFileSystem.setNull();
}
else
{
auto osFileSystem = OSFileSystem::getExtSingleton();
SlangPathType pathType;
if (SLANG_FAILED(osFileSystem->getPathType(reproDirectory.value.getBuffer(), &pathType) )
|| pathType != SLANG_PATH_TYPE_DIRECTORY)
{
return SLANG_FAIL;
}
// Make the fallback directory use a relative file system, to the specified directory
m_requestImpl->m_reproFallbackFileSystem = new RelativeFileSystem(osFileSystem, reproDirectory.value);
}
break;
}
case OptionKind::ReproFileSystem: SLANG_RETURN_ON_FAIL(_parseReproFileSystem(arg)); break;
case OptionKind::SerialIr: m_frontEndReq->useSerialIRBottleneck = true; break;
case OptionKind::DisableSpecialization: m_requestImpl->disableSpecialization = true; break;
case OptionKind::DisableDynamicDispatch: m_requestImpl->disableDynamicDispatch = true; break;
case OptionKind::TrackLiveness: m_requestImpl->setTrackLiveness(true); break;
case OptionKind::VerbosePaths: m_requestImpl->getSink()->setFlag(DiagnosticSink::Flag::VerbosePath); break;
case OptionKind::DumpWarningDiagnostics: _dumpDiagnostics(Severity::Warning); break;
case OptionKind::WarningsAsErrors:
{
CommandLineArg operand;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(operand));
if (operand.value == "all")
{
// TODO(JS):
// Perhaps there needs to be a way to disable this selectively.
m_requestImpl->getSink()->setFlag(DiagnosticSink::Flag::TreatWarningsAsErrors);
}
else
{
SLANG_RETURN_ON_FAIL(_overrideDiagnostics(operand.value.getUnownedSlice(), Severity::Warning, Severity::Error));
}
break;
}
case OptionKind::DisableWarnings:
{
CommandLineArg operand;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(operand));
SLANG_RETURN_ON_FAIL(_overrideDiagnostics(operand.value.getUnownedSlice(), Severity::Warning, Severity::Disable));
break;
}
case OptionKind::DisableWarning:
{
// 5 because -Wno-
auto name = argValue.getUnownedSlice().tail(5);
SLANG_RETURN_ON_FAIL(_overrideDiagnostic(name, Severity::Warning, Severity::Disable));
break;
}
case OptionKind::EnableWarning:
{
// 2 because -W
auto name = argValue.getUnownedSlice().tail(2);
// Enable the warning
SLANG_RETURN_ON_FAIL(_overrideDiagnostic(name, Severity::Warning, Severity::Warning));
break;
}
case OptionKind::VerifyDebugSerialIr: m_frontEndReq->verifyDebugSerialization = true; break;
case OptionKind::ValidateIr: m_frontEndReq->shouldValidateIR = true; break;
case OptionKind::SkipCodeGen: m_requestImpl->m_shouldSkipCodegen = true; break;
case OptionKind::ParameterBlocksUseRegisterSpaces:
{
getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_PARAMETER_BLOCKS_USE_REGISTER_SPACES;
break;
}
case OptionKind::IrCompression:
{
CommandLineArg name;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));
SLANG_RETURN_ON_FAIL(SerialParseUtil::parseCompressionType(name.value.getUnownedSlice(), m_requestImpl->getLinkage()->serialCompressionType));
break;
}
case OptionKind::Target:
{
CommandLineArg name;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));
const CodeGenTarget format = (CodeGenTarget)TypeTextUtil::findCompileTargetFromName(name.value.getUnownedSlice());
if (format == CodeGenTarget::Unknown)
{
m_sink->diagnose(name.loc, Diagnostics::unknownCodeGenerationTarget, name.value);
return SLANG_FAIL;
}
RawTarget rawTarget;
rawTarget.format = CodeGenTarget(format);
m_rawTargets.add(rawTarget);
break;
}
case OptionKind::VulkanBindShift:
{
// -fvk-{b|s|t|u}-shift <binding-shift> <set>
const auto slice = arg.value.getUnownedSlice().subString(5, 1);
HLSLToVulkanLayoutOptions::Kind kind;
SLANG_RETURN_ON_FAIL(_getValue(arg, slice, kind));
Int shift;
SLANG_RETURN_ON_FAIL(_expectInt(arg, shift));
if (m_reader.hasArg() && m_reader.peekArg().value == toSlice("all"))
{
m_reader.advance();
m_hlslToVulkanLayoutOptions->setAllShift(kind, shift);
}
else
{
Int set;
SLANG_RETURN_ON_FAIL(_expectInt(arg, set));
m_hlslToVulkanLayoutOptions->setShift(kind, set, shift);
}
break;
}
case OptionKind::VulkanBindGlobals:
{
// -fvk-bind-globals <index> <set>
Int binding, bindingSet;
SLANG_RETURN_ON_FAIL(_expectInt(arg, binding));
SLANG_RETURN_ON_FAIL(_expectInt(arg, bindingSet));
m_hlslToVulkanLayoutOptions->setGlobalsBinding(Index(bindingSet), Index(binding));
break;
}
case OptionKind::VulkanInvertY:
{
// -fvk-invert-y
m_hlslToVulkanLayoutOptions->setInvertY(true);
break;
}
case OptionKind::VulkanUseEntryPointName:
{
// -fvk-use-entrypoint-name
m_hlslToVulkanLayoutOptions->setUseOriginalEntryPointName(true);
break;
}
case OptionKind::VulkanUseGLLayout:
{
// -fvk-use-gl-layout
m_hlslToVulkanLayoutOptions->setUseGLLayout(true);
break;
}
case OptionKind::VulkanEmitReflection:
{
// -fvk-invert-y
m_hlslToVulkanLayoutOptions->setEmitSPIRVReflectionInfo(true);
break;
}
case OptionKind::Profile: SLANG_RETURN_ON_FAIL(_parseProfile(arg)); break;
case OptionKind::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(m_reader.expectArg(operand));
List<UnownedStringSlice> slices;
StringUtil::split(operand.value.getUnownedSlice(), '+', slices);
Index sliceCount = slices.getCount();
for (Index i = 0; i < sliceCount; ++i)
{
UnownedStringSlice atomName = slices[i];
CapabilityName atom = findCapabilityName(atomName);
if (atom == CapabilityName::Invalid)
{
m_sink->diagnose(operand.loc, Diagnostics::unknownProfile, atomName);
return SLANG_FAIL;
}
addCapabilityAtom(getCurrentTarget(), atom);
}
break;
}
case OptionKind::Stage:
{
CommandLineArg name;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));
Stage stage = findStageByName(name.value);
if (stage == Stage::Unknown)
{
m_sink->diagnose(name.loc, Diagnostics::unknownStage, name.value);
return SLANG_FAIL;
}
else
{
setStage(getCurrentEntryPoint(), stage);
}
break;
}
case OptionKind::GLSLForceScalarLayout:
{
getCurrentTarget()->forceGLSLScalarLayout = true;
break;
}
case OptionKind::EnableEffectAnnotations:
{
m_compileRequest->setEnableEffectAnnotations(true);
break;
}
case OptionKind::EntryPointName:
{
CommandLineArg name;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));
RawEntryPoint rawEntryPoint;
rawEntryPoint.name = name.value;
rawEntryPoint.translationUnitIndex = m_currentTranslationUnitIndex;
m_rawEntryPoints.add(rawEntryPoint);
break;
}
case OptionKind::Language:
{
CommandLineArg name;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));
const SourceLanguage sourceLanguage = (SourceLanguage)TypeTextUtil::findSourceLanguage(name.value.getUnownedSlice());
if (sourceLanguage == SourceLanguage::Unknown)
{
m_sink->diagnose(name.loc, Diagnostics::unknownSourceLanguage, name.value);
return SLANG_FAIL;
}
else
{
while (m_reader.hasArg() && !m_reader.peekValue().startsWith("-"))
{
SLANG_RETURN_ON_FAIL(addInputPath(m_reader.getValueAndAdvance().getBuffer(), sourceLanguage));
}
}
break;
}
case OptionKind::PassThrough:
{
CommandLineArg name;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));
SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE;
if (SLANG_FAILED(TypeTextUtil::findPassThrough(name.value.getUnownedSlice(), passThrough)))
{
m_sink->diagnose(name.loc, Diagnostics::unknownPassThroughTarget, name.value);
return SLANG_FAIL;
}
m_compileRequest->setPassThrough(passThrough);
break;
}
case OptionKind::MacroDefine:
{
// 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(m_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...
m_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
m_compileRequest->addPreprocessorDefine(String(slice).getBuffer(), "");
}
break;
}
case OptionKind::Include:
{
// 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(m_reader.expectArg(nextArg));
slice = nextArg.value.getUnownedSlice();
}
m_compileRequest->addSearchPath(String(slice).getBuffer());
break;
}
case OptionKind::Output:
{
//
// A `-o` option is used to specify a desired output file.
CommandLineArg outputPath;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(outputPath));
addOutputPath(outputPath.value.getBuffer());
break;
}
case OptionKind::DepFile:
{
CommandLineArg dependencyPath;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(dependencyPath));
if (m_requestImpl->m_dependencyOutputPath.getLength() == 0)
{
m_requestImpl->m_dependencyOutputPath = dependencyPath.value;
}
else
{
m_sink->diagnose(dependencyPath.loc, Diagnostics::duplicateDependencyOutputPaths);
return SLANG_FAIL;
}
break;
}
case OptionKind::MatrixLayoutRow: m_defaultMatrixLayoutMode = SlangMatrixLayoutMode(kMatrixLayoutMode_RowMajor); break;
case OptionKind::MatrixLayoutColumn: m_defaultMatrixLayoutMode = SlangMatrixLayoutMode(kMatrixLayoutMode_ColumnMajor); break;
case OptionKind::LineDirectiveMode:
{
SlangLineDirectiveMode value;
SLANG_RETURN_ON_FAIL(_expectValue(value));
m_compileRequest->setLineDirectiveMode(value);
break;
}
case OptionKind::FloatingPointMode:
{
FloatingPointMode value;
SLANG_RETURN_ON_FAIL(_expectValue(value));
setFloatingPointMode(getCurrentTarget(), value);
break;
}
case OptionKind::Optimization:
{
UnownedStringSlice levelSlice = argValue.getUnownedSlice().tail(2);
SlangOptimizationLevel level = SLANG_OPTIMIZATION_LEVEL_DEFAULT;
if (levelSlice.getLength())
{
SLANG_RETURN_ON_FAIL(_getValue(arg, levelSlice, level));
}
m_compileRequest->setOptimizationLevel(level);
break;
}
case OptionKind::DebugInformation: SLANG_RETURN_ON_FAIL(_parseDebugInformation(arg)); break;
case OptionKind::DefaultImageFormatUnknown: m_requestImpl->useUnknownImageFormatAsDefault = true; break;
case OptionKind::Obfuscate: m_requestImpl->getLinkage()->m_obfuscateCode = true; break;
case OptionKind::FileSystem:
{
typedef TypeTextUtil::FileSystemType FileSystemType;
FileSystemType value;
SLANG_RETURN_ON_FAIL(_expectValue(value));
switch (value)
{
case FileSystemType::Default: m_compileRequest->setFileSystem(nullptr); break;
case FileSystemType::LoadFile: m_compileRequest->setFileSystem(OSFileSystem::getLoadSingleton()); break;
case FileSystemType::Os: m_compileRequest->setFileSystem(OSFileSystem::getExtSingleton()); break;
}
break;
}
case OptionKind::ReferenceModule: SLANG_RETURN_ON_FAIL(_parseReferenceModule(arg)); break;
case OptionKind::Version:
{
m_sink->diagnoseRaw(Severity::Note, m_session->getBuildTagString());
break;
}
case OptionKind::HelpStyle: SLANG_RETURN_ON_FAIL(_expectValue(m_helpStyle)); break;
case OptionKind::Help:
{
SLANG_RETURN_ON_FAIL(_parseHelp(arg));
// We retun an error so after this has successfully passed, we quit
return SLANG_FAIL;
}
case OptionKind::EmitSpirvViaGLSL:
{
getCurrentTarget()->targetFlags &= ~SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY;
}
break;
case OptionKind::EmitSpirvDirectly:
{
getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY;
}
break;
case OptionKind::SPIRVCoreGrammarJSON:
{
CommandLineArg path;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(path));
m_spirvCoreGrammarJSONPath = path.value;
}
break;
case OptionKind::DefaultDownstreamCompiler:
{
CommandLineArg sourceLanguageArg, compilerArg;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(sourceLanguageArg));
SLANG_RETURN_ON_FAIL(m_reader.expectArg(compilerArg));
SlangSourceLanguage sourceLanguage = TypeTextUtil::findSourceLanguage(sourceLanguageArg.value.getUnownedSlice());
if (sourceLanguage == SLANG_SOURCE_LANGUAGE_UNKNOWN)
{
m_sink->diagnose(sourceLanguageArg.loc, Diagnostics::unknownSourceLanguage, sourceLanguageArg.value);
return SLANG_FAIL;
}
SlangPassThrough compiler;
if (SLANG_FAILED(TypeTextUtil::findPassThrough(compilerArg.value.getUnownedSlice(), compiler)))
{
m_sink->diagnose(compilerArg.loc, Diagnostics::unknownPassThroughTarget, compilerArg.value);
return SLANG_FAIL;
}
if (SLANG_FAILED(m_session->setDefaultDownstreamCompiler(sourceLanguage, compiler)))
{
m_sink->diagnose(arg.loc, Diagnostics::unableToSetDefaultDownstreamCompiler, compilerArg.value, sourceLanguageArg.value);
return SLANG_FAIL;
}
break;
}
case OptionKind::CompilerPath:
{
const Index index = argValue.lastIndexOf('-');
if (index >= 0)
{
CommandLineArg name;
SLANG_RETURN_ON_FAIL(m_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)))
{
m_session->setDownstreamCompilerPath(passThrough, name.value.getBuffer());
continue;
}
else
{
m_sink->diagnose(arg.loc, Diagnostics::unknownDownstreamCompiler, passThroughSlice);
return SLANG_FAIL;
}
}
break;
}
case OptionKind::InputFilesRemain:
{
// The `--` option causes us to stop trying to parse options,
// and treat the rest of the command line as input file names:
while (m_reader.hasArg())
{
SLANG_RETURN_ON_FAIL(addInputPath(m_reader.getValueAndAdvance().getBuffer()));
}
break;
}
case OptionKind::SourceEmbedStyle:
{
SLANG_RETURN_ON_FAIL(_expectValue(m_requestImpl->m_sourceEmbedStyle));
break;
}
case OptionKind::SourceEmbedName:
{
CommandLineArg name;
SLANG_RETURN_ON_FAIL(m_reader.expectArg(name));
m_requestImpl->m_sourceEmbedName = name.value;
break;
}
case OptionKind::SourceEmbedLanguage:
{
SLANG_RETURN_ON_FAIL(_expectValue(m_requestImpl->m_sourceEmbedLanguage));
if (!SourceEmbedUtil::isSupported((SlangSourceLanguage)m_requestImpl->m_sourceEmbedLanguage))
{
m_sink->diagnose(arg.loc, Diagnostics::unhandledLanguageForSourceEmbedding);
return SLANG_FAIL;
}
break;
}
default:
{
// Hmmm, we looked up and produced a valid enum, but it wasn't handled in the switch...
m_sink->diagnose(arg.loc, Diagnostics::unknownCommandLineOption, argValue);
_outputMinimalUsage();
return SLANG_FAIL;
}
}
}
// If there is state set on HLSL to Vulkan layout settings, set on the end to end request
// such can be added when target requests are setup
if (!m_hlslToVulkanLayoutOptions->isReset())
{
m_requestImpl->setHLSLToVulkanLayoutOptions(m_hlslToVulkanLayoutOptions);
}
// No longer need to track
m_hlslToVulkanLayoutOptions.setNull();
if (m_compileStdLib)
{
SLANG_RETURN_ON_FAIL(m_session->compileStdLib(m_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 (m_hasLoadedRepro)
{
return SLANG_OK;
}
m_compileRequest->setCompileFlags(m_flags);
m_compileRequest->setAllowGLSLInput(m_allowGLSLInput);
// 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 (m_rawEntryPoints.getCount() == 0
&& m_rawTranslationUnits.getCount() == 1
&& (m_defaultEntryPoint.stage != Stage::Unknown
|| m_rawTranslationUnits[0].impliedStage != Stage::Unknown))
{
RawEntryPoint entry;
entry.name = "main";
entry.translationUnitIndex = 0;
m_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 (m_rawEntryPoints.getCount() == 1)
{
if (m_defaultEntryPoint.stage != Stage::Unknown)
{
setStage(getCurrentEntryPoint(), m_defaultEntryPoint.stage);
}
if (m_defaultEntryPoint.redundantStageSet)
getCurrentEntryPoint()->redundantStageSet = true;
if (m_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 (m_defaultEntryPoint.stage != Stage::Unknown)
{
if (m_rawEntryPoints.getCount() == 0)
{
m_sink->diagnose(SourceLoc(), Diagnostics::stageSpecificationIgnoredBecauseNoEntryPoints);
}
else
{
m_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 (m_translationUnitCount == 1)
{
for (auto& entryPoint : m_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 : m_rawEntryPoints)
{
// Skip entry points that are already associated with a translation unit...
if (entryPoint.translationUnitIndex != -1)
continue;
anyEntryPointWithoutTranslationUnit = true;
}
if (anyEntryPointWithoutTranslationUnit)
{
m_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 : m_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 = m_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 : m_rawEntryPoints)
{
if (rawEntryPoint.conflictingStagesSet)
{
m_sink->diagnose(SourceLoc(), Diagnostics::conflictingStagesForEntryPoint, rawEntryPoint.name);
}
else if (rawEntryPoint.redundantStageSet)
{
m_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 = m_rawTranslationUnits[rawEntryPoint.translationUnitIndex];
if (rawTranslationUnit.impliedStage != Stage::Unknown
&& rawEntryPoint.stage != Stage::Unknown
&& rawTranslationUnit.impliedStage != rawEntryPoint.stage)
{
m_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(m_requestImpl->m_passThrough))
{
for (auto& rawEntryPoint : m_rawEntryPoints)
{
if (rawEntryPoint.stage == Stage::Unknown)
{
m_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 : m_rawEntryPoints)
{
if (rawEntryPoint.translationUnitIndex < 0)
continue;
auto translationUnitID = m_rawTranslationUnits[rawEntryPoint.translationUnitIndex].translationUnitID;
int entryPointID = m_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<CodeGenTarget, int> mapFormatToTargetIndex;
// If there was no explicit `-target` specified, then we will look
// at the `-o` options to see what we can infer.
//
if (m_rawTargets.getCount() == 0)
{
// If there are no targets and no outputs
if (m_rawOutputs.getCount() == 0)
{
// And we have a container for output, then enable emitting SlangIR module
if (m_requestImpl->m_containerFormat != ContainerFormat::None)
{
m_requestImpl->m_emitIr = true;
}
}
else
{
for (auto& rawOutput : m_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)m_rawTargets.getCount();
RawTarget rawTarget;
rawTarget.format = impliedFormat;
m_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)m_rawTargets.getCount();
for (int targetIndex = 0; targetIndex < targetCount; ++targetIndex)
{
auto format = m_rawTargets[targetIndex].format;
if (mapFormatToTargetIndex.containsKey(format))
{
m_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 (m_rawTargets.getCount() == 0
&& m_defaultTarget.profileVersion != ProfileVersion::Unknown
&& !m_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 = m_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;
m_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 (m_rawTargets.getCount() == 1)
{
if (m_defaultTarget.profileVersion != ProfileVersion::Unknown)
{
setProfileVersion(getCurrentTarget(), m_defaultTarget.profileVersion);
}
for (auto atom : m_defaultTarget.capabilityAtoms)
{
addCapabilityAtom(getCurrentTarget(), atom);
}
getCurrentTarget()->targetFlags |= m_defaultTarget.targetFlags;
if (m_defaultTarget.floatingPointMode != FloatingPointMode::Default)
{
setFloatingPointMode(getCurrentTarget(), m_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 (m_defaultTarget.profileVersion != ProfileVersion::Unknown)
{
if (m_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.
//
m_sink->diagnose(SourceLoc(), Diagnostics::profileSpecificationIgnoredBecauseNoTargets);
}
else
{
m_sink->diagnose(SourceLoc(), Diagnostics::profileSpecificationIgnoredBecauseBeforeAllTargets);
}
}
if (m_defaultTarget.targetFlags != kDefaultTargetFlags)
{
if (m_rawTargets.getCount() == 0)
{
m_sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets);
}
else
{
m_sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets);
}
}
if (m_defaultTarget.floatingPointMode != FloatingPointMode::Default)
{
if (m_rawTargets.getCount() == 0)
{
m_sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseNoTargets);
}
else
{
m_sink->diagnose(SourceLoc(), Diagnostics::targetFlagsIgnoredBecauseBeforeAllTargets);
}
}
}
for (auto& rawTarget : m_rawTargets)
{
if (rawTarget.conflictingProfilesSet)
{
m_sink->diagnose(SourceLoc(), Diagnostics::conflictingProfilesSpecifiedForTarget, rawTarget.format);
}
else if (rawTarget.redundantProfileSet)
{
m_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 : m_rawTargets)
{
int targetID = m_compileRequest->addCodeGenTarget(SlangCompileTarget(rawTarget.format));
rawTarget.targetID = targetID;
if (rawTarget.profileVersion != ProfileVersion::Unknown)
{
m_compileRequest->setTargetProfile(targetID, SlangProfileID(Profile(rawTarget.profileVersion).raw));
}
for (auto atom : rawTarget.capabilityAtoms)
{
m_requestImpl->addTargetCapability(targetID, SlangCapabilityID(atom));
}
if (rawTarget.targetFlags)
{
m_compileRequest->setTargetFlags(targetID, rawTarget.targetFlags);
}
if (rawTarget.floatingPointMode != FloatingPointMode::Default)
{
m_compileRequest->setTargetFloatingPointMode(targetID, SlangFloatingPointMode(rawTarget.floatingPointMode));
}
if (rawTarget.forceGLSLScalarLayout)
{
m_compileRequest->setTargetForceGLSLScalarBufferLayout(targetID, true);
}
}
if (m_defaultMatrixLayoutMode != SLANG_MATRIX_LAYOUT_MODE_UNKNOWN)
{
m_compileRequest->setMatrixLayoutMode(m_defaultMatrixLayoutMode);
}
if(m_spirvCoreGrammarJSONPath.getLength())
{
m_session->setSPIRVCoreGrammar(m_spirvCoreGrammarJSONPath.getBuffer());
}
// 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 (m_rawEntryPoints.getCount() == 1)
{
for (auto& rawOutput : m_rawOutputs)
{
rawOutput.entryPointIndex = 0;
}
}
//
// Similarly, if there is only one target, then all outputs must
// implicitly appertain to that target.
//
if (m_rawTargets.getCount() == 1)
{
for (auto& rawOutput : m_rawOutputs)
{
rawOutput.targetIndex = 0;
}
}
// If we don't have any raw outputs but do have a raw target,
// add an empty' rawOutput for certain targets where the expected behavior is obvious.
if (m_rawOutputs.getCount() == 0 &&
m_rawTargets.getCount() == 1 &&
(m_rawTargets[0].format == CodeGenTarget::HostCPPSource ||
m_rawTargets[0].format == CodeGenTarget::PyTorchCppBinding ||
m_rawTargets[0].format == CodeGenTarget::CUDASource ||
m_rawTargets[0].format == CodeGenTarget::SPIRV ||
m_rawTargets[0].format == CodeGenTarget::SPIRVAssembly ||
ArtifactDescUtil::makeDescForCompileTarget(asExternal(m_rawTargets[0].format)).kind == ArtifactKind::HostCallable))
{
RawOutput rawOutput;
rawOutput.impliedFormat = m_rawTargets[0].format;
rawOutput.targetIndex = 0;
m_rawOutputs.add(rawOutput);
}
// Consider the output files specified via `-o` and try to figure
// out how to deal with them.
//
for (auto& rawOutput : m_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).
//
m_sink->diagnose(SourceLoc(), Diagnostics::cannotDeduceOutputFormatFromPath, rawOutput.path);
}
else if (mapFormatToTargetIndex.tryGetValue(rawOutput.impliedFormat, targetIndex))
{
rawOutput.targetIndex = targetIndex;
}
else
{
m_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 = m_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::HostHostCallable:
case CodeGenTarget::ShaderHostCallable:
case CodeGenTarget::HostExecutable:
case CodeGenTarget::ShaderSharedLibrary:
case CodeGenTarget::PyTorchCppBinding:
case CodeGenTarget::DXIL:
rawOutput.isWholeProgram = true;
break;
case CodeGenTarget::SPIRV:
case CodeGenTarget::SPIRVAssembly:
if (getCurrentTarget()->targetFlags & SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY)
{
rawOutput.isWholeProgram = true;
}
break;
default:
m_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 : m_rawOutputs)
{
if (rawOutput.targetIndex == -1) continue;
auto targetID = m_rawTargets[rawOutput.targetIndex].targetID;
auto target = m_requestImpl->getLinkage()->targets[targetID];
RefPtr<EndToEndCompileRequest::TargetInfo> targetInfo;
if (!m_requestImpl->m_targetInfos.tryGetValue(target, targetInfo))
{
targetInfo = new EndToEndCompileRequest::TargetInfo();
m_requestImpl->m_targetInfos[target] = targetInfo;
}
if (rawOutput.isWholeProgram)
{
if (targetInfo->wholeTargetOutputPath != "")
{
m_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 = m_rawEntryPoints[rawOutput.entryPointIndex].entryPointID;
auto entryPointReq = m_requestImpl->getFrontEndReq()->getEntryPointReqs()[entryPointID];
//String outputPath;
if (targetInfo->entryPointOutputPaths.containsKey(entryPointID))
{
m_sink->diagnose(SourceLoc(), Diagnostics::duplicateOutputPathsForEntryPointAndTarget, entryPointReq->getName(), target->getTarget());
}
else
{
targetInfo->entryPointOutputPaths[entryPointID] = rawOutput.path;
}
}
}
return (m_sink->getErrorCount() == 0) ? SLANG_OK : SLANG_FAIL;
}
SlangResult OptionsParser::parse(
SlangCompileRequest* compileRequest,
int argc,
char const* const* argv)
{
m_compileRequest = compileRequest;
// Set up useful members
m_requestImpl = asInternal(compileRequest);
auto session = asInternal(m_requestImpl->getSession());
m_session = session;
m_frontEndReq = m_requestImpl->getFrontEndReq();
m_hlslToVulkanLayoutOptions = new HLSLToVulkanLayoutOptions;
m_cmdOptions = &session->m_commandOptions;
DiagnosticSink* requestSink = m_requestImpl->getSink();
m_cmdLineContext = m_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.
m_parseSink.init(m_cmdLineContext->getSourceManager(), nullptr);
{
m_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);
m_parseSink.setFlag(DiagnosticSink::Flag::SourceLocationLine);
}
// All diagnostics will also be sent to requestSink
m_parseSink.setParentSink(requestSink);
m_sink = &m_parseSink;
Result res = _parse(argc, argv);
m_sink = nullptr;
if (requestSink->getErrorCount() > 0)
{
// Put the errors in the diagnostic
m_requestImpl->m_diagnosticOutput = requestSink->outputBuffer.produceString();
}
return res;
}
SlangResult parseOptions(
SlangCompileRequest* inCompileRequest,
int argc,
char const* const* argv)
{
OptionsParser parser;
return parser.parse(inCompileRequest, argc, argv);
}
} // namespace Slang