[clang] [llvm] [HLSL] Add HLSL pack and pack_clamp intrinsics (PR #228089)

Alexander Johnston via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 1 07:56:55 PDT 2026


https://github.com/Alexander-Johnston updated https://github.com/llvm/llvm-project/pull/228089

>From d499d664750792c840cf510f4b5ed466d17b25b2 Mon Sep 17 00:00:00 2001
From: Alexander Johnston <alexander.johnston at amd.com>
Date: Thu, 1 Oct 2026 12:59:05 +0100
Subject: [PATCH 1/3] [HLSL] Add hlsl pack intrinsics to clang

---
 clang/include/clang/Basic/Builtins.td         | 24 +++++++++
 clang/include/clang/Basic/HLSLIntrinsics.td   | 36 +++++++++++++
 clang/lib/CodeGen/CGHLSLBuiltins.cpp          | 24 +++++++++
 clang/lib/CodeGen/CGHLSLRuntime.h             |  4 ++
 clang/lib/Sema/SemaHLSL.cpp                   | 52 +++++++++++++++++++
 .../CodeGenHLSL/builtins/pack_clamp_s8.hlsl   | 16 ++++++
 .../CodeGenHLSL/builtins/pack_clamp_u8.hlsl   | 16 ++++++
 clang/test/CodeGenHLSL/builtins/pack_s8.hlsl  | 16 ++++++
 clang/test/CodeGenHLSL/builtins/pack_u8.hlsl  | 16 ++++++
 .../BuiltIns/pack_clamp_s8-errors.hlsl        | 31 +++++++++++
 .../BuiltIns/pack_clamp_u8-errors.hlsl        | 31 +++++++++++
 .../SemaHLSL/BuiltIns/pack_s8-errors.hlsl     | 31 +++++++++++
 .../SemaHLSL/BuiltIns/pack_u8-errors.hlsl     | 31 +++++++++++
 llvm/include/llvm/IR/IntrinsicsDirectX.td     |  6 +++
 llvm/include/llvm/IR/IntrinsicsSPIRV.td       |  4 ++
 15 files changed, 338 insertions(+)
 create mode 100644 clang/test/CodeGenHLSL/builtins/pack_clamp_s8.hlsl
 create mode 100644 clang/test/CodeGenHLSL/builtins/pack_clamp_u8.hlsl
 create mode 100644 clang/test/CodeGenHLSL/builtins/pack_s8.hlsl
 create mode 100644 clang/test/CodeGenHLSL/builtins/pack_u8.hlsl
 create mode 100644 clang/test/SemaHLSL/BuiltIns/pack_clamp_s8-errors.hlsl
 create mode 100644 clang/test/SemaHLSL/BuiltIns/pack_clamp_u8-errors.hlsl
 create mode 100644 clang/test/SemaHLSL/BuiltIns/pack_s8-errors.hlsl
 create mode 100644 clang/test/SemaHLSL/BuiltIns/pack_u8-errors.hlsl

diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 5480aed3d543972..dfdadc87a6ce02f 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -5968,6 +5968,30 @@ def HLSLDdyFine : LangBuiltin<"HLSL_LANG"> {
   let Prototype = "void(...)";
 }
 
+def HLSLPackU8 : LangBuiltin<"HLSL_LANG"> {
+  let Spellings = ["__builtin_hlsl_pack_u8"];
+  let Attributes = [NoThrow, CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def HLSLPackS8 : LangBuiltin<"HLSL_LANG"> {
+  let Spellings = ["__builtin_hlsl_pack_s8"];
+  let Attributes = [NoThrow, CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def HLSLPackClampU8 : LangBuiltin<"HLSL_LANG"> {
+  let Spellings = ["__builtin_hlsl_pack_clamp_u8"];
+  let Attributes = [NoThrow, CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
+def HLSLPackClampS8 : LangBuiltin<"HLSL_LANG"> {
+  let Spellings = ["__builtin_hlsl_pack_clamp_s8"];
+  let Attributes = [NoThrow, CustomTypeChecking];
+  let Prototype = "void(...)";
+}
+
 // Builtins for XRay.
 def XRayCustomEvent : Builtin {
   let Spellings = ["__xray_customevent"];
diff --git a/clang/include/clang/Basic/HLSLIntrinsics.td b/clang/include/clang/Basic/HLSLIntrinsics.td
index 4be051476395cdc..49e7f83a14472d7 100644
--- a/clang/include/clang/Basic/HLSLIntrinsics.td
+++ b/clang/include/clang/Basic/HLSLIntrinsics.td
@@ -1927,3 +1927,39 @@ let VaryingLongVector = 1;
 let IsConvergent = 1;
 let Availability = SM6_0;
 }
+
+def hlsl_pack_u8 : HLSLOneArgBuiltin<"pack_u8", "__builtin_hlsl_pack_u8"> {
+  let VaryingTypes = [UInt16Ty, UIntTy];
+  let VaryingScalar = 0;
+  let VaryingVecSizes = [4];
+  let VaryingMatDims = [];
+  let ReturnType = UInt8PackedTy;
+  let Availability = SM6_6;
+}
+
+def hlsl_pack_s8 : HLSLOneArgBuiltin<"pack_s8", "__builtin_hlsl_pack_s8"> {
+  let VaryingTypes = [Int16Ty, IntTy];
+  let VaryingScalar = 0;
+  let VaryingVecSizes = [4];
+  let VaryingMatDims = [];
+  let ReturnType = Int8PackedTy;
+  let Availability = SM6_6;
+}
+
+def hlsl_pack_clamp_u8 : HLSLOneArgBuiltin<"pack_clamp_u8", "__builtin_hlsl_pack_clamp_u8"> {
+  let VaryingTypes = [Int16Ty, IntTy];
+  let VaryingScalar = 0;
+  let VaryingVecSizes = [4];
+  let VaryingMatDims = [];
+  let ReturnType = UInt8PackedTy;
+  let Availability = SM6_6;
+}
+
+def hlsl_pack_clamp_s8 : HLSLOneArgBuiltin<"pack_clamp_s8", "__builtin_hlsl_pack_clamp_s8"> {
+  let VaryingTypes = [Int16Ty, IntTy];
+  let VaryingScalar = 0;
+  let VaryingVecSizes = [4];
+  let VaryingMatDims = [];
+  let ReturnType = Int8PackedTy;
+  let Availability = SM6_6;
+}
diff --git a/clang/lib/CodeGen/CGHLSLBuiltins.cpp b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
index aa26727e8ad17eb..9e75d33f29c3a0a 100644
--- a/clang/lib/CodeGen/CGHLSLBuiltins.cpp
+++ b/clang/lib/CodeGen/CGHLSLBuiltins.cpp
@@ -1799,6 +1799,30 @@ Value *CodeGenFunction::EmitHLSLBuiltinExpr(unsigned BuiltinID,
                                    ArrayRef<Value *>{Op0}, nullptr,
                                    "hlsl.ddy.fine");
   }
+  case Builtin::BI__builtin_hlsl_pack_u8: {
+    Value *Op0 = EmitScalarExpr(E->getArg(0));
+    Intrinsic::ID ID = CGM.getHLSLRuntime().getPackU8Intrinsic();
+    return Builder.CreateIntrinsic(/*ReturnType=*/Builder.getInt32Ty(), ID,
+                                   {Op0}, nullptr, "hlsl.pack.u8");
+  }
+  case Builtin::BI__builtin_hlsl_pack_s8: {
+    Value *Op0 = EmitScalarExpr(E->getArg(0));
+    Intrinsic::ID ID = CGM.getHLSLRuntime().getPackS8Intrinsic();
+    return Builder.CreateIntrinsic(/*ReturnType=*/Builder.getInt32Ty(), ID,
+                                   {Op0}, nullptr, "hlsl.pack.s8");
+  }
+  case Builtin::BI__builtin_hlsl_pack_clamp_u8: {
+    Value *Op0 = EmitScalarExpr(E->getArg(0));
+    Intrinsic::ID ID = CGM.getHLSLRuntime().getPackClampU8Intrinsic();
+    return Builder.CreateIntrinsic(/*ReturnType=*/Builder.getInt32Ty(), ID,
+                                   {Op0}, nullptr, "hlsl.pack.clamp.u8");
+  }
+  case Builtin::BI__builtin_hlsl_pack_clamp_s8: {
+    Value *Op0 = EmitScalarExpr(E->getArg(0));
+    Intrinsic::ID ID = CGM.getHLSLRuntime().getPackClampS8Intrinsic();
+    return Builder.CreateIntrinsic(/*ReturnType=*/Builder.getInt32Ty(), ID,
+                                   {Op0}, nullptr, "hlsl.pack.clamp.s8");
+  }
   case Builtin::BI__builtin_get_spirv_spec_constant_bool:
   case Builtin::BI__builtin_get_spirv_spec_constant_short:
   case Builtin::BI__builtin_get_spirv_spec_constant_ushort:
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.h b/clang/lib/CodeGen/CGHLSLRuntime.h
index 11b3e0220dcfbab..267fce649779780 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.h
+++ b/clang/lib/CodeGen/CGHLSLRuntime.h
@@ -214,6 +214,10 @@ class CGHLSLRuntime {
   GENERATE_HLSL_INTRINSIC_FUNCTION(DdyCoarse, ddy_coarse)
   GENERATE_HLSL_INTRINSIC_FUNCTION(DdxFine, ddx_fine)
   GENERATE_HLSL_INTRINSIC_FUNCTION(DdyFine, ddy_fine)
+  GENERATE_HLSL_INTRINSIC_FUNCTION(PackS8, pack_s8)
+  GENERATE_HLSL_INTRINSIC_FUNCTION(PackU8, pack_u8)
+  GENERATE_HLSL_INTRINSIC_FUNCTION(PackClampS8, pack_clamp_s8)
+  GENERATE_HLSL_INTRINSIC_FUNCTION(PackClampU8, pack_clamp_u8)
 
   //===----------------------------------------------------------------------===//
   // End of reserved area for HLSL intrinsic getters.
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index d06491b32ea1b2f..db446bee969fc6d 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -4952,6 +4952,58 @@ bool SemaHLSL::CheckBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) {
                                getASTContext().UnsignedIntTy);
     break;
   }
+  case Builtin::BI__builtin_hlsl_pack_u8: {
+    if (SemaRef.checkArgCount(TheCall, 1))
+      return true;
+    const auto *VecTy = TheCall->getArg(0)->getType()->getAs<VectorType>();
+    if (!VecTy || !VecTy->getElementType()->isUnsignedIntegerType() ||
+        (SemaRef.Context.getTypeSize(VecTy->getElementType()) != 16 &&
+         SemaRef.Context.getTypeSize(VecTy->getElementType()) != 32) ||
+        VecTy->getNumElements() != 4) {
+      SemaRef.Diag(TheCall->getArg(0)->getBeginLoc(),
+                   diag::err_builtin_invalid_arg_type)
+          << 1 << /* vector of */ 4 << /* unsigned integer */ 3 << /* no fp */ 0
+          << TheCall->getArg(0)->getType();
+      return true;
+    }
+    TheCall->setType(getASTContext().UInt8_4PackedTy);
+    break;
+  }
+  case Builtin::BI__builtin_hlsl_pack_clamp_u8: {
+    if (SemaRef.checkArgCount(TheCall, 1))
+      return true;
+    const auto *VecTy = TheCall->getArg(0)->getType()->getAs<VectorType>();
+    if (!VecTy || !VecTy->getElementType()->isSignedIntegerType() ||
+        (SemaRef.Context.getTypeSize(VecTy->getElementType()) != 16 &&
+         SemaRef.Context.getTypeSize(VecTy->getElementType()) != 32) ||
+        VecTy->getNumElements() != 4) {
+      SemaRef.Diag(TheCall->getArg(0)->getBeginLoc(),
+                   diag::err_builtin_invalid_arg_type)
+          << 1 << /* vector of */ 4 << /* signed integer */ 2 << /* no fp */ 0
+          << TheCall->getArg(0)->getType();
+      return true;
+    }
+    TheCall->setType(getASTContext().UInt8_4PackedTy);
+    break;
+  }
+  case Builtin::BI__builtin_hlsl_pack_s8:
+  case Builtin::BI__builtin_hlsl_pack_clamp_s8: {
+    if (SemaRef.checkArgCount(TheCall, 1))
+      return true;
+    const auto *VecTy = TheCall->getArg(0)->getType()->getAs<VectorType>();
+    if (!VecTy || !VecTy->getElementType()->isSignedIntegerType() ||
+        (SemaRef.Context.getTypeSize(VecTy->getElementType()) != 16 &&
+         SemaRef.Context.getTypeSize(VecTy->getElementType()) != 32) ||
+        VecTy->getNumElements() != 4) {
+      SemaRef.Diag(TheCall->getArg(0)->getBeginLoc(),
+                   diag::err_builtin_invalid_arg_type)
+          << 1 << /* vector of */ 4 << /* signed integer */ 2 << /* no fp */ 0
+          << TheCall->getArg(0)->getType();
+      return true;
+    }
+    TheCall->setType(getASTContext().Int8_4PackedTy);
+    break;
+  }
   }
   return false;
 }
diff --git a/clang/test/CodeGenHLSL/builtins/pack_clamp_s8.hlsl b/clang/test/CodeGenHLSL/builtins/pack_clamp_s8.hlsl
new file mode 100644
index 000000000000000..80db19d467f1096
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/pack_clamp_s8.hlsl
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple dxil-pc-shadermodel6.6-library %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-int16-type -fnative-half-type -o - | \
+// RUN:  FileCheck %s -DCALL=dx
+// RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple spirv-pc-vulkan-library %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-int16-type -fnative-half-type -o - | \
+// RUN:  FileCheck %s -DCALL=spv
+
+// CHECK-LABEL: define {{.*}} i32 @_Z8test_u16Dv4_s
+// CHECK: [[VAR:%.*]] = call i32 @llvm.[[CALL]].pack.clamp.s8.v4i16(<4 x i16> %{{.*}})
+// CHECK-NEXT: ret i32 [[VAR]]
+int8_t4_packed test_u16(int16_t4 val) { return pack_clamp_s8(val); }
+
+// CHECK-LABEL: define {{.*}} i32 @_Z8test_u32Dv4_i
+// CHECK: [[VAR:%.*]] = call i32 @llvm.[[CALL]].pack.clamp.s8.v4i32(<4 x i32> %{{.*}})
+// CHECK-NEXT: ret i32 [[VAR]]
+int8_t4_packed test_u32(int4 val) { return pack_clamp_s8(val); }
diff --git a/clang/test/CodeGenHLSL/builtins/pack_clamp_u8.hlsl b/clang/test/CodeGenHLSL/builtins/pack_clamp_u8.hlsl
new file mode 100644
index 000000000000000..99beb20843ea8e2
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/pack_clamp_u8.hlsl
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple dxil-pc-shadermodel6.6-library %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-int16-type -fnative-half-type -o - | \
+// RUN:  FileCheck %s -DCALL=dx
+// RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple spirv-pc-vulkan-library %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-int16-type -fnative-half-type -o - | \
+// RUN:  FileCheck %s -DCALL=spv
+
+// CHECK-LABEL: define {{.*}} i32 @_Z8test_u16Dv4_s
+// CHECK: [[VAR:%.*]] = call i32 @llvm.[[CALL]].pack.clamp.u8.v4i16(<4 x i16> %{{.*}})
+// CHECK-NEXT: ret i32 [[VAR]]
+uint8_t4_packed test_u16(int16_t4 val) { return pack_clamp_u8(val); }
+
+// CHECK-LABEL: define {{.*}} i32 @_Z8test_u32Dv4_i
+// CHECK: [[VAR:%.*]] = call i32 @llvm.[[CALL]].pack.clamp.u8.v4i32(<4 x i32> %{{.*}})
+// CHECK-NEXT: ret i32 [[VAR]]
+uint8_t4_packed test_u32(int4 val) { return pack_clamp_u8(val); }
diff --git a/clang/test/CodeGenHLSL/builtins/pack_s8.hlsl b/clang/test/CodeGenHLSL/builtins/pack_s8.hlsl
new file mode 100644
index 000000000000000..4b90a2890a54ba5
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/pack_s8.hlsl
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple dxil-pc-shadermodel6.6-library %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-int16-type -fnative-half-type -o - | \
+// RUN:  FileCheck %s -DCALL=dx
+// RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple spirv-pc-vulkan-library %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-int16-type -fnative-half-type -o - | \
+// RUN:  FileCheck %s -DCALL=spv
+
+// CHECK-LABEL: define {{.*}} i32 @_Z8test_s16Dv4_s
+// CHECK: [[VAR:%.*]] = call i32 @llvm.[[CALL]].pack.s8.v4i16(<4 x i16> %{{.*}})
+// CHECK-NEXT: ret i32 [[VAR]]
+int8_t4_packed test_s16(int16_t4 val) { return pack_s8(val); }
+
+// CHECK-LABEL: define {{.*}} i32 @_Z8test_s32Dv4_i
+// CHECK: [[VAR:%.*]] = call i32 @llvm.[[CALL]].pack.s8.v4i32(<4 x i32> %{{.*}})
+// CHECK-NEXT: ret i32 [[VAR]]
+int8_t4_packed test_s32(int4 val) { return pack_s8(val); }
diff --git a/clang/test/CodeGenHLSL/builtins/pack_u8.hlsl b/clang/test/CodeGenHLSL/builtins/pack_u8.hlsl
new file mode 100644
index 000000000000000..b4ea9e78b12b424
--- /dev/null
+++ b/clang/test/CodeGenHLSL/builtins/pack_u8.hlsl
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple dxil-pc-shadermodel6.6-library %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-int16-type -fnative-half-type -o - | \
+// RUN:  FileCheck %s -DCALL=dx
+// RUN: %clang_cc1 -finclude-default-header  -x hlsl  -triple spirv-pc-vulkan-library %s \
+// RUN:  -emit-llvm -disable-llvm-passes -fnative-int16-type -fnative-half-type -o - | \
+// RUN:  FileCheck %s -DCALL=spv
+
+// CHECK-LABEL: define {{.*}} i32 @_Z8test_u16Dv4_t
+// CHECK: [[VAR:%.*]] = call i32 @llvm.[[CALL]].pack.u8.v4i16(<4 x i16> %{{.*}})
+// CHECK-NEXT: ret i32 [[VAR]]
+uint8_t4_packed test_u16(uint16_t4 val) { return pack_u8(val); }
+
+// CHECK-LABEL: define {{.*}} i32 @_Z8test_u32Dv4_j
+// CHECK: [[VAR:%.*]] = call i32 @llvm.[[CALL]].pack.u8.v4i32(<4 x i32> %{{.*}})
+// CHECK-NEXT: ret i32 [[VAR]]
+uint8_t4_packed test_u32(uint4 val) { return pack_u8(val); }
diff --git a/clang/test/SemaHLSL/BuiltIns/pack_clamp_s8-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/pack_clamp_s8-errors.hlsl
new file mode 100644
index 000000000000000..e9a0ce049b66377
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/pack_clamp_s8-errors.hlsl
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -fnative-half-type -fnative-int16-type -emit-llvm-only -disable-llvm-passes -verify
+
+void test_builtin_no_args() {
+  __builtin_hlsl_pack_clamp_s8();
+  // expected-error at -1 {{too few arguments to function call, expected 1, have 0}}
+}
+
+int8_t4_packed test_builtin_extra_args(int32_t4 p0) {
+  return __builtin_hlsl_pack_clamp_s8(p0, p0);
+  // expected-error at -1 {{too many arguments to function call, expected 1, have 2}}
+}
+
+int8_t4_packed test_builtin_64bit_arg(int64_t4 p0) {
+  return __builtin_hlsl_pack_clamp_s8(p0);
+  // expected-error at -1 {{1st argument must be a vector of signed integer types (was 'int64_t4' (aka 'vector<int64_t, 4>'))}}
+}
+
+int8_t4_packed test_builtin_float_vec_arg(float32_t4 p0) {
+  return __builtin_hlsl_pack_clamp_s8(p0);
+  // expected-error at -1 {{1st argument must be a vector of signed integer types (was 'float32_t4' (aka 'vector<float32_t, 4>'))}}
+}
+
+int8_t4_packed test_builtin_wrong_vec_elems(int32_t3 p0) {
+  return __builtin_hlsl_pack_clamp_s8(p0);
+  // expected-error at -1 {{1st argument must be a vector of signed integer types (was 'int32_t3' (aka 'vector<int32_t, 3>'))}}
+}
+
+int8_t4_packed test_builtin_scalar_arg(int p0) {
+  return __builtin_hlsl_pack_clamp_s8(p0);
+  // expected-error at -1 {{1st argument must be a vector of signed integer types (was 'int')}}
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/pack_clamp_u8-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/pack_clamp_u8-errors.hlsl
new file mode 100644
index 000000000000000..d4675c0a5968eb6
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/pack_clamp_u8-errors.hlsl
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -fnative-half-type -fnative-int16-type -emit-llvm-only -disable-llvm-passes -verify
+
+void test_builtin_no_args() {
+  __builtin_hlsl_pack_clamp_u8();
+  // expected-error at -1 {{too few arguments to function call, expected 1, have 0}}
+}
+
+uint8_t4_packed test_builtin_extra_args(int32_t4 p0) {
+  return __builtin_hlsl_pack_clamp_u8(p0, p0);
+  // expected-error at -1 {{too many arguments to function call, expected 1, have 2}}
+}
+
+uint8_t4_packed test_builtin_64bit_arg(int64_t4 p0) {
+  return __builtin_hlsl_pack_clamp_u8(p0);
+  // expected-error at -1 {{1st argument must be a vector of signed integer types (was 'int64_t4' (aka 'vector<int64_t, 4>'))}}
+}
+
+uint8_t4_packed test_builtin_float_vec_arg(float32_t4 p0) {
+  return __builtin_hlsl_pack_clamp_u8(p0);
+  // expected-error at -1 {{1st argument must be a vector of signed integer types (was 'float32_t4' (aka 'vector<float32_t, 4>'))}}
+}
+
+uint8_t4_packed test_builtin_wrong_vec_elems(int32_t3 p0) {
+  return __builtin_hlsl_pack_clamp_u8(p0);
+  // expected-error at -1 {{1st argument must be a vector of signed integer types (was 'int32_t3' (aka 'vector<int32_t, 3>'))}}
+}
+
+uint8_t4_packed test_builtin_scalar_arg(int p0) {
+  return __builtin_hlsl_pack_clamp_u8(p0);
+  // expected-error at -1 {{1st argument must be a vector of signed integer types (was 'int')}}
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/pack_s8-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/pack_s8-errors.hlsl
new file mode 100644
index 000000000000000..0f0a5a082ad7ee1
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/pack_s8-errors.hlsl
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -fnative-half-type -fnative-int16-type -emit-llvm-only -disable-llvm-passes -verify
+
+void test_builtin_no_args() {
+  __builtin_hlsl_pack_s8();
+  // expected-error at -1 {{too few arguments to function call, expected 1, have 0}}
+}
+
+int8_t4_packed test_builtin_extra_args(int32_t4 p0) {
+  return __builtin_hlsl_pack_s8(p0, p0);
+  // expected-error at -1 {{too many arguments to function call, expected 1, have 2}}
+}
+
+int8_t4_packed test_builtin_64bit_arg(int64_t4 p0) {
+  return __builtin_hlsl_pack_s8(p0);
+  // expected-error at -1 {{1st argument must be a vector of signed integer types (was 'int64_t4' (aka 'vector<int64_t, 4>'))}}
+}
+
+int8_t4_packed test_builtin_float_vec_arg(float32_t4 p0) {
+  return __builtin_hlsl_pack_s8(p0);
+  // expected-error at -1 {{1st argument must be a vector of signed integer types (was 'float32_t4' (aka 'vector<float32_t, 4>'))}}
+}
+
+int8_t4_packed test_builtin_wrong_vec_elems(int32_t3 p0) {
+  return __builtin_hlsl_pack_s8(p0);
+  // expected-error at -1 {{1st argument must be a vector of signed integer types (was 'int32_t3' (aka 'vector<int32_t, 3>'))}}
+}
+
+int8_t4_packed test_builtin_scalar_arg(int p0) {
+  return __builtin_hlsl_pack_s8(p0);
+  // expected-error at -1 {{1st argument must be a vector of signed integer types (was 'int')}}
+}
diff --git a/clang/test/SemaHLSL/BuiltIns/pack_u8-errors.hlsl b/clang/test/SemaHLSL/BuiltIns/pack_u8-errors.hlsl
new file mode 100644
index 000000000000000..faf493103dae535
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/pack_u8-errors.hlsl
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -finclude-default-header -triple dxil-pc-shadermodel6.6-library %s -fnative-half-type -fnative-int16-type -emit-llvm-only -disable-llvm-passes -verify
+
+void test_builtin_no_args() {
+  __builtin_hlsl_pack_u8();
+  // expected-error at -1 {{too few arguments to function call, expected 1, have 0}}
+}
+
+uint8_t4_packed test_builtin_extra_args(uint32_t4 p0) {
+  return __builtin_hlsl_pack_u8(p0, p0);
+  // expected-error at -1 {{too many arguments to function call, expected 1, have 2}}
+}
+
+uint8_t4_packed test_builtin_64bit_arg(uint64_t4 p0) {
+  return __builtin_hlsl_pack_u8(p0);
+  // expected-error at -1 {{1st argument must be a vector of unsigned integer types (was 'uint64_t4' (aka 'vector<uint64_t, 4>'))}}
+}
+
+uint8_t4_packed test_builtin_float_vec_arg(float32_t4 p0) {
+  return __builtin_hlsl_pack_u8(p0);
+  // expected-error at -1 {{1st argument must be a vector of unsigned integer types (was 'float32_t4' (aka 'vector<float32_t, 4>'))}}
+}
+
+uint8_t4_packed test_builtin_wrong_vec_elems(uint32_t3 p0) {
+  return __builtin_hlsl_pack_u8(p0);
+  // expected-error at -1 {{1st argument must be a vector of unsigned integer types (was 'uint32_t3' (aka 'vector<uint32_t, 3>'))}}
+}
+
+uint8_t4_packed test_builtin_scalar_arg(uint p0) {
+  return __builtin_hlsl_pack_u8(p0);
+  // expected-error at -1 {{1st argument must be a vector of unsigned integer types (was 'uint' (aka 'unsigned int'))}}
+}
diff --git a/llvm/include/llvm/IR/IntrinsicsDirectX.td b/llvm/include/llvm/IR/IntrinsicsDirectX.td
index e087216b822da32..68faf4c2e04734d 100644
--- a/llvm/include/llvm/IR/IntrinsicsDirectX.td
+++ b/llvm/include/llvm/IR/IntrinsicsDirectX.td
@@ -359,4 +359,10 @@ def int_dx_store_output
           [llvm_i32_ty /*SigElementId*/, llvm_i32_ty /*RowIndex*/,
            llvm_i8_ty /*ColIndex*/, llvm_any_ty /*Value*/],
           [IntrConvergent]>;
+
+def int_dx_pack_u8 : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_any_vector_int_ty], [IntrNoMem]>;
+def int_dx_pack_s8 : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_any_vector_int_ty], [IntrNoMem]>;
+def int_dx_pack_clamp_u8 : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_any_vector_int_ty], [IntrNoMem]>;
+def int_dx_pack_clamp_s8 : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_any_vector_int_ty], [IntrNoMem]>;
+
 }
