mirror of
https://github.com/apple/swift.git
synced 2026-06-20 15:42:51 +02:00
097b0d3400
We cannot use spare bits or other overlapping storage layout tricks with fundamentally address-only enums, and we can take advantage of this to do borrowing switches or other in-place projections without copying the value. However, for resilient enums, the implementation may use spare bit packing, but the type must be handled address-only outside of its defining module, and we didn't have a way to express that with borrowing switch. Optimization passes have also been running into problems with the complexity that we were using `unchecked_take_enum_data_addr` sometimes as a pure operation. This patch splits the instruction into three: - `unchecked_inplace_enum_data_addr` represents a nondestructive in-place enum projection. It is only allowed for enums whose projection operation is nondestructive. - `unchecked_take_enum_data_addr` represents a destructive enum projection, invalidating the enum and leaving the payload to be further consumed. This matches the current instruction's semantics. - `unchecked_borrow_enum_data_addr` represents a borrowing enum projection. The instruction takes a second operand for "scratch" space, which the enum representation may be copied into in order to avoid invalidating the enum value, so the result is dependent on the lifetime of both the original enum and the scratch buffer. This allows for borrowing switches over resilient enums. `unchecked_borrow_enum_data_addr` is implemented by taking advantage of the "address-only enums can't do spare bit optimization" property at runtime. We inspect the operand type's bitwise-borrowability from its metadata. If the type is bitwise-borrowable, then we are allowed to bitwise-copy the enum to the scratch space and apply the projection to the scratch space, preserving the original value. If the type is not bitwise-borrowable, then we cannot use spare bit optimization in its layout, so we apply the projection in-place. Fixes rdar://174952822.
297 lines
10 KiB
C++
297 lines
10 KiB
C++
//===--- SILGenCleanup.cpp ------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2019 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// Perform peephole-style "cleanup" to aid SIL diagnostic passes.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define DEBUG_TYPE "silgen-cleanup"
|
|
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/Basic/Defer.h"
|
|
#include "swift/SIL/BasicBlockBits.h"
|
|
#include "swift/SIL/BasicBlockDatastructures.h"
|
|
#include "swift/SIL/BasicBlockUtils.h"
|
|
#include "swift/SIL/PrettyStackTrace.h"
|
|
#include "swift/SIL/PrunedLiveness.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
|
|
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
|
|
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
|
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/CanonicalizeInstruction.h"
|
|
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
|
|
#include "swift/SILOptimizer/Utils/InstructionDeleter.h"
|
|
#include "llvm/ADT/PostOrderIterator.h"
|
|
#include "swift/SILOptimizer/Utils/OwnershipOptUtils.h"
|
|
|
|
using namespace swift;
|
|
|
|
// Define a CanonicalizeInstruction subclass for use in SILGenCleanup.
|
|
struct SILGenCanonicalize final : CanonicalizeInstruction {
|
|
bool changed = false;
|
|
llvm::SmallPtrSet<SILInstruction *, 16> deadOperands;
|
|
|
|
SILGenCanonicalize(DeadEndBlocks &deadEndBlocks)
|
|
: CanonicalizeInstruction(DEBUG_TYPE, deadEndBlocks) {}
|
|
|
|
void notifyNewInstruction(SILInstruction *) override { changed = true; }
|
|
|
|
// Just delete the given 'inst' and record its operands. The callback isn't
|
|
// allowed to mutate any other instructions.
|
|
void killInstruction(SILInstruction *inst) override {
|
|
deadOperands.erase(inst);
|
|
for (auto &operand : inst->getAllOperands()) {
|
|
if (auto *operInst = operand.get()->getDefiningInstruction())
|
|
deadOperands.insert(operInst);
|
|
}
|
|
inst->eraseFromParent();
|
|
changed = true;
|
|
}
|
|
|
|
void notifyHasNewUsers(SILValue) override { changed = true; }
|
|
|
|
/// Delete trivially dead instructions in non-deterministic order.
|
|
///
|
|
/// We either have that nextII is endII or if nextII is not endII then endII
|
|
/// is nextII->getParent()->end().
|
|
SILBasicBlock::iterator deleteDeadOperands(SILBasicBlock::iterator nextII,
|
|
SILBasicBlock::iterator endII) {
|
|
auto callbacks = InstModCallbacks().onDelete([&](SILInstruction *deadInst) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Trivially dead: " << *deadInst);
|
|
|
|
// If nextII is the instruction we are going to delete, move nextII past
|
|
// it.
|
|
if (deadInst->getIterator() == nextII)
|
|
++nextII;
|
|
|
|
// Then remove the instruction from the set and delete it.
|
|
deadOperands.erase(deadInst);
|
|
deadInst->eraseFromParent();
|
|
});
|
|
|
|
while (!deadOperands.empty()) {
|
|
SILInstruction *deadOperInst = *deadOperands.begin();
|
|
|
|
// Make sure at least the first instruction is removed from the set.
|
|
deadOperands.erase(deadOperInst);
|
|
|
|
// Then delete this instruction/everything else that we can.
|
|
eliminateDeadInstruction(deadOperInst, callbacks);
|
|
}
|
|
return nextII;
|
|
}
|
|
};
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// SILGenCleanup: Top-Level Module Transform
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
// SILGenCleanup must run on all functions that will be seen by any analysis
|
|
// used by diagnostics before transforming the function that requires the
|
|
// analysis. e.g. Closures need to be cleaned up before the closure's parent can
|
|
// be diagnosed.
|
|
//
|
|
// TODO: This pass can be converted to a function transform if the mandatory
|
|
// pipeline runs in bottom-up closure order.
|
|
struct SILGenCleanup : SILModuleTransform {
|
|
void run() override;
|
|
|
|
bool fixupBorrowAccessors(SILFunction *function);
|
|
bool removeAccessToNonDestructiveEnumProjection(SILFunction *function);
|
|
};
|
|
|
|
/* SILGen may produce a borrow accessor result from within a local borrow
|
|
* scope. Such as:
|
|
*
|
|
* ```
|
|
* %ld = load_borrow %self
|
|
* %fwd = unchecked_ownership %ld
|
|
* %ex = struct_extract %fwd, #Struct.storedProperty
|
|
* end_borrow %ld
|
|
* return %ex
|
|
* ```
|
|
* This is illegal OSSA, since the return uses a value outside it's borrow
|
|
* scope.
|
|
*
|
|
* Transform this into valid OSSA:
|
|
*
|
|
* ```
|
|
* %ld = load_borrow %self
|
|
* %ex = struct_extract %ld, #Struct.storedProperty
|
|
* return_borrow %ex from_scopes %ld
|
|
* ```
|
|
*/
|
|
bool SILGenCleanup::fixupBorrowAccessors(SILFunction *function) {
|
|
if (!function->getConventions().hasGuaranteedResult()) {
|
|
return false;
|
|
}
|
|
auto returnBB = function->findReturnBB();
|
|
if (returnBB == function->end()) {
|
|
return false;
|
|
}
|
|
|
|
auto *returnInst = cast<ReturnInst>(returnBB->getTerminator());
|
|
if (returnInst->getOperand()->getOwnershipKind() !=
|
|
OwnershipKind::Guaranteed) {
|
|
return false;
|
|
}
|
|
|
|
SmallVector<SILValue, 8> enclosingValues;
|
|
findGuaranteedReferenceRoots(returnInst->getOperand(),
|
|
/*lookThroughNestedBorrows=*/false,
|
|
enclosingValues);
|
|
SmallVector<SILInstruction *> scopeEnds;
|
|
SmallVector<SILValue> operands;
|
|
SmallVector<SILInstruction *> toDelete;
|
|
|
|
// For all the local borrow scopes that enclose the return value, delete
|
|
// their end_borrow instructions and use them as an enclosing value in
|
|
// return_borrow instruction.
|
|
for (auto enclosingValue : enclosingValues) {
|
|
BorrowedValue borrow(enclosingValue);
|
|
if (!borrow.isLocalScope()) {
|
|
continue;
|
|
}
|
|
borrow.getLocalScopeEndingInstructions(scopeEnds);
|
|
for (auto *scopeEnd : scopeEnds) {
|
|
if (auto *endBorrow = dyn_cast<EndBorrowInst>(scopeEnd)) {
|
|
operands.push_back(endBorrow->getOperand());
|
|
endBorrow->eraseFromParent();
|
|
}
|
|
}
|
|
for (auto *uncheckedOwnership :
|
|
borrow->getUsersOfType<UncheckedOwnershipInst>()) {
|
|
uncheckedOwnership->replaceAllUsesWith(*borrow);
|
|
toDelete.push_back(uncheckedOwnership);
|
|
}
|
|
}
|
|
|
|
for (auto *inst : toDelete) {
|
|
inst->eraseFromParent();
|
|
}
|
|
|
|
if (operands.empty()) {
|
|
return false;
|
|
}
|
|
|
|
SILBuilderWithScope(returnInst)
|
|
.createReturnBorrow(returnInst->getLoc(), returnInst->getOperand(),
|
|
operands);
|
|
returnInst->eraseFromParent();
|
|
return true;
|
|
}
|
|
|
|
// When switching over an address enum with a potentially noncopyable payload,
|
|
// SILGen emits a scoped 'begin_access' in the payload projection block. This
|
|
// scoped access is problematic when returning a borrowing of the projection for
|
|
// non-destructive enums because it gets treated as an escape:
|
|
//
|
|
// extension Optional where Wrapped: ~Copyable & ~Escapable {
|
|
// func borrow() -> Borrow<Wrapped>? {
|
|
// switch self {
|
|
// case .some(let wrapped):
|
|
// return Borrow(wrapped)
|
|
// case .none:
|
|
// return nil
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// The above is perfectly safe since projecting the wrapped value out of an
|
|
// optional does not destroy the enum value itself. For destructive enums
|
|
// however, the above cannot possibly work because after switching over the
|
|
// value we need to reconstruct the enum, so borrows cannot be returned from
|
|
// such scopes (they could be yielded). Remove this scoped access for
|
|
// non-destructive cases only.
|
|
bool SILGenCleanup::removeAccessToNonDestructiveEnumProjection(
|
|
SILFunction *function) {
|
|
auto changed = false;
|
|
|
|
for (auto &bb : *function) {
|
|
for (auto &inst : bb) {
|
|
auto beginAccess = dyn_cast<BeginAccessInst>(&inst);
|
|
|
|
if (!beginAccess) {
|
|
continue;
|
|
}
|
|
|
|
auto enumProjection =
|
|
dyn_cast<UncheckedInPlaceEnumDataAddrInst>(beginAccess->getSource());
|
|
|
|
if (!enumProjection) {
|
|
continue;
|
|
}
|
|
|
|
for (auto end : beginAccess->getEndAccesses()) {
|
|
end->eraseFromParent();
|
|
}
|
|
|
|
beginAccess->replaceAllUsesWith(enumProjection);
|
|
beginAccess->eraseFromParent();
|
|
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
void SILGenCleanup::run() {
|
|
auto &module = *getModule();
|
|
for (auto &function : module) {
|
|
if (!function.isDefinition())
|
|
continue;
|
|
|
|
getPassManager()->getSwiftPassInvocation()->initializeNestedSwiftPassInvocation(&function);
|
|
|
|
PrettyStackTraceSILFunction stackTrace("silgen cleanup", &function);
|
|
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "\nRunning SILGenCleanup on " << function.getName() << "\n");
|
|
|
|
removeUnreachableBlocks(function);
|
|
bool changed = fixupBorrowAccessors(&function);
|
|
changed |= removeAccessToNonDestructiveEnumProjection(&function);
|
|
breakInfiniteLoops(getPassManager(), &function);
|
|
completeAllLifetimes(getPassManager(), &function, /*includeTrivialVars=*/ true);
|
|
function.verifyOwnership(/*deadEndBlocks=*/nullptr);
|
|
|
|
DeadEndBlocks deadEndBlocks(&function);
|
|
SILGenCanonicalize sgCanonicalize(deadEndBlocks);
|
|
|
|
// Iterate over all blocks even if they aren't reachable. No phi-less
|
|
// dataflow cycles should have been created yet, and these transformations
|
|
// are simple enough they shouldn't be affected by cycles.
|
|
for (auto &bb : function) {
|
|
for (auto ii = bb.begin(), ie = bb.end(); ii != ie;) {
|
|
ii = sgCanonicalize.canonicalize(&*ii);
|
|
ii = sgCanonicalize.deleteDeadOperands(ii, ie);
|
|
}
|
|
}
|
|
changed |= sgCanonicalize.changed;
|
|
if (changed) {
|
|
auto invalidKind = SILAnalysis::InvalidationKind::Instructions;
|
|
invalidateAnalysis(&function, invalidKind);
|
|
}
|
|
|
|
getPassManager()->getSwiftPassInvocation()->deinitializeNestedSwiftPassInvocation();
|
|
}
|
|
}
|
|
|
|
} // end anonymous namespace
|
|
|
|
SILTransform *swift::createSILGenCleanup() { return new SILGenCleanup(); }
|