blob: 8ba5fcdb4ce471814916a87fbfa1feffdfb016d2 [file] [log] [blame]
//===--- Cleanup.cpp - Implements the Cleanup mechanics -------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
//
//===----------------------------------------------------------------------===//
#include "Cleanup.h"
#include "ManagedValue.h"
#include "RValue.h"
#include "Scope.h"
#include "SILGenBuilder.h"
#include "SILGenFunction.h"
using namespace swift;
using namespace Lowering;
//===----------------------------------------------------------------------===//
// CleanupState
//===----------------------------------------------------------------------===//
llvm::raw_ostream &Lowering::operator<<(llvm::raw_ostream &os,
CleanupState state) {
switch (state) {
case CleanupState::Dormant:
return os << "Dormant";
case CleanupState::Dead:
return os << "Dead";
case CleanupState::Active:
return os << "Active";
case CleanupState::PersistentlyActive:
return os << "PersistentlyActive";
}
llvm_unreachable("Unhandled CleanupState in switch.");
}
//===----------------------------------------------------------------------===//
// CleanupManager
//===----------------------------------------------------------------------===//
/// Are there any active cleanups in the given range?
static bool hasAnyActiveCleanups(DiverseStackImpl<Cleanup>::iterator begin,
DiverseStackImpl<Cleanup>::iterator end) {
for (; begin != end; ++begin)
if (begin->isActive())
return true;
return false;
}
void CleanupManager::popTopDeadCleanups() {
auto end = (innermostScope ? innermostScope->depth : stack.stable_end());
assert(end.isValid());
stack.checkIterator(end);
while (stack.stable_begin() != end && stack.begin()->isDead()) {
assert(!stack.empty());
stack.pop();
stack.checkIterator(end);
}
}
using CleanupBuffer = DiverseValueBuffer<Cleanup>;
void CleanupManager::popAndEmitCleanup(CleanupHandle handle,
CleanupLocation loc,
ForUnwind_t forUnwind) {
auto iter = stack.find(handle);
Cleanup &stackCleanup = *iter;
// Copy the cleanup off the cleanup stack.
CleanupBuffer buffer(stackCleanup);
Cleanup &cleanup = buffer.getCopy();
// Deactivate it.
forwardCleanup(handle);
// Emit the cleanup.
cleanup.emit(SGF, loc, forUnwind);
}
void CleanupManager::emitCleanups(CleanupsDepth depth, CleanupLocation loc,
ForUnwind_t forUnwind, bool popCleanups) {
auto cur = stack.stable_begin();
#ifndef NDEBUG
auto topOfStack = cur;
#endif
while (cur != depth) {
// Advance the iterator.
auto cleanupHandle = cur;
auto iter = stack.find(cleanupHandle);
Cleanup &stackCleanup = *iter;
cur = stack.stabilize(++iter);
// Copy the cleanup off the stack if it needs to be emitted.
// This is necessary both because we might need to pop the cleanup and
// because the cleanup might push other cleanups that will invalidate
// references onto the stack.
Optional<CleanupBuffer> copiedCleanup;
if (stackCleanup.isActive() && SGF.B.hasValidInsertionPoint()) {
copiedCleanup.emplace(stackCleanup);
}
// Pop now if that was requested.
if (popCleanups) {
#ifndef NDEBUG
// This is an implicit deactivation.
if (stackCleanup.isActive()) {
SGF.FormalEvalContext.checkCleanupDeactivation(cleanupHandle);
}
#endif
stack.pop();
#ifndef NDEBUG
topOfStack = stack.stable_begin();
#endif
}
// Emit the cleanup.
if (copiedCleanup) {
copiedCleanup->getCopy().emit(SGF, loc, forUnwind);
#ifndef NDEBUG
if (hasAnyActiveCleanups(stack.stable_begin(), topOfStack)) {
copiedCleanup->getCopy().dump(SGF);
llvm_unreachable("cleanup left active cleanups on stack");
}
#endif
}
stack.checkIterator(cur);
}
}
/// Leave a scope, emitting all the cleanups that are currently active.
void CleanupManager::endScope(CleanupsDepth depth, CleanupLocation loc) {
emitCleanups(depth, loc, NotForUnwind, /*popCleanups*/ true);
}
bool CleanupManager::hasAnyActiveCleanups(CleanupsDepth from,
CleanupsDepth to) {
return ::hasAnyActiveCleanups(stack.find(from), stack.find(to));
}
bool CleanupManager::hasAnyActiveCleanups(CleanupsDepth from) {
return ::hasAnyActiveCleanups(stack.begin(), stack.find(from));
}
/// emitBranchAndCleanups - Emit a branch to the given jump destination,
/// threading out through any cleanups we might need to run. This does not
/// pop the cleanup stack.
void CleanupManager::emitBranchAndCleanups(JumpDest dest, SILLocation branchLoc,
ArrayRef<SILValue> args,
ForUnwind_t forUnwind) {
SILGenBuilder &builder = SGF.getBuilder();
assert(builder.hasValidInsertionPoint() && "Emitting branch in invalid spot");
emitCleanups(dest.getDepth(), dest.getCleanupLocation(),
forUnwind, /*popCleanups=*/false);
builder.createBranch(branchLoc, dest.getBlock(), args);
}
void CleanupManager::emitCleanupsForReturn(CleanupLocation loc,
ForUnwind_t forUnwind) {
SILGenBuilder &builder = SGF.getBuilder();
assert(builder.hasValidInsertionPoint() && "Emitting return in invalid spot");
(void)builder;
emitCleanups(stack.stable_end(), loc, forUnwind, /*popCleanups=*/false);
}
/// Emit a new block that jumps to the specified location and runs necessary
/// cleanups based on its level. Emit a block even if there are no cleanups;
/// this is usually the destination of a conditional branch, so jumping
/// straight to `dest` creates a critical edge.
SILBasicBlock *CleanupManager::emitBlockForCleanups(JumpDest dest,
SILLocation branchLoc,
ArrayRef<SILValue> args,
ForUnwind_t forUnwind) {
// Otherwise, create and emit a new block.
auto *newBlock = SGF.createBasicBlock();
SILGenSavedInsertionPoint IPRAII(SGF, newBlock);
emitBranchAndCleanups(dest, branchLoc, args, forUnwind);
return newBlock;
}
Cleanup &CleanupManager::initCleanup(Cleanup &cleanup,
size_t allocSize,
CleanupState state) {
cleanup.allocatedSize = allocSize;
cleanup.state = state;
return cleanup;
}
#ifndef NDEBUG
static void checkCleanupDeactivation(SILGenFunction &SGF,
CleanupsDepth handle,
CleanupState state) {
if (state != CleanupState::Dead) return;
SGF.FormalEvalContext.checkCleanupDeactivation(handle);
}
#endif
void CleanupManager::setCleanupState(CleanupsDepth depth, CleanupState state) {
auto iter = stack.find(depth);
assert(iter != stack.end() && "can't change end of cleanups stack");
setCleanupState(*iter, state);
#ifndef NDEBUG
// This must be done after setting the state because setting the state can
// itself finish a formal evaluation in some cases.
checkCleanupDeactivation(SGF, depth, state);
#endif
if (state == CleanupState::Dead && iter == stack.begin())
popTopDeadCleanups();
}
void CleanupManager::forwardCleanup(CleanupsDepth handle) {
auto iter = stack.find(handle);
assert(iter != stack.end() && "can't change end of cleanups stack");
Cleanup &cleanup = *iter;
assert(cleanup.isActive() && "forwarding inactive or dead cleanup?");
CleanupState newState = (cleanup.getState() == CleanupState::Active
? CleanupState::Dead
: CleanupState::Dormant);
setCleanupState(cleanup, newState);
#ifndef NDEBUG
// This must be done after setting the state because setting the state can
// itself finish a formal evaluation in some cases.
checkCleanupDeactivation(SGF, handle, newState);
#endif
if (newState == CleanupState::Dead && iter == stack.begin())
popTopDeadCleanups();
}
void CleanupManager::setCleanupState(Cleanup &cleanup, CleanupState state) {
assert(SGF.B.hasValidInsertionPoint() &&
"changing cleanup state at invalid IP");
// Do the transition now to avoid doing it in N places below.
CleanupState oldState = cleanup.getState();
(void)oldState;
cleanup.setState(SGF, state);
assert(state != oldState && "trivial cleanup state change");
assert(oldState != CleanupState::Dead && "changing state of dead cleanup");
// Our current cleanup emission logic, where we don't try to re-use
// cleanup emissions between various branches, doesn't require any
// code to be emitted at transition points.
}
void CleanupManager::dump() const {
#ifndef NDEBUG
auto begin = stack.stable_begin();
auto end = stack.stable_end();
while (begin != end) {
auto iter = stack.find(begin);
const Cleanup &stackCleanup = *iter;
llvm::errs() << "CLEANUP DEPTH: " << begin.getDepth() << "\n";
stackCleanup.dump(SGF);
begin = stack.stabilize(++iter);
stack.checkIterator(begin);
}
#endif
}
void CleanupManager::dump(CleanupHandle handle) const {
auto iter = stack.find(handle);
const Cleanup &stackCleanup = *iter;
llvm::errs() << "CLEANUP DEPTH: " << handle.getDepth() << "\n";
stackCleanup.dump(SGF);
}
void CleanupManager::checkIterator(CleanupHandle handle) const {
#ifndef NDEBUG
stack.checkIterator(handle);
#endif
}
//===----------------------------------------------------------------------===//
// CleanupStateRestorationScope
//===----------------------------------------------------------------------===//
void CleanupStateRestorationScope::pushCleanupState(CleanupHandle handle,
CleanupState newState) {
// Don't put the cleanup in a state we can't restore it from.
assert(newState != CleanupState::Dead && "cannot restore cleanup from death");
auto iter = cleanups.stack.find(handle);
assert(iter != cleanups.stack.end() && "can't change end of cleanups stack");
Cleanup &cleanup = *iter;
assert(cleanup.getState() != CleanupState::Dead &&
"changing state of dead cleanup");
CleanupState oldState = cleanup.getState();
cleanup.setState(cleanups.SGF, newState);
savedStates.push_back({handle, oldState});
}
void
CleanupStateRestorationScope::pushCurrentCleanupState(CleanupHandle handle) {
auto iter = cleanups.stack.find(handle);
assert(iter != cleanups.stack.end() && "can't change end of cleanups stack");
Cleanup &cleanup = *iter;
assert(cleanup.getState() != CleanupState::Dead &&
"changing state of dead cleanup");
CleanupState oldState = cleanup.getState();
savedStates.push_back({handle, oldState});
}
void CleanupStateRestorationScope::popImpl() {
// Restore cleanup states in the opposite order in which we saved them.
for (auto i = savedStates.rbegin(), e = savedStates.rend(); i != e; ++i) {
CleanupHandle handle = i->first;
CleanupState stateToRestore = i->second;
auto iter = cleanups.stack.find(handle);
assert(iter != cleanups.stack.end() &&
"can't change end of cleanups stack");
Cleanup &cleanup = *iter;
assert(cleanup.getState() != CleanupState::Dead &&
"changing state of dead cleanup");
cleanup.setState(cleanups.SGF, stateToRestore);
}
savedStates.clear();
}
void CleanupStateRestorationScope::pop() && { popImpl(); }
//===----------------------------------------------------------------------===//
// Cleanup Cloner
//===----------------------------------------------------------------------===//
CleanupCloner::CleanupCloner(SILGenFunction &SGF, const ManagedValue &mv)
: SGF(SGF), hasCleanup(mv.hasCleanup()), isLValue(mv.isLValue()) {}
CleanupCloner::CleanupCloner(SILGenBuilder &builder, const ManagedValue &mv)
: CleanupCloner(builder.getSILGenFunction(), mv) {}
CleanupCloner::CleanupCloner(SILGenFunction &SGF, const RValue &rv)
: SGF(SGF), hasCleanup(rv.isPlusOne(SGF)), isLValue(false) {}
CleanupCloner::CleanupCloner(SILGenBuilder &builder, const RValue &rv)
: CleanupCloner(builder.getSILGenFunction(), rv) {}
ManagedValue CleanupCloner::clone(SILValue value) const {
if (isLValue) {
return ManagedValue::forLValue(value);
}
if (!hasCleanup) {
return ManagedValue::forUnmanaged(value);
}
if (value->getType().isAddress()) {
return SGF.emitManagedBufferWithCleanup(value);
}
return SGF.emitManagedRValueWithCleanup(value);
}