diff --git a/llvm/include/llvm/IR/IntrinsicsSPIRV.td b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
index 25b5a3c38546546..0f4685ca2582ecc 100644
--- a/llvm/include/llvm/IR/IntrinsicsSPIRV.td
+++ b/llvm/include/llvm/IR/IntrinsicsSPIRV.td
@@ -375,5 +375,9 @@ def int_spv_rsqrt : DefaultAttrsIntrinsic<[LLVMMatchType<0>], [llvm_anyfloat_ty]
   def int_spv_unpackhalf2x16 : DefaultAttrsIntrinsic<[llvm_anyfloat_ty], [llvm_i32_ty], [IntrNoMem]>;
   def int_spv_packhalf2x16 : DefaultAttrsIntrinsic<[llvm_anyint_ty], [llvm_anyfloat_ty], [IntrNoMem]>;
 
+  def int_spv_pack_u8 : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_any_vector_int_ty], [IntrNoMem]>;
+  def int_spv_pack_s8 : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_any_vector_int_ty], [IntrNoMem]>;
+  def int_spv_pack_clamp_u8 : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_any_vector_int_ty], [IntrNoMem]>;
+  def int_spv_pack_clamp_s8 : DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_any_vector_int_ty], [IntrNoMem]>;
 
 }

>From 8454a247cd45bc96c76dcbfb0673d6d1f7d89275 Mon Sep 17 00:00:00 2001
From: Alexander Johnston <alexander.johnston at amd.com>
Date: Thu, 1 Oct 2026 12:59:50 +0100
Subject: [PATCH 2/3] [DirectX] Add hlsl pack intrinsics to DirectX backend

