https://github.com/shader-slang/slang
Raw File
Tip revision: b68516e2c2e39af79dda2ec7871fe4d821ef67c4 authored by Yong He on 03 April 2023, 22:54:16 UTC
Emit simpler vector element access code. (#2770)
Tip revision: b68516e
slang-emit-source-writer.cpp
// slang-emit-source-writer.cpp
#include "slang-emit-source-writer.h"

#include "../core/slang-char-encode.h"

// Disable warnings about sprintf
#ifdef _WIN32
#   pragma warning(disable:4996)
#endif

// Note: using C++ stdio just to get a locale-independent
// way to format floating-point values.
//
// TODO: Go ahead and implement the Dragon4 algorithm so
// that we can print floating-point values to arbitrary
// precision as needed.
#include <sstream>

namespace Slang {

SourceWriter::SourceWriter(SourceManager* sourceManager, LineDirectiveMode lineDirectiveMode, SourceMap* sourceMap)
{
    m_sourceMap = sourceMap;
    m_lineDirectiveMode = lineDirectiveMode;
    m_sourceManager = sourceManager;
}

String SourceWriter::getContentAndClear()
{
    String content(getContent());
    clearContent();
    return content;
}

void SourceWriter::emitRawTextSpan(char const* textBegin, char const* textEnd)
{
    // TODO(tfoley): Need to make "corelib" not use `int` for pointer-sized things...
    auto len = textEnd - textBegin;
    m_builder.Append(textBegin, len);
}

void SourceWriter::emitRawText(char const* text)
{
    emitRawTextSpan(text, text + strlen(text));
}

void SourceWriter::_emitTextSpan(char const* textBegin, char const* textEnd)
{
    // Don't change anything given an empty string
    if (textBegin == textEnd)
        return;

    // If the source location has changed in a way that required update,
    // do it now!
    _flushSourceLocationChange();

    // Note: we don't want to emit indentation on a line that is empty.
    // The logic in `Emit(textBegin, textEnd)` below will have broken
    // the text into lines, so we can simply check if a line consists
    // of just a newline.
    if (m_isAtStartOfLine && *textBegin != '\n')
    {
        // We are about to emit text (other than a newline)
        // at the start of a line, so we will emit the proper
        // amount of indentation to keep things looking nice.
        m_isAtStartOfLine = false;
        for (Int ii = 0; ii < m_indentLevel; ++ii)
        {
            char const* indentString = "    ";
            size_t indentStringSize = strlen(indentString);
            emitRawTextSpan(indentString, indentString + indentStringSize);

            // We will also update our tracking location, just in
            // case other logic needs it.
            //
            // TODO: We may need to have a switch that controls whether
            // we are in "pretty-printing" mode or "follow the locations
            // in the original code" mode.
            m_loc.column += indentStringSize;
        }
    }

    // Emit the raw text
    emitRawTextSpan(textBegin, textEnd);

    // Update our logical position
    auto len = int(textEnd - textBegin);
    m_loc.column += len;
}

void SourceWriter::indent()
{
    m_indentLevel++;
}

void SourceWriter::dedent()
{
    m_indentLevel--;
}

void SourceWriter::emitChar(char c)
{
    emit(&c, &c + 1);
}

void SourceWriter::emit(char const* textBegin, char const* textEnd)
{
    char const* spanBegin = textBegin;
    char const* spanEnd = spanBegin;
    for (;;)
    {
        if (spanEnd == textEnd)
        {
            // We have a whole range of text waiting to be flushed
            _emitTextSpan(spanBegin, spanEnd);
            return;
        }

        auto c = *spanEnd++;

        if (c == '\n')
        {
            // At the end of a line, we need to update our tracking
            // information on code positions
            _emitTextSpan(spanBegin, spanEnd);
            m_loc.line++;
            m_loc.column = 1;
            m_isAtStartOfLine = true;

            // Start a new span for emit purposes
            spanBegin = spanEnd;
        }
    }
}

void SourceWriter::emit(char const* text)
{
    emit(text, text + strlen(text));
}

void SourceWriter::emit(const String& text)
{
    emit(text.begin(), text.end());
}

void SourceWriter::emit(const UnownedStringSlice& text)
{
    emit(text.begin(), text.end());
}

void SourceWriter::emit(Name* name)
{
    emit(getText(name));
}

void SourceWriter::emit(const NameLoc& nameAndLoc)
{
    advanceToSourceLocationIfValid(nameAndLoc.loc);
    emit(getText(nameAndLoc.name));
}

void SourceWriter::emit(const StringSliceLoc& nameAndLoc)
{
    advanceToSourceLocationIfValid(nameAndLoc.loc);
    emit(nameAndLoc.name);
}

void SourceWriter::emitName(Name* name, const SourceLoc& locIn)
{
    advanceToSourceLocationIfValid(locIn);
    emit(name);
}

void SourceWriter::emitName(const NameLoc& nameAndLoc)
{
    emitName(nameAndLoc.name, nameAndLoc.loc);
}

void SourceWriter::emitName(const StringSliceLoc& nameAndLoc)
{
    emit(nameAndLoc);
}

void SourceWriter::emitName(Name* name)
{
    emitName(name, SourceLoc());
}

void SourceWriter::emit(IntegerLiteralValue value)
{
    char buffer[32];
    sprintf(buffer, "%lld", (long long int)value);
    emit(buffer);
}

void SourceWriter::emit(UInt value)
{
    char buffer[32];
    sprintf(buffer, "%llu", (unsigned long long)(value));
    emit(buffer);
}

void SourceWriter::emitUInt64(uint64_t value)
{
    char buffer[32];
    sprintf(buffer, "%llu", (unsigned long long)(value));
    emit(buffer);
}

void SourceWriter::emitInt64(int64_t value)
{
    char buffer[32];
    sprintf(buffer, "%lld", (long long int)value);
    emit(buffer);
}

void SourceWriter::emit(int value)
{
    char buffer[16];
    sprintf(buffer, "%d", value);
    emit(buffer);
}

void SourceWriter::emit(double value)
{
    // There are a few different requirements here that we need to deal with:
    //
    // 1) We need to print something that is valid syntax in the target language
    //    (this means that hex floats are off the table for now)
    //
    // 2) We need our printing to be independent of the current global locale in C,
    //    so that we don't depend on the application leaving it as the default,
    //    and we also don't revert any changes they make.
    //    (this means that `sprintf` and friends are off the table)
    //
    // 3) We need to be sure that floating-point literals specified by the user will
    //    "round-trip" and turn into the same value when parsed back in. This means
    //    that we need to print a reasonable number of digits of precision.
    //
    // For right now, the easiest option that can balance these is to use
    // the C++ standard library `iostream`s, because they support an explicit locale,
    // and can (hopefully) print floating-point numbers accurately.
    //
    // Eventually, the right move here would be to implement proper floating-point
    // number formatting ourselves, but that would require extensive testing to
    // make sure we get it right.

    std::ostringstream stream;
    stream.imbue(std::locale::classic());
    stream.setf(std::ios::fixed, std::ios::floatfield);
    stream.precision(20);
    stream << value;
    auto str = stream.str();
    auto slice = UnownedStringSlice(str.c_str());
    // Remove redundant trailing 0s.
    if (slice.end() > slice.begin())
    {
        auto lastChar = slice.end() - 1;
        while (lastChar > slice.begin() && *lastChar == '0')
            lastChar--;
        if (*lastChar == '.')
            lastChar++;
        if (lastChar > slice.end() - 1)
            lastChar = slice.end() - 1;
        slice = slice.subString(0, lastChar - slice.begin() + 1);
    }
    emit(slice);
}

void SourceWriter::advanceToSourceLocationIfValid(const SourceLoc& sourceLocation)
{
    if (sourceLocation.isValid())
    {
        advanceToSourceLocation(sourceLocation);
    }
}

void SourceWriter::advanceToSourceLocation(const SourceLoc& sourceLocation)
{
    // If we don't have any line directives *and* we don't want to output 
    // source map, we can just ignore
    if (getLineDirectiveMode() == LineDirectiveMode::None &&
        m_sourceMap == nullptr)
    {
        // Ignore if we aren't outputting directives 
        return;
    }
    
    if (!sourceLocation.isValid())
    {
        // If it's not valid, we will just keep the current location.

        // The question now is if we want to trigger outputting the source location again. We do so if
        // * The nextSourceLoc is valid
        // * The line number on the output is different from that location
        m_needToUpdateSourceLocation = m_needToUpdateSourceLocation || (m_nextSourceLoc.isValid() && m_nextHumaneSourceLocation.line != m_loc.line);
        return;
    }

    // Even if the source location is the same, we still want to trigger output, as long
    // as we have a valid line location.
    if (sourceLocation == m_nextSourceLoc)
    {
        // This is important because we can end up with many instructions with the same source location - for example
        // when we have [__unsafeForceInlineEarly] all the inlined instructions get the same location.
        // When we output lines of source, the target sources line numbers change, therefore we need to
        // output  the same #line directive multiple times.
     
        m_needToUpdateSourceLocation = m_needToUpdateSourceLocation || (m_nextHumaneSourceLocation.line > 0);
        return;
    }

    // Workout the humane source location.
    const HumaneSourceLoc humaneSourceLoc = getSourceManager()->getHumaneLoc(sourceLocation);

    // If the location is valid, mark need to update, and the new location
    if (humaneSourceLoc.line > 0)
    {
        m_needToUpdateSourceLocation = true;        
        m_nextHumaneSourceLocation = humaneSourceLoc;
    }

    // Either way set this as the current source location.
    m_nextSourceLoc = sourceLocation;
}

void SourceWriter::_flushSourceLocationChange()
{
    if (!m_needToUpdateSourceLocation)
        return;

    // Note: the order matters here, because trying to update
    // the source location may involve outputting text that
    // advances the location, and outputting text is what
    // triggers this flush operation.
    m_needToUpdateSourceLocation = false;
    
    _emitLineDirectiveIfNeeded(m_nextHumaneSourceLocation);

    // If we have a source map update state
    if (m_sourceMap)
    {
        _updateSourceMap(m_nextHumaneSourceLocation);
    }
}

void SourceWriter::_emitLineDirectiveAndUpdateSourceLocation(const HumaneSourceLoc& sourceLocation)
{
    _emitLineDirective(sourceLocation);

    HumaneSourceLoc newLoc = sourceLocation;
    newLoc.column = 1;

    m_loc = newLoc;
}

void SourceWriter::_updateSourceMap(const HumaneSourceLoc& sourceLocation)
{
    // Ignore invalid source locations
    if (sourceLocation.line <= 0)
        return;

    // We need to work out the current column in the generated (ie being written) output
    Index generatedLineIndex, generatedColumnIndex;
    _calcLocation(generatedLineIndex, generatedColumnIndex);

    // Advance to the current output line
    m_sourceMap->advanceToLine(generatedLineIndex);

    // Add the entry into the map, mapping back to the original source
    SourceMap::Entry entry;
    entry.init();

    entry.sourceFileIndex = m_sourceMap->getSourceFileIndex(sourceLocation.pathInfo.getName().getUnownedSlice());
    entry.sourceLine = sourceLocation.line - 1;
    entry.sourceColumn = sourceLocation.column - 1;
    entry.generatedColumn = generatedColumnIndex;
    
    m_sourceMap->addEntry(entry);
}

void SourceWriter::_emitLineDirectiveIfNeeded(const HumaneSourceLoc& sourceLocation)
{
    if (m_supressLineDirective)
        return;

    // Don't do any of this work if the user has requested that we
    // not emit line directives.
    auto mode = getLineDirectiveMode();
    switch (mode)
    {
        case LineDirectiveMode::None:
            return;

        case LineDirectiveMode::Default:
        default:
            break;
    }

    // Ignore invalid source locations
    if (sourceLocation.line <= 0)
        return;

    // If we are currently emitting code at a source location with
    // a differnet file or line, *or* if the source location is
    // somehow later on the line than what we want to emit,
    // then we need to emit a new `#line` directive.
    if (sourceLocation.pathInfo.foundPath != m_loc.pathInfo.foundPath
        || sourceLocation.line != m_loc.line
        || sourceLocation.column < m_loc.column)
    {
        // Special case: if we are in the same file, and within a small number
        // of lines of the target location, then go ahead and output newlines
        // to get us caught up.
        enum { kSmallLineCount = 3 };
        auto lineDiff = sourceLocation.line - m_loc.line;
        if (sourceLocation.pathInfo.foundPath == m_loc.pathInfo.foundPath
            && sourceLocation.line > m_loc.line
            && lineDiff <= kSmallLineCount)
        {
            for (int ii = 0; ii < lineDiff; ++ii)
            {
                emit("\n");
            }
            SLANG_RELEASE_ASSERT(sourceLocation.line == m_loc.line);
        }
        else
        {
            // Go ahead and output a `#line` directive to get us caught up
            _emitLineDirectiveAndUpdateSourceLocation(sourceLocation);
        }
    }
}

void SourceWriter::_emitLineDirective(const HumaneSourceLoc& sourceLocation)
{
    emitRawText("\n#line ");

    char buffer[16];
    sprintf(buffer, "%llu", (unsigned long long)sourceLocation.line);
    emitRawText(buffer);

    // Only emit the path part of a `#line` directive if needed
    if (sourceLocation.pathInfo.foundPath != m_loc.pathInfo.foundPath)
    {
        emitRawText(" ");

        auto mode = getLineDirectiveMode();
        switch (mode)
        {
            default:
            case LineDirectiveMode::None:
                SLANG_UNEXPECTED("should not be trying to emit '#line' directive");
                return;
            case LineDirectiveMode::GLSL:
            {
                auto path = sourceLocation.pathInfo.foundPath;

                // GLSL doesn't support the traditional form of a `#line` directive without
                // an extension. Rather than depend on that extension we will output
                // a directive in the traditional GLSL fashion.
                //
                // TODO: Add some kind of configuration where we require the appropriate
                // extension and then emit a traditional line directive.

                int id = 0;
                if (!m_mapGLSLSourcePathToID.TryGetValue(path, id))
                {
                    id = m_glslSourceIDCount++;
                    m_mapGLSLSourcePathToID.Add(path, id);
                }

                sprintf(buffer, "%d", id);
                emitRawText(buffer);
                break;
            }
            case LineDirectiveMode::Default:
            case LineDirectiveMode::Standard:
            {
                // The simple case is to emit the path for the current source
                // location. We need to be a little bit careful with this,
                // because the path might include backslash characters if we
                // are on Windows, and we want to canonicalize those over
                // to forward slashes.
                //
                // TODO: Canonicalization like this should be done centrally
                // in a module that tracks source files.

                emitRawText("\"");
                const auto& path = sourceLocation.pathInfo.foundPath;
                for (auto c : path)
                {
                    char charBuffer[] = { c, 0 };
                    switch (c)
                    {
                        default:
                            emitRawText(charBuffer);
                            break;

                            // The incoming file path might use `/` and/or `\\` as
                            // a directory separator. We want to canonicalize this.
                            //
                            // TODO: should probably canonicalize paths to not use backslash somewhere else
                            // in the compilation pipeline...
                        case '\\':
                            emitRawText("/");
                            break;
                    }
                }
                emitRawText("\"");
                break;
            }
        }
    }

    emitRawText("\n");
}

void SourceWriter::_calcLocation(Index& outLineIndex, Index& outColumnIndex)
{
    // If there are move chars we need to update 
    if (m_currentOutputOffset < m_builder.getLength())
    {
        const char* cur = m_builder.getBuffer() + m_currentOutputOffset;
        const char* end = m_builder.end();

        const char* start = cur;

        while (cur < end)
        {
            // Reset start
            start = cur;

            // Look for the end of the line
            while (*cur != '\n' && *cur != '\r' && cur < end)
            {
                cur++;
            }

            // If we are not at the total end then we must have hit a \n or \r
            if (cur < end)
            {
                const auto c = *cur++;

                // Next line
                ++m_currentLineIndex;
                
                // Check the next char to see if it's part of a CR/LF combination
                if (cur < end)
                {
                    const auto d = *cur;
                    // If it is combination skip the next byte
                    cur += ((c ^ d) == ('\r' ^ '\n'));
                }

                // Calculate the offset to the start of this line
                m_currentColumnIndex = 0;
                start = cur;
            }
        }

        // Set the current offset to the end
        m_currentOutputOffset = m_builder.getLength();

        // Get the bytes remaining on this line (which may not be complete)
        const UnownedStringSlice lineRemaining(start, m_builder.end());

        // Offset the column index in codepoints 
        m_currentColumnIndex += UTF8Util::calcCodePointCount(lineRemaining);
    }

    // Output the position
    outColumnIndex = m_currentColumnIndex;
    outLineIndex = m_currentLineIndex;
}

} // namespace Slang
back to top