Skip to content

Commit 865b23a

Browse files
authored
Merge pull request #3456 from swiftix/cse-of-open-existentials
[sil-cse] Add CSE support for open_existential_ref
2 parents f8a1e31 + cfdc943 commit 865b23a

File tree

8 files changed

+242
-23
lines changed

8 files changed

+242
-23
lines changed

include/swift/SIL/SILInstruction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ class SILInstruction : public ValueBase,public llvm::ilist_node<SILInstruction>{
178178
/// Return the array of opened archetypes operands for this instruction.
179179
MutableArrayRef<Operand> getOpenedArchetypeOperands();
180180

181+
/// Returns true if a given kind of instruciton may have opened archetype
182+
/// operands.
183+
bool mayHaveOpenedArchetypeOperands() const;
184+
181185
unsigned getNumOperands() const { return getAllOperands().size(); }
182186

183187
unsigned getNumOpenedArchetypeOperands() const {

lib/SIL/SILInstruction.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,10 @@ namespace {
620620
}
621621

622622
bool visitMarkDependenceInst(const MarkDependenceInst *RHS) {
623+
return true;
624+
}
625+
626+
bool visitOpenExistentialRefInst(const OpenExistentialRefInst *RHS) {
623627
return true;
624628
}
625629

@@ -704,6 +708,23 @@ namespace {
704708
return {}; \
705709
return I->getOpenedArchetypeOperands(); \
706710
}
711+
#include "swift/SIL/SILNodes.def"
712+
};
713+
714+
class MayHaveOpenedArchetypeOperandsAccessor
715+
: public SILVisitor<MayHaveOpenedArchetypeOperandsAccessor,
716+
bool> {
717+
public:
718+
#define VALUE(CLASS, PARENT) \
719+
bool visit##CLASS(const CLASS *I) { \
720+
llvm_unreachable("accessing non-instruction " #CLASS); \
721+
}
722+
#define INST(CLASS, PARENT, MEMBEHAVIOR, RELEASINGBEHAVIOR) \
723+
bool visit##CLASS(const CLASS *I) { \
724+
return IMPLEMENTS_METHOD(CLASS, SILInstruction, \
725+
getOpenedArchetypeOperands, \
726+
ArrayRef<Operand>() const); \
727+
}
707728
#include "swift/SIL/SILNodes.def"
708729
};
709730
} // end anonymous namespace
@@ -725,6 +746,11 @@ MutableArrayRef<Operand> SILInstruction::getOpenedArchetypeOperands() {
725746
return OpenedArchetypeOperandsMutableAccessor().visit(this);
726747
}
727748

749+
bool SILInstruction::mayHaveOpenedArchetypeOperands() const {
750+
return MayHaveOpenedArchetypeOperandsAccessor().visit(
751+
const_cast<SILInstruction *>(this));
752+
}
753+
728754
/// getOperandNumber - Return which operand this is in the operand list of the
729755
/// using instruction.
730756
unsigned Operand::getOperandNumber() const {

lib/SILOptimizer/Transforms/CSE.cpp

Lines changed: 132 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -362,12 +362,21 @@ class HashVisitor : public SILInstructionVisitor<HashVisitor, llvm::hash_code> {
362362
Operands.begin(),
363363
Operands.end()));
364364
}
365+
365366
hash_code visitMarkDependenceInst(MarkDependenceInst *X) {
366367
OperandValueArrayRef Operands(X->getAllOperands());
367368
return llvm::hash_combine(
368369
X->getKind(), X->getType(),
369370
llvm::hash_combine_range(Operands.begin(), Operands.end()));
370371
}
372+
373+
hash_code visitOpenExistentialRefInst(OpenExistentialRefInst *X) {
374+
auto ArchetypeTy = cast<ArchetypeType>(X->getType().getSwiftRValueType());
375+
auto ConformsTo = ArchetypeTy->getConformsTo();
376+
return llvm::hash_combine(
377+
X->getKind(), X->getOperand(),
378+
llvm::hash_combine_range(ConformsTo.begin(), ConformsTo.end()));
379+
}
371380
};
372381
} // end anonymous namespace
373382