---
 llvm/lib/Target/DirectX/DXIL.td            |  9 ++++++
 llvm/lib/Target/DirectX/DXILOpLowering.cpp | 34 ++++++++++++++++++++++
 llvm/test/CodeGen/DirectX/pack_clamp_s8.ll | 26 +++++++++++++++++
 llvm/test/CodeGen/DirectX/pack_clamp_u8.ll | 26 +++++++++++++++++
 llvm/test/CodeGen/DirectX/pack_s8.ll       | 26 +++++++++++++++++
 llvm/test/CodeGen/DirectX/pack_u8.ll       | 26 +++++++++++++++++
 6 files changed, 147 insertions(+)
 create mode 100644 llvm/test/CodeGen/DirectX/pack_clamp_s8.ll
 create mode 100644 llvm/test/CodeGen/DirectX/pack_clamp_u8.ll
 create mode 100644 llvm/test/CodeGen/DirectX/pack_s8.ll
 create mode 100644 llvm/test/CodeGen/DirectX/pack_u8.ll

diff --git a/llvm/lib/Target/DirectX/DXIL.td b/llvm/lib/Target/DirectX/DXIL.td
index 5e63e94a1c069ad..be5a7dca7f44f9e 100644
--- a/llvm/lib/Target/DirectX/DXIL.td
+++ b/llvm/lib/Target/DirectX/DXIL.td
@@ -1560,3 +1560,12 @@ def CreateHandleFromHeap : DXILOp<218, createHandleFromHeap> {
   let stages = [Stages<DXIL1_6, [all_stages]>];
   let attributes = [Attributes<DXIL1_0, [ReadNone]>];
 }
