Hi,
I'm a mentee participating in the Swift Mentorship program. I have interest in the ownership system but I'm fairly new to the compiler implementation. I am picking up issue Compiler crash: `borrowing` parameter in actor method captured by closure · Issue #86458 · swiftlang/swift · GitHub (and SIL verification failed: init_existential_ref operand must be lowered to the right abstraction level for the existential · Issue #86530 · swiftlang/swift · GitHub) to as a starter, but I'm not sure if my solution is sound.
PR: Fixes #86458 and #86530 by FlickerSoul · Pull Request #90656 · swiftlang/swift · GitHub
The code that breaks when borrowing is introduced
final class S {}
func f(_ s: borrowing S) {
s === s
}
It produces SIL like the following
bb0(%0 : @noImplicitCopy @guaranteed $S):
%1 = copyable_to_moveonlywrapper [guaranteed] %0 // user: %2
%2 = copy_value %1 // user: %3
%3 = mark_unresolved_non_copyable_value [no_consume_or_assign] %2 // users: %20, %9, %5, %4
debug_value %3, let, name "s", argno 1 // id: %4
%5 = begin_borrow %3 // users: %18, %6
%6 = copy_value %5 // user: %7
%7 = init_existential_ref %6 : $@moveOnly S : $S, $AnyObject // user: %8
... more after
because the introduction of borrowing keyword, in the argument setup phase, the argument variable is wrapped with a wrapper that annotates moveOnly. The computation of %7 crashes,
%7 = init_existential_ref %6 : $@moveOnly S : $S, $AnyObject
because when the SIL verifier verifies that if $@moveOnly S can be the same as $S, with the requireSameType assertioin, near the SILVerifier.cpp:5067
SILType concreteType = IEI->getOperand()->getType();
// omitted ....
// The operand must be at the right abstraction level for the existential.
SILType exType = IEI->getType();
auto archetype = ExistentialArchetypeType::get(exType.getASTType());
auto loweredTy = F.getLoweredType(Lowering::AbstractionPattern(archetype),
IEI->getFormalConcreteType());
requireSameType(concreteType, loweredTy,
"init_existential_ref operand must be lowered to the right "
"abstraction level for the existential");
My intuition tells me that the the moveOnly container needs to be "reverted", so a solution would be inserting a createOwnedMoveOnlyWrapperToCopyableValue instruction before doing the existential erasure. (After reading the documents more, it appears that @moveOnly is only part of SIL, not in formal type, and therefore causing the SIL verifier assertion failure, which wants the the type of the operand on the left side of as to be the same as the concrete formal type)
ManagedValue SILGenFunction::emitExistentialErasure(
SILLocation loc,
CanType concreteFormalType,
const TypeLowering &concreteTL,
const TypeLowering &existentialTL,
ArrayRef<ProtocolConformanceRef> conformances,
SGFContext C,
llvm::function_ref<ManagedValue (SGFContext)> F,
bool allowEmbeddedNSError) {
// omitted ....
// SILGenConvert.cpp:810
case ExistentialRepresentation::Class: {
assert(existentialTL.isLoadable());
ManagedValue sub = F(SGFContext());
// Unwrap the wrapper according to the value's
// ownership: for an owned value this is a consuming use, which the
// move-only checker will diagnose against borrowed values
if (sub.getType().isMoveOnlyWrapped()) {
if (sub.isPlusOne(*this)) {
sub = B.createOwnedMoveOnlyWrapperToCopyableValue(loc, sub);
} else {
sub = B.createGuaranteedMoveOnlyWrapperToCopyableValue(loc, sub);
}
}
return B.createInitExistentialRef(loc, existentialTL.getLoweredType(),
concreteFormalType, sub, conformances);
}
// omitted ...
}
which produces the following, and seems to fix the crashing issue
bb0(%0 : @noImplicitCopy @guaranteed $S):
%1 = copyable_to_moveonlywrapper [guaranteed] %0 // user: %2
%2 = copy_value %1 // user: %3
%3 = mark_unresolved_non_copyable_value [no_consume_or_assign] %2 // users: %22, %10, %5, %4
debug_value %3, let, name "s", argno 1 // id: %4
%5 = begin_borrow %3 // users: %20, %6
%6 = copy_value %5 // user: %7
%7 = moveonlywrapper_to_copyable [owned] %6 // user: %8
%8 = init_existential_ref %7 : $S : $S, $AnyObject // user: %9
After the fix is introduced, the compiler doesn't crash anymore
❯ ../build/Ninja-RelWithDebInfoAssert/swift-macosx-arm64/bin/swiftc 86458.swift
86458.swift:4:7: warning: result of operator '===' is unused [#NoUsage]
2 |
3 | func f(_ s: borrowing S) {
4 | s === s
| `- warning: result of operator '===' is unused [#NoUsage]
5 | }
6 |
86458.swift:3:10: error: 's' is borrowed and cannot be consumed
1 | final class S {}
2 |
3 | func f(_ s: borrowing S) {
| `- error: 's' is borrowed and cannot be consumed
4 | s === s
| | `- note: consumed here
| `- note: consumed here
5 | }
6 |
It looks like my approach only fixes the branch when existential representation is a class and may not be comprehensive. I vaguely remember if a type resides in other modules, it may be address only (opaque?), and may have similar issue, which I haven't checked yet.
Please let me know what you think and I appreciate your feedback!!
Thanks!
Best regards,
Larry