@@ -381,14 +390,33 @@ bool llvm::DenseMapInfo<SimpleValue>::isEqual(SimpleValue LHS,
381390
if (LHS.isSentinel() || RHS.isSentinel())
382391
return LHSI == RHSI;
383392

393+
if (isa<OpenExistentialRefInst>(LHSI) && isa<OpenExistentialRefInst>(RHSI)) {
394+
if (LHSI->getNumOperands() != RHSI->getNumOperands())
395+
return false;
396+
397+
// Check operands.
398+
for (unsigned i = 0, e = LHSI->getNumOperands(); i != e; ++i)
399+
if (LHSI->getOperand(i) != RHSI->getOperand(i))
400+
return false;
401+
402+
// Consider the types of two open_existential_ref instructions to be equal,
403+
// if the sets of protocols they conform to are equal.
404+
auto LHSArchetypeTy =
405+
cast<ArchetypeType>(LHSI->getType().getSwiftRValueType());
406+
auto LHSConformsTo = LHSArchetypeTy->getConformsTo();
407+
auto RHSArchetypeTy =
408+
cast<ArchetypeType>(RHSI->getType().getSwiftRValueType());
409+
auto RHSConformsTo = RHSArchetypeTy->getConformsTo();
410+
return LHSConformsTo == RHSConformsTo;
411+
}
384412
return LHSI->getKind() == RHSI->getKind() && LHSI->isIdenticalTo(RHSI);
385413
}
386414

387415
//===----------------------------------------------------------------------===//
388416
// CSE Interface
389417
//===----------------------------------------------------------------------===//
390418

391-
namespace {
419+
namespace swift {
392420

393421
/// CSE - This pass does a simple depth-first walk over the dominator tree,
394422
/// eliminating trivially redundant instructions and using simplifyInstruction
@@ -476,6 +504,8 @@ class CSE {
476504
};
477505

478506
bool processNode(DominanceInfoNode *Node);
507+
bool processOpenExistentialRef(SILInstruction *Inst, ValueBase *V,
508+
SILBasicBlock::iterator &I);
479509
};
480510
} // end anonymous namespace
481511

@@ -525,6 +555,95 @@ bool CSE::processFunction(SILFunction &Fm, DominanceInfo *DT) {
525555
return Changed;
526556
}
527557

