mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
This is the first in a series of patches that reworks EscapeAnalysis. For this patch, I extracted every change that does not introduce new features, rewrite logic, or appear to change functionality. These cleanups were done in preparation for: - adding a graph representation for reference counted objects - rewriting parts to the query logic - ...which then allows the analysis to safely assume that all exclusive arguments are unique - ...which then allows more aggressive optimization of local variables that don't escape There are two possible functional changes: 1. getUnderlyingAddressRoot in InstructionUtils now sees through OSSA instructions: begin_borrow and copy_value 2. The getPointerBase helper in EscapeAnalysis now sees through all of these reference and pointer casts: + case ValueKind::UncheckedRefCastInst: + case ValueKind::ConvertFunctionInst: + case ValueKind::UpcastInst: + case ValueKind::InitExistentialRefInst: + case ValueKind::OpenExistentialRefInst: + case ValueKind::RawPointerToRefInst: + case ValueKind::RefToRawPointerInst: + case ValueKind::RefToBridgeObjectInst: + case ValueKind::BridgeObjectToRefInst: + case ValueKind::UncheckedAddrCastInst: + case ValueKind::UnconditionalCheckedCastInst: + case ValueKind::RefTo##Name##Inst: + case ValueKind::Name##ToRefInst: This coalesces a whole bunch of nodes together that were just there because of casts. The existing code was already doing this for one level of casts, but there was a bug that prevented it from happening transitively. So, in theory, anything that breaks with this fix could also break without this fix, but may not have been exposed. The fact that this analysis coalesces address-to-reference casts at all is what caused me to spent vast amounts of time debugging any time I tried to force some structure on the graph via assertions. If it is done at all, it should be done everywhere consistently to expose issues as early as possible. Here is a description of the changes in diff order. If something in the diff is not listed here, then the code probably just moved around in the file: Rename isNotAliasedIndirectParameter to isExclusiveIndirectParameter. The argument may be aliased in the caller's scope and it's contents may have already escaped. Add comments to SILType APIs (isTrivial, isReferenceCounted) that give answers about the AST type which don't really make sense for address SILTypes. Add comments about CGNode's 'Value' field. I spent lots of time attempting to tighten this down with asserts, but it's only possible for non-content nodes. For content nodes, the node's value is highly unpredictable and basically nonsense but needed for debugging. Add comments about not assuming that the content nodes pointsTo edge represents physical indirection. This matters when reasoning about aliasing and it's a tempting assumption to make. Add a CGNode::mergeProperties placeholder for adding node properties. Factor out a CGNode::canAddDeferred helper for use later. Rename `setPointsTo` to `setPointsToEdge` because it actually creates an edge rather than just setting `pointsTo`. Add CGNode::getValue() and related helpers to help maintain invariants. Factor out a `markEscaping` helper. Clean up the `escapesInsideFunction` helper. Add node visitor helpers: visitSuccessors, visitDefers. This made is much easier to prototype utilities. Add comments to clarify the `pointsTo` invariant. In particular, an entire defer web may have a null pointsTo for a while. Add an `activeWorklist` to avoid nasty bugs when composing multiple helpers that each use the worklist. Remove the `EA` argument from `getNode`. I ended up needing access to the `EA` context from the ConnectionGraph many times during prototyping and passing `this` was all the `getNode` calls was very silly. Add graph visitor helpers: backwardTraverse, forwardTraverseDefer, forwardTraversePointsToEdges, and mayReach for ease in developing new logic and utilities. Add isExclusiveArgument helper and distinguish exclusive arguments from local objects. Confusing these properties could lead to scary bugs. For example, unlike a local object, an exclusive argument's contents may still escape even when the content's connection graph node is non-escaping! Add isUniquelyIdentified helper when we want to treat exclusive arguments and local objects uniformly. getUnderlyingAddressRoot now looks through OSSA instructions. Rename `getAccessedMemory` to `getDirectlyAccessedMemory` with comments. This is another dangerous API because it assumes the memory access to a given address won't touch memory represented by different graph nodes, but graph edges don't necessarily represent physical indirection. Further clarify this issue in comments in AliasAnalysis.cpp. Factor out a 'findRecursiveRefType' helper from the old 'mayContainReference' for checking whether types may or must contain references. Support both kinds of queries so the analysis can be certain when a pointer's content is a physical heap object. Factor out 'getPointerBase' and 'getPointerRoot' helpers that follow address projections within what EscapeAnalysis will consider a single node. Create a CGNodeWorklist abstraction to safely formalize the worklist mechanism that's used all over the place. In one place, there were even two separate independent lists used independently (nodes added to one list could appear to be in the other list). The CGNodeMap abstraction did not significantly change, it was just moved. Added 'dumpCG' for dumping .dot files making it possible to remote debug. Added '-escapes-enable-graphwriter' option to dump .dot files, since they are so much more useful than the textual dump of the connection graph, which lacks node identity!
393 lines
13 KiB
C++
393 lines
13 KiB
C++
//===--- ValueTracking.cpp - SIL Value Tracking Analysis ------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define DEBUG_TYPE "sil-value-tracking"
|
|
#include "swift/SILOptimizer/Analysis/ValueTracking.h"
|
|
#include "swift/SIL/InstructionUtils.h"
|
|
#include "swift/SIL/PatternMatch.h"
|
|
#include "swift/SIL/SILArgument.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILValue.h"
|
|
#include "swift/SILOptimizer/Analysis/SimplifyInstruction.h"
|
|
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
|
|
#include "llvm/Support/Debug.h"
|
|
using namespace swift;
|
|
using namespace swift::PatternMatch;
|
|
|
|
bool swift::isExclusiveArgument(SILValue V) {
|
|
auto *Arg = dyn_cast<SILFunctionArgument>(V);
|
|
if (!Arg)
|
|
return false;
|
|
|
|
SILArgumentConvention Conv = Arg->getArgumentConvention();
|
|
return Conv.isExclusiveIndirectParameter();
|
|
}
|
|
|
|
/// Check if the parameter \V is based on a local object, e.g. it is an
|
|
/// allocation instruction or a struct/tuple constructed from the local objects.
|
|
/// Returns a found local object. If a local object was not found, returns an
|
|
/// empty SILValue.
|
|
static bool isLocalObject(SILValue Obj) {
|
|
// Set of values to be checked for their locality.
|
|
SmallVector<SILValue, 8> WorkList;
|
|
// Set of processed values.
|
|
llvm::SmallPtrSet<SILValue, 8> Processed;
|
|
WorkList.push_back(Obj);
|
|
|
|
while (!WorkList.empty()) {
|
|
auto V = WorkList.pop_back_val();
|
|
if (!V)
|
|
return false;
|
|
if (Processed.count(V))
|
|
continue;
|
|
Processed.insert(V);
|
|
// It should be a local object.
|
|
V = getUnderlyingObject(V);
|
|
if (isa<AllocationInst>(V))
|
|
continue;
|
|
if (isa<StructInst>(V) || isa<TupleInst>(V) || isa<EnumInst>(V)) {
|
|
// A compound value is local, if all of its components are local.
|
|
for (auto &Op : cast<SingleValueInstruction>(V)->getAllOperands()) {
|
|
WorkList.push_back(Op.get());
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (auto *Arg = dyn_cast<SILPhiArgument>(V)) {
|
|
// A BB argument is local if all of its
|
|
// incoming values are local.
|
|
SmallVector<SILValue, 4> IncomingValues;
|
|
if (Arg->getSingleTerminatorOperands(IncomingValues)) {
|
|
for (auto InValue : IncomingValues) {
|
|
WorkList.push_back(InValue);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Everything else is considered to be non-local.
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool swift::pointsToLocalObject(SILValue V) {
|
|
return isLocalObject(getUnderlyingObject(V));
|
|
}
|
|
|
|
/// Check if the value \p Value is known to be zero, non-zero or unknown.
|
|
IsZeroKind swift::isZeroValue(SILValue Value) {
|
|
// Inspect integer literals.
|
|
if (auto *L = dyn_cast<IntegerLiteralInst>(Value)) {
|
|
if (!L->getValue())
|
|
return IsZeroKind::Zero;
|
|
return IsZeroKind::NotZero;
|
|
}
|
|
|
|
// Inspect Structs.
|
|
switch (Value->getKind()) {
|
|
// Bitcast of zero is zero.
|
|
case ValueKind::UncheckedTrivialBitCastInst:
|
|
// Extracting from a zero class returns a zero.
|
|
case ValueKind::StructExtractInst:
|
|
return isZeroValue(cast<SingleValueInstruction>(Value)->getOperand(0));
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Inspect casts.
|
|
if (auto *BI = dyn_cast<BuiltinInst>(Value)) {
|
|
switch (BI->getBuiltinInfo().ID) {
|
|
case BuiltinValueKind::IntToPtr:
|
|
case BuiltinValueKind::PtrToInt:
|
|
case BuiltinValueKind::ZExt:
|
|
return isZeroValue(BI->getArguments()[0]);
|
|
case BuiltinValueKind::UDiv:
|
|
case BuiltinValueKind::SDiv: {
|
|
if (IsZeroKind::Zero == isZeroValue(BI->getArguments()[0]))
|
|
return IsZeroKind::Zero;
|
|
return IsZeroKind::Unknown;
|
|
}
|
|
case BuiltinValueKind::Mul:
|
|
case BuiltinValueKind::SMulOver:
|
|
case BuiltinValueKind::UMulOver: {
|
|
IsZeroKind LHS = isZeroValue(BI->getArguments()[0]);
|
|
IsZeroKind RHS = isZeroValue(BI->getArguments()[1]);
|
|
if (LHS == IsZeroKind::Zero || RHS == IsZeroKind::Zero)
|
|
return IsZeroKind::Zero;
|
|
|
|
return IsZeroKind::Unknown;
|
|
}
|
|
default:
|
|
return IsZeroKind::Unknown;
|
|
}
|
|
}
|
|
|
|
// Handle results of XXX_with_overflow arithmetic.
|
|
if (auto *T = dyn_cast<TupleExtractInst>(Value)) {
|
|
// Make sure we are extracting the number value and not
|
|
// the overflow flag.
|
|
if (T->getFieldNo() != 0)
|
|
return IsZeroKind::Unknown;
|
|
|
|
auto *BI = dyn_cast<BuiltinInst>(T->getOperand());
|
|
if (!BI)
|
|
return IsZeroKind::Unknown;
|
|
|
|
return isZeroValue(BI);
|
|
}
|
|
|
|
//Inspect allocations and pointer literals.
|
|
if (isa<StringLiteralInst>(Value) ||
|
|
isa<AllocationInst>(Value) ||
|
|
isa<GlobalAddrInst>(Value))
|
|
return IsZeroKind::NotZero;
|
|
|
|
return IsZeroKind::Unknown;
|
|
}
|
|
|
|
/// Check if the sign bit of the value \p V is known to be:
|
|
/// set (true), not set (false) or unknown (None).
|
|
Optional<bool> swift::computeSignBit(SILValue V) {
|
|
SILValue Value = V;
|
|
while (true) {
|
|
ValueBase *Def = Value;
|
|
// Inspect integer literals.
|
|
if (auto *L = dyn_cast<IntegerLiteralInst>(Def)) {
|
|
if (L->getValue().isNonNegative())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
switch (Def->getKind()) {
|
|
// Bitcast of non-negative is non-negative
|
|
case ValueKind::UncheckedTrivialBitCastInst:
|
|
Value = cast<UncheckedTrivialBitCastInst>(Def)->getOperand();
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (auto *BI = dyn_cast<BuiltinInst>(Def)) {
|
|
switch (BI->getBuiltinInfo().ID) {
|
|
// Sizeof always returns non-negative results.
|
|
case BuiltinValueKind::Sizeof:
|
|
return false;
|
|
// Strideof always returns non-negative results.
|
|
case BuiltinValueKind::Strideof:
|
|
return false;
|
|
// Alignof always returns non-negative results.
|
|
case BuiltinValueKind::Alignof:
|
|
return false;
|
|
// Both operands to AND must have the top bit set for V to.
|
|
case BuiltinValueKind::And: {
|
|
// Compute the sign bit of the LHS and RHS.
|
|
auto Left = computeSignBit(BI->getArguments()[0]);
|
|
auto Right = computeSignBit(BI->getArguments()[1]);
|
|
|
|
// We don't know either's sign bit so we can't
|
|
// say anything about the result.
|
|
if (!Left && !Right) {
|
|
return None;
|
|
}
|
|
|
|
// Now we know that we were able to determine the sign bit
|
|
// for at least one of Left/Right. Canonicalize the determined
|
|
// sign bit on the left.
|
|
if (Right) {
|
|
std::swap(Left, Right);
|
|
}
|
|
|
|
// We know we must have at least one result and it must be on
|
|
// the Left. If Right is still not None, then get both values
|
|
// and AND them together.
|
|
if (Right) {
|
|
return Left.getValue() && Right.getValue();
|
|
}
|
|
|
|
// Now we know that Right is None and Left has a value. If
|
|
// Left's value is true, then we return None as the final
|
|
// sign bit depends on the unknown Right value.
|
|
if (Left.getValue()) {
|
|
return None;
|
|
}
|
|
|
|
// Otherwise, Left must be false and false AND'd with anything
|
|
// else yields false.
|
|
return false;
|
|
}
|
|
// At least one operand to OR must have the top bit set.
|
|
case BuiltinValueKind::Or: {
|
|
// Compute the sign bit of the LHS and RHS.
|
|
auto Left = computeSignBit(BI->getArguments()[0]);
|
|
auto Right = computeSignBit(BI->getArguments()[1]);
|
|
|
|
// We don't know either's sign bit so we can't
|
|
// say anything about the result.
|
|
if (!Left && !Right) {
|
|
return None;
|
|
}
|
|
|
|
// Now we know that we were able to determine the sign bit
|
|
// for at least one of Left/Right. Canonicalize the determined
|
|
// sign bit on the left.
|
|
if (Right) {
|
|
std::swap(Left, Right);
|
|
}
|
|
|
|
// We know we must have at least one result and it must be on
|
|
// the Left. If Right is still not None, then get both values
|
|
// and OR them together.
|
|
if (Right) {
|
|
return Left.getValue() || Right.getValue();
|
|
}
|
|
|
|
// Now we know that Right is None and Left has a value. If
|
|
// Left's value is false, then we return None as the final
|
|
// sign bit depends on the unknown Right value.
|
|
if (!Left.getValue()) {
|
|
return None;
|
|
}
|
|
|
|
// Otherwise, Left must be true and true OR'd with anything
|
|
// else yields true.
|
|
return true;
|
|
}
|
|
// Only one of the operands to XOR must have the top bit set.
|
|
case BuiltinValueKind::Xor: {
|
|
// Compute the sign bit of the LHS and RHS.
|
|
auto Left = computeSignBit(BI->getArguments()[0]);
|
|
auto Right = computeSignBit(BI->getArguments()[1]);
|
|
|
|
// If either Left or Right is unknown then we can't say
|
|
// anything about the sign of the final result since
|
|
// XOR does not short-circuit.
|
|
if (!Left || !Right) {
|
|
return None;
|
|
}
|
|
|
|
// Now we know that both Left and Right must have a value.
|
|
// For the sign of the final result to be set, only one
|
|
// of Left or Right should be true.
|
|
return Left.getValue() != Right.getValue();
|
|
}
|
|
case BuiltinValueKind::LShr: {
|
|
// If count is provably >= 1, then top bit is not set.
|
|
auto *ILShiftCount = dyn_cast<IntegerLiteralInst>(BI->getArguments()[1]);
|
|
if (ILShiftCount) {
|
|
if (ILShiftCount->getValue().isStrictlyPositive()) {
|
|
return false;
|
|
}
|
|
}
|
|
// May be top bit is not set in the value being shifted.
|
|
Value = BI->getArguments()[0];
|
|
continue;
|
|
}
|
|
|
|
// Sign bit of the operand is promoted.
|
|
case BuiltinValueKind::SExt:
|
|
Value = BI->getArguments()[0];
|
|
continue;
|
|
|
|
// Source type is always smaller than the target type.
|
|
// Therefore the sign bit of a result is always 0.
|
|
case BuiltinValueKind::ZExt:
|
|
return false;
|
|
|
|
// Sign bit of the operand is promoted.
|
|
case BuiltinValueKind::SExtOrBitCast:
|
|
Value = BI->getArguments()[0];
|
|
continue;
|
|
|
|
// TODO: If source type size is smaller than the target type
|
|
// the result will be always false.
|
|
case BuiltinValueKind::ZExtOrBitCast:
|
|
Value = BI->getArguments()[0];
|
|
continue;
|
|
|
|
// Inspect casts.
|
|
case BuiltinValueKind::IntToPtr:
|
|
case BuiltinValueKind::PtrToInt:
|
|
Value = BI->getArguments()[0];
|
|
continue;
|
|
default:
|
|
return None;
|
|
}
|
|
}
|
|
|
|
return None;
|
|
}
|
|
}
|
|
|
|
/// Check if a checked trunc instruction can overflow.
|
|
/// Returns false if it can be proven that no overflow can happen.
|
|
/// Otherwise returns true.
|
|
static bool checkTruncOverflow(BuiltinInst *BI) {
|
|
SILValue Left, Right;
|
|
if (match(BI, m_CheckedTrunc(m_And(m_SILValue(Left),
|
|
m_SILValue(Right))))) {
|
|
// [US]ToSCheckedTrunc(And(x, mask)) cannot overflow
|
|
// if mask has the following properties:
|
|
// Only the first (N-1) bits are allowed to be set, where N is the width
|
|
// of the trunc result type.
|
|
//
|
|
// [US]ToUCheckedTrunc(And(x, mask)) cannot overflow
|
|
// if mask has the following properties:
|
|
// Only the first N bits are allowed to be set, where N is the width
|
|
// of the trunc result type.
|
|
if (auto BITy = BI->getType().
|
|
getTupleElementType(0).
|
|
getAs<BuiltinIntegerType>()) {
|
|
unsigned Width = BITy->getFixedWidth();
|
|
|
|
switch (BI->getBuiltinInfo().ID) {
|
|
case BuiltinValueKind::SToSCheckedTrunc:
|
|
case BuiltinValueKind::UToSCheckedTrunc:
|
|
// If it is a trunc to a signed value
|
|
// then sign bit should not be set to avoid overflows.
|
|
--Width;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (auto *ILLeft = dyn_cast<IntegerLiteralInst>(Left)) {
|
|
APInt Value = ILLeft->getValue();
|
|
if (Value.isIntN(Width)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (auto *ILRight = dyn_cast<IntegerLiteralInst>(Right)) {
|
|
APInt Value = ILRight->getValue();
|
|
if (Value.isIntN(Width)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Check if execution of a given Apply instruction can result in overflows.
|
|
/// Returns true if an overflow can happen. Otherwise returns false.
|
|
bool swift::canOverflow(BuiltinInst *BI) {
|
|
if (simplifyOverflowBuiltinInstruction(BI) != SILValue())
|
|
return false;
|
|
|
|
if (!checkTruncOverflow(BI))
|
|
return false;
|
|
|
|
// Conservatively assume that an overflow can happen
|
|
return true;
|
|
}
|