Skip to content

[HLSL] Adding HLSL clip function. #114588

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 15, 2024
Merged

Conversation

joaosaffran
Copy link
Contributor

@joaosaffran joaosaffran commented Nov 1, 2024

Adding HLSL clip function.

  • adding llvm intrinsic
  • adding sema checks
  • adding dxil lowering
  • ading spirv lowering
  • adding sema tests
  • adding codegen tests
  • adding lowering tests

Closes #99093

Copy link

github-actions bot commented Nov 1, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@joaosaffran joaosaffran force-pushed the intrinsics/clip branch 6 times, most recently from ed1275d to 0e14607 Compare November 4, 2024 18:14
@joaosaffran joaosaffran changed the title Intrinsics/clip [HLSL] Adding HLSL clip function. Nov 4, 2024
@joaosaffran joaosaffran force-pushed the intrinsics/clip branch 3 times, most recently from 2fb4bf9 to 20587a9 Compare November 4, 2024 18:24
@joaosaffran joaosaffran marked this pull request as ready for review November 4, 2024 19:15
@llvmbot llvmbot added clang Clang issues not falling into any other category backend:X86 clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:headers Headers provided by Clang, e.g. for intrinsics clang:codegen IR generation bugs: mangling, exceptions, etc. backend:DirectX HLSL HLSL Language Support backend:SPIR-V llvm:ir labels Nov 4, 2024
@llvmbot
Copy link
Member

llvmbot commented Nov 4, 2024

@llvm/pr-subscribers-clang-codegen
@llvm/pr-subscribers-hlsl
@llvm/pr-subscribers-backend-directx

@llvm/pr-subscribers-backend-x86

Author: None (joaosaffran)

Changes

Adding HLSL clip function.

  • adding llvm intrinsic
  • adding sema checks
  • adding dxil lowering
  • ading spirv lowering
  • adding sema tests
  • adding codegen tests
  • adding lowering tests

Closes #99093


Patch is 22.88 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/114588.diff

16 Files Affected:

  • (modified) clang/include/clang/Basic/Builtins.td (+6)
  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+41)
  • (modified) clang/lib/Headers/hlsl/hlsl_intrinsics.h (+17)
  • (modified) clang/lib/Sema/SemaHLSL.cpp (+8)
  • (added) clang/test/CodeGenHLSL/builtins/clip.hlsl (+39)
  • (added) clang/test/SemaHLSL/BuiltIns/clip-errors.hlsl (+22)
  • (modified) llvm/include/llvm/IR/IntrinsicsDirectX.td (+1)
  • (modified) llvm/include/llvm/IR/IntrinsicsSPIRV.td (+1)
  • (modified) llvm/lib/Target/DirectX/DXIL.td (+30-21)
  • (modified) llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp (+3-1)
  • (modified) llvm/lib/Target/SPIRV/SPIRVInstrInfo.td (+1)
  • (modified) llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp (+32)
  • (modified) llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp (+11)
  • (modified) llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td (+1)
  • (added) llvm/test/CodeGen/DirectX/clip.ll (+29)
  • (added) llvm/test/CodeGen/SPIRV/hlsl-intrinsics/clip.ll (+77)
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 9bd67e0cefebc3..13ba369e323f72 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -4877,6 +4877,12 @@ def HLSLSplitDouble: LangBuiltin<"HLSL_LANG"> {
   let Prototype = "void(...)";
 }
 
