[CIR] Add poison attribute (#150760)

This patch adds the `#cir.poison` attribute which represents a poison
value. This patch also updates various operation folders to let them
propagate poison values from their inputs to their outputs.
This commit is contained in:
Sirui Mu
2025-07-27 19:30:37 +08:00
committed by GitHub
parent c639475974
commit e2b4ba0414
7 changed files with 147 additions and 7 deletions

View File

@@ -146,6 +146,18 @@ def UndefAttr : CIR_TypedAttr<"Undef", "undef"> {
}];
}
//===----------------------------------------------------------------------===//
// PoisonAttr
//===----------------------------------------------------------------------===//
def CIR_PoisonAttr : CIR_TypedAttr<"Poison", "poison"> {
let summary = "Represent a typed poison constant";
let description = [{
The PoisonAttr represents a typed poison constant, corresponding to LLVM's
notion of poison.
}];
}
//===----------------------------------------------------------------------===//
// IntegerAttr
//===----------------------------------------------------------------------===//

View File

@@ -287,7 +287,6 @@ struct MissingFeatures {
// Future CIR attributes
static bool optInfoAttr() { return false; }
static bool poisonAttr() { return false; }
};
} // namespace cir

View File

@@ -339,7 +339,7 @@ static LogicalResult checkConstantTypes(mlir::Operation *op, mlir::Type opType,
}
if (mlir::isa<cir::ConstArrayAttr, cir::ConstVectorAttr,
cir::ConstComplexAttr>(attrType))
cir::ConstComplexAttr, cir::PoisonAttr>(attrType))
return success();
assert(isa<TypedAttr>(attrType) && "What else could we be looking at here?");
@@ -629,6 +629,11 @@ static Value tryFoldCastChain(cir::CastOp op) {
}
OpFoldResult cir::CastOp::fold(FoldAdaptor adaptor) {
if (mlir::isa_and_present<cir::PoisonAttr>(adaptor.getSrc())) {
// Propagate poison value
return cir::PoisonAttr::get(getContext(), getType());
}
if (getSrc().getType() == getType()) {
switch (getKind()) {
case cir::CastKind::integral: {
@@ -1783,6 +1788,12 @@ static bool isBoolNot(cir::UnaryOp op) {
//
// and the argument of the first one (%0) will be used instead.
OpFoldResult cir::UnaryOp::fold(FoldAdaptor adaptor) {
if (auto poison =
mlir::dyn_cast_if_present<cir::PoisonAttr>(adaptor.getInput())) {
// Propagate poison values
return poison;
}
if (isBoolNot(*this))
if (auto previous = dyn_cast_or_null<UnaryOp>(getInput().getDefiningOp()))
if (isBoolNot(previous))
@@ -2239,16 +2250,18 @@ static OpFoldResult
foldUnaryBitOp(mlir::Attribute inputAttr,
llvm::function_ref<llvm::APInt(const llvm::APInt &)> func,
bool poisonZero = false) {
if (mlir::isa_and_present<cir::PoisonAttr>(inputAttr)) {
// Propagate poison value
return inputAttr;
}
auto input = mlir::dyn_cast_if_present<IntAttr>(inputAttr);
if (!input)
return nullptr;
llvm::APInt inputValue = input.getValue();
if (poisonZero && inputValue.isZero()) {
// TODO(cir): maybe we should return a poison value here?
assert(!MissingFeatures::poisonAttr());
return nullptr;
}
if (poisonZero && inputValue.isZero())
return cir::PoisonAttr::get(input.getType());
llvm::APInt resultValue = func(inputValue);
return IntAttr::get(input.getType(), resultValue);
@@ -2307,6 +2320,12 @@ OpFoldResult ByteSwapOp::fold(FoldAdaptor adaptor) {
}
OpFoldResult RotateOp::fold(FoldAdaptor adaptor) {
if (mlir::isa_and_present<cir::PoisonAttr>(adaptor.getInput()) ||
mlir::isa_and_present<cir::PoisonAttr>(adaptor.getAmount())) {
// Propagate poison values
return cir::PoisonAttr::get(getType());
}
auto input = mlir::dyn_cast_if_present<IntAttr>(adaptor.getInput());
auto amount = mlir::dyn_cast_if_present<IntAttr>(adaptor.getAmount());
if (!input && !amount)

View File

@@ -1027,6 +1027,12 @@ mlir::LogicalResult CIRToLLVMConstantOpLowering::matchAndRewrite(
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Attribute attr = op.getValue();
if (mlir::isa<cir::PoisonAttr>(attr)) {
rewriter.replaceOpWithNewOp<mlir::LLVM::PoisonOp>(
op, getTypeConverter()->convertType(op.getType()));
return mlir::success();
}
if (mlir::isa<mlir::IntegerType>(op.getType())) {
// Verified cir.const operations cannot actually be of these types, but the
// lowering pass may generate temporary cir.const operations with these

View File

@@ -0,0 +1,14 @@
// RUN: cir-translate -cir-to-llvmir --disable-cc-lowering -o %t.ll %s
// RUN: FileCheck -check-prefix=LLVM --input-file=%t.ll %s
!s32i = !cir.int<s, 32>
module {
cir.func @lower_poison() -> !s32i {
%0 = cir.const #cir.poison : !s32i
cir.return %0 : !s32i
}
// LLVM-LABEL: @lower_poison
// LLVM-NEXT: ret i32 poison
// LLVM-NEXT: }
}

View File

@@ -25,6 +25,26 @@ module {
// CHECK-NEXT: cir.return %[[R]] : !u32i
// CHECK-NEXT: }
cir.func @fold_clz_zero_poison() -> !u32i {
%0 = cir.const #cir.int<0> : !u32i
%1 = cir.clz %0 poison_zero : !u32i
cir.return %1 : !u32i
}
// CHECK-LABEL: @fold_clz_zero_poison
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.poison : !u32i
// CHECK-NEXT: cir.return %[[R]] : !u32i
// CHECK-NEXT: }
cir.func @fold_clz_zero_no_poison() -> !u32i {
%0 = cir.const #cir.int<0> : !u32i
%1 = cir.clz %0 : !u32i
cir.return %1 : !u32i
}
// CHECK-LABEL: @fold_clz_zero_no_poison
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<32> : !u32i
// CHECK-NEXT: cir.return %[[R]] : !u32i
// CHECK-NEXT: }
cir.func @fold_ctz() -> !u32i {
%0 = cir.const #cir.int<2> : !u32i
%1 = cir.ctz %0 : !u32i
@@ -35,6 +55,26 @@ module {
// CHECK-NEXT: cir.return %[[R]] : !u32i
// CHECK-NEXT: }
cir.func @fold_ctz_zero_poison() -> !u32i {
%0 = cir.const #cir.int<0> : !u32i
%1 = cir.ctz %0 poison_zero : !u32i
cir.return %1 : !u32i
}
// CHECK-LABEL: @fold_ctz_zero_poison
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.poison : !u32i
// CHECK-NEXT: cir.return %[[R]] : !u32i
// CHECK-NEXT: }
cir.func @fold_ctz_zero_no_poison() -> !u32i {
%0 = cir.const #cir.int<0> : !u32i
%1 = cir.ctz %0 : !u32i
cir.return %1 : !u32i
}
// CHECK-LABEL: @fold_ctz_zero_no_poison
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<32> : !u32i
// CHECK-NEXT: cir.return %[[R]] : !u32i
// CHECK-NEXT: }
cir.func @fold_parity() -> !u32i {
// 0xdeadbeef is 0b1101_1110_1010_1101_1011_1110_1110_1111
// 0xdeadbeef contains 24 ones
@@ -82,6 +122,16 @@ module {
// CHECK-NEXT: cir.return %[[R]] : !u32i
// CHECK-NEXT: }
cir.func @fold_input_poison() -> !s32i {
%0 = cir.const #cir.poison : !s32i
%1 = cir.clrsb %0 : !s32i
cir.return %1 : !s32i
}
// CHECK-LABEL: @fold_input_poison
// CHECK-NEXT: %[[P:.+]] = cir.const #cir.poison : !s32i
// CHECK-NEXT: cir.return %[[P]] : !s32i
// CHECK-NEXT: }
cir.func @fold_rotate_input_all_zeros(%arg0 : !u32i) -> !u32i {
%0 = cir.const #cir.int<0> : !u32i
%1 = cir.rotate left %0, %arg0 : !u32i
@@ -138,4 +188,24 @@ module {
// CHECK-NEXT: %[[R:.+]] = cir.const #cir.int<4024348094> : !u32i
// CHECK-NEXT: cir.return %[[R]] : !u32i
// CHECK-NEXT: }
cir.func @fold_rotate_input_poison(%arg0 : !u32i) -> !u32i {
%0 = cir.const #cir.poison : !u32i
%1 = cir.rotate left %0, %arg0 : !u32i
cir.return %1 : !u32i
}
// CHECK-LABEL: @fold_rotate_input_poison
// CHECK-NEXT: %[[P:.+]] = cir.const #cir.poison : !u32i
// CHECK-NEXT: cir.return %[[P]] : !u32i
// CHECK-NEXT: }
cir.func @fold_rotate_amount_poison(%arg0 : !u32i) -> !u32i {
%0 = cir.const #cir.poison : !u32i
%1 = cir.rotate left %arg0, %0 : !u32i
cir.return %1 : !u32i
}
// CHECK-LABEL: @fold_rotate_amount_poison
// CHECK-NEXT: %[[P:.+]] = cir.const #cir.poison : !u32i
// CHECK-NEXT: cir.return %[[P]] : !u32i
// CHECK-NEXT: }
}

View File

@@ -39,6 +39,16 @@ module {
// CHECK: cir.func{{.*}} @unary_not(%arg0: !cir.bool) -> !cir.bool
// CHECK-NEXT: cir.return %arg0 : !cir.bool
cir.func @unary_poison() -> !s32i {
%0 = cir.const #cir.poison : !s32i
%1 = cir.unary(inc, %0) : !s32i, !s32i
cir.return %1 : !s32i
}
// CHECK: @unary_poison
// CHECK-NEXT: %[[P:.+]] = cir.const #cir.poison : !s32i
// CHECK-NEXT: cir.return %[[P]] : !s32i
// CHECK-NEXT: }
cir.func @cast1(%arg0: !cir.bool) -> !cir.bool {
%0 = cir.cast(bool_to_int, %arg0 : !cir.bool), !s32i
%1 = cir.cast(int_to_bool, %0 : !s32i), !cir.bool
@@ -70,4 +80,14 @@ module {
// CHECK-NEXT: %[[CAST3:.*]] = cir.cast(integral, %[[CAST2]] : !s32i), !s64i
// CHECK-NEXT: cir.return %[[CAST3]] : !s64i
cir.func @cast_poison() -> !s64i {
%0 = cir.const #cir.poison : !s32i
%1 = cir.cast(integral, %0 : !s32i), !s64i
cir.return %1 : !s64i
}
// CHECK: @cast_poison
// CHECK-NEXT: %[[P:.+]] = cir.const #cir.poison : !s64i
// CHECK-NEXT: cir.return %[[P]] : !s64i
// CHECK-NEXT: }
}