Files
swift-mirror/unittests/SILOptimizer/PartitionUtilsTest.cpp
Michael Gottesman 8745ab00de [rbi] Teach RegionIsolation how to properly error when 'inout sending' params are returned.
We want 'inout sending' parameters to have the semantics that not only are they
disconnected on return from the function but additionally they are guaranteed to
be in their own disconnected region on return. This implies that we must emit
errors when an 'inout sending' parameter or any element that is in the same
region as the current value within an 'inout sending' parameter is
returned. This commit contains a new diagnostic for RegionIsolation that adds
specific logic for detecting and emitting errors in these situations.

To implement this, we introduce 3 new diagnostics with each individual
diagnostic being slightly different to reflect the various ways that this error
can come up in source:

* Returning 'inout sending' directly:

```swift
func returnInOutSendingDirectly(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
  return x // expected-warning {{cannot return 'inout sending' parameter 'x' from global function 'returnInOutSendingDirectly'}}
  // expected-note @-1 {{returning 'x' risks concurrent access since caller assumes that 'x' and the result of global function 'returnInOutSendingDirectly' can be safely sent to different isolation domains}}
}
```

* Returning a value in the same region as an 'inout sending' parameter. E.x.:

```swift
func returnInOutSendingRegionVar(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
  var y = x
  y = x
  return y // expected-warning {{cannot return 'y' from global function 'returnInOutSendingRegionVar'}}
  // expected-note @-1 {{returning 'y' risks concurrent access to 'inout sending' parameter 'x' since the caller assumes that 'x' and the result of global function 'returnInOutSendingRegionVar' can be safely sent to different isolation domains}}
}
```

* Returning the result of a function or computed property that is in the same
region as the 'inout parameter'.

```swift
func returnInOutSendingViaHelper(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
  let y = x
  return useNonSendableKlassAndReturn(y) // expected-warning {{cannot return result of global function 'useNonSendableKlassAndReturn' from global function 'returnInOutSendingViaHelper'}}
  // expected-note @-1 {{returning result of global function 'useNonSendableKlassAndReturn' risks concurrent access to 'inout sending' parameter 'x' since the caller assumes that 'x' and the result of global function 'returnInOutSendingViaHelper' can be safely sent to different isolation domains}}
}
```

Additionally, I had to introduce a specific variant for each of these
diagnostics for cases where due to us being in a method, we are actually in our
caller causing the 'inout sending' parameter to be in the same region as an
actor isolated value:

* Returning 'inout sending' directly:

```swift
extension MyActor {
  func returnInOutSendingDirectly(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
    return x // expected-warning {{cannot return 'inout sending' parameter 'x' from instance method 'returnInOutSendingDirectly'}}
    // expected-note @-1 {{returning 'x' risks concurrent access since caller assumes that 'x' is not actor-isolated and the result of instance method 'returnInOutSendingDirectly' is 'self'-isolated}}
  }
}
```

* Returning a value in the same region as an 'inout sending' parameter. E.x.:

```swift
extension MyActor {
  func returnInOutSendingRegionLet(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
    let y = x
    return y // expected-warning {{cannot return 'y' from instance method 'returnInOutSendingRegionLet'}}
    // expected-note @-1 {{returning 'y' risks concurrent access to 'inout sending' parameter 'x' since the caller assumes that 'x' is not actor-isolated and the result of instance method 'returnInOutSendingRegionLet' is 'self'-isolated}}
  }
}
```

* Returning the result of a function or computed property that is in the same region as the 'inout parameter'.

```swift
extension MyActor {
  func returnInOutSendingViaHelper(_ x: inout sending NonSendableKlass) -> NonSendableKlass {
    let y = x
    return useNonSendableKlassAndReturn(y) // expected-warning {{cannot return result of global function 'useNonSendableKlassAndReturn' from instance method 'returnInOutSendingViaHelper'; this is an error in the Swift 6 language mode}}
    // expected-note @-1 {{returning result of global function 'useNonSendableKlassAndReturn' risks concurrent access to 'inout sending' parameter 'x' since the caller assumes that 'x' is not actor-isolated and the result of instance method 'returnInOutSendingViaHelper' is 'self'-isolated}}
  }
}
```

To implement this, I used two different approaches depending on whether or not
the returned value was generic or not.

* Concrete

In the case where we had a concrete value, I was able to in simple cases emit
diagnostics based off of the values returned by the return inst. In cases where
we phied together results due to multiple results in the same function, we
determine which of the incoming phied values caused the error by grabbing the
exit partition information of each of the incoming value predecessors and seeing
if an InOutSendingAtFunctionExit would emit an error.

* Generic

In the case of generic code, it is a little more interesting since the result is
a value stored in an our parameter instead of being a value directly returned by
a return inst. To work around this, I use PrunedLiveness to determine the last
values stored into the out parameter in the function to avoid having to do a
full dataflow. Then I take the exit blocks where we assign each of those values
and run the same check as we do in the direct phi case to emit the appropriate
error.

rdar://152454571
2025-08-25 14:57:44 -07:00

1039 lines
40 KiB
C++