+def HLSLClip: LangBuiltin<"HLSL_LANG"> {
+  let Spellings = ["__builtin_hlsl_elementwise_clip"];
+  let Attributes = [NoThrow, Const];
+  let Prototype = "void(...)";
+}
+
 // Builtins for XRay.
 def XRayCustomEvent : Builtin {
   let Spellings = ["__xray_customevent"];
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 34fedd67114751..8177b144639180 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -99,6 +99,42 @@ static void initializeAlloca(CodeGenFunction &CGF, AllocaInst *AI, Value *Size,
   I->addAnnotationMetadata("auto-init");
 }
 
+static Value *handleHlslClip(const CallExpr *E, CodeGenFunction *CGF) {
+  Value *Op0 = CGF->EmitScalarExpr(E->getArg(0));
+
+  Constant *FZeroConst = ConstantFP::getZero(CGF->FloatTy);
+  Value *CMP;
+
+  if (const auto *VecTy = E->getArg(0)->getType()->getAs<clang::VectorType>()) {
+    FZeroConst = ConstantVector::getSplat(
+        ElementCount::getFixed(VecTy->getNumElements()), FZeroConst);
+    auto *FCompInst = CGF->Builder.CreateFCmpOLT(Op0, FZeroConst);
+    CMP = CGF->Builder.CreateIntrinsic(
+        CGF->Builder.getInt1Ty(), CGF->CGM.getHLSLRuntime().getAnyIntrinsic(),
+        {FCompInst}, nullptr);
+  } else
+    CMP = CGF->Builder.CreateFCmpOLT(Op0, FZeroConst);
+
+  if (CGF->CGM.getTarget().getTriple().isDXIL())
+    return CGF->Builder.CreateIntrinsic(CGF->VoidTy, llvm::Intrinsic::dx_clip,
+                                        {CMP}, nullptr);
+
+  BasicBlock *LT0 = CGF->createBasicBlock("lt0", CGF->CurFn);
+  BasicBlock *End = CGF->createBasicBlock("end", CGF->CurFn);
+
+  CGF->Builder.CreateCondBr(CMP, LT0, End);
+
+  CGF->Builder.SetInsertPoint(LT0);
+
+  CGF->Builder.CreateIntrinsic(CGF->VoidTy, llvm::Intrinsic::spv_clip, {},
+                               nullptr);
+
+  auto *BrCall = CGF->Builder.CreateBr(End);
+
+  CGF->Builder.SetInsertPoint(End);
+  return BrCall;
+}
+
 static Value *handleHlslSplitdouble(const CallExpr *E, CodeGenFunction *CGF) {
   Value *Op0 = CGF->EmitScalarExpr(E->getArg(0));
   const auto *OutArg1 = dyn_cast<HLSLOutArgExpr>(E->getArg(1));
@@ -19093,6 +19129,11 @@ case Builtin::BI__builtin_hlsl_elementwise_isinf: {
            "asuint operands types mismatch");
     return handleHlslSplitdouble(E, this);
   }
+  case Builtin::BI__builtin_hlsl_elementwise_clip:
+
+    assert(E->getArg(0)->getType()->hasFloatingRepresentation() &&
+           "clip operands types mismatch");
+    return handleHlslClip(E, this);
   }
   return nullptr;
 }
diff --git a/clang/lib/Headers/hlsl/hlsl_intrinsics.h b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
index d9f3a17ea23d8e..424c2f7e7c23ee 100644
--- a/clang/lib/Headers/hlsl/hlsl_intrinsics.h
+++ b/clang/lib/Headers/hlsl/hlsl_intrinsics.h
@@ -655,6 +655,23 @@ double3 clamp(double3, double3, double3);
 _HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_clamp)
 double4 clamp(double4, double4, double4);
 
+//===----------------------------------------------------------------------===//
+// clip builtins
+//===----------------------------------------------------------------------===//
+
+/// \fn void clip(T Val)
+/// \brief Discards the current pixel if the specified value is less than zero.
+/// \param Val The input value.
+
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_clip)
+void clip(float);
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_clip)
+void clip(float2);
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_clip)
+void clip(float3);
+_HLSL_BUILTIN_ALIAS(__builtin_hlsl_elementwise_clip)
+void clip(float4);
+
 //===----------------------------------------------------------------------===//
 // cos builtins
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index a472538236e2d9..e360c54dc0760e 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2110,6 +2110,14 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
       return true;
     break;
   }
+  case Builtin::BI__builtin_hlsl_elementwise_clip: {
+    if (SemaRef.checkArgCount(TheCall, 1))
+      return true;
+
+    if (CheckScalarOrVector(&SemaRef, TheCall, SemaRef.Context.FloatTy, 0))
+      return true;
+    break;
+  }
   case Builtin::BI__builtin_elementwise_acos:
   case Builtin::BI__builtin_elementwise_asin:
   case Builtin::BI__builtin_elementwise_atan:
