diff options
Diffstat (limited to 'source')
| -rw-r--r-- | source/core/slang-command-options.cpp | 842 | ||||
| -rw-r--r-- | source/core/slang-command-options.h | 253 | ||||
| -rw-r--r-- | source/core/slang-name-value.cpp | 163 | ||||
| -rw-r--r-- | source/core/slang-name-value.h | 76 | ||||
| -rw-r--r-- | source/core/slang-string-util.cpp | 24 | ||||
| -rw-r--r-- | source/core/slang-string-util.h | 7 | ||||
| -rw-r--r-- | source/core/slang-type-text-util.cpp | 292 | ||||
| -rw-r--r-- | source/core/slang-type-text-util.h | 31 | ||||
| -rw-r--r-- | source/slang/slang-capability.cpp | 9 | ||||
| -rw-r--r-- | source/slang/slang-capability.h | 4 | ||||
| -rw-r--r-- | source/slang/slang-compiler.cpp | 13 | ||||
| -rwxr-xr-x | source/slang/slang-compiler.h | 4 | ||||
| -rw-r--r-- | source/slang/slang-diagnostic-defs.h | 5 | ||||
| -rw-r--r-- | source/slang/slang-options.cpp | 1223 | ||||
| -rw-r--r-- | source/slang/slang-options.h | 4 | ||||
| -rw-r--r-- | source/slang/slang-profile.h | 8 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 4 |
17 files changed, 2309 insertions, 653 deletions
diff --git a/source/core/slang-command-options.cpp b/source/core/slang-command-options.cpp new file mode 100644 index 000000000..4577bcb8e --- /dev/null +++ b/source/core/slang-command-options.cpp @@ -0,0 +1,842 @@ +// slang-command-options.cpp + +#include "slang-command-options.h" + +#include "slang-string-util.h" +#include "slang-char-util.h" +#include "slang-byte-encode-util.h" + +namespace Slang { + +SlangResult CommandOptions::_addName(LookupKind kind, const UnownedStringSlice& name, Index targetIndex) +{ + NameKey nameKey; + nameKey.kind = kind; + nameKey.nameIndex = (Index)m_pool.add(name); + + if (m_nameMap.tryGetValueOrAdd(nameKey, targetIndex)) + { + SLANG_ASSERT(!"Option is already added!"); + return SLANG_FAIL; + } + return SLANG_OK; +} + +SlangResult CommandOptions::_addOptionName(const UnownedStringSlice& name, Flags flags, Index targetIndex) +{ + SLANG_RETURN_ON_FAIL(_addName(LookupKind::Option, name, targetIndex)); + + // Add to prefix flags + if (flags & (Flag::CanPrefix | Flag::IsPrefix)) + { + const auto length = name.getLength(); + SLANG_ASSERT(length < 32); + m_prefixSizes |= uint32_t(1) << length; + } + + return SLANG_OK; +} + +SlangResult CommandOptions::_addValueName(const UnownedStringSlice& name, Index categoryIndex, Index optionIndex) +{ + return _addName(LookupKind(categoryIndex), name, optionIndex); +} + +SlangResult CommandOptions::_addUserValue(LookupKind kind, UserValue userValue, Index targetIndex) +{ + // If it's invalid we don't need to add it + if (userValue == kInvalidUserValue) + { + return SLANG_OK; + } + + UserValueKey userValueKey; + userValueKey.kind = kind; + userValueKey.userValue = userValue; + + if (m_userValueMap.tryGetValueOrAdd(userValueKey, targetIndex)) + { + SLANG_ASSERT(!"UserValue is already used for this kind!"); + return SLANG_FAIL; + } + return SLANG_OK; +} + +UnownedStringSlice CommandOptions::_addString(const char* text) +{ + if (text == nullptr) + { + return UnownedStringSlice(); + } + return _addString(UnownedStringSlice(text)); +} + +UnownedStringSlice CommandOptions::_addString(const UnownedStringSlice& slice) +{ + const auto length = slice.getLength(); + const char* dst = m_arena.allocateString(slice.begin(), length); + return UnownedStringSlice(dst, length); +} + +Index CommandOptions::_addOption(const UnownedStringSlice& name, const Option& inOption) +{ + return _addOption(&name, 1, inOption); +} + +Index CommandOptions::_addOption(const UnownedStringSlice* names, Count namesCount, const Option& inOption) +{ + SLANG_ASSERT(namesCount > 0); + SLANG_ASSERT(inOption.categoryIndex >= 0); + + if (namesCount <= 0 || inOption.categoryIndex < 0) + { + return -1; + } + + auto& cat = m_categories[inOption.categoryIndex]; + + // If there are already options associated with this category, we have to be in the run of the last ones added + if (cat.optionStartIndex != cat.optionEndIndex) + { + // If we aren't at the end then this is an error + if (cat.optionEndIndex != m_options.getCount()) + { + return -1; + } + } + else + { + // Move to the end of the option list + cat.optionStartIndex = m_options.getCount(); + cat.optionEndIndex = cat.optionStartIndex; + } + + Option option(inOption); + + const Index optionIndex = m_options.getCount(); + + if (cat.kind == CategoryKind::Option) + { + for (Index i = 0; i < namesCount; ++i) + { + if (SLANG_FAILED(_addOptionName(names[i], inOption.flags, optionIndex))) + { + return -1; + } + } + if (SLANG_FAILED(_addUserValue(LookupKind::Option, inOption.userValue, optionIndex))) + { + return -1; + } + } + else + { + for (Index i = 0; i < namesCount; ++i) + { + _addValueName(names[i], inOption.categoryIndex, optionIndex); + } + if (SLANG_FAILED(_addUserValue(LookupKind(inOption.categoryIndex), inOption.userValue, optionIndex))) + { + return -1; + } + } + + if (namesCount == 1) + { + // We already have storage on the slice + option.names = m_pool.addAndGetSlice(names[0]); + } + else + { + // Put all of the names in the list + StringBuilder buf; + StringUtil::join(names, namesCount, ',', buf); + // Allocate storage no in the pool + option.names = _addString(buf.getUnownedSlice()); + } + + m_options.add(option); + + // Set the end index + cat.optionEndIndex = optionIndex + 1; + + return optionIndex; +} + +static void _handlePostFix(UnownedStringSlice& ioSlice, CommandOptions::Flags& ioFlags) +{ + if (ioSlice.endsWith(toSlice("..."))) + { + if (ioSlice.endsWith(toSlice("?..."))) + { + ioFlags |= CommandOptions::Flag::CanPrefix; + ioSlice = ioSlice.head(ioSlice.getLength() - 4); + } + else + { + ioFlags |= CommandOptions::Flag::IsPrefix; + ioSlice = ioSlice.head(ioSlice.getLength() - 3); + } + } +} + +void CommandOptions::add(const char* inName, const char* usage, const char* description, UserValue userValue) +{ + UnownedStringSlice nameSlice(inName); + + Option option; + option.categoryIndex = m_currentCategoryIndex; + option.usage = _addString(usage); + option.description = _addString(UnownedStringSlice(description)); + option.userValue = userValue; + option.flags = 0; + + if (nameSlice.indexOf(',') >= 0) + { + List<UnownedStringSlice> names; + StringUtil::split(nameSlice, ',', names); + + for (auto& name : names) + { + _handlePostFix(name, option.flags); + } + + _addOption(names.getBuffer(), names.getCount(), option); + } + else + { + _handlePostFix(nameSlice, option.flags); + + _addOption(&nameSlice, 1, option); + } +} + +void CommandOptions::add(const UnownedStringSlice* names, Count namesCount, const char* usage, const char* description, UserValue userValue, Flags flags) +{ + Option option; + option.categoryIndex = m_currentCategoryIndex; + option.usage = _addString(usage); + option.description = _addString(UnownedStringSlice(description)); + option.flags = flags; + option.userValue = userValue; + + _addOption(names, namesCount, option); +} + +Index CommandOptions::_addValue(const UnownedStringSlice& name, const Option& inOption) +{ + SLANG_ASSERT(m_currentCategoryIndex >= 0); + SLANG_ASSERT(m_categories[m_currentCategoryIndex].kind == CategoryKind::Value); + + return _addOption(name, inOption); +} + +void CommandOptions::addValues(const ValuePair* pairs, Count pairsCount) +{ + for (auto& pair : makeConstArrayView(pairs, pairsCount)) + { + addValue(pair.name, pair.description); + } +} + +void CommandOptions::addValues(const ConstArrayView<NameValue>& values) +{ + for (const auto& value : values) + { + addValue(value.name, UserValue(value.value)); + } +} + +void CommandOptions::addValues(const ConstArrayView<NamesValue>& values) +{ + for (const auto& value : values) + { + addValue(value.names, UserValue(value.value)); + } +} + +void CommandOptions::addValues(const ConstArrayView<NamesDescriptionValue>& values) +{ + for (const auto& value : values) + { + addValue(value.names, value.description, UserValue(value.value)); + } +} + +void CommandOptions::addValuesWithAliases(const ConstArrayView<NameValue>& inValues) +{ + List<NameValue> values; + values.addRange(inValues.getBuffer(), inValues.getCount()); + + values.sort([](const NameValue& a, const NameValue& b) -> bool { return a.value < b.value; }); + + List<UnownedStringSlice> names; + + const Count count = values.getCount(); + Index i = 0; + while (i < count) + { + names.clear(); + + const auto value = values[i].value; + names.add(UnownedStringSlice(values[i++].name)); + + for (; i < count && values[i].value == value; ++i) + { + names.add(UnownedStringSlice(values[i].name)); + } + + addValue(names.getBuffer(), names.getCount(), UserValue(value)); + } +} + +void CommandOptions::addValue(const UnownedStringSlice& name, UserValue userValue) +{ + Option option; + option.categoryIndex = m_currentCategoryIndex; + option.userValue = userValue; + _addValue(name, option); +} + +void CommandOptions::addValue(const UnownedStringSlice& name, const UnownedStringSlice& description, UserValue userValue) +{ + Option option; + option.categoryIndex = m_currentCategoryIndex; + option.description = _addString(description); + option.userValue = userValue; + _addValue(name, option); +} + +void CommandOptions::addValue(const UnownedStringSlice* names, Count namesCount, UserValue userValue) +{ + Option option; + option.categoryIndex = m_currentCategoryIndex; + option.userValue = userValue; + + SLANG_ASSERT(m_currentCategoryIndex >= 0); + SLANG_ASSERT(m_categories[m_currentCategoryIndex].kind == CategoryKind::Value); + + _addOption(names, namesCount, option); +} + +void CommandOptions::addValue(const char* inName, const char* description, UserValue userValue) +{ + const UnownedStringSlice name(inName); + + if (description) + { + addValue(name, UnownedStringSlice(description), userValue); + } + else + { + addValue(name, userValue); + } +} + +void CommandOptions::addValue(const char* name, UserValue userValue) +{ + addValue(UnownedStringSlice(name), userValue); +} + +Index CommandOptions::addCategory(CategoryKind kind, const char* name, const char* description, UserValue userValue) +{ + const UnownedStringSlice nameSlice(name); + + const auto categoryIndex = m_categories.getCount(); + + if (SLANG_FAILED(_addName(LookupKind::Category, nameSlice, categoryIndex))) + { + return -1; + } + + if (userValue != kInvalidUserValue) + { + _addUserValue(LookupKind::Category, userValue, categoryIndex); + } + + Category cat; + cat.kind = kind; + cat.name = _addString(nameSlice); + cat.description = _addString(description); + cat.userValue = userValue; + + m_currentCategoryIndex = categoryIndex; + + m_categories.add(cat); + + return categoryIndex; +} + +void CommandOptions::setCategory(const char* name) +{ + const UnownedStringSlice nameSlice(name); + + for (Index i = 0; i < m_categories.getCount(); ++i) + { + auto& cat = m_categories[i]; + if (cat.name == nameSlice) + { + m_currentCategoryIndex = i; + return; + } + } + + SLANG_ASSERT(!"Category not found"); + + m_currentCategoryIndex = -1; +} + +Index CommandOptions::findTargetIndexByName(LookupKind kind, const UnownedStringSlice& name) const +{ + const auto nameIndex = m_pool.findIndex(name); + // If the name isn't in the pool then there isn't a category with this name + if (nameIndex < 0) + { + return -1; + } + + NameKey key; + key.kind = kind; + key.nameIndex = nameIndex; + + if (auto ptr = m_nameMap.tryGetValue(key)) + { + return *ptr; + } + + return -1; +} + +Index CommandOptions::findTargetIndexByUserValue(LookupKind kind, UserValue userValue) const +{ + UserValueKey key; + key.kind = kind; + key.userValue = userValue; + + if (auto ptr = m_userValueMap.tryGetValue(key)) + { + return *ptr; + } + + return -1; +} + +Index CommandOptions::findCategoryByCaseInsensitiveName(const UnownedStringSlice& slice) const +{ + const Count count = m_categories.getCount(); + for (Index i = 0; i < count; ++i) + { + const auto& cat = m_categories[i]; + + if (cat.name.caseInsensitiveEquals(slice)) + { + return i; + } + } + return -1; +} + +Index CommandOptions::findOptionByCategoryUserValue(UserValue categoryUserValue, const UnownedStringSlice& name) const +{ + Index categoryIndex = findTargetIndexByUserValue(LookupKind::Category, categoryUserValue); + if (categoryIndex < 0) + { + return -1; + } + + return findValueByName(categoryIndex, name); +} + +Index CommandOptions::findOptionByName(const UnownedStringSlice& name) const +{ + { + auto index = findTargetIndexByName(LookupKind::Option, name); + if (index >= 0) + { + return index; + } + } + + // We need to search for partials + auto prefixSizes = m_prefixSizes; + + while (prefixSizes) + { + auto prefixSize = ByteEncodeUtil::calcMsb32(prefixSizes); + + if (prefixSize < name.getLength()) + { + // Look it up + const auto index = findTargetIndexByName(LookupKind::Option, name.head(prefixSize)); + if (index >= 0) + { + auto& option = m_options[index]; + + // If the option accepts prefixes, we return the index + if (option.flags & (Flag::CanPrefix | Flag::IsPrefix)) + { + return index; + } + } + } + + // Remove the bit + prefixSizes &= ~(uint32_t(1) << prefixSize); + } + + // Was not found + return -1; +} + +ConstArrayView<CommandOptions::Option> CommandOptions::getOptionsForCategory(Index categoryIndex) const +{ + const auto& cat = m_categories[categoryIndex]; + return makeConstArrayView(m_options.getBuffer() + cat.optionStartIndex, cat.optionEndIndex - cat.optionStartIndex); +} + +void CommandOptions::getCategoryOptionNames(Index categoryIndex, List<UnownedStringSlice>& outNames) const +{ + outNames.clear(); + for (const auto& option : getOptionsForCategory(categoryIndex)) + { + StringUtil::appendSplit(option.names, ',', outNames); + } +} + +void CommandOptions::findCategoryIndicesFromUsage(const UnownedStringSlice& slice, List<Index>& outCategories) const +{ + const auto* cur = slice.begin(); + const auto* end = slice.end(); + + while (cur < end) + { + // Find < + while (cur < end && *cur != '<') cur++; + + // If we found it look for the end + if (cur < end && *cur == '<') + { + ++cur; + auto start = cur; + while (cur < end && (CharUtil::isAlphaOrDigit(*cur) || *cur == '-' || *cur == '_') && *cur != '>') + { + cur++; + } + + // If we hit closing > we want to lookup + if (cur < end && *cur == '>') + { + const UnownedStringSlice categoryName(start, cur); + + Index categoryIndex = findCategoryByName(categoryName); + if (categoryIndex >= 0 && outCategories.indexOf(categoryIndex) < 0) + { + outCategories.add(categoryIndex); + } + } + + cur++; + } + } +} + +Count CommandOptions::getOptionCountInRange(Index categoryIndex, UserValue start, UserValue nonInclEnd) const +{ + const UserIndex startIndex = UserIndex(start); + const UserIndex endIndex = UserIndex(nonInclEnd); + + Count count = 0; + + for (auto& opt : getOptionsForCategory(categoryIndex)) + { + const auto val = opt.userValue; + if (val == kInvalidUserValue) + { + continue; + } + + const auto valIndex = UserIndex(val); + count += Index(valIndex >= startIndex && valIndex < endIndex); + } + + return count; +} + +Count CommandOptions::getOptionCountInRange(LookupKind kind, UserValue start, UserValue nonInclEnd) const +{ + Index count = 0; + + if (kind == LookupKind::Category) + { + const UserIndex startIndex = UserIndex(start); + const UserIndex endIndex = UserIndex(nonInclEnd); + + for (auto& cat : m_categories) + { + if (cat.userValue != kInvalidUserValue) + { + const auto valIndex = UserIndex(cat.userValue); + count += Index(valIndex >= startIndex && valIndex < endIndex); + } + } + } + if (kind == LookupKind::Option) + { + // If we are lookup up options, then we iterate over all option categories + const auto catCount = m_categories.getCount(); + for (Index categoryIndex = 0; categoryIndex < catCount; ++categoryIndex) + { + if (m_categories[categoryIndex].kind == CategoryKind::Option) + { + count += getOptionCountInRange(categoryIndex, start, nonInclEnd); + } + } + } + else if (Index(kind) >= 0) + { + // It's a regular category + count = getOptionCountInRange(Index(kind), start, nonInclEnd); + } + + return count; +} + + +bool CommandOptions::hasContiguousUserValueRange(LookupKind kind, UserValue start, UserValue nonInclEnd) const +{ + const Count rangeCount = Count(nonInclEnd) - Count(start); + SLANG_ASSERT(rangeCount >= 0); + + if (rangeCount <= 0) + { + return true; + } + + const Count count = getOptionCountInRange(kind, start, nonInclEnd); + return rangeCount == count; +} + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!! CommandOptionsWriter !!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +void CommandOptionsWriter::appendDescriptionForCategory(const CommandOptions& options, Index categoryIndex) +{ + const auto& categories = options.getCategories(); + + const auto& category = categories[categoryIndex]; + + // Header + { + const auto count = m_builder.getLength(); + if (category.kind == CategoryKind::Value) + { + m_builder << "<" << category.name << ">"; + } + else + { + m_builder << category.name; + } + + const auto length = m_builder.getLength() - count; + m_builder << "\n"; + + m_builder.appendRepeatedChar('=', length); + + m_builder << "\n\n"; + + // If there is a description output it + if (category.description.getLength() > 0) + { + _appendText(0, category.description); + m_builder << "\n"; + } + } + + for (auto& option : options.getOptionsForCategory(categoryIndex)) + { + m_builder << m_indentSlice; + + if (option.usage.getLength()) + { + m_builder << option.usage; + } + else + { + List<UnownedStringSlice> names; + StringUtil::split(option.names, ',', names); + + _appendWithWrap(1, names, toSlice(", ")); + } + + if (option.description.getLength() == 0) + { + m_builder << "\n"; + continue; + } + + m_builder << ": "; + + _appendText(2, option.description); + + if (option.usage.getLength()) + { + List<Index> usageCategoryIndices; + options.findCategoryIndicesFromUsage(option.usage, usageCategoryIndices); + + for (auto usageCategoryIndex : usageCategoryIndices) + { + auto& usageCat = categories[usageCategoryIndex]; + + m_builder << m_indentSlice << m_indentSlice; + m_builder << "<" << usageCat.name << "> can be: "; + + List<UnownedStringSlice> optionNames; + options.getCategoryOptionNames(usageCategoryIndex, optionNames); + + _appendWithWrap(2, optionNames, toSlice(", ")); + + m_builder << "\n"; + } + } + } + + m_builder << "\n"; +} + +void CommandOptionsWriter::appendDescription(const CommandOptions& options) +{ + // Go through categories in order + + const auto& categories = options.getCategories(); + + for (Index categoryIndex = 0; categoryIndex < categories.getCount(); ++categoryIndex) + { + appendDescriptionForCategory(options, categoryIndex); + } +} + +void CommandOptionsWriter::_appendText(Count indentCount, const UnownedStringSlice& text) +{ + List<UnownedStringSlice> lines; + StringUtil::calcLines(text, lines); + + // Remove very last line if it's empty + if (lines.getCount() > 1 && lines.getLast().trim().getLength() == 0) + { + lines.removeLast(); + } + + _appendWithWrap(indentCount, lines); +} + +Count CommandOptionsWriter::_getCurrentLineLength() +{ + // Work out the current line length + const char* start = m_builder.begin(); + const char* cur = m_builder.end(); + + Count lineLength = 0; + + if (cur > start) + { + for (--cur; cur > start; --cur) + { + const auto c = *cur; + if (c == '\n' || c == '\r') + { + ++cur; + break; + } + } + + lineLength = Count(ptrdiff_t(m_builder.end() - cur)); + } + + return lineLength; +} + +void CommandOptionsWriter::_requireIndent(Count indentCount) +{ + const auto length = m_builder.getLength(); + if (length) + { + const auto c = m_builder[length - 1]; + if (c == '\n' || c == '\r') + { + for (Index j = 0; j < indentCount; j++) + { + m_builder.append(m_indentSlice); + } + } + } +} + +void CommandOptionsWriter::_appendWithWrap(Count indentCount, List<UnownedStringSlice>& lines) +{ + List<UnownedStringSlice> words; + + for (auto line : lines) + { + if (line.trim().getLength() == 0 || line.startsWith(toSlice(" "))) + { + // Append the line as is after the indent + _requireIndent(indentCount); + m_builder << line << "\n"; + } + else + { + words.clear(); + StringUtil::split(line, ' ', words); + + _requireIndent(indentCount); + + _appendWithWrap(indentCount, words, toSlice(" ")); + m_builder << "\n"; + } + } +} + + + +void CommandOptionsWriter::_appendWithWrap(Count indentCount, List<UnownedStringSlice>& slices, const UnownedStringSlice& delimit) +{ + Count lineLength = _getCurrentLineLength(); + + const auto count = slices.getCount(); + + for (Index i = 0; i < count; ++i) + { + auto slice = slices[i]; + + auto sliceLength = slice.getLength(); + + if (i < count - 1) + { + sliceLength += delimit.getLength(); + } + + // If out of space onto the next line + if (lineLength + sliceLength > m_lineLength) + { + m_builder.append("\n"); + + lineLength = indentCount * m_indentSlice.getLength(); + + for (Index j = 0; j < indentCount; j++) + { + m_builder.append(m_indentSlice); + } + } + + m_builder.append(slice); + if (i < count - 1) + { + m_builder.append(delimit); + } + + lineLength += sliceLength; + } +} + +} // namespace Slang + + diff --git a/source/core/slang-command-options.h b/source/core/slang-command-options.h new file mode 100644 index 000000000..a92d4d8f8 --- /dev/null +++ b/source/core/slang-command-options.h @@ -0,0 +1,253 @@ +#ifndef SLANG_CORE_COMMAND_OPTIONS_H +#define SLANG_CORE_COMMAND_OPTIONS_H + +#include "slang-basic.h" +#include "slang-string-slice-pool.h" +#include "slang-name-value.h" + +namespace Slang +{ + +/* For convenience we encode within "names" flags. +"-D..." means that -D *must* be followed by the value +"-D?..." means that -D *can* be a prefix, or it might be followed with the arg +*/ + +struct CommandOptions +{ + typedef uint32_t Flags; + + typedef int32_t UserIndex; + enum class UserValue : UserIndex; + static const UserValue kInvalidUserValue = UserValue(0x80000000); + + enum class LookupKind : int32_t + { + Category = -2, ///< Lookup a category name + Option = -1, ///< Lookup an option name (all options use the same lookup index even if in different categories) + Base = 0, ///< Lookup via category index + }; + + enum class CategoryKind + { + Option, ///< Command line option (like "-D") + Value, ///< One of a set of values (such as an enum or some other kind of list of values) + }; + + struct ValuePair + { + const char* name; + const char* description; + }; + + struct Category + { + UserValue userValue = kInvalidUserValue; + + CategoryKind kind; + UnownedStringSlice name; + UnownedStringSlice description; + + // Holds the span that defines all of the options associated with the category + Index optionStartIndex = 0; + Index optionEndIndex = 0; + }; + + struct Flag + { + enum Enum : Flags + { + CanPrefix = 0x1, /// Allows -Dfsggf or -D fdsfsd + IsPrefix = 0x2, /// Is an option that can only be a prefix + }; + }; + + struct Option + { + UnownedStringSlice names; ///< Comma delimited list of names, first name is the default + UnownedStringSlice usage; ///< Describes usage, can be empty + UnownedStringSlice description; ///< A description of usage + + UserValue userValue = kInvalidUserValue; + + Index categoryIndex = -1; ///< Category this option belongs to + Flags flags = 0; ///< Flags about this option + }; + + /// Add a category + Index addCategory(CategoryKind kind, const char* name, const char* description, UserValue userValue = kInvalidUserValue); + /// Use an already known category. It's an error if the category isn't found + void setCategory(const char* name); + + void add(const char* name, const char* usage, const char* description, UserValue userValue = kInvalidUserValue); + void add(const UnownedStringSlice* names, Count namesCount, const char* usage, const char* description, UserValue userValue = kInvalidUserValue, Flags flags = 0); + + void addValue(const UnownedStringSlice& name, UserValue userValue = kInvalidUserValue); + void addValue(const UnownedStringSlice& name, const UnownedStringSlice& description, UserValue userValue = kInvalidUserValue); + void addValue(const char* name, const char* description, UserValue userValue = kInvalidUserValue); + void addValue(const char* name, UserValue userValue = kInvalidUserValue); + void addValue(const UnownedStringSlice* names, Count namesCount, UserValue userValue = kInvalidUserValue); + + /// Add values (without UserValue association) + void addValues(const ValuePair* pairs, Count pairsCount); + + /// Add values + void addValues(const ConstArrayView<NameValue>& values); + void addValues(const ConstArrayView<NamesValue>& values); + void addValues(const ConstArrayView<NamesDescriptionValue>& values); + + /// Sometimes values are listed with *names* per value. This method will take into account the aliases + void addValuesWithAliases(const ConstArrayView<NameValue>& values); + + /// Get the target index based off the name and the kind + Index findTargetIndexByName(LookupKind kind, const UnownedStringSlice& name) const; + /// Given a kind and a user value lookup the target index + Index findTargetIndexByUserValue(LookupKind kind, UserValue userValue) const; + + /// Finds the category by name or -1 if not found + Index findCategoryByName(const UnownedStringSlice& name) const { return findTargetIndexByName(LookupKind::Category, name); } + /// Finds the option index by name or -1 if not found + Index findOptionByName(const UnownedStringSlice& name) const; + /// Find the option index of a value, using it's category index and the name + Index findValueByName(Index categoryIndex, const UnownedStringSlice& name) const { return findTargetIndexByName(LookupKind(categoryIndex), name); } + + /// Get the category index from a user value + Index findCategoryByUserValue(UserValue userValue) const { return findTargetIndexByUserValue(LookupKind::Category, userValue); } + /// Can only get options + Index findOptionByUserValue(UserValue userValue) const { return findTargetIndexByUserValue(LookupKind::Option, userValue); } + /// Get a value associated with a category + Index findValueByUserValue(Index categoryIndex, UserValue userValue) const { return findTargetIndexByUserValue(LookupKind(categoryIndex), userValue); } + + /// Given a category user value, find the associated name + /// Returns -1 if not found + Index findOptionByCategoryUserValue(UserValue categoryUserValue, const UnownedStringSlice& name) const; + + /// Find a category by case insensitive name. Returns -1 if not found + Index findCategoryByCaseInsensitiveName(const UnownedStringSlice& slice) const; + + /// Given a category index returns all the options associated. + ConstArrayView<Option> getOptionsForCategory(Index categoryIndex) const; + + /// Get the categories + const List<Category>& getCategories() const { return m_categories; } + + /// Get all the options + const List<Option>& getOptions() const { return m_options; } + + /// Get the option at the specified index + const Option& getOptionAt(Index index) const { return m_options[index]; } + + /// Find all of the categories in the usage slice + void findCategoryIndicesFromUsage(const UnownedStringSlice& usageSlice, List<Index>& outCategories) const; + /// Get all the option names associated with a category index + void getCategoryOptionNames(Index categoryIndex, List<UnownedStringSlice>& outNames) const; + + /// Set up a lookup kind from a category index + static LookupKind makeLookupKind(Index categoryIndex) { return LookupKind(categoryIndex); } + + /// Returns true, if all values from [start, end) are found for the kind + bool hasContiguousUserValueRange(LookupKind kind, UserValue start, UserValue nonInclEnd) const; + + /// Returns the number of options in the range + Count getOptionCountInRange(Index categoryIndex, UserValue start, UserValue nonInclEnd) const; + Count getOptionCountInRange(LookupKind kind, UserValue start, UserValue nonInclEnd) const; + + + /// Ctor + CommandOptions() : + m_pool(StringSlicePool::Style::Default), + m_arena(1024 * 2) + { + } + + /// Returns name in the m_optionPool or -1 on error + SlangResult _addOptionName(const UnownedStringSlice& name, Flags flags, Index targetIndex); + SlangResult _addValueName(const UnownedStringSlice& name, Index categoryIndex, Index targetIndex); + SlangResult _addName(LookupKind kind, const UnownedStringSlice& name, Index targetIndex); + + SlangResult _addUserValue(LookupKind kind, UserValue userValue, Index targetIndex); + + Index _addOption(const UnownedStringSlice& name, const Option& inOption); + Index _addOption(const UnownedStringSlice* names, Count namesCount, const Option& option); + + Index _addValue(const UnownedStringSlice& name, const Option& inOption); + + UnownedStringSlice _addString(const char* text); + UnownedStringSlice _addString(const UnownedStringSlice& slice); + + /// A key type that uses the combination of the lookup kind and a name index. + /// Maps to a target index that could be a category or an option index. + struct NameKey + { + typedef NameKey ThisType; + + SLANG_FORCE_INLINE bool operator==(const ThisType& rhs) const { return kind == rhs.kind && nameIndex == rhs.nameIndex; } + SLANG_FORCE_INLINE bool operator!=(const ThisType& rhs) const { return !(*this == rhs); } + HashCode getHashCode() const { return combineHash(Slang::getHashCode(kind), Slang::getHashCode(nameIndex)); } + + LookupKind kind; ///< The kind of lookup + Index nameIndex; ///< The name index in the pool + }; + + struct UserValueKey + { + typedef UserValueKey ThisType; + + SLANG_FORCE_INLINE bool operator==(const ThisType& rhs) const { return kind == rhs.kind && userValue == rhs.userValue; } + SLANG_FORCE_INLINE bool operator!=(const ThisType& rhs) const { return !(*this == rhs); } + HashCode getHashCode() const { return combineHash(Slang::getHashCode(kind), Slang::getHashCode(userValue)); } + + LookupKind kind; ///< The kind of lookup + UserValue userValue; ///< The user value + }; + + Index m_currentCategoryIndex = -1; + + List<Category> m_categories; + + // Holds a bit for all valid prefix sizes. Max prefix size is therefore 32 chars + uint32_t m_prefixSizes = 0; + + List<Option> m_options; ///< All of the entries describing each of the options + StringSlicePool m_pool; ///< Only holds options, and handle therefore matches up to m_entries + Dictionary<NameKey, Index> m_nameMap; ///< Maps a name to an option index + Dictionary<UserValueKey, Index> m_userValueMap; ///< Maps a user value (for a kind) to an index + + MemoryArena m_arena; ///< For other misc storage +}; + +struct CommandOptionsWriter +{ + typedef CommandOptions::CategoryKind CategoryKind; + + /// Append descirption for a category + void appendDescriptionForCategory(const CommandOptions& options, Index categoryIndex); + /// Appends a description of all of the options + void appendDescription(const CommandOptions& options); + + /// Get the builder that string is being written to + StringBuilder& getBuilder() { return m_builder; } + + /// Ctor + CommandOptionsWriter(): + m_indentSlice(toSlice(" ")) + { + } + + Count _getCurrentLineLength(); + + void _appendWithWrap(Count indentCount, List<UnownedStringSlice>& slices, const UnownedStringSlice& delimit); + void _appendWithWrap(Count indentCount, List<UnownedStringSlice>& lines); + void _requireIndent(Count indentCount); + void _appendText(Count indentCount, const UnownedStringSlice& text); + + + UnownedStringSlice m_indentSlice; + Count m_lineLength = 80; + + StringBuilder m_builder; +}; + +} // namespace Slang + +#endif diff --git a/source/core/slang-name-value.cpp b/source/core/slang-name-value.cpp new file mode 100644 index 000000000..5418043f5 --- /dev/null +++ b/source/core/slang-name-value.cpp @@ -0,0 +1,163 @@ +// slang-name-value.cpp + +#include "slang-name-value.h" + +#include "slang-string-util.h" +#include "slang-char-util.h" + +namespace Slang { + +/* static */ValueInt NameValueUtil::findValue(const ConstArrayView<NameValue>& opts, const UnownedStringSlice& slice, ValueInt defaultValue) +{ + for (const auto& opt : opts) + { + if (UnownedStringSlice(opt.name) == slice) + { + return opt.value; + } + } + return defaultValue; +} + +/* static */ValueInt NameValueUtil::findValue(const ConstArrayView<NamesValue>& opts, const UnownedStringSlice& slice, ValueInt defaultValue) +{ + for (const auto& opt : opts) + { + UnownedStringSlice names(opt.names); + + if (StringUtil::indexOfInSplit(names, ',', slice) >= 0) + { + return opt.value; + } + } + return defaultValue; +} + +/* static */ValueInt NameValueUtil::findValue(const ConstArrayView<NamesDescriptionValue>& opts, const UnownedStringSlice& slice, ValueInt defaultValue) +{ + for (const auto& opt : opts) + { + UnownedStringSlice names(opt.names); + if (StringUtil::indexOfInSplit(names, ',', slice) >= 0) + { + return opt.value; + } + } + return defaultValue; +} + +/* static */ UnownedStringSlice NameValueUtil::findName(const ConstArrayView<NameValue>& opts, ValueInt value, const UnownedStringSlice& defaultName) +{ + for (const auto& opt : opts) + { + if (opt.value == value) + { + return UnownedStringSlice(opt.name); + } + } + return defaultName; +} + +/* static */ UnownedStringSlice NameValueUtil::findName(const ConstArrayView<NamesValue>& opts, ValueInt value, const UnownedStringSlice& defaultName) +{ + for (const auto& opt : opts) + { + if (opt.value == value) + { + // Get the first name + return StringUtil::getAtInSplit(UnownedStringSlice(opt.names), ',', 0); + } + } + + return defaultName; +} + +/* static */ UnownedStringSlice NameValueUtil::findName(const ConstArrayView<NamesDescriptionValue>& opts, ValueInt value, const UnownedStringSlice& defaultName) +{ + for (const auto& opt : opts) + { + if (opt.value == value) + { + // Get the first name + return StringUtil::getAtInSplit(UnownedStringSlice(opt.names), ',', 0); + } + } + + return defaultName; +} + + +/* static */UnownedStringSlice NameValueUtil::findDescription(const ConstArrayView<NamesDescriptionValue>& opts, ValueInt value, const UnownedStringSlice& defaultDescription) +{ + for (const auto& opt : opts) + { + if (opt.value == value && opt.description) + { + return UnownedStringSlice(opt.description); + } + } + + return defaultDescription; +} + +/* static */ void NameValueUtil::appendNames(NameKind kind, const ConstArrayView<NameValue>& opts, List<UnownedStringSlice>& out) +{ + SLANG_UNUSED(kind); + for (auto& opt : opts) + { + out.add(UnownedStringSlice(opt.name)); + } +} + +static void _appendNames(NameValueUtil::NameKind kind, const char* names, List<UnownedStringSlice>& out) +{ + if (kind == NameValueUtil::NameKind::All) + { + StringUtil::appendSplit(UnownedStringSlice(names), ',', out); + } + else + { + out.add(StringUtil::getAtInSplit(UnownedStringSlice(names), ',', 0)); + } +} + +/* static */ void NameValueUtil::appendNames(NameKind kind, const ConstArrayView<NamesValue>& opts, List<UnownedStringSlice>& out) +{ + for (auto& opt : opts) + { + _appendNames(kind, opt.names, out); + } +} + +/* static */ void NameValueUtil::appendNames(NameKind kind, const ConstArrayView<NamesDescriptionValue>& opts, List<UnownedStringSlice>& out) +{ + for (auto& opt : opts) + { + _appendNames(kind, opt.names, out); + } +} + +/* static */ List<UnownedStringSlice> NameValueUtil::getNames(NameKind kind, const ConstArrayView<NameValue>& opts) +{ + List<UnownedStringSlice> names; + appendNames(kind, opts, names); + return names; +} + +/* static */ List<UnownedStringSlice> NameValueUtil::getNames(NameKind kind, const ConstArrayView<NamesValue>& opts) +{ + List<UnownedStringSlice> names; + appendNames(kind, opts, names); + return names; +} + +/* static */ List<UnownedStringSlice> NameValueUtil::getNames(NameKind kind, const ConstArrayView<NamesDescriptionValue>& opts) +{ + List<UnownedStringSlice> names; + appendNames(kind, opts, names); + return names; +} + +} // namespace Slang + + diff --git a/source/core/slang-name-value.h b/source/core/slang-name-value.h new file mode 100644 index 000000000..a50e149a8 --- /dev/null +++ b/source/core/slang-name-value.h @@ -0,0 +1,76 @@ +#ifndef SLANG_CORE_NAME_VALUE_H +#define SLANG_CORE_NAME_VALUE_H + +#include "slang-basic.h" + +#include "slang-array-view.h" + +namespace Slang +{ + +// When associating values with names we use this type +typedef int32_t ValueInt; + +struct NameValue +{ + ValueInt value; + const char* name; +}; + +struct NamesValue +{ + ValueInt value; + const char* names; ///< Can hold comma delimited list of names + +}; + +struct NamesDescriptionValue +{ + ValueInt value; + const char* names; ///< Can hold comma delimited list of names + const char* description; ///< Optional description, can hold null ptr or empty string if not defined +}; + +struct NameValueUtil +{ + enum class NameKind + { + First, ///< Only the first name if there is more than one + All, ///< All valid associated names + }; + + /// Use the default type to infer the actual type desired + template <typename T> + static T findValue(const ConstArrayView<NameValue>& opts, const UnownedStringSlice& slice, T defaultValue) { return (T)findValue(opts, slice, ValueInt(defaultValue)); } + template <typename T> + static T findValue(const ConstArrayView<NamesValue>& opts, const UnownedStringSlice& slice, T defaultValue) { return (T)findValue(opts, slice, ValueInt(defaultValue)); } + template <typename T> + static T findValue(const ConstArrayView<NamesDescriptionValue>& opts, const UnownedStringSlice& slice, T defaultValue) { return (T)findValue(opts, slice, ValueInt(defaultValue)); } + + /// Given a slice finds the associated value. If no entry is found, defaultValue is returned + static ValueInt findValue(const ConstArrayView<NameValue>& opts, const UnownedStringSlice& slice, ValueInt defaultValue = -1); + static ValueInt findValue(const ConstArrayView<NamesValue>& opts, const UnownedStringSlice& slice, ValueInt defaultValue = -1); + static ValueInt findValue(const ConstArrayView<NamesDescriptionValue>& opts, const UnownedStringSlice& slice, ValueInt defaultValue = -1); + + /// Given a value find the name. If there are multiple names, returns the first name + static UnownedStringSlice findName(const ConstArrayView<NameValue>& opts, ValueInt value, const UnownedStringSlice& defaultName = UnownedStringSlice()); + static UnownedStringSlice findName(const ConstArrayView<NamesValue>& opts, ValueInt value, const UnownedStringSlice& defaultName = UnownedStringSlice()); + static UnownedStringSlice findName(const ConstArrayView<NamesDescriptionValue>& opts, ValueInt value, const UnownedStringSlice& defaultName = UnownedStringSlice()); + + /// Append all the names from opts to out + static void appendNames(NameKind kind, const ConstArrayView<NameValue>& opts, List<UnownedStringSlice>& out); + static void appendNames(NameKind kind, const ConstArrayView<NamesValue>& opts, List<UnownedStringSlice>& out); + static void appendNames(NameKind kind, const ConstArrayView<NamesDescriptionValue>& opts, List<UnownedStringSlice>& out); + + /// Return the list of all valid names + static List<UnownedStringSlice> getNames(NameKind kind, const ConstArrayView<NameValue>& opts); + static List<UnownedStringSlice> getNames(NameKind kind, const ConstArrayView<NamesValue>& opts); + static List<UnownedStringSlice> getNames(NameKind kind, const ConstArrayView<NamesDescriptionValue>& opts); + + /// Get the description + static UnownedStringSlice findDescription(const ConstArrayView<NamesDescriptionValue>& opts, ValueInt value, const UnownedStringSlice& defaultDescription = UnownedStringSlice()); +}; + +} // namespace Slang + +#endif diff --git a/source/core/slang-string-util.cpp b/source/core/slang-string-util.cpp index 29e0dad5d..7de6846d5 100644 --- a/source/core/slang-string-util.cpp +++ b/source/core/slang-string-util.cpp @@ -35,10 +35,8 @@ namespace Slang { return areAllEqual(slicesA, slicesB, equalFn); } -/* static */void StringUtil::split(const UnownedStringSlice& in, char splitChar, List<UnownedStringSlice>& outSlices) +/* static */void StringUtil::appendSplit(const UnownedStringSlice& in, char splitChar, List<UnownedStringSlice>& outSlices) { - outSlices.clear(); - const char* start = in.begin(); const char* end = in.end(); @@ -59,17 +57,15 @@ namespace Slang { } } -/* static */void StringUtil::split(const UnownedStringSlice& in, const UnownedStringSlice& splitSlice, List<UnownedStringSlice>& outSlices) +/* static */void StringUtil::appendSplit(const UnownedStringSlice& in, const UnownedStringSlice& splitSlice, List<UnownedStringSlice>& outSlices) { const Index splitLen = splitSlice.getLength(); if (splitLen == 1) { - return split(in, splitSlice[0], outSlices); + return appendSplit(in, splitSlice[0], outSlices); } - outSlices.clear(); - SLANG_ASSERT(splitLen > 0); if (splitLen <= 0) { @@ -96,7 +92,7 @@ namespace Slang { cur++; } - + // Add to output outSlices.add(UnownedStringSlice(start, cur)); @@ -105,6 +101,18 @@ namespace Slang { } } +/* static */void StringUtil::split(const UnownedStringSlice& in, char splitChar, List<UnownedStringSlice>& outSlices) +{ + outSlices.clear(); + appendSplit(in, splitChar, outSlices); +} + +/* static */void StringUtil::split(const UnownedStringSlice& in, const UnownedStringSlice& splitSlice, List<UnownedStringSlice>& outSlices) +{ + outSlices.clear(); + appendSplit(in, splitSlice, outSlices); +} + /* static */Index StringUtil::split(const UnownedStringSlice& in, char splitChar, Index maxSlices, UnownedStringSlice* outSlices) { Index index = 0; diff --git a/source/core/slang-string-util.h b/source/core/slang-string-util.h index b7576da1d..2293acf8d 100644 --- a/source/core/slang-string-util.h +++ b/source/core/slang-string-util.h @@ -33,6 +33,13 @@ struct StringUtil /// Splits into outSlices up to maxSlices. Returns SLANG_OK if of 'in' consumed. static SlangResult split(const UnownedStringSlice& in, char splitChar, Index maxSlices, UnownedStringSlice* outSlices, Index& outSlicesCount); + /// Split in, by specified splitChar append into slices out + /// Slices contents will directly address into in, so contents will only stay valid as long as in does. + static void appendSplit(const UnownedStringSlice& in, char splitChar, List<UnownedStringSlice>& slicesOut); + /// Split in by the specified splitSlice + /// Slices contents will directly address into in, so contents will only stay valid as long as in does. + static void appendSplit(const UnownedStringSlice& in, const UnownedStringSlice& splitSlice, List<UnownedStringSlice>& slicesOut); + /// Append the joining of in items, separated by 'separator' onto out static void join(const List<String>& in, char separator, StringBuilder& out); static void join(const List<String>& in, const UnownedStringSlice& separator, StringBuilder& out); diff --git a/source/core/slang-type-text-util.cpp b/source/core/slang-type-text-util.cpp index 0d396195f..172426bba 100644 --- a/source/core/slang-type-text-util.cpp +++ b/source/core/slang-type-text-util.cpp @@ -7,6 +7,11 @@ namespace Slang { + + + +namespace { // anonymous + #define SLANG_SCALAR_TYPES(x) \ x(None, none) \ x(Void, void) \ @@ -19,28 +24,6 @@ namespace Slang x(Float32, float) \ x(Float64, double) -#define SLANG_PASS_THROUGH_TYPES(x) \ - x(none, NONE) \ - x(fxc, FXC) \ - x(dxc, DXC) \ - x(glslang, GLSLANG) \ - x(visualstudio, VISUAL_STUDIO) \ - x(clang, CLANG) \ - x(gcc, GCC) \ - x(genericcpp, GENERIC_C_CPP) \ - x(nvrtc, NVRTC) \ - x(llvm, LLVM) - -#define SLANG_DEBUG_INFO_FORMATS(x) \ - x(default-format, DEFAULT) \ - x(c7, C7) \ - x(pdb, PDB) \ - x(stabs, STABS) \ - x(coff, COFF) \ - x(dwarf, DWARF) - -namespace { // anonymous - struct ScalarTypeInfo { slang::TypeReflection::ScalarType type; @@ -54,105 +37,160 @@ static const ScalarTypeInfo s_scalarTypeInfos[] = SLANG_SCALAR_TYPES(SLANG_SCALAR_TYPE_INFO) }; -struct CompileTargetInfo -{ - SlangCompileTarget target; ///< The target - const char* extensions; ///< Comma delimited list of extensions associated with the target - const char* names; ///< Comma delimited list of names associated with the target. NOTE! First name is taken as the normal display name. -}; - // Make sure to keep this table in sync with that in slang/slang-options.cpp getHelpText -static const CompileTargetInfo s_compileTargetInfos[] = +static const TypeTextUtil::CompileTargetInfo s_compileTargetInfos[] = { { SLANG_TARGET_UNKNOWN, "", "unknown"}, { SLANG_TARGET_NONE, "", "none"}, - { SLANG_HLSL, "hlsl,fx", "hlsl"}, - { SLANG_DXBC, "dxbc", "dxbc"}, - { SLANG_DXBC_ASM, "dxbc-asm", "dxbc-asm,dxbc-assembly" }, - { SLANG_DXIL, "dxil", "dxil" }, - { SLANG_DXIL_ASM, "dxil-asm", "dxil-asm,dxil-assembly" }, - { SLANG_GLSL, "glsl,vert,frag,geom,tesc,tese,comp", "glsl" }, - { SLANG_GLSL_VULKAN, "", "glsl-vulkan" }, - { SLANG_GLSL_VULKAN_ONE_DESC, "", "glsl-vulkan-one-desc" }, - { SLANG_SPIRV, "spv", "spirv"}, - { SLANG_SPIRV_ASM, "spv-asm", "spirv-asm,spirv-assembly" }, - { SLANG_C_SOURCE, "c", "c" }, - { SLANG_CPP_SOURCE, "cpp,c++,cxx", "cpp,c++,cxx" }, - { SLANG_CPP_PYTORCH_BINDING, "cpp,c++,cxx", "torch,torch-binding,torch-cpp,torch-cpp-binding" }, - { SLANG_HOST_CPP_SOURCE, "cpp,c++,cxx", "host-cpp,host-c++,host-cxx"}, - { SLANG_HOST_EXECUTABLE,"exe", "exe,executable" }, - { SLANG_SHADER_SHARED_LIBRARY, "dll,so", "sharedlib,sharedlibrary,dll" }, - { SLANG_CUDA_SOURCE, "cu", "cuda,cu" }, - { SLANG_PTX, "ptx", "ptx" }, - { SLANG_CUDA_OBJECT_CODE, "obj,o", "cuobj,cubin" }, - { SLANG_SHADER_HOST_CALLABLE, "", "host-callable,callable" }, - { SLANG_OBJECT_CODE, "obj,o", "object-code" }, - { SLANG_HOST_HOST_CALLABLE, "", "host-host-callable" }, + { SLANG_HLSL, "hlsl,fx", "hlsl", "HLSL source code"}, + { SLANG_DXBC, "dxbc", "dxbc", "DirectX shader bytecode binary"}, + { SLANG_DXBC_ASM, "dxbc-asm", "dxbc-asm,dxbc-assembly", "DirectX shader bytecode assembly" }, + { SLANG_DXIL, "dxil", "dxil", "DirectX Intermediate Language binary" }, + { SLANG_DXIL_ASM, "dxil-asm", "dxil-asm,dxil-assembly", "DirectX Intermediate Language assembly"}, + { SLANG_GLSL, "glsl,vert,frag,geom,tesc,tese,comp", "glsl", "GLSL source code" }, + { SLANG_GLSL_VULKAN, "", "glsl-vulkan", "GLSL Vulkan source code" }, + { SLANG_GLSL_VULKAN_ONE_DESC, "", "glsl-vulkan-one-desc", "GLSL Vulkan source code" }, + { SLANG_SPIRV, "spv", "spirv", "SPIR-V binary"}, + { SLANG_SPIRV_ASM, "spv-asm", "spirv-asm,spirv-assembly", "SPIR-V assembly" }, + { SLANG_C_SOURCE, "c", "c", "C source code" }, + { SLANG_CPP_SOURCE, "cpp,c++,cxx", "cpp,c++,cxx", "C++ source code" }, + { SLANG_CPP_PYTORCH_BINDING, "cpp,c++,cxx", "torch,torch-binding,torch-cpp,torch-cpp-binding", "C++ for pytorch binding" }, + { SLANG_HOST_CPP_SOURCE, "cpp,c++,cxx", "host-cpp,host-c++,host-cxx", "C++ source for host execution"}, + { SLANG_HOST_EXECUTABLE,"exe", "exe,executable", "Executable binary" }, + { SLANG_SHADER_SHARED_LIBRARY, "dll,so", "sharedlib,sharedlibrary,dll", "Shared library/Dll" }, + { SLANG_CUDA_SOURCE, "cu", "cuda,cu", "CUDA source code" }, + { SLANG_PTX, "ptx", "ptx", "PTX assembly" }, + { SLANG_CUDA_OBJECT_CODE, "obj,o", "cuobj,cubin", "CUDA binary" }, + { SLANG_SHADER_HOST_CALLABLE, "", "host-callable,callable", "Host callable" }, + { SLANG_OBJECT_CODE, "obj,o", "object-code", "Object code" }, + { SLANG_HOST_HOST_CALLABLE, "", "host-host-callable", "Host callable for host execution" }, +}; + +static const NamesValue s_languageInfos[] = +{ + { SLANG_SOURCE_LANGUAGE_C, "c,C" }, + { SLANG_SOURCE_LANGUAGE_CPP, "cpp,c++,C++,cxx" }, + { SLANG_SOURCE_LANGUAGE_SLANG, "slang" }, + { SLANG_SOURCE_LANGUAGE_GLSL, "glsl" }, + { SLANG_SOURCE_LANGUAGE_HLSL, "hlsl" }, + { SLANG_SOURCE_LANGUAGE_CUDA, "cu,cuda" }, +}; + +static const NamesDescriptionValue s_compilerInfos[] = +{ + { SLANG_PASS_THROUGH_NONE, "none", "Unknown" }, + { SLANG_PASS_THROUGH_FXC, "fxc", "FXC HLSL compiler" }, + { SLANG_PASS_THROUGH_DXC, "dxc", "DXC HLSL compiler" }, + { SLANG_PASS_THROUGH_GLSLANG, "glslang", "GLSLANG GLSL compiler" }, + { SLANG_PASS_THROUGH_VISUAL_STUDIO, "visualstudio,vs", "Visual Studio C/C++ compiler" }, + { SLANG_PASS_THROUGH_CLANG, "clang", "Clang C/C++ compiler" }, + { SLANG_PASS_THROUGH_GCC, "gcc", "GCC C/C++ compiler" }, + { SLANG_PASS_THROUGH_GENERIC_C_CPP, "genericcpp,c,cpp", "A generic C++ compiler (can be any one of visual studio, clang or gcc depending on system and availability)" }, + { SLANG_PASS_THROUGH_NVRTC, "nvrtc", "NVRTC CUDA compiler" }, + { SLANG_PASS_THROUGH_LLVM, "llvm", "LLVM/Clang `slang-llvm`" }, +}; +static const NameValue s_archiveTypeInfos[] = +{ + { SLANG_ARCHIVE_TYPE_RIFF_DEFLATE, "riff-deflate"}, + { SLANG_ARCHIVE_TYPE_RIFF_LZ4, "riff-lz4"}, + { SLANG_ARCHIVE_TYPE_ZIP, "zip"}, + { SLANG_ARCHIVE_TYPE_RIFF, "riff"}, }; -struct ArchiveTypeInfo + +static const NamesDescriptionValue s_debugInfoFormatInfos[] = { - SlangArchiveType type; - UnownedStringSlice text; + { SLANG_DEBUG_INFO_FORMAT_DEFAULT, "default-format", "Use the default debugging format for the target" }, + { SLANG_DEBUG_INFO_FORMAT_C7, "c7", "CodeView C7 format (typically means debugging infomation is embedded in the binary)" }, + { SLANG_DEBUG_INFO_FORMAT_PDB, "pdb", "Program database" }, + { SLANG_DEBUG_INFO_FORMAT_STABS, "stabs", "STABS debug format" }, + { SLANG_DEBUG_INFO_FORMAT_COFF, "coff", "COFF debug format" }, + { SLANG_DEBUG_INFO_FORMAT_DWARF, "dwarf", "DWARF debug format"}, }; -static const ArchiveTypeInfo s_archiveTypeInfos[] = +static const NamesDescriptionValue s_lineDirectiveInfos[] = { - { SLANG_ARCHIVE_TYPE_RIFF_DEFLATE, UnownedStringSlice::fromLiteral("riff-deflate")}, - { SLANG_ARCHIVE_TYPE_RIFF_LZ4, UnownedStringSlice::fromLiteral("riff-lz4")}, - { SLANG_ARCHIVE_TYPE_ZIP, UnownedStringSlice::fromLiteral("zip")}, - { SLANG_ARCHIVE_TYPE_RIFF, UnownedStringSlice::fromLiteral("riff")}, + { SLANG_LINE_DIRECTIVE_MODE_NONE, "none", "Don't emit `#line` directives at all"}, + { SLANG_LINE_DIRECTIVE_MODE_SOURCE_MAP, "source-map", "Use source map to track line associations (doen't emit #line)"}, + { SLANG_LINE_DIRECTIVE_MODE_DEFAULT, "default", "Default behavior"}, + { SLANG_LINE_DIRECTIVE_MODE_STANDARD, "standard", "Emit standard C-style `#line` directives." }, + { SLANG_LINE_DIRECTIVE_MODE_GLSL, "glsl", "Emit GLSL-style directives with file *number* instead of name." }, +}; + +static const NamesDescriptionValue s_floatingPointModes[] = +{ + { SLANG_FLOATING_POINT_MODE_PRECISE, "precise", + "Disable optimization that could change the output of floating-" + "point computations, including around infinities, NaNs, denormalized " + "values, and negative zero. Prefer the most precise versions of special " + "functions supported by the target."}, + { SLANG_FLOATING_POINT_MODE_FAST,"fast", + "Allow optimizations that may change results of floating-point " + "computations. Prefer the fastest version of special functions supported " + "by the target."}, + { SLANG_FLOATING_POINT_MODE_DEFAULT, "default", + "Default floating point mode" } }; } // anonymous -/* static */SlangArchiveType TypeTextUtil::findArchiveType(const UnownedStringSlice& slice) +/* static */ConstArrayView<TypeTextUtil::CompileTargetInfo> TypeTextUtil::getCompileTargetInfos() { - for (const auto& entry : s_archiveTypeInfos) - { - if (slice == entry.text) - { - return entry.type; - } - } - return SLANG_ARCHIVE_TYPE_UNDEFINED; + return makeConstArrayView(s_compileTargetInfos); } -struct DebugInfoFormatTable +/* static */ConstArrayView<NamesValue> TypeTextUtil::getLanguageInfos() { - UnownedStringSlice entries[SLANG_DEBUG_INFO_FORMAT_COUNT_OF]; + return makeConstArrayView(s_languageInfos); +} - static DebugInfoFormatTable _makeTable() - { - DebugInfoFormatTable dst; -#define SLANG_DEBUG_INFO_FORMAT_ENTRY(name, value) \ - dst.entries[SLANG_DEBUG_INFO_FORMAT_##value] = toSlice(#name); - SLANG_DEBUG_INFO_FORMATS(SLANG_DEBUG_INFO_FORMAT_ENTRY) - return dst; - } +/* static */ConstArrayView<NamesDescriptionValue> TypeTextUtil::getCompilerInfos() +{ + return makeConstArrayView(s_compilerInfos); +} - static Index findIndex(const UnownedStringSlice slice) { return makeConstArrayView(table.entries).indexOf(slice); } - static UnownedStringSlice getSlice(SlangDebugInfoFormat format) { return table.entries[Index(format)]; } +/* static */ConstArrayView<NameValue> TypeTextUtil::getArchiveTypeInfos() +{ + return makeConstArrayView(s_archiveTypeInfos); +} - static const DebugInfoFormatTable table; -}; +/* static */ConstArrayView<NamesDescriptionValue> TypeTextUtil::getDebugInfoFormatInfos() +{ + return makeConstArrayView(s_debugInfoFormatInfos); +} -/* static */const DebugInfoFormatTable DebugInfoFormatTable::table = DebugInfoFormatTable::_makeTable(); +/* static */ConstArrayView<NamesDescriptionValue> TypeTextUtil::getLineDirectiveInfos() +{ + return makeConstArrayView(s_lineDirectiveInfos); +} + +/* static */ConstArrayView<NamesDescriptionValue> TypeTextUtil::getFloatingPointModeInfos() +{ + return makeConstArrayView(s_floatingPointModes); +} + +/* static */SlangArchiveType TypeTextUtil::findArchiveType(const UnownedStringSlice& slice) +{ + return NameValueUtil::findValue(getArchiveTypeInfos(), slice, SLANG_ARCHIVE_TYPE_UNDEFINED); +} /* static */SlangResult TypeTextUtil::findDebugInfoFormat(const Slang::UnownedStringSlice& text, SlangDebugInfoFormat& out) { - const auto index = DebugInfoFormatTable::findIndex(text); - if (index >= 0) + const ValueInt value = NameValueUtil::findValue(getDebugInfoFormatInfos(), text, -1); + if (value >= 0) { - out = SlangDebugInfoFormat(index); + out = SlangDebugInfoFormat(value); return SLANG_OK; } return SLANG_FAIL; } -/* static */UnownedStringSlice TypeTextUtil::getDebugInfoFormatName(SlangDebugInfoFormat format) { return DebugInfoFormatTable::getSlice(format); } +/* static */UnownedStringSlice TypeTextUtil::getDebugInfoFormatName(SlangDebugInfoFormat format) +{ + return NameValueUtil::findName(getDebugInfoFormatInfos(), format, toSlice("unknown")); +} /* static */UnownedStringSlice TypeTextUtil::getScalarTypeName(slang::TypeReflection::ScalarType scalarType) { @@ -180,82 +218,20 @@ struct DebugInfoFormatTable return slang::TypeReflection::ScalarType::None; } -#define SLANG_PASS_THROUGH_HUMAN_TEXT(x) \ - x(NONE, "Unknown") \ - x(VISUAL_STUDIO, "Visual Studio") \ - x(GCC, "GCC") \ - x(CLANG, "Clang") \ - x(NVRTC, "NVRTC") \ - x(FXC, "fxc") \ - x(DXC, "dxc") \ - x(GLSLANG, "glslang") \ - x(LLVM, "LLVM/Clang") /* static */UnownedStringSlice TypeTextUtil::getPassThroughAsHumanText(SlangPassThrough type) { -#define SLANG_PASS_THROUGH_HUMAN_CASE(value, text) case SLANG_PASS_THROUGH_##value: return UnownedStringSlice::fromLiteral(text); - - switch (type) - { - default: [[fallthrough]]; /* fall-through to none */ - SLANG_PASS_THROUGH_HUMAN_TEXT(SLANG_PASS_THROUGH_HUMAN_CASE) - } -} - -/* static */SlangResult TypeTextUtil::findPassThroughFromHumanText(const UnownedStringSlice& inText, SlangPassThrough& outPassThrough) -{ - #define SLANG_PASS_THROUGH_HUMAN_IF(value, text) if (inText == UnownedStringSlice::fromLiteral(text)) { outPassThrough = SLANG_PASS_THROUGH_##value; return SLANG_OK; } else - SLANG_PASS_THROUGH_HUMAN_TEXT(SLANG_PASS_THROUGH_HUMAN_IF) - return SLANG_FAIL; + return NameValueUtil::findName(getCompilerInfos(), type, toSlice("unknown")); } /* static */SlangSourceLanguage TypeTextUtil::findSourceLanguage(const UnownedStringSlice& text) { - if (text == "c" || text == "C") - { - return SLANG_SOURCE_LANGUAGE_C; - } - else if (text == "cpp" || text == "c++" || text == "C++" || text == "cxx") - { - return SLANG_SOURCE_LANGUAGE_CPP; - } - else if (text == "slang") - { - return SLANG_SOURCE_LANGUAGE_SLANG; - } - else if (text == "glsl") - { - return SLANG_SOURCE_LANGUAGE_GLSL; - } - else if (text == "hlsl") - { - return SLANG_SOURCE_LANGUAGE_HLSL; - } - else if (text == "cu" || text == "cuda") - { - return SLANG_SOURCE_LANGUAGE_CUDA; - } - return SLANG_SOURCE_LANGUAGE_UNKNOWN; + return NameValueUtil::findValue(getLanguageInfos(), text, SLANG_SOURCE_LANGUAGE_UNKNOWN); } /* static */SlangPassThrough TypeTextUtil::findPassThrough(const UnownedStringSlice& slice) { -#define SLANG_PASS_THROUGH_NAME_TO_TYPE(x, y) \ - if (slice == UnownedStringSlice::fromLiteral(#x)) return SLANG_PASS_THROUGH_##y; - - SLANG_PASS_THROUGH_TYPES(SLANG_PASS_THROUGH_NAME_TO_TYPE) - - // Other options - if (slice == "c" || slice == "cpp") - { - return SLANG_PASS_THROUGH_GENERIC_C_CPP; - } - else if (slice == "vs") - { - return SLANG_PASS_THROUGH_VISUAL_STUDIO; - } - - return SLANG_PASS_THROUGH_NONE; + return NameValueUtil::findValue(getCompilerInfos(), slice, SLANG_PASS_THROUGH_NONE); } /* static */SlangResult TypeTextUtil::findPassThrough(const UnownedStringSlice& slice, SlangPassThrough& outPassThrough) @@ -271,15 +247,7 @@ struct DebugInfoFormatTable /* static */UnownedStringSlice TypeTextUtil::getPassThroughName(SlangPassThrough passThru) { -#define SLANG_PASS_THROUGH_TYPE_TO_NAME(x, y) \ - case SLANG_PASS_THROUGH_##y: return UnownedStringSlice::fromLiteral(#x); - - switch (passThru) - { - SLANG_PASS_THROUGH_TYPES(SLANG_PASS_THROUGH_TYPE_TO_NAME) - default: break; - } - return UnownedStringSlice::fromLiteral("unknown"); + return NameValueUtil::findName(getCompilerInfos(), passThru, toSlice("unknown")); } /* static */SlangCompileTarget TypeTextUtil::findCompileTargetFromExtension(const UnownedStringSlice& slice) diff --git a/source/core/slang-type-text-util.h b/source/core/slang-type-text-util.h index df9b83013..2249b1215 100644 --- a/source/core/slang-type-text-util.h +++ b/source/core/slang-type-text-util.h @@ -4,7 +4,8 @@ #include "../../slang.h" #include "slang-string.h" - +#include "slang-array-view.h" +#include "slang-name-value.h" namespace Slang { @@ -12,6 +13,30 @@ namespace Slang /// Utility class to allow conversion of types (such as enums) to and from text types struct TypeTextUtil { + struct CompileTargetInfo + { + SlangCompileTarget target; ///< The target + const char* extensions; ///< Comma delimited list of extensions associated with the target + const char* names; ///< Comma delimited list of names associated with the target. NOTE! First name is taken as the normal display name. + const char* description; ///< Description, can be null + }; + + /// Get the compile target infos + static ConstArrayView<CompileTargetInfo> getCompileTargetInfos(); + + /// Get the language infos + static ConstArrayView<NamesValue> getLanguageInfos(); + /// Get the compiler infos + static ConstArrayView<NamesDescriptionValue> getCompilerInfos(); + /// Get the archive type infos + static ConstArrayView<NameValue> getArchiveTypeInfos(); + /// Get the debug format types + static ConstArrayView<NamesDescriptionValue> getDebugInfoFormatInfos(); + + static ConstArrayView<NamesDescriptionValue> getFloatingPointModeInfos(); + + static ConstArrayView<NamesDescriptionValue> getLineDirectiveInfos(); + /// Get the scalar type as text. static Slang::UnownedStringSlice getScalarTypeName(slang::TypeReflection::ScalarType scalarType); @@ -26,9 +51,7 @@ struct TypeTextUtil /// As human readable text static UnownedStringSlice getPassThroughAsHumanText(SlangPassThrough type); - /// Gets pass through from human text (as from getPassThroughAsHumanText) - static SlangResult findPassThroughFromHumanText(const UnownedStringSlice& text, SlangPassThrough& outPassThrough); - + /// Given a source language name returns a source language. Name here is distinct from extension static SlangSourceLanguage findSourceLanguage(const UnownedStringSlice& text); diff --git a/source/slang/slang-capability.cpp b/source/slang/slang-capability.cpp index 770685247..28dea8d23 100644 --- a/source/slang/slang-capability.cpp +++ b/source/slang/slang-capability.cpp @@ -117,6 +117,15 @@ static CapabilityAtomInfo const& _getInfo(CapabilityAtom atom) return kCapabilityAtoms[Int(atom)]; } +void getCapabilityAtomNames(List<UnownedStringSlice>& ioNames) +{ + ioNames.setCount(Count(CapabilityAtom::Count)); + for (Index i = 0; i < Count(CapabilityAtom::Count); ++i) + { + ioNames[i] = UnownedStringSlice(_getInfo(CapabilityAtom(i)).name); + } +} + CapabilityAtom findCapabilityAtom(UnownedStringSlice const& name) { // For now we are implementing a linear search over the diff --git a/source/slang/slang-capability.h b/source/slang/slang-capability.h index 90b03ed29..55c5044ed 100644 --- a/source/slang/slang-capability.h +++ b/source/slang/slang-capability.h @@ -161,4 +161,8 @@ bool isCapabilityDerivedFrom(CapabilityAtom atom, CapabilityAtom base); /// Find a capability atom with the given `name`, or return CapabilityAtom::Invalid. CapabilityAtom findCapabilityAtom(UnownedStringSlice const& name); + + /// Gets the capability names. +void getCapabilityAtomNames(List<UnownedStringSlice>& ioNames); + } diff --git a/source/slang/slang-compiler.cpp b/source/slang/slang-compiler.cpp index 0aa7e48a9..4ca76c53a 100644 --- a/source/slang/slang-compiler.cpp +++ b/source/slang/slang-compiler.cpp @@ -372,13 +372,9 @@ namespace Slang } } - static const struct + static const StageInfo kStages[] = { - char const* name; - Stage stage; - } kStages[] = - { - #define PROFILE_STAGE(ID, NAME, ENUM) \ + #define PROFILE_STAGE(ID, NAME, ENUM) \ { #NAME, Stage::ID }, #define PROFILE_STAGE_ALIAS(ID, NAME, VAL) \ @@ -387,6 +383,11 @@ namespace Slang #include "slang-profile-defs.h" }; + ConstArrayView<StageInfo> getStageInfos() + { + return makeConstArrayView(kStages); + } + Stage findStageByName(String const& name) { for(auto entry : kStages) diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 83acbdd9d..fc46b25b6 100755 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -13,6 +13,7 @@ #include "../compiler-core/slang-command-line-args.h" #include "../core/slang-std-writers.h" +#include "../core/slang-command-options.h" #include "../../slang-com-ptr.h" @@ -3005,6 +3006,9 @@ namespace Slang DownstreamCompilerLocatorFunc m_downstreamCompilerLocators[int(PassThroughMode::CountOf)]; Name* m_completionTokenName = nullptr; ///< The name of a completion request token. + /// For parsing command line options + CommandOptions m_commandOptions; + private: void _initCodeGenTransitionMap(); diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index cb441ade8..8738c71cc 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -76,8 +76,6 @@ DIAGNOSTIC( 19, Error, unknownSourceLanguage, "unknown source language '$0'") DIAGNOSTIC( 20, Error, entryPointsNeedToBeAssociatedWithTranslationUnits, "when using multiple source files, entry points must be specified after their corresponding source file(s)") DIAGNOSTIC( 22, Error, unknownDownstreamCompiler, "unknown downstream compiler '$0'") -DIAGNOSTIC( 24, Error, unknownLineDirectiveMode, "unknown '#line' directive mode '$0'") -DIAGNOSTIC( 25, Error, unknownFloatingPointMode, "unknown floating-point mode '$0'") DIAGNOSTIC( 26, Error, unknownOptimiziationLevel, "unknown optimization level '$0'") DIAGNOSTIC( 27, Error, unknownDebugInfoLevel, "unknown debug info level '$0'") @@ -104,6 +102,9 @@ DIAGNOSTIC( 50, Error, duplicateTargets, "the target '$0' has been specified DIAGNOSTIC( 60, Error, cannotDeduceOutputFormatFromPath, "cannot infer an output format from the output path '$0'") DIAGNOSTIC( 61, Error, cannotMatchOutputFileToTarget, "no specified '-target' option matches the output path '$0', which implies the '$1' format") +DIAGNOSTIC( 62, Error, unknownCommandLineValue, "unknown value for option. Valid values are '$0'") +DIAGNOSTIC( 63, Error, unknownHelpCategory, "unknown help category") + DIAGNOSTIC( 70, Error, cannotMatchOutputFileToEntryPoint, "the output path '$0' is not associated with any entry point; a '-o' option for a compiled kernel must follow the '-entry' option for its corresponding entry point") DIAGNOSTIC( 80, Error, duplicateOutputPathsForEntryPointAndTarget, "multiple output paths have been specified entry point '$0' on target '$1'") diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index 8817095df..6cb53496f 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -29,12 +29,494 @@ #include "../compiler-core/slang-artifact-desc-util.h" #include "../compiler-core/slang-core-diagnostics.h" +#include "../core/slang-string-slice-pool.h" #include "../core/slang-char-util.h" +#include "../core/slang-command-options.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, + Include, + Language, + MatrixLayoutColumn, + MatrixLayoutRow, + ModuleName, + Output, + Profile, + Stage, + Target, + Version, + WarningsAsErrors, + DisableWarnings, + EnableWarning, + DisableWarning, + DumpWarningDiagnostics, + InputFilesRemain, + EmitIr, + + // Target + + Capability, + DefaultImageFormatUnknown, + DisableDynamicDispatch, + DisableSpecialization, + FloatingPointMode, + DebugInformation, + LineDirectiveMode, + Optimization, + Obfuscate, + + // Downstream + + CompilerPath, + DefaultDownstreamCompiler, + DownstreamArgs, + PassThrough, + + // Debugging + + DumpAst, + DumpIntermediatePrefix, + DumpIntermediates, + DumpIr, + DumpIrIds, + DumpRepro, + DumpReproOnError, + PreprocessorOutput, + ExtractRepro, + LoadRepro, + LoadReproDirectory, + NoCodeGen, + OutputIncludes, + ReproFileSystem, + SerialIr, + SkipCodeGen, + ValidateIr, + VerbosePaths, + VerifyDebugSerialIr, + + // Experimental + + EmitSpirvDirectly, + FileSystem, + Heterogeneous, + NoMangle, + + // Internal + + ArchiveType, + CompileStdLib, + Doc, + IrCompression, + LoadStdLib, + ReferenceModule, + SaveStdLib, + SaveStdLibBinSource, + TrackLiveness, + + // Depreciated + 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, + + CountOf, +}; + +} // 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, "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, "Depreciated", "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()); + } + + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 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()); + } + + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 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; + getCapabilityAtomNames(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_")) + { + continue; + } + else if (name.startsWith("GL_")) + { + // 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"}, + {"dxbc-asm", "dxbc-assembly"}, + {"dxil"}, + {"dxil-asm", "dxil-assembly"}, + {"glsl"}, + {"vert", "glsl (vertex)"}, + {"frag", "glsl (fragment)"}, + {"geom", "glsl (geoemtry)"}, + {"tesc", "glsl (hull)"}, + {"tese", "glsl (domain)"}, + {"comp", "glsl (compute)"}, + {"slang"}, + {"spv", "SPIR-V"}, + {"spv-asm", "SPIR-V assembly"}, + {"c"}, + {"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." }, + { 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" + "Multiple -entry options may be used in a single invocation. " + "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::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."}, + { 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."} + }; + + _addOptions(makeConstArrayView(generalOpts), options); + + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Target !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + + options.setCategory("Target"); + + 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<N>, -g<debug-info-format>", + "Include debug information in the generated code, where possible.\n" + "N 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 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<N>", + "Set the optimization level.\n" + "N is the amount of optimization, 0..3, default is 1" }, + { OptionKind::Obfuscate, "-obfuscate", nullptr, "Remove all source file information from outputs." }, + }; + + _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 \n" + "existing compiler <compiler>." }, + }; + + _addOptions(makeConstArrayView(downstreamOpts), 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::DumpRepro, "-dump-repro", nullptr, "Dump a `.slang-repro` file that can be used to reproduce " + "a compilation on another machine.\n"}, + { OptionKind::DumpReproOnError, "-dump-repro-on-error", nullptr, "Dump `.slang-repro` file on any compilation error." }, + { OptionKind::PreprocessorOutput, "-E,-output-preprocessor", nullptr, "Output the preprocessing result and exit." }, + { 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::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::ReproFileSystem, "-repro-file-system", "-repro-file-system <name>", "Use a repro as a file system" }, + { 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::EmitSpirvDirectly, "-emit-spirv-directly", nullptr, + "Generate SPIR-V output directly (otherwise through " + "GLSL and using the glslang compiler)"}, + { OptionKind::FileSystem, "-file-system", "-file-system <fs>", + "Set the filesystem hook to use for a compile request.\n" + "Accepted file systems: default, load-file, os" }, + { OptionKind::Heterogeneous, "-heterogeneous", nullptr, "Output heterogeneity-related code." }, + { OptionKind::NoMangle, "-no-mangle", nullptr, "Do as little mangling of names as possible." } + }; + _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); + + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Depreciated !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + + options.setCategory("Depreciated"); + + const Option depreciatedOpts[] = + { + { OptionKind::ParameterBlocksUseRegisterSpaces, "-parameter-blocks-use-register-spaces", nullptr, "Parameter blocks will use register spaces" }, + }; + _addOptions(makeConstArrayView(depreciatedOpts), 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); struct OptionsParser @@ -516,231 +998,6 @@ struct OptionsParser return SLANG_OK; } - static char const* getHelpText() - { -#ifdef _WIN32 -#define EXECUTABLE_EXTENSION ".exe" -#else -#define EXECUTABLE_EXTENSION "" -#endif - - return - "Usage: slangc" EXECUTABLE_EXTENSION " [options...] [--] <input files>\n" - "\n" - "General options:\n" - "\n" - " -D<name>[=<value>], -D <name>[=<value>]: Insert a preprocessor macro.\n" - " -depfile <path>: Save the source file dependency list in a file.\n" - " -entry <name>: Specify the name of an entry-point function.\n" - " Multiple -entry options may be used in a single invocation.\n" - " If no -entry options are given, compiler will use [shader(...)]\n" - " attributes to detect entry points.\n" - " -h, -help, --help: Print this message.\n" - " -I<path>, -I <path>: Add a path to be used in resolving '#include'\n" - " and 'import' operations.\n" - " -lang <language>: Set the language for the following input files.\n" - " Accepted languages are:\n" - " c, cpp, c++, cxx, slang, glsl, hlsl, cu, cuda\n" - " -matrix-layout-column-major: Set the default matrix layout to column-major.\n" - " -matrix-layout-row-major: Set the default matrix layout to row-major.\n" - " -module-name <name>: Set the module name to use when compiling multiple\n" - " .slang source files into a single module.\n" - " -o <path>: Specify a path where generated output should be written.\n" - " If no -target or -stage is specified, one may be inferred\n" - " from file extension (see File Extensions).\n" - " If multiple -target options and a single -entry are present, each -o\n" - " associates with the first -target to its left.\n" - " Otherwise, if multiple -entry options are present, each -o associates\n" - " with the first -entry to its left, and with the -target that matches\n" - " the one inferred from <path>.\n" - " -profile <profile>[+<capability>...]: Specify the shader profile for code\n" - " generation.\n" - " Accepted profiles are:\n" - " sm_{4_0,4_1,5_0,5_1,6_0,6_1,6_2,6_3,6_4,6_5,6_6}\n" - " glsl_{110,120,130,140,150,330,400,410,420,430,440,450,460}\n" - " Additional profiles that include -stage information:\n" - " {vs,hs,ds,gs,ps}_<version>\n" - " See -capability for information on <capability>\n" - " When multiple -target options are present, each -profile associates\n" - " with the first -target to its left.\n" - " -stage <name>: Specify the stage of an entry-point function.\n" - " Accepted stages are:\n" - " vertex, hull, domain, geometry, fragment, compute,\n" - " raygeneration, intersection, anyhit, closesthit, miss, callable\n" - " When multiple -entry options are present, each -stage associated with\n" - " the first -entry to its left.\n" - " May be omitted if entry-point function has a [shader(...)] attribute;\n" - " otherwise required for each -entry option.\n" - " -target <format>: Specifies the format in which code should be generated.\n" - " Accepted formats are:\n" - " glsl, hlsl, spirv, spirv-assembly, dxbc,\n" - " dxbc-assembly, dxil, dxil-assembly\n" - " -v, -version: Display the build version.\n" - " -warnings-as-errors all: Treat all warnings as errors.\n" - " -warnings-as-errors <id>[,<id>...]: Treat specific warning ids as errors.\n" - " -warnings-disable <id>[,<id>...]: Disable specific warning ids.\n" - " -W<id>: Enable a warning with the specified id.\n" - " -Wno-<id>: Disable a warning with the specified id.\n" - " -dump-warning-diagnostics: Dump to output list of warning diagnostic numeric and name ids.\n" - " --: Treat the rest of the command line as input files.\n" - "\n" - "Target code generation options:\n" - "\n" - " -capability <capability>[+<capability>...]: Add optional capabilities\n" - " to a code generation target. See Capabilities below.\n" - " -default-image-format-unknown: Set the format of R/W images with unspecified\n" - " format to 'unknown'. Otherwise try to guess the format.\n" - " -disable-dynamic-dispatch: Disables generating dynamic dispatch code.\n" - " -disable-specialization: Disables generics and specialization pass.\n" - " -fp-mode <mode>, -floating-point-mode <mode>: Set the floating point mode.\n" - " Accepted modes are:\n" - " precise : Disable optimization that could change the output of floating-\n" - " point computations, including around infinities, NaNs, denormalized\n" - " values, and negative zero. Prefer the most precise versions of special\n" - " functions supported by the target.\n" - " fast : Allow optimizations that may change results of floating-point\n" - " computations. Prefer the fastest version of special functions supported\n" - " by the target.\n" - " -g, -g<N>: Include debug information in the generated code, where possible.\n" - " N is the amount of information, 0..3, unspecified means 2\n" - " -line-directive-mode <mode>: Sets how the `#line` directives should be\n" - " produced. Available options are:\n" - " none : Don't emit `#line` directives at all\n" - " source-map : Use source map to track line associations (doen't emit #line)\n" - " default : Default behavior\n" - " If not specified, default behavior is to use C-style `#line` directives\n" - " for HLSL and C/C++ output, and traditional GLSL-style `#line` directives\n" - " for GLSL output.\n" - " -O<N>: Set the optimization level.\n" - " N is the amount of optimization, 0..3, default is 1\n" - " -obfuscate: Remove all source file information from outputs.\n" - "\n" - "Downstream compiler options:\n" - "\n" - " -<compiler>-path: Specify path to a downstream <compiler>\n" - " executable or library. Accepted compilers are:\n" - " fxc (d3dcompiler_47.dll)\n" - " dxc (dxcompiler.*)\n" - " glslang (slang-glslang.*)\n" - " vs = visualstudio (cl.exe)\n" - " clang\n" - " gcc (g++)\n" - " c = cpp = genericcpp\n" - " nvrtc\n" - " llvm\n" - " -default-downstream-compiler <language> <compiler>: Set a default compiler\n" - " for the given language. See -lang for the list of languages.\n" - " -X<compiler> <option>: Pass arguments to downstream <compiler>.\n" - "\n" - "Compiler debugging/instrumentation options:\n" - "\n" - " -dump-ast: Dump the AST to a .slang-ast file next to the input.\n" - " -dump-intermediate-prefix <prefix>: File name prefix for -dump-intermediates \n" - " outputs, default is 'slang-dump-'\n" - " -dump-intermediates: Dump intermediate outputs for debugging.\n" - " -dump-ir: Dump the IR for debugging.\n" - " -dump-ir-ids: Dump the IDs with -dump-ir (debug builds only)\n" - " -dump-repro: Dump a `.slang-repro` file that can be used to reproduce\n" - " a compilation on another machine.\n" - " -dump-repro-on-error: Dump `.slang-repro` file on any compilation error.\n" - " -E, -output-preprocessor: Output the preprocessing result and exit.\n" - " -extract-repro <name>: Extract the repro files into a folder.\n" - " -load-repro <name>\n" - " -load-repro-directory <path>\n" - " -no-codegen: Skip the code generation step, just check the code and\n" - " generate layout.\n" - " -output-includes: Print the hierarchy of the processed source files.\n" - " -pass-through <name>: Pass the input through mostly unmodified to the \n" - " existing compiler <name>. Accepted compilers are:\n" - " fxc, glslang, dxc\n" - " -repro-file-system <name>\n" - " -serial-ir: Serialize the IR between front-end and back-end.\n" - " -skip-codegen: Skip the code generation phase.\n" - " -validate-ir: Validate the IR between the phases.\n" - " -verbose-paths: Display more detailed paths in diagnostic output.\n" - " -verify-debug-serial-ir: Verify IR in the front-end.\n" - "\n" - "Experimental options (use at your own risk):\n" - "\n" - " -emit-spirv-directly: Generate SPIR-V output directly (otherwise through \n" - " GLSL and using the glslang compiler)\n" - " -file-system <fs>: Set the filesystem hook to use for a compile request.\n" - " Accepted file systems:\n" - " default, load-file, os\n" - " -heterogeneous: Output heterogeneity-related code.\n" - " -no-mangle: Do as little mangling of names as possible.\n" - "\n" - "Internal-use options (use at your own risk):\n" - "\n" - " -archive-type <type>: Set the archive type for -save-stdlib. Default is zip.\n" - " Accepted archive types:\n" - " zip, riff, riff-deflate, riff-lz4\n" - " -compile-stdlib: Compile the StdLib from embedded sources.\n" - " Will return a failure if there is already a StdLib available.\n" - " -doc: Write documentation for -compile-stdlib\n" - " -ir-compression <type>: Set compression for IR and AST outputs.\n" - " Accepted compression types:\n" - " none, lite\n" - " -load-stdlib <filename>: Load the StdLib from file.\n" - " -r <name>: reference module <name>\n" - " -save-stdlib <filename>: Save the StdLib modules to an archive file.\n" - " -save-stdlib-bin-source <filename>: Same as -save-stdlib but output\n" - " the data as a C array.\n" - " -track-liveness: Enable liveness tracking. Places SLANG_LIVE_START, and SLANG_LIVE_END in output source to indicate value liveness.\n" - " -source-map: Enables outputting of a source map. Note this is *distinct* from line-directive-mode.\n" - "\n" - "Deprecated options (allowed but ignored; may be removed in future):\n" - "\n" - " -parameter-blocks-use-register-spaces\n" - "\n" - "File Extensions:\n" - "\n" - " A <language>, <format>, and/or <stage> may be inferred from the\n" - " extension of an input or -o path:\n" - "\n" - " extension | language/format | stage\n" - " ====================|=================|======\n" - " .hlsl, .fx -> hlsl\n" - " .dxbc -> dxbc\n" - " .dxbc-asm -> dxbc-assembly\n" - " .dxil -> dxil\n" - " .dxil-asm -> dxil-assembly\n" - " .glsl -> glsl\n" - " .vert -> glsl vertex\n" - " .frag -> glsl fragment\n" - " .geom -> glsl geoemtry\n" - " .tesc -> glsl hull\n" - " .tese -> glsl domain\n" - " .comp -> glsl compute\n" - " .slang -> slang\n" - " .spv -> spirv\n" - " .spv-asm -> spirv-assembly\n" - " .c -> c\n" - " .cpp, .c++, .cxx -> c++\n" - " .exe -> executable\n" - " .dll, .so -> sharedlibrary\n" - " .cu -> cuda\n" - " .ptx -> ptx\n" - " .obj, .o -> object-code\n" - "\n" - "Capabilities:\n" - "\n" - " A capability describes an optional feature that a target may or\n" - " may not support. When a -capability is specified, the compiler\n" - " may assume that the target supports that capability, and generate\n" - " code accordingly.\n" - " Currently defined capabilities are:\n" - "\n" - " spirv_1_{0,1,2,3,4,5} - minimum supported SPIR-V version\n" - " GL_NV_ray_tracing - enables the GL_NV_ray_tracing extension\n" - " GL_EXT_ray_tracing - enables the GL_EXT_ray_tracing extension\n" - " GL_NV_fragment_shader_barycentric - enables the GL_NV_fragment_shader_barycentric extension\n" - " GL_EXT_fragment_shader_barycentric - enables the GL_EXT_fragment_shader_barycentric extension\n" - "\n"; - -#undef EXECUTABLE_EXTENSION - } - // Pass Severity::Disabled to allow any original severity SlangResult _overrideDiagnostics(const UnownedStringSlice& identifierList, Severity originalSeverity, Severity overrideSeverity, DiagnosticSink* sink) { @@ -830,6 +1087,59 @@ struct OptionsParser return SLANG_OK; } + SlangResult _getValue(ValueCategory valueCategory, const CommandLineArg& arg, DiagnosticSink* sink, CommandOptions::UserValue& outValue) + { + auto& cmdOptions = asInternal(session)->m_commandOptions; + + const auto optionIndex = cmdOptions.findOptionByCategoryUserValue(CommandOptions::UserValue(valueCategory), arg.value.getUnownedSlice()); + if (optionIndex < 0) + { + const auto categoryIndex = cmdOptions.findCategoryByUserValue(CommandOptions::UserValue(valueCategory)); + SLANG_ASSERT(categoryIndex >= 0); + if (categoryIndex < 0) + { + return SLANG_FAIL; + } + + List<UnownedStringSlice> names; + cmdOptions.getCategoryOptionNames(categoryIndex, names); + + StringBuilder buf; + StringUtil::join(names.getBuffer(), names.getCount(), toSlice(", "), buf); + + sink->diagnose(arg.loc, Diagnostics::unknownCommandLineValue, buf); + return SLANG_FAIL; + } + + outValue = cmdOptions.getOptionAt(optionIndex).userValue; + return SLANG_OK; + } + SlangResult _expectValue(ValueCategory valueCategory, CommandLineReader& reader, DiagnosticSink* sink, CommandOptions::UserValue& outValue) + { + CommandLineArg arg; + SLANG_RETURN_ON_FAIL(reader.expectArg(arg)); + SLANG_RETURN_ON_FAIL(_getValue(valueCategory, arg, sink, outValue)); + return SLANG_OK; + } + + void _appendUsageTitle(StringBuilder& out) + { + out << "Usage: slangc [options...] [--] <input files>\n\n"; + } + void _appendMinimalUsage(StringBuilder& out) + { + _appendUsageTitle(out); + out << "For help: slangc -h\n"; + } + void _outputMinimalUsage(DiagnosticSink* sink) + { + // Output usage info + StringBuilder buf; + _appendMinimalUsage(buf); + + sink->diagnoseRaw(Severity::Note, buf.getUnownedSlice()); + } + SlangResult parse( int argc, char const* const* argv) @@ -896,23 +1206,39 @@ struct OptionsParser slang::CompileStdLibFlags compileStdLibFlags = 0; bool hasLoadedRepro = false; + // Get the options on the session + CommandOptions& options = asInternal(session)->m_commandOptions; + + auto frontEndReq = requestImpl->getFrontEndReq(); + while (reader.hasArg()) { auto arg = reader.getArgAndAdvance(); const auto& argValue = arg.value; - if (argValue[0] == '-') + // If it's not an option we assume it's a path + if (argValue[0] != '-') { - if(argValue == "-no-mangle" ) - { - flags |= SLANG_COMPILE_FLAG_NO_MANGLING; - } - else if (argValue == toSlice("-emit-ir")) - { - // Enable emitting IR - requestImpl->m_emitIr = true; - } - else if (argValue == "-load-stdlib") + SLANG_RETURN_ON_FAIL(addInputPath(argValue.getBuffer())); + continue; + } + + const Index optionIndex = options.findOptionByName(argValue.getUnownedSlice()); + + if (optionIndex < 0) + { + sink->diagnose(arg.loc, Diagnostics::unknownCommandLineOption, argValue); + _outputMinimalUsage(sink); + return SLANG_FAIL; + } + + const auto optionKind = OptionKind(options.getOptionAt(optionIndex).userValue); + + switch (optionKind) + { + case OptionKind::NoMangle: flags |= SLANG_COMPILE_FLAG_NO_MANGLING; break; + case OptionKind::EmitIr: requestImpl->m_emitIr = true; break; + case OptionKind::LoadStdLib: { CommandLineArg fileName; SLANG_RETURN_ON_FAIL(reader.expectArg(fileName)); @@ -921,24 +1247,17 @@ struct OptionsParser ScopedAllocation contents; SLANG_RETURN_ON_FAIL(File::readAllBytes(fileName.value, contents)); SLANG_RETURN_ON_FAIL(session->loadStdLib(contents.getData(), contents.getSizeInBytes())); + break; } - else if (argValue == "-compile-stdlib") - { - compileStdLib = true; - } - else if (argValue == "-archive-type") + case OptionKind::CompileStdLib: compileStdLib = true; break; + case OptionKind::ArchiveType: { - CommandLineArg archiveTypeName; - SLANG_RETURN_ON_FAIL(reader.expectArg(archiveTypeName)); - - archiveType = TypeTextUtil::findArchiveType(archiveTypeName.value.getUnownedSlice()); - if (archiveType == SLANG_ARCHIVE_TYPE_UNDEFINED) - { - sink->diagnose(archiveTypeName.loc, Diagnostics::unknownArchiveType, archiveTypeName.value); - return SLANG_FAIL; - } + CommandOptions::UserValue value; + SLANG_RETURN_ON_FAIL(_expectValue(ValueCategory::ArchiveType, reader, sink, value)); + archiveType = SlangArchiveType(value); + break; } - else if (argValue == "-save-stdlib") + case OptionKind::SaveStdLib: { CommandLineArg fileName; SLANG_RETURN_ON_FAIL(reader.expectArg(fileName)); @@ -947,8 +1266,9 @@ struct OptionsParser SLANG_RETURN_ON_FAIL(session->saveStdLib(archiveType, blob.writeRef())); SLANG_RETURN_ON_FAIL(File::writeAllBytes(fileName.value, blob->getBufferPointer(), blob->getBufferSize())); + break; } - else if (argValue == "-save-stdlib-bin-source") + case OptionKind::SaveStdLibBinSource: { CommandLineArg fileName; SLANG_RETURN_ON_FAIL(reader.expectArg(fileName)); @@ -963,61 +1283,45 @@ struct OptionsParser SLANG_RETURN_ON_FAIL(HexDumpUtil::dumpSourceBytes((const uint8_t*)blob->getBufferPointer(), blob->getBufferSize(), 16, &writer)); File::writeAllText(fileName.value, builder); + break; } - else if (argValue == "-no-codegen") - { - flags |= SLANG_COMPILE_FLAG_NO_CODEGEN; - } - else if (argValue == "-dump-intermediates") - { - compileRequest->setDumpIntermediates(true); - } - else if (argValue == "-dump-ir-ids") + case OptionKind::NoCodeGen: flags |= SLANG_COMPILE_FLAG_NO_CODEGEN; break; + case OptionKind::DumpIntermediates: compileRequest->setDumpIntermediates(true); break; + case OptionKind::DumpIrIds: { - requestImpl->getFrontEndReq()->m_irDumpOptions.flags |= IRDumpOptions::Flag::DumpDebugIds; + frontEndReq->m_irDumpOptions.flags |= IRDumpOptions::Flag::DumpDebugIds; + break; } - else if (argValue == "-dump-intermediate-prefix") + case OptionKind::DumpIntermediatePrefix: { CommandLineArg prefix; SLANG_RETURN_ON_FAIL(reader.expectArg(prefix)); requestImpl->m_dumpIntermediatePrefix = prefix.value; + break; } - else if (argValue == "-output-includes") - { - requestImpl->getFrontEndReq()->outputIncludes = true; - } - else if(argValue == "-dump-ir" ) - { - requestImpl->getFrontEndReq()->shouldDumpIR = true; - } - else if (argValue == "-E" || argValue == "-output-preprocessor") - { - requestImpl->getFrontEndReq()->outputPreprocessor = true; - } - else if (argValue == "-dump-ast") - { - requestImpl->getFrontEndReq()->shouldDumpAST = true; - } - else if (argValue == "-doc") + case OptionKind::OutputIncludes: frontEndReq->outputIncludes = true; break; + case OptionKind::DumpIr: frontEndReq->shouldDumpIR = true; break; + case OptionKind::PreprocessorOutput: frontEndReq->outputPreprocessor = true; break; + case OptionKind::DumpAst: frontEndReq->shouldDumpAST = true; break; + case OptionKind::Doc: { // If compiling stdlib is enabled, will write out documentation compileStdLibFlags |= slang::CompileStdLibFlag::WriteDocumentation; // Enable writing out documentation on the req - requestImpl->getFrontEndReq()->shouldDocument = true; + frontEndReq->shouldDocument = true; + break; } - else if (argValue == "-dump-repro") + case OptionKind::DumpRepro: { CommandLineArg dumpRepro; SLANG_RETURN_ON_FAIL(reader.expectArg(dumpRepro)); requestImpl->m_dumpRepro = dumpRepro.value; compileRequest->enableReproCapture(); + break; } - else if (argValue == "-dump-repro-on-error") - { - requestImpl->m_dumpReproOnError = true; - } - else if (argValue == "-extract-repro") + case OptionKind::DumpReproOnError: requestImpl->m_dumpReproOnError = true; break; + case OptionKind::ExtractRepro: { CommandLineArg reproName; SLANG_RETURN_ON_FAIL(reader.expectArg(reproName)); @@ -1030,15 +1334,17 @@ struct OptionsParser return res; } } + break; } - else if (argValue == "-module-name") + case OptionKind::ModuleName: { CommandLineArg moduleName; SLANG_RETURN_ON_FAIL(reader.expectArg(moduleName)); compileRequest->setDefaultModuleName(moduleName.value.getBuffer()); + break; } - else if(argValue == "-load-repro") + case OptionKind::LoadRepro: { CommandLineArg reproName; SLANG_RETURN_ON_FAIL(reader.expectArg(reproName)); @@ -1072,15 +1378,17 @@ struct OptionsParser SLANG_RETURN_ON_FAIL(ReproUtil::load(base, requestState, fileSystem, requestImpl)); hasLoadedRepro = true; + break; } - else if (argValue == "-load-repro-directory") + case OptionKind::LoadReproDirectory: { CommandLineArg reproDirectory; SLANG_RETURN_ON_FAIL(reader.expectArg(reproDirectory)); SLANG_RETURN_ON_FAIL(_compileReproDirectory(session, requestImpl, reproDirectory.value, sink)); + break; } - else if (argValue == "-repro-file-system") + case OptionKind::ReproFileSystem: { CommandLineArg reproName; SLANG_RETURN_ON_FAIL(reader.expectArg(reproName)); @@ -1122,32 +1430,15 @@ struct OptionsParser // Set as the file system compileRequest->setFileSystem(fileSystem); + break; } - else if (argValue == "-serial-ir") - { - requestImpl->getFrontEndReq()->useSerialIRBottleneck = true; - } - else if (argValue == "-disable-specialization") - { - requestImpl->disableSpecialization = true; - } - else if (argValue == "-disable-dynamic-dispatch") - { - requestImpl->disableDynamicDispatch = true; - } - else if (argValue == "-track-liveness") - { - requestImpl->setTrackLiveness(true); - } - else if (argValue == "-verbose-paths") - { - requestImpl->getSink()->setFlag(DiagnosticSink::Flag::VerbosePath); - } - else if (argValue == "-dump-warning-diagnostics") - { - _dumpDiagnostics(Severity::Warning, sink); - } - else if (argValue == "-warnings-as-errors") + case OptionKind::SerialIr: frontEndReq->useSerialIRBottleneck = true; break; + case OptionKind::DisableSpecialization: requestImpl->disableSpecialization = true; break; + case OptionKind::DisableDynamicDispatch: requestImpl->disableDynamicDispatch = true; break; + case OptionKind::TrackLiveness: requestImpl->setTrackLiveness(true); break; + case OptionKind::VerbosePaths: requestImpl->getSink()->setFlag(DiagnosticSink::Flag::VerbosePath); break; + case OptionKind::DumpWarningDiagnostics: _dumpDiagnostics(Severity::Warning, sink); break; + case OptionKind::WarningsAsErrors: { CommandLineArg operand; SLANG_RETURN_ON_FAIL(reader.expectArg(operand)); @@ -1162,52 +1453,47 @@ struct OptionsParser { SLANG_RETURN_ON_FAIL(_overrideDiagnostics(operand.value.getUnownedSlice(), Severity::Warning, Severity::Error, sink)); } + break; } - else if (argValue == "-warnings-disable") + case OptionKind::DisableWarnings: { CommandLineArg operand; SLANG_RETURN_ON_FAIL(reader.expectArg(operand)); SLANG_RETURN_ON_FAIL(_overrideDiagnostics(operand.value.getUnownedSlice(), Severity::Warning, Severity::Disable, sink)); + break; } - else if (argValue.startsWith(toSlice("-W"))) - { - auto name = argValue.getUnownedSlice().tail(2); - - // If prefixed with 'no-', disable the warning - if (name.startsWith(toSlice("no-"))) - { - SLANG_RETURN_ON_FAIL(_overrideDiagnostic(name.tail(3), Severity::Warning, Severity::Disable, sink)); - } - else - { - // Enable the warning - SLANG_RETURN_ON_FAIL(_overrideDiagnostic(name, Severity::Warning, Severity::Warning, sink)); - } - } - else if (argValue == "-verify-debug-serial-ir") - { - requestImpl->getFrontEndReq()->verifyDebugSerialization = true; - } - else if(argValue == "-validate-ir" ) + case OptionKind::DisableWarning: { - requestImpl->getFrontEndReq()->shouldValidateIR = true; + // 5 because -Wno- + auto name = argValue.getUnownedSlice().tail(5); + SLANG_RETURN_ON_FAIL(_overrideDiagnostic(name, Severity::Warning, Severity::Disable, sink)); + break; } - else if(argValue == "-skip-codegen" ) + case OptionKind::EnableWarning: { - requestImpl->m_shouldSkipCodegen = true; + // 2 because -W + auto name = argValue.getUnownedSlice().tail(5); + // Enable the warning + SLANG_RETURN_ON_FAIL(_overrideDiagnostic(name, Severity::Warning, Severity::Warning, sink)); + break; } - else if(argValue == "-parameter-blocks-use-register-spaces" ) + case OptionKind::VerifyDebugSerialIr: frontEndReq->verifyDebugSerialization = true; break; + case OptionKind::ValidateIr: frontEndReq->shouldValidateIR = true; break; + case OptionKind::SkipCodeGen: requestImpl->m_shouldSkipCodegen = true; break; + case OptionKind::ParameterBlocksUseRegisterSpaces: { - getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_PARAMETER_BLOCKS_USE_REGISTER_SPACES; + getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_PARAMETER_BLOCKS_USE_REGISTER_SPACES; + break; } - else if (argValue == "-ir-compression") + case OptionKind::IrCompression: { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); SLANG_RETURN_ON_FAIL(SerialParseUtil::parseCompressionType(name.value.getUnownedSlice(), requestImpl->getLinkage()->serialCompressionType)); + break; } - else if (argValue == "-target") + case OptionKind::Target: { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); @@ -1224,12 +1510,14 @@ struct OptionsParser rawTarget.format = CodeGenTarget(format); rawTargets.add(rawTarget); + break; } - // A "profile" can specify both a general capability level for - // a target, and also (as a legacy/compatibility feature) a - // specific stage to use for an entry point. - else if (argValue == "-profile") + case OptionKind::Profile: { + // 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(reader.expectArg(operand)); @@ -1284,8 +1572,10 @@ struct OptionsParser addCapabilityAtom(getCurrentTarget(), atom); } + + break; } - else if( argValue == "-capability" ) + case OptionKind::Capability: { // The `-capability` option is similar to `-profile` but does not set the actual profile // for a target (it just adds capabilities). @@ -1313,8 +1603,9 @@ struct OptionsParser addCapabilityAtom(getCurrentTarget(), atom); } + break; } - else if (argValue == "-stage") + case OptionKind::Stage: { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); @@ -1329,8 +1620,9 @@ struct OptionsParser { setStage(getCurrentEntryPoint(), stage); } + break; } - else if (argValue == "-entry") + case OptionKind::EntryPointName: { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); @@ -1340,8 +1632,9 @@ struct OptionsParser rawEntryPoint.translationUnitIndex = currentTranslationUnitIndex; rawEntryPoints.add(rawEntryPoint); + break; } - else if (argValue == "-lang") + case OptionKind::Language: { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); @@ -1360,8 +1653,9 @@ struct OptionsParser SLANG_RETURN_ON_FAIL(addInputPath(reader.getValueAndAdvance().getBuffer(), sourceLanguage)); } } + break; } - else if (argValue == "-pass-through") + case OptionKind::PassThrough: { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); @@ -1374,8 +1668,9 @@ struct OptionsParser } compileRequest->setPassThrough(passThrough); + break; } - else if (argValue.getLength() >= 2 && argValue[1] == 'D') + case OptionKind::MacroDefine: { // The value to be defined might be part of the same option, as in: // -DFOO @@ -1396,7 +1691,7 @@ struct OptionsParser const Index equalIndex = slice.indexOf('='); // Now set the preprocessor define - + if (equalIndex >= 0) { // If we found an `=`, we split the string... @@ -1407,8 +1702,9 @@ struct OptionsParser // If there was no `=`, then just #define it to an empty string compileRequest->addPreprocessorDefine(String(slice).getBuffer(), ""); } + break; } - else if (argValue.getLength() >= 2 && argValue[1] == 'I') + case OptionKind::Include: { // The value to be defined might be part of the same option, as in: // -IFOO @@ -1426,18 +1722,19 @@ struct OptionsParser } compileRequest->addSearchPath(String(slice).getBuffer()); + break; } - // - // A `-o` option is used to specify a desired output file. - else if (argValue == "-o") + case OptionKind::Output: { + // + // A `-o` option is used to specify a desired output file. CommandLineArg outputPath; SLANG_RETURN_ON_FAIL(reader.expectArg(outputPath)); addOutputPath(outputPath.value.getBuffer()); + break; } - // A -depfile option is used to specify the file name where the dependency lists will be written - else if (argValue == "-depfile") + case OptionKind::DepFile: { CommandLineArg dependencyPath; SLANG_RETURN_ON_FAIL(reader.expectArg(dependencyPath)); @@ -1451,65 +1748,25 @@ struct OptionsParser sink->diagnose(dependencyPath.loc, Diagnostics::duplicateDependencyOutputPaths); return SLANG_FAIL; } + break; } - else if(argValue == "-matrix-layout-row-major") - { - defaultMatrixLayoutMode = SlangMatrixLayoutMode(kMatrixLayoutMode_RowMajor); - } - else if(argValue == "-matrix-layout-column-major") - { - defaultMatrixLayoutMode = SlangMatrixLayoutMode(kMatrixLayoutMode_ColumnMajor); - } - else if(argValue == "-line-directive-mode") + case OptionKind::MatrixLayoutRow: defaultMatrixLayoutMode = SlangMatrixLayoutMode(kMatrixLayoutMode_RowMajor); break; + case OptionKind::MatrixLayoutColumn: defaultMatrixLayoutMode = SlangMatrixLayoutMode(kMatrixLayoutMode_ColumnMajor); break; + case OptionKind::LineDirectiveMode: { - CommandLineArg name; - SLANG_RETURN_ON_FAIL(reader.expectArg(name)); - - SlangLineDirectiveMode mode = SLANG_LINE_DIRECTIVE_MODE_DEFAULT; - - if(name.value == toSlice("none")) - { - mode = SLANG_LINE_DIRECTIVE_MODE_NONE; - } - else if (name.value == toSlice("source-map")) - { - mode = SLANG_LINE_DIRECTIVE_MODE_SOURCE_MAP; - } - else if (name.value == toSlice("default")) - { - mode = SLANG_LINE_DIRECTIVE_MODE_DEFAULT; - } - else - { - sink->diagnose(name.loc, Diagnostics::unknownLineDirectiveMode, name.value); - return SLANG_FAIL; - } - - compileRequest->setLineDirectiveMode(mode); + CommandOptions::UserValue value; + SLANG_RETURN_ON_FAIL(_expectValue(ValueCategory::LineDirectiveMode, reader, sink, value)); + compileRequest->setLineDirectiveMode(SlangLineDirectiveMode(value)); + break; } - else if( argValue == "-fp-mode" || argValue == "-floating-point-mode" ) + case OptionKind::FloatingPointMode: { - CommandLineArg name; - SLANG_RETURN_ON_FAIL(reader.expectArg(name)); - - FloatingPointMode mode = FloatingPointMode::Default; - if(name.value == "fast") - { - mode = FloatingPointMode::Fast; - } - else if(name.value == "precise") - { - mode = FloatingPointMode::Precise; - } - else - { - sink->diagnose(name.loc, Diagnostics::unknownFloatingPointMode, name.value); - return SLANG_FAIL; - } - - setFloatingPointMode(getCurrentTarget(), mode); + CommandOptions::UserValue value; + SLANG_RETURN_ON_FAIL(_expectValue(ValueCategory::FloatingPointMode, reader, sink, value)); + setFloatingPointMode(getCurrentTarget(), FloatingPointMode(value)); + break; } - else if( argValue.getLength() >= 2 && argValue[1] == 'O' ) + case OptionKind::Optimization: { UnownedStringSlice levelSlice = argValue.getUnownedSlice().tail(2); SlangOptimizationLevel level = SLANG_OPTIMIZATION_LEVEL_DEFAULT; @@ -1530,12 +1787,13 @@ struct OptionsParser } compileRequest->setOptimizationLevel(level); + break; } - // Note: unlike with `-O` above, we have to consider that other - // options might have names that start with `-g` and so cannot - // just detect it as a prefix. - else if (argValue.startsWith("-g")) + case OptionKind::DebugInformation: { + // 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 (argValue == toSlice("-g")) { // The default is standard @@ -1594,16 +1852,11 @@ struct OptionsParser compileRequest->setDebugInfoFormat(format); } + break; } - else if( argValue == "-default-image-format-unknown" ) - { - requestImpl->useUnknownImageFormatAsDefault = true; - } - else if (argValue == "-obfuscate") - { - requestImpl->getLinkage()->m_obfuscateCode = true; - } - else if (argValue == "-file-system") + case OptionKind::DefaultImageFormatUnknown: requestImpl->useUnknownImageFormatAsDefault = true; break; + case OptionKind::Obfuscate: requestImpl->getLinkage()->m_obfuscateCode = true; break; + case OptionKind::FileSystem: { CommandLineArg name; SLANG_RETURN_ON_FAIL(reader.expectArg(name)); @@ -1627,8 +1880,9 @@ struct OptionsParser sink->diagnose(name.loc, Diagnostics::unknownFileSystemOption, name.value); return SLANG_FAIL; } + break; } - else if (argValue == "-r") + case OptionKind::ReferenceModule: { CommandLineArg referenceModuleName; SLANG_RETURN_ON_FAIL(reader.expectArg(referenceModuleName)); @@ -1689,21 +1943,50 @@ struct OptionsParser artifact->addRepresentation(fileRep); SLANG_RETURN_ON_FAIL(_addLibraryReference(requestImpl, artifact)); + break; } - else if (argValue == "-v" || argValue == "-version") + case OptionKind::Version: { sink->diagnoseRaw(Severity::Note, session->getBuildTagString()); + break; } - else if (argValue == "-h" || argValue == "-help" || argValue == "--help") + case OptionKind::Help: { - sink->diagnoseRaw(Severity::Note, getHelpText()); + + Index categoryIndex = -1; + + if (reader.hasArg()) + { + auto catArg = reader.getArgAndAdvance(); + + categoryIndex = options.findCategoryByCaseInsensitiveName(catArg.value.getUnownedSlice()); + if (categoryIndex) + { + sink->diagnose(catArg.loc, Diagnostics::unknownHelpCategory); + return SLANG_FAIL; + } + } + + CommandOptionsWriter writer; + auto& buf = writer.getBuilder(); + + if (categoryIndex < 0) + { + _appendUsageTitle(buf); + writer.appendDescription(options); + } + else + { + writer.appendDescriptionForCategory(options, categoryIndex); + } + + sink->diagnoseRaw(Severity::Note, buf.getBuffer()); + return SLANG_FAIL; } - else if( argValue == "-emit-spirv-directly" ) - { - getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY; - } - else if (argValue == "-default-downstream-compiler") + case OptionKind::EmitSpirvDirectly: getCurrentTarget()->targetFlags |= SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY; break; + + case OptionKind::DefaultDownstreamCompiler: { CommandLineArg sourceLanguageArg, compilerArg; SLANG_RETURN_ON_FAIL(reader.expectArg(sourceLanguageArg)); @@ -1728,8 +2011,34 @@ struct OptionsParser sink->diagnose(arg.loc, Diagnostics::unableToSetDefaultDownstreamCompiler, compilerArg.value, sourceLanguageArg.value); return SLANG_FAIL; } + break; } - else if (argValue == "--") + case OptionKind::CompilerPath: + { + const Index index = argValue.lastIndexOf('-'); + if (index >= 0) + { + CommandLineArg name; + SLANG_RETURN_ON_FAIL(reader.expectArg(name)); + + UnownedStringSlice passThroughSlice = argValue.getUnownedSlice().head(index).tail(1); + + // Skip the initial -, up to the last - + SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE; + if (SLANG_SUCCEEDED(TypeTextUtil::findPassThrough(passThroughSlice, passThrough))) + { + session->setDownstreamCompilerPath(passThrough, name.value.getBuffer()); + continue; + } + else + { + sink->diagnose(arg.loc, Diagnostics::unknownDownstreamCompiler, passThroughSlice); + return SLANG_FAIL; + } + } + 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: @@ -1738,43 +2047,16 @@ struct OptionsParser SLANG_RETURN_ON_FAIL(addInputPath(reader.getValueAndAdvance().getBuffer())); } break; - } - else + } + default: { - if (argValue.endsWith("-path")) - { - const Index index = argValue.lastIndexOf('-'); - if (index >= 0) - { - CommandLineArg name; - SLANG_RETURN_ON_FAIL(reader.expectArg(name)); - - UnownedStringSlice passThroughSlice = argValue.getUnownedSlice().head(index).tail(1); - - // Skip the initial -, up to the last - - SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE; - if (SLANG_SUCCEEDED(TypeTextUtil::findPassThrough(passThroughSlice, passThrough))) - { - session->setDownstreamCompilerPath(passThrough, name.value.getBuffer()); - continue; - } - else - { - sink->diagnose(arg.loc, Diagnostics::unknownDownstreamCompiler, passThroughSlice); - return SLANG_FAIL; - } - } - } - + // Hmmm, we looked up and produced a valid enum, but it wasn't handled in the switch... sink->diagnose(arg.loc, Diagnostics::unknownCommandLineOption, argValue); - // TODO: print a usage message + + _outputMinimalUsage(sink); return SLANG_FAIL; } } - else - { - SLANG_RETURN_ON_FAIL(addInputPath(argValue.getBuffer())); - } } if (compileStdLib) @@ -2371,7 +2653,6 @@ struct OptionsParser } }; - SlangResult parseOptions( SlangCompileRequest* inCompileRequest, int argc, diff --git a/source/slang/slang-options.h b/source/slang/slang-options.h index 2426c08cb..bb05312a3 100644 --- a/source/slang/slang-options.h +++ b/source/slang/slang-options.h @@ -7,6 +7,7 @@ namespace Slang { +struct CommandOptions; UnownedStringSlice getCodeGenTargetName(SlangCompileTarget target); @@ -15,5 +16,8 @@ SlangResult parseOptions( int argc, char const* const* argv); +// Initialize command options. Holds the details how parsing works. +void initCommandOptions(CommandOptions& commandOptions); + } #endif diff --git a/source/slang/slang-profile.h b/source/slang/slang-profile.h index 661274ad4..0adb9dee9 100644 --- a/source/slang/slang-profile.h +++ b/source/slang/slang-profile.h @@ -50,6 +50,7 @@ namespace Slang #define PROFILE_STAGE(TAG, NAME, VAL) TAG = VAL, #define PROFILE_STAGE_ALIAS(TAG, NAME, VAL) TAG = VAL, #include "slang-profile-defs.h" + }; const char* getStageName(Stage stage); @@ -110,7 +111,14 @@ namespace Slang RawVal raw = Unknown; }; + struct StageInfo + { + const char* name; + Stage stage; + }; + /// Note! There can be multiple stages with the same name + ConstArrayView<StageInfo> getStageInfos(); Stage findStageByName(String const& name); diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 8aea1bc37..9c75aa63e 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -132,6 +132,7 @@ void Session::init() { SLANG_ASSERT(BaseTypeInfo::check()); + _initCodeGenTransitionMap(); ::memset(m_downstreamCompilerLocators, 0, sizeof(m_downstreamCompilerLocators)); @@ -144,6 +145,9 @@ void Session::init() m_sharedLibraryLoader = DefaultSharedLibraryLoader::getSingleton(); + // Set up the command line options + initCommandOptions(m_commandOptions); + // Set up shared AST builder m_sharedASTBuilder = new SharedASTBuilder; m_sharedASTBuilder->init(this); |
