mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
SILOptimizer: Replace [].append(contentsOf:) with [].append(element:)
if the argument is an array literal. For example: arr += [1, 2, 3] is replaced by: arr.append(1) arr.append(2) arr.append(3) This gives considerable speedups up to 10x (for our micro-benchmarks which test this). This is based on the work of @ben-ng, who implemented the first version of this optimization (thanks!).
This commit is contained in:
@@ -135,6 +135,12 @@ public:
|
|||||||
/// Returns true on success, false otherwise.
|
/// Returns true on success, false otherwise.
|
||||||
bool replaceByValue(SILValue V);
|
bool replaceByValue(SILValue V);
|
||||||
|
|
||||||
|
/// Replace a call to append(contentsOf: ) with a series of
|
||||||
|
/// append(element: ) calls.
|
||||||
|
bool replaceByAppendingValues(SILModule &M, SILFunction *AppendFn,
|
||||||
|
const llvm::SmallVectorImpl<SILValue> &Vals,
|
||||||
|
ArrayRef<Substitution> Subs);
|
||||||
|
|
||||||
/// Hoist the call to the insert point.
|
/// Hoist the call to the insert point.
|
||||||
void hoist(SILInstruction *InsertBefore, DominanceInfo *DT) {
|
void hoist(SILInstruction *InsertBefore, DominanceInfo *DT) {
|
||||||
hoistOrCopy(InsertBefore, DT, false);
|
hoistOrCopy(InsertBefore, DT, false);
|
||||||
|
|||||||
@@ -693,3 +693,55 @@ bool swift::ArraySemanticsCall::replaceByValue(SILValue V) {
|
|||||||
removeCall();
|
removeCall();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool swift::ArraySemanticsCall::replaceByAppendingValues(
|
||||||
|
SILModule &M, SILFunction *AppendFn, const SmallVectorImpl<SILValue> &Vals,
|
||||||
|
ArrayRef<Substitution> Subs) {
|
||||||
|
assert(getKind() == ArrayCallKind::kAppendContentsOf &&
|
||||||
|
"Must be an append_contentsOf call");
|
||||||
|
assert(AppendFn && "Must provide an append SILFunction");
|
||||||
|
|
||||||
|
// We only handle loadable types.
|
||||||
|
if (any_of(Vals, [&M](SILValue V) -> bool {
|
||||||
|
return !V->getType().isLoadable(M);
|
||||||
|
}))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
CanSILFunctionType AppendFnTy = AppendFn->getLoweredFunctionType();
|
||||||
|
SILValue ArrRef = SemanticsCall->getArgument(1);
|
||||||
|
SILBuilderWithScope Builder(SemanticsCall);
|
||||||
|
auto Loc = SemanticsCall->getLoc();
|
||||||
|
auto *FnRef = Builder.createFunctionRef(Loc, AppendFn);
|
||||||
|
auto FnTy = FnRef->getType();
|
||||||
|
|
||||||
|
for (SILValue V : Vals) {
|
||||||
|
auto SubTy = V->getType();
|
||||||
|
auto &ValLowering = Builder.getModule().getTypeLowering(SubTy);
|
||||||
|
auto CopiedVal = ValLowering.emitCopyValue(Builder, Loc, V);
|
||||||
|
auto *AllocStackInst = Builder.createAllocStack(Loc, SubTy);
|
||||||
|
|
||||||
|
ValLowering.emitStoreOfCopy(Builder, Loc, CopiedVal, AllocStackInst,
|
||||||
|
IsInitialization_t::IsInitialization);
|
||||||
|
|
||||||
|
SILValue Args[] = {AllocStackInst, ArrRef};
|
||||||
|
Builder.createApply(Loc, FnRef, FnTy.substGenericArgs(M, Subs),
|
||||||
|
FnTy.castTo<SILFunctionType>()->getAllResultsType(), Subs,
|
||||||
|
Args, false);
|
||||||
|
Builder.createDeallocStack(Loc, AllocStackInst);
|
||||||
|
if (!isConsumedParameter(AppendFnTy->getParameters()[0].getConvention())) {
|
||||||
|
ValLowering.emitDestroyValue(Builder, Loc, CopiedVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CanSILFunctionType AppendContentsOfFnTy =
|
||||||
|
SemanticsCall->getReferencedFunction()->getLoweredFunctionType();
|
||||||
|
if (AppendContentsOfFnTy->getParameters()[0].getConvention() ==
|
||||||
|
ParameterConvention::Direct_Owned) {
|
||||||
|
SILValue SrcArray = SemanticsCall->getArgument(0);
|
||||||
|
Builder.createReleaseValue(SemanticsCall->getLoc(), SrcArray,
|
||||||
|
Builder.getDefaultAtomicity());
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCall();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -217,6 +217,9 @@ void addSSAPasses(SILPassPipelinePlan &P, OptimizationLevelKind OpLevel) {
|
|||||||
P.addSimplifyCFG();
|
P.addSimplifyCFG();
|
||||||
P.addSILCombine();
|
P.addSILCombine();
|
||||||
|
|
||||||
|
// Mainly for Array.append(contentsOf) optimization.
|
||||||
|
P.addArrayElementPropagation();
|
||||||
|
|
||||||
// Run the devirtualizer, specializer, and inliner. If any of these
|
// Run the devirtualizer, specializer, and inliner. If any of these
|
||||||
// makes a change we'll end up restarting the function passes on the
|
// makes a change we'll end up restarting the function passes on the
|
||||||
// current function (after optimizing any new callees).
|
// current function (after optimizing any new callees).
|
||||||
|
|||||||
@@ -11,7 +11,9 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
#define DEBUG_TYPE "array-element-propagation"
|
#define DEBUG_TYPE "array-element-propagation"
|
||||||
|
|
||||||
#include "llvm/ADT/SetVector.h"
|
#include "swift/AST/NameLookup.h"
|
||||||
|
#include "swift/AST/ParameterList.h"
|
||||||
|
#include "swift/AST/GenericEnvironment.h"
|
||||||
#include "swift/SIL/SILBasicBlock.h"
|
#include "swift/SIL/SILBasicBlock.h"
|
||||||
#include "swift/SIL/SILInstruction.h"
|
#include "swift/SIL/SILInstruction.h"
|
||||||
#include "swift/SIL/DebugUtils.h"
|
#include "swift/SIL/DebugUtils.h"
|
||||||
@@ -23,12 +25,14 @@
|
|||||||
using namespace swift;
|
using namespace swift;
|
||||||
|
|
||||||
/// Propagate the elements of array values to calls of the array's get_element
|
/// Propagate the elements of array values to calls of the array's get_element
|
||||||
/// method.
|
/// method, and replace calls of append(contentsOf:) with append(element:).
|
||||||
///
|
///
|
||||||
/// Array literal construction and array initialization of array values
|
/// Array literal construction and array initialization of array values
|
||||||
/// associates element values with the array value. These values can be
|
/// associates element values with the array value. These values can be
|
||||||
/// propagated to the get_element method if we can prove that the array value
|
/// propagated to the get_element method if we can prove that the array value
|
||||||
/// has not changed until reading the array value's element.
|
/// has not changed until reading the array value's element. These values can
|
||||||
|
/// also be used to replace append(contentsOf:) with multiple append(element:)
|
||||||
|
/// calls.
|
||||||
///
|
///
|
||||||
/// Propagation of the elements of one array allocation.
|
/// Propagation of the elements of one array allocation.
|
||||||
///
|
///
|
||||||
@@ -42,53 +46,72 @@ using namespace swift;
|
|||||||
/// The stores on the returned array element buffer pointer.
|
/// The stores on the returned array element buffer pointer.
|
||||||
///
|
///
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
/// Utility class for analysis array literal initializations.
|
||||||
|
///
|
||||||
|
/// Array literals are initialized by allocating an array buffer, and storing
|
||||||
|
/// the elements into it.
|
||||||
|
/// This class analysis all the code which does the array literal
|
||||||
|
/// initialization. It also collects uses of the array, like getElement calls
|
||||||
|
/// and append(contentsOf) calls.
|
||||||
class ArrayAllocation {
|
class ArrayAllocation {
|
||||||
/// The array allocation call.
|
|
||||||
ApplyInst *Alloc;
|
|
||||||
/// The array value returned by the allocation call.
|
/// The array value returned by the allocation call.
|
||||||
SILValue ArrayValue;
|
SILValue ArrayValue;
|
||||||
|
|
||||||
/// The pointer to the returned array element buffer pointer.
|
/// The calls to Array get_element that use this array allocation.
|
||||||
SILValue ElementBuffer;
|
|
||||||
|
|
||||||
// The calls to Array get_element that use this array allocation.
|
|
||||||
llvm::SmallSetVector<ApplyInst *, 16> GetElementCalls;
|
llvm::SmallSetVector<ApplyInst *, 16> GetElementCalls;
|
||||||
|
|
||||||
|
/// The calls to Array append_contentsOf that use this array allocation.
|
||||||
|
llvm::SmallVector<ApplyInst *, 4> AppendContentsOfCalls;
|
||||||
|
|
||||||
|
/// A map of Array indices to element values
|
||||||
llvm::DenseMap<uint64_t, SILValue> ElementValueMap;
|
llvm::DenseMap<uint64_t, SILValue> ElementValueMap;
|
||||||
|
|
||||||
// Array get_element calls and their matching array element value for later
|
bool mapInitializationStores(SILValue ElementBuffer);
|
||||||
// replacement.
|
|
||||||
llvm::SmallVectorImpl<std::pair<ApplyInst *, SILValue>> &ReplacementMap;
|
|
||||||
|
|
||||||
ArrayAllocation(
|
|
||||||
ApplyInst *AI,
|
|
||||||
llvm::SmallVectorImpl<std::pair<ApplyInst *, SILValue>> &Replacements)
|
|
||||||
: Alloc(AI), ReplacementMap(Replacements) {}
|
|
||||||
|
|
||||||
bool findValueReplacements();
|
|
||||||
bool isInitializationWithKnownElements();
|
|
||||||
bool mapInitializationStores();
|
|
||||||
bool analyzeArrayValueUses();
|
|
||||||
bool recursivelyCollectUses(ValueBase *Def);
|
bool recursivelyCollectUses(ValueBase *Def);
|
||||||
bool collectForwardableValues();
|
bool collectForwardableValues();
|
||||||
|
bool replacementsAreValid();
|
||||||
|
|
||||||
|
// After approx. this many elements, it's faster to use append(contentsOf:)
|
||||||
|
static constexpr unsigned APPEND_CONTENTSOF_REPLACEMENT_VALUES_MAX = 6;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/// Find a set of get_element calls that can be replace by the initialization
|
/// Specifies the value with which a get-element call can be replaced.
|
||||||
/// value of the array allocation call.
|
struct GetElementReplacement {
|
||||||
///
|
ApplyInst *GetElementCall;
|
||||||
/// Returns true if an access can be replaced. The replacements are stored in
|
SILValue Replacement;
|
||||||
/// the \p ReplacementMap.
|
};
|
||||||
static bool findValueReplacements(
|
|
||||||
ApplyInst *Inst,
|
|
||||||
llvm::SmallVectorImpl<std::pair<ApplyInst *, SILValue>> &Replacements) {
|
|
||||||
return ArrayAllocation(Inst, Replacements).findValueReplacements();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // end anonymous namespace
|
|
||||||
|
|
||||||
|
/// Specifies the set of elements with which a append-contentof call can be
|
||||||
|
/// replaced.
|
||||||
|
struct AppendContentOfReplacement {
|
||||||
|
ApplyInst *AppendContentOfCall;
|
||||||
|
llvm::SmallVector<SILValue, 4> ReplacementValues;
|
||||||
|
SILValue Array;
|
||||||
|
};
|
||||||
|
|
||||||
|
ArrayAllocation() {}
|
||||||
|
|
||||||
|
/// Analyzes an array allocation call.
|
||||||
|
///
|
||||||
|
/// Returns true if \p Alloc is the allocation of an array literal (or a
|
||||||
|
/// similar pattern) and the array values can be used to replace get_element
|
||||||
|
/// or append(contentof) calls.
|
||||||
|
bool analyze(ApplyInst *Alloc);
|
||||||
|
|
||||||
|
/// Gets the list of get_element calls which can be replaced.
|
||||||
|
void getGetElementReplacements(
|
||||||
|
llvm::SmallVectorImpl<GetElementReplacement> &Replacements);
|
||||||
|
|
||||||
|
/// Gets the list of append(contentof) calls which can be replaced by a
|
||||||
|
/// set of values.
|
||||||
|
void getAppendContentOfReplacements(
|
||||||
|
llvm::SmallVectorImpl<AppendContentOfReplacement> &Replacements);
|
||||||
|
};
|
||||||
|
|
||||||
/// Map the indices of array element initialization stores to their values.
|
/// Map the indices of array element initialization stores to their values.
|
||||||
bool ArrayAllocation::mapInitializationStores() {
|
bool ArrayAllocation::mapInitializationStores(SILValue ElementBuffer) {
|
||||||
assert(ElementBuffer &&
|
assert(ElementBuffer &&
|
||||||
"Must have identified an array element storage pointer");
|
"Must have identified an array element storage pointer");
|
||||||
|
|
||||||
@@ -148,44 +171,18 @@ bool ArrayAllocation::mapInitializationStores() {
|
|||||||
return !ElementValueMap.empty();
|
return !ElementValueMap.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check that we have an array initialization call with known elements.
|
bool ArrayAllocation::replacementsAreValid() {
|
||||||
///
|
unsigned ElementCount = ElementValueMap.size();
|
||||||
/// The returned array value is known not to be aliased since it was just
|
|
||||||
/// allocated.
|
|
||||||
bool ArrayAllocation::isInitializationWithKnownElements() {
|
|
||||||
ArraySemanticsCall Uninitialized(Alloc, "array.uninitialized");
|
|
||||||
if (Uninitialized &&
|
|
||||||
(ArrayValue = Uninitialized.getArrayValue()) &&
|
|
||||||
(ElementBuffer = Uninitialized.getArrayElementStoragePointer()))
|
|
||||||
return mapInitializationStores();
|
|
||||||
|
|
||||||
return false;
|
if (ElementCount > APPEND_CONTENTSOF_REPLACEMENT_VALUES_MAX)
|
||||||
}
|
|
||||||
|
|
||||||
/// Propagate the elements of an array literal to get_element method calls on
|
|
||||||
/// the same array.
|
|
||||||
///
|
|
||||||
/// We have to prove that the array value is not changed in between the
|
|
||||||
/// creation and the method call to get_element.
|
|
||||||
bool ArrayAllocation::findValueReplacements() {
|
|
||||||
if (!isInitializationWithKnownElements())
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// The array value was stored or has escaped.
|
// Bail if elements aren't contiguous
|
||||||
if (!analyzeArrayValueUses())
|
for (unsigned i = 0; i < ElementCount; ++i)
|
||||||
return false;
|
if (!ElementValueMap.count(i))
|
||||||
|
return false;
|
||||||
|
|
||||||
// No count users.
|
return true;
|
||||||
if (GetElementCalls.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return collectForwardableValues();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collect all get_element users and check that there are no escapes or uses
|
|
||||||
/// that could change the array value.
|
|
||||||
bool ArrayAllocation::analyzeArrayValueUses() {
|
|
||||||
return recursivelyCollectUses(ArrayValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recursively look at all uses of this definition. Abort if the array value
|
/// Recursively look at all uses of this definition. Abort if the array value
|
||||||
@@ -206,10 +203,16 @@ bool ArrayAllocation::recursivelyCollectUses(ValueBase *Def) {
|
|||||||
|
|
||||||
// Check array semantic calls.
|
// Check array semantic calls.
|
||||||
ArraySemanticsCall ArrayOp(User);
|
ArraySemanticsCall ArrayOp(User);
|
||||||
if (ArrayOp && ArrayOp.doesNotChangeArray()) {
|
if (ArrayOp) {
|
||||||
if (ArrayOp.getKind() == ArrayCallKind::kGetElement)
|
if (ArrayOp.getKind() == ArrayCallKind::kAppendContentsOf) {
|
||||||
|
AppendContentsOfCalls.push_back(ArrayOp);
|
||||||
|
continue;
|
||||||
|
} else if (ArrayOp.getKind() == ArrayCallKind::kGetElement) {
|
||||||
GetElementCalls.insert(ArrayOp);
|
GetElementCalls.insert(ArrayOp);
|
||||||
continue;
|
continue;
|
||||||
|
} else if (ArrayOp.doesNotChangeArray()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An operation that escapes or modifies the array value.
|
// An operation that escapes or modifies the array value.
|
||||||
@@ -218,9 +221,32 @@ bool ArrayAllocation::recursivelyCollectUses(ValueBase *Def) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look at the get_element calls and match them to values by index.
|
bool ArrayAllocation::analyze(ApplyInst *Alloc) {
|
||||||
bool ArrayAllocation::collectForwardableValues() {
|
ArraySemanticsCall Uninitialized(Alloc, "array.uninitialized");
|
||||||
bool FoundForwardableValue = false;
|
if (!Uninitialized)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ArrayValue = Uninitialized.getArrayValue();
|
||||||
|
if (!ArrayValue)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SILValue ElementBuffer = Uninitialized.getArrayElementStoragePointer();
|
||||||
|
if (!ElementBuffer)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Figure out all stores to the array.
|
||||||
|
if (!mapInitializationStores(ElementBuffer))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Check if the array value was stored or has escaped.
|
||||||
|
if (!recursivelyCollectUses(ArrayValue))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArrayAllocation::getGetElementReplacements(
|
||||||
|
llvm::SmallVectorImpl<GetElementReplacement> &Replacements) {
|
||||||
for (auto *GetElementCall : GetElementCalls) {
|
for (auto *GetElementCall : GetElementCalls) {
|
||||||
ArraySemanticsCall GetElement(GetElementCall);
|
ArraySemanticsCall GetElement(GetElementCall);
|
||||||
assert(GetElement.getKind() == ArrayCallKind::kGetElement);
|
assert(GetElement.getKind() == ArrayCallKind::kGetElement);
|
||||||
@@ -235,19 +261,29 @@ bool ArrayAllocation::collectForwardableValues() {
|
|||||||
if (EltValueIt == ElementValueMap.end())
|
if (EltValueIt == ElementValueMap.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ReplacementMap.push_back(
|
Replacements.push_back({GetElementCall, EltValueIt->second});
|
||||||
std::make_pair(GetElementCall, EltValueIt->second));
|
|
||||||
FoundForwardableValue = true;
|
|
||||||
}
|
}
|
||||||
return FoundForwardableValue;
|
}
|
||||||
|
|
||||||
|
void ArrayAllocation::getAppendContentOfReplacements(
|
||||||
|
llvm::SmallVectorImpl<AppendContentOfReplacement> &Replacements) {
|
||||||
|
if (AppendContentsOfCalls.empty())
|
||||||
|
return;
|
||||||
|
if (!replacementsAreValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
llvm::SmallVector<SILValue, 4> ElementValueVector;
|
||||||
|
for (unsigned i = 0; i < ElementValueMap.size(); ++i)
|
||||||
|
ElementValueVector.push_back(ElementValueMap[i]);
|
||||||
|
|
||||||
|
for (auto *Call : AppendContentsOfCalls)
|
||||||
|
Replacements.push_back({Call, ElementValueVector, ArrayValue});
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Driver
|
// Driver
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
class ArrayElementPropagation : public SILFunctionTransform {
|
class ArrayElementPropagation : public SILFunctionTransform {
|
||||||
public:
|
public:
|
||||||
ArrayElementPropagation() {}
|
ArrayElementPropagation() {}
|
||||||
@@ -256,30 +292,83 @@ public:
|
|||||||
return "Array Element Propagation";
|
return "Array Element Propagation";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool replaceAppendCalls(
|
||||||
|
ArrayRef<ArrayAllocation::AppendContentOfReplacement> Repls) {
|
||||||
|
auto &Fn = *getFunction();
|
||||||
|
auto &M = Fn.getModule();
|
||||||
|
auto &Ctx = M.getASTContext();
|
||||||
|
|
||||||
|
if (Repls.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DEBUG(llvm::dbgs() << "Array append contentsOf calls replaced in "
|
||||||
|
<< Fn.getName() << " (" << Repls.size() << ")\n");
|
||||||
|
|
||||||
|
auto *AppendFnDecl = Ctx.getArrayAppendElementDecl();
|
||||||
|
if (!AppendFnDecl)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto Mangled = SILDeclRef(AppendFnDecl, SILDeclRef::Kind::Func).mangle();
|
||||||
|
auto *AppendFn = M.findFunction(Mangled, SILLinkage::PublicExternal);
|
||||||
|
if (!AppendFn)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (const ArrayAllocation::AppendContentOfReplacement &Repl : Repls) {
|
||||||
|
ArraySemanticsCall AppendContentsOf(Repl.AppendContentOfCall);
|
||||||
|
assert(AppendContentsOf && "Must be AppendContentsOf call");
|
||||||
|
|
||||||
|
SILType ArrayType = Repl.Array->getType();
|
||||||
|
auto *NTD = ArrayType.getSwiftRValueType()->getAnyNominal();
|
||||||
|
SubstitutionMap ArraySubMap = ArrayType.getSwiftRValueType()
|
||||||
|
->getContextSubstitutionMap(M.getSwiftModule(), NTD);
|
||||||
|
|
||||||
|
GenericSignature *Sig = NTD->getGenericSignature();
|
||||||
|
assert(Sig && "Array type must have generic signature");
|
||||||
|
SmallVector<Substitution, 4> Subs;
|
||||||
|
Sig->getSubstitutions(ArraySubMap, Subs);
|
||||||
|
|
||||||
|
AppendContentsOf.replaceByAppendingValues(M, AppendFn,
|
||||||
|
Repl.ReplacementValues, Subs);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void run() override {
|
void run() override {
|
||||||
auto &Fn = *getFunction();
|
auto &Fn = *getFunction();
|
||||||
|
|
||||||
bool Changed = false;
|
|
||||||
|
|
||||||
// Propagate the elements an of array value to its users.
|
// Propagate the elements an of array value to its users.
|
||||||
SmallVector<std::pair<ApplyInst *, SILValue>, 16> ValueReplacements;
|
llvm::SmallVector<ArrayAllocation::GetElementReplacement, 16>
|
||||||
|
GetElementReplacements;
|
||||||
|
llvm::SmallVector<ArrayAllocation::AppendContentOfReplacement, 4>
|
||||||
|
AppendContentsOfReplacements;
|
||||||
|
|
||||||
for (auto &BB :Fn) {
|
for (auto &BB :Fn) {
|
||||||
for (auto &Inst : BB) {
|
for (auto &Inst : BB) {
|
||||||
if (auto *Apply = dyn_cast<ApplyInst>(&Inst))
|
if (auto *Apply = dyn_cast<ApplyInst>(&Inst)) {
|
||||||
Changed |=
|
ArrayAllocation ALit;
|
||||||
ArrayAllocation::findValueReplacements(Apply, ValueReplacements);
|
if (ALit.analyze(Apply)) {
|
||||||
|
ALit.getGetElementReplacements(GetElementReplacements);
|
||||||
|
ALit.getAppendContentOfReplacements(AppendContentsOfReplacements);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DEBUG(if (Changed) {
|
|
||||||
|
DEBUG(if (!GetElementReplacements.empty()) {
|
||||||
llvm::dbgs() << "Array elements replaced in " << Fn.getName() << " ("
|
llvm::dbgs() << "Array elements replaced in " << Fn.getName() << " ("
|
||||||
<< ValueReplacements.size() << ")\n";
|
<< GetElementReplacements.size() << ")\n";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bool Changed = false;
|
||||||
|
|
||||||
// Perform the actual replacement of the get_element call by its value.
|
// Perform the actual replacement of the get_element call by its value.
|
||||||
for (auto &Repl : ValueReplacements) {
|
for (ArrayAllocation::GetElementReplacement &Repl : GetElementReplacements) {
|
||||||
ArraySemanticsCall GetElement(Repl.first);
|
ArraySemanticsCall GetElement(Repl.GetElementCall);
|
||||||
GetElement.replaceByValue(Repl.second);
|
Changed |= GetElement.replaceByValue(Repl.Replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Changed |= replaceAppendCalls(AppendContentsOfReplacements);
|
||||||
|
|
||||||
if (Changed) {
|
if (Changed) {
|
||||||
PM->invalidateAnalysis(
|
PM->invalidateAnalysis(
|
||||||
&Fn, SILAnalysis::InvalidationKind::CallsAndInstructions);
|
&Fn, SILAnalysis::InvalidationKind::CallsAndInstructions);
|
||||||
|
|||||||
52
test/SILOptimizer/array_contentof_opt.swift
Normal file
52
test/SILOptimizer/array_contentof_opt.swift
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// RUN: %target-swift-frontend -O -emit-sil %s | %FileCheck %s
|
||||||
|
// REQUIRES: swift_stdlib_no_asserts,optimized_stdlib
|
||||||
|
|
||||||
|
// This is an end-to-end test of the array(contentsOf) -> array(Element) optimization
|
||||||
|
|
||||||
|
// CHECK-LABEL: sil @{{.*}}testInt
|
||||||
|
// CHECK-NOT: apply
|
||||||
|
// CHECK: [[F:%[0-9]+]] = function_ref @_T0Sa6appendyxFSi_Tg
|
||||||
|
// CHECK-NOT: apply
|
||||||
|
// CHECK: apply [[F]]
|
||||||
|
// CHECK-NEXT: tuple
|
||||||
|
// CHECK-NEXT: return
|
||||||
|
public func testInt(_ a: inout [Int]) {
|
||||||
|
a += [1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: sil @{{.*}}testThreeInt
|
||||||
|
// CHECK-NOT: apply
|
||||||
|
// CHECK: [[F:%[0-9]+]] = function_ref @_T0Sa6appendyxFSi_Tg
|
||||||
|
// CHECK-NOT: apply
|
||||||
|
// CHECK: apply [[F]]
|
||||||
|
// CHECK-NEXT: apply [[F]]
|
||||||
|
// CHECK-NEXT: apply [[F]]
|
||||||
|
// CHECK-NEXT: tuple
|
||||||
|
// CHECK-NEXT: return
|
||||||
|
public func testThreeInts(_ a: inout [Int]) {
|
||||||
|
a += [1, 2, 3]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: sil @{{.*}}testTooManyInts
|
||||||
|
// CHECK-NOT: apply
|
||||||
|
// CHECK: [[F:%[0-9]+]] = function_ref @_T0Sa6appendyqd__10contentsOf_t8Iterator_7ElementQYd__Rszs8SequenceRd__lF
|
||||||
|
// CHECK-NOT: apply
|
||||||
|
// CHECK: apply [[F]]
|
||||||
|
// CHECK-NOT: apply
|
||||||
|
// CHECK: return
|
||||||
|
public func testTooManyInts(_ a: inout [Int]) {
|
||||||
|
a += [1, 2, 3, 4, 5, 6, 7]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: sil @{{.*}}testString
|
||||||
|
// CHECK-NOT: apply
|
||||||
|
// CHECK: [[F:%[0-9]+]] = function_ref @_T0Sa6appendyxFSS_Tg
|
||||||
|
// CHECK-NOT: apply
|
||||||
|
// CHECK: apply [[F]]
|
||||||
|
// CHECK-NOT: apply
|
||||||
|
// CHECK: tuple
|
||||||
|
// CHECK-NEXT: return
|
||||||
|
public func testString(_ a: inout [String], s: String) {
|
||||||
|
a += [s]
|
||||||
|
}
|
||||||
|
|
||||||
@@ -31,6 +31,9 @@ sil [_semantics "array.check_subscript"] @checkSubscript : $@convention(method)
|
|||||||
sil [_semantics "array.get_element"] @getElement : $@convention(method) (MyInt, MyBool, _MyDependenceToken, @guaranteed MyArray<MyInt>) -> @out MyInt
|
sil [_semantics "array.get_element"] @getElement : $@convention(method) (MyInt, MyBool, _MyDependenceToken, @guaranteed MyArray<MyInt>) -> @out MyInt
|
||||||
sil [_semantics "array.get_element"] @getElement2 : $@convention(method) (MyInt, MyBool, _MyDependenceToken, @guaranteed MyArray<MyInt>) -> MyInt
|
sil [_semantics "array.get_element"] @getElement2 : $@convention(method) (MyInt, MyBool, _MyDependenceToken, @guaranteed MyArray<MyInt>) -> MyInt
|
||||||
sil @unknown_array_use : $@convention(method) (@guaranteed MyArray<MyInt>) -> MyBool
|
sil @unknown_array_use : $@convention(method) (@guaranteed MyArray<MyInt>) -> MyBool
|
||||||
|
sil [_semantics "array.uninitialized"] @arrayAdoptStorage : $@convention(thin) (@owned AnyObject, MyInt, @thin Array<MyInt>.Type) -> @owned (Array<MyInt>, UnsafeMutablePointer<MyInt>)
|
||||||
|
sil @arrayInit : $@convention(method) (@thin Array<MyInt>.Type) -> @owned Array<MyInt>
|
||||||
|
sil [_semantics "array.append_contentsOf"] @arrayAppendContentsOf : $@convention(method) (@owned Array<MyInt>, @inout Array<MyInt>) -> ()
|
||||||
|
|
||||||
// CHECK-LABEL: sil @propagate01
|
// CHECK-LABEL: sil @propagate01
|
||||||
// CHECK: struct $MyInt
|
// CHECK: struct $MyInt
|
||||||
@@ -300,3 +303,83 @@ sil @unknown_use : $@convention(thin) () -> () {
|
|||||||
strong_release %25 : $Builtin.BridgeObject
|
strong_release %25 : $Builtin.BridgeObject
|
||||||
return %52 : $()
|
return %52 : $()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CHECK-LABEL: sil @append_contentsOf_int
|
||||||
|
// CHECK: [[ASFUN:%.*]] = function_ref @arrayAdoptStorage
|
||||||
|
// CHECK-NEXT: [[ARR:%.*]] = apply [[ASFUN]]
|
||||||
|
// CHECK-NEXT: [[OWNER:%.*]] = tuple_extract [[ARR]]{{.*}}, 0
|
||||||
|
// CHECK-NOT: apply [[ACFUN]]
|
||||||
|
// CHECK: [[AEFUN:%.*]] = function_ref @_T0Sa6appendyxF
|
||||||
|
// CHECK-NEXT: [[STACK:%.*]] = alloc_stack $MyInt
|
||||||
|
// CHECK-NEXT: store %{{[0-9]+}} to [[STACK]]
|
||||||
|
// CHECK-NEXT: apply [[AEFUN]]<MyInt>([[STACK]]
|
||||||
|
// CHECK-NEXT: dealloc_stack [[STACK]]
|
||||||
|
// CHECK-NEXT: release_value [[OWNER]]
|
||||||
|
// CHECK: return
|
||||||
|
sil @append_contentsOf_int : $@convention(thin) () -> () {
|
||||||
|
%0 = function_ref @swift_bufferAllocate : $@convention(thin) () -> @owned AnyObject
|
||||||
|
%1 = integer_literal $Builtin.Int64, 1
|
||||||
|
%2 = struct $MyInt (%1 : $Builtin.Int64)
|
||||||
|
%3 = apply %0() : $@convention(thin) () -> @owned AnyObject
|
||||||
|
%4 = metatype $@thin Array<MyInt>.Type
|
||||||
|
%5 = function_ref @arrayAdoptStorage : $@convention(thin) (@owned AnyObject, MyInt, @thin Array<MyInt>.Type) -> @owned (Array<MyInt>, UnsafeMutablePointer<MyInt>)
|
||||||
|
%6 = apply %5(%3, %2, %4) : $@convention(thin) (@owned AnyObject, MyInt, @thin Array<MyInt>.Type) -> @owned (Array<MyInt>, UnsafeMutablePointer<MyInt>)
|
||||||
|
%7 = tuple_extract %6 : $(Array<MyInt>, UnsafeMutablePointer<MyInt>), 0
|
||||||
|
%8 = tuple_extract %6 : $(Array<MyInt>, UnsafeMutablePointer<MyInt>), 1
|
||||||
|
%9 = struct_extract %8 : $UnsafeMutablePointer<MyInt>, #UnsafeMutablePointer._rawValue
|
||||||
|
%10 = pointer_to_address %9 : $Builtin.RawPointer to [strict] $*MyInt
|
||||||
|
%11 = integer_literal $Builtin.Int64, 27
|
||||||
|
%12 = struct $MyInt (%11 : $Builtin.Int64)
|
||||||
|
store %12 to %10 : $*MyInt
|
||||||
|
%13 = alloc_stack $Array<MyInt>
|
||||||
|
%14 = metatype $@thin Array<MyInt>.Type
|
||||||
|
%15 = function_ref @arrayInit : $@convention(method) (@thin Array<MyInt>.Type) -> @owned Array<MyInt>
|
||||||
|
%16 = apply %15(%14) : $@convention(method) (@thin Array<MyInt>.Type) -> @owned Array<MyInt>
|
||||||
|
store %16 to %13 : $*Array<MyInt>
|
||||||
|
%17 = function_ref @arrayAppendContentsOf : $@convention(method) (@owned Array<MyInt>, @inout Array<MyInt>) -> ()
|
||||||
|
%18 = apply %17(%7, %13) : $@convention(method) (@owned Array<MyInt>, @inout Array<MyInt>) -> ()
|
||||||
|
dealloc_stack %13 : $*Array<MyInt>
|
||||||
|
%19 = tuple ()
|
||||||
|
return %19 : $()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Hello {
|
||||||
|
}
|
||||||
|
|
||||||
|
sil [_semantics "array.uninitialized"] @adoptStorageHello : $@convention(method) (@owned _ContiguousArrayStorage<Hello>, MyInt, @thin Array<Hello>.Type) -> (@owned Array<Hello>, UnsafeMutablePointer<Hello>)
|
||||||
|
sil [_semantics "array.append_contentsOf"] @arrayAppendContentsOfHello : $@convention(method) (@owned Array<Hello>, @inout Array<Hello>) -> ()
|
||||||
|
|
||||||
|
// CHECK-LABEL: sil @append_contentsOf_class
|
||||||
|
// CHECK: [[ASFUN:%.*]] = function_ref @adoptStorageHello
|
||||||
|
// CHECK-NEXT: [[ARR:%.*]] = apply [[ASFUN]]
|
||||||
|
// CHECK-NEXT: [[OWNER:%.*]] = tuple_extract [[ARR]]{{.*}}, 0
|
||||||
|
// CHECK: strong_retain %1 : $Hello
|
||||||
|
// CHECK-NEXT: store %1 to %{{[0-9]+}} : $*Hello
|
||||||
|
// CHECK-NOT: apply
|
||||||
|
// CHECK: [[AEFUN:%.*]] = function_ref @_T0Sa6appendyxF
|
||||||
|
// CHECK-NEXT: strong_retain %1 : $Hello
|
||||||
|
// CHECK-NEXT: [[STACK:%.*]] = alloc_stack $Hello
|
||||||
|
// CHECK-NEXT: store %1 to [[STACK]]
|
||||||
|
// CHECK-NEXT: apply [[AEFUN]]<Hello>([[STACK]], %0)
|
||||||
|
// CHECK-NEXT: dealloc_stack [[STACK]]
|
||||||
|
// CHECK-NEXT: release_value [[OWNER]]
|
||||||
|
// CHECK-NEXT: return
|
||||||
|
sil @append_contentsOf_class : $@convention(thin) (@inout Array<Hello>, @owned Hello) -> @owned Hello {
|
||||||
|
bb0(%0 : $*Array<Hello>, %1 : $Hello):
|
||||||
|
%4 = integer_literal $Builtin.Word, 1
|
||||||
|
%5 = integer_literal $Builtin.Int64, 1
|
||||||
|
%6 = struct $MyInt (%5 : $Builtin.Int64)
|
||||||
|
%7 = alloc_ref [tail_elems $Hello * %4 : $Builtin.Word] $_ContiguousArrayStorage<Hello>
|
||||||
|
%8 = metatype $@thin Array<Hello>.Type
|
||||||
|
%9 = function_ref @adoptStorageHello : $@convention(method) (@owned _ContiguousArrayStorage<Hello>, MyInt, @thin Array<Hello>.Type) -> (@owned Array<Hello>, UnsafeMutablePointer<Hello>)
|
||||||
|
%10 = apply %9(%7, %6, %8) : $@convention(method) (@owned _ContiguousArrayStorage<Hello>, MyInt, @thin Array<Hello>.Type) -> (@owned Array<Hello>, UnsafeMutablePointer<Hello>)
|
||||||
|
%11 = tuple_extract %10 : $(Array<Hello>, UnsafeMutablePointer<Hello>), 0
|
||||||
|
%12 = tuple_extract %10 : $(Array<Hello>, UnsafeMutablePointer<Hello>), 1
|
||||||
|
%13 = struct_extract %12 : $UnsafeMutablePointer<Hello>, #UnsafeMutablePointer._rawValue
|
||||||
|
%22 = pointer_to_address %13 : $Builtin.RawPointer to [strict] $*Hello
|
||||||
|
strong_retain %1 : $Hello
|
||||||
|
store %1 to %22 : $*Hello
|
||||||
|
%25 = function_ref @arrayAppendContentsOfHello : $@convention(method) (@owned Array<Hello>, @inout Array<Hello>) -> ()
|
||||||
|
%26 = apply %25(%11, %0) : $@convention(method) (@owned Array<Hello>, @inout Array<Hello>) -> ()
|
||||||
|
return %1 : $Hello
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user