summaryrefslogtreecommitdiffstats
path: root/source/compiler-core
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2021-04-01 13:39:11 -0400
committerGitHub <noreply@github.com>2021-04-01 10:39:11 -0700
commitfa31d21ba92669a521a7768467246918e3947e02 (patch)
treeaf98a593e24bc6309ac4d11a59562be4b22c93d7 /source/compiler-core
parent3f1632a1450a5879f337b4bd178e48880cd583f8 (diff)
Added compiler-core project (#1775)
* #include an absolute path didn't work - because paths were taken to always be relative. * Split out compiler-core initially with just slang-source-loc.cpp * More lexer, name, token to compiler-core. * Split Lexer and Core diagnostics. * Move slang-file-system to core. * Add slang-file-system to core. * More DownstreamCompiler into compiler-core * Fix typo. * Add compiler-core to bootstrap proj. * Small fixes to premake * For linux try with compiler-core * Remove compiler-core from examples. * Added NameConventionUtil to compiler-core * Add global function to CharUtil to *hopefully* avoid linking issue. * Hack to make linkage of CharUtil work on linux.
Diffstat (limited to 'source/compiler-core')
-rw-r--r--source/compiler-core/slang-core-diagnostics.cpp50
-rw-r--r--source/compiler-core/slang-core-diagnostics.h32
-rw-r--r--source/compiler-core/slang-diagnostic-sink.cpp537
-rw-r--r--source/compiler-core/slang-diagnostic-sink.h316
-rw-r--r--source/compiler-core/slang-downstream-compiler.cpp716
-rw-r--r--source/compiler-core/slang-downstream-compiler.h501
-rw-r--r--source/compiler-core/slang-gcc-compiler-util.cpp645
-rw-r--r--source/compiler-core/slang-gcc-compiler-util.h56
-rw-r--r--source/compiler-core/slang-lexer-diagnostic-defs.h33
-rw-r--r--source/compiler-core/slang-lexer.cpp1400
-rw-r--r--source/compiler-core/slang-lexer.h164
-rw-r--r--source/compiler-core/slang-misc-diagnostic-defs.h27
-rw-r--r--source/compiler-core/slang-name-convention-util.cpp213
-rw-r--r--source/compiler-core/slang-name-convention-util.h54
-rw-r--r--source/compiler-core/slang-name.cpp42
-rw-r--r--source/compiler-core/slang-name.h89
-rw-r--r--source/compiler-core/slang-nvrtc-compiler.cpp773
-rw-r--r--source/compiler-core/slang-nvrtc-compiler.h19
-rw-r--r--source/compiler-core/slang-source-loc.cpp689
-rw-r--r--source/compiler-core/slang-source-loc.h487
-rw-r--r--source/compiler-core/slang-token-defs.h96
-rw-r--r--source/compiler-core/slang-token.cpp21
-rw-r--r--source/compiler-core/slang-token.h140
-rw-r--r--source/compiler-core/slang-visual-studio-compiler-util.cpp452
-rw-r--r--source/compiler-core/slang-visual-studio-compiler-util.h42
-rw-r--r--source/compiler-core/windows/slang-win-visual-studio-util.cpp366
-rw-r--r--source/compiler-core/windows/slang-win-visual-studio-util.h68
27 files changed, 8028 insertions, 0 deletions
diff --git a/source/compiler-core/slang-core-diagnostics.cpp b/source/compiler-core/slang-core-diagnostics.cpp
new file mode 100644
index 000000000..7aa6c5c14
--- /dev/null
+++ b/source/compiler-core/slang-core-diagnostics.cpp
@@ -0,0 +1,50 @@
+// slang-core-diagnostics.cpp
+#include "slang-core-diagnostics.h"
+
+namespace Slang {
+
+namespace MiscDiagnostics
+{
+#define DIAGNOSTIC(id, severity, name, messageFormat) const DiagnosticInfo name = { id, Severity::severity, #name, messageFormat };
+#include "slang-misc-diagnostic-defs.h"
+#undef DIAGNOSTIC
+}
+
+static const DiagnosticInfo* const kMiscDiagnostics[] =
+{
+#define DIAGNOSTIC(id, severity, name, messageFormat) &MiscDiagnostics::name,
+#include "slang-misc-diagnostic-defs.h"
+#undef DIAGNOSTIC
+};
+
+
+namespace LexerDiagnostics
+{
+#define DIAGNOSTIC(id, severity, name, messageFormat) const DiagnosticInfo name = { id, Severity::severity, #name, messageFormat };
+#include "slang-lexer-diagnostic-defs.h"
+#undef DIAGNOSTIC
+}
+
+static const DiagnosticInfo* const kLexerDiagnostics[] =
+{
+#define DIAGNOSTIC(id, severity, name, messageFormat) &LexerDiagnostics::name,
+#include "slang-lexer-diagnostic-defs.h"
+#undef DIAGNOSTIC
+};
+
+static DiagnosticsLookup* _newCoreDiagnosticsLookup()
+{
+ auto lookup = new DiagnosticsLookup;
+ lookup->add(kMiscDiagnostics, SLANG_COUNT_OF(kMiscDiagnostics));
+ lookup->add(kLexerDiagnostics, SLANG_COUNT_OF(kLexerDiagnostics));
+
+ return lookup;
+}
+
+DiagnosticsLookup* getCoreDiagnosticsLookup()
+{
+ static RefPtr<DiagnosticsLookup> s_lookup = _newCoreDiagnosticsLookup();
+ return s_lookup;
+}
+
+} // namespace Slang
diff --git a/source/compiler-core/slang-core-diagnostics.h b/source/compiler-core/slang-core-diagnostics.h
new file mode 100644
index 000000000..2d56591fd
--- /dev/null
+++ b/source/compiler-core/slang-core-diagnostics.h
@@ -0,0 +1,32 @@
+#ifndef SLANG_CORE_DIAGNOSTICS_H
+#define SLANG_CORE_DIAGNOSTICS_H
+
+#include "../core/slang-basic.h"
+#include "../core/slang-writer.h"
+
+#include "slang-source-loc.h"
+#include "slang-diagnostic-sink.h"
+#include "slang-token.h"
+
+#include "../../slang.h"
+
+namespace Slang
+{
+
+DiagnosticsLookup* getCoreDiagnosticsLookup();
+
+namespace MiscDiagnostics
+{
+#define DIAGNOSTIC(id, severity, name, messageFormat) extern const DiagnosticInfo name;
+#include "slang-misc-diagnostic-defs.h"
+}
+
+namespace LexerDiagnostics
+{
+#define DIAGNOSTIC(id, severity, name, messageFormat) extern const DiagnosticInfo name;
+#include "slang-lexer-diagnostic-defs.h"
+}
+
+}
+
+#endif
diff --git a/source/compiler-core/slang-diagnostic-sink.cpp b/source/compiler-core/slang-diagnostic-sink.cpp
new file mode 100644
index 000000000..2adf31f69
--- /dev/null
+++ b/source/compiler-core/slang-diagnostic-sink.cpp
@@ -0,0 +1,537 @@
+// slang-diagnostic-sink.cpp
+#include "slang-diagnostic-sink.h"
+
+#include "slang-name.h"
+#include "slang-core-diagnostics.h"
+#include "slang-name-convention-util.h"
+
+#include "../core/slang-memory-arena.h"
+#include "../core/slang-dictionary.h"
+#include "../core/slang-string-util.h"
+#include "../core/slang-char-util.h"
+
+namespace Slang {
+
+void printDiagnosticArg(StringBuilder& sb, char const* str)
+{
+ sb << str;
+}
+
+void printDiagnosticArg(StringBuilder& sb, int32_t val)
+{
+ sb << val;
+}
+
+void printDiagnosticArg(StringBuilder& sb, uint32_t val)
+{
+ sb << val;
+}
+
+void printDiagnosticArg(StringBuilder& sb, int64_t val)
+{
+ sb << val;
+}
+
+void printDiagnosticArg(StringBuilder& sb, uint64_t val)
+{
+ sb << val;
+}
+
+void printDiagnosticArg(StringBuilder& sb, double val)
+{
+ sb << val;
+}
+
+void printDiagnosticArg(StringBuilder& sb, Slang::String const& str)
+{
+ sb << str;
+}
+
+void printDiagnosticArg(StringBuilder& sb, Slang::UnownedStringSlice const& str)
+{
+ sb.append(str);
+}
+
+
+void printDiagnosticArg(StringBuilder& sb, Name* name)
+{
+ sb << getText(name);
+}
+
+
+void printDiagnosticArg(StringBuilder& sb, TokenType tokenType)
+{
+ sb << TokenTypeToString(tokenType);
+}
+
+void printDiagnosticArg(StringBuilder& sb, Token const& token)
+{
+ sb << token.getContent();
+}
+
+SourceLoc const& getDiagnosticPos(Token const& token)
+{
+ return token.loc;
+}
+
+// Take the format string for a diagnostic message, along with its arguments, and turn it into a
+static void formatDiagnosticMessage(StringBuilder& sb, char const* format, int argCount, DiagnosticArg const* const* args)
+{
+ char const* spanBegin = format;
+ for(;;)
+ {
+ char const* spanEnd = spanBegin;
+ while (int c = *spanEnd)
+ {
+ if (c == '$')
+ break;
+ spanEnd++;
+ }
+
+ sb.Append(spanBegin, int(spanEnd - spanBegin));
+ if (!*spanEnd)
+ return;
+
+ SLANG_ASSERT(*spanEnd == '$');
+ spanEnd++;
+ int d = *spanEnd++;
+ switch (d)
+ {
+ // A double dollar sign `$$` is used to emit a single `$`
+ case '$':
+ sb.Append('$');
+ break;
+
+ // A single digit means to emit the corresponding argument.
+ // TODO: support more than 10 arguments, and add options
+ // to control formatting, etc.
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ {
+ int index = d - '0';
+ if (index >= argCount)
+ {
+ // TODO(tfoley): figure out what a good policy will be for "panic" situations like this
+ throw InvalidOperationException("too few arguments for diagnostic message");
+ }
+ else
+ {
+ DiagnosticArg const* arg = args[index];
+ arg->printFunc(sb, arg->data);
+ }
+ }
+ break;
+
+ default:
+ throw InvalidOperationException("invalid diagnostic message format");
+ break;
+ }
+
+ spanBegin = spanEnd;
+ }
+}
+
+static void formatDiagnostic(const HumaneSourceLoc& humaneLoc, Diagnostic const& diagnostic, StringBuilder& outBuilder)
+{
+ outBuilder << humaneLoc.pathInfo.foundPath;
+ outBuilder << "(";
+ outBuilder << Int32(humaneLoc.line);
+ outBuilder << "): ";
+
+ outBuilder << getSeverityName(diagnostic.severity);
+
+ if (diagnostic.ErrorID >= 0)
+ {
+ outBuilder << " ";
+ outBuilder << diagnostic.ErrorID;
+ }
+
+ outBuilder << ": ";
+ outBuilder << diagnostic.Message;
+ outBuilder << "\n";
+}
+
+static void _replaceTabWithSpaces(const UnownedStringSlice& slice, Int tabSize, StringBuilder& out)
+{
+ const char* start = slice.begin();
+ const char*const end = slice.end();
+
+ const Index startLength = out.getLength();
+
+ for (const char* cur = start; cur < end; cur++)
+ {
+ if (*cur == '\t')
+ {
+ if (start < cur)
+ {
+ out.append(start, cur);
+ }
+
+ // The amount of spaces we add depends on the current position.
+ const Index lastPosition = out.getLength() - startLength;
+ Index tabPosition = lastPosition;
+
+ // Strip the tabPosition so it's back to the tab stop
+ // Special case if tabSize is a power of 2
+ if ((tabSize & (tabSize - 1)) == 0)
+ {
+ tabPosition = tabPosition & ~Index(tabSize - 1);
+ }
+ else
+ {
+ tabPosition -= tabPosition % tabSize;
+ }
+
+ // Move to next tab
+ tabPosition += tabSize;
+
+ // The amount of spaces to simulate the tab
+ const Index spacesCount = tabPosition - lastPosition;
+
+ // Add the spaces
+ out.appendRepeatedChar(' ', spacesCount);
+
+ // Set the start at the first character past
+ start = cur + 1;
+ }
+ }
+
+ if (start < end)
+ {
+ out.append(start, end);
+ }
+}
+
+// Given multi-line text, and a position within the text (as a pointer into the memory of text)
+// extract the line that contains pos
+static UnownedStringSlice _extractLineContainingPosition(const UnownedStringSlice& text, const char* pos)
+{
+ SLANG_ASSERT(text.isMemoryContained(pos));
+
+ const char*const contentStart = text.begin();
+ const char*const contentEnd = text.end();
+
+ // We want to determine the start of the line, and the end of the line
+ const char* start = pos;
+ for (; start > contentStart; --start)
+ {
+ const char c = *start;
+ if (c == '\n' || c == '\r')
+ {
+ // We want the character after, but we can only do this if not already at pos
+ start += int(start < pos);
+ break;
+ }
+ }
+ const char* end = pos;
+ for (; end < contentEnd; ++end)
+ {
+ const char c = *end;
+ if (c == '\n' || c == '\r')
+ {
+ break;
+ }
+ }
+
+ return UnownedStringSlice(start, end);
+}
+
+static void _sourceLocationNoteDiagnostic(SourceView* sourceView, SourceLoc sourceLoc, DiagnosticSink::SourceLocationLexer lexer, StringBuilder& sb)
+{
+ SourceFile* sourceFile = sourceView->getSourceFile();
+ if (!sourceFile)
+ {
+ return;
+ }
+
+ UnownedStringSlice content = sourceFile->getContent();
+
+ // Make sure the offset is within content.
+ // This is important because it's possible to have a 'SourceFile' that doesn't contain any content
+ // (for example when reconstructed via serialization with just line offsets, the actual source text 'content' isn't available).
+ const int offset = sourceView->getRange().getOffset(sourceLoc);
+ if (offset < 0 || offset >= content.getLength())
+ {
+ return;
+ }
+
+ // Work out the position of the SourceLoc in the source
+ const char*const pos = content.begin() + offset;
+
+ UnownedStringSlice line = _extractLineContainingPosition(content, pos);
+
+ // Trim any trailing white space
+ line = UnownedStringSlice(line.begin(), line.trim().end());
+
+ // TODO(JS): The tab size should ideally be configurable from command line.
+ // For now just go with 4.
+ const Index tabSize = 4;
+
+ StringBuilder sourceLine;
+ StringBuilder caretLine;
+
+ // First work out the sourceLine
+ _replaceTabWithSpaces(line, tabSize, sourceLine);
+
+ // Now the caretLine which appears underneath the sourceLine
+ {
+ // Produce the text up to the caret position (at pos), taking into account tabs
+ _replaceTabWithSpaces(UnownedStringSlice(line.begin(), pos), tabSize, caretLine);
+
+ // Now make all spaces
+ const Index length = caretLine.getLength();
+ caretLine.Clear();
+ caretLine.appendRepeatedChar(' ', length);
+
+ // Add caret
+ caretLine << "^";
+
+ if (lexer)
+ {
+ UnownedStringSlice token = lexer(UnownedStringSlice(pos, line.end()));
+
+ if (token.getLength() > 1)
+ {
+ caretLine.appendRepeatedChar('~', token.getLength() - 1);
+ }
+ }
+ }
+
+ // We could have handling here for if the line is too long, that we surround the important section
+ // will ellipsis for example.
+ // For now we just output.
+
+ sb << sourceLine << "\n";
+ sb << caretLine << "\n";
+}
+
+
+static void formatDiagnostic(
+ DiagnosticSink* sink,
+ Diagnostic const& diagnostic,
+ StringBuilder& sb)
+{
+ auto sourceManager = sink->getSourceManager();
+
+ SourceView* sourceView = nullptr;
+ HumaneSourceLoc humaneLoc;
+ const auto sourceLoc = diagnostic.loc;
+ {
+ sourceView = sourceManager->findSourceViewRecursively(sourceLoc);
+ if (sourceView)
+ {
+ humaneLoc = sourceView->getHumaneLoc(sourceLoc);
+ }
+ formatDiagnostic(humaneLoc, diagnostic, sb);
+
+ {
+ SourceView* currentView = sourceView;
+
+ while (currentView && currentView->getInitiatingSourceLoc().isValid() && currentView->getSourceFile()->getPathInfo().type == PathInfo::Type::TokenPaste)
+ {
+ SourceView* initiatingView = sourceManager->findSourceView(currentView->getInitiatingSourceLoc());
+ if (initiatingView == nullptr)
+ {
+ break;
+ }
+
+ const DiagnosticInfo& diagnosticInfo = MiscDiagnostics::seeTokenPasteLocation;
+
+ // Turn the message format into a message. For the moment it assumes no parameters.
+ StringBuilder msg;
+ formatDiagnosticMessage(msg, diagnosticInfo.messageFormat, 0, nullptr);
+
+ // Set up the diagnostic.
+ Diagnostic initiationDiagnostic;
+ initiationDiagnostic.ErrorID = diagnosticInfo.id;
+ initiationDiagnostic.Message = msg.ProduceString();
+ initiationDiagnostic.loc = sourceView->getInitiatingSourceLoc();
+ initiationDiagnostic.severity = diagnosticInfo.severity;
+
+ // TODO(JS):
+ // Not 100% clear what the best sourceLoc type is most useful here - we will go with default for now
+ HumaneSourceLoc pasteHumaneLoc = initiatingView->getHumaneLoc(sourceView->getInitiatingSourceLoc());
+
+ // Okay we should output where the token paste took place
+ formatDiagnostic(pasteHumaneLoc, initiationDiagnostic, sb);
+
+ // Make the initiatingView the current view
+ currentView = initiatingView;
+ }
+ }
+ }
+
+ // We don't don't output source line information if this is a 'note' as a note is extra information for one
+ // of the other main severity types, and so the information should already be output on the initial line
+ if (sourceView && sink->isFlagSet(DiagnosticSink::Flag::SourceLocationLine) && diagnostic.severity != Severity::Note)
+ {
+ _sourceLocationNoteDiagnostic(sourceView, sourceLoc, sink->getSourceLocationLexer(), sb);
+ }
+
+ if (sourceView && sink->isFlagSet(DiagnosticSink::Flag::VerbosePath))
+ {
+ auto actualHumaneLoc = sourceView->getHumaneLoc(diagnostic.loc, SourceLocType::Actual);
+
+ // Look up the path verbosely (will get the canonical path if necessary)
+ actualHumaneLoc.pathInfo.foundPath = sourceView->getSourceFile()->calcVerbosePath();
+
+ // Only output if it's actually different
+ if (actualHumaneLoc.pathInfo.foundPath != humaneLoc.pathInfo.foundPath ||
+ actualHumaneLoc.line != humaneLoc.line ||
+ actualHumaneLoc.column != humaneLoc.column)
+ {
+ formatDiagnostic(actualHumaneLoc, diagnostic, sb);
+ }
+ }
+}
+
+void DiagnosticSink::diagnoseImpl(SourceLoc const& pos, DiagnosticInfo const& info, int argCount, DiagnosticArg const* const* args)
+{
+ StringBuilder sb;
+ formatDiagnosticMessage(sb, info.messageFormat, argCount, args);
+
+ Diagnostic diagnostic;
+ diagnostic.ErrorID = info.id;
+ diagnostic.Message = sb.ProduceString();
+ diagnostic.loc = pos;
+ diagnostic.severity = info.severity;
+
+ if (diagnostic.severity >= Severity::Error)
+ {
+ m_errorCount++;
+ }
+
+ // Did the client supply a callback for us to use?
+ if( writer )
+ {
+ // If so, pass the error string along to them
+ StringBuilder messageBuilder;
+ formatDiagnostic(this, diagnostic, messageBuilder);
+
+ writer->write(messageBuilder.getBuffer(), messageBuilder.getLength());
+ }
+ else
+ {
+ // If the user doesn't have a callback, then just
+ // collect our diagnostic messages into a buffer
+ formatDiagnostic(this, diagnostic, outputBuffer);
+ }
+
+ if (diagnostic.severity >= Severity::Fatal)
+ {
+ // TODO: figure out a better policy for aborting compilation
+ throw AbortCompilationException();
+ }
+}
+
+void DiagnosticSink::diagnoseRaw(
+ Severity severity,
+ char const* message)
+{
+ return diagnoseRaw(severity, UnownedStringSlice(message));
+}
+
+void DiagnosticSink::diagnoseRaw(
+ Severity severity,
+ const UnownedStringSlice& message)
+{
+ if (severity >= Severity::Error)
+ {
+ m_errorCount++;
+ }
+
+ // Did the client supply a callback for us to use?
+ if(writer)
+ {
+ // If so, pass the error string along to them
+ writer->write(message.begin(), message.getLength());
+ }
+ else
+ {
+ // If the user doesn't have a callback, then just
+ // collect our diagnostic messages into a buffer
+ outputBuffer.append(message);
+ }
+
+ if (severity >= Severity::Fatal)
+ {
+ // TODO: figure out a better policy for aborting compilation
+ throw InvalidOperationException();
+ }
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DiagnosticLookup !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+void DiagnosticsLookup::_add(const char* name, Index index)
+{
+ UnownedStringSlice nameSlice(name);
+ m_map.Add(nameSlice, index);
+
+ // Add a dashed version (KababCase)
+ {
+ m_work.Clear();
+
+ NameConventionUtil::convert(NameConvention::Camel, nameSlice, CharCase::Lower, NameConvention::Kabab, m_work);
+
+ UnownedStringSlice dashSlice(m_arena.allocateString(m_work.getBuffer(), m_work.getLength()), m_work.getLength());
+
+ m_map.AddIfNotExists(dashSlice, index);
+ }
+}
+void DiagnosticsLookup::addAlias(const char* name, const char* diagnosticName)
+{
+ const Index index = _findDiagnosticIndex(UnownedStringSlice(diagnosticName));
+ SLANG_ASSERT(index >= 0);
+ if (index >= 0)
+ {
+ _add(name, index);
+ }
+}
+
+Index DiagnosticsLookup::add(const DiagnosticInfo* info)
+{
+ // Check it's not already added
+ SLANG_ASSERT(m_diagnostics.indexOf(info) < 0);
+
+ const Index index = m_diagnostics.getCount();
+
+ _add(info->name, index);
+
+ m_diagnostics.add(info);
+ return index;
+}
+
+void DiagnosticsLookup::add(const DiagnosticInfo*const* infos, Index infosCount)
+{
+ for (Index i = 0; i < infosCount; ++i)
+ {
+ add(infos[i]);
+ }
+}
+
+DiagnosticsLookup::DiagnosticsLookup():
+ m_arena(kArenaInitialSize)
+{
+}
+
+DiagnosticsLookup::DiagnosticsLookup(const DiagnosticInfo*const* diagnostics, Index diagnosticsCount) :
+ m_arena(kArenaInitialSize)
+{
+ m_diagnostics.addRange(diagnostics, diagnosticsCount);
+
+ // TODO: We should eventually have a more formal system for associating individual
+ // diagnostics, or groups of diagnostics, with user-exposed names for use when
+ // enabling/disabling warnings (or turning warnings into errors, etc.).
+ //
+ // For now we build a map from diagnostic name to it's entry. Two entries are typically
+ // added - the 'original name' as associated with the diagnostic in lowerCamel, and
+ // a dashified version.
+
+ for (Index i = 0; i < diagnosticsCount; ++i)
+ {
+ const DiagnosticInfo* diagnostic = diagnostics[i];
+ _add(diagnostic->name, i);
+ }
+}
+
+} // namespace Slang
diff --git a/source/compiler-core/slang-diagnostic-sink.h b/source/compiler-core/slang-diagnostic-sink.h
new file mode 100644
index 000000000..84001e6f5
--- /dev/null
+++ b/source/compiler-core/slang-diagnostic-sink.h
@@ -0,0 +1,316 @@
+#ifndef SLANG_DIAGNOSTIC_SINK_H
+#define SLANG_DIAGNOSTIC_SINK_H
+
+#include "../core/slang-basic.h"
+#include "../core/slang-writer.h"
+#include "../core/slang-memory-arena.h"
+
+#include "slang-source-loc.h"
+#include "slang-token.h"
+
+#include "../../slang.h"
+
+namespace Slang
+{
+
+enum class Severity
+{
+ Note,
+ Warning,
+ Error,
+ Fatal,
+ Internal,
+};
+
+// TODO(tfoley): move this into a source file...
+inline const char* getSeverityName(Severity severity)
+{
+ switch (severity)
+ {
+ case Severity::Note: return "note";
+ case Severity::Warning: return "warning";
+ case Severity::Error: return "error";
+ case Severity::Fatal: return "fatal error";
+ case Severity::Internal: return "internal error";
+ default: return "unknown error";
+ }
+}
+
+// A structure to be used in static data describing different
+// diagnostic messages.
+struct DiagnosticInfo
+{
+ int id;
+ Severity severity;
+ char const* name; ///< Unique name
+ char const* messageFormat;
+};
+
+class Diagnostic
+{
+public:
+ String Message;
+ SourceLoc loc;
+ int ErrorID;
+ Severity severity;
+
+ Diagnostic()
+ {
+ ErrorID = -1;
+ }
+ Diagnostic(
+ const String & msg,
+ int id,
+ const SourceLoc & pos,
+ Severity severity)
+ : severity(severity)
+ {
+ Message = msg;
+ ErrorID = id;
+ loc = pos;
+ }
+};
+
+class Name;
+
+void printDiagnosticArg(StringBuilder& sb, char const* str);
+
+void printDiagnosticArg(StringBuilder& sb, int32_t val);
+void printDiagnosticArg(StringBuilder& sb, uint32_t val);
+
+void printDiagnosticArg(StringBuilder& sb, int64_t val);
+void printDiagnosticArg(StringBuilder& sb, uint64_t val);
+
+void printDiagnosticArg(StringBuilder& sb, double val);
+
+void printDiagnosticArg(StringBuilder& sb, Slang::String const& str);
+void printDiagnosticArg(StringBuilder& sb, Slang::UnownedStringSlice const& str);
+void printDiagnosticArg(StringBuilder& sb, Name* name);
+
+void printDiagnosticArg(StringBuilder& sb, TokenType tokenType);
+void printDiagnosticArg(StringBuilder& sb, Token const& token);
+
+struct IRInst;
+void printDiagnosticArg(StringBuilder& sb, IRInst* irObject);
+
+template<typename T>
+void printDiagnosticArg(StringBuilder& sb, RefPtr<T> ptr)
+{
+ printDiagnosticArg(sb, ptr.Ptr());
+}
+
+inline SourceLoc const& getDiagnosticPos(SourceLoc const& pos) { return pos; }
+
+SourceLoc const& getDiagnosticPos(Token const& token);
+
+
+template<typename T>
+SourceLoc getDiagnosticPos(RefPtr<T> const& ptr)
+{
+ return getDiagnosticPos(ptr.Ptr());
+}
+
+struct DiagnosticArg
+{
+ void* data;
+ void (*printFunc)(StringBuilder&, void*);
+
+ template<typename T>
+ struct Helper
+ {
+ static void printFunc(StringBuilder& sb, void* data) { printDiagnosticArg(sb, *(T*)data); }
+ };
+
+ template<typename T>
+ DiagnosticArg(T const& arg)
+ : data((void*)&arg)
+ , printFunc(&Helper<T>::printFunc)
+ {}
+};
+
+
+class DiagnosticSink
+{
+public:
+ /// Flags to control some aspects of Diagnostic sink behavior
+ typedef uint32_t Flags;
+ struct Flag
+ {
+ enum Enum: Flags
+ {
+ VerbosePath = 0x1, ///< Will display a more verbose path (if available) - such as a canonical or absolute path
+ SourceLocationLine = 0x2, ///< If set will display the location line if source is available
+ };
+ };
+
+ /// Used by diagnostic sink to be able to underline tokens. If not defined on the DiagnosticSink,
+ /// will only display a caret at the SourceLoc
+ typedef UnownedStringSlice(*SourceLocationLexer)(const UnownedStringSlice& text);
+
+ /// Get the total amount of errors that have taken place on this DiagnosticSink
+ SLANG_FORCE_INLINE int getErrorCount() { return m_errorCount; }
+
+ void diagnoseDispatch(SourceLoc const& pos, DiagnosticInfo const& info)
+ {
+ diagnoseImpl(pos, info, 0, nullptr);
+ }
+
+ void diagnoseDispatch(SourceLoc const& pos, DiagnosticInfo const& info, DiagnosticArg const& arg0)
+ {
+ DiagnosticArg const* args[] = { &arg0 };
+ diagnoseImpl(pos, info, 1, args);
+ }
+
+ void diagnoseDispatch(SourceLoc const& pos, DiagnosticInfo const& info, DiagnosticArg const& arg0, DiagnosticArg const& arg1)
+ {
+ DiagnosticArg const* args[] = { &arg0, &arg1 };
+ diagnoseImpl(pos, info, 2, args);
+ }
+
+ void diagnoseDispatch(SourceLoc const& pos, DiagnosticInfo const& info, DiagnosticArg const& arg0, DiagnosticArg const& arg1, DiagnosticArg const& arg2)
+ {
+ DiagnosticArg const* args[] = { &arg0, &arg1, &arg2 };
+ diagnoseImpl(pos, info, 3, args);
+ }
+
+ void diagnoseDispatch(SourceLoc const& pos, DiagnosticInfo const& info, DiagnosticArg const& arg0, DiagnosticArg const& arg1, DiagnosticArg const& arg2, DiagnosticArg const& arg3)
+ {
+ DiagnosticArg const* args[] = { &arg0, &arg1, &arg2, &arg3 };
+ diagnoseImpl(pos, info, 4, args);
+ }
+
+ template<typename P, typename... Args>
+ void diagnose(P const& pos, DiagnosticInfo const& info, Args const&... args )
+ {
+ diagnoseDispatch(getDiagnosticPos(pos), info, args...);
+ }
+
+ // Add a diagnostic with raw text
+ // (used when we get errors from a downstream compiler)
+ void diagnoseRaw(Severity severity, char const* message);
+ void diagnoseRaw(Severity severity, const UnownedStringSlice& message);
+
+ /// During propagation of an exception for an internal
+ /// error, note that this source location was involved
+ void noteInternalErrorLoc(SourceLoc const& loc);
+
+ /// Create a blob containing diagnostics if there were any errors.
+ /// *note* only works if writer is not set, the blob is created from outputBuffer
+ SlangResult getBlobIfNeeded(ISlangBlob** outBlob);
+
+ /// Get the source manager used
+ SourceManager* getSourceManager() const { return m_sourceManager; }
+ /// Set the source manager used for lookup of source locs
+ void setSourceManager(SourceManager* inSourceManager) { m_sourceManager = inSourceManager; }
+
+ /// Get the flags
+ Flags getFlags() const { return m_flags; }
+ /// Set a flag
+ void setFlag(Flag::Enum flag) { m_flags |= Flags(flag); }
+ /// Reset a flag
+ void resetFlag(Flag::Enum flag) { m_flags &= ~Flags(flag); }
+ /// Test if flag is set
+ bool isFlagSet(Flag::Enum flag) { return (m_flags & Flags(flag)) != 0; }
+
+ /// Get the (optional) diagnostic sink lexer. This is used to
+ /// improve quality of highlighting a locations token. If not set, will just have a single
+ /// character caret at location
+ SourceLocationLexer getSourceLocationLexer() const { return m_sourceLocationLexer; }
+
+ /// Ctor
+ DiagnosticSink(SourceManager* sourceManager, SourceLocationLexer sourceLocationLexer)
+ : m_sourceManager(sourceManager),
+ m_sourceLocationLexer(sourceLocationLexer)
+ {
+ // If we have a source location lexer, we'll by default enable source location output
+ if (sourceLocationLexer)
+ {
+ setFlag(Flag::SourceLocationLine);
+ }
+ }
+
+ // Public members
+
+ /// The outputBuffer will contain any diagnostics *iff* the writer is *not* set
+ StringBuilder outputBuffer;
+ /// If a writer is set output will *not* be written to the outputBuffer
+ ISlangWriter* writer = nullptr;
+
+protected:
+ void diagnoseImpl(SourceLoc const& pos, DiagnosticInfo const& info, int argCount, DiagnosticArg const* const* args);
+
+ int m_errorCount = 0;
+ int m_internalErrorLocsNoted = 0;
+
+ Flags m_flags = 0;
+
+ // The source manager to use when mapping source locations to file+line info
+ SourceManager* m_sourceManager = nullptr;
+
+ SourceLocationLexer m_sourceLocationLexer;
+};
+
+ /// An `ISlangWriter` that writes directly to a diagnostic sink.
+class DiagnosticSinkWriter : public AppendBufferWriter
+{
+public:
+ typedef AppendBufferWriter Super;
+
+ DiagnosticSinkWriter(DiagnosticSink* sink)
+ : Super(WriterFlag::IsStatic)
+ , m_sink(sink)
+ {}
+
+ // ISlangWriter
+ SLANG_NO_THROW virtual SlangResult SLANG_MCALL write(const char* chars, size_t numChars) SLANG_OVERRIDE
+ {
+ m_sink->diagnoseRaw(Severity::Note, UnownedStringSlice(chars, chars+numChars));
+ return SLANG_OK;
+ }
+
+private:
+ DiagnosticSink* m_sink = nullptr;
+};
+
+class DiagnosticsLookup : public RefObject
+{
+public:
+ static const Index kArenaInitialSize = 2048;
+
+ const DiagnosticInfo* findDiagostic(const UnownedStringSlice& slice) const
+ {
+ const Index* indexPtr = m_map.TryGetValue(slice);
+ return indexPtr ? m_diagnostics[*indexPtr] : nullptr;
+ }
+ Index _findDiagnosticIndex(const UnownedStringSlice& slice) const
+ {
+ const Index* indexPtr = m_map.TryGetValue(slice);
+ return indexPtr ? *indexPtr : 0;
+ }
+
+ /// info must stay in scope
+ Index add(const DiagnosticInfo* info);
+ void add(const DiagnosticInfo*const* infos, Index infosCount);
+
+ void addAlias(const char* name, const char* diagnosticName);
+
+ /// Get the diagnostics held in this lookup
+ const List<const DiagnosticInfo*>& getDiagnostics() const { return m_diagnostics; }
+
+ /// NOTE! diagnostics must stay in scope for lifetime of lookup
+ DiagnosticsLookup(const DiagnosticInfo*const* diagnostics, Index diagnosticsCount);
+ DiagnosticsLookup();
+
+protected:
+ void _add(const char* name, Index index);
+
+ List<const DiagnosticInfo*> m_diagnostics;
+
+ StringBuilder m_work;
+ Dictionary<UnownedStringSlice, Index> m_map;
+ MemoryArena m_arena;
+};
+
+}
+
+#endif
diff --git a/source/compiler-core/slang-downstream-compiler.cpp b/source/compiler-core/slang-downstream-compiler.cpp
new file mode 100644
index 000000000..696376b49
--- /dev/null
+++ b/source/compiler-core/slang-downstream-compiler.cpp
@@ -0,0 +1,716 @@
+// slang-downstream-compiler.cpp
+#include "slang-downstream-compiler.h"
+
+#include "../core/slang-common.h"
+#include "../../slang-com-helper.h"
+#include "../core/slang-string-util.h"
+
+#include "../core/slang-type-text-util.h"
+
+#include "../core/slang-io.h"
+#include "../core/slang-shared-library.h"
+#include "../core/slang-blob.h"
+
+#ifdef SLANG_VC
+# include "windows/slang-win-visual-studio-util.h"
+#endif
+
+#include "slang-visual-studio-compiler-util.h"
+#include "slang-gcc-compiler-util.h"
+#include "slang-nvrtc-compiler.h"
+
+namespace Slang
+{
+
+static DownstreamCompiler::Infos _calcInfos()
+{
+ typedef DownstreamCompiler::Info Info;
+ typedef DownstreamCompiler::SourceLanguageFlag SourceLanguageFlag;
+ typedef DownstreamCompiler::SourceLanguageFlags SourceLanguageFlags;
+
+ DownstreamCompiler::Infos infos;
+
+ infos.infos[int(SLANG_PASS_THROUGH_CLANG)] = Info(SourceLanguageFlag::CPP | SourceLanguageFlag::C);
+ infos.infos[int(SLANG_PASS_THROUGH_VISUAL_STUDIO)] = Info(SourceLanguageFlag::CPP | SourceLanguageFlag::C);
+ infos.infos[int(SLANG_PASS_THROUGH_GCC)] = Info(SourceLanguageFlag::CPP | SourceLanguageFlag::C);
+
+ infos.infos[int(SLANG_PASS_THROUGH_NVRTC)] = Info(SourceLanguageFlag::CUDA);
+
+ infos.infos[int(SLANG_PASS_THROUGH_DXC)] = Info(SourceLanguageFlag::HLSL);
+ infos.infos[int(SLANG_PASS_THROUGH_FXC)] = Info(SourceLanguageFlag::HLSL);
+ infos.infos[int(SLANG_PASS_THROUGH_GLSLANG)] = Info(SourceLanguageFlag::GLSL);
+
+ return infos;
+}
+
+/* static */DownstreamCompiler::Infos DownstreamCompiler::s_infos = _calcInfos();
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DownstreamCompiler::Desc !!!!!!!!!!!!!!!!!!!!!!*/
+
+void DownstreamCompiler::Desc::appendAsText(StringBuilder& out) const
+{
+ out << TypeTextUtil::getPassThroughAsHumanText(type);
+
+ // Append the version if there is a version
+ if (majorVersion || minorVersion)
+ {
+ out << " ";
+ out << majorVersion;
+ out << ".";
+ out << minorVersion;
+ }
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DownstreamDiagnostic !!!!!!!!!!!!!!!!!!!!!!!!*/
+
+/* static */UnownedStringSlice DownstreamDiagnostic::getSeverityText(Severity severity)
+{
+ switch (severity)
+ {
+ default: return UnownedStringSlice::fromLiteral("Unknown");
+ case Severity::Info: return UnownedStringSlice::fromLiteral("Info");
+ case Severity::Warning: return UnownedStringSlice::fromLiteral("Warning");
+ case Severity::Error: return UnownedStringSlice::fromLiteral("Error");
+ }
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DownstreamCompiler !!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
+
+
+/* static */bool DownstreamCompiler::canCompile(SlangPassThrough compiler, SlangSourceLanguage sourceLanguage)
+{
+ const auto& info = getInfo(compiler);
+ return (info.sourceLanguageFlags & (SourceLanguageFlags(1) << int(sourceLanguage))) != 0;
+}
+
+/* static */SlangCompileTarget DownstreamCompiler::getCompileTarget(SlangSourceLanguage sourceLanguage)
+{
+ switch (sourceLanguage)
+ {
+ case SLANG_SOURCE_LANGUAGE_HLSL: return SLANG_HLSL;
+ case SLANG_SOURCE_LANGUAGE_GLSL: return SLANG_GLSL;
+ case SLANG_SOURCE_LANGUAGE_C: return SLANG_C_SOURCE;
+ case SLANG_SOURCE_LANGUAGE_CPP: return SLANG_CPP_SOURCE;
+ case SLANG_SOURCE_LANGUAGE_CUDA: return SLANG_CUDA_SOURCE;
+ default: return SLANG_TARGET_UNKNOWN;
+ }
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DownstreamDiagnostics !!!!!!!!!!!!!!!!!!!!!!*/
+
+Index DownstreamDiagnostics::getCountBySeverity(Diagnostic::Severity severity) const
+{
+ Index count = 0;
+ for (const auto& msg : diagnostics)
+ {
+ count += Index(msg.severity == severity);
+ }
+ return count;
+}
+
+Int DownstreamDiagnostics::countByStage(Diagnostic::Stage stage, Index counts[Int(Diagnostic::Severity::CountOf)]) const
+{
+ Int count = 0;
+ ::memset(counts, 0, sizeof(Index) * Int(Diagnostic::Severity::CountOf));
+ for (const auto& diagnostic : diagnostics)
+ {
+ if (diagnostic.stage == stage)
+ {
+ count++;
+ counts[Index(diagnostic.severity)]++;
+ }
+ }
+ return count++;
+}
+
+static void _appendCounts(const Index counts[Int(DownstreamDiagnostic::Severity::CountOf)], StringBuilder& out)
+{
+ typedef DownstreamDiagnostic::Severity Severity;
+
+ for (Index i = 0; i < Int(Severity::CountOf); i++)
+ {
+ if (counts[i] > 0)
+ {
+ out << DownstreamDiagnostic::getSeverityText(Severity(i)) << "(" << counts[i] << ") ";
+ }
+ }
+}
+
+static void _appendSimplified(const Index counts[Int(DownstreamDiagnostic::Severity::CountOf)], StringBuilder& out)
+{
+ typedef DownstreamDiagnostic::Severity Severity;
+ for (Index i = 0; i < Int(Severity::CountOf); i++)
+ {
+ if (counts[i] > 0)
+ {
+ out << DownstreamDiagnostic::getSeverityText(Severity(i)) << " ";
+ }
+ }
+}
+
+void DownstreamDiagnostics::appendSummary(StringBuilder& out) const
+{
+ Index counts[Int(Diagnostic::Severity::CountOf)];
+ if (countByStage(Diagnostic::Stage::Compile, counts) > 0)
+ {
+ out << "Compile: ";
+ _appendCounts(counts, out);
+ out << "\n";
+ }
+ if (countByStage(Diagnostic::Stage::Link, counts) > 0)
+ {
+ out << "Link: ";
+ _appendCounts(counts, out);
+ out << "\n";
+ }
+}
+
+void DownstreamDiagnostics::appendSimplifiedSummary(StringBuilder& out) const
+{
+ Index counts[Int(Diagnostic::Severity::CountOf)];
+ if (countByStage(Diagnostic::Stage::Compile, counts) > 0)
+ {
+ out << "Compile: ";
+ _appendSimplified(counts, out);
+ out << "\n";
+ }
+ if (countByStage(Diagnostic::Stage::Link, counts) > 0)
+ {
+ out << "Link: ";
+ _appendSimplified(counts, out);
+ out << "\n";
+ }
+}
+
+void DownstreamDiagnostics::removeBySeverity(Diagnostic::Severity severity)
+{
+ Index count = diagnostics.getCount();
+ for (Index i = 0; i < count; ++i)
+ {
+ if (diagnostics[i].severity == severity)
+ {
+ diagnostics.removeAt(i);
+ i--;
+ count--;
+ }
+ }
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CommandLineDownstreamCompileResult !!!!!!!!!!!!!!!!!!!!!!*/
+
+SlangResult CommandLineDownstreamCompileResult::getHostCallableSharedLibrary(ComPtr<ISlangSharedLibrary>& outLibrary)
+{
+ if (m_hostCallableSharedLibrary)
+ {
+ outLibrary = m_hostCallableSharedLibrary;
+ return SLANG_OK;
+ }
+
+ // Okay we want to load
+ // Try loading the shared library
+ SharedLibrary::Handle handle;
+ if (SLANG_FAILED(SharedLibrary::loadWithPlatformPath(m_moduleFilePath.getBuffer(), handle)))
+ {
+ return SLANG_FAIL;
+ }
+ // The shared library needs to keep temp files in scope
+ RefPtr<TemporarySharedLibrary> sharedLib(new TemporarySharedLibrary(handle, m_moduleFilePath));
+ sharedLib->m_temporaryFileSet = m_temporaryFiles;
+
+ m_hostCallableSharedLibrary = sharedLib;
+ outLibrary = m_hostCallableSharedLibrary;
+ return SLANG_OK;
+}
+
+SlangResult CommandLineDownstreamCompileResult::getBinary(ComPtr<ISlangBlob>& outBlob)
+{
+ if (m_binaryBlob)
+ {
+ outBlob = m_binaryBlob;
+ return SLANG_OK;
+ }
+
+ // Read the binary
+ try
+ {
+ // Read the contents of the binary
+ List<uint8_t> contents = File::readAllBytes(m_moduleFilePath);
+
+ m_binaryBlob = new ScopeRefObjectBlob(ListBlob::moveCreate(contents), m_temporaryFiles);
+ outBlob = m_binaryBlob;
+ return SLANG_OK;
+ }
+ catch (const Slang::IOException&)
+ {
+ return SLANG_FAIL;
+ }
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CommandLineDownstreamCompiler !!!!!!!!!!!!!!!!!!!!!!*/
+
+static bool _isContentsInFile(const DownstreamCompiler::CompileOptions& options)
+{
+ if (options.sourceContentsPath.getLength() <= 0)
+ {
+ return false;
+ }
+
+ // We can see if we can load it
+ if (File::exists(options.sourceContentsPath))
+ {
+ // Here we look for the file on the regular file system (as opposed to using the
+ // ISlangFileSystem. This is unfortunate but necessary - because when we call out
+ // to the compiler all it is able to (currently) see are files on the file system.
+ //
+ // Note that it could be coincidence that the filesystem has a file that's identical in
+ // contents/name. That being the case though, any includes wouldn't work for a generated
+ // file either from some specialized ISlangFileSystem, so this is probably as good as it gets
+ // until we can integrate directly to a C/C++ compiler through say a shared library where we can control
+ // file system access.
+ try
+ {
+ String readContents = File::readAllText(options.sourceContentsPath);
+ // We should see if they are the same
+ return options.sourceContents == readContents.getUnownedSlice();
+ }
+ catch (const Slang::IOException&)
+ {
+ }
+ }
+ return false;
+}
+
+SlangResult CommandLineDownstreamCompiler::compile(const CompileOptions& inOptions, RefPtr<DownstreamCompileResult>& out)
+{
+ // Copy the command line options
+ CommandLine cmdLine(m_cmdLine);
+
+ CompileOptions options(inOptions);
+
+ // Find all the files that will be produced
+ RefPtr<TemporaryFileSet> productFileSet(new TemporaryFileSet);
+
+ if (options.modulePath.getLength() == 0 || options.sourceContents.getLength() != 0)
+ {
+ String modulePath = options.modulePath;
+
+ // If there is no module path, generate one.
+ if (modulePath.getLength() == 0)
+ {
+ SLANG_RETURN_ON_FAIL(File::generateTemporary(UnownedStringSlice::fromLiteral("slang-generated"), modulePath));
+ options.modulePath = modulePath;
+ }
+
+ if (_isContentsInFile(options))
+ {
+ options.sourceFiles.add(options.sourceContentsPath);
+ }
+ else
+ {
+ String compileSourcePath = modulePath;
+
+ // NOTE: Strictly speaking producing filenames by modifying the generateTemporary path that may introduce a temp filename clash, but in practice is extraordinary unlikely
+ compileSourcePath.append("-src");
+
+ // Make the temporary filename have the appropriate extension.
+ if (options.sourceLanguage == SLANG_SOURCE_LANGUAGE_C)
+ {
+ compileSourcePath.append(".c");
+ }
+ else
+ {
+ compileSourcePath.append(".cpp");
+ }
+
+ // Write it out
+ try
+ {
+ productFileSet->add(compileSourcePath);
+
+ File::writeAllText(compileSourcePath, options.sourceContents);
+ }
+ catch (...)
+ {
+ return SLANG_FAIL;
+ }
+
+ // Add it as a source file
+ options.sourceFiles.add(compileSourcePath);
+ }
+
+ // There is no source contents
+ options.sourceContents = String();
+ options.sourceContentsPath = String();
+ }
+
+ // Append command line args to the end of cmdLine using the target specific function for the specified options
+ SLANG_RETURN_ON_FAIL(calcArgs(options, cmdLine));
+
+ String moduleFilePath;
+
+ {
+ StringBuilder builder;
+ SLANG_RETURN_ON_FAIL(calcModuleFilePath(options, builder));
+ moduleFilePath = builder.ProduceString();
+ }
+
+ {
+ List<String> paths;
+ SLANG_RETURN_ON_FAIL(calcCompileProducts(options, DownstreamCompiler::ProductFlag::All, paths));
+ productFileSet->add(paths);
+ }
+
+ ExecuteResult exeRes;
+
+#if 0
+ // Test
+ {
+ String line = ProcessUtil::getCommandLineString(cmdLine);
+ printf("%s", line.getBuffer());
+ }
+#endif
+
+ SLANG_RETURN_ON_FAIL(ProcessUtil::execute(cmdLine, exeRes));
+
+#if 0
+ {
+ printf("stdout=\"%s\"\nstderr=\"%s\"\nret=%d\n", exeRes.standardOutput.getBuffer(), exeRes.standardError.getBuffer(), int(exeRes.resultCode));
+ }
+#endif
+
+ DownstreamDiagnostics diagnostics;
+ SLANG_RETURN_ON_FAIL(parseOutput(exeRes, diagnostics));
+
+
+ out = new CommandLineDownstreamCompileResult(diagnostics, moduleFilePath, productFileSet);
+
+ return SLANG_OK;
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!! DownstreamCompiler::Desc !!!!!!!!!!!!!!!!!!!!!!*/
+
+static DownstreamCompiler::Desc _calcCompiledWithDesc()
+{
+ DownstreamCompiler::Desc desc;
+
+#if SLANG_VC
+ desc = WinVisualStudioUtil::getDesc(WinVisualStudioUtil::getCompiledVersion());
+#elif SLANG_CLANG
+ desc.type = SLANG_PASS_THROUGH_CLANG;
+ desc.majorVersion = Int(__clang_major__);
+ desc.minorVersion = Int(__clang_minor__);
+#elif SLANG_GCC
+ desc.type = SLANG_PASS_THROUGH_GCC;
+ desc.majorVersion = Int(__GNUC__);
+ desc.minorVersion = Int(__GNUC_MINOR__);
+#else
+ // TODO(JS): Hmmm None is not quite the same as unknown. It works for now, but we might want to have a distinct enum for unknown.
+ desc.type = SLANG_PASS_THROUGH_NONE;
+#endif
+
+ return desc;
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!! DownstreamCompilerUtil !!!!!!!!!!!!!!!!!!!!!!*/
+
+const DownstreamCompiler::Desc& DownstreamCompilerUtil::getCompiledWithDesc()
+{
+ static DownstreamCompiler::Desc s_desc = _calcCompiledWithDesc();
+ return s_desc;
+}
+
+/* static */DownstreamCompiler* DownstreamCompilerUtil::findCompiler(const DownstreamCompilerSet* set, MatchType matchType, const DownstreamCompiler::Desc& desc)
+{
+ List<DownstreamCompiler*> compilers;
+ set->getCompilers(compilers);
+ return findCompiler(compilers, matchType, desc);
+}
+
+/* static */DownstreamCompiler* DownstreamCompilerUtil::findCompiler(const List<DownstreamCompiler*>& compilers, MatchType matchType, const DownstreamCompiler::Desc& desc)
+{
+ if (compilers.getCount() <= 0)
+ {
+ return nullptr;
+ }
+
+ Int bestIndex = -1;
+
+ const SlangPassThrough compilerType = desc.type;
+
+ Int maxVersionValue = 0;
+ Int minVersionDiff = 0x7fffffff;
+
+ Int descVersionValue = desc.getVersionValue();
+
+ // If we don't have version set, then anything 0 or above is good enough, and just take newest
+ if (descVersionValue == 0)
+ {
+ maxVersionValue = -1;
+ matchType = MatchType::Newest;
+ }
+
+ for (Index i = 0; i < compilers.getCount(); ++i)
+ {
+ DownstreamCompiler* compiler = compilers[i];
+ auto compilerDesc = compiler->getDesc();
+
+ if (compilerType == compilerDesc.type)
+ {
+ const Int versionValue = compilerDesc.getVersionValue();
+ switch (matchType)
+ {
+ case MatchType::MinGreaterEqual:
+ {
+ auto diff = descVersionValue - versionValue;
+ if (diff >= 0 && diff < minVersionDiff)
+ {
+ bestIndex = i;
+ minVersionDiff = diff;
+ }
+ break;
+ }
+ case MatchType::MinAbsolute:
+ {
+ auto diff = descVersionValue - versionValue;
+ diff = (diff >= 0) ? diff : -diff;
+ if (diff < minVersionDiff)
+ {
+ bestIndex = i;
+ minVersionDiff = diff;
+ }
+ break;
+ }
+ case MatchType::Newest:
+ {
+ if (versionValue > maxVersionValue)
+ {
+ maxVersionValue = versionValue;
+ bestIndex = i;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ return (bestIndex >= 0) ? compilers[bestIndex] : nullptr;
+}
+
+/* static */DownstreamCompiler* DownstreamCompilerUtil::findClosestCompiler(const List<DownstreamCompiler*>& compilers, const DownstreamCompiler::Desc& desc)
+{
+ DownstreamCompiler* compiler;
+
+ compiler = findCompiler(compilers, MatchType::MinGreaterEqual, desc);
+ if (compiler)
+ {
+ return compiler;
+ }
+ compiler = findCompiler(compilers, MatchType::MinAbsolute, desc);
+ if (compiler)
+ {
+ return compiler;
+ }
+
+ // If we are gcc, we can try clang and vice versa
+ if (desc.type == SLANG_PASS_THROUGH_GCC || desc.type == SLANG_PASS_THROUGH_CLANG)
+ {
+ DownstreamCompiler::Desc compatible = desc;
+ compatible.type = (compatible.type == SLANG_PASS_THROUGH_CLANG) ? SLANG_PASS_THROUGH_GCC : SLANG_PASS_THROUGH_CLANG;
+
+ compiler = findCompiler(compilers, MatchType::MinGreaterEqual, compatible);
+ if (compiler)
+ {
+ return compiler;
+ }
+ compiler = findCompiler(compilers, MatchType::MinAbsolute, compatible);
+ if (compiler)
+ {
+ return compiler;
+ }
+ }
+
+ return nullptr;
+}
+
+/* static */DownstreamCompiler* DownstreamCompilerUtil::findClosestCompiler(const DownstreamCompilerSet* set, const DownstreamCompiler::Desc& desc)
+{
+ DownstreamCompiler* compiler = set->getCompiler(desc);
+ if (compiler)
+ {
+ return compiler;
+ }
+ List<DownstreamCompiler*> compilers;
+ set->getCompilers(compilers);
+ return findClosestCompiler(compilers, desc);
+}
+
+/* static */void DownstreamCompilerUtil::updateDefault(DownstreamCompilerSet* set, SlangSourceLanguage sourceLanguage)
+{
+ DownstreamCompiler* compiler = nullptr;
+
+ switch (sourceLanguage)
+ {
+ case SLANG_SOURCE_LANGUAGE_CPP:
+ case SLANG_SOURCE_LANGUAGE_C:
+ {
+ compiler = findClosestCompiler(set, getCompiledWithDesc());
+ break;
+ }
+ case SLANG_SOURCE_LANGUAGE_CUDA:
+ {
+ DownstreamCompiler::Desc desc;
+ desc.type = SLANG_PASS_THROUGH_NVRTC;
+ compiler = findCompiler(set, MatchType::Newest, desc);
+ break;
+ }
+ default: break;
+ }
+
+ set->setDefaultCompiler(sourceLanguage, compiler);
+}
+
+/* static */void DownstreamCompilerUtil::updateDefaults(DownstreamCompilerSet* set)
+{
+ for (Index i = 0; i < Index(SLANG_SOURCE_LANGUAGE_COUNT_OF); ++i)
+ {
+ updateDefault(set, SlangSourceLanguage(i));
+ }
+}
+
+static SlangResult _locateDXCCompilers(const String& path, ISlangSharedLibraryLoader* loader, DownstreamCompilerSet* set)
+{
+ // First try dxil, so it's loaded from the same path if it's there
+ ComPtr<ISlangSharedLibrary> dxil;
+ DefaultSharedLibraryLoader::load(loader, path, "dxil", dxil.writeRef());
+
+ ComPtr<ISlangSharedLibrary> sharedLibrary;
+ if (SLANG_SUCCEEDED(DefaultSharedLibraryLoader::load(loader, path, "dxcompiler", sharedLibrary.writeRef())))
+ {
+ // Can we determine the version?
+ DownstreamCompiler::Desc desc(SLANG_PASS_THROUGH_DXC);
+ RefPtr<DownstreamCompiler> compiler(new SharedLibraryDownstreamCompiler(desc, sharedLibrary));
+
+ set->addCompiler(compiler);
+ }
+ return SLANG_OK;
+}
+
+static SlangResult _locateFXCCompilers(const String& path, ISlangSharedLibraryLoader* loader, DownstreamCompilerSet* set)
+{
+ ComPtr<ISlangSharedLibrary> sharedLibrary;
+ if (SLANG_SUCCEEDED(DefaultSharedLibraryLoader::load(loader, path, "d3dcompiler_47", sharedLibrary.writeRef())))
+ {
+ // Can we determine the version?
+ DownstreamCompiler::Desc desc(SLANG_PASS_THROUGH_FXC);
+ RefPtr<DownstreamCompiler> compiler(new SharedLibraryDownstreamCompiler(desc, sharedLibrary));
+ set->addCompiler(compiler);
+ }
+ return SLANG_OK;
+}
+
+static SlangResult _locateGlslangCompilers(const String& path, ISlangSharedLibraryLoader* loader, DownstreamCompilerSet* set)
+{
+#if SLANG_UNIX_FAMILY
+ // On unix systems we need to ensure pthread is loaded first.
+ ComPtr<ISlangSharedLibrary> pthreadLibrary;
+ DefaultSharedLibraryLoader::load(loader, path, "pthread", pthreadLibrary.writeRef());
+#endif
+ ComPtr<ISlangSharedLibrary> sharedLibrary;
+ if (SLANG_SUCCEEDED(DefaultSharedLibraryLoader::load(loader, path, "slang-glslang", sharedLibrary.writeRef())))
+ {
+ // Can we determine the version?
+ DownstreamCompiler::Desc desc(SLANG_PASS_THROUGH_GLSLANG);
+ RefPtr<DownstreamCompiler> compiler(new SharedLibraryDownstreamCompiler(desc, sharedLibrary));
+ set->addCompiler(compiler);
+ }
+ return SLANG_OK;
+}
+
+/* static */void DownstreamCompilerUtil::setDefaultLocators(DownstreamCompilerLocatorFunc outFuncs[int(SLANG_PASS_THROUGH_COUNT_OF)])
+{
+ outFuncs[int(SLANG_PASS_THROUGH_VISUAL_STUDIO)] = &VisualStudioCompilerUtil::locateCompilers;
+ outFuncs[int(SLANG_PASS_THROUGH_CLANG)] = &GCCDownstreamCompilerUtil::locateClangCompilers;
+ outFuncs[int(SLANG_PASS_THROUGH_GCC)] = &GCCDownstreamCompilerUtil::locateGCCCompilers;
+ outFuncs[int(SLANG_PASS_THROUGH_NVRTC)] = &NVRTCDownstreamCompilerUtil::locateCompilers;
+ outFuncs[int(SLANG_PASS_THROUGH_DXC)] = &_locateDXCCompilers;
+ outFuncs[int(SLANG_PASS_THROUGH_FXC)] = &_locateFXCCompilers;
+ outFuncs[int(SLANG_PASS_THROUGH_GLSLANG)] = &_locateGlslangCompilers;
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DownstreamCompilerSet !!!!!!!!!!!!!!!!!!!!!!*/
+
+void DownstreamCompilerSet::getCompilerDescs(List<DownstreamCompiler::Desc>& outCompilerDescs) const
+{
+ outCompilerDescs.clear();
+ for (DownstreamCompiler* compiler : m_compilers)
+ {
+ outCompilerDescs.add(compiler->getDesc());
+ }
+}
+
+Index DownstreamCompilerSet::_findIndex(const DownstreamCompiler::Desc& desc) const
+{
+ const Index count = m_compilers.getCount();
+ for (Index i = 0; i < count; ++i)
+ {
+ if (m_compilers[i]->getDesc() == desc)
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+DownstreamCompiler* DownstreamCompilerSet::getCompiler(const DownstreamCompiler::Desc& compilerDesc) const
+{
+ const Index index = _findIndex(compilerDesc);
+ return index >= 0 ? m_compilers[index] : nullptr;
+}
+
+void DownstreamCompilerSet::getCompilers(List<DownstreamCompiler*>& outCompilers) const
+{
+ outCompilers.clear();
+ outCompilers.addRange((DownstreamCompiler*const*)m_compilers.begin(), m_compilers.getCount());
+}
+
+bool DownstreamCompilerSet::hasCompiler(SlangPassThrough compilerType) const
+{
+ for (DownstreamCompiler* compiler : m_compilers)
+ {
+ const auto& desc = compiler->getDesc();
+ if (desc.type == compilerType)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+void DownstreamCompilerSet::remove(SlangPassThrough compilerType)
+{
+ for (Index i = 0; i < m_compilers.getCount(); ++i)
+ {
+ DownstreamCompiler* compiler = m_compilers[i];
+ if (compiler->getDesc().type == compilerType)
+ {
+ m_compilers.fastRemoveAt(i);
+ i--;
+ }
+ }
+}
+
+void DownstreamCompilerSet::addCompiler(DownstreamCompiler* compiler)
+{
+ const Index index = _findIndex(compiler->getDesc());
+ if (index >= 0)
+ {
+ m_compilers[index] = compiler;
+ }
+ else
+ {
+ m_compilers.add(compiler);
+ }
+}
+
+}
diff --git a/source/compiler-core/slang-downstream-compiler.h b/source/compiler-core/slang-downstream-compiler.h
new file mode 100644
index 000000000..35acc820e
--- /dev/null
+++ b/source/compiler-core/slang-downstream-compiler.h
@@ -0,0 +1,501 @@
+#ifndef SLANG_DOWNSTREAM_COMPILER_H
+#define SLANG_DOWNSTREAM_COMPILER_H
+
+#include "../core/slang-common.h"
+#include "../core/slang-string.h"
+
+#include "../core/slang-process-util.h"
+
+#include "../core/slang-platform.h"
+#include "../core/slang-semantic-version.h"
+
+#include "../core/slang-io.h"
+
+#include "../../slang-com-ptr.h"
+
+namespace Slang
+{
+
+struct DownstreamDiagnostic
+{
+ typedef DownstreamDiagnostic ThisType;
+
+ enum class Severity
+ {
+ Unknown,
+ Info,
+ Warning,
+ Error,
+ CountOf,
+ };
+ enum class Stage
+ {
+ Compile,
+ Link,
+ };
+
+ void reset()
+ {
+ severity = Severity::Unknown;
+ stage = Stage::Compile;
+ fileLine = 0;
+ }
+
+ bool operator==(const ThisType& rhs) const
+ {
+ return severity == rhs.severity &&
+ stage == rhs.stage &&
+ text == rhs.text &&
+ code == rhs.code &&
+ filePath == rhs.filePath &&
+ fileLine == rhs.fileLine;
+ }
+ bool operator!=(const ThisType& rhs) const { return !(*this == rhs); }
+
+ static UnownedStringSlice getSeverityText(Severity severity);
+
+ Severity severity; ///< The severity of error
+ Stage stage; ///< The stage the error came from
+ String text; ///< The text of the error
+ String code; ///< The compiler specific error code
+ String filePath; ///< The path the error originated from
+ Int fileLine; ///< The line number the error came from
+};
+
+struct DownstreamDiagnostics
+{
+ typedef DownstreamDiagnostic Diagnostic;
+
+ /// Reset to an initial empty state
+ void reset() { diagnostics.clear(); rawDiagnostics = String(); result = SLANG_OK; }
+
+ /// Get the number of diagnostics by severity
+ Index getCountBySeverity(Diagnostic::Severity severity) const;
+ /// True if there are any diagnostics of severity
+ bool has(Diagnostic::Severity severity) const { return getCountBySeverity(severity) > 0; }
+
+ /// Stores in outCounts, the amount of diagnostics for the stage of each severity
+ Int countByStage(Diagnostic::Stage stage, Index outCounts[Int(Diagnostic::Severity::CountOf)]) const;
+
+ /// Append a summary to out
+ void appendSummary(StringBuilder& out) const;
+ /// Appends a summary that just identifies if there is an error of a type (not a count)
+ void appendSimplifiedSummary(StringBuilder& out) const;
+
+ /// Remove all diagnostics of the type
+ void removeBySeverity(Diagnostic::Severity severity);
+
+ String rawDiagnostics;
+
+ SlangResult result;
+ List<Diagnostic> diagnostics;
+};
+
+class DownstreamCompileResult : public RefObject
+{
+public:
+
+ virtual SlangResult getHostCallableSharedLibrary(ComPtr<ISlangSharedLibrary>& outLibrary) = 0;
+ virtual SlangResult getBinary(ComPtr<ISlangBlob>& outBlob) = 0;
+
+ const DownstreamDiagnostics& getDiagnostics() const { return m_diagnostics; }
+
+ /// Ctor
+ DownstreamCompileResult(const DownstreamDiagnostics& diagnostics):
+ m_diagnostics(diagnostics)
+ {}
+
+protected:
+ DownstreamDiagnostics m_diagnostics;
+};
+
+
+class BlobDownstreamCompileResult : public DownstreamCompileResult
+{
+public:
+ typedef DownstreamCompileResult Super;
+
+ virtual SlangResult getHostCallableSharedLibrary(ComPtr<ISlangSharedLibrary>& outLibrary) SLANG_OVERRIDE { SLANG_UNUSED(outLibrary); return SLANG_FAIL; }
+ virtual SlangResult getBinary(ComPtr<ISlangBlob>& outBlob) SLANG_OVERRIDE { outBlob = m_blob; return m_blob ? SLANG_OK : SLANG_FAIL; }
+
+ BlobDownstreamCompileResult(const DownstreamDiagnostics& diags, ISlangBlob* blob):
+ Super(diags),
+ m_blob(blob)
+ {
+
+ }
+protected:
+ ComPtr<ISlangBlob> m_blob;
+};
+
+class DownstreamCompiler: public RefObject
+{
+public:
+ typedef RefObject Super;
+
+ typedef DownstreamCompileResult CompileResult;
+
+ typedef uint32_t SourceLanguageFlags;
+ struct SourceLanguageFlag
+ {
+ enum Enum : SourceLanguageFlags
+ {
+ Unknown = SourceLanguageFlags(1) << SLANG_SOURCE_LANGUAGE_UNKNOWN,
+ Slang = SourceLanguageFlags(1) << SLANG_SOURCE_LANGUAGE_SLANG,
+ HLSL = SourceLanguageFlags(1) << SLANG_SOURCE_LANGUAGE_HLSL,
+ GLSL = SourceLanguageFlags(1) << SLANG_SOURCE_LANGUAGE_GLSL,
+ C = SourceLanguageFlags(1) << SLANG_SOURCE_LANGUAGE_C,
+ CPP = SourceLanguageFlags(1) << SLANG_SOURCE_LANGUAGE_CPP,
+ CUDA = SourceLanguageFlags(1) << SLANG_SOURCE_LANGUAGE_CUDA,
+ };
+ };
+
+ struct Info
+ {
+ Info():sourceLanguageFlags(0) {}
+
+ Info(SourceLanguageFlags inSourceLanguageFlags):
+ sourceLanguageFlags(inSourceLanguageFlags)
+ {}
+ SourceLanguageFlags sourceLanguageFlags;
+ };
+ struct Infos
+ {
+ Info infos[int(SLANG_PASS_THROUGH_COUNT_OF)];
+ };
+
+ struct Desc
+ {
+ typedef Desc ThisType;
+
+ HashCode getHashCode() const { return combineHash(HashCode(type), combineHash(HashCode(majorVersion), HashCode(minorVersion))); }
+ bool operator==(const ThisType& rhs) const { return type == rhs.type && majorVersion == rhs.majorVersion && minorVersion == rhs.minorVersion; }
+ bool operator!=(const ThisType& rhs) const { return !(*this == rhs); }
+
+ /// Get the version as a value
+ Int getVersionValue() const { return majorVersion * 100 + minorVersion; }
+
+ void appendAsText(StringBuilder& out) const;
+ /// true if has a version set
+ bool hasVersion() const { return majorVersion || minorVersion; }
+
+ /// Ctor
+ explicit Desc(SlangPassThrough inType = SLANG_PASS_THROUGH_NONE, Int inMajorVersion = 0, Int inMinorVersion = 0):type(inType), majorVersion(inMajorVersion), minorVersion(inMinorVersion) {}
+
+ SlangPassThrough type; ///< The type of the compiler
+ Int majorVersion; ///< Major version (interpretation is type specific)
+ Int minorVersion; ///< Minor version
+ };
+
+ enum class OptimizationLevel
+ {
+ None, ///< Don't optimize at all.
+ Default, ///< Default optimization level: balance code quality and compilation time.
+ High, ///< Optimize aggressively.
+ Maximal, ///< Include optimizations that may take a very long time, or may involve severe space-vs-speed tradeoffs
+ };
+
+ enum class DebugInfoType
+ {
+ None, ///< Don't emit debug information at all.
+ Minimal, ///< Emit as little debug information as possible, while still supporting stack traces.
+ Standard, ///< Emit whatever is the standard level of debug information for each target.
+ Maximal, ///< Emit as much debug information as possible for each target.
+ };
+ enum class FloatingPointMode
+ {
+ Default,
+ Fast,
+ Precise,
+ };
+
+ enum TargetType
+ {
+ Executable, ///< Produce an executable
+ SharedLibrary, ///< Produce a shared library object/dll
+ Object, ///< Produce an object file
+ };
+
+ enum PipelineType
+ {
+ Unknown,
+ Compute,
+ Rasterization,
+ RayTracing,
+ };
+
+ struct Define
+ {
+ String nameWithSig; ///< If macro takes parameters include in brackets
+ String value;
+ };
+
+ struct CapabilityVersion
+ {
+ enum class Kind
+ {
+ CUDASM, ///< What the version is for
+ };
+ Kind kind;
+ SemanticVersion version;
+ };
+
+ struct CompileOptions
+ {
+ typedef uint32_t Flags;
+ struct Flag
+ {
+ enum Enum : Flags
+ {
+ EnableExceptionHandling = 0x01,
+ Verbose = 0x02,
+ EnableSecurityChecks = 0x04,
+ };
+ };
+
+ OptimizationLevel optimizationLevel = OptimizationLevel::Default;
+ DebugInfoType debugInfoType = DebugInfoType::Standard;
+ TargetType targetType = TargetType::Executable;
+ SlangSourceLanguage sourceLanguage = SLANG_SOURCE_LANGUAGE_CPP;
+ FloatingPointMode floatingPointMode = FloatingPointMode::Default;
+ PipelineType pipelineType = PipelineType::Unknown;
+
+ Flags flags = Flag::EnableExceptionHandling;
+
+ PlatformKind platform = PlatformKind::Unknown;
+
+ /// The path/name of the output module. Should not have the extension, as that will be added for each of the target types.
+ /// If not set a module path will be internally generated internally on a command line based compiler
+ String modulePath;
+
+ List<Define> defines;
+
+ /// The contents of the source to compile. This can be empty is sourceFiles is set.
+ /// If the compiler is a commandLine file this source will be written to a temporary file.
+ String sourceContents;
+ /// 'Path' that the contents originated from. NOTE! This is for reporting only and doesn't have to exist on file system
+ String sourceContentsPath;
+
+ /// The names/paths of source to compile. This can be empty if sourceContents is set.
+ List<String> sourceFiles;
+
+ List<String> includePaths;
+ List<String> libraryPaths;
+
+ List<CapabilityVersion> requiredCapabilityVersions;
+ };
+
+ typedef uint32_t ProductFlags;
+ struct ProductFlag
+ {
+ enum Enum : ProductFlags
+ {
+ Debug = 0x1, ///< Used by debugger during execution
+ Execution = 0x2, ///< Required for execution
+ Compile = 0x4, ///< A product *required* for compilation
+ Miscellaneous = 0x8, ///< Anything else
+ };
+ enum Mask : ProductFlags
+ {
+ All = 0xf, ///< All the flags
+ };
+ };
+
+ enum class Product
+ {
+ DebugRun,
+ Run,
+ CompileTemporary,
+ All,
+ };
+
+ /// Get the desc of this compiler
+ const Desc& getDesc() const { return m_desc; }
+ /// Compile using the specified options. The result is in resOut
+ virtual SlangResult compile(const CompileOptions& options, RefPtr<DownstreamCompileResult>& outResult) = 0;
+ /// Some downstream compilers are backed by a shared library. This allows access to the shared library to access internal functions.
+ virtual ISlangSharedLibrary* getSharedLibrary() { return nullptr; }
+
+ /// Get info for a compiler type
+ static const Info& getInfo(SlangPassThrough compiler) { return s_infos.infos[int(compiler)]; }
+ /// True if this compiler can compile the specified language
+ static bool canCompile(SlangPassThrough compiler, SlangSourceLanguage sourceLanguage);
+
+ /// Given a source language return as the equivalent compile target
+ static SlangCompileTarget getCompileTarget(SlangSourceLanguage sourceLanguage);
+
+protected:
+ static Infos s_infos;
+
+ DownstreamCompiler(const Desc& desc) :
+ m_desc(desc)
+ {}
+ DownstreamCompiler() {}
+
+ Desc m_desc;
+};
+
+class CommandLineDownstreamCompileResult : public DownstreamCompileResult
+{
+public:
+ typedef DownstreamCompileResult Super;
+
+ virtual SlangResult getHostCallableSharedLibrary(ComPtr<ISlangSharedLibrary>& outLibrary) SLANG_OVERRIDE;
+ virtual SlangResult getBinary(ComPtr<ISlangBlob>& outBlob) SLANG_OVERRIDE;
+
+ CommandLineDownstreamCompileResult(const DownstreamDiagnostics& diagnostics, const String& moduleFilePath, TemporaryFileSet* temporaryFileSet) :
+ Super(diagnostics),
+ m_moduleFilePath(moduleFilePath),
+ m_temporaryFiles(temporaryFileSet)
+ {
+ }
+
+ RefPtr<TemporaryFileSet> m_temporaryFiles;
+
+protected:
+
+ String m_moduleFilePath;
+ DownstreamCompiler::CompileOptions m_options;
+ ComPtr<ISlangBlob> m_binaryBlob;
+ /// Cache of the shared library if appropriate
+ ComPtr<ISlangSharedLibrary> m_hostCallableSharedLibrary;
+};
+
+class CommandLineDownstreamCompiler : public DownstreamCompiler
+{
+public:
+ typedef DownstreamCompiler Super;
+
+ // DownstreamCompiler
+ virtual SlangResult compile(const CompileOptions& options, RefPtr<DownstreamCompileResult>& outResult) SLANG_OVERRIDE;
+
+ // Functions to be implemented for a specific CommandLine
+
+ /// Given the compilation options and the module name, determines the actual file name used for output
+ virtual SlangResult calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath) = 0;
+ /// Given options determines the paths to products produced (including the 'moduleFilePath').
+ /// Note that does *not* guarentee all products were or should be produced. Just aims to include all that could
+ /// be produced, such that can be removed on completion.
+ virtual SlangResult calcCompileProducts(const CompileOptions& options, ProductFlags flags, List<String>& outPaths) = 0;
+
+ virtual SlangResult calcArgs(const CompileOptions& options, CommandLine& cmdLine) = 0;
+ virtual SlangResult parseOutput(const ExecuteResult& exeResult, DownstreamDiagnostics& output) = 0;
+
+ CommandLineDownstreamCompiler(const Desc& desc, const String& exeName) :
+ Super(desc)
+ {
+ m_cmdLine.setExecutableFilename(exeName);
+ }
+
+ CommandLineDownstreamCompiler(const Desc& desc, const CommandLine& cmdLine) :
+ Super(desc),
+ m_cmdLine(cmdLine)
+ {}
+
+ CommandLineDownstreamCompiler(const Desc& desc):Super(desc) {}
+
+ CommandLine m_cmdLine;
+};
+
+class SharedLibraryDownstreamCompiler: public DownstreamCompiler
+{
+public:
+ typedef DownstreamCompiler Super;
+
+ // DownstreamCompiler
+ virtual SlangResult compile(const CompileOptions& options, RefPtr<DownstreamCompileResult>& outResult) SLANG_OVERRIDE { SLANG_UNUSED(options); SLANG_UNUSED(outResult); return SLANG_E_NOT_IMPLEMENTED; }
+ virtual ISlangSharedLibrary* getSharedLibrary() SLANG_OVERRIDE { return m_library; }
+
+ SharedLibraryDownstreamCompiler(const Desc& desc, ISlangSharedLibrary* library):
+ Super(desc),
+ m_library(library)
+ {
+ }
+protected:
+ ComPtr<ISlangSharedLibrary> m_library;
+};
+
+class DownstreamCompilerSet : public RefObject
+{
+public:
+ typedef RefObject Super;
+
+ /// Find all the available compilers
+ void getCompilerDescs(List<DownstreamCompiler::Desc>& outCompilerDescs) const;
+ /// Returns list of all compilers
+ void getCompilers(List<DownstreamCompiler*>& outCompilers) const;
+
+ /// Get a compiler
+ DownstreamCompiler* getCompiler(const DownstreamCompiler::Desc& compilerDesc) const;
+
+ /// Will replace if there is one with same desc
+ void addCompiler(DownstreamCompiler* compiler);
+
+ /// Get a default compiler
+ DownstreamCompiler* getDefaultCompiler(SlangSourceLanguage sourceLanguage) const { return m_defaultCompilers[int(sourceLanguage)]; }
+ /// Set the default compiler
+ void setDefaultCompiler(SlangSourceLanguage sourceLanguage, DownstreamCompiler* compiler) { m_defaultCompilers[int(sourceLanguage)] = compiler; }
+
+ /// True if has a compiler of the specified type
+ bool hasCompiler(SlangPassThrough compilerType) const;
+
+ void remove(SlangPassThrough compilerType);
+
+ void clear() { m_compilers.clear(); }
+
+protected:
+
+ Index _findIndex(const DownstreamCompiler::Desc& desc) const;
+
+ RefPtr<DownstreamCompiler> m_defaultCompilers[int(SLANG_SOURCE_LANGUAGE_COUNT_OF)];
+ // This could be a dictionary/map - but doing a linear search is going to be fine and it makes
+ // somethings easier.
+ List<RefPtr<DownstreamCompiler>> m_compilers;
+};
+
+typedef SlangResult (*DownstreamCompilerLocatorFunc)(const String& path, ISlangSharedLibraryLoader* loader, DownstreamCompilerSet* set);
+
+/* Only purpose of having base-class here is to make all the DownstreamCompiler types available directly in derived Utils */
+struct DownstreamCompilerBaseUtil
+{
+ typedef DownstreamCompiler::CompileOptions CompileOptions;
+ typedef DownstreamCompiler::OptimizationLevel OptimizationLevel;
+ typedef DownstreamCompiler::TargetType TargetType;
+ typedef DownstreamCompiler::DebugInfoType DebugInfoType;
+
+ typedef DownstreamDiagnostics::Diagnostic Diagnostic;
+
+ typedef DownstreamCompiler::FloatingPointMode FloatingPointMode;
+ typedef DownstreamCompiler::ProductFlag ProductFlag;
+ typedef DownstreamCompiler::ProductFlags ProductFlags;
+};
+
+struct DownstreamCompilerUtil: public DownstreamCompilerBaseUtil
+{
+ enum class MatchType
+ {
+ MinGreaterEqual,
+ MinAbsolute,
+ Newest,
+ };
+
+ /// Find a compiler
+ static DownstreamCompiler* findCompiler(const DownstreamCompilerSet* set, MatchType matchType, const DownstreamCompiler::Desc& desc);
+ static DownstreamCompiler* findCompiler(const List<DownstreamCompiler*>& compilers, MatchType matchType, const DownstreamCompiler::Desc& desc);
+
+ /// Find the compiler closest to the desc
+ static DownstreamCompiler* findClosestCompiler(const List<DownstreamCompiler*>& compilers, const DownstreamCompiler::Desc& desc);
+ static DownstreamCompiler* findClosestCompiler(const DownstreamCompilerSet* set, const DownstreamCompiler::Desc& desc);
+
+ /// Get the information on the compiler used to compile this source
+ static const DownstreamCompiler::Desc& getCompiledWithDesc();
+
+ static void updateDefault(DownstreamCompilerSet* set, SlangSourceLanguage sourceLanguage);
+ static void updateDefaults(DownstreamCompilerSet* set);
+
+ static void setDefaultLocators(DownstreamCompilerLocatorFunc outFuncs[int(SLANG_PASS_THROUGH_COUNT_OF)]);
+};
+
+}
+
+#endif
diff --git a/source/compiler-core/slang-gcc-compiler-util.cpp b/source/compiler-core/slang-gcc-compiler-util.cpp
new file mode 100644
index 000000000..a51d5735f
--- /dev/null
+++ b/source/compiler-core/slang-gcc-compiler-util.cpp
@@ -0,0 +1,645 @@
+// slang-gcc-compiler-util.cpp
+#include "slang-gcc-compiler-util.h"
+
+#include "../core/slang-common.h"
+#include "../../slang-com-helper.h"
+#include "../core/slang-string-util.h"
+
+#include "../core/slang-io.h"
+#include "../core/slang-shared-library.h"
+
+namespace Slang
+{
+
+/* static */SlangResult GCCDownstreamCompilerUtil::parseVersion(const UnownedStringSlice& text, const UnownedStringSlice& prefix, DownstreamCompiler::Desc& outDesc)
+{
+ List<UnownedStringSlice> lines;
+ StringUtil::calcLines(text, lines);
+
+ for (auto line : lines)
+ {
+ if (line.startsWith(prefix))
+ {
+ const UnownedStringSlice remainingSlice = UnownedStringSlice(line.begin() + prefix.getLength(), line.end()).trim();
+ const Index versionEndIndex = remainingSlice.indexOf(' ');
+ if (versionEndIndex < 0)
+ {
+ return SLANG_FAIL;
+ }
+
+ const UnownedStringSlice versionSlice(remainingSlice.begin(), remainingSlice.begin() + versionEndIndex);
+
+ // Version is in format 0.0.0
+ List<UnownedStringSlice> split;
+ StringUtil::split(versionSlice, '.', split);
+ List<Int> digits;
+
+ for (auto v : split)
+ {
+ Int version;
+ SLANG_RETURN_ON_FAIL(StringUtil::parseInt(v, version));
+ digits.add(version);
+ }
+
+ if (digits.getCount() < 2)
+ {
+ return SLANG_FAIL;
+ }
+
+ outDesc.majorVersion = digits[0];
+ outDesc.minorVersion = digits[1];
+ return SLANG_OK;
+ }
+ }
+
+ return SLANG_FAIL;
+}
+
+SlangResult GCCDownstreamCompilerUtil::calcVersion(const String& exeName, DownstreamCompiler::Desc& outDesc)
+{
+ CommandLine cmdLine;
+ cmdLine.setExecutableFilename(exeName);
+ cmdLine.addArg("-v");
+
+ ExecuteResult exeRes;
+ SLANG_RETURN_ON_FAIL(ProcessUtil::execute(cmdLine, exeRes));
+
+ const UnownedStringSlice prefixes[] =
+ {
+ UnownedStringSlice::fromLiteral("clang version"),
+ UnownedStringSlice::fromLiteral("gcc version"),
+ UnownedStringSlice::fromLiteral("Apple LLVM version"),
+ };
+ const SlangPassThrough types[] =
+ {
+ SLANG_PASS_THROUGH_CLANG,
+ SLANG_PASS_THROUGH_GCC,
+ SLANG_PASS_THROUGH_CLANG,
+ };
+
+ SLANG_COMPILE_TIME_ASSERT(SLANG_COUNT_OF(prefixes) == SLANG_COUNT_OF(types));
+
+ for (Index i = 0; i < SLANG_COUNT_OF(prefixes); ++i)
+ {
+ // Set the type
+ outDesc.type = types[i];
+ // Extract the version
+ if (SLANG_SUCCEEDED(parseVersion(exeRes.standardError.getUnownedSlice(), prefixes[i], outDesc)))
+ {
+ return SLANG_OK;
+ }
+ }
+ return SLANG_FAIL;
+}
+
+static SlangResult _parseSeverity(const UnownedStringSlice& in, DownstreamDiagnostic::Severity& outSeverity)
+{
+ typedef DownstreamDiagnostic::Severity Severity;
+
+ if (in == "error" || in == "fatal error")
+ {
+ outSeverity = Severity::Error;
+ }
+ else if (in == "warning")
+ {
+ outSeverity = Severity::Warning;
+ }
+ else if (in == "info" || in == "note")
+ {
+ outSeverity = Severity::Info;
+ }
+ else
+ {
+ return SLANG_FAIL;
+ }
+ return SLANG_OK;
+}
+
+namespace { // anonymous
+
+enum class LineParseResult
+{
+ Single, ///< It's a single line
+ Start, ///< Line was the start of a message
+ Continuation, ///< Not totally clear, add to previous line if nothing else hit
+ Ignore, ///< Ignore the line
+};
+
+} // anonymous
+
+static SlangResult _parseGCCFamilyLine(const UnownedStringSlice& line, LineParseResult& outLineParseResult, DownstreamDiagnostic& outDiagnostic)
+{
+ typedef DownstreamDiagnostic Diagnostic;
+ typedef Diagnostic::Severity Severity;
+
+ // Set to default case
+ outLineParseResult = LineParseResult::Ignore;
+
+ /* example error output from different scenarios */
+
+ /*
+ tests/cpp-compiler/c-compile-error.c: In function 'int main(int, char**)':
+ tests/cpp-compiler/c-compile-error.c:8:13: error: 'b' was not declared in this scope
+ int a = b + c;
+ ^
+ tests/cpp-compiler/c-compile-error.c:8:17: error: 'c' was not declared in this scope
+ int a = b + c;
+ ^
+ */
+
+ /* /tmp/ccS0JCWe.o:c-compile-link-error.c:(.rdata$.refptr.thing[.refptr.thing]+0x0): undefined reference to `thing'
+ collect2: error: ld returned 1 exit status*/
+
+ /*
+ clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated [-Wdeprecated]
+ Undefined symbols for architecture x86_64:
+ "_thing", referenced from:
+ _main in c-compile-link-error-a83ace.o
+ ld: symbol(s) not found for architecture x86_64
+ clang: error: linker command failed with exit code 1 (use -v to see invocation) */
+
+ /* /tmp/c-compile-link-error-ccf151.o: In function `main':
+ c-compile-link-error.c:(.text+0x19): undefined reference to `thing'
+ clang: error: linker command failed with exit code 1 (use -v to see invocation)
+ */
+
+ /* /tmp/c-compile-link-error-301c8c.o: In function `main':
+ /home/travis/build/shader-slang/slang/tests/cpp-compiler/c-compile-link-error.c:10: undefined reference to `thing'
+ clang-7: error: linker command failed with exit code 1 (use -v to see invocation)*/
+
+ /* /path/slang-cpp-prelude.h:4:10: fatal error: ../slang.h: No such file or directory
+ #include "../slang.h"
+ ^~~~~~~~~~~~
+ compilation terminated.*/
+
+ outDiagnostic.stage = Diagnostic::Stage::Compile;
+
+ List<UnownedStringSlice> split;
+ StringUtil::split(line, ':', split);
+
+ // On windows we can have paths that are a: etc... if we detect this we can combine 0 - 1 to be 1.
+ if (split.getCount() > 1 && split[0].getLength() == 1)
+ {
+ const char c = split[0][0];
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+ {
+ // We'll assume it's a path
+ UnownedStringSlice path(split[0].begin(), split[1].end());
+ split.removeAt(0);
+ split[0] = path;
+ }
+ }
+
+ if (split.getCount() == 2)
+ {
+ const auto split0 = split[0].trim();
+ if (split0 == UnownedStringSlice::fromLiteral("ld"))
+ {
+ // We'll ignore for now
+ outDiagnostic.stage = Diagnostic::Stage::Link;
+ outDiagnostic.severity = Severity::Info;
+ outDiagnostic.text = split[1].trim();
+ outLineParseResult = LineParseResult::Start;
+ return SLANG_OK;
+ }
+
+ if (SLANG_SUCCEEDED(_parseSeverity(split0, outDiagnostic.severity)))
+ {
+ // Command line errors can be just contain 'error:' etc. Can be seen on apple/clang
+ outDiagnostic.stage = Diagnostic::Stage::Compile;
+ outDiagnostic.text = split[1].trim();
+ outLineParseResult = LineParseResult::Single;
+ return SLANG_OK;
+ }
+
+ outLineParseResult = LineParseResult::Ignore;
+ return SLANG_OK;
+ }
+ else if (split.getCount() == 3)
+ {
+ const auto split0 = split[0].trim();
+ const auto split1 = split[1].trim();
+ const auto text = split[2].trim();
+
+ // Check for special handling for clang (Can be Clang or clang apparently)
+ if (split0.startsWith(UnownedStringSlice::fromLiteral("clang")) ||
+ split0.startsWith(UnownedStringSlice::fromLiteral("Clang")) )
+ {
+ // Extract the type
+ SLANG_RETURN_ON_FAIL(_parseSeverity(split[1].trim(), outDiagnostic.severity));
+
+ if (text.startsWith("linker command failed"))
+ {
+ outDiagnostic.stage = Diagnostic::Stage::Link;
+ }
+
+ outDiagnostic.text = text;
+ outLineParseResult = LineParseResult::Start;
+ return SLANG_OK;
+ }
+ else if (split1.startsWith("(.text"))
+ {
+ // This is a little weak... but looks like it's a link error
+ outDiagnostic.filePath = split[0];
+ outDiagnostic.severity = Severity::Error;
+ outDiagnostic.stage = Diagnostic::Stage::Link;
+ outDiagnostic.text = text;
+ outLineParseResult = LineParseResult::Single;
+ return SLANG_OK;
+ }
+ else if (text.startsWith("ld returned"))
+ {
+ outDiagnostic.stage = DownstreamDiagnostic::Stage::Link;
+ SLANG_RETURN_ON_FAIL(_parseSeverity(split[1].trim(), outDiagnostic.severity));
+ outDiagnostic.text = line;
+ outLineParseResult = LineParseResult::Single;
+ return SLANG_OK;
+ }
+ else if (text == "")
+ {
+ // This is probably a prelude line, we'll just ignore it
+ outLineParseResult = LineParseResult::Ignore;
+ return SLANG_OK;
+ }
+ }
+ else if (split.getCount() == 4)
+ {
+ // Probably a link error, give the source line
+ String ext = Path::getPathExt(split[0]);
+
+ // Maybe a bit fragile -> but probably okay for now
+ if (ext != "o" && ext != "obj")
+ {
+ outLineParseResult = LineParseResult::Ignore;
+ return SLANG_OK;
+ }
+ else
+ {
+ outDiagnostic.filePath = split[1];
+ outDiagnostic.fileLine = 0;
+ outDiagnostic.severity = Diagnostic::Severity::Error;
+ outDiagnostic.stage = Diagnostic::Stage::Link;
+ outDiagnostic.text = split[3];
+
+ outLineParseResult = LineParseResult::Start;
+ return SLANG_OK;
+ }
+ }
+ else if (split.getCount() >= 5)
+ {
+ // Probably a regular error line
+ SLANG_RETURN_ON_FAIL(_parseSeverity(split[3].trim(), outDiagnostic.severity));
+
+ outDiagnostic.filePath = split[0];
+ SLANG_RETURN_ON_FAIL(StringUtil::parseInt(split[1], outDiagnostic.fileLine));
+
+ // Everything from 4 to the end is the error
+ outDiagnostic.text = UnownedStringSlice(split[4].begin(), split.getLast().end());
+
+ outLineParseResult = LineParseResult::Start;
+ return SLANG_OK;
+ }
+
+ // Assume it's a continuation
+ outLineParseResult = LineParseResult::Continuation;
+ return SLANG_OK;
+}
+
+/* static */SlangResult GCCDownstreamCompilerUtil::parseOutput(const ExecuteResult& exeRes, DownstreamDiagnostics& outOutput)
+{
+ LineParseResult prevLineResult = LineParseResult::Ignore;
+
+ outOutput.reset();
+ outOutput.rawDiagnostics = exeRes.standardError;
+
+ for (auto line : LineParser(exeRes.standardError.getUnownedSlice()))
+ {
+ Diagnostic diagnostic;
+ diagnostic.reset();
+
+ LineParseResult lineRes;
+
+ SLANG_RETURN_ON_FAIL(_parseGCCFamilyLine(line, lineRes, diagnostic));
+
+ switch (lineRes)
+ {
+ case LineParseResult::Start:
+ {
+ // It's start of a new message
+ outOutput.diagnostics.add(diagnostic);
+ prevLineResult = LineParseResult::Start;
+ break;
+ }
+ case LineParseResult::Single:
+ {
+ // It's a single message, without anything following
+ outOutput.diagnostics.add(diagnostic);
+ prevLineResult = LineParseResult::Ignore;
+ break;
+ }
+ case LineParseResult::Continuation:
+ {
+ if (prevLineResult == LineParseResult::Start || prevLineResult == LineParseResult::Continuation)
+ {
+ if (outOutput.diagnostics.getCount() > 0)
+ {
+ // We are now in a continuation, add to the last
+ auto& text = outOutput.diagnostics.getLast().text;
+ text.append("\n");
+ text.append(line);
+ }
+ prevLineResult = LineParseResult::Continuation;
+ }
+ break;
+ }
+ case LineParseResult::Ignore:
+ {
+ prevLineResult = lineRes;
+ break;
+ }
+ default: return SLANG_FAIL;
+ }
+ }
+
+ if (outOutput.has(Diagnostic::Severity::Error) || exeRes.resultCode != 0)
+ {
+ outOutput.result = SLANG_FAIL;
+ }
+
+ return SLANG_OK;
+}
+
+/* static */ SlangResult GCCDownstreamCompilerUtil::calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath)
+{
+ SLANG_ASSERT(options.modulePath.getLength());
+
+ outPath.Clear();
+
+ switch (options.targetType)
+ {
+ case TargetType::SharedLibrary:
+ {
+ outPath << SharedLibrary::calcPlatformPath(options.modulePath.getUnownedSlice());
+ return SLANG_OK;
+ }
+ case TargetType::Executable:
+ {
+ outPath << options.modulePath;
+ outPath << ProcessUtil::getExecutableSuffix();
+ return SLANG_OK;
+ }
+ case TargetType::Object:
+ {
+#if __CYGWIN__
+ outPath << options.modulePath << ".obj";
+#else
+ // Will be .o for typical gcc targets
+ outPath << options.modulePath << ".o";
+#endif
+ return SLANG_OK;
+ }
+ }
+
+ return SLANG_FAIL;
+}
+
+/* static */SlangResult GCCDownstreamCompilerUtil::calcCompileProducts(const CompileOptions& options, ProductFlags flags, List<String>& outPaths)
+{
+ SLANG_ASSERT(options.modulePath.getLength());
+
+ outPaths.clear();
+
+ if (flags & ProductFlag::Execution)
+ {
+ StringBuilder builder;
+ SLANG_RETURN_ON_FAIL(calcModuleFilePath(options, builder));
+ outPaths.add(builder);
+ }
+
+ return SLANG_OK;
+}
+
+/* static */SlangResult GCCDownstreamCompilerUtil::calcArgs(const CompileOptions& options, CommandLine& cmdLine)
+{
+ SLANG_ASSERT(options.sourceContents.getLength() == 0);
+ SLANG_ASSERT(options.modulePath.getLength());
+
+ PlatformKind platformKind = (options.platform == PlatformKind::Unknown) ? PlatformUtil::getPlatformKind() : options.platform;
+
+ if (options.sourceLanguage == SLANG_SOURCE_LANGUAGE_CPP)
+ {
+ cmdLine.addArg("-fvisibility=hidden");
+
+ // Need C++14 for partial specialization
+ cmdLine.addArg("-std=c++14");
+ }
+
+ // TODO(JS): Here we always set -m32 on x86. It could be argued it is only necessary when creating a shared library
+ // but if we create an object file, we don't know what to choose because we don't know what final usage is.
+ // It could also be argued that the platformKind could define the actual desired target - but as it stands
+ // we only have a target of 'Linux' (as opposed to Win32/64). Really it implies we need an arch enumeration too.
+ //
+ // For now we just make X86 binaries try and produce x86 compatible binaries as fixes the immediate problems.
+#if SLANG_PROCESSOR_X86
+ /* Used to specify the processor more broadly. For a x86 binary we need to make sure we build x86 builds
+ even when on an x64 system.
+ -m32
+ -m64*/
+ cmdLine.addArg("-m32");
+#endif
+
+ switch (options.optimizationLevel)
+ {
+ case OptimizationLevel::None:
+ {
+ // No optimization
+ cmdLine.addArg("-O0");
+ break;
+ }
+ case OptimizationLevel::Default:
+ {
+ cmdLine.addArg("-Os");
+ break;
+ }
+ case OptimizationLevel::High:
+ {
+ cmdLine.addArg("-O2");
+ break;
+ }
+ case OptimizationLevel::Maximal:
+ {
+ cmdLine.addArg("-O4");
+ break;
+ }
+ default: break;
+ }
+
+ if (options.debugInfoType != DebugInfoType::None)
+ {
+ cmdLine.addArg("-g");
+ }
+
+ if (options.flags & CompileOptions::Flag::Verbose)
+ {
+ cmdLine.addArg("-v");
+ }
+
+ switch (options.floatingPointMode)
+ {
+ case FloatingPointMode::Default: break;
+ case FloatingPointMode::Precise:
+ {
+ //cmdLine.addArg("-fno-unsafe-math-optimizations");
+ break;
+ }
+ case FloatingPointMode::Fast:
+ {
+ // We could enable SSE with -mfpmath=sse
+ // But that would only make sense on a x64/x86 type processor and only if that feature is present (it is on all x64)
+ cmdLine.addArg("-ffast-math");
+ break;
+ }
+ }
+
+ StringBuilder moduleFilePath;
+ calcModuleFilePath(options, moduleFilePath);
+
+ cmdLine.addArg("-o");
+ cmdLine.addArg(moduleFilePath);
+
+ switch (options.targetType)
+ {
+ case TargetType::SharedLibrary:
+ {
+ // Shared library
+ cmdLine.addArg("-shared");
+
+ if (PlatformUtil::isFamily(PlatformFamily::Unix, platformKind))
+ {
+ // Position independent
+ cmdLine.addArg("-fPIC");
+ }
+ break;
+ }
+ case TargetType::Executable:
+ {
+ break;
+ }
+ case TargetType::Object:
+ {
+ // Don't link, just produce object file
+ cmdLine.addArg("-c");
+ break;
+ }
+ default: break;
+ }
+
+ // Add defines
+ for (const auto& define : options.defines)
+ {
+ StringBuilder builder;
+
+ builder << "-D";
+ builder << define.nameWithSig;
+ if (define.value.getLength())
+ {
+ builder << "=" << define.value;
+ }
+
+ cmdLine.addArg(builder);
+ }
+
+ // Add includes
+ for (const auto& include : options.includePaths)
+ {
+ cmdLine.addArg("-I");
+ cmdLine.addArg(include);
+ }
+
+ // Link options
+ if (0) // && options.targetType != TargetType::Object)
+ {
+ //linkOptions << "-Wl,";
+ //cmdLine.addArg(linkOptions);
+ }
+
+ if (options.targetType == TargetType::SharedLibrary)
+ {
+ if (!PlatformUtil::isFamily(PlatformFamily::Apple, platformKind))
+ {
+ // On MacOS, this linker option is not supported. That's ok though in
+ // so far as on MacOS it does report any unfound symbols without the option.
+
+ // Linker flag to report any undefined symbols as a link error
+ cmdLine.addArg("-Wl,--no-undefined");
+ }
+ }
+
+ // Files to compile
+ for (const auto& sourceFile : options.sourceFiles)
+ {
+ cmdLine.addArg(sourceFile);
+ }
+
+ for (const auto& libPath : options.libraryPaths)
+ {
+ // Note that any escaping of the path is handled in the ProcessUtil::
+ cmdLine.addArg("-L");
+ cmdLine.addArg(libPath);
+ cmdLine.addArg("-F");
+ cmdLine.addArg(libPath);
+ }
+
+ if (options.sourceLanguage == SLANG_SOURCE_LANGUAGE_CPP && !PlatformUtil::isFamily(PlatformFamily::Windows, platformKind))
+ {
+ // Make STD libs available
+ cmdLine.addArg("-lstdc++");
+ // Make maths lib available
+ cmdLine.addArg("-lm");
+ }
+
+ return SLANG_OK;
+}
+
+/* static */SlangResult GCCDownstreamCompilerUtil::createCompiler(const String& path, const String& inExeName, RefPtr<DownstreamCompiler>& outCompiler)
+{
+ String exeName(inExeName);
+ if (path.getLength() > 0)
+ {
+ exeName = Path::combine(path, inExeName);
+ }
+
+ DownstreamCompiler::Desc desc;
+ SLANG_RETURN_ON_FAIL(GCCDownstreamCompilerUtil::calcVersion(exeName, desc));
+
+ RefPtr<CommandLineDownstreamCompiler> compiler(new GCCDownstreamCompiler(desc));
+ compiler->m_cmdLine.setExecutableFilename(exeName);
+
+ outCompiler = compiler;
+ return SLANG_OK;
+}
+
+/* static */SlangResult GCCDownstreamCompilerUtil::locateGCCCompilers(const String& path, ISlangSharedLibraryLoader* loader, DownstreamCompilerSet* set)
+{
+ SLANG_UNUSED(loader);
+ RefPtr<DownstreamCompiler> compiler;
+ if (SLANG_SUCCEEDED(createCompiler(path, "g++", compiler)))
+ {
+ set->addCompiler(compiler);
+ }
+ return SLANG_OK;
+}
+
+/* static */SlangResult GCCDownstreamCompilerUtil::locateClangCompilers(const String& path, ISlangSharedLibraryLoader* loader, DownstreamCompilerSet* set)
+{
+ SLANG_UNUSED(loader);
+
+ RefPtr<DownstreamCompiler> compiler;
+ if (SLANG_SUCCEEDED(createCompiler(path, "clang", compiler)))
+ {
+ set->addCompiler(compiler);
+ }
+ return SLANG_OK;
+}
+
+}
diff --git a/source/compiler-core/slang-gcc-compiler-util.h b/source/compiler-core/slang-gcc-compiler-util.h
new file mode 100644
index 000000000..b97144e35
--- /dev/null
+++ b/source/compiler-core/slang-gcc-compiler-util.h
@@ -0,0 +1,56 @@
+#ifndef SLANG_GCC_COMPILER_UTIL_H
+#define SLANG_GCC_COMPILER_UTIL_H
+
+#include "slang-downstream-compiler.h"
+
+namespace Slang
+{
+
+/* Utility for processing input and output of gcc-like compilers, including clang */
+struct GCCDownstreamCompilerUtil : public DownstreamCompilerBaseUtil
+{
+ /// Extracts version number into desc from text (assumes gcc/clang -v layout with a line with version)
+ static SlangResult parseVersion(const UnownedStringSlice& text, const UnownedStringSlice& prefix, DownstreamCompiler::Desc& outDesc);
+
+ /// Runs the exeName, and extracts the version info into outDesc
+ static SlangResult calcVersion(const String& exeName, DownstreamCompiler::Desc& outDesc);
+
+ /// Calculate gcc family compilers (including clang) cmdLine arguments from options
+ static SlangResult calcArgs(const CompileOptions& options, CommandLine& cmdLine);
+
+ /// Parse ExecuteResult into Output
+ static SlangResult parseOutput(const ExecuteResult& exeRes, DownstreamDiagnostics& outOutput);
+
+ /// Calculate the output module filename
+ static SlangResult calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath);
+
+ /// Given options, calculate paths to products produced for a compilation
+ static SlangResult calcCompileProducts(const CompileOptions& options, ProductFlags flags, List<String>& outPaths);
+
+ /// Given a path and an exe name, detects if compiler is present, and if so adds to compiler set.
+ static SlangResult createCompiler(const String& path, const String& inExeName, RefPtr<DownstreamCompiler>& outCompiler);
+
+ static SlangResult locateGCCCompilers(const String& path, ISlangSharedLibraryLoader* loader, DownstreamCompilerSet* set);
+
+ static SlangResult locateClangCompilers(const String& path, ISlangSharedLibraryLoader* loader, DownstreamCompilerSet* set);
+
+};
+
+class GCCDownstreamCompiler : public CommandLineDownstreamCompiler
+{
+public:
+ typedef CommandLineDownstreamCompiler Super;
+ typedef GCCDownstreamCompilerUtil Util;
+
+ // CommandLineCPPCompiler impl - just forwards to the Util
+ virtual SlangResult calcArgs(const CompileOptions& options, CommandLine& cmdLine) SLANG_OVERRIDE { return Util::calcArgs(options, cmdLine); }
+ virtual SlangResult parseOutput(const ExecuteResult& exeResult, DownstreamDiagnostics& output) SLANG_OVERRIDE { return Util::parseOutput(exeResult, output); }
+ virtual SlangResult calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath) SLANG_OVERRIDE { return Util::calcModuleFilePath(options, outPath); }
+ virtual SlangResult calcCompileProducts(const CompileOptions& options, ProductFlags flags, List<String>& outPaths) SLANG_OVERRIDE { return Util::calcCompileProducts(options, flags, outPaths); }
+
+ GCCDownstreamCompiler(const Desc& desc):Super(desc) {}
+};
+
+}
+
+#endif
diff --git a/source/compiler-core/slang-lexer-diagnostic-defs.h b/source/compiler-core/slang-lexer-diagnostic-defs.h
new file mode 100644
index 000000000..666ab057f
--- /dev/null
+++ b/source/compiler-core/slang-lexer-diagnostic-defs.h
@@ -0,0 +1,33 @@
+// The file is meant to be included multiple times, to produce different
+// pieces of declaration/definition code related to diagnostic messages
+//
+// Each diagnostic is declared here with:
+//
+// DIAGNOSTIC(id, severity, name, messageFormat)
+//
+// Where `id` is the unique diagnostic ID, `severity` is the default
+// severity (from the `Severity` enum), `name` is a name used to refer
+// to this diagnostic from code, and `messageFormat` is the default
+// (non-localized) message for the diagnostic, with placeholders
+// for any arguments.
+
+#ifndef DIAGNOSTIC
+#error Need to #define DIAGNOSTIC(...) before including
+#define DIAGNOSTIC(id, severity, name, messageFormat) /* */
+#endif
+
+//
+// 1xxxx - Lexical analysis
+//
+
+DIAGNOSTIC(10000, Error, illegalCharacterPrint, "illegal character '$0'")
+DIAGNOSTIC(10000, Error, illegalCharacterHex, "illegal character (0x$0)")
+DIAGNOSTIC(10001, Error, illegalCharacterLiteral, "illegal character literal")
+
+DIAGNOSTIC(10002, Warning, octalLiteral, "'0' prefix indicates octal literal")
+DIAGNOSTIC(10003, Error, invalidDigitForBase, "invalid digit for base-$1 literal: '$0'")
+
+DIAGNOSTIC(10004, Error, endOfFileInLiteral, "end of file in literal")
+DIAGNOSTIC(10005, Error, newlineInLiteral, "newline in literal")
+
+#undef DIAGNOSTIC
diff --git a/source/compiler-core/slang-lexer.cpp b/source/compiler-core/slang-lexer.cpp
new file mode 100644
index 000000000..83b8e0eec
--- /dev/null
+++ b/source/compiler-core/slang-lexer.cpp
@@ -0,0 +1,1400 @@
+// slang-lexer.cpp
+#include "slang-lexer.h"
+
+// This file implements the lexer/scanner, which is responsible for taking a raw stream of
+// input bytes and turning it into semantically useful tokens.
+//
+
+#include "slang-name.h"
+#include "slang-source-loc.h"
+
+#include "slang-core-diagnostics.h"
+
+namespace Slang
+{
+ Token TokenReader::getEndOfFileToken()
+ {
+ return Token(TokenType::EndOfFile, UnownedStringSlice::fromLiteral(""), SourceLoc());
+ }
+
+ const Token* TokenList::begin() const
+ {
+ SLANG_ASSERT(m_tokens.getCount());
+ return &m_tokens[0];
+ }
+
+ const Token* TokenList::end() const
+ {
+ SLANG_ASSERT(m_tokens.getCount());
+ SLANG_ASSERT(m_tokens[m_tokens.getCount() - 1].type == TokenType::EndOfFile);
+ return &m_tokens[m_tokens.getCount() - 1];
+ }
+
+ TokenSpan::TokenSpan()
+ : m_begin(nullptr)
+ , m_end (nullptr)
+ {}
+
+ TokenReader::TokenReader()
+ : m_cursor(nullptr)
+ , m_end (nullptr)
+ {}
+
+
+ Token& TokenReader::peekToken()
+ {
+ return m_nextToken;
+ }
+
+ TokenType TokenReader::peekTokenType() const
+ {
+ return m_nextToken.type;
+ }
+
+ SourceLoc TokenReader::peekLoc() const
+ {
+ return m_nextToken.loc;
+ }
+
+ Token TokenReader::advanceToken()
+ {
+ if (!m_cursor)
+ return getEndOfFileToken();
+
+ Token token = m_nextToken;
+ if (m_cursor < m_end)
+ {
+ m_cursor++;
+ m_nextToken = *m_cursor;
+ }
+ else
+ m_nextToken.type = TokenType::EndOfFile;
+ return token;
+ }
+
+ // Lexer
+
+ void Lexer::initialize(
+ SourceView* sourceView,
+ DiagnosticSink* sink,
+ NamePool* namePool,
+ MemoryArena* memoryArena,
+ OptionFlags optionFlags)
+ {
+ m_sourceView = sourceView;
+ m_sink = sink;
+ m_namePool = namePool;
+ m_memoryArena = memoryArena;
+
+ auto content = sourceView->getContent();
+
+ m_begin = content.begin();
+ m_cursor = content.begin();
+ m_end = content.end();
+
+ // Set the start location
+ m_startLoc = sourceView->getRange().begin;
+
+ m_tokenFlags = TokenFlag::AtStartOfLine | TokenFlag::AfterWhitespace;
+ m_lexerFlags = 0;
+ m_optionFlags = optionFlags;
+ }
+
+ Lexer::~Lexer()
+ {
+ }
+
+ enum { kEOF = -1 };
+
+ // Get the next input byte, without any handling of
+ // escaped newlines, non-ASCII code points, source locations, etc.
+ static int _peekRaw(Lexer* lexer)
+ {
+ // If we are at the end of the input, return a designated end-of-file value
+ if(lexer->m_cursor == lexer->m_end)
+ return kEOF;
+
+ // Otherwise, just look at the next byte
+ return *lexer->m_cursor;
+ }
+
+ // Read one input byte without any special handling (similar to `peekRaw`)
+ static int _advanceRaw(Lexer* lexer)
+ {
+ // The logic here is basically the same as for `peekRaw()`,
+ // escape we advance `cursor` if we aren't at the end.
+
+ if (lexer->m_cursor == lexer->m_end)
+ return kEOF;
+
+ return *lexer->m_cursor++;
+ }
+
+ // When the cursor is already at the first byte of an end-of-line sequence,
+ // consume one or two bytes that compose the sequence.
+ //
+ // Basically, a newline is one of:
+ //
+ // "\n"
+ // "\r"
+ // "\r\n"
+ // "\n\r"
+ //
+ // We always look for the longest match possible.
+ //
+ static void _handleNewLineInner(Lexer* lexer, int c)
+ {
+ SLANG_ASSERT(c == '\n' || c == '\r');
+
+ int d = _peekRaw(lexer);
+ if( (c ^ d) == ('\n' ^ '\r') )
+ {
+ _advanceRaw(lexer);
+ }
+ }
+
+ // Look ahead one code point, dealing with complications like
+ // escaped newlines.
+ static int _peek(Lexer* lexer)
+ {
+ // Look at the next raw byte, and decide what to do
+ int c = _peekRaw(lexer);
+
+ if(c == '\\')
+ {
+ // We might have a backslash-escaped newline.
+ // Look at the next byte (if any) to see.
+ //
+ // Note(tfoley): We are assuming a null-terminated input here,
+ // so that we can safely look at the next byte without issue.
+ int d = lexer->m_cursor[1];
+ switch (d)
+ {
+ case '\r': case '\n':
+ {
+ // The newline was escaped, so return the code point after *that*
+
+ int e = lexer->m_cursor[2];
+ if ((d ^ e) == ('\r' ^ '\n'))
+ return lexer->m_cursor[3];
+ return e;
+ }
+
+ default:
+ break;
+ }
+ }
+ // TODO: handle UTF-8 encoding for non-ASCII code points here
+
+ // Default case is to just hand along the byte we read as an ASCII code point.
+ return c;
+ }
+
+ // Get the next code point from the input, and advance the cursor.
+ static int _advance(Lexer* lexer)
+ {
+ // We are going to loop, but only as a way of handling
+ // escaped line endings.
+ for (;;)
+ {
+ // If we are at the end of the input, then the task is easy.
+ if (lexer->m_cursor == lexer->m_end)
+ return kEOF;
+
+ // Look at the next raw byte, and decide what to do
+ int c = *lexer->m_cursor++;
+
+ if (c == '\\')
+ {
+ // We might have a backslash-escaped newline.
+ // Look at the next byte (if any) to see.
+ //
+ // Note(tfoley): We are assuming a null-terminated input here,
+ // so that we can safely look at the next byte without issue.
+ int d = *lexer->m_cursor;
+ switch (d)
+ {
+ case '\r': case '\n':
+ // handle the end-of-line for our source location tracking
+ lexer->m_cursor++;
+ _handleNewLineInner(lexer, d);
+
+ lexer->m_tokenFlags |= TokenFlag::ScrubbingNeeded;
+
+ // Now try again, looking at the character after the
+ // escaped newline.
+ continue;
+
+ default:
+ break;
+ }
+ }
+
+ // TODO: Need to handle non-ASCII code points.
+
+ // Default case is to return the raw byte we saw.
+ return c;
+ }
+ }
+
+ static void _handleNewLine(Lexer* lexer)
+ {
+ int c = _advance(lexer);
+ _handleNewLineInner(lexer, c);
+ }
+
+ static void _lexLineComment(Lexer* lexer)
+ {
+ for(;;)
+ {
+ switch(_peek(lexer))
+ {
+ case '\n': case '\r': case kEOF:
+ return;
+
+ default:
+ _advance(lexer);
+ continue;
+ }
+ }
+ }
+
+ static void _lexBlockComment(Lexer* lexer)
+ {
+ for(;;)
+ {
+ switch(_peek(lexer))
+ {
+ case kEOF:
+ // TODO(tfoley) diagnostic!
+ return;
+
+ case '\n': case '\r':
+ _handleNewLine(lexer);
+ continue;
+
+ case '*':
+ _advance(lexer);
+ switch( _peek(lexer) )
+ {
+ case '/':
+ _advance(lexer);
+ return;
+
+ default:
+ continue;
+ }
+
+ default:
+ _advance(lexer);
+ continue;
+ }
+ }
+ }
+
+ static void _lexHorizontalSpace(Lexer* lexer)
+ {
+ for(;;)
+ {
+ switch(_peek(lexer))
+ {
+ case ' ': case '\t':
+ _advance(lexer);
+ continue;
+
+ default:
+ return;
+ }
+ }
+ }
+
+ static void _lexIdentifier(Lexer* lexer)
+ {
+ for(;;)
+ {
+ int c = _peek(lexer);
+ if(('a' <= c ) && (c <= 'z')
+ || ('A' <= c) && (c <= 'Z')
+ || ('0' <= c) && (c <= '9')
+ || (c == '_'))
+ {
+ _advance(lexer);
+ continue;
+ }
+
+ return;
+ }
+ }
+
+ static SourceLoc _getSourceLoc(Lexer* lexer)
+ {
+ return lexer->m_startLoc + (lexer->m_cursor - lexer->m_begin);
+ }
+
+ static void _lexDigits(Lexer* lexer, int base)
+ {
+ for(;;)
+ {
+ int c = _peek(lexer);
+
+ int digitVal = 0;
+ switch(c)
+ {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ digitVal = c - '0';
+ break;
+
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ if(base <= 10) return;
+ digitVal = 10 + c - 'a';
+ break;
+
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ if(base <= 10) return;
+ digitVal = 10 + c - 'A';
+ break;
+
+ default:
+ // Not more digits!
+ return;
+ }
+
+ if(digitVal >= base)
+ {
+ char buffer[] = { (char) c, 0 };
+ lexer->m_sink->diagnose(_getSourceLoc(lexer), LexerDiagnostics::invalidDigitForBase, buffer, base);
+ }
+
+ _advance(lexer);
+ }
+ }
+
+ static TokenType _maybeLexNumberSuffix(Lexer* lexer, TokenType tokenType)
+ {
+ // Be liberal in what we accept here, so that figuring out
+ // the semantics of a numeric suffix is left up to the parser
+ // and semantic checking logic.
+ //
+ for( ;;)
+ {
+ int c = _peek(lexer);
+
+ // Accept any alphanumeric character, plus underscores.
+ if(('a' <= c ) && (c <= 'z')
+ || ('A' <= c) && (c <= 'Z')
+ || ('0' <= c) && (c <= '9')
+ || (c == '_'))
+ {
+ _advance(lexer);
+ continue;
+ }
+
+ // Stop at the first character that isn't
+ // alphanumeric.
+ return tokenType;
+ }
+ }
+
+ static bool _isNumberExponent(int c, int base)
+ {
+ switch( c )
+ {
+ default:
+ return false;
+
+ case 'e': case 'E':
+ if(base != 10) return false;
+ break;
+
+ case 'p': case 'P':
+ if(base != 16) return false;
+ break;
+ }
+
+ return true;
+ }
+
+ static bool _maybeLexNumberExponent(Lexer* lexer, int base)
+ {
+ if(!_isNumberExponent(_peek(lexer), base))
+ return false;
+
+ // we saw an exponent marker
+ _advance(lexer);
+
+ // Now start to read the exponent
+ switch( _peek(lexer) )
+ {
+ case '+': case '-':
+ _advance(lexer);
+ break;
+ }
+
+ // TODO(tfoley): it would be an error to not see digits here...
+
+ _lexDigits(lexer, 10);
+
+ return true;
+ }
+
+ static TokenType _lexNumberAfterDecimalPoint(Lexer* lexer, int base)
+ {
+ _lexDigits(lexer, base);
+ _maybeLexNumberExponent(lexer, base);
+
+ return _maybeLexNumberSuffix(lexer, TokenType::FloatingPointLiteral);
+ }
+
+ static TokenType _lexNumber(Lexer* lexer, int base)
+ {
+ // TODO(tfoley): Need to consider whehter to allow any kind of digit separator character.
+
+ TokenType tokenType = TokenType::IntegerLiteral;
+
+ // At the start of things, we just concern ourselves with digits
+ _lexDigits(lexer, base);
+
+ if( _peek(lexer) == '.' )
+ {
+ tokenType = TokenType::FloatingPointLiteral;
+
+ _advance(lexer);
+ _lexDigits(lexer, base);
+ }
+
+ if( _maybeLexNumberExponent(lexer, base))
+ {
+ tokenType = TokenType::FloatingPointLiteral;
+ }
+
+ _maybeLexNumberSuffix(lexer, tokenType);
+ return tokenType;
+ }
+
+ static int _maybeReadDigit(char const** ioCursor, int base)
+ {
+ auto& cursor = *ioCursor;
+
+ for(;;)
+ {
+ int c = *cursor;
+ switch(c)
+ {
+ default:
+ return -1;
+
+ // TODO: need to decide on digit separator characters
+ case '_':
+ cursor++;
+ continue;
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ cursor++;
+ return c - '0';
+
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ if(base > 10)
+ {
+ cursor++;
+ return 10 + c - 'a';
+ }
+ return -1;
+
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ if(base > 10)
+ {
+ cursor++;
+ return 10 + c - 'A';
+ }
+ return -1;
+ }
+ }
+ }
+
+ static int _readOptionalBase(char const** ioCursor)
+ {
+ auto& cursor = *ioCursor;
+ if( *cursor == '0' )
+ {
+ cursor++;
+ switch(*cursor)
+ {
+ case 'x': case 'X':
+ cursor++;
+ return 16;
+
+ case 'b': case 'B':
+ cursor++;
+ return 2;
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return 8;
+
+ default:
+ return 10;
+ }
+ }
+
+ return 10;
+ }
+
+
+
+ IntegerLiteralValue getIntegerLiteralValue(Token const& token, UnownedStringSlice* outSuffix)
+ {
+ IntegerLiteralValue value = 0;
+
+ const UnownedStringSlice content = token.getContent();
+
+ char const* cursor = content.begin();
+ char const* end = content.end();
+
+ int base = _readOptionalBase(&cursor);
+
+ for( ;;)
+ {
+ int digit = _maybeReadDigit(&cursor, base);
+ if(digit < 0)
+ break;
+
+ value = value*base + digit;
+ }
+
+ if(outSuffix)
+ {
+ *outSuffix = UnownedStringSlice(cursor, end);
+ }
+
+ return value;
+ }
+
+ FloatingPointLiteralValue getFloatingPointLiteralValue(Token const& token, UnownedStringSlice* outSuffix)
+ {
+ FloatingPointLiteralValue value = 0;
+
+ const UnownedStringSlice content = token.getContent();
+
+ char const* cursor = content.begin();
+ char const* end = content.end();
+
+ int radix = _readOptionalBase(&cursor);
+
+ bool seenDot = false;
+ FloatingPointLiteralValue divisor = 1;
+ for( ;;)
+ {
+ if(*cursor == '.')
+ {
+ cursor++;
+ seenDot = true;
+ continue;
+ }
+
+ int digit = _maybeReadDigit(&cursor, radix);
+ if(digit < 0)
+ break;
+
+ value = value*radix + digit;
+
+ if(seenDot)
+ {
+ divisor *= radix;
+ }
+ }
+
+ // Now read optional exponent
+ if(_isNumberExponent(*cursor, radix))
+ {
+ cursor++;
+
+ bool exponentIsNegative = false;
+ switch(*cursor)
+ {
+ default:
+ break;
+
+ case '-':
+ exponentIsNegative = true;
+ cursor++;
+ break;
+
+ case '+':
+ cursor++;
+ break;
+ }
+
+ int exponentRadix = 10;
+ int exponent = 0;
+
+ for(;;)
+ {
+ int digit = _maybeReadDigit(&cursor, exponentRadix);
+ if(digit < 0)
+ break;
+
+ exponent = exponent*exponentRadix + digit;
+ }
+
+ FloatingPointLiteralValue exponentBase = 10;
+ if(radix == 16)
+ {
+ exponentBase = 2;
+ }
+
+ FloatingPointLiteralValue exponentValue = pow(exponentBase, exponent);
+
+ if( exponentIsNegative )
+ {
+ divisor *= exponentValue;
+ }
+ else
+ {
+ value *= exponentValue;
+ }
+ }
+
+ value /= divisor;
+
+ if(outSuffix)
+ {
+ *outSuffix = UnownedStringSlice(cursor, end);
+ }
+
+ return value;
+ }
+
+ static void _lexStringLiteralBody(Lexer* lexer, char quote)
+ {
+ for(;;)
+ {
+ int c = _peek(lexer);
+ if(c == quote)
+ {
+ _advance(lexer);
+ return;
+ }
+
+ switch(c)
+ {
+ case kEOF:
+ lexer->m_sink->diagnose(_getSourceLoc(lexer), LexerDiagnostics::endOfFileInLiteral);
+ return;
+
+ case '\n': case '\r':
+ lexer->m_sink->diagnose(_getSourceLoc(lexer), LexerDiagnostics::newlineInLiteral);
+ return;
+
+ case '\\':
+ // Need to handle various escape sequence cases
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '\'':
+ case '\"':
+ case '\\':
+ case '?':
+ case 'a':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ case 'v':
+ _advance(lexer);
+ break;
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7':
+ // octal escape: up to 3 characters
+ _advance(lexer);
+ for(int ii = 0; ii < 3; ++ii)
+ {
+ int d = _peek(lexer);
+ if(('0' <= d) && (d <= '7'))
+ {
+ _advance(lexer);
+ continue;
+ }
+ else
+ {
+ break;
+ }
+ }
+ break;
+
+ case 'x':
+ // hexadecimal escape: any number of characters
+ _advance(lexer);
+ for(;;)
+ {
+ int d = _peek(lexer);
+ if(('0' <= d) && (d <= '9')
+ || ('a' <= d) && (d <= 'f')
+ || ('A' <= d) && (d <= 'F'))
+ {
+ _advance(lexer);
+ continue;
+ }
+ else
+ {
+ break;
+ }
+ }
+ break;
+
+ // TODO: Unicode escape sequences
+
+ }
+ break;
+
+ default:
+ _advance(lexer);
+ continue;
+ }
+ }
+ }
+
+ String getStringLiteralTokenValue(Token const& token)
+ {
+ SLANG_ASSERT(token.type == TokenType::StringLiteral
+ || token.type == TokenType::CharLiteral);
+
+ const UnownedStringSlice content = token.getContent();
+
+ char const* cursor = content.begin();
+ char const* end = content.end();
+ SLANG_UNREFERENCED_VARIABLE(end);
+
+ auto quote = *cursor++;
+ SLANG_ASSERT(quote == '\'' || quote == '"');
+
+ StringBuilder valueBuilder;
+ for(;;)
+ {
+ SLANG_ASSERT(cursor != end);
+
+ auto c = *cursor++;
+
+ // If we see a closing quote, then we are at the end of the string literal
+ if(c == quote)
+ {
+ SLANG_ASSERT(cursor == end);
+ return valueBuilder.ProduceString();
+ }
+
+ // Characters that don't being escape sequences are easy;
+ // just append them to the buffer and move on.
+ if(c != '\\')
+ {
+ valueBuilder.Append(c);
+ continue;
+ }
+
+ // Now we look at another character to figure out the kind of
+ // escape sequence we are dealing with:
+
+ char d = *cursor++;
+
+ switch(d)
+ {
+ // Simple characters that just needed to be escaped
+ case '\'':
+ case '\"':
+ case '\\':
+ case '?':
+ valueBuilder.Append(d);
+ continue;
+
+ // Traditional escape sequences for special characters
+ case 'a': valueBuilder.Append('\a'); continue;
+ case 'b': valueBuilder.Append('\b'); continue;
+ case 'f': valueBuilder.Append('\f'); continue;
+ case 'n': valueBuilder.Append('\n'); continue;
+ case 'r': valueBuilder.Append('\r'); continue;
+ case 't': valueBuilder.Append('\t'); continue;
+ case 'v': valueBuilder.Append('\v'); continue;
+
+ // Octal escape: up to 3 characterws
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7':
+ {
+ cursor--;
+ int value = 0;
+ for(int ii = 0; ii < 3; ++ii)
+ {
+ d = *cursor;
+ if(('0' <= d) && (d <= '7'))
+ {
+ value = value*8 + (d - '0');
+
+ cursor++;
+ continue;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ // TODO: add support for appending an arbitrary code point?
+ valueBuilder.Append((char) value);
+ }
+ continue;
+
+ // Hexadecimal escape: any number of characters
+ case 'x':
+ {
+ cursor--;
+ int value = 0;
+ for(;;)
+ {
+ d = *cursor++;
+ int digitValue = 0;
+ if(('0' <= d) && (d <= '9'))
+ {
+ digitValue = d - '0';
+ }
+ else if( ('a' <= d) && (d <= 'f') )
+ {
+ digitValue = d - 'a';
+ }
+ else if( ('A' <= d) && (d <= 'F') )
+ {
+ digitValue = d - 'A';
+ }
+ else
+ {
+ cursor--;
+ break;
+ }
+
+ value = value*16 + digitValue;
+ }
+
+ // TODO: add support for appending an arbitrary code point?
+ valueBuilder.Append((char) value);
+ }
+ continue;
+
+ // TODO: Unicode escape sequences
+
+ }
+ }
+ }
+
+ String getFileNameTokenValue(Token const& token)
+ {
+ const UnownedStringSlice content = token.getContent();
+
+ // A file name usually doesn't process escape sequences
+ // (this is import on Windows, where `\\` is a valid
+ // path separator character).
+
+ // Just trim off the first and last characters to remove the quotes
+ // (whether they were `""` or `<>`.
+ return String(content.begin() + 1, content.end() - 1);
+ }
+
+
+
+ static TokenType _lexTokenImpl(Lexer* lexer, LexerFlags effectiveFlags)
+ {
+ if(effectiveFlags & kLexerFlag_ExpectDirectiveMessage)
+ {
+ for(;;)
+ {
+ switch(_peek(lexer))
+ {
+ default:
+ _advance(lexer);
+ continue;
+
+ case kEOF: case '\r': case '\n':
+ break;
+ }
+ break;
+ }
+ return TokenType::DirectiveMessage;
+ }
+
+ switch(_peek(lexer))
+ {
+ default:
+ break;
+
+ case kEOF:
+ if((effectiveFlags & kLexerFlag_InDirective) != 0)
+ return TokenType::EndOfDirective;
+ return TokenType::EndOfFile;
+
+ case '\r': case '\n':
+ if((effectiveFlags & kLexerFlag_InDirective) != 0)
+ return TokenType::EndOfDirective;
+ _handleNewLine(lexer);
+ return TokenType::NewLine;
+
+ case ' ': case '\t':
+ _lexHorizontalSpace(lexer);
+ return TokenType::WhiteSpace;
+
+ case '.':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return _lexNumberAfterDecimalPoint(lexer, 10);
+
+ // TODO(tfoley): handle ellipsis (`...`)
+
+ default:
+ return TokenType::Dot;
+ }
+
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return _lexNumber(lexer, 10);
+
+ case '0':
+ {
+ auto loc = _getSourceLoc(lexer);
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ default:
+ return _maybeLexNumberSuffix(lexer, TokenType::IntegerLiteral);
+
+ case '.':
+ _advance(lexer);
+ return _lexNumberAfterDecimalPoint(lexer, 10);
+
+ case 'x': case 'X':
+ _advance(lexer);
+ return _lexNumber(lexer, 16);
+
+ case 'b': case 'B':
+ _advance(lexer);
+ return _lexNumber(lexer, 2);
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ lexer->m_sink->diagnose(loc, LexerDiagnostics::octalLiteral);
+ return _lexNumber(lexer, 8);
+ }
+ }
+
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f': case 'g': case 'h': case 'i': case 'j':
+ case 'k': case 'l': case 'm': case 'n': case 'o':
+ case 'p': case 'q': case 'r': case 's': case 't':
+ case 'u': case 'v': case 'w': case 'x': case 'y':
+ case 'z':
+ case 'A': case 'B': case 'C': case 'D': case 'E':
+ case 'F': case 'G': case 'H': case 'I': case 'J':
+ case 'K': case 'L': case 'M': case 'N': case 'O':
+ case 'P': case 'Q': case 'R': case 'S': case 'T':
+ case 'U': case 'V': case 'W': case 'X': case 'Y':
+ case 'Z':
+ case '_':
+ _lexIdentifier(lexer);
+ return TokenType::Identifier;
+
+ case '\"':
+ _advance(lexer);
+ _lexStringLiteralBody(lexer, '\"');
+ return TokenType::StringLiteral;
+
+ case '\'':
+ _advance(lexer);
+ _lexStringLiteralBody(lexer, '\'');
+ return TokenType::CharLiteral;
+
+ case '+':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '+': _advance(lexer); return TokenType::OpInc;
+ case '=': _advance(lexer); return TokenType::OpAddAssign;
+ default:
+ return TokenType::OpAdd;
+ }
+
+ case '-':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '-': _advance(lexer); return TokenType::OpDec;
+ case '=': _advance(lexer); return TokenType::OpSubAssign;
+ case '>': _advance(lexer); return TokenType::RightArrow;
+ default:
+ return TokenType::OpSub;
+ }
+
+ case '*':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '=': _advance(lexer); return TokenType::OpMulAssign;
+ default:
+ return TokenType::OpMul;
+ }
+
+ case '/':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '=': _advance(lexer); return TokenType::OpDivAssign;
+ case '/': _advance(lexer); _lexLineComment(lexer); return TokenType::LineComment;
+ case '*': _advance(lexer); _lexBlockComment(lexer); return TokenType::BlockComment;
+ default:
+ return TokenType::OpDiv;
+ }
+
+ case '%':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '=': _advance(lexer); return TokenType::OpModAssign;
+ default:
+ return TokenType::OpMod;
+ }
+
+ case '|':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '|': _advance(lexer); return TokenType::OpOr;
+ case '=': _advance(lexer); return TokenType::OpOrAssign;
+ default:
+ return TokenType::OpBitOr;
+ }
+
+ case '&':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '&': _advance(lexer); return TokenType::OpAnd;
+ case '=': _advance(lexer); return TokenType::OpAndAssign;
+ default:
+ return TokenType::OpBitAnd;
+ }
+
+ case '^':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '=': _advance(lexer); return TokenType::OpXorAssign;
+ default:
+ return TokenType::OpBitXor;
+ }
+
+ case '>':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '>':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '=': _advance(lexer); return TokenType::OpShrAssign;
+ default: return TokenType::OpRsh;
+ }
+ case '=': _advance(lexer); return TokenType::OpGeq;
+ default:
+ return TokenType::OpGreater;
+ }
+
+ case '<':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '<':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '=': _advance(lexer); return TokenType::OpShlAssign;
+ default: return TokenType::OpLsh;
+ }
+ case '=': _advance(lexer); return TokenType::OpLeq;
+ default:
+ return TokenType::OpLess;
+ }
+
+ case '=':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '=': _advance(lexer); return TokenType::OpEql;
+ default:
+ return TokenType::OpAssign;
+ }
+
+ case '!':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '=': _advance(lexer); return TokenType::OpNeq;
+ default:
+ return TokenType::OpNot;
+ }
+
+ case '#':
+ _advance(lexer);
+ switch(_peek(lexer))
+ {
+ case '#': _advance(lexer); return TokenType::PoundPound;
+ default:
+ return TokenType::Pound;
+ }
+
+ case '~': _advance(lexer); return TokenType::OpBitNot;
+
+ case ':':
+ {
+ _advance(lexer);
+ if (_peek(lexer) == ':')
+ {
+ _advance(lexer);
+ return TokenType::Scope;
+ }
+ return TokenType::Colon;
+ }
+ case ';': _advance(lexer); return TokenType::Semicolon;
+ case ',': _advance(lexer); return TokenType::Comma;
+
+ case '{': _advance(lexer); return TokenType::LBrace;
+ case '}': _advance(lexer); return TokenType::RBrace;
+ case '[': _advance(lexer); return TokenType::LBracket;
+ case ']': _advance(lexer); return TokenType::RBracket;
+ case '(': _advance(lexer); return TokenType::LParent;
+ case ')': _advance(lexer); return TokenType::RParent;
+
+ case '?': _advance(lexer); return TokenType::QuestionMark;
+ case '@': _advance(lexer); return TokenType::At;
+ case '$': _advance(lexer); return TokenType::Dollar;
+
+ }
+
+ // TODO(tfoley): If we ever wanted to support proper Unicode
+ // in identifiers, etc., then this would be the right place
+ // to perform a more expensive dispatch based on the actual
+ // code point (and not just the first byte).
+
+ {
+ // If none of the above cases matched, then we have an
+ // unexpected/invalid character.
+
+ auto loc = _getSourceLoc(lexer);
+ int c = _advance(lexer);
+ if(!(effectiveFlags & kLexerFlag_IgnoreInvalid))
+ {
+ auto sink = lexer->m_sink;
+ if(c >= 0x20 && c <= 0x7E)
+ {
+ char buffer[] = { (char) c, 0 };
+ sink->diagnose(loc, LexerDiagnostics::illegalCharacterPrint, buffer);
+ }
+ else
+ {
+ // Fallback: print as hexadecimal
+ sink->diagnose(loc, LexerDiagnostics::illegalCharacterHex, String((unsigned char)c, 16));
+ }
+ }
+
+ return TokenType::Invalid;
+ }
+ }
+
+ Token Lexer::lexToken(LexerFlags extraFlags)
+ {
+ auto& flags = m_tokenFlags;
+ for(;;)
+ {
+ Token token;
+ token.loc = _getSourceLoc(this);
+
+ char const* textBegin = m_cursor;
+
+ auto tokenType = _lexTokenImpl(this, m_lexerFlags | extraFlags);
+
+ // The low-level lexer produces tokens for things we want
+ // to ignore, such as white space, so we skip them here.
+ switch(tokenType)
+ {
+ case TokenType::Invalid:
+ flags = 0;
+ continue;
+
+ case TokenType::NewLine:
+ flags = TokenFlag::AtStartOfLine | TokenFlag::AfterWhitespace;
+ continue;
+
+ case TokenType::WhiteSpace:
+ {
+ flags |= TokenFlag::AfterWhitespace;
+ continue;
+ }
+ case TokenType::BlockComment:
+ case TokenType::LineComment:
+ {
+ flags |= TokenFlag::AfterWhitespace;
+ if (m_optionFlags & OptionFlag::TokenizeComments)
+ {
+ // We don't break here, and use the normal token adding logic
+ // because we want the behavior to be identical (in terms of flags etc)
+ // as if TokenizeComments is not enabled
+ char const* textEnd = m_cursor;
+
+ token.type = tokenType;
+ token.flags = m_tokenFlags;
+ token.setContent(UnownedStringSlice(textBegin, textEnd));
+
+ return token;
+ }
+
+ continue;
+ }
+
+ // We don't want to skip the end-of-file token, but we *do*
+ // want to make sure it has appropriate flags to make our life easier
+ case TokenType::EndOfFile:
+ flags |= TokenFlag::AtStartOfLine | TokenFlag::AfterWhitespace;
+ break;
+
+ // We will also do some book-keeping around preprocessor directives here:
+ //
+ // If we see a `#` at the start of a line, then we are entering a
+ // preprocessor directive.
+ case TokenType::Pound:
+ if((flags & TokenFlag::AtStartOfLine) != 0)
+ m_lexerFlags |= kLexerFlag_InDirective;
+ break;
+ //
+ // And if we saw an end-of-line during a directive, then we are
+ // now leaving that directive.
+ //
+ case TokenType::EndOfDirective:
+ m_lexerFlags &= ~kLexerFlag_InDirective;
+ break;
+
+ default:
+ break;
+ }
+
+ token.type = tokenType;
+
+ char const* textEnd = m_cursor;
+
+ // Note(tfoley): `StringBuilder::Append()` seems to crash when appending zero bytes
+ if(textEnd != textBegin)
+ {
+ // "scrubbing" token value here to remove escaped newlines...
+ //
+ // Only perform this work if we encountered an escaped newline
+ // while lexing this token (e.g., keep a flag on the lexer), or
+ // do it on-demand when the actual value of the token is needed.
+ if (m_tokenFlags & TokenFlag::ScrubbingNeeded)
+ {
+ // Allocate space that will always be more than enough for stripped contents
+ char* startDst = (char*)m_memoryArena->allocateUnaligned(textEnd - textBegin);
+ char* dst = startDst;
+
+ auto tt = textBegin;
+ while (tt != textEnd)
+ {
+ char c = *tt++;
+ if (c == '\\')
+ {
+ char d = *tt;
+ switch (d)
+ {
+ case '\r': case '\n':
+ {
+ tt++;
+ char e = *tt;
+ if ((d ^ e) == ('\r' ^ '\n'))
+ {
+ tt++;
+ }
+ }
+ continue;
+
+ default:
+ break;
+ }
+ }
+ *dst++ = c;
+ }
+ token.setContent(UnownedStringSlice(startDst, dst));
+ }
+ else
+ {
+ token.setContent(UnownedStringSlice(textBegin, textEnd));
+ }
+ }
+
+ token.flags = flags;
+
+ m_tokenFlags = 0;
+
+ if (tokenType == TokenType::Identifier)
+ {
+ token.setName(m_namePool->getName(token.getContent()));
+ }
+
+ return token;
+ }
+ }
+
+ TokenList Lexer::lexAllTokens()
+ {
+ TokenList tokenList;
+ for(;;)
+ {
+ Token token = lexToken();
+ tokenList.add(token);
+
+ if(token.type == TokenType::EndOfFile)
+ return tokenList;
+ }
+ }
+
+ /* static */UnownedStringSlice Lexer::sourceLocationLexer(const UnownedStringSlice& in)
+ {
+ Lexer lexer;
+
+ SourceManager sourceManager;
+ sourceManager.initialize(nullptr, nullptr);
+
+ auto sourceFile = sourceManager.createSourceFileWithString(PathInfo::makeUnknown(), in);
+ auto sourceView = sourceManager.createSourceView(sourceFile, nullptr, SourceLoc::fromRaw(0));
+
+ DiagnosticSink sink(&sourceManager, nullptr);
+
+ MemoryArena arena;
+
+ RootNamePool rootNamePool;
+ NamePool namePool;
+ namePool.setRootNamePool(&rootNamePool);
+
+ lexer.initialize(sourceView, &sink, &namePool, &arena);
+
+ Token tok = lexer.lexToken();
+
+ if (tok.type == TokenType::Invalid)
+ {
+ return UnownedStringSlice();
+ }
+
+ const int offset = sourceView->getRange().getOffset(tok.loc);
+
+ SLANG_ASSERT(offset >= 0 && offset <= in.getLength());
+ SLANG_ASSERT(Index(offset + tok.charsCount) <= in.getLength());
+
+ return UnownedStringSlice(in.begin() + offset, in.begin() + offset + tok.charsCount);
+ }
+
+}
diff --git a/source/compiler-core/slang-lexer.h b/source/compiler-core/slang-lexer.h
new file mode 100644
index 000000000..f9a698568
--- /dev/null
+++ b/source/compiler-core/slang-lexer.h
@@ -0,0 +1,164 @@
+#ifndef SLANG_LEXER_H
+#define SLANG_LEXER_H
+
+#include "../core/slang-basic.h"
+#include "slang-diagnostic-sink.h"
+
+namespace Slang
+{
+ struct NamePool;
+
+ //
+
+ struct TokenList
+ {
+ const Token* begin() const;
+ const Token* end() const;
+
+ SLANG_FORCE_INLINE void add(const Token& token) { m_tokens.add(token); }
+
+ List<Token> m_tokens;
+ };
+
+ struct TokenSpan
+ {
+ TokenSpan();
+ TokenSpan(
+ TokenList const& tokenList)
+ : m_begin(tokenList.begin())
+ , m_end (tokenList.end ())
+ {}
+
+ const Token* begin() const { return m_begin; }
+ const Token* end () const { return m_end ; }
+
+ int getCount() { return (int)(m_end - m_begin); }
+
+ const Token* m_begin;
+ const Token* m_end;
+ };
+
+ struct TokenReader
+ {
+ Token m_nextToken;
+ TokenReader();
+ explicit TokenReader(TokenSpan const& tokens)
+ : m_cursor(tokens.begin())
+ , m_end (tokens.end ())
+ , m_nextToken(tokens.begin() ? *tokens.begin() : getEndOfFileToken())
+ {}
+ explicit TokenReader(TokenList const& tokens)
+ : m_cursor(tokens.begin())
+ , m_end (tokens.end ())
+ , m_nextToken(tokens.begin() ? *tokens.begin() : getEndOfFileToken())
+ {}
+ struct ParsingCursor
+ {
+ Token nextToken;
+ const Token* tokenReaderCursor = nullptr;
+ };
+ ParsingCursor getCursor()
+ {
+ ParsingCursor rs;
+ rs.nextToken = m_nextToken;
+ rs.tokenReaderCursor = m_cursor;
+ return rs;
+ }
+ void setCursor(ParsingCursor cursor)
+ {
+ m_cursor = cursor.tokenReaderCursor;
+ m_nextToken = cursor.nextToken;
+ }
+ bool isAtCursor(const ParsingCursor& cursor) const
+ {
+ return cursor.tokenReaderCursor == m_cursor;
+ }
+ bool isAtEnd() const { return m_cursor == m_end; }
+ Token& peekToken();
+ TokenType peekTokenType() const;
+ SourceLoc peekLoc() const;
+
+ Token advanceToken();
+
+ int getCount() { return (int)(m_end - m_cursor); }
+
+ const Token* m_cursor;
+ const Token* m_end;
+ static Token getEndOfFileToken();
+ };
+
+ typedef unsigned int LexerFlags;
+ enum
+ {
+ kLexerFlag_InDirective = 1 << 0, ///< Turn end-of-line and end-of-file into end-of-directive
+ kLexerFlag_ExpectFileName = 1 << 1, ///< Support `<>` style strings for file paths
+ kLexerFlag_IgnoreInvalid = 1 << 2, ///< Suppress errors about invalid/unsupported characters
+ kLexerFlag_ExpectDirectiveMessage = 1 << 3, ///< Don't lexer ordinary tokens, and instead consume rest of line as a string
+ };
+
+ struct Lexer
+ {
+ typedef uint32_t OptionFlags;
+ struct OptionFlag
+ {
+ enum Enum : OptionFlags
+ {
+ TokenizeComments = 1 << 0, ///< If set comments will be output to the token stream
+ };
+ };
+
+ void initialize(
+ SourceView* sourceView,
+ DiagnosticSink* sink,
+ NamePool* namePool,
+ MemoryArena* memoryArena,
+ OptionFlags optionFlags = 0);
+
+ ~Lexer();
+
+ /// Runs the lexer to try and extract a single token, which is returned.
+ /// This can be used by the DiagnosticSink to be able to display more appropriate
+ /// information when displaying a source location - such as underscoring the
+ /// token at that location.
+ ///
+ /// NOTE! This function is relatively slow, and is designed for use around this specific
+ /// purpose. It does not return a token or a token type, because that information is
+ /// not needed by the DiagnosticSink.
+ static UnownedStringSlice sourceLocationLexer(const UnownedStringSlice& in);
+
+ Token lexToken(LexerFlags extraFlags = 0);
+
+ TokenList lexAllTokens();
+
+ SourceView* m_sourceView;
+ DiagnosticSink* m_sink;
+ NamePool* m_namePool;
+
+ char const* m_cursor;
+
+ char const* m_begin;
+ char const* m_end;
+
+ /// The starting sourceLoc (same as first location of SourceView)
+ SourceLoc m_startLoc;
+
+ TokenFlags m_tokenFlags;
+ LexerFlags m_lexerFlags;
+ OptionFlags m_optionFlags;
+
+ MemoryArena* m_memoryArena;
+ };
+
+
+ // Helper routines for extracting values from tokens
+ String getStringLiteralTokenValue(Token const& token);
+ String getFileNameTokenValue(Token const& token);
+
+ typedef int64_t IntegerLiteralValue;
+ typedef double FloatingPointLiteralValue;
+
+ IntegerLiteralValue getIntegerLiteralValue(Token const& token, UnownedStringSlice* outSuffix = 0);
+ FloatingPointLiteralValue getFloatingPointLiteralValue(Token const& token, UnownedStringSlice* outSuffix = 0);
+}
+
+#endif
diff --git a/source/compiler-core/slang-misc-diagnostic-defs.h b/source/compiler-core/slang-misc-diagnostic-defs.h
new file mode 100644
index 000000000..a1fd01475
--- /dev/null
+++ b/source/compiler-core/slang-misc-diagnostic-defs.h
@@ -0,0 +1,27 @@
+//
+
+// The file is meant to be included multiple times, to produce different
+// pieces of declaration/definition code related to diagnostic messages
+//
+// Each diagnostic is declared here with:
+//
+// DIAGNOSTIC(id, severity, name, messageFormat)
+//
+// Where `id` is the unique diagnostic ID, `severity` is the default
+// severity (from the `Severity` enum), `name` is a name used to refer
+// to this diagnostic from code, and `messageFormat` is the default
+// (non-localized) message for the diagnostic, with placeholders
+// for any arguments.
+
+#ifndef DIAGNOSTIC
+#error Need to #define DIAGNOSTIC(...) before including
+#define DIAGNOSTIC(id, severity, name, messageFormat) /* */
+#endif
+
+//
+// -1 - Notes that decorate another diagnostic.
+//
+
+DIAGNOSTIC(-1, Note, seeTokenPasteLocation, "see token pasted location")
+
+#undef DIAGNOSTIC
diff --git a/source/compiler-core/slang-name-convention-util.cpp b/source/compiler-core/slang-name-convention-util.cpp
new file mode 100644
index 000000000..930e00e18
--- /dev/null
+++ b/source/compiler-core/slang-name-convention-util.cpp
@@ -0,0 +1,213 @@
+
+#include "slang-name-convention-util.h"
+
+#include "../core/slang-char-util.h"
+#include "../core/slang-string-util.h"
+
+namespace Slang
+{
+
+/* static */NameConvention NameConventionUtil::getConvention(const UnownedStringSlice& slice)
+{
+ for (const char c : slice)
+ {
+ switch (c)
+ {
+ case '-': return NameConvention::Kabab;
+ case '_': return NameConvention::Snake;
+ default: break;
+ }
+ }
+ return NameConvention::Camel;
+}
+
+/* static */void NameConventionUtil::split(NameConvention convention, const UnownedStringSlice& slice, List<UnownedStringSlice>& out)
+{
+ switch (convention)
+ {
+ case NameConvention::Kabab:
+ {
+ StringUtil::split(slice, '-', out);
+ break;
+ }
+ case NameConvention::Snake:
+ {
+ StringUtil::split(slice, '_', out);
+ break;
+ }
+ case NameConvention::Camel:
+ {
+ typedef CharUtil::Flags CharFlags;
+ typedef CharUtil::Flag CharFlag;
+
+ CharFlags prevFlags = 0;
+ const char*const end = slice.end();
+
+ const char* start = slice.begin();
+ for (const char* cur = start; cur < end; ++cur)
+ {
+ const char c = *cur;
+ const CharUtil::Flags flags = CharUtil::getFlags(c);
+
+ if (flags & CharFlag::Upper)
+ {
+ if (prevFlags & CharFlag::Lower)
+ {
+ // If we go from lower to upper, we have a transition
+ out.add(UnownedStringSlice(start, cur));
+ start = cur;
+ }
+ else if ((prevFlags & CharFlag::Upper) && cur + 1 < end)
+ {
+ // This works with capital or uncapitalized acronyms, but if we have two capitalized acronyms following each other - it can't split.
+ //
+ // For example
+ // "IAABBSystem" -> "IAABB", "System"
+ //
+ // If it only accepted lower case acronyms the logic could be changed such that the following could be produced
+ // "IAabbSystem" -> "I", "Aabb", "System"
+ //
+ // Since Slang source largely goes with upper case acronyms, we work with the heuristic here..
+
+ if (CharUtil::isLower(cur[1]))
+ {
+ out.add(UnownedStringSlice(start, cur));
+ start = cur;
+ }
+ }
+ }
+
+ prevFlags = flags;
+ }
+
+ // Add any end section
+ if (start < end)
+ {
+ out.add(UnownedStringSlice(start, end));
+ }
+ break;
+ }
+ }
+}
+
+void NameConventionUtil::split(const UnownedStringSlice& slice, List<UnownedStringSlice>& out)
+{
+ split(getConvention(slice), slice, out);
+}
+
+/* static */void NameConventionUtil::join(const UnownedStringSlice* slices, Index slicesCount, CharCase charCase, char joinChar, StringBuilder& out)
+{
+ if (slicesCount <= 0)
+ {
+ return;
+ }
+
+ Index totalSize = slicesCount - 1;
+ for (Index i = 0; i < slicesCount; ++i)
+ {
+ totalSize += slices[i].getLength();
+ }
+
+ char*const dstStart = out.prepareForAppend(totalSize);
+ char* dst = dstStart;
+
+ for (Index i = 0; i < slicesCount; ++i)
+ {
+ const UnownedStringSlice& slice = slices[i];
+ const Index count = slice.getLength();
+ const char*const src = slice.begin();
+
+ if (i > 0)
+ {
+ *dst++ = joinChar;
+ }
+
+ switch (charCase)
+ {
+ case CharCase::Upper:
+ {
+ for (Index j = 0; j < count; ++j)
+ {
+ dst[j] = CharUtil::toUpper(src[j]);
+ }
+ break;
+ }
+ case CharCase::Lower:
+ {
+ for (Index j = 0; j < count; ++j)
+ {
+ dst[j] = CharUtil::toLower(src[j]);
+ }
+ break;
+ }
+ }
+
+ dst += count;
+ }
+
+ SLANG_ASSERT(dstStart + totalSize == dst);
+ out.appendInPlace(dstStart, totalSize);
+}
+
+/* static */void NameConventionUtil::join(const UnownedStringSlice* slices, Index slicesCount, CharCase charCase, NameConvention convention, StringBuilder& out)
+{
+ switch (convention)
+ {
+ case NameConvention::Kabab: return join(slices, slicesCount, charCase, '-', out);
+ case NameConvention::Snake: return join(slices, slicesCount, charCase, '_', out);
+ case NameConvention::Camel:
+ {
+ Index totalSize = 0;
+
+ for (Index i = 0; i < slicesCount; ++i)
+ {
+ totalSize += slices[i].getLength();
+ }
+
+ char*const dstStart = out.prepareForAppend(totalSize);
+ char* dst = dstStart;
+
+ for (Index i = 0; i < slicesCount; ++i)
+ {
+ const UnownedStringSlice& slice = slices[i];
+ Index count = slice.getLength();
+ const char* src = slice.begin();
+
+ Int j = 0;
+
+ if (count > 0 && !(i == 0 && charCase == CharCase::Lower))
+ {
+ // Capitalize first letter of each word, unless on first word and 'lower'
+ dst[j] = CharUtil::toUpper(src[j]);
+ j++;
+ }
+
+ for (; j < count; ++j)
+ {
+ dst[j] = CharUtil::toLower(src[j]);
+ }
+
+ dst += count;
+ }
+ break;
+ }
+ }
+}
+
+/* static */void NameConventionUtil::convert(NameConvention fromConvention, const UnownedStringSlice& slice, CharCase charCase, NameConvention toConvention, StringBuilder& out)
+{
+ // Split into slices
+ List<UnownedStringSlice> slices;
+ split(fromConvention, slice, slices);
+
+ // Join the slices in the toConvention
+ join(slices.getBuffer(), slices.getCount(), charCase, toConvention, out);
+}
+
+/* static */void NameConventionUtil::convert(const UnownedStringSlice& slice, CharCase charCase, NameConvention toConvention, StringBuilder& out)
+{
+ convert(getConvention(slice), slice, charCase, toConvention, out);
+}
+
+}
+
diff --git a/source/compiler-core/slang-name-convention-util.h b/source/compiler-core/slang-name-convention-util.h
new file mode 100644
index 000000000..d84ffd3b7
--- /dev/null
+++ b/source/compiler-core/slang-name-convention-util.h
@@ -0,0 +1,54 @@
+#ifndef SLANG_COMPILER_CORE_NAME_CONVENTION_UTIL_H
+#define SLANG_COMPILER_CORE_NAME_CONVENTION_UTIL_H
+
+#include "../core/slang-string.h"
+#include "../core/slang-list.h"
+
+namespace Slang
+{
+
+enum class NameConvention
+{
+ Kabab, /// Words are separated with -. WORDS-ARE-SEPARATED
+ Snake, /// Words are separated with _. WORDS_ARE_SEPARATED
+ Camel, /// Words start with a capital. (Upper will make first words character capitalized, aka PascalCase)
+};
+
+enum class CharCase
+{
+ Upper,
+ Lower,
+};
+
+/* This utility is to enable easy conversion and interpretation of names that use standard conventions, typically in programming
+languages. The conventions are largely how to represent multiple words together.
+
+Split is used to split up a name into it's constituent 'words' based on a convention.
+Join is used to combine words based on a convention/character case
+
+Convert uses split and join to allow easy conversion between conventions.
+*/
+struct NameConventionUtil
+{
+ /// Given a slice tries to determine the convention used.
+ /// If no separators are found, will assume Camel
+ static NameConvention getConvention(const UnownedStringSlice& slice);
+
+ /// Given a slice and a naming convention, split into it's constituent parts. If convention isn't specified, will infer from slice using getConvention.
+ static void split(NameConvention convention, const UnownedStringSlice& slice, List<UnownedStringSlice>& out);
+ static void split(const UnownedStringSlice& slice, List<UnownedStringSlice>& out);
+
+ /// Given slices, join together with the specified convention into out
+ static void join(const UnownedStringSlice* slices, Index slicesCount, CharCase charCase, NameConvention convention, StringBuilder& out);
+
+ /// Join with a join char, and potentially changing case of input slices
+ static void join(const UnownedStringSlice* slices, Index slicesCount, CharCase charCase, char joinChar, StringBuilder& out);
+
+ /// Convert from one convention to another. If fromConvention isn't specified, will infer from slice using getConvention.
+ static void convert(NameConvention fromConvention, const UnownedStringSlice& slice, CharCase charCase, NameConvention toConvention, StringBuilder& out);
+ static void convert(const UnownedStringSlice& slice, CharCase charCase, NameConvention toConvention, StringBuilder& out);
+};
+
+}
+
+#endif // SLANG_CORE_NAME_CONVENTION_UTIL_H
diff --git a/source/compiler-core/slang-name.cpp b/source/compiler-core/slang-name.cpp
new file mode 100644
index 000000000..b6035982b
--- /dev/null
+++ b/source/compiler-core/slang-name.cpp
@@ -0,0 +1,42 @@
+// slang-name.cpp
+#include "slang-name.h"
+
+namespace Slang {
+
+String getText(Name* name)
+{
+ if (!name) return String();
+ return name->text;
+}
+
+UnownedStringSlice getUnownedStringSliceText(Name* name)
+{
+ return name ? name->text.getUnownedSlice() : UnownedStringSlice();
+}
+
+const char* getCstr(Name* name)
+{
+ return name ? name->text.getBuffer() : nullptr;
+}
+
+Name* NamePool::getName(String const& text)
+{
+ RefPtr<Name> name;
+ if (rootPool->names.TryGetValue(text, name))
+ return name;
+
+ name = new Name();
+ name->text = text;
+ rootPool->names.Add(text, name);
+ return name;
+}
+
+Name* NamePool::tryGetName(String const& text)
+{
+ RefPtr<Name> name;
+ if (rootPool->names.TryGetValue(text, name))
+ return name;
+ return nullptr;
+}
+
+} // namespace Slang
diff --git a/source/compiler-core/slang-name.h b/source/compiler-core/slang-name.h
new file mode 100644
index 000000000..cf702686b
--- /dev/null
+++ b/source/compiler-core/slang-name.h
@@ -0,0 +1,89 @@
+// slang-name.h
+#ifndef SLANG_NAME_H_INCLUDED
+#define SLANG_NAME_H_INCLUDED
+
+// This file defines the `Name` type, used to represent
+// the name of types, variables, etc. in the AST.
+
+#include "../core/slang-basic.h"
+
+namespace Slang {
+
+// The `Name` type is used to represent the name of a type, variable, etc.
+//
+// The key benefit of using `Name`s instead of raw strings is that `Name`s
+// can be compared for equality just by testing pointer equality. Names
+// also don't require any memory management; you can just retain an ordinary
+// pointer to one and not deal with reference-counting overhead.
+//
+// In order to provide these benefits, a `Name` can only be created using
+// a `NamePool` that owns the allocations for all the names (so they get
+// cleaned up when the pool is deleted), and which is responsible for
+// ensuring the uniqueness of name objects.
+//
+class Name : public RefObject
+{
+public:
+ // The raw text of the name.
+ //
+ // Note that at some point in the future we might have other categories
+ // of name than "simple" names, and so this might change to a structured
+ // ADT instead of a simple string.
+ String text;
+};
+
+// Get the textual string representation of a name
+// (e.g., so that it can be printed).
+String getText(Name* name);
+
+/// Get the text as unowned string slice
+UnownedStringSlice getUnownedStringSliceText(Name* name);
+
+// Get a name as a C style string, or nullptr if name is nullptr
+const char* getCstr(Name* name);
+
+// A `RootNamePool` is used to store and look up names.
+// If two systems need to work together with names, and be sure that they
+// get equivalent names for a string like `"Foo"`, then they need to use
+// the same root name pool (directly or indirectly).
+//
+struct RootNamePool
+{
+ // The mapping from text strings to the corresponding name.
+ Dictionary<String, RefPtr<Name> > names;
+};
+
+// A `NamePool` is effectively a way of storing a subset of the
+// names that have been created through a `RootNamePool`.
+//
+// The intention is that eventually we will add the ability to clean
+// up a `NamePool`, and remove the names it created from the corresponding
+// `RootNamePool` *if* those names are no longer in use.
+//
+// The goal of such an approach would be to ensure that the memory
+// usage of a `Session` can't bloat over time just because of multiple
+// `CompileRequest`s being created, used, and then destroyed (each time
+// adding just a few more strings to the name mapping).
+//
+struct NamePool
+{
+ // Find or create the `Name` that represents the given `text`.
+ Name* getName(String const& text);
+ // Try find the `Name` that represents the given `text`.
+ // If the name does not exist, return nullptr
+ Name* tryGetName(String const& text);
+ // Set the parent name pool to use for lookup
+ void setRootNamePool(RootNamePool* rootNamePool)
+ {
+ this->rootPool = rootNamePool;
+ }
+
+ //
+
+ // The root name pool to use for storage/lookup
+ RootNamePool* rootPool = nullptr;
+};
+
+} // namespace Slang
+
+#endif
diff --git a/source/compiler-core/slang-nvrtc-compiler.cpp b/source/compiler-core/slang-nvrtc-compiler.cpp
new file mode 100644
index 000000000..b581d21fd
--- /dev/null
+++ b/source/compiler-core/slang-nvrtc-compiler.cpp
@@ -0,0 +1,773 @@
+// slang-nvrtc-compiler.cpp
+#include "slang-nvrtc-compiler.h"
+
+#include "../core/slang-common.h"
+#include "../../slang-com-helper.h"
+
+#include "../core/slang-blob.h"
+
+#include "../core/slang-string-util.h"
+#include "../core/slang-string-slice-pool.h"
+
+#include "../core/slang-io.h"
+#include "../core/slang-shared-library.h"
+#include "../core/slang-semantic-version.h"
+
+
+namespace nvrtc
+{
+
+typedef enum {
+ NVRTC_SUCCESS = 0,
+ NVRTC_ERROR_OUT_OF_MEMORY = 1,
+ NVRTC_ERROR_PROGRAM_CREATION_FAILURE = 2,
+ NVRTC_ERROR_INVALID_INPUT = 3,
+ NVRTC_ERROR_INVALID_PROGRAM = 4,
+ NVRTC_ERROR_INVALID_OPTION = 5,
+ NVRTC_ERROR_COMPILATION = 6,
+ NVRTC_ERROR_BUILTIN_OPERATION_FAILURE = 7,
+ NVRTC_ERROR_NO_NAME_EXPRESSIONS_AFTER_COMPILATION = 8,
+ NVRTC_ERROR_NO_LOWERED_NAMES_BEFORE_COMPILATION = 9,
+ NVRTC_ERROR_NAME_EXPRESSION_NOT_VALID = 10,
+ NVRTC_ERROR_INTERNAL_ERROR = 11
+} nvrtcResult;
+
+typedef struct _nvrtcProgram *nvrtcProgram;
+
+#define SLANG_NVRTC_FUNCS(x) \
+ x(const char*, nvrtcGetErrorString, (nvrtcResult result)) \
+ x(nvrtcResult, nvrtcVersion, (int *major, int *minor)) \
+ x(nvrtcResult, nvrtcCreateProgram, (nvrtcProgram *prog, const char *src, const char *name, int numHeaders, const char * const *headers, const char * const *includeNames)) \
+ x(nvrtcResult, nvrtcDestroyProgram, (nvrtcProgram *prog)) \
+ x(nvrtcResult, nvrtcCompileProgram, (nvrtcProgram prog, int numOptions, const char * const *options)) \
+ x(nvrtcResult, nvrtcGetPTXSize, (nvrtcProgram prog, size_t *ptxSizeRet)) \
+ x(nvrtcResult, nvrtcGetPTX, (nvrtcProgram prog, char *ptx)) \
+ x(nvrtcResult, nvrtcGetProgramLogSize, (nvrtcProgram prog, size_t *logSizeRet)) \
+ x(nvrtcResult, nvrtcGetProgramLog, (nvrtcProgram prog, char *log))\
+ x(nvrtcResult, nvrtcAddNameExpression, (nvrtcProgram prog, const char * const name_expression)) \
+ x(nvrtcResult, nvrtcGetLoweredName, (nvrtcProgram prog, const char *const name_expression, const char** lowered_name))
+
+} // namespace nvrtc
+
+namespace Slang
+{
+using namespace nvrtc;
+
+static SlangResult _asResult(nvrtcResult res)
+{
+ switch (res)
+ {
+ case NVRTC_SUCCESS:
+ {
+ return SLANG_OK;
+ }
+ case NVRTC_ERROR_OUT_OF_MEMORY:
+ {
+ return SLANG_E_OUT_OF_MEMORY;
+ }
+ case NVRTC_ERROR_PROGRAM_CREATION_FAILURE:
+ case NVRTC_ERROR_INVALID_INPUT:
+ case NVRTC_ERROR_INVALID_PROGRAM:
+ {
+ return SLANG_FAIL;
+ }
+ case NVRTC_ERROR_INVALID_OPTION:
+ {
+ return SLANG_E_INVALID_ARG;
+ }
+ case NVRTC_ERROR_COMPILATION:
+ case NVRTC_ERROR_BUILTIN_OPERATION_FAILURE:
+ case NVRTC_ERROR_NO_NAME_EXPRESSIONS_AFTER_COMPILATION:
+ case NVRTC_ERROR_NO_LOWERED_NAMES_BEFORE_COMPILATION:
+ case NVRTC_ERROR_NAME_EXPRESSION_NOT_VALID:
+ {
+ return SLANG_FAIL;
+ }
+ case NVRTC_ERROR_INTERNAL_ERROR:
+ {
+ return SLANG_E_INTERNAL_FAIL;
+ }
+ default: return SLANG_FAIL;
+ }
+}
+
+class NVRTCDownstreamCompiler : public DownstreamCompiler
+{
+public:
+ typedef DownstreamCompiler Super;
+
+ // DownstreamCompiler
+ virtual SlangResult compile(const CompileOptions& options, RefPtr<DownstreamCompileResult>& outResult) SLANG_OVERRIDE;
+ virtual ISlangSharedLibrary* getSharedLibrary() SLANG_OVERRIDE { return m_sharedLibrary; }
+
+ /// Must be called before use
+ SlangResult init(ISlangSharedLibrary* library);
+
+ NVRTCDownstreamCompiler() {}
+
+protected:
+
+ struct ScopeProgram
+ {
+ ScopeProgram(NVRTCDownstreamCompiler* compiler, nvrtcProgram program):
+ m_compiler(compiler),
+ m_program(program)
+ {
+ }
+ ~ScopeProgram()
+ {
+ m_compiler->m_nvrtcDestroyProgram(&m_program);
+ }
+ NVRTCDownstreamCompiler* m_compiler;
+ nvrtcProgram m_program;
+ };
+
+
+#define SLANG_NVTRC_MEMBER_FUNCS(ret, name, params) \
+ ret (*m_##name) params;
+
+ SLANG_NVRTC_FUNCS(SLANG_NVTRC_MEMBER_FUNCS);
+
+ ComPtr<ISlangSharedLibrary> m_sharedLibrary;
+};
+
+#define SLANG_NVRTC_RETURN_ON_FAIL(x) { nvrtcResult _res = x; if (_res != NVRTC_SUCCESS) return _asResult(_res); }
+
+SlangResult NVRTCDownstreamCompiler::init(ISlangSharedLibrary* library)
+{
+#define SLANG_NVTRC_GET_FUNC(ret, name, params) \
+ m_##name = (ret (*) params)library->findFuncByName(#name); \
+ if (m_##name == nullptr) return SLANG_FAIL;
+
+ SLANG_NVRTC_FUNCS(SLANG_NVTRC_GET_FUNC)
+
+ m_sharedLibrary = library;
+
+ m_desc.type = SLANG_PASS_THROUGH_NVRTC;
+
+ int major, minor;
+ m_nvrtcVersion(&major, &minor);
+ m_desc.majorVersion = major;
+ m_desc.minorVersion = minor;
+
+ return SLANG_OK;
+}
+
+static SlangResult _parseLocation(const UnownedStringSlice& in, DownstreamDiagnostic& outDiagnostic)
+{
+ const Index startIndex = in.indexOf('(');
+
+ if (startIndex >= 0)
+ {
+ outDiagnostic.filePath = UnownedStringSlice(in.begin(), in.begin() + startIndex);
+ UnownedStringSlice remaining(in.begin() + startIndex + 1, in.end());
+ const Int endIndex = remaining.indexOf(')');
+
+ UnownedStringSlice lineText = UnownedStringSlice(remaining.begin(), remaining.begin() + endIndex);
+
+ Int line;
+ SLANG_RETURN_ON_FAIL(StringUtil::parseInt(lineText, line));
+ outDiagnostic.fileLine = line;
+ }
+ else
+ {
+ outDiagnostic.fileLine = 0;
+ outDiagnostic.filePath = in;
+ }
+ return SLANG_OK;
+}
+
+static bool _isDriveLetter(char c)
+{
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+static bool _hasDriveLetter(const UnownedStringSlice& line)
+{
+ return line.getLength() > 2 && line[1] == ':' && _isDriveLetter(line[0]);
+}
+
+static SlangResult _parseNVRTCLine(const UnownedStringSlice& line, DownstreamDiagnostic& outDiagnostic)
+{
+ typedef DownstreamDiagnostic Diagnostic;
+ typedef Diagnostic::Severity Severity;
+
+ outDiagnostic.stage = Diagnostic::Stage::Compile;
+
+ List<UnownedStringSlice> split;
+ if (_hasDriveLetter(line))
+ {
+ // The drive letter has :, which confuses things, so skip that and then fix up first entry
+ UnownedStringSlice lineWithoutDrive(line.begin() + 2, line.end());
+ StringUtil::split(lineWithoutDrive, ':', split);
+ split[0] = UnownedStringSlice(line.begin(), split[0].end());
+ }
+ else
+ {
+ StringUtil::split(line, ':', split);
+ }
+
+ if (split.getCount() >= 3)
+ {
+ // tests/cuda/cuda-compile.cu(7): warning: variable "c" is used before its value is set
+
+ const auto split1 = split[1].trim();
+
+ if (split1 == "error")
+ {
+ outDiagnostic.severity = Severity::Error;
+ }
+ else if (split1 == "warning")
+ {
+ outDiagnostic.severity = Severity::Warning;
+ }
+ outDiagnostic.text = split[2].trim();
+
+ SLANG_RETURN_ON_FAIL(_parseLocation(split[0], outDiagnostic));
+ return SLANG_OK;
+ }
+
+ return SLANG_E_NOT_FOUND;
+}
+
+SlangResult NVRTCDownstreamCompiler::compile(const CompileOptions& options, RefPtr<DownstreamCompileResult>& outResult)
+{
+ // This compiler doesn't read files, they should be read externally and stored in sourceContents/sourceContentsPath
+ if (options.sourceFiles.getCount() > 0)
+ {
+ return SLANG_FAIL;
+ }
+
+ CommandLine cmdLine;
+
+ switch (options.debugInfoType)
+ {
+ case DebugInfoType::None:
+ {
+ break;
+ }
+ default:
+ {
+ cmdLine.addArg("--device-debug");
+ break;
+ }
+ case DebugInfoType::Maximal:
+ {
+ cmdLine.addArg("--device-debug");
+ cmdLine.addArg("--generate-line-info");
+ break;
+ }
+ }
+
+ // Don't seem to have such a control, so ignore for now
+ //switch (options.optimizationLevel)
+ //{
+ // default: break;
+ //}
+
+ switch (options.floatingPointMode)
+ {
+ case FloatingPointMode::Default: break;
+ case FloatingPointMode::Precise:
+ {
+ break;
+ }
+ case FloatingPointMode::Fast:
+ {
+ cmdLine.addArg("--use_fast_math");
+ break;
+ }
+ }
+
+ // Add defines
+ for (const auto& define : options.defines)
+ {
+ StringBuilder builder;
+ builder << "-D";
+ builder << define.nameWithSig;
+ if (define.value.getLength())
+ {
+ builder << "=" << define.value;
+ }
+
+ cmdLine.addArg(builder);
+ }
+
+ // Add includes
+ for (const auto& include : options.includePaths)
+ {
+ cmdLine.addArg("-I");
+ cmdLine.addArg(include);
+ }
+
+ // Neither of these options are strictly required, for general use of nvrtc,
+ // but are enabled to make use withing Slang work more smoothly
+ {
+ // Require c++14, as makes initialization construction with {} available and so simplifies code generation
+ cmdLine.addArg("-std=c++14");
+
+ // Disable all warnings
+ // This is arguably too much - but nvrtc does not appear to have a mechanism to switch off individual warnings.
+ // I tried the -Xcudafe mechanism but that does not appear to work for nvrtc
+ cmdLine.addArg("-w");
+ }
+
+ {
+ // The lowest supported CUDA architecture version supported
+ // by NVRTC is `compute_30`.
+ //
+ SemanticVersion version(3);
+
+ // Newer releases of NVRTC only support `compute_35` and up
+ // (with everything before `compute_52` being deprecated).
+ //
+ if( m_desc.majorVersion >= 11 )
+ {
+ version = SemanticVersion(3, 5);
+ }
+
+ // If constructs used in the code to be compield require
+ // a higher architecture version than the minimum, then
+ // we will set the version to the highest version listed
+ // among the requirements.
+ //
+ for (const auto& capabilityVersion : options.requiredCapabilityVersions)
+ {
+ if (capabilityVersion.kind == DownstreamCompiler::CapabilityVersion::Kind::CUDASM)
+ {
+ if (capabilityVersion.version > version)
+ {
+ version = capabilityVersion.version;
+ }
+ }
+ }
+
+ StringBuilder builder;
+ builder << "-arch=compute_";
+ builder << version.m_major;
+
+ SLANG_ASSERT(version.m_minor >= 0 && version.m_minor <= 9);
+ builder << char('0' + version.m_minor);
+
+ cmdLine.addArg(builder);
+ }
+
+ List<const char*> headers;
+ List<const char*> headerIncludeNames;
+
+ // If compiling for OptiX, we need to add the appropriate search paths to the command line.
+ //
+ if(options.pipelineType == PipelineType::RayTracing)
+ {
+ // The device-side OptiX API is accessed through a constellation
+ // of headers provided by the OptiX SDK, so we need to set an
+ // include path for the compile that makes those visible.
+ //
+ // TODO: The OptiX SDK installer doesn't set any kind of environment
+ // variable to indicate where the SDK was installed, so we seemingly
+ // need to probe paths instead. The form of the path will differ
+ // betwene Windows and Unix-y platforms, and we will need some kind
+ // of approach to probe multiple versiosn and use the latest.
+ //
+ // HACK: For now I'm using the fixed path for the most recent SDK
+ // release on Windows. This means that OptiX cross-compilation will
+ // only "work" on a subset of platforms, but that doesn't matter
+ // for now since it doesn't really "work" at all.
+ //
+ cmdLine.addArg("-I");
+ cmdLine.addArg("C:/ProgramData/NVIDIA Corporation/OptiX SDK 7.0.0/include/");
+
+ // The OptiX headers in turn `#include <stddef.h>` and expect that
+ // to work. We could try to also add in an include path from the CUDA
+ // SDK (which seems to provide a `stddef.h` in the most recent version),
+ // but using that version doesn't seem to work (and also bakes in a
+ // requirement that the user have the CUDA SDK installed in addition
+ // to the OptiX SDK).
+ //
+ // Instead, we will rely on the NVRTC feature that lets us set up
+ // memory buffers to be used as include files by the we compile.
+ // We will define a dummy `stddef.h` that includes the bare minimum
+ // lines required to get the OptiX headers to compile without complaint.
+ //
+ // TODO: Confirm that the `LP64` definition herei s actually needed.
+ //
+ headerIncludeNames.add("stddef.h");
+ headers.add("#pragma once\n" "#define LP64\n");
+
+ // Finally, we want the CUDA prelude to be able to react to whether
+ // or not OptiX is required (most notably by `#include`ing the appropriate
+ // header(s)), so we will insert a preprocessor define to indicate
+ // the requirement.
+ //
+ cmdLine.addArg("-DSLANG_CUDA_ENABLE_OPTIX");
+ }
+
+ SLANG_ASSERT(headers.getCount() == headerIncludeNames.getCount());
+
+ nvrtcProgram program = nullptr;
+ nvrtcResult res = m_nvrtcCreateProgram(&program, options.sourceContents.getBuffer(), options.sourceContentsPath.getBuffer(),
+ (int) headers.getCount(),
+ headers.getBuffer(),
+ headerIncludeNames.getBuffer());
+ if (res != NVRTC_SUCCESS)
+ {
+ return _asResult(res);
+ }
+ ScopeProgram scope(this, program);
+
+ List<const char*> dstOptions;
+ dstOptions.setCount(cmdLine.m_args.getCount());
+ for (Index i = 0; i < cmdLine.m_args.getCount(); ++i)
+ {
+ dstOptions[i] = cmdLine.m_args[i].value.getBuffer();
+ }
+
+ res = m_nvrtcCompileProgram(program, int(dstOptions.getCount()), dstOptions.getBuffer());
+
+ RefPtr<ListBlob> blob;
+ DownstreamDiagnostics diagnostics;
+
+ diagnostics.result = _asResult(res);
+
+ {
+ String rawDiagnostics;
+
+ size_t logSize = 0;
+ SLANG_NVRTC_RETURN_ON_FAIL(m_nvrtcGetProgramLogSize(program, &logSize));
+
+ if (logSize)
+ {
+ char* dst = rawDiagnostics.prepareForAppend(Index(logSize));
+ SLANG_NVRTC_RETURN_ON_FAIL(m_nvrtcGetProgramLog(program, dst));
+ rawDiagnostics.appendInPlace(dst, Index(logSize));
+
+ diagnostics.rawDiagnostics = rawDiagnostics;
+ }
+
+ // Parse the diagnostics here
+ for (auto line : LineParser(diagnostics.rawDiagnostics.getUnownedSlice()))
+ {
+ DownstreamDiagnostic diagnostic;
+ SlangResult lineRes = _parseNVRTCLine(line, diagnostic);
+
+ if (SLANG_SUCCEEDED(lineRes))
+ {
+ diagnostics.diagnostics.add(diagnostic);
+ }
+ else if (lineRes != SLANG_E_NOT_FOUND)
+ {
+ return lineRes;
+ }
+ }
+
+ // if it has a compilation error.. set on output
+ if (diagnostics.has(DownstreamDiagnostic::Severity::Error))
+ {
+ diagnostics.result = SLANG_FAIL;
+ }
+ }
+
+ if (res == nvrtc::NVRTC_SUCCESS)
+ {
+ // We should parse the log to set up the diagnostics
+ size_t ptxSize;
+ SLANG_NVRTC_RETURN_ON_FAIL(m_nvrtcGetPTXSize(program, &ptxSize));
+
+ List<uint8_t> ptx;
+ ptx.setCount(Index(ptxSize));
+
+ SLANG_NVRTC_RETURN_ON_FAIL(m_nvrtcGetPTX(program, (char*)ptx.getBuffer()));
+
+ blob = ListBlob::moveCreate(ptx);
+ }
+
+ outResult = new BlobDownstreamCompileResult(diagnostics, blob);
+
+ return SLANG_OK;
+}
+
+/* An implementation of Path::Visitor that can be used for finding NVRTC shared library installations. */
+struct NVRTCPathVisitor : Path::Visitor
+{
+ struct Candidate
+ {
+ typedef Candidate ThisType;
+
+ bool operator==(const ThisType& rhs) const { return path == rhs.path && version == rhs.version; }
+ bool operator!=(const ThisType& rhs) const { return !(*this == rhs); }
+
+ static Candidate make(const String& path, const SemanticVersion& version)
+ {
+ Candidate can;
+ can.version = version;
+ can.path = path;
+ return can;
+ }
+ String path;
+ SemanticVersion version;
+ };
+
+ Index findVersion(const SemanticVersion& version) const
+ {
+ const Index count = m_candidates.getCount();
+ for (Index i = 0; i < count; ++i)
+ {
+ if (m_candidates[i].version == version)
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ static bool _orderCandiate(const Candidate& a, const Candidate& b) { return a.version < b.version; }
+ void sortCandidates() { m_candidates.sort(_orderCandiate); }
+
+ void accept(Path::Type type, const UnownedStringSlice& filename) SLANG_OVERRIDE
+ {
+ // Lets make sure it start's with nvrtc64, but not worry about case
+ if (type == Path::Type::File)
+ {
+ // If there is a defined extension, make sure it has it
+ if (m_postfix.getLength() && filename.getLength() >= m_postfix.getLength())
+ {
+ // We test without case - really for windows
+ UnownedStringSlice filenamePostfix = filename.tail(filename.getLength() - m_postfix.getLength());
+ if (!filenamePostfix.caseInsensitiveEquals(m_postfix.getUnownedSlice()))
+ {
+ return;
+ }
+ }
+
+ if (filename.getLength() >= m_prefix.getLength() &&
+ filename.subString(0, m_prefix.getLength()).caseInsensitiveEquals(m_prefix.getUnownedSlice()))
+ {
+ // Versions are typically (on windows) of the form
+ // nvrtc64_110_2.dll
+ // 11 - Major
+ // 0 Minor
+ // 2 Patch
+ Index endIndex = filename.indexOf('.');
+ endIndex = (endIndex < 0) ? filename.getLength() : endIndex;
+
+ UnownedStringSlice versionSlice = UnownedStringSlice(filename.begin() + m_prefix.getLength(), filename.begin() + endIndex);
+
+ Int patch = 0;
+ UnownedStringSlice majorMinorSlice;
+ {
+ List<UnownedStringSlice> slices;
+ StringUtil::split(versionSlice, '_', slices);
+ if (slices.getCount() >= 2)
+ {
+ // We don't bother checking for error here, if it's not parsable, it will be 0
+ StringUtil::parseInt(slices[1], patch);
+ }
+ majorMinorSlice = slices[0];
+ }
+
+ if (majorMinorSlice.getLength() < 2)
+ {
+ // Must be a major and minor
+ return;
+ }
+
+ UnownedStringSlice majorSlice = majorMinorSlice.head(majorMinorSlice.getLength() - 1);
+ UnownedStringSlice minorSlice = majorMinorSlice.subString(majorMinorSlice.getLength() - 1, 1);
+
+ Int major;
+ Int minor;
+
+ if (SLANG_FAILED(StringUtil::parseInt(majorSlice, major)) ||
+ SLANG_FAILED(StringUtil::parseInt(minorSlice, minor)))
+ {
+ return;
+ }
+
+ const SemanticVersion version = SemanticVersion(int(major), int(minor), int(patch));
+
+ // We may want to add multiple versions, if they are in different locations - as there may be multiple entries
+ // in the PATH, and only one works. We'll only know which works by loading
+#if 0
+ // We already found this version, so let's not add it again
+ if (findVersion(version) >= 0)
+ {
+ return;
+ }
+#endif
+
+ // Strip to make a shared library name
+ UnownedStringSlice sharedLibraryName = filename.tail(m_prefix.getLength() - m_sharedLibraryStem.getLength());
+ sharedLibraryName = filename.head(filename.getLength() - m_postfix.getLength());
+
+ auto candidate = Candidate::make(Path::combine(m_basePath, sharedLibraryName), version);
+
+ // If we already have this candidate, then skip
+ if (m_candidates.indexOf(candidate) >= 0)
+ {
+ return;
+ }
+
+ // Add to the list of candidates
+ m_candidates.add(candidate);
+ }
+ }
+ }
+
+ SlangResult findInDirectory(const String& path)
+ {
+ m_basePath = path;
+ return Path::find(path, nullptr, this);
+ }
+
+ bool hasCandidates() const { return m_candidates.getCount() > 0; }
+
+ NVRTCPathVisitor(const UnownedStringSlice& sharedLibraryStem):
+ m_sharedLibraryStem(sharedLibraryStem)
+ {
+ // Work out the prefix and postfix of the shader
+ StringBuilder buf;
+ SharedLibrary::appendPlatformFileName(sharedLibraryStem, buf);
+ const Index index = buf.indexOf(sharedLibraryStem);
+ SLANG_ASSERT(index >= 0);
+
+ m_prefix = buf.getUnownedSlice().head(index + sharedLibraryStem.getLength());
+ m_postfix = buf.getUnownedSlice().tail(index + sharedLibraryStem.getLength());
+ }
+
+ String m_prefix;
+ String m_postfix;
+ String m_basePath;
+ String m_sharedLibraryStem;
+
+ List<Candidate> m_candidates;
+};
+
+static SlangResult _findAndLoadNVRTC(ISlangSharedLibraryLoader* loader, ComPtr<ISlangSharedLibrary>& outLibrary)
+{
+#if SLANG_WINDOWS_FAMILY
+ // We only need to search 64 bit versions on windows
+ NVRTCPathVisitor visitor(UnownedStringSlice::fromLiteral("nvrtc64_"));
+
+ // First try the instance path (if supported on platform)
+ {
+ StringBuilder instancePath;
+ if (SLANG_SUCCEEDED(PlatformUtil::getInstancePath(instancePath)))
+ {
+ visitor.findInDirectory(instancePath);
+ }
+ }
+
+ // If we don't have a candidate try CUDA_PATH
+ if (!visitor.hasCandidates())
+ {
+ StringBuilder buf;
+ if (!SLANG_SUCCEEDED(PlatformUtil::getEnvironmentVariable(UnownedStringSlice::fromLiteral("CUDA_PATH"), buf)))
+ {
+ // Look for candidates in the directory
+ visitor.findInDirectory(Path::combine(buf, "bin"));
+ }
+ }
+
+ // If we haven't we go searching through PATH
+ if (!visitor.hasCandidates())
+ {
+ List<UnownedStringSlice> splitPath;
+
+ StringBuilder buf;
+ if (SLANG_SUCCEEDED(PlatformUtil::getEnvironmentVariable(UnownedStringSlice::fromLiteral("PATH"), buf)))
+ {
+ // Split so we get individual paths
+ List<UnownedStringSlice> paths;
+ StringUtil::split(buf.getUnownedSlice(), ';', paths);
+
+ // We use a pool to make sure we only check each path once
+ StringSlicePool pool(StringSlicePool::Style::Empty);
+
+ // We are going to search the paths in order
+ for (const auto path : paths)
+ {
+ // PATH can have the same path multiple times. If we have already searched this path, we don't need to again
+ if (!pool.has(path))
+ {
+ pool.add(path);
+
+ Path::split(path, splitPath);
+
+ // We could search every path, but here we restrict to paths that look like CUDA installations.
+ // It's a path that contains a CUDA directory and has bin
+ if (splitPath.indexOf("CUDA") >= 0 && splitPath[splitPath.getCount() - 1].caseInsensitiveEquals(UnownedStringSlice::fromLiteral("bin")))
+ {
+ // Okay lets search it
+ visitor.findInDirectory(path);
+ }
+ }
+ }
+ }
+ }
+
+ // Put into version order with oldest first.
+ visitor.sortCandidates();
+
+ // We want to start with the newest version...
+ for (Index i = visitor.m_candidates.getCount() - 1; i >= 0; --i)
+ {
+ const auto& candidate = visitor.m_candidates[i];
+ if (SLANG_SUCCEEDED(loader->loadSharedLibrary(candidate.path.getBuffer(), outLibrary.writeRef())))
+ {
+ return SLANG_OK;
+ }
+ }
+#endif
+ // This is an official-ish list of versions is here:
+ // https://developer.nvidia.com/cuda-toolkit-archive
+
+ // Filenames for NVRTC
+ // https://docs.nvidia.com/cuda/nvrtc/index.html
+ //
+ // From this it appears on platforms other than windows the SharedLibrary name
+ // should be nvrtc which is already tried, so we can give up now.
+ return SLANG_E_NOT_FOUND;
+}
+
+/* static */SlangResult NVRTCDownstreamCompilerUtil::locateCompilers(const String& path, ISlangSharedLibraryLoader* loader, DownstreamCompilerSet* set)
+{
+ ComPtr<ISlangSharedLibrary> library;
+
+ // If the user supplies a path to their preferred version of NVRTC,
+ // we just use this.
+ if (path.getLength() != 0)
+ {
+ SLANG_RETURN_ON_FAIL(loader->loadSharedLibrary(path.getBuffer(), library.writeRef()));
+ }
+ else
+ {
+ // As a catch-all for non-Windows platforms, we search for
+ // a library simply named `nvrtc` (well, `libnvrtc`) which
+ // is expected to match whatever the user has installed.
+ //
+ // On Windows an installation could place the version of nvrtc it uses in the same directory
+ // as the slang binary, such that it's loaded.
+ // Using this name also allows a ISlangSharedLibraryLoader to easily identify what is required
+ // and perhaps load a specific version
+ if (SLANG_FAILED(loader->loadSharedLibrary("nvrtc", library.writeRef())))
+ {
+ // Try something more sophisticated to locate NVRTC
+ SLANG_RETURN_ON_FAIL(_findAndLoadNVRTC(loader, library));
+ }
+ }
+
+ SLANG_ASSERT(library);
+ if (!library)
+ {
+ return SLANG_FAIL;
+ }
+
+ RefPtr<NVRTCDownstreamCompiler> compiler(new NVRTCDownstreamCompiler);
+ SLANG_RETURN_ON_FAIL(compiler->init(library));
+
+ set->addCompiler(compiler);
+ return SLANG_OK;
+}
+
+
+}
diff --git a/source/compiler-core/slang-nvrtc-compiler.h b/source/compiler-core/slang-nvrtc-compiler.h
new file mode 100644
index 000000000..48c6d4da6
--- /dev/null
+++ b/source/compiler-core/slang-nvrtc-compiler.h
@@ -0,0 +1,19 @@
+#ifndef SLANG_NVRTC_COMPILER_UTIL_H
+#define SLANG_NVRTC_COMPILER_UTIL_H
+
+#include "slang-downstream-compiler.h"
+
+#include "../core/slang-platform.h"
+
+namespace Slang
+{
+
+
+struct NVRTCDownstreamCompilerUtil
+{
+ static SlangResult locateCompilers(const String& path, ISlangSharedLibraryLoader* loader, DownstreamCompilerSet* set);
+};
+
+}
+
+#endif
diff --git a/source/compiler-core/slang-source-loc.cpp b/source/compiler-core/slang-source-loc.cpp
new file mode 100644
index 000000000..4b589fbf3
--- /dev/null
+++ b/source/compiler-core/slang-source-loc.cpp
@@ -0,0 +1,689 @@
+// slang-source-loc.cpp
+#include "slang-source-loc.h"
+
+#include "../core/slang-string-util.h"
+
+namespace Slang {
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!! SourceView !!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+const String PathInfo::getMostUniqueIdentity() const
+{
+ switch (type)
+ {
+ case Type::Normal: return uniqueIdentity;
+ case Type::FoundPath:
+ case Type::FromString:
+ {
+ return foundPath;
+ }
+ default: return "";
+ }
+}
+
+bool PathInfo::operator==(const ThisType& rhs) const
+{
+ // They must be the same type
+ if (type != rhs.type)
+ {
+ return false;
+ }
+
+ switch (type)
+ {
+ case Type::TokenPaste:
+ case Type::TypeParse:
+ case Type::Unknown:
+ case Type::CommandLine:
+ {
+ return true;
+ }
+ case Type::Normal:
+ {
+ return foundPath == rhs.foundPath && uniqueIdentity == rhs.uniqueIdentity;
+ }
+ case Type::FromString:
+ case Type::FoundPath:
+ {
+ // Only have a found path
+ return foundPath == rhs.foundPath;
+ }
+ default: break;
+ }
+
+ return false;
+}
+
+void PathInfo::appendDisplayName(StringBuilder& out) const
+{
+ switch (type)
+ {
+ case Type::TokenPaste: out << "[Token Paste]"; break;
+ case Type::TypeParse: out << "[Type Parse]"; break;
+ case Type::Unknown: out << "[Unknown]"; break;
+ case Type::CommandLine: out << "[Command Line]"; break;
+ case Type::Normal:
+ case Type::FromString:
+ case Type::FoundPath:
+ {
+
+ out.appendChar('"');
+ StringUtil::appendEscaped(foundPath.getUnownedSlice(), out);
+ out.appendChar('"');
+ break;
+ }
+ default: break;
+ }
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!! SourceView !!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+int SourceView::findEntryIndex(SourceLoc sourceLoc) const
+{
+ if (!m_range.contains(sourceLoc))
+ {
+ return -1;
+ }
+
+ const auto rawValue = sourceLoc.getRaw();
+
+ Index hi = m_entries.getCount();
+ // If there are no entries, or it is in front of the first entry, then there is no associated entry
+ if (hi == 0 ||
+ m_entries[0].m_startLoc.getRaw() > sourceLoc.getRaw())
+ {
+ return -1;
+ }
+
+ Index lo = 0;
+ while (lo + 1 < hi)
+ {
+ const Index mid = (hi + lo) >> 1;
+ const Entry& midEntry = m_entries[mid];
+ SourceLoc::RawValue midValue = midEntry.m_startLoc.getRaw();
+ if (midValue <= rawValue)
+ {
+ // The location we seek is at or after this entry
+ lo = mid;
+ }
+ else
+ {
+ // The location we seek is before this entry
+ hi = mid;
+ }
+ }
+
+ return int(lo);
+}
+
+void SourceView::addLineDirective(SourceLoc directiveLoc, StringSlicePool::Handle pathHandle, int line)
+{
+ SLANG_ASSERT(pathHandle != StringSlicePool::Handle(0));
+ SLANG_ASSERT(m_range.contains(directiveLoc));
+
+ // Check that the directiveLoc values are always increasing
+ SLANG_ASSERT(m_entries.getCount() == 0 || (m_entries.getLast().m_startLoc.getRaw() < directiveLoc.getRaw()));
+
+ // Calculate the offset
+ const int offset = m_range.getOffset(directiveLoc);
+
+ // Get the line index in the original file
+ const int lineIndex = m_sourceFile->calcLineIndexFromOffset(offset);
+
+ Entry entry;
+ entry.m_startLoc = directiveLoc;
+ entry.m_pathHandle = pathHandle;
+
+ // We also need to make sure that any lookups for line numbers will
+ // get corrected based on this files location.
+ // We assume the line number coming from the directive is a line number, NOT an index, so the correction needs + 1
+ // There is an additional + 1 because we want the NEXT line - ie the line after the #line directive, to the specified value
+ // Taking both into account means +2 is correct 'fix'
+ entry.m_lineAdjust = line - (lineIndex + 2);
+
+ m_entries.add(entry);
+}
+
+void SourceView::addLineDirective(SourceLoc directiveLoc, const String& path, int line)
+{
+ StringSlicePool::Handle pathHandle = getSourceManager()->getStringSlicePool().add(path.getUnownedSlice());
+ return addLineDirective(directiveLoc, pathHandle, line);
+}
+
+void SourceView::addDefaultLineDirective(SourceLoc directiveLoc)
+{
+ SLANG_ASSERT(m_range.contains(directiveLoc));
+ // Check that the directiveLoc values are always increasing
+ SLANG_ASSERT(m_entries.getCount() == 0 || (m_entries.getLast().m_startLoc.getRaw() < directiveLoc.getRaw()));
+
+ // Well if there are no entries, or the last one puts it in default case, then we don't need to add anything
+ if (m_entries.getCount() == 0 || (m_entries.getCount() && m_entries.getLast().isDefault()))
+ {
+ return;
+ }
+
+ Entry entry;
+ entry.m_startLoc = directiveLoc;
+ entry.m_lineAdjust = 0; // No line adjustment... we are going back to default
+ entry.m_pathHandle = StringSlicePool::Handle(0); // Mark that there is no path, and that this is a 'default'
+
+ SLANG_ASSERT(entry.isDefault());
+
+ m_entries.add(entry);
+}
+
+HumaneSourceLoc SourceView::getHumaneLoc(SourceLoc loc, SourceLocType type)
+{
+ const int offset = m_range.getOffset(loc);
+
+ // We need the line index from the original source file
+ const int lineIndex = m_sourceFile->calcLineIndexFromOffset(offset);
+
+ // TODO: we should really translate the byte index in the line
+ // to deal with:
+ //
+ // - Non-ASCII characters, while might consume multiple bytes
+ //
+ // - Tab characters, which should really adjust how we report
+ // columns (although how are we supposed to know the setting
+ // that an IDE expects us to use when reporting locations?)
+ const int columnIndex = m_sourceFile->calcColumnIndex(lineIndex, offset);
+
+ HumaneSourceLoc humaneLoc;
+ humaneLoc.column = columnIndex + 1;
+ humaneLoc.line = lineIndex + 1;
+
+ // Make up a default entry
+ StringSlicePool::Handle pathHandle = StringSlicePool::Handle(0);
+
+ // Only bother looking up the entry information if we want a 'Normal' lookup
+ const int entryIndex = (type == SourceLocType::Nominal) ? findEntryIndex(loc) : -1;
+ if (entryIndex >= 0)
+ {
+ const Entry& entry = m_entries[entryIndex];
+ // Adjust the line
+ humaneLoc.line += entry.m_lineAdjust;
+ // Get the pathHandle..
+ pathHandle = entry.m_pathHandle;
+ }
+
+ humaneLoc.pathInfo = _getPathInfoFromHandle(pathHandle);
+ return humaneLoc;
+}
+
+PathInfo SourceView::_getPathInfo() const
+{
+ if (m_viewPath.getLength())
+ {
+ PathInfo pathInfo(m_sourceFile->getPathInfo());
+ pathInfo.foundPath = m_viewPath;
+ return pathInfo;
+ }
+ else
+ {
+ return m_sourceFile->getPathInfo();
+ }
+}
+
+PathInfo SourceView::_getPathInfoFromHandle(StringSlicePool::Handle pathHandle) const
+{
+ // If there is no override path, then just the source files path
+ if (pathHandle == StringSlicePool::Handle(0))
+ {
+ return _getPathInfo();
+ }
+ else
+ {
+ return PathInfo::makePath(getSourceManager()->getStringSlicePool().getSlice(pathHandle));
+ }
+}
+
+PathInfo SourceView::getPathInfo(SourceLoc loc, SourceLocType type)
+{
+ if (type == SourceLocType::Actual)
+ {
+ return _getPathInfo();
+ }
+
+ const int entryIndex = findEntryIndex(loc);
+ return _getPathInfoFromHandle((entryIndex >= 0) ? m_entries[entryIndex].m_pathHandle : StringSlicePool::Handle(0));
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!! SourceFile !!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+void SourceFile::setLineBreakOffsets(const uint32_t* offsets, UInt numOffsets)
+{
+ m_lineBreakOffsets.clear();
+ m_lineBreakOffsets.addRange(offsets, numOffsets);
+}
+
+const List<uint32_t>& SourceFile::getLineBreakOffsets()
+{
+ // We now have a raw input file that we can search for line breaks.
+ // We obviously don't want to do a linear scan over and over, so we will
+ // cache an array of line break locations in the file.
+ if (m_lineBreakOffsets.getCount() == 0)
+ {
+ UnownedStringSlice content(getContent()), line;
+ char const* contentBegin = content.begin();
+ while (StringUtil::extractLine(content, line))
+ {
+ m_lineBreakOffsets.add(uint32_t(line.begin() - contentBegin));
+ }
+ // Note that we do *not* treat the end of the file as a line
+ // break, because otherwise we would report errors like
+ // "end of file inside string literal" with a line number
+ // that points at a line that doesn't exist.
+ }
+
+ return m_lineBreakOffsets;
+}
+
+SourceFile::OffsetRange SourceFile::getOffsetRangeAtLineIndex(Index lineIndex)
+{
+ const List<uint32_t>& offsets = getLineBreakOffsets();
+ const Index count = offsets.getCount();
+
+ if (lineIndex >= count - 1)
+ {
+ // Work out the line start
+ const uint32_t offsetEnd = uint32_t(getContentSize());
+ const uint32_t offsetStart = (lineIndex >= count) ? offsetEnd : offsets[lineIndex];
+ // The line is the span from start, to the end of the content
+ return OffsetRange{ offsetStart, offsetEnd };
+ }
+ else
+ {
+ const uint32_t offsetStart = offsets[lineIndex];
+ const uint32_t offsetEnd = offsets[lineIndex + 1];
+ return OffsetRange { offsetStart, offsetEnd };
+ }
+}
+
+UnownedStringSlice SourceFile::getLineAtIndex(Index lineIndex)
+{
+ const OffsetRange range = getOffsetRangeAtLineIndex(lineIndex);
+
+ if (range.isValid() && hasContent())
+ {
+ const UnownedStringSlice content = getContent();
+ SLANG_ASSERT(range.end <= uint32_t(content.getLength()));
+
+ const char*const text = content.begin();
+ return UnownedStringSlice(text + range.start, text + range.end);
+ }
+
+ return UnownedStringSlice();
+}
+
+UnownedStringSlice SourceFile::getLineContainingOffset(uint32_t offset)
+{
+ const Index lineIndex = calcLineIndexFromOffset(offset);
+ return getLineAtIndex(lineIndex);
+}
+
+bool SourceFile::isOffsetOnLine(uint32_t offset, Index lineIndex)
+{
+ const OffsetRange range = getOffsetRangeAtLineIndex(lineIndex);
+ return range.isValid() && range.containsInclusive(offset);
+}
+
+int SourceFile::calcLineIndexFromOffset(int offset)
+{
+ SLANG_ASSERT(UInt(offset) <= getContentSize());
+
+ // Make sure we have the line break offsets
+ const auto& lineBreakOffsets = getLineBreakOffsets();
+
+ // At this point we can assume the `lineBreakOffsets` array has been filled in.
+ // We will use a binary search to find the line index that contains our
+ // chosen offset.
+ Index lo = 0;
+ Index hi = lineBreakOffsets.getCount();
+
+ while (lo + 1 < hi)
+ {
+ const Index mid = (hi + lo) >> 1;
+ const uint32_t midOffset = lineBreakOffsets[mid];
+ if (midOffset <= uint32_t(offset))
+ {
+ lo = mid;
+ }
+ else
+ {
+ hi = mid;
+ }
+ }
+
+ return int(lo);
+}
+
+int SourceFile::calcColumnIndex(int lineIndex, int offset)
+{
+ const auto& lineBreakOffsets = getLineBreakOffsets();
+ return offset - lineBreakOffsets[lineIndex];
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!! SourceFile !!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+void SourceFile::setContents(ISlangBlob* blob)
+{
+ const UInt contentSize = blob->getBufferSize();
+
+ SLANG_ASSERT(contentSize == m_contentSize);
+
+ char const* contentBegin = (char const*)blob->getBufferPointer();
+ char const* contentEnd = contentBegin + contentSize;
+
+ m_contentBlob = blob;
+ m_content = UnownedStringSlice(contentBegin, contentEnd);
+}
+
+void SourceFile::setContents(const String& content)
+{
+ ComPtr<ISlangBlob> contentBlob = StringUtil::createStringBlob(content);
+ setContents(contentBlob);
+}
+
+SourceFile::SourceFile(SourceManager* sourceManager, const PathInfo& pathInfo, size_t contentSize) :
+ m_sourceManager(sourceManager),
+ m_pathInfo(pathInfo),
+ m_contentSize(contentSize)
+{
+}
+
+SourceFile::~SourceFile()
+{
+}
+
+String SourceFile::calcVerbosePath() const
+{
+ ISlangFileSystemExt* fileSystemExt = getSourceManager()->getFileSystemExt();
+
+ if (fileSystemExt)
+ {
+ String canonicalPath;
+ ComPtr<ISlangBlob> canonicalPathBlob;
+ if (SLANG_SUCCEEDED(fileSystemExt->getCanonicalPath(m_pathInfo.foundPath.getBuffer(), canonicalPathBlob.writeRef())))
+ {
+ canonicalPath = StringUtil::getString(canonicalPathBlob);
+ }
+ if (canonicalPath.getLength() > 0)
+ {
+ return canonicalPath;
+ }
+ }
+
+ return m_pathInfo.foundPath;
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!! SourceManager !!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+void SourceManager::initialize(
+ SourceManager* p,
+ ISlangFileSystemExt* fileSystemExt)
+{
+ m_fileSystemExt = fileSystemExt;
+
+ m_parent = p;
+
+ if( p )
+ {
+ // If we have a parent source manager, then we assume that all code at that level
+ // has already been loaded, and it is safe to start our own source locations
+ // right after those from the parent.
+ //
+ // TODO: more clever allocation in cases where that might not be reasonable
+ m_startLoc = p->m_nextLoc;
+ }
+ else
+ {
+ // Location zero is reserved for an invalid location,
+ // so we need to start reserving locations starting at 1.
+ m_startLoc = SourceLoc::fromRaw(1);
+ }
+
+ m_nextLoc = m_startLoc;
+}
+
+SourceManager::~SourceManager()
+{
+ for (auto item : m_sourceViews)
+ {
+ delete item;
+ }
+
+ for (auto item : m_sourceFiles)
+ {
+ delete item;
+ }
+}
+
+UnownedStringSlice SourceManager::allocateStringSlice(const UnownedStringSlice& slice)
+{
+ const UInt numChars = slice.getLength();
+
+ char* dst = (char*)m_memoryArena.allocate(numChars);
+ ::memcpy(dst, slice.begin(), numChars);
+
+ return UnownedStringSlice(dst, numChars);
+}
+
+SourceRange SourceManager::allocateSourceRange(UInt size)
+{
+ // TODO: consider using atomics here
+
+
+ SourceLoc beginLoc = m_nextLoc;
+ SourceLoc endLoc = beginLoc + size;
+
+ // We need to be able to represent the location that is *at* the end of
+ // the input source, so the next available location for a new file
+ // must be placed one after the end of this one.
+
+ m_nextLoc = endLoc + 1;
+
+ return SourceRange(beginLoc, endLoc);
+}
+
+SourceFile* SourceManager::createSourceFileWithSize(const PathInfo& pathInfo, size_t contentSize)
+{
+ SourceFile* sourceFile = new SourceFile(this, pathInfo, contentSize);
+ m_sourceFiles.add(sourceFile);
+ return sourceFile;
+}
+
+SourceFile* SourceManager::createSourceFileWithString(const PathInfo& pathInfo, const String& contents)
+{
+ SourceFile* sourceFile = new SourceFile(this, pathInfo, contents.getLength());
+ m_sourceFiles.add(sourceFile);
+ sourceFile->setContents(contents);
+ return sourceFile;
+}
+
+SourceFile* SourceManager::createSourceFileWithBlob(const PathInfo& pathInfo, ISlangBlob* blob)
+{
+ SourceFile* sourceFile = new SourceFile(this, pathInfo, blob->getBufferSize());
+ m_sourceFiles.add(sourceFile);
+ sourceFile->setContents(blob);
+ return sourceFile;
+}
+
+SourceView* SourceManager::createSourceView(SourceFile* sourceFile, const PathInfo* pathInfo, SourceLoc initiatingSourceLoc)
+{
+ SourceRange range = allocateSourceRange(sourceFile->getContentSize());
+
+ SourceView* sourceView = nullptr;
+ if (pathInfo &&
+ (pathInfo->foundPath.getLength() && sourceFile->getPathInfo().foundPath != pathInfo->foundPath))
+ {
+ sourceView = new SourceView(sourceFile, range, &pathInfo->foundPath, initiatingSourceLoc);
+ }
+ else
+ {
+ sourceView = new SourceView(sourceFile, range, nullptr, initiatingSourceLoc);
+ }
+
+ m_sourceViews.add(sourceView);
+
+ return sourceView;
+}
+
+SourceView* SourceManager::findSourceView(SourceLoc loc) const
+{
+ Index hi = m_sourceViews.getCount();
+ // It must be in the range of this manager and have associated views for it to possibly be a hit
+ if (!getSourceRange().contains(loc) || hi == 0)
+ {
+ return nullptr;
+ }
+
+ // If we don't have very many, we may as well just linearly search
+ if (hi <= 8)
+ {
+ for (int i = 0; i < hi; ++i)
+ {
+ SourceView* view = m_sourceViews[i];
+ if (view->getRange().contains(loc))
+ {
+ return view;
+ }
+ }
+ return nullptr;
+ }
+
+ const SourceLoc::RawValue rawLoc = loc.getRaw();
+
+ // Binary chop to see if we can find the associated SourceUnit
+ Index lo = 0;
+ while (lo + 1 < hi)
+ {
+ Index mid = (hi + lo) >> 1;
+
+ SourceView* midView = m_sourceViews[mid];
+ if (midView->getRange().contains(loc))
+ {
+ return midView;
+ }
+
+ const SourceLoc::RawValue midValue = midView->getRange().begin.getRaw();
+ if (midValue <= rawLoc)
+ {
+ // The location we seek is at or after this entry
+ lo = mid;
+ }
+ else
+ {
+ // The location we seek is before this entry
+ hi = mid;
+ }
+ }
+
+ // Check if low is actually a hit
+ SourceView* view = m_sourceViews[lo];
+ return (view->getRange().contains(loc)) ? view : nullptr;
+}
+
+SourceView* SourceManager::findSourceViewRecursively(SourceLoc loc) const
+{
+ // Start with this manager
+ const SourceManager* manager = this;
+ do
+ {
+ SourceView* sourceView = manager->findSourceView(loc);
+ // If we found a hit we are done
+ if (sourceView)
+ {
+ return sourceView;
+ }
+ // Try the parent
+ manager = manager->m_parent;
+ }
+ while (manager);
+ // Didn't find it
+ return nullptr;
+}
+
+SourceFile* SourceManager::findSourceFile(const String& uniqueIdentity) const
+{
+ SourceFile*const* filePtr = m_sourceFileMap.TryGetValue(uniqueIdentity);
+ return (filePtr) ? *filePtr : nullptr;
+}
+
+SourceFile* SourceManager::findSourceFileRecursively(const String& uniqueIdentity) const
+{
+ const SourceManager* manager = this;
+ do
+ {
+ SourceFile* sourceFile = manager->findSourceFile(uniqueIdentity);
+ if (sourceFile)
+ {
+ return sourceFile;
+ }
+ manager = manager->m_parent;
+ } while (manager);
+ return nullptr;
+}
+
+SourceFile* SourceManager::findSourceFileByContentRecursively(const char* text)
+{
+ const SourceManager* manager = this;
+ do
+ {
+ SourceFile* sourceFile = manager->findSourceFileByContent(text);
+ if (sourceFile)
+ {
+ return sourceFile;
+ }
+ manager = manager->m_parent;
+ } while (manager);
+ return nullptr;
+}
+
+SourceFile* SourceManager::findSourceFileByContent(const char* text) const
+{
+ for (SourceFile* sourceFile : getSourceFiles())
+ {
+ auto content = sourceFile->getContent();
+
+ if (text >= content.begin() && text <= content.end())
+ {
+ return sourceFile;
+ }
+ }
+ return nullptr;
+}
+
+void SourceManager::addSourceFile(const String& uniqueIdentity, SourceFile* sourceFile)
+{
+ SLANG_ASSERT(!findSourceFileRecursively(uniqueIdentity));
+ m_sourceFileMap.Add(uniqueIdentity, sourceFile);
+}
+
+HumaneSourceLoc SourceManager::getHumaneLoc(SourceLoc loc, SourceLocType type)
+{
+ SourceView* sourceView = findSourceViewRecursively(loc);
+ if (sourceView)
+ {
+ return sourceView->getHumaneLoc(loc, type);
+ }
+ else
+ {
+ return HumaneSourceLoc();
+ }
+}
+
+PathInfo SourceManager::getPathInfo(SourceLoc loc, SourceLocType type)
+{
+ SourceView* sourceView = findSourceViewRecursively(loc);
+ if (sourceView)
+ {
+ return sourceView->getPathInfo(loc, type);
+ }
+ else
+ {
+ return PathInfo::makeUnknown();
+ }
+}
+
+} // namespace Slang
diff --git a/source/compiler-core/slang-source-loc.h b/source/compiler-core/slang-source-loc.h
new file mode 100644
index 000000000..54811918f
--- /dev/null
+++ b/source/compiler-core/slang-source-loc.h
@@ -0,0 +1,487 @@
+// slang-source-loc.h
+#ifndef SLANG_SOURCE_LOC_H_INCLUDED
+#define SLANG_SOURCE_LOC_H_INCLUDED
+
+#include "../core/slang-basic.h"
+#include "../core/slang-memory-arena.h"
+#include "../core/slang-string-slice-pool.h"
+
+#include "../../slang-com-ptr.h"
+#include "../../slang.h"
+
+namespace Slang {
+
+/** Overview:
+
+There needs to be a mechanism where we can easily and quickly track a specific locations in any source file used during a compilation.
+This is important because that original location is meaningful to the user as it relates to their original source. Thus SourceLoc are
+used so we can display meaningful and accurate errors/warnings as well as being able to always map generated code locations back to their origins.
+
+A 'SourceLoc' along with associated structures (SourceView, SourceFile, SourceMangager) this can pinpoint the location down to the byte across the
+compilation. This could be achieved by storing for every token and instruction the file, line and column number came from. The SourceLoc is used in
+lots of places - every AST node, every Token from the lexer, every IRInst - so we really want to make it small. So for this reason we actually
+encode SourceLoc as a single integer and then use the associated structures when needed to determine what the location actually refers to -
+the source file, line and column number, or in effect the byte in the original file.
+
+Unfortunately there is extra complications. When a source is parsed it's interpretation (in terms of how a piece of source maps to an 'original' file etc)
+can be overridden - for example by using #line directives. Moreover a single source file can be parsed multiple times. When it's parsed multiple times the
+interpretation of the mapping (#line directives for example) can change. This is the purpose of the SourceView - it holds the interpretation of a source file
+for a specific Lex/Parse.
+
+Another complication is that not all 'source' comes from SourceFiles, a macro expansion, may generate new 'source' we need to handle this, but also be able
+to have a SourceLoc map to the expansion unambiguously. This is handled by creating a SourceFile and SourceView that holds only the macro generated
+specific information.
+
+SourceFile - Is the immutable text contents of a file (or perhaps some generated source - say from doing a macro substitution)
+SourceView - Tracks a single parse of a SourceFile. Each SourceView defines a range of source locations used. If a SourceFile is parsed twice, two
+SourceViews are created, with unique SourceRanges. This is so that it is possible to tell which specific parse a SourceLoc is from - and so know the right
+interpretation for that lex/parse.
+*/
+
+struct PathInfo
+{
+ typedef PathInfo ThisType;
+
+ /// To be more rigorous about where a path comes from, the type identifies what a paths origin is
+ enum class Type : uint8_t
+ {
+ Unknown, ///< The path is not known
+ Normal, ///< Normal has both path and uniqueIdentity
+ FoundPath, ///< Just has a found path (uniqueIdentity is unknown, or even 'unknowable')
+ FromString, ///< Created from a string (so found path might not be defined and should not be taken as to map to a loaded file)
+ TokenPaste, ///< No paths, just created to do a macro expansion
+ TypeParse, ///< No path, just created to do a type parse
+ CommandLine, ///< A macro constructed from the command line
+ };
+
+ /// True if has a canonical path
+ SLANG_FORCE_INLINE bool hasUniqueIdentity() const { return type == Type::Normal && uniqueIdentity.getLength() > 0; }
+ /// True if has a regular found path
+ SLANG_FORCE_INLINE bool hasFoundPath() const { return type == Type::Normal || type == Type::FoundPath || (type == Type::FromString && foundPath.getLength() > 0); }
+ /// True if has a found path that has originated from a file (as opposed to string or some other origin)
+ SLANG_FORCE_INLINE bool hasFileFoundPath() const { return (type == Type::Normal || type == Type::FoundPath) && foundPath.getLength() > 0; }
+
+ bool operator==(const ThisType& rhs) const;
+ bool operator!=(const ThisType& rhs) const { return !(*this == rhs); }
+
+ /// Returns the 'most unique' identity for the path. If has a 'uniqueIdentity' returns that, else the foundPath, else "".
+ const String getMostUniqueIdentity() const;
+
+ /// Append to out, how to display the path
+ void appendDisplayName(StringBuilder& out) const;
+
+ // So simplify construction. In normal usage it's safer to use make methods over constructing directly.
+ static PathInfo makeUnknown() { return PathInfo { Type::Unknown, String(), String() }; }
+ static PathInfo makeTokenPaste() { return PathInfo{ Type::TokenPaste, "token paste", String()}; }
+ static PathInfo makeNormal(const String& foundPathIn, const String& uniqueIdentity) { SLANG_ASSERT(uniqueIdentity.getLength() > 0 && foundPathIn.getLength() > 0); return PathInfo { Type::Normal, foundPathIn, uniqueIdentity }; }
+ static PathInfo makePath(const String& pathIn) { SLANG_ASSERT(pathIn.getLength() > 0); return PathInfo { Type::FoundPath, pathIn, String()}; }
+ static PathInfo makeTypeParse() { return PathInfo { Type::TypeParse, "type string", String() }; }
+ static PathInfo makeCommandLine() { return PathInfo { Type::CommandLine, "command line", String() }; }
+ static PathInfo makeFromString(const String& userPath) { return PathInfo{ Type::FromString, userPath, String() }; }
+
+ Type type; ///< The type of path
+ String foundPath; ///< The path where the file was found (might contain relative elements)
+ String uniqueIdentity; ///< The unique identity of the file on the path found
+};
+
+class SourceLoc
+{
+public:
+ typedef SourceLoc ThisType;
+ typedef uint32_t RawValue;
+
+private:
+ RawValue raw;
+
+public:
+ SourceLoc()
+ : raw(0)
+ {}
+
+ SourceLoc(
+ SourceLoc const& loc)
+ : raw(loc.raw)
+ {}
+
+ SLANG_FORCE_INLINE bool operator==(const ThisType& rhs) const { return raw == rhs.raw; }
+ SLANG_FORCE_INLINE bool operator!=(const ThisType& rhs) const { return !(raw == rhs.raw); }
+
+ RawValue getRaw() const { return raw; }
+ void setRaw(RawValue value) { raw = value; }
+
+ static SourceLoc fromRaw(RawValue value)
+ {
+ SourceLoc result;
+ result.setRaw(value);
+ return result;
+ }
+
+ bool isValid() const
+ {
+ return raw != 0;
+ }
+ SourceLoc& operator=(const ThisType& rhs) = default;
+};
+
+inline SourceLoc operator+(SourceLoc loc, Int offset)
+{
+ return SourceLoc::fromRaw(SourceLoc::RawValue(Int(loc.getRaw()) + offset));
+}
+
+// A range of locations in the input source
+struct SourceRange
+{
+ /// True if the loc is in the range. Range is inclusive on begin to end.
+ bool contains(SourceLoc loc) const { const auto rawLoc = loc.getRaw(); return rawLoc >= begin.getRaw() && rawLoc <= end.getRaw(); }
+ /// Get the total size
+ UInt getSize() const { return UInt(end.getRaw() - begin.getRaw()); }
+
+ /// Get the offset of a loc in this range
+ int getOffset(SourceLoc loc) const { SLANG_ASSERT(contains(loc)); return int(loc.getRaw() - begin.getRaw()); }
+
+ /// Convert an offset to a loc
+ SourceLoc getSourceLocFromOffset(uint32_t offset) const { SLANG_ASSERT(offset <= getSize()); return begin + Int(offset); }
+
+ SourceRange()
+ {}
+
+ SourceRange(SourceLoc loc)
+ : begin(loc)
+ , end(loc)
+ {}
+
+ SourceRange(SourceLoc begin, SourceLoc end)
+ : begin(begin)
+ , end(end)
+ {}
+
+ SourceLoc begin;
+ SourceLoc end;
+};
+
+
+// Pre-declare
+struct SourceManager;
+
+// A logical or physical storage object for a range of input code
+// that has logically contiguous source locations.
+class SourceFile
+{
+public:
+
+ struct OffsetRange
+ {
+ /// We need a value to indicate an invalid range. We can't use 0 as that is valid for an offset range
+ /// We can't use a negative number, and don't want to make signed so we get the full 32-bits.
+ /// So we just use the max value as invalid
+ static const uint32_t kInvalid = 0xffffffff;
+
+ /// True if the range is valid
+ SLANG_FORCE_INLINE bool isValid() const { return end >= start && start != kInvalid; }
+ /// True if offset is within range (inclusively)
+ SLANG_FORCE_INLINE bool containsInclusive(uint32_t offset) const { return offset >= start && offset <= end; }
+
+ /// Get the count
+ SLANG_FORCE_INLINE uint32_t getCount() const { return end - start; }
+
+ /// Return an invalid range.
+ static OffsetRange makeInvalid() { return OffsetRange{ kInvalid, kInvalid }; }
+
+ uint32_t start;
+ uint32_t end;
+ };
+
+ /// Returns the line break offsets (in bytes from start of content)
+ /// Note that this is lazily evaluated - the line breaks are only calculated on the first request
+ const List<uint32_t>& getLineBreakOffsets();
+
+ /// Returns true if the offset is on the specified line
+ /// NOTE! If offsets are not fully setup (because we don't have source), will only be correct for lines that have offsets
+ bool isOffsetOnLine(uint32_t offset, Index lineIndex);
+
+ /// Get the line containing the offset. Requires that content is available, else will return an empty slice.
+ UnownedStringSlice getLineContainingOffset(uint32_t offset);
+
+ /// Get the line at the specified line index. Requires that content is available, else will return an empty slice.
+ UnownedStringSlice getLineAtIndex(Index lineIndex);
+
+ /// Get the offset range at the specified line index. Works without content.
+ OffsetRange getOffsetRangeAtLineIndex(Index lineIndex);
+
+ /// Set the line break offsets
+ void setLineBreakOffsets(const uint32_t* offsets, UInt numOffsets);
+
+ /// Calculate the line based on the offset
+ int calcLineIndexFromOffset(int offset);
+
+ /// Calculate the offset for a line
+ int calcColumnIndex(int line, int offset);
+
+ /// Get the content holding blob
+ ISlangBlob* getContentBlob() const { return m_contentBlob; }
+
+ /// True if has full set content
+ bool hasContent() const { return m_contentBlob != nullptr; }
+
+ /// Get the content size
+ size_t getContentSize() const { return m_contentSize; }
+
+ /// Get the content
+ const UnownedStringSlice& getContent() const { return m_content; }
+
+ /// Get path info
+ const PathInfo& getPathInfo() const { return m_pathInfo; }
+
+ /// Set the content as a blob
+ void setContents(ISlangBlob* blob);
+ /// Set the content as a string
+ void setContents(const String& content);
+
+ /// Calculate a display path -> can canonicalize if necessary
+ String calcVerbosePath() const;
+
+ /// Get the source manager this was created on
+ SourceManager* getSourceManager() const { return m_sourceManager; }
+
+ /// Ctor
+ SourceFile(SourceManager* sourceManager, const PathInfo& pathInfo, size_t contentSize);
+ /// Dtor
+ ~SourceFile();
+
+ protected:
+
+ SourceManager* m_sourceManager; ///< The source manager this belongs to
+ PathInfo m_pathInfo; ///< The path The logical file path to report for locations inside this span.
+ ComPtr<ISlangBlob> m_contentBlob; ///< A blob that owns the storage for the file contents. If nullptr, there is no contents
+ UnownedStringSlice m_content; ///< The actual contents of the file.
+ size_t m_contentSize; ///< The size of the actual contents
+
+ // In order to speed up lookup of line number information,
+ // we will cache the starting offset of each line break in
+ // the input file:
+ List<uint32_t> m_lineBreakOffsets;
+};
+
+enum class SourceLocType
+{
+ Nominal, ///< The normal interpretation which takes into account #line directives
+ Actual, ///< Ignores #line directives - and is the location as seen in the actual file
+};
+
+// A source location in a format a human might like to see
+struct HumaneSourceLoc
+{
+ PathInfo pathInfo = PathInfo::makeUnknown();
+ Int line = 0;
+ Int column = 0;
+};
+
+
+/* A SourceView maps to a single span of SourceLoc range and is equivalent to a single include or more precisely use of a source file.
+It is distinct from a SourceFile - because a SourceFile may be included multiple times, with different interpretations (depending
+on #defines for example).
+*/
+class SourceView
+{
+ public:
+
+ // Each entry represents some contiguous span of locations that
+ // all map to the same logical file.
+ struct Entry
+ {
+ /// True if this resets the line numbering. It is distinct from a m_lineAdjust being 0, because it also means the path returns to the default.
+ bool isDefault() const { return m_pathHandle == StringSlicePool::Handle(0); }
+
+ SourceLoc m_startLoc; ///< Where does this entry begin?
+ StringSlicePool::Handle m_pathHandle; ///< What is the presumed path for this entry. If 0 it means there is no path.
+ int32_t m_lineAdjust; ///< Adjustment to apply to source line numbers when printing presumed locations. Relative to the line number in the underlying file.
+ };
+
+ /// Given a sourceLoc finds the entry associated with it. If returns -1 then no entry is
+ /// associated with this location, and therefore the location should be interpreted as an offset
+ /// into the underlying sourceFile.
+ int findEntryIndex(SourceLoc sourceLoc) const;
+
+ /// Add a line directive for this view. The directiveLoc must of course be in this SourceView
+ /// The path handle, must have been constructed on the SourceManager associated with the view
+ /// NOTE! Directives are assumed to be added IN ORDER during parsing such that every directiveLoc > previous
+ void addLineDirective(SourceLoc directiveLoc, StringSlicePool::Handle pathHandle, int line);
+ void addLineDirective(SourceLoc directiveLoc, const String& path, int line);
+
+ /// Removes any corrections on line numbers and reverts to the source files path
+ void addDefaultLineDirective(SourceLoc directiveLoc);
+
+ /// Get the range that this view applies to
+ const SourceRange& getRange() const { return m_range; }
+ /// Get the entries
+ const List<Entry>& getEntries() const { return m_entries; }
+ /// Set the entries list
+ void setEntries(const Entry* entries, UInt numEntries) { m_entries.clear(); m_entries.addRange(entries, numEntries); }
+
+ /// Get the source file holds the contents this view
+ SourceFile* getSourceFile() const { return m_sourceFile; }
+ /// Get the source manager
+ SourceManager* getSourceManager() const { return m_sourceFile->getSourceManager(); }
+
+ /// Get the associated 'content' (the source text)
+ const UnownedStringSlice& getContent() const { return m_sourceFile->getContent(); }
+
+ /// Get the size of the content
+ size_t getContentSize() const { return m_sourceFile->getContentSize(); }
+
+ /// Get the humane location
+ /// Type determines if the location wanted is the original, or the 'normal' (which modifys behavior based on #line directives)
+ HumaneSourceLoc getHumaneLoc(SourceLoc loc, SourceLocType type = SourceLocType::Nominal);
+
+ /// Get the path associated with a location
+ PathInfo getPathInfo(SourceLoc loc, SourceLocType type = SourceLocType::Nominal);
+
+ /// Get the initiating source location - that is the source location that caused the this SourceView to be created
+ /// Can be SourceLoc(0) if there is no initiating location.
+ /// For example for a #include - the view's initiating source loc for the view that is the contents of the view
+ /// will be the location of the #include in the source.
+ /// For the original source file (ie not an include) - the view will have an initiating source loc of SourceLoc(0)
+ SourceLoc getInitiatingSourceLoc() const { return m_initiatingSourceLoc; }
+
+ /// Ctor
+ SourceView(SourceFile* sourceFile, SourceRange range, const String* viewPath, SourceLoc initiatingSourceLoc):
+ m_range(range),
+ m_sourceFile(sourceFile),
+ m_initiatingSourceLoc(initiatingSourceLoc)
+ {
+ if (viewPath)
+ {
+ m_viewPath = *viewPath;
+ }
+ }
+
+ protected:
+ /// Get the pathInfo from a string handle. If it's 0, it will return the _getPathInfo
+ PathInfo _getPathInfoFromHandle(StringSlicePool::Handle pathHandle) const;
+ /// Gets the pathInfo for this view. It may be different from the m_sourceFile's if the path has been
+ /// overridden by m_viewPath
+ PathInfo _getPathInfo() const;
+
+ String m_viewPath; ///< Path to this view. If empty the path is the path to the SourceView
+
+ SourceLoc m_initiatingSourceLoc; ///< An optional source loc that defines where this view was initiated from. SourceLoc(0) if not defined.
+
+ SourceRange m_range; ///< The range that this SourceView applies to
+ SourceFile* m_sourceFile; ///< The source file. Can hold the line breaks
+ List<Entry> m_entries; ///< An array entries describing how we should interpret a range, starting from the start location.
+};
+
+struct SourceManager
+{
+ // Initialize a source manager, with an optional parent
+ void initialize(SourceManager* parent, ISlangFileSystemExt* fileSystemExt);
+
+ /// Allocate a range of SourceLoc locations, these can be used to identify a specific location in the source
+ SourceRange allocateSourceRange(UInt size);
+
+ /// Create a SourceFile defined with the specified path, and content held within a blob
+ SourceFile* createSourceFileWithSize(const PathInfo& pathInfo, size_t contentSize);
+ SourceFile* createSourceFileWithString(const PathInfo& pathInfo, const String& contents);
+ SourceFile* createSourceFileWithBlob(const PathInfo& pathInfo, ISlangBlob* blob);
+
+ /// Get the humane source location
+ HumaneSourceLoc getHumaneLoc(SourceLoc loc, SourceLocType type = SourceLocType::Nominal);
+
+ /// Get the path associated with a location
+ PathInfo getPathInfo(SourceLoc loc, SourceLocType type = SourceLocType::Nominal);
+
+ /// Create a new source view from a file
+ /// @param sourceFile is the source file that contains the source
+ /// @param pathInfo is path used to read the file from
+ /// @param initiatingSourceLoc the (optional) location in the source that led the the creation of this view. If there isn't an initiating source location pass SourceLoc(0)s
+ SourceView* createSourceView(SourceFile* sourceFile, const PathInfo* pathInfo, SourceLoc initiatingSourceLoc);
+
+ /// Find a view by a source file location.
+ /// If not found in this manager will look in the parent SourceManager
+ /// Returns nullptr if not found.
+ SourceView* findSourceViewRecursively(SourceLoc loc) const;
+
+ /// Find the SourceView associated with this manager for a specified location
+ /// Returns nullptr if not found.
+ SourceView* findSourceView(SourceLoc loc) const;
+
+ /// Searches this manager, and then the parent to see if can find a match for path.
+ /// If not found returns nullptr.
+ SourceFile* findSourceFileRecursively(const String& uniqueIdentity) const;
+ /// Find if the source file is defined on this manager.
+ SourceFile* findSourceFile(const String& uniqueIdentity) const;
+
+ /// Searches this manager, and then the parent to see if can find a match
+ SourceFile* findSourceFileByContentRecursively(const char* text);
+ /// Find the source file that contains *the memory* text points to.
+ SourceFile* findSourceFileByContent(const char* text) const;
+
+ /// Get the file system associated with this source manager
+ ISlangFileSystemExt* getFileSystemExt() const { return m_fileSystemExt; }
+ /// Get the file system associated with this source manager
+ void setFileSystemExt(ISlangFileSystemExt* fileSystemExt) { m_fileSystemExt = fileSystemExt; }
+
+ /// Add a source file, uniqueIdentity must be unique for this manager AND any parents
+ void addSourceFile(const String& uniqueIdentity, SourceFile* sourceFile);
+
+ /// Get the slice pool
+ StringSlicePool& getStringSlicePool() { return m_slicePool; }
+
+ /// Get the source range for just this manager
+ /// Caution - the range will change if allocations are made to this manager.
+ SourceRange getSourceRange() const { return SourceRange(m_startLoc, m_nextLoc); }
+
+ /// Get the parent manager to this manager. Returns nullptr if there isn't any.
+ SourceManager* getParent() const { return m_parent; }
+
+ /// A memory arena to hold allocations that are in scope for the same time as SourceManager
+ MemoryArena* getMemoryArena() { return &m_memoryArena; }
+
+ /// Allocate a string slice
+ UnownedStringSlice allocateStringSlice(const UnownedStringSlice& slice);
+
+ /// Get all of the source files
+ const List<SourceFile*>& getSourceFiles() const { return m_sourceFiles; }
+
+ /// Get the source views
+ const List<SourceView*>& getSourceViews() const { return m_sourceViews; }
+
+ SourceManager() :
+ m_memoryArena(2048),
+ m_slicePool(StringSlicePool::Style::Default)
+ {}
+ ~SourceManager();
+
+ protected:
+
+ // The first location available to this source manager
+ // (may not be the first location of all, because we might
+ // have a parent source manager)
+ SourceLoc m_startLoc;
+
+ // The "parent" source manager that owns locations ahead of `startLoc`
+ SourceManager* m_parent = nullptr;
+
+ // The location to be used by the next source file to be loaded
+ SourceLoc m_nextLoc;
+
+ // All of the SourceViews constructed on this SourceManager. These are held in increasing order of range, so can find by doing a binary chop.
+ List<SourceView*> m_sourceViews;
+ // All of the SourceFiles constructed on this SourceManager. This owns the SourceFile.
+ List<SourceFile*> m_sourceFiles;
+
+ StringSlicePool m_slicePool;
+
+ // Memory arena that can be used for holding data to held in scope as long as the Source is
+ // Can be used for storing the decoded contents of Token. Content for example.
+ MemoryArena m_memoryArena;
+
+ // Maps uniqueIdentities to source files
+ Dictionary<String, SourceFile*> m_sourceFileMap;
+
+ ComPtr<ISlangFileSystemExt> m_fileSystemExt;
+};
+
+} // namespace Slang
+
+#endif
diff --git a/source/compiler-core/slang-token-defs.h b/source/compiler-core/slang-token-defs.h
new file mode 100644
index 000000000..6cece330e
--- /dev/null
+++ b/source/compiler-core/slang-token-defs.h
@@ -0,0 +1,96 @@
+// slang-token-defs.h
+
+// This file is meant to be included multiple times, to produce different
+// pieces of code related to tokens
+//
+// Each token is declared here with:
+//
+// TOKEN(id, desc)
+//
+// where `id` is the identifier that will be used for the token in
+// ordinary code, while `desc` is name we should print when
+// referring to this token in diagnostic messages.
+
+
+#ifndef TOKEN
+#error Need to define TOKEN(ID, DESC) before including "token-defs.h"
+#endif
+
+TOKEN(Unknown, "<unknown>")
+TOKEN(EndOfFile, "end of file")
+TOKEN(EndOfDirective, "end of line")
+TOKEN(Invalid, "invalid character")
+TOKEN(Identifier, "identifier")
+TOKEN(IntegerLiteral, "integer literal")
+TOKEN(FloatingPointLiteral, "floating-point literal")
+TOKEN(StringLiteral, "string literal")
+TOKEN(CharLiteral, "character literal")
+TOKEN(WhiteSpace, "whitespace")
+TOKEN(NewLine, "newline")
+TOKEN(LineComment, "line comment")
+TOKEN(BlockComment, "block comment")
+TOKEN(DirectiveMessage, "user-defined message")
+
+#define PUNCTUATION(id, text) \
+ TOKEN(id, "'" text "'")
+
+PUNCTUATION(Semicolon, ";")
+PUNCTUATION(Comma, ",")
+PUNCTUATION(Dot, ".")
+
+PUNCTUATION(LBrace, "{")
+PUNCTUATION(RBrace, "}")
+PUNCTUATION(LBracket, "[")
+PUNCTUATION(RBracket, "]")
+PUNCTUATION(LParent, "(")
+PUNCTUATION(RParent, ")")
+
+PUNCTUATION(OpAssign, "=")
+PUNCTUATION(OpAdd, "+")
+PUNCTUATION(OpSub, "-")
+PUNCTUATION(OpMul, "*")
+PUNCTUATION(OpDiv, "/")
+PUNCTUATION(OpMod, "%")
+PUNCTUATION(OpNot, "!")
+PUNCTUATION(OpBitNot, "~")
+PUNCTUATION(OpLsh, "<<")
+PUNCTUATION(OpRsh, ">>")
+PUNCTUATION(OpEql, "==")
+PUNCTUATION(OpNeq, "!=")
+PUNCTUATION(OpGreater, ">")
+PUNCTUATION(OpLess, "<")
+PUNCTUATION(OpGeq, ">=")
+PUNCTUATION(OpLeq, "<=")
+PUNCTUATION(OpAnd, "&&")
+PUNCTUATION(OpOr, "||")
+PUNCTUATION(OpBitAnd, "&")
+PUNCTUATION(OpBitOr, "|")
+PUNCTUATION(OpBitXor, "^")
+PUNCTUATION(OpInc, "++")
+PUNCTUATION(OpDec, "--")
+
+PUNCTUATION(OpAddAssign, "+=")
+PUNCTUATION(OpSubAssign, "-=")
+PUNCTUATION(OpMulAssign, "*=")
+PUNCTUATION(OpDivAssign, "/=")
+PUNCTUATION(OpModAssign, "%=")
+PUNCTUATION(OpShlAssign, "<<=")
+PUNCTUATION(OpShrAssign, ">>=")
+PUNCTUATION(OpAndAssign, "&=")
+PUNCTUATION(OpOrAssign, "|=")
+PUNCTUATION(OpXorAssign, "^=")
+
+PUNCTUATION(QuestionMark, "?")
+PUNCTUATION(Colon, ":")
+PUNCTUATION(RightArrow, "->")
+PUNCTUATION(At, "@")
+PUNCTUATION(Dollar, "$")
+PUNCTUATION(Pound, "#")
+PUNCTUATION(PoundPound, "##")
+
+PUNCTUATION(Scope, "::")
+
+#undef PUNCTUATION
+
+// Un-define the `TOKEN` macro so that client doesn't have to
+#undef TOKEN
diff --git a/source/compiler-core/slang-token.cpp b/source/compiler-core/slang-token.cpp
new file mode 100644
index 000000000..576b08d66
--- /dev/null
+++ b/source/compiler-core/slang-token.cpp
@@ -0,0 +1,21 @@
+// slang-token.cpp
+#include "slang-token.h"
+
+//#include <assert.h>
+
+namespace Slang {
+
+char const* TokenTypeToString(TokenType type)
+{
+ switch( type )
+ {
+ default:
+ SLANG_ASSERT(!"unexpected");
+ return "<uknown>";
+
+#define TOKEN(NAME, DESC) case TokenType::NAME: return DESC;
+#include "slang-token-defs.h"
+ }
+}
+
+} // namespace Slang
diff --git a/source/compiler-core/slang-token.h b/source/compiler-core/slang-token.h
new file mode 100644
index 000000000..9697a5c2d
--- /dev/null
+++ b/source/compiler-core/slang-token.h
@@ -0,0 +1,140 @@
+// slang-token.h
+#ifndef SLANG_TOKEN_H_INCLUDED
+#define SLANG_TOKEN_H_INCLUDED
+
+#include "../core/slang-basic.h"
+
+#include "slang-source-loc.h"
+#include "slang-name.h"
+
+namespace Slang {
+
+class Name;
+
+enum class TokenType : uint8_t
+{
+#define TOKEN(NAME, DESC) NAME,
+#include "slang-token-defs.h"
+};
+
+char const* TokenTypeToString(TokenType type);
+
+typedef uint8_t TokenFlags;
+struct TokenFlag
+{
+ enum Enum : TokenFlags
+ {
+ AtStartOfLine = 1 << 0,
+ AfterWhitespace = 1 << 1,
+ SuppressMacroExpansion = 1 << 2,
+ ScrubbingNeeded = 1 << 3,
+ Name = 1 << 4, ///< Determines if 'name' is set or 'chars' in the charsNameUnion
+ };
+};
+
+class Token
+{
+public:
+
+ TokenType type = TokenType::Unknown;
+ TokenFlags flags = 0;
+
+ SourceLoc loc;
+ uint32_t charsCount = 0; ///< Amount of characters. Is set if name or not.
+
+ union CharsNameUnion
+ {
+ const char* chars;
+ Name* name;
+ };
+
+ CharsNameUnion charsNameUnion;
+
+ bool hasContent() const { return charsCount > 0; }
+ Index getContentLength() const { return charsCount; }
+
+ UnownedStringSlice getContent() const;
+ /// Set content
+ void setContent(const UnownedStringSlice& content);
+
+ Name* getName() const;
+
+ Name* getNameOrNull() const;
+
+ SourceLoc getLoc() const { return loc; }
+
+ /// Set the name
+ SLANG_FORCE_INLINE void setName(Name* inName);
+
+ Token()
+ {
+ charsNameUnion.chars = nullptr;
+ }
+
+ Token(
+ TokenType inType,
+ const UnownedStringSlice& inContent,
+ SourceLoc inLoc,
+ TokenFlags inFlags = 0)
+ : flags(inFlags)
+ {
+ SLANG_ASSERT((inFlags & TokenFlag::Name) == 0);
+ type = inType;
+ charsNameUnion.chars = inContent.begin();
+ charsCount = uint32_t(inContent.getLength());
+ loc = inLoc;
+ }
+ Token(
+ TokenType inType,
+ Name* name,
+ SourceLoc inLoc,
+ TokenFlags inFlags = 0)
+ {
+ SLANG_ASSERT(name);
+ type = inType;
+ flags = inFlags | TokenFlag::Name;
+ charsNameUnion.name = name;
+ charsCount = uint32_t(name->text.getLength());
+ loc = inLoc;
+ }
+};
+
+// ---------------------------------------------------------------------------
+SLANG_FORCE_INLINE UnownedStringSlice Token::getContent() const
+{
+ return (flags & TokenFlag::Name) ? charsNameUnion.name->text.getUnownedSlice() : UnownedStringSlice(charsNameUnion.chars, charsCount);
+}
+
+// ---------------------------------------------------------------------------
+SLANG_FORCE_INLINE Name* Token::getName() const
+{
+ return getNameOrNull();
+}
+
+// ---------------------------------------------------------------------------
+SLANG_FORCE_INLINE Name* Token::getNameOrNull() const
+{
+ return (flags & TokenFlag::Name) ? charsNameUnion.name : nullptr;
+}
+
+// ---------------------------------------------------------------------------
+SLANG_FORCE_INLINE void Token::setContent(const UnownedStringSlice& content)
+{
+ flags &= ~TokenFlag::Name;
+ charsNameUnion.chars = content.begin();
+ charsCount = uint32_t(content.getLength());
+}
+
+// ---------------------------------------------------------------------------
+SLANG_FORCE_INLINE void Token::setName(Name* inName)
+{
+ SLANG_ASSERT(inName);
+ flags |= TokenFlag::Name;
+ charsNameUnion.name = inName;
+ charsCount = uint32_t(inName->text.getLength());
+}
+
+
+} // namespace Slang
+
+#endif
diff --git a/source/compiler-core/slang-visual-studio-compiler-util.cpp b/source/compiler-core/slang-visual-studio-compiler-util.cpp
new file mode 100644
index 000000000..fd879e63f
--- /dev/null
+++ b/source/compiler-core/slang-visual-studio-compiler-util.cpp
@@ -0,0 +1,452 @@
+// slang-visual-studio-compiler-util.cpp
+#include "slang-visual-studio-compiler-util.h"
+
+#include "../core/slang-common.h"
+#include "../../slang-com-helper.h"
+#include "../core/slang-string-util.h"
+
+// if Visual Studio import the visual studio platform specific header
+#if SLANG_VC
+# include "windows/slang-win-visual-studio-util.h"
+#endif
+
+#include "../core/slang-io.h"
+
+namespace Slang
+{
+
+/* static */ SlangResult VisualStudioCompilerUtil::calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath)
+{
+ SLANG_ASSERT(options.modulePath.getLength());
+
+ outPath.Clear();
+
+ switch (options.targetType)
+ {
+ case TargetType::SharedLibrary:
+ {
+ outPath << options.modulePath << ".dll";
+ return SLANG_OK;
+ }
+ case TargetType::Executable:
+ {
+ outPath << options.modulePath << ".exe";
+ return SLANG_OK;
+ }
+ case TargetType::Object:
+ {
+ outPath << options.modulePath << ".obj";
+ return SLANG_OK;
+ }
+ default: break;
+ }
+
+ return SLANG_FAIL;
+}
+
+/* static */SlangResult VisualStudioCompilerUtil::calcCompileProducts(const CompileOptions& options, ProductFlags flags, List<String>& outPaths)
+{
+ SLANG_ASSERT(options.modulePath.getLength());
+
+ outPaths.clear();
+
+ if (flags & ProductFlag::Execution)
+ {
+ StringBuilder builder;
+ SLANG_RETURN_ON_FAIL(calcModuleFilePath(options, builder));
+ outPaths.add(builder);
+ }
+ if (flags & ProductFlag::Miscellaneous)
+ {
+ outPaths.add(options.modulePath + ".ilk");
+
+ if (options.targetType == TargetType::SharedLibrary)
+ {
+ outPaths.add(options.modulePath + ".exp");
+ outPaths.add(options.modulePath + ".lib");
+ }
+ }
+ if (flags & ProductFlag::Compile)
+ {
+ outPaths.add(options.modulePath + ".obj");
+ }
+ if (flags & ProductFlag::Debug)
+ {
+ // TODO(JS): Could try and determine based on debug information
+ outPaths.add(options.modulePath + ".pdb");
+ }
+
+ return SLANG_OK;
+}
+
+/* static */SlangResult VisualStudioCompilerUtil::calcArgs(const CompileOptions& options, CommandLine& cmdLine)
+{
+ SLANG_ASSERT(options.sourceContents.getLength() == 0);
+ SLANG_ASSERT(options.modulePath.getLength());
+
+ // https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically?view=vs-2019
+
+ cmdLine.addArg("/nologo");
+ // Generate complete debugging information
+ cmdLine.addArg("/Zi");
+ // Display full path of source files in diagnostics
+ cmdLine.addArg("/FC");
+
+ if (options.flags & CompileOptions::Flag::EnableExceptionHandling)
+ {
+ if (options.sourceLanguage == SLANG_SOURCE_LANGUAGE_CPP)
+ {
+ // https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=vs-2019
+ // Assumes c functions cannot throw
+ cmdLine.addArg("/EHsc");
+ }
+ }
+
+ if (options.flags & CompileOptions::Flag::Verbose)
+ {
+ // Doesn't appear to be a VS equivalent
+ }
+
+ if (options.flags & CompileOptions::Flag::EnableSecurityChecks)
+ {
+ cmdLine.addArg("/GS");
+ }
+ else
+ {
+ cmdLine.addArg("/GS-");
+ }
+
+ switch (options.debugInfoType)
+ {
+ default:
+ {
+ // Multithreaded statically linked runtime library
+ cmdLine.addArg("/MD");
+ break;
+ }
+ case DebugInfoType::None:
+ {
+ break;
+ }
+ case DebugInfoType::Maximal:
+ {
+ // Multithreaded statically linked *debug* runtime library
+ cmdLine.addArg("/MDd");
+ break;
+ }
+ }
+
+ // /Fd - followed by name of the pdb file
+ if (options.debugInfoType != DebugInfoType::None)
+ {
+ cmdLine.addPrefixPathArg("/Fd", options.modulePath, ".pdb");
+ }
+
+ switch (options.optimizationLevel)
+ {
+ case OptimizationLevel::None:
+ {
+ // No optimization
+ cmdLine.addArg("/Od");
+ break;
+ }
+ case OptimizationLevel::Default:
+ {
+ break;
+ }
+ case OptimizationLevel::High:
+ {
+ cmdLine.addArg("/O2");
+ break;
+ }
+ case OptimizationLevel::Maximal:
+ {
+ cmdLine.addArg("/Ox");
+ break;
+ }
+ default: break;
+ }
+
+ switch (options.floatingPointMode)
+ {
+ case FloatingPointMode::Default: break;
+ case FloatingPointMode::Precise:
+ {
+ // precise is default behavior, VS also has 'strict'
+ //
+ // ```/fp:strict has behavior similar to /fp:precise, that is, the compiler preserves the source ordering and rounding properties of floating-point code when
+ // it generates and optimizes object code for the target machine, and observes the standard when handling special values. In addition, the program may safely
+ // access or modify the floating-point environment at runtime.```
+
+ cmdLine.addArg("/fp:precise");
+ break;
+ }
+ case FloatingPointMode::Fast:
+ {
+ cmdLine.addArg("/fp:fast");
+ break;
+ }
+ }
+
+ switch (options.targetType)
+ {
+ case TargetType::SharedLibrary:
+ {
+ // Create dynamic link library
+ if (options.debugInfoType == DebugInfoType::None)
+ {
+ cmdLine.addArg("/LDd");
+ }
+ else
+ {
+ cmdLine.addArg("/LD");
+ }
+
+ cmdLine.addPrefixPathArg("/Fe", options.modulePath, ".dll");
+ break;
+ }
+ case TargetType::Executable:
+ {
+ cmdLine.addPrefixPathArg("/Fe", options.modulePath, ".exe");
+ break;
+ }
+ default: break;
+ }
+
+ // Object file specify it's location - needed if we are out
+ cmdLine.addPrefixPathArg("/Fo", options.modulePath, ".obj");
+
+ // Add defines
+ for (const auto& define : options.defines)
+ {
+ StringBuilder builder;
+ builder << "/D";
+ builder << define.nameWithSig;
+ if (define.value.getLength())
+ {
+ builder << "=" << define.value;
+ }
+
+ cmdLine.addArg(builder);
+ }
+
+ // Add includes
+ for (const auto& include : options.includePaths)
+ {
+ cmdLine.addArg("/I");
+ cmdLine.addArg(include);
+ }
+
+ // https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=vs-2019
+ // /Eha - Specifies the model of exception handling. (a, s, c, r are options)
+
+ // Files to compile
+ for (const auto& sourceFile : options.sourceFiles)
+ {
+ cmdLine.addArg(sourceFile);
+ }
+
+ // Link options (parameters past /link go to linker)
+ cmdLine.addArg("/link");
+
+ for (const auto& libPath : options.libraryPaths)
+ {
+ // Note that any escaping of the path is handled in the ProcessUtil::
+ cmdLine.addPrefixPathArg("/LIBPATH:", libPath);
+ }
+
+ return SLANG_OK;
+}
+
+static SlangResult _parseSeverity(const UnownedStringSlice& in, DownstreamDiagnostics::Diagnostic::Severity& outSeverity)
+{
+ typedef DownstreamDiagnostics::Diagnostic::Severity Type;
+
+ if (in == "error" || in == "fatal error")
+ {
+ outSeverity = Type::Error;
+ }
+ else if (in == "warning")
+ {
+ outSeverity = Type::Warning;
+ }
+ else if (in == "info")
+ {
+ outSeverity = Type::Info;
+ }
+ else
+ {
+ return SLANG_FAIL;
+ }
+ return SLANG_OK;
+}
+
+static SlangResult _parseVisualStudioLine(const UnownedStringSlice& line, DownstreamDiagnostics::Diagnostic& outDiagnostic)
+{
+ typedef DownstreamDiagnostics::Diagnostic Diagnostic;
+
+ UnownedStringSlice linkPrefix = UnownedStringSlice::fromLiteral("LINK :");
+ if (line.startsWith(linkPrefix))
+ {
+ outDiagnostic.stage = Diagnostic::Stage::Link;
+ outDiagnostic.severity = Diagnostic::Severity::Info;
+
+ outDiagnostic.text = UnownedStringSlice(line.begin() + linkPrefix.getLength(), line.end());
+
+ return SLANG_OK;
+ }
+
+ outDiagnostic.stage = Diagnostic::Stage::Compile;
+
+ const char*const start = line.begin();
+ const char*const end = line.end();
+
+ UnownedStringSlice postPath;
+ // Handle the path and line no
+ {
+ const char* cur = start;
+
+ // We have to assume it is a path up to the first : that isn't part of a drive specification
+
+ if ((end - cur > 2) && Path::isDriveSpecification(UnownedStringSlice(start, start + 2)))
+ {
+ // Skip drive spec
+ cur += 2;
+ }
+
+ // Find the first colon after this
+ Index colonIndex = UnownedStringSlice(cur, end).indexOf(':');
+ if (colonIndex < 0)
+ {
+ return SLANG_FAIL;
+ }
+
+ // Looks like we have a line number
+ if (cur[colonIndex - 1] == ')')
+ {
+ const char* lineNoEnd = cur + colonIndex - 1;
+ const char* lineNoStart = lineNoEnd;
+ while (lineNoStart > start && *lineNoStart != '(')
+ {
+ lineNoStart--;
+ }
+ // Check this appears plausible
+ if (*lineNoStart != '(' || *lineNoEnd != ')')
+ {
+ return SLANG_FAIL;
+ }
+ Int numDigits = 0;
+ Int lineNo = 0;
+ for (const char* digitCur = lineNoStart + 1; digitCur < lineNoEnd; ++digitCur)
+ {
+ char c = *digitCur;
+ if (c >= '0' && c <= '9')
+ {
+ lineNo = lineNo * 10 + (c - '0');
+ numDigits++;
+ }
+ else
+ {
+ return SLANG_FAIL;
+ }
+ }
+ if (numDigits == 0)
+ {
+ return SLANG_FAIL;
+ }
+
+ outDiagnostic.filePath = UnownedStringSlice(start, lineNoStart);
+ outDiagnostic.fileLine = lineNo;
+ }
+ else
+ {
+ outDiagnostic.filePath = UnownedStringSlice(start, cur + colonIndex);
+ outDiagnostic.fileLine = 0;
+ }
+
+ // Save the remaining text in 'postPath'
+ postPath = UnownedStringSlice(cur + colonIndex + 1, end);
+ }
+
+ // Split up the error section
+ UnownedStringSlice postError;
+ {
+ // tests/cpp-compiler/c-compile-link-error.exe : fatal error LNK1120: 1 unresolved externals
+
+ const Index errorColonIndex = postPath.indexOf(':');
+ if (errorColonIndex < 0)
+ {
+ return SLANG_FAIL;
+ }
+
+ const UnownedStringSlice errorSection = UnownedStringSlice(postPath.begin(), postPath.begin() + errorColonIndex);
+ Index errorCodeIndex = errorSection.lastIndexOf(' ');
+ if (errorCodeIndex < 0)
+ {
+ return SLANG_FAIL;
+ }
+
+ // Extract the code
+ outDiagnostic.code = UnownedStringSlice(errorSection.begin() + errorCodeIndex + 1, errorSection.end());
+ if (outDiagnostic.code.startsWith(UnownedStringSlice::fromLiteral("LNK")))
+ {
+ outDiagnostic.stage = Diagnostic::Stage::Link;
+ }
+
+ // Extract the bit before the code
+ SLANG_RETURN_ON_FAIL(_parseSeverity(UnownedStringSlice(errorSection.begin(), errorSection.begin() + errorCodeIndex).trim(), outDiagnostic.severity));
+
+ // Link codes start with LNK prefix
+ postError = UnownedStringSlice(postPath.begin() + errorColonIndex + 1, end);
+ }
+
+ outDiagnostic.text = postError;
+
+ return SLANG_OK;
+}
+
+/* static */SlangResult VisualStudioCompilerUtil::parseOutput(const ExecuteResult& exeRes, DownstreamDiagnostics& outDiagnostics)
+{
+ outDiagnostics.reset();
+
+ outDiagnostics.rawDiagnostics = exeRes.standardOutput;
+
+ for (auto line : LineParser(exeRes.standardOutput.getUnownedSlice()))
+ {
+#if 0
+ fwrite(line.begin(), 1, line.size(), stdout);
+ fprintf(stdout, "\n");
+#endif
+
+ Diagnostic diagnostic;
+ if (SLANG_SUCCEEDED(_parseVisualStudioLine(line, diagnostic)))
+ {
+ outDiagnostics.diagnostics.add(diagnostic);
+ }
+ }
+
+ // if it has a compilation error.. set on output
+ if (outDiagnostics.has(Diagnostic::Severity::Error))
+ {
+ outDiagnostics.result = SLANG_FAIL;
+ }
+
+ return SLANG_OK;
+}
+
+/* static */SlangResult VisualStudioCompilerUtil::locateCompilers(const String& path, ISlangSharedLibraryLoader* loader, DownstreamCompilerSet* set)
+{
+ SLANG_UNUSED(loader);
+
+ // TODO(JS): We don't support fixed path for visual studio just yet
+ if (path.getLength() == 0)
+ {
+#if SLANG_VC
+ return WinVisualStudioUtil::find(set);
+#endif
+ }
+
+ return SLANG_OK;
+}
+
+}
diff --git a/source/compiler-core/slang-visual-studio-compiler-util.h b/source/compiler-core/slang-visual-studio-compiler-util.h
new file mode 100644
index 000000000..018dde212
--- /dev/null
+++ b/source/compiler-core/slang-visual-studio-compiler-util.h
@@ -0,0 +1,42 @@
+#ifndef SLANG_VISUAL_STUDIO_COMPILER_UTIL_H
+#define SLANG_VISUAL_STUDIO_COMPILER_UTIL_H
+
+#include "slang-downstream-compiler.h"
+
+namespace Slang
+{
+
+
+struct VisualStudioCompilerUtil : public DownstreamCompilerBaseUtil
+{
+ /// Calculate Visual Studio family compilers cmdLine arguments from options
+ static SlangResult calcArgs(const CompileOptions& options, CommandLine& cmdLine);
+ /// Parse Visual Studio exeRes into CPPCompiler::Output
+ static SlangResult parseOutput(const ExecuteResult& exeRes, DownstreamDiagnostics& outOutput);
+
+ static SlangResult calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath);
+
+ static SlangResult calcCompileProducts(const CompileOptions& options, ProductFlags flags, List<String>& outPaths);
+
+ static SlangResult locateCompilers(const String& path, ISlangSharedLibraryLoader* loader, DownstreamCompilerSet* set);
+};
+
+class VisualStudioDownstreamCompiler : public CommandLineDownstreamCompiler
+{
+public:
+ typedef CommandLineDownstreamCompiler Super;
+ typedef VisualStudioCompilerUtil Util;
+
+ // CommandLineDownstreamCompiler impl - just forwards to the Util
+ virtual SlangResult calcArgs(const CompileOptions& options, CommandLine& cmdLine) SLANG_OVERRIDE { return Util::calcArgs(options, cmdLine); }
+ virtual SlangResult parseOutput(const ExecuteResult& exeResult, DownstreamDiagnostics& output) SLANG_OVERRIDE { return Util::parseOutput(exeResult, output); }
+ virtual SlangResult calcModuleFilePath(const CompileOptions& options, StringBuilder& outPath) SLANG_OVERRIDE { return Util::calcModuleFilePath(options, outPath); }
+ virtual SlangResult calcCompileProducts(const CompileOptions& options, ProductFlags productFlags, List<String>& outPaths) SLANG_OVERRIDE { return Util::calcCompileProducts(options, productFlags, outPaths); }
+
+ VisualStudioDownstreamCompiler(const Desc& desc):Super(desc) {}
+};
+
+
+}
+
+#endif
diff --git a/source/compiler-core/windows/slang-win-visual-studio-util.cpp b/source/compiler-core/windows/slang-win-visual-studio-util.cpp
new file mode 100644
index 000000000..0dc8ea764
--- /dev/null
+++ b/source/compiler-core/windows/slang-win-visual-studio-util.cpp
@@ -0,0 +1,366 @@
+#include "slang-win-visual-studio-util.h"
+
+#include "../../core/slang-common.h"
+#include "../../core/slang-process-util.h"
+#include "../../core/slang-string-util.h"
+
+#include "../slang-visual-studio-compiler-util.h"
+
+#ifdef _WIN32
+# define WIN32_LEAN_AND_MEAN
+# define NOMINMAX
+# include <Windows.h>
+# undef WIN32_LEAN_AND_MEAN
+# undef NOMINMAX
+
+# include <Shlobj.h>
+
+#endif
+
+// The method used to invoke VS was originally inspired by some ideas in
+// https://github.com/RuntimeCompiledCPlusPlus/RuntimeCompiledCPlusPlus/
+
+namespace Slang {
+
+// Information on VS versioning can be found here
+// https://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B#Internal_version_numbering
+
+
+namespace { // anonymous
+
+typedef WinVisualStudioUtil::Version Version;
+
+struct RegistryInfo
+{
+ const char* regName; ///< The name of the entry in the registry
+ const char* pathFix; ///< With the value from the registry how to fix the path
+};
+
+struct VersionInfo
+{
+ Version version; ///< The version
+ const char* name; ///< The name of the registry key
+};
+
+} // anonymous
+
+static SlangResult _readRegistryKey(const char* path, const char* keyName, String& outString)
+{
+ // https://docs.microsoft.com/en-us/windows/desktop/api/winreg/nf-winreg-regopenkeyexa
+ HKEY key;
+ LONG ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, path, 0, KEY_READ | KEY_WOW64_32KEY, &key);
+ if (ret != ERROR_SUCCESS)
+ {
+ return SLANG_FAIL;
+ }
+
+ char value[MAX_PATH];
+ DWORD size = MAX_PATH;
+
+ // https://docs.microsoft.com/en-us/windows/desktop/api/winreg/nf-winreg-regqueryvalueexa
+ ret = RegQueryValueExA(key, keyName, nullptr, nullptr, (LPBYTE)value, &size);
+ RegCloseKey(key);
+
+ if (ret != ERROR_SUCCESS)
+ {
+ return SLANG_FAIL;
+ }
+
+ outString = value;
+ return SLANG_OK;
+}
+
+// Make easier to set up the array
+static Version _makeVersion(int main, int dot = 0) { return WinVisualStudioUtil::makeVersion(main, dot); }
+
+VersionInfo _makeVersionInfo(const char* name, int high, int dot = 0)
+{
+ VersionInfo info;
+ info.name = name;
+ info.version = WinVisualStudioUtil::makeVersion(high, dot);
+ return info;
+}
+
+static const VersionInfo s_versionInfos[] =
+{
+ _makeVersionInfo("VS 2005", 8),
+ _makeVersionInfo("VS 2008", 9),
+ _makeVersionInfo("VS 2010", 10),
+ _makeVersionInfo("VS 2012", 11),
+ _makeVersionInfo("VS 2013", 12),
+ _makeVersionInfo("VS 2015", 14),
+ _makeVersionInfo("VS 2017", 15),
+ _makeVersionInfo("VS 2019", 16),
+};
+
+// When trying to figure out how this stuff works by running regedit - care is needed,
+// because what regedit displays varies on which version of regedit is used.
+// In order to use the registry paths used here it's necessary to use Start/Run with
+// %systemroot%\syswow64\regedit to view 32 bit keys
+
+static const RegistryInfo s_regInfos[] =
+{
+ {"SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VC7", "" },
+ {"SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", "VC\\Auxiliary\\Build\\" },
+};
+
+static bool _canUseVSWhere(Version version)
+{
+ // If greater than 15.0 we can use vswhere tool
+ return (int(version) >= int(_makeVersion(15)));
+}
+
+static int _getRegistryKeyIndex(Version version)
+{
+ if (int(version) >= int(_makeVersion(15)))
+ {
+ return 1;
+ }
+ return 0;
+}
+
+/* static */void WinVisualStudioUtil::getVersions(List<Version>& outVersions)
+{
+ const int count = SLANG_COUNT_OF(s_versionInfos);
+ outVersions.setCount(count);
+
+ Version* dst = outVersions.begin();
+ for (int i = 0; i < count; ++i)
+ {
+ dst[i] = s_versionInfos[i].version;
+ }
+}
+
+/* static */WinVisualStudioUtil::Version WinVisualStudioUtil::getCompiledVersion()
+{
+ // Get the version of visual studio used to compile this source
+ const uint32_t version = _MSC_VER;
+
+ switch (version)
+ {
+ case 1400: return _makeVersion(8);
+ case 1500: return _makeVersion(9);
+ case 1600: return _makeVersion(10);
+ case 1700: return _makeVersion(11);
+ case 1800: return _makeVersion(12);
+ case 1900:
+ {
+ return _makeVersion(14);
+ }
+ case 1911:
+ case 1912:
+ case 1913:
+ case 1914:
+ case 1915:
+ case 1916:
+ {
+ return _makeVersion(15);
+ }
+ case 1920:
+ {
+ return _makeVersion(16);
+ }
+ default:
+ {
+ int lastKnownVersion = 1920;
+ if (version > lastKnownVersion)
+ {
+ // Its an unknown newer version
+ return Version::Future;
+ }
+ break;
+ }
+ }
+
+ // Unknown version
+ return Version::Unknown;
+}
+
+static SlangResult _find(int versionIndex, WinVisualStudioUtil::VersionPath& outPath)
+{
+ const auto& versionInfo = s_versionInfos[versionIndex];
+
+ auto version = versionInfo.version;
+
+ outPath.version = version;
+ outPath.vcvarsPath = String();
+
+ if (_canUseVSWhere(version))
+ {
+ CommandLine cmd;
+
+ // Lookup directly %ProgramFiles(x86)% path
+ // https://docs.microsoft.com/en-us/windows/desktop/api/shlobj_core/nf-shlobj_core-shgetfolderpatha
+ HWND hwnd = GetConsoleWindow();
+
+ char programFilesPath[_MAX_PATH];
+ SHGetFolderPathA(hwnd, CSIDL_PROGRAM_FILESX86, NULL, 0, programFilesPath);
+
+ String vswherePath = programFilesPath;
+ vswherePath.append("\\Microsoft Visual Studio\\Installer\\vswhere");
+
+ cmd.setExecutableFilename(vswherePath);
+
+ StringBuilder versionName;
+ WinVisualStudioUtil::append(version, versionName);
+
+ String args[] = { "-version", versionName, "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-property", "installationPath" };
+ cmd.addArgs(args, SLANG_COUNT_OF(args));
+
+ ExecuteResult exeRes;
+ if (SLANG_SUCCEEDED(ProcessUtil::execute(cmd, exeRes)))
+ {
+ // We need to chopoff CR/LF if there is one
+ List<UnownedStringSlice> lines;
+ StringUtil::calcLines(exeRes.standardOutput.getUnownedSlice(), lines);
+
+ if (lines.getCount())
+ {
+ outPath.vcvarsPath = lines[0];
+ outPath.vcvarsPath.append("\\VC\\Auxiliary\\Build\\");
+ return SLANG_OK;
+ }
+ }
+ }
+
+ const Int keyIndex = _getRegistryKeyIndex(version);
+ if (keyIndex >= 0)
+ {
+ SLANG_ASSERT(keyIndex < SLANG_COUNT_OF(s_regInfos));
+
+ // Try reading the key
+ const auto& keyInfo = s_regInfos[keyIndex];
+
+ StringBuilder keyName;
+ WinVisualStudioUtil::append(versionInfo.version, keyName);
+
+ String value;
+ if (SLANG_SUCCEEDED(_readRegistryKey(keyInfo.regName, keyName.getBuffer(), value)))
+ {
+ outPath.vcvarsPath = value;
+ return SLANG_OK;
+ }
+ }
+
+ return SLANG_FAIL;
+}
+
+/* static */SlangResult WinVisualStudioUtil::find(List<VersionPath>& outVersionPaths)
+{
+ outVersionPaths.clear();
+
+ const int versionCount = SLANG_COUNT_OF(s_versionInfos);
+
+ for (int i = versionCount - 1; i >= 0; --i)
+ {
+ VersionPath versionPath;
+ if (SLANG_SUCCEEDED(_find(i, versionPath)))
+ {
+ outVersionPaths.add(versionPath);
+ }
+ }
+
+ return SLANG_OK;
+}
+
+/* static */SlangResult WinVisualStudioUtil::find(Version version, VersionPath& outPath)
+{
+ const int versionCount = SLANG_COUNT_OF(s_versionInfos);
+
+ for (int i = 0; i < versionCount; ++i)
+ {
+ const auto& versionInfo = s_versionInfos[i];
+ if (versionInfo.version == version)
+ {
+ return _find(i, outPath);
+ }
+ }
+ return SLANG_FAIL;
+}
+
+/* static */SlangResult WinVisualStudioUtil::find(DownstreamCompilerSet* set)
+{
+ const int versionCount = SLANG_COUNT_OF(s_versionInfos);
+
+ for (int i = versionCount - 1; i >= 0; --i)
+ {
+ const auto& versionInfo = s_versionInfos[i];
+ auto desc = getDesc(versionInfo.version);
+
+ VersionPath versionPath;
+ if (!set->getCompiler(desc) && SLANG_SUCCEEDED(_find(i, versionPath)))
+ {
+ RefPtr<CommandLineDownstreamCompiler> compiler = new VisualStudioDownstreamCompiler(desc);
+ calcExecuteCompilerArgs(versionPath, compiler->m_cmdLine);
+ set->addCompiler(compiler);
+ }
+ }
+
+ return SLANG_OK;
+}
+
+/* static */void WinVisualStudioUtil::calcExecuteCompilerArgs(const VersionPath& versionPath, CommandLine& outCmdLine)
+{
+ // To invoke cl we need to run the suitable vcvars. In order to run this we have to have MS CommandLine.
+ // So here we build up a cl command line that is run by first running vcvars, and then executing cl with the parameters as passed to commandLine
+
+ CommandLine cmdLine;
+
+ cmdLine.setExecutableFilename("cmd.exe");
+ {
+ String options[] = { "/q", "/c", "@prompt", "$" };
+ cmdLine.addArgs(options, SLANG_COUNT_OF(options));
+ }
+
+ cmdLine.addArg("&&");
+
+ {
+ StringBuilder path;
+ path << versionPath.vcvarsPath;
+ path << "\\vcvarsall.bat";
+ cmdLine.addArg(path);
+ }
+
+#if SLANG_PTR_IS_32
+ cmdLine.addArg("x86");
+#else
+ cmdLine.addArg("x86_amd64");
+#endif
+
+ cmdLine.addArg("&&");
+ cmdLine.addArg("cl");
+
+ outCmdLine = cmdLine;
+}
+
+/* static */SlangResult WinVisualStudioUtil::executeCompiler(const VersionPath& versionPath, const CommandLine& commandLine, ExecuteResult& outResult)
+{
+ CommandLine cmdLine;
+ calcExecuteCompilerArgs(versionPath, cmdLine);
+ // Append the command line options
+ cmdLine.addArgs(commandLine.m_args.getBuffer(), commandLine.m_args.getCount());
+ return ProcessUtil::execute(cmdLine, outResult);
+}
+
+/* static */void WinVisualStudioUtil::append(Version version, StringBuilder& outBuilder)
+{
+ switch (version)
+ {
+ case Version::Unknown:
+ {
+ outBuilder << "unknown";
+ }
+ case Version::Future:
+ {
+ outBuilder << "future";
+ break;
+ }
+ default:
+ {
+ outBuilder << (int(version) / 10) << "." << (int(version) % 10);
+ break;
+ }
+ }
+}
+
+} // namespace Slang
diff --git a/source/compiler-core/windows/slang-win-visual-studio-util.h b/source/compiler-core/windows/slang-win-visual-studio-util.h
new file mode 100644
index 000000000..05a3ea061
--- /dev/null
+++ b/source/compiler-core/windows/slang-win-visual-studio-util.h
@@ -0,0 +1,68 @@
+#ifndef SLANG_WIN_VISUAL_STUDIO_UTIL_H
+#define SLANG_WIN_VISUAL_STUDIO_UTIL_H
+
+#include "../../core/slang-list.h"
+#include "../../core/slang-string.h"
+
+#include "../../core/slang-process-util.h"
+
+#include "../slang-downstream-compiler.h"
+
+namespace Slang {
+
+struct WinVisualStudioUtil
+{
+ enum class Version: uint32_t
+ {
+ Unknown = 0, ///< This is an unknown (and not later) version
+ Future = 0xff * 10, ///< This is a version 'from the future' - that isn't specifically known. Will be treated as latest
+ };
+
+ struct VersionPath
+ {
+ Version version; ///< The visual studio version
+ String vcvarsPath; ///< The path to vcvars bat files, that need to be executed before executing the compiler
+ };
+
+ /// Find all the installations
+ static SlangResult find(List<VersionPath>& outVersionPaths);
+
+ /// Given a version find it's path
+ static SlangResult find(Version version, VersionPath& outPath);
+
+ /// Find and add to the set (if not already there)
+ static SlangResult find(DownstreamCompilerSet* set);
+
+ /// Create the cmdLine to start compiler for specified path
+ static void calcExecuteCompilerArgs(const VersionPath& versionPath, CommandLine& outCmdLine);
+
+ /// Run visual studio on specified path with the parameters specified on the command line. Output placed in outResult.
+ static SlangResult executeCompiler(const VersionPath& versionPath, const CommandLine& commandLine, ExecuteResult& outResult);
+
+ /// Get all the known version numbers
+ static void getVersions(List<Version>& outVersions);
+
+ /// Gets the msc compiler used to compile this version. Returning Version(0) means unknown
+ static Version getCompiledVersion();
+
+ /// Create a version from a high and low indices
+ static Version makeVersion(int high, int low = 0) { SLANG_ASSERT(low >= 0 && low <= 9); return Version(high * 10 + low); }
+
+ /// Convert a version number into a string
+ static void append(Version version, StringBuilder& outBuilder);
+
+ /// Get version as desc
+ static DownstreamCompiler::Desc getDesc(Version version)
+ {
+ DownstreamCompiler::Desc desc;
+ desc.type = SLANG_PASS_THROUGH_VISUAL_STUDIO;
+ desc.majorVersion = Int(version) / 10;
+ desc.minorVersion = Int(version) % 10;
+ return desc;
+ }
+
+};
+
+} // namespace Slang
+
+#endif