Skip to content

Commit c832b41

Browse files
committed
[move-only] Teach the move checker how to handle global_addr.
As part of this I also had to change how we emit global_addr in SILGenLValue. Specifically, only for noncopyable types, we no longer emit a single global_addr at the beginning of the function (in a sense auto-CSEing) and instead always emit a new global_addr for each access. The reason why we do this is that otherwise, access base visitor will consider all accesses to the global to be for the same single access. In contrast, by always emitting the global_addr each time, we provide a new base for each access allowing us to emit the diagnostics that we want to. rdar://102794400
1 parent e0ad817 commit c832b41

File tree

7 files changed

+265
-21
lines changed

7 files changed

+265
-21
lines changed

lib/SILGen/SILGenGlobalVariable.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,22 @@ SILGenFunction::emitGlobalVariableRef(SILLocation loc, VarDecl *var,
100100
return ManagedValue::forLValue(addr);
101101
}
102102

103-
// Global variables can be accessed directly with global_addr. Emit this
104-
// instruction into the prolog of the function so we can memoize/CSE it in
105-
// VarLocs.
103+
// Global variables can be accessed directly with global_addr. If we have a
104+
// noncopyable type, just emit the global_addr so each individual access has
105+
// its own base projection. This is important so that the checker can
106+
// distinguish in between different accesses to the same global.
107+
auto *silG = SGM.getSILGlobalVariable(var, NotForDefinition);
108+
if (silG->getLoweredType().isMoveOnly()) {
109+
SILValue addr = B.createGlobalAddr(var, silG);
110+
return ManagedValue::forLValue(addr);
111+
}
112+
113+
// If we have a copyable type, emit this instruction into the prolog of the
114+
// function so we can memoize/CSE it via the VarLocs map.
106115
auto *entryBB = &*getFunction().begin();
107116
SILGenBuilder prologueB(*this, entryBB, entryBB->begin());
108117
prologueB.setTrackingList(B.getTrackingList());
109118

110-
auto *silG = SGM.getSILGlobalVariable(var, NotForDefinition);
111119
SILValue addr = prologueB.createGlobalAddr(var, silG);
112120

113121
VarLocs[var] = SILGenFunction::VarLoc::get(addr);