+
+def Pack4x8 : DXILOp<220, pack4x8> {
+  let Doc = "pack 4 integer values into a single 32 bit value";
+  let arguments = [Int8Ty, OverloadTy, OverloadTy, OverloadTy, OverloadTy];
+  let overloads = [Overloads<DXIL1_6, [Int16Ty, Int32Ty]>];
+  let result = Int32Ty;
+  let stages = [Stages<DXIL1_6, [all_stages]>];
+  let attributes = [Attributes<DXIL1_6, [ReadNone]>];
+}
diff --git a/llvm/lib/Target/DirectX/DXILOpLowering.cpp b/llvm/lib/Target/DirectX/DXILOpLowering.cpp
index 2807e58942fbd05..0c146d5de56c331 100644
--- a/llvm/lib/Target/DirectX/DXILOpLowering.cpp
+++ b/llvm/lib/Target/DirectX/DXILOpLowering.cpp
@@ -1359,6 +1359,30 @@ class OpLowerer {
     });
   }
 
+  [[nodiscard]] bool lowerPack(Function &F, uint32_t PackOpCode) {
+    IRBuilder<> &IRB = OpBuilder.getIRB();
+    Type *RetTy = IRB.getInt32Ty();
+    return replaceFunction(F, [&](CallInst *CI) -> Error {
+      IRB.SetInsertPoint(CI);
+      SmallVector<Value *, 5> Args;
+      Args.push_back(IRB.getInt8(PackOpCode));
+
+      // Disassemble the vector to fill args 1-5 of the pack op.
+      Value *VecArg = CI->getArgOperand(0);
+      for (int i = 1; i < 5; i++)
+        Args.push_back(IRB.CreateExtractElement(VecArg, i - 1));
+
+      Expected<CallInst *> OpCall =
+          OpBuilder.tryCreateOp(OpCode::Pack4x8, Args, CI->getName(), RetTy);
+      if (Error E = OpCall.takeError())
+        return E;
+
+      CI->replaceAllUsesWith(*OpCall);
+      CI->eraseFromParent();
+      return Error::success();
+    });
+  }
+
   bool lowerIntrinsics() {
     bool Updated = false;
     bool HasErrors = false;
@@ -1482,6 +1506,16 @@ class OpLowerer {
       case Intrinsic::is_fpclass:
         HasErrors |= lowerIsFPClass(F);
         break;
+      case Intrinsic::dx_pack_u8:
+      case Intrinsic::dx_pack_s8:
+        HasErrors |= lowerPack(F, 0);
+        break;
+      case Intrinsic::dx_pack_clamp_u8:
+        HasErrors |= lowerPack(F, 1);
+        break;
+      case Intrinsic::dx_pack_clamp_s8:
+        HasErrors |= lowerPack(F, 2);
+        break;
       }
       Updated = true;
     }
