diff options
| author | Tim Foley <tfoley@nvidia.com> | 2017-08-31 14:13:28 -0700 |
|---|---|---|
| committer | Tim Foley <tfoley@nvidia.com> | 2017-09-05 12:15:28 -0700 |
| commit | ff7c190b838dffc79e5acd555f41506cb52a9f47 (patch) | |
| tree | a4543443ab301cf946089904e889d7018b08252e /source/slang/emit.cpp | |
| parent | 620734080f825cb205b887fa1d6203e35dd60663 (diff) | |
Move implicit conversion operations to stdlib
- Previously, there were a variety of rules in `check.cpp` to pick the conversion cost for various cases involving scalar, vector, and matrix types.
- The main problem of the previous approach is that any lowering pass would need to convert an arbitrary "type cast" node into the right low-level operation(s).
- The new approach is that a type conversion (implicit or explicit) always resolves as a call to a constructor/initializer for the destination type. This means that the existing rules around marking operations as builtins should work for lowering.
- The support this, the checking logic needs to perform lookup of intializers/constructors when asked to perform conversion between types. It does this by re-using the existing logic for lookup and overload resolution if/when a type was applied in an ordinary context.
- Next, we define a modifier that can be attached to constructors/initializers to mark them as suitable for implicit conversion, and associate them with the correct cost to be used when doing overload comparisons.
- We add the modifier to all the scalar-to-scalar cases in the stdlib, using the logic that previously existed in semantic checking.
- Next we add cases for general vector-to-scalar conversions that also convert type, using the same cost computation as above.
- This probably misses various cases, but at this point they can hopefully be added just in the stdlib.
- One gotcha here is that in lowering, we need to make sure to lower any kind of call expression to another call expression of the same AST node class, so that we don't lose information on what casts were implicit/hidden in teh source-to-source case.
Two notes for potential longer-term changes:
1. There is still some duplication between the type conversion declarations here and the "join" logic for types used for generic arguments. Ideally we'd eventually clean up the "join" logic to be based on convertability, but that isn't a high priority right now, as long as joins continue to pick the right type.
2. It is a bit gross to have to declare all the N^2 conversions for vector/matrix types to duplicate the cases for scalars. For the simple scalar-to-vector case, we might try to support multiple conversion "steps" where both a scalar-to-scalar and a scalar-to-vector step can be allowed (this could be tagged on the modifiers already introduced). That simple option doesn't scale to vector-to-vector element type conversions, though, where you'd really want to make it a generic with a constraint like:
vector<T,N> init<U>(vector<U,N> value) where T : ConvertibleFrom<U>;
Here the `ConvertibleFrom<U>` interface expresses the fact that a conforming type has an initializer that takes a `U`. What doesn't appear in this context is any notion of conversion costs. We'd need some kind of system for computing the conversion cost of the vector conversion from the cost of the `T` to `U` converion.
Diffstat (limited to 'source/slang/emit.cpp')
| -rw-r--r-- | source/slang/emit.cpp | 96 |
1 files changed, 72 insertions, 24 deletions
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index 6440c63a3..2deb0cea7 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -1367,7 +1367,7 @@ struct EmitVisitor { if(auto typeCastExpr = expr.As<TypeCastExpr>()) { - expr = typeCastExpr->Expression; + expr = typeCastExpr->Arguments[0]; } // TODO: any other cases? else @@ -1519,14 +1519,66 @@ struct EmitVisitor return bestModifier; } + void emitSimpleCallArgs( + RefPtr<InvokeExpr> callExpr) + { + Emit("("); + UInt argCount = callExpr->Arguments.Count(); + for (UInt aa = 0; aa < argCount; ++aa) + { + if (aa != 0) Emit(", "); + EmitExpr(callExpr->Arguments[aa]); + } + Emit(")"); + } + + void emitSimpleConstructorCallExpr( + RefPtr<InvokeExpr> callExpr, + EOpInfo outerPrec) + { + if(context->shared->target == CodeGenTarget::HLSL) + { + // HLSL needs to special-case a constructor call with a single argument. + if(callExpr->Arguments.Count() == 1) + { + auto prec = kEOp_Prefix; + bool needClose = MaybeEmitParens(outerPrec, prec); + + Emit("("); + EmitType(callExpr->type); + Emit(") "); + + EmitExprWithPrecedence(callExpr->Arguments[0], rightSide(outerPrec, prec)); + + if(needClose) Emit(")"); + return; + } + } + + + // Default handling is to emit what amounts to an ordinary call, + // but using the type of the expression directly as the "function" to call. + auto prec = kEOp_Postfix; + bool needClose = MaybeEmitParens(outerPrec, prec); + + EmitType(callExpr->type); + + emitSimpleCallArgs(callExpr); + + if (needClose) + { + Emit(")"); + } + } + // Emit a call expression that doesn't involve any special cases, // just an expression of the form `f(a0, a1, ...)` void emitSimpleCallExpr( RefPtr<InvokeExpr> callExpr, EOpInfo outerPrec) { - auto prec = kEOp_Postfix; - bool needClose = MaybeEmitParens(outerPrec, prec); + // We will first check if this represents a constructor call, + // since those may need to be handled differently. auto funcExpr = callExpr->FunctionExpr; if (auto funcDeclRefExpr = funcExpr.As<DeclRefExpr>()) @@ -1534,29 +1586,20 @@ struct EmitVisitor auto declRef = funcDeclRefExpr->declRef; if (auto ctorDeclRef = declRef.As<ConstructorDecl>()) { - // We really want to emit a reference to the type begin constructed - EmitType(callExpr->type); - } - else - { - // default case: just emit the decl ref - EmitExprWithPrecedence(funcExpr, leftSide(outerPrec, prec)); + emitSimpleConstructorCallExpr(callExpr, outerPrec); + return; } } - else - { - // default case: just emit the expression - EmitExprWithPrecedence(funcExpr, leftSide(outerPrec, prec)); - } - Emit("("); - UInt argCount = callExpr->Arguments.Count(); - for (UInt aa = 0; aa < argCount; ++aa) - { - if (aa != 0) Emit(", "); - EmitExpr(callExpr->Arguments[aa]); - } - Emit(")"); + // Once we've ruled out constructor calls, we can move on + // to just emitting an ordinary calll expression. + + auto prec = kEOp_Postfix; + bool needClose = MaybeEmitParens(outerPrec, prec); + + EmitExprWithPrecedence(funcExpr, leftSide(outerPrec, prec)); + + emitSimpleCallArgs(callExpr); if (needClose) { @@ -2416,11 +2459,15 @@ struct EmitVisitor { // This was an implicit cast inserted in code parsed in "rewriter" mode, // so we don't want to output it and change what the user's code looked like. - ExprVisitorWithArg::dispatch(castExpr->Expression, arg); + ExprVisitorWithArg::dispatch(castExpr->Arguments[0], arg); } void visitTypeCastExpr(TypeCastExpr* castExpr, ExprEmitArg const& arg) { + // We emit a type cast expression as a constructor call + emitSimpleConstructorCallExpr(castExpr, arg.outerPrec); + +#if 0 bool needClose = false; switch(context->shared->target) { @@ -2449,6 +2496,7 @@ struct EmitVisitor break; } if(needClose) Emit(")"); +#endif } void visitInitializerListExpr(InitializerListExpr* expr, ExprEmitArg const&) |
