Skip to content

Commit

Permalink
[HLSL] Rework implicit conversion sequences (llvm#96011)
Browse files Browse the repository at this point in the history
This PR reworks HLSL's implicit conversion sequences. Initially I was
seeking to match DXC's behavior more closely, but that was leading to a
pile of special case rules to tie-break ambiguous cases that should
really be left as ambiguous. We've decided that we're going to break
compatibility with DXC here, and we may port this new behavior over to
DXC instead.

This change is a bit closer to C++'s overload resolution rules, but it
does have a bit of nuance around how dimension adjustment conversions
are ranked. Conversion sequence ranks for HLSL are:

* Exact match
* Scalar Widening (i.e. splat)
* Promotion
* Scalar Widening with Promotion
* Conversion
* Scalar Widening with Conversion
* Dimension Reduction (i.e. truncation)
* Dimension Reduction with Promotion
* Dimension Reduction with Conversion

In this implementation I've folded the disambiguation into the
conversion sequence ranks which does add some complexity as compared to
C++, however this avoids needing to add special casing in
`CompareStandardConversionSequences`. I believe the added conversion
rank values provide a simpler approach, but feedback is appreciated.

The HLSL language spec updates are in the PR here:
microsoft/hlsl-specs#261
  • Loading branch information
llvm-beanz committed Jul 13, 2024
1 parent 2d2893d commit d91ff3f
Show file tree
Hide file tree
Showing 16 changed files with 446 additions and 339 deletions.
11 changes: 11 additions & 0 deletions clang/docs/HLSL/ExpectedDifferences.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,16 @@ behavior between Clang and DXC. Some examples include:
void takesDoubles(double, double, double);

cbuffer CB {
bool B;
uint U;
int I;
float X, Y, Z;
double3 A, B;
}

void twoParams(int, int);
void twoParams(float, float);

export void call() {
halfOrInt16(U); // DXC: Fails with call ambiguous between int16_t and uint16_t overloads
// Clang: Resolves to halfOrInt16(uint16_t).
Expand All @@ -98,6 +102,13 @@ behavior between Clang and DXC. Some examples include:
// FXC: Expands to compute double dot product with fmul/fadd
// Clang: Resolves to dot(float3, float3), emits conversion warnings.

#ifndef IGNORE_ERRORS
tan(B); // DXC: resolves to tan(float).
// Clang: Fails to resolve, ambiguous between integer types.

twoParams(I, X); // DXC: resolves twoParams(int, int).
// Clang: Fails to resolve ambiguous conversions.
#endif
}

.. note::
Expand Down
38 changes: 31 additions & 7 deletions clang/include/clang/Sema/Overload.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ class Sema;
/// HLSL non-decaying array rvalue cast.
ICK_HLSL_Array_RValue,

// HLSL vector splat from scalar or boolean type.
ICK_HLSL_Vector_Splat,

/// The number of conversion kinds
ICK_Num_Conversion_Kinds,
};
Expand All @@ -213,15 +216,27 @@ class Sema;
/// Exact Match
ICR_Exact_Match = 0,

/// HLSL Scalar Widening
ICR_HLSL_Scalar_Widening,

/// Promotion
ICR_Promotion,

/// HLSL Scalar Widening with promotion
ICR_HLSL_Scalar_Widening_Promotion,

/// HLSL Matching Dimension Reduction
ICR_HLSL_Dimension_Reduction,

/// Conversion
ICR_Conversion,

/// OpenCL Scalar Widening
ICR_OCL_Scalar_Widening,

/// HLSL Scalar Widening with conversion
ICR_HLSL_Scalar_Widening_Conversion,

/// Complex <-> Real conversion
ICR_Complex_Real_Conversion,

Expand All @@ -233,11 +248,21 @@ class Sema;

/// Conversion not allowed by the C standard, but that we accept as an
/// extension anyway.
ICR_C_Conversion_Extension
ICR_C_Conversion_Extension,

/// HLSL Dimension reduction with promotion
ICR_HLSL_Dimension_Reduction_Promotion,

/// HLSL Dimension reduction with conversion
ICR_HLSL_Dimension_Reduction_Conversion,
};

ImplicitConversionRank GetConversionRank(ImplicitConversionKind Kind);

ImplicitConversionRank
GetDimensionConversionRank(ImplicitConversionRank Base,
ImplicitConversionKind Dimension);