diff --git a/llvm/test/CodeGen/DirectX/pack_clamp_s8.ll b/llvm/test/CodeGen/DirectX/pack_clamp_s8.ll
new file mode 100644
index 000000000000000..7829991991b0a65
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/pack_clamp_s8.ll
@@ -0,0 +1,26 @@
+; RUN: opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.6-library %s | FileCheck %s
+
+define noundef i32 @pack_clamp_s8_16(<4 x i16> noundef %a) {
+; CHECK: [[one:%.*]] = extractelement <4 x i16> %a, i64 0
+; CHECK: [[two:%.*]] = extractelement <4 x i16> %a, i64 1
+; CHECK: [[three:%.*]] = extractelement <4 x i16> %a, i64 2
+; CHECK: [[four:%.*]] = extractelement <4 x i16> %a, i64 3
+; CHECK: [[packed:%.*]] = call i32 @dx.op.pack4x8.i16(i32 220, i8 2, i16 [[one]], i16 [[two]], i16 [[three]], i16 [[four]])
+; CHECK: ret i32 [[packed]]
+  %packed = call i32 @llvm.dx.pack.clamp.s8.v4i16(<4 x i16> %a)
+  ret i32 %packed
+}
+
+define noundef i32 @pack_clamp_s8_32(<4 x i32> noundef %a) {
+; CHECK: [[one:%.*]] = extractelement <4 x i32> %a, i64 0
+; CHECK: [[two:%.*]] = extractelement <4 x i32> %a, i64 1
+; CHECK: [[three:%.*]] = extractelement <4 x i32> %a, i64 2
+; CHECK: [[four:%.*]] = extractelement <4 x i32> %a, i64 3
+; CHECK: [[packed:%.*]] = call i32 @dx.op.pack4x8.i32(i32 220, i8 2, i32 [[one]], i32 [[two]], i32 [[three]], i32 [[four]])
+; CHECK: ret i32 [[packed]]
+  %packed = call i32 @llvm.dx.pack.clamp.s8.v4i32(<4 x i32> %a)
+  ret i32 %packed
+}
+
+declare i32 @llvm.dx.pack.clamp.s8.v4i16(<4 x i16>)
+declare i32 @llvm.dx.pack.clamp.s8.v4i32(<4 x i32>)
diff --git a/llvm/test/CodeGen/DirectX/pack_clamp_u8.ll b/llvm/test/CodeGen/DirectX/pack_clamp_u8.ll
new file mode 100644
index 000000000000000..b85530b06429dc7
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/pack_clamp_u8.ll
@@ -0,0 +1,26 @@
+; RUN: opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.6-library %s | FileCheck %s
+
+define noundef i32 @pack_clamp_u8_16(<4 x i16> noundef %a) {
+; CHECK: [[one:%.*]] = extractelement <4 x i16> %a, i64 0
+; CHECK: [[two:%.*]] = extractelement <4 x i16> %a, i64 1
+; CHECK: [[three:%.*]] = extractelement <4 x i16> %a, i64 2
+; CHECK: [[four:%.*]] = extractelement <4 x i16> %a, i64 3
+; CHECK: [[packed:%.*]] = call i32 @dx.op.pack4x8.i16(i32 220, i8 1, i16 [[one]], i16 [[two]], i16 [[three]], i16 [[four]])
+; CHECK: ret i32 [[packed]]
+  %packed = call i32 @llvm.dx.pack.clamp.u8.v4i16(<4 x i16> %a)
+  ret i32 %packed
+}
+
+define noundef i32 @pack_clamp_u8_32(<4 x i32> noundef %a) {
+; CHECK: [[one:%.*]] = extractelement <4 x i32> %a, i64 0
+; CHECK: [[two:%.*]] = extractelement <4 x i32> %a, i64 1
+; CHECK: [[three:%.*]] = extractelement <4 x i32> %a, i64 2
+; CHECK: [[four:%.*]] = extractelement <4 x i32> %a, i64 3
+; CHECK: [[packed:%.*]] = call i32 @dx.op.pack4x8.i32(i32 220, i8 1, i32 [[one]], i32 [[two]], i32 [[three]], i32 [[four]])
+; CHECK: ret i32 [[packed]]
+  %packed = call i32 @llvm.dx.pack.clamp.u8.v4i32(<4 x i32> %a)
+  ret i32 %packed
+}
+
+declare i32 @llvm.dx.pack.clamp.u8.v4i16(<4 x i16>)
+declare i32 @llvm.dx.pack.clamp.u8.v4i32(<4 x i32>)
diff --git a/llvm/test/CodeGen/DirectX/pack_s8.ll b/llvm/test/CodeGen/DirectX/pack_s8.ll
new file mode 100644
index 000000000000000..c54798270d1f628
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/pack_s8.ll
@@ -0,0 +1,26 @@
+; RUN: opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.6-library %s | FileCheck %s
+
+define noundef i32 @pack_s8_16(<4 x i16> noundef %a) {
+; CHECK: [[one:%.*]] = extractelement <4 x i16> %a, i64 0
+; CHECK: [[two:%.*]] = extractelement <4 x i16> %a, i64 1
+; CHECK: [[three:%.*]] = extractelement <4 x i16> %a, i64 2
+; CHECK: [[four:%.*]] = extractelement <4 x i16> %a, i64 3
+; CHECK: [[packed:%.*]] = call i32 @dx.op.pack4x8.i16(i32 220, i8 0, i16 [[one]], i16 [[two]], i16 [[three]], i16 [[four]])
+; CHECK: ret i32 [[packed]]
+  %packed = call i32 @llvm.dx.pack.s8.v4i16(<4 x i16> %a)
+  ret i32 %packed
+}
+
+define noundef i32 @pack_s8_32(<4 x i32> noundef %a) {
+; CHECK: [[one:%.*]] = extractelement <4 x i32> %a, i64 0
+; CHECK: [[two:%.*]] = extractelement <4 x i32> %a, i64 1
+; CHECK: [[three:%.*]] = extractelement <4 x i32> %a, i64 2
+; CHECK: [[four:%.*]] = extractelement <4 x i32> %a, i64 3
+; CHECK: [[packed:%.*]] = call i32 @dx.op.pack4x8.i32(i32 220, i8 0, i32 [[one]], i32 [[two]], i32 [[three]], i32 [[four]])
+; CHECK: ret i32 [[packed]]
+  %packed = call i32 @llvm.dx.pack.s8.v4i32(<4 x i32> %a)
+  ret i32 %packed
+}
+
+declare i32 @llvm.dx.pack.s8.v4i16(<4 x i16>)
+declare i32 @llvm.dx.pack.s8.v4i32(<4 x i32>)
diff --git a/llvm/test/CodeGen/DirectX/pack_u8.ll b/llvm/test/CodeGen/DirectX/pack_u8.ll
new file mode 100644
index 000000000000000..bd52047d5748898
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/pack_u8.ll
@@ -0,0 +1,26 @@
+; RUN: opt -S -dxil-op-lower -mtriple=dxil-pc-shadermodel6.6-library %s | FileCheck %s
+
+define noundef i32 @pack_u8_16(<4 x i16> noundef %a) {
+; CHECK: [[one:%.*]] = extractelement <4 x i16> %a, i64 0
+; CHECK: [[two:%.*]] = extractelement <4 x i16> %a, i64 1
+; CHECK: [[three:%.*]] = extractelement <4 x i16> %a, i64 2
+; CHECK: [[four:%.*]] = extractelement <4 x i16> %a, i64 3
+; CHECK: [[packed:%.*]] = call i32 @dx.op.pack4x8.i16(i32 220, i8 0, i16 [[one]], i16 [[two]], i16 [[three]], i16 [[four]])
+; CHECK: ret i32 [[packed]]
+  %packed = call i32 @llvm.dx.pack.u8.v4i16(<4 x i16> %a)
+  ret i32 %packed
+}
+
+define noundef i32 @pack_u8_32(<4 x i32> noundef %a) {
+; CHECK: [[one:%.*]] = extractelement <4 x i32> %a, i64 0
+; CHECK: [[two:%.*]] = extractelement <4 x i32> %a, i64 1
+; CHECK: [[three:%.*]] = extractelement <4 x i32> %a, i64 2
+; CHECK: [[four:%.*]] = extractelement <4 x i32> %a, i64 3
+; CHECK: [[packed:%.*]] = call i32 @dx.op.pack4x8.i32(i32 220, i8 0, i32 [[one]], i32 [[two]], i32 [[three]], i32 [[four]])
+; CHECK: ret i32 [[packed]]
+  %packed = call i32 @llvm.dx.pack.u8.v4i32(<4 x i32> %a)
+  ret i32 %packed
+}
+
+declare i32 @llvm.dx.pack.u8.v4i16(<4 x i16>)
+declare i32 @llvm.dx.pack.u8.v4i32(<4 x i32>)

