https://github.com/bob80905 updated https://github.com/llvm/llvm-project/pull/222165
>From 5cfda26c181ee77eea54c7d5e01540bebf3bd3eb Mon Sep 17 00:00:00 2001 From: Joshua Batista <[email protected]> Date: Tue, 8 Sep 2026 13:06:24 -0700 Subject: [PATCH 1/3] First attempt implementing InterlockedCompareExchange --- clang/include/clang/Basic/Builtins.td | 7 + clang/lib/CodeGen/CGHLSLBuiltins.cpp | 25 ++- clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp | 29 ++- clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h | 5 +- clang/lib/Sema/HLSLExternalSemaSource.cpp | 65 ++++--- clang/lib/Sema/SemaHLSL.cpp | 168 ++++++++++-------- .../builtins/InterlockedCompareExchange.hlsl | 70 ++++++++ .../builtins/RWBuffer-Interlocked.hlsl | 5 + ...ressBuffer-InterlockedCompareExchange.hlsl | 44 +++++ ...ressBuffer-InterlockedCompareExchange.hlsl | 35 ++++ ...nterlockedCompareExchange-sm65-errors.hlsl | 30 ++++ .../InterlockedCompareExchange-errors.hlsl | 113 ++++++++++++ 12 files changed, 477 insertions(+), 119 deletions(-) create mode 100644 clang/test/CodeGenHLSL/builtins/InterlockedCompareExchange.hlsl create mode 100644 clang/test/CodeGenHLSL/builtins/RWByteAddressBuffer-InterlockedCompareExchange.hlsl create mode 100644 clang/test/CodeGenHLSL/builtins/RasterizerOrderedByteAddressBuffer-InterlockedCompareExchange.hlsl create mode 100644 clang/test/SemaHLSL/BuiltIns/ByteAddressBuffer-InterlockedCompareExchange-sm65-errors.hlsl create mode 100644 clang/test/SemaHLSL/BuiltIns/InterlockedCompareExchange-errors.hlsl diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 6191911af06965..5377e39f377d45 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -5559,6 +5559,13 @@ def HLSLInterlockedAnd : LangBuiltin<"HLSL_LANG"> { let Prototype = "void (...)"; } +def HLSLInterlockedCompareExchange : LangBuiltin<"HLSL_LANG"> { + let Spellings = ["__builtin_hlsl_interlocked_compare_exchange"]; + // Prevent inadvertent float -> double arg promotion. + let Attributes = [NoThrow, CustomTypeChecking]; + let Prototype = "void (...)"; +} + def HLSLInterlockedCompareStore : LangBuiltin<"HLSL_LANG"> { let Spellings = ["__builtin_hlsl_interlocked_compare_store"]; let Attributes = [NoThrow, CustomTypeChecking]; diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp index b06831871d0a6d..c16ed42667db97 100644 --- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp +++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp @@ -349,19 +349,29 @@ static Value *handleInterlockedOp(CodeGenFunction &CGF, const CallExpr *E, return Call; } -// InterlockedCompareStore(dest, compare_value, value) stores `value` only when -// `dest` holds `compare_value`. It reports nothing, so the `cmpxchg` result is -// unused. DXILResourceAccess and the SPIR-V selector both match `cmpxchg`. -static Value *handleInterlockedCompareStore(CodeGenFunction &CGF, - const CallExpr *E) { +// Emit `cmpxchg` for InterlockedCompareStore and InterlockedCompareExchange. +// Compare-exchange also reports the previous value. +static Value *handleInterlockedCompareOp(CodeGenFunction &CGF, + const CallExpr *E) { LValue DestLV = CGF.EmitLValue(E->getArg(0)); Address DestAddr = DestLV.getAddress(); Value *Compare = CGF.EmitScalarExpr(E->getArg(1)); Value *Val = CGF.EmitScalarExpr(E->getArg(2)); - return CGF.Builder.CreateAtomicCmpXchg( + Value *Pair = CGF.Builder.CreateAtomicCmpXchg( DestAddr, Compare, Val, llvm::AtomicOrdering::Monotonic, llvm::AtomicOrdering::Monotonic, getHLSLAtomicScope(CGF, DestLV)); + + // Compare-store reports nothing, so it leaves the `cmpxchg` result unused. + if (E->getNumArgs() < 4) + return Pair; + + // `cmpxchg` yields a { previous value, success } pair. HLSL reports only the + // previous value, through the `original_value` reference parameter. + Value *Original = CGF.Builder.CreateExtractValue(Pair, 0); + LValue OrigLV = CGF.EmitLValue(E->getArg(3)); + CGF.EmitStoreThroughLValue(RValue::get(Original), OrigLV); + return Original; } static Value *emitBufferStride(CodeGenFunction *CGF, const Expr *HandleExpr, @@ -1485,8 +1495,9 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID, case Builtin::BI__builtin_hlsl_interlocked_and: { return handleInterlockedOp(*this, E, llvm::AtomicRMWInst::And); } + case Builtin::BI__builtin_hlsl_interlocked_compare_exchange: case Builtin::BI__builtin_hlsl_interlocked_compare_store: { - return handleInterlockedCompareStore(*this, E); + return handleInterlockedCompareOp(*this, E); } case Builtin::BI__builtin_hlsl_interlocked_exchange: { return handleInterlockedOp(*this, E, llvm::AtomicRMWInst::Xchg); diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp index 68a246c318a664..df1ad894e2e976 100644 --- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp +++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp @@ -1762,7 +1762,11 @@ BuiltinTypeDeclBuilder::addByteAddressBufferInterlockedMethods() { "__builtin_hlsl_interlocked_add"); addByteAddressBufferInterlockedMethod("InterlockedAnd", AST.UnsignedIntTy, "__builtin_hlsl_interlocked_and"); - addByteAddressBufferInterlockedCompareStoreMethod( + addByteAddressBufferInterlockedCompareMethod( + "InterlockedCompareExchange", AST.UnsignedIntTy, + "__builtin_hlsl_interlocked_compare_exchange", + /*WithOriginalValue=*/true); + addByteAddressBufferInterlockedCompareMethod( "InterlockedCompareStore", AST.UnsignedIntTy, "__builtin_hlsl_interlocked_compare_store"); addByteAddressBufferInterlockedMethod( @@ -1797,7 +1801,11 @@ BuiltinTypeDeclBuilder::addByteAddressBufferInterlockedMethods() { addByteAddressBufferInterlockedMethod("InterlockedAnd64", AST.UnsignedLongTy, "__builtin_hlsl_interlocked_and"); - addByteAddressBufferInterlockedCompareStoreMethod( + addByteAddressBufferInterlockedCompareMethod( + "InterlockedCompareExchange64", AST.UnsignedLongTy, + "__builtin_hlsl_interlocked_compare_exchange", + /*WithOriginalValue=*/true); + addByteAddressBufferInterlockedCompareMethod( "InterlockedCompareStore64", AST.UnsignedLongTy, "__builtin_hlsl_interlocked_compare_store"); addByteAddressBufferInterlockedMethod( @@ -2728,14 +2736,13 @@ BuiltinTypeDeclBuilder::addByteAddressBufferInterlockedMethod( } BuiltinTypeDeclBuilder & -BuiltinTypeDeclBuilder::addByteAddressBufferInterlockedCompareStoreMethod( - StringRef MethodName, QualType ValueTy, StringRef BuiltinName) { +BuiltinTypeDeclBuilder::addByteAddressBufferInterlockedCompareMethod( + StringRef MethodName, QualType ValueTy, StringRef BuiltinName, + bool WithOriginalValue) { assert(!Record->isCompleteDefinition() && "record is already complete"); ASTContext &AST = SemaRef.getASTContext(); using PH = BuiltinTypeMethodBuilder::PlaceHolder; - // Compare-store reports nothing, so it has a single overload. It reaches the - // buffer slot the same way as the other interlocked methods. QualType AddrSpaceElemTy = AST.getAddrSpaceQualType(ValueTy, LangAS::hlsl_device); QualType ElemPtrTy = AST.getPointerType(AddrSpaceElemTy); @@ -2744,10 +2751,16 @@ BuiltinTypeDeclBuilder::addByteAddressBufferInterlockedCompareStoreMethod( MMB.addParam("Offset", AST.UnsignedIntTy) .addParam("CompareValue", ValueTy) .addParam("Value", ValueTy); + if (WithOriginalValue) + MMB.addParam("OriginalValue", ValueTy, HLSLParamModifierAttr::Keyword_out); MMB.callBuiltin("__builtin_hlsl_resource_getpointer_typed", ElemPtrTy, PH::Handle, PH::_0, ValueTy) - .dereference(PH::LastStmt) - .callBuiltin(BuiltinName, AST.VoidTy, PH::LastStmt, PH::_1, PH::_2); + .dereference(PH::LastStmt); + if (WithOriginalValue) + MMB.callBuiltin(BuiltinName, AST.VoidTy, PH::LastStmt, PH::_1, PH::_2, + PH::_3); + else + MMB.callBuiltin(BuiltinName, AST.VoidTy, PH::LastStmt, PH::_1, PH::_2); MMB.finalize(); return *this; } diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h index 92260868c27190..aa77dc7aa7164e 100644 --- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h +++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h @@ -151,8 +151,9 @@ class BuiltinTypeDeclBuilder { addByteAddressBufferInterlockedMethod(StringRef MethodName, QualType ValueTy, StringRef BuiltinName, bool RequiresOriginalValue = false); - BuiltinTypeDeclBuilder &addByteAddressBufferInterlockedCompareStoreMethod( - StringRef MethodName, QualType ValueTy, StringRef BuiltinName); + BuiltinTypeDeclBuilder &addByteAddressBufferInterlockedCompareMethod( + StringRef MethodName, QualType ValueTy, StringRef BuiltinName, + bool WithOriginalValue = false); BuiltinTypeDeclBuilder &addAppendMethod(); BuiltinTypeDeclBuilder &addConsumeMethod(); diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp index 312c6539ee10b1..4918a3bab52b23 100644 --- a/clang/lib/Sema/HLSLExternalSemaSource.cpp +++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp @@ -834,12 +834,13 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() { } // Shapes of the synthesized atomic overloads. The read-modify-write operations -// can report the previous value through a trailing reference. Every argument -// of compare-store is an input. +// can report the previous value through a trailing reference. Compare-store +// takes only inputs; compare-exchange adds the trailing reference. enum class AtomicOverloadShape { Binary, // (dest, value) BinaryWithOriginal, // (dest, value, original_value) CompareStore, // (dest, compare_value, value) + CompareExchange, // (dest, compare_value, value, original_value) }; // Build a single overload of an HLSL atomic intrinsic in the hlsl namespace. @@ -855,20 +856,32 @@ static void buildAtomicOverload(Sema &S, NamespaceDecl *NS, StringRef FuncName, AST.getLValueReferenceType(AST.getAddrSpaceQualType(ElemTy, DestAS)); QualType OrigRefTy = AST.getLValueReferenceType(ElemTy); - SmallVector<QualType, 3> ParamTypes = {DestTy, ElemTy}; - if (Shape == AtomicOverloadShape::BinaryWithOriginal) + SmallVector<QualType, 4> ParamTypes = {DestTy, ElemTy}; + switch (Shape) { + case AtomicOverloadShape::Binary: + break; + case AtomicOverloadShape::BinaryWithOriginal: ParamTypes.push_back(OrigRefTy); - else if (Shape == AtomicOverloadShape::CompareStore) + break; + case AtomicOverloadShape::CompareStore: + ParamTypes.push_back(ElemTy); + break; + case AtomicOverloadShape::CompareExchange: ParamTypes.push_back(ElemTy); + ParamTypes.push_back(OrigRefTy); + break; + } - // The loop below stops at the end of ParamTypes, so a two-argument overload - // ignores the trailing name. + // The loop below stops at the end of ParamTypes, so a shorter overload + // ignores the trailing names. constexpr const char *BinaryNames[] = {"dest", "value", "original_value"}; - constexpr const char *CompareStoreNames[] = {"dest", "compare_value", - "value"}; - ArrayRef<const char *> ParamNames = Shape == AtomicOverloadShape::CompareStore - ? CompareStoreNames - : BinaryNames; + constexpr const char *CompareNames[] = {"dest", "compare_value", "value", + "original_value"}; + const bool IsCompare = Shape == AtomicOverloadShape::CompareStore || + Shape == AtomicOverloadShape::CompareExchange; + ArrayRef<const char *> ParamNames = IsCompare + ? ArrayRef<const char *>(CompareNames) + : ArrayRef<const char *>(BinaryNames); FunctionProtoType::ExtProtoInfo EPI; QualType FuncTy = AST.getFunctionType(AST.VoidTy, ParamTypes, EPI); @@ -882,7 +895,7 @@ static void buildAtomicOverload(Sema &S, NamespaceDecl *NS, StringRef FuncName, SC_Extern, /*UsesFPIntrin=*/false, /*isInlineSpecified=*/false, /*hasWrittenPrototype=*/true); - SmallVector<ParmVarDecl *, 3> ParmDecls; + SmallVector<ParmVarDecl *, 4> ParmDecls; unsigned I = 0; for (auto [ParamType, ParamName] : llvm::zip(ParamTypes, ParamNames)) { IdentifierInfo &PII = AST.Idents.get(ParamName, tok::TokenKind::identifier); @@ -929,20 +942,19 @@ static void defineHLSLInterlockedFunc(Sema &S, NamespaceDecl *NS, } } -// Synthesize the InterlockedCompareStore overload set: {int, uint, int64_t, -// uint64_t} x {groupshared, device}. The operation reports nothing, so it has -// a single arity. -static void defineHLSLInterlockedCompareStoreFunc(Sema &S, NamespaceDecl *NS, - StringRef FuncName, - StringRef BuiltinName) { +// Synthesize the compare-and-swap overload sets: {int, uint, int64_t, +// uint64_t} x {groupshared, device}. Each function has one form only. +static void defineHLSLInterlockedCompareFunc(Sema &S, NamespaceDecl *NS, + StringRef FuncName, + StringRef BuiltinName, + AtomicOverloadShape Shape) { ASTContext &AST = S.getASTContext(); QualType Elems[] = {AST.IntTy, AST.UnsignedIntTy, AST.LongTy, AST.UnsignedLongTy}; for (QualType ElemTy : Elems) for (LangAS AS : {LangAS::hlsl_groupshared, LangAS::hlsl_device}) - buildAtomicOverload(S, NS, FuncName, BuiltinName, ElemTy, AS, - AtomicOverloadShape::CompareStore); + buildAtomicOverload(S, NS, FuncName, BuiltinName, ElemTy, AS, Shape); } void HLSLExternalSemaSource::defineHLSLAtomicIntrinsics() { @@ -950,9 +962,14 @@ void HLSLExternalSemaSource::defineHLSLAtomicIntrinsics() { "__builtin_hlsl_interlocked_add"); defineHLSLInterlockedFunc(*SemaPtr, HLSLNamespace, "InterlockedAnd", "__builtin_hlsl_interlocked_and"); - defineHLSLInterlockedCompareStoreFunc( - *SemaPtr, HLSLNamespace, "InterlockedCompareStore", - "__builtin_hlsl_interlocked_compare_store"); + defineHLSLInterlockedCompareFunc( + *SemaPtr, HLSLNamespace, "InterlockedCompareExchange", + "__builtin_hlsl_interlocked_compare_exchange", + AtomicOverloadShape::CompareExchange); + defineHLSLInterlockedCompareFunc(*SemaPtr, HLSLNamespace, + "InterlockedCompareStore", + "__builtin_hlsl_interlocked_compare_store", + AtomicOverloadShape::CompareStore); defineHLSLInterlockedFunc(*SemaPtr, HLSLNamespace, "InterlockedExchange", "__builtin_hlsl_interlocked_exchange", /*RequiresOriginalValue=*/true, diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index 17e56b6f57f85e..f230fd4b4163a2 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -4110,6 +4110,77 @@ static bool CheckSamplingBuiltin(Sema &S, CallExpr *TheCall, SampleKind Kind) { return false; } +/// The `dest` types an interlocked operation accepts. Float is 32-bit only. +enum class InterlockedDest { Int, IntOrFloat }; + +/// Check a call to an HLSL interlocked builtin. The builtins are variadic, so +/// this is the only check a direct call gets. Overload resolution checks the +/// calls that come through the `InterlockedOp` overload sets. +static bool CheckInterlockedBuiltin(Sema &S, CallExpr *TheCall, + unsigned MinArgs, unsigned MaxArgs, + InterlockedDest Dest, + bool ReportsOriginalValue) { + if (MinArgs == MaxArgs) { + if (S.checkArgCount(TheCall, MinArgs)) + return true; + } else if (TheCall->getNumArgs() < MinArgs) { + S.Diag(TheCall->getEndLoc(), diag::err_typecheck_call_too_few_args_at_least) + << /*callee_type=*/0 << /*min_arg_count=*/MinArgs + << TheCall->getNumArgs() << /*is_non_object=*/0 + << TheCall->getSourceRange(); + return true; + } else if (S.checkArgCountAtMost(TheCall, MaxArgs)) { + return true; + } + + QualType DestTy = TheCall->getArg(0)->getType().getUnqualifiedType(); + const bool AllowsFloat = Dest == InterlockedDest::IntOrFloat; + if (!DestTy->isIntegerType() && + !(AllowsFloat && DestTy->isSpecificBuiltinType(BuiltinType::Float))) { + S.Diag(TheCall->getArg(0)->getBeginLoc(), + diag::err_builtin_invalid_arg_type) + << /*ordinal=*/1 << /*scalar*/ 1 << /*integer*/ 1 + << /*32 bit floating-point*/ (AllowsFloat ? 3 : 0) << DestTy; + return true; + } + + // 64-bit interlocked ops require SM 6.6 on DXIL. The synthesized wrapper + // methods (e.g. RWByteAddressBuffer::InterlockedAdd64) are only declared on + // SM 6.6+, so this defensive check only fires for direct builtin calls; skip + // synthetic invocations (invalid source location). + const TargetInfo &TI = S.Context.getTargetInfo(); + if (TheCall->getBeginLoc().isValid() && + TI.getTriple().getArch() == llvm::Triple::dxil && + S.Context.getTypeSize(DestTy) == 64 && + TI.getPlatformMinVersion() < VersionTuple(6, 6)) { + S.Diag(TheCall->getBeginLoc(), diag::err_hlsl_builtin_requires_sm) + << TheCall->getDirectCallee() << VersionTuple(6, 6).getAsString(); + return true; + } + + if (CheckModifiableLValue(&S, TheCall, 0)) + return true; + + if (CheckArgAddrSpaceOneOf(&S, TheCall, 0, + {LangAS::hlsl_groupshared, LangAS::hlsl_device})) + return true; + + // Every argument after `dest` has the destination's type. + for (unsigned I = 1, E = TheCall->getNumArgs(); I != E; ++I) + if (CheckArgTypeMatches(&S, TheCall->getArg(I), DestTy)) + return true; + + // Operations that report the previous value write it back through their last + // argument. + const unsigned NumArgs = TheCall->getNumArgs(); + if (ReportsOriginalValue && NumArgs == MaxArgs && + CheckModifiableLValue(&S, TheCall, NumArgs - 1)) + return true; + + TheCall->setType(S.Context.VoidTy); + return false; +} + // Note: returning true in this case results in CheckBuiltinFunctionCall // returning an ExprError bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) { @@ -4602,92 +4673,33 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) { } case Builtin::BI__builtin_hlsl_interlocked_add: case Builtin::BI__builtin_hlsl_interlocked_and: - case Builtin::BI__builtin_hlsl_interlocked_compare_store: - case Builtin::BI__builtin_hlsl_interlocked_exchange: case Builtin::BI__builtin_hlsl_interlocked_max: case Builtin::BI__builtin_hlsl_interlocked_min: case Builtin::BI__builtin_hlsl_interlocked_or: - case Builtin::BI__builtin_hlsl_interlocked_xor: { - // The builtin's prototype in Builtins.td is `void (...)`, so direct calls - // to `__builtin_hlsl_interlocked_op` bypass argument checking entirely. - // When reached via the synthesized `InterlockedOp` overload set in - // HLSLExternalSemaSource, overload resolution has already enforced the - // argument count, integer-type matching, and the address-space requirement - // on `dest`. The checks below are a safety net for callers that invoke the - // builtin by its mangled name and would otherwise reach CodeGen unchecked. - // InterlockedCompareStore takes `compare_value` and `value`, so its third - // argument is an input rather than an output. - const bool IsCompareStore = - BuiltinID == Builtin::BI__builtin_hlsl_interlocked_compare_store; - // InterlockedExchange always reports the previous value, so it requires - // `original_value` instead of accepting it as an optional argument. - if (IsCompareStore || - BuiltinID == Builtin::BI__builtin_hlsl_interlocked_exchange) { - if (SemaRef.checkArgCount(TheCall, 3)) - return true; - } else { - if (TheCall->getNumArgs() < 2) { - SemaRef.Diag(TheCall->getEndLoc(), - diag::err_typecheck_call_too_few_args_at_least) - << /*callee_type=*/0 << /*min_arg_count=*/2 << TheCall->getNumArgs() - << /*is_non_object=*/0 << TheCall->getSourceRange(); - return true; - } - if (SemaRef.checkArgCountAtMost(TheCall, 3)) - return true; - } - - QualType DestTy = TheCall->getArg(0)->getType().getUnqualifiedType(); - // InterlockedExchange also operates on float. DXIL lowers that as a - // bitwise exchange of the value's bit pattern, and DXC accepts 32-bit - // float only, so half and double are rejected. - const bool AllowsFloat = - BuiltinID == Builtin::BI__builtin_hlsl_interlocked_exchange; - if (!DestTy->isIntegerType() && - !(AllowsFloat && DestTy->isSpecificBuiltinType(BuiltinType::Float))) { - SemaRef.Diag(TheCall->getArg(0)->getBeginLoc(), - diag::err_builtin_invalid_arg_type) - << /*ordinal=*/1 << /*scalar*/ 1 << /*integer*/ 1 - << /*32 bit floating-point*/ (AllowsFloat ? 3 : 0) << DestTy; - return true; - } - - // 64-bit interlocked ops require SM 6.6 on DXIL. The synthesized wrapper - // methods (e.g. RWByteAddressBuffer::InterlockedAdd64) are only declared - // on SM 6.6+, so this defensive check only fires for direct builtin - // calls; skip synthetic invocations (invalid source location). - const TargetInfo &TI = SemaRef.Context.getTargetInfo(); - if (TheCall->getBeginLoc().isValid() && - TI.getTriple().getArch() == llvm::Triple::dxil && - SemaRef.Context.getTypeSize(DestTy) == 64 && - TI.getPlatformMinVersion() < VersionTuple(6, 6)) { - SemaRef.Diag(TheCall->getBeginLoc(), diag::err_hlsl_builtin_requires_sm) - << TheCall->getDirectCallee() << VersionTuple(6, 6).getAsString(); + case Builtin::BI__builtin_hlsl_interlocked_xor: + if (CheckInterlockedBuiltin(SemaRef, TheCall, /*MinArgs=*/2, /*MaxArgs=*/3, + InterlockedDest::Int, + /*ReportsOriginalValue=*/true)) return true; - } - - if (CheckModifiableLValue(&SemaRef, TheCall, 0)) + break; + case Builtin::BI__builtin_hlsl_interlocked_exchange: + if (CheckInterlockedBuiltin(SemaRef, TheCall, /*MinArgs=*/3, /*MaxArgs=*/3, + InterlockedDest::IntOrFloat, + /*ReportsOriginalValue=*/true)) return true; - - if (CheckArgAddrSpaceOneOf(&SemaRef, TheCall, 0, - {LangAS::hlsl_groupshared, LangAS::hlsl_device})) + break; + case Builtin::BI__builtin_hlsl_interlocked_compare_store: + if (CheckInterlockedBuiltin(SemaRef, TheCall, /*MinArgs=*/3, /*MaxArgs=*/3, + InterlockedDest::Int, + /*ReportsOriginalValue=*/false)) return true; - - if (CheckArgTypeMatches(&SemaRef, TheCall->getArg(1), DestTy)) + break; + case Builtin::BI__builtin_hlsl_interlocked_compare_exchange: + if (CheckInterlockedBuiltin(SemaRef, TheCall, /*MinArgs=*/4, /*MaxArgs=*/4, + InterlockedDest::Int, + /*ReportsOriginalValue=*/true)) return true; - - if (TheCall->getNumArgs() == 3) { - if (CheckArgTypeMatches(&SemaRef, TheCall->getArg(2), DestTy)) - return true; - // Only the read-modify-write operations write the previous value back - // through the third argument. For compare-store it is the new value. - if (!IsCompareStore && CheckModifiableLValue(&SemaRef, TheCall, 2)) - return true; - } - - TheCall->setType(SemaRef.Context.VoidTy); break; - } // Note these are llvm builtins that we want to catch invalid intrinsic // generation. Normal handling of these builtins will occur elsewhere. case Builtin::BI__builtin_elementwise_bitreverse: { diff --git a/clang/test/CodeGenHLSL/builtins/InterlockedCompareExchange.hlsl b/clang/test/CodeGenHLSL/builtins/InterlockedCompareExchange.hlsl new file mode 100644 index 00000000000000..2992d423849527 --- /dev/null +++ b/clang/test/CodeGenHLSL/builtins/InterlockedCompareExchange.hlsl @@ -0,0 +1,70 @@ +// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -triple \ +// RUN: dxil-pc-shadermodel6.6-library %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,DXCHECK + +// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -triple \ +// RUN: spirv-pc-vulkan-library %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,SPVCHECK + +// Test basic lowering of HLSL InterlockedCompareExchange to `cmpxchg +// monotonic`. The operation reports the value that was in the destination +// before the operation, so the first element of the `cmpxchg` result goes to +// the out parameter. + +groupshared int gs_i32; +groupshared uint gs_u32; +groupshared int64_t gs_i64; +groupshared uint64_t gs_u64; + +// CHECK-LABEL: define {{.*}}void @{{.*}}test_int +// DXCHECK: [[PAIR:%.*]] = cmpxchg ptr addrspace(3) {{.*}}@gs_i32{{.*}}, i32 %{{.*}}, i32 %{{.*}} syncscope("workgroup") monotonic monotonic +// SPVCHECK: [[PAIR:%.*]] = cmpxchg ptr addrspace(3) {{.*}}@gs_i32{{.*}}, i32 %{{.*}}, i32 %{{.*}} syncscope("workgroup") monotonic monotonic +// CHECK-NEXT: [[OLD:%.*]] = extractvalue { i32, i1 } [[PAIR]], 0 +// CHECK-NEXT: store i32 [[OLD]], ptr {{.*}}%orig +export void test_int(int cmp, int v) { + int orig; + InterlockedCompareExchange(gs_i32, cmp, v, orig); +} + +// CHECK-LABEL: define {{.*}}void @{{.*}}test_uint +// DXCHECK: [[PAIR:%.*]] = cmpxchg ptr addrspace(3) {{.*}}@gs_u32{{.*}}, i32 %{{.*}}, i32 %{{.*}} syncscope("workgroup") monotonic monotonic +// SPVCHECK: [[PAIR:%.*]] = cmpxchg ptr addrspace(3) {{.*}}@gs_u32{{.*}}, i32 %{{.*}}, i32 %{{.*}} syncscope("workgroup") monotonic monotonic +// CHECK-NEXT: [[OLD:%.*]] = extractvalue { i32, i1 } [[PAIR]], 0 +// CHECK-NEXT: store i32 [[OLD]], ptr {{.*}}%orig +export void test_uint(uint cmp, uint v) { + uint orig; + InterlockedCompareExchange(gs_u32, cmp, v, orig); +} + +// CHECK-LABEL: define {{.*}}void @{{.*}}test_int64 +// DXCHECK: [[PAIR:%.*]] = cmpxchg ptr addrspace(3) {{.*}}@gs_i64{{.*}}, i64 %{{.*}}, i64 %{{.*}} syncscope("workgroup") monotonic monotonic +// SPVCHECK: [[PAIR:%.*]] = cmpxchg ptr addrspace(3) {{.*}}@gs_i64{{.*}}, i64 %{{.*}}, i64 %{{.*}} syncscope("workgroup") monotonic monotonic +// CHECK-NEXT: [[OLD:%.*]] = extractvalue { i64, i1 } [[PAIR]], 0 +// CHECK-NEXT: store i64 [[OLD]], ptr {{.*}}%orig +export void test_int64(int64_t cmp, int64_t v) { + int64_t orig; + InterlockedCompareExchange(gs_i64, cmp, v, orig); +} + +// CHECK-LABEL: define {{.*}}void @{{.*}}test_uint64 +// DXCHECK: [[PAIR:%.*]] = cmpxchg ptr addrspace(3) {{.*}}@gs_u64{{.*}}, i64 %{{.*}}, i64 %{{.*}} syncscope("workgroup") monotonic monotonic +// SPVCHECK: [[PAIR:%.*]] = cmpxchg ptr addrspace(3) {{.*}}@gs_u64{{.*}}, i64 %{{.*}}, i64 %{{.*}} syncscope("workgroup") monotonic monotonic +// CHECK-NEXT: [[OLD:%.*]] = extractvalue { i64, i1 } [[PAIR]], 0 +// CHECK-NEXT: store i64 [[OLD]], ptr {{.*}}%orig +export void test_uint64(uint64_t cmp, uint64_t v) { + uint64_t orig; + InterlockedCompareExchange(gs_u64, cmp, v, orig); +} + +// A device-address-space destination uses the "device" scope instead. +RWBuffer<uint> Buf : register(u0); + +// CHECK-LABEL: define {{.*}}void @{{.*}}test_device +// DXCHECK: [[PAIR:%.*]] = cmpxchg ptr %{{.*}}, i32 %{{.*}}, i32 %{{.*}} syncscope("device") monotonic monotonic +// SPVCHECK: [[PAIR:%.*]] = cmpxchg ptr addrspace(11) %{{.*}}, i32 %{{.*}}, i32 %{{.*}} syncscope("device") monotonic monotonic +// CHECK-NEXT: [[OLD:%.*]] = extractvalue { i32, i1 } [[PAIR]], 0 +// CHECK-NEXT: store i32 [[OLD]], ptr {{.*}}%orig +export void test_device(uint cmp, uint v) { + uint orig; + InterlockedCompareExchange(Buf[0], cmp, v, orig); +} diff --git a/clang/test/CodeGenHLSL/builtins/RWBuffer-Interlocked.hlsl b/clang/test/CodeGenHLSL/builtins/RWBuffer-Interlocked.hlsl index 3af09218eec626..47cff714a7df75 100644 --- a/clang/test/CodeGenHLSL/builtins/RWBuffer-Interlocked.hlsl +++ b/clang/test/CodeGenHLSL/builtins/RWBuffer-Interlocked.hlsl @@ -41,6 +41,8 @@ RWBuffer<uint> UOut : register(u1); // DXCHECK: atomicrmw xchg ptr %[[PTR9]], i32 1 syncscope("device") monotonic // DXCHECK: %[[PTR10:.*]] = call {{.*}} @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0_1t.i32(target("dx.TypedBuffer", i32, 1, 0, 1) %{{.*}}, i32 %{{.*}}) // DXCHECK: cmpxchg ptr %[[PTR10]], i32 1, i32 2 syncscope("device") monotonic monotonic +// DXCHECK: %[[PTR11:.*]] = call {{.*}} @llvm.dx.resource.getpointer.p0.tdx.TypedBuffer_i32_1_0_1t.i32(target("dx.TypedBuffer", i32, 1, 0, 1) %{{.*}}, i32 %{{.*}}) +// DXCHECK: cmpxchg ptr %[[PTR11]], i32 1, i32 2 syncscope("device") monotonic monotonic // SPVCHECK: %[[PTR1:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, i32 %{{.*}}) // SPVCHECK: atomicrmw add ptr addrspace(11) %[[PTR1]], i32 1 syncscope("device") monotonic // SPVCHECK: %[[PTR2:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, i32 %{{.*}}) @@ -61,6 +63,8 @@ RWBuffer<uint> UOut : register(u1); // SPVCHECK: atomicrmw xchg ptr addrspace(11) %[[PTR9]], i32 1 syncscope("device") monotonic // SPVCHECK: %[[PTR10:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, i32 %{{.*}}) // SPVCHECK: cmpxchg ptr addrspace(11) %[[PTR10]], i32 1, i32 2 syncscope("device") monotonic monotonic +// SPVCHECK: %[[PTR11:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, i32 %{{.*}}) +// SPVCHECK: cmpxchg ptr addrspace(11) %[[PTR11]], i32 1, i32 2 syncscope("device") monotonic monotonic [shader("compute")] [numthreads(1,1,1)] void main(uint3 id : SV_DispatchThreadID) { @@ -75,4 +79,5 @@ void main(uint3 id : SV_DispatchThreadID) { int orig; InterlockedExchange(Out[id.x], 1, orig); InterlockedCompareStore(Out[id.x], 1, 2); + InterlockedCompareExchange(Out[id.x], 1, 2, orig); } diff --git a/clang/test/CodeGenHLSL/builtins/RWByteAddressBuffer-InterlockedCompareExchange.hlsl b/clang/test/CodeGenHLSL/builtins/RWByteAddressBuffer-InterlockedCompareExchange.hlsl new file mode 100644 index 00000000000000..1723761b567927 --- /dev/null +++ b/clang/test/CodeGenHLSL/builtins/RWByteAddressBuffer-InterlockedCompareExchange.hlsl @@ -0,0 +1,44 @@ +// RUN: %clang_cc1 -std=hlsl202x -finclude-default-header -triple \ +// RUN: dxil-pc-shadermodel6.6-library %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,DXCHECK + +// RUN: %clang_cc1 -std=hlsl202x -finclude-default-header -triple \ +// RUN: spirv-pc-vulkan1.3-library %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,SPVCHECK + +// Test that the RWByteAddressBuffer::InterlockedCompareExchange and +// InterlockedCompareExchange64 member methods lower to `resource_getpointer -> +// cmpxchg`, for both DXIL and SPIR-V targets. The value that was in the +// destination before the operation goes to the out parameter. + +RWByteAddressBuffer BAB : register(u0); + +// CHECK-LABEL: define {{.*}}void @{{.*}}test_bab_uint +// DXCHECK: %[[HANDLE:.*]] = load target("dx.RawBuffer", i8, 1, 0), ptr {{.*}} +// DXCHECK: %[[PTR:.*]] = call ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i8_1_0t.i32(target("dx.RawBuffer", i8, 1, 0) %[[HANDLE]], i32 %{{.*}}) +// DXCHECK: %[[PAIR:.*]] = cmpxchg ptr %[[PTR]], i32 %{{.*}}, i32 %{{.*}} syncscope("device") monotonic monotonic +// SPVCHECK: %[[HANDLE:.*]] = load target("spirv.VulkanBuffer", [0 x i8], 12, 1), ptr {{.*}} +// SPVCHECK: %[[PTR:.*]] = call ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i8_12_1t.i32(target("spirv.VulkanBuffer", [0 x i8], 12, 1) %[[HANDLE]], i32 %{{.*}}) +// SPVCHECK: %[[PAIR:.*]] = cmpxchg ptr addrspace(11) %[[PTR]], i32 %{{.*}}, i32 %{{.*}} syncscope("device") monotonic monotonic +// CHECK-NEXT: %[[OLD:.*]] = extractvalue { i32, i1 } %[[PAIR]], 0 +// CHECK-NEXT: %[[OUT:.*]] = load ptr{{.*}}, ptr {{.*}}%OriginalValue.addr +// CHECK-NEXT: store i32 %[[OLD]], ptr{{.*}} %[[OUT]] +export void test_bab_uint(uint off, uint cmp, uint v) { + uint orig; + BAB.InterlockedCompareExchange(off, cmp, v, orig); +} + +// CHECK-LABEL: define {{.*}}void @{{.*}}test_bab_uint64 +// DXCHECK: %[[HANDLE:.*]] = load target("dx.RawBuffer", i8, 1, 0), ptr {{.*}} +// DXCHECK: %[[PTR:.*]] = call ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i8_1_0t.i32(target("dx.RawBuffer", i8, 1, 0) %[[HANDLE]], i32 %{{.*}}) +// DXCHECK: %[[PAIR:.*]] = cmpxchg ptr %[[PTR]], i64 %{{.*}}, i64 %{{.*}} syncscope("device") monotonic monotonic +// SPVCHECK: %[[HANDLE:.*]] = load target("spirv.VulkanBuffer", [0 x i8], 12, 1), ptr {{.*}} +// SPVCHECK: %[[PTR:.*]] = call ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0i8_12_1t.i32(target("spirv.VulkanBuffer", [0 x i8], 12, 1) %[[HANDLE]], i32 %{{.*}}) +// SPVCHECK: %[[PAIR:.*]] = cmpxchg ptr addrspace(11) %[[PTR]], i64 %{{.*}}, i64 %{{.*}} syncscope("device") monotonic monotonic +// CHECK-NEXT: %[[OLD:.*]] = extractvalue { i64, i1 } %[[PAIR]], 0 +// CHECK-NEXT: %[[OUT:.*]] = load ptr{{.*}}, ptr {{.*}}%OriginalValue.addr +// CHECK-NEXT: store i64 %[[OLD]], ptr{{.*}} %[[OUT]] +export void test_bab_uint64(uint off, uint64_t cmp, uint64_t v) { + uint64_t orig; + BAB.InterlockedCompareExchange64(off, cmp, v, orig); +} diff --git a/clang/test/CodeGenHLSL/builtins/RasterizerOrderedByteAddressBuffer-InterlockedCompareExchange.hlsl b/clang/test/CodeGenHLSL/builtins/RasterizerOrderedByteAddressBuffer-InterlockedCompareExchange.hlsl new file mode 100644 index 00000000000000..e69b12cde05868 --- /dev/null +++ b/clang/test/CodeGenHLSL/builtins/RasterizerOrderedByteAddressBuffer-InterlockedCompareExchange.hlsl @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -std=hlsl202x -finclude-default-header -triple \ +// RUN: dxil-pc-shadermodel6.6-library %s -emit-llvm -disable-llvm-passes -o - | \ +// RUN: FileCheck %s --check-prefixes=CHECK,DXCHECK + +// SPIR-V codegen for RasterizerOrderedByteAddressBuffer is not implemented +// yet (asserts in clang/lib/CodeGen/Targets/SPIR.cpp on +// `!ResAttrs.IsROV && "Rasterizer order views not implemented for SPIR-V yet"`). +// Add a `spirv-pc-vulkan1.3-library` RUN line here when SPIR-V ROV support +// lands. + +RasterizerOrderedByteAddressBuffer ROVB : register(u1); + +// CHECK-LABEL: define void @{{.*}}test_rovb_uint +// DXCHECK: %[[HANDLE:.*]] = load target("dx.RawBuffer", i8, 1, 1), ptr {{.*}} +// DXCHECK: %[[PTR:.*]] = call ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i8_1_1t.i32(target("dx.RawBuffer", i8, 1, 1) %[[HANDLE]], i32 %{{.*}}) +// DXCHECK: %[[PAIR:.*]] = cmpxchg ptr %[[PTR]], i32 %{{.*}}, i32 %{{.*}} syncscope("device") monotonic monotonic +// DXCHECK-NEXT: %[[OLD:.*]] = extractvalue { i32, i1 } %[[PAIR]], 0 +// DXCHECK-NEXT: %[[OUT:.*]] = load ptr, ptr {{.*}}%OriginalValue.addr +// DXCHECK-NEXT: store i32 %[[OLD]], ptr %[[OUT]] +export void test_rovb_uint(uint off, uint cmp, uint v) { + uint orig; + ROVB.InterlockedCompareExchange(off, cmp, v, orig); +} + +// CHECK-LABEL: define void @{{.*}}test_rovb_uint64 +// DXCHECK: %[[HANDLE:.*]] = load target("dx.RawBuffer", i8, 1, 1), ptr {{.*}} +// DXCHECK: %[[PTR:.*]] = call ptr @llvm.dx.resource.getpointer.p0.tdx.RawBuffer_i8_1_1t.i32(target("dx.RawBuffer", i8, 1, 1) %[[HANDLE]], i32 %{{.*}}) +// DXCHECK: %[[PAIR:.*]] = cmpxchg ptr %[[PTR]], i64 %{{.*}}, i64 %{{.*}} syncscope("device") monotonic monotonic +// DXCHECK-NEXT: %[[OLD:.*]] = extractvalue { i64, i1 } %[[PAIR]], 0 +// DXCHECK-NEXT: %[[OUT:.*]] = load ptr, ptr {{.*}}%OriginalValue.addr +// DXCHECK-NEXT: store i64 %[[OLD]], ptr %[[OUT]] +export void test_rovb_uint64(uint off, uint64_t cmp, uint64_t v) { + uint64_t orig; + ROVB.InterlockedCompareExchange64(off, cmp, v, orig); +} diff --git a/clang/test/SemaHLSL/BuiltIns/ByteAddressBuffer-InterlockedCompareExchange-sm65-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/ByteAddressBuffer-InterlockedCompareExchange-sm65-errors.hlsl new file mode 100644 index 00000000000000..cdb6a8c9969bf0 --- /dev/null +++ b/clang/test/SemaHLSL/BuiltIns/ByteAddressBuffer-InterlockedCompareExchange-sm65-errors.hlsl @@ -0,0 +1,30 @@ +// RUN: %clang_cc1 -std=hlsl202x -finclude-default-header \ +// RUN: -triple dxil-pc-shadermodel6.5-library %s -fsyntax-only -verify \ +// RUN: -verify-ignore-unexpected=warning + +RWByteAddressBuffer BAB : register(u0); +RasterizerOrderedByteAddressBuffer ROVB : register(u1); + +void sm65_no_bab_compare_exchange64(uint off, uint64_t cmp, uint64_t v) { + uint64_t orig; + BAB.InterlockedCompareExchange64(off, cmp, v, orig); + // expected-error@-1 {{no member named 'InterlockedCompareExchange64' in 'hlsl::RWByteAddressBuffer'}} +} + +void sm65_no_rovb_compare_exchange64(uint off, uint64_t cmp, uint64_t v) { + uint64_t orig; + ROVB.InterlockedCompareExchange64(off, cmp, v, orig); + // expected-error@-1 {{no member named 'InterlockedCompareExchange64' in 'hlsl::RasterizerOrderedByteAddressBuffer'}} +} + +void sm65_bab_compare_exchange32_ok(uint off, uint cmp, uint v) { + uint orig; + BAB.InterlockedCompareExchange(off, cmp, v, orig); +} + +groupshared int64_t gs_i64; +void sm65_direct_builtin(int64_t cmp, int64_t v) { + int64_t orig; + __builtin_hlsl_interlocked_compare_exchange(gs_i64, cmp, v, orig); + // expected-error@-1 {{'__builtin_hlsl_interlocked_compare_exchange' requires shader model 6.6 or newer}} +} diff --git a/clang/test/SemaHLSL/BuiltIns/InterlockedCompareExchange-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/InterlockedCompareExchange-errors.hlsl new file mode 100644 index 00000000000000..5f878bf556d5f8 --- /dev/null +++ b/clang/test/SemaHLSL/BuiltIns/InterlockedCompareExchange-errors.hlsl @@ -0,0 +1,113 @@ +// RUN: %clang_cc1 -std=hlsl202x -finclude-default-header \ +// RUN: -triple dxil-pc-shadermodel6.6-library %s -emit-llvm-only \ +// RUN: -disable-llvm-passes -verify + +// InterlockedCompareExchange is provided as a set of address-space-qualified +// overloads (groupshared/device, {int,uint,int64_t,uint64_t}). It reports the +// value that was in the destination, so it has a single 4-argument form with a +// trailing out parameter. + +groupshared int gs_i32; +groupshared float gs_f32; +struct S { int x; }; +groupshared S gs_s; + +void too_few(int cmp, int v) { + InterlockedCompareExchange(gs_i32, cmp, v); // expected-error{{no matching function for call to 'InterlockedCompareExchange'}} + // expected-note@*:* 8 {{candidate function}} +} + +void too_many(int cmp, int v, int extra) { + int orig; + InterlockedCompareExchange(gs_i32, cmp, v, orig, extra); // expected-error{{no matching function for call to 'InterlockedCompareExchange'}} + // expected-note@*:* 8 {{candidate function}} +} + +void local_dest(int cmp, int v) { + int dest; + int orig; + InterlockedCompareExchange(dest, cmp, v, orig); // expected-error{{no matching function for call to 'InterlockedCompareExchange'}} + // expected-note@*:* 8 {{candidate function}} +} + +void float_dest(float cmp, float v) { + float orig; + InterlockedCompareExchange(gs_f32, cmp, v, orig); // expected-error{{no matching function for call to 'InterlockedCompareExchange'}} + // expected-note@*:* 8 {{candidate function}} +} + +void struct_dest(int cmp, int v) { + S orig; + InterlockedCompareExchange(gs_s, cmp, v, orig); // expected-error{{no matching function for call to 'InterlockedCompareExchange'}} + // expected-note@*:* 8 {{candidate function}} +} + +// The out parameter is a reference, so it cannot bind across types. +void mismatched_orig_type(int cmp, int v) { + float orig; + InterlockedCompareExchange(gs_i32, cmp, v, orig); // expected-error{{no matching function for call to 'InterlockedCompareExchange'}} + // expected-note@*:* 8 {{candidate function}} +} + +void direct_too_few(int cmp, int v) { + __builtin_hlsl_interlocked_compare_exchange(gs_i32, cmp, v); + // expected-error@-1 {{too few arguments to function call, expected 4, have 3}} +} + +void direct_too_many(int cmp, int v, int extra) { + int orig; + __builtin_hlsl_interlocked_compare_exchange(gs_i32, cmp, v, orig, extra); + // expected-error@-1 {{too many arguments to function call, expected 4, have 5}} +} + +void direct_non_integer_dest() { + S local_s; + int orig; + __builtin_hlsl_interlocked_compare_exchange(local_s, 1, 2, orig); + // expected-error@-1 {{1st argument must be a scalar integer type (was 'S')}} +} + +void direct_float_dest(float cmp, float v) { + float orig; + __builtin_hlsl_interlocked_compare_exchange(gs_f32, cmp, v, orig); + // expected-error@-1 {{1st argument must be a scalar integer type (was 'float')}} +} + +void direct_nonlvalue_dest(int cmp, int v) { + int orig; + __builtin_hlsl_interlocked_compare_exchange(1, cmp, v, orig); + // expected-error@-1 {{cannot bind non-lvalue argument '1' to out parameter}} +} + +// The last argument is an out parameter, so an rvalue is rejected there. +void direct_nonlvalue_original_value(int cmp, int v) { + __builtin_hlsl_interlocked_compare_exchange(gs_i32, cmp, v, 0); + // expected-error@-1 {{cannot bind non-lvalue argument '0' to out parameter}} +} + +void direct_mismatched_compare() { + uint cmp = 1; + int orig; + __builtin_hlsl_interlocked_compare_exchange(gs_i32, cmp, 2, orig); + // expected-error@-1 {{passing 'uint' (aka 'unsigned int') to parameter of incompatible type 'int'}} +} + +void direct_mismatched_value() { + uint v = 1; + int orig; + __builtin_hlsl_interlocked_compare_exchange(gs_i32, 1, v, orig); + // expected-error@-1 {{passing 'uint' (aka 'unsigned int') to parameter of incompatible type 'int'}} +} + +void direct_mismatched_original_value() { + uint orig; + __builtin_hlsl_interlocked_compare_exchange(gs_i32, 1, 2, orig); + // expected-error@-1 {{passing 'uint' (aka 'unsigned int') to parameter of incompatible type 'int'}} +} + +void direct_default_as_dest(int cmp, int v) { + int local; + int orig; + __builtin_hlsl_interlocked_compare_exchange(local, cmp, v, orig); + // expected-error@-1 {{1st argument to atomic builtin must reference groupshared or device memory (was 'int')}} +} >From c1b3e307fd9d245294c9ce5d2e3388435047a116 Mon Sep 17 00:00:00 2001 From: Joshua Batista <[email protected]> Date: Thu, 17 Sep 2026 11:27:56 -0700 Subject: [PATCH 2/3] Add InterlockedCompareExchange coverage to the texture test --- clang/test/CodeGenHLSL/builtins/RWTexture-Interlocked.hlsl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clang/test/CodeGenHLSL/builtins/RWTexture-Interlocked.hlsl b/clang/test/CodeGenHLSL/builtins/RWTexture-Interlocked.hlsl index 7dcff605bd9713..6fc44101d48793 100644 --- a/clang/test/CodeGenHLSL/builtins/RWTexture-Interlocked.hlsl +++ b/clang/test/CodeGenHLSL/builtins/RWTexture-Interlocked.hlsl @@ -42,6 +42,8 @@ RWTexture2D<float> FOut : register(u2); // DXCHECK: atomicrmw xchg ptr %[[PTR9]], float 1.000000e+00 syncscope("device") monotonic // DXCHECK: %[[PTR10:.*]] = call {{.*}} @llvm.dx.resource.getpointer.{{.*}}(target("dx.Texture", i32, 1, 0, 1, 2) %{{.*}}, <2 x i32> %{{.*}}) // DXCHECK: cmpxchg ptr %[[PTR10]], i32 1, i32 2 syncscope("device") monotonic monotonic +// DXCHECK: %[[PTR11:.*]] = call {{.*}} @llvm.dx.resource.getpointer.{{.*}}(target("dx.Texture", i32, 1, 0, 1, 2) %{{.*}}, <2 x i32> %{{.*}}) +// DXCHECK: cmpxchg ptr %[[PTR11]], i32 1, i32 2 syncscope("device") monotonic monotonic // SPVCHECK: %[[PTR1:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, <2 x i32> %{{.*}}) // SPVCHECK: atomicrmw add ptr addrspace(11) %[[PTR1]], i32 1 syncscope("device") monotonic // SPVCHECK: %[[PTR2:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, <2 x i32> %{{.*}}) @@ -62,6 +64,8 @@ RWTexture2D<float> FOut : register(u2); // SPVCHECK: atomicrmw xchg ptr addrspace(11) %[[PTR9]], float 1.000000e+00 syncscope("device") monotonic // SPVCHECK: %[[PTR10:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, <2 x i32> %{{.*}}) // SPVCHECK: cmpxchg ptr addrspace(11) %[[PTR10]], i32 1, i32 2 syncscope("device") monotonic monotonic +// SPVCHECK: %[[PTR11:.*]] = call {{.*}} @llvm.spv.resource.getpointer.{{.*}}(target("spirv.SignedImage", i32, {{.*}}) %{{.*}}, <2 x i32> %{{.*}}) +// SPVCHECK: cmpxchg ptr addrspace(11) %[[PTR11]], i32 1, i32 2 syncscope("device") monotonic monotonic [shader("compute")] [numthreads(1,1,1)] void main(uint3 id : SV_DispatchThreadID) { @@ -77,4 +81,5 @@ void main(uint3 id : SV_DispatchThreadID) { float FOrig; InterlockedExchange(FOut[id.xy], 1.0f, FOrig); InterlockedCompareStore(Out[id.xy], 1, 2); + InterlockedCompareExchange(Out[id.xy], 1, 2, Orig); } >From 0cc77028cb9abc08b6fc55b0812a1de3a8e18f58 Mon Sep 17 00:00:00 2001 From: Joshua Batista <[email protected]> Date: Thu, 17 Sep 2026 14:50:44 -0700 Subject: [PATCH 3/3] Remove the comment on the InterlockedCompareExchange builtin --- clang/include/clang/Basic/Builtins.td | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 5377e39f377d45..9c6994ddc7d741 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -5561,7 +5561,6 @@ def HLSLInterlockedAnd : LangBuiltin<"HLSL_LANG"> { def HLSLInterlockedCompareExchange : LangBuiltin<"HLSL_LANG"> { let Spellings = ["__builtin_hlsl_interlocked_compare_exchange"]; - // Prevent inadvertent float -> double arg promotion. let Attributes = [NoThrow, CustomTypeChecking]; let Prototype = "void (...)"; } _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
