Files
swift-mirror/include/swift/SILOptimizer/Utils/ScopeOptUtils.h
Ben Barham ef8825bfe6 Migrate llvm::Optional to std::optional
LLVM has removed llvm::Optional, move over to std::optional. Also
clang-format to fix up all the renamed #includes.
2024-02-21 11:20:06 -08:00

284 lines
9.8 KiB
C++

//===--- ScopeOptUtils.h --------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2021 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
//
//===----------------------------------------------------------------------===//
///
/// A simple scope like construct for use in the SILOptimizer.
///
//===----------------------------------------------------------------------===//
#ifndef SWIFT_SILOPTIMIZER_UTILS_SCOPEOPTUTILS_H
#define SWIFT_SILOPTIMIZER_UTILS_SCOPEOPTUTILS_H
#include "swift/Basic/LLVM.h"
#include "swift/SIL/SILBuilder.h"
#include "swift/SIL/SILValue.h"
#include "llvm/ADT/SmallVector.h"
#include <functional>
#include <optional>
namespace swift {
namespace siloptimizer {
template <typename Result, typename... Args>
struct Cleanup {
std::function<Result(Args...)> func;
};
/// A simple stack data structure that manages a stack of std::function cleanups
/// that can be invalidated by index.
///
/// It is called asserting, since we force our user to explicitly pop the scope
/// and abort otherwise.
template <typename Result, typename... Args>
class AssertingScope {
using ScopeCleanup = Cleanup<Result, Args...>;
SmallVector<std::optional<ScopeCleanup>, 64> cleanups;
bool didPop = false;
protected:
~AssertingScope() { assert(didPop && "Did not pop scope?!"); }
AssertingScope() {}
/// Push a new SIL function based cleanup. Meant to be very
/// lightweight. Returns the index of the cleanup.
unsigned pushScope(std::function<Result(Args...)> func) {
assert(!didPop && "Can not reuse scope once used?!");
unsigned index = cleanups.size();
cleanups.emplace_back(ScopeCleanup{func});
return index;
}
/// Invalidate the cleanup at the specific index. Asserts on index too big and
/// on Optional has value.
void invalidateCleanup(unsigned cleanupIndex) {
assert(!didPop && "Should not invalidate once popped?!");
assert(cleanups[cleanupIndex].has_value());
cleanups[cleanupIndex] = std::nullopt;
}
/// Pop the scope, running all non-Optional cleanups, and setting didPop. Once
/// this is done the scope has been consumed and no longer has any state.
///
/// Can only be done once the scope has been moved.
void pop(Args... args) && {
while (cleanups.size()) {
auto val = cleanups.pop_back_val();
if (val) {
val->func(std::forward<Args>(args)...);
}
}
didPop = true;
}
/// Can not be called once the scope is moved.
void nonDestructivePop(Args... args) const {
for (auto val : llvm::reverse(cleanups)) {
if (val) {
val->func(std::forward<Args>(args)...);
}
}
}
};
class ScopedValue;
class SILOptScope;
/// Part of the SILRAIIScope interface that is known to ScopedValue.
class SILOptScopeBase : public AssertingScope<void, SILInstruction *> {
protected:
SILOptScopeBase() {}
public:
using Index = unsigned;
/// Destructive pop at end of scope.
void popAtEndOfScope(SILInstruction *insertPt) && {
std::move(*this).pop(insertPt);
}
/// Non-destructive pop along a control flow path that is serving as an early
/// exit.
void popAtEarlyExit(SILInstruction *insertPt) const {
nonDestructivePop(insertPt);
}
private:
friend class ScopedValue;
/// Invalidate the passed in scoped value destructively.
void invalidateValue(ScopedValue &&value);
};
/// A light weight wrapper around a SILValue that also contains information
/// about a Cleanup closure that ends the lifetime of the given value.
///
/// In order to make it really easy to access the underlying value, we define
/// operator * and operator-> as returning the underlying SILValue. It also
/// defines explicit operator bool() so one should be able to use if statements
/// to perform conditional initialization by returning an empty ScopedValue().
///
/// If one realizes that one does not actually need to cleanup a value, one can
/// destructively forward the ScopedValue by writing:
///
/// std::move(scopedValue).forward();
///
/// This will cause the cleanup in the SILOptScopeBase to be invalidated and
/// thus will not be emitted when the scope is popped.
class ScopedValue {
friend class SILOptScopeBase;
friend class SILOptScope;
/// A back pointer to the scope we are managed by so that we can invalidate
/// the cleanup associated with us. Also used to invalidate the underlying
/// ScopedValue by setting this to nullptr.
SILOptScopeBase *scope;
/// The index in our parent scope object of our cleanup. This is used to
/// enable invalidation.
std::optional<unsigned> scopeIndex;
/// The underlying SILValue that we are working with.
SILValue value;
/// A scoped value that has a cleanup associated with it at the index \p
/// scopeIndex within \p scope.
ScopedValue(SILOptScopeBase &scope, SILValue value, unsigned scopeIndex)
: scope(&scope), scopeIndex(scopeIndex), value(value) {}
/// A scoped value that does not have an associated cleanup in the scope.
ScopedValue(SILOptScopeBase &scope, SILValue value)
: scope(&scope), scopeIndex(std::nullopt), value(value) {}
public:
/// Default empty constructor. Should be used in combination with operator
/// bool() to perform conditional initialization.
ScopedValue() : scope(nullptr), scopeIndex(), value() {}
/// Explicit operator that says if this is a valid scoped Value.
///
/// Allows for conditional initialization via return value:
///
/// if (auto value = getScopedValue(...)) {
/// ...
/// }
explicit operator bool() const { return isValid(); }
/// Check just for the validity of the value, this doesn't
bool isValid() const { return scope && value; }
/// Returns true if this scoped value has a cleanup associated with it that
/// can be invalidated.
///
/// Example: Result of SILOptScope::copyValue would return true, but result of
/// a SILOptScope::borrowValue would return false since we do not allow for
/// borrows to be cancelled.
bool hasInvalidatableCleanup() const {
assert(isValid());
return bool(scopeIndex);
}
/// Operator to access internal SILValue.
SILValue operator*() const {
assert(isValid());
return value;
}
/// Operator to call methods on internal SILValue.
SILValue operator->() const {
assert(isValid());
return value;
}
/// If this scope has a scoped index, invalidate the scoped value so that its
/// cleanup is not emitted at end of scope.
void invalidate() && {
assert(isValid());
// If we have an invalidated cleanup, do the invalidation.
if (hasInvalidatableCleanup()) {
scope->invalidateValue(std::move(*this));
}
// No matter what we do, make it so that if this object is used again, we
// get an assert.
scope = nullptr;
scopeIndex.reset();
}
};
inline void SILOptScopeBase::invalidateValue(ScopedValue &&value) {
if (value.scopeIndex)
invalidateCleanup(*value.scopeIndex);
}
/// A scope like stack data structure that one can use to manage cleanups for
/// SILValues using light weight ScopedValue wrappers. This is done by using the
/// copyValue/borrowValue methods. These copy/borrow the value and then push a
/// destroy_value/end_borrow cleanup and return a ScopedValue containing that
/// the user can then manipulate the value without needing to worry about the
/// cleanup. If the user then decides a specific ScopedValue should be able to
/// be cancelled, we allow for the ScopedValue to be invalidated
class SILOptScope : public SILOptScopeBase {
public:
SILOptScope() {}
/// Create a destroy_value cleanup for \p value. Returns a scoped value for \p
/// value with a live cleanup.
ScopedValue pushDestroyValue(SILValue value) {
// We need to capture value by value, not by address.
auto func = std::function<void(SILInstruction *)>(
[innerValue = value](SILInstruction *insertPt) {
SILBuilderWithScope builder(insertPt);
builder.emitDestroyValue(RegularLocation::getAutoGeneratedLocation(),
innerValue);
});
return {*this, value, pushScope(func)};
}
/// Copy \p value at \p insertPt and push a destroy_value cleanup. Return the
/// new copied value as a ScopedValue with cleanup.
ScopedValue copyValue(SILInstruction *insertPt, SILValue value) {
SILValue copy = SILBuilderWithScope(insertPt).emitCopyValueOperation(
insertPt->getLoc(), value);
return pushDestroyValue(copy);
}
ScopedValue pushEndBorrow(SILValue value) {
// We need to capture value by value, not by address.
auto func = std::function<void(SILInstruction *)>(
[innerValue = value](SILInstruction *insertPt) {
SILBuilderWithScope builder(insertPt);
builder.emitEndBorrowOperation(
RegularLocation::getAutoGeneratedLocation(), innerValue);
});
// We do not return the index of our end_borrow since an end_borrow should
// never have its cleanup cancelled.
pushScope(func);
return {*this, value};
}
/// Borrow \p value at \p insertPt and push an end_borrow cleanup. Return the
/// new borrowed value as a ScopedValue without cleanup. The cleanup is not
/// returned since end_borrows should not be cancellable.
ScopedValue borrowValue(SILInstruction *insertPt, SILValue value) {
if (!insertPt->getFunction()->hasOwnership() ||
value->getOwnershipKind().isCompatibleWith(OwnershipKind::Guaranteed))
return {};
SILValue borrow = SILBuilderWithScope(insertPt).emitBeginBorrowOperation(
insertPt->getLoc(), value);
return pushEndBorrow(borrow);
}
};
} // namespace siloptimizer
} // namespace swift
#endif