//===--- PartitionUtilsTest.cpp -------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "swift/SILOptimizer/Utils/PartitionUtils.h"
#include "gtest/gtest.h"
#include <array>
using namespace swift;
using namespace swift::PartitionPrimitives;
//===----------------------------------------------------------------------===//
// Utilities
//===----------------------------------------------------------------------===//
struct Partition::PartitionTester {
const Partition &p;
PartitionTester(const Partition &p) : p(p) {}
unsigned getRegion(unsigned elt) const {
return unsigned(p.elementToRegionMap.at(Element(elt)));
}
};
namespace {
using PartitionTester = Partition::PartitionTester;
struct MockedPartitionOpEvaluator final
: PartitionOpEvaluatorBaseImpl<MockedPartitionOpEvaluator> {
MockedPartitionOpEvaluator(Partition &workingPartition,
SendingOperandSetFactory &ptrSetFactory,
SendingOperandToStateMap &operandToStateMap)
: PartitionOpEvaluatorBaseImpl(workingPartition, ptrSetFactory,
operandToStateMap) {}
// Just say that we always have a disconnected value.
SILIsolationInfo getIsolationRegionInfo(Element elt) const {
return SILIsolationInfo::getDisconnected(false /*isUnsafeNonIsolated*/);
}
bool shouldTryToSquelchErrors() const { return false; }
static SILLocation getLoc(SILInstruction *inst) {
return SILLocation::invalid();
}
static SILLocation getLoc(Operand *op) { return SILLocation::invalid(); }
static SILIsolationInfo getIsolationInfo(const PartitionOp &partitionOp) {
return {};
}
static SILInstruction *getSourceInst(const PartitionOp &partitionOp) {
return nullptr;
}
static bool doesFunctionHaveSendingResult(const PartitionOp &partitionOp) {
return false;
}
};
} // namespace
namespace {
struct MockedPartitionOpEvaluatorWithFailureCallback final
: PartitionOpEvaluatorBaseImpl<
MockedPartitionOpEvaluatorWithFailureCallback> {
using FailureCallbackTy =
std::function<void(const PartitionOp &, unsigned, Operand *)>;
FailureCallbackTy failureCallback;
MockedPartitionOpEvaluatorWithFailureCallback(
Partition &workingPartition, SendingOperandSetFactory &ptrSetFactory,
SendingOperandToStateMap &operandToStateMap,
FailureCallbackTy failureCallback)
: PartitionOpEvaluatorBaseImpl(workingPartition, ptrSetFactory,
operandToStateMap),
failureCallback(failureCallback) {}
void handleError(PartitionOpError error) {
switch (error.getKind()) {
case PartitionOpError::UnknownCodePattern:
case PartitionOpError::SentNeverSendable:
case PartitionOpError::AssignNeverSendableIntoSendingResult:
case PartitionOpError::InOutSendingNotInitializedAtExit:
case PartitionOpError::InOutSendingNotDisconnectedAtExit:
case PartitionOpError::NonSendableIsolationCrossingResult:
case PartitionOpError::InOutSendingReturned:
llvm_unreachable("Unsupported");
case PartitionOpError::LocalUseAfterSend: {
auto state = error.getLocalUseAfterSendError();
failureCallback(*state.op, state.sentElement, state.sendingOp);
}
}
}
// Just say that we always have a disconnected value.
SILIsolationInfo getIsolationRegionInfo(Element elt) const {
return SILIsolationInfo::getDisconnected(false /*nonisolated(unsafe)*/);
}
bool shouldTryToSquelchErrors() const { return false; }
static SILLocation getLoc(SILInstruction *inst) {
return SILLocation::invalid();
}
static SILLocation getLoc(Operand *op) { return SILLocation::invalid(); }
static SILIsolationInfo getIsolationInfo(const PartitionOp &partitionOp) {
return {};
}
static SILInstruction *getSourceInst(const PartitionOp &partitionOp) {
return nullptr;
}
};
} // namespace
//===----------------------------------------------------------------------===//
// Tests
//===----------------------------------------------------------------------===//
// When we send we need a specific send instruction. We do not ever
// actually dereference the instruction, so just use some invalid ptr values so
// we can compare.
Operand *operandSingletons[5] = {
(Operand *)0xDEAD0000, (Operand *)0xFEAD0000, (Operand *)0xAEDF0000,
(Operand *)0xFEDA0000, (Operand *)0xFBDA0000,
};
SILInstruction *instSingletons[5] = {
(SILInstruction *)0xBEAD0000, (SILInstruction *)0xBEAD0000,
(SILInstruction *)0xBEDF0000, (SILInstruction *)0xBEDA0000,
(SILInstruction *)0xBBDA0000,
};
SILLocation fakeLoc = SILLocation::invalid();
// This test tests that if a series of merges is split between two partitions
// p1 and p2, but also applied in its entirety to p3, then joining p1 and p2
// yields p3.
TEST(PartitionUtilsTest, TestMergeAndJoin) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap sendingOpToStateMap(historyFactory);
Partition p1(historyFactory.get());
Partition p2(historyFactory.get());
Partition p3(historyFactory.get());
{
MockedPartitionOpEvaluator eval(p1, factory, sendingOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(0)),
PartitionOp::AssignFresh(Element(1)),
PartitionOp::AssignFresh(Element(2)),
PartitionOp::AssignFresh(Element(3))});
}
{
MockedPartitionOpEvaluator eval(p2, factory, sendingOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(5)),
PartitionOp::AssignFresh(Element(6)),
PartitionOp::AssignFresh(Element(7)),
PartitionOp::AssignFresh(Element(0))});
}
{
MockedPartitionOpEvaluator eval(p3, factory, sendingOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(2)),
PartitionOp::AssignFresh(Element(3)),
PartitionOp::AssignFresh(Element(4)),
PartitionOp::AssignFresh(Element(5))});
}
EXPECT_FALSE(Partition::equals(p1, p2));
EXPECT_FALSE(Partition::equals(p2, p3));
EXPECT_FALSE(Partition::equals(p1, p3));
{
MockedPartitionOpEvaluator eval(p1, factory, sendingOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(4)),
PartitionOp::AssignFresh(Element(5)),
PartitionOp::AssignFresh(Element(6)),
PartitionOp::AssignFresh(Element(7)),
PartitionOp::AssignFresh(Element(8))});
}
{
MockedPartitionOpEvaluator eval(p2, factory, sendingOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(1)),
PartitionOp::AssignFresh(Element(2)),
PartitionOp::AssignFresh(Element(3)),
PartitionOp::AssignFresh(Element(4)),
PartitionOp::AssignFresh(Element(8))});
}
{
MockedPartitionOpEvaluator eval(p3, factory, sendingOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(6)),
PartitionOp::AssignFresh(Element(7)),
PartitionOp::AssignFresh(Element(0)),
PartitionOp::AssignFresh(Element(1)),
PartitionOp::AssignFresh(Element(8))});
}
EXPECT_TRUE(Partition::equals(p1, p2));
EXPECT_TRUE(Partition::equals(p2, p3));
EXPECT_TRUE(Partition::equals(p1, p3));
auto expect_join_eq = [&]() {
Partition joined = Partition::join(p1, p2);
EXPECT_TRUE(Partition::equals(p3, joined));
};
auto apply_to_p1_and_p3 = [&](PartitionOp op) {
{
MockedPartitionOpEvaluator eval(p1, factory, sendingOpToStateMap);
eval.apply(op);
}
{
MockedPartitionOpEvaluator eval(p3, factory, sendingOpToStateMap);
eval.apply(op);
}
expect_join_eq();
};
auto apply_to_p2_and_p3 = [&](PartitionOp op) {
{
MockedPartitionOpEvaluator eval(p2, factory, sendingOpToStateMap);
eval.apply(op);
}
{
MockedPartitionOpEvaluator eval(p3, factory, sendingOpToStateMap);
eval.apply(op);
}
expect_join_eq();
};
apply_to_p1_and_p3(PartitionOp::Merge(Element(1), Element(2)));
apply_to_p2_and_p3(PartitionOp::Merge(Element(7), Element(8)));
apply_to_p1_and_p3(PartitionOp::Merge(Element(2), Element(7)));
apply_to_p2_and_p3(PartitionOp::Merge(Element(1), Element(3)));
apply_to_p1_and_p3(PartitionOp::Merge(Element(3), Element(4)));
EXPECT_FALSE(Partition::equals(p1, p2));
EXPECT_FALSE(Partition::equals(p2, p3));
EXPECT_FALSE(Partition::equals(p1, p3));
apply_to_p2_and_p3(PartitionOp::Merge(Element(2), Element(5)));
apply_to_p1_and_p3(PartitionOp::Merge(Element(5), Element(6)));
apply_to_p2_and_p3(PartitionOp::Merge(Element(1), Element(6)));
apply_to_p1_and_p3(PartitionOp::Merge(Element(2), Element(6)));
apply_to_p2_and_p3(PartitionOp::Merge(Element(3), Element(7)));
apply_to_p1_and_p3(PartitionOp::Merge(Element(7), Element(8)));
}
TEST(PartitionUtilsTest, Join1) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
Element data1[] = {Element(0), Element(1), Element(2),
Element(3), Element(4), Element(5)};
Partition p1 = Partition::separateRegions(fakeLoc, llvm::ArrayRef(data1),
historyFactory.get());
{
MockedPartitionOpEvaluator eval(p1, factory, transferringOpToStateMap);
eval.apply({PartitionOp::Assign(Element(0), Element(0)),
PartitionOp::Assign(Element(1), Element(0)),
PartitionOp::Assign(Element(2), Element(2)),
PartitionOp::Assign(Element(3), Element(3)),
PartitionOp::Assign(Element(4), Element(3)),
PartitionOp::Assign(Element(5), Element(2))});
}
Partition p2 = Partition::separateRegions(fakeLoc, llvm::ArrayRef(data1),
historyFactory.get());
{
MockedPartitionOpEvaluator eval(p2, factory, transferringOpToStateMap);
eval.apply({PartitionOp::Assign(Element(0), Element(0)),
PartitionOp::Assign(Element(1), Element(0)),
PartitionOp::Assign(Element(2), Element(2)),
PartitionOp::Assign(Element(3), Element(3)),
PartitionOp::Assign(Element(4), Element(3)),
PartitionOp::Assign(Element(5), Element(5))});
}
auto result = Partition::join(p1, p2);
PartitionTester tester(result);
ASSERT_EQ(tester.getRegion(0), 0);
ASSERT_EQ(tester.getRegion(1), 0);
ASSERT_EQ(tester.getRegion(2), 2);
ASSERT_EQ(tester.getRegion(3), 3);
ASSERT_EQ(tester.getRegion(4), 3);
ASSERT_EQ(tester.getRegion(5), 2);
}
TEST(PartitionUtilsTest, Join2) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
Element data1[] = {Element(0), Element(1), Element(2),
Element(3), Element(4), Element(5)};
Partition p1 = Partition::separateRegions(fakeLoc, llvm::ArrayRef(data1),
historyFactory.get());
{
MockedPartitionOpEvaluator eval(p1, factory, transferringOpToStateMap);
eval.apply({PartitionOp::Assign(Element(0), Element(0)),
PartitionOp::Assign(Element(1), Element(0)),
PartitionOp::Assign(Element(2), Element(2)),
PartitionOp::Assign(Element(3), Element(3)),
PartitionOp::Assign(Element(4), Element(3)),
PartitionOp::Assign(Element(5), Element(2))});
}
Element data2[] = {Element(4), Element(5), Element(6),
Element(7), Element(8), Element(9)};
Partition p2 = Partition::separateRegions(fakeLoc, llvm::ArrayRef(data2),
historyFactory.get());
{
MockedPartitionOpEvaluator eval(p2, factory, transferringOpToStateMap);
eval.apply({PartitionOp::Assign(Element(4), Element(4)),
PartitionOp::Assign(Element(5), Element(5)),
PartitionOp::Assign(Element(6), Element(4)),
PartitionOp::Assign(Element(7), Element(7)),
PartitionOp::Assign(Element(8), Element(7)),
PartitionOp::Assign(Element(9), Element(4))});
}
auto result = Partition::join(p1, p2);
PartitionTester tester(result);
ASSERT_EQ(tester.getRegion(0), 0);
ASSERT_EQ(tester.getRegion(1), 0);
ASSERT_EQ(tester.getRegion(2), 2);
ASSERT_EQ(tester.getRegion(3), 3);
ASSERT_EQ(tester.getRegion(4), 3);
ASSERT_EQ(tester.getRegion(5), 2);
ASSERT_EQ(tester.getRegion(6), 3);
ASSERT_EQ(tester.getRegion(7), 7);
ASSERT_EQ(tester.getRegion(8), 7);
ASSERT_EQ(tester.getRegion(9), 3);
}
TEST(PartitionUtilsTest, Join2Reversed) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
Element data1[] = {Element(0), Element(1), Element(2),
Element(3), Element(4), Element(5)};
Partition p1 = Partition::separateRegions(fakeLoc, llvm::ArrayRef(data1),
historyFactory.get());
{
MockedPartitionOpEvaluator eval(p1, factory, transferringOpToStateMap);
eval.apply({PartitionOp::Assign(Element(0), Element(0)),
PartitionOp::Assign(Element(1), Element(0)),
PartitionOp::Assign(Element(2), Element(2)),
PartitionOp::Assign(Element(3), Element(3)),
PartitionOp::Assign(Element(4), Element(3)),
PartitionOp::Assign(Element(5), Element(2))});
}
Element data2[] = {Element(4), Element(5), Element(6),
Element(7), Element(8), Element(9)};
Partition p2 = Partition::separateRegions(fakeLoc, llvm::ArrayRef(data2),
historyFactory.get());
{
MockedPartitionOpEvaluator eval(p2, factory, transferringOpToStateMap);
eval.apply({PartitionOp::Assign(Element(4), Element(4)),
PartitionOp::Assign(Element(5), Element(5)),
PartitionOp::Assign(Element(6), Element(4)),
PartitionOp::Assign(Element(7), Element(7)),
PartitionOp::Assign(Element(8), Element(7)),
PartitionOp::Assign(Element(9), Element(4))});
}
auto result = Partition::join(p2, p1);
PartitionTester tester(result);
ASSERT_EQ(tester.getRegion(0), 0);
ASSERT_EQ(tester.getRegion(1), 0);
ASSERT_EQ(tester.getRegion(2), 2);
ASSERT_EQ(tester.getRegion(3), 3);
ASSERT_EQ(tester.getRegion(4), 3);
ASSERT_EQ(tester.getRegion(5), 2);
ASSERT_EQ(tester.getRegion(6), 3);
ASSERT_EQ(tester.getRegion(7), 7);
ASSERT_EQ(tester.getRegion(8), 7);
ASSERT_EQ(tester.getRegion(9), 3);
}
TEST(PartitionUtilsTest, JoinLarge) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
Element data1[] = {
Element(0), Element(1), Element(2), Element(3), Element(4),
Element(5), Element(6), Element(7), Element(8), Element(9),
Element(10), Element(11), Element(12), Element(13), Element(14),
Element(15), Element(16), Element(17), Element(18), Element(19),
Element(20), Element(21), Element(22), Element(23), Element(24),
Element(25), Element(26), Element(27), Element(28), Element(29)};
Partition p1 = Partition::separateRegions(fakeLoc, llvm::ArrayRef(data1),
historyFactory.get());
{
MockedPartitionOpEvaluator eval(p1, factory, transferringOpToStateMap);
eval.apply({PartitionOp::Assign(Element(0), Element(29)),
PartitionOp::Assign(Element(1), Element(17)),
PartitionOp::Assign(Element(2), Element(0)),
PartitionOp::Assign(Element(3), Element(12)),
PartitionOp::Assign(Element(4), Element(13)),
PartitionOp::Assign(Element(5), Element(9)),
PartitionOp::Assign(Element(6), Element(15)),
PartitionOp::Assign(Element(7), Element(27)),
PartitionOp::Assign(Element(8), Element(3)),
PartitionOp::Assign(Element(9), Element(3)),
PartitionOp::Assign(Element(10), Element(3)),
PartitionOp::Assign(Element(11), Element(21)),
PartitionOp::Assign(Element(12), Element(14)),
PartitionOp::Assign(Element(13), Element(25)),
PartitionOp::Assign(Element(14), Element(1)),
PartitionOp::Assign(Element(15), Element(25)),
PartitionOp::Assign(Element(16), Element(12)),
PartitionOp::Assign(Element(17), Element(3)),
PartitionOp::Assign(Element(18), Element(25)),
PartitionOp::Assign(Element(19), Element(13)),
PartitionOp::Assign(Element(20), Element(19)),
PartitionOp::Assign(Element(21), Element(7)),
PartitionOp::Assign(Element(22), Element(19)),
PartitionOp::Assign(Element(23), Element(27)),
PartitionOp::Assign(Element(24), Element(1)),
PartitionOp::Assign(Element(25), Element(9)),
PartitionOp::Assign(Element(26), Element(18)),
PartitionOp::Assign(Element(27), Element(29)),
PartitionOp::Assign(Element(28), Element(28)),
PartitionOp::Assign(Element(29), Element(13))});
}
Element data2[] = {
Element(15), Element(16), Element(17), Element(18), Element(19),
Element(20), Element(21), Element(22), Element(23), Element(24),
Element(25), Element(26), Element(27), Element(28), Element(29),
Element(30), Element(31), Element(32), Element(33), Element(34),
Element(35), Element(36), Element(37), Element(38), Element(39),
Element(40), Element(41), Element(42), Element(43), Element(44)};
Partition p2 = Partition::separateRegions(fakeLoc, llvm::ArrayRef(data2),
historyFactory.get());
{
MockedPartitionOpEvaluator eval(p2, factory, transferringOpToStateMap);
eval.apply({PartitionOp::Assign(Element(15), Element(31)),
PartitionOp::Assign(Element(16), Element(34)),
PartitionOp::Assign(Element(17), Element(35)),
PartitionOp::Assign(Element(18), Element(41)),
PartitionOp::Assign(Element(19), Element(15)),
PartitionOp::Assign(Element(20), Element(32)),
PartitionOp::Assign(Element(21), Element(17)),
PartitionOp::Assign(Element(22), Element(31)),
PartitionOp::Assign(Element(23), Element(21)),
PartitionOp::Assign(Element(24), Element(33)),
PartitionOp::Assign(Element(25), Element(25)),
PartitionOp::Assign(Element(26), Element(31)),
PartitionOp::Assign(Element(27), Element(16)),
PartitionOp::Assign(Element(28), Element(35)),
PartitionOp::Assign(Element(29), Element(40)),
PartitionOp::Assign(Element(30), Element(33)),
PartitionOp::Assign(Element(31), Element(34)),
PartitionOp::Assign(Element(32), Element(22)),
PartitionOp::Assign(Element(33), Element(42)),
PartitionOp::Assign(Element(34), Element(37)),
PartitionOp::Assign(Element(35), Element(34)),
PartitionOp::Assign(Element(36), Element(18)),
PartitionOp::Assign(Element(37), Element(32)),
PartitionOp::Assign(Element(38), Element(22)),
PartitionOp::Assign(Element(39), Element(44)),
PartitionOp::Assign(Element(40), Element(20)),
PartitionOp::Assign(Element(41), Element(37)),
PartitionOp::Assign(Element(43), Element(29)),
PartitionOp::Assign(Element(44), Element(25))});
}
auto result = Partition::join(p1, p2);
PartitionTester tester(result);
ASSERT_EQ(tester.getRegion(0), 0);
ASSERT_EQ(tester.getRegion(1), 1);
ASSERT_EQ(tester.getRegion(2), 0);
ASSERT_EQ(tester.getRegion(3), 3);
ASSERT_EQ(tester.getRegion(4), 4);
ASSERT_EQ(tester.getRegion(5), 5);
ASSERT_EQ(tester.getRegion(6), 6);
ASSERT_EQ(tester.getRegion(7), 3);
ASSERT_EQ(tester.getRegion(8), 3);
ASSERT_EQ(tester.getRegion(9), 3);
ASSERT_EQ(tester.getRegion(10), 3);
ASSERT_EQ(tester.getRegion(11), 11);
ASSERT_EQ(tester.getRegion(12), 0);
ASSERT_EQ(tester.getRegion(13), 13);
ASSERT_EQ(tester.getRegion(14), 1);
ASSERT_EQ(tester.getRegion(15), 13);
ASSERT_EQ(tester.getRegion(16), 0);
ASSERT_EQ(tester.getRegion(17), 3);
ASSERT_EQ(tester.getRegion(18), 13);
ASSERT_EQ(tester.getRegion(19), 13);
ASSERT_EQ(tester.getRegion(20), 13);
ASSERT_EQ(tester.getRegion(21), 3);
ASSERT_EQ(tester.getRegion(22), 13);
ASSERT_EQ(tester.getRegion(23), 3);
ASSERT_EQ(tester.getRegion(24), 1);
ASSERT_EQ(tester.getRegion(25), 3);
ASSERT_EQ(tester.getRegion(26), 13);
ASSERT_EQ(tester.getRegion(27), 0);
ASSERT_EQ(tester.getRegion(28), 3);
ASSERT_EQ(tester.getRegion(29), 13);
ASSERT_EQ(tester.getRegion(30), 1);
ASSERT_EQ(tester.getRegion(31), 0);
ASSERT_EQ(tester.getRegion(32), 13);
ASSERT_EQ(tester.getRegion(33), 33);
ASSERT_EQ(tester.getRegion(34), 34);
ASSERT_EQ(tester.getRegion(35), 34);
ASSERT_EQ(tester.getRegion(36), 13);
ASSERT_EQ(tester.getRegion(37), 13);
ASSERT_EQ(tester.getRegion(38), 13);
ASSERT_EQ(tester.getRegion(39), 39);
ASSERT_EQ(tester.getRegion(40), 13);
ASSERT_EQ(tester.getRegion(41), 13);
ASSERT_EQ(tester.getRegion(42), 33);
ASSERT_EQ(tester.getRegion(43), 13);
ASSERT_EQ(tester.getRegion(44), 3);
}
// This test tests the semantics of assignment.
TEST(PartitionUtilsTest, TestAssign) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
Partition p1(historyFactory.get());
Partition p2(historyFactory.get());
Partition p3(historyFactory.get());
MockedPartitionOpEvaluator evalP1(p1, factory, transferringOpToStateMap);
evalP1.apply({PartitionOp::AssignFresh(Element(0)),
PartitionOp::AssignFresh(Element(1)),
PartitionOp::AssignFresh(Element(2)),
PartitionOp::AssignFresh(Element(3))});
MockedPartitionOpEvaluator evalP2(p2, factory, transferringOpToStateMap);
evalP2.apply({PartitionOp::AssignFresh(Element(0)),
PartitionOp::AssignFresh(Element(1)),
PartitionOp::AssignFresh(Element(2)),
PartitionOp::AssignFresh(Element(3))});
MockedPartitionOpEvaluator evalP3(p3, factory, transferringOpToStateMap);
evalP3.apply({PartitionOp::AssignFresh(Element(0)),
PartitionOp::AssignFresh(Element(1)),
PartitionOp::AssignFresh(Element(2)),
PartitionOp::AssignFresh(Element(3))});
// expected: p1: ((Element(0)) (Element(1)) (Element(2)) (Element(3))), p2:
// ((Element(0)) (Element(1)) (Element(2)) (Element(3))), p3: ((Element(0))
// (Element(1)) (Element(2)) (Element(3)))
EXPECT_TRUE(Partition::equals(p1, p2));
EXPECT_TRUE(Partition::equals(p2, p3));
EXPECT_TRUE(Partition::equals(p1, p3));
evalP1.apply(PartitionOp::Assign(Element(0), Element(1)));
evalP2.apply(PartitionOp::Assign(Element(1), Element(0)));
evalP3.apply(PartitionOp::Assign(Element(2), Element(1)));
// expected: p1: ((0 1) (Element(2)) (Element(3))), p2: ((0 1) (Element(2))
// (Element(3))), p3: ((Element(0)) (1 2) (Element(3)))
EXPECT_TRUE(Partition::equals(p1, p2));
EXPECT_FALSE(Partition::equals(p2, p3));
EXPECT_FALSE(Partition::equals(p1, p3));
evalP1.apply(PartitionOp::Assign(Element(2), Element(0)));
evalP2.apply(PartitionOp::Assign(Element(2), Element(1)));
evalP3.apply(PartitionOp::Assign(Element(0), Element(2)));
// expected: p1: ((0 1 2) (Element(3))), p2: ((0 1 2) (Element(3))), p3: ((0 1
// 2) (Element(3)))
EXPECT_TRUE(Partition::equals(p1, p2));
EXPECT_TRUE(Partition::equals(p2, p3));
EXPECT_TRUE(Partition::equals(p1, p3));
evalP1.apply(PartitionOp::Assign(Element(0), Element(3)));
evalP2.apply(PartitionOp::Assign(Element(1), Element(3)));
evalP3.apply(PartitionOp::Assign(Element(2), Element(3)));
// expected: p1: ((1 2) (0 3)), p2: ((0 2) (1 3)), p3: ((0 1) (2 3))
EXPECT_FALSE(Partition::equals(p1, p2));
EXPECT_FALSE(Partition::equals(p2, p3));
EXPECT_FALSE(Partition::equals(p1, p3));
evalP1.apply(PartitionOp::Assign(Element(1), Element(0)));
evalP2.apply(PartitionOp::Assign(Element(2), Element(1)));
evalP3.apply(PartitionOp::Assign(Element(0), Element(2)));
// expected: p1: ((Element(2)) (0 1 3)), p2: ((Element(0)) (1 2 3)), p3:
// ((Element(1)) (0 2 3))
EXPECT_FALSE(Partition::equals(p1, p2));
EXPECT_FALSE(Partition::equals(p2, p3));
EXPECT_FALSE(Partition::equals(p1, p3));
evalP1.apply(PartitionOp::Assign(Element(2), Element(3)));
evalP2.apply(PartitionOp::Assign(Element(0), Element(3)));
evalP3.apply(PartitionOp::Assign(Element(1), Element(3)));
// expected: p1: ((0 1 2 3)), p2: ((0 1 2 3)), p3: ((0 1 2 3))
EXPECT_TRUE(Partition::equals(p1, p2));
EXPECT_TRUE(Partition::equals(p2, p3));
EXPECT_TRUE(Partition::equals(p1, p3));
}
// This test tests that consumption consumes entire regions as expected
TEST(PartitionUtilsTest, TestConsumeAndRequire) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
Partition p(historyFactory.get());
{
MockedPartitionOpEvaluator eval(p, factory, transferringOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(0)),
PartitionOp::AssignFresh(Element(1)),
PartitionOp::AssignFresh(Element(2)),
PartitionOp::AssignFresh(Element(3)),
PartitionOp::AssignFresh(Element(4)),
PartitionOp::AssignFresh(Element(5)),
PartitionOp::AssignFresh(Element(6)),
PartitionOp::AssignFresh(Element(7)),
PartitionOp::AssignFresh(Element(8)),
PartitionOp::AssignFresh(Element(9)),
PartitionOp::AssignFresh(Element(10)),
PartitionOp::AssignFresh(Element(11)),
PartitionOp::Assign(Element(1), Element(0)),
PartitionOp::Assign(Element(2), Element(1)),
PartitionOp::Assign(Element(4), Element(3)),
PartitionOp::Assign(Element(5), Element(4)),
PartitionOp::Assign(Element(7), Element(6)),
PartitionOp::Assign(Element(9), Element(8)),
// expected: p: ((0 1 2) (3 4 5) (6 7) (8 9) (Element(10))
// (Element(11)))
PartitionOp::Send(Element(2), operandSingletons[0]),
PartitionOp::Send(Element(7), operandSingletons[1]),
PartitionOp::Send(Element(10), operandSingletons[2])});
}
// expected: p: ({0 1 2 6 7 10} (3 4 5) (8 9) (Element(11)))
auto never_called = [](const PartitionOp &, unsigned, Operand *) {
EXPECT_TRUE(false);
};
int times_called = 0;
auto increment_times_called = [&](const PartitionOp &, unsigned, Operand *) {
times_called++;
};
{
MockedPartitionOpEvaluatorWithFailureCallback eval(
p, factory, transferringOpToStateMap, increment_times_called);
eval.apply({PartitionOp::Require(Element(0)),
PartitionOp::Require(Element(1)),
PartitionOp::Require(Element(2))});
}
EXPECT_EQ(times_called, 3);
{
MockedPartitionOpEvaluatorWithFailureCallback eval(
p, factory, transferringOpToStateMap, never_called);
eval.apply({PartitionOp::Require(Element(3)),
PartitionOp::Require(Element(4)),
PartitionOp::Require(Element(5))});
}
{
MockedPartitionOpEvaluatorWithFailureCallback eval(
p, factory, transferringOpToStateMap, increment_times_called);
eval.apply(
{PartitionOp::Require(Element(6)), PartitionOp::Require(Element(7))});
}
{
MockedPartitionOpEvaluatorWithFailureCallback eval(
p, factory, transferringOpToStateMap, never_called);
eval.apply(
{PartitionOp::Require(Element(8)), PartitionOp::Require(Element(9))});
}
{
MockedPartitionOpEvaluatorWithFailureCallback eval(
p, factory, transferringOpToStateMap, increment_times_called);
eval.apply(PartitionOp::Require(Element(10)));
}
{
MockedPartitionOpEvaluatorWithFailureCallback eval(
p, factory, transferringOpToStateMap, never_called);
eval.apply(PartitionOp::Require(Element(11)));
}
EXPECT_EQ(times_called, 6);
}
// This test tests that the copy constructor is usable to create fresh
// copies of partitions
TEST(PartitionUtilsTest, TestCopyConstructor) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
Partition p1(historyFactory.get());
{
MockedPartitionOpEvaluator eval(p1, factory, transferringOpToStateMap);
eval.apply(PartitionOp::AssignFresh(Element(0)));
}
// Make copy.
Partition p2 = p1;
// Change p1 again.
{
MockedPartitionOpEvaluator eval(p1, factory, transferringOpToStateMap);
eval.apply(PartitionOp::Send(Element(0), operandSingletons[0]));
}
{
bool failure = false;
MockedPartitionOpEvaluatorWithFailureCallback eval(
p1, factory, transferringOpToStateMap,
[&](const PartitionOp &, unsigned, Operand *) { failure = true; });
eval.apply(PartitionOp::Require(Element(0)));
EXPECT_TRUE(failure);
}
{
MockedPartitionOpEvaluatorWithFailureCallback eval(
p2, factory, transferringOpToStateMap,
[](const PartitionOp &, unsigned, Operand *) { EXPECT_TRUE(false); });
eval.apply(PartitionOp::Require(Element(0)));
}
}
TEST(PartitionUtilsTest, TestUndoTransfer) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
Partition p(historyFactory.get());
MockedPartitionOpEvaluatorWithFailureCallback eval(
p, factory, transferringOpToStateMap,
[&](const PartitionOp &, unsigned, Operand *) { EXPECT_TRUE(false); });
// Shouldn't error on this.
eval.apply({PartitionOp::AssignFresh(Element(0)),
PartitionOp::Send(Element(0), operandSingletons[0]),
PartitionOp::UndoSend(Element(0), instSingletons[0]),
PartitionOp::Require(Element(0), instSingletons[0])});
}
TEST(PartitionUtilsTest, TestLastEltInTransferredRegion) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
// First make sure that we do this correctly with an assign fresh.
Partition p(historyFactory.get());
{
MockedPartitionOpEvaluator eval(p, factory, transferringOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(0)),
PartitionOp::AssignFresh(Element(1)),
PartitionOp::AssignFresh(Element(2)),
PartitionOp::Send(Element(0), operandSingletons[0]),
PartitionOp::AssignFresh(Element(0))});
}
p.validateRegionToSendingOpMapRegions();
// Now make sure that we do this correctly with assign.
Partition p2(historyFactory.get());
{
MockedPartitionOpEvaluator eval(p2, factory, transferringOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(0)),
PartitionOp::AssignFresh(Element(1)),
PartitionOp::AssignFresh(Element(2)),
PartitionOp::Send(Element(0), operandSingletons[0]),
PartitionOp::Assign(Element(0), Element(2))});
}
p2.validateRegionToSendingOpMapRegions();
}
TEST(PartitionUtilsTest, TestHistory_CreateVariable) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SmallVector<IsolationHistory, 8> joinedHistories;
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
// First make sure that we do this correctly with an assign fresh.
Partition p(historyFactory.get());
{
MockedPartitionOpEvaluator eval(p, factory, transferringOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(0)),
PartitionOp::AssignFresh(Element(1))});
}
Partition pSnapshot = p;
{
MockedPartitionOpEvaluator eval(p, factory, transferringOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(2))});
}
p.popHistory(joinedHistories);
EXPECT_TRUE(Partition::equals(p, pSnapshot));
EXPECT_TRUE(joinedHistories.empty());
}
TEST(PartitionUtilsTest, TestHistory_AssignRegion) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
SmallVector<IsolationHistory, 8> joinedHistories;
// First make sure that we do this correctly with an assign fresh.
Partition p(historyFactory.get());
{
MockedPartitionOpEvaluator eval(p, factory, transferringOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(0)),
PartitionOp::AssignFresh(Element(1)),
PartitionOp::AssignFresh(Element(2))});
}
Partition pSnapshot = p;
{
MockedPartitionOpEvaluator eval(p, factory, transferringOpToStateMap);
eval.apply({PartitionOp::Assign(Element(1), Element(2))});
}
Partition pSnapshot2 = p;
{
MockedPartitionOpEvaluator eval(p, factory, transferringOpToStateMap);
eval.apply({PartitionOp::Assign(Element(0), Element(2))});
}
p.popHistory(joinedHistories);
EXPECT_TRUE(Partition::equals(p, pSnapshot2));
EXPECT_TRUE(joinedHistories.empty());
p.popHistory(joinedHistories);
EXPECT_TRUE(Partition::equals(p, pSnapshot));
EXPECT_TRUE(joinedHistories.empty());
}
TEST(PartitionUtilsTest, TestHistory_BuildNewRegionRepIsMergee) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
SmallVector<IsolationHistory, 8> joinedHistories;
Partition p(historyFactory.get());
{
MockedPartitionOpEvaluator eval(p, factory, transferringOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(2)),
PartitionOp::AssignFresh(Element(3)),
PartitionOp::AssignFresh(Element(10)),
PartitionOp::AssignFresh(Element(0)),
PartitionOp::Assign(Element(3), Element(2)),
PartitionOp::Assign(Element(10), Element(2)),
PartitionOp::Merge(Element(2), Element(0))});
}
Partition pSnapshot = p;
{
MockedPartitionOpEvaluator eval(p, factory, transferringOpToStateMap);
eval.apply({PartitionOp::Assign(Element(1), Element(2))});
}
Partition pSnapshot2 = p;
{
MockedPartitionOpEvaluator eval(p, factory, transferringOpToStateMap);
eval.apply({PartitionOp::Assign(Element(0), Element(2))});
}
// Even though we pushed a new instruction, nothing changed in our region.
EXPECT_TRUE(Partition::equals(p, pSnapshot2));
// We pop but nothing changes since we did not need to change anything.
p.popHistory(joinedHistories);
EXPECT_TRUE(Partition::equals(p, pSnapshot2));
EXPECT_TRUE(joinedHistories.empty());
// We pop a last time to return to our original value.
p.popHistory(joinedHistories);
EXPECT_TRUE(Partition::equals(p, pSnapshot));
EXPECT_TRUE(joinedHistories.empty());
}
TEST(PartitionUtilsTest, TestHistory_ReturnFalseWhenNoneLeft) {
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SmallVector<IsolationHistory, 8> joinedHistories;
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
Partition p(historyFactory.get());
EXPECT_FALSE(p.popHistory(joinedHistories));
EXPECT_TRUE(joinedHistories.empty());
{
MockedPartitionOpEvaluator eval(p, factory, transferringOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(2)),
PartitionOp::AssignFresh(Element(3))});
}
EXPECT_TRUE(p.popHistory(joinedHistories));
EXPECT_TRUE(joinedHistories.empty());
EXPECT_FALSE(p.popHistory(joinedHistories));
EXPECT_TRUE(joinedHistories.empty());
}
TEST(PartitionUtilsTest, TestHistory_JoiningTwoEmpty) {
// Make sure that we do sane things when we join empty history.
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SmallVector<IsolationHistory, 8> joinedHistories;
Partition p1(historyFactory.get());
Partition p2(historyFactory.get());
auto result = Partition::join(p1, p2);
EXPECT_TRUE(result.begin() == result.end());
EXPECT_FALSE(result.hasHistory());
}
TEST(PartitionUtilsTest, TestHistory_JoiningNotEmptyAndEmpty) {
// Make sure that we do sane things when we join empty history.
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SmallVector<IsolationHistory, 8> joinedHistories;
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
Partition p1(historyFactory.get());
Partition p2(historyFactory.get());
{
MockedPartitionOpEvaluator eval(p1, factory, transferringOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(2))});
}
EXPECT_TRUE(p1.historySize() == 2);
EXPECT_TRUE(p2.historySize() == 0);
auto result = Partition::join(p1, p2);
EXPECT_TRUE(std::next(result.begin()) == result.end());
// Since p2 doesn't have any history, we do not actually perform any join and
// thus do not insert a CFGHistory change.
EXPECT_TRUE(result.historySize() == 2);
}
TEST(PartitionUtilsTest, TestHistory_JoiningEmptyAndNotEmpty) {
// Make sure that we do sane things when we join empty history.
llvm::BumpPtrAllocator allocator;
Partition::SendingOperandSetFactory factory(allocator);
IsolationHistory::Factory historyFactory(allocator);
SendingOperandToStateMap transferringOpToStateMap(historyFactory);
SmallVector<IsolationHistory, 8> joinedHistories;
Partition p1(historyFactory.get());
Partition p2(historyFactory.get());
{
MockedPartitionOpEvaluator eval(p1, factory, transferringOpToStateMap);
eval.apply({PartitionOp::AssignFresh(Element(2))});
}
EXPECT_TRUE(p1.historySize() == 2);
EXPECT_TRUE(p2.historySize() == 0);
auto result = Partition::join(p1, p2);
EXPECT_TRUE(std::next(result.begin()) == result.end());
// Since p2 doesn't have any history, we do not actually perform any join and
// thus do not insert a CFGHistory change.
EXPECT_TRUE(result.historySize() == 2);
}