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 | |
| 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')
| -rw-r--r-- | source/slang/check.cpp | 454 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 96 | ||||
| -rw-r--r-- | source/slang/expr-defs.h | 6 | ||||
| -rw-r--r-- | source/slang/lower.cpp | 42 | ||||
| -rw-r--r-- | source/slang/modifier-defs.h | 8 | ||||
| -rw-r--r-- | source/slang/parser.cpp | 20 | ||||
| -rw-r--r-- | source/slang/slang-stdlib.cpp | 209 | ||||
| -rw-r--r-- | source/slang/syntax-base-defs.h | 12 | ||||
| -rw-r--r-- | source/slang/syntax.cpp | 6 | ||||
| -rw-r--r-- | source/slang/syntax.h | 45 |
10 files changed, 667 insertions, 231 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp index cc2954fc4..ce9b2de55 100644 --- a/source/slang/check.cpp +++ b/source/slang/check.cpp @@ -140,14 +140,14 @@ namespace Slang } RefPtr<Expr> ConstructDeclRefExpr( - DeclRef<Decl> declRef, + DeclRef<Decl> declRef, RefPtr<Expr> baseExpr, - RefPtr<Expr> originalExpr) + SourceLoc loc) { if (baseExpr) { auto expr = new MemberExpr(); - expr->loc = originalExpr->loc; + expr->loc = loc; expr->BaseExpression = baseExpr; expr->name = declRef.GetName(); expr->type = GetTypeForDeclRef(declRef); @@ -157,7 +157,7 @@ namespace Slang else { auto expr = new VarExpr(); - expr->loc = originalExpr->loc; + expr->loc = loc; expr->name = declRef.GetName(); expr->type = GetTypeForDeclRef(declRef); expr->declRef = declRef; @@ -166,14 +166,14 @@ namespace Slang } RefPtr<Expr> ConstructDerefExpr( - RefPtr<Expr> base, - RefPtr<Expr> originalExpr) + RefPtr<Expr> base, + SourceLoc loc) { auto ptrLikeType = base->type->As<PointerLikeType>(); SLANG_ASSERT(ptrLikeType); auto derefExpr = new DerefExpr(); - derefExpr->loc = originalExpr->loc; + derefExpr->loc = loc; derefExpr->base = base; derefExpr->type = QualType(ptrLikeType->elementType); @@ -183,9 +183,9 @@ namespace Slang } RefPtr<Expr> ConstructLookupResultExpr( - LookupResultItem const& item, - RefPtr<Expr> baseExpr, - RefPtr<Expr> originalExpr) + LookupResultItem const& item, + RefPtr<Expr> baseExpr, + SourceLoc loc) { // If we collected any breadcrumbs, then these represent // additional segments of the lookup path that we need @@ -196,28 +196,28 @@ namespace Slang switch (breadcrumb->kind) { case LookupResultItem::Breadcrumb::Kind::Member: - bb = ConstructDeclRefExpr(breadcrumb->declRef, bb, originalExpr); + bb = ConstructDeclRefExpr(breadcrumb->declRef, bb, loc); break; case LookupResultItem::Breadcrumb::Kind::Deref: - bb = ConstructDerefExpr(bb, originalExpr); + bb = ConstructDerefExpr(bb, loc); break; default: SLANG_UNREACHABLE("all cases handle"); } } - return ConstructDeclRefExpr(item.declRef, bb, originalExpr); + return ConstructDeclRefExpr(item.declRef, bb, loc); } RefPtr<Expr> createLookupResultExpr( - LookupResult const& lookupResult, - RefPtr<Expr> baseExpr, - RefPtr<Expr> originalExpr) + LookupResult const& lookupResult, + RefPtr<Expr> baseExpr, + SourceLoc loc) { if (lookupResult.isOverloaded()) { auto overloadedExpr = new OverloadedExpr(); - overloadedExpr->loc = originalExpr->loc; + overloadedExpr->loc = loc; overloadedExpr->type = QualType( getSession()->getOverloadedType()); overloadedExpr->base = baseExpr; @@ -226,7 +226,7 @@ namespace Slang } else { - return ConstructLookupResultExpr(lookupResult.item, baseExpr, originalExpr); + return ConstructLookupResultExpr(lookupResult.item, baseExpr, loc); } } @@ -264,7 +264,7 @@ namespace Slang } // otherwise, we had a single decl and it was valid, hooray! - return ConstructLookupResultExpr(lookupResult.item, overloadedExpr->base, overloadedExpr); + return ConstructLookupResultExpr(lookupResult.item, overloadedExpr->base, overloadedExpr->loc); } RefPtr<Expr> ExpectATypeRepr(RefPtr<Expr> expr) @@ -583,90 +583,6 @@ namespace Slang public: - typedef unsigned int ConversionCost; - enum : ConversionCost - { - // No conversion at all - kConversionCost_None = 0, - - // Conversions based on explicit sub-typing relationships are the cheapest - // - // TODO(tfoley): We will eventually need a discipline for ranking - // when two up-casts are comparable. - kConversionCost_CastToInterface = 50, - - // Conversion that is lossless and keeps the "kind" of the value the same - kConversionCost_RankPromotion = 100, - - // Conversions that are lossless, but change "kind" - kConversionCost_UnsignedToSignedPromotion = 200, - - // Conversion from signed->unsigned integer of same or greater size - kConversionCost_SignedToUnsignedConversion = 300, - - // Cost of converting an integer to a floating-point type - kConversionCost_IntegerToFloatConversion = 400, - - // Catch-all for conversions that should be discouraged - // (i.e., that really shouldn't be made implicitly) - // - // TODO: make these conversions not be allowed implicitly in "Slang mode" - kConversionCost_GeneralConversion = 900, - - // Additional conversion cost to add when promoting from a scalar to - // a vector (this will be added to the cost, if any, of converting - // the element type of the vector) - kConversionCost_ScalarToVector = 1, - }; - - enum BaseTypeConversionKind : uint8_t - { - kBaseTypeConversionKind_Signed, - kBaseTypeConversionKind_Unsigned, - kBaseTypeConversionKind_Float, - kBaseTypeConversionKind_Error, - }; - - enum BaseTypeConversionRank : uint8_t - { - kBaseTypeConversionRank_Bool, - kBaseTypeConversionRank_Int8, - kBaseTypeConversionRank_Int16, - kBaseTypeConversionRank_Int32, - kBaseTypeConversionRank_IntPtr, - kBaseTypeConversionRank_Int64, - kBaseTypeConversionRank_Error, - }; - - struct BaseTypeConversionInfo - { - BaseTypeConversionKind kind; - BaseTypeConversionRank rank; - }; - static BaseTypeConversionInfo GetBaseTypeConversionInfo(BaseType baseType) - { - switch (baseType) - { - #define CASE(TAG, KIND, RANK) \ - case BaseType::TAG: { BaseTypeConversionInfo info = {kBaseTypeConversionKind_##KIND, kBaseTypeConversionRank_##RANK}; return info; } break - - CASE(Bool, Unsigned, Bool); - CASE(Int, Signed, Int32); - CASE(UInt, Unsigned, Int32); - CASE(UInt64, Unsigned, Int64); - CASE(Half, Float, Int16); - CASE(Float, Float, Int32); - CASE(Double, Float, Int64); - CASE(Void, Error, Error); - - #undef CASE - - default: - break; - } - SLANG_UNREACHABLE("all cases handled"); - } - bool ValuesAreEqual( RefPtr<IntVal> left, RefPtr<IntVal> right) @@ -692,6 +608,19 @@ namespace Slang return false; } + // Compute the cost of using a particular declaration to + // perform implicit type conversion. + ConversionCost getImplicitConversionCost( + Decl* decl) + { + if(auto modifier = decl->FindModifier<ImplicitConversionModifier>()) + { + return modifier->cost; + } + + return kConversionCost_Explicit; + } + // Central engine for implementing implicit coercion logic bool TryCoerceImpl( RefPtr<Type> toType, // the target type for conversion @@ -836,6 +765,7 @@ namespace Slang // +#if 0 if (auto toBasicType = toType->AsBasicType()) { if (auto fromBasicType = fromType->AsBasicType()) @@ -951,6 +881,7 @@ namespace Slang } } } +#endif if (auto toDeclRefType = toType->As<DeclRefType>()) { @@ -971,7 +902,97 @@ namespace Slang } } - // TODO: more cases! + // Look for an initializer/constructor declaration in the target type, + // which is marked as usable for implicit conversion, and which takes + // the source type as an argument. + + OverloadResolveContext overloadContext; + + List<RefPtr<Expr>> args; + args.Add(fromExpr); + + overloadContext.disallowNestedConversions = true; + overloadContext.argCount = 1; + overloadContext.argTypes = &fromType; + + overloadContext.originalExpr = nullptr; + if(fromExpr) + { + overloadContext.loc = fromExpr->loc; + overloadContext.funcLoc = fromExpr->loc; + overloadContext.args = &fromExpr; + } + + overloadContext.baseExpr = nullptr; + overloadContext.mode = OverloadResolveContext::Mode::JustTrying; + + AddTypeOverloadCandidates(toType, overloadContext); + + if(overloadContext.bestCandidates.Count() != 0) + { + // There were multiple candidates that were equally good. + + // First, we will check if these candidates are even applicable. + // If they aren't, then they can't be used for conversion. + if(overloadContext.bestCandidates[0].status != OverloadCandidate::Status::Appicable) + return false; + + // If we reach this point, then we have multiple candidates which are + // all equally applicable, which means we have an ambiguity. + // If the user is just querying whether a conversion is possible, we + // will tell them it is, because ambiguity should trigger an ambiguity + // error, and not a "no conversion possible" error. + + // We will compute a nominal conversion cost as the minimum over + // all the conversions available. + ConversionCost cost = kConversionCost_GeneralConversion; + for(auto candidate : overloadContext.bestCandidates) + { + ConversionCost candidateCost = getImplicitConversionCost( + candidate.item.declRef.getDecl()); + + if(candidateCost < cost) + cost = candidateCost; + } + + if(outCost) + *outCost = cost; + + if(outToExpr) + { + // The user is asking for us to actually perform the conversion, + // so we need to generate an appropriate expression here. + + throw "foo bar baz"; + } + + return true; + } + else if(overloadContext.bestCandidate) + { + // There is a single best candidate for conversion. + + // It might not actually be usable, so let's check that first. + if(overloadContext.bestCandidate->status != OverloadCandidate::Status::Appicable) + return false; + + // Okay, it is applicable, and we just need to let the user + // know about it, and optionally construct a call. + + // We need to extract the conversion cost from the candidate we found. + ConversionCost cost = getImplicitConversionCost( + overloadContext.bestCandidate->item.declRef.getDecl());; + + if(outCost) + *outCost = cost; + + if(outToExpr) + { + *outToExpr = CompleteOverloadCandidate(overloadContext, *overloadContext.bestCandidate); + } + + return true; + } return false; } @@ -991,7 +1012,7 @@ namespace Slang } RefPtr<Expr> CreateImplicitCastExpr( - RefPtr<Type> toType, + RefPtr<Type> toType, RefPtr<Expr> fromExpr) { // In "rewrite" mode, we will generate a different syntax node @@ -1007,10 +1028,17 @@ namespace Slang castExpr = new ImplicitCastExpr(); } + auto typeType = new TypeType(); + typeType->type = toType; + + auto typeExpr = new SharedTypeExpr(); + typeExpr->type.type = typeType; + typeExpr->base.type = toType; + castExpr->loc = fromExpr->loc; - castExpr->TargetType.type = toType; + castExpr->FunctionExpr = typeExpr; castExpr->type = QualType(toType); - castExpr->Expression = fromExpr; + castExpr->Arguments.Add(fromExpr); return castExpr; } @@ -2272,7 +2300,7 @@ namespace Slang } else if(auto castExpr = dynamic_cast<TypeCastExpr*>(expr)) { - auto val = TryConstantFoldExpr(castExpr->Expression.Ptr()); + auto val = TryConstantFoldExpr(castExpr->Arguments[0].Ptr()); if(val) return val; } @@ -2457,7 +2485,7 @@ namespace Slang } RefPtr<Expr> subscriptFuncExpr = createLookupResultExpr( - lookupResult, subscriptExpr->BaseExpression, subscriptExpr); + lookupResult, subscriptExpr->BaseExpression, subscriptExpr->loc); // Now that we know there is at least one subscript member, // we will construct a reference to it and try to call it @@ -2982,7 +3010,32 @@ namespace Slang ForReal, }; - RefPtr<AppExprBase> appExpr; + // Location to use when reporting overload-resolution errors. + SourceLoc loc; + + // The original expression (if any) that triggered things + RefPtr<Expr> originalExpr; + + // Source location of the "function" part of the expression, if any + SourceLoc funcLoc; + + // The original arguments to the call + UInt argCount = 0; + RefPtr<Expr>* args = nullptr; + RefPtr<Type>* argTypes = nullptr; + + UInt getArgCount() { return argCount; } + RefPtr<Expr>& getArg(UInt index) { return args[index]; } + RefPtr<Type>& getArgType(UInt index) + { + if(argTypes) + return argTypes[index]; + else + return getArg(index)->type.type; + } + + bool disallowNestedConversions = false; + RefPtr<Expr> baseExpr; // Are we still trying out candidates, or are we @@ -3060,7 +3113,7 @@ namespace Slang OverloadResolveContext& context, OverloadCandidate const& candidate) { - UInt argCount = context.appExpr->Arguments.Count(); + UInt argCount = context.getArgCount(); ParamCounts paramCounts = { 0, 0 }; switch (candidate.flavor) { @@ -3087,7 +3140,7 @@ namespace Slang { if (!isRewriteMode()) { - getSink()->diagnose(context.appExpr, Diagnostics::notEnoughArguments, argCount, paramCounts.required); + getSink()->diagnose(context.loc, Diagnostics::notEnoughArguments, argCount, paramCounts.required); } } else @@ -3095,7 +3148,7 @@ namespace Slang SLANG_ASSERT(argCount > paramCounts.allowed); if (!isRewriteMode()) { - getSink()->diagnose(context.appExpr, Diagnostics::tooManyArguments, argCount, paramCounts.allowed); + getSink()->diagnose(context.loc, Diagnostics::tooManyArguments, argCount, paramCounts.allowed); } } } @@ -3107,7 +3160,7 @@ namespace Slang OverloadResolveContext& context, OverloadCandidate const& candidate) { - auto expr = context.appExpr; + auto expr = context.originalExpr; auto decl = candidate.item.declRef.decl; @@ -3120,7 +3173,7 @@ namespace Slang { if (!isRewriteMode()) { - getSink()->diagnose(context.appExpr, Diagnostics::expectedPrefixOperator); + getSink()->diagnose(context.loc, Diagnostics::expectedPrefixOperator); getSink()->diagnose(decl, Diagnostics::seeDefinitionOf, decl->getName()); } } @@ -3136,7 +3189,7 @@ namespace Slang { if (!isRewriteMode()) { - getSink()->diagnose(context.appExpr, Diagnostics::expectedPostfixOperator); + getSink()->diagnose(context.loc, Diagnostics::expectedPostfixOperator); getSink()->diagnose(decl, Diagnostics::seeDefinitionOf, decl->getName()); } } @@ -3155,8 +3208,6 @@ namespace Slang OverloadResolveContext& context, OverloadCandidate& candidate) { - auto& args = context.appExpr->Arguments; - auto genericDeclRef = candidate.item.declRef.As<GenericDecl>(); int aa = 0; @@ -3164,7 +3215,7 @@ namespace Slang { if (auto typeParamRef = memberRef.As<GenericTypeParamDecl>()) { - auto arg = args[aa++]; + auto arg = context.getArg(aa++); if (context.mode == OverloadResolveContext::Mode::JustTrying) { @@ -3180,7 +3231,7 @@ namespace Slang } else if (auto valParamRef = memberRef.As<GenericValueParamDecl>()) { - auto arg = args[aa++]; + auto arg = context.getArg(aa++); if (context.mode == OverloadResolveContext::Mode::JustTrying) { @@ -3210,8 +3261,7 @@ namespace Slang OverloadResolveContext& context, OverloadCandidate& candidate) { - auto& args = context.appExpr->Arguments; - UInt argCount = args.Count(); + UInt argCount = context.getArgCount(); List<DeclRef<ParamDecl>> params; switch (candidate.flavor) @@ -3234,13 +3284,20 @@ namespace Slang for (UInt ii = 0; ii < argCount; ++ii) { - auto& arg = args[ii]; + auto& arg = context.getArg(ii); + auto argType = context.getArgType(ii); auto param = params[ii]; if (context.mode == OverloadResolveContext::Mode::JustTrying) { ConversionCost cost = kConversionCost_None; - if (!CanCoerce(GetType(param), arg->type, &cost)) + if( context.disallowNestedConversions ) + { + // We need an exact match in this case. + if(!GetType(param)->Equals(argType)) + return false; + } + else if (!CanCoerce(GetType(param), argType, &cost)) { return false; } @@ -3288,28 +3345,31 @@ namespace Slang // Create the representation of a given generic applied to some arguments RefPtr<Expr> CreateGenericDeclRef( - RefPtr<Expr> baseExpr, - RefPtr<AppExprBase> appExpr) + RefPtr<Expr> baseExpr, + RefPtr<Expr> originalExpr, + UInt argCount, + RefPtr<Expr> const* args) { auto baseDeclRefExpr = baseExpr.As<DeclRefExpr>(); if (!baseDeclRefExpr) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), baseExpr, "expected a reference to a generic declaration"); - return CreateErrorExpr(appExpr.Ptr()); + return CreateErrorExpr(originalExpr); } auto baseGenericRef = baseDeclRefExpr->declRef.As<GenericDecl>(); if (!baseGenericRef) { SLANG_DIAGNOSE_UNEXPECTED(getSink(), baseExpr, "expected a reference to a generic declaration"); - return CreateErrorExpr(appExpr.Ptr()); + return CreateErrorExpr(originalExpr); } RefPtr<Substitutions> subst = new Substitutions(); subst->genericDecl = baseGenericRef.getDecl(); subst->outer = baseGenericRef.substitutions; - for (auto arg : appExpr->Arguments) + for(UInt aa = 0; aa < argCount; ++aa) { + auto arg = args[aa]; subst->args.Add(ExtractGenericArgVal(arg)); } @@ -3318,7 +3378,7 @@ namespace Slang return ConstructDeclRefExpr( innerDeclRef, nullptr, - appExpr); + originalExpr->loc); } // Take an overload candidate that previously got through @@ -3334,11 +3394,11 @@ namespace Slang // special case for generic argument inference failure if (candidate.status == OverloadCandidate::Status::GenericArgumentInferenceFailed) { - String callString = GetCallSignatureString(context.appExpr); + String callString = getCallSignatureString(context); if (!isRewriteMode()) { getSink()->diagnose( - context.appExpr, + context.loc, Diagnostics::genericArgumentInferenceFailed, callString); @@ -3349,7 +3409,6 @@ namespace Slang } context.mode = OverloadResolveContext::Mode::ForReal; - context.appExpr->type = QualType(getSession()->getErrorType()); if (!TryCheckOverloadCandidateArity(context, candidate)) goto error; @@ -3365,41 +3424,66 @@ namespace Slang { auto baseExpr = ConstructLookupResultExpr( - candidate.item, context.baseExpr, context.appExpr->FunctionExpr); + candidate.item, context.baseExpr, context.funcLoc); switch(candidate.flavor) { case OverloadCandidate::Flavor::Func: - context.appExpr->FunctionExpr = baseExpr; - context.appExpr->type = QualType(candidate.resultType); - - // A call may yield an l-value, and we should take a look at the candidate to be sure - if(auto subscriptDeclRef = candidate.item.declRef.As<SubscriptDecl>()) { - for(auto setter : subscriptDeclRef.getDecl()->getMembersOfType<SetterDecl>()) + RefPtr<AppExprBase> callExpr = context.originalExpr.As<InvokeExpr>(); + if(!callExpr) { - context.appExpr->type.IsLeftValue = true; + callExpr = new InvokeExpr(); + callExpr->loc = context.loc; + + for(UInt aa = 0; aa < context.argCount; ++aa) + callExpr->Arguments.Add(context.getArg(aa)); + } + + + callExpr->FunctionExpr = baseExpr; + callExpr->type = QualType(candidate.resultType); + + // A call may yield an l-value, and we should take a look at the candidate to be sure + if(auto subscriptDeclRef = candidate.item.declRef.As<SubscriptDecl>()) + { + for(auto setter : subscriptDeclRef.getDecl()->getMembersOfType<SetterDecl>()) + { + callExpr->type.IsLeftValue = true; + } } - } - // TODO: there may be other cases that confer l-value-ness + // TODO: there may be other cases that confer l-value-ness + + return callExpr; + } - return context.appExpr; break; case OverloadCandidate::Flavor::Generic: - return CreateGenericDeclRef(baseExpr, context.appExpr); + return CreateGenericDeclRef(baseExpr, context.originalExpr, + context.argCount, + context.args); break; default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), context.appExpr, "unknown overload candidate flavor"); + SLANG_DIAGNOSE_UNEXPECTED(getSink(), context.loc, "unknown overload candidate flavor"); break; } } error: - return CreateErrorExpr(context.appExpr.Ptr()); + + if(context.originalExpr) + { + return CreateErrorExpr(context.originalExpr.Ptr()); + } + else + { + SLANG_DIAGNOSE_UNEXPECTED(getSink(), context.loc, "no original expression for overload result"); + return nullptr; + } } // Implement a comparison operation between overload candidates, @@ -3859,6 +3943,7 @@ namespace Slang } } +#if 0 bool TryUnifyArgAndParamTypes( ConstraintSystem& system, RefPtr<Expr> argExpr, @@ -3869,6 +3954,7 @@ namespace Slang // an overload group... return TryUnifyTypes(system, argExpr->type, GetType(paramDeclRef)); } +#endif // Take a generic declaration and try to specialize its parameters // so that the resulting inner declaration can be applicable in @@ -3889,10 +3975,9 @@ namespace Slang // to match it up with the arguments accordingly... if (auto funcDeclRef = unspecializedInnerRef.As<CallableDecl>()) { - auto& args = context.appExpr->Arguments; auto params = GetParameters(funcDeclRef).ToArray(); - UInt argCount = args.Count(); + UInt argCount = context.getArgCount(); UInt paramCount = params.Count(); // Bail out on mismatch. @@ -3926,7 +4011,7 @@ namespace Slang // So the question is then whether a mismatch during the // unification step should be taken as an immediate failure... - TryUnifyArgAndParamTypes(constraints, args[aa], params[aa]); + TryUnifyTypes(constraints, context.getArgType(aa), GetType(params[aa])); #endif } } @@ -4225,21 +4310,28 @@ namespace Slang return getDeclSignatureString(item.declRef); } - String GetCallSignatureString(RefPtr<AppExprBase> expr) + String getCallSignatureString( + OverloadResolveContext& context) { - StringBuilder argsListBuilder; + StringBuilder argsListBuilder; argsListBuilder << "("; - bool first = true; - for (auto a : expr->Arguments) + + UInt argCount = context.getArgCount(); + for( UInt aa = 0; aa < argCount; ++aa ) { - if (!first) argsListBuilder << ", "; - argsListBuilder << a->type->ToString(); - first = false; + if(aa != 0) argsListBuilder << ", "; + argsListBuilder << context.getArgType(aa)->ToString(); } argsListBuilder << ")"; return argsListBuilder.ProduceString(); } +#if 0 + String GetCallSignatureString(RefPtr<AppExprBase> expr) + { + return getCallSignatureString(expr->Arguments); + } +#endif RefPtr<Expr> ResolveInvoke(InvokeExpr * expr) { @@ -4262,7 +4354,14 @@ namespace Slang } OverloadResolveContext context; - context.appExpr = expr; + + context.originalExpr = expr; + context.funcLoc = funcExpr->loc; + + context.argCount = expr->Arguments.Count(); + context.args = expr->Arguments.Buffer(); + context.loc = expr->loc; + if (auto funcMemberExpr = funcExpr.As<MemberExpr>()) { context.baseExpr = funcMemberExpr->BaseExpression; @@ -4297,7 +4396,7 @@ namespace Slang else if(auto baseMemberRef = funcExpr.As<MemberExpr>()) funcName = baseMemberRef->name; - String argsList = GetCallSignatureString(expr); + String argsList = getCallSignatureString(context); if (context.bestCandidates[0].status != OverloadCandidate::Status::Appicable) { @@ -4457,7 +4556,12 @@ namespace Slang // Otherwise, let's start looking at how to find an overload... OverloadResolveContext context; - context.appExpr = genericAppExpr; + context.originalExpr = genericAppExpr; + context.funcLoc = baseExpr->loc; + context.argCount = args.Count(); + context.args = args.Buffer(); + context.loc = genericAppExpr->loc; + context.baseExpr = GetBaseExpr(baseExpr); AddGenericOverloadCandidates(baseExpr, context); @@ -4606,7 +4710,7 @@ namespace Slang return createLookupResultExpr( lookupResult, nullptr, - expr); + expr->loc); } if (!isRewriteMode()) @@ -4619,6 +4723,29 @@ namespace Slang RefPtr<Expr> visitTypeCastExpr(TypeCastExpr * expr) { + // Check the term we are applying first + auto funcExpr = expr->FunctionExpr; + funcExpr = CheckTerm(funcExpr); + + // Now ensure that the term represnets a (proper) type. + TypeExp typeExp; + typeExp.exp = funcExpr; + typeExp = CheckProperType(typeExp); + + expr->FunctionExpr = typeExp.exp; + expr->type.type = typeExp.type; + + // Next check the argument expression (there should be only one) + for (auto & arg : expr->Arguments) + { + arg = CheckExpr(arg); + } + + // Now process this like any other explicit call (so casts + // and constructor calls are semantically equivalent). + return CheckInvokeExprWithCheckedOperands(expr); + +#if 0 expr->Expression = CheckTerm(expr->Expression); auto targetType = CheckProperType(expr->TargetType); expr->TargetType = targetType; @@ -4659,6 +4786,7 @@ namespace Slang } expr->type = QualType(getSession()->getErrorType()); return expr; +#endif } // Get the type to use when referencing a declaration @@ -4906,7 +5034,7 @@ namespace Slang return createLookupResultExpr( lookupResult, expr->BaseExpression, - expr); + expr->loc); } } } @@ -4928,7 +5056,7 @@ namespace Slang return createLookupResultExpr( lookupResult, expr->BaseExpression, - expr); + expr->loc); } // catch-all 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&) diff --git a/source/slang/expr-defs.h b/source/slang/expr-defs.h index 5e3d23a02..5821bddc3 100644 --- a/source/slang/expr-defs.h +++ b/source/slang/expr-defs.h @@ -102,9 +102,9 @@ SYNTAX_CLASS(DerefExpr, Expr) END_SYNTAX_CLASS() // Any operation that performs type-casting -SYNTAX_CLASS(TypeCastExpr, Expr) - SYNTAX_FIELD(TypeExp, TargetType) - SYNTAX_FIELD(RefPtr<Expr>, Expression) +SYNTAX_CLASS(TypeCastExpr, InvokeExpr) +// SYNTAX_FIELD(TypeExp, TargetType) +// SYNTAX_FIELD(RefPtr<Expr>, Expression) END_SYNTAX_CLASS() // An explicit type-cast that appear in the user's code with `(type) expr` syntax diff --git a/source/slang/lower.cpp b/source/slang/lower.cpp index 7a487d29f..f0d756a02 100644 --- a/source/slang/lower.cpp +++ b/source/slang/lower.cpp @@ -1781,19 +1781,9 @@ struct LoweringVisitor LoweredExpr visitInvokeExpr( InvokeExpr* expr) { - return LoweredExpr(lowerCallExpr(new InvokeExpr(), expr)); - } - - LoweredExpr visitInfixExpr( - InfixExpr* expr) - { - return LoweredExpr(lowerCallExpr(new InfixExpr(), expr)); - } - - LoweredExpr visitPrefixExpr( - PrefixExpr* expr) - { - return LoweredExpr(lowerCallExpr(new PrefixExpr(), expr)); + // Create a clone with the same class + InvokeExpr* loweredExpr = (InvokeExpr*) expr->getClass().createInstance(); + return LoweredExpr(lowerCallExpr(loweredExpr, expr)); } LoweredExpr visitSelectExpr( @@ -1804,12 +1794,6 @@ struct LoweringVisitor return LoweredExpr(lowerCallExpr(new SelectExpr(), expr)); } - LoweredExpr visitPostfixExpr( - PostfixExpr* expr) - { - return LoweredExpr(lowerCallExpr(new PostfixExpr(), expr)); - } - LoweredExpr visitDerefExpr( DerefExpr* expr) { @@ -2412,15 +2396,29 @@ struct LoweringVisitor assign(expr, LoweredExpr(createVarRef(getPosition(expr), varDecl))); } + RefPtr<Expr> createTypeExpr( + RefPtr<Type> type) + { + auto typeType = new TypeType(); + typeType->type = type; + + auto result = new SharedTypeExpr(); + result->base.type = type; + result->type.type = typeType; + + return result; + } + RefPtr<Expr> createCastExpr( - RefPtr<Type> type, + RefPtr<Type> type, RefPtr<Expr> expr) { RefPtr<ExplicitCastExpr> castExpr = new ExplicitCastExpr(); castExpr->loc = expr->loc; castExpr->type.type = type; - castExpr->TargetType.type = type; - castExpr->Expression = expr; + + castExpr->FunctionExpr = createTypeExpr(type); + castExpr->Arguments.Add(expr); return castExpr; } diff --git a/source/slang/modifier-defs.h b/source/slang/modifier-defs.h index ddfb9c410..597d59d9a 100644 --- a/source/slang/modifier-defs.h +++ b/source/slang/modifier-defs.h @@ -315,3 +315,11 @@ END_SYNTAX_CLASS() SYNTAX_CLASS(TupleVarModifier, Modifier) FIELD_INIT(TupleFieldModifier*, tupleField, nullptr) END_SYNTAX_CLASS() + +// A modifier to indicate that a constructor/initializer can be used +// to perform implicit type conversion, and to specify the cost of +// the conversion, if applied. +SYNTAX_CLASS(ImplicitConversionModifier, Modifier) + // The conversion cost, used to rank conversions + FIELD(ConversionCost, cost) +END_SYNTAX_CLASS() diff --git a/source/slang/parser.cpp b/source/slang/parser.cpp index e232880e9..7da309d29 100644 --- a/source/slang/parser.cpp +++ b/source/slang/parser.cpp @@ -3341,9 +3341,12 @@ namespace Slang { RefPtr<TypeCastExpr> tcexpr = new ExplicitCastExpr(); parser->FillPosition(tcexpr.Ptr()); - tcexpr->TargetType = parser->ParseTypeExp(); + tcexpr->FunctionExpr = parser->ParseType(); parser->ReadToken(TokenType::RParent); - tcexpr->Expression = parser->ParseExpression(Precedence::Multiplicative); // Note(tfoley): need to double-check this + + auto arg = parser->ParseExpression(Precedence::Multiplicative); // Note(tfoley): need to double-check this + tcexpr->Arguments.Add(arg); + return tcexpr; } else @@ -3913,7 +3916,19 @@ namespace Slang return modifier; } + static RefPtr<RefObject> parseImplicitConversionModifier(Parser* parser, void* /*userData*/) + { + RefPtr<ImplicitConversionModifier> modifier = new ImplicitConversionModifier(); + ConversionCost cost = kConversionCost_Default; + if( AdvanceIf(parser, TokenType::LParent) ) + { + cost = ConversionCost(StringToInt(parser->ReadToken(TokenType::IntegerLiteral).Content)); + parser->ReadToken(TokenType::RParent); + } + modifier->cost = cost; + return modifier; + } RefPtr<ModuleDecl> populateBaseLanguageModule( Session* session, @@ -4005,6 +4020,7 @@ namespace Slang MODIFIER(__builtin_type, parseBuiltinTypeModifier); MODIFIER(__magic_type, parseMagicTypeModifier); + MODIFIER(__implicit_conversion, parseImplicitConversionModifier); #undef MODIFIER diff --git a/source/slang/slang-stdlib.cpp b/source/slang/slang-stdlib.cpp index 3ec8b9db4..89fcd0800 100644 --- a/source/slang/slang-stdlib.cpp +++ b/source/slang/slang-stdlib.cpp @@ -1066,6 +1066,13 @@ namespace Slang return stdlibPath; } + // We are going to generate the stdlib source code from a more compact + // description. For example, we need to generate all the `operator` + // declarations for the basic unary and binary math operations on + // builtin types. To do this, we will make a big array of all these + // types, and associate them with data on their categories/capabilities + // so that we generate only the correct operations. + // enum { SINT_MASK = 1 << 0, @@ -1082,21 +1089,142 @@ namespace Slang ANY_MASK = INT_MASK | FLOAT_MASK | BOOL_MASK, }; - static const struct { + // We are going to declare initializers that allow for conversion between + // all of our base types, and we need a way to priotize those conversion + // by giving them different costs. Rather than maintain a hard-coded table + // of N^2 costs for N basic types, we are going to try to do things a bit + // more systematically. + // + // Every base type will be given a "kind" and a "rank" for conversion. + // The kind will classify it as signed/unsigned/float, and the rank will + // classify it by its logical bit size (with a distinct rank for pointer-sized + // types that logically sits between 32- and 64-bit types). + // + enum BaseTypeConversionKind : uint8_t + { + kBaseTypeConversionKind_Signed, + kBaseTypeConversionKind_Unsigned, + kBaseTypeConversionKind_Float, + kBaseTypeConversionKind_Error, + }; + enum BaseTypeConversionRank : uint8_t + { + kBaseTypeConversionRank_Bool, + kBaseTypeConversionRank_Int8, + kBaseTypeConversionRank_Int16, + kBaseTypeConversionRank_Int32, + kBaseTypeConversionRank_IntPtr, + kBaseTypeConversionRank_Int64, + kBaseTypeConversionRank_Error, + }; + + // Here we declare the table of all our builtin types, so that we can generate all the relevant declarations. + // + struct BaseTypeInfo + { char const* name; BaseType tag; unsigned flags; - } kBaseTypes[] = { - { "void", BaseType::Void, 0 }, - { "int", BaseType::Int, SINT_MASK }, - { "half", BaseType::Half, FLOAT_MASK }, - { "float", BaseType::Float, FLOAT_MASK }, - { "double", BaseType::Double, FLOAT_MASK }, - { "uint", BaseType::UInt, UINT_MASK }, - { "bool", BaseType::Bool, BOOL_MASK }, - { "uint64_t", BaseType::UInt64, UINT_MASK }, + BaseTypeConversionKind conversionKind; + BaseTypeConversionRank conversionRank; + }; + static const BaseTypeInfo kBaseTypes[] = { + // TODO: `void` really shouldn't be in the `BaseType` enumeration, since it behaves so differently across the board + { "void", BaseType::Void, 0, kBaseTypeConversionKind_Error, kBaseTypeConversionRank_Error}, + + { "bool", BaseType::Bool, BOOL_MASK, kBaseTypeConversionKind_Unsigned, kBaseTypeConversionRank_Bool }, + + // TODO: should declare explicit types for 8-, 16-, 32- and 64-bit integers + { "int", BaseType::Int, SINT_MASK, kBaseTypeConversionKind_Signed, kBaseTypeConversionRank_Int32}, + + { "half", BaseType::Half, FLOAT_MASK, kBaseTypeConversionKind_Float, kBaseTypeConversionRank_Int16}, + { "float", BaseType::Float, FLOAT_MASK, kBaseTypeConversionKind_Float, kBaseTypeConversionRank_Int32}, + { "double", BaseType::Double, FLOAT_MASK, kBaseTypeConversionKind_Float, kBaseTypeConversionRank_Int64}, + + { "uint", BaseType::UInt, UINT_MASK, kBaseTypeConversionKind_Unsigned, kBaseTypeConversionRank_Int32}, + { "uint64_t", BaseType::UInt64, UINT_MASK, kBaseTypeConversionKind_Unsigned, kBaseTypeConversionRank_Int64}, }; + // Given two base types, we need to be able to compute the cost of converting between them. + ConversionCost getBaseTypeConversionCost( + BaseTypeInfo const& toInfo, + BaseTypeInfo const& fromInfo) + { + if(toInfo.conversionKind == fromInfo.conversionKind + && toInfo.conversionRank == fromInfo.conversionRank) + { + // Thse should represent the exact same type. + return kConversionCost_None; + } + + // Conversions within the same kind are easist to handle + if (toInfo.conversionKind == fromInfo.conversionKind) + { + // If we are converting to a "larger" type, then + // we are doing a lossless promotion, and otherwise + // we are doing a demotion. + if( toInfo.conversionRank > fromInfo.conversionRank) + return kConversionCost_RankPromotion; + else + return kConversionCost_GeneralConversion; + } + + // If we are converting from an unsigned integer type to + // a signed integer type that is guaranteed to be larger, + // then that is also a lossless promotion. + // + // There is one additional wrinkle here, which is that + // a conversion from a 32-bit unsigned integer to a + // "pointer-sized" signed integer should be treated + // as unsafe, because the pointer size might also be + // 32 bits. + // + // The same basic exemption applied when converting + // *from* a pointer-sized unsigned integer. + else if(toInfo.conversionKind == kBaseTypeConversionKind_Signed + && fromInfo.conversionKind == kBaseTypeConversionKind_Unsigned + && toInfo.conversionRank > fromInfo.conversionRank + && toInfo.conversionRank != kBaseTypeConversionRank_IntPtr + && fromInfo.conversionRank != kBaseTypeConversionRank_IntPtr) + { + return kConversionCost_UnsignedToSignedPromotion; + } + + // Conversion from signed to unsigned is always lossy, + // but it is preferred over conversions from unsigned + // to signed, for same-size types. + else if(toInfo.conversionKind == kBaseTypeConversionKind_Unsigned + && fromInfo.conversionKind == kBaseTypeConversionKind_Signed + && toInfo.conversionRank >= fromInfo.conversionRank) + { + return kConversionCost_SignedToUnsignedConversion; + } + + // Conversion from an integer to a floating-point type + // is never considered a promotion (even when the value + // would fit in the available mantissa bits). + // If the destination type is at least 32 bits we consider + // this a reasonably good conversion, though. + // + // Note that this means we do *not* consider implicit + // conversion to `half` as a good conversion, even for small + // types. This makes sense because we relaly want to prefer + // conversion to `float` as the default. + else if (toInfo.conversionKind == kBaseTypeConversionKind_Float + && toInfo.conversionRank >= kBaseTypeConversionRank_Int32) + { + return kConversionCost_IntegerToFloatConversion; + } + + // All other cases are considered as "general" conversions, + // where we don't consider any one conversion better than + // any others. + else + { + return kConversionCost_GeneralConversion; + } + } + struct OpInfo { IntrinsicOp opCode; char const* opName; unsigned flags; }; static const OpInfo unaryOps[] = { @@ -1147,7 +1275,6 @@ namespace Slang { IntrinsicOp::RshAssign, ">>=", ASSIGNMENT | INT_MASK }, }; - String Session::getCoreLibraryCode() { if (coreLibraryCode.Length() > 0) @@ -1201,9 +1328,20 @@ namespace Slang // Declare initializers to convert from various other types for (int ss = 0; ss < kBaseTypeCount; ++ss) { + // Don't allow conversion from `void` if (kBaseTypes[ss].tag == BaseType::Void) continue; + // We need to emit a modifier so that the semantic-checking + // layer will know it can use these operations for implicit + // conversion. + ConversionCost conversionCost = getBaseTypeConversionCost( + kBaseTypes[tt], + kBaseTypes[ss]); + + EMIT_LINE_DIRECTIVE(); + sb << "__implicit_conversion(" << conversionCost << ")\n"; + EMIT_LINE_DIRECTIVE(); sb << "__init(" << kBaseTypes[ss].name << " value);\n"; } @@ -1215,8 +1353,14 @@ namespace Slang sb << "__generic<T = float, let N : int = 4> __magic_type(Vector) struct vector\n{\n"; sb << " typedef T Element;\n"; - sb << " __init(T value);\n"; // initialize from single scalar + + // Declare initializer taking a single scalar of the elemnt type + sb << " __implicit_conversion(" << kConversionCost_ScalarToVector << ")\n"; + sb << " __init(T value);\n"; + sb << "};\n"; + + // TODO: Probably need to do similar sb << "__generic<T = float, let R : int = 4, let C : int = 4> __magic_type(Matrix) struct matrix {};\n"; static const struct { @@ -1306,6 +1450,39 @@ namespace Slang sb << "}\n"; } + // The above extension was generic in the *type* of the vector, + // but explicit in the *size*. We will now declare an extension + // for each builtin type that is generic in the size. + // + for (int tt = 0; tt < kBaseTypeCount; ++tt) + { + if(kBaseTypes[tt].tag == BaseType::Void) continue; + + sb << "__generic<let N : int> __extension vector<" + << kBaseTypes[tt].name << ",N>\n{\n"; + + for (int ff = 0; ff < kBaseTypeCount; ++ff) + { + if(kBaseTypes[ff].tag == BaseType::Void) continue; + + // We need a constructor to make a vector from a scalar + // of another type. + + if( tt != ff ) + { + auto cost = getBaseTypeConversionCost( + kBaseTypes[tt], + kBaseTypes[ff]); + cost += kConversionCost_ScalarToVector; + + sb << " __implicit_conversion(" << cost << ")\n"; + sb << " __init(" << kBaseTypes[ff].name << " value);\n"; + } + } + + sb << "}\n"; + } + for( int R = 2; R <= 4; ++R ) for( int C = 2; C <= 4; ++C ) { @@ -1337,6 +1514,14 @@ namespace Slang // with implicit conversion, in the `vector` case above sb << "__generic<U> __init(matrix<U," << R << ", " << C << ">);\n"; + // initialize from a matrix of larger size + for(int rr = R; rr <= 4; ++rr) + for( int cc = C; cc <= 4; ++cc ) + { + if(rr == R && cc == C) continue; + sb << "__init(matrix<T," << rr << "," << cc << "> value);\n"; + } + sb << "}\n"; } diff --git a/source/slang/syntax-base-defs.h b/source/slang/syntax-base-defs.h index e6f7cc55e..8a22a61d2 100644 --- a/source/slang/syntax-base-defs.h +++ b/source/slang/syntax-base-defs.h @@ -4,10 +4,16 @@ // AST nodes and related objects. For example, this is where the // basic `Decl`, `Stmt`, `Expr`, `type`, etc. definitions come from. +ABSTRACT_SYNTAX_CLASS(NodeBase, RefObject) + // A helper to access the corresponding class on a concrete instance + RAW( + virtual SyntaxClass<NodeBase> getClass() = 0; + ) +END_SYNTAX_CLASS() + // Base class for all nodes representing actual syntax // (thus having a location in the source code) -ABSTRACT_SYNTAX_CLASS(SyntaxNodeBase, RefObject) - +ABSTRACT_SYNTAX_CLASS(SyntaxNodeBase, NodeBase) // The primary source location associated with this AST node FIELD(SourceLoc, loc) @@ -26,7 +32,7 @@ END_SYNTAX_CLASS() // These are *not* syntax nodes, because they do not have // a unique location, and any two `Val`s representing // the same value should be conceptually equal. -ABSTRACT_SYNTAX_CLASS(Val, RefObject) +ABSTRACT_SYNTAX_CLASS(Val, NodeBase) RAW(typedef IValVisitor Visitor;) RAW(virtual void accept(IValVisitor* visitor, void* extra) = 0;) diff --git a/source/slang/syntax.cpp b/source/slang/syntax.cpp index be1a429a6..8bd42a7e4 100644 --- a/source/slang/syntax.cpp +++ b/source/slang/syntax.cpp @@ -69,6 +69,7 @@ namespace Slang void NAME::accept(NAME::Visitor* visitor, void* extra) \ { visitor->dispatch_##NAME(this, extra); } \ void* SyntaxClassBase::Impl<NAME>::createFunc() { return new NAME(); } \ + SyntaxClass<NodeBase> NAME::getClass() { return Slang::getClass<NAME>(); } \ SyntaxClassBase::ClassInfo const SyntaxClassBase::Impl<NAME>::kClassInfo = { #NAME, &SyntaxClassBase::Impl<BASE>::kClassInfo, &SyntaxClassBase::Impl<NAME>::createFunc }; #include "expr-defs.h" #include "decl-defs.h" @@ -79,13 +80,14 @@ namespace Slang SyntaxClassBase::ClassInfo const SyntaxClassBase::Impl<RefObject>::kClassInfo = { "RefObject", nullptr, nullptr }; -ABSTRACT_SYNTAX_CLASS(SyntaxNodeBase, RefObject); +ABSTRACT_SYNTAX_CLASS(NodeBase, RefObject); +ABSTRACT_SYNTAX_CLASS(SyntaxNodeBase, NodeBase); ABSTRACT_SYNTAX_CLASS(SyntaxNode, SyntaxNodeBase); ABSTRACT_SYNTAX_CLASS(ModifiableSyntaxNode, SyntaxNode); ABSTRACT_SYNTAX_CLASS(DeclBase, ModifiableSyntaxNode); ABSTRACT_SYNTAX_CLASS(Decl, DeclBase); ABSTRACT_SYNTAX_CLASS(Stmt, ModifiableSyntaxNode); -ABSTRACT_SYNTAX_CLASS(Val, RefObject); +ABSTRACT_SYNTAX_CLASS(Val, NodeBase); ABSTRACT_SYNTAX_CLASS(Type, Val); ABSTRACT_SYNTAX_CLASS(Modifier, SyntaxNodeBase); ABSTRACT_SYNTAX_CLASS(Expr, SyntaxNode); diff --git a/source/slang/syntax.h b/source/slang/syntax.h index e25f096ff..3a7d25f6c 100644 --- a/source/slang/syntax.h +++ b/source/slang/syntax.h @@ -30,6 +30,50 @@ namespace Slang typedef RefPtr<RefObject> (*SyntaxParseCallback)(Parser* parser, void* userData); + typedef unsigned int ConversionCost; + enum : ConversionCost + { + // No conversion at all + kConversionCost_None = 0, + + // Conversions based on explicit sub-typing relationships are the cheapest + // + // TODO(tfoley): We will eventually need a discipline for ranking + // when two up-casts are comparable. + kConversionCost_CastToInterface = 50, + + // Conversion that is lossless and keeps the "kind" of the value the same + kConversionCost_RankPromotion = 100, + + // Conversions that are lossless, but change "kind" + kConversionCost_UnsignedToSignedPromotion = 200, + + // Conversion from signed->unsigned integer of same or greater size + kConversionCost_SignedToUnsignedConversion = 300, + + // Cost of converting an integer to a floating-point type + kConversionCost_IntegerToFloatConversion = 400, + + // Default case (usable for user-defined conversions) + kConversionCost_Default = 500, + + // Catch-all for conversions that should be discouraged + // (i.e., that really shouldn't be made implicitly) + // + // TODO: make these conversions not be allowed implicitly in "Slang mode" + kConversionCost_GeneralConversion = 900, + + // This is the cost of an explicit conversion, which should + // not actually be performed. + kConversionCost_Explicit = 90000, + + // Additional conversion cost to add when promoting from a scalar to + // a vector (this will be added to the cost, if any, of converting + // the element type of the vector) + kConversionCost_ScalarToVector = 1, + }; + + // Forward-declare all syntax classes #define SYNTAX_CLASS(NAME, BASE, ...) class NAME; #include "object-meta-begin.h" @@ -836,6 +880,7 @@ namespace Slang #define SYNTAX_CLASS(NAME, BASE, ...) \ class NAME : public BASE { \ virtual void accept(NAME::Visitor* visitor, void* extra) override; \ + public: virtual SyntaxClass<NodeBase> getClass() override; \ public: /* ... */ #include "expr-defs.h" #include "decl-defs.h" |