diff --git a/clang/test/CodeGenHLSL/builtins/clip.hlsl b/clang/test/CodeGenHLSL/builtins/clip.hlsl
new file mode 100644
index 00000000000000..81976839bbe7d9
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/clip.hlsl
@@ -0,0 +1,39 @@
+// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple dxil-pc-shadermodel6.3-pixel %s -fnative-half-type -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 -finclude-default-header -x hlsl -triple spirv-vulkan-pixel %s -fnative-half-type -emit-llvm -o - | FileCheck %s --check-prefix=SPIRV
+
+
+void test_scalar(float Buf) {
+  // CHECK:      define void @{{.*}}test_scalar{{.*}}(float {{.*}} [[VALP:%.*]])
+  // CHECK:      [[LOAD:%.*]] = load float, ptr [[VALP]].addr, align 4
+  // CHECK-NEXT: [[FCMP:%.*]] = fcmp olt float [[LOAD]], 0.000000e+00
+  // CHECK-NO:   call i1 @llvm.dx.any
+  // CHECK-NEXT: call void @llvm.dx.clip(i1 [[FCMP]])
+  //
+  // SPIRV:      define spir_func void @{{.*}}test_scalar{{.*}}(float {{.*}} [[VALP:%.*]])
+  // SPIRV:      [[LOAD:%.*]] = load float, ptr [[VALP]].addr, align 4
+  // SPIRV-NEXT: [[FCMP:%.*]] = fcmp olt float [[LOAD]], 0.000000e+00
+  // SPIRV-NO:   call i1 @llvm.dx.any
+  // SPIRV-NEXT: br i1 [[FCMP]], label %[[LTL:.*]], label %[[ENDL:.*]]
+  // SPIRV:      [[LTL]]: ; preds = %entry
+  // SPIRV-NEXT: call void @llvm.spv.clip()
+  // SPIRV:      br label %[[ENDL]]
+  clip(Buf);
+}
+
+void test_vector4(float4 Buf) {
+  // CHECK:      define void @{{.*}}test_vector{{.*}}(<4 x float> {{.*}} [[VALP:%.*]])
+  // CHECK:      [[LOAD:%.*]] = load <4 x float>, ptr [[VALP]].addr, align 16
+  // CHECK-NEXT: [[FCMP:%.*]] = fcmp olt <4 x float> [[LOAD]], zeroinitializer
+  // CHECK-NEXT: [[ANYC:%.*]] = call i1 @llvm.dx.any.v4i1(<4 x i1> [[FCMP]])
+  // CHECK-NEXT: call void @llvm.dx.clip(i1 [[ANYC]])
+  //
+  // SPIRV:      define spir_func void @{{.*}}test_vector{{.*}}(<4 x float> {{.*}} [[VALP:%.*]])
+  // SPIRV:      [[LOAD:%.*]] = load <4 x float>, ptr [[VALP]].addr, align 16
+  // SPIRV-NEXT: [[FCMP:%.*]] = fcmp olt <4 x float> [[LOAD]], zeroinitializer
+  // SPIRV-NEXT: [[ANYC:%.*]] = call i1 @llvm.spv.any.v4i1(<4 x i1> [[FCMP]]) 
+  // SPIRV-NEXT: br i1 [[ANYC]], label %[[LTL:.*]], label %[[ENDL:.*]]
+  // SPIRV:      [[LTL]]: ; preds = %entry
+  // SPIRV-NEXT: call void @llvm.spv.clip()
+  // SPIRV-NEXT: br label %[[ENDL]]
+  clip(Buf);
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/clip-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/clip-errors.hlsl
new file mode 100644
index 00000000000000..570b4bc18dcd4b
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/clip-errors.hlsl
@@ -0,0 +1,22 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -fnative-half-type -verify
+
+
+void test_arg_missing() {
+  __builtin_hlsl_elementwise_clip();
+ // expected-error@-1 {{too few arguments to function call, expected 1, have 0}} 
+}
+
+void test_too_many_args(float p1, float p2) {
+  __builtin_hlsl_elementwise_clip(p1, p2);
+ // expected-error@-1 {{too many arguments to function call, expected 1, have 2}} 
+}
+
+void test_first_arg_type_mismatch(bool p) {
+  __builtin_hlsl_elementwise_clip(p);
+ // expected-error@-1 {{invalid operand of type 'bool' where 'float' or a vector of such type is required}} 
+}
+
+void test_first_arg_type_mismatch_2(half p) {
+  __builtin_hlsl_elementwise_clip(p);
+ // expected-error@-1 {{invalid operand of type 'double' where 'float' or a vector of such type is required}} 
+}
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index e30d37f69f781e..0d8dc4ead02c8a 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -92,4 +92,5 @@ def int_dx_step : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty, L
 def int_dx_splitdouble : DefaultAttrsIntrinsic<[llvm_anyint_ty, LLVMMatchType<0>], 
     [LLVMScalarOrSameVectorWidth<0, llvm_double_ty>], [IntrNoMem]>;
 def int_dx_radians : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [LLVMMatchType<0>], [IntrNoMem]>;
+def int_dx_clip : DefaultAttrsIntrinsic<[], [llvm_i1_ty], [IntrNoMem]>;
 }
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index ddb47390537412..9d2f85315f63e0 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -88,6 +88,7 @@ let TargetPrefix = "spv" in {
   def int_spv_sign : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_any_ty], [IntrNoMem]>;
   def int_spv_radians : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty], [IntrNoMem]>;
   def int_spv_group_memory_barrier_with_group_sync : DefaultAttrsIntrinsic<[], [], []>;