>From d347fc7eab301dc09029537407d52fb29aca9652 Mon Sep 17 00:00:00 2001
From: Alexander Johnston <alexander.johnston at amd.com>
Date: Thu, 1 Oct 2026 13:00:15 +0100
Subject: [PATCH 3/3] [SPIRV] Add hlsl pack intrinsics to SPIRV backend

---
 .../Target/SPIRV/SPIRVInstructionSelector.cpp | 53 +++++++++++++++++++
 .../SPIRV/hlsl-intrinsics/pack_clamp_s8.ll    | 44 +++++++++++++++
 .../SPIRV/hlsl-intrinsics/pack_clamp_u8.ll    | 42 +++++++++++++++
 .../CodeGen/SPIRV/hlsl-intrinsics/pack_s8.ll  | 28 ++++++++++
 .../CodeGen/SPIRV/hlsl-intrinsics/pack_u8.ll  | 28 ++++++++++
 5 files changed, 195 insertions(+)
 create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_clamp_s8.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_clamp_u8.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_s8.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_u8.ll

diff --git a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
index 143e1c0e3ef2abb..84eb442bdaeddd9 100644
--- a/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
@@ -485,6 +485,8 @@ class SPIRVInstructionSelector : public InstructionSelector {
                    MachineInstr &I) const;
   bool selectDerivativeInst(Register ResVReg, SPIRVTypeInst ResType,
                             MachineInstr &I, const unsigned DPdOpCode) const;
+  bool selectPackInst(Register ResVReg, SPIRVTypeInst ResType, MachineInstr &I,
+                      const bool Signed, const bool Clamp) const;
   // Utilities
   Register buildI32Constant(uint32_t Val, MachineInstr &I,
                             SPIRVTypeInst ResType = nullptr) const;
