mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
refactoring: Split MemoryLifetime.cpp/h into three separate files
And rename MemoryDataflow -> BitDataflow. MemoryLifetime contained MemoryLocations, MemoryDataflow and the MemoryLifetimeVerifier. Three independent things, for which it makes sense to have them in three separated files. NFC.
This commit is contained in:
146
include/swift/SIL/BitDataflow.h
Normal file
146
include/swift/SIL/BitDataflow.h
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
//===--- BitDataflow.h ------------------------------------------*- C++ -*-===//
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
///
|
||||||
|
/// \file Contains the BitDataflow utility for performing bit-wise dataflow
|
||||||
|
/// analysis on a SILFunction.
|
||||||
|
///
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
#ifndef SWIFT_SIL_BIT_DATAFLOW_H
|
||||||
|
#define SWIFT_SIL_BIT_DATAFLOW_H
|
||||||
|
|
||||||
|
#include "swift/SIL/BasicBlockData.h"
|
||||||
|
#include "llvm/ADT/SmallBitVector.h"
|
||||||
|
|
||||||
|
namespace swift {
|
||||||
|
|
||||||
|
class SILFunction;
|
||||||
|
|
||||||
|
/// A utility to calculate forward or backward dataflow of bit sets on a
|
||||||
|
/// SILFunction.
|
||||||
|
class BitDataflow {
|
||||||
|
|
||||||
|
/// What kind of terminators can be reached from a block.
|
||||||
|
enum class ExitReachability : uint8_t {
|
||||||
|
/// Worst case: the block is part of a cycle which neither reaches a
|
||||||
|
/// function-exit nor an unreachable-instruction.
|
||||||
|
InInfiniteLoop,
|
||||||
|
|
||||||
|
/// An unreachable-instruction can be reached from the block, but not a
|
||||||
|
/// function-exit (like "return" or "throw").
|
||||||
|
ReachesUnreachable,
|
||||||
|
|
||||||
|
/// A function-exit can be reached from the block.
|
||||||
|
/// This is the case for most basic blocks.
|
||||||
|
ReachesExit
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Bits = llvm::SmallBitVector;
|
||||||
|
|
||||||
|
/// Basic-block specific information used for dataflow analysis.
|
||||||
|
struct BlockState {
|
||||||
|
/// The bits valid at the entry (i.e. the first instruction) of the block.
|
||||||
|
Bits entrySet;
|
||||||
|
|
||||||
|
/// The bits valid at the exit (i.e. after the terminator) of the block.
|
||||||
|
Bits exitSet;
|
||||||
|
|
||||||
|
/// Generated bits of the block.
|
||||||
|
Bits genSet;
|
||||||
|
|
||||||
|
/// Killed bits of the block.
|
||||||
|
Bits killSet;
|
||||||
|
|
||||||
|
/// True, if this block is reachable from the entry block, i.e. is not an
|
||||||
|
/// unreachable block.
|
||||||
|
///
|
||||||
|
/// This flag is only computed if entryReachabilityAnalysis is called.
|
||||||
|
bool reachableFromEntry = false;
|
||||||
|
|
||||||
|
/// What kind of terminators can be reached from this block.
|
||||||
|
///
|
||||||
|
/// This is only computed if exitReachableAnalysis is called.
|
||||||
|
ExitReachability exitReachability = ExitReachability::InInfiniteLoop;
|
||||||
|
|
||||||
|
BlockState(unsigned numLocations) :
|
||||||
|
entrySet(numLocations), exitSet(numLocations),
|
||||||
|
genSet(numLocations), killSet(numLocations) {}
|
||||||
|
|
||||||
|
bool exitReachable() const {
|
||||||
|
return exitReachability == ExitReachability::ReachesExit;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInInfiniteLoop() const {
|
||||||
|
return exitReachability == ExitReachability::InInfiniteLoop;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
BasicBlockData<BlockState> blockStates;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
using iterator = BasicBlockData<BlockState>::iterator;
|
||||||
|
|
||||||
|
/// Sets up the BlockState datastructures and associates all basic blocks with
|
||||||
|
/// a state.
|
||||||
|
BitDataflow(SILFunction *function, unsigned numLocations);
|
||||||
|
|
||||||
|
BitDataflow(const BitDataflow &) = delete;
|
||||||
|
BitDataflow &operator=(const BitDataflow &) = delete;
|
||||||
|
|
||||||
|
iterator begin() { return blockStates.begin(); }
|
||||||
|
iterator end() { return blockStates.end(); }
|
||||||
|
|
||||||
|
/// Returns the state of a block.
|
||||||
|
BlockState &operator[] (SILBasicBlock *block) {
|
||||||
|
return blockStates[block];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the BlockState::reachableFromEntry flags.
|
||||||
|
void entryReachabilityAnalysis();
|
||||||
|
|
||||||
|
/// Calculates the BlockState::exitReachable flags.
|
||||||
|
void exitReachableAnalysis();
|
||||||
|
|
||||||
|
using JoinOperation = std::function<void (Bits &dest, const Bits &src)>;
|
||||||
|
|
||||||
|
/// Derives the block exit sets from the entry sets by applying the gen and
|
||||||
|
/// kill sets.
|
||||||
|
/// At control flow joins, the \p join operation is applied.
|
||||||
|
void solveForward(JoinOperation join);
|
||||||
|
|
||||||
|
/// Calls solveForward() with a bit-intersection as join operation.
|
||||||
|
void solveForwardWithIntersect();
|
||||||
|
|
||||||
|
/// Calls solveForward() with a bit-union as join operation.
|
||||||
|
void solveForwardWithUnion();
|
||||||
|
|
||||||
|
/// Derives the block entry sets from the exit sets by applying the gen and
|
||||||
|
/// kill sets.
|
||||||
|
/// At control flow joins, the \p join operation is applied.
|
||||||
|
void solveBackward(JoinOperation join);
|
||||||
|
|
||||||
|
/// Calls solveBackward() with a bit-intersection as join operation.
|
||||||
|
void solveBackwardWithIntersect();
|
||||||
|
|
||||||
|
/// Calls solveBackward() with a bit-union as join operation.
|
||||||
|
void solveBackwardWithUnion();
|
||||||
|
|
||||||
|
/// Debug dump the BitDataflow state.
|
||||||
|
void dump() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // end swift namespace
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
//===--- MemoryLifetime.h ---------------------------------------*- C++ -*-===//
|
//===--- MemoryLocations.h --------------------------------------*- C++ -*-===//
|
||||||
//
|
//
|
||||||
// This source file is part of the Swift.org open source project
|
// This source file is part of the Swift.org open source project
|
||||||
//
|
//
|
||||||
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
|
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
|
||||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||||
//
|
//
|
||||||
// See https://swift.org/LICENSE.txt for license information
|
// See https://swift.org/LICENSE.txt for license information
|
||||||
@@ -10,19 +10,25 @@
|
|||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
///
|
///
|
||||||
/// \file Contains utilities for calculating and verifying memory lifetime.
|
/// \file Contains the MemoryLocations utility for analyzing memory locations in
|
||||||
|
/// a SILFunction.
|
||||||
///
|
///
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
#ifndef SWIFT_SIL_MEMORY_LIFETIME_H
|
#ifndef SWIFT_SIL_MEMORY_LOCATIONS_H
|
||||||
#define SWIFT_SIL_MEMORY_LIFETIME_H
|
#define SWIFT_SIL_MEMORY_LOCATIONS_H
|
||||||
|
|
||||||
#include "swift/SIL/SILBasicBlock.h"
|
#include "swift/SIL/SILValue.h"
|
||||||
#include "swift/SIL/SILFunction.h"
|
#include "llvm/ADT/DenseMap.h"
|
||||||
#include "swift/SIL/BasicBlockData.h"
|
#include "llvm/ADT/SmallBitVector.h"
|
||||||
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
|
||||||
namespace swift {
|
namespace swift {
|
||||||
|
|
||||||
|
class SILFunction;
|
||||||
|
class SILBasicBlock;
|
||||||
|
class SingleValueInstruction;
|
||||||
|
|
||||||
void printBitsAsArray(llvm::raw_ostream &OS, const SmallBitVector &bits);
|
void printBitsAsArray(llvm::raw_ostream &OS, const SmallBitVector &bits);
|
||||||
|
|
||||||
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
|
inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
|
||||||
@@ -40,10 +46,6 @@ void dumpBits(const SmallBitVector &bits);
|
|||||||
/// Currently only a certain set of address instructions are supported:
|
/// Currently only a certain set of address instructions are supported:
|
||||||
/// Specifically those instructions which are going to be included when SIL
|
/// Specifically those instructions which are going to be included when SIL
|
||||||
/// supports opaque values.
|
/// supports opaque values.
|
||||||
/// TODO: Support more address instructions, like cast instructions.
|
|
||||||
///
|
|
||||||
/// The MemoryLocations works well together with MemoryDataflow, which can be
|
|
||||||
/// used to calculate global dataflow of location information.
|
|
||||||
class MemoryLocations {
|
class MemoryLocations {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@@ -115,10 +117,10 @@ public:
|
|||||||
/// sub-locations 3 and 4. But bit 0 is set in location 0 (the "self" bit),
|
/// sub-locations 3 and 4. But bit 0 is set in location 0 (the "self" bit),
|
||||||
/// because it represents the untracked field ``Outer.z``.
|
/// because it represents the untracked field ``Outer.z``.
|
||||||
///
|
///
|
||||||
/// Single-payload enums are represented by a location with a single sub-
|
/// Enums and existentials are represented by a location with a single sub-
|
||||||
/// location (the projected payload address, i.e. an ``init_enum_data_addr``
|
/// location (the projected payload/existential address, i.e. an
|
||||||
/// or an ``unchecked_take_enum_data_addr``.
|
/// ``init_enum_data_addr``, ``unchecked_take_enum_data_addr`` or
|
||||||
/// Multi-payload enums are not supported right now.
|
/// ``init_existential_addr``.
|
||||||
Bits subLocations;
|
Bits subLocations;
|
||||||
|
|
||||||
/// The accumulated parent bits, including the "self" bit.
|
/// The accumulated parent bits, including the "self" bit.
|
||||||
@@ -228,7 +230,7 @@ public:
|
|||||||
const Location *getRootLocation(unsigned index) const;
|
const Location *getRootLocation(unsigned index) const;
|
||||||
|
|
||||||
/// Registers an address projection instruction for a location.
|
/// Registers an address projection instruction for a location.
|
||||||
void registerProjection(SingleValueInstruction *projection, unsigned locIdx) {
|
void registerProjection(SILValue projection, unsigned locIdx) {
|
||||||
addr2LocIdx[projection] = locIdx;
|
addr2LocIdx[projection] = locIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,139 +315,6 @@ private:
|
|||||||
void initFieldsCounter(Location &loc);
|
void initFieldsCounter(Location &loc);
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The MemoryDataflow utility calculates global dataflow of memory locations.
|
|
||||||
///
|
|
||||||
/// The MemoryDataflow works well together with MemoryLocations, which can be
|
|
||||||
/// used to analyze locations as input to the dataflow.
|
|
||||||
/// TODO: Actuall this utility can be used for any kind of dataflow, not just
|
|
||||||
/// for memory locations. Consider renaming it.
|
|
||||||
class MemoryDataflow {
|
|
||||||
|
|
||||||
/// What kind of terminators can be reached from a block.
|
|
||||||
enum class ExitReachability : uint8_t {
|
|
||||||
/// Worst case: the block is part of a cycle which neither reaches a
|
|
||||||
/// function-exit nor an unreachable-instruction.
|
|
||||||
InInfiniteLoop,
|
|
||||||
|
|
||||||
/// An unreachable-instruction can be reached from the block, but not a
|
|
||||||
/// function-exit (like "return" or "throw").
|
|
||||||
ReachesUnreachable,
|
|
||||||
|
|
||||||
/// A function-exit can be reached from the block.
|
|
||||||
/// This is the case for most basic blocks.
|
|
||||||
ReachesExit
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
using Bits = MemoryLocations::Bits;
|
|
||||||
|
|
||||||
/// Basic-block specific information used for dataflow analysis.
|
|
||||||
struct BlockState {
|
|
||||||
/// The bits valid at the entry (i.e. the first instruction) of the block.
|
|
||||||
Bits entrySet;
|
|
||||||
|
|
||||||
/// The bits valid at the exit (i.e. after the terminator) of the block.
|
|
||||||
Bits exitSet;
|
|
||||||
|
|
||||||
/// Generated bits of the block.
|
|
||||||
Bits genSet;
|
|
||||||
|
|
||||||
/// Killed bits of the block.
|
|
||||||
Bits killSet;
|
|
||||||
|
|
||||||
/// True, if this block is reachable from the entry block, i.e. is not an
|
|
||||||
/// unreachable block.
|
|
||||||
///
|
|
||||||
/// This flag is only computed if entryReachabilityAnalysis is called.
|
|
||||||
bool reachableFromEntry = false;
|
|
||||||
|
|
||||||
/// What kind of terminators can be reached from this block.
|
|
||||||
///
|
|
||||||
/// This is only computed if exitReachableAnalysis is called.
|
|
||||||
ExitReachability exitReachability = ExitReachability::InInfiniteLoop;
|
|
||||||
|
|
||||||
BlockState(unsigned numLocations) :
|
|
||||||
entrySet(numLocations), exitSet(numLocations),
|
|
||||||
genSet(numLocations), killSet(numLocations) {}
|
|
||||||
|
|
||||||
// Utility functions for setting and clearing gen- and kill-bits.
|
|
||||||
|
|
||||||
void genBits(SILValue addr, const MemoryLocations &locs) {
|
|
||||||
locs.genBits(genSet, killSet, addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void killBits(SILValue addr, const MemoryLocations &locs) {
|
|
||||||
locs.killBits(genSet, killSet, addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool exitReachable() const {
|
|
||||||
return exitReachability == ExitReachability::ReachesExit;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isInInfiniteLoop() const {
|
|
||||||
return exitReachability == ExitReachability::InInfiniteLoop;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
BasicBlockData<BlockState> blockStates;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
using iterator = BasicBlockData<BlockState>::iterator;
|
|
||||||
|
|
||||||
/// Sets up the BlockState datastructures and associates all basic blocks with
|
|
||||||
/// a state.
|
|
||||||
MemoryDataflow(SILFunction *function, unsigned numLocations);
|
|
||||||
|
|
||||||
MemoryDataflow(const MemoryDataflow &) = delete;
|
|
||||||
MemoryDataflow &operator=(const MemoryDataflow &) = delete;
|
|
||||||
|
|
||||||
iterator begin() { return blockStates.begin(); }
|
|
||||||
iterator end() { return blockStates.end(); }
|
|
||||||
|
|
||||||
/// Returns the state of a block.
|
|
||||||
BlockState &operator[] (SILBasicBlock *block) {
|
|
||||||
return blockStates[block];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates the BlockState::reachableFromEntry flags.
|
|
||||||
void entryReachabilityAnalysis();
|
|
||||||
|
|
||||||
/// Calculates the BlockState::exitReachable flags.
|
|
||||||
void exitReachableAnalysis();
|
|
||||||
|
|
||||||
using JoinOperation = std::function<void (Bits &dest, const Bits &src)>;
|
|
||||||
|
|
||||||
/// Derives the block exit sets from the entry sets by applying the gen and
|
|
||||||
/// kill sets.
|
|
||||||
/// At control flow joins, the \p join operation is applied.
|
|
||||||
void solveForward(JoinOperation join);
|
|
||||||
|
|
||||||
/// Calls solveForward() with a bit-intersection as join operation.
|
|
||||||
void solveForwardWithIntersect();
|
|
||||||
|
|
||||||
/// Calls solveForward() with a bit-union as join operation.
|
|
||||||
void solveForwardWithUnion();
|
|
||||||
|
|
||||||
/// Derives the block entry sets from the exit sets by applying the gen and
|
|
||||||
/// kill sets.
|
|
||||||
/// At control flow joins, the \p join operation is applied.
|
|
||||||
void solveBackward(JoinOperation join);
|
|
||||||
|
|
||||||
/// Calls solveBackward() with a bit-intersection as join operation.
|
|
||||||
void solveBackwardWithIntersect();
|
|
||||||
|
|
||||||
/// Calls solveBackward() with a bit-union as join operation.
|
|
||||||
void solveBackwardWithUnion();
|
|
||||||
|
|
||||||
/// Debug dump the MemoryLifetime internals.
|
|
||||||
void dump() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Verifies the lifetime of memory locations in a function.
|
|
||||||
void verifyMemoryLifetime(SILFunction *function);
|
|
||||||
|
|
||||||
} // end swift namespace
|
} // end swift namespace
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1147,6 +1147,9 @@ public:
|
|||||||
/// invariants.
|
/// invariants.
|
||||||
void verify(bool SingleFunction = true) const;
|
void verify(bool SingleFunction = true) const;
|
||||||
|
|
||||||
|
/// Verifies the lifetime of memory locations in the function.
|
||||||
|
void verifyMemoryLifetime();
|
||||||
|
|
||||||
/// Run the SIL ownership verifier to check for ownership invariant failures.
|
/// Run the SIL ownership verifier to check for ownership invariant failures.
|
||||||
///
|
///
|
||||||
/// NOTE: The ownership verifier is always run when performing normal IR
|
/// NOTE: The ownership verifier is always run when performing normal IR
|
||||||
|
|||||||
151
lib/SIL/Utils/BitDataflow.cpp
Normal file
151
lib/SIL/Utils/BitDataflow.cpp
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
//===--- BitDataflow.cpp --------------------------------------------------===//
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
#define DEBUG_TYPE "bit-dataflow"
|
||||||
|
#include "swift/SIL/BitDataflow.h"
|
||||||
|
#include "swift/SIL/SILBasicBlock.h"
|
||||||
|
#include "swift/SIL/SILFunction.h"
|
||||||
|
#include "swift/SIL/MemoryLocations.h"
|
||||||
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
|
||||||
|
using namespace swift;
|
||||||
|
|
||||||
|
BitDataflow::BitDataflow(SILFunction *function, unsigned numLocations) :
|
||||||
|
blockStates(function, [numLocations](SILBasicBlock *block) {
|
||||||
|
return BlockState(numLocations);
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
void BitDataflow::entryReachabilityAnalysis() {
|
||||||
|
llvm::SmallVector<SILBasicBlock *, 16> workList;
|
||||||
|
auto entry = blockStates.entry();
|
||||||
|
entry.data.reachableFromEntry = true;
|
||||||
|
workList.push_back(&entry.block);
|
||||||
|
|
||||||
|
while (!workList.empty()) {
|
||||||
|
SILBasicBlock *block = workList.pop_back_val();
|
||||||
|
for (SILBasicBlock *succ : block->getSuccessorBlocks()) {
|
||||||
|
BlockState &succState = blockStates[succ];
|
||||||
|
if (!succState.reachableFromEntry) {
|
||||||
|
succState.reachableFromEntry = true;
|
||||||
|
workList.push_back(succ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitDataflow::exitReachableAnalysis() {
|
||||||
|
llvm::SmallVector<SILBasicBlock *, 16> workList;
|
||||||
|
for (auto bd : blockStates) {
|
||||||
|
if (bd.block.getTerminator()->isFunctionExiting()) {
|
||||||
|
bd.data.exitReachability = ExitReachability::ReachesExit;
|
||||||
|
workList.push_back(&bd.block);
|
||||||
|
} else if (isa<UnreachableInst>(bd.block.getTerminator())) {
|
||||||
|
bd.data.exitReachability = ExitReachability::ReachesUnreachable;
|
||||||
|
workList.push_back(&bd.block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!workList.empty()) {
|
||||||
|
SILBasicBlock *block = workList.pop_back_val();
|
||||||
|
BlockState &state = blockStates[block];
|
||||||
|
for (SILBasicBlock *pred : block->getPredecessorBlocks()) {
|
||||||
|
BlockState &predState = blockStates[pred];
|
||||||
|
if (predState.exitReachability < state.exitReachability) {
|
||||||
|
// As there are 3 states, each block can be put into the workList 2
|
||||||
|
// times maximum.
|
||||||
|
predState.exitReachability = state.exitReachability;
|
||||||
|
workList.push_back(pred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitDataflow::solveForward(JoinOperation join) {
|
||||||
|
// Pretty standard data flow solving.
|
||||||
|
bool changed = false;
|
||||||
|
bool firstRound = true;
|
||||||
|
do {
|
||||||
|
changed = false;
|
||||||
|
for (auto bd : blockStates) {
|
||||||
|
Bits bits = bd.data.entrySet;
|
||||||
|
assert(!bits.empty());
|
||||||
|
for (SILBasicBlock *pred : bd.block.getPredecessorBlocks()) {
|
||||||
|
join(bits, blockStates[pred].exitSet);
|
||||||
|
}
|
||||||
|
if (firstRound || bits != bd.data.entrySet) {
|
||||||
|
changed = true;
|
||||||
|
bd.data.entrySet = bits;
|
||||||
|
bits |= bd.data.genSet;
|
||||||
|
bits.reset(bd.data.killSet);
|
||||||
|
bd.data.exitSet = bits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
firstRound = false;
|
||||||
|
} while (changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitDataflow::solveForwardWithIntersect() {
|
||||||
|
solveForward([](Bits &entry, const Bits &predExit){
|
||||||
|
entry &= predExit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitDataflow::solveForwardWithUnion() {
|
||||||
|
solveForward([](Bits &entry, const Bits &predExit){
|
||||||
|
entry |= predExit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitDataflow::solveBackward(JoinOperation join) {
|
||||||
|
// Pretty standard data flow solving.
|
||||||
|
bool changed = false;
|
||||||
|
bool firstRound = true;
|
||||||
|
do {
|
||||||
|
changed = false;
|
||||||
|
for (auto bd : llvm::reverse(blockStates)) {
|
||||||
|
Bits bits = bd.data.exitSet;
|
||||||
|
assert(!bits.empty());
|
||||||
|
for (SILBasicBlock *succ : bd.block.getSuccessorBlocks()) {
|
||||||
|
join(bits, blockStates[succ].entrySet);
|
||||||
|
}
|
||||||
|
if (firstRound || bits != bd.data.exitSet) {
|
||||||
|
changed = true;
|
||||||
|
bd.data.exitSet = bits;
|
||||||
|
bits |= bd.data.genSet;
|
||||||
|
bits.reset(bd.data.killSet);
|
||||||
|
bd.data.entrySet = bits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
firstRound = false;
|
||||||
|
} while (changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitDataflow::solveBackwardWithIntersect() {
|
||||||
|
solveBackward([](Bits &entry, const Bits &predExit){
|
||||||
|
entry &= predExit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitDataflow::solveBackwardWithUnion() {
|
||||||
|
solveBackward([](Bits &entry, const Bits &predExit){
|
||||||
|
entry |= predExit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitDataflow::dump() const {
|
||||||
|
for (auto bd : blockStates) {
|
||||||
|
llvm::dbgs() << "bb" << bd.block.getDebugID() << ":\n"
|
||||||
|
<< " entry: " << bd.data.entrySet << '\n'
|
||||||
|
<< " gen: " << bd.data.genSet << '\n'
|
||||||
|
<< " kill: " << bd.data.killSet << '\n'
|
||||||
|
<< " exit: " << bd.data.exitSet << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
target_sources(swiftSIL PRIVATE
|
target_sources(swiftSIL PRIVATE
|
||||||
BasicBlockUtils.cpp
|
BasicBlockUtils.cpp
|
||||||
|
BitDataflow.cpp
|
||||||
DebugUtils.cpp
|
DebugUtils.cpp
|
||||||
Dominance.cpp
|
Dominance.cpp
|
||||||
DynamicCasts.cpp
|
DynamicCasts.cpp
|
||||||
@@ -7,6 +8,7 @@ target_sources(swiftSIL PRIVATE
|
|||||||
InstructionUtils.cpp
|
InstructionUtils.cpp
|
||||||
LoopInfo.cpp
|
LoopInfo.cpp
|
||||||
MemAccessUtils.cpp
|
MemAccessUtils.cpp
|
||||||
|
MemoryLocations.cpp
|
||||||
OptimizationRemark.cpp
|
OptimizationRemark.cpp
|
||||||
OwnershipUtils.cpp
|
OwnershipUtils.cpp
|
||||||
PrettyStackTrace.cpp
|
PrettyStackTrace.cpp
|
||||||
|
|||||||
469
lib/SIL/Utils/MemoryLocations.cpp
Normal file
469
lib/SIL/Utils/MemoryLocations.cpp
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
//===--- MemoryLocations.cpp ----------------------------------------------===//
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
#define DEBUG_TYPE "sil-memory-locations"
|
||||||
|
#include "swift/SIL/MemoryLocations.h"
|
||||||
|
#include "swift/SIL/SILBasicBlock.h"
|
||||||
|
#include "swift/SIL/SILFunction.h"
|
||||||
|
#include "swift/SIL/ApplySite.h"
|
||||||
|
#include "swift/SIL/SILModule.h"
|
||||||
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
|
||||||
|
using namespace swift;
|
||||||
|
|
||||||
|
/// Debug dump a location bit vector.
|
||||||
|
void swift::printBitsAsArray(llvm::raw_ostream &OS, const SmallBitVector &bits) {
|
||||||
|
const char *separator = "";
|
||||||
|
OS << '[';
|
||||||
|
for (int idx = bits.find_first(); idx >= 0; idx = bits.find_next(idx)) {
|
||||||
|
OS << separator << idx;
|
||||||
|
separator = ",";
|
||||||
|
}
|
||||||
|
OS << ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
void swift::dumpBits(const SmallBitVector &bits) {
|
||||||
|
llvm::dbgs() << bits << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace swift {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
// Utility functions
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
/// Enlarge the bitset if needed to set the bit with \p idx.
|
||||||
|
static void setBitAndResize(SmallBitVector &bits, unsigned idx) {
|
||||||
|
if (bits.size() <= idx)
|
||||||
|
bits.resize(idx + 1);
|
||||||
|
bits.set(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool allUsesInSameBlock(AllocStackInst *ASI) {
|
||||||
|
SILBasicBlock *BB = ASI->getParent();
|
||||||
|
int numDeallocStacks = 0;
|
||||||
|
for (Operand *use : ASI->getUses()) {
|
||||||
|
SILInstruction *user = use->getUser();
|
||||||
|
if (isa<DeallocStackInst>(user)) {
|
||||||
|
++numDeallocStacks;
|
||||||
|
if (user->getParent() != BB)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// In case of an unreachable, the dealloc_stack can be missing. In this
|
||||||
|
// case we don't treat it as a single-block location.
|
||||||
|
assert(numDeallocStacks <= 1 &&
|
||||||
|
"A single-block stack location cannot have multiple deallocations");
|
||||||
|
return numDeallocStacks == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool shouldTrackLocation(SILType ty, SILFunction *function) {
|
||||||
|
// Ignore empty tuples and empty structs.
|
||||||
|
if (auto tupleTy = ty.getAs<TupleType>()) {
|
||||||
|
return tupleTy->getNumElements() != 0;
|
||||||
|
}
|
||||||
|
if (StructDecl *decl = ty.getStructOrBoundGenericStruct()) {
|
||||||
|
return decl->getStoredProperties().size() != 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
} // namespace swift
|
||||||
|
|
||||||
|
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
// MemoryLocations members
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
MemoryLocations::Location::Location(SILValue val, unsigned index, int parentIdx) :
|
||||||
|
representativeValue(val),
|
||||||
|
parentIdx(parentIdx) {
|
||||||
|
assert(((parentIdx >= 0) ==
|
||||||
|
(isa<StructElementAddrInst>(val) || isa<TupleElementAddrInst>(val) ||
|
||||||
|
isa<InitEnumDataAddrInst>(val) || isa<UncheckedTakeEnumDataAddrInst>(val) ||
|
||||||
|
isa<InitExistentialAddrInst>(val) || isa<OpenExistentialAddrInst>(val)))
|
||||||
|
&& "sub-locations can only be introduced with struct/tuple/enum projections");
|
||||||
|
setBitAndResize(subLocations, index);
|
||||||
|
setBitAndResize(selfAndParents, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryLocations::Location::updateFieldCounters(SILType ty, int increment) {
|
||||||
|
SILFunction *function = representativeValue->getFunction();
|
||||||
|
if (shouldTrackLocation(ty, function)) {
|
||||||
|
numFieldsNotCoveredBySubfields += increment;
|
||||||
|
if (!ty.isTrivial(*function))
|
||||||
|
numNonTrivialFieldsNotCovered += increment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SILValue getBaseValue(SILValue addr) {
|
||||||
|
while (true) {
|
||||||
|
switch (addr->getKind()) {
|
||||||
|
case ValueKind::BeginAccessInst:
|
||||||
|
addr = cast<BeginAccessInst>(addr)->getOperand();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int MemoryLocations::getLocationIdx(SILValue addr) const {
|
||||||
|
auto iter = addr2LocIdx.find(getBaseValue(addr));
|
||||||
|
if (iter == addr2LocIdx.end())
|
||||||
|
return -1;
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MemoryLocations::Location *
|
||||||
|
MemoryLocations::getRootLocation(unsigned index) const {
|
||||||
|
while (true) {
|
||||||
|
const Location &loc = locations[index];
|
||||||
|
if (loc.parentIdx < 0)
|
||||||
|
return &loc;
|
||||||
|
index = loc.parentIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool canHandleAllocStack(AllocStackInst *asi) {
|
||||||
|
assert(asi);
|
||||||
|
|
||||||
|
// An alloc_stack with dynamic lifetime set has a lifetime that relies on
|
||||||
|
// unrelated conditional control flow for correctness. This means that we may
|
||||||
|
// statically leak along paths that were known by the emitter to never be
|
||||||
|
// taken if the value is live. So bail since we can't verify this.
|
||||||
|
if (asi->hasDynamicLifetime())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Otherwise we can optimize!
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryLocations::analyzeLocations(SILFunction *function) {
|
||||||
|
// As we have to limit the set of handled locations to memory, which is
|
||||||
|
// guaranteed to be not aliased, we currently only handle indirect function
|
||||||
|
// arguments and alloc_stack locations.
|
||||||
|
for (SILArgument *arg : function->getArguments()) {
|
||||||
|
SILFunctionArgument *funcArg = cast<SILFunctionArgument>(arg);
|
||||||
|
switch (funcArg->getArgumentConvention()) {
|
||||||
|
case SILArgumentConvention::Indirect_In:
|
||||||
|
case SILArgumentConvention::Indirect_In_Constant:
|
||||||
|
case SILArgumentConvention::Indirect_In_Guaranteed:
|
||||||
|
case SILArgumentConvention::Indirect_Out:
|
||||||
|
// These are not SIL addresses under -enable-sil-opaque-values
|
||||||
|
if (!function->getConventions().useLoweredAddresses())
|
||||||
|
break;
|
||||||
|
|
||||||
|
LLVM_FALLTHROUGH;
|
||||||
|
case SILArgumentConvention::Indirect_Inout:
|
||||||
|
analyzeLocation(funcArg);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (SILBasicBlock &BB : *function) {
|
||||||
|
for (SILInstruction &I : BB) {
|
||||||
|
if (auto *ASI = dyn_cast<AllocStackInst>(&I)) {
|
||||||
|
if (canHandleAllocStack(ASI)) {
|
||||||
|
if (allUsesInSameBlock(ASI)) {
|
||||||
|
singleBlockLocations.push_back(ASI);
|
||||||
|
} else {
|
||||||
|
analyzeLocation(ASI);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryLocations::analyzeLocation(SILValue loc) {
|
||||||
|
SILFunction *function = loc->getFunction();
|
||||||
|
assert(function && "cannot analyze a SILValue which is not in a function");
|
||||||
|
|
||||||
|
// Ignore trivial types to keep the number of locations small. Trivial types
|
||||||
|
// are not interesting anyway, because such memory locations are not
|
||||||
|
// destroyed.
|
||||||
|
if (loc->getType().isTrivial(*function))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!shouldTrackLocation(loc->getType(), function))
|
||||||
|
return;
|
||||||
|
|
||||||
|
unsigned currentLocIdx = locations.size();
|
||||||
|
locations.push_back(Location(loc, currentLocIdx));
|
||||||
|
SmallVector<SILValue, 8> collectedVals;
|
||||||
|
SubLocationMap subLocationMap;
|
||||||
|
if (!analyzeLocationUsesRecursively(loc, currentLocIdx, collectedVals,
|
||||||
|
subLocationMap)) {
|
||||||
|
locations.set_size(currentLocIdx);
|
||||||
|
for (SILValue V : collectedVals) {
|
||||||
|
addr2LocIdx.erase(V);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addr2LocIdx[loc] = currentLocIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryLocations::handleSingleBlockLocations(
|
||||||
|
std::function<void (SILBasicBlock *block)> handlerFunc) {
|
||||||
|
SILBasicBlock *currentBlock = nullptr;
|
||||||
|
clear();
|
||||||
|
|
||||||
|
// Walk over all collected single-block locations.
|
||||||
|
for (SingleValueInstruction *SVI : singleBlockLocations) {
|
||||||
|
// Whenever the parent block changes, process the block's locations.
|
||||||
|
if (currentBlock && SVI->getParent() != currentBlock) {
|
||||||
|
handlerFunc(currentBlock);
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
currentBlock = SVI->getParent();
|
||||||
|
analyzeLocation(SVI);
|
||||||
|
}
|
||||||
|
// Process the last block's locations.
|
||||||
|
if (currentBlock)
|
||||||
|
handlerFunc(currentBlock);
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
const MemoryLocations::Bits &MemoryLocations::getNonTrivialLocations() {
|
||||||
|
if (nonTrivialLocations.empty()) {
|
||||||
|
// Compute the bitset lazily.
|
||||||
|
nonTrivialLocations.resize(getNumLocations());
|
||||||
|
nonTrivialLocations.reset();
|
||||||
|
unsigned idx = 0;
|
||||||
|
for (Location &loc : locations) {
|
||||||
|
initFieldsCounter(loc);
|
||||||
|
if (loc.numNonTrivialFieldsNotCovered != 0)
|
||||||
|
nonTrivialLocations.set(idx);
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nonTrivialLocations;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryLocations::dump() const {
|
||||||
|
unsigned idx = 0;
|
||||||
|
for (const Location &loc : locations) {
|
||||||
|
llvm::dbgs() << "location #" << idx << ": sublocs=" << loc.subLocations
|
||||||
|
<< ", parent=" << loc.parentIdx
|
||||||
|
<< ", parentbits=" << loc.selfAndParents
|
||||||
|
<< ", #f=" << loc.numFieldsNotCoveredBySubfields
|
||||||
|
<< ", #ntf=" << loc.numNonTrivialFieldsNotCovered
|
||||||
|
<< ": " << loc.representativeValue;
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryLocations::clear() {
|
||||||
|
locations.clear();
|
||||||
|
addr2LocIdx.clear();
|
||||||
|
nonTrivialLocations.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hasInoutArgument(ApplySite AS) {
|
||||||
|
for (Operand &op : AS.getArgumentOperands()) {
|
||||||
|
switch (AS.getArgumentConvention(op)) {
|
||||||
|
case SILArgumentConvention::Indirect_Inout:
|
||||||
|
case SILArgumentConvention::Indirect_InoutAliasable:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryLocations::analyzeLocationUsesRecursively(SILValue V, unsigned locIdx,
|
||||||
|
SmallVectorImpl<SILValue> &collectedVals,
|
||||||
|
SubLocationMap &subLocationMap) {
|
||||||
|
for (Operand *use : V->getUses()) {
|
||||||
|
// We can safely ignore type dependent operands, because the lifetime of a
|
||||||
|
// type is decoupled from the lifetime of its value. For example, even if
|
||||||
|
// the result of an open_existential_addr is destroyed its type is still
|
||||||
|
// valid.
|
||||||
|
if (use->isTypeDependent())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
SILInstruction *user = use->getUser();
|
||||||
|
|
||||||
|
// We only handle addr-instructions which are planned to be used with
|
||||||
|
// opaque values. We can still consider to support other addr-instructions
|
||||||
|
// like addr-cast instructions. This somehow depends how opaque values will
|
||||||
|
// look like.
|
||||||
|
switch (user->getKind()) {
|
||||||
|
case SILInstructionKind::StructElementAddrInst: {
|
||||||
|
auto SEAI = cast<StructElementAddrInst>(user);
|
||||||
|
if (!analyzeAddrProjection(SEAI, locIdx, SEAI->getFieldIndex(),
|
||||||
|
collectedVals, subLocationMap))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SILInstructionKind::TupleElementAddrInst: {
|
||||||
|
auto *TEAI = cast<TupleElementAddrInst>(user);
|
||||||
|
if (!analyzeAddrProjection(TEAI, locIdx, TEAI->getFieldIndex(),
|
||||||
|
collectedVals, subLocationMap))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SILInstructionKind::BeginAccessInst:
|
||||||
|
if (!analyzeLocationUsesRecursively(cast<BeginAccessInst>(user), locIdx,
|
||||||
|
collectedVals, subLocationMap))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case SILInstructionKind::InitExistentialAddrInst:
|
||||||
|
case SILInstructionKind::OpenExistentialAddrInst:
|
||||||
|
case SILInstructionKind::InitEnumDataAddrInst:
|
||||||
|
case SILInstructionKind::UncheckedTakeEnumDataAddrInst:
|
||||||
|
if (!handleNonTrivialProjections)
|
||||||
|
return false;
|
||||||
|
// The payload is represented as a single sub-location of the enum.
|
||||||
|
if (!analyzeAddrProjection(cast<SingleValueInstruction>(user), locIdx,
|
||||||
|
/*fieldNr*/ 0, collectedVals, subLocationMap))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case SILInstructionKind::PartialApplyInst:
|
||||||
|
// inout/inout_aliasable conventions means that the argument "escapes".
|
||||||
|
// This is okay for memory verification, but cannot handled by other
|
||||||
|
// optimizations, like DestroyHoisting.
|
||||||
|
if (!handleNonTrivialProjections && hasInoutArgument(ApplySite(user)))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case SILInstructionKind::InjectEnumAddrInst:
|
||||||
|
case SILInstructionKind::SelectEnumAddrInst:
|
||||||
|
case SILInstructionKind::ExistentialMetatypeInst:
|
||||||
|
case SILInstructionKind::ValueMetatypeInst:
|
||||||
|
case SILInstructionKind::IsUniqueInst:
|
||||||
|
case SILInstructionKind::FixLifetimeInst:
|
||||||
|
case SILInstructionKind::LoadInst:
|
||||||
|
case SILInstructionKind::StoreInst:
|
||||||
|
case SILInstructionKind::StoreBorrowInst:
|
||||||
|
case SILInstructionKind::EndAccessInst:
|
||||||
|
case SILInstructionKind::LoadBorrowInst:
|
||||||
|
case SILInstructionKind::DestroyAddrInst:
|
||||||
|
case SILInstructionKind::CheckedCastAddrBranchInst:
|
||||||
|
case SILInstructionKind::UncheckedRefCastAddrInst:
|
||||||
|
case SILInstructionKind::UnconditionalCheckedCastAddrInst:
|
||||||
|
case SILInstructionKind::ApplyInst:
|
||||||
|
case SILInstructionKind::TryApplyInst:
|
||||||
|
case SILInstructionKind::BeginApplyInst:
|
||||||
|
case SILInstructionKind::DebugValueAddrInst:
|
||||||
|
case SILInstructionKind::CopyAddrInst:
|
||||||
|
case SILInstructionKind::YieldInst:
|
||||||
|
case SILInstructionKind::DeallocStackInst:
|
||||||
|
case SILInstructionKind::SwitchEnumAddrInst:
|
||||||
|
case SILInstructionKind::WitnessMethodInst:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryLocations::analyzeAddrProjection(
|
||||||
|
SingleValueInstruction *projection, unsigned parentLocIdx,unsigned fieldNr,
|
||||||
|
SmallVectorImpl<SILValue> &collectedVals, SubLocationMap &subLocationMap) {
|
||||||
|
|
||||||
|
if (!shouldTrackLocation(projection->getType(), projection->getFunction()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
unsigned &subLocIdx = subLocationMap[std::make_pair(parentLocIdx, fieldNr)];
|
||||||
|
if (subLocIdx == 0) {
|
||||||
|
subLocIdx = locations.size();
|
||||||
|
assert(subLocIdx > 0);
|
||||||
|
locations.push_back(Location(projection, subLocIdx, parentLocIdx));
|
||||||
|
|
||||||
|
Location &parentLoc = locations[parentLocIdx];
|
||||||
|
locations.back().selfAndParents |= parentLoc.selfAndParents;
|
||||||
|
|
||||||
|
int idx = (int)parentLocIdx;
|
||||||
|
do {
|
||||||
|
Location &loc = locations[idx];
|
||||||
|
setBitAndResize(loc.subLocations, subLocIdx);
|
||||||
|
idx = loc.parentIdx;
|
||||||
|
} while (idx >= 0);
|
||||||
|
|
||||||
|
initFieldsCounter(parentLoc);
|
||||||
|
assert(parentLoc.numFieldsNotCoveredBySubfields >= 1);
|
||||||
|
parentLoc.updateFieldCounters(projection->getType(), -1);
|
||||||
|
|
||||||
|
if (parentLoc.numFieldsNotCoveredBySubfields == 0) {
|
||||||
|
int idx = (int)parentLocIdx;
|
||||||
|
do {
|
||||||
|
Location &loc = locations[idx];
|
||||||
|
loc.subLocations.reset(parentLocIdx);
|
||||||
|
idx = loc.parentIdx;
|
||||||
|
} while (idx >= 0);
|
||||||
|
}
|
||||||
|
} else if (!isa<OpenExistentialAddrInst>(projection)) {
|
||||||
|
Location *loc = &locations[subLocIdx];
|
||||||
|
if (loc->representativeValue->getType() != projection->getType()) {
|
||||||
|
assert(isa<InitEnumDataAddrInst>(projection) ||
|
||||||
|
isa<UncheckedTakeEnumDataAddrInst>(projection) ||
|
||||||
|
isa<InitExistentialAddrInst>(projection));
|
||||||
|
|
||||||
|
// We can only handle a single enum payload type for a location or or a
|
||||||
|
// single concrete existential type. Mismatching types can have a differnt
|
||||||
|
// number of (non-trivial) sub-locations and we cannot handle this.
|
||||||
|
// But we ignore opened existential types, because those cannot have
|
||||||
|
// sub-locations (there cannot be an address projection on an
|
||||||
|
// open_existential_addr).
|
||||||
|
if (!isa<OpenExistentialAddrInst>(loc->representativeValue))
|
||||||
|
return false;
|
||||||
|
assert(loc->representativeValue->getType().isOpenedExistential());
|
||||||
|
loc->representativeValue = projection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!analyzeLocationUsesRecursively(projection, subLocIdx, collectedVals,
|
||||||
|
subLocationMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
registerProjection(projection, subLocIdx);
|
||||||
|
collectedVals.push_back(projection);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryLocations::initFieldsCounter(Location &loc) {
|
||||||
|
if (loc.numFieldsNotCoveredBySubfields >= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
assert(loc.numNonTrivialFieldsNotCovered < 0);
|
||||||
|
|
||||||
|
loc.numFieldsNotCoveredBySubfields = 0;
|
||||||
|
loc.numNonTrivialFieldsNotCovered = 0;
|
||||||
|
SILFunction *function = loc.representativeValue->getFunction();
|
||||||
|
SILType ty = loc.representativeValue->getType();
|
||||||
|
if (StructDecl *decl = ty.getStructOrBoundGenericStruct()) {
|
||||||
|
if (decl->isResilient(function->getModule().getSwiftModule(),
|
||||||
|
function->getResilienceExpansion())) {
|
||||||
|
loc.numFieldsNotCoveredBySubfields = INT_MAX;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SILModule &module = function->getModule();
|
||||||
|
for (VarDecl *field : decl->getStoredProperties()) {
|
||||||
|
loc.updateFieldCounters(
|
||||||
|
ty.getFieldType(field, module, TypeExpansionContext(*function)), +1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (auto tupleTy = ty.getAs<TupleType>()) {
|
||||||
|
for (unsigned idx = 0, end = tupleTy->getNumElements(); idx < end; ++idx) {
|
||||||
|
loc.updateFieldCounters(ty.getTupleElementType(idx), +1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loc.updateFieldCounters(ty, +1);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
target_sources(swiftSIL PRIVATE
|
target_sources(swiftSIL PRIVATE
|
||||||
LoadBorrowImmutabilityChecker.cpp
|
LoadBorrowImmutabilityChecker.cpp
|
||||||
LinearLifetimeChecker.cpp
|
LinearLifetimeChecker.cpp
|
||||||
MemoryLifetime.cpp
|
MemoryLifetimeVerifier.cpp
|
||||||
ReborrowVerifier.cpp
|
ReborrowVerifier.cpp
|
||||||
SILOwnershipVerifier.cpp
|
SILOwnershipVerifier.cpp
|
||||||
SILVerifier.cpp)
|
SILVerifier.cpp)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
//===--- MemoryLifetime.cpp -----------------------------------------------===//
|
//===--- MemoryLifetimeVerifier.cpp ---------------------------------------===//
|
||||||
//
|
//
|
||||||
// This source file is part of the Swift.org open source project
|
// This source file is part of the Swift.org open source project
|
||||||
//
|
//
|
||||||
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
|
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
|
||||||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||||
//
|
//
|
||||||
// See https://swift.org/LICENSE.txt for license information
|
// See https://swift.org/LICENSE.txt for license information
|
||||||
@@ -10,17 +10,14 @@
|
|||||||
//
|
//
|
||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
#define DEBUG_TYPE "sil-memory-lifetime"
|
#define DEBUG_TYPE "sil-memory-lifetime-verifier"
|
||||||
#include "swift/SIL/MemoryLifetime.h"
|
#include "swift/SIL/MemoryLocations.h"
|
||||||
#include "swift/SIL/SILArgument.h"
|
#include "swift/SIL/BitDataflow.h"
|
||||||
#include "swift/SIL/SILBasicBlock.h"
|
#include "swift/SIL/SILBasicBlock.h"
|
||||||
#include "swift/SIL/SILFunction.h"
|
#include "swift/SIL/SILFunction.h"
|
||||||
#include "swift/SIL/ApplySite.h"
|
#include "swift/SIL/ApplySite.h"
|
||||||
#include "swift/SIL/SILModule.h"
|
|
||||||
#include "swift/SIL/BasicBlockBits.h"
|
#include "swift/SIL/BasicBlockBits.h"
|
||||||
#include "llvm/ADT/DenseMap.h"
|
|
||||||
#include "llvm/Support/CommandLine.h"
|
#include "llvm/Support/CommandLine.h"
|
||||||
#include "llvm/Support/raw_ostream.h"
|
|
||||||
|
|
||||||
using namespace swift;
|
using namespace swift;
|
||||||
|
|
||||||
@@ -29,595 +26,8 @@ llvm::cl::opt<bool> DontAbortOnMemoryLifetimeErrors(
|
|||||||
llvm::cl::desc("Don't abort compliation if the memory lifetime checker "
|
llvm::cl::desc("Don't abort compliation if the memory lifetime checker "
|
||||||
"detects an error."));
|
"detects an error."));
|
||||||
|
|
||||||
/// Debug dump a location bit vector.
|
|
||||||
void swift::printBitsAsArray(llvm::raw_ostream &OS, const SmallBitVector &bits) {
|
|
||||||
const char *separator = "";
|
|
||||||
OS << '[';
|
|
||||||
for (int idx = bits.find_first(); idx >= 0; idx = bits.find_next(idx)) {
|
|
||||||
OS << separator << idx;
|
|
||||||
separator = ",";
|
|
||||||
}
|
|
||||||
OS << ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
void swift::dumpBits(const SmallBitVector &bits) {
|
|
||||||
llvm::dbgs() << bits << '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace swift {
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
// Utility functions
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
/// Enlarge the bitset if needed to set the bit with \p idx.
|
|
||||||
static void setBitAndResize(SmallBitVector &bits, unsigned idx) {
|
|
||||||
if (bits.size() <= idx)
|
|
||||||
bits.resize(idx + 1);
|
|
||||||
bits.set(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool allUsesInSameBlock(AllocStackInst *ASI) {
|
|
||||||
SILBasicBlock *BB = ASI->getParent();
|
|
||||||
int numDeallocStacks = 0;
|
|
||||||
for (Operand *use : ASI->getUses()) {
|
|
||||||
SILInstruction *user = use->getUser();
|
|
||||||
if (isa<DeallocStackInst>(user)) {
|
|
||||||
++numDeallocStacks;
|
|
||||||
if (user->getParent() != BB)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// In case of an unreachable, the dealloc_stack can be missing. In this
|
|
||||||
// case we don't treat it as a single-block location.
|
|
||||||
assert(numDeallocStacks <= 1 &&
|
|
||||||
"A single-block stack location cannot have multiple deallocations");
|
|
||||||
return numDeallocStacks == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool shouldTrackLocation(SILType ty, SILFunction *function) {
|
|
||||||
// Ignore empty tuples and empty structs.
|
|
||||||
if (auto tupleTy = ty.getAs<TupleType>()) {
|
|
||||||
return tupleTy->getNumElements() != 0;
|
|
||||||
}
|
|
||||||
if (StructDecl *decl = ty.getStructOrBoundGenericStruct()) {
|
|
||||||
return decl->getStoredProperties().size() != 0;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // anonymous namespace
|
|
||||||
} // namespace swift
|
|
||||||
|
|
||||||
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
// MemoryLocations members
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
MemoryLocations::Location::Location(SILValue val, unsigned index, int parentIdx) :
|
|
||||||
representativeValue(val),
|
|
||||||
parentIdx(parentIdx) {
|
|
||||||
assert(((parentIdx >= 0) ==
|
|
||||||
(isa<StructElementAddrInst>(val) || isa<TupleElementAddrInst>(val) ||
|
|
||||||
isa<InitEnumDataAddrInst>(val) || isa<UncheckedTakeEnumDataAddrInst>(val) ||
|
|
||||||
isa<InitExistentialAddrInst>(val) || isa<OpenExistentialAddrInst>(val)))
|
|
||||||
&& "sub-locations can only be introduced with struct/tuple/enum projections");
|
|
||||||
setBitAndResize(subLocations, index);
|
|
||||||
setBitAndResize(selfAndParents, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryLocations::Location::updateFieldCounters(SILType ty, int increment) {
|
|
||||||
SILFunction *function = representativeValue->getFunction();
|
|
||||||
if (shouldTrackLocation(ty, function)) {
|
|
||||||
numFieldsNotCoveredBySubfields += increment;
|
|
||||||
if (!ty.isTrivial(*function))
|
|
||||||
numNonTrivialFieldsNotCovered += increment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static SILValue getBaseValue(SILValue addr) {
|
|
||||||
while (true) {
|
|
||||||
switch (addr->getKind()) {
|
|
||||||
case ValueKind::BeginAccessInst:
|
|
||||||
addr = cast<BeginAccessInst>(addr)->getOperand();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return addr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int MemoryLocations::getLocationIdx(SILValue addr) const {
|
|
||||||
auto iter = addr2LocIdx.find(getBaseValue(addr));
|
|
||||||
if (iter == addr2LocIdx.end())
|
|
||||||
return -1;
|
|
||||||
return iter->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MemoryLocations::Location *
|
|
||||||
MemoryLocations::getRootLocation(unsigned index) const {
|
|
||||||
while (true) {
|
|
||||||
const Location &loc = locations[index];
|
|
||||||
if (loc.parentIdx < 0)
|
|
||||||
return &loc;
|
|
||||||
index = loc.parentIdx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool canHandleAllocStack(AllocStackInst *asi) {
|
|
||||||
assert(asi);
|
|
||||||
|
|
||||||
// An alloc_stack with dynamic lifetime set has a lifetime that relies on
|
|
||||||
// unrelated conditional control flow for correctness. This means that we may
|
|
||||||
// statically leak along paths that were known by the emitter to never be
|
|
||||||
// taken if the value is live. So bail since we can't verify this.
|
|
||||||
if (asi->hasDynamicLifetime())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Otherwise we can optimize!
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryLocations::analyzeLocations(SILFunction *function) {
|
|
||||||
// As we have to limit the set of handled locations to memory, which is
|
|
||||||
// guaranteed to be not aliased, we currently only handle indirect function
|
|
||||||
// arguments and alloc_stack locations.
|
|
||||||
for (SILArgument *arg : function->getArguments()) {
|
|
||||||
SILFunctionArgument *funcArg = cast<SILFunctionArgument>(arg);
|
|
||||||
switch (funcArg->getArgumentConvention()) {
|
|
||||||
case SILArgumentConvention::Indirect_In:
|
|
||||||
case SILArgumentConvention::Indirect_In_Constant:
|
|
||||||
case SILArgumentConvention::Indirect_In_Guaranteed:
|
|
||||||
case SILArgumentConvention::Indirect_Out:
|
|
||||||
// These are not SIL addresses under -enable-sil-opaque-values
|
|
||||||
if (!function->getConventions().useLoweredAddresses())
|
|
||||||
break;
|
|
||||||
|
|
||||||
LLVM_FALLTHROUGH;
|
|
||||||
case SILArgumentConvention::Indirect_Inout:
|
|
||||||
analyzeLocation(funcArg);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (SILBasicBlock &BB : *function) {
|
|
||||||
for (SILInstruction &I : BB) {
|
|
||||||
if (auto *ASI = dyn_cast<AllocStackInst>(&I)) {
|
|
||||||
if (canHandleAllocStack(ASI)) {
|
|
||||||
if (allUsesInSameBlock(ASI)) {
|
|
||||||
singleBlockLocations.push_back(ASI);
|
|
||||||
} else {
|
|
||||||
analyzeLocation(ASI);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryLocations::analyzeLocation(SILValue loc) {
|
|
||||||
SILFunction *function = loc->getFunction();
|
|
||||||
assert(function && "cannot analyze a SILValue which is not in a function");
|
|
||||||
|
|
||||||
// Ignore trivial types to keep the number of locations small. Trivial types
|
|
||||||
// are not interesting anyway, because such memory locations are not
|
|
||||||
// destroyed.
|
|
||||||
if (loc->getType().isTrivial(*function))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!shouldTrackLocation(loc->getType(), function))
|
|
||||||
return;
|
|
||||||
|
|
||||||
unsigned currentLocIdx = locations.size();
|
|
||||||
locations.push_back(Location(loc, currentLocIdx));
|
|
||||||
SmallVector<SILValue, 8> collectedVals;
|
|
||||||
SubLocationMap subLocationMap;
|
|
||||||
if (!analyzeLocationUsesRecursively(loc, currentLocIdx, collectedVals,
|
|
||||||
subLocationMap)) {
|
|
||||||
locations.set_size(currentLocIdx);
|
|
||||||
for (SILValue V : collectedVals) {
|
|
||||||
addr2LocIdx.erase(V);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
addr2LocIdx[loc] = currentLocIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryLocations::handleSingleBlockLocations(
|
|
||||||
std::function<void (SILBasicBlock *block)> handlerFunc) {
|
|
||||||
SILBasicBlock *currentBlock = nullptr;
|
|
||||||
clear();
|
|
||||||
|
|
||||||
// Walk over all collected single-block locations.
|
|
||||||
for (SingleValueInstruction *SVI : singleBlockLocations) {
|
|
||||||
// Whenever the parent block changes, process the block's locations.
|
|
||||||
if (currentBlock && SVI->getParent() != currentBlock) {
|
|
||||||
handlerFunc(currentBlock);
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
currentBlock = SVI->getParent();
|
|
||||||
analyzeLocation(SVI);
|
|
||||||
}
|
|
||||||
// Process the last block's locations.
|
|
||||||
if (currentBlock)
|
|
||||||
handlerFunc(currentBlock);
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
const MemoryLocations::Bits &MemoryLocations::getNonTrivialLocations() {
|
|
||||||
if (nonTrivialLocations.empty()) {
|
|
||||||
// Compute the bitset lazily.
|
|
||||||
nonTrivialLocations.resize(getNumLocations());
|
|
||||||
nonTrivialLocations.reset();
|
|
||||||
unsigned idx = 0;
|
|
||||||
for (Location &loc : locations) {
|
|
||||||
initFieldsCounter(loc);
|
|
||||||
if (loc.numNonTrivialFieldsNotCovered != 0)
|
|
||||||
nonTrivialLocations.set(idx);
|
|
||||||
++idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nonTrivialLocations;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryLocations::dump() const {
|
|
||||||
unsigned idx = 0;
|
|
||||||
for (const Location &loc : locations) {
|
|
||||||
llvm::dbgs() << "location #" << idx << ": sublocs=" << loc.subLocations
|
|
||||||
<< ", parent=" << loc.parentIdx
|
|
||||||
<< ", parentbits=" << loc.selfAndParents
|
|
||||||
<< ", #f=" << loc.numFieldsNotCoveredBySubfields
|
|
||||||
<< ", #ntf=" << loc.numNonTrivialFieldsNotCovered
|
|
||||||
<< ": " << loc.representativeValue;
|
|
||||||
++idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryLocations::clear() {
|
|
||||||
locations.clear();
|
|
||||||
addr2LocIdx.clear();
|
|
||||||
nonTrivialLocations.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool hasInoutArgument(ApplySite AS) {
|
|
||||||
for (Operand &op : AS.getArgumentOperands()) {
|
|
||||||
switch (AS.getArgumentConvention(op)) {
|
|
||||||
case SILArgumentConvention::Indirect_Inout:
|
|
||||||
case SILArgumentConvention::Indirect_InoutAliasable:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MemoryLocations::analyzeLocationUsesRecursively(SILValue V, unsigned locIdx,
|
|
||||||
SmallVectorImpl<SILValue> &collectedVals,
|
|
||||||
SubLocationMap &subLocationMap) {
|
|
||||||
for (Operand *use : V->getUses()) {
|
|
||||||
// We can safely ignore type dependent operands, because the lifetime of a
|
|
||||||
// type is decoupled from the lifetime of its value. For example, even if
|
|
||||||
// the result of an open_existential_addr is destroyed its type is still
|
|
||||||
// valid.
|
|
||||||
if (use->isTypeDependent())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
SILInstruction *user = use->getUser();
|
|
||||||
|
|
||||||
// We only handle addr-instructions which are planned to be used with
|
|
||||||
// opaque values. We can still consider to support other addr-instructions
|
|
||||||
// like addr-cast instructions. This somehow depends how opaque values will
|
|
||||||
// look like.
|
|
||||||
switch (user->getKind()) {
|
|
||||||
case SILInstructionKind::StructElementAddrInst: {
|
|
||||||
auto SEAI = cast<StructElementAddrInst>(user);
|
|
||||||
if (!analyzeAddrProjection(SEAI, locIdx, SEAI->getFieldIndex(),
|
|
||||||
collectedVals, subLocationMap))
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SILInstructionKind::TupleElementAddrInst: {
|
|
||||||
auto *TEAI = cast<TupleElementAddrInst>(user);
|
|
||||||
if (!analyzeAddrProjection(TEAI, locIdx, TEAI->getFieldIndex(),
|
|
||||||
collectedVals, subLocationMap))
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SILInstructionKind::BeginAccessInst:
|
|
||||||
if (!analyzeLocationUsesRecursively(cast<BeginAccessInst>(user), locIdx,
|
|
||||||
collectedVals, subLocationMap))
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
case SILInstructionKind::InitExistentialAddrInst:
|
|
||||||
case SILInstructionKind::OpenExistentialAddrInst:
|
|
||||||
case SILInstructionKind::InitEnumDataAddrInst:
|
|
||||||
case SILInstructionKind::UncheckedTakeEnumDataAddrInst:
|
|
||||||
if (!handleNonTrivialProjections)
|
|
||||||
return false;
|
|
||||||
// The payload is represented as a single sub-location of the enum.
|
|
||||||
if (!analyzeAddrProjection(cast<SingleValueInstruction>(user), locIdx,
|
|
||||||
/*fieldNr*/ 0, collectedVals, subLocationMap))
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
case SILInstructionKind::PartialApplyInst:
|
|
||||||
// inout/inout_aliasable conventions means that the argument "escapes".
|
|
||||||
// This is okay for memory verification, but cannot handled by other
|
|
||||||
// optimizations, like DestroyHoisting.
|
|
||||||
if (!handleNonTrivialProjections && hasInoutArgument(ApplySite(user)))
|
|
||||||
return false;
|
|
||||||
break;
|
|
||||||
case SILInstructionKind::InjectEnumAddrInst:
|
|
||||||
case SILInstructionKind::SelectEnumAddrInst:
|
|
||||||
case SILInstructionKind::ExistentialMetatypeInst:
|
|
||||||
case SILInstructionKind::ValueMetatypeInst:
|
|
||||||
case SILInstructionKind::IsUniqueInst:
|
|
||||||
case SILInstructionKind::FixLifetimeInst:
|
|
||||||
case SILInstructionKind::LoadInst:
|
|
||||||
case SILInstructionKind::StoreInst:
|
|
||||||
case SILInstructionKind::StoreBorrowInst:
|
|
||||||
case SILInstructionKind::EndAccessInst:
|
|
||||||
case SILInstructionKind::LoadBorrowInst:
|
|
||||||
case SILInstructionKind::DestroyAddrInst:
|
|
||||||
case SILInstructionKind::CheckedCastAddrBranchInst:
|
|
||||||
case SILInstructionKind::UncheckedRefCastAddrInst:
|
|
||||||
case SILInstructionKind::UnconditionalCheckedCastAddrInst:
|
|
||||||
case SILInstructionKind::ApplyInst:
|
|
||||||
case SILInstructionKind::TryApplyInst:
|
|
||||||
case SILInstructionKind::BeginApplyInst:
|
|
||||||
case SILInstructionKind::DebugValueAddrInst:
|
|
||||||
case SILInstructionKind::CopyAddrInst:
|
|
||||||
case SILInstructionKind::YieldInst:
|
|
||||||
case SILInstructionKind::DeallocStackInst:
|
|
||||||
case SILInstructionKind::SwitchEnumAddrInst:
|
|
||||||
case SILInstructionKind::WitnessMethodInst:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MemoryLocations::analyzeAddrProjection(
|
|
||||||
SingleValueInstruction *projection, unsigned parentLocIdx,unsigned fieldNr,
|
|
||||||
SmallVectorImpl<SILValue> &collectedVals, SubLocationMap &subLocationMap) {
|
|
||||||
|
|
||||||
if (!shouldTrackLocation(projection->getType(), projection->getFunction()))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
unsigned &subLocIdx = subLocationMap[std::make_pair(parentLocIdx, fieldNr)];
|
|
||||||
if (subLocIdx == 0) {
|
|
||||||
subLocIdx = locations.size();
|
|
||||||
assert(subLocIdx > 0);
|
|
||||||
locations.push_back(Location(projection, subLocIdx, parentLocIdx));
|
|
||||||
|
|
||||||
Location &parentLoc = locations[parentLocIdx];
|
|
||||||
locations.back().selfAndParents |= parentLoc.selfAndParents;
|
|
||||||
|
|
||||||
int idx = (int)parentLocIdx;
|
|
||||||
do {
|
|
||||||
Location &loc = locations[idx];
|
|
||||||
setBitAndResize(loc.subLocations, subLocIdx);
|
|
||||||
idx = loc.parentIdx;
|
|
||||||
} while (idx >= 0);
|
|
||||||
|
|
||||||
initFieldsCounter(parentLoc);
|
|
||||||
assert(parentLoc.numFieldsNotCoveredBySubfields >= 1);
|
|
||||||
parentLoc.updateFieldCounters(projection->getType(), -1);
|
|
||||||
|
|
||||||
if (parentLoc.numFieldsNotCoveredBySubfields == 0) {
|
|
||||||
int idx = (int)parentLocIdx;
|
|
||||||
do {
|
|
||||||
Location &loc = locations[idx];
|
|
||||||
loc.subLocations.reset(parentLocIdx);
|
|
||||||
idx = loc.parentIdx;
|
|
||||||
} while (idx >= 0);
|
|
||||||
}
|
|
||||||
} else if (!isa<OpenExistentialAddrInst>(projection)) {
|
|
||||||
Location *loc = &locations[subLocIdx];
|
|
||||||
if (loc->representativeValue->getType() != projection->getType()) {
|
|
||||||
assert(isa<InitEnumDataAddrInst>(projection) ||
|
|
||||||
isa<UncheckedTakeEnumDataAddrInst>(projection) ||
|
|
||||||
isa<InitExistentialAddrInst>(projection));
|
|
||||||
|
|
||||||
// We can only handle a single enum payload type for a location or or a
|
|
||||||
// single concrete existential type. Mismatching types can have a differnt
|
|
||||||
// number of (non-trivial) sub-locations and we cannot handle this.
|
|
||||||
// But we ignore opened existential types, because those cannot have
|
|
||||||
// sub-locations (there cannot be an address projection on an
|
|
||||||
// open_existential_addr).
|
|
||||||
if (!isa<OpenExistentialAddrInst>(loc->representativeValue))
|
|
||||||
return false;
|
|
||||||
assert(loc->representativeValue->getType().isOpenedExistential());
|
|
||||||
loc->representativeValue = projection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!analyzeLocationUsesRecursively(projection, subLocIdx, collectedVals,
|
|
||||||
subLocationMap)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
registerProjection(projection, subLocIdx);
|
|
||||||
collectedVals.push_back(projection);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryLocations::initFieldsCounter(Location &loc) {
|
|
||||||
if (loc.numFieldsNotCoveredBySubfields >= 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
assert(loc.numNonTrivialFieldsNotCovered < 0);
|
|
||||||
|
|
||||||
loc.numFieldsNotCoveredBySubfields = 0;
|
|
||||||
loc.numNonTrivialFieldsNotCovered = 0;
|
|
||||||
SILFunction *function = loc.representativeValue->getFunction();
|
|
||||||
SILType ty = loc.representativeValue->getType();
|
|
||||||
if (StructDecl *decl = ty.getStructOrBoundGenericStruct()) {
|
|
||||||
if (decl->isResilient(function->getModule().getSwiftModule(),
|
|
||||||
function->getResilienceExpansion())) {
|
|
||||||
loc.numFieldsNotCoveredBySubfields = INT_MAX;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SILModule &module = function->getModule();
|
|
||||||
for (VarDecl *field : decl->getStoredProperties()) {
|
|
||||||
loc.updateFieldCounters(
|
|
||||||
ty.getFieldType(field, module, TypeExpansionContext(*function)), +1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (auto tupleTy = ty.getAs<TupleType>()) {
|
|
||||||
for (unsigned idx = 0, end = tupleTy->getNumElements(); idx < end; ++idx) {
|
|
||||||
loc.updateFieldCounters(ty.getTupleElementType(idx), +1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
loc.updateFieldCounters(ty, +1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
// MemoryDataflow members
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
MemoryDataflow::MemoryDataflow(SILFunction *function, unsigned numLocations) :
|
|
||||||
blockStates(function, [numLocations](SILBasicBlock *block) {
|
|
||||||
return BlockState(numLocations);
|
|
||||||
}) {}
|
|
||||||
|
|
||||||
void MemoryDataflow::entryReachabilityAnalysis() {
|
|
||||||
llvm::SmallVector<SILBasicBlock *, 16> workList;
|
|
||||||
auto entry = blockStates.entry();
|
|
||||||
entry.data.reachableFromEntry = true;
|
|
||||||
workList.push_back(&entry.block);
|
|
||||||
|
|
||||||
while (!workList.empty()) {
|
|
||||||
SILBasicBlock *block = workList.pop_back_val();
|
|
||||||
for (SILBasicBlock *succ : block->getSuccessorBlocks()) {
|
|
||||||
BlockState &succState = blockStates[succ];
|
|
||||||
if (!succState.reachableFromEntry) {
|
|
||||||
succState.reachableFromEntry = true;
|
|
||||||
workList.push_back(succ);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryDataflow::exitReachableAnalysis() {
|
|
||||||
llvm::SmallVector<SILBasicBlock *, 16> workList;
|
|
||||||
for (auto bd : blockStates) {
|
|
||||||
if (bd.block.getTerminator()->isFunctionExiting()) {
|
|
||||||
bd.data.exitReachability = ExitReachability::ReachesExit;
|
|
||||||
workList.push_back(&bd.block);
|
|
||||||
} else if (isa<UnreachableInst>(bd.block.getTerminator())) {
|
|
||||||
bd.data.exitReachability = ExitReachability::ReachesUnreachable;
|
|
||||||
workList.push_back(&bd.block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (!workList.empty()) {
|
|
||||||
SILBasicBlock *block = workList.pop_back_val();
|
|
||||||
BlockState &state = blockStates[block];
|
|
||||||
for (SILBasicBlock *pred : block->getPredecessorBlocks()) {
|
|
||||||
BlockState &predState = blockStates[pred];
|
|
||||||
if (predState.exitReachability < state.exitReachability) {
|
|
||||||
// As there are 3 states, each block can be put into the workList 2
|
|
||||||
// times maximum.
|
|
||||||
predState.exitReachability = state.exitReachability;
|
|
||||||
workList.push_back(pred);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryDataflow::solveForward(JoinOperation join) {
|
|
||||||
// Pretty standard data flow solving.
|
|
||||||
bool changed = false;
|
|
||||||
bool firstRound = true;
|
|
||||||
do {
|
|
||||||
changed = false;
|
|
||||||
for (auto bd : blockStates) {
|
|
||||||
Bits bits = bd.data.entrySet;
|
|
||||||
assert(!bits.empty());
|
|
||||||
for (SILBasicBlock *pred : bd.block.getPredecessorBlocks()) {
|
|
||||||
join(bits, blockStates[pred].exitSet);
|
|
||||||
}
|
|
||||||
if (firstRound || bits != bd.data.entrySet) {
|
|
||||||
changed = true;
|
|
||||||
bd.data.entrySet = bits;
|
|
||||||
bits |= bd.data.genSet;
|
|
||||||
bits.reset(bd.data.killSet);
|
|
||||||
bd.data.exitSet = bits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
firstRound = false;
|
|
||||||
} while (changed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryDataflow::solveForwardWithIntersect() {
|
|
||||||
solveForward([](Bits &entry, const Bits &predExit){
|
|
||||||
entry &= predExit;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryDataflow::solveForwardWithUnion() {
|
|
||||||
solveForward([](Bits &entry, const Bits &predExit){
|
|
||||||
entry |= predExit;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryDataflow::solveBackward(JoinOperation join) {
|
|
||||||
// Pretty standard data flow solving.
|
|
||||||
bool changed = false;
|
|
||||||
bool firstRound = true;
|
|
||||||
do {
|
|
||||||
changed = false;
|
|
||||||
for (auto bd : llvm::reverse(blockStates)) {
|
|
||||||
Bits bits = bd.data.exitSet;
|
|
||||||
assert(!bits.empty());
|
|
||||||
for (SILBasicBlock *succ : bd.block.getSuccessorBlocks()) {
|
|
||||||
join(bits, blockStates[succ].entrySet);
|
|
||||||
}
|
|
||||||
if (firstRound || bits != bd.data.exitSet) {
|
|
||||||
changed = true;
|
|
||||||
bd.data.exitSet = bits;
|
|
||||||
bits |= bd.data.genSet;
|
|
||||||
bits.reset(bd.data.killSet);
|
|
||||||
bd.data.entrySet = bits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
firstRound = false;
|
|
||||||
} while (changed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryDataflow::solveBackwardWithIntersect() {
|
|
||||||
solveBackward([](Bits &entry, const Bits &predExit){
|
|
||||||
entry &= predExit;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryDataflow::solveBackwardWithUnion() {
|
|
||||||
solveBackward([](Bits &entry, const Bits &predExit){
|
|
||||||
entry |= predExit;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryDataflow::dump() const {
|
|
||||||
for (auto bd : blockStates) {
|
|
||||||
llvm::dbgs() << "bb" << bd.block.getDebugID() << ":\n"
|
|
||||||
<< " entry: " << bd.data.entrySet << '\n'
|
|
||||||
<< " gen: " << bd.data.genSet << '\n'
|
|
||||||
<< " kill: " << bd.data.killSet << '\n'
|
|
||||||
<< " exit: " << bd.data.exitSet << '\n';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
// MemoryLifetimeVerifier
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
/// A utility for verifying memory lifetime.
|
/// A utility for verifying memory lifetime.
|
||||||
///
|
///
|
||||||
/// The MemoryLifetime utility checks the lifetime of memory locations.
|
/// The MemoryLifetime utility checks the lifetime of memory locations.
|
||||||
@@ -630,7 +40,7 @@ class MemoryLifetimeVerifier {
|
|||||||
|
|
||||||
using Bits = MemoryLocations::Bits;
|
using Bits = MemoryLocations::Bits;
|
||||||
using Location = MemoryLocations::Location;
|
using Location = MemoryLocations::Location;
|
||||||
using BlockState = MemoryDataflow::BlockState;
|
using BlockState = BitDataflow::BlockState;
|
||||||
|
|
||||||
SILFunction *function;
|
SILFunction *function;
|
||||||
MemoryLocations locations;
|
MemoryLocations locations;
|
||||||
@@ -689,7 +99,7 @@ class MemoryLifetimeVerifier {
|
|||||||
void setBitsOfPredecessor(Bits &genSet, Bits &killSet, SILBasicBlock *block);
|
void setBitsOfPredecessor(Bits &genSet, Bits &killSet, SILBasicBlock *block);
|
||||||
|
|
||||||
/// Initializes the data flow bits sets in the block states for all blocks.
|
/// Initializes the data flow bits sets in the block states for all blocks.
|
||||||
void initDataflow(MemoryDataflow &dataFlow);
|
void initDataflow(BitDataflow &dataFlow);
|
||||||
|
|
||||||
/// Initializes the data flow bits sets in the block state for a single block.
|
/// Initializes the data flow bits sets in the block state for a single block.
|
||||||
void initDataflowInBlock(SILBasicBlock *block, BlockState &state);
|
void initDataflowInBlock(SILBasicBlock *block, BlockState &state);
|
||||||
@@ -700,7 +110,7 @@ class MemoryLifetimeVerifier {
|
|||||||
bool isTryApply);
|
bool isTryApply);
|
||||||
|
|
||||||
/// Perform all checks in the function after the data flow has been computed.
|
/// Perform all checks in the function after the data flow has been computed.
|
||||||
void checkFunction(MemoryDataflow &dataFlow);
|
void checkFunction(BitDataflow &dataFlow);
|
||||||
|
|
||||||
/// Check all instructions in \p block, starting with \p bits as entry set.
|
/// Check all instructions in \p block, starting with \p bits as entry set.
|
||||||
void checkBlock(SILBasicBlock *block, Bits &bits);
|
void checkBlock(SILBasicBlock *block, Bits &bits);
|
||||||
@@ -711,6 +121,16 @@ class MemoryLifetimeVerifier {
|
|||||||
SILArgumentConvention argumentConvention,
|
SILArgumentConvention argumentConvention,
|
||||||
SILInstruction *applyInst);
|
SILInstruction *applyInst);
|
||||||
|
|
||||||
|
// Utility functions for setting and clearing gen- and kill-bits.
|
||||||
|
|
||||||
|
void genBits(BitDataflow::BlockState &blockState, SILValue addr) {
|
||||||
|
locations.genBits(blockState.genSet, blockState.killSet, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void killBits(BitDataflow::BlockState &blockState, SILValue addr) {
|
||||||
|
locations.killBits(blockState.genSet, blockState.killSet, addr);
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MemoryLifetimeVerifier(SILFunction *function) :
|
MemoryLifetimeVerifier(SILFunction *function) :
|
||||||
function(function), locations(/*handleNonTrivialProjections*/ true) {}
|
function(function), locations(/*handleNonTrivialProjections*/ true) {}
|
||||||
@@ -857,7 +277,7 @@ void MemoryLifetimeVerifier::registerStoreBorrowLocation(SILValue addr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryLifetimeVerifier::initDataflow(MemoryDataflow &dataFlow) {
|
void MemoryLifetimeVerifier::initDataflow(BitDataflow &dataFlow) {
|
||||||
// Initialize the entry and exit sets to all-bits-set. Except for the function
|
// Initialize the entry and exit sets to all-bits-set. Except for the function
|
||||||
// entry.
|
// entry.
|
||||||
for (auto bs : dataFlow) {
|
for (auto bs : dataFlow) {
|
||||||
@@ -896,7 +316,7 @@ void MemoryLifetimeVerifier::initDataflowInBlock(SILBasicBlock *block,
|
|||||||
auto *LI = cast<LoadInst>(&I);
|
auto *LI = cast<LoadInst>(&I);
|
||||||
switch (LI->getOwnershipQualifier()) {
|
switch (LI->getOwnershipQualifier()) {
|
||||||
case LoadOwnershipQualifier::Take:
|
case LoadOwnershipQualifier::Take:
|
||||||
state.killBits(LI->getOperand(), locations);
|
killBits(state, LI->getOperand());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -904,19 +324,19 @@ void MemoryLifetimeVerifier::initDataflowInBlock(SILBasicBlock *block,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SILInstructionKind::StoreInst:
|
case SILInstructionKind::StoreInst:
|
||||||
state.genBits(cast<StoreInst>(&I)->getDest(), locations);
|
genBits(state, cast<StoreInst>(&I)->getDest());
|
||||||
break;
|
break;
|
||||||
case SILInstructionKind::StoreBorrowInst: {
|
case SILInstructionKind::StoreBorrowInst: {
|
||||||
SILValue destAddr = cast<StoreBorrowInst>(&I)->getDest();
|
SILValue destAddr = cast<StoreBorrowInst>(&I)->getDest();
|
||||||
state.genBits(destAddr, locations);
|
genBits(state, destAddr);
|
||||||
registerStoreBorrowLocation(destAddr);
|
registerStoreBorrowLocation(destAddr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SILInstructionKind::CopyAddrInst: {
|
case SILInstructionKind::CopyAddrInst: {
|
||||||
auto *CAI = cast<CopyAddrInst>(&I);
|
auto *CAI = cast<CopyAddrInst>(&I);
|
||||||
if (CAI->isTakeOfSrc())
|
if (CAI->isTakeOfSrc())
|
||||||
state.killBits(CAI->getSrc(), locations);
|
killBits(state, CAI->getSrc());
|
||||||
state.genBits(CAI->getDest(), locations);
|
genBits(state, CAI->getDest());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SILInstructionKind::InjectEnumAddrInst: {
|
case SILInstructionKind::InjectEnumAddrInst: {
|
||||||
@@ -926,20 +346,20 @@ void MemoryLifetimeVerifier::initDataflowInBlock(SILBasicBlock *block,
|
|||||||
// This is a bit tricky: an injected no-payload case means that the
|
// This is a bit tricky: an injected no-payload case means that the
|
||||||
// "full" enum is initialized. So, for the purpose of dataflow, we
|
// "full" enum is initialized. So, for the purpose of dataflow, we
|
||||||
// treat it like a full initialization of the payload data.
|
// treat it like a full initialization of the payload data.
|
||||||
state.genBits(IEAI->getOperand(), locations);
|
genBits(state, IEAI->getOperand());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SILInstructionKind::DestroyAddrInst:
|
case SILInstructionKind::DestroyAddrInst:
|
||||||
case SILInstructionKind::DeallocStackInst:
|
case SILInstructionKind::DeallocStackInst:
|
||||||
state.killBits(I.getOperand(0), locations);
|
killBits(state, I.getOperand(0));
|
||||||
break;
|
break;
|
||||||
case SILInstructionKind::UncheckedRefCastAddrInst:
|
case SILInstructionKind::UncheckedRefCastAddrInst:
|
||||||
case SILInstructionKind::UnconditionalCheckedCastAddrInst: {
|
case SILInstructionKind::UnconditionalCheckedCastAddrInst: {
|
||||||
SILValue src = I.getOperand(CopyLikeInstruction::Src);
|
SILValue src = I.getOperand(CopyLikeInstruction::Src);
|
||||||
SILValue dest = I.getOperand(CopyLikeInstruction::Dest);
|
SILValue dest = I.getOperand(CopyLikeInstruction::Dest);
|
||||||
state.killBits(src, locations);
|
killBits(state, src);
|
||||||
state.genBits(dest, locations);
|
genBits(state, dest);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SILInstructionKind::PartialApplyInst:
|
case SILInstructionKind::PartialApplyInst:
|
||||||
@@ -1013,14 +433,14 @@ void MemoryLifetimeVerifier::setFuncOperandBits(BlockState &state, Operand &op,
|
|||||||
switch (convention) {
|
switch (convention) {
|
||||||
case SILArgumentConvention::Indirect_In:
|
case SILArgumentConvention::Indirect_In:
|
||||||
case SILArgumentConvention::Indirect_In_Constant:
|
case SILArgumentConvention::Indirect_In_Constant:
|
||||||
state.killBits(op.get(), locations);
|
killBits(state, op.get());
|
||||||
break;
|
break;
|
||||||
case SILArgumentConvention::Indirect_Out:
|
case SILArgumentConvention::Indirect_Out:
|
||||||
// try_apply is special, because an @out result is only initialized
|
// try_apply is special, because an @out result is only initialized
|
||||||
// in the normal-block, but not in the throw-block.
|
// in the normal-block, but not in the throw-block.
|
||||||
// We handle @out result of try_apply in setBitsOfPredecessor.
|
// We handle the @out result of try_apply in setBitsOfPredecessor.
|
||||||
if (!isTryApply)
|
if (!isTryApply)
|
||||||
state.genBits(op.get(), locations);
|
genBits(state, op.get());
|
||||||
break;
|
break;
|
||||||
case SILArgumentConvention::Indirect_In_Guaranteed:
|
case SILArgumentConvention::Indirect_In_Guaranteed:
|
||||||
case SILArgumentConvention::Indirect_Inout:
|
case SILArgumentConvention::Indirect_Inout:
|
||||||
@@ -1032,7 +452,7 @@ void MemoryLifetimeVerifier::setFuncOperandBits(BlockState &state, Operand &op,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryLifetimeVerifier::checkFunction(MemoryDataflow &dataFlow) {
|
void MemoryLifetimeVerifier::checkFunction(BitDataflow &dataFlow) {
|
||||||
|
|
||||||
// Collect the bits which we require to be set at function exits.
|
// Collect the bits which we require to be set at function exits.
|
||||||
Bits expectedReturnBits(locations.getNumLocations());
|
Bits expectedReturnBits(locations.getNumLocations());
|
||||||
@@ -1296,7 +716,7 @@ void MemoryLifetimeVerifier::verify() {
|
|||||||
// blocks.
|
// blocks.
|
||||||
locations.analyzeLocations(function);
|
locations.analyzeLocations(function);
|
||||||
if (locations.getNumLocations() > 0) {
|
if (locations.getNumLocations() > 0) {
|
||||||
MemoryDataflow dataFlow(function, locations.getNumLocations());
|
BitDataflow dataFlow(function, locations.getNumLocations());
|
||||||
dataFlow.entryReachabilityAnalysis();
|
dataFlow.entryReachabilityAnalysis();
|
||||||
dataFlow.exitReachableAnalysis();
|
dataFlow.exitReachableAnalysis();
|
||||||
initDataflow(dataFlow);
|
initDataflow(dataFlow);
|
||||||
@@ -1311,7 +731,9 @@ void MemoryLifetimeVerifier::verify() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void swift::verifyMemoryLifetime(SILFunction *function) {
|
} // anonymous namespace
|
||||||
MemoryLifetimeVerifier verifier(function);
|
|
||||||
|
void SILFunction::verifyMemoryLifetime() {
|
||||||
|
MemoryLifetimeVerifier verifier(this);
|
||||||
verifier.verify();
|
verifier.verify();
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,6 @@
|
|||||||
#include "swift/SIL/Dominance.h"
|
#include "swift/SIL/Dominance.h"
|
||||||
#include "swift/SIL/DynamicCasts.h"
|
#include "swift/SIL/DynamicCasts.h"
|
||||||
#include "swift/SIL/MemAccessUtils.h"
|
#include "swift/SIL/MemAccessUtils.h"
|
||||||
#include "swift/SIL/MemoryLifetime.h"
|
|
||||||
#include "swift/SIL/OwnershipUtils.h"
|
#include "swift/SIL/OwnershipUtils.h"
|
||||||
#include "swift/SIL/PostOrder.h"
|
#include "swift/SIL/PostOrder.h"
|
||||||
#include "swift/SIL/PrettyStackTrace.h"
|
#include "swift/SIL/PrettyStackTrace.h"
|
||||||
@@ -5624,7 +5623,7 @@ public:
|
|||||||
|
|
||||||
if (F->hasOwnership() && F->shouldVerifyOwnership() &&
|
if (F->hasOwnership() && F->shouldVerifyOwnership() &&
|
||||||
!F->getModule().getASTContext().hadError()) {
|
!F->getModule().getASTContext().hadError()) {
|
||||||
verifyMemoryLifetime(F);
|
F->verifyMemoryLifetime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#include "swift/SIL/SILBuilder.h"
|
#include "swift/SIL/SILBuilder.h"
|
||||||
#include "swift/SIL/SILFunction.h"
|
#include "swift/SIL/SILFunction.h"
|
||||||
#include "swift/SIL/ApplySite.h"
|
#include "swift/SIL/ApplySite.h"
|
||||||
#include "swift/SIL/MemoryLifetime.h"
|
#include "swift/SIL/MemoryLocations.h"
|
||||||
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
||||||
#include "swift/SIL/MemAccessUtils.h"
|
#include "swift/SIL/MemAccessUtils.h"
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
#include "swift/SIL/Projection.h"
|
#include "swift/SIL/Projection.h"
|
||||||
#include "swift/SIL/SILArgument.h"
|
#include "swift/SIL/SILArgument.h"
|
||||||
#include "swift/SIL/SILBuilder.h"
|
#include "swift/SIL/SILBuilder.h"
|
||||||
#include "swift/SIL/MemoryLifetime.h"
|
#include "swift/SIL/MemoryLocations.h"
|
||||||
#include "swift/SILOptimizer/Analysis/AliasAnalysis.h"
|
#include "swift/SILOptimizer/Analysis/AliasAnalysis.h"
|
||||||
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
|
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
|
||||||
#include "swift/SILOptimizer/PassManager/Passes.h"
|
#include "swift/SILOptimizer/PassManager/Passes.h"
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
#define DEBUG_TYPE "sil-destroy-hoisting"
|
#define DEBUG_TYPE "sil-destroy-hoisting"
|
||||||
#include "swift/SIL/MemoryLifetime.h"
|
#include "swift/SIL/MemoryLocations.h"
|
||||||
|
#include "swift/SIL/BitDataflow.h"
|
||||||
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
|
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
|
||||||
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
||||||
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
|
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
|
||||||
@@ -65,7 +66,7 @@ namespace {
|
|||||||
class DestroyHoisting {
|
class DestroyHoisting {
|
||||||
|
|
||||||
using Bits = MemoryLocations::Bits;
|
using Bits = MemoryLocations::Bits;
|
||||||
using BlockState = MemoryDataflow::BlockState;
|
using BlockState = BitDataflow::BlockState;
|
||||||
|
|
||||||
SILFunction *function;
|
SILFunction *function;
|
||||||
MemoryLocations locations;
|
MemoryLocations locations;
|
||||||
@@ -78,14 +79,14 @@ class DestroyHoisting {
|
|||||||
DominanceInfo *domTree = nullptr;
|
DominanceInfo *domTree = nullptr;
|
||||||
bool madeChanges = false;
|
bool madeChanges = false;
|
||||||
|
|
||||||
void expandStores(MemoryDataflow &dataFlow);
|
void expandStores(BitDataflow &dataFlow);
|
||||||
|
|
||||||
void initDataflow(MemoryDataflow &dataFlow);
|
void initDataflow(BitDataflow &dataFlow);
|
||||||
|
|
||||||
void initDataflowInBlock(SILBasicBlock *block, BlockState &state);
|
void initDataflowInBlock(SILBasicBlock *block, BlockState &state);
|
||||||
|
|
||||||
bool canIgnoreUnreachableBlock(SILBasicBlock *block,
|
bool canIgnoreUnreachableBlock(SILBasicBlock *block,
|
||||||
MemoryDataflow &dataFlow);
|
BitDataflow &dataFlow);
|
||||||
|
|
||||||
int getDestroyedLoc(SILInstruction *I) {
|
int getDestroyedLoc(SILInstruction *I) {
|
||||||
if (auto *DAI = dyn_cast<DestroyAddrInst>(I))
|
if (auto *DAI = dyn_cast<DestroyAddrInst>(I))
|
||||||
@@ -99,7 +100,7 @@ class DestroyHoisting {
|
|||||||
|
|
||||||
void getUsedLocationsOfInst(Bits &bits, SILInstruction *Inst);
|
void getUsedLocationsOfInst(Bits &bits, SILInstruction *Inst);
|
||||||
|
|
||||||
void moveDestroys(MemoryDataflow &dataFlow);
|
void moveDestroys(BitDataflow &dataFlow);
|
||||||
|
|
||||||
bool locationOverlaps(const MemoryLocations::Location *loc,
|
bool locationOverlaps(const MemoryLocations::Location *loc,
|
||||||
const Bits &destroys);
|
const Bits &destroys);
|
||||||
@@ -117,9 +118,9 @@ class DestroyHoisting {
|
|||||||
|
|
||||||
SILValue createAddress(unsigned locIdx, SILBuilder &builder);
|
SILValue createAddress(unsigned locIdx, SILBuilder &builder);
|
||||||
|
|
||||||
void tailMerging(MemoryDataflow &dataFlow);
|
void tailMerging(BitDataflow &dataFlow);
|
||||||
|
|
||||||
bool tailMergingInBlock(SILBasicBlock *block, MemoryDataflow &dataFlow);
|
bool tailMergingInBlock(SILBasicBlock *block, BitDataflow &dataFlow);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DestroyHoisting(SILFunction *function, DominanceAnalysis *DA) :
|
DestroyHoisting(SILFunction *function, DominanceAnalysis *DA) :
|
||||||
@@ -147,7 +148,7 @@ static bool isExpansionEnabler(SILInstruction *I) {
|
|||||||
// This enables the switch-enum optimization (see above).
|
// This enables the switch-enum optimization (see above).
|
||||||
// TODO: investigate the benchmark regressions and enable store-expansion more
|
// TODO: investigate the benchmark regressions and enable store-expansion more
|
||||||
// aggressively.
|
// aggressively.
|
||||||
void DestroyHoisting::expandStores(MemoryDataflow &dataFlow) {
|
void DestroyHoisting::expandStores(BitDataflow &dataFlow) {
|
||||||
Bits usedLocs(locations.getNumLocations());
|
Bits usedLocs(locations.getNumLocations());
|
||||||
|
|
||||||
// Initialize the dataflow, which tells us which destroy_addr instructions are
|
// Initialize the dataflow, which tells us which destroy_addr instructions are
|
||||||
@@ -212,7 +213,7 @@ void DestroyHoisting::expandStores(MemoryDataflow &dataFlow) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the dataflow for moving destroys up the control flow.
|
// Initialize the dataflow for moving destroys up the control flow.
|
||||||
void DestroyHoisting::initDataflow(MemoryDataflow &dataFlow) {
|
void DestroyHoisting::initDataflow(BitDataflow &dataFlow) {
|
||||||
for (auto bs : dataFlow) {
|
for (auto bs : dataFlow) {
|
||||||
bs.data.genSet.reset();
|
bs.data.genSet.reset();
|
||||||
bs.data.killSet.reset();
|
bs.data.killSet.reset();
|
||||||
@@ -290,7 +291,7 @@ void DestroyHoisting::initDataflowInBlock(SILBasicBlock *block,
|
|||||||
// unreachable which does not touch any of our memory locations. We can just
|
// unreachable which does not touch any of our memory locations. We can just
|
||||||
// ignore those blocks.
|
// ignore those blocks.
|
||||||
bool DestroyHoisting::canIgnoreUnreachableBlock(SILBasicBlock *block,
|
bool DestroyHoisting::canIgnoreUnreachableBlock(SILBasicBlock *block,
|
||||||
MemoryDataflow &dataFlow) {
|
BitDataflow &dataFlow) {
|
||||||
assert(isa<UnreachableInst>(block->getTerminator()));
|
assert(isa<UnreachableInst>(block->getTerminator()));
|
||||||
|
|
||||||
// Is it a single unreachable-block (i.e. it has a single predecessor from
|
// Is it a single unreachable-block (i.e. it has a single predecessor from
|
||||||
@@ -384,7 +385,7 @@ static void processRemoveList(SmallVectorImpl<SILInstruction *> &toRemove) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do the actual moving of destroy_addr instructions.
|
// Do the actual moving of destroy_addr instructions.
|
||||||
void DestroyHoisting::moveDestroys(MemoryDataflow &dataFlow) {
|
void DestroyHoisting::moveDestroys(BitDataflow &dataFlow) {
|
||||||
// Don't eagerly delete destroy_addr instructions, instead put them into this
|
// Don't eagerly delete destroy_addr instructions, instead put them into this
|
||||||
// list. When we are about to "move" a destroy_addr just over some sideeffect-
|
// list. When we are about to "move" a destroy_addr just over some sideeffect-
|
||||||
// free instructions, we'll keep it at the current location.
|
// free instructions, we'll keep it at the current location.
|
||||||
@@ -630,7 +631,7 @@ SILValue DestroyHoisting::createAddress(unsigned locIdx, SILBuilder &builder) {
|
|||||||
// destroy_addr %a // will be hoisted (duplicated) into bb2 and bb2
|
// destroy_addr %a // will be hoisted (duplicated) into bb2 and bb2
|
||||||
// \endcode
|
// \endcode
|
||||||
// This is mainly a code size reduction optimization.
|
// This is mainly a code size reduction optimization.
|
||||||
void DestroyHoisting::tailMerging(MemoryDataflow &dataFlow) {
|
void DestroyHoisting::tailMerging(BitDataflow &dataFlow) {
|
||||||
|
|
||||||
// TODO: we could do a worklist algorithm here instead of iterating through
|
// TODO: we could do a worklist algorithm here instead of iterating through
|
||||||
// all the function blocks.
|
// all the function blocks.
|
||||||
@@ -644,7 +645,7 @@ void DestroyHoisting::tailMerging(MemoryDataflow &dataFlow) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool DestroyHoisting::tailMergingInBlock(SILBasicBlock *block,
|
bool DestroyHoisting::tailMergingInBlock(SILBasicBlock *block,
|
||||||
MemoryDataflow &dataFlow) {
|
BitDataflow &dataFlow) {
|
||||||
if (block->pred_empty() || block->getSinglePredecessorBlock())
|
if (block->pred_empty() || block->getSinglePredecessorBlock())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -707,7 +708,7 @@ bool DestroyHoisting::tailMergingInBlock(SILBasicBlock *block,
|
|||||||
bool DestroyHoisting::hoistDestroys() {
|
bool DestroyHoisting::hoistDestroys() {
|
||||||
locations.analyzeLocations(function);
|
locations.analyzeLocations(function);
|
||||||
if (locations.getNumLocations() > 0) {
|
if (locations.getNumLocations() > 0) {
|
||||||
MemoryDataflow dataFlow(function, locations.getNumLocations());
|
BitDataflow dataFlow(function, locations.getNumLocations());
|
||||||
dataFlow.exitReachableAnalysis();
|
dataFlow.exitReachableAnalysis();
|
||||||
|
|
||||||
// Step 1: pre-processing: expand store instructions
|
// Step 1: pre-processing: expand store instructions
|
||||||
|
|||||||
Reference in New Issue
Block a user