[clang] [CIR] Use declarative TableGen constraints for overflow flag verification (PR #195476)
Henrich Lauko via cfe-commits
cfe-commits at lists.llvm.org
Sat May 2 14:23:03 PDT 2026
https://github.com/xlauko updated https://github.com/llvm/llvm-project/pull/195476
>From 1cd649cef733f8eeaeee43222dc34e53777f119d Mon Sep 17 00:00:00 2001
From: xlauko <xlauko at mail.muni.cz>
Date: Sat, 2 May 2026 20:33:36 +0200
Subject: [PATCH] [CIR] Use declarative TableGen constraints for overflow flag
verification
Replace hand-written C++ verifiers with PredOpTrait-based constraints
(FlagRequiresIntType, HasAtMostOneOfAttrs). Introduce CIR_SaturatableBinaryOp
base class and use append/prepend ODS directives to compose arguments, format,
and traits across the op hierarchy. Fix HasAtMostOneOfAttrsPred to use
accessor methods instead of dollar-sign references. Add Commutative trait
to AddOp and MulOp.
---
clang/include/clang/CIR/Dialect/IR/CIROps.td | 84 ++++++++++++--------
clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 39 ---------
clang/test/CIR/CodeGen/delete-array.cpp | 4 +-
clang/test/CIR/CodeGen/size-of-vla.cpp | 8 +-
clang/test/CIR/CodeGen/vla.c | 8 +-
5 files changed, 59 insertions(+), 84 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 97d623ba5e6d9..46b23c32b1a98 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -114,14 +114,26 @@ class CIR_Op<string mnemonic, list<Trait> traits = []> :
//===----------------------------------------------------------------------===//
class HasAtMostOneOfAttrsPred<list<string> names> :
- CPred<!foldl("0", names, acc, name, acc # " + (" # name # " ? 1 : 0)")
+ CPred<!foldl("0", names, acc, name,
+ acc # " + (this->get" # snakeCaseToCamelCase<name>.ret # "() ? 1 : 0)")
# " <= 1">;
class HasAtMostOneOfAttrs<list<string> names> : PredOpTrait<
"has only one of the optional attributes: " # !interleave(names, ", "),
- HasAtMostOneOfAttrsPred<!foreach(name, names, "$" # name)>
+ HasAtMostOneOfAttrsPred<names>
>;
+// Requires that a boolean flag attribute implies integer result type.
+class FlagRequiresIntType<string flag> : PredOpTrait<
+ "only operations on integer values may have the " # flag # " flag",
+ Or<[Neg<CPred<"this->get" # snakeCaseToCamelCase<flag>.ret # "()">>,
+ CPred<"::mlir::isa<::cir::IntType>(this->getResult().getType())">]>
+>;
+
+def NSWFlagIntOnly : FlagRequiresIntType<"no_signed_wrap">;
+def NUWFlagIntOnly : FlagRequiresIntType<"no_unsigned_wrap">;
+def SatFlagIntOnly : FlagRequiresIntType<"saturated">;
+
//===----------------------------------------------------------------------===//
// CastOp
//===----------------------------------------------------------------------===//
@@ -1819,11 +1831,12 @@ class CIR_UnaryOpWithOverflowFlag<string mnemonic, Type type,
list<Trait> traits = []>
: CIR_UnaryOp<mnemonic, type, traits>
{
- let arguments = (ins type:$input, UnitProp:$no_signed_wrap);
+ let append traits = [NSWFlagIntOnly];
- let assemblyFormat = [{
+ let append arguments = (ins UnitProp:$no_signed_wrap);
+
+ let prepend assemblyFormat = [{
(`nsw` $no_signed_wrap^)?
- $input `:` type($input) attr-dict
}];
}
@@ -2366,34 +2379,47 @@ class CIR_BinaryOp<string mnemonic, Type type, list<Trait> traits = []>
}];
}
-// Base class for binary ops that support integer overflow flags (nsw/nuw)
-// and saturated arithmetic.
-class CIR_BinaryOpWithOverflowFlags<string mnemonic, Type type,
- list<Trait> traits = []>
- : CIR_BinaryOp<mnemonic, type, !listconcat([Pure], traits)>
+// Base class for binary ops that support integer overflow flags (nsw/nuw).
+class CIR_BinaryOpWithOverflowFlags<string mnemonic, Type type>
+ : CIR_BinaryOp<mnemonic, type>
{
- let arguments = (ins
- type:$lhs, type:$rhs,
+ let append traits = [Pure, NSWFlagIntOnly, NUWFlagIntOnly];
+
+ let append arguments = (ins
UnitProp:$no_signed_wrap,
- UnitProp:$no_unsigned_wrap,
- UnitProp:$saturated
+ UnitProp:$no_unsigned_wrap
);
- let assemblyFormat = [{
+ let prepend assemblyFormat = [{
(`nsw` $no_signed_wrap^)?
(`nuw` $no_unsigned_wrap^)?
- (`sat` $saturated^)?
- $lhs `,` $rhs `:` type($lhs) attr-dict
}];
+}
- let hasVerifier = 1;
+// Extends CIR_BinaryOpWithOverflowFlags with saturated arithmetic support.
+class CIR_SaturatableBinaryOp<string mnemonic, Type type>
+ : CIR_BinaryOpWithOverflowFlags<mnemonic, type>
+{
+ let append traits = [
+ SatFlagIntOnly,
+ HasAtMostOneOfAttrs<["saturated", "no_signed_wrap"]>,
+ HasAtMostOneOfAttrs<["saturated", "no_unsigned_wrap"]>
+ ];
+
+ let append arguments = (ins UnitProp:$saturated);
+
+ let prepend assemblyFormat = [{
+ (`sat` $saturated^)?
+ }];
}
//===----------------------------------------------------------------------===//
// AddOp
//===----------------------------------------------------------------------===//
-def CIR_AddOp : CIR_BinaryOpWithOverflowFlags<"add", CIR_AnyArithType> {
+def CIR_AddOp : CIR_SaturatableBinaryOp<"add", CIR_AnyArithType> {
+ let append traits = [Commutative];
+
let summary = "Integer or floating-point addition";
let description = [{
The `cir.add` operation performs addition on integer or floating-point
@@ -2421,7 +2447,7 @@ def CIR_AddOp : CIR_BinaryOpWithOverflowFlags<"add", CIR_AnyArithType> {
// SubOp
//===----------------------------------------------------------------------===//
-def CIR_SubOp : CIR_BinaryOpWithOverflowFlags<"sub", CIR_AnyArithType> {
+def CIR_SubOp : CIR_SaturatableBinaryOp<"sub", CIR_AnyArithType> {
let summary = "Integer or floating-point subtraction";
let description = [{
The `cir.sub` operation performs subtraction on integer or floating-point
@@ -2448,7 +2474,9 @@ def CIR_SubOp : CIR_BinaryOpWithOverflowFlags<"sub", CIR_AnyArithType> {
// MulOp
//===----------------------------------------------------------------------===//
-def CIR_MulOp : CIR_BinaryOp<"mul", CIR_AnyArithType> {
+def CIR_MulOp : CIR_BinaryOpWithOverflowFlags<"mul", CIR_AnyArithType> {
+ let append traits = [Commutative];
+
let summary = "Integer or floating-point multiplication";
let description = [{
The `cir.mul` operation performs multiplication on integer or floating-point
@@ -2467,20 +2495,6 @@ def CIR_MulOp : CIR_BinaryOp<"mul", CIR_AnyArithType> {
%3 = cir.mul %a, %b : !cir.float
```
}];
-
- let arguments = (ins
- CIR_AnyArithType:$lhs, CIR_AnyArithType:$rhs,
- UnitProp:$no_signed_wrap,
- UnitProp:$no_unsigned_wrap
- );
-
- let assemblyFormat = [{
- (`nsw` $no_signed_wrap^)?
- (`nuw` $no_unsigned_wrap^)?
- $lhs `,` $rhs `:` type($lhs) attr-dict
- }];
-
- let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 7386819d8fce9..05aff42f7c148 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -2708,45 +2708,6 @@ mlir::LogicalResult cir::FuncOp::verify() {
return success();
}
-//===----------------------------------------------------------------------===//
-// AddOp / SubOp / MulOp
-//===----------------------------------------------------------------------===//
-
-static LogicalResult verifyBinaryOverflowOp(mlir::Operation *op,
- bool noSignedWrap,
- bool noUnsignedWrap, bool saturated,
- bool hasSat) {
- bool noWrap = noSignedWrap || noUnsignedWrap;
- if (!isa<cir::IntType>(op->getResultTypes()[0]) && noWrap)
- return op->emitError()
- << "only operations on integer values may have nsw/nuw flags";
- if (hasSat && saturated && !isa<cir::IntType>(op->getResultTypes()[0]))
- return op->emitError()
- << "only operations on integer values may have sat flag";
- if (hasSat && noWrap && saturated)
- return op->emitError()
- << "the nsw/nuw flags and the saturated flag are mutually exclusive";
- return mlir::success();
-}
-
-LogicalResult cir::AddOp::verify() {
- return verifyBinaryOverflowOp(getOperation(), getNoSignedWrap(),
- getNoUnsignedWrap(), getSaturated(),
- /*hasSat=*/true);
-}
-
-LogicalResult cir::SubOp::verify() {
- return verifyBinaryOverflowOp(getOperation(), getNoSignedWrap(),
- getNoUnsignedWrap(), getSaturated(),
- /*hasSat=*/true);
-}
-
-LogicalResult cir::MulOp::verify() {
- return verifyBinaryOverflowOp(getOperation(), getNoSignedWrap(),
- getNoUnsignedWrap(), /*saturated=*/false,
- /*hasSat=*/false);
-}
-
//===----------------------------------------------------------------------===//
// TernaryOp
//===----------------------------------------------------------------------===//
diff --git a/clang/test/CIR/CodeGen/delete-array.cpp b/clang/test/CIR/CodeGen/delete-array.cpp
index 25a49cf668978..97a2210e3ce12 100644
--- a/clang/test/CIR/CodeGen/delete-array.cpp
+++ b/clang/test/CIR/CodeGen/delete-array.cpp
@@ -134,7 +134,7 @@ void test_sized_array_delete(SizedArrayDelete *ptr) {
// LLVM: [[DELETE_NOTNULL]]:
// LLVM: %[[ALLOC_PTR:.*]] = getelementptr i8, ptr %[[PTR]], i64 -8
// LLVM: %[[NUM_ELEM:.*]] = load i64, ptr %[[ALLOC_PTR]], align 4
-// LLVM: %[[ARRAY_SIZE:.*]] = mul i64 4, %[[NUM_ELEM]]
+// LLVM: %[[ARRAY_SIZE:.*]] = mul i64 %[[NUM_ELEM]], 4
// LLVM: %[[TOTAL_SIZE:.*]] = add i64 %[[ARRAY_SIZE]], 8
// LLVM: call void @_ZN16SizedArrayDeletedaEPvm(ptr %[[ALLOC_PTR]], i64 %[[TOTAL_SIZE]])
// LLVM: br label %[[DELETE_END]]
@@ -241,7 +241,7 @@ void test_delete_array_destructed(Destructed *ptr) {
// LLVM: [[LOOP_END]]:
// LLVM: br label %[[CALL_DELETE]]
// LLVM: [[CALL_DELETE]]:
-// LLVM: %[[ARRAY_SIZE:.*]] = mul i64 4, %[[NUM_ELEM]]
+// LLVM: %[[ARRAY_SIZE:.*]] = mul i64 %[[NUM_ELEM]], 4
// LLVM: %[[TOTAL_SIZE:.*]] = add i64 %[[ARRAY_SIZE]], 8
// LLVM: call void @_ZdaPvm(ptr %[[ALLOC_PTR]], i64 %[[TOTAL_SIZE]])
// LLVM: br label %[[DONE]]
diff --git a/clang/test/CIR/CodeGen/size-of-vla.cpp b/clang/test/CIR/CodeGen/size-of-vla.cpp
index c6838155189ef..3f9b2f5fecbbd 100644
--- a/clang/test/CIR/CodeGen/size-of-vla.cpp
+++ b/clang/test/CIR/CodeGen/size-of-vla.cpp
@@ -40,14 +40,14 @@ void vla_type_with_element_type_int() {
// CIR: cir.store {{.*}} %[[CONST_10]], %[[N_ADDR]] : !u64i, !cir.ptr<!u64i>
// CIR: %[[TMP_N:.*]] = cir.load {{.*}} %[[N_ADDR]] : !cir.ptr<!u64i>, !u64i
// CIR: %[[CONST_4:.*]] = cir.const #cir.int<4> : !u64i
-// CIR: %[[SIZE:.*]] = cir.mul nuw %[[CONST_4]], %[[TMP_N]] : !u64i
+// CIR: %[[SIZE:.*]] = cir.mul nuw %[[TMP_N]], %[[CONST_4]] : !u64i
// CIR: cir.store {{.*}} %[[SIZE]], %[[SIZE_ADDR]] : !u64i, !cir.ptr<!u64i>
// LLVM: %[[N_ADDR:.*]] = alloca i64, i64 1, align 8
// LLVM: %[[SIZE_ADDR:.*]] = alloca i64, i64 1, align 8
// LLVM: store i64 10, ptr %[[N_ADDR]], align 8
// LLVM: %[[TMP_N:.*]] = load i64, ptr %[[N_ADDR]], align 8
-// LLVM: %[[SIZE:.*]] = mul nuw i64 4, %[[TMP_N]]
+// LLVM: %[[SIZE:.*]] = mul nuw i64 %[[TMP_N]], 4
// LLVM: store i64 %[[SIZE]], ptr %[[SIZE_ADDR]], align 8
// OGCG: %[[N_ADDR:.*]] = alloca i64, align 8
@@ -126,7 +126,7 @@ void vla_expr_element_type_int() {
// CIR: cir.cleanup.scope {
// CIR: %[[ARR_ADDR:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, %[[TMP_N]] : !u64i, ["arr"]
// CIR: %[[CONST_4:.*]] = cir.const #cir.int<4> : !u64i
-// CIR: %[[SIZE:.*]] = cir.mul nuw %[[CONST_4]], %[[TMP_N]] : !u64i
+// CIR: %[[SIZE:.*]] = cir.mul nuw %[[TMP_N]], %[[CONST_4]] : !u64i
// CIR: cir.store {{.*}} %[[SIZE]], %[[SIZE_ADDR]] : !u64i, !cir.ptr<!u64i>
// CIR: cir.yield
// CIR: } cleanup normal {
@@ -143,7 +143,7 @@ void vla_expr_element_type_int() {
// LLVM: %[[STACK_SAVE:.*]] = call ptr @llvm.stacksave.p0()
// LLVM: store ptr %[[STACK_SAVE]], ptr %[[SAVED_STACK_ADDR]], align 8
// LLVM: %[[ARR_ADDR:.*]] = alloca i32, i64 %[[TMP_N]], align 16
-// LLVM: %[[SIZE:.*]] = mul nuw i64 4, %[[TMP_N]]
+// LLVM: %[[SIZE:.*]] = mul nuw i64 %[[TMP_N]], 4
// LLVM: store i64 %[[SIZE]], ptr %[[SIZE_ADDR]], align 8
// LLVM: %[[TMP_SAVED_STACK:.*]] = load ptr, ptr %[[SAVED_STACK_ADDR]], align 8
// LLVM: call void @llvm.stackrestore.p0(ptr %[[TMP_SAVED_STACK]])
diff --git a/clang/test/CIR/CodeGen/vla.c b/clang/test/CIR/CodeGen/vla.c
index f86ca88bc124c..7055167bd6ecd 100644
--- a/clang/test/CIR/CodeGen/vla.c
+++ b/clang/test/CIR/CodeGen/vla.c
@@ -62,7 +62,7 @@ void f1(int len) {
// CIR: %[[LEN_SIZE_T:.*]] = cir.cast integral %[[LEN]] : !s32i -> !u64i
// CIR: %[[STACK_PTR:.*]] = cir.stacksave
// CIR: cir.store{{.*}} %[[STACK_PTR]], %[[SAVED_STACK]]
-// CIR: %[[TOTAL_LEN:.*]] = cir.mul nuw %[[SIXTEEN]], %[[LEN_SIZE_T]]
+// CIR: %[[TOTAL_LEN:.*]] = cir.mul nuw %[[LEN_SIZE_T]], %[[SIXTEEN]]
// CIR: %[[ARR:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, %[[TOTAL_LEN]] : !u64i, ["arr"]
// CIR: %[[STACK_RESTORE_PTR:.*]] = cir.load{{.*}} %[[SAVED_STACK]]
// CIR: cir.stackrestore %[[STACK_RESTORE_PTR]]
@@ -75,7 +75,7 @@ void f1(int len) {
// LLVM: %[[LEN_SIZE_T:.*]] = sext i32 %[[LEN]] to i64
// LLVM: %[[STACK_PTR:.*]] = call ptr @llvm.stacksave.p0()
// LLVM: store ptr %[[STACK_PTR]], ptr %[[SAVED_STACK]]
-// LLVM: %[[TOTAL_LEN:.*]] = mul nuw i64 16, %[[LEN_SIZE_T]]
+// LLVM: %[[TOTAL_LEN:.*]] = mul nuw i64 %[[LEN_SIZE_T]], 16
// LLVM: %[[ARR:.*]] = alloca i32, i64 %[[TOTAL_LEN]]
// LLVM: %[[STACK_RESTORE_PTR:.*]] = load ptr, ptr %[[SAVED_STACK]]
// LLVM: call void @llvm.stackrestore.p0(ptr %[[STACK_RESTORE_PTR]])
@@ -361,7 +361,7 @@ void vla_subscript_expr() {
// CIR: %[[COMPOUND_PTR:.*]] = cir.ptr_stride %[[TMP_COMPOUND]], %[[CONST_0]] : (!cir.ptr<!cir.ptr<!s32i>>, !s64i) -> !cir.ptr<!cir.ptr<!s32i>>
// CIR: %[[TMP_COMPOUND:.*]] = cir.load {{.*}} %[[COMPOUND_PTR]] : !cir.ptr<!cir.ptr<!s32i>>, !cir.ptr<!s32i>
// CIR: %[[CONST_1:.*]] = cir.const #cir.int<1> : !u64i
-// CIR: %[[VLA_IDX:.*]] = cir.mul nsw %[[CONST_1]], %[[TMP_N]] : !u64i
+// CIR: %[[VLA_IDX:.*]] = cir.mul nsw %[[TMP_N]], %[[CONST_1]] : !u64i
// CIR: %[[VLA_A_PTR:.*]] = cir.ptr_stride %[[TMP_COMPOUND]], %[[VLA_IDX]] : (!cir.ptr<!s32i>, !u64i) -> !cir.ptr<!s32i>
// CIR: %[[ELEM_5_PTR:.*]] = cir.ptr_stride %[[VLA_A_PTR]], %[[CONST_5]] : (!cir.ptr<!s32i>, !s64i) -> !cir.ptr<!s32i>
// CIR: cir.store {{.*}} %[[CONST_0_VAL]], %[[ELEM_5_PTR]] : !s32i, !cir.ptr<!s32i>
@@ -375,7 +375,7 @@ void vla_subscript_expr() {
// LLVM: %[[TMP_COMPOUND:.*]] = load ptr, ptr %[[COMPOUND_ADDR]], align 8
// LLVM: %[[COMPOUND_PTR:.*]] = getelementptr ptr, ptr %[[TMP_COMPOUND]], i64 0
// LLVM: %[[TMP_COMPOUND:.*]] = load ptr, ptr %[[COMPOUND_PTR]], align 8
-// LLVM: %[[VLA_IDX:.*]] = mul nsw i64 1, %[[TMP_N]]
+// LLVM: %[[VLA_IDX:.*]] = mul nsw i64 %[[TMP_N]], 1
// LLVM: %[[VLA_A_PTR:.*]] = getelementptr i32, ptr %[[TMP_COMPOUND]], i64 %[[VLA_IDX]]
// LLVM: %[[ELEM_5_PTR:.*]] = getelementptr i32, ptr %[[VLA_A_PTR]], i64 5
// LLVM: store i32 0, ptr %[[ELEM_5_PTR]], align 4
More information about the cfe-commits
mailing list