lib/SILGen/SILGenLValue.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2988,6 +2988,14 @@ void LValue::addNonMemberVarComponent(SILGenFunction &SGF, SILLocation loc,
29882988
// The only other case that should get here is a global variable.
29892989
if (!address) {
29902990
address = SGF.emitGlobalVariableRef(Loc, Storage, ActorIso);
2991+
if (address.getType().isMoveOnly()) {
2992+
auto checkKind =
2993+
MarkMustCheckInst::CheckKind::AssignableButNotConsumable;
2994+
if (isReadAccess(AccessKind)) {
2995+
checkKind = MarkMustCheckInst::CheckKind::NoConsumeOrAssign;
2996+
}
2997+
address = SGF.B.createMarkMustCheckInst(Loc, address, checkKind);
2998+
}
29912999
} else {
29923000
assert((!ActorIso || Storage->isTopLevelGlobal()) &&
29933001
"local var should not be actor isolated!");

lib/SILOptimizer/Mandatory/MoveOnlyAddressChecker.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,15 @@ void UseState::initializeLiveness(
615615
liveness.initializeDef(address, liveness.getTopLevelSpan());
616616
}
617617

618+
// Check if our address is from a global_addr. In such a case, we treat the
619+
// mark_must_check as the initialization.
620+
if (auto *globalAddr = dyn_cast<GlobalAddrInst>(address->getOperand())) {
621+
LLVM_DEBUG(llvm::dbgs() << "Found global_addr use... "
622+
"adding mark_must_check as init!\n");
623+
initInsts.insert({address, liveness.getTopLevelSpan()});
624+
liveness.initializeDef(address, liveness.getTopLevelSpan());
625+
}
626+
618627
// Now that we have finished initialization of defs, change our multi-maps
619628
// from their array form to their map form.
620629
liveness.finishedInitializationOfDefs();

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,14 @@ static void diagnose(ASTContext &context, SILInstruction *inst, Diag<T...> diag,
5959
context.Diags.diagnose(loc.getSourceLoc(), diag, std::forward<U>(args)...);
6060
}
6161

62-
static void getVariableNameForValue(MarkMustCheckInst *mmci,
62+
/// Helper function that actually implements getVariableNameForValue. Do not
63+
/// call it directly! Call the unary variants instead.
64+
static void getVariableNameForValue(SILValue value2,
65+
SILValue searchValue,
6366
SmallString<64> &resultingString) {
6467
// Before we do anything, lets see if we have an exact debug_value on our
6568
// mmci. In such a case, we can end early and are done.
66-
if (auto *use = getSingleDebugUse(mmci)) {
69+
if (auto *use = getSingleDebugUse(value2)) {
6770
if (auto debugVar = DebugVarCarryingInst(use->getUser())) {
6871
assert(debugVar.getKind() == DebugVarCarryingInst::Kind::DebugValue);
6972
resultingString += debugVar.getName();
@@ -72,28 +75,27 @@ static void getVariableNameForValue(MarkMustCheckInst *mmci,
7275
}
7376

7477
// Otherwise, we need to look at our mark_must_check's operand.
75-
StackList<SILInstruction *> variableNamePath(mmci->getFunction());
76-
SILValue value = mmci->getOperand();
78+
StackList<SILInstruction *> variableNamePath(value2->getFunction());
7779
while (true) {
78-
if (auto *allocInst = dyn_cast<AllocationInst>(value)) {
80+
if (auto *allocInst = dyn_cast<AllocationInst>(searchValue)) {
7981
variableNamePath.push_back(allocInst);
8082
break;
8183
}
8284

83-
if (auto *globalAddrInst = dyn_cast<GlobalAddrInst>(value)) {
85+
if (auto *globalAddrInst = dyn_cast<GlobalAddrInst>(searchValue)) {
8486
variableNamePath.push_back(globalAddrInst);
8587
break;
8688
}
8789

88-
if (auto *rei = dyn_cast<RefElementAddrInst>(value)) {
90+
if (auto *rei = dyn_cast<RefElementAddrInst>(searchValue)) {
8991
variableNamePath.push_back(rei);
90-
value = rei->getOperand();
92+
searchValue = rei->getOperand();
9193
continue;
9294
}
9395

9496
// If we do not do an exact match, see if we can find a debug_var inst. If
9597
// we do, we always break since we have a root value.
96-
if (auto *use = getSingleDebugUse(value)) {
98+
if (auto *use = getSingleDebugUse(searchValue)) {
9799
if (auto debugVar = DebugVarCarryingInst(use->getUser())) {
98100
assert(debugVar.getKind() == DebugVarCarryingInst::Kind::DebugValue);
99101
variableNamePath.push_back(use->getUser());
@@ -103,9 +105,9 @@ static void getVariableNameForValue(MarkMustCheckInst *mmci,
103105

104106
// Otherwise, try to see if we have a single value instruction we can look
105107
// through.
106-
if (isa<BeginBorrowInst>(value) || isa<LoadInst>(value) ||
107-
isa<BeginAccessInst>(value) || isa<MarkMustCheckInst>(value)) {
108-
value = cast<SingleValueInstruction>(value)->getOperand(0);
108+
if (isa<BeginBorrowInst>(searchValue) || isa<LoadInst>(searchValue) ||
109+
isa<BeginAccessInst>(searchValue) || isa<MarkMustCheckInst>(searchValue)) {
110+
searchValue = cast<SingleValueInstruction>(searchValue)->getOperand(0);
109111
continue;
110112
}
111113

@@ -132,6 +134,19 @@ static void getVariableNameForValue(MarkMustCheckInst *mmci,
132134
}
133135
}
134136

137+
138+
static void getVariableNameForValue(MarkMustCheckInst *mmci,
139+
SmallString<64> &resultingString) {
140+
return getVariableNameForValue(mmci, mmci->getOperand(), resultingString);
141+
}
142+
143+
#if 0
144+
static void getVariableNameForValue(SILValue value,
145+
SmallString<64> &resultingString) {
146+
return getVariableNameForValue(value, value, resultingString);
147+
}
148+
#endif
149+
135150
//===----------------------------------------------------------------------===//
136151
// MARK: Misc Diagnostics
137152
//===----------------------------------------------------------------------===//

test/SILGen/moveonly.swift

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public enum NonTrivialEnum {
4444
case third(NonTrivialStruct)
4545
}
4646

47+
var varGlobal = NonTrivialStruct()
48+
let letGlobal = NonTrivialStruct()
49+
4750
public func borrowVal(_ e : NonTrivialEnum) {}
4851
public func borrowVal(_ e : FD) {}
4952
public func borrowVal(_ k: CopyableKlass) {}
@@ -517,3 +520,123 @@ func enumSwitchTest1(_ e: EnumSwitchTests.E) {
517520
break
518521
}
519522
}
523+
524+
//////////////////////
525+
// Global Addr Test //
526+
//////////////////////
527+
528+
// Make sure that we emit a new global_addr for each use.
529+
// CHECK-LABEL: sil hidden [ossa] @$s8moveonly16testGlobalBorrowyyF : $@convention(thin) () -> () {
530+
// CHECK: [[GLOBAL:%.*]] = global_addr @$s8moveonly9varGlobalAA16NonTrivialStructVvp :
531+
// CHECK: [[MARKED_GLOBAL:%.*]] = mark_must_check [no_consume_or_assign] [[GLOBAL]]
532+
// CHECK: [[ACCESS:%.*]] = begin_access [read] [dynamic] [[MARKED_GLOBAL]]
533+
// CHECK: [[LOADED_VAL:%.*]] = load_borrow [[ACCESS]]
534+
// CHECK: apply {{%.*}}([[LOADED_VAL]])
535+
// CHECK: end_borrow [[LOADED_VAL]]
536+
// CHECK: end_access [[ACCESS]]
537+
//
538+
// CHECK: [[GLOBAL:%.*]] = global_addr @$s8moveonly9varGlobalAA16NonTrivialStructVvp :
539+
// CHECK: [[MARKED_GLOBAL:%.*]] = mark_must_check [no_consume_or_assign] [[GLOBAL]]
540+
// CHECK: [[ACCESS:%.*]] = begin_access [read] [dynamic] [[MARKED_GLOBAL]]
541+
// CHECK: [[GEP:%.*]] = struct_element_addr [[ACCESS]]
542+
// CHECK: [[LOADED_VAL:%.*]] = load_borrow [[GEP]]
543+
// CHECK: apply {{%.*}}([[LOADED_VAL]])
544+
// CHECK: end_borrow [[LOADED_VAL]]
545+
// CHECK: end_access [[ACCESS]]
546+
//
547+
// CHECK: [[GLOBAL:%.*]] = global_addr @$s8moveonly9letGlobalAA16NonTrivialStructVvp :
548+
// CHECK: [[MARKED_GLOBAL:%.*]] = mark_must_check [no_consume_or_assign] [[GLOBAL]]
549+
// CHECK: [[LOADED_VAL:%.*]] = load [copy] [[MARKED_GLOBAL]]
550+
// CHECK: apply {{%.*}}([[LOADED_VAL]])
551+
// CHECK: destroy_value [[LOADED_VAL]]
552+
//
553+
// CHECK: [[GLOBAL:%.*]] = global_addr @$s8moveonly9letGlobalAA16NonTrivialStructVvp :
554+
// CHECK: [[MARKED_GLOBAL:%.*]] = mark_must_check [no_consume_or_assign] [[GLOBAL]]
555+
// CHECK: [[LOADED_VAL:%.*]] = load [copy] [[MARKED_GLOBAL]]
556+
// CHECK: [[LOADED_BORROWED_VAL:%.*]] = begin_borrow [[LOADED_VAL]]
557+
// CHECK: [[LOADED_GEP:%.*]] = struct_extract [[LOADED_BORROWED_VAL]]
558+
// CHECK: [[LOADED_GEP_COPY:%.*]] = copy_value [[LOADED_GEP]]
559+
// CHECK: end_borrow [[LOADED_BORROWED_VAL]]
560+
// CHECK: destroy_value [[LOADED_VAL]]
561+
// CHECK: apply {{%.*}}([[LOADED_GEP_COPY]])
562+
// CHECK: destroy_value [[LOADED_GEP_COPY]]
563+
// CHECK: } // end sil function '$s8moveonly16testGlobalBorrowyyF'
564+
func testGlobalBorrow() {
565+
borrowVal(varGlobal)
566+
borrowVal(varGlobal.nonTrivialStruct2)
567+
borrowVal(letGlobal)
568+
borrowVal(letGlobal.nonTrivialStruct2)
569+
}
570+
571+
// CHECK-LABEL: sil hidden [ossa] @$s8moveonly17testGlobalConsumeyyF : $@convention(thin) () -> () {
572+
// CHECK: [[GLOBAL:%.*]] = global_addr @$s8moveonly9varGlobalAA16NonTrivialStructVvp :
573+
// CHECK: [[MARKED_GLOBAL:%.*]] = mark_must_check [assignable_but_not_consumable] [[GLOBAL]]
574+
// CHECK: [[ACCESS:%.*]] = begin_access [deinit] [dynamic] [[MARKED_GLOBAL]]
575+
// CHECK: [[LOADED_VAL:%.*]] = load [take]
576+
// CHECK: apply {{%.*}}([[LOADED_VAL]])
577+
// CHECK: end_access [[ACCESS]]
578+
//
579+
// CHECK: [[GLOBAL:%.*]] = global_addr @$s8moveonly9varGlobalAA16NonTrivialStructVvp :
580+
// CHECK: [[MARKED_GLOBAL:%.*]] = mark_must_check [assignable_but_not_consumable] [[GLOBAL]]
581+
// CHECK: [[ACCESS:%.*]] = begin_access [deinit] [dynamic] [[MARKED_GLOBAL]]
582+
// CHECK: [[GEP:%.*]] = struct_element_addr [[ACCESS]]
583+
// CHECK: [[LOADED_VAL:%.*]] = load [take] [[GEP]]
584+
// CHECK: apply {{%.*}}([[LOADED_VAL]])
585+
// CHECK: end_access [[ACCESS]]
586+
//
587+
// CHECK: [[GLOBAL:%.*]] = global_addr @$s8moveonly9letGlobalAA16NonTrivialStructVvp :
588+
// CHECK: [[MARKED_GLOBAL:%.*]] = mark_must_check [no_consume_or_assign] [[GLOBAL]]
589+
// CHECK: [[LOADED_VAL:%.*]] = load [copy] [[MARKED_GLOBAL]]
590+
// CHECK: apply {{%.*}}([[LOADED_VAL]])
591+
//
592+
// CHECK: [[GLOBAL:%.*]] = global_addr @$s8moveonly9letGlobalAA16NonTrivialStructVvp :
593+
// CHECK: [[MARKED_GLOBAL:%.*]] = mark_must_check [no_consume_or_assign] [[GLOBAL]]
594+
// CHECK: [[LOADED_VAL:%.*]] = load [copy] [[MARKED_GLOBAL]]
595+
// CHECK: [[LOADED_BORROWED_VAL:%.*]] = begin_borrow [[LOADED_VAL]]
596+
// CHECK: [[LOADED_GEP:%.*]] = struct_extract [[LOADED_BORROWED_VAL]]
597+
// CHECK: [[LOADED_GEP_COPY:%.*]] = copy_value [[LOADED_GEP]]
598+
// CHECK: end_borrow [[LOADED_BORROWED_VAL]]
599+
// CHECK: destroy_value [[LOADED_VAL]]
600+
// CHECK: apply {{%.*}}([[LOADED_GEP_COPY]])
601+
//
602+
// CHECK: } // end sil function '$s8moveonly17testGlobalConsumeyyF'
603+
func testGlobalConsume() {
604+
consumeVal(varGlobal)
605+
consumeVal(varGlobal.nonTrivialStruct2)
606+
consumeVal(letGlobal)
607+
consumeVal(letGlobal.nonTrivialStruct2)
608+
}
609+
610+
// CHECK-LABEL: sil hidden [ossa] @$s8moveonly16testGlobalAssignyyF : $@convention(thin) () -> () {
611+
// CHECK: [[GLOBAL:%.*]] = global_addr @$s8moveonly9varGlobalAA16NonTrivialStructVvp :
612+
// CHECK: [[MARKED_GLOBAL:%.*]] = mark_must_check [assignable_but_not_consumable] [[GLOBAL]]
613+
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [dynamic] [[MARKED_GLOBAL]]
614+
// CHECK: assign {{%.*}} to [[ACCESS]]
615+
// CHECK: end_access [[ACCESS]]
616+
//
617+
// CHECK: [[GLOBAL:%.*]] = global_addr @$s8moveonly9varGlobalAA16NonTrivialStructVvp :
618+
// CHECK: [[MARKED_GLOBAL:%.*]] = mark_must_check [assignable_but_not_consumable] [[GLOBAL]]
619+
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [dynamic] [[MARKED_GLOBAL]]
620+
// CHECK: assign {{%.*}} to [[ACCESS]]
621+
// CHECK: end_access [[ACCESS]]
622+
//
623+
// CHECK: [[GLOBAL:%.*]] = global_addr @$s8moveonly9varGlobalAA16NonTrivialStructVvp :
624+
// CHECK: [[MARKED_GLOBAL:%.*]] = mark_must_check [assignable_but_not_consumable] [[GLOBAL]]
625+
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [dynamic] [[MARKED_GLOBAL]]
626+
// CHECK: [[GEP:%.*]] = struct_element_addr [[ACCESS]]
627+
// CHECK: assign {{%.*}} to [[GEP]]
628+
// CHECK: end_access [[ACCESS]]
629+
//
630+
// CHECK: [[GLOBAL:%.*]] = global_addr @$s8moveonly9varGlobalAA16NonTrivialStructVvp :
631+
// CHECK: [[MARKED_GLOBAL:%.*]] = mark_must_check [assignable_but_not_consumable] [[GLOBAL]]
632+
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [dynamic] [[MARKED_GLOBAL]]
633+
// CHECK: [[GEP:%.*]] = struct_element_addr [[ACCESS]]
634+
// CHECK: assign {{%.*}} to [[GEP]]
635+
// CHECK: end_access [[ACCESS]]
636+
// CHECK: } // end sil function '$s8moveonly16testGlobalAssignyyF'
637+
func testGlobalAssign() {
638+
varGlobal = NonTrivialStruct()
639+
varGlobal = NonTrivialStruct()
640+
varGlobal.nonTrivialStruct2 = NonTrivialStruct2()
641+
varGlobal.nonTrivialStruct2 = NonTrivialStruct2()
642+
}

test/SILOptimizer/moveonly_addresschecker.sil

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-sil-opt -sil-move-only-address-checker -enable-experimental-move-only -enable-experimental-feature MoveOnlyClasses -enable-sil-verify-all %s | %FileCheck %s
1+
// RUN: %target-sil-opt -module-name moveonly_addresschecker -sil-move-only-address-checker -enable-experimental-move-only -enable-experimental-feature MoveOnlyClasses -enable-sil-verify-all %s | %FileCheck %s
22

33
sil_stage raw
44

@@ -39,6 +39,8 @@ public struct NonTrivialStruct2 {
3939
var copyableKlass = CopyableKlass()
4040
}
4141

42+
sil @useNonTrivialStruct2 : $@convention(thin) (@guaranteed NonTrivialStruct2) -> ()
43+
4244
@_moveOnly struct MyBufferView<T> {
4345
var ptr: UnsafeBufferPointer<T>
4446
}
@@ -47,6 +49,11 @@ final class ClassContainingMoveOnly {
4749
var value = NonTrivialStruct()
4850
}
4951

52+
@_hasStorage @_hasInitialValue var varGlobal: NonTrivialStruct { get set }
53+
@_hasStorage @_hasInitialValue let letGlobal: NonTrivialStruct { get }
54+
sil_global hidden @$s23moveonly_addresschecker9varGlobalAA16NonTrivialStructVvp : $NonTrivialStruct
55+
sil_global hidden [let] @$s23moveonly_addresschecker9letGlobalAA16NonTrivialStructVvp : $NonTrivialStruct
56+
5057
///////////
5158
// Tests //
5259
///////////
@@ -347,8 +354,8 @@ bb0(%0 : $UnsafeBufferPointer<T>, %1 : $*MyBufferView<T>):
347354
return %9 : $()
348355
}
349356

350-
// Just make sure that we accept this and do not crash. There isn't any
351-
// transformation to perform.
357+
// No transformation here, just make sure that we treat the mark_must_check as
358+
// the def so that we don't crash.
352359
sil [ossa] @test_ref_element_addr_borrow_use : $@convention(thin) (@guaranteed ClassContainingMoveOnly) -> () {
353360
bb0(%0 : @guaranteed $ClassContainingMoveOnly):
354361
%f = function_ref @useNonTrivialStruct : $@convention(thin) (@guaranteed NonTrivialStruct) -> ()
@@ -361,4 +368,43 @@ bb0(%0 : @guaranteed $ClassContainingMoveOnly):
361368
end_access %3 : $*NonTrivialStruct
362369
%9999 = tuple()
363370
return %9999 : $()
364-
}
371+
}
372+
373+
// No transformation here, just make sure that we treat the mark_must_check as
374+
// the def so that we don't crash.
375+
sil [ossa] @test_global_addr_borrow_use : $@convention(thin) () -> () {
376+
bb0:
377+
%0 = global_addr @$s23moveonly_addresschecker9varGlobalAA16NonTrivialStructVvp : $*NonTrivialStruct
378+
%1 = mark_must_check [no_consume_or_assign] %0 : $*NonTrivialStruct
379+
%2 = begin_access [read] [dynamic] %1 : $*NonTrivialStruct
380+
%3 = load_borrow %2 : $*NonTrivialStruct
381+
%4 = function_ref @useNonTrivialStruct : $@convention(thin) (@guaranteed NonTrivialStruct) -> ()
382+
%5 = apply %4(%3) : $@convention(thin) (@guaranteed NonTrivialStruct) -> ()
383+
end_borrow %3 : $NonTrivialStruct
384+
end_access %2 : $*NonTrivialStruct
385+
%8 = tuple ()
386+
return %8 : $()
387+
}
388+
389+
// Make sure that we do not emit any errors here.
390+
sil [ossa] @test_global_addr_multiple_borrow_uses : $@convention(thin) () -> () {
391+
bb0:
392+
%0 = global_addr @$s23moveonly_addresschecker9varGlobalAA16NonTrivialStructVvp : $*NonTrivialStruct
393+
%1 = mark_must_check [no_consume_or_assign] %0 : $*NonTrivialStruct
394+
%2 = begin_access [read] [dynamic] %1 : $*NonTrivialStruct
395+
%3 = load_borrow %2 : $*NonTrivialStruct
396+
%4 = function_ref @useNonTrivialStruct : $@convention(thin) (@guaranteed NonTrivialStruct) -> ()
397+
%5 = apply %4(%3) : $@convention(thin) (@guaranteed NonTrivialStruct) -> ()
398+
end_borrow %3 : $NonTrivialStruct
399+
end_access %2 : $*NonTrivialStruct
400+
%0a = global_addr @$s23moveonly_addresschecker9varGlobalAA16NonTrivialStructVvp : $*NonTrivialStruct
401+
%8 = begin_access [read] [dynamic] %0a : $*NonTrivialStruct
402+
%9 = struct_element_addr %8 : $*NonTrivialStruct, #NonTrivialStruct.nonTrivialStruct2
403+
%10 = load_borrow %9 : $*NonTrivialStruct2
404+
%11 = function_ref @useNonTrivialStruct2 : $@convention(thin) (@guaranteed NonTrivialStruct2) -> ()
405+
%12 = apply %11(%10) : $@convention(thin) (@guaranteed NonTrivialStruct2) -> ()
406+
end_borrow %10 : $NonTrivialStruct2
407+
end_access %8 : $*NonTrivialStruct
408+
%15 = tuple ()
409+
return %15 : $()
410+
}

0 commit comments

Comments
 (0)