[clang] [clang][clangIR]: X86 vperm2f128 builtin implementation (PR #208851)
Lucas Ribeiro Lima via cfe-commits
cfe-commits at lists.llvm.org
Sun Jul 12 17:26:40 PDT 2026
https://github.com/lucaslive974 updated https://github.com/llvm/llvm-project/pull/208851
>From 19dc8973140ace79b77832e312a897163c0d7d44 Mon Sep 17 00:00:00 2001
From: Lucas-Ribeiro-Lima <lucasribeirolima974 at gmail.com>
Date: Thu, 9 Jul 2026 16:56:33 -0300
Subject: [PATCH 1/2] [clang][clangIR]: X86 vperm2f128 builtin implementation
---
clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp | 49 +++++
.../CIR/CodeGenBuiltins/X86/avx-vperm2f128 | 171 ++++++++++++++++++
2 files changed, 220 insertions(+)
create mode 100644 clang/test/CIR/CodeGenBuiltins/X86/avx-vperm2f128
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
index 2b5b27fa7cfb2..51d3c6916a1ac 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltinX86.cpp
@@ -811,6 +811,53 @@ static mlir::Value emitX86MaskedLoad(CIRGenBuilderTy &builder,
return builder.createMaskedLoad(loc, ty, ptr, alignment, maskVec, ops[1]);
}
+static mlir::Value emitX86VPerm2f128(CIRGenBuilderTy &builder,
+ mlir::Location loc, mlir::Type resType,
+ llvm::SmallVector<mlir::Value> ops) {
+ auto inputType = cast<cir::VectorType>(ops[0].getType());
+ const unsigned imm = CIRGenFunction::getZExtIntValueFromConstOp(ops[2]);
+ const uint8_t zeroMask = 0x08, controlMask = 0x0F;
+
+ // Mirror hardware and OGCG behaviour returning a zero vector
+ if ((imm & zeroMask) && (imm & zeroMask << 4))
+ return builder.getZero(loc, resType);
+
+ mlir::Value lanes[2];
+ llvm::SmallVector<int64_t, 64> mask;
+ const unsigned numElts = inputType.getSize();
+
+ // We must evaluated each lane(128 bits) separetely
+ for (auto lane : llvm::seq(0, 2)) {
+ uint8_t controlBits = (imm >> (lane * 4)) & controlMask;
+
+ llvm::Boolean isZeroBit = controlBits & zeroMask;
+ llvm::Boolean isSourceA = controlBits <= 1;
+ llvm::Boolean isLowerHalf = controlBits % 2 == 0;
+
+ // Determine the source for this lane
+ if (isZeroBit)
+ lanes[lane] = builder.getZero(loc, resType);
+ else
+ lanes[lane] = isSourceA ? ops[0] : ops[1];
+
+ // We need to built the shuffle mask selecting the right half
+ for (auto elt : llvm::seq(0u, numElts / 2u)) {
+ unsigned idx = (lane * numElts) + elt;
+ if (!isLowerHalf)
+ idx += numElts / 2;
+ mask.push_back(idx);
+ }
+ }
+
+ mlir::Value shuffleResult =
+ builder.createVecShuffle(loc, lanes[0], lanes[1], mask);
+
+ if (inputType != resType)
+ builder.createBitcast(shuffleResult, resType);
+
+ return shuffleResult;
+}
+
std::optional<mlir::Value>
CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) {
if (builtinID == Builtin::BI__builtin_cpu_is) {
@@ -1801,6 +1848,8 @@ CIRGenFunction::emitX86BuiltinExpr(unsigned builtinID, const CallExpr *expr) {
case X86::BI__builtin_ia32_vperm2f128_ps256:
case X86::BI__builtin_ia32_vperm2f128_si256:
case X86::BI__builtin_ia32_permti256:
+ return emitX86VPerm2f128(builder, getLoc(expr->getExprLoc()),
+ this->convertType(expr->getType()), ops);
case X86::BI__builtin_ia32_pslldqi128_byteshift:
case X86::BI__builtin_ia32_pslldqi256_byteshift:
case X86::BI__builtin_ia32_pslldqi512_byteshift:
diff --git a/clang/test/CIR/CodeGenBuiltins/X86/avx-vperm2f128 b/clang/test/CIR/CodeGenBuiltins/X86/avx-vperm2f128
new file mode 100644
index 0000000000000..e46e4d342df98
--- /dev/null
+++ b/clang/test/CIR/CodeGenBuiltins/X86/avx-vperm2f128
@@ -0,0 +1,171 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir -target-feature +avx
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll -target-feature +avx
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.og.ll -target-feature +avx
+// RUN: FileCheck --input-file=%t.og.ll %s -check-prefix=OGCG
+
+// Tests perm2f128 byte permutation intrinsics implementation in ClangIR
+// Compares CIR emission, LLVM lowering, and original CodeGen output
+
+typedef float __m256 __attribute__((__vector_size__(32)));
+typedef int __m256i __attribute__((__vector_size__(32)));
+typedef double __m256d __attribute__((__vector_size__(32)));
+
+//First operand sources half lane(64 bits)
+#define A_LOW 0x00
+#define A_HIGH 0x01
+
+//Second operand sources half lane(64 bits)
+#define B_LOW 0x02
+#define B_HIGH 0x03
+
+//Zero overwritte bit
+#define ZERO_BIT 0x08
+
+//Macro helpers to set control bits
+#define LANES(low, high) (((low) | ((high) << 4)))
+
+//The test will be named following this convention for selected lanes
+// test_perm2f128pd_a_{{lane/zero}}_b_{{lane/zero}}
+
+__m256d test_perm2f128pd_a_low_b_low(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_a_low_b_low
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<4 x !cir.double>) [#cir.int<0> : !s32i, #cir.int<1> : !s32i, #cir.int<4> : !s32i, #cir.int<5> : !s32i] : !cir.vector<4 x !cir.double>
+ //LLVM: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ //OGCG: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(A_LOW, B_LOW));
+}
+
+__m256d test_perm2f128pd_a_low_b_high(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_a_low_b_high
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<4 x !cir.double>) [#cir.int<0> : !s32i, #cir.int<1> : !s32i, #cir.int<6> : !s32i, #cir.int<7> : !s32i] : !cir.vector<4 x !cir.double>
+ //LLVM: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 0, i32 1, i32 6, i32 7>
+ //OGCG: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 0, i32 1, i32 6, i32 7>
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(A_LOW, B_HIGH));
+}
+
+__m256d test_perm2f128pd_a_high_b_low(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_a_high_b_low
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<4 x !cir.double>) [#cir.int<2> : !s32i, #cir.int<3> : !s32i, #cir.int<4> : !s32i, #cir.int<5> : !s32i] : !cir.vector<4 x !cir.double>
+ //LLVM: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 2, i32 3, i32 4, i32 5>
+ //OGCG: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 2, i32 3, i32 4, i32 5>
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(A_HIGH, B_LOW));
+}
+
+__m256d test_perm2f128pd_a_high_b_high(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_a_high_b_high
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<4 x !cir.double>) [#cir.int<2> : !s32i, #cir.int<3> : !s32i, #cir.int<6> : !s32i, #cir.int<7> : !s32i] : !cir.vector<4 x !cir.double>
+ //LLVM: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 2, i32 3, i32 6, i32 7>
+ //OGCG: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 2, i32 3, i32 6, i32 7>
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(A_HIGH, B_HIGH));
+}
+
+__m256d test_perm2f128pd_b_high_b_low(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_b_high_b_low
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<4 x !cir.double>) [#cir.int<2> : !s32i, #cir.int<3> : !s32i, #cir.int<4> : !s32i, #cir.int<5> : !s32i] : !cir.vector<4 x !cir.double>
+ //LLVM: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 2, i32 3, i32 4, i32 5>
+ //OGCG: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 2, i32 3, i32 4, i32 5>
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(B_HIGH, B_LOW));
+}
+
+__m256d test_perm2f128pd_a_high_a_low(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_a_high_a_low
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<4 x !cir.double>) [#cir.int<2> : !s32i, #cir.int<3> : !s32i, #cir.int<4> : !s32i, #cir.int<5> : !s32i] : !cir.vector<4 x !cir.double>
+ //LLVM: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 2, i32 3, i32 4, i32 5>
+ //OGCG: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 2, i32 3, i32 4, i32 5>
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(A_HIGH, A_LOW));
+}
+
+
+__m256d test_perm2f128pd_a_low_a_low(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_a_low_a_low
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<4 x !cir.double>) [#cir.int<0> : !s32i, #cir.int<1> : !s32i, #cir.int<4> : !s32i, #cir.int<5> : !s32i] : !cir.vector<4 x !cir.double>
+ //LLVM: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ //OGCG: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(A_LOW, A_LOW));
+}
+
+__m256d test_perm2f128pd_a_high_a_high(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_a_high_a_high
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<4 x !cir.double>) [#cir.int<0> : !s32i, #cir.int<1> : !s32i, #cir.int<4> : !s32i, #cir.int<5> : !s32i] : !cir.vector<4 x !cir.double>
+ //LLVM: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ //OGCG: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> {{.*}}, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(A_LOW, A_LOW));
+}
+
+//Zero bit tests
+__m256d test_perm2f128pd_a_zero_b_low(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_a_zero_b_low
+ //CIR: {{.*}} = cir.const #cir.zero : !cir.vector<4 x !cir.double>
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<4 x !cir.double>) [#cir.int<0> : !s32i, #cir.int<1> : !s32i, #cir.int<4> : !s32i, #cir.int<5> : !s32i] : !cir.vector<4 x !cir.double>
+ //LLVM: {{.*}} = shufflevector <4 x double> zeroinitializer, <4 x double> {{.*}}, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ //OGCG: {{.*}} = shufflevector <4 x double> zeroinitializer, <4 x double> {{.*}}, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(ZERO_BIT, B_LOW));
+}
+
+//Zero bit tests
+__m256d test_perm2f128pd_a_low_b_zero(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_a_low_b_zero
+ //CIR: {{.*}} = cir.const #cir.zero : !cir.vector<4 x !cir.double>
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<4 x !cir.double>) [#cir.int<0> : !s32i, #cir.int<1> : !s32i, #cir.int<4> : !s32i, #cir.int<5> : !s32i] : !cir.vector<4 x !cir.double>
+ //LLVM: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> zeroinitializer, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ //OGCG: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> zeroinitializer, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(A_LOW, ZERO_BIT));
+}
+
+//Zero bit overwrittes control bits
+__m256d test_perm2f128pd_a_zero_overwritte(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_a_zero_overwritte
+ //CIR: {{.*}} = cir.const #cir.zero : !cir.vector<4 x !cir.double>
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<4 x !cir.double>) [#cir.int<0> : !s32i, #cir.int<1> : !s32i, #cir.int<4> : !s32i, #cir.int<5> : !s32i] : !cir.vector<4 x !cir.double>
+ //LLVM: {{.*}} = shufflevector <4 x double> zeroinitializer, <4 x double> {{.*}}, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ //OGCG: {{.*}} = shufflevector <4 x double> zeroinitializer, <4 x double> {{.*}}, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(ZERO_BIT | A_LOW, B_LOW));
+}
+
+__m256d test_perm2f128pd_b_zero_overwritte(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_b_zero_overwritte
+ //CIR: {{.*}} = cir.const #cir.zero : !cir.vector<4 x !cir.double>
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<4 x !cir.double>) [#cir.int<0> : !s32i, #cir.int<1> : !s32i, #cir.int<4> : !s32i, #cir.int<5> : !s32i] : !cir.vector<4 x !cir.double>
+ //LLVM: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> zeroinitializer, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ //OGCG: {{.*}} = shufflevector <4 x double> {{.*}}, <4 x double> zeroinitializer, <4 x i32> <i32 0, i32 1, i32 4, i32 5>
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(A_LOW, ZERO_BIT | B_LOW));
+}
+
+__m256d test_perm2f128pd_ab_zero_overwritte(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_ab_zero_overwritte
+ //CIR: {{.*}} = cir.const #cir.zero : !cir.vector<4 x !cir.double>
+ //CIR: cir.return {{.*}} : !cir.vector<4 x !cir.double>
+ //LLVM: store <4 x double> zeroinitializer
+ //OGCG: ret <4 x double> zeroinitializer
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(ZERO_BIT | A_LOW, ZERO_BIT | B_LOW));
+}
+
+__m256d test_perm2f128pd_a_zero_b_zero(__m256d a, __m256d b) {
+ //CIR-LABEL: test_perm2f128pd_a_zero_b_zero
+ //CIR: {{.*}} = cir.const #cir.zero : !cir.vector<4 x !cir.double>
+ //CIR: cir.return {{.*}} : !cir.vector<4 x !cir.double>
+ //LLVM: store <4 x double> zeroinitializer
+ //OGCG: ret <4 x double> zeroinitializer
+ return __builtin_ia32_vperm2f128_pd256(a, b, LANES(ZERO_BIT, ZERO_BIT));
+}
+
+__m256 test_perm2f128ps_bitcast(__m256 a, __m256 b) {
+ //CIR-LABEL: test_perm2f128ps_bitcast
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<8 x !cir.float>) [#cir.int<0> : !s32i, #cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.int<3> : !s32i, #cir.int<12> : !s32i, #cir.int<13> : !s32i, #cir.int<14> : !s32i, #cir.int<15> : !s32i] : !cir.vector<8 x !cir.float>
+ //LLVM: {{.*}} = shufflevector <8 x float> {{.*}}, <8 x float> {{.*}}, <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 12, i32 13, i32 14, i32 15>
+ //OGCG: {{.*}} = shufflevector <8 x float> {{.*}}, <8 x float> {{.*}}, <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 12, i32 13, i32 14, i32 15>
+ return __builtin_ia32_vperm2f128_ps256(a, b, LANES(A_LOW, B_HIGH));
+}
+
+__m256 test_perm2f128si_bitcast(__m256 a, __m256 b) {
+ //CIR-LABEL: test_perm2f128si_bitcast
+ //CIR: {{.*}} = cir.vec.shuffle({{.*}}, {{.*}} : !cir.vector<8 x !s32i>) [#cir.int<0> : !s32i, #cir.int<1> : !s32i, #cir.int<2> : !s32i, #cir.int<3> : !s32i, #cir.int<12> : !s32i, #cir.int<13> : !s32i, #cir.int<14> : !s32i, #cir.int<15> : !s32i] : !cir.vector<8 x !s32i>
+ //CIR: {{.*}} = cir.cast bitcast {{.*}} : !cir.vector<8 x !s32i> -> !cir.vector<8 x !cir.float>
+ //LLVM: {{.*}} = shufflevector <8 x i32> {{.*}} <8 x i32> {{.*}} <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 12, i32 13, i32 14, i32 15>
+ //LLVM: {{.*}} = bitcast <8 x i32> {{.*}} to <8 x float>
+ //OGCG: {{.*}} = shufflevector <8 x i32> {{.*}} <8 x i32> {{.*}} <8 x i32> <i32 0, i32 1, i32 2, i32 3, i32 12, i32 13, i32 14, i32 15>
+ //OGCG: {{.*}} = bitcast <8 x i32> {{.*}} to <8 x float>
+ return __builtin_ia32_vperm2f128_si256(a, b, LANES(A_LOW, B_HIGH));
+}
>From 05ae30b9d216af8be5dd960e110e1ce83fab5c3b Mon Sep 17 00:00:00 2001
From: Lucas-Ribeiro-Lima <lucasribeirolima974 at gmail.com>
Date: Sun, 12 Jul 2026 21:23:37 -0300
Subject: [PATCH 2/2] [clang][ClangIR][style]: modernize-macro-to-enum
clang-tidy
---
.../X86/{avx-vperm2f128 => avx-vperm2f128.c} | 20 +++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
rename clang/test/CIR/CodeGenBuiltins/X86/{avx-vperm2f128 => avx-vperm2f128.c} (98%)
diff --git a/clang/test/CIR/CodeGenBuiltins/X86/avx-vperm2f128 b/clang/test/CIR/CodeGenBuiltins/X86/avx-vperm2f128.c
similarity index 98%
rename from clang/test/CIR/CodeGenBuiltins/X86/avx-vperm2f128
rename to clang/test/CIR/CodeGenBuiltins/X86/avx-vperm2f128.c
index e46e4d342df98..a2c546885f3d6 100644
--- a/clang/test/CIR/CodeGenBuiltins/X86/avx-vperm2f128
+++ b/clang/test/CIR/CodeGenBuiltins/X86/avx-vperm2f128.c
@@ -12,16 +12,16 @@ typedef float __m256 __attribute__((__vector_size__(32)));
typedef int __m256i __attribute__((__vector_size__(32)));
typedef double __m256d __attribute__((__vector_size__(32)));
-//First operand sources half lane(64 bits)
-#define A_LOW 0x00
-#define A_HIGH 0x01
-
-//Second operand sources half lane(64 bits)
-#define B_LOW 0x02
-#define B_HIGH 0x03
-
-//Zero overwritte bit
-#define ZERO_BIT 0x08
+enum VPerm2F128LaneControl {
+ //First operand 128 bits lane
+ A_LOW= 0x00,
+ A_HIGH = 0x01,
+ //Second operand 128 bits lane
+ B_LOW = 0x02,
+ B_HIGH = 0x03,
+ //Zero overwritte bit
+ ZERO_BIT = 0x08,
+};
//Macro helpers to set control bits
#define LANES(low, high) (((low) | ((high) << 4)))
More information about the cfe-commits
mailing list