| //===--- GlobalPropertyOpt.cpp - Optimizes global array properties --------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #define DEBUG_TYPE "globalpropertyopt" |
| #include "swift/SIL/SILArgument.h" |
| #include "swift/SIL/SILBuilder.h" |
| #include "swift/SIL/SILFunction.h" |
| #include "swift/SIL/SILInstruction.h" |
| #include "swift/SIL/SILModule.h" |
| #include "swift/SILOptimizer/Analysis/ArraySemantic.h" |
| #include "swift/SILOptimizer/PassManager/Passes.h" |
| #include "swift/SILOptimizer/PassManager/Transforms.h" |
| #include "swift/SILOptimizer/Utils/InstOptUtils.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/DenseMapInfo.h" |
| #include "llvm/ADT/Statistic.h" |
| #include "llvm/Support/Allocator.h" |
| #include "llvm/Support/Debug.h" |
| |
| using namespace swift; |
| |
| STATISTIC(NumPropertiesReplaced, "Number of array property calls replaced"); |
| |
| namespace { |
| |
| /// The GlobalPropertyOpt performs an analysis on the whole module to determine |
| /// the values of high-level properties. |
| /// |
| /// Currently only one property is handled and that's the isNativeTypeChecked |
| /// property for arrays. If the property can be proved to be true, the |
| /// corresponding semantics-call is replaced by a true-literal. |
| class GlobalPropertyOpt { |
| |
| /// An entry in the dependency graph. An entry can represent |
| /// *) a value of type Array, |
| /// *) a value of type tuple, which contains an Array, |
| /// *) an AllocStack instruction which allocates an Array or |
| /// *) a struct or class field of type Array. |
| struct Entry { |
| |
| Entry(SILValue Value, VarDecl *Field) : |
| Value(Value), Field(Field), isNativeTypeChecked(true) { |
| } |
| |
| /// Non-null if the entry represents an array value, a tuple with an array |
| /// or an AllocStack of an array. |
| SILValue Value; |
| |
| /// Non-null if the entry represents a struct or class field. |
| VarDecl *Field; |
| |
| /// The property which we want to track: is the value/field a native swift |
| /// array which doesn't need deferred type check. |
| bool isNativeTypeChecked; |
| |
| /// The edges in the dependency graph, i.e. entries, which depend on this |
| /// entry. |
| SmallVector<Entry *, 8> Dependencies; |
| |
| #ifndef NDEBUG |
| friend raw_ostream &operator<<(raw_ostream &os, const Entry &entry) { |
| if (entry.Field) |
| return os << "field " << entry.Field->getName() << '\n'; |
| if (!entry.Value) |
| return os << "unknown-address\n"; |
| if (auto *Inst = entry.Value->getDefiningInstruction()) |
| return os << Inst->getFunction()->getName() << ": " << entry.Value; |
| if (auto *Arg = dyn_cast<SILArgument>(entry.Value)) |
| return os << Arg->getFunction()->getName() << ": " << entry.Value; |
| return os << entry.Value; |
| } |
| #endif |
| }; |
| |
| /// The module that we are optimizing. |
| SILModule &M; |
| |
| NominalTypeDecl *ArrayType; |
| |
| /// All entries of the dependency graph, which represent values or AllocStack. |
| llvm::DenseMap<SILValue, Entry *> ValueEntries; |
| |
| /// All entries of the dependency graph, which represent fields. |
| llvm::DenseMap<VarDecl *, Entry *> FieldEntries; |
| |
| llvm::SpecificBumpPtrAllocator<Entry> EntryAllocator; |
| |
| /// Represents an address of an unknown array. |
| Entry unknownAddressEntry = Entry(SILValue(), nullptr); |
| |
| /// All found calls to get-property semantic functions. |
| std::vector<ApplyInst *> propertyCalls; |
| |
| llvm::SetVector<SILFunction *> ChangedFunctions; |
| |
| /// Contains entries with a false property value, which must be propagated |
| /// to their dependencies. |
| llvm::SmallVector<Entry *, 32> WorkList; |
| |
| bool isArrayType(SILType type) { |
| return type.getNominalOrBoundGenericNominal() == ArrayType && |
| !type.isAddress(); |
| } |
| |
| bool isArrayAddressType(SILType type) { |
| return type.getNominalOrBoundGenericNominal() == ArrayType && |
| type.isAddress(); |
| } |
| |
| /// Returns true if the type is a tuple which contains at least one array |
| /// (we don't check for arrays in nested tuples). |
| bool isTupleWithArray(CanType type) { |
| if (auto tuple = dyn_cast<TupleType>(type)) { |
| for (Type subType : tuple->getElementTypes()) { |
| if (CanType(subType).getNominalOrBoundGenericNominal() == ArrayType) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static bool canAddressEscape(SILValue V, bool acceptStore); |
| |
| /// Gets the entry for a struct or class field. |
| Entry *getFieldEntry(VarDecl *Field) { |
| Entry * &entry = FieldEntries[Field]; |
| if (!entry) { |
| entry = new (EntryAllocator.Allocate()) Entry(SILValue(), Field); |
| if (M.isVisibleExternally(Field)) |
| setAddressEscapes(entry); |
| } |
| return entry; |
| } |
| |
| /// Gets the entry for a value at an address, e.g. a struct/class field or |
| /// an alloc_stack. |
| Entry *getAddrEntry(SILValue value) { |
| ValueBase *def = value; |
| if (auto *MDI = dyn_cast<MarkDependenceInst>(def)) { |
| return getAddrEntry(MDI->getOperand(0)); |
| } |
| if (auto *RAI = dyn_cast<RefElementAddrInst>(def)) { |
| return getFieldEntry(RAI->getField()); |
| } |
| if (auto *SEI = dyn_cast<StructElementAddrInst>(def)) { |
| return getFieldEntry(SEI->getField()); |
| } |
| if (isa<AllocStackInst>(def)) { |
| Entry * &entry = ValueEntries[value]; |
| if (!entry) { |
| entry = new (EntryAllocator.Allocate()) Entry(value, nullptr); |
| if (canAddressEscape(value, true)) |
| setAddressEscapes(entry); |
| } |
| return entry; |
| } |
| return &unknownAddressEntry; |
| } |
| |
| /// Gets the entry for a SIL value, e.g. an array-value or a tuple containing |
| /// an array. |
| Entry *getValueEntry(SILValue value) { |
| Entry * &entry = ValueEntries[value]; |
| if (!entry) { |
| entry = new (EntryAllocator.Allocate()) Entry(value, nullptr); |
| } |
| return entry; |
| } |
| |
| void setAddressEscapes(Entry *entry) { |
| LLVM_DEBUG(llvm::dbgs() << " address escapes: " << *entry); |
| setNotNative(entry); |
| } |
| |
| void setNotNative(Entry *entry) { |
| if (entry->isNativeTypeChecked) { |
| LLVM_DEBUG(llvm::dbgs() << " set not-native: " << *entry); |
| entry->isNativeTypeChecked = false; |
| WorkList.push_back(entry); |
| } |
| } |
| |
| void addDependency(Entry *from, Entry *to) { |
| LLVM_DEBUG(llvm::dbgs() << " add dependency from: " << *from |
| << " to: " << *to); |
| from->Dependencies.push_back(to); |
| } |
| |
| void scanInstruction(swift::SILInstruction *Inst); |
| |
| void scanInstructions(); |
| |
| void propagatePropertiesInGraph(); |
| |
| void replacePropertyCalls(); |
| |
| public: |
| GlobalPropertyOpt(SILModule &Module) : |
| M(Module), ArrayType(nullptr) {} |
| |
| void run(SILModuleTransform *T); |
| }; |
| |
| /// Checks if an address value does escape. If \p acceptStore is false, then |
| /// we handle a store to the address like if the address would escape. |
| bool GlobalPropertyOpt::canAddressEscape(SILValue V, bool acceptStore) { |
| for (auto UI : V->getUses()) { |
| auto *User = UI->getUser(); |
| |
| // These instructions do not cause the address to escape. |
| if (isa<LoadInst>(User) || |
| isa<DebugValueInst>(User) || |
| isa<DebugValueAddrInst>(User) || |
| isa<StrongReleaseInst>(User) || |
| isa<StrongRetainInst>(User) || |
| isa<DeallocBoxInst>(User) || |
| isa<DeallocStackInst>(User)) |
| continue; |
| |
| if (acceptStore) { |
| if (auto *Store = dyn_cast<StoreInst>(User)) { |
| if (Store->getDest() == UI->get()) |
| continue; |
| } |
| } |
| |
| // These instructions only cause the value to escape if they are used in |
| // a way that escapes. Recursively check that the uses of the instruction |
| // don't escape. |
| if (isa<StructElementAddrInst>(User) || isa<TupleElementAddrInst>(User) || |
| isa<AddressToPointerInst>(User) || isa<PointerToAddressInst>(User)) { |
| // We don't handle these instructions if we see them in store addresses. |
| // So going through them lets stores be as bad as if the address would |
| // escape. |
| auto value = cast<SingleValueInstruction>(User); |
| if (canAddressEscape(value, false)) |
| return true; |
| continue; |
| } |
| if (auto markDependence = dyn_cast<MarkDependenceInst>(User)) { |
| unsigned opNum = UI->getOperandNumber(); |
| if (opNum == 0 && canAddressEscape(markDependence, acceptStore)) |
| return true; |
| continue; |
| } |
| if (auto apply = dyn_cast<ApplyInst>(User)) { |
| // Check if the value is the this-argument of the array method. |
| ArraySemanticsCall Call(apply); |
| if (Call && Call.hasSelf() && &Call.getSelfOperand() == UI) |
| continue; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| /// Scan an instruction and build dependencies for it. |
| void GlobalPropertyOpt::scanInstruction(swift::SILInstruction *Inst) { |
| if (auto *AI = dyn_cast<ApplyInst>(Inst)) { |
| ArraySemanticsCall semCall(AI); |
| switch (semCall.getKind()) { |
| case ArrayCallKind::kArrayInit: |
| case ArrayCallKind::kArrayInitEmpty: |
| case ArrayCallKind::kArrayUninitialized: |
| case ArrayCallKind::kMutateUnknown: |
| case ArrayCallKind::kMakeMutable: |
| // The return value of those calls (if any) do not return a non-native |
| // swift array. |
| LLVM_DEBUG(llvm::dbgs() << " array semantics call: " << *AI); |
| return; |
| case ArrayCallKind::kArrayPropsIsNativeTypeChecked: |
| // Remember the property-calls for later. |
| LLVM_DEBUG(llvm::dbgs() << " property check: " << *AI); |
| propertyCalls.push_back(AI); |
| break; |
| default: |
| break; |
| } |
| } else if (auto *LI = dyn_cast<LoadInst>(Inst)) { |
| if (isArrayType(LI->getType())) { |
| // Add a dependency from the value at the address to the loaded value. |
| SILValue loadAddr = LI->getOperand(); |
| assert(loadAddr->getType().isAddress()); |
| addDependency(getAddrEntry(loadAddr), getValueEntry(LI)); |
| return; |
| } |
| } else if (auto *SI = dyn_cast<StoreInst>(Inst)) { |
| SILValue src = SI->getSrc(); |
| if (isArrayType(src->getType())) { |
| // Add a dependency from the operand to the value at the store-address. |
| // |
| SILValue dst = SI->getDest(); |
| assert(dst->getType().isAddress()); |
| addDependency(getValueEntry(src), getAddrEntry(dst)); |
| return; |
| } |
| } else if (isa<RefElementAddrInst>(Inst) || isa<StructElementAddrInst>(Inst)) { |
| auto projection = cast<SingleValueInstruction>(Inst); |
| if (isArrayAddressType(projection->getType())) { |
| // If the address of an array-field escapes, we give up for that field. |
| if (canAddressEscape(projection, true)) { |
| setAddressEscapes(getAddrEntry(projection)); |
| LLVM_DEBUG(llvm::dbgs() << " field address escapes: " |
| << *projection); |
| } |
| return; |
| } |
| } else if (auto *SEI = dyn_cast<StructExtractInst>(Inst)) { |
| if (isArrayType(SEI->getType())) { |
| // Add a dependency from the field to the extracted value. |
| VarDecl *Field = SEI->getField(); |
| addDependency(getFieldEntry(Field), getValueEntry(SEI)); |
| return; |
| } |
| } else if (auto *TEI = dyn_cast<TupleExtractInst>(Inst)) { |
| if (isArrayType(TEI->getType())) { |
| // Add a dependency from the tuple itself to the extracted element. |
| SILValue tuple = TEI->getOperand(); |
| addDependency(getValueEntry(tuple), getValueEntry(TEI)); |
| return; |
| } |
| } else if (auto *TI = dyn_cast<TupleInst>(Inst)) { |
| if (isTupleWithArray(TI->getType().getASTType())) { |
| // Add dependencies from array elements to the tuple itself. |
| for (Operand &Op : TI->getAllOperands()) { |
| SILValue V = Op.get(); |
| if (isArrayType(V->getType())) { |
| addDependency(getValueEntry(V), getValueEntry(TI)); |
| } |
| } |
| return; |
| } |
| } else if (auto *SI = dyn_cast<StructInst>(Inst)) { |
| // Add dependencies from the array operands to the struct array-fields. |
| StructDecl *S = SI->getStructDecl(); |
| auto Props = S->getStoredProperties(); |
| auto Operands = SI->getAllOperands(); |
| for (unsigned I = 0, E = Props.size(); I < E; ++I) { |
| VarDecl *VD = Props[I]; |
| const Operand &Op = Operands[I]; |
| if (isArrayType(Op.get()->getType())) { |
| addDependency(getValueEntry(Op.get()), getFieldEntry(VD)); |
| } |
| } |
| } else if (isa<AllocStackInst>(Inst)) { |
| // An alloc_stack itself does not introduce any non-native swift arrays. |
| return; |
| } |
| // TODO: handle enums with array data. |
| |
| // For everything else which we didn't handle above: we set the property of |
| // the instruction value to false. |
| for (auto result : Inst->getResults()) { |
| SILType Type = result->getType(); |
| if (isArrayType(Type) || isTupleWithArray(Type.getASTType())) { |
| LLVM_DEBUG(llvm::dbgs() << " value could be non-native array: " |
| << *result); |
| setNotNative(getValueEntry(result)); |
| } |
| } |
| } |
| |
| /// Scans all instructions of the module and builds the dependency graph. |
| void GlobalPropertyOpt::scanInstructions() { |
| for (auto &F : M) { |
| LLVM_DEBUG(llvm::dbgs() << " scan function " << F.getName() << "\n"); |
| for (auto &BB : F) { |
| LLVM_DEBUG(llvm::dbgs() << " scan basic block " << BB.getDebugID() |
| << "\n"); |
| |
| // Add dependencies from predecessor's terminator operands to the block |
| // arguments. |
| int argIdx = 0; |
| for (auto *BBArg : BB.getArguments()) { |
| bool hasPreds = false; |
| SILType Type = BBArg->getType(); |
| if (isArrayType(Type) || isTupleWithArray(Type.getASTType())) { |
| for (auto *Pred : BB.getPredecessorBlocks()) { |
| hasPreds = true; |
| auto *Term = Pred->getTerminator(); |
| SILValue PredArg; |
| if (auto *BI = dyn_cast<BranchInst>(Term)) { |
| PredArg = BI->getArg(argIdx); |
| } else if (auto *CBI = dyn_cast<CondBranchInst>(Term)) { |
| PredArg = CBI->getArgForDestBB(&BB, BBArg); |
| } |
| if (PredArg) { |
| addDependency(getValueEntry(PredArg), getValueEntry(BBArg)); |
| } else { |
| // Some unknown terminator instruction. |
| setNotNative(getValueEntry(BBArg)); |
| break; |
| } |
| } |
| if (!hasPreds) { |
| // This is the case for the function entry block. |
| setNotNative(getValueEntry(BBArg)); |
| LLVM_DEBUG(llvm::dbgs() << " unknown entry argument " << *BBArg); |
| } |
| } |
| ++argIdx; |
| } |
| // Go through all instructions of the block. |
| for (auto &Inst : BB) { |
| scanInstruction(&Inst); |
| } |
| } |
| } |
| } |
| |
| /// Propagates the properties through the graph. |
| void GlobalPropertyOpt::propagatePropertiesInGraph() { |
| LLVM_DEBUG(llvm::dbgs() << " propagate properties\n"); |
| |
| setAddressEscapes(&unknownAddressEntry); |
| |
| while (!WorkList.empty()) { |
| Entry *entry = WorkList.pop_back_val(); |
| LLVM_DEBUG(llvm::dbgs() << " handle non-native entry: " << *entry); |
| assert(!entry->isNativeTypeChecked); |
| |
| // Propagate the false-value to the dependent entries. |
| for (Entry *depEntry : entry->Dependencies) { |
| setNotNative(depEntry); |
| } |
| } |
| } |
| |
| /// Replaces all get-property calls, which we can prove to be true, with |
| /// true-literals. |
| void GlobalPropertyOpt::replacePropertyCalls() { |
| for (ApplyInst *AI : propertyCalls) { |
| SILFunction *F = AI->getFunction(); |
| // Don't optimize functions that are marked with the opt.never attribute. |
| if (!F->shouldOptimize()) |
| continue; |
| |
| ChangedFunctions.insert(F); |
| |
| SILValue array = AI->getArgument(0); |
| |
| // Is the argument a native swift array? |
| if (ValueEntries.count(array) != 0 && |
| getValueEntry(array)->isNativeTypeChecked) { |
| |
| ArraySemanticsCall semCall(AI); |
| assert( |
| (semCall.getKind() == ArrayCallKind::kArrayPropsIsNativeTypeChecked) && |
| "invalid semantics type"); |
| |
| LLVM_DEBUG(llvm::dbgs() << " remove property check in function " |
| << AI->getParent()->getParent()->getName() |
| << ": " << *AI); |
| SILBuilder B(AI); |
| SILType IntBoolTy = SILType::getBuiltinIntegerType(1, B.getASTContext()); |
| auto C1 = B.createIntegerLiteral(AI->getLoc(), IntBoolTy, 1); |
| auto TrueStruct = B.createStruct(AI->getLoc(), AI->getType(), {C1}); |
| AI->replaceAllUsesWith(TrueStruct); |
| |
| semCall.removeCall(); |
| ++NumPropertiesReplaced; |
| } |
| } |
| } |
| |
| /// The main entry point to the optimization. |
| void GlobalPropertyOpt::run(SILModuleTransform *T) { |
| |
| assert(WorkList.empty()); |
| assert(FieldEntries.empty() && ValueEntries.empty()); |
| |
| ArrayType = M.getASTContext().getArrayDecl(); |
| |
| // Step 1: scan the whole module and build the dependency graph. |
| scanInstructions(); |
| |
| // Step 2: propagate the flags through the dependency graph. |
| propagatePropertiesInGraph(); |
| |
| // Step 3: replace get-property calls with literals. |
| replacePropertyCalls(); |
| |
| for (SILFunction *ChangedFn : ChangedFunctions) { |
| T->invalidateAnalysis(ChangedFn, |
| SILAnalysis::InvalidationKind::CallsAndInstructions); |
| } |
| } |
| |
| /// The module pass, which runs the optimization. |
| class GlobalPropertyOptPass : public SILModuleTransform { |
| |
| void run() override { |
| SILModule *M = getModule(); |
| |
| LLVM_DEBUG(llvm::dbgs() << "** GlobalPropertyOpt **\n"); |
| |
| GlobalPropertyOpt(*M).run(this); |
| } |
| |
| }; |
| |
| } // end anonymous namespace |
| |
| SILTransform *swift::createGlobalPropertyOpt() { |
| return new GlobalPropertyOptPass(); |
| } |