+  def int_spv_clip : Intrinsic<[], [], []>;
 
   // Create resource handle given the binding information. Returns a 
   // type appropriate for the kind of resource given the set id, binding id,
diff --git a/llvm/lib/Target/DirectX/DXIL.td b/llvm/lib/Target/DirectX/DXIL.td
index 1e8dc63ffa257e..94b1f6ff9cc088 100644
--- a/llvm/lib/Target/DirectX/DXIL.td
+++ b/llvm/lib/Target/DirectX/DXIL.td
@@ -739,6 +739,15 @@ def CheckAccessFullyMapped : DXILOp<71, checkAccessFullyMapped> {
   let stages = [Stages<DXIL1_0, [all_stages]>];
 }
 
+def Discard : DXILOp<82, discard> {
+  let Doc = "discard the current pixel";
+  let LLVMIntrinsic = int_dx_clip;
+  let arguments = [Int1Ty];
+  let result = VoidTy;
+  let stages = [Stages<DXIL1_0, [pixel]>];
+  let attributes = [Attributes<DXIL1_0, [ReadNone]>];
+}
+
 def ThreadId :  DXILOp<93, threadId> {
   let Doc = "Reads the thread ID";
   let LLVMIntrinsic = int_dx_thread_id;
@@ -788,20 +797,6 @@ def SplitDouble :  DXILOp<102, splitDouble> {
   let attributes = [Attributes<DXIL1_0, [ReadNone]>];
 }
 
-def AnnotateHandle : DXILOp<217, annotateHandle> {
-  let Doc = "annotate handle with resource properties";
-  let arguments = [HandleTy, ResPropsTy];
-  let result = HandleTy;
-  let stages = [Stages<DXIL1_6, [all_stages]>];
-}
-
-def CreateHandleFromBinding : DXILOp<218, createHandleFromBinding> {
-  let Doc = "create resource handle from binding";
-  let arguments = [ResBindTy, Int32Ty, Int1Ty];
-  let result = HandleTy;
-  let stages = [Stages<DXIL1_6, [all_stages]>];
-}
-
 def WaveIsFirstLane :  DXILOp<110, waveIsFirstLane> {
   let Doc = "returns 1 for the first lane in the wave";
   let LLVMIntrinsic = int_dx_wave_is_first_lane;
@@ -811,6 +806,15 @@ def WaveIsFirstLane :  DXILOp<110, waveIsFirstLane> {
   let attributes = [Attributes<DXIL1_0, [ReadNone]>];
 }
 
+def WaveGetLaneIndex : DXILOp<111, waveGetLaneIndex> {
+  let Doc = "returns the index of the current lane in the wave";
+  let LLVMIntrinsic = int_dx_wave_getlaneindex;
+  let arguments = [];
+  let result = Int32Ty;
+  let stages = [Stages<DXIL1_0, [all_stages]>];
+  let attributes = [Attributes<DXIL1_0, [ReadNone]>];
+}
+
 def WaveReadLaneAt:  DXILOp<117, waveReadLaneAt> {
   let Doc = "returns the value from the specified lane";
   let LLVMIntrinsic = int_dx_wave_readlane;
@@ -821,11 +825,16 @@ def WaveReadLaneAt:  DXILOp<117, waveReadLaneAt> {
   let attributes = [Attributes<DXIL1_0, [ReadNone]>];
 }
 
-def WaveGetLaneIndex : DXILOp<111, waveGetLaneIndex> {
-  let Doc = "returns the index of the current lane in the wave";
-  let LLVMIntrinsic = int_dx_wave_getlaneindex;
-  let arguments = [];
-  let result = Int32Ty;
-  let stages = [Stages<DXIL1_0, [all_stages]>];
-  let attributes = [Attributes<DXIL1_0, [ReadNone]>];
+def AnnotateHandle : DXILOp<217, annotateHandle> {
+  let Doc = "annotate handle with resource properties";
+  let arguments = [HandleTy, ResPropsTy];
+  let result = HandleTy;
+  let stages = [Stages<DXIL1_6, [all_stages]>];
+}
+
+def CreateHandleFromBinding : DXILOp<218, createHandleFromBinding> {
+  let Doc = "create resource handle from binding";
+  let arguments = [ResBindTy, Int32Ty, Int1Ty];
+  let result = HandleTy;
+  let stages = [Stages<DXIL1_6, [all_stages]>];
 }
diff --git a/llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp b/llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp
index dbfc133864bba4..23221cacca7df3 100644
--- a/llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp
@@ -73,7 +73,9 @@ static const std::map<std::string, SPIRV::Extension::Extension>
         {"SPV_KHR_cooperative_matrix",
          SPIRV::Extension::Extension::SPV_KHR_cooperative_matrix},
         {"SPV_KHR_non_semantic_info",
-         SPIRV::Extension::Extension::SPV_KHR_non_semantic_info}};
+         SPIRV::Extension::Extension::SPV_KHR_non_semantic_info},
+        {"SPV_EXT_demote_to_helper_invocation",
+         SPIRV::Extension::Extension::SPV_EXT_demote_to_helper_invocation}};
 
 bool SPIRVExtensionsParser::parse(cl::Option &O, llvm::StringRef ArgName,
                                   llvm::StringRef ArgValue,
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstrInfo.td b/llvm/lib/Target/SPIRV/SPIRVInstrInfo.td
index ee6b70a16417f4..d8e27153bd7d24 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstrInfo.td
+++ b/llvm/lib/Target/SPIRV/SPIRVInstrInfo.td
@@ -636,6 +636,7 @@ let isReturn = 1, hasDelaySlot = 0, isBarrier = 0, isTerminator = 1, isNotDuplic
 }
 def OpLifetimeStart: Op<256, (outs), (ins ID:$ptr, i32imm:$sz), "OpLifetimeStart $ptr, $sz">;
 def OpLifetimeStop: Op<257, (outs), (ins ID:$ptr, i32imm:$sz), "OpLifetimeStop $ptr, $sz">;
+def OpDemoteToHelperInvocation: SimpleOp<"OpDemoteToHelperInvocation", 5380>;
 
 // 3.42.18 Atomic Instructions
 
diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 892912a5680113..be3852ed482c83 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -147,6 +147,9 @@ class SPIRVInstructionSelector : public InstructionSelector {
                  unsigned comparisonOpcode, MachineInstr &I) const;
   bool selectCross(Register ResVReg, const SPIRVType *ResType,
                    MachineInstr &I) const;
+  bool selectClip(Register ResVReg, const SPIRVType *ResType,
+                  MachineInstr &I) const;
+
   bool selectICmp(Register ResVReg, const SPIRVType *ResType,
                   MachineInstr &I) const;
   bool selectFCmp(Register ResVReg, const SPIRVType *ResType,
@@ -1966,6 +1969,32 @@ bool SPIRVInstructionSelector::selectSplatVector(Register ResVReg,
   return MIB.constrainAllUses(TII, TRI, RBI);
 }
 
+bool SPIRVInstructionSelector::selectClip(Register ResVReg,
+                                          const SPIRVType *ResType,
+                                          MachineInstr &I) const {
+
+  unsigned Opcode;
+
+  if (STI.isAtLeastSPIRVVer(VersionTuple(1, 6))) {
+    if (!STI.canUseExtension(
+            SPIRV::Extension::SPV_EXT_demote_to_helper_invocation))
+      report_fatal_error(
+          "llvm.spv.clip intrinsic: this instruction requires the following "
+          "SPIR-V extension: SPV_EXT_demote_to_helper_invocation",
+          false);
+    Opcode = SPIRV::OpDemoteToHelperInvocation;
+  } else {
+    Opcode = SPIRV::OpKill;
+    // OpKill must be the last operation of any basic block.
+    MachineInstr *NextI = I.getNextNode();
+    NextI->removeFromParent();
+  }
+
+  MachineBasicBlock &BB = *I.getParent();
+  return BuildMI(BB, I, I.getDebugLoc(), TII.get(Opcode))
+      .constrainAllUses(TII, TRI, RBI);
+}
+
 bool SPIRVInstructionSelector::selectCmp(Register ResVReg,
                                          const SPIRVType *ResType,
                                          unsigned CmpOpc,
@@ -2599,6 +2628,9 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
     selectHandleFromBinding(ResVReg, ResType, I);
     return true;
   }
+  case Intrinsic::spv_clip: {
+    return selectClip(ResVReg, ResType, I);
+  }
   default: {
     std::string DiagMsg;
     raw_string_ostream OS(DiagMsg);
diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
index 29ce60d9983e38..bc41ee96f2af66 100644
--- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
@@ -1376,6 +1376,17 @@ void addInstrRequirements(const MachineInstr &MI,
       Reqs.addCapability(SPIRV::Capability::SplitBarrierINTEL);
     }
     break;
+
+  case SPIRV::OpKill: {
+    Reqs.addCapability(SPIRV::Capability::Shader);
+  } break;
+  case SPIRV::OpDemoteToHelperInvocation:
+    if (ST.canUseExtension(
+            SPIRV::Extension::SPV_EXT_demote_to_helper_invocation)) {
+      Reqs.addExtension(SPIRV::Extension::SPV_EXT_demote_to_helper_invocation);
+      Reqs.addCapability(SPIRV::Capability::DemoteToHelperInvocation);
+    }
+    break;
   default:
     break;
   }
diff --git a/llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td b/llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td
index d63438baca7e76..edf6e5547631a1 100644
--- a/llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td
+++ b/llvm/lib/Target/SPIRV/SPIRVSymbolicOperands.td
@@ -456,6 +456,7 @@ defm VulkanMemoryModelDeviceScopeKHR : CapabilityOperand<5346, 0, 0, [], []>;
 defm ImageFootprintNV : CapabilityOperand<5282, 0, 0, [], []>;
 defm FragmentBarycentricNV : CapabilityOperand<5284, 0, 0, [], []>;
 defm ComputeDerivativeGroupQuadsNV : CapabilityOperand<5288, 0, 0, [], []>;
+defm DemoteToHelperInvocation : CapabilityOperand<5379, 0, 0, [SPV_EXT_demote_to_helper_invocation], []>;
 defm ComputeDerivativeGroupLinearNV : CapabilityOperand<5350, 0, 0, [], []>;
 defm FragmentDensityEXT : CapabilityOperand<5291, 0, 0, [], [Shader]>;
 defm PhysicalStorageBufferAddressesEXT : CapabilityOperand<5347, 0, 0, [], [Shader]>;
diff --git a/llvm/test/CodeGen/DirectX/clip.ll b/llvm/test/CodeGen/DirectX/clip.ll
new file mode 100644
index 00000000000000..71789e7048363a
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/clip.ll
@@ -0,0 +1,29 @@
+; RUN: opt -passes='function(scalarizer),module(dxil-op-lower,dxil-intrinsic-expansion)' -S -mtriple=dxil-pc-shadermodel6.3-pixel %s | FileCheck %s
+
+; CHECK-LABEL: define void @test_scalar
+; CHECK: call void @dx.op.discard(i32 82, i1 %0)
+;
+define void @test_scalar(float noundef %p) #0 {
+entry:
+  %0 = fcmp olt float %p, 0.000000e+00
+  call void @llvm.dx.clip(i1 %0)
+  ret void
+}
+
+; CHECK-LABEL: define void @test_vector
+; CHECK:       [[EXTR0:%.*]] = extractelement <4 x i1> [[INP:%.*]], i64 0
+; CHECK-NEXT:  [[EXTR1:%.*]] = extractelement <4 x i1> [[INP:%.*]], i64 1
+; CHECK-NEXT:  [[OR1:%.*]] = or i1 [[EXTR0]], [[EXTR1]]
+; CHECK-NEXT:  [[EXTR2:%.*]] = extractelement <4 x i1> [[INP:%.*]], i64 2
+; CHECK-NEXT:  [[OR2:%.*]] = or i1 [[OR1]], [[EXTR2]]
+; CHECK-NEXT:  [[EXTR3:%.*]] = extractelement <4 x i1> [[INP:%.*]], i64 3
+; CHECK-NEXT:  [[OR3:%.*]] = or i1 [[OR2]], [[EXTR3]]
+; CHECK-NEXT:  call void @dx.op.discard(i32 82, i1 [[OR3]])
+;
+define void @test_vector(<4 x float> noundef %p) #0 {
+entry:
+  %0 = fcmp olt <4 x float> %p, zeroinitializer
+  %1 = call i1 @llvm.dx.any.v4i1(<4 x i1> %0)
+  call void @llvm.dx.clip(i1 %1)
+  ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/clip.ll b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/clip.ll
new file mode 100644
index 00000000000000..89db4be3494ebd
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/clip.ll
@@ -0,0 +1,77 @@
+; RUN: llc -verify-machineinstrs -O0 -mtriple=spirv-unknown-unknown %s -o - | FileCheck %s --check-prefixes=CHECK,SPIRV15
+; RUN: llc -verify-machineinstrs -spirv-ext=+SPV_EXT_demote_to_helper_invocation -O0 -mtriple=spirv32v1.6-unknown-unknown %s -o - | FileCheck %s --check-prefixes=CHECK,SPIRV16
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+
+; Make sure lowering is ...
[truncated]

@joaosaffran joaosaffran force-pushed the intrinsics/clip branch 2 times, most recently from 834ebff to 61c626d Compare November 7, 2024 00:38
@@ -89,6 +89,7 @@ let TargetPrefix = "spv" in {
def int_spv_sign : DefaultAttrsIntrinsic<[LLVMScalarOrSameVectorWidth<0, llvm_i32_ty>], [llvm_any_ty], [IntrNoMem]>;
def int_spv_radians : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty], [IntrNoMem]>;
def int_spv_group_memory_barrier_with_group_sync : DefaultAttrsIntrinsic<[], [], []>;
def int_spv_clip : Intrinsic<[], [], []>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The attributes here don't match with the dx version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature for this intrinsic is different in DX and SPIRV by design. As discussed here: #114588 (comment), SPIRVInstructionSelector.cpp doesn't support creating new basic blocks. So after discussing with @farzonl, we came to a conclusion that, in order to not introduce new patterns to intrisic creations, we are handling most of the codegen and CGBuiltin.cpp. As a consequence, the codegen requires those intrinsic to have different signatures.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I see. Although I suppose I don't understand why that wouldn't allow us to specify the IntrNoMem attribute here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This intrinsic has side effects, it discards the current pixel, therefore it cannot use IntrNoMem

Copy link
Contributor

@s-perron s-perron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just need to fix up the misunderstanding about how the spirv version and the extension interact.

@joaosaffran joaosaffran force-pushed the intrinsics/clip branch 4 times, most recently from 953c7dd to 3b7d9e5 Compare November 8, 2024 19:55
Copy link
Member

@farzonl farzonl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments, but rest LGTM. Code correct as ticket is currently defined. We may want to move the behavior you currently have in cgbuiltins to hlsl_intrinsics.h. That way for spirv instead of a basic block we could do a c++ if statement. That can wait though until the team finalizes our plan.

@joaosaffran joaosaffran merged commit bc6c068 into llvm:main Nov 15, 2024
7 of 10 checks passed
@joaosaffran joaosaffran deleted the intrinsics/clip branch February 26, 2025 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:DirectX backend:SPIR-V backend:X86 clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:headers Headers provided by Clang, e.g. for intrinsics clang Clang issues not falling into any other category HLSL HLSL Language Support llvm:ir
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Implement the clip HLSL Function
6 participants