From c0bcb9c358e22a5c61c3efe77a14a368632bac70 Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Fri, 7 Jul 2017 12:31:38 -0700 Subject: Overhaul emit logic to use visitor abstraction - This is in preparation for splitting out HLSL vs. GLSL emit as different cases. - Along the way, I added more cases to the visitor implementation, to handle visitors with arguments - This is getting a bit busy, though, and we might be reaching the breaking point where a more general bit of meta-magic is needed to clean things up (either going further down the ugly template route, or plugging in a more real code generation strategy) --- source/slang/emit.cpp | 4824 ++++++++++++++++++++++++------------------------ source/slang/syntax.h | 5 + source/slang/visitor.h | 82 + 3 files changed, 2541 insertions(+), 2370 deletions(-) (limited to 'source') diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index 48644cc6f..2496480ce 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -4,6 +4,7 @@ #include "lower.h" #include "syntax.h" #include "type-layout.h" +#include "visitor.h" #include @@ -64,2955 +65,3036 @@ struct EmitContext // -static void EmitDecl(EmitContext* context, RefPtr decl); -static void EmitDecl(EmitContext* context, RefPtr declBase); -static void EmitDeclUsingLayout(EmitContext* context, RefPtr decl, RefPtr layout); - -static void EmitType(EmitContext* context, RefPtr type, Token const& nameToken); -static void EmitType(EmitContext* context, RefPtr type, String const& name); -static void EmitType(EmitContext* context, RefPtr type); - -static void EmitType(EmitContext* context, TypeExp const& typeExp, Token const& nameToken); -static void EmitType(EmitContext* context, TypeExp const& typeExp, String const& name); -static void EmitType(EmitContext* context, TypeExp const& typeExp); - -static void EmitExpr(EmitContext* context, RefPtr expr); -static void EmitStmt(EmitContext* context, RefPtr stmt); -static void EmitDeclRef(EmitContext* context, DeclRef declRef); - -static void advanceToSourceLocation( - EmitContext* context, - CodePosition const& sourceLocation); - -static void flushSourceLocationChange( - EmitContext* context); - -// Low-level emit logic - -static void emitRawTextSpan(EmitContext* context, char const* textBegin, char const* textEnd) +static String getStringOrIdentifierTokenValue( + Token const& token) { - // TODO(tfoley): Need to make "corelib" not use `int` for pointer-sized things... - auto len = int(textEnd - textBegin); + switch(token.Type) + { + default: + assert(!"unexpected"); + return ""; - context->shared->sb.Append(textBegin, len); -} + case TokenType::Identifier: + return token.Content; -static void emitRawText(EmitContext* context, char const* text) -{ - emitRawTextSpan(context, text, text + strlen(text)); + case TokenType::StringLiteral: + return getStringLiteralTokenValue(token); + break; + } } -static void emitTextSpan(EmitContext* context, char const* textBegin, char const* textEnd) -{ - // If the source location has changed in a way that required update, - // do it now! - flushSourceLocationChange(context); - - // Emit the raw text - emitRawTextSpan(context, textBegin, textEnd); - // Update our logical position - // TODO(tfoley): Need to make "corelib" not use `int` for pointer-sized things... - auto len = int(textEnd - textBegin); - context->shared->loc.Col += len; -} +// -static void Emit(EmitContext* context, char const* textBegin, char const* textEnd) +// represents a declarator for use in emitting types +struct EDeclarator { - char const* spanBegin = textBegin; - - char const* spanEnd = spanBegin; - for(;;) + enum class Flavor { - if(spanEnd == textEnd) - { - // We have a whole range of text waiting to be flushed - emitTextSpan(context, spanBegin, spanEnd); - return; - } - - auto c = *spanEnd++; + Name, + Array, + UnsizedArray, + }; + Flavor flavor; + EDeclarator* next = nullptr; - if( c == '\n' ) - { - // At the end of a line, we need to update our tracking - // information on code positions - emitTextSpan(context, spanBegin, spanEnd); - context->shared->loc.Line++; - context->shared->loc.Col = 1; + // Used for `Flavor::Name` + String name; + CodePosition loc; - // Start a new span for emit purposes - spanBegin = spanEnd; - } - } -} + // Used for `Flavor::Array` + IntVal* elementCount; +}; -static void Emit(EmitContext* context, char const* text) +struct TypeEmitArg { - Emit(context, text, text + strlen(text)); -} + EDeclarator* declarator; +}; -static void emit(EmitContext* context, String const& text) +struct ExprEmitArg { - Emit(context, text.begin(), text.end()); -} + int outerPrec; +}; -static bool isReservedWord(EmitContext* context, String const& name) +struct DeclEmitArg { - return context->shared->reservedWords.TryGetValue(name) != nullptr; -} + VarLayout* layout; +}; -static void emitName( - EmitContext* context, - String const& inName, - CodePosition const& loc) +struct EmitVisitor + : TypeVisitorWithArg + , ExprVisitorWithArg + , DeclVisitorWithArg { - String name = inName; + EmitContext* context; + EmitVisitor(EmitContext* context) + : context(context) + {} - // By default, we would like to emit a name in the generated - // code exactly as it appeared in the soriginal program. - // When that isn't possible, we'd like to emit a name as - // close to the original as possible (to ensure that existing - // debugging tools still work reasonably well). - // - // One reason why a name might not be allowed as-is is that - // it could collide with a reserved word in the target language. - // Another reason is that it might not follow a naming convention - // imposed by the target (e.g., in GLSL names starting with - // `gl_` or containing `__` are reserved). - // - // Given a name that should not be allowed, we want to - // change it to a name that *is* allowed. e.g., by adding - // `_` to the end of a reserved word. - // - // The next problem this creates is that the modified name - // could not collide with an existing use of the same - // (valid) name. - // - // For now we are going to solve this problem in a simple - // and ad hoc fashion, but longer term we'll want to do - // something sytematic. + // Low-level emit logic - if (isReservedWord(context, name)) + void emitRawTextSpan(char const* textBegin, char const* textEnd) { - name = name + "_"; - } - - advanceToSourceLocation(context, loc); - emit(context, name); -} + // TODO(tfoley): Need to make "corelib" not use `int` for pointer-sized things... + auto len = int(textEnd - textBegin); -static void emitName(EmitContext* context, Token const& nameToken) -{ - emitName(context, nameToken.Content, nameToken.Position); -} + context->shared->sb.Append(textBegin, len); + } -static void emitName(EmitContext* context, String const& name) -{ - emitName(context, name, CodePosition()); -} + void emitRawText(char const* text) + { + emitRawTextSpan(text, text + strlen(text)); + } -static void Emit(EmitContext* context, IntegerLiteralValue value) -{ - char buffer[32]; - sprintf(buffer, "%lld", value); - Emit(context, buffer); -} + void emitTextSpan(char const* textBegin, char const* textEnd) + { + // If the source location has changed in a way that required update, + // do it now! + flushSourceLocationChange(); + // Emit the raw text + emitRawTextSpan(textBegin, textEnd); -static void Emit(EmitContext* context, UInt value) -{ - char buffer[32]; - sprintf(buffer, "%llu", (unsigned long long)(value)); - Emit(context, buffer); -} + // Update our logical position + // TODO(tfoley): Need to make "corelib" not use `int` for pointer-sized things... + auto len = int(textEnd - textBegin); + context->shared->loc.Col += len; + } -static void Emit(EmitContext* context, int value) -{ - char buffer[16]; - sprintf(buffer, "%d", value); - Emit(context, buffer); -} + void Emit(char const* textBegin, char const* textEnd) + { + char const* spanBegin = textBegin; -static void Emit(EmitContext* context, double value) -{ - // TODO(tfoley): need to print things in a way that can round-trip - char buffer[128]; - sprintf(buffer, "%.20ff", value); - Emit(context, buffer); -} + char const* spanEnd = spanBegin; + for(;;) + { + if(spanEnd == textEnd) + { + // We have a whole range of text waiting to be flushed + emitTextSpan(spanBegin, spanEnd); + return; + } -static void emitTokenWithLocation(EmitContext* context, Token const& token); + auto c = *spanEnd++; -// Expressions + if( c == '\n' ) + { + // At the end of a line, we need to update our tracking + // information on code positions + emitTextSpan(spanBegin, spanEnd); + context->shared->loc.Line++; + context->shared->loc.Col = 1; + + // Start a new span for emit purposes + spanBegin = spanEnd; + } + } + } -// Determine if an expression should not be emitted when it is the base of -// a member reference expression. -static bool IsBaseExpressionImplicit(EmitContext* /*context*/, RefPtr expr) -{ - // HACK(tfoley): For now, anything with a constant-buffer type should be - // left implicit. + void Emit(char const* text) + { + Emit(text, text + strlen(text)); + } - // Look through any dereferencing that took place - RefPtr e = expr; - while (auto derefExpr = e.As()) + void emit(String const& text) { - e = derefExpr->base; + Emit(text.begin(), text.end()); } - // Is the expression referencing a constant buffer? - if (auto cbufferType = e->Type->As()) + + bool isReservedWord(String const& name) { - return true; + return context->shared->reservedWords.TryGetValue(name) != nullptr; } - return false; -} + void emitName( + String const& inName, + CodePosition const& loc) + { + String name = inName; -enum -{ - kPrecedence_None, - kPrecedence_Comma, - - kPrecedence_Assign, - kPrecedence_AddAssign = kPrecedence_Assign, - kPrecedence_SubAssign = kPrecedence_Assign, - kPrecedence_MulAssign = kPrecedence_Assign, - kPrecedence_DivAssign = kPrecedence_Assign, - kPrecedence_ModAssign = kPrecedence_Assign, - kPrecedence_LshAssign = kPrecedence_Assign, - kPrecedence_RshAssign = kPrecedence_Assign, - kPrecedence_OrAssign = kPrecedence_Assign, - kPrecedence_AndAssign = kPrecedence_Assign, - kPrecedence_XorAssign = kPrecedence_Assign, - - kPrecedence_General = kPrecedence_Assign, - - kPrecedence_Conditional, // "ternary" - kPrecedence_Or, - kPrecedence_And, - kPrecedence_BitOr, - kPrecedence_BitXor, - kPrecedence_BitAnd, - - kPrecedence_Eql, - kPrecedence_Neq = kPrecedence_Eql, - - kPrecedence_Less, - kPrecedence_Greater = kPrecedence_Less, - kPrecedence_Leq = kPrecedence_Less, - kPrecedence_Geq = kPrecedence_Less, - - kPrecedence_Lsh, - kPrecedence_Rsh = kPrecedence_Lsh, - - kPrecedence_Add, - kPrecedence_Sub = kPrecedence_Add, - - kPrecedence_Mul, - kPrecedence_Div = kPrecedence_Mul, - kPrecedence_Mod = kPrecedence_Mul, - - kPrecedence_Prefix, - kPrecedence_Postfix, - kPrecedence_Atomic = kPrecedence_Postfix -}; + // By default, we would like to emit a name in the generated + // code exactly as it appeared in the soriginal program. + // When that isn't possible, we'd like to emit a name as + // close to the original as possible (to ensure that existing + // debugging tools still work reasonably well). + // + // One reason why a name might not be allowed as-is is that + // it could collide with a reserved word in the target language. + // Another reason is that it might not follow a naming convention + // imposed by the target (e.g., in GLSL names starting with + // `gl_` or containing `__` are reserved). + // + // Given a name that should not be allowed, we want to + // change it to a name that *is* allowed. e.g., by adding + // `_` to the end of a reserved word. + // + // The next problem this creates is that the modified name + // could not collide with an existing use of the same + // (valid) name. + // + // For now we are going to solve this problem in a simple + // and ad hoc fashion, but longer term we'll want to do + // something sytematic. -static void EmitExprWithPrecedence(EmitContext* context, RefPtr expr, int outerPrec); + if (isReservedWord(name)) + { + name = name + "_"; + } -static void EmitPostfixExpr(EmitContext* context, RefPtr expr) -{ - EmitExprWithPrecedence(context, expr, kPrecedence_Postfix); -} + advanceToSourceLocation(loc); + emit(name); + } -static void EmitExpr(EmitContext* context, RefPtr expr) -{ - EmitExprWithPrecedence(context, expr, kPrecedence_General); -} + void emitName(Token const& nameToken) + { + emitName(nameToken.Content, nameToken.Position); + } -static bool MaybeEmitParens(EmitContext* context, int outerPrec, int prec) -{ - if (prec <= outerPrec) + void emitName(String const& name) { - Emit(context, "("); - return true; + emitName(name, CodePosition()); } - return false; -} -// When we are going to emit an expression in an l-value context, -// we may need to ignore certain constructs that the type-checker -// might have introduced, but which interfere with our ability -// to use it effectively in the target language -static RefPtr prepareLValueExpr( - EmitContext* /*context*/, - RefPtr expr) -{ - for(;;) + void Emit(IntegerLiteralValue value) { - if(auto typeCastExpr = expr.As()) - { - expr = typeCastExpr->Expression; - } - // TODO: any other cases? - else - { - return expr; - } + char buffer[32]; + sprintf(buffer, "%lld", value); + Emit(buffer); } -} -static void emitInfixExprImpl( - EmitContext* context, - int outerPrec, - int prec, - char const* op, - RefPtr binExpr, - bool isAssign) -{ - bool needsClose = MaybeEmitParens(context, outerPrec, prec); + void Emit(UInt value) + { + char buffer[32]; + sprintf(buffer, "%llu", (unsigned long long)(value)); + Emit(buffer); + } - auto left = binExpr->Arguments[0]; - if(isAssign) + void Emit(int value) { - left = prepareLValueExpr(context, left); + char buffer[16]; + sprintf(buffer, "%d", value); + Emit(buffer); } - EmitExprWithPrecedence(context, left, prec); - Emit(context, " "); - Emit(context, op); - Emit(context, " "); - EmitExprWithPrecedence(context, binExpr->Arguments[1], prec); - if (needsClose) + void Emit(double value) { - Emit(context, ")"); + // TODO(tfoley): need to print things in a way that can round-trip + char buffer[128]; + sprintf(buffer, "%.20ff", value); + Emit(buffer); } -} -static void EmitBinExpr(EmitContext* context, int outerPrec, int prec, char const* op, RefPtr binExpr) -{ - emitInfixExprImpl(context, outerPrec, prec, op, binExpr, false); -} -static void EmitBinAssignExpr(EmitContext* context, int outerPrec, int prec, char const* op, RefPtr binExpr) -{ - emitInfixExprImpl(context, outerPrec, prec, op, binExpr, true); -} + // Emit a `#line` directive to the output. + // Doesn't udpate state of source-location tracking. + void emitLineDirective( + CodePosition const& sourceLocation) + { + emitRawText("\n#line "); -static void emitUnaryExprImpl( - EmitContext* context, - int outerPrec, - int prec, - char const* preOp, - char const* postOp, - RefPtr expr, - bool isAssign) -{ - bool needsClose = MaybeEmitParens(context, outerPrec, prec); - Emit(context, preOp); + char buffer[16]; + sprintf(buffer, "%d", sourceLocation.Line); + emitRawText(buffer); - auto arg = expr->Arguments[0]; - if(isAssign) - { - arg = prepareLValueExpr(context, arg); - } + emitRawText(" "); - EmitExprWithPrecedence(context, arg, prec); - Emit(context, postOp); - if (needsClose) - { - Emit(context, ")"); - } -} + if(context->shared->target == CodeGenTarget::GLSL) + { + auto path = sourceLocation.FileName; -static void EmitUnaryExpr( - EmitContext* context, - int outerPrec, - int prec, - char const* preOp, - char const* postOp, - RefPtr expr) -{ - emitUnaryExprImpl(context, outerPrec, prec, preOp, postOp, expr, false); -} + // 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. -static void EmitUnaryAssignExpr( - EmitContext* context, - int outerPrec, - int prec, - char const* preOp, - char const* postOp, - RefPtr expr) -{ - emitUnaryExprImpl(context, outerPrec, prec, preOp, postOp, expr, true); -} + int id = 0; + if(!context->shared->mapGLSLSourcePathToID.TryGetValue(path, id)) + { + id = context->shared->glslSourceIDCount++; + context->shared->mapGLSLSourcePathToID.Add(path, id); + } -// Determine if a target intrinsic modifer is applicable to the target -// we are currently emitting code for. -static bool isTargetIntrinsicModifierApplicable( - EmitContext* context, - RefPtr modifier) -{ - auto const& targetToken = modifier->targetToken; + sprintf(buffer, "%d", id); + emitRawText(buffer); + } + else + { + // 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("\""); + for(auto c : sourceLocation.FileName) + { + char charBuffer[] = { c, 0 }; + switch(c) + { + default: + emitRawText(charBuffer); + break; - // If no target name was specified, then the modifier implicitly - // applies to all targets. - if(targetToken.Type == TokenType::Unknown) - return true; + // 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("\""); + } - // Otherwise, we need to check if the target name matches what - // we expect. - auto const& targetName = targetToken.Content; + emitRawText("\n"); + } - switch(context->shared->target) + // Emit a `#line` directive to the output, and also + // ensure that source location tracking information + // is correct based on the directive we just output. + void emitLineDirectiveAndUpdateSourceLocation( + CodePosition const& sourceLocation) { - default: - assert(!"unexpected"); - return false; - - case CodeGenTarget::GLSL: return targetName == "glsl"; - case CodeGenTarget::HLSL: return targetName == "hlsl"; + emitLineDirective(sourceLocation); + + context->shared->loc.FileName = sourceLocation.FileName; + context->shared->loc.Line = sourceLocation.Line; + context->shared->loc.Col = 1; } -} -// Find an intrinsic modifier appropriate to the current compilation target. -// -// If there are multiple such modifiers, this should return the best one. -static RefPtr findTargetIntrinsicModifier( - EmitContext* context, - RefPtr syntax) -{ - RefPtr bestModifier; - for(auto m : syntax->GetModifiersOfType()) + void emitLineDirectiveIfNeeded( + CodePosition const& sourceLocation) { - if(!isTargetIntrinsicModifierApplicable(context, m)) - continue; + // Ignore invalid source locations + if(sourceLocation.Line <= 0) + return; - // For now "better"-ness is defined as: a modifier - // with a specified target is better than one without - // (it is more specific) - if(!bestModifier || bestModifier->targetToken.Type == TokenType::Unknown) - { - bestModifier = m; + // 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.FileName != context->shared->loc.FileName + || sourceLocation.Line != context->shared->loc.Line + || sourceLocation.Col < context->shared->loc.Col) + { + // 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 - context->shared->loc.Line; + if(sourceLocation.FileName == context->shared->loc.FileName + && sourceLocation.Line > context->shared->loc.Line + && lineDiff <= kSmallLineCount) + { + for(int ii = 0; ii < lineDiff; ++ii ) + { + Emit("\n"); + } + assert(sourceLocation.Line == context->shared->loc.Line); + } + else + { + // Go ahead and output a `#line` directive to get us caught up + emitLineDirectiveAndUpdateSourceLocation(sourceLocation); + } } - } - return bestModifier; -} + // Now indent up to the appropriate column, so that error messages + // that reference columns will be correct. + // + // TODO: This logic does not take into account whether indentation + // came in as spaces or tabs, so there is necessarily going to be + // coupling between how the downstream compiler counts columns, + // and how we do. + if(sourceLocation.Col > context->shared->loc.Col) + { + int delta = sourceLocation.Col - context->shared->loc.Col; + for( int ii = 0; ii < delta; ++ii ) + { + emitRawText(" "); + } + context->shared->loc.Col = sourceLocation.Col; + } + } -static String getStringOrIdentifierTokenValue( - Token const& token) -{ - switch(token.Type) + void advanceToSourceLocation( + CodePosition const& sourceLocation) { - default: - assert(!"unexpected"); - return ""; - - case TokenType::Identifier: - return token.Content; + // Skip invalid locations + if(sourceLocation.Line <= 0) + return; - case TokenType::StringLiteral: - return getStringLiteralTokenValue(token); - break; + context->shared->needToUpdateSourceLocation = true; + context->shared->nextSourceLocation = sourceLocation; } -} -// Emit a call expression that doesn't involve any special cases, -// just an expression of the form `f(a0, a1, ...)` -static void emitSimpleCallExpr( - EmitContext* context, - RefPtr callExpr, - int outerPrec) -{ - bool needClose = MaybeEmitParens(context, outerPrec, kPrecedence_Postfix); + void flushSourceLocationChange() + { + if(!context->shared->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. + context->shared->needToUpdateSourceLocation = false; + emitLineDirectiveIfNeeded(context->shared->nextSourceLocation); + } - auto funcExpr = callExpr->FunctionExpr; - if (auto funcDeclRefExpr = funcExpr.As()) + void emitTokenWithLocation(Token const& token) { - auto declRef = funcDeclRefExpr->declRef; - if (auto ctorDeclRef = declRef.As()) + if( token.Position.FileName.Length() != 0 ) { - // We really want to emit a reference to the type begin constructed - EmitType(context, callExpr->Type); + advanceToSourceLocation(token.Position); } else { - // default case: just emit the decl ref - EmitExpr(context, funcExpr); + // If we don't have the original position info, we need to play + // it safe and emit whitespace to line things up nicely + + if(token.flags & TokenFlag::AtStartOfLine) + Emit("\n"); + // TODO(tfoley): macro expansion can currently lead to whitespace getting dropped, + // so we will just insert it aggressively, to play it safe. + else // if(token.flags & TokenFlag::AfterWhitespace) + Emit(" "); } - } - else - { - // default case: just emit the expression - EmitPostfixExpr(context, funcExpr); - } - Emit(context, "("); - UInt argCount = callExpr->Arguments.Count(); - for (UInt aa = 0; aa < argCount; ++aa) - { - if (aa != 0) Emit(context, ", "); - EmitExpr(context, callExpr->Arguments[aa]); + // Emit the raw textual content of the token + emit(token.Content); } - Emit(context, ")"); - if (needClose) + + // + // Types + // + + void Emit(RefPtr val) { - Emit(context, ")"); + if(auto constantIntVal = val.As()) + { + Emit(constantIntVal->value); + } + else if(auto varRefVal = val.As()) + { + EmitDeclRef(varRefVal->declRef); + } + else + { + assert(!"unimplemented"); + } } -} -static void emitCallExpr( - EmitContext* context, - RefPtr callExpr, - int outerPrec) -{ - auto funcExpr = callExpr->FunctionExpr; - if (auto funcDeclRefExpr = funcExpr.As()) + void EmitDeclarator(EDeclarator* declarator) { - auto funcDeclRef = funcDeclRefExpr->declRef; - auto funcDecl = funcDeclRef.getDecl(); - if(!funcDecl) + if (!declarator) return; + + Emit(" "); + + switch (declarator->flavor) { - // This can occur when we are dealing with unchecked input syntax, - // because we are in "rewriter" mode. In this case we should go - // ahead and emit things in the form that they were written. - if( auto infixExpr = callExpr.As() ) - { - EmitBinExpr( - context, - outerPrec, - kPrecedence_Comma, - funcDeclRefExpr->name.Buffer(), - callExpr); - } - else if( auto prefixExpr = callExpr.As() ) - { - EmitUnaryExpr( - context, - outerPrec, - kPrecedence_Prefix, - funcDeclRefExpr->name.Buffer(), - "", - callExpr); - } - else if(auto postfixExpr = callExpr.As()) - { - EmitUnaryExpr( - context, - outerPrec, - kPrecedence_Postfix, - "", - funcDeclRefExpr->name.Buffer(), - callExpr); - } - else + case EDeclarator::Flavor::Name: + emitName(declarator->name, declarator->loc); + break; + + case EDeclarator::Flavor::Array: + EmitDeclarator(declarator->next); + Emit("["); + if(auto elementCount = declarator->elementCount) { - emitSimpleCallExpr(context, callExpr, outerPrec); + Emit(elementCount); } - return; + Emit("]"); + break; + + case EDeclarator::Flavor::UnsizedArray: + EmitDeclarator(declarator->next); + Emit("[]"); + break; + + default: + assert(!"unreachable"); + break; } - else if (auto intrinsicOpModifier = funcDecl->FindModifier()) + } + + void emitGLSLTypePrefix( + RefPtr type) + { + if(auto basicElementType = type->As()) { - switch (intrinsicOpModifier->op) + switch (basicElementType->BaseType) { -#define CASE(NAME, OP) case IntrinsicOp::NAME: EmitBinExpr(context, outerPrec, kPrecedence_##NAME, #OP, callExpr); return - CASE(Mul, *); - CASE(Div, / ); - CASE(Mod, %); - CASE(Add, +); - CASE(Sub, -); - CASE(Lsh, << ); - CASE(Rsh, >> ); - CASE(Eql, == ); - CASE(Neq, != ); - CASE(Greater, >); - CASE(Less, <); - CASE(Geq, >= ); - CASE(Leq, <= ); - CASE(BitAnd, &); - CASE(BitXor, ^); - CASE(BitOr, | ); - CASE(And, &&); - CASE(Or, || ); -#undef CASE - -#define CASE(NAME, OP) case IntrinsicOp::NAME: EmitBinAssignExpr(context, outerPrec, kPrecedence_##NAME, #OP, callExpr); return - CASE(Assign, =); - CASE(AddAssign, +=); - CASE(SubAssign, -=); - CASE(MulAssign, *=); - CASE(DivAssign, /=); - CASE(ModAssign, %=); - CASE(LshAssign, <<=); - CASE(RshAssign, >>=); - CASE(OrAssign, |=); - CASE(AndAssign, &=); - CASE(XorAssign, ^=); -#undef CASE - - case IntrinsicOp::Sequence: EmitBinExpr(context, outerPrec, kPrecedence_Comma, ",", callExpr); return; - -#define CASE(NAME, OP) case IntrinsicOp::NAME: EmitUnaryExpr(context, outerPrec, kPrecedence_Prefix, #OP, "", callExpr); return - CASE(Neg, -); - CASE(Not, !); - CASE(BitNot, ~); -#undef CASE - -#define CASE(NAME, OP) case IntrinsicOp::NAME: EmitUnaryAssignExpr(context, outerPrec, kPrecedence_Prefix, #OP, "", callExpr); return - CASE(PreInc, ++); - CASE(PreDec, --); -#undef CASE - -#define CASE(NAME, OP) case IntrinsicOp::NAME: EmitUnaryAssignExpr(context, outerPrec, kPrecedence_Postfix, "", #OP, callExpr); return - CASE(PostInc, ++); - CASE(PostDec, --); -#undef CASE - - case IntrinsicOp::InnerProduct_Vector_Vector: - // HLSL allows `mul()` to be used as a synonym for `dot()`, - // so we need to translate to `dot` for GLSL - if (context->shared->target == CodeGenTarget::GLSL) - { - Emit(context, "dot("); - EmitExpr(context, callExpr->Arguments[0]); - Emit(context, ", "); - EmitExpr(context, callExpr->Arguments[1]); - Emit(context, ")"); - return; - } - break; - - case IntrinsicOp::InnerProduct_Matrix_Matrix: - case IntrinsicOp::InnerProduct_Matrix_Vector: - case IntrinsicOp::InnerProduct_Vector_Matrix: - // HLSL exposes these with the `mul()` function, while GLSL uses ordinary - // `operator*`. - // - // The other critical detail here is that the way we handle matrix - // conventions requires that the operands to the product be swapped. - if (context->shared->target == CodeGenTarget::GLSL) - { - Emit(context, "(("); - EmitExpr(context, callExpr->Arguments[1]); - Emit(context, ") * ("); - EmitExpr(context, callExpr->Arguments[0]); - Emit(context, "))"); - return; - } + case BaseType::Float: + // no prefix break; + case BaseType::Int: Emit("i"); break; + case BaseType::UInt: Emit("u"); break; + case BaseType::Bool: Emit("b"); break; default: + assert(!"unreachable"); break; } } - else if(auto targetIntrinsicModifier = findTargetIntrinsicModifier(context, funcDecl)) + else if(auto vectorType = type->As()) { - if(targetIntrinsicModifier->definitionToken.Type != TokenType::Unknown) - { - auto name = getStringOrIdentifierTokenValue(targetIntrinsicModifier->definitionToken); - - if(name.IndexOf('$') < 0) - { - // Simple case: it is just an ordinary name, so we call it like a builtin. - // - // TODO: this case could probably handle things like operators, for generality? - - emit(context, name); - Emit(context, "("); - UInt argCount = callExpr->Arguments.Count(); - for (UInt aa = 0; aa < argCount; ++aa) - { - if (aa != 0) Emit(context, ", "); - EmitExpr(context, callExpr->Arguments[aa]); - } - Emit(context, ")"); - return; - } - else - { - // General case: we are going to emit some more complex text. - - UInt argCount = callExpr->Arguments.Count(); - - Emit(context, "("); - - char const* cursor = name.begin(); - char const* end = name.end(); - while(cursor != end) - { - char c = *cursor++; - if( c != '$' ) - { - // Not an escape sequence - emitRawTextSpan(context, &c, &c+1); - continue; - } + emitGLSLTypePrefix(vectorType->elementType); + } + else if(auto matrixType = type->As()) + { + emitGLSLTypePrefix(matrixType->getElementType()); + } + else + { + assert(!"unreachable"); + } + } - assert(cursor != end); + void emitHLSLTextureType( + RefPtr texType) + { + switch(texType->getAccess()) + { + case SLANG_RESOURCE_ACCESS_READ: + break; - char d = *cursor++; - assert(('0' <= d) && (d <= '9')); + case SLANG_RESOURCE_ACCESS_READ_WRITE: + Emit("RW"); + break; - UInt argIndex = d - '0'; - assert((0 <= argIndex) && (argIndex < argCount)); - Emit(context, "("); - EmitExpr(context, callExpr->Arguments[argIndex]); - Emit(context, ")"); - } + case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: + Emit("RasterizerOrdered"); + break; - Emit(context, ")"); - } + case SLANG_RESOURCE_ACCESS_APPEND: + Emit("Append"); + break; - return; - } + case SLANG_RESOURCE_ACCESS_CONSUME: + Emit("Consume"); + break; - // TODO: emit as approperiate for this target + default: + assert(!"unreachable"); + break; + } - // We might be calling an intrinsic subscript operation, - // and should desugar it accordingly - if(auto subscriptDeclRef = funcDeclRef.As()) - { - // We expect any subscript operation to be invoked as a member, - // so the function expression had better be in the correct form. - if(auto memberExpr = funcExpr.As()) - { + switch (texType->GetBaseShape()) + { + case TextureType::Shape1D: Emit("Texture1D"); break; + case TextureType::Shape2D: Emit("Texture2D"); break; + case TextureType::Shape3D: Emit("Texture3D"); break; + case TextureType::ShapeCube: Emit("TextureCube"); break; + default: + assert(!"unreachable"); + break; + } - Emit(context, "("); - EmitExpr(context, memberExpr->BaseExpression); - Emit(context, ")["); - UInt argCount = callExpr->Arguments.Count(); - for (UInt aa = 0; aa < argCount; ++aa) - { - if (aa != 0) Emit(context, ", "); - EmitExpr(context, callExpr->Arguments[aa]); - } - Emit(context, "]"); - return; - } - } + if (texType->isMultisample()) + { + Emit("MS"); + } + if (texType->isArray()) + { + Emit("Array"); } + Emit("<"); + EmitType(texType->elementType); + Emit(" >"); } - // Fall through to default handling... - emitSimpleCallExpr(context, callExpr, outerPrec); -} - -static void emitStringLiteral( - EmitContext* context, - String const& value) -{ - emit(context, "\""); - for (auto c : value) + void emitGLSLTextureOrTextureSamplerType( + RefPtr type, + char const* baseName) { - // TODO: This needs a more complete implementation, - // especially if we want to support Unicode. + emitGLSLTypePrefix(type->elementType); - char buffer[] = { c, 0 }; - switch (c) + Emit(baseName); + switch (type->GetBaseShape()) { + case TextureType::Shape1D: Emit("1D"); break; + case TextureType::Shape2D: Emit("2D"); break; + case TextureType::Shape3D: Emit("3D"); break; + case TextureType::ShapeCube: Emit("Cube"); break; default: - emit(context, buffer); + assert(!"unreachable"); break; + } - case '\"': emit(context, "\\\""); - case '\'': emit(context, "\\\'"); - case '\\': emit(context, "\\\\"); - case '\n': emit(context, "\\n"); - case '\r': emit(context, "\\r"); - case '\t': emit(context, "\\t"); + if (type->isMultisample()) + { + Emit("MS"); + } + if (type->isArray()) + { + Emit("Array"); } } - emit(context, "\""); -} -static void EmitExprWithPrecedence(EmitContext* context, RefPtr expr, int outerPrec) -{ - bool needClose = false; - if (auto selectExpr = expr.As()) + void emitGLSLTextureType( + RefPtr texType) { - needClose = MaybeEmitParens(context, outerPrec, kPrecedence_Conditional); - - EmitExprWithPrecedence(context, selectExpr->Arguments[0], kPrecedence_Conditional); - Emit(context, " ? "); - EmitExprWithPrecedence(context, selectExpr->Arguments[1], kPrecedence_Conditional); - Emit(context, " : "); - EmitExprWithPrecedence(context, selectExpr->Arguments[2], kPrecedence_Conditional); + emitGLSLTextureOrTextureSamplerType(texType, "texture"); } - else if (auto assignExpr = expr.As()) + + void emitGLSLTextureSamplerType( + RefPtr type) { - needClose = MaybeEmitParens(context, outerPrec, kPrecedence_Assign); - EmitExprWithPrecedence(context, assignExpr->left, kPrecedence_Assign); - Emit(context, " = "); - EmitExprWithPrecedence(context, assignExpr->right, kPrecedence_Assign); + emitGLSLTextureOrTextureSamplerType(type, "sampler"); } - else if (auto callExpr = expr.As()) + + void emitGLSLImageType( + RefPtr type) { - emitCallExpr(context, callExpr, outerPrec); + emitGLSLTextureOrTextureSamplerType(type, "image"); } - else if (auto memberExpr = expr.As()) - { - needClose = MaybeEmitParens(context, outerPrec, kPrecedence_Postfix); - // TODO(tfoley): figure out a good way to reference - // declarations that might be generic and/or might - // not be generated as lexically nested declarations... + void emitTextureType( + RefPtr texType) + { + switch(context->shared->target) + { + case CodeGenTarget::HLSL: + emitHLSLTextureType(texType); + break; - // TODO(tfoley): also, probably need to special case - // this for places where we are using a built-in... + case CodeGenTarget::GLSL: + emitGLSLTextureType(texType); + break; - auto base = memberExpr->BaseExpression; - if (IsBaseExpressionImplicit(context, base)) - { - // don't emit the base expression + default: + assert(!"unreachable"); + break; } - else + } + + void emitTextureSamplerType( + RefPtr type) + { + switch(context->shared->target) { - EmitExprWithPrecedence(context, memberExpr->BaseExpression, kPrecedence_Postfix); - Emit(context, "."); - } + case CodeGenTarget::GLSL: + emitGLSLTextureSamplerType(type); + break; - emitName(context, memberExpr->declRef.GetName()); + default: + assert(!"unreachable"); + break; + } } - else if (auto swizExpr = expr.As()) - { - needClose = MaybeEmitParens(context, outerPrec, kPrecedence_Postfix); - EmitExprWithPrecedence(context, swizExpr->base, kPrecedence_Postfix); - Emit(context, "."); - static const char* kComponentNames[] = { "x", "y", "z", "w" }; - int elementCount = swizExpr->elementCount; - for (int ee = 0; ee < elementCount; ++ee) + void emitImageType( + RefPtr type) + { + switch(context->shared->target) { - Emit(context, kComponentNames[swizExpr->elementIndices[ee]]); + case CodeGenTarget::HLSL: + emitHLSLTextureType(type); + break; + + case CodeGenTarget::GLSL: + emitGLSLImageType(type); + break; + + default: + assert(!"unreachable"); + break; } } - else if (auto indexExpr = expr.As()) + + void emitTypeImpl(RefPtr type, EDeclarator* declarator) { - needClose = MaybeEmitParens(context, outerPrec, kPrecedence_Postfix); + TypeEmitArg arg; + arg.declarator = declarator; - EmitExprWithPrecedence(context, indexExpr->BaseExpression, kPrecedence_Postfix); - Emit(context, "["); - EmitExpr(context, indexExpr->IndexExpression); - Emit(context, "]"); + TypeVisitorWithArg::dispatch(type, arg); } - else if (auto varExpr = expr.As()) - { - needClose = MaybeEmitParens(context, outerPrec, kPrecedence_Atomic); - // TODO: This won't be valid if we had to generate a qualified - // reference for some reason. - advanceToSourceLocation(context, varExpr->Position); +#define UNEXPECTED(NAME) \ + void visit##NAME(NAME*, TypeEmitArg const& arg) \ + { Emit(#NAME); EmitDeclarator(arg.declarator); } - // Because of the "rewriter" use case, it is possible that we will - // be trying to emit an expression that hasn't been wired up to - // any associated declaration. In that case, we will just emit - // the variable name. - // - // TODO: A better long-term solution here is to have a distinct - // case for an "unchecked" `NameExpr` that doesn't include - // a declaration reference. + UNEXPECTED(ErrorType); + UNEXPECTED(OverloadGroupType); + UNEXPECTED(FuncType); + UNEXPECTED(TypeType); + UNEXPECTED(GenericDeclRefType); + UNEXPECTED(InitializerListType); - if(varExpr->declRef) - { - EmitDeclRef(context, varExpr->declRef); - } - else +#undef UNEXPECTED + + void visitNamedExpressionType(NamedExpressionType* type, TypeEmitArg const& arg) + { + // Named types are valid for GLSL + if (context->shared->target == CodeGenTarget::GLSL) { - emitName(context, varExpr->name); + emitTypeImpl(GetType(type->declRef), arg.declarator); + return; } + + EmitDeclRef(type->declRef); + EmitDeclarator(arg.declarator); } - else if (auto derefExpr = expr.As()) + + void visitBasicExpressionType(BasicExpressionType* basicType, TypeEmitArg const& arg) { - // TODO(tfoley): dereference shouldn't always be implicit - EmitExprWithPrecedence(context, derefExpr->base, outerPrec); + auto declarator = arg.declarator; + switch (basicType->BaseType) + { + case BaseType::Void: Emit("void"); break; + case BaseType::Int: Emit("int"); break; + case BaseType::Float: Emit("float"); break; + case BaseType::UInt: Emit("uint"); break; + case BaseType::Bool: Emit("bool"); break; + default: + assert(!"unreachable"); + break; + } + + EmitDeclarator(declarator); } - else if (auto litExpr = expr.As()) - { - needClose = MaybeEmitParens(context, outerPrec, kPrecedence_Atomic); - char const* suffix = ""; - auto type = litExpr->Type.type; - switch (litExpr->ConstType) + void visitVectorExpressionType(VectorExpressionType* vecType, TypeEmitArg const& arg) + { + auto declarator = arg.declarator; + switch(context->shared->target) { - case ConstantExpressionSyntaxNode::ConstantType::Int: - if(!type) - { - // Special case for "rewrite" mode - emitTokenWithLocation(context, litExpr->token); - break; - } - if(type->Equals(ExpressionType::GetInt())) - {} - else if(type->Equals(ExpressionType::GetUInt())) - { - suffix = "u"; - } - else + case CodeGenTarget::GLSL: + case CodeGenTarget::GLSL_Vulkan: + case CodeGenTarget::GLSL_Vulkan_OneDesc: { - assert(!"unimplemented"); + emitGLSLTypePrefix(vecType->elementType); + Emit("vec"); + Emit(vecType->elementCount); } - Emit(context, litExpr->integerValue); - Emit(context, suffix); break; + case CodeGenTarget::HLSL: + // TODO(tfoley): should really emit these with sugar + Emit("vector<"); + EmitType(vecType->elementType); + Emit(","); + Emit(vecType->elementCount); + Emit(">"); + break; - case ConstantExpressionSyntaxNode::ConstantType::Float: - if(!type) - { - // Special case for "rewrite" mode - emitTokenWithLocation(context, litExpr->token); - break; - } - if(type->Equals(ExpressionType::GetFloat())) - {} - else if(type->Equals(ExpressionType::getDoubleType())) - { - suffix = "l"; - } - else + default: + assert(!"unreachable"); + break; + } + + EmitDeclarator(declarator); + } + + void visitMatrixExpressionType(MatrixExpressionType* matType, TypeEmitArg const& arg) + { + auto declarator = arg.declarator; + switch(context->shared->target) + { + case CodeGenTarget::GLSL: + case CodeGenTarget::GLSL_Vulkan: + case CodeGenTarget::GLSL_Vulkan_OneDesc: { - assert(!"unimplemented"); + emitGLSLTypePrefix(matType->getElementType()); + Emit("mat"); + Emit(matType->getRowCount()); + // TODO(tfoley): only emit the next bit + // for non-square matrix + Emit("x"); + Emit(matType->getColumnCount()); } - Emit(context, litExpr->floatingPointValue); - Emit(context, suffix); break; - case ConstantExpressionSyntaxNode::ConstantType::Bool: - Emit(context, litExpr->integerValue ? "true" : "false"); - break; - case ConstantExpressionSyntaxNode::ConstantType::String: - emitStringLiteral(context, litExpr->stringValue); + case CodeGenTarget::HLSL: + // TODO(tfoley): should really emit these with sugar + Emit("matrix<"); + EmitType(matType->getElementType()); + Emit(","); + Emit(matType->getRowCount()); + Emit(","); + Emit(matType->getColumnCount()); + Emit("> "); break; + default: assert(!"unreachable"); break; } + + EmitDeclarator(declarator); + } + + void visitTextureType(TextureType* texType, TypeEmitArg const& arg) + { + auto declarator = arg.declarator; + emitTextureType(texType); + EmitDeclarator(declarator); + } + + void visitTextureSamplerType(TextureSamplerType* textureSamplerType, TypeEmitArg const& arg) + { + auto declarator = arg.declarator; + emitTextureSamplerType(textureSamplerType); + EmitDeclarator(declarator); + } + + void visitGLSLImageType(GLSLImageType* imageType, TypeEmitArg const& arg) + { + auto declarator = arg.declarator; + emitImageType(imageType); + EmitDeclarator(declarator); } - else if (auto castExpr = expr.As()) + + void visitSamplerStateType(SamplerStateType* samplerStateType, TypeEmitArg const& arg) { + auto declarator = arg.declarator; switch(context->shared->target) { - case CodeGenTarget::GLSL: - // GLSL requires constructor syntax for all conversions - EmitType(context, castExpr->Type); - Emit(context, "("); - EmitExpr(context, castExpr->Expression); - Emit(context, ")"); - break; - + case CodeGenTarget::HLSL: default: - // HLSL (and C/C++) prefer cast syntax - // (In fact, HLSL doesn't allow constructor syntax for some conversions it allows as a cast) - needClose = MaybeEmitParens(context, outerPrec, kPrecedence_Prefix); + switch (samplerStateType->flavor) + { + case SamplerStateType::Flavor::SamplerState: Emit("SamplerState"); break; + case SamplerStateType::Flavor::SamplerComparisonState: Emit("SamplerComparisonState"); break; + default: + assert(!"unreachable"); + break; + } + break; - Emit(context, "("); - EmitType(context, castExpr->Type); - Emit(context, ")("); - EmitExpr(context, castExpr->Expression); - Emit(context, ")"); + case CodeGenTarget::GLSL: + Emit("sampler"); break; } + EmitDeclarator(declarator); + } + + void visitDeclRefType(DeclRefType* declRefType, TypeEmitArg const& arg) + { + auto declarator = arg.declarator; + EmitDeclRef(declRefType->declRef); + EmitDeclarator(declarator); } - else if(auto initExpr = expr.As()) + + void visitArrayExpressionType(ArrayExpressionType* arrayType, TypeEmitArg const& arg) { - Emit(context, "{ "); - for(auto& arg : initExpr->args) + auto declarator = arg.declarator; + + EDeclarator arrayDeclarator; + arrayDeclarator.next = declarator; + + if(arrayType->ArrayLength) + { + arrayDeclarator.flavor = EDeclarator::Flavor::Array; + arrayDeclarator.elementCount = arrayType->ArrayLength.Ptr(); + } + else { - EmitExpr(context, arg); - Emit(context, ", "); + arrayDeclarator.flavor = EDeclarator::Flavor::UnsizedArray; } - Emit(context, "}"); + + + emitTypeImpl(arrayType->BaseType, &arrayDeclarator); } - else + + void EmitType( + RefPtr type, + CodePosition const& typeLoc, + String const& name, + CodePosition const& nameLoc) { - throw "unimplemented"; + advanceToSourceLocation(typeLoc); + + EDeclarator nameDeclarator; + nameDeclarator.flavor = EDeclarator::Flavor::Name; + nameDeclarator.name = name; + nameDeclarator.loc = nameLoc; + emitTypeImpl(type, &nameDeclarator); } - if (needClose) + + + void EmitType(RefPtr type, Token const& nameToken) { - Emit(context, ")"); + EmitType(type, CodePosition(), nameToken.Content, nameToken.Position); } -} -// Types - -void Emit(EmitContext* context, RefPtr val) -{ - if(auto constantIntVal = val.As()) + void EmitType(RefPtr type) { - Emit(context, constantIntVal->value); + emitTypeImpl(type, nullptr); } - else if(auto varRefVal = val.As()) + + void EmitType(TypeExp const& typeExp, Token const& nameToken) { - EmitDeclRef(context, varRefVal->declRef); + EmitType(typeExp.type, + typeExp.exp ? typeExp.exp->Position : CodePosition(), + nameToken.Content, nameToken.Position); } - else + + void EmitType(TypeExp const& typeExp, String const& name) { - assert(!"unimplemented"); + EmitType(typeExp.type, + typeExp.exp ? typeExp.exp->Position : CodePosition(), + name, CodePosition()); } -} -// represents a declarator for use in emitting types -struct EDeclarator -{ - enum class Flavor + void emitTypeExp(TypeExp const& typeExp) { - Name, - Array, - UnsizedArray, - }; - Flavor flavor; - EDeclarator* next = nullptr; - - // Used for `Flavor::Name` - String name; - CodePosition loc; - - // Used for `Flavor::Array` - IntVal* elementCount; -}; - -static void EmitDeclarator(EmitContext* context, EDeclarator* declarator) -{ - if (!declarator) return; + // TODO: we need to handle cases where the type part of things is bad... + emitTypeImpl(typeExp.type, nullptr); + } - Emit(context, " "); + // + // Expressions + // - switch (declarator->flavor) + // Determine if an expression should not be emitted when it is the base of + // a member reference expression. + bool IsBaseExpressionImplicit(RefPtr expr) { - case EDeclarator::Flavor::Name: - emitName(context, declarator->name, declarator->loc); - break; + // HACK(tfoley): For now, anything with a constant-buffer type should be + // left implicit. - case EDeclarator::Flavor::Array: - EmitDeclarator(context, declarator->next); - Emit(context, "["); - if(auto elementCount = declarator->elementCount) + // Look through any dereferencing that took place + RefPtr e = expr; + while (auto derefExpr = e.As()) { - Emit(context, elementCount); + e = derefExpr->base; + } + // Is the expression referencing a constant buffer? + if (auto cbufferType = e->Type->As()) + { + return true; } - Emit(context, "]"); - break; - - case EDeclarator::Flavor::UnsizedArray: - EmitDeclarator(context, declarator->next); - Emit(context, "[]"); - break; - default: - assert(!"unreachable"); - break; + return false; } -} -static void emitGLSLTypePrefix( - EmitContext* context, - RefPtr type) -{ - if(auto basicElementType = type->As()) + enum { - switch (basicElementType->BaseType) - { - case BaseType::Float: - // no prefix - break; + kPrecedence_None, + kPrecedence_Comma, - case BaseType::Int: Emit(context, "i"); break; - case BaseType::UInt: Emit(context, "u"); break; - case BaseType::Bool: Emit(context, "b"); break; - default: - assert(!"unreachable"); - break; - } - } - else if(auto vectorType = type->As()) - { - emitGLSLTypePrefix(context, vectorType->elementType); - } - else if(auto matrixType = type->As()) - { - emitGLSLTypePrefix(context, matrixType->getElementType()); - } - else - { - assert(!"unreachable"); - } -} + kPrecedence_Assign, + kPrecedence_AddAssign = kPrecedence_Assign, + kPrecedence_SubAssign = kPrecedence_Assign, + kPrecedence_MulAssign = kPrecedence_Assign, + kPrecedence_DivAssign = kPrecedence_Assign, + kPrecedence_ModAssign = kPrecedence_Assign, + kPrecedence_LshAssign = kPrecedence_Assign, + kPrecedence_RshAssign = kPrecedence_Assign, + kPrecedence_OrAssign = kPrecedence_Assign, + kPrecedence_AndAssign = kPrecedence_Assign, + kPrecedence_XorAssign = kPrecedence_Assign, -static void emitHLSLTextureType( - EmitContext* context, - RefPtr texType) -{ - switch(texType->getAccess()) - { - case SLANG_RESOURCE_ACCESS_READ: - break; + kPrecedence_General = kPrecedence_Assign, - case SLANG_RESOURCE_ACCESS_READ_WRITE: - Emit(context, "RW"); - break; + kPrecedence_Conditional, // "ternary" + kPrecedence_Or, + kPrecedence_And, + kPrecedence_BitOr, + kPrecedence_BitXor, + kPrecedence_BitAnd, - case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: - Emit(context, "RasterizerOrdered"); - break; + kPrecedence_Eql, + kPrecedence_Neq = kPrecedence_Eql, - case SLANG_RESOURCE_ACCESS_APPEND: - Emit(context, "Append"); - break; + kPrecedence_Less, + kPrecedence_Greater = kPrecedence_Less, + kPrecedence_Leq = kPrecedence_Less, + kPrecedence_Geq = kPrecedence_Less, - case SLANG_RESOURCE_ACCESS_CONSUME: - Emit(context, "Consume"); - break; + kPrecedence_Lsh, + kPrecedence_Rsh = kPrecedence_Lsh, - default: - assert(!"unreachable"); - break; - } + kPrecedence_Add, + kPrecedence_Sub = kPrecedence_Add, + + kPrecedence_Mul, + kPrecedence_Div = kPrecedence_Mul, + kPrecedence_Mod = kPrecedence_Mul, - switch (texType->GetBaseShape()) + kPrecedence_Prefix, + kPrecedence_Postfix, + kPrecedence_Atomic = kPrecedence_Postfix + }; + + void EmitPostfixExpr(RefPtr expr) { - case TextureType::Shape1D: Emit(context, "Texture1D"); break; - case TextureType::Shape2D: Emit(context, "Texture2D"); break; - case TextureType::Shape3D: Emit(context, "Texture3D"); break; - case TextureType::ShapeCube: Emit(context, "TextureCube"); break; - default: - assert(!"unreachable"); - break; + EmitExprWithPrecedence(expr, kPrecedence_Postfix); } - if (texType->isMultisample()) + void EmitExpr(RefPtr expr) { - Emit(context, "MS"); + EmitExprWithPrecedence(expr, kPrecedence_General); } - if (texType->isArray()) + + bool MaybeEmitParens(int outerPrec, int prec) { - Emit(context, "Array"); + if (prec <= outerPrec) + { + Emit("("); + return true; + } + return false; } - Emit(context, "<"); - EmitType(context, texType->elementType); - Emit(context, " >"); -} - -static void emitGLSLTextureOrTextureSamplerType( - EmitContext* context, - RefPtr type, - char const* baseName) -{ - emitGLSLTypePrefix(context, type->elementType); - Emit(context, baseName); - switch (type->GetBaseShape()) + // When we are going to emit an expression in an l-value context, + // we may need to ignore certain constructs that the type-checker + // might have introduced, but which interfere with our ability + // to use it effectively in the target language + RefPtr prepareLValueExpr( + RefPtr expr) { - case TextureType::Shape1D: Emit(context, "1D"); break; - case TextureType::Shape2D: Emit(context, "2D"); break; - case TextureType::Shape3D: Emit(context, "3D"); break; - case TextureType::ShapeCube: Emit(context, "Cube"); break; - default: - assert(!"unreachable"); - break; + for(;;) + { + if(auto typeCastExpr = expr.As()) + { + expr = typeCastExpr->Expression; + } + // TODO: any other cases? + else + { + return expr; + } + } + } - if (type->isMultisample()) + void emitInfixExprImpl( + int outerPrec, + int prec, + char const* op, + RefPtr binExpr, + bool isAssign) { - Emit(context, "MS"); + bool needsClose = MaybeEmitParens(outerPrec, prec); + + auto left = binExpr->Arguments[0]; + if(isAssign) + { + left = prepareLValueExpr(left); + } + + EmitExprWithPrecedence(left, prec); + Emit(" "); + Emit(op); + Emit(" "); + EmitExprWithPrecedence(binExpr->Arguments[1], prec); + if (needsClose) + { + Emit(")"); + } } - if (type->isArray()) + + void EmitBinExpr(int outerPrec, int prec, char const* op, RefPtr binExpr) { - Emit(context, "Array"); + emitInfixExprImpl(outerPrec, prec, op, binExpr, false); } -} - -static void emitGLSLTextureType( - EmitContext* context, - RefPtr texType) -{ - emitGLSLTextureOrTextureSamplerType(context, texType, "texture"); -} -static void emitGLSLTextureSamplerType( - EmitContext* context, - RefPtr type) -{ - emitGLSLTextureOrTextureSamplerType(context, type, "sampler"); -} - -static void emitGLSLImageType( - EmitContext* context, - RefPtr type) -{ - emitGLSLTextureOrTextureSamplerType(context, type, "image"); -} + void EmitBinAssignExpr(int outerPrec, int prec, char const* op, RefPtr binExpr) + { + emitInfixExprImpl(outerPrec, prec, op, binExpr, true); + } -static void emitTextureType( - EmitContext* context, - RefPtr texType) -{ - switch(context->shared->target) + void emitUnaryExprImpl( + int outerPrec, + int prec, + char const* preOp, + char const* postOp, + RefPtr expr, + bool isAssign) { - case CodeGenTarget::HLSL: - emitHLSLTextureType(context, texType); - break; + bool needsClose = MaybeEmitParens(outerPrec, prec); + Emit(preOp); - case CodeGenTarget::GLSL: - emitGLSLTextureType(context, texType); - break; + auto arg = expr->Arguments[0]; + if(isAssign) + { + arg = prepareLValueExpr(arg); + } - default: - assert(!"unreachable"); - break; + EmitExprWithPrecedence(arg, prec); + Emit(postOp); + if (needsClose) + { + Emit(")"); + } } -} -static void emitTextureSamplerType( - EmitContext* context, - RefPtr type) -{ - switch(context->shared->target) + void EmitUnaryExpr( + int outerPrec, + int prec, + char const* preOp, + char const* postOp, + RefPtr expr) { - case CodeGenTarget::GLSL: - emitGLSLTextureSamplerType(context, type); - break; + emitUnaryExprImpl(outerPrec, prec, preOp, postOp, expr, false); + } - default: - assert(!"unreachable"); - break; + void EmitUnaryAssignExpr( + int outerPrec, + int prec, + char const* preOp, + char const* postOp, + RefPtr expr) + { + emitUnaryExprImpl(outerPrec, prec, preOp, postOp, expr, true); } -} -static void emitImageType( - EmitContext* context, - RefPtr type) -{ - switch(context->shared->target) + // Determine if a target intrinsic modifer is applicable to the target + // we are currently emitting code for. + bool isTargetIntrinsicModifierApplicable( + RefPtr modifier) { - case CodeGenTarget::HLSL: - emitHLSLTextureType(context, type); - break; + auto const& targetToken = modifier->targetToken; - case CodeGenTarget::GLSL: - emitGLSLImageType(context, type); - break; + // If no target name was specified, then the modifier implicitly + // applies to all targets. + if(targetToken.Type == TokenType::Unknown) + return true; - default: - assert(!"unreachable"); - break; - } -} + // Otherwise, we need to check if the target name matches what + // we expect. + auto const& targetName = targetToken.Content; -static void EmitType(EmitContext* context, RefPtr type, EDeclarator* declarator) -{ - if (auto basicType = type->As()) - { - switch (basicType->BaseType) + switch(context->shared->target) { - case BaseType::Void: Emit(context, "void"); break; - case BaseType::Int: Emit(context, "int"); break; - case BaseType::Float: Emit(context, "float"); break; - case BaseType::UInt: Emit(context, "uint"); break; - case BaseType::Bool: Emit(context, "bool"); break; default: - assert(!"unreachable"); - break; - } + assert(!"unexpected"); + return false; - EmitDeclarator(context, declarator); - return; + case CodeGenTarget::GLSL: return targetName == "glsl"; + case CodeGenTarget::HLSL: return targetName == "hlsl"; + } } - else if (auto vecType = type->As()) + + // Find an intrinsic modifier appropriate to the current compilation target. + // + // If there are multiple such modifiers, this should return the best one. + RefPtr findTargetIntrinsicModifier( + RefPtr syntax) { - switch(context->shared->target) + RefPtr bestModifier; + for(auto m : syntax->GetModifiersOfType()) { - case CodeGenTarget::GLSL: - case CodeGenTarget::GLSL_Vulkan: - case CodeGenTarget::GLSL_Vulkan_OneDesc: + if(!isTargetIntrinsicModifierApplicable(m)) + continue; + + // For now "better"-ness is defined as: a modifier + // with a specified target is better than one without + // (it is more specific) + if(!bestModifier || bestModifier->targetToken.Type == TokenType::Unknown) { - emitGLSLTypePrefix(context, vecType->elementType); - Emit(context, "vec"); - Emit(context, vecType->elementCount); + bestModifier = m; } - break; - - case CodeGenTarget::HLSL: - // TODO(tfoley): should really emit these with sugar - Emit(context, "vector<"); - EmitType(context, vecType->elementType); - Emit(context, ","); - Emit(context, vecType->elementCount); - Emit(context, ">"); - break; - - default: - assert(!"unreachable"); - break; } - EmitDeclarator(context, declarator); - return; + return bestModifier; } - else if (auto matType = type->As()) + + // Emit a call expression that doesn't involve any special cases, + // just an expression of the form `f(a0, a1, ...)` + void emitSimpleCallExpr( + RefPtr callExpr, + int outerPrec) { - switch(context->shared->target) + bool needClose = MaybeEmitParens(outerPrec, kPrecedence_Postfix); + + auto funcExpr = callExpr->FunctionExpr; + if (auto funcDeclRefExpr = funcExpr.As()) { - case CodeGenTarget::GLSL: - case CodeGenTarget::GLSL_Vulkan: - case CodeGenTarget::GLSL_Vulkan_OneDesc: + auto declRef = funcDeclRefExpr->declRef; + if (auto ctorDeclRef = declRef.As()) { - emitGLSLTypePrefix(context, matType->getElementType()); - Emit(context, "mat"); - Emit(context, matType->getRowCount()); - // TODO(tfoley): only emit the next bit - // for non-square matrix - Emit(context, "x"); - Emit(context, matType->getColumnCount()); + // We really want to emit a reference to the type begin constructed + EmitType(callExpr->Type); } - break; - - case CodeGenTarget::HLSL: - // TODO(tfoley): should really emit these with sugar - Emit(context, "matrix<"); - EmitType(context, matType->getElementType()); - Emit(context, ","); - Emit(context, matType->getRowCount()); - Emit(context, ","); - Emit(context, matType->getColumnCount()); - Emit(context, "> "); - break; + else + { + // default case: just emit the decl ref + EmitExpr(funcExpr); + } + } + else + { + // default case: just emit the expression + EmitPostfixExpr(funcExpr); + } - default: - assert(!"unreachable"); - break; + Emit("("); + UInt argCount = callExpr->Arguments.Count(); + for (UInt aa = 0; aa < argCount; ++aa) + { + if (aa != 0) Emit(", "); + EmitExpr(callExpr->Arguments[aa]); } + Emit(")"); - EmitDeclarator(context, declarator); - return; - } - else if (auto texType = type->As()) - { - emitTextureType(context, texType); - EmitDeclarator(context, declarator); - return; - } - else if (auto textureSamplerType = type->As()) - { - emitTextureSamplerType(context, textureSamplerType); - EmitDeclarator(context, declarator); - return; - } - else if (auto imageType = type->As()) - { - emitImageType(context, imageType); - EmitDeclarator(context, declarator); - return; + if (needClose) + { + Emit(")"); + } } - else if (auto samplerStateType = type->As()) + + void emitStringLiteral( + String const& value) { - switch(context->shared->target) + emit("\""); + for (auto c : value) { - case CodeGenTarget::HLSL: - default: - switch (samplerStateType->flavor) + // TODO: This needs a more complete implementation, + // especially if we want to support Unicode. + + char buffer[] = { c, 0 }; + switch (c) { - case SamplerStateType::Flavor::SamplerState: Emit(context, "SamplerState"); break; - case SamplerStateType::Flavor::SamplerComparisonState: Emit(context, "SamplerComparisonState"); break; default: - assert(!"unreachable"); + emit(buffer); break; - } - break; - case CodeGenTarget::GLSL: - Emit(context, "sampler"); - break; + case '\"': emit("\\\""); + case '\'': emit("\\\'"); + case '\\': emit("\\\\"); + case '\n': emit("\\n"); + case '\r': emit("\\r"); + case '\t': emit("\\t"); + } } + emit("\""); + } + + void EmitExprWithPrecedence(RefPtr expr, int outerPrec) + { + ExprEmitArg arg; + arg.outerPrec = outerPrec; + ExprVisitorWithArg::dispatch(expr, arg); + } + +#define UNEXPECTED(NAME) \ + void visit##NAME(NAME*, ExprEmitArg const&) \ + { Emit(#NAME); } + + UNEXPECTED(GenericAppExpr); + +#undef UNEXPECTED - EmitDeclarator(context, declarator); - return; + void visitSharedTypeExpr(SharedTypeExpr* expr, ExprEmitArg const& arg) + { + emitTypeExp(expr->base); } - else if (auto declRefType = type->As()) + + void visitSelectExpressionSyntaxNode(SelectExpressionSyntaxNode* selectExpr, ExprEmitArg const& arg) { - EmitDeclRef(context, declRefType->declRef); + auto outerPrec = arg.outerPrec; + bool needClose = MaybeEmitParens(outerPrec, kPrecedence_Conditional); + + EmitExprWithPrecedence(selectExpr->Arguments[0], kPrecedence_Conditional); + Emit(" ? "); + EmitExprWithPrecedence(selectExpr->Arguments[1], kPrecedence_Conditional); + Emit(" : "); + EmitExprWithPrecedence(selectExpr->Arguments[2], kPrecedence_Conditional); + + if(needClose) Emit(")"); + } - EmitDeclarator(context, declarator); - return; + void visitAssignExpr(AssignExpr* assignExpr, ExprEmitArg const& arg) + { + auto outerPrec = arg.outerPrec; + bool needClose = MaybeEmitParens(outerPrec, kPrecedence_Assign); + EmitExprWithPrecedence(assignExpr->left, kPrecedence_Assign); + Emit(" = "); + EmitExprWithPrecedence(assignExpr->right, kPrecedence_Assign); + if(needClose) Emit(")"); } - else if( auto arrayType = type->As() ) + + void visitInvokeExpressionSyntaxNode( + RefPtr callExpr, + ExprEmitArg const& arg) { - EDeclarator arrayDeclarator; - arrayDeclarator.next = declarator; + auto outerPrec = arg.outerPrec; - if(arrayType->ArrayLength) - { - arrayDeclarator.flavor = EDeclarator::Flavor::Array; - arrayDeclarator.elementCount = arrayType->ArrayLength.Ptr(); - } - else + auto funcExpr = callExpr->FunctionExpr; + if (auto funcDeclRefExpr = funcExpr.As()) { - arrayDeclarator.flavor = EDeclarator::Flavor::UnsizedArray; - } + auto funcDeclRef = funcDeclRefExpr->declRef; + auto funcDecl = funcDeclRef.getDecl(); + if(!funcDecl) + { + // This can occur when we are dealing with unchecked input syntax, + // because we are in "rewriter" mode. In this case we should go + // ahead and emit things in the form that they were written. + if( auto infixExpr = callExpr.As() ) + { + EmitBinExpr( + outerPrec, + kPrecedence_Comma, + funcDeclRefExpr->name.Buffer(), + callExpr); + } + else if( auto prefixExpr = callExpr.As() ) + { + EmitUnaryExpr( + outerPrec, + kPrecedence_Prefix, + funcDeclRefExpr->name.Buffer(), + "", + callExpr); + } + else if(auto postfixExpr = callExpr.As()) + { + EmitUnaryExpr( + outerPrec, + kPrecedence_Postfix, + "", + funcDeclRefExpr->name.Buffer(), + callExpr); + } + else + { + emitSimpleCallExpr(callExpr, outerPrec); + } + return; + } + else if (auto intrinsicOpModifier = funcDecl->FindModifier()) + { + switch (intrinsicOpModifier->op) + { + #define CASE(NAME, OP) case IntrinsicOp::NAME: EmitBinExpr(outerPrec, kPrecedence_##NAME, #OP, callExpr); return + CASE(Mul, *); + CASE(Div, / ); + CASE(Mod, %); + CASE(Add, +); + CASE(Sub, -); + CASE(Lsh, << ); + CASE(Rsh, >> ); + CASE(Eql, == ); + CASE(Neq, != ); + CASE(Greater, >); + CASE(Less, <); + CASE(Geq, >= ); + CASE(Leq, <= ); + CASE(BitAnd, &); + CASE(BitXor, ^); + CASE(BitOr, | ); + CASE(And, &&); + CASE(Or, || ); + #undef CASE + + #define CASE(NAME, OP) case IntrinsicOp::NAME: EmitBinAssignExpr(outerPrec, kPrecedence_##NAME, #OP, callExpr); return + CASE(Assign, =); + CASE(AddAssign, +=); + CASE(SubAssign, -=); + CASE(MulAssign, *=); + CASE(DivAssign, /=); + CASE(ModAssign, %=); + CASE(LshAssign, <<=); + CASE(RshAssign, >>=); + CASE(OrAssign, |=); + CASE(AndAssign, &=); + CASE(XorAssign, ^=); + #undef CASE + + case IntrinsicOp::Sequence: EmitBinExpr(outerPrec, kPrecedence_Comma, ",", callExpr); return; + + #define CASE(NAME, OP) case IntrinsicOp::NAME: EmitUnaryExpr(outerPrec, kPrecedence_Prefix, #OP, "", callExpr); return + CASE(Neg, -); + CASE(Not, !); + CASE(BitNot, ~); + #undef CASE + + #define CASE(NAME, OP) case IntrinsicOp::NAME: EmitUnaryAssignExpr(outerPrec, kPrecedence_Prefix, #OP, "", callExpr); return + CASE(PreInc, ++); + CASE(PreDec, --); + #undef CASE + + #define CASE(NAME, OP) case IntrinsicOp::NAME: EmitUnaryAssignExpr(outerPrec, kPrecedence_Postfix, "", #OP, callExpr); return + CASE(PostInc, ++); + CASE(PostDec, --); + #undef CASE + + case IntrinsicOp::InnerProduct_Vector_Vector: + // HLSL allows `mul()` to be used as a synonym for `dot()`, + // so we need to translate to `dot` for GLSL + if (context->shared->target == CodeGenTarget::GLSL) + { + Emit("dot("); + EmitExpr(callExpr->Arguments[0]); + Emit(", "); + EmitExpr(callExpr->Arguments[1]); + Emit(")"); + return; + } + break; + case IntrinsicOp::InnerProduct_Matrix_Matrix: + case IntrinsicOp::InnerProduct_Matrix_Vector: + case IntrinsicOp::InnerProduct_Vector_Matrix: + // HLSL exposes these with the `mul()` function, while GLSL uses ordinary + // `operator*`. + // + // The other critical detail here is that the way we handle matrix + // conventions requires that the operands to the product be swapped. + if (context->shared->target == CodeGenTarget::GLSL) + { + Emit("(("); + EmitExpr(callExpr->Arguments[1]); + Emit(") * ("); + EmitExpr(callExpr->Arguments[0]); + Emit("))"); + return; + } + break; - EmitType(context, arrayType->BaseType, &arrayDeclarator); - return; - } + default: + break; + } + } + else if(auto targetIntrinsicModifier = findTargetIntrinsicModifier(funcDecl)) + { + if(targetIntrinsicModifier->definitionToken.Type != TokenType::Unknown) + { + auto name = getStringOrIdentifierTokenValue(targetIntrinsicModifier->definitionToken); - throw "unimplemented"; -} + if(name.IndexOf('$') < 0) + { + // Simple case: it is just an ordinary name, so we call it like a builtin. + // + // TODO: this case could probably handle things like operators, for generality? + + emit(name); + Emit("("); + UInt argCount = callExpr->Arguments.Count(); + for (UInt aa = 0; aa < argCount; ++aa) + { + if (aa != 0) Emit(", "); + EmitExpr(callExpr->Arguments[aa]); + } + Emit(")"); + return; + } + else + { + // General case: we are going to emit some more complex text. -static void EmitType( - EmitContext* context, - RefPtr type, - CodePosition const& typeLoc, - String const& name, - CodePosition const& nameLoc) -{ - advanceToSourceLocation(context, typeLoc); + UInt argCount = callExpr->Arguments.Count(); - EDeclarator nameDeclarator; - nameDeclarator.flavor = EDeclarator::Flavor::Name; - nameDeclarator.name = name; - nameDeclarator.loc = nameLoc; - EmitType(context, type, &nameDeclarator); -} + Emit("("); + char const* cursor = name.begin(); + char const* end = name.end(); + while(cursor != end) + { + char c = *cursor++; + if( c != '$' ) + { + // Not an escape sequence + emitRawTextSpan(&c, &c+1); + continue; + } + + assert(cursor != end); + + char d = *cursor++; + assert(('0' <= d) && (d <= '9')); + + UInt argIndex = d - '0'; + assert((0 <= argIndex) && (argIndex < argCount)); + Emit("("); + EmitExpr(callExpr->Arguments[argIndex]); + Emit(")"); + } -static void EmitType(EmitContext* context, RefPtr type, Token const& nameToken) -{ - EmitType(context, type, CodePosition(), nameToken.Content, nameToken.Position); -} + Emit(")"); + } -static void EmitType(EmitContext* context, RefPtr type) -{ - EmitType(context, type, nullptr); -} + return; + } -static void EmitType(EmitContext* context, TypeExp const& typeExp, Token const& nameToken) -{ - EmitType(context, typeExp.type, - typeExp.exp ? typeExp.exp->Position : CodePosition(), - nameToken.Content, nameToken.Position); -} + // TODO: emit as approperiate for this target -static void EmitType(EmitContext* context, TypeExp const& typeExp, String const& name) -{ - EmitType(context, typeExp.type, - typeExp.exp ? typeExp.exp->Position : CodePosition(), - name, CodePosition()); -} + // We might be calling an intrinsic subscript operation, + // and should desugar it accordingly + if(auto subscriptDeclRef = funcDeclRef.As()) + { + // We expect any subscript operation to be invoked as a member, + // so the function expression had better be in the correct form. + if(auto memberExpr = funcExpr.As()) + { -// Statements + Emit("("); + EmitExpr(memberExpr->BaseExpression); + Emit(")["); + UInt argCount = callExpr->Arguments.Count(); + for (UInt aa = 0; aa < argCount; ++aa) + { + if (aa != 0) Emit(", "); + EmitExpr(callExpr->Arguments[aa]); + } + Emit("]"); + return; + } + } + } + } -// Emit a statement as a `{}`-enclosed block statement, but avoid adding redundant -// curly braces if the statement is itself a block statement. -static void EmitBlockStmt(EmitContext* context, RefPtr stmt) -{ - // TODO(tfoley): support indenting - Emit(context, "{\n"); - if( auto blockStmt = stmt.As() ) - { - EmitStmt(context, blockStmt->body); - } - else - { - EmitStmt(context, stmt); + // Fall through to default handling... + emitSimpleCallExpr(callExpr, outerPrec); } - Emit(context, "}\n"); -} -static void EmitLoopAttributes(EmitContext* context, RefPtr decl) -{ - // TODO(tfoley): There really ought to be a semantic checking step for attributes, - // that turns abstract syntax into a concrete hierarchy of attribute types (e.g., - // a specific `LoopModifier` or `UnrollModifier`). - for(auto attr : decl->GetModifiersOfType()) + + void visitMemberExpressionSyntaxNode(MemberExpressionSyntaxNode* memberExpr, ExprEmitArg const& arg) { - if(attr->nameToken.Content == "loop") + auto outerPrec = arg.outerPrec; + bool needClose = MaybeEmitParens(outerPrec, kPrecedence_Postfix); + + // TODO(tfoley): figure out a good way to reference + // declarations that might be generic and/or might + // not be generated as lexically nested declarations... + + // TODO(tfoley): also, probably need to special case + // this for places where we are using a built-in... + + auto base = memberExpr->BaseExpression; + if (IsBaseExpressionImplicit(base)) { - Emit(context, "[loop]"); + // don't emit the base expression } - else if(attr->nameToken.Content == "unroll") + else { - Emit(context, "[unroll]"); + EmitExprWithPrecedence(memberExpr->BaseExpression, kPrecedence_Postfix); + Emit("."); } + + emitName(memberExpr->declRef.GetName()); + + if(needClose) Emit(")"); } -} -// Emit a `#line` directive to the output. -// Doesn't udpate state of source-location tracking. -static void emitLineDirective( - EmitContext* context, - CodePosition const& sourceLocation) -{ - emitRawText(context, "\n#line "); + void visitSwizzleExpr(SwizzleExpr* swizExpr, ExprEmitArg const& arg) + { + auto outerPrec = arg.outerPrec; + bool needClose = MaybeEmitParens(outerPrec, kPrecedence_Postfix); + + EmitExprWithPrecedence(swizExpr->base, kPrecedence_Postfix); + Emit("."); + static const char* kComponentNames[] = { "x", "y", "z", "w" }; + int elementCount = swizExpr->elementCount; + for (int ee = 0; ee < elementCount; ++ee) + { + Emit(kComponentNames[swizExpr->elementIndices[ee]]); + } + + if(needClose) Emit(")"); + } + + void visitIndexExpressionSyntaxNode(IndexExpressionSyntaxNode* indexExpr, ExprEmitArg const& arg) + { + auto outerPrec = arg.outerPrec; + bool needClose = MaybeEmitParens(outerPrec, kPrecedence_Postfix); + + EmitExprWithPrecedence(indexExpr->BaseExpression, kPrecedence_Postfix); + Emit("["); + EmitExpr(indexExpr->IndexExpression); + Emit("]"); - char buffer[16]; - sprintf(buffer, "%d", sourceLocation.Line); - emitRawText(context, buffer); + if(needClose) Emit(")"); + } - emitRawText(context, " "); + void visitOverloadedExpr(OverloadedExpr* expr, ExprEmitArg const& arg) + { + emitName(expr->lookupResult2.getName()); + } - if(context->shared->target == CodeGenTarget::GLSL) + void visitVarExpressionSyntaxNode(VarExpressionSyntaxNode* varExpr, ExprEmitArg const& arg) { - auto path = sourceLocation.FileName; + bool needClose = MaybeEmitParens(arg.outerPrec, kPrecedence_Atomic); + + // TODO: This won't be valid if we had to generate a qualified + // reference for some reason. + advanceToSourceLocation(varExpr->Position); - // 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. + // Because of the "rewriter" use case, it is possible that we will + // be trying to emit an expression that hasn't been wired up to + // any associated declaration. In that case, we will just emit + // the variable name. // - // TODO: Add some kind of configuration where we require the appropriate - // extension and then emit a traditional line directive. + // TODO: A better long-term solution here is to have a distinct + // case for an "unchecked" `NameExpr` that doesn't include + // a declaration reference. - int id = 0; - if(!context->shared->mapGLSLSourcePathToID.TryGetValue(path, id)) + if(varExpr->declRef) { - id = context->shared->glslSourceIDCount++; - context->shared->mapGLSLSourcePathToID.Add(path, id); + EmitDeclRef(varExpr->declRef); + } + else + { + emitName(varExpr->name); } - sprintf(buffer, "%d", id); - emitRawText(context, buffer); + if(needClose) Emit(")"); } - else + + void visitDerefExpr(DerefExpr* derefExpr, ExprEmitArg const& arg) { - // 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. + auto outerPrec = arg.outerPrec; + + // TODO(tfoley): dereference shouldn't always be implicit + EmitExprWithPrecedence(derefExpr->base, outerPrec); + } + + void visitConstantExpressionSyntaxNode(ConstantExpressionSyntaxNode* litExpr, ExprEmitArg const& arg) + { + auto outerPrec = arg.outerPrec; + bool needClose = MaybeEmitParens(outerPrec, kPrecedence_Atomic); - emitRawText(context, "\""); - for(auto c : sourceLocation.FileName) + char const* suffix = ""; + auto type = litExpr->Type.type; + switch (litExpr->ConstType) { - char charBuffer[] = { c, 0 }; - switch(c) + case ConstantExpressionSyntaxNode::ConstantType::Int: + if(!type) { - default: - emitRawText(context, charBuffer); + // Special case for "rewrite" mode + emitTokenWithLocation(litExpr->token); break; + } + if(type->Equals(ExpressionType::GetInt())) + {} + else if(type->Equals(ExpressionType::GetUInt())) + { + suffix = "u"; + } + else + { + assert(!"unimplemented"); + } + Emit(litExpr->integerValue); + Emit(suffix); + 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(context, "/"); + + case ConstantExpressionSyntaxNode::ConstantType::Float: + if(!type) + { + // Special case for "rewrite" mode + emitTokenWithLocation(litExpr->token); break; } + if(type->Equals(ExpressionType::GetFloat())) + {} + else if(type->Equals(ExpressionType::getDoubleType())) + { + suffix = "l"; + } + else + { + assert(!"unimplemented"); + } + Emit(litExpr->floatingPointValue); + Emit(suffix); + break; + + case ConstantExpressionSyntaxNode::ConstantType::Bool: + Emit(litExpr->integerValue ? "true" : "false"); + break; + case ConstantExpressionSyntaxNode::ConstantType::String: + emitStringLiteral(litExpr->stringValue); + break; + default: + assert(!"unreachable"); + break; } - emitRawText(context, "\""); + if(needClose) Emit(")"); } - emitRawText(context, "\n"); -} + void visitTypeCastExpressionSyntaxNode(TypeCastExpressionSyntaxNode* castExpr, ExprEmitArg const& arg) + { + bool needClose = false; + switch(context->shared->target) + { + case CodeGenTarget::GLSL: + // GLSL requires constructor syntax for all conversions + EmitType(castExpr->Type); + Emit("("); + EmitExpr(castExpr->Expression); + Emit(")"); + break; -// Emit a `#line` directive to the output, and also -// ensure that source location tracking information -// is correct based on the directive we just output. -static void emitLineDirectiveAndUpdateSourceLocation( - EmitContext* context, - CodePosition const& sourceLocation) -{ - emitLineDirective(context, sourceLocation); - - context->shared->loc.FileName = sourceLocation.FileName; - context->shared->loc.Line = sourceLocation.Line; - context->shared->loc.Col = 1; -} + default: + // HLSL (and C/C++) prefer cast syntax + // (In fact, HLSL doesn't allow constructor syntax for some conversions it allows as a cast) + needClose = MaybeEmitParens(arg.outerPrec, kPrecedence_Prefix); -static void emitLineDirectiveIfNeeded( - EmitContext* context, - CodePosition const& sourceLocation) -{ - // 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.FileName != context->shared->loc.FileName - || sourceLocation.Line != context->shared->loc.Line - || sourceLocation.Col < context->shared->loc.Col) - { - // 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 - context->shared->loc.Line; - if(sourceLocation.FileName == context->shared->loc.FileName - && sourceLocation.Line > context->shared->loc.Line - && lineDiff <= kSmallLineCount) - { - for(int ii = 0; ii < lineDiff; ++ii ) - { - Emit(context, "\n"); - } - assert(sourceLocation.Line == context->shared->loc.Line); + Emit("("); + EmitType(castExpr->Type); + Emit(")("); + EmitExpr(castExpr->Expression); + Emit(")"); + break; } - else + if(needClose) Emit(")"); + } + + void visitInitializerListExpr(InitializerListExpr* expr, ExprEmitArg const& arg) + { + Emit("{ "); + for(auto& arg : expr->args) { - // Go ahead and output a `#line` directive to get us caught up - emitLineDirectiveAndUpdateSourceLocation(context, sourceLocation); + EmitExpr(arg); + Emit(", "); } + Emit("}"); } - // Now indent up to the appropriate column, so that error messages - // that reference columns will be correct. // - // TODO: This logic does not take into account whether indentation - // came in as spaces or tabs, so there is necessarily going to be - // coupling between how the downstream compiler counts columns, - // and how we do. - if(sourceLocation.Col > context->shared->loc.Col) + // Statements + // + + // Emit a statement as a `{}`-enclosed block statement, but avoid adding redundant + // curly braces if the statement is itself a block statement. + void EmitBlockStmt(RefPtr stmt) { - int delta = sourceLocation.Col - context->shared->loc.Col; - for( int ii = 0; ii < delta; ++ii ) + // TODO(tfoley): support indenting + Emit("{\n"); + if( auto blockStmt = stmt.As() ) + { + EmitStmt(blockStmt->body); + } + else { - emitRawText(context, " "); + EmitStmt(stmt); } - context->shared->loc.Col = sourceLocation.Col; + Emit("}\n"); } -} -static void advanceToSourceLocation( - EmitContext* context, - CodePosition const& sourceLocation) -{ - // Skip invalid locations - if(sourceLocation.Line <= 0) - return; + void EmitLoopAttributes(RefPtr decl) + { + // TODO(tfoley): There really ought to be a semantic checking step for attributes, + // that turns abstract syntax into a concrete hierarchy of attribute types (e.g., + // a specific `LoopModifier` or `UnrollModifier`). - context->shared->needToUpdateSourceLocation = true; - context->shared->nextSourceLocation = sourceLocation; -} + for(auto attr : decl->GetModifiersOfType()) + { + if(attr->nameToken.Content == "loop") + { + Emit("[loop]"); + } + else if(attr->nameToken.Content == "unroll") + { + Emit("[unroll]"); + } + } + } -static void flushSourceLocationChange( - EmitContext* context) -{ - if(!context->shared->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. - context->shared->needToUpdateSourceLocation = false; - emitLineDirectiveIfNeeded(context, context->shared->nextSourceLocation); -} -static void emitTokenWithLocation(EmitContext* context, Token const& token) -{ - if( token.Position.FileName.Length() != 0 ) + void EmitUnparsedStmt(RefPtr stmt) { - advanceToSourceLocation(context, token.Position); + // TODO: actually emit the tokens that made up the statement... + Emit("{\n"); + for( auto& token : stmt->tokens ) + { + emitTokenWithLocation(token); + } + Emit("}\n"); } - else + + void EmitStmt(RefPtr stmt) { - // If we don't have the original position info, we need to play - // it safe and emit whitespace to line things up nicely + // Try to ensure that debugging can find the right location + advanceToSourceLocation(stmt->Position); - if(token.flags & TokenFlag::AtStartOfLine) - Emit(context, "\n"); - // TODO(tfoley): macro expansion can currently lead to whitespace getting dropped, - // so we will just insert it aggressively, to play it safe. - else // if(token.flags & TokenFlag::AfterWhitespace) - Emit(context, " "); - } + if (auto blockStmt = stmt.As()) + { + EmitBlockStmt(blockStmt); + return; + } + else if (auto seqStmt = stmt.As()) + { + for (auto ss : seqStmt->stmts) + { + EmitStmt(ss); + } + return; + } + else if( auto unparsedStmt = stmt.As() ) + { + EmitUnparsedStmt(unparsedStmt); + return; + } + else if (auto exprStmt = stmt.As()) + { + EmitExpr(exprStmt->Expression); + Emit(";\n"); + return; + } + else if (auto returnStmt = stmt.As()) + { + Emit("return"); + if (auto expr = returnStmt->Expression) + { + Emit(" "); + EmitExpr(expr); + } + Emit(";\n"); + return; + } + else if (auto declStmt = stmt.As()) + { + EmitDecl(declStmt->decl); + return; + } + else if (auto ifStmt = stmt.As()) + { + Emit("if("); + EmitExpr(ifStmt->Predicate); + Emit(")\n"); + EmitBlockStmt(ifStmt->PositiveStatement); + if(auto elseStmt = ifStmt->NegativeStatement) + { + Emit("\nelse\n"); + EmitBlockStmt(elseStmt); + } + return; + } + else if (auto forStmt = stmt.As()) + { + // We are going to always take a `for` loop like: + // + // for(A; B; C) { D } + // + // and emit it as: + // + // { A; for(; B; C) { D } } + // + // This ensures that we are robust against any kind + // of statement appearing in `A`, including things + // that might occur due to lowering steps. + // - // Emit the raw textual content of the token - emit(context, token.Content); -} + // The one wrinkle is that HLSL implements the + // bad approach to scoping a `for` loop variable, + // so we need to avoid those outer `{...}` when + // we are generating HLSL via "rewrite" (that is, + // without our semantic checks). + // + bool brokenScoping = false; + if (context->shared->target == CodeGenTarget::HLSL + && context->isRewrite) + { + brokenScoping = true; + } -static void EmitUnparsedStmt(EmitContext* context, RefPtr stmt) -{ - // TODO: actually emit the tokens that made up the statement... - Emit(context, "{\n"); - for( auto& token : stmt->tokens ) - { - emitTokenWithLocation(context, token); - } - Emit(context, "}\n"); -} + auto initStmt = forStmt->InitialStatement; + if(initStmt) + { + if(!brokenScoping) + Emit("{\n"); + EmitStmt(initStmt); + } -static void EmitStmt(EmitContext* context, RefPtr stmt) -{ - // Try to ensure that debugging can find the right location - advanceToSourceLocation(context, stmt->Position); + EmitLoopAttributes(forStmt); + + Emit("for(;"); + if (auto testExp = forStmt->PredicateExpression) + { + EmitExpr(testExp); + } + Emit(";"); + if (auto incrExpr = forStmt->SideEffectExpression) + { + EmitExpr(incrExpr); + } + Emit(")\n"); + EmitBlockStmt(forStmt->Statement); + + if (initStmt) + { + if(!brokenScoping) + Emit("}\n"); + } + + return; + } + else if (auto whileStmt = stmt.As()) + { + EmitLoopAttributes(whileStmt); + + Emit("while("); + EmitExpr(whileStmt->Predicate); + Emit(")\n"); + EmitBlockStmt(whileStmt->Statement); + return; + } + else if (auto doWhileStmt = stmt.As()) + { + EmitLoopAttributes(doWhileStmt); + + Emit("do("); + EmitBlockStmt(doWhileStmt->Statement); + Emit(" while("); + EmitExpr(doWhileStmt->Predicate); + Emit(")\n"); + return; + } + else if (auto discardStmt = stmt.As()) + { + Emit("discard;\n"); + return; + } + else if (auto emptyStmt = stmt.As()) + { + return; + } + else if (auto switchStmt = stmt.As()) + { + Emit("switch("); + EmitExpr(switchStmt->condition); + Emit(")\n"); + EmitBlockStmt(switchStmt->body); + return; + } + else if (auto caseStmt = stmt.As()) + { + Emit("case "); + EmitExpr(caseStmt->expr); + Emit(":\n"); + return; + } + else if (auto defaultStmt = stmt.As()) + { + Emit("default:{}\n"); + return; + } + else if (auto breakStmt = stmt.As()) + { + Emit("break;\n"); + return; + } + else if (auto continueStmt = stmt.As()) + { + Emit("continue;\n"); + return; + } + + throw "unimplemented"; - if (auto blockStmt = stmt.As()) - { - EmitBlockStmt(context, blockStmt); - return; } - else if (auto seqStmt = stmt.As()) + + // + // Declaration References + // + + // Declaration References + + void EmitVal(RefPtr val) { - for (auto ss : seqStmt->stmts) + if (auto type = val.As()) { - EmitStmt(context, ss); + EmitType(type); } - return; - } - else if( auto unparsedStmt = stmt.As() ) - { - EmitUnparsedStmt(context, unparsedStmt); - return; - } - else if (auto exprStmt = stmt.As()) - { - EmitExpr(context, exprStmt->Expression); - Emit(context, ";\n"); - return; - } - else if (auto returnStmt = stmt.As()) - { - Emit(context, "return"); - if (auto expr = returnStmt->Expression) + else if (auto intVal = val.As()) { - Emit(context, " "); - EmitExpr(context, expr); + Emit(intVal); } - Emit(context, ";\n"); - return; - } - else if (auto declStmt = stmt.As()) - { - EmitDecl(context, declStmt->decl); - return; - } - else if (auto ifStmt = stmt.As()) - { - Emit(context, "if("); - EmitExpr(context, ifStmt->Predicate); - Emit(context, ")\n"); - EmitBlockStmt(context, ifStmt->PositiveStatement); - if(auto elseStmt = ifStmt->NegativeStatement) + else { - Emit(context, "\nelse\n"); - EmitBlockStmt(context, elseStmt); + // Note(tfoley): ignore unhandled cases for semantics for now... + // assert(!"unimplemented"); } - return; } - else if (auto forStmt = stmt.As()) + + void EmitDeclRef(DeclRef declRef) { - // We are going to always take a `for` loop like: - // - // for(A; B; C) { D } - // - // and emit it as: - // - // { A; for(; B; C) { D } } - // - // This ensures that we are robust against any kind - // of statement appearing in `A`, including things - // that might occur due to lowering steps. - // + // TODO: need to qualify a declaration name based on parent scopes/declarations - // The one wrinkle is that HLSL implements the - // bad approach to scoping a `for` loop variable, - // so we need to avoid those outer `{...}` when - // we are generating HLSL via "rewrite" (that is, - // without our semantic checks). - // - bool brokenScoping = false; - if (context->shared->target == CodeGenTarget::HLSL - && context->isRewrite) - { - brokenScoping = true; - } + // Emit the name for the declaration itself + emitName(declRef.GetName()); - auto initStmt = forStmt->InitialStatement; - if(initStmt) + // If the declaration is nested directly in a generic, then + // we need to output the generic arguments here + auto parentDeclRef = declRef.GetParent(); + if (auto genericDeclRef = parentDeclRef.As()) { - if(!brokenScoping) - Emit(context, "{\n"); - EmitStmt(context, initStmt); + // Only do this for declarations of appropriate flavors + if(auto funcDeclRef = declRef.As()) + { + // Don't emit generic arguments for functions, because HLSL doesn't allow them + return; + } + + Substitutions* subst = declRef.substitutions.Ptr(); + Emit("<"); + UInt argCount = subst->args.Count(); + for (UInt aa = 0; aa < argCount; ++aa) + { + if (aa != 0) Emit(","); + EmitVal(subst->args[aa]); + } + Emit(" >"); } - EmitLoopAttributes(context, forStmt); + } - Emit(context, "for(;"); - if (auto testExp = forStmt->PredicateExpression) - { - EmitExpr(context, testExp); - } - Emit(context, ";"); - if (auto incrExpr = forStmt->SideEffectExpression) - { - EmitExpr(context, incrExpr); - } - Emit(context, ")\n"); - EmitBlockStmt(context, forStmt->Statement); - if (initStmt) - { - if(!brokenScoping) - Emit(context, "}\n"); - } + // + // Declarations + // - return; - } - else if (auto whileStmt = stmt.As()) + void emitDeclImpl( + Decl* decl, + VarLayout* layout) { - EmitLoopAttributes(context, whileStmt); + // Don't emit code for declarations that came from the stdlib. + // + // TODO(tfoley): We probably need to relax this eventually, + // since different targets might have different sets of builtins. + if (decl->HasModifier()) + return; - Emit(context, "while("); - EmitExpr(context, whileStmt->Predicate); - Emit(context, ")\n"); - EmitBlockStmt(context, whileStmt->Statement); - return; - } - else if (auto doWhileStmt = stmt.As()) - { - EmitLoopAttributes(context, doWhileStmt); + // Try to ensure that debugging can find the right location + advanceToSourceLocation(decl->Position); - Emit(context, "do("); - EmitBlockStmt(context, doWhileStmt->Statement); - Emit(context, " while("); - EmitExpr(context, doWhileStmt->Predicate); - Emit(context, ")\n"); - return; - } - else if (auto discardStmt = stmt.As()) - { - Emit(context, "discard;\n"); - return; - } - else if (auto emptyStmt = stmt.As()) - { - return; - } - else if (auto switchStmt = stmt.As()) - { - Emit(context, "switch("); - EmitExpr(context, switchStmt->condition); - Emit(context, ")\n"); - EmitBlockStmt(context, switchStmt->body); - return; - } - else if (auto caseStmt = stmt.As()) - { - Emit(context, "case "); - EmitExpr(context, caseStmt->expr); - Emit(context, ":\n"); - return; - } - else if (auto defaultStmt = stmt.As()) - { - Emit(context, "default:{}\n"); - return; - } - else if (auto breakStmt = stmt.As()) - { - Emit(context, "break;\n"); - return; - } - else if (auto continueStmt = stmt.As()) - { - Emit(context, "continue;\n"); - return; + DeclEmitArg arg; + arg.layout = layout; + + DeclVisitorWithArg::dispatch(decl, arg); } - throw "unimplemented"; +#define IGNORED(NAME) \ + void visit##NAME(NAME*, DeclEmitArg const&) {} -} + // Only used by stdlib + IGNORED(ModifierDecl) -// Declaration References + // Don't emit generic decls directly; we will only + // ever emit particular instantiations of them. + IGNORED(GenericDecl) + IGNORED(GenericTypeConstraintDecl) + IGNORED(GenericValueParamDecl) + IGNORED(GenericTypeParamDecl) -static void EmitVal(EmitContext* context, RefPtr val) -{ - if (auto type = val.As()) - { - EmitType(context, type); - } - else if (auto intVal = val.As()) + // Not epected to appear (probably dead code) + IGNORED(ClassSyntaxNode) + + // Not semantically meaningful for emit, or expected + // to be lowered out of existence before we get here + IGNORED(InheritanceDecl) + IGNORED(ExtensionDecl) + IGNORED(ScopeDecl) + + // Catch-all cases where we handle the types that matter, + // while others will be lowered out of exitence + IGNORED(CallableDecl) + IGNORED(AggTypeDeclBase) + + // Should not appear nested inside other decls + IGNORED(ProgramSyntaxNode) + +#undef IGNORED + + void visitDeclGroup(DeclGroup* declGroup, DeclEmitArg const&) { - Emit(context, intVal); + for (auto decl : declGroup->decls) + { + EmitDecl(decl); + } } - else + + void visitTypeDefDecl(TypeDefDecl* decl, DeclEmitArg const&) { - // Note(tfoley): ignore unhandled cases for semantics for now... -// assert(!"unimplemented"); + // Note(tfoley): any `typedef`s should already have been filtered + // out if we are generating GLSL. + assert(context->shared->target != CodeGenTarget::GLSL); + + Emit("typedef "); + EmitType(decl->Type, decl->Name.Content); + Emit(";\n"); } -} -static void EmitDeclRef(EmitContext* context, DeclRef declRef) -{ - // TODO: need to qualify a declaration name based on parent scopes/declarations + void visitImportDecl(ImportDecl* decl, DeclEmitArg const&) + { + // When in "rewriter" mode, we need to emit the code of the imported + // module in-place at the `import` site. - // Emit the name for the declaration itself - emitName(context, declRef.GetName()); + auto moduleDecl = decl->importedModuleDecl.Ptr(); - // If the declaration is nested directly in a generic, then - // we need to output the generic arguments here - auto parentDeclRef = declRef.GetParent(); - if (auto genericDeclRef = parentDeclRef.As()) - { - // Only do this for declarations of appropriate flavors - if(auto funcDeclRef = declRef.As()) + // We might import the same module along two different paths, + // so we need to be careful to only emit each module once + // per output. + if(!context->shared->modulesAlreadyEmitted.Contains(moduleDecl)) { - // Don't emit generic arguments for functions, because HLSL doesn't allow them - return; - } + // Add the module to our set before emitting it, just + // in case a circular reference would lead us to + // infinite recursion (but that shouldn't be allowed + // in the first place). + context->shared->modulesAlreadyEmitted.Add(moduleDecl); - Substitutions* subst = declRef.substitutions.Ptr(); - Emit(context, "<"); - UInt argCount = subst->args.Count(); - for (UInt aa = 0; aa < argCount; ++aa) - { - if (aa != 0) Emit(context, ","); - EmitVal(context, subst->args[aa]); + // TODO: do we need to modify the code generation environment at + // all when doing this recursive emit? + + EmitDeclsInContainerUsingLayout(moduleDecl, context->shared->globalStructLayout); } - Emit(context, " >"); } -} + void visitEmptyDecl(EmptyDecl* decl, DeclEmitArg const&) + { + // GLSL uses empty declarations to carry semantically relevant modifiers, + // so we can't just skip empty declarations in general -// Declarations + EmitModifiers(decl); + Emit(";\n"); + } -// Emit any modifiers that should go in front of a declaration -static void EmitModifiers(EmitContext* context, RefPtr decl) -{ - // Emit any GLSL `layout` modifiers first - bool anyLayout = false; - for( auto mod : decl->GetModifiersOfType()) + // Emit any modifiers that should go in front of a declaration + void EmitModifiers(RefPtr decl) { - if(!anyLayout) + // Emit any GLSL `layout` modifiers first + bool anyLayout = false; + for( auto mod : decl->GetModifiersOfType()) { - Emit(context, "layout("); - anyLayout = true; + if(!anyLayout) + { + Emit("layout("); + anyLayout = true; + } + else + { + Emit(", "); + } + + emit(mod->nameToken.Content); + if(mod->valToken.Type != TokenType::Unknown) + { + Emit(" = "); + emit(mod->valToken.Content); + } } - else + if(anyLayout) { - Emit(context, ", "); + Emit(")\n"); } - emit(context, mod->nameToken.Content); - if(mod->valToken.Type != TokenType::Unknown) + for (auto mod = decl->modifiers.first; mod; mod = mod->next) { - Emit(context, " = "); - emit(context, mod->valToken.Content); - } - } - if(anyLayout) - { - Emit(context, ")\n"); - } - - for (auto mod = decl->modifiers.first; mod; mod = mod->next) - { - advanceToSourceLocation(context, mod->Position); + advanceToSourceLocation(mod->Position); - if (0) {} + if (0) {} - #define CASE(TYPE, KEYWORD) \ - else if(auto mod_##TYPE = mod.As()) Emit(context, #KEYWORD " ") + #define CASE(TYPE, KEYWORD) \ + else if(auto mod_##TYPE = mod.As()) Emit(#KEYWORD " ") - CASE(RowMajorLayoutModifier, row_major); - CASE(ColumnMajorLayoutModifier, column_major); - CASE(HLSLNoInterpolationModifier, nointerpolation); - CASE(HLSLPreciseModifier, precise); - CASE(HLSLEffectSharedModifier, shared); - CASE(HLSLGroupSharedModifier, groupshared); - CASE(HLSLUniformModifier, uniform); - CASE(HLSLVolatileModifier, volatile); + CASE(RowMajorLayoutModifier, row_major); + CASE(ColumnMajorLayoutModifier, column_major); + CASE(HLSLNoInterpolationModifier, nointerpolation); + CASE(HLSLPreciseModifier, precise); + CASE(HLSLEffectSharedModifier, shared); + CASE(HLSLGroupSharedModifier, groupshared); + CASE(HLSLUniformModifier, uniform); + CASE(HLSLVolatileModifier, volatile); - CASE(InOutModifier, inout); - CASE(InModifier, in); - CASE(OutModifier, out); + CASE(InOutModifier, inout); + CASE(InModifier, in); + CASE(OutModifier, out); - CASE(HLSLPointModifier, point); - CASE(HLSLLineModifier, line); - CASE(HLSLTriangleModifier, triangle); - CASE(HLSLLineAdjModifier, lineadj); - CASE(HLSLTriangleAdjModifier, triangleadj); + CASE(HLSLPointModifier, point); + CASE(HLSLLineModifier, line); + CASE(HLSLTriangleModifier, triangle); + CASE(HLSLLineAdjModifier, lineadj); + CASE(HLSLTriangleAdjModifier, triangleadj); - CASE(HLSLLinearModifier, linear); - CASE(HLSLSampleModifier, sample); - CASE(HLSLCentroidModifier, centroid); + CASE(HLSLLinearModifier, linear); + CASE(HLSLSampleModifier, sample); + CASE(HLSLCentroidModifier, centroid); - CASE(ConstModifier, const); + CASE(ConstModifier, const); - #undef CASE + #undef CASE - else if (auto staticModifier = mod.As()) - { - // GLSL does not support the `static` keyword. - // HLSL uses it both to mark global variables as being "thread-local" - // (rather than shader inputs), and also seems to support function-`static` - // variables. - // The latter case needs to be dealt with in lowering anyway, so that - // we only need to deal with globals here, and GLSL variables - // don't need a `static` modifier anyway. - - switch(context->shared->target) + else if (auto staticModifier = mod.As()) { - default: - Emit(context, "static"); - break; + // GLSL does not support the `static` keyword. + // HLSL uses it both to mark global variables as being "thread-local" + // (rather than shader inputs), and also seems to support function-`static` + // variables. + // The latter case needs to be dealt with in lowering anyway, so that + // we only need to deal with globals here, and GLSL variables + // don't need a `static` modifier anyway. + + switch(context->shared->target) + { + default: + Emit("static"); + break; - case CodeGenTarget::GLSL: - break; + case CodeGenTarget::GLSL: + break; + } } - } - // TODO: eventually we should be checked these modifiers, but for - // now we can emit them unchecked, I guess - else if (auto uncheckedAttr = mod.As()) - { - Emit(context, "["); - emit(context, uncheckedAttr->nameToken.Content); - auto& args = uncheckedAttr->args; - auto argCount = args.Count(); - if (argCount != 0) + // TODO: eventually we should be checked these modifiers, but for + // now we can emit them unchecked, I guess + else if (auto uncheckedAttr = mod.As()) { - Emit(context, "("); - for (UInt aa = 0; aa < argCount; ++aa) + Emit("["); + emit(uncheckedAttr->nameToken.Content); + auto& args = uncheckedAttr->args; + auto argCount = args.Count(); + if (argCount != 0) { - if (aa != 0) Emit(context, ", "); - EmitExpr(context, args[aa]); + Emit("("); + for (UInt aa = 0; aa < argCount; ++aa) + { + if (aa != 0) Emit(", "); + EmitExpr(args[aa]); + } + Emit(")"); } - Emit(context, ")"); + Emit("]"); } - Emit(context, "]"); - } - else if(auto simpleModifier = mod.As()) - { - emit(context, simpleModifier->nameToken.Content); - Emit(context, " "); - } + else if(auto simpleModifier = mod.As()) + { + emit(simpleModifier->nameToken.Content); + Emit(" "); + } - else - { - // skip any extra modifiers + else + { + // skip any extra modifiers + } } } -} -typedef unsigned int ESemanticMask; -enum -{ - kESemanticMask_None = 0, + typedef unsigned int ESemanticMask; + enum + { + kESemanticMask_None = 0, - kESemanticMask_NoPackOffset = 1 << 0, + kESemanticMask_NoPackOffset = 1 << 0, - kESemanticMask_Default = kESemanticMask_NoPackOffset, -}; + kESemanticMask_Default = kESemanticMask_NoPackOffset, + }; -static void EmitSemantic(EmitContext* context, RefPtr semantic, ESemanticMask /*mask*/) -{ - if (auto simple = semantic.As()) + void EmitSemantic(RefPtr semantic, ESemanticMask /*mask*/) { - Emit(context, ": "); - emit(context, simple->name.Content); - } - else if(auto registerSemantic = semantic.As()) - { - // Don't print out semantic from the user, since we are going to print the same thing our own way... -#if 0 - Emit(context, ": register("); - Emit(context, registerSemantic->registerName.Content); - if(registerSemantic->componentMask.Type != TokenType::Unknown) + if (auto simple = semantic.As()) { - Emit(context, "."); - Emit(context, registerSemantic->componentMask.Content); + Emit(": "); + emit(simple->name.Content); } - Emit(context, ")"); -#endif - } - else if(auto packOffsetSemantic = semantic.As()) - { - // Don't print out semantic from the user, since we are going to print the same thing our own way... -#if 0 - if(mask & kESemanticMask_NoPackOffset) - return; + else if(auto registerSemantic = semantic.As()) + { + // Don't print out semantic from the user, since we are going to print the same thing our own way... + #if 0 + Emit(": register("); + Emit(registerSemantic->registerName.Content); + if(registerSemantic->componentMask.Type != TokenType::Unknown) + { + Emit("."); + Emit(registerSemantic->componentMask.Content); + } + Emit(")"); + #endif + } + else if(auto packOffsetSemantic = semantic.As()) + { + // Don't print out semantic from the user, since we are going to print the same thing our own way... + #if 0 + if(mask & kESemanticMask_NoPackOffset) + return; - Emit(context, ": packoffset("); - Emit(context, packOffsetSemantic->registerName.Content); - if(packOffsetSemantic->componentMask.Type != TokenType::Unknown) + Emit(": packoffset("); + Emit(packOffsetSemantic->registerName.Content); + if(packOffsetSemantic->componentMask.Type != TokenType::Unknown) + { + Emit("."); + Emit(packOffsetSemantic->componentMask.Content); + } + Emit(")"); + #endif + } + else { - Emit(context, "."); - Emit(context, packOffsetSemantic->componentMask.Content); + assert(!"unimplemented"); } - Emit(context, ")"); -#endif - } - else - { - assert(!"unimplemented"); } -} -static void EmitSemantics(EmitContext* context, RefPtr decl, ESemanticMask mask = kESemanticMask_Default ) -{ - // Don't emit semantics if we aren't translating down to HLSL - switch (context->shared->target) + void EmitSemantics(RefPtr decl, ESemanticMask mask = kESemanticMask_Default ) { - case CodeGenTarget::HLSL: - break; + // Don't emit semantics if we aren't translating down to HLSL + switch (context->shared->target) + { + case CodeGenTarget::HLSL: + break; - default: - return; - } + default: + return; + } - for (auto mod = decl->modifiers.first; mod; mod = mod->next) - { - auto semantic = mod.As(); - if (!semantic) - continue; + for (auto mod = decl->modifiers.first; mod; mod = mod->next) + { + auto semantic = mod.As(); + if (!semantic) + continue; - EmitSemantic(context, semantic, mask); + EmitSemantic(semantic, mask); + } } -} -static void EmitDeclsInContainer(EmitContext* context, RefPtr container) -{ - for (auto member : container->Members) + void EmitDeclsInContainer(RefPtr container) { - EmitDecl(context, member); + for (auto member : container->Members) + { + EmitDecl(member); + } } -} -static void EmitDeclsInContainerUsingLayout( - EmitContext* context, - RefPtr container, - RefPtr containerLayout) -{ - for (auto member : container->Members) + void EmitDeclsInContainerUsingLayout( + RefPtr container, + RefPtr containerLayout) { - RefPtr memberLayout; - if( containerLayout->mapVarToLayout.TryGetValue(member.Ptr(), memberLayout) ) - { - EmitDeclUsingLayout(context, member, memberLayout); - } - else + for (auto member : container->Members) { - // No layout for this decl - EmitDecl(context, member); + RefPtr memberLayout; + if( containerLayout->mapVarToLayout.TryGetValue(member.Ptr(), memberLayout) ) + { + EmitDeclUsingLayout(member, memberLayout); + } + else + { + // No layout for this decl + EmitDecl(member); + } } } -} - -static void EmitTypeDefDecl(EmitContext* context, RefPtr decl) -{ - // TODO(tfoley): check if current compilation target even supports typedefs - Emit(context, "typedef "); - EmitType(context, decl->Type, decl->Name.Content); - Emit(context, ";\n"); -} + void visitStructSyntaxNode(RefPtr decl, DeclEmitArg const&) + { + // Don't emit a declaration that was only generated implicitly, for + // the purposes of semantic checking. + if(decl->HasModifier()) + return; -static void EmitStructDecl(EmitContext* context, RefPtr decl) -{ - // Don't emit a declaration that was only generated implicitly, for - // the purposes of semantic checking. - if(decl->HasModifier()) - return; + Emit("struct "); + emitName(decl->Name); + Emit("\n{\n"); - Emit(context, "struct "); - emitName(context, decl->Name); - Emit(context, "\n{\n"); + // TODO(tfoley): Need to hoist members functions, etc. out to global scope + EmitDeclsInContainer(decl); - // TODO(tfoley): Need to hoist members functions, etc. out to global scope - EmitDeclsInContainer(context, decl); + Emit("};\n"); + } - Emit(context, "};\n"); -} + // Shared emit logic for variable declarations (used for parameters, locals, globals, fields) + void EmitVarDeclCommon(DeclRef declRef) + { + EmitModifiers(declRef.getDecl()); -// Shared emit logic for variable declarations (used for parameters, locals, globals, fields) -static void EmitVarDeclCommon(EmitContext* context, DeclRef declRef) -{ - EmitModifiers(context, declRef.getDecl()); + EmitType(GetType(declRef), declRef.getDecl()->getNameToken()); - EmitType(context, GetType(declRef), declRef.getDecl()->getNameToken()); + EmitSemantics(declRef.getDecl()); - EmitSemantics(context, declRef.getDecl()); + // TODO(tfoley): technically have to apply substitution here too... + if (auto initExpr = declRef.getDecl()->Expr) + { + Emit(" = "); + EmitExpr(initExpr); + } + } - // TODO(tfoley): technically have to apply substitution here too... - if (auto initExpr = declRef.getDecl()->Expr) + // Shared emit logic for variable declarations (used for parameters, locals, globals, fields) + void EmitVarDeclCommon(RefPtr decl) { - Emit(context, " = "); - EmitExpr(context, initExpr); + EmitVarDeclCommon(DeclRef(decl.Ptr(), nullptr).As()); } -} - -// Shared emit logic for variable declarations (used for parameters, locals, globals, fields) -static void EmitVarDeclCommon(EmitContext* context, RefPtr decl) -{ - EmitVarDeclCommon(context, DeclRef(decl.Ptr(), nullptr).As()); -} -// Emit a single `regsiter` semantic, as appropriate for a given resource-type-specific layout info -static void emitHLSLRegisterSemantic( - EmitContext* context, - VarLayout::ResourceInfo const& info) -{ - if( info.kind == LayoutResourceKind::Uniform ) + // Emit a single `regsiter` semantic, as appropriate for a given resource-type-specific layout info + void emitHLSLRegisterSemantic( + VarLayout::ResourceInfo const& info) { - size_t offset = info.index; + if( info.kind == LayoutResourceKind::Uniform ) + { + size_t offset = info.index; - // The HLSL `c` register space is logically grouped in 16-byte registers, - // while we try to traffic in byte offsets. That means we need to pick - // a register number, based on the starting offset in 16-byte register - // units, and then a "component" within that register, based on 4-byte - // offsets from there. We cannot support more fine-grained offsets than that. + // The HLSL `c` register space is logically grouped in 16-byte registers, + // while we try to traffic in byte offsets. That means we need to pick + // a register number, based on the starting offset in 16-byte register + // units, and then a "component" within that register, based on 4-byte + // offsets from there. We cannot support more fine-grained offsets than that. - Emit(context, ": packoffset(c"); + Emit(": packoffset(c"); - // Size of a logical `c` register in bytes - auto registerSize = 16; + // Size of a logical `c` register in bytes + auto registerSize = 16; - // Size of each component of a logical `c` register, in bytes - auto componentSize = 4; + // Size of each component of a logical `c` register, in bytes + auto componentSize = 4; - size_t startRegister = offset / registerSize; - Emit(context, int(startRegister)); + size_t startRegister = offset / registerSize; + Emit(int(startRegister)); - size_t byteOffsetInRegister = offset % registerSize; + size_t byteOffsetInRegister = offset % registerSize; - // If this field doesn't start on an even register boundary, - // then we need to emit additional information to pick the - // right component to start from - if (byteOffsetInRegister != 0) - { - // The value had better occupy a whole number of components. - assert(byteOffsetInRegister % componentSize == 0); + // If this field doesn't start on an even register boundary, + // then we need to emit additional information to pick the + // right component to start from + if (byteOffsetInRegister != 0) + { + // The value had better occupy a whole number of components. + assert(byteOffsetInRegister % componentSize == 0); - size_t startComponent = byteOffsetInRegister / componentSize; + size_t startComponent = byteOffsetInRegister / componentSize; - static const char* kComponentNames[] = {"x", "y", "z", "w"}; - Emit(context, "."); - Emit(context, kComponentNames[startComponent]); + static const char* kComponentNames[] = {"x", "y", "z", "w"}; + Emit("."); + Emit(kComponentNames[startComponent]); + } + Emit(")"); + } + else + { + Emit(": register("); + switch( info.kind ) + { + case LayoutResourceKind::ConstantBuffer: + Emit("b"); + break; + case LayoutResourceKind::ShaderResource: + Emit("t"); + break; + case LayoutResourceKind::UnorderedAccess: + Emit("u"); + break; + case LayoutResourceKind::SamplerState: + Emit("s"); + break; + default: + assert(!"unexpected"); + break; + } + Emit(info.index); + if(info.space) + { + Emit(", space"); + Emit(info.space); + } + Emit(")"); } - Emit(context, ")"); } - else + + // Emit all the `register` semantics that are appropriate for a particular variable layout + void emitHLSLRegisterSemantics( + RefPtr layout) { - Emit(context, ": register("); - switch( info.kind ) + if (!layout) return; + + switch( context->shared->target ) { - case LayoutResourceKind::ConstantBuffer: - Emit(context, "b"); - break; - case LayoutResourceKind::ShaderResource: - Emit(context, "t"); - break; - case LayoutResourceKind::UnorderedAccess: - Emit(context, "u"); - break; - case LayoutResourceKind::SamplerState: - Emit(context, "s"); - break; default: - assert(!"unexpected"); + return; + + case CodeGenTarget::HLSL: break; } - Emit(context, info.index); - if(info.space) - { - Emit(context, ", space"); - Emit(context, info.space); - } - Emit(context, ")"); - } -} - -// Emit all the `register` semantics that are appropriate for a particular variable layout -static void emitHLSLRegisterSemantics( - EmitContext* context, - RefPtr layout) -{ - if (!layout) return; - - switch( context->shared->target ) - { - default: - return; - case CodeGenTarget::HLSL: - break; + for( auto rr : layout->resourceInfos ) + { + emitHLSLRegisterSemantic(rr); + } } - for( auto rr : layout->resourceInfos ) + static RefPtr maybeFetchLayout( + RefPtr decl, + RefPtr layout) { - emitHLSLRegisterSemantic(context, rr); - } -} - -static RefPtr maybeFetchLayout( - RefPtr decl, - RefPtr layout) -{ - // If we have already found layout info, don't go searching - if (layout) return layout; - - // Otherwise, we need to look and see if computed layout - // information has been attached to the declaration. - auto modifier = decl->FindModifier(); - if (!modifier) return nullptr; + // If we have already found layout info, don't go searching + if (layout) return layout; - auto computedLayout = modifier->layout; - assert(computedLayout); + // Otherwise, we need to look and see if computed layout + // information has been attached to the declaration. + auto modifier = decl->FindModifier(); + if (!modifier) return nullptr; - auto varLayout = computedLayout.As(); - return varLayout; -} + auto computedLayout = modifier->layout; + assert(computedLayout); -static void emitHLSLParameterBlockDecl( - EmitContext* context, - RefPtr varDecl, - RefPtr parameterBlockType, - RefPtr layout) -{ - // The data type that describes where stuff in the constant buffer should go - RefPtr dataType = parameterBlockType->elementType; + auto varLayout = computedLayout.As(); + return varLayout; + } - // We expect/require the data type to be a user-defined `struct` type - auto declRefType = dataType->As(); - assert(declRefType); + void emitHLSLParameterBlockDecl( + RefPtr varDecl, + RefPtr parameterBlockType, + RefPtr layout) + { + // The data type that describes where stuff in the constant buffer should go + RefPtr dataType = parameterBlockType->elementType; - // We expect to always have layout information - layout = maybeFetchLayout(varDecl, layout); - assert(layout); + // We expect/require the data type to be a user-defined `struct` type + auto declRefType = dataType->As(); + assert(declRefType); - // We expect the layout to be for a structured type... - RefPtr bufferLayout = layout->typeLayout.As(); - assert(bufferLayout); + // We expect to always have layout information + layout = maybeFetchLayout(varDecl, layout); + assert(layout); - RefPtr structTypeLayout = bufferLayout->elementTypeLayout.As(); - assert(structTypeLayout); + // We expect the layout to be for a structured type... + RefPtr bufferLayout = layout->typeLayout.As(); + assert(bufferLayout); - if( auto constantBufferType = parameterBlockType->As() ) - { - Emit(context, "cbuffer "); - } - else if( auto textureBufferType = parameterBlockType->As() ) - { - Emit(context, "tbuffer "); - } + RefPtr structTypeLayout = bufferLayout->elementTypeLayout.As(); + assert(structTypeLayout); - if( auto reflectionNameModifier = varDecl->FindModifier() ) - { - Emit(context, " "); - emitName(context, reflectionNameModifier->nameToken); - } + if( auto constantBufferType = parameterBlockType->As() ) + { + Emit("cbuffer "); + } + else if( auto textureBufferType = parameterBlockType->As() ) + { + Emit("tbuffer "); + } - EmitSemantics(context, varDecl, kESemanticMask_None); + if( auto reflectionNameModifier = varDecl->FindModifier() ) + { + Emit(" "); + emitName(reflectionNameModifier->nameToken); + } - auto info = layout->FindResourceInfo(LayoutResourceKind::ConstantBuffer); - assert(info); - emitHLSLRegisterSemantic(context, *info); + EmitSemantics(varDecl, kESemanticMask_None); - Emit(context, "\n{\n"); - if (auto structRef = declRefType->declRef.As()) - { - int fieldCounter = 0; + auto info = layout->FindResourceInfo(LayoutResourceKind::ConstantBuffer); + assert(info); + emitHLSLRegisterSemantic(*info); - for (auto field : getMembersOfType(structRef)) + Emit("\n{\n"); + if (auto structRef = declRefType->declRef.As()) { - int fieldIndex = fieldCounter++; - - EmitVarDeclCommon(context, field); + int fieldCounter = 0; - RefPtr fieldLayout = structTypeLayout->fields[fieldIndex]; - assert(fieldLayout->varDecl.GetName() == field.GetName()); - - // Emit explicit layout annotations for every field - for( auto rr : fieldLayout->resourceInfos ) + for (auto field : getMembersOfType(structRef)) { - auto kind = rr.kind; + int fieldIndex = fieldCounter++; + + EmitVarDeclCommon(field); - auto offsetResource = rr; + RefPtr fieldLayout = structTypeLayout->fields[fieldIndex]; + assert(fieldLayout->varDecl.GetName() == field.GetName()); - if(kind != LayoutResourceKind::Uniform) + // Emit explicit layout annotations for every field + for( auto rr : fieldLayout->resourceInfos ) { - // Add the base index from the cbuffer into the index of the field - // - // TODO(tfoley): consider maybe not doing this, since it actually - // complicates logic around constant buffers... + auto kind = rr.kind; + + auto offsetResource = rr; - // If the member of the cbuffer uses a resource, it had better - // appear as part of the cubffer layout as well. - auto cbufferResource = layout->FindResourceInfo(kind); - assert(cbufferResource); + if(kind != LayoutResourceKind::Uniform) + { + // Add the base index from the cbuffer into the index of the field + // + // TODO(tfoley): consider maybe not doing this, since it actually + // complicates logic around constant buffers... + + // If the member of the cbuffer uses a resource, it had better + // appear as part of the cubffer layout as well. + auto cbufferResource = layout->FindResourceInfo(kind); + assert(cbufferResource); + + offsetResource.index += cbufferResource->index; + offsetResource.space += cbufferResource->space; + } - offsetResource.index += cbufferResource->index; - offsetResource.space += cbufferResource->space; + emitHLSLRegisterSemantic(offsetResource); } - emitHLSLRegisterSemantic(context, offsetResource); + Emit(";\n"); } - - Emit(context, ";\n"); } + Emit("}\n"); } - Emit(context, "}\n"); -} -static void -emitGLSLLayoutQualifier( - EmitContext* context, - VarLayout::ResourceInfo const& info) -{ - switch(info.kind) + void emitGLSLLayoutQualifier( + VarLayout::ResourceInfo const& info) { - case LayoutResourceKind::Uniform: - Emit(context, "layout(offset = "); - Emit(context, info.index); - Emit(context, ")\n"); - break; + switch(info.kind) + { + case LayoutResourceKind::Uniform: + Emit("layout(offset = "); + Emit(info.index); + Emit(")\n"); + break; - case LayoutResourceKind::VertexInput: - case LayoutResourceKind::FragmentOutput: - Emit(context, "layout(location = "); - Emit(context, info.index); - Emit(context, ")\n"); - break; + case LayoutResourceKind::VertexInput: + case LayoutResourceKind::FragmentOutput: + Emit("layout(location = "); + Emit(info.index); + Emit(")\n"); + break; - case LayoutResourceKind::SpecializationConstant: - Emit(context, "layout(constant_id = "); - Emit(context, info.index); - Emit(context, ")\n"); - break; + case LayoutResourceKind::SpecializationConstant: + Emit("layout(constant_id = "); + Emit(info.index); + Emit(")\n"); + break; - case LayoutResourceKind::ConstantBuffer: - case LayoutResourceKind::ShaderResource: - case LayoutResourceKind::UnorderedAccess: - case LayoutResourceKind::SamplerState: - case LayoutResourceKind::DescriptorTableSlot: - Emit(context, "layout(binding = "); - Emit(context, info.index); - if(info.space) - { - Emit(context, ", set = "); - Emit(context, info.space); + case LayoutResourceKind::ConstantBuffer: + case LayoutResourceKind::ShaderResource: + case LayoutResourceKind::UnorderedAccess: + case LayoutResourceKind::SamplerState: + case LayoutResourceKind::DescriptorTableSlot: + Emit("layout(binding = "); + Emit(info.index); + if(info.space) + { + Emit(", set = "); + Emit(info.space); + } + Emit(")\n"); + break; } - Emit(context, ")\n"); - break; } -} - -static void -emitGLSLLayoutQualifiers( - EmitContext* context, - RefPtr layout) -{ - if(!layout) return; - switch( context->shared->target ) + void emitGLSLLayoutQualifiers( + RefPtr layout) { - default: - return; + if(!layout) return; - case CodeGenTarget::GLSL: - break; - } + switch( context->shared->target ) + { + default: + return; - for( auto info : layout->resourceInfos ) - { - emitGLSLLayoutQualifier(context, info); + case CodeGenTarget::GLSL: + break; + } + + for( auto info : layout->resourceInfos ) + { + emitGLSLLayoutQualifier(info); + } } -} -static void emitGLSLParameterBlockDecl( - EmitContext* context, - RefPtr varDecl, - RefPtr parameterBlockType, - RefPtr layout) -{ - // The data type that describes where stuff in the constant buffer should go - RefPtr dataType = parameterBlockType->elementType; + void emitGLSLParameterBlockDecl( + RefPtr varDecl, + RefPtr parameterBlockType, + RefPtr layout) + { + // The data type that describes where stuff in the constant buffer should go + RefPtr dataType = parameterBlockType->elementType; - // We expect/require the data type to be a user-defined `struct` type - auto declRefType = dataType->As(); - assert(declRefType); + // We expect/require the data type to be a user-defined `struct` type + auto declRefType = dataType->As(); + assert(declRefType); - // We expect to always have layout information - assert(layout); + // We expect to always have layout information + assert(layout); - // We expect the layout to be for a structured type... - RefPtr bufferLayout = layout->typeLayout.As(); - assert(bufferLayout); + // We expect the layout to be for a structured type... + RefPtr bufferLayout = layout->typeLayout.As(); + assert(bufferLayout); - RefPtr structTypeLayout = bufferLayout->elementTypeLayout.As(); - assert(structTypeLayout); + RefPtr structTypeLayout = bufferLayout->elementTypeLayout.As(); + assert(structTypeLayout); - emitGLSLLayoutQualifiers(context, layout); + emitGLSLLayoutQualifiers(layout); - EmitModifiers(context, varDecl); + EmitModifiers(varDecl); - // Emit an apprpriate declaration keyword based on the kind of block - if (parameterBlockType->As()) - { - Emit(context, "uniform"); - } - else if (parameterBlockType->As()) - { - Emit(context, "in"); - } - else if (parameterBlockType->As()) - { - Emit(context, "out"); - } - else if (parameterBlockType->As()) - { - Emit(context, "buffer"); - } - else - { - assert(!"unexpected"); - Emit(context, "uniform"); - } + // Emit an apprpriate declaration keyword based on the kind of block + if (parameterBlockType->As()) + { + Emit("uniform"); + } + else if (parameterBlockType->As()) + { + Emit("in"); + } + else if (parameterBlockType->As()) + { + Emit("out"); + } + else if (parameterBlockType->As()) + { + Emit("buffer"); + } + else + { + assert(!"unexpected"); + Emit("uniform"); + } - if( auto reflectionNameModifier = varDecl->FindModifier() ) - { - Emit(context, " "); - emitName(context, reflectionNameModifier->nameToken); - } + if( auto reflectionNameModifier = varDecl->FindModifier() ) + { + Emit(" "); + emitName(reflectionNameModifier->nameToken); + } - Emit(context, "\n{\n"); - if (auto structRef = declRefType->declRef.As()) - { - for (auto field : getMembersOfType(structRef)) + Emit("\n{\n"); + if (auto structRef = declRefType->declRef.As()) { - RefPtr fieldLayout; - structTypeLayout->mapVarToLayout.TryGetValue(field.getDecl(), fieldLayout); -// assert(fieldLayout); + for (auto field : getMembersOfType(structRef)) + { + RefPtr fieldLayout; + structTypeLayout->mapVarToLayout.TryGetValue(field.getDecl(), fieldLayout); + // assert(fieldLayout); - // TODO(tfoley): We may want to emit *some* of these, - // some of the time... -// emitGLSLLayoutQualifiers(context, fieldLayout); + // TODO(tfoley): We may want to emit *some* of these, + // some of the time... + // emitGLSLLayoutQualifiers(fieldLayout); - EmitVarDeclCommon(context, field); + EmitVarDeclCommon(field); - Emit(context, ";\n"); + Emit(";\n"); + } } - } - Emit(context, "}"); + Emit("}"); - if( varDecl->Name.Type != TokenType::Unknown ) - { - Emit(context, " "); - emitName(context, varDecl->Name); - } + if( varDecl->Name.Type != TokenType::Unknown ) + { + Emit(" "); + emitName(varDecl->Name); + } - Emit(context, ";\n"); -} + Emit(";\n"); + } -static void emitParameterBlockDecl( - EmitContext* context, - RefPtr varDecl, - RefPtr parameterBlockType, - RefPtr layout) -{ - switch(context->shared->target) + void emitParameterBlockDecl( + RefPtr varDecl, + RefPtr parameterBlockType, + RefPtr layout) { - case CodeGenTarget::HLSL: - emitHLSLParameterBlockDecl(context, varDecl, parameterBlockType, layout); - break; + switch(context->shared->target) + { + case CodeGenTarget::HLSL: + emitHLSLParameterBlockDecl(varDecl, parameterBlockType, layout); + break; - case CodeGenTarget::GLSL: - emitGLSLParameterBlockDecl(context, varDecl, parameterBlockType, layout); - break; + case CodeGenTarget::GLSL: + emitGLSLParameterBlockDecl(varDecl, parameterBlockType, layout); + break; - default: - assert(!"unexpected"); - break; + default: + assert(!"unexpected"); + break; + } } -} - -static void EmitVarDecl(EmitContext* context, RefPtr decl, RefPtr layout) -{ - layout = maybeFetchLayout(decl, layout); - // As a special case, a variable using a parameter block type - // will be translated into a declaration using the more primitive - // language syntax. - // - // TODO(tfoley): Be sure to unwrap arrays here, in the GLSL case. - // - // TODO(tfoley): Detect cases where we need to fall back to - // ordinary variable declaration syntax in HLSL. - // - // TODO(tfoley): there might be a better way to detect this, e.g., - // with an attribute that gets attached to the variable declaration. - if (auto parameterBlockType = decl->Type->As()) + void visitVarDeclBase(RefPtr decl, DeclEmitArg const& arg) { - emitParameterBlockDecl(context, decl, parameterBlockType, layout); - return; - } - - emitGLSLLayoutQualifiers(context, layout); - - EmitVarDeclCommon(context, decl); - - emitHLSLRegisterSemantics(context, layout); - - Emit(context, ";\n"); -} + RefPtr layout = arg.layout; + layout = maybeFetchLayout(decl, layout); -static void EmitParamDecl(EmitContext* context, RefPtr decl) -{ - EmitVarDeclCommon(context, decl); -} + // As a special case, a variable using a parameter block type + // will be translated into a declaration using the more primitive + // language syntax. + // + // TODO(tfoley): Be sure to unwrap arrays here, in the GLSL case. + // + // TODO(tfoley): Detect cases where we need to fall back to + // ordinary variable declaration syntax in HLSL. + // + // TODO(tfoley): there might be a better way to detect this, e.g., + // with an attribute that gets attached to the variable declaration. + if (auto parameterBlockType = decl->Type->As()) + { + emitParameterBlockDecl(decl, parameterBlockType, layout); + return; + } -static void EmitFuncDecl(EmitContext* context, RefPtr decl) -{ - EmitModifiers(context, decl); + emitGLSLLayoutQualifiers(layout); - // TODO: if a function returns an array type, or something similar that - // isn't allowed by declarator syntax and/or language rules, we could - // hypothetically wrap things in a `typedef` and work around it. + EmitVarDeclCommon(decl); - EmitType(context, decl->ReturnType, decl->Name); + emitHLSLRegisterSemantics(layout); - Emit(context, "("); - bool first = true; - for (auto paramDecl : decl->getMembersOfType()) - { - if (!first) Emit(context, ", "); - EmitParamDecl(context, paramDecl); - first = false; + Emit(";\n"); } - Emit(context, ")"); - EmitSemantics(context, decl); - - if (auto bodyStmt = decl->Body) - { - EmitBlockStmt(context, bodyStmt); - } - else + void EmitParamDecl(RefPtr decl) { - Emit(context, ";\n"); + EmitVarDeclCommon(decl); } -} -static void emitGLSLPreprocessorDirectives( - EmitContext* context, - RefPtr program) -{ - switch(context->shared->target) + void visitFunctionSyntaxNode(RefPtr decl, DeclEmitArg const&) { - // Don't emit this stuff unless we are targetting GLSL - default: - return; + EmitModifiers(decl); - case CodeGenTarget::GLSL: - break; - } + // TODO: if a function returns an array type, or something similar that + // isn't allowed by declarator syntax and/or language rules, we could + // hypothetically wrap things in a `typedef` and work around it. - if( auto versionDirective = program->FindModifier() ) - { - // TODO(tfoley): Emit an appropriate `#line` directive... + EmitType(decl->ReturnType, decl->Name); - Emit(context, "#version "); - emit(context, versionDirective->versionNumberToken.Content); - if(versionDirective->glslProfileToken.Type != TokenType::Unknown) + Emit("("); + bool first = true; + for (auto paramDecl : decl->getMembersOfType()) { - Emit(context, " "); - emit(context, versionDirective->glslProfileToken.Content); + if (!first) Emit(", "); + EmitParamDecl(paramDecl); + first = false; } - Emit(context, "\n"); - } - else - { - // No explicit version was given (probably because we are cross-compiling). - // - // We need to pick an appropriate version, ideally based on the features - // that the shader ends up using. - // - // For now we just fall back to a reasonably recent version. - - Emit(context, "#version 420\n"); - } + Emit(")"); - // TODO: when cross-compiling we may need to output additional `#extension` directives - // based on the features that we have used. + EmitSemantics(decl); - for( auto extensionDirective : program->GetModifiersOfType() ) - { - // TODO(tfoley): Emit an appropriate `#line` directive... - - Emit(context, "#extension "); - emit(context, extensionDirective->extensionNameToken.Content); - Emit(context, " : "); - emit(context, extensionDirective->dispositionToken.Content); - Emit(context, "\n"); + if (auto bodyStmt = decl->Body) + { + EmitBlockStmt(bodyStmt); + } + else + { + Emit(";\n"); + } } - // TODO: handle other cases... -} + void emitGLSLPreprocessorDirectives( + RefPtr program) + { + switch(context->shared->target) + { + // Don't emit this stuff unless we are targetting GLSL + default: + return; -static void EmitDeclImpl(EmitContext* context, RefPtr decl, RefPtr layout) -{ - // Don't emit code for declarations that came from the stdlib. - // - // TODO(tfoley): We probably need to relax this eventually, - // since different targets might have different sets of builtins. - if (decl->HasModifier()) - return; + case CodeGenTarget::GLSL: + break; + } + if( auto versionDirective = program->FindModifier() ) + { + // TODO(tfoley): Emit an appropriate `#line` directive... - // Try to ensure that debugging can find the right location - advanceToSourceLocation(context, decl->Position); + Emit("#version "); + emit(versionDirective->versionNumberToken.Content); + if(versionDirective->glslProfileToken.Type != TokenType::Unknown) + { + Emit(" "); + emit(versionDirective->glslProfileToken.Content); + } + Emit("\n"); + } + else + { + // No explicit version was given (probably because we are cross-compiling). + // + // We need to pick an appropriate version, ideally based on the features + // that the shader ends up using. + // + // For now we just fall back to a reasonably recent version. - if (auto typeDefDecl = decl.As()) - { - EmitTypeDefDecl(context, typeDefDecl); - return; - } - else if (auto structDecl = decl.As()) - { - EmitStructDecl(context, structDecl); - return; - } - else if (auto varDecl = decl.As()) - { - EmitVarDecl(context, varDecl, layout); - return; - } - else if (auto funcDecl = decl.As()) - { - EmitFuncDecl(context, funcDecl); - return; - } - else if (auto genericDecl = decl.As()) - { - // Don't emit generic decls directly; we will only - // ever emit particular instantiations of them. - return; - } - else if (auto classDecl = decl.As()) - { - return; - } - else if( auto importDecl = decl.As()) - { - // When in "rewriter" mode, we need to emit the code of the imported - // module in-place at the `import` site. + Emit("#version 420\n"); + } - auto moduleDecl = importDecl->importedModuleDecl.Ptr(); + // TODO: when cross-compiling we may need to output additional `#extension` directives + // based on the features that we have used. - // We might import the same module along two different paths, - // so we need to be careful to only emit each module once - // per output. - if(!context->shared->modulesAlreadyEmitted.Contains(moduleDecl)) + for( auto extensionDirective : program->GetModifiersOfType() ) { - // Add the module to our set before emitting it, just - // in case a circular reference would lead us to - // infinite recursion (but that shouldn't be allowed - // in the first place). - context->shared->modulesAlreadyEmitted.Add(moduleDecl); - - // TODO: do we need to modify the code generation environment at - // all when doing this recursive emit? + // TODO(tfoley): Emit an appropriate `#line` directive... - EmitDeclsInContainerUsingLayout(context, moduleDecl, context->shared->globalStructLayout); + Emit("#extension "); + emit(extensionDirective->extensionNameToken.Content); + Emit(" : "); + emit(extensionDirective->dispositionToken.Content); + Emit("\n"); } - return; + // TODO: handle other cases... } - else if( auto emptyDecl = decl.As() ) + + void EmitDecl(RefPtr decl) { - EmitModifiers(context, emptyDecl); - Emit(context, ";\n"); - return; + emitDeclImpl(decl, nullptr); } - throw "unimplemented"; -} - -static void EmitDecl(EmitContext* context, RefPtr decl) -{ - EmitDeclImpl(context, decl, nullptr); -} - -static void EmitDeclUsingLayout(EmitContext* context, RefPtr decl, RefPtr layout) -{ - EmitDeclImpl(context, decl, layout); -} -static void EmitDecl(EmitContext* context, RefPtr declBase) -{ - if( auto decl = declBase.As() ) + void EmitDeclUsingLayout(RefPtr decl, RefPtr layout) { - EmitDecl(context, decl); + emitDeclImpl(decl, layout); } - else if(auto declGroup = declBase.As()) + + void EmitDecl(RefPtr declBase) { - for(auto d : declGroup->decls) - EmitDecl(context, d); + if( auto decl = declBase.As() ) + { + EmitDecl(decl); + } + else if(auto declGroup = declBase.As()) + { + for(auto d : declGroup->decls) + EmitDecl(d); + } + else + { + throw "unimplemented"; + } } - else + + void registerReservedWord( + String const& name) { - throw "unimplemented"; + context->shared->reservedWords.Add(name, name); } -} - -static void registerReservedWord( - EmitContext* context, - String const& name) -{ - context->shared->reservedWords.Add(name, name); -} -static void registerReservedWords( - EmitContext* context) -{ -#define WORD(NAME) registerReservedWord(context, #NAME) - - switch (context->shared->target) + void registerReservedWords() { - case CodeGenTarget::GLSL: - WORD(attribute); - WORD(const); - WORD(uniform); - WORD(varying); - WORD(buffer); - - WORD(shared); - WORD(coherent); - WORD(volatile); - WORD(restrict); - WORD(readonly); - WORD(writeonly); - WORD(atomic_unit); - WORD(layout); - WORD(centroid); - WORD(flat); - WORD(smooth); - WORD(noperspective); - WORD(patch); - WORD(sample); - WORD(break); - WORD(continue); - WORD(do); - WORD(for); - WORD(while); - WORD(switch); - WORD(case); - WORD(default); - WORD(if); - WORD(else); - WORD(subroutine); - WORD(in); - WORD(out); - WORD(inout); - WORD(float); - WORD(double); - WORD(int); - WORD(void); - WORD(bool); - WORD(true); - WORD(false); - WORD(invariant); - WORD(precise); - WORD(discard); - WORD(return); - - WORD(lowp); - WORD(mediump); - WORD(highp); - WORD(precision); - WORD(struct); - WORD(uint); - - WORD(common); - WORD(partition); - WORD(active); - WORD(asm); - WORD(class); - WORD(union); - WORD(enum); - WORD(typedef); - WORD(template); - WORD(this); - WORD(resource); - - WORD(goto); - WORD(inline); - WORD(noinline); - WORD(public); - WORD(static); - WORD(extern); - WORD(external); - WORD(interface); - WORD(long); - WORD(short); - WORD(half); - WORD(fixed); - WORD(unsigned); - WORD(superp); - WORD(input); - WORD(output); - WORD(filter); - WORD(sizeof); - WORD(cast); - WORD(namespace); - WORD(using); - -#define CASE(NAME) \ - WORD(NAME ## 2); WORD(NAME ## 3); WORD(NAME ## 4) - - CASE(mat); - CASE(dmat); - CASE(mat2x); - CASE(mat3x); - CASE(mat4x); - CASE(dmat2x); - CASE(dmat3x); - CASE(dmat4x); - CASE(vec); - CASE(ivec); - CASE(bvec); - CASE(dvec); - CASE(uvec); - CASE(hvec); - CASE(fvec); - -#undef CASE - -#define CASE(NAME) \ - WORD(NAME ## 1D); \ - WORD(NAME ## 2D); \ - WORD(NAME ## 3D); \ - WORD(NAME ## Cube); \ - WORD(NAME ## 1DArray); \ - WORD(NAME ## 2DArray); \ - WORD(NAME ## 3DArray); \ - WORD(NAME ## CubeArray);\ - WORD(NAME ## 2DMS); \ - WORD(NAME ## 2DMSArray) \ - /* end */ - -#define CASE2(NAME) \ - CASE(NAME); \ - CASE(i ## NAME); \ - CASE(u ## NAME) \ - /* end */ - - CASE2(sampler); - CASE2(image); - CASE2(texture); - -#undef CASE2 -#undef CASE - break; + #define WORD(NAME) registerReservedWord(#NAME) - default: - break; + switch (context->shared->target) + { + case CodeGenTarget::GLSL: + WORD(attribute); + WORD(const); + WORD(uniform); + WORD(varying); + WORD(buffer); + + WORD(shared); + WORD(coherent); + WORD(volatile); + WORD(restrict); + WORD(readonly); + WORD(writeonly); + WORD(atomic_unit); + WORD(layout); + WORD(centroid); + WORD(flat); + WORD(smooth); + WORD(noperspective); + WORD(patch); + WORD(sample); + WORD(break); + WORD(continue); + WORD(do); + WORD(for); + WORD(while); + WORD(switch); + WORD(case); + WORD(default); + WORD(if); + WORD(else); + WORD(subroutine); + WORD(in); + WORD(out); + WORD(inout); + WORD(float); + WORD(double); + WORD(int); + WORD(void); + WORD(bool); + WORD(true); + WORD(false); + WORD(invariant); + WORD(precise); + WORD(discard); + WORD(return); + + WORD(lowp); + WORD(mediump); + WORD(highp); + WORD(precision); + WORD(struct); + WORD(uint); + + WORD(common); + WORD(partition); + WORD(active); + WORD(asm); + WORD(class); + WORD(union); + WORD(enum); + WORD(typedef); + WORD(template); + WORD(this); + WORD(resource); + + WORD(goto); + WORD(inline); + WORD(noinline); + WORD(public); + WORD(static); + WORD(extern); + WORD(external); + WORD(interface); + WORD(long); + WORD(short); + WORD(half); + WORD(fixed); + WORD(unsigned); + WORD(superp); + WORD(input); + WORD(output); + WORD(filter); + WORD(sizeof); + WORD(cast); + WORD(namespace); + WORD(using); + + #define CASE(NAME) \ + WORD(NAME ## 2); WORD(NAME ## 3); WORD(NAME ## 4) + + CASE(mat); + CASE(dmat); + CASE(mat2x); + CASE(mat3x); + CASE(mat4x); + CASE(dmat2x); + CASE(dmat3x); + CASE(dmat4x); + CASE(vec); + CASE(ivec); + CASE(bvec); + CASE(dvec); + CASE(uvec); + CASE(hvec); + CASE(fvec); + + #undef CASE + + #define CASE(NAME) \ + WORD(NAME ## 1D); \ + WORD(NAME ## 2D); \ + WORD(NAME ## 3D); \ + WORD(NAME ## Cube); \ + WORD(NAME ## 1DArray); \ + WORD(NAME ## 2DArray); \ + WORD(NAME ## 3DArray); \ + WORD(NAME ## CubeArray);\ + WORD(NAME ## 2DMS); \ + WORD(NAME ## 2DMSArray) \ + /* end */ + + #define CASE2(NAME) \ + CASE(NAME); \ + CASE(i ## NAME); \ + CASE(u ## NAME) \ + /* end */ + + CASE2(sampler); + CASE2(image); + CASE2(texture); + + #undef CASE2 + #undef CASE + break; + + default: + break; + } } -} +}; bool isRewriteRequest( SourceLanguage sourceLanguage, @@ -3077,14 +3159,16 @@ String emitEntryPoint( translationUnit->sourceLanguage, target); + EmitVisitor visitor(&context); + // TODO: this should only need to take the shared context - registerReservedWords(&context); + visitor.registerReservedWords(); auto translationUnitSyntax = translationUnit->SyntaxNode.Ptr(); // There may be global-scope modifiers that we should emit now - emitGLSLPreprocessorDirectives(&context, translationUnitSyntax); + visitor.emitGLSLPreprocessorDirectives(translationUnitSyntax); switch(target) { @@ -3101,7 +3185,7 @@ String emitEntryPoint( auto lowered = lowerEntryPoint(entryPoint, programLayout, target); - EmitDeclsInContainer(&context, lowered.program.Ptr()); + visitor.EmitDeclsInContainer(lowered.program.Ptr()); #if 0 if( isRewrite ) diff --git a/source/slang/syntax.h b/source/slang/syntax.h index 4329f6cbd..1704c1224 100644 --- a/source/slang/syntax.h +++ b/source/slang/syntax.h @@ -671,6 +671,11 @@ namespace Slang bool isValid() const { return item.declRef.getDecl() != nullptr; } bool isOverloaded() const { return items.Count() > 1; } + + String const& getName() const + { + return items.Count() > 1 ? items[0].declRef.GetName() : item.declRef.GetName(); + } }; struct SemanticsVisitor; diff --git a/source/slang/visitor.h b/source/slang/visitor.h index eaddc5f32..7000927c7 100644 --- a/source/slang/visitor.h +++ b/source/slang/visitor.h @@ -80,6 +80,33 @@ struct TypeVisitor : Base #include "object-meta-end.h" }; +template +struct TypeVisitorWithArg : Base +{ + void dispatch(ExpressionType* type, Arg const& arg) + { + type->accept(this, (void*)&arg); + } + +#define ABSTRACT_SYNTAX_CLASS(NAME,BASE) /* empty */ +#define SYNTAX_CLASS(NAME, BASE) \ + virtual void dispatch_##NAME(NAME* obj, void* arg) override \ + { ((Derived*) this)->visit##NAME(obj, *(Arg*)arg); } + +#include "object-meta-begin.h" +#include "type-defs.h" +#include "object-meta-end.h" + +#define ABSTRACT_SYNTAX_CLASS(NAME,BASE) SYNTAX_CLASS(NAME, BASE) +#define SYNTAX_CLASS(NAME, BASE) \ + void visit##NAME(NAME* obj, Arg const& arg) \ + { ((Derived*) this)->visit##BASE(obj, arg); } + +#include "object-meta-begin.h" +#include "type-defs.h" +#include "object-meta-end.h" +}; + // // Expression Visitors // @@ -151,6 +178,33 @@ struct ExprVisitor : IExprVisitor #include "object-meta-end.h" }; +template +struct ExprVisitorWithArg : IExprVisitor +{ + void dispatch(ExpressionSyntaxNode* obj, Arg const& arg) + { + obj->accept(this, (void*)&arg); + } + +#define ABSTRACT_SYNTAX_CLASS(NAME,BASE) /* empty */ +#define SYNTAX_CLASS(NAME, BASE) \ + virtual void dispatch_##NAME(NAME* obj, void* arg) override \ + { ((Derived*) this)->visit##NAME(obj, *(Arg*)arg); } + +#include "object-meta-begin.h" +#include "expr-defs.h" +#include "object-meta-end.h" + +#define ABSTRACT_SYNTAX_CLASS(NAME,BASE) SYNTAX_CLASS(NAME, BASE) +#define SYNTAX_CLASS(NAME, BASE) \ + void visit##NAME(NAME* obj, Arg const& arg) \ + { ((Derived*) this)->visit##BASE(obj, arg); } + +#include "object-meta-begin.h" +#include "expr-defs.h" +#include "object-meta-end.h" +}; + // // Statement Visitors // @@ -293,6 +347,34 @@ struct DeclVisitor : IDeclVisitor #include "object-meta-end.h" }; +template +struct DeclVisitorWithArg : IDeclVisitor +{ + void dispatch(DeclBase* obj, Arg const& arg) + { + obj->accept(this, (void*)&arg); + } + +#define ABSTRACT_SYNTAX_CLASS(NAME,BASE) /* empty */ +#define SYNTAX_CLASS(NAME, BASE) \ + virtual void dispatch_##NAME(NAME* obj, void* arg) override \ + { ((Derived*) this)->visit##NAME(obj, *(Arg*)arg); } + +#include "object-meta-begin.h" +#include "decl-defs.h" +#include "object-meta-end.h" + +#define ABSTRACT_SYNTAX_CLASS(NAME,BASE) SYNTAX_CLASS(NAME, BASE) +#define SYNTAX_CLASS(NAME, BASE) \ + void visit##NAME(NAME* obj, Arg const& arg) \ + { ((Derived*) this)->visit##BASE(obj, arg); } + +#include "object-meta-begin.h" +#include "decl-defs.h" +#include "object-meta-end.h" +}; + + // // Modifier Visitors // -- cgit v1.2.3