@@ -5262,6 +5264,49 @@ bool SPIRVInstructionSelector::selectDerivativeInst(
   return true;
 }
 
+bool SPIRVInstructionSelector::selectPackInst(Register ResVReg,
+                                              SPIRVTypeInst ResType,
+                                              MachineInstr &I,
+                                              const bool Signed,
+                                              const bool Clamp) const {
+  MachineIRBuilder MIRBuilder(I);
+  Register SrcReg = I.getOperand(2).getReg();
+  SPIRVTypeInst SrcType = GR.getSPIRVTypeForVReg(SrcReg);
+
+  // pack_clamp_ instructions require SClamp before performing SConvert
+  // limits are determined by if the pack_clamp is signed or not
+  if (Clamp) {
+    const unsigned ElemWidth = GR.getScalarOrVectorBitWidth(SrcType);
+    APInt Lower =
+        Signed ? APInt(ElemWidth, -128, true) : APInt::getZero(ElemWidth);
+    APInt Upper =
+        Signed ? APInt(ElemWidth, 127, true) : APInt(ElemWidth, 255, true);
+    bool ZeroAsNull = !STI.isShader();
+    Register LowerLimit =
+        GR.getOrCreateConstVector(Lower, I, SrcType, TII, ZeroAsNull);
+    Register UpperLimit =
+        GR.getOrCreateConstVector(Upper, I, SrcType, TII, ZeroAsNull);
+
+    Register ClampedReg = MRI->createVirtualRegister(GR.getRegClass(SrcType));
+    if (!selectExtInst(ClampedReg, SrcType, I, CL::s_clamp, GL::SClamp,
+                       /*setMIFlags=*/true, /*useMISrc=*/false,
+                       {SrcReg, LowerLimit, UpperLimit}))
+      return false;
+    SrcReg = ClampedReg;
+  }
+
+  // Narrow to an i8 vector, then bitcast. Convert sign doesn't matter here
+  SPIRVTypeInst I8Type = GR.getOrCreateSPIRVIntegerType(8, MIRBuilder);
+  SPIRVTypeInst I8x4Type =
+      GR.getOrCreateSPIRVVectorType(I8Type, 4, MIRBuilder, true);
+  Register I8x4Reg = MRI->createVirtualRegister(GR.getRegClass(I8x4Type));
+  auto ConvertOpcode = Signed ? SPIRV::OpSConvert : SPIRV::OpUConvert;
+  if (!selectOpWithSrcs(I8x4Reg, I8x4Type, I, {SrcReg}, ConvertOpcode))
+    return false;
+
+  return selectOpWithSrcs(ResVReg, ResType, I, {I8x4Reg}, SPIRV::OpBitcast);
+}
+
 bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
                                                SPIRVTypeInst ResType,
                                                MachineInstr &I) const {
@@ -5853,6 +5898,14 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
     MIB.constrainAllUses(TII, TRI, RBI);
     return true;
   }
