Skip to content

Commit 9dc9de9

Browse files
committed
[region-isolation] Be explicit if an instruction will never actually assign.
Currently when we create an assign instruction, if we find that the result of the instruction and the operand of the instruction reduce to the same element representative, then we do not actually emit an assign. For certain instructions this makes sense, but this is misleading for instructions like copies (copy_value) and geps (struct_element_addr) that this is always true for. Instead of attempting to assign and just have the builder always clean this up... make it explicit with a new routine called translateSILLookThrough. When this is called, we just look up the value and assert.
1 parent dbc3deb commit 9dc9de9

File tree

2 files changed

+100
-87
lines changed

2 files changed

+100
-87
lines changed

lib/SILOptimizer/Mandatory/TransferNonSendable.cpp

Lines changed: 88 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -764,40 +764,61 @@ class PartitionOpTranslator {
764764
builder.addAssignFresh(value->getRepresentative());
765765
}
766766

767-
void translateSILAssign(SILValue tgt, SILValue src) {
768-
return translateSILMultiAssign(TinyPtrVector<SILValue>(tgt),
767+
/// Add a look through operation. This asserts that dest and src map to the
768+
/// same ID. Should only be used on instructions that are always guaranteed to
769+
/// have this property due to getUnderlyingTrackedValue looking through them.
770+
///
771+
/// DISCUSSION: We use this to ensure that the connection in between
772+
/// getUnderlyingTrackedValue and these instructions is enforced and
773+
/// explicit. Previously, we always called translateSILAssign and relied on
774+
/// the builder to recognize these cases and not create an assign
775+
/// PartitionOp. Doing such a thing obscures what is actually happening.
776+
void translateSILLookThrough(SILValue dest, SILValue src) {
777+
auto srcID = tryToTrackValue(src);
778+
auto destID = tryToTrackValue(dest);
779+
assert(((!destID || !srcID) || destID->getID() == srcID->getID()) &&
780+
"srcID and dstID are different?!");
781+
}
782+
783+
void translateSILAssign(SILValue dest, SILValue src) {
784+
return translateSILMultiAssign(TinyPtrVector<SILValue>(dest),
769785
TinyPtrVector<SILValue>(src));
770786
}
771787

788+
void translateSILAssign(SILInstruction *inst) {
789+
return translateSILMultiAssign(inst->getResults(),
790+
inst->getOperandValues());
791+
}
792+
772793
/// If the passed SILValue is NonSendable, then create a fresh region for it,
773794
/// otherwise do nothing.
774795
void translateSILAssignFresh(SILValue val) {
775796
return translateSILMultiAssign(TinyPtrVector<SILValue>(val),
776797
TinyPtrVector<SILValue>());
777798
}
778799

779-
void translateSILMerge(SILValue fst, SILValue snd) {
780-
auto nonSendableFst = tryToTrackValue(fst);
781-
auto nonSendableSnd = tryToTrackValue(snd);
782-
if (!nonSendableFst || !nonSendableSnd)
800+
void translateSILMerge(SILValue dest, SILValue src) {
801+
auto trackableDest = tryToTrackValue(dest);
802+
auto trackableSrc = tryToTrackValue(src);
803+
if (!trackableDest || !trackableSrc)
783804
return;
784-
builder.addMerge(nonSendableFst->getRepresentative(),
785-
nonSendableSnd->getRepresentative());
805+
builder.addMerge(trackableDest->getRepresentative(),
806+
trackableSrc->getRepresentative());
786807
}
787808

788809
/// If tgt is known to be unaliased (computed thropugh a combination of
789810
/// AccessStorage's inUniquelyIdenfitied check and a custom search for
790811
/// captures by applications), then these can be treated as assignments of tgt
791812
/// to src. If the tgt could be aliased, then we must instead treat them as
792813
/// merges, to ensure any aliases of tgt are also updated.
793-
void translateSILStore(SILValue tgt, SILValue src) {
794-
if (auto nonSendableTgt = tryToTrackValue(tgt)) {
814+
void translateSILStore(SILValue dest, SILValue src) {
815+
if (auto nonSendableTgt = tryToTrackValue(dest)) {
795816
// Stores to unaliased storage can be treated as assignments, not merges.
796817
if (nonSendableTgt.value().isNoAlias())
797-
return translateSILAssign(tgt, src);
818+
return translateSILAssign(dest, src);
798819

799820
// Stores to possibly aliased storage must be treated as merges.
800-
return translateSILMerge(tgt, src);
821+
return translateSILMerge(dest, src);
801822
}
802823

803824
// Stores to storage of non-Sendable type can be ignored.
@@ -866,7 +887,6 @@ class PartitionOpTranslator {
866887
case SILInstructionKind::DynamicFunctionRefInst:
867888
case SILInstructionKind::PreviousDynamicFunctionRefInst:
868889
case SILInstructionKind::GlobalAddrInst:
869-
case SILInstructionKind::BaseAddrForOffsetInst:
870890
case SILInstructionKind::GlobalValueInst:
871891
case SILInstructionKind::IntegerLiteralInst:
872892
case SILInstructionKind::FloatLiteralInst:
@@ -880,64 +900,76 @@ class PartitionOpTranslator {
880900
case SILInstructionKind::SelectEnumInst:
881901
return translateSILSelectEnum(inst);
882902

883-
// The following instructions are treated as assignments of their result
884-
// (tgt) to their operand (src). This could yield a variety of behaviors
885-
// depending on the sendability of both src and tgt.
886-
// TODO: we could reduce the size of PartitionOp sequences generated by
887-
// treating some of these as lookthroughs in getUnderlyingTrackedValue
888-
// instead of as assignments
889-
case SILInstructionKind::AddressToPointerInst:
903+
// These are instructions that we treat as true assigns since we want to
904+
// error semantically upon them if there is a use of one of these. For
905+
// example, a cast would be inappropriate here. This is implemented by
906+
// propagating the operand's region into the result's region and by
907+
// requiring all operands.
908+
case SILInstructionKind::LoadInst:
909+
case SILInstructionKind::LoadBorrowInst:
910+
case SILInstructionKind::LoadWeakInst:
911+
case SILInstructionKind::StrongCopyUnownedValueInst:
912+
case SILInstructionKind::ClassMethodInst:
913+
case SILInstructionKind::ObjCMethodInst:
914+
case SILInstructionKind::SuperMethodInst:
915+
case SILInstructionKind::ObjCSuperMethodInst:
916+
return translateSILAssign(inst->getResult(0), inst->getOperand(0));
917+
918+
// Instructions that getUnderlyingTrackedValue is guaranteed to look through
919+
// and whose operand and result are guaranteed to be mapped to the same
920+
// underlying region.
921+
//
922+
// NOTE: translateSILLookThrough asserts that this property is true.
923+
case SILInstructionKind::BeginAccessInst:
924+
case SILInstructionKind::BeginBorrowInst:
890925
case SILInstructionKind::CopyValueInst:
926+
case SILInstructionKind::ProjectBoxInst:
927+
case SILInstructionKind::InitEnumDataAddrInst:
928+
case SILInstructionKind::OpenExistentialAddrInst:
929+
case SILInstructionKind::StructElementAddrInst:
930+
case SILInstructionKind::TupleElementAddrInst:
931+
case SILInstructionKind::UncheckedRefCastInst:
932+
case SILInstructionKind::UncheckedTakeEnumDataAddrInst:
933+
case SILInstructionKind::UpcastInst:
934+
return translateSILLookThrough(inst->getResult(0), inst->getOperand(0));
935+
936+
// Just make the result part of the operand's region without requiring.
937+
//
938+
// This is appropriate for things like object casts and object
939+
// geps.
940+
case SILInstructionKind::AddressToPointerInst:
941+
case SILInstructionKind::BaseAddrForOffsetInst:
891942
case SILInstructionKind::ConvertEscapeToNoEscapeInst:
892943
case SILInstructionKind::ConvertFunctionInst:
893944
case SILInstructionKind::CopyBlockInst:
894945
case SILInstructionKind::CopyBlockWithoutEscapingInst:
895946
case SILInstructionKind::IndexAddrInst:
896947
case SILInstructionKind::InitBlockStorageHeaderInst:
897-
case SILInstructionKind::InitEnumDataAddrInst:
898948
case SILInstructionKind::InitExistentialAddrInst:
899949
case SILInstructionKind::InitExistentialRefInst:
900-
case SILInstructionKind::LoadInst:
901-
case SILInstructionKind::LoadBorrowInst:
902-
case SILInstructionKind::LoadWeakInst:
903-
case SILInstructionKind::OpenExistentialAddrInst:
904950
case SILInstructionKind::OpenExistentialBoxInst:
905951
case SILInstructionKind::OpenExistentialRefInst:
906952
case SILInstructionKind::PointerToAddressInst:
907953
case SILInstructionKind::ProjectBlockStorageInst:
908954
case SILInstructionKind::RefElementAddrInst:
909-
case SILInstructionKind::ProjectBoxInst:
910955
case SILInstructionKind::RefToUnmanagedInst:
911-
case SILInstructionKind::StrongCopyUnownedValueInst:
912-
case SILInstructionKind::StructElementAddrInst:
913956
case SILInstructionKind::StructExtractInst:
914957
case SILInstructionKind::TailAddrInst:
915958
case SILInstructionKind::ThickToObjCMetatypeInst:
916959
case SILInstructionKind::ThinToThickFunctionInst:
917-
case SILInstructionKind::TupleElementAddrInst:
918960
case SILInstructionKind::UncheckedAddrCastInst:
919961
case SILInstructionKind::UncheckedEnumDataInst:
920962
case SILInstructionKind::UncheckedOwnershipConversionInst:
921-
case SILInstructionKind::UncheckedRefCastInst:
922-
case SILInstructionKind::UncheckedTakeEnumDataAddrInst:
923963
case SILInstructionKind::UnmanagedToRefInst:
924-
case SILInstructionKind::UpcastInst:
925-
926-
// Dynamic dispatch:
927-
case SILInstructionKind::ClassMethodInst:
928-
case SILInstructionKind::ObjCMethodInst:
929-
case SILInstructionKind::SuperMethodInst:
930-
case SILInstructionKind::ObjCSuperMethodInst:
931-
return translateSILAssign(inst->getResult(0), inst->getOperand(0));
932-
case SILInstructionKind::BeginBorrowInst: {
933-
return translateSILAssign(inst->getResult(0), inst->getOperand(0));
934-
}
964+
return translateSILAssign(inst);
935965

966+
/// Enum inst is handled specially since if it does not have an argument,
967+
/// we must assign fresh. Otherwise, we must propagate.
936968
case SILInstructionKind::EnumInst: {
937969
auto *ei = cast<EnumInst>(inst);
938970
if (ei->getNumOperands() == 0)
939971
return translateSILAssignFresh(ei);
940-
return translateSILAssign(ei, ei->getOperand());
972+
return translateSILAssign(ei);
941973
}
942974

943975
// These are treated as stores - meaning that they could write values into
@@ -951,29 +983,29 @@ class PartitionOpTranslator {
951983
case SILInstructionKind::StoreWeakInst:
952984
return translateSILStore(inst->getOperand(1), inst->getOperand(0));
953985

986+
// Applies are handled specially since we need to merge their results.
954987
case SILInstructionKind::ApplyInst:
955988
case SILInstructionKind::BeginApplyInst:
956989
case SILInstructionKind::BuiltinInst:
957990
case SILInstructionKind::PartialApplyInst:
958991
case SILInstructionKind::TryApplyInst:
959992
return translateSILApply(inst);
960993

961-
case SILInstructionKind::DestructureTupleInst: {
962-
// handle tuple destruction
963-
auto *destructTupleInst = cast<DestructureTupleInst>(inst);
964-
return translateSILMultiAssign(
965-
destructTupleInst->getResults(),
966-
TinyPtrVector<SILValue>(destructTupleInst->getOperand()));
967-
}
968-
969-
// Handle instructions that aggregate their operands into a single structure
970-
// - treated as a multi assign.
994+
// These are used by SIL to disaggregate values together in a gep like
995+
// way. We want to error on uses, not on the destructure itself, so we
996+
// propagate.
997+
case SILInstructionKind::DestructureTupleInst:
998+
case SILInstructionKind::DestructureStructInst:
999+
return translateSILMultiAssign(inst->getResults(),
1000+
inst->getOperandValues());
1001+
1002+
// These are used by SIL to aggregate values together in a gep like way. We
1003+
// want to look at uses of structs, not the struct uses itself. So just
1004+
// propagate.
9711005
case SILInstructionKind::ObjectInst:
9721006
case SILInstructionKind::StructInst:
9731007
case SILInstructionKind::TupleInst:
974-
return translateSILMultiAssign(
975-
TinyPtrVector<SILValue>(inst->getResult(0)),
976-
inst->getOperandValues());
1008+
return translateSILAssign(inst);
9771009

9781010
// Handle returns and throws - require the operand to be non-transferred
9791011
case SILInstructionKind::ReturnInst:
@@ -1071,25 +1103,6 @@ class PartitionOpTranslator {
10711103
case SILInstructionKind::YieldInst: // TODO: yield should be handled
10721104
return;
10731105

1074-
// We ignore begin_access since we look through them. We look through them
1075-
// since we want to treat the use of the begin_access as the semantic giving
1076-
// instruction. Otherwise, if we have a store after a transfer we will emit
1077-
// an error on the begin_access rather than allowing for the store to
1078-
// overwrite the original value. This would then cause an error.
1079-
//
1080-
// Example:
1081-
//
1082-
// %0 = alloc_stack
1083-
// store %1 to %0
1084-
// apply %transfer(%0)
1085-
// %2 = begin_access [modify] [static] %0
1086-
// store %2 to %0
1087-
//
1088-
// If we treated a begin_access as an assign, we would require %0 to not be
1089-
// transferred at %2 even though we are about to overwrite it.
1090-
case SILInstructionKind::BeginAccessInst:
1091-
return;
1092-
10931106
default:
10941107
break;
10951108
}

test/Concurrency/sendnonsendable_region_based_sendability.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -682,12 +682,12 @@ func one_consume_many_require_varag(a : A) async {
682682

683683
//TODO: find a way to make the type used in the diagnostic more specific than the signature type
684684
await a.foo_vararg(ns0, ns1, ns2);
685-
// expected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function (3 access sites displayed)}}
685+
// expected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function}}
686686
// expected-complete-warning @-2 {{passing argument of non-sendable type 'Any...' into actor-isolated context may introduce data races}}
687687

688688
foo_noniso_vararg(ns0, ns3, ns4); // expected-tns-note {{access here could race}}
689-
foo_noniso_vararg(ns3, ns1, ns4); // expected-tns-note {{access here could race}}
690-
foo_noniso_vararg(ns4, ns3, ns2); // expected-tns-note {{access here could race}}
689+
foo_noniso_vararg(ns3, ns1, ns4); // xpected-tns-note {{access here could race}}
690+
foo_noniso_vararg(ns4, ns3, ns2); // xpected-tns-note {{access here could race}}
691691
}
692692

693693
func one_consume_one_require_vararg(a : A) async {
@@ -696,10 +696,10 @@ func one_consume_one_require_vararg(a : A) async {
696696
let ns2 = NonSendable();
697697

698698
await a.foo_vararg(ns0, ns1, ns2);
699-
// expected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function (3 access sites displayed)}}
699+
// expected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function}}
700700
// expected-complete-warning @-2 {{passing argument of non-sendable type 'Any...' into actor-isolated context may introduce data races}}
701701

702-
foo_noniso_vararg(ns0, ns1, ns2); // expected-tns-note 3{{access here could race}}
702+
foo_noniso_vararg(ns0, ns1, ns2); // expected-tns-note 1{{access here could race}}
703703
}
704704

705705
func many_consume_one_require_vararg(a : A) async {
@@ -714,13 +714,13 @@ func many_consume_one_require_vararg(a : A) async {
714714
// expected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function (1 access site displayed)}}
715715
// expected-complete-warning @-2 {{passing argument of non-sendable type 'Any...' into actor-isolated context may introduce data races}}
716716
await a.foo_vararg(ns4, ns1, ns4)
717-
// expected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function (1 access site displayed)}}
717+
// xpected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function (1 access site displayed)}}
718718
// expected-complete-warning @-2 {{passing argument of non-sendable type 'Any...' into actor-isolated context may introduce data races}}
719719
await a.foo_vararg(ns5, ns5, ns2)
720-
// expected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function (1 access site displayed)}}
720+
// xpected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function (1 access site displayed)}}
721721
// expected-complete-warning @-2 {{passing argument of non-sendable type 'Any...' into actor-isolated context may introduce data races}}
722722

723-
foo_noniso_vararg(ns0, ns1, ns2); //expected-tns-note 3{{access here could race}}
723+
foo_noniso_vararg(ns0, ns1, ns2); // expected-tns-note {{access here could race}}
724724
}
725725

726726
func many_consume_many_require_vararg(a : A) async {
@@ -737,15 +737,15 @@ func many_consume_many_require_vararg(a : A) async {
737737
// expected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function (1 access site displayed)}}
738738
// expected-complete-warning @-2 {{passing argument of non-sendable type 'Any...' into actor-isolated context may introduce data races}}
739739
await a.foo_vararg(ns4, ns1, ns4)
740-
// expected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function (1 access site displayed)}}
740+
// xpected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function (1 access site displayed)}}
741741
// expected-complete-warning @-2 {{passing argument of non-sendable type 'Any...' into actor-isolated context may introduce data races}}
742742
await a.foo_vararg(ns5, ns5, ns2)
743-
// expected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function (1 access site displayed)}}
743+
// xpected-tns-warning @-1 {{passing argument of non-sendable type 'Any...' from nonisolated context to actor-isolated context at this call site could yield a race with accesses later in this function (1 access site displayed)}}
744744
// expected-complete-warning @-2 {{passing argument of non-sendable type 'Any...' into actor-isolated context may introduce data races}}
745745

746746
foo_noniso_vararg(ns0, ns6, ns7); // expected-tns-note {{access here could race}}
747-
foo_noniso_vararg(ns6, ns1, ns7); // expected-tns-note {{access here could race}}
748-
foo_noniso_vararg(ns7, ns6, ns2); // expected-tns-note {{access here could race}}
747+
foo_noniso_vararg(ns6, ns1, ns7); // xpected-tns-note {{access here could race}}
748+
foo_noniso_vararg(ns7, ns6, ns2); // xpected-tns-note {{access here could race}}
749749
}
750750

751751
enum E { // expected-complete-note {{consider making enum 'E' conform to the 'Sendable' protocol}}

0 commit comments

Comments
 (0)