diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 0f7bd45022eb5..39be3a320fe1e 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -375,6 +375,12 @@ class SILInstruction /// The instruction may read memory. MayRead, /// The instruction may write to memory. + /// This includes destroying or taking from memory (e.g. destroy_addr, + /// copy_addr [take], load [take]). + /// Although, physically, destroying or taking does not modify the memory, + /// it is important to model it is a write. Optimizations must not assume + /// that the value stored in memory is still available for loading after + /// the memory is destroyed or taken. MayWrite, /// The instruction may read or write memory. MayReadWrite, diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index 88630d491744e..82f18120c6038 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -283,6 +283,8 @@ PASS(PerformanceSILLinker, "performance-linker", "Deserialize all referenced SIL functions") PASS(RawSILInstLowering, "raw-sil-inst-lowering", "Lower all raw SIL instructions to canonical equivalents.") +PASS(TempLValueOpt, "temp-lvalue-opt", + "Remove short-lived immutable temporary l-values") PASS(TempRValueOpt, "temp-rvalue-opt", "Remove short-lived immutable temporary copies") PASS(SideEffectsDumper, "side-effects-dump", diff --git a/lib/SILOptimizer/Analysis/AliasAnalysis.cpp b/lib/SILOptimizer/Analysis/AliasAnalysis.cpp index d82d49a40a117..f9b28518454ca 100644 --- a/lib/SILOptimizer/Analysis/AliasAnalysis.cpp +++ b/lib/SILOptimizer/Analysis/AliasAnalysis.cpp @@ -569,6 +569,15 @@ AliasResult AliasAnalysis::alias(SILValue V1, SILValue V2, return Result; } +/// Get the underlying object, looking through init_enum_data_addr and +/// init_existential_addr. +static SILValue stripInitEnumAndExistentialAddr(SILValue v) { + while (isa(v) || isa(v)) { + v = getUnderlyingObject(cast(v)->getOperand(0)); + } + return v; +} + /// The main AA entry point. Performs various analyses on V1, V2 in an attempt /// to disambiguate the two values. AliasResult AliasAnalysis::aliasInner(SILValue V1, SILValue V2, @@ -614,9 +623,12 @@ AliasResult AliasAnalysis::aliasInner(SILValue V1, SILValue V2, LLVM_DEBUG(llvm::dbgs() << " Underlying V1:" << *O1); LLVM_DEBUG(llvm::dbgs() << " Underlying V2:" << *O2); - // If O1 and O2 do not equal, see if we can prove that they cannot be the - // same object. If we can, return No Alias. - if (O1 != O2 && aliasUnequalObjects(O1, O2)) + // If the underlying objects are not equal, see if we can prove that they + // cannot be the same object. If we can, return No Alias. + // For this we even look through init_enum_data_addr and init_existential_addr. + SILValue StrippedO1 = stripInitEnumAndExistentialAddr(O1); + SILValue StrippedO2 = stripInitEnumAndExistentialAddr(O2); + if (StrippedO1 != StrippedO2 && aliasUnequalObjects(StrippedO1, StrippedO2)) return AliasResult::NoAlias; // Ok, either O1, O2 are the same or we could not prove anything based off of diff --git a/lib/SILOptimizer/Analysis/MemoryBehavior.cpp b/lib/SILOptimizer/Analysis/MemoryBehavior.cpp index 5eed0b391ea95..62b15e917b1d3 100644 --- a/lib/SILOptimizer/Analysis/MemoryBehavior.cpp +++ b/lib/SILOptimizer/Analysis/MemoryBehavior.cpp @@ -174,6 +174,7 @@ class MemoryBehaviorVisitor MemBehavior visitLoadInst(LoadInst *LI); MemBehavior visitStoreInst(StoreInst *SI); + MemBehavior visitCopyAddrInst(CopyAddrInst *CAI); MemBehavior visitApplyInst(ApplyInst *AI); MemBehavior visitTryApplyInst(TryApplyInst *AI); MemBehavior visitBuiltinInst(BuiltinInst *BI); @@ -245,6 +246,10 @@ MemBehavior MemoryBehaviorVisitor::visitLoadInst(LoadInst *LI) { if (!mayAlias(LI->getOperand())) return MemBehavior::None; + // A take is modelled as a write. See MemoryBehavior::MayWrite. + if (LI->getOwnershipQualifier() == LoadOwnershipQualifier::Take) + return MemBehavior::MayReadWrite; + LLVM_DEBUG(llvm::dbgs() << " Could not prove that load inst does not alias " "pointer. Returning may read.\n"); return MemBehavior::MayRead; @@ -268,6 +273,34 @@ MemBehavior MemoryBehaviorVisitor::visitStoreInst(StoreInst *SI) { return MemBehavior::MayWrite; } +MemBehavior MemoryBehaviorVisitor::visitCopyAddrInst(CopyAddrInst *CAI) { + // If it's an assign to the destination, a destructor might be called on the + // old value. This can have any side effects. + // We could also check if it's a trivial type (which cannot have any side + // effect on destruction), but such copy_addr instructions are optimized to + // load/stores anyway, so it's probably not worth it. + if (!CAI->isInitializationOfDest()) + return MemBehavior::MayHaveSideEffects; + + bool mayWrite = mayAlias(CAI->getDest()); + bool mayRead = mayAlias(CAI->getSrc()); + + if (mayRead) { + if (mayWrite) + return MemBehavior::MayReadWrite; + + // A take is modelled as a write. See MemoryBehavior::MayWrite. + if (CAI->isTakeOfSrc()) + return MemBehavior::MayReadWrite; + + return MemBehavior::MayRead; + } + if (mayWrite) + return MemBehavior::MayWrite; + + return MemBehavior::None; +} + MemBehavior MemoryBehaviorVisitor::visitBuiltinInst(BuiltinInst *BI) { // If our callee is not a builtin, be conservative and return may have side // effects. diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index ae25c92c1a410..25ca8699e9c68 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -283,6 +283,9 @@ void addFunctionPasses(SILPassPipelinePlan &P, // splits up copy_addr. P.addCopyForwarding(); + // Optimize copies from a temporary (an "l-value") to a destination. + P.addTempLValueOpt(); + // Split up opaque operations (copy_addr, retain_value, etc.). P.addLowerAggregateInstrs(); diff --git a/lib/SILOptimizer/Transforms/CMakeLists.txt b/lib/SILOptimizer/Transforms/CMakeLists.txt index f1e604be3aeb5..932a2e264e3a5 100644 --- a/lib/SILOptimizer/Transforms/CMakeLists.txt +++ b/lib/SILOptimizer/Transforms/CMakeLists.txt @@ -38,5 +38,6 @@ target_sources(swiftSILOptimizer PRIVATE Sink.cpp SpeculativeDevirtualizer.cpp StackPromotion.cpp + TempLValueOpt.cpp TempRValueElimination.cpp UnsafeGuaranteedPeephole.cpp) diff --git a/lib/SILOptimizer/Transforms/TempLValueOpt.cpp b/lib/SILOptimizer/Transforms/TempLValueOpt.cpp new file mode 100644 index 0000000000000..8511de4452e75 --- /dev/null +++ b/lib/SILOptimizer/Transforms/TempLValueOpt.cpp @@ -0,0 +1,299 @@ +//===--- TempLValueOpt.cpp - Optimize temporary l-values ------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 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 +// +//===----------------------------------------------------------------------===// +// +// This pass optimizes copies from a temporary (an "l-value") to a destination. +// +//===----------------------------------------------------------------------===// + +#define DEBUG_TYPE "cow-opts" +#include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Analysis/AliasAnalysis.h" +#include "swift/SIL/SILFunction.h" +#include "swift/SIL/SILBasicBlock.h" +#include "swift/SIL/Projection.h" +#include "swift/SIL/SILBuilder.h" +#include "llvm/Support/Debug.h" + +using namespace swift; + +namespace { + +/// Optimizes copies from a temporary (an "l-value") to a destination. +/// +/// \code +/// %temp = alloc_stack $Ty +/// instructions_which_store_to %temp +/// copy_addr [take] %temp to %destination +/// dealloc_stack %temp +/// \endcode +/// +/// is optimized to +/// +/// \code +/// destroy_addr %destination +/// instructions_which_store_to %destination +/// \endcode +/// +/// The name TempLValueOpt refers to the TempRValueOpt pass, which performs +/// a related transformation, just with the temporary on the "right" side. +/// +/// Note that TempLValueOpt is similar to CopyForwarding::backwardPropagateCopy. +/// It's more restricted (e.g. the copy-source must be an alloc_stack). +/// That enables other patterns to be optimized, which backwardPropagateCopy +/// cannot handle. +/// +/// The pass also performs a peephole optimization on copy_addr - destroy +/// sequences. +/// It replaces +/// +/// \code +/// copy_addr %source to %destination +/// destroy_addr %source +/// \endcode +/// +/// with +/// +/// \code +/// copy_addr [take] %source to %destination +/// \endcode +/// +/// This peephole optimization is also done by the DestroyHoisting pass. But +/// DestroyHoisting currently only runs on OSSA. +/// TODO: when DestroyHoisting can run later in the pipeline, check if we still +/// need this peephole here. +class TempLValueOptPass : public SILFunctionTransform { +public: + TempLValueOptPass() {} + + void run() override; + +private: + AliasAnalysis *AA = nullptr; + + bool tempLValueOpt(CopyAddrInst *copyInst); + bool combineCopyAndDestroy(CopyAddrInst *copyInst); +}; + +void TempLValueOptPass::run() { + SILFunction *F = getFunction(); + if (!F->shouldOptimize()) + return; + + LLVM_DEBUG(llvm::dbgs() << "*** TempLValueOptPass on function: " + << F->getName() << " ***\n"); + + AA = PM->getAnalysis(); + + bool changed = false; + for (SILBasicBlock &block : *F) { + // First collect all copy_addr instructions upfront to avoid iterator + // invalidation problems (the optimizations might delete the copy_addr + // itself or any following instruction). + llvm::SmallVector copyInsts; + for (SILInstruction &inst : block) { + if (auto *copyInst = dyn_cast(&inst)) + copyInsts.push_back(copyInst); + } + // Do the optimizations. + for (CopyAddrInst *copyInst : copyInsts) { + changed |=combineCopyAndDestroy(copyInst); + changed |=tempLValueOpt(copyInst); + } + } + + if (changed) { + invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); + } +} + +static SingleValueInstruction *isMovableProjection(SILValue addr) { + if (auto *enumData = dyn_cast(addr)) + return enumData; + if (auto *existentialAddr = dyn_cast(addr)) + return existentialAddr; + + if (SingleValueInstruction *svi = Projection::isAddressProjection(addr)) { + if (svi->getNumOperands() == 1) + return svi; + } + return nullptr; +} + +bool TempLValueOptPass::tempLValueOpt(CopyAddrInst *copyInst) { + + // An overview of the algorithm: + // + // alloc_stack %temp + // ... + // first_use_of %temp // beginOfLiferange + // ... // no reads or writes from/to %destination + // copy_addr [take] %temp to %destination // copyInst + // ... // no further uses of %temp (copyInst is the end of %temp liferange) + // dealloc_stack %temp + // + // All projections to %destination are hoisted above the first use of %temp. + // Then all uses of %temp are replaced by %destination. + // In case the copyInst is not initializing %destination, a + // 'destroy_addr %destination' is inserted before the first use of %temp. + + if (!copyInst->isTakeOfSrc()) + return false; + + auto *temporary = dyn_cast(copyInst->getSrc()); + if (!temporary) + return false; + + // Collect all users of the temporary into a set. Also, for simplicity, + // require that all users are within a single basic block. + SmallPtrSet users; + SILBasicBlock *block = temporary->getParent(); + for (Operand *use : temporary->getUses()) { + SILInstruction *user = use->getUser(); + if (user->getParent() != block) + return false; + users.insert(user); + } + + // Collect all address projections of the destination - in case we need to + // hoist them. + SmallPtrSet projections; + SILValue destination = copyInst->getDest(); + SILValue destRootAddr = destination; + while (SingleValueInstruction *projInst = isMovableProjection(destRootAddr)) { + // In theory, users of the temporary and address projections of the + // destination should be completely distinct. Otherwise the copyInst would + // be an identity copy (source == destination). + // Just to be on the safe side, bail if it's not the case (instead of an + // assert). + if (users.count(projInst)) + return false; + projections.insert(projInst); + destRootAddr = projInst->getOperand(0); + } + // The root address of the destination. It's null if it's not an instruction, + // but a block argument. + SILInstruction *destRootInst = destRootAddr->getDefiningInstruction(); + + // Iterate over the liferange of the temporary and make some validity checks. + SILInstruction *beginOfLiferange = nullptr; + bool endOfLiferangeReached = false; + for (auto iter = temporary->getIterator(); iter != block->end(); ++iter) { + SILInstruction *inst = &*iter; + // The dealloc_stack is the last user of the temporary. + if (isa(inst) && inst->getOperand(0) == temporary) + break; + + if (users.count(inst) != 0) { + // Check if the copyInst is the last user of the temporary (beside the + // dealloc_stack). + if (endOfLiferangeReached) + return false; + + // Find the first user of the temporary to get a more precise liferange. + // It would be too conservative to treat the alloc_stack itself as the + // begin of the liferange. + if (!beginOfLiferange) + beginOfLiferange = inst; + + if (inst == copyInst) + endOfLiferangeReached = true; + } + if (beginOfLiferange && !endOfLiferangeReached) { + // If the root address of the destination is within the liferange of the + // temporary, we cannot replace all uses of the temporary with the + // destination (it would break the def-use dominance rule). + if (inst == destRootInst) + return false; + + // Check if the destination is not accessed within the liferange of + // the temporary. + // This is unlikely, because the destination is initialized at the + // copyInst. But still, the destination could contain an initialized value + // which is destroyed before the copyInst. + if (AA->mayReadOrWriteMemory(inst, destination) && + // Needed to treat init_existential_addr as not-writing projection. + projections.count(inst) == 0) + return false; + } + } + assert(endOfLiferangeReached); + + // Move all projections of the destination address before the liferange of + // the temporary. + for (auto iter = beginOfLiferange->getIterator(); + iter != copyInst->getIterator();) { + SILInstruction *inst = &*iter++; + if (projections.count(inst)) + inst->moveBefore(beginOfLiferange); + } + + if (!copyInst->isInitializationOfDest()) { + // Make sure the the destination is uninitialized before the liferange of + // the temporary. + SILBuilderWithScope builder(beginOfLiferange); + builder.createDestroyAddr(copyInst->getLoc(), destination); + } + + // Replace all uses of the temporary with the destination address. + while (!temporary->use_empty()) { + Operand *use = *temporary->use_begin(); + SILInstruction *user = use->getUser(); + switch (user->getKind()) { + case SILInstructionKind::DeallocStackInst: + user->eraseFromParent(); + break; + default: + use->set(destination); + } + } + temporary->eraseFromParent(); + copyInst->eraseFromParent(); + return true; +} + +bool TempLValueOptPass::combineCopyAndDestroy(CopyAddrInst *copyInst) { + if (copyInst->isTakeOfSrc()) + return false; + + // Find a destroy_addr of the copy's source address. + DestroyAddrInst *destroy = nullptr; + for (Operand *srcUse : copyInst->getSrc()->getUses()) { + if ((destroy = dyn_cast(srcUse->getUser()))) + break; + } + SILBasicBlock *block = copyInst->getParent(); + if (!destroy || destroy->getParent() != block) + return false; + assert(destroy->getOperand() == copyInst->getSrc()); + + // Check if the destroy_addr is after the copy_addr and if there are no + // memory accesses between them. + for (auto iter = std::next(copyInst->getIterator()); + iter != block->end(); ++iter) { + SILInstruction *inst = &*iter; + if (inst == destroy) { + copyInst->setIsTakeOfSrc(IsTake); + destroy->eraseFromParent(); + return true; + } + if (inst->mayReadOrWriteMemory()) + return false; + } + return false; +} + +} // end anonymous namespace + +SILTransform *swift::createTempLValueOpt() { + return new TempLValueOptPass(); +} + diff --git a/test/AutoDiff/SILOptimizer/differentiation_diagnostics.swift b/test/AutoDiff/SILOptimizer/differentiation_diagnostics.swift index cb0e1030bc87e..2aaca3f5f1d3c 100644 --- a/test/AutoDiff/SILOptimizer/differentiation_diagnostics.swift +++ b/test/AutoDiff/SILOptimizer/differentiation_diagnostics.swift @@ -66,14 +66,162 @@ func noReturn(_ x: Float) -> Float { } //===----------------------------------------------------------------------===// -// Conversion to `@differentiable(linear)` (not yet supported) +// Global variables //===----------------------------------------------------------------------===// -// expected-error @+1 {{conversion to '@differentiable(linear)' function type is not yet supported}} -let _: @differentiable(linear) (Float) -> Float = { x in x } +var global: Float = 3.0 + +// expected-error @+2 {{function is not differentiable}} +// expected-note @+2 {{when differentiating this function definition}} +@differentiable(wrt: x) +func testWriteToGlobalVariable(x: Float) -> Float { + // expected-note @+1 {{cannot differentiate writes to global variables}} + global = global + x + return global +} //===----------------------------------------------------------------------===// -// Non-differentiable arguments and results +// Class differentiation +//===----------------------------------------------------------------------===// + +class Class : Differentiable { + @differentiable + var stored: Float = 1 + + @differentiable + func testRefElementAddr(_ x: Float) -> Float { + return stored * x + } + + var nonDifferentiableStored: Float = 1 + + @differentiable + func testNonDifferentiableRefElementAddr(_ x: Float) -> Float { + // expected-error @+2 {{expression is not differentiable}} + // expected-note @+1 {{member is not differentiable because the corresponding class member is not '@differentiable'}} + return nonDifferentiableStored * x + } + + @differentiable + func method(_ x: Float) -> Float { x } + + @differentiable + static func testClassMethod(x: Float) -> Float { + return Class().method(x) + } + + func nonDifferentiableMethod(_ x: Float) -> Float { x } + + @differentiable + static func testDifferentiableClassMethod(x: Float) -> Float { + // expected-error @+2 {{expression is not differentiable}} + // expected-note @+1 {{member is not differentiable because the corresponding class member is not '@differentiable'}} + return Class().nonDifferentiableMethod(x) + } +} + +// TF-676: Test differentiation of class method with multiple `@differentiable` +// attributes. +class ClassMethodMultipleDifferentiableAttribute : Differentiable { + @differentiable(wrt: (self, x)) + @differentiable(wrt: x) + func f(_ x: Float) -> Float { x } +} +func testMultipleDiffAttrsClass( + _ c: C, _ x: Float +) { + _ = gradient(at: c, x) { c, x in c.f(x) } + _ = gradient(at: x) { x in c.f(x) } +} + +// TF-1149: Test class with loadable type but address-only `TangentVector` type. +class C: Differentiable { + // expected-error @+1 {{function is not differentiable}} + @differentiable + // expected-note @+2 {{when differentiating this function definition}} + // expected-note @+1 {{cannot yet differentiate value whose type 'C' has a compile-time known size, but whose 'TangentVector' contains stored properties of unknown size; consider modifying 'C<τ_0_0>.TangentVector' to use fewer generic parameters in stored properties}} + var stored: T + + init(_ stored: T) { + self.stored = stored + } + + // expected-error @+1 {{function is not differentiable}} + @differentiable + // expected-note @+2 {{when differentiating this function definition}} + // expected-note @+1 {{cannot yet differentiate value whose type 'C' has a compile-time known size, but whose 'TangentVector' contains stored properties of unknown size; consider modifying 'C<τ_0_0>.TangentVector' to use fewer generic parameters in stored properties}} + func method(_ x: T) -> T { + stored + } +} + +//===----------------------------------------------------------------------===// +// Enum differentiation +//===----------------------------------------------------------------------===// + +// expected-error @+1 {{function is not differentiable}} +@differentiable +// expected-note @+1 {{when differentiating this function definition}} +func usesOptionals(_ x: Float) -> Float { + // expected-note @+1 {{differentiating enum values is not yet supported}} + var maybe: Float? = 10 + maybe = x + return maybe! +} + +enum DirectEnum: Differentiable & AdditiveArithmetic { + case leaf(Float) + + typealias TangentVector = Self + + static var zero: Self { fatalError() } + static func +(_ lhs: Self, _ rhs: Self) -> Self { fatalError() } + static func -(_ lhs: Self, _ rhs: Self) -> Self { fatalError() } +} + +// expected-error @+1 {{function is not differentiable}} +@differentiable(wrt: e) +// expected-note @+2 {{when differentiating this function definition}} +// expected-note @+1 {{differentiating enum values is not yet supported}} +func enum_active(_ e: DirectEnum, _ x: Float) -> Float { + switch e { + case let .leaf(y): return y + } +} + +// expected-error @+1 {{function is not differentiable}} +@differentiable(wrt: e) +// expected-note @+2 {{when differentiating this function definition}} +// expected-note @+1 {{differentiating enum values is not yet supported}} +func activeEnumValue(_ e: DirectEnum, _ x: Float) -> Float { + switch e { + case let .leaf(y): return y + } +} + +enum IndirectEnum: Differentiable & AdditiveArithmetic { + case leaf(T) + + typealias TangentVector = Self + + static func ==(_ lhs: Self, _ rhs: Self) -> Bool { fatalError() } + static var zero: Self { fatalError() } + static func +(_ lhs: Self, _ rhs: Self) -> Self { fatalError() } + static func -(_ lhs: Self, _ rhs: Self) -> Self { fatalError() } +} + +// expected-error @+1 {{function is not differentiable}} +@differentiable(wrt: e) +// expected-note @+2 {{when differentiating this function definition}} +// expected-note @+1 {{differentiating enum values is not yet supported}} +func activeEnumAddr(_ e: IndirectEnum, _ x: Float) -> Float { + switch e { + case let .leaf(y): return y + } +} + +//===----------------------------------------------------------------------===// +// Unmet derivative generic requirements //===----------------------------------------------------------------------===// @differentiable @@ -82,7 +230,6 @@ func generic(_ x: T) -> T { // expected-note @+1 {{member is not differentiable because the corresponding protocol requirement is not '@differentiable'}} return x + 1 } -_ = gradient(at: 1.0, in: generic) // Test unmet generic requirements. @@ -95,12 +242,12 @@ func vjpWeirdExtraRequirements(_ x: T) -> ( ) where T.AllCases : ExpressibleByStringLiteral { return (x, { $0 }) } +@differentiable func weirdWrapper(_ x: T) -> T { // expected-error @+2 {{expression is not differentiable}} // expected-note @+1 {{function call is not differentiable because generic requirements are not met: 'T : CaseIterable, T.AllCases : ExpressibleByStringLiteral'}} return weird(x) } -_ = gradient(at: Float(1), in: { x in weirdWrapper(x) }) @differentiable func direct(_ x: T) -> T { @@ -118,13 +265,13 @@ extension Tensor where Scalar : BinaryFloatingPoint { } } -protocol TF8Proto : Differentiable { +protocol TF8_Proto : Differentiable { associatedtype Scalar @differentiable(wrt: (self, input)) func applied(to input: Float) -> Float } -struct TF8Struct : TF8Proto where Scalar : FloatingPoint & Differentiable { +struct TF8_Struct : TF8_Proto where Scalar : FloatingPoint & Differentiable { @noDerivative let bar: Scalar @differentiable(wrt: (self, input)) @@ -133,7 +280,24 @@ struct TF8Struct : TF8Proto where Scalar : FloatingPoint & Differentiabl } } -_ = gradient(at: 1.0, in: { x in x.squareRoot() }) +//===----------------------------------------------------------------------===// +// `Differentiable` conformance requirement inference +//===----------------------------------------------------------------------===// + +func id(_ x: T) -> T { x } +let _: @differentiable (Float) -> Float = { x in id(x) } + +struct TF_691 { + var x: Scalar + init(_ x: Scalar) { + self.x = x + } +} +extension TF_691: Differentiable where Scalar: Differentiable {} + +func identity(_ x: TF_691) -> TF_691 { x } +let _: @differentiable (Float) -> TF_691 = { x in identity(TF_691(x)) } +let _: @differentiable (Float) -> TF_691 = { x in id(TF_691(x)) } //===----------------------------------------------------------------------===// // Non-differentiable arguments and results @@ -152,27 +316,162 @@ struct TF_687 : Differentiable { // expected-note @+1 {{cannot differentiate through a non-differentiable argument; do you want to use 'withoutDerivative(at:)'?}} let _: @differentiable (Float) -> TF_687 = { x in TF_687(x, dummy: x) } +// expected-error @+1 {{function is not differentiable}} +@differentiable +// expected-note @+1 {{when differentiating this function definition}} +func roundingGivesError(x: Float) -> Float { + // expected-note @+1 {{cannot differentiate through a non-differentiable result; do you want to use 'withoutDerivative(at:)'?}} + return Float(Int(x)) +} + //===----------------------------------------------------------------------===// -// Add `Differentiable` conformance for generic wrt parameters +// Non-varied results //===----------------------------------------------------------------------===// -func id(_ x: T) -> T { x } -let _: @differentiable (Float) -> Float = { x in id(x) } +@differentiable +func nonVariedResult(_ x: Float) -> Float { + // TODO(TF-788): Re-enable non-varied result warning. + // xpected-warning @+1 {{result does not depend on differentiation arguments and will always have a zero derivative; do you want to use 'withoutDerivative(at:)'?}} {{10-10=withoutDerivative(at:}} {{15-15=)}} + return 0 +} -struct TF_691 { - var x: Scalar - init(_ x: Scalar) { - self.x = x +// Check that `withoutDerivative(at:)` silences the non-varied result warning. + +struct TF_775: Differentiable { + @differentiable(wrt: (self)) + func nonVariedResult(_ input: Float) -> Float { + return withoutDerivative(at: input) } } -extension TF_691: Differentiable where Scalar: Differentiable {} -func identity(_ x: TF_691) -> TF_691 { x } -let _: @differentiable (Float) -> TF_691 = { x in identity(TF_691(x)) } -let _: @differentiable (Float) -> TF_691 = { x in id(TF_691(x)) } +//===----------------------------------------------------------------------===// +// Multiple results +//===----------------------------------------------------------------------===// + +func multipleResults(_ x: Float) -> (Float, Float) { + return (x, x) +} + +// TODO(TF-983): Support differentiation of multiple results. +// expected-error @+2 {{function is not differentiable}} +// expected-note @+2 {{when differentiating this function definition}} +@differentiable +func usesMultipleResults(_ x: Float) -> Float { + // expected-note @+1 {{cannot differentiate through multiple results}} + let tuple = multipleResults(x) + return tuple.0 + tuple.1 +} //===----------------------------------------------------------------------===// -// Property wrappers +// `inout` parameter differentiation +//===----------------------------------------------------------------------===// + +@differentiable +func activeInoutParam(_ x: Float) -> Float { + var result = x + result += x + return result +} + +@differentiable +func activeInoutParamNonactiveInitialResult(_ x: Float) -> Float { + var result: Float = 1 + result += x + return result +} + +@differentiable +func activeInoutParamTuple(_ x: Float) -> Float { + var tuple = (x, x) + tuple.0 *= x + return x * tuple.0 +} + +@differentiable +func activeInoutParamControlFlow(_ array: [Float]) -> Float { + var result: Float = 1 + for i in withoutDerivative(at: array).indices { + result += array[i] + } + return result +} + +@differentiable +func activeInoutParamControlFlowComplex(_ array: [Float], _ bool: Bool) -> Float { + var result: Float = 1 + if bool { + if bool {} + for i in withoutDerivative(at: array).indices { + switch i % 2 { + case 0: continue + case 1: break + default: break + } + result = result + 1 + result += array[i] + } + } + return result +} + +struct Mut: Differentiable {} +extension Mut { + @differentiable(wrt: x) + mutating func mutatingMethod(_ x: Mut) {} +} + +@differentiable(wrt: x) +func nonActiveInoutParam(_ nonactive: inout Mut, _ x: Mut) { + nonactive.mutatingMethod(x) +} + +@differentiable(wrt: x) +func activeInoutParamMutatingMethod(_ x: Mut) -> Mut { + var result = x + result.mutatingMethod(result) + return result +} + +@differentiable(wrt: x) +func activeInoutParamMutatingMethodVar(_ nonactive: inout Mut, _ x: Mut) { + var result = nonactive + result.mutatingMethod(x) + nonactive = result +} + +@differentiable(wrt: x) +func activeInoutParamMutatingMethodTuple(_ nonactive: inout Mut, _ x: Mut) { + var result = (nonactive, x) + result.0.mutatingMethod(result.0) + nonactive = result.0 +} + +// TODO(TF-983): Support differentiation of multiple results. +func twoInoutParameters(_ x: inout Float, _ y: inout Float) {} +// expected-error @+2 {{function is not differentiable}} +// expected-note @+2 {{when differentiating this function definition}} +@differentiable +func testTwoInoutParameters(_ x: Float, _ y: Float) -> Float { + var x = x + var y = y + // expected-note @+1 {{cannot differentiate through multiple results}} + twoInoutParameters(&x, &y) + return x +} + +// TODO(TF-983): Support differentiation of multiple results. +func inoutParameterAndFormalResult(_ x: inout Float) -> Float { x } +// expected-error @+2 {{function is not differentiable}} +// expected-note @+2 {{when differentiating this function definition}} +@differentiable +func testInoutParameterAndFormalResult(_ x: Float) -> Float { + var x = x + // expected-note @+1 {{cannot differentiate through multiple results}} + return inoutParameterAndFormalResult(&x) +} + +//===----------------------------------------------------------------------===// +// Wrapped property differentiation //===----------------------------------------------------------------------===// @propertyWrapper @@ -240,3 +539,172 @@ func modify(_ s: Struct, _ x: Float) -> Float { s.x *= x * s.z return s.x } + +//===----------------------------------------------------------------------===// +// Array literal initialization +//===----------------------------------------------------------------------===// + +// expected-error @+1 {{function is not differentiable}} +@differentiable +// expected-note @+1 {{when differentiating this function definition}} +func tupleArrayLiteralInitialization(_ x: Float, _ y: Float) -> Float { + // `Array<(Float, Float)>` does not conform to `Differentiable`. + let array = [(x * y, x * y)] + // expected-note @+1 {{cannot differentiate through a non-differentiable argument; do you want to use 'withoutDerivative(at:)'?}} + return array[0].0 +} + +//===----------------------------------------------------------------------===// +// Subset parameter differentiation thunks +//===----------------------------------------------------------------------===// + +// FIXME(SR-13046): Non-differentiability diagnostic crash due to invalid source location. +/* +func testNoDerivativeParameter(_ f: @differentiable (Float, @noDerivative Float) -> Float) -> Float { + return gradient(at: 2) { x in f(x * x, x) } +} +*/ + +// Test parameter subset thunk + partially-applied original function. +struct TF_675 : Differentiable { + @differentiable + func method(_ x: Float) -> Float { + return x + } +} +let _: @differentiable (Float) -> Float = TF_675().method + +// TF-918: Test parameter subset thunk + partially-applied original function. +let _: @differentiable (Float, Float) -> Float = (+) as @differentiable (Float, @noDerivative Float) -> Float + +//===----------------------------------------------------------------------===// +// Differentiation in fragile functions +//===----------------------------------------------------------------------===// + +public func implicitlyDifferentiableFromFragile(_ x: Float) -> Float { x } + +public func hasImplicitlyDifferentiatedTopLevelDefaultArgument( +// expected-error @+2 {{function is not differentiable}} +// expected-note @+1 {{differentiated functions in default arguments must be marked '@differentiable' or have a public '@derivative'}} + _ f: @differentiable (Float) -> Float = implicitlyDifferentiableFromFragile +) {} + +// TODO(TF-1030): This will eventually not be an error. +// expected-error @+2 {{function is not differentiable}} +// expected-note @+1 {{differentiated functions in default arguments must be marked '@differentiable' or have a public '@derivative'; this is not possible with a closure, make a top-level function instead}} +public func hasImplicitlyDifferentiatedClosureDefaultArgument(_ f: @differentiable (Float) -> Float = { $0 }) {} + +@inlinable +public func fragileFuncWithGradient() { + // expected-error @+2 {{function is not differentiable}} + // expected-note @+1 {{differentiated functions in '@inlinable' functions must be marked '@differentiable' or have a public '@derivative'}} + _ = gradient(at: 0, in: implicitlyDifferentiableFromFragile) +} + +@inlinable +@differentiable +public func fragileDifferentiable(_ x: Float) -> Float { + // expected-error @+2 {{expression is not differentiable}} + // expected-note @+1 {{differentiated functions in '@inlinable' functions must be marked '@differentiable' or have a public '@derivative'}} + implicitlyDifferentiableFromFragile(x) +} + +// TF-1208: Test curry thunk differentiation regression. +public struct TF_1208_Struct { + var x: Scalar +} +extension TF_1208_Struct: Differentiable where Scalar: Differentiable { + @differentiable + public static func id(x: Self) -> Self { + return x + } +} +@differentiable(wrt: x) +public func TF_1208( + _ x: TF_1208_Struct, + // NOTE(TF-1208): This diagnostic is unexpected because `TF_1208_Struct.id` is marked `@differentiable`. + // expected-error @+3 2 {{function is not differentiable}} + // expected-note @+2 {{differentiated functions in '@inlinable' functions must be marked '@differentiable' or have a public '@derivative'; this is not possible with a closure, make a top-level function instead}} + // expected-note @+1 {{opaque non-'@differentiable' function is not differentiable}} + reduction: @differentiable (TF_1208_Struct) -> TF_1208_Struct = TF_1208_Struct.id +) -> TF_1208_Struct { + reduction(x) +} + +//===----------------------------------------------------------------------===// +// Coroutines (SIL function yields, `begin_apply`) (not yet supported) +//===----------------------------------------------------------------------===// + +struct HasCoroutineAccessors: Differentiable { + var stored: Float + var computed: Float { + // `_read` is a coroutine: `(Self) -> () -> ()`. + _read { yield stored } + // `_modify` is a coroutine: `(inout Self) -> () -> ()`. + _modify { yield &stored } + } +} +// expected-error @+2 {{function is not differentiable}} +// expected-note @+2 {{when differentiating this function definition}} +@differentiable +func testAccessorCoroutines(_ x: HasCoroutineAccessors) -> HasCoroutineAccessors { + var x = x + // expected-note @+1 {{differentiation of coroutine calls is not yet supported}} + x.computed = x.computed + return x +} + +// TF-1078: Diagnose `_modify` accessor application with active `inout` argument. +// expected-error @+2 {{function is not differentiable}} +// expected-note @+2 {{when differentiating this function definition}} +@differentiable +func TF_1078(array: [Float], x: Float) -> Float { + var array = array + // Array subscript assignment below calls `Array.subscript.modify`. + // expected-note @+1 {{differentiation of coroutine calls is not yet supported}} + array[0] = x + return array[0] +} + +// TF-1115: Diagnose `_modify` accessor application with initially non-active `inout` argument. +// expected-error @+2 {{function is not differentiable}} +// expected-note @+2 {{when differentiating this function definition}} +@differentiable +func TF_1115(_ x: Float) -> Float { + var array: [Float] = [0] + // Array subscript assignment below calls `Array.subscript.modify`. + // expected-note @+1 {{differentiation of coroutine calls is not yet supported}} + array[0] = x + return array[0] +} + +// TF-1115: Test `_modify` accessor application with initially non-active `inout` argument, +// where the yielded value is not a projection from `self`. +extension Float { + static var staticProperty: Float = 1 + + var projection: Float { + get { self } + // This `modify` accessor yields a static variable, not a projection from `self`. + // Diagnosing active applications is nonetheless a safe over-approximation. + _modify { yield &Float.staticProperty } + } +} + +// expected-error @+2 {{function is not differentiable}} +// expected-note @+2 {{when differentiating this function definition}} +@differentiable +func TF_1115_modifyNonSelfProjection(x: Float) -> Float { + var result: Float = 0 + // Assignment below calls `Float.projection.modify`. + // expected-note @+1 {{differentiation of coroutine calls is not yet supported}} + result.projection = x + return result +} + +//===----------------------------------------------------------------------===// +// Conversion to `@differentiable(linear)` (not yet supported) +//===----------------------------------------------------------------------===// + +// expected-error @+1 {{conversion to '@differentiable(linear)' function type is not yet supported}} +let _: @differentiable(linear) (Float) -> Float = { x in x } diff --git a/test/AutoDiff/SILOptimizer/forward_mode_diagnostics.swift b/test/AutoDiff/SILOptimizer/forward_mode_diagnostics.swift new file mode 100644 index 0000000000000..a2ab040928b92 --- /dev/null +++ b/test/AutoDiff/SILOptimizer/forward_mode_diagnostics.swift @@ -0,0 +1,150 @@ +// RUN: %target-swift-frontend -enable-experimental-forward-mode-differentiation -emit-sil -verify %s + +// Test forward-mode differentiation transform diagnostics. + +// TODO: Move these tests back into `autodiff_diagnostics.swift` once +// forward mode reaches feature parity with reverse mode. + +import _Differentiation + +//===----------------------------------------------------------------------===// +// Basic function +//===----------------------------------------------------------------------===// + +@differentiable +func basic(_ x: Float) -> Float { + return x +} + +//===----------------------------------------------------------------------===// +// Control flow +//===----------------------------------------------------------------------===// + +// expected-error @+1 {{function is not differentiable}} +@differentiable +// expected-note @+2 {{when differentiating this function definition}} +// expected-note @+1 {{forward-mode differentiation does not yet support control flow}} +func cond(_ x: Float) -> Float { + if x > 0 { + return x * x + } + return x + x +} + +//===----------------------------------------------------------------------===// +// Non-varied results +//===----------------------------------------------------------------------===// + +@differentiable +func nonVariedResult(_ x: Float) -> Float { + // TODO(TF-788): Re-enable non-varied result warning. + // xpected-warning @+1 {{result does not depend on differentiation arguments and will always have a zero derivative; do you want to use 'withoutDerivative(at:)'?}} {{10-10=withoutDerivative(at:}} {{15-15=)}} + return 0 +} + +//===----------------------------------------------------------------------===// +// Multiple results +//===----------------------------------------------------------------------===// + +// TODO(TF-983): Support differentiation of multiple results. +/* +func multipleResults(_ x: Float) -> (Float, Float) { + return (x, x) +} +@differentiable +func usesMultipleResults(_ x: Float) -> Float { + let tuple = multipleResults(x) + return tuple.0 + tuple.1 +} +*/ + +//===----------------------------------------------------------------------===// +// `inout` parameter differentiation +//===----------------------------------------------------------------------===// + +// expected-error @+1 {{function is not differentiable}} +@differentiable +// expected-note @+1 {{when differentiating this function definition}} +func activeInoutParamNonactiveInitialResult(_ x: Float) -> Float { + var result: Float = 1 + // expected-note @+1 {{cannot differentiate through 'inout' arguments}} + result += x + return result +} + +// expected-error @+1 {{function is not differentiable}} +@differentiable +// expected-note @+1 {{when differentiating this function definition}} +func activeInoutParamTuple(_ x: Float) -> Float { + var tuple = (x, x) + // expected-note @+1 {{cannot differentiate through 'inout' arguments}} + tuple.0 *= x + return x * tuple.0 +} + +// expected-error @+1 {{function is not differentiable}} +@differentiable +// expected-note @+2 {{when differentiating this function definition}} +// expected-note @+1 {{forward-mode differentiation does not yet support control flow}} +func activeInoutParamControlFlow(_ array: [Float]) -> Float { + var result: Float = 1 + for i in withoutDerivative(at: array).indices { + result += array[i] + } + return result +} + +struct Mut: Differentiable {} +extension Mut { + @differentiable(wrt: x) + mutating func mutatingMethod(_ x: Mut) {} +} + +// FIXME(TF-984): Forward-mode crash due to unset tangent buffer. +/* +@differentiable(wrt: x) +func nonActiveInoutParam(_ nonactive: inout Mut, _ x: Mut) -> Mut { + return nonactive.mutatingMethod(x) +} +*/ + +// FIXME(TF-984): Forward-mode crash due to unset tangent buffer. +/* +@differentiable(wrt: x) +func activeInoutParamMutatingMethod(_ x: Mut) -> Mut { + var result = x + result = result.mutatingMethod(result) + return result +} +*/ + +// FIXME(TF-984): Forward-mode crash due to unset tangent buffer. +/* +@differentiable(wrt: x) +func activeInoutParamMutatingMethodVar(_ nonactive: inout Mut, _ x: Mut) -> Mut { + var result = nonactive + result = result.mutatingMethod(x) + return result +} +*/ + +// FIXME(TF-984): Forward-mode crash due to unset tangent buffer. +/* +@differentiable(wrt: x) +func activeInoutParamMutatingMethodTuple(_ nonactive: inout Mut, _ x: Mut) -> Mut { + var result = (nonactive, x) + let result2 = result.0.mutatingMethod(result.0) + return result2 +} +*/ + +//===----------------------------------------------------------------------===// +// Subset parameter differentiation thunks +//===----------------------------------------------------------------------===// + +// FIXME(SR-13046): Non-differentiability diagnostic crash due to invalid source location. +/* +func testNoDerivativeParameter(_ f: @differentiable (Float, @noDerivative Float) -> Float) -> Float { + return derivative(at: 2, 3) { (x, y) in f(x * x, y) } +} +*/ diff --git a/test/DebugInfo/modulecache.swift b/test/DebugInfo/modulecache.swift index c4eb24e70c66c..5ddb4e800318d 100644 --- a/test/DebugInfo/modulecache.swift +++ b/test/DebugInfo/modulecache.swift @@ -9,7 +9,7 @@ import ClangModule // RUN: %empty-directory(%t) // RUN: %swift-ide-test_plain -print-usrs -target %target-triple -module-cache-path %t -I %S/Inputs -source-filename %s -// RUN: head -c 4 %t/*/ClangModule-*.pcm | grep -q CPCH +// RUN: dd bs=1 count=4 < %t/*/ClangModule-*.pcm 2>/dev/null | grep -q CPCH // 2. Test that swift is creating clang modules with debug info. diff --git a/test/SILOptimizer/basic-aa.sil b/test/SILOptimizer/basic-aa.sil index bf6c3fca8366e..b24fe632275d2 100644 --- a/test/SILOptimizer/basic-aa.sil +++ b/test/SILOptimizer/basic-aa.sil @@ -579,3 +579,97 @@ bb0: %2 = tuple() return %2 : $() } + +struct TwoInts { + var a: Int + var b: Int +} + +struct StructWithOptional { + var i: Optional +} + +// CHECK-LABEL: @init_enum_data_addr +// CHECK: PAIR #3. +// CHECK-NEXT: %0 = argument of bb0 : $*StructWithOptional +// CHECK-NEXT: %3 = init_enum_data_addr %2 : $*Optional, #Optional.some!enumelt +// CHECK-NEXT: NoAlias +// CHECK: PAIR #4. +// CHECK-NEXT: %0 = argument of bb0 : $*StructWithOptional +// CHECK-NEXT: %4 = struct_element_addr %3 : $*TwoInts, #TwoInts.a +// CHECK-NEXT: NoAlias +// CHECK: PAIR #5. +// CHECK-NEXT: %0 = argument of bb0 : $*StructWithOptional +// CHECK-NEXT: %5 = struct_element_addr %3 : $*TwoInts, #TwoInts.b +// CHECK-NEXT: NoAlias +// CHECK: PAIR #14. +// CHECK-NEXT: %2 = struct_element_addr %1 : $*StructWithOptional, #StructWithOptional.i +// CHECK-NEXT: %3 = init_enum_data_addr %2 : $*Optional, #Optional.some!enumelt +// CHECK-NEXT: MayAlias +// CHECK: PAIR #15. +// CHECK-NEXT: %2 = struct_element_addr %1 : $*StructWithOptional, #StructWithOptional.i +// CHECK-NEXT: %4 = struct_element_addr %3 : $*TwoInts, #TwoInts.a +// CHECK-NEXT: MayAlias +// CHECK: PAIR #16. +// CHECK-NEXT: %2 = struct_element_addr %1 : $*StructWithOptional, #StructWithOptional.i +// CHECK-NEXT: %5 = struct_element_addr %3 : $*TwoInts, #TwoInts.b +// CHECK-NEXT: MayAlias +// CHECK: PAIR #19. +// CHECK-NEXT: %3 = init_enum_data_addr %2 : $*Optional, #Optional.some!enumelt +// CHECK-NEXT: %4 = struct_element_addr %3 : $*TwoInts, #TwoInts.a +// CHECK-NEXT: PartialAlias +// CHECK: PAIR #20. +// CHECK-NEXT: %3 = init_enum_data_addr %2 : $*Optional, #Optional.some!enumelt +// CHECK-NEXT: %5 = struct_element_addr %3 : $*TwoInts, #TwoInts.b +// CHECK-NEXT: PartialAlias +// CHECK: PAIR #23. +// CHECK-NEXT: %4 = struct_element_addr %3 : $*TwoInts, #TwoInts.a +// CHECK-NEXT: %5 = struct_element_addr %3 : $*TwoInts, #TwoInts.b +// CHECK-NEXT: NoAlias +sil @init_enum_data_addr : $@convention(thin) (@in_guaranteed StructWithOptional) -> () { +bb0(%0 : $*StructWithOptional): + %1 = alloc_stack $StructWithOptional + %2 = struct_element_addr %1 : $*StructWithOptional, #StructWithOptional.i + %3 = init_enum_data_addr %2 : $*Optional, #Optional.some!enumelt + %4 = struct_element_addr %3 : $*TwoInts, #TwoInts.a + %5 = struct_element_addr %3 : $*TwoInts, #TwoInts.b + dealloc_stack %1 : $*StructWithOptional + %6 = tuple () + return %6 : $() +} + +protocol P {} + +struct S : P { + var i: Int +} + +// CHECK-LABEL: @init_existential_addr +// CHECK: PAIR #3. +// CHECK-NEXT: %0 = argument of bb0 : $P +// CHECK-NEXT: %3 = init_existential_addr %2 : $*P, $S +// CHECK-NEXT: NoAlias +// CHECK: PAIR #12. +// CHECK-NEXT: %2 = alloc_stack $P +// CHECK-NEXT: %3 = init_existential_addr %2 : $*P, $S +// CHECK-NEXT: MayAlias +// CHECK: PAIR #13. +// CHECK-NEXT: %2 = alloc_stack $P +// CHECK-NEXT: %4 = struct_element_addr %3 : $*S, #S.i +// CHECK-NEXT: MayAlias +// CHECK: PAIR #16. +// CHECK-NEXT: %3 = init_existential_addr %2 : $*P, $S +// CHECK-NEXT: %4 = struct_element_addr %3 : $*S, #S.i +// CHECK-NEXT: PartialAlias +sil @init_existential_addr : $@convention(thin) (P) -> () { +bb0(%0 : $P): + %1 = alloc_stack $S + %2 = alloc_stack $P + %3 = init_existential_addr %2 : $*P, $S + %4 = struct_element_addr %3 : $*S, #S.i + dealloc_stack %2 : $*P + dealloc_stack %1 : $*S + %6 = tuple () + return %6 : $() +} + diff --git a/test/SILOptimizer/mem-behavior-all.sil b/test/SILOptimizer/mem-behavior-all.sil index 11e4ab6199467..fb101d2b3224d 100644 --- a/test/SILOptimizer/mem-behavior-all.sil +++ b/test/SILOptimizer/mem-behavior-all.sil @@ -113,6 +113,21 @@ bb0(%0 : $*Builtin.Int32): return %val : $Builtin.Int32 } +// CHECK-LABEL: @testLoadTake +// CHECK: PAIR #0. +// CHECK-NEXT: %2 = load [take] %0 : $*C +// CHECK-NEXT: %0 = argument of bb0 : $*C +// CHECK-NEXT: r=1,w=1,se=1 +// CHECK: PAIR #1. +// CHECK-NEXT: %2 = load [take] %0 : $*C +// CHECK-NEXT: %1 = argument of bb0 : $*C +// CHECK-NEXT: r=0,w=0,se=0 +sil [ossa] @testLoadTake : $@convention(thin) (@in C, @in_guaranteed C) -> @owned C { +bb0(%0 : $*C, %1 : $*C): + %2 = load [take] %0 : $*C + return %2 : $C +} + // ===-----------------------------------------------------------------------=== // Test the effect of a [deinit] access on a global 'let' // @@ -168,3 +183,64 @@ bb0(%0 : $*Int64Wrapper): %8 = tuple () return %8 : $() } + +// CHECK-LABEL: @test_copy_addr_initialize +// CHECK: PAIR #0. +// CHECK-NEXT: copy_addr %1 to [initialization] %0 : $*C +// CHECK-NEXT: %0 = argument of bb0 : $*C +// CHECK-NEXT: r=0,w=1,se=1 +// CHECK: PAIR #1. +// CHECK-NEXT: copy_addr %1 to [initialization] %0 : $*C +// CHECK-NEXT: %1 = argument of bb0 : $*C +// CHECK-NEXT: r=1,w=0,se=0 +// CHECK: PAIR #2. +// CHECK-NEXT: copy_addr %1 to [initialization] %0 : $*C +// CHECK-NEXT: %2 = argument of bb0 : $*C +// CHECK-NEXT: r=0,w=0,se=0 +sil @test_copy_addr_initialize : $@convention(thin) (@inout C, @inout C) -> @out C { +bb0(%0 : $*C, %1 : $*C, %2: $*C): + copy_addr %1 to [initialization] %0 : $*C + %6 = tuple () + return %6 : $() +} + +// CHECK-LABEL: @test_copy_addr_assign +// CHECK: PAIR #0. +// CHECK-NEXT: copy_addr %1 to %0 : $*C +// CHECK-NEXT: %0 = argument of bb0 : $*C +// CHECK-NEXT: r=1,w=1,se=1 +// CHECK: PAIR #1. +// CHECK-NEXT: copy_addr %1 to %0 : $*C +// CHECK-NEXT: %1 = argument of bb0 : $*C +// CHECK-NEXT: r=1,w=1,se=1 +// CHECK: PAIR #2. +// CHECK-NEXT: copy_addr %1 to %0 : $*C +// CHECK-NEXT: %2 = argument of bb0 : $*C +// CHECK-NEXT: r=1,w=1,se=1 +sil @test_copy_addr_assign : $@convention(thin) (@inout C, @inout C, @inout C) -> () { +bb0(%0 : $*C, %1 : $*C, %2: $*C): + copy_addr %1 to %0 : $*C + %6 = tuple () + return %6 : $() +} + +// CHECK-LABEL: @test_copy_addr_take +// CHECK: PAIR #0. +// CHECK-NEXT: copy_addr [take] %1 to [initialization] %0 : $*C +// CHECK-NEXT: %0 = argument of bb0 : $*C +// CHECK-NEXT: r=0,w=1,se=1 +// CHECK: PAIR #1. +// CHECK-NEXT: copy_addr [take] %1 to [initialization] %0 : $*C +// CHECK-NEXT: %1 = argument of bb0 : $*C +// CHECK-NEXT: r=1,w=1,se=1 +// CHECK: PAIR #2. +// CHECK-NEXT: copy_addr [take] %1 to [initialization] %0 : $*C +// CHECK-NEXT: %2 = argument of bb0 : $*C +// CHECK-NEXT: r=0,w=0,se=0 +sil @test_copy_addr_take : $@convention(thin) (@in C, @inout C) -> @out C { +bb0(%0 : $*C, %1 : $*C, %2: $*C): + copy_addr [take] %1 to [initialization] %0 : $*C + %6 = tuple () + return %6 : $() +} + diff --git a/test/SILOptimizer/temp_rvalue_opt.sil b/test/SILOptimizer/temp_rvalue_opt.sil index 153527ed7be9f..5ca28bac003f9 100644 --- a/test/SILOptimizer/temp_rvalue_opt.sil +++ b/test/SILOptimizer/temp_rvalue_opt.sil @@ -16,6 +16,11 @@ struct GS { class Klass {} +struct Str { + var kl: Klass + var _value: Builtin.Int64 +} + sil @unknown : $@convention(thin) () -> () sil @inguaranteed_user_without_result : $@convention(thin) (@in_guaranteed Klass) -> () { @@ -33,6 +38,8 @@ bb0(%0 : $*Klass, %1 : $*Klass): sil @throwing_function : $@convention(thin) (@in_guaranteed Klass) -> ((), @error Error) +sil [ossa] @takeStr : $@convention(thin) (@owned Str) -> () + /////////// // Tests // /////////// @@ -124,6 +131,26 @@ bb0(%0 : $*B, %1 : $*GS): return %9999 : $() } +// Check that we don't cause a memory lifetime failure with load [take] + +// CHECK-LABEL: sil [ossa] @take_from_source +// CHECK: alloc_stack +// CHECK: copy_addr +// CHECK: load +// CHECK: load +// CHECK: } // end sil function 'take_from_source' +sil [ossa] @take_from_source : $@convention(thin) (@in Str) -> @owned Str { +bb0(%0 : $*Str): + %1 = alloc_stack $Str + copy_addr %0 to [initialization] %1 : $*Str + %3 = load [take] %0 : $*Str + %4 = load [copy] %1 : $*Str + %f = function_ref @takeStr : $@convention(thin) (@owned Str) -> () + %a = apply %f(%4) : $@convention(thin) (@owned Str) -> () + destroy_addr %1 : $*Str + dealloc_stack %1 : $*Str + return %3 : $Str +} // CHECK-LABEL: sil @load_in_wrong_block // CHECK: bb0(%0 : $*GS): // CHECK-NEXT: alloc_stack diff --git a/test/SILOptimizer/templvalueopt.sil b/test/SILOptimizer/templvalueopt.sil new file mode 100644 index 0000000000000..81f57e2d4da75 --- /dev/null +++ b/test/SILOptimizer/templvalueopt.sil @@ -0,0 +1,251 @@ +// RUN: %target-sil-opt -enable-sil-verify-all %s -temp-lvalue-opt | %FileCheck %s + +import Swift +import Builtin + +// CHECK-LABEL: sil @test_enum_with_initialize +// CHECK: bb0(%0 : $*Optional, %1 : $*Any): +// CHECK-NEXT: [[E:%[0-9]+]] = init_enum_data_addr %0 +// CHECK-NEXT: copy_addr [take] %1 to [initialization] [[E]] +// CHECK-NEXT: inject_enum_addr %0 : $*Optional, #Optional.some!enumelt +// CHECK-NOT: copy_addr +// CHECK: } // end sil function 'test_enum_with_initialize' +sil @test_enum_with_initialize : $@convention(thin) (@in Any) -> @out Optional { +bb0(%0 : $*Optional, %1 : $*Any): + %2 = alloc_stack $Optional + %3 = init_enum_data_addr %2 : $*Optional, #Optional.some!enumelt + copy_addr [take] %1 to [initialization] %3 : $*Any + inject_enum_addr %2 : $*Optional, #Optional.some!enumelt + copy_addr [take] %2 to [initialization] %0 : $*Optional + dealloc_stack %2 : $*Optional + %6 = tuple () + return %6 : $() +} + +// CHECK-LABEL: sil @test_enum_without_initialize +// CHECK: bb0(%0 : $*Optional, %1 : $*Any): +// CHECK-NEXT: destroy_addr %0 +// CHECK-NEXT: [[E:%[0-9]+]] = init_enum_data_addr %0 +// CHECK-NEXT: copy_addr [take] %1 to [initialization] [[E]] +// CHECK-NEXT: inject_enum_addr %0 : $*Optional, #Optional.some!enumelt +// CHECK-NOT: copy_addr +// CHECK: } // end sil function 'test_enum_without_initialize' +sil @test_enum_without_initialize : $@convention(thin) (@in Any) -> @out Optional { +bb0(%0 : $*Optional, %1 : $*Any): + %2 = alloc_stack $Optional + %3 = init_enum_data_addr %2 : $*Optional, #Optional.some!enumelt + copy_addr [take] %1 to [initialization] %3 : $*Any + inject_enum_addr %2 : $*Optional, #Optional.some!enumelt + copy_addr [take] %2 to %0 : $*Optional + dealloc_stack %2 : $*Optional + %6 = tuple () + return %6 : $() +} + +protocol Proto {} + +struct ConformingStruct : Proto { + @_hasStorage let a: Any +} + +// CHECK-LABEL: sil @test_existential +// CHECK: bb0(%0 : $*Proto, %1 : $*ConformingStruct): +// CHECK-NEXT: [[E:%[0-9]+]] = init_existential_addr %0 +// CHECK-NEXT: copy_addr [take] %1 to [initialization] [[E]] +// CHECK-NOT: copy_addr +// CHECK: } // end sil function 'test_existential' +sil @test_existential : $@convention(thin) (@in ConformingStruct) -> @out Proto { +bb0(%0 : $*Proto, %1 : $*ConformingStruct): + %2 = alloc_stack $Proto + %3 = init_existential_addr %2 : $*Proto, $ConformingStruct + copy_addr [take] %1 to [initialization] %3 : $*ConformingStruct + copy_addr [take] %2 to [initialization] %0 : $*Proto + dealloc_stack %2 : $*Proto + %6 = tuple () + return %6 : $() +} + +enum Either { + case left(Left), right(Right) +} + +public struct TestStruct { + @_hasStorage var e: Either +} + +struct AddressOnlyPayload { + @_hasStorage let a: Any + @_hasStorage let i: Int +} + +// There should only be a single copy_addr after optimization. +// +// CHECK-LABEL: sil @test_initialize_struct +// CHECK: bb0(%0 : $*TestStruct, %1 : $*Any, %2 : $Int): +// CHECK-NEXT: [[E:%[0-9]+]] = struct_element_addr %0 +// CHECK-NEXT: [[LEFT:%[0-9]+]] = init_enum_data_addr [[E]] +// CHECK-NEXT: [[A:%[0-9]+]] = struct_element_addr [[LEFT]] +// CHECK-NEXT: copy_addr %1 to [initialization] [[A]] +// CHECK-NEXT: [[I:%[0-9]+]] = struct_element_addr [[LEFT]] +// CHECK-NEXT: store %2 to [[I]] +// CHECK-NEXT: inject_enum_addr [[E]] +// CHECK-NOT: copy_addr +// CHECK: } // end sil function 'test_initialize_struct' +sil @test_initialize_struct : $@convention(method) (@in Any, Int) -> @out TestStruct { +bb0(%0 : $*TestStruct, %1 : $*Any, %2 : $Int): + %3 = alloc_stack $TestStruct + %4 = alloc_stack $Either + %5 = alloc_stack $AddressOnlyPayload + %6 = struct_element_addr %5 : $*AddressOnlyPayload, #AddressOnlyPayload.a + copy_addr %1 to [initialization] %6 : $*Any + %8 = struct_element_addr %5 : $*AddressOnlyPayload, #AddressOnlyPayload.i + store %2 to %8 : $*Int + %10 = init_enum_data_addr %4 : $*Either, #Either.left!enumelt + copy_addr [take] %5 to [initialization] %10 : $*AddressOnlyPayload + inject_enum_addr %4 : $*Either, #Either.left!enumelt + dealloc_stack %5 : $*AddressOnlyPayload + %14 = struct_element_addr %3 : $*TestStruct, #TestStruct.e + copy_addr [take] %4 to [initialization] %14 : $*Either + dealloc_stack %4 : $*Either + copy_addr %3 to [initialization] %0 : $*TestStruct + destroy_addr %3 : $*TestStruct + dealloc_stack %3 : $*TestStruct + %20 = tuple () + return %20 : $() +} + +// CHECK-LABEL: sil @bail_on_write_to_dest +// CHECK: alloc_stack +// CHECK: copy_addr +// CHECK: copy_addr +// CHECK: } // end sil function 'bail_on_write_to_dest' +sil @bail_on_write_to_dest : $@convention(thin) (@inout Optional, @in Any) -> () { +bb0(%0 : $*Optional, %1 : $*Any): + %2 = alloc_stack $Optional + %3 = init_enum_data_addr %2 : $*Optional, #Optional.some!enumelt + copy_addr [take] %1 to [initialization] %3 : $*Any + inject_enum_addr %2 : $*Optional, #Optional.some!enumelt + destroy_addr %0 : $*Optional + copy_addr [take] %2 to [initialization] %0 : $*Optional + dealloc_stack %2 : $*Optional + %6 = tuple () + return %6 : $() +} + +// CHECK-LABEL: sil @write_to_dest_ok_if_before_liferange +// CHECK: bb0(%0 : $*Optional, %1 : $*Any): +// CHECK-NEXT: destroy_addr +// CHECK-NEXT: init_enum_data_addr +// CHECK-NEXT: copy_addr +// CHECK-NEXT: inject_enum_addr +// CHECK-NOT: copy_addr +// CHECK: } // end sil function 'write_to_dest_ok_if_before_liferange' +sil @write_to_dest_ok_if_before_liferange : $@convention(thin) (@inout Optional, @in Any) -> () { +bb0(%0 : $*Optional, %1 : $*Any): + %2 = alloc_stack $Optional + destroy_addr %0 : $*Optional + %3 = init_enum_data_addr %2 : $*Optional, #Optional.some!enumelt + copy_addr [take] %1 to [initialization] %3 : $*Any + inject_enum_addr %2 : $*Optional, #Optional.some!enumelt + copy_addr [take] %2 to [initialization] %0 : $*Optional + dealloc_stack %2 : $*Optional + %6 = tuple () + return %6 : $() +} + +enum Enum { + case A(Optional), B +} + +struct StructWithEnum : Proto { + @_hasStorage let e: Enum +} + +// CHECK-LABEL: sil @move_projections +// CHECK: bb0(%0 : $*Proto, %1 : $*Any): +// CHECK-NEXT: [[S:%[0-9]+]] = init_existential_addr %0 : $*Proto, $StructWithEnum +// CHECK-NEXT: [[E:%[0-9]+]] = struct_element_addr [[S]] : $*StructWithEnum, #StructWithEnum.e +// CHECK-NEXT: [[ENUMA:%[0-9]+]] = init_enum_data_addr [[E]] : $*Enum, #Enum.A!enumelt +// CHECK-NEXT: [[OPTIONAL:%[0-9]+]] = init_enum_data_addr [[ENUMA]] : $*Optional, #Optional.some!enumelt +// CHECK-NEXT: copy_addr [take] %1 to [initialization] [[OPTIONAL]] : $*Any +// CHECK-NEXT: inject_enum_addr [[ENUMA]] : $*Optional, #Optional.some!enumelt +// CHECK-NEXT: inject_enum_addr [[E]] : $*Enum, #Enum.A!enumelt +// CHECK-NOT: copy_addr +// CHECK: } // end sil function 'move_projections' +sil @move_projections : $@convention(thin) (@in Any) -> @out Proto { +bb0(%0 : $*Proto, %1 : $*Any): + %2 = alloc_stack $Optional + %3 = init_enum_data_addr %2 : $*Optional, #Optional.some!enumelt + copy_addr [take] %1 to [initialization] %3 : $*Any + inject_enum_addr %2 : $*Optional, #Optional.some!enumelt + %4 = init_existential_addr %0 : $*Proto, $StructWithEnum + %5 = struct_element_addr %4 : $*StructWithEnum, #StructWithEnum.e + %6 = init_enum_data_addr %5 : $*Enum, #Enum.A!enumelt + copy_addr [take] %2 to [initialization] %6 : $*Optional + inject_enum_addr %5 : $*Enum, #Enum.A!enumelt + dealloc_stack %2 : $*Optional + %10 = tuple () + return %10 : $() +} + +// CHECK-LABEL: sil @cant_move_projections +// CHECK: alloc_stack +// CHECK: copy_addr +// CHECK: load +// CHECK: copy_addr +// CHECK: } // end sil function 'cant_move_projections' +sil @cant_move_projections : $@convention(thin) (@in Any, @in_guaranteed Builtin.RawPointer) -> () { +bb0(%0 : $*Any, %1 : $*Builtin.RawPointer): + %2 = alloc_stack $Optional + %3 = init_enum_data_addr %2 : $*Optional, #Optional.some!enumelt + copy_addr [take] %0 to [initialization] %3 : $*Any + inject_enum_addr %2 : $*Optional, #Optional.some!enumelt + %4 = load %1 : $*Builtin.RawPointer + %5 = pointer_to_address %4 : $Builtin.RawPointer to $*Optional + copy_addr [take] %2 to [initialization] %5 : $*Optional + dealloc_stack %2 : $*Optional + %10 = tuple () + return %10 : $() +} + +sil @init_optional : $@convention(thin) () -> @out Optional + +// CHECK-LABEL: sil @instructions_after_copy_addr +// CHECK: alloc_stack +// CHECK: copy_addr +// CHECK: copy_addr +// CHECK: apply +// CHECK: } // end sil function 'instructions_after_copy_addr' +sil @instructions_after_copy_addr : $@convention(thin) (@in Any) -> @out Optional { +bb0(%0 : $*Optional, %1 : $*Any): + %2 = alloc_stack $Optional + %3 = init_enum_data_addr %2 : $*Optional, #Optional.some!enumelt + copy_addr [take] %1 to [initialization] %3 : $*Any + inject_enum_addr %2 : $*Optional, #Optional.some!enumelt + copy_addr [take] %2 to [initialization] %0 : $*Optional + %4 = function_ref @init_optional : $@convention(thin) () -> @out Optional + %5 = apply %4(%2) : $@convention(thin) () -> @out Optional + destroy_addr %2 : $*Optional + dealloc_stack %2 : $*Optional + %6 = tuple () + return %6 : $() +} + +// CHECK-LABEL: sil @dont_optimize_swap +// CHECK: alloc_stack +// CHECK: copy_addr +// CHECK: copy_addr +// CHECK: copy_addr +// CHECK: dealloc_stack +// CHECK: } // end sil function 'dont_optimize_swap' +sil @dont_optimize_swap : $@convention(thin) (@inout T, @inout T) -> () { +bb0(%0 : $*T, %1 : $*T): + %2 = alloc_stack $T + copy_addr [take] %0 to [initialization] %2 : $*T + copy_addr [take] %1 to [initialization] %0 : $*T + copy_addr [take] %2 to [initialization] %1 : $*T + dealloc_stack %2 : $*T + %78 = tuple () + return %78 : $() +} + diff --git a/test/SILOptimizer/templvalueopt.swift b/test/SILOptimizer/templvalueopt.swift new file mode 100644 index 0000000000000..05ab18c00bfc5 --- /dev/null +++ b/test/SILOptimizer/templvalueopt.swift @@ -0,0 +1,61 @@ +// RUN: %target-swift-frontend -module-name=test -O -emit-sil %s | %FileCheck %s + +// RUN: %empty-directory(%t) +// RUN: %target-build-swift -O -module-name=test %s -o %t/a.out +// RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT + +// REQUIRES: executable_test +// REQUIRES: CPU=arm64 || CPU=x86_64 + +// Check that the compiler creates optimal code for address-only enum initializations. + +public enum Either { + case left(Left), right(Right) +} + +internal struct AddressOnlyPayload { + let a: Any + let i: Int +} + +public struct TestStruct { + internal var e: Either + + // CHECK-LABEL: sil [noinline] @$s4test10TestStructVyACyp_SitcfC + // CHECK: [[E:%[0-9]+]] = struct_element_addr %0 : $*TestStruct, #TestStruct.e + // CHECK: [[LEFT:%[0-9]+]] = init_enum_data_addr [[E]] : $*Either, #Either.left!enumelt + // CHECK: [[A:%[0-9]+]] = struct_element_addr [[LEFT]] : $*AddressOnlyPayload, #AddressOnlyPayload.a + // CHECK: copy_addr [take] %1 to [initialization] [[A]] : $*Any + // CHECK: [[I:%[0-9]+]] = struct_element_addr [[LEFT]] : $*AddressOnlyPayload, #AddressOnlyPayload.i + // CHECK: store %2 to [[I]] : $*Int + // CHECK: inject_enum_addr [[E]] : $*Either, #Either.left!enumelt + // CHECK: } // end sil function '$s4test10TestStructVyACyp_SitcfC' + @inline(never) + public init(_ a: Any, _ i: Int) { + e = Either.left(AddressOnlyPayload(a: a, i: i)) + } +} + +// CHECK-LABEL: sil [noinline] @$s4test13createAnyLeftyAA6EitherOyypSgSiGypF +// CHECK: [[E:%[0-9]+]] = init_enum_data_addr %0 : $*Either, Int>, #Either.left!enumelt +// CHECK: [[SOME:%[0-9]+]] = init_enum_data_addr [[E]] : $*Optional, #Optional.some!enumelt +// CHECK: copy_addr %1 to [initialization] [[SOME]] : $*Any +// CHECK: inject_enum_addr [[E]] : $*Optional, #Optional.some!enumelt +// CHECK: inject_enum_addr %0 : $*Either, Int>, #Either.left!enumelt +// CHECK: } // end sil function '$s4test13createAnyLeftyAA6EitherOyypSgSiGypF' +@inline(never) +public func createAnyLeft(_ a: Any) -> Either { + return Either.left(a) +} + + +// CHECK-OUTPUT: TestStruct(e: test.Either.left(test.AddressOnlyPayload(a: 27, i: 1))) +// CHECK-OUTPUT: TestStruct(e: test.Either.left(test.AddressOnlyPayload(a: "A non-trivial value", i: 1))) +print(TestStruct(27, 1)) +print(TestStruct("A non-trivial value", 1)) + +// CHECK-OUTPUT: left(Optional(27)) +// CHECK-OUTPUT: left(Optional("A non-trivial value")) +print(createAnyLeft(27)) +print(createAnyLeft("A non-trivial value")) + diff --git a/utils/analyze_code_size.py b/utils/analyze_code_size.py index 7d0b7b003c5af..3e84cf18ff01d 100755 --- a/utils/analyze_code_size.py +++ b/utils/analyze_code_size.py @@ -124,8 +124,12 @@ def add(self, symbol): self.size += symbol.size def list_symbols(self): + sorted_symbols = [] for symbol in self.symbols: - print(" " + symbol.name + " " + str(symbol.size)) + sorted_symbols.append((symbol.name, symbol.size)) + sorted_symbols.sort(key=lambda entry: entry[1], reverse=True) + for symbol in sorted_symbols: + print("%9d %s" % (symbol[1], symbol[0])) class Categories(object): @@ -229,7 +233,7 @@ def __init__(self): self.categories = {} self.specializations = {} self.specialization_matcher = re.compile( - r'.*generic specialization <(?P[^>]*)>.* of' + + r'.*generic specialization <(?P.*)> of' + r' (static )?(\(extension in Swift\):)?(?P[^.]*)\.' + r'(?:(?P[^.^(^<]*)\.){0,1}' + r'(?:(?P[^.^(^<]*)\.)*(?P[^(^<]*)' @@ -255,7 +259,7 @@ def __init__(self): self.array_type_matcher = re.compile(r'Array') self.dictionary = re.compile(r'Array') self.single_specialized_types_matcher = re.compile( - r'(?P[^,^.]*)\.(?P[^,^.]*)$' + r'(?P[^,^.]*)\.([^,^.]*\.)*(?P[^,^.]*)$' ) self.is_class_type_dict = {} self.stdlib_and_other_type_matcher = re.compile(