+  case Intrinsic::spv_pack_u8:
+    return selectPackInst(ResVReg, ResType, I, false, false);
+  case Intrinsic::spv_pack_s8:
+    return selectPackInst(ResVReg, ResType, I, true, false);
+  case Intrinsic::spv_pack_clamp_u8:
+    return selectPackInst(ResVReg, ResType, I, false, true);
+  case Intrinsic::spv_pack_clamp_s8:
+    return selectPackInst(ResVReg, ResType, I, true, true);
   default:
     return diagnoseUnsupported(I, "intrinsic selection not implemented.");
   }
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_clamp_s8.ll b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_clamp_s8.ll
new file mode 100644
index 000000000000000..f6784e076265c89
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_clamp_s8.ll
@@ -0,0 +1,44 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-unknown-vulkan %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan %s -o - -filetype=obj | spirv-val %}
+
+; CHECK: [[import:%.*]] = OpExtInstImport "GLSL.std.450"
+
+; CHECK-DAG: [[int8:%.*]] = OpTypeInt 8 0
+; CHECK-DAG: [[int8x4:%.*]] = OpTypeVector [[int8]] 4
+; CHECK-DAG: [[int16:%.*]] = OpTypeInt 16 0
+; CHECK-DAG: [[int16x4:%.*]] = OpTypeVector [[int16]] 4
+; CHECK-DAG: [[int32:%.*]] = OpTypeInt 32 0
+; CHECK-DAG: [[int32x4:%.*]] = OpTypeVector [[int32]] 4
+
+; 65408 is -128 for SClamp
+; CHECK-DAG: [[int16_65408:%.*]] = OpConstant [[int16]] 65408
+; CHECK-DAG: [[int16_65408x4:%.*]] = OpConstantComposite [[int16x4]] [[int16_65408]] [[int16_65408]] [[int16_65408]] [[int16_65408]]
+; CHECK-DAG: [[int16_127:%.*]] = OpConstant [[int16]] 127
+; CHECK-DAG: [[int16_127x4:%.*]] = OpConstantComposite [[int16x4]] [[int16_127]] [[int16_127]] [[int16_127]] [[int16_127]]
+
+; 4294967168 is -128 for SClamp
+; CHECK-DAG: [[int32_4294967168:%.*]] = OpConstant [[int32]] 4294967168
+; CHECK-DAG: [[int32_4294967168x4:%.*]] = OpConstantComposite [[int32x4]] [[int32_4294967168]] [[int32_4294967168]] [[int32_4294967168]] [[int32_4294967168]]
+; CHECK-DAG: [[int32_127:%.*]] = OpConstant [[int32]] 127
+; CHECK-DAG: [[int32_127x4:%.*]] = OpConstantComposite [[int32x4]] [[int32_127]] [[int32_127]] [[int32_127]] [[int32_127]]
+
+define noundef i32 @pack_clamp_s8_16(<4 x i16> noundef %a) {
+; CHECK: [[in:%.*]] = OpFunctionParameter [[int16x4]]
+; CHECK: [[clamped:%.*]] = OpExtInst [[int16x4]] [[import]] SClamp [[in]] [[int16_65408x4]] [[int16_127x4]]
+; CHECK: [[converted:%.*]] = OpSConvert [[int8x4]] [[clamped]]
+; CHECK: [[cast:%.*]] = OpBitcast [[int32]] [[converted]]
+  %packed = call i32 @llvm.spv.pack.clamp.s8.v4i16(<4 x i16> %a)
+  ret i32 %packed
+}
+
+define noundef i32 @pack_clamp_s8_32(<4 x i32> noundef %a) {
+; CHECK: [[in:%.*]] = OpFunctionParameter [[int32x4]]
+; CHECK: [[clamped:%.*]] = OpExtInst [[int32x4]] [[import]] SClamp [[in]] [[int32_4294967168x4]] [[int32_127x4]]
+; CHECK: [[converted:%.*]] = OpSConvert [[int8x4]] [[clamped]]
+; CHECK: [[cast:%.*]] = OpBitcast [[int32]] [[converted]]
+  %packed = call i32 @llvm.spv.pack.clamp.s8.v4i32(<4 x i32> %a)
+  ret i32 %packed
+}
+
+declare i32 @llvm.spv.pack.clamp.s8.v4i16(<4 x i16>)
+declare i32 @llvm.spv.pack.clamp.s8.v4i32(<4 x i32>)
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_clamp_u8.ll b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_clamp_u8.ll
new file mode 100644
index 000000000000000..c5234e3283fee70
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_clamp_u8.ll
@@ -0,0 +1,42 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-unknown-vulkan %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan %s -o - -filetype=obj | spirv-val %}
+
+; CHECK: [[import:%.*]] = OpExtInstImport "GLSL.std.450"
+
+; CHECK-DAG: [[int8:%.*]] = OpTypeInt 8 0
+; CHECK-DAG: [[int8x4:%.*]] = OpTypeVector [[int8]] 4
+; CHECK-DAG: [[int16:%.*]] = OpTypeInt 16 0
+; CHECK-DAG: [[int16x4:%.*]] = OpTypeVector [[int16]] 4
+; CHECK-DAG: [[int32:%.*]] = OpTypeInt 32 0
+; CHECK-DAG: [[int32x4:%.*]] = OpTypeVector [[int32]] 4
+
+; CHECK-DAG: [[int16_0:%.*]] = OpConstant [[int16]] 0
+; CHECK-DAG: [[int16_0x4:%.*]] = OpConstantComposite [[int16x4]] [[int16_0]] [[int16_0]] [[int16_0]] [[int16_0]]
+; CHECK-DAG: [[int16_255:%.*]] = OpConstant [[int16]] 255
+; CHECK-DAG: [[int16_255x4:%.*]] = OpConstantComposite [[int16x4]] [[int16_255]] [[int16_255]] [[int16_255]] [[int16_255]]
+
+; CHECK-DAG: [[int32_0:%.*]] = OpConstant [[int32]] 0
+; CHECK-DAG: [[int32_0x4:%.*]] = OpConstantComposite [[int32x4]] [[int32_0]] [[int32_0]] [[int32_0]] [[int32_0]]
+; CHECK-DAG: [[int32_255:%.*]] = OpConstant [[int32]] 255
+; CHECK-DAG: [[int32_255x4:%.*]] = OpConstantComposite [[int32x4]] [[int32_255]] [[int32_255]] [[int32_255]] [[int32_255]]
+
+define noundef i32 @pack_clamp_u8_16(<4 x i16> noundef %a) {
+; CHECK: [[in:%.*]] = OpFunctionParameter [[int16x4]]
+; CHECK: [[clamped:%.*]] = OpExtInst [[int16x4]] [[import]] SClamp [[in]] [[int16_0x4]] [[int16_255x4]]
+; CHECK: [[converted:%.*]] = OpUConvert [[int8x4]] [[clamped]]
+; CHECK: [[cast:%.*]] = OpBitcast [[int32]] [[converted]]
+  %packed = call i32 @llvm.spv.pack.clamp.u8.v4i16(<4 x i16> %a)
+  ret i32 %packed
+}
+
+define noundef i32 @pack_clamp_u8_32(<4 x i32> noundef %a) {
+; CHECK: [[in:%.*]] = OpFunctionParameter [[int32x4]]
+; CHECK: [[clamped:%.*]] = OpExtInst [[int32x4]] [[import]] SClamp [[in]] [[int32_0x4]] [[int32_255x4]]
+; CHECK: [[converted:%.*]] = OpUConvert [[int8x4]] [[clamped]]
+; CHECK: [[cast:%.*]] = OpBitcast [[int32]] [[converted]]
+  %packed = call i32 @llvm.spv.pack.clamp.u8.v4i32(<4 x i32> %a)
+  ret i32 %packed
+}
+
+declare i32 @llvm.spv.pack.clamp.u8.v4i16(<4 x i16>)
+declare i32 @llvm.spv.pack.clamp.u8.v4i32(<4 x i32>)
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_s8.ll b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_s8.ll
new file mode 100644
index 000000000000000..191382e66b6e570
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_s8.ll
@@ -0,0 +1,28 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-unknown-vulkan %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: [[int8:%.*]] = OpTypeInt 8 0
+; CHECK-DAG: [[int8x4:%.*]] = OpTypeVector [[int8]] 4
+; CHECK-DAG: [[int16:%.*]] = OpTypeInt 16 0
+; CHECK-DAG: [[int16x4:%.*]] = OpTypeVector [[int16]] 4
+; CHECK-DAG: [[int32:%.*]] = OpTypeInt 32 0
+; CHECK-DAG: [[int32x4:%.*]] = OpTypeVector [[int32]] 4
+
+define noundef i32 @pack_s8_16(<4 x i16> noundef %a) {
+; CHECK: [[in:%.*]] = OpFunctionParameter [[int16x4]]
+; CHECK: [[converted:%.*]] = OpSConvert [[int8x4]] [[in]]
+; CHECK: [[cast:%.*]] = OpBitcast [[int32]] [[converted]]
+  %packed = call i32 @llvm.spv.pack.s8.v4i16(<4 x i16> %a)
+  ret i32 %packed
+}
+
+define noundef i32 @pack_s8_32(<4 x i32> noundef %a) {
+; CHECK: [[in:%.*]] = OpFunctionParameter [[int32x4]]
+; CHECK: [[converted:%.*]] = OpSConvert [[int8x4]] [[in]]
+; CHECK: [[cast:%.*]] = OpBitcast [[int32]] [[converted]]
+  %packed = call i32 @llvm.spv.pack.s8.v4i32(<4 x i32> %a)
+  ret i32 %packed
+}
+
+declare i32 @llvm.spv.pack.s8.v4i16(<4 x i16>)
+declare i32 @llvm.spv.pack.s8.v4i32(<4 x i32>)
diff --git a/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_u8.ll b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_u8.ll
new file mode 100644
index 000000000000000..e87e57984d967df
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/hlsl-intrinsics/pack_u8.ll
@@ -0,0 +1,28 @@
+; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-unknown-vulkan %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG: [[int8:%.*]] = OpTypeInt 8 0
+; CHECK-DAG: [[int8x4:%.*]] = OpTypeVector [[int8]] 4
+; CHECK-DAG: [[int16:%.*]] = OpTypeInt 16 0
+; CHECK-DAG: [[int16x4:%.*]] = OpTypeVector [[int16]] 4
+; CHECK-DAG: [[int32:%.*]] = OpTypeInt 32 0
+; CHECK-DAG: [[int32x4:%.*]] = OpTypeVector [[int32]] 4
+
+define noundef i32 @pack_u8_16(<4 x i16> noundef %a) {
+; CHECK: [[in:%.*]] = OpFunctionParameter [[int16x4]]
+; CHECK: [[converted:%.*]] = OpUConvert [[int8x4]] [[in]]
+; CHECK: [[cast:%.*]] = OpBitcast [[int32]] [[converted]]
+  %packed = call i32 @llvm.spv.pack.u8.v4i16(<4 x i16> %a)
+  ret i32 %packed
+}
+
+define noundef i32 @pack_u8_32(<4 x i32> noundef %a) {
+; CHECK: [[in:%.*]] = OpFunctionParameter [[int32x4]]
+; CHECK: [[converted:%.*]] = OpUConvert [[int8x4]] [[in]]
+; CHECK: [[cast:%.*]] = OpBitcast [[int32]] [[converted]]
+  %packed = call i32 @llvm.spv.pack.u8.v4i32(<4 x i32> %a)
+  ret i32 %packed
+}
+
+declare i32 @llvm.spv.pack.u8.v4i16(<4 x i16>)
+declare i32 @llvm.spv.pack.u8.v4i32(<4 x i32>)



More information about the llvm-commits mailing list