Skip to content
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

[HLSL] Adding HLSL clip function. #114588

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

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

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff a6637ae2cc9a0e7c9a37603b3d277d7ca642bc36 61c626de4c4d7315795545887e028f89116b054d --extensions cpp,h -- clang/lib/CodeGen/CGBuiltin.cpp clang/lib/Headers/hlsl/hlsl_intrinsics.h clang/lib/Sema/SemaHLSL.cpp llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
View the diff from clang-format here.
diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
index 110faae3dc..b50fb4ba50 100644
--- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
@@ -1408,14 +1408,15 @@ void addInstrRequirements(const MachineInstr &MI,
     }
     break;
   case SPIRV::OpKill: {
-    Reqs.addCapability(SPIRV::Capability::Shader) ;
+    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;
+    }
+    break;
   case SPIRV::OpSDot:
   case SPIRV::OpUDot:
     AddDotProductRequirements(MI, Reqs, ST);

@joaosaffran joaosaffran force-pushed the intrinsics/clip branch 4 times, most recently from 061e3f4 to bfad38c Compare November 4, 2024 06:39
@joaosaffran joaosaffran force-pushed the intrinsics/clip branch 2 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 2 times, most recently from ec42b5b to 2fb4bf9 Compare November 4, 2024 18:21
@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 backend:DirectX HLSL HLSL Language Support backend:SPIR-V llvm:ir labels Nov 4, 2024
@llvmbot
Copy link
Collaborator

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]

CMP = CGF->Builder.CreateFCmpOLT(Op0, FZeroConst);

if (CGF->CGM.getTarget().getTriple().isDXIL())
return CGF->Builder.CreateIntrinsic(CGF->VoidTy, llvm::Intrinsic::dx_clip,
Copy link
Member

@farzonl farzonl Nov 4, 2024

Choose a reason for hiding this comment

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

This is a bit different than how we have done intrinsics that invoke other intrinsics. For example see expandNormalizeIntrinsic in llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp. In this example dx_normaize exapnds to:

fdiv
dx_dot
dx_rsqrt
fmul

While in this case we associate the instruction expansion with the clang builtin. This isn't wrong, its just different as past implementers have tried to keep target specific behaviors in their respective targets.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will be happy to move the expansion into llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp, in order to keep code consistency. However, I have a question, do we have a similar "expassion" pass for spirv? Asking because the any intrinsic is used both by SPIRV and DXIL backend in order to handle the vector scenario.

Copy link
Member

Choose a reason for hiding this comment

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

For SPIRV I beleive you could just invoke selectAny in llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp. Also not saying this has to change. But I want to be conservative with adding new patterns unless there is a good reason to do so.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here is an example of the current generated IR:

define void @test_vector(<4 x float> noundef %Buf) {
entry:
  %Buf.addr = alloca <4 x float>, align 16
  store <4 x float> %Buf, ptr %Buf.addr, align 16
  %1 = load <4 x float>, ptr %Buf.addr, align 16
  %2 = fcmp olt <4 x float> %1, zeroinitializer
  %3 = call i1 @llvm.spv.any.v4i1(<4 x i1> %2)
  br i1 %3, label %lt0, label %end

lt0:                                              ; preds = %entry
  call void @llvm.spv.clip()
  br label %end

end:                                              ; preds = %lt0, %entry
  ret void
}

The challenge, I see, with invoke selectAny when handling selectClip is that we need to call any before calling the conditional branch. In order to call selectAny in llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp that would also require creating the branch call when selecting the spirv instruction. That doesn't seem to be the proper place to generate the branching code. Please, let me know if I am missing something here.

Copy link
Member

@farzonl farzonl Nov 5, 2024

Choose a reason for hiding this comment

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

Right now, you have @llvm.spv.clip() maps to OpDemoteToHelperInvocation or OpKill depending on some extension availability. What I'm suggesting is twofold. First make your intrinsic the same number of arguments as your clang alias and the same as the dx intrinsic. Second that will mean register 1 to selectClip is what you will be used by selectAny Then do your branch invocations only in the spirv backend. In summary the instruction selection will be everything you need for this intrinsic not just OpDemoteToHelperInvocation orOpKill`.

https://hlsl.godbolt.org/z/ose5EGWEx

%21 = OpFOrdLessThan %v4bool %20 %11
%22 = OpAny %bool %21
      OpSelectionMerge %23 None
      OpBranchConditional %22 %24 %23
%24 = OpLabel
      OpKill | OpDemoteToHelperInvocation
%23 = OpLabel

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 process make sense, however generating basic blocks doesn't seem to be allowed in SPIRV backend yet, as I see this error when creating new MBBs in llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp.

LLVM ERROR: inserting blocks is not supported yet (in function: test_scalar)

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 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
Status: No status
Development

Successfully merging this pull request may close these issues.

Implement the clip HLSL Function
4 participants