[llvm-branch-commits] [mlir] [mlir] Add `OpFoldResults` definition (PR #229215)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Mon Oct 5 13:50:14 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir
Author: Victor Perez (victor-eds)
<details>
<summary>Changes</summary>
MLIR does not support partial folds for multi-result operations. This patch starts the enablement of this feature by adding the `OpFoldResults` new folder methods will return. Nothing uses the class yet.
The new class `OpFoldResults` in `mlir/IR/OpFoldResult.h` holds either no replacement or one replacement per result, and an in-place bit. For each replacement (of `OpFoldResult` type), a null replacement keeps its result. The class provides constructor for common constructs: `{}`, `nullptr`, and `failure()` are failures, and `success()` is an in-place change with no replacement. A helper `normalize` turns a replacement that is its own result into a null replacement, and it clears the replacements if no result is replaced. In assert builds, `normalize` also checks the type of each `Value` replacement. `normalize` won't be called from drivers, only by hooks, `fold` callers always get normalized `OpFoldResults`.
We include a `detail::convertLegacyFoldResults` migration utility while the legacy form is still allowed. This converts the result of a legacy vector fold to the new return type.
RFC: https://discourse.llvm.org/t/rfc-partial-folding-for-multi-result-ops/91954
Assisted by: Claude Code.
---
Patch is 26.01 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/229215.diff
4 Files Affected:
- (modified) mlir/include/mlir/IR/OpFoldResult.h (+104-1)
- (modified) mlir/lib/IR/OpFoldResult.cpp (+125)
- (modified) mlir/unittests/IR/CMakeLists.txt (+1)
- (added) mlir/unittests/IR/OpFoldResultsTest.cpp (+452)
``````````diff
diff --git a/mlir/include/mlir/IR/OpFoldResult.h b/mlir/include/mlir/IR/OpFoldResult.h
index 94d3072c9040666..e39dbe2d2db7f36 100644
--- a/mlir/include/mlir/IR/OpFoldResult.h
+++ b/mlir/include/mlir/IR/OpFoldResult.h
@@ -7,7 +7,7 @@
//===----------------------------------------------------------------------===//
//
// This file defines OpFoldResult, the result of a fold for one result of an
-// operation.
+// operation, and OpFoldResults, the result of a fold of any operation.
//
//===----------------------------------------------------------------------===//
@@ -18,9 +18,16 @@
#include "mlir/IR/Value.h"
#include "mlir/Support/LLVM.h"
#include "llvm/ADT/PointerUnion.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Compiler.h"
+#include <cstddef>
+#include <initializer_list>
+#include <type_traits>
namespace mlir {
+class Operation;
+
/// This class represents a single result from folding an operation.
class OpFoldResult : public PointerUnion<Attribute, Value> {
using PointerUnion<Attribute, Value>::PointerUnion;
@@ -54,6 +61,102 @@ struct CastInfo<To, const mlir::OpFoldResult>
namespace mlir {
/// Allow printing to a stream.
raw_ostream &operator<<(raw_ostream &os, OpFoldResult ofr);
+
+/// The result of a fold of any op. Replacement i is an Attribute (replace
+/// result i with a constant), a Value (replace result i with that value), or
+/// null / op->getResult(i) (keep result i). A separate bit records an in-place
+/// change of the op.
+class [[nodiscard]] OpFoldResults {
+public:
+ /// Failure: the fold did not apply and the IR is unchanged.
+ OpFoldResults() = default;
+ /// Failure, the same as the default constructor.
+ OpFoldResults(std::nullptr_t);
+ /// success(): the op changed in place. failure(): failure.
+ OpFoldResults(LogicalResult status);
+ /// One replacement. Valid only when the op has exactly one result at run
+ /// time. A null replacement, or the op's own result, keeps the result, so the
+ /// fold fails. Unlike `OpFoldResult fold`, the op's own result does not mean
+ /// in place; use success() or setModifiedInPlace() for an in-place change.
+ OpFoldResults(OpFoldResult replacement);
+ OpFoldResults(Value replacement);
+ OpFoldResults(Attribute replacement);
+ /// One replacement per result.
+ OpFoldResults(std::initializer_list<OpFoldResult> replacements);
+ /// One replacement per range element. An empty range is a failure.
+ template <typename RangeT,
+ typename = std::enable_if_t<
+ !std::is_convertible_v<RangeT, Attribute> &&
+ !std::is_convertible_v<RangeT, Value> &&
+ std::is_convertible_v<llvm::detail::ValueOfRange<RangeT>,
+ OpFoldResult>>>
+ OpFoldResults(RangeT &&range) {
+ llvm::append_range(replacements, range);
+ }
+ /// Incremental form: every result is kept and the op is not changed in
+ /// place.
+ explicit OpFoldResults(Operation *op);
+
+ /// Set the replacement of `result`, which must be a result of the op. If
+ /// another op owns `result`, or if normalize() already ran, the behavior is
+ /// undefined. The object must already hold one replacement per result, for
+ /// example from OpFoldResults(op). A null replacement, or
+ /// `replacement == result`, keeps the result. The last write wins.
+ void replace(Value result, OpFoldResult replacement);
+ /// Same as above, with the index of the result. normalize() maps a
+ /// replacement that is the op's own result to "keep".
+ void replace(unsigned resultIndex, OpFoldResult replacement);
+ /// Set whether the fold changed the op in place (operands, attributes,
+ /// properties, or regions).
+ void setModifiedInPlace(bool modified = true);
+
+ // Queries for drivers. They are valid after normalize().
+
+ /// Return true if the fold changed the op in place or replaces a result.
+ bool succeeded() const;
+ /// Return true if the fold did not apply.
+ bool failed() const;
+ /// Return true if the fold changed the op in place.
+ bool modifiedInPlace() const;
+ /// Return true if the fold replaces at least one result.
+ bool replacesAny() const;
+ /// Return true if the operation has at least one result and all the results
+ /// are replaced.
+ bool replacesAll() const;
+ /// Return the number of replacements: 0 if no replacement, or one per result
+ /// of the op.
+ unsigned size() const;
+ /// Return the replacement of result `i`; null means keep. If size() == 0,
+ /// every index reads as keep; otherwise `i` must be less than size().
+ OpFoldResult operator[](unsigned i) const;
+ /// Return the replacements. The returned range points into this object.
+ ArrayRef<OpFoldResult> getReplacements() const LLVM_LIFETIME_BOUND;
+
+ /// If replacement i matches op's result i, set replacement i to null. Clear
+ /// replacements if all end up null. Idempotent.
+ void normalize(Operation *op);
+
+private:
+ SmallVector<OpFoldResult, 2> replacements;
+ bool inPlace = false;
+};
+
+/// Return true if `result` changed the op in place or replaces a result.
+inline bool succeeded(const OpFoldResults &result) {
+ return result.succeeded();
+}
+
+/// Return true if `result` is a failure.
+inline bool failed(const OpFoldResults &result) { return result.failed(); }
+
+namespace detail {
+/// Convert the result of a legacy vector fold with the strict legacy contract:
+/// failure stays failure, success with an empty vector means "in place", and
+/// success with a full vector has one replacement per result. The result is
+/// then normalized.
+OpFoldResults convertLegacyFoldResults(LogicalResult status,
+ ArrayRef<OpFoldResult> results);
+} // namespace detail
} // namespace mlir
#endif // MLIR_IR_OPFOLDRESULT_H
diff --git a/mlir/lib/IR/OpFoldResult.cpp b/mlir/lib/IR/OpFoldResult.cpp
index 3a448d4b456edf5..185d771e773b91b 100644
--- a/mlir/lib/IR/OpFoldResult.cpp
+++ b/mlir/lib/IR/OpFoldResult.cpp
@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
#include "mlir/IR/OpFoldResult.h"
+#include "mlir/IR/Diagnostics.h"
+#include "mlir/IR/Operation.h"
#include "llvm/Support/raw_ostream.h"
using namespace mlir;
@@ -24,3 +26,126 @@ raw_ostream &mlir::operator<<(raw_ostream &os, OpFoldResult ofr) {
llvm::dyn_cast_if_present<Attribute>(ofr).print(os);
return os;
}
+
+/// Return true if every replacement is null.
+static bool replacesNone(ArrayRef<OpFoldResult> replacements) {
+ return llvm::none_of(replacements, [](OpFoldResult replacement) {
+ return static_cast<bool>(replacement);
+ });
+}
+
+//===----------------------------------------------------------------------===//
+// OpFoldResults
+//===----------------------------------------------------------------------===//
+
+OpFoldResults::OpFoldResults(std::nullptr_t) {}
+
+OpFoldResults::OpFoldResults(LogicalResult status)
+ : inPlace(status.succeeded()) {}
+
+OpFoldResults::OpFoldResults(OpFoldResult replacement) {
+ if (replacement)
+ replacements.push_back(replacement);
+}
+
+OpFoldResults::OpFoldResults(Value replacement)
+ : OpFoldResults(OpFoldResult(replacement)) {}
+
+OpFoldResults::OpFoldResults(Attribute replacement)
+ : OpFoldResults(OpFoldResult(replacement)) {}
+
+OpFoldResults::OpFoldResults(std::initializer_list<OpFoldResult> replacements)
+ : replacements(replacements) {}
+
+OpFoldResults::OpFoldResults(Operation *op) {
+ assert(op && "expected a non-null operation");
+ replacements.resize(op->getNumResults());
+}
+
+void OpFoldResults::replace(Value result, OpFoldResult replacement) {
+ auto opResult = dyn_cast_if_present<OpResult>(result);
+ assert(opResult && "expected an OpResult");
+ if (dyn_cast_if_present<Value>(replacement) == result)
+ replacement = OpFoldResult();
+ replace(opResult.getResultNumber(), replacement);
+}
+
+void OpFoldResults::replace(unsigned resultIndex, OpFoldResult replacement) {
+ assert(resultIndex < replacements.size() && "result index out of range");
+ replacements[resultIndex] = replacement;
+}
+
+void OpFoldResults::setModifiedInPlace(bool modified) { inPlace = modified; }
+
+bool OpFoldResults::succeeded() const { return inPlace || replacesAny(); }
+
+bool OpFoldResults::failed() const { return !succeeded(); }
+
+bool OpFoldResults::modifiedInPlace() const { return inPlace; }
+
+bool OpFoldResults::replacesAny() const { return !replacesNone(replacements); }
+
+bool OpFoldResults::replacesAll() const {
+ return !replacements.empty() &&
+ llvm::all_of(replacements, [](OpFoldResult replacement) {
+ return static_cast<bool>(replacement);
+ });
+}
+
+unsigned OpFoldResults::size() const { return replacements.size(); }
+
+OpFoldResult OpFoldResults::operator[](unsigned i) const {
+ if (replacements.empty())
+ return OpFoldResult();
+ assert(i < replacements.size() && "result index out of range");
+ return replacements[i];
+}
+
+ArrayRef<OpFoldResult> OpFoldResults::getReplacements() const {
+ return replacements;
+}
+
+void OpFoldResults::normalize(Operation *op) {
+ assert(op && "expected a non-null operation");
+ if (!replacements.empty()) {
+ assert(replacements.size() == op->getNumResults() &&
+ "expected one replacement per operation result");
+ for (auto [replacement, result] :
+ llvm::zip_equal(replacements, op->getResults()))
+ if (dyn_cast_if_present<Value>(replacement) == result)
+ replacement = OpFoldResult();
+ if (replacesNone(replacements))
+ replacements.clear();
+ }
+
+#ifndef NDEBUG
+ for (auto [index, replacement] : llvm::enumerate(replacements)) {
+ auto value = dyn_cast_if_present<Value>(replacement);
+ if (!value)
+ continue;
+ Type expectedType = op->getResult(index).getType();
+ if (value.getType() != expectedType) {
+ op->emitOpError() << "folder produced a value of incorrect type: "
+ << value.getType() << ", expected: " << expectedType;
+ assert(false && "incorrect fold result type");
+ }
+ }
+#endif // NDEBUG
+}
+
+//===----------------------------------------------------------------------===//
+// Legacy fold results
+//===----------------------------------------------------------------------===//
+
+OpFoldResults detail::convertLegacyFoldResults(LogicalResult status,
+ ArrayRef<OpFoldResult> results) {
+ if (failed(status))
+ return failure();
+ if (results.empty())
+ return success();
+ assert(llvm::all_of(
+ results,
+ [](OpFoldResult result) { return static_cast<bool>(result); }) &&
+ "legacy fold returned a null result");
+ return results;
+}
diff --git a/mlir/unittests/IR/CMakeLists.txt b/mlir/unittests/IR/CMakeLists.txt
index ce9b230222f6212..e6858585aaf6c49 100644
--- a/mlir/unittests/IR/CMakeLists.txt
+++ b/mlir/unittests/IR/CMakeLists.txt
@@ -14,6 +14,7 @@ add_mlir_unittest(MLIRIRTests
LocationTest.cpp
MLIRContextResetTest.cpp
MemrefLayoutTest.cpp
+ OpFoldResultsTest.cpp
OpImplementationTest.cpp
OperationSupportTest.cpp
PatternMatchTest.cpp
diff --git a/mlir/unittests/IR/OpFoldResultsTest.cpp b/mlir/unittests/IR/OpFoldResultsTest.cpp
new file mode 100644
index 000000000000000..c492c0df83a3aee
--- /dev/null
+++ b/mlir/unittests/IR/OpFoldResultsTest.cpp
@@ -0,0 +1,452 @@
+//===- OpFoldResultsTest.cpp - OpFoldResults unit tests -------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/IR/Builders.h"
+#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/MLIRContext.h"
+#include "mlir/IR/OpFoldResult.h"
+#include "mlir/IR/Operation.h"
+#include "gtest/gtest.h"
+
+using namespace mlir;
+
+namespace {
+class OpFoldResultsTest : public ::testing::Test {
+protected:
+ OpFoldResultsTest() : builder(&context) {
+ context.allowUnregisteredDialects();
+ i32 = builder.getI32Type();
+ f32 = builder.getF32Type();
+ }
+
+ ~OpFoldResultsTest() override {
+ // Destroy users before the ops that define their operands.
+ for (Operation *op : llvm::reverse(ops))
+ op->destroy();
+ }
+
+ /// Create an op with the given result types and operands. The fixture
+ /// destroys it.
+ Operation *createOp(TypeRange resultTypes, StringRef name = "foo.bar",
+ ValueRange operands = {}) {
+ OperationState state(UnknownLoc::get(&context), name);
+ state.addTypes(resultTypes);
+ state.addOperands(operands);
+ Operation *op = Operation::create(state);
+ ops.push_back(op);
+ return op;
+ }
+
+ MLIRContext context;
+ Builder builder;
+ Type i32;
+ Type f32;
+ SmallVector<Operation *> ops;
+};
+} // namespace
+
+static void expectOneReplacement(const OpFoldResults &result,
+ OpFoldResult expected) {
+ EXPECT_TRUE(result.succeeded());
+ EXPECT_FALSE(result.modifiedInPlace());
+ EXPECT_TRUE(result.replacesAny());
+ EXPECT_TRUE(result.replacesAll());
+ ASSERT_EQ(result.size(), 1u);
+ EXPECT_EQ(result[0], expected);
+}
+
+// The helpers below return the way a folder returns, so they use copy
+// initialization.
+static OpFoldResults foldToEmptyBraces() { return {}; }
+static OpFoldResults foldToTypedValue(TypedValue<IntegerType> value) {
+ return value;
+}
+static OpFoldResults foldToIntegerAttr(IntegerAttr attr) { return attr; }
+static OpFoldResults foldToArrayAttr(ArrayAttr attr) { return attr; }
+static OpFoldResults foldToList(OpFoldResult lhs, OpFoldResult rhs) {
+ return {lhs, rhs};
+}
+
+TEST_F(OpFoldResultsTest, DefaultIsFailure) {
+ OpFoldResults result;
+ EXPECT_TRUE(result.failed());
+ EXPECT_FALSE(result.succeeded());
+ EXPECT_FALSE(result.modifiedInPlace());
+ EXPECT_FALSE(result.replacesAny());
+ EXPECT_FALSE(result.replacesAll());
+ EXPECT_EQ(result.size(), 0u);
+ EXPECT_TRUE(result.getReplacements().empty());
+}
+
+TEST_F(OpFoldResultsTest, EmptyBracesAreFailure) {
+ OpFoldResults result = foldToEmptyBraces();
+ EXPECT_TRUE(result.failed());
+ EXPECT_FALSE(result.modifiedInPlace());
+ EXPECT_EQ(result.size(), 0u);
+}
+
+TEST_F(OpFoldResultsTest, NullptrIsFailure) {
+ OpFoldResults result = nullptr;
+ EXPECT_TRUE(result.failed());
+ EXPECT_FALSE(result.modifiedInPlace());
+ EXPECT_EQ(result.size(), 0u);
+}
+
+TEST_F(OpFoldResultsTest, LogicalResult) {
+ OpFoldResults inPlace = success();
+ EXPECT_TRUE(inPlace.succeeded());
+ EXPECT_TRUE(inPlace.modifiedInPlace());
+ EXPECT_FALSE(inPlace.replacesAny());
+ EXPECT_FALSE(inPlace.replacesAll());
+ EXPECT_EQ(inPlace.size(), 0u);
+
+ OpFoldResults failedResult = failure();
+ EXPECT_TRUE(failedResult.failed());
+ EXPECT_FALSE(failedResult.modifiedInPlace());
+ EXPECT_FALSE(failedResult.replacesAny());
+ EXPECT_EQ(failedResult.size(), 0u);
+}
+
+TEST_F(OpFoldResultsTest, SingleReplacement) {
+ Operation *producer = createOp({i32});
+ Operation *op = createOp({i32});
+ Value value = producer->getResult(0);
+ IntegerAttr intAttr = builder.getI32IntegerAttr(7);
+ Attribute attr = intAttr;
+
+ OpFoldResults fromOpFoldResult = OpFoldResult(attr);
+ expectOneReplacement(fromOpFoldResult, attr);
+
+ OpFoldResults fromValue = value;
+ expectOneReplacement(fromValue, value);
+
+ OpFoldResults fromAttr = attr;
+ expectOneReplacement(fromAttr, attr);
+
+ // TypedValue and IntegerAttr use the Value and Attribute constructors.
+ OpFoldResults fromTypedValue =
+ foldToTypedValue(cast<TypedValue<IntegerType>>(value));
+ expectOneReplacement(fromTypedValue, value);
+
+ OpFoldResults fromIntegerAttr = foldToIntegerAttr(intAttr);
+ expectOneReplacement(fromIntegerAttr, attr);
+
+ // The object holds one replacement for the single result.
+ fromValue.normalize(op);
+ expectOneReplacement(fromValue, value);
+ EXPECT_EQ(fromValue.size(), op->getNumResults());
+}
+
+TEST_F(OpFoldResultsTest, ArrayAttrFillsOneReplacement) {
+ Operation *op = createOp({i32});
+ ArrayAttr arrayAttr = builder.getI32ArrayAttr({1, 2, 3});
+ OpFoldResults result = foldToArrayAttr(arrayAttr);
+ expectOneReplacement(result, arrayAttr);
+ result.normalize(op);
+ expectOneReplacement(result, arrayAttr);
+}
+
+TEST_F(OpFoldResultsTest, InitializerList) {
+ Operation *producer = createOp({i32});
+ Operation *op = createOp({i32, f32});
+ Value value = producer->getResult(0);
+ Attribute attr = builder.getF32FloatAttr(1.0);
+
+ OpFoldResults result = {value, attr};
+ EXPECT_TRUE(result.succeeded());
+ ASSERT_EQ(result.size(), 2u);
+ EXPECT_EQ(result[0], OpFoldResult(value));
+ EXPECT_EQ(result[1], OpFoldResult(attr));
+ result.normalize(op);
+ EXPECT_TRUE(result.succeeded());
+ EXPECT_FALSE(result.modifiedInPlace());
+ EXPECT_TRUE(result.replacesAll());
+
+ OpFoldResults partial = foldToList(OpFoldResult(), attr);
+ partial.normalize(op);
+ EXPECT_TRUE(partial.succeeded());
+ EXPECT_TRUE(partial.replacesAny());
+ EXPECT_FALSE(partial.replacesAll());
+ ASSERT_EQ(partial.size(), 2u);
+ EXPECT_FALSE(partial[0]);
+ EXPECT_EQ(partial[1], OpFoldResult(attr));
+}
+
+TEST_F(OpFoldResultsTest, Range) {
+ Operation *producer = createOp({i32, f32});
+ Operation *op = createOp({i32, f32});
+ Attribute attr = builder.getI32IntegerAttr(3);
+
+ SmallVector<OpFoldResult> vector = {attr, OpFoldResult()};
+ OpFoldResults fromVector = vector;
+ EXPECT_TRUE(fromVector.succeeded());
+ ASSERT_EQ(fromVector.size(), 2u);
+ EXPECT_EQ(fromVector[0], OpFoldResult(attr));
+ EXPECT_FALSE(fromVector[1]);
+ fromVector.normalize(op);
+ EXPECT_TRUE(fromVector.replacesAny());
+ EXPECT_FALSE(fromVector.replacesAll());
+
+ ValueRange range = producer->getResults();
+ OpFoldResults fromRange = range;
+ EXPECT_TRUE(fromRange.succeeded());
+ ASSERT_EQ(fromRange.size(), 2u);
+ EXPECT_EQ(fromRange[0], OpFoldResult(producer->getResult(0)));
+ EXPECT_EQ(fromRange[1], OpFoldResult(producer->getResult(1)));
+ fromRange.normalize(op);
+ EXPECT_TRUE(fromRange.replacesAll());
+
+ OpFoldResults fromEmptyRange = ValueRange();
+ EXPECT_TRUE(fromEmptyRange.failed());
+ EXPECT_EQ(fromEmptyRange.size(), 0u);
+
+ OpFoldResults fromEmptyVector = SmallVector<OpFoldResult>();
+ EXPECT_TRUE(fromEmptyVector.failed());
+
+ SmallVector<OpFoldResult> nulls(2);
+ OpFoldResults fromNulls = nulls;
+ EXPECT_TRUE(fromNulls.failed());
+}
+
+TEST_F(OpFoldResultsTest, IncrementalForm) {
+ Operation *producer = createOp({i32});
+ Operation *op = createOp({i32, i32});
+ Value value = producer->getResult(0);
+ Attribute attr = builder.getI32IntegerAttr(5);
+
+ OpFoldResults result(op);
+ EXPECT_TRUE(result.failed());
+ EXPECT_FALSE(result.modifiedInPlace());
+ EXPECT_FALSE(result.replacesAny());
+ EXPECT_EQ(result.size(), 2u);
+
+ result.replace(op->getResult(1), attr);
+ EXPECT_TRUE(result.succeeded());
+ EXPECT_TRUE(result.replacesAny());
+ EXPECT_FALSE(result.replacesAll());
+ EXPECT_FALSE(result[0]);
+ EXPECT_EQ(result[1], OpFoldResult(attr));
+
+ // A literal 0 picks the unsigned overload.
+ result.replace(0, value);
+ EXPECT_TRUE(result.replacesAll());
+ EXPECT_EQ(result[0], OpFoldResult(value));
+
+ // The last write wins.
+ result.replace(0u, attr);
+ EXPECT_EQ(result[0], OpFoldResult(attr));
+
+ // A null replacement, or the result itself, keeps the result.
+ result.replace(op->getResult(0), OpFoldResult());
+ EXPECT_FALSE(result[0]);
+ result.replace(op->getResult(1), op->getResult(1));
+ EXPECT_FALSE(result[1]);
+ EXPECT_TRUE(result.failed());
+ EXPECT_FALSE(result.replacesAny());
+
+ result.setModifiedInPlace();
+ EXPECT_TRUE(result.succeeded());
+ EXPECT_TRUE(result.modifiedInPlace());
+ result.setModifiedInPlace(false);
+ EXPECT_TRUE(result.failed());
+ EXPECT_FALSE(result.modifiedInPlace());
+
+ // normalize() maps the op's own result, set by index, to "keep".
+ OpFoldResults ownResult(op);
+ ownResult.replace(1u, op->getResult(1));
+ ownResult.normalize(op);
+ EXPECT_TRUE(ownResult.failed());
+ EXPECT_EQ(ownResult.size(), 0u);
+
+ OpFoldResults partial(op);
+ partial.replace(1u, value);
+ partial.normalize(op);
+ EXPECT_TRUE(pa...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/229215
More information about the llvm-branch-commits
mailing list