558+
namespace {
559+
// A very simple cloner for cloning instructions inside
560+
// the same function. The only interesting thing it does
561+
// is remapping the archetypes when it is required.
562+
class InstructionCloner : public SILCloner<InstructionCloner> {
563+
friend class SILCloner<InstructionCloner>;
564+
friend class SILVisitor<InstructionCloner>;
565+
SILInstruction *Result = nullptr;
566+
public:
567+
InstructionCloner(SILFunction *F) : SILCloner(*F) {}
568+
569+
static SILInstruction *doIt(SILInstruction *I) {
570+
InstructionCloner TC(I->getFunction());
571+
return TC.clone(I);
572+
}
573+
574+
SILInstruction *clone(SILInstruction *I) {
575+
visit(I);
576+
return Result;
577+
}
578+
579+
void postProcess(SILInstruction *Orig, SILInstruction *Cloned) {
580+
assert(Orig->getFunction() == &getBuilder().getFunction() &&
581+
"cloning between functions is not supported");
582+
583+
Result = Cloned;
584+
SILCloner<InstructionCloner>::postProcess(Orig, Cloned);
585+
}
586+
SILValue remapValue(SILValue Value) {
587+
return Value;
588+
}
589+
SILBasicBlock *remapBasicBlock(SILBasicBlock *BB) { return BB; }
590+
};
591+
}
592+
593+
/// Handle CSE of open_existential_ref instructions.
594+
/// Returns true if uses of open_existential_ref can
595+
/// be replaced by a dominating instruction.
596+
/// \Inst is the open_existential_ref instruction
597+
/// \V is the dominating open_existential_ref instruction
598+
/// \I is the iterator referring to the current instruction.
599+
bool CSE::processOpenExistentialRef(SILInstruction *Inst, ValueBase *V,
600+
SILBasicBlock::iterator &I) {
601+
assert(isa<OpenExistentialRefInst>(Inst));
602+
llvm::SmallSetVector<SILInstruction *, 16> Candidates;
603+
// Collect all candidates that may contain opened archetypes
604+
// that need to be replaced.
605+
for (auto Use : Inst->getUses()) {
606+
auto User = Use->getUser();
607+
if (User->mayHaveOpenedArchetypeOperands()) {
608+
if (canHandle(User)) {
609+
auto It = AvailableValues->begin(User);
610+
if (It != AvailableValues->end()) {
611+
return false;
612+
}
613+
}
614+
Candidates.insert(User);
615+
}
616+
}
617+
// Now process candidates.
618+
auto OldOpenedArchetype = getOpenedArchetypeOf(Inst);
619+
auto NewOpenedArchetype = getOpenedArchetypeOf(dyn_cast<SILInstruction>(V));
620+
// TODO: Move it to CSE instance to avoid recreating it every time?
621+
SILOpenedArchetypesTracker OpenedArchetypesTracker(*Inst->getFunction());
622+
// Register the new archetype to be used.
623+
OpenedArchetypesTracker.registerOpenedArchetypes(dyn_cast<SILInstruction>(V));
624+
// Use a cloner. It makes copying the instruction and remaping of
625+
// opened archetypes trivial.
626+
InstructionCloner Cloner(I->getFunction());
627+
Cloner.registerOpenedExistentialRemapping(
628+
OldOpenedArchetype->castTo<ArchetypeType>(), NewOpenedArchetype);
629+
auto &Builder = Cloner.getBuilder();
630+
Builder.setOpenedArchetypesTracker(&OpenedArchetypesTracker);
631+
632+
// Now clone each candidate and replace the opened archetype
633+
// by a dominating one.
634+
for (auto Candidate : Candidates) {
635+
Builder.getOpenedArchetypes().addOpenedArchetypeOperands(
636+
Candidate->getOpenedArchetypeOperands());
637+
Builder.setInsertionPoint(Candidate);
638+
auto NewI = Cloner.clone(Candidate);
639+
Candidate->replaceAllUsesWith(NewI);
640+
if (I == Candidate->getIterator())
641+
I = NewI->getIterator();
642+
eraseFromParentWithDebugInsts(Candidate, I);
643+
}
644+
return true;
645+
}
646+
528647
bool CSE::processNode(DominanceInfoNode *Node) {
529648
SILBasicBlock *BB = Node->getBlock();
530649
bool Changed = false;
@@ -572,11 +691,17 @@ bool CSE::processNode(DominanceInfoNode *Node) {
572691
// instruction has an available value. If so, use it.
573692
if (ValueBase *V = AvailableValues->lookup(Inst)) {
574693
DEBUG(llvm::dbgs() << "SILCSE CSE: " << *Inst << " to: " << *V << '\n');
575-
Inst->replaceAllUsesWith(V);
576-
Inst->eraseFromParent();
577-
Changed = true;
578-
++NumCSE;
579-
continue;
694+
// Instructions producing a new opened archetype need a special handling,
695+
// because replacing these intructions may require a replacement
696+
// of the opened archetype type operands in some of the uses.
697+
if (!isa<OpenExistentialRefInst>(Inst) ||
698+
processOpenExistentialRef(Inst, V, I)) {
699+
Inst->replaceAllUsesWith(V);
700+
Inst->eraseFromParent();
701+
Changed = true;
702+
++NumCSE;
703+
continue;
704+
}
580705
}
581706

582707
// Otherwise, just remember that this value is available.
@@ -687,6 +812,7 @@ bool CSE::canHandle(SILInstruction *Inst) {
687812
case ValueKind::ThinFunctionToPointerInst:
688813
case ValueKind::PointerToThinFunctionInst:
689814
case ValueKind::MarkDependenceInst:
815+
case ValueKind::OpenExistentialRefInst:
690816
return true;
691817
default:
692818
return false;

lib/Serialization/SerializeSIL.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
#include "Serialization.h"
1616
#include "swift/Strings.h"
1717
#include "swift/AST/Module.h"
18+
#include "swift/SIL/CFG.h"
1819
#include "swift/SIL/SILArgument.h"
1920
#include "swift/SIL/SILModule.h"
2021
#include "swift/SIL/SILUndef.h"
2122
#include "swift/SILOptimizer/Utils/Generics.h"
2223

2324
#include "llvm/ADT/MapVector.h"
25+
#include "llvm/ADT/PostOrderIterator.h"
2426
#include "llvm/ADT/SmallString.h"
2527
#include "llvm/ADT/SmallVector.h"
2628
#include "llvm/ADT/StringExtras.h"
@@ -390,7 +392,10 @@ void SILSerializer::writeSILFunction(const SILFunction &F, bool DeclOnly) {
390392
// Assign a value ID to each SILInstruction that has value and to each basic
391393
// block argument.
392394
unsigned ValueID = 0;
393-
for (const SILBasicBlock &BB : F) {
395+
llvm::ReversePostOrderTraversal<SILFunction *> RPOT(
396+
const_cast<SILFunction *>(&F));
397+
for (auto Iter = RPOT.begin(), E = RPOT.end(); Iter != E; ++Iter) {
398+
auto &BB = **Iter;
394399
BasicBlockMap.insert(std::make_pair(&BB, BasicID++));
395400

396401
for (auto I = BB.bbarg_begin(), E = BB.bbarg_end(); I != E; ++I)
@@ -401,8 +406,17 @@ void SILSerializer::writeSILFunction(const SILFunction &F, bool DeclOnly) {
401406
ValueIDs[&SI] = ++ValueID;
402407
}
403408

404-
for (const SILBasicBlock &BB : F)
405-
writeSILBasicBlock(BB);
409+
// Write SIL basic blocks in the RPOT order
410+
// to make sure that instructions defining open archetypes
411+
// are serialized before instructions using those opened
412+
// archetypes.
413+
unsigned SerializedBBNum = 0;
414+
for (auto Iter = RPOT.begin(), E = RPOT.end(); Iter != E; ++Iter) {
415+
auto *BB = *Iter;
416+
writeSILBasicBlock(*BB);
417+
SerializedBBNum++;
418+
}
419+
assert(BasicID == SerializedBBNum && "Wrong number of BBs was serialized");
406420
}
407421

408422
void SILSerializer::writeSILBasicBlock(const SILBasicBlock &BB) {

test/ClangModules/serialization-sil.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,36 @@ public func testPartialApply(_ obj: Test) {
1010
// CHECK-LABEL: @_TF4Test16testPartialApplyFPSo4Test_T_ : $@convention(thin) (@owned Test) -> () {
1111
if let curried1 = obj.normalObject {
1212
// CHECK: dynamic_method_br [[CURRIED1_OBJ:%.+]] : $@opened([[CURRIED1_EXISTENTIAL:.+]]) Test, #Test.normalObject!1.foreign, [[CURRIED1_TRUE:[^,]+]], [[CURRIED1_FALSE:[^,]+]]
13+
// CHECK: [[CURRIED1_FALSE]]:
1314
// CHECK: [[CURRIED1_TRUE]]([[CURRIED1_METHOD:%.+]] : $@convention(objc_method) (@opened([[CURRIED1_EXISTENTIAL]]) Test) -> @autoreleased AnyObject):
1415
// CHECK: [[CURRIED1_PARTIAL:%.+]] = partial_apply [[CURRIED1_METHOD]]([[CURRIED1_OBJ]]) : $@convention(objc_method) (@opened([[CURRIED1_EXISTENTIAL]]) Test) -> @autoreleased AnyObject
1516
// CHECK: [[CURRIED1_THUNK:%.+]] = function_ref @_TTRXFo__oPs9AnyObject__XFo_iT__iPS___ : $@convention(thin) (@in (), @owned @callee_owned () -> @owned AnyObject) -> @out AnyObject
1617
// CHECK: = partial_apply [[CURRIED1_THUNK]]([[CURRIED1_PARTIAL]]) : $@convention(thin) (@in (), @owned @callee_owned () -> @owned AnyObject) -> @out AnyObject
17-
// CHECK: [[CURRIED1_FALSE]]:
1818
curried1()
1919
}
2020
if let curried2 = obj.innerPointer {
2121
// CHECK: dynamic_method_br [[CURRIED2_OBJ:%.+]] : $@opened([[CURRIED2_EXISTENTIAL:.+]]) Test, #Test.innerPointer!1.foreign, [[CURRIED2_TRUE:[^,]+]], [[CURRIED2_FALSE:[^,]+]]
22+
// CHECK: [[CURRIED2_FALSE]]:
2223
// CHECK: [[CURRIED2_TRUE]]([[CURRIED2_METHOD:%.+]] : $@convention(objc_method) (@opened([[CURRIED2_EXISTENTIAL]]) Test) -> @unowned_inner_pointer UnsafeMutablePointer<()>):
2324
// CHECK: [[CURRIED2_PARTIAL:%.+]] = partial_apply [[CURRIED2_METHOD]]([[CURRIED2_OBJ]]) : $@convention(objc_method) (@opened([[CURRIED2_EXISTENTIAL]]) Test) -> @unowned_inner_pointer UnsafeMutablePointer<()>
2425
// CHECK: [[CURRIED2_THUNK:%.+]] = function_ref @_TTRXFo__dGSpT___XFo_iT__iGSpT___ : $@convention(thin) (@in (), @owned @callee_owned () -> UnsafeMutablePointer<()>) -> @out UnsafeMutablePointer<()>
2526
// CHECK: = partial_apply [[CURRIED2_THUNK]]([[CURRIED2_PARTIAL]]) : $@convention(thin) (@in (), @owned @callee_owned () -> UnsafeMutablePointer<()>) -> @out UnsafeMutablePointer<()>
26-
// CHECK: [[CURRIED2_FALSE]]:
2727
curried2()
2828
}
2929
if let prop1 = obj.normalObjectProp {
3030
// CHECK: dynamic_method_br [[PROP1_OBJ:%.+]] : $@opened([[PROP1_EXISTENTIAL:.+]]) Test, #Test.normalObjectProp!getter.1.foreign, [[PROP1_TRUE:[^,]+]], [[PROP1_FALSE:[^,]+]]
31+
// CHECK: [[PROP1_FALSE]]:
3132
// CHECK: [[PROP1_TRUE]]([[PROP1_METHOD:%.+]] : $@convention(objc_method) (@opened([[PROP1_EXISTENTIAL]]) Test) -> @autoreleased AnyObject):
3233
// CHECK: [[PROP1_PARTIAL:%.+]] = partial_apply [[PROP1_METHOD]]([[PROP1_OBJ]]) : $@convention(objc_method) (@opened([[PROP1_EXISTENTIAL]]) Test) -> @autoreleased AnyObject
3334
// CHECK: = apply [[PROP1_PARTIAL]]() : $@callee_owned () -> @owned AnyObject
34-
// CHECK: [[PROP1_FALSE]]:
3535
_ = prop1
3636
}
3737
if let prop2 = obj.innerPointerProp {
3838
// CHECK: dynamic_method_br [[PROP2_OBJ:%.+]] : $@opened([[PROP2_EXISTENTIAL:.+]]) Test, #Test.innerPointerProp!getter.1.foreign, [[PROP2_TRUE:[^,]+]], [[PROP2_FALSE:[^,]+]]
39+
// CHECK: [[PROP2_FALSE]]:
3940
// CHECK: [[PROP2_TRUE]]([[PROP2_METHOD:%.+]] : $@convention(objc_method) (@opened([[PROP2_EXISTENTIAL]]) Test) -> @unowned_inner_pointer UnsafeMutablePointer<()>):
4041
// CHECK: [[PROP2_PARTIAL:%.+]] = partial_apply [[PROP2_METHOD]]([[PROP2_OBJ]]) : $@convention(objc_method) (@opened([[PROP2_EXISTENTIAL]]) Test) -> @unowned_inner_pointer UnsafeMutablePointer<()>
4142
// CHECK: = apply [[PROP2_PARTIAL]]() : $@callee_owned () -> UnsafeMutablePointer<()>
42-
// CHECK: [[PROP2_FALSE]]:
4343
_ = prop2
4444
}
4545
} // CHECK: {{^}$}}

test/SILOptimizer/cse.sil

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,3 +1319,51 @@ bb0(%0 : $*Builtin.Int64, %1 : $Builtin.NativeObject):
13191319
%6 = tuple(%4 : $Builtin.Int64, %5 : $Builtin.Int64)
13201320
return %6 : $(Builtin.Int64, Builtin.Int64)
13211321
}
1322+
1323+
protocol Proto : class {
1324+
func doThis()
1325+
func doThat()
1326+
}
1327+
1328+
// Check that all open_existential_ref instructions are CSEd
1329+
// CHECK-LABEL: sil @cse_open_existential : $@convention(thin) (@guaranteed Proto, Bool) -> ()
1330+
// CHECK: bb0
1331+
// CHECK: %[[OPENED_EXISTENTIAL:[0-9]+]] = open_existential_ref %{{[0-9]+}} : $Proto to $@opened("1B68354A-4796-11E6-B7DF-B8E856428C60") Proto
1332+
// CHECK: %[[WM1:[0-9]+]] = witness_method $@opened("1B68354A-4796-11E6-B7DF-B8E856428C60") Proto, #Proto.doThis!1
1333+
// CHECK: apply %[[WM1]]{{.*}}(%[[OPENED_EXISTENTIAL]])
1334+
// CHECK: cond_br
1335+
// CHECK: bb1:
1336+
// CHECK-NEXT: %[[WM2:[0-9]+]] = witness_method $@opened("1B68354A-4796-11E6-B7DF-B8E856428C60") Proto, #Proto.doThat!1
1337+
// CHECK-NEXT: apply %[[WM2]]{{.*}}(%[[OPENED_EXISTENTIAL]])
1338+
// CHECK-NEXT: br
1339+
// CHECK: bb2:
1340+
// CHECK-NEXT: apply %[[WM1]]{{.*}}(%[[OPENED_EXISTENTIAL]])
1341+
// CHECK-NEXT: br
1342+
// CHECK: bb3:
1343+
// CHECK-NEXT: tuple
1344+
// CHECK-NEXT: return
1345+
sil @cse_open_existential : $@convention(thin) (@guaranteed Proto, Bool) -> () {
1346+
bb0(%0 : $Proto, %1 : $Bool):
1347+
%4 = open_existential_ref %0 : $Proto to $@opened("1B68354A-4796-11E6-B7DF-B8E856428C60") Proto
1348+
%5 = witness_method $@opened("1B68354A-4796-11E6-B7DF-B8E856428C60") Proto, #Proto.doThis!1, %4 : $@opened("1B68354A-4796-11E6-B7DF-B8E856428C60") Proto : $@convention(witness_method) <τ_0_0 where τ_0_0 : Proto> (@guaranteed τ_0_0) -> ()
1349+
%6 = apply %5<@opened("1B68354A-4796-11E6-B7DF-B8E856428C60") Proto>(%4) : $@convention(witness_method) <τ_0_0 where τ_0_0 : Proto> (@guaranteed τ_0_0) -> ()
1350+
%7 = struct_extract %1 : $Bool, #Bool._value
1351+
cond_br %7, bb1, bb2
1352+
1353+
bb1:
1354+
%9 = open_existential_ref %0 : $Proto to $@opened("1B685052-4796-11E6-B7DF-B8E856428C60") Proto
1355+
%10 = witness_method $@opened("1B685052-4796-11E6-B7DF-B8E856428C60") Proto, #Proto.doThat!1, %9 : $@opened("1B685052-4796-11E6-B7DF-B8E856428C60") Proto : $@convention(witness_method) <τ_0_0 where τ_0_0 : Proto> (@guaranteed τ_0_0) -> ()
1356+
%11 = apply %10<@opened("1B685052-4796-11E6-B7DF-B8E856428C60") Proto>(%9) : $@convention(witness_method) <τ_0_0 where τ_0_0 : Proto> (@guaranteed τ_0_0) -> ()
1357+
br bb3
1358+
1359+
bb2: // Preds: bb0
1360+
%13 = open_existential_ref %0 : $Proto to $@opened("1B6851A6-4796-11E6-B7DF-B8E856428C60") Proto
1361+
%14 = witness_method $@opened("1B6851A6-4796-11E6-B7DF-B8E856428C60") Proto, #Proto.doThis!1, %13 : $@opened("1B6851A6-4796-11E6-B7DF-B8E856428C60") Proto : $@convention(witness_method) <τ_0_0 where τ_0_0 : Proto> (@guaranteed τ_0_0) -> ()
1362+
%15 = apply %14<@opened("1B6851A6-4796-11E6-B7DF-B8E856428C60") Proto>(%13) : $@convention(witness_method) <τ_0_0 where τ_0_0 : Proto> (@guaranteed τ_0_0) -> ()
1363+
br bb3
1364+
1365+
bb3:
1366+
%17 = tuple ()
1367+
return %17 : $()
1368+
}
1369+

0 commit comments

Comments
 (0)