/// NarrowingKind - The kind of narrowing conversion being performed by a
/// standard conversion sequence according to C++11 [dcl.init.list]p7.
enum NarrowingKind {
Expand Down Expand Up @@ -277,11 +302,10 @@ class Sema;
/// pointer-to-member conversion, or boolean conversion.
ImplicitConversionKind Second : 8;

/// Element - Between the second and third conversion a vector or matrix
/// element conversion may occur. If this is not ICK_Identity this
/// conversion is applied element-wise to each element in the vector or
/// matrix.
ImplicitConversionKind Element : 8;
/// Dimension - Between the second and third conversion a vector or matrix
/// dimension conversion may occur. If this is not ICK_Identity this
/// conversion truncates the vector or matrix, or extends a scalar.
ImplicitConversionKind Dimension : 8;

/// Third - The third conversion can be a qualification conversion
/// or a function conversion.
Expand Down Expand Up @@ -379,7 +403,7 @@ class Sema;
void setAsIdentityConversion();

bool isIdentityConversion() const {
return Second == ICK_Identity && Element == ICK_Identity &&
return Second == ICK_Identity && Dimension == ICK_Identity &&
Third == ICK_Identity;
}

Expand Down
160 changes: 76 additions & 84 deletions clang/lib/Sema/SemaExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4294,6 +4294,20 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
return From;
}

// adjustVectorType - Compute the intermediate cast type casting elements of the
// from type to the elements of the to type without resizing the vector.
static QualType adjustVectorType(ASTContext &Context, QualType FromTy,
QualType ToType, QualType *ElTy = nullptr) {
auto *ToVec = ToType->castAs<VectorType>();
QualType ElType = ToVec->getElementType();
if (ElTy)
*ElTy = ElType;
if (!FromTy->isVectorType())
return ElType;
auto *FromVec = FromTy->castAs<VectorType>();
return Context.getExtVectorType(ElType, FromVec->getNumElements());
}

ExprResult
Sema::PerformImplicitConversion(Expr *From, QualType ToType,
const StandardConversionSequence& SCS,
Expand Down Expand Up @@ -4443,27 +4457,36 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
break;

case ICK_Integral_Promotion:
case ICK_Integral_Conversion:
if (ToType->isBooleanType()) {
case ICK_Integral_Conversion: {
QualType ElTy = ToType;
QualType StepTy = ToType;
if (ToType->isVectorType())
StepTy = adjustVectorType(Context, FromType, ToType, &ElTy);
if (ElTy->isBooleanType()) {
assert(FromType->castAs<EnumType>()->getDecl()->isFixed() &&
SCS.Second == ICK_Integral_Promotion &&
"only enums with fixed underlying type can promote to bool");
From = ImpCastExprToType(From, ToType, CK_IntegralToBoolean, VK_PRValue,
From = ImpCastExprToType(From, StepTy, CK_IntegralToBoolean, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
} else {
From = ImpCastExprToType(From, ToType, CK_IntegralCast, VK_PRValue,
From = ImpCastExprToType(From, StepTy, CK_IntegralCast, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
}
break;
}

case ICK_Floating_Promotion:
case ICK_Floating_Conversion:
From = ImpCastExprToType(From, ToType, CK_FloatingCast, VK_PRValue,
case ICK_Floating_Conversion: {
QualType StepTy = ToType;
if (ToType->isVectorType())
StepTy = adjustVectorType(Context, FromType, ToType);
From = ImpCastExprToType(From, StepTy, CK_FloatingCast, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
break;
}

case ICK_Complex_Promotion:
case ICK_Complex_Conversion: {
Expand All @@ -4486,16 +4509,21 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
break;
}

case ICK_Floating_Integral:
if (ToType->isRealFloatingType())
From = ImpCastExprToType(From, ToType, CK_IntegralToFloating, VK_PRValue,
case ICK_Floating_Integral: {
QualType ElTy = ToType;
QualType StepTy = ToType;
if (ToType->isVectorType())
StepTy = adjustVectorType(Context, FromType, ToType, &ElTy);
if (ElTy->isRealFloatingType())
From = ImpCastExprToType(From, StepTy, CK_IntegralToFloating, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
else
From = ImpCastExprToType(From, ToType, CK_FloatingToIntegral, VK_PRValue,
From = ImpCastExprToType(From, StepTy, CK_FloatingToIntegral, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
break;
}

case ICK_Fixed_Point_Conversion:
assert((FromType->isFixedPointType() || ToType->isFixedPointType()) &&
Expand Down Expand Up @@ -4617,18 +4645,26 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
break;
}

case ICK_Boolean_Conversion:
case ICK_Boolean_Conversion: {
// Perform half-to-boolean conversion via float.
if (From->getType()->isHalfType()) {
From = ImpCastExprToType(From, Context.FloatTy, CK_FloatingCast).get();
FromType = Context.FloatTy;
}
QualType ElTy = FromType;
QualType StepTy = ToType;
if (FromType->isVectorType()) {
if (getLangOpts().HLSL)
StepTy = adjustVectorType(Context, FromType, ToType);
ElTy = FromType->castAs<VectorType>()->getElementType();
}

From = ImpCastExprToType(From, Context.BoolTy,
ScalarTypeToBooleanCastKind(FromType), VK_PRValue,
From = ImpCastExprToType(From, StepTy, ScalarTypeToBooleanCastKind(ElTy),
VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
break;
}

case ICK_Derived_To_Base: {
CXXCastPath BasePath;
Expand Down Expand Up @@ -4754,22 +4790,6 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
CK_ZeroToOCLOpaqueType,
From->getValueKind()).get();
break;
case ICK_HLSL_Vector_Truncation: {
// Note: HLSL built-in vectors are ExtVectors. Since this truncates a vector
// to a smaller vector, this can only operate on arguments where the source
// and destination types are ExtVectors.
assert(From->getType()->isExtVectorType() && ToType->isExtVectorType() &&
"HLSL vector truncation should only apply to ExtVectors");
auto *FromVec = From->getType()->castAs<VectorType>();
auto *ToVec = ToType->castAs<VectorType>();
QualType ElType = FromVec->getElementType();
QualType TruncTy =
Context.getExtVectorType(ElType, ToVec->getNumElements());
From = ImpCastExprToType(From, TruncTy, CK_HLSLVectorTruncation,
From->getValueKind())
.get();
break;
}

case ICK_Lvalue_To_Rvalue:
case ICK_Array_To_Pointer:
Expand All @@ -4780,73 +4800,45 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
case ICK_C_Only_Conversion:
case ICK_Incompatible_Pointer_Conversion:
case ICK_HLSL_Array_RValue:
case ICK_HLSL_Vector_Truncation:
case ICK_HLSL_Vector_Splat:
llvm_unreachable("Improper second standard conversion");
}

if (SCS.Element != ICK_Identity) {
if (SCS.Dimension != ICK_Identity) {
// If SCS.Element is not ICK_Identity the To and From types must be HLSL
// vectors or matrices.

// TODO: Support HLSL matrices.
assert((!From->getType()->isMatrixType() && !ToType->isMatrixType()) &&
"Element conversion for matrix types is not implemented yet.");
assert(From->getType()->isVectorType() && ToType->isVectorType() &&
"Element conversion is only supported for vector types.");
assert(From->getType()->getAs<VectorType>()->getNumElements() ==
ToType->getAs<VectorType>()->getNumElements() &&
"Element conversion is only supported for vectors with the same "
"element counts.");
QualType FromElTy = From->getType()->getAs<VectorType>()->getElementType();
unsigned NumElts = ToType->getAs<VectorType>()->getNumElements();
switch (SCS.Element) {
case ICK_Boolean_Conversion:
// Perform half-to-boolean conversion via float.
if (FromElTy->isHalfType()) {
QualType FPExtType = Context.getExtVectorType(FromElTy, NumElts);
From = ImpCastExprToType(From, FPExtType, CK_FloatingCast).get();
FromType = FPExtType;
}

From =
ImpCastExprToType(From, ToType, ScalarTypeToBooleanCastKind(FromElTy),
VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
break;
case ICK_Integral_Promotion:
case ICK_Integral_Conversion:
if (ToType->isBooleanType()) {
assert(FromType->castAs<EnumType>()->getDecl()->isFixed() &&
SCS.Second == ICK_Integral_Promotion &&
"only enums with fixed underlying type can promote to bool");
From = ImpCastExprToType(From, ToType, CK_IntegralToBoolean, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
} else {
From = ImpCastExprToType(From, ToType, CK_IntegralCast, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
}
break;

case ICK_Floating_Promotion:
case ICK_Floating_Conversion:
From = ImpCastExprToType(From, ToType, CK_FloatingCast, VK_PRValue,
"Dimension conversion for matrix types is not implemented yet.");
assert(ToType->isVectorType() &&
"Dimension conversion is only supported for vector types.");
switch (SCS.Dimension) {
case ICK_HLSL_Vector_Splat: {
// Vector splat from any arithmetic type to a vector.
Expr *Elem = prepareVectorSplat(ToType, From).get();
From = ImpCastExprToType(Elem, ToType, CK_VectorSplat, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
break;
case ICK_Floating_Integral:
if (ToType->hasFloatingRepresentation())
From =
ImpCastExprToType(From, ToType, CK_IntegralToFloating, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
else
From =
ImpCastExprToType(From, ToType, CK_FloatingToIntegral, VK_PRValue,
/*BasePath=*/nullptr, CCK)
.get();
}
case ICK_HLSL_Vector_Truncation: {
// Note: HLSL built-in vectors are ExtVectors. Since this truncates a
// vector to a smaller vector, this can only operate on arguments where
// the source and destination types are ExtVectors.
assert(From->getType()->isExtVectorType() && ToType->isExtVectorType() &&
"HLSL vector truncation should only apply to ExtVectors");
auto *FromVec = From->getType()->castAs<VectorType>();
auto *ToVec = ToType->castAs<VectorType>();
QualType ElType = FromVec->getElementType();
QualType TruncTy =
Context.getExtVectorType(ElType, ToVec->getNumElements());
From = ImpCastExprToType(From, TruncTy, CK_HLSLVectorTruncation,
From->getValueKind())
.get();
break;
}
case ICK_Identity:
default:
llvm_unreachable("Improper element standard conversion");
Expand Down
Loading

0 comments on commit d91ff3f

Please sign in to comment.