diff --git a/CMakeLists.txt b/CMakeLists.txt index a7418df27ce88..d290ba575d5bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -776,7 +776,7 @@ elseif("${SWIFT_HOST_VARIANT_SDK}" MATCHES "(OSX|IOS*|TVOS*|WATCHOS*)") # # Primary variant is always OSX; even on iOS hosts. set(SWIFT_PRIMARY_VARIANT_SDK_default "OSX") - set(SWIFT_PRIMARY_VARIANT_ARCH_default "x86_64") + set(SWIFT_PRIMARY_VARIANT_ARCH_default "${CMAKE_HOST_SYSTEM_PROCESSOR}") endif() diff --git a/README.md b/README.md index b2b9e5a76cc25..6e4685ee4780c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ | | **Architecture** | **main** | **Package** | |---|:---:|:---:|:---:| -| **macOS** | x86_64 |[![Build Status](https://ci.swift.org/job/oss-swift-incremental-RA-osx/lastCompletedBuild/badge/icon)](https://ci.swift.org/job/oss-swift-incremental-RA-osx)|[![Build Status](https://ci.swift.org/job/oss-swift-package-osx/lastCompletedBuild/badge/icon)](https://ci.swift.org/job/oss-swift-package-osx)| +| **macOS** | x86_64 |[![Build Status](https://ci.swift.org/job/oss-swift-incremental-RA-macos/lastCompletedBuild/badge/icon)](https://ci.swift.org/job/oss-swift-incremental-RA-macos)|[![Build Status](https://ci.swift.org/job/oss-swift-package-macos/lastCompletedBuild/badge/icon)](https://ci.swift.org/job/oss-swift-package-macos)| | **Ubuntu 16.04** | x86_64 | [![Build Status](https://ci.swift.org/job/oss-swift-incremental-RA-linux-ubuntu-16_04/lastCompletedBuild/badge/icon)](https://ci.swift.org/job/oss-swift-incremental-RA-linux-ubuntu-16_04)|[![Build Status](https://ci.swift.org/job/oss-swift-package-linux-ubuntu-16_04/lastCompletedBuild/badge/icon)](https://ci.swift.org/job/oss-swift-package-linux-ubuntu-16_04)| | **Ubuntu 18.04** | x86_64 | [![Build Status](https://ci.swift.org/job/oss-swift-incremental-RA-linux-ubuntu-18_04/lastCompletedBuild/badge/icon)](https://ci.swift.org/job/oss-swift-incremental-RA-linux-ubuntu-18_04)|[![Build Status](https://ci.swift.org/job/oss-swift-package-linux-ubuntu-18_04/lastCompletedBuild/badge/icon)](https://ci.swift.org/job/oss-swift-package-linux-ubuntu-18_04)| | **Ubuntu 20.04** | x86_64 | [![Build Status](https://ci.swift.org/job/oss-swift-package-ubuntu-20_04/lastCompletedBuild/badge/icon)](https://ci.swift.org/job/oss-swift-package-ubuntu-20_04)|[![Build Status](https://ci.swift.org/job/oss-swift-package-ubuntu-20_04/lastCompletedBuild/badge/icon)](https://ci.swift.org/job/oss-swift-package-ubuntu-20_04)| diff --git a/benchmark/README.md b/benchmark/README.md index c4df4f21fef13..5536a963ec875 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -74,6 +74,7 @@ The following build options are available: The following build targets are available: * `swift-benchmark-macosx-x86_64` +* `swift-benchmark-macosx-arm64` * `swift-benchmark-iphoneos-arm64e` * `swift-benchmark-iphoneos-arm64` * `swift-benchmark-iphoneos-armv7` @@ -84,7 +85,7 @@ Build steps (with example options): 1. `$ mkdir build; cd build` 2. `$ cmake [path to swift src]/benchmark -G Ninja -DSWIFT_EXEC=[path to built swiftc]` -3. `$ ninja swift-benchmark-macosx-x86_64` +3. `$ ninja swift-benchmark-macosx-$(uname -m)` Benchmark binaries are placed in `bin`. @@ -98,7 +99,7 @@ relative to the benchmark binary at the time it was executed For example, to benchmark against a locally built `swiftc`, including any standard library changes in that build, you might configure using: - cmake /benchmark -G Ninja -DSWIFT_EXEC=/swift-macosx-x86_64/bin/swiftc + cmake /benchmark -G Ninja -DSWIFT_EXEC=/swift-macosx-$(uname -m)/bin/swiftc ninja swift-benchmark-iphoneos-arm64 To build against the installed Xcode, simply omit SWIFT_EXEC: @@ -319,12 +320,12 @@ swift-source$ ./swift/utils/build-script -R -B ```` you can rebuild just the benchmarks: ```` -swift-source$ export SWIFT_BUILD_DIR=`pwd`/build/Ninja-ReleaseAssert/swift-macosx-x86_64 -swift-source$ ninja -C ${SWIFT_BUILD_DIR} swift-benchmark-macosx-x86_64 +swift-source$ export SWIFT_BUILD_DIR=`pwd`/build/Ninja-ReleaseAssert/swift-macosx-$(uname -m) +swift-source$ ninja -C ${SWIFT_BUILD_DIR} swift-benchmark-macosx-$(uname -m) ```` When modifying the testing infrastructure, you should verify that your changes pass all the tests: ```` -swift-source$ ./llvm/utils/lit/lit.py -sv ${SWIFT_BUILD_DIR}/test-macosx-x86_64/benchmark +swift-source$ ./llvm/utils/lit/lit.py -sv ${SWIFT_BUILD_DIR}/test-macosx-$(uname -m)/benchmark ```` diff --git a/docs/ContinuousIntegration.md b/docs/ContinuousIntegration.md index 7e55056cabe7b..2d332a96e6e66 100644 --- a/docs/ContinuousIntegration.md +++ b/docs/ContinuousIntegration.md @@ -49,12 +49,12 @@ We describe each in detail below: Platform | Comment | Check Status ------------ | ------- | ------------ -All supported platforms | @swift-ci Please smoke test | Swift Test Linux Platform (smoke test)
Swift Test OS X Platform (smoke test) -All supported platforms | @swift-ci Please clean smoke test | Swift Test Linux Platform (smoke test)
Swift Test OS X Platform (smoke test) -All supported platforms | @swift-ci Please smoke test and merge | Swift Test Linux Platform (smoke test)
Swift Test OS X Platform (smoke test) -All supported platforms | @swift-ci Please clean smoke test and merge | Swift Test Linux Platform (smoke test)
Swift Test OS X Platform (smoke test) -macOS platform | @swift-ci Please smoke test OS X platform | Swift Test OS X Platform (smoke test) -macOS platform | @swift-ci Please clean smoke test OS X platform | Swift Test OS X Platform (smoke test) +All supported platforms | @swift-ci Please smoke test | Swift Test Linux Platform (smoke test)
Swift Test macOS Platform (smoke test) +All supported platforms | @swift-ci Please clean smoke test | Swift Test Linux Platform (smoke test)
Swift Test macOS Platform (smoke test) +All supported platforms | @swift-ci Please smoke test and merge | Swift Test Linux Platform (smoke test)
Swift Test macOS Platform (smoke test) +All supported platforms | @swift-ci Please clean smoke test and merge | Swift Test Linux Platform (smoke test)
Swift Test macOS Platform (smoke test) +macOS platform | @swift-ci Please smoke test macOS platform | Swift Test macOS Platform (smoke test) +macOS platform | @swift-ci Please clean smoke test macOS platform | Swift Test macOS Platform (smoke test) Linux platform | @swift-ci Please smoke test Linux platform | Swift Test Linux Platform (smoke test) Linux platform | @swift-ci Please clean smoke test Linux platform | Swift Test Linux Platform (smoke test) @@ -84,17 +84,17 @@ A smoke test on Linux does the following: Platform | Comment | Check Status ------------ | ------- | ------------ -All supported platforms | @swift-ci Please test | Swift Test Linux Platform (smoke test)
Swift Test OS X Platform (smoke test)
Swift Test Linux Platform
Swift Test OS X Platform
-All supported platforms | @swift-ci Please clean test | Swift Test Linux Platform (smoke test)
Swift Test OS X Platform (smoke test)
Swift Test Linux Platform
Swift Test OS X Platform
-All supported platforms | @swift-ci Please test and merge | Swift Test Linux Platform (smoke test)
Swift Test OS X Platform (smoke test)
Swift Test Linux Platform
Swift Test OS X Platform -All supported platforms | @swift-ci Please clean test and merge | Swift Test Linux Platform (smoke test)
Swift Test OS X Platform (smoke test)
Swift Test Linux Platform
Swift Test OS X Platform -macOS platform | @swift-ci Please test OS X platform | Swift Test OS X Platform (smoke test)
Swift Test OS X Platform -macOS platform | @swift-ci Please clean test OS X platform | Swift Test OS X Platform (smoke test)
Swift Test OS X Platform -macOS platform | @swift-ci Please benchmark | Swift Benchmark on OS X Platform (many runs - rigorous) -macOS platform | @swift-ci Please smoke benchmark | Swift Benchmark on OS X Platform (few runs - sanity) +All supported platforms | @swift-ci Please test | Swift Test Linux Platform (smoke test)
Swift Test macOS Platform (smoke test)
Swift Test Linux Platform
Swift Test macOS Platform
+All supported platforms | @swift-ci Please clean test | Swift Test Linux Platform (smoke test)
Swift Test macOS Platform (smoke test)
Swift Test Linux Platform
Swift Test macOS Platform
+All supported platforms | @swift-ci Please test and merge | Swift Test Linux Platform (smoke test)
Swift Test macOS Platform (smoke test)
Swift Test Linux Platform
Swift Test macOS Platform +All supported platforms | @swift-ci Please clean test and merge | Swift Test Linux Platform (smoke test)
Swift Test macOS Platform (smoke test)
Swift Test Linux Platform
Swift Test macOS Platform +macOS platform | @swift-ci Please test macOS platform | Swift Test macOS Platform (smoke test)
Swift Test macOS Platform +macOS platform | @swift-ci Please clean test macOS platform | Swift Test macOS Platform (smoke test)
Swift Test macOS Platform +macOS platform | @swift-ci Please benchmark | Swift Benchmark on macOS Platform (many runs - rigorous) +macOS platform | @swift-ci Please smoke benchmark | Swift Benchmark macOS Platform (few runs - sanity) Linux platform | @swift-ci Please test Linux platform | Swift Test Linux Platform (smoke test)
Swift Test Linux Platform Linux platform | @swift-ci Please clean test Linux platform | Swift Test Linux Platform (smoke test)
Swift Test Linux Platform -macOS platform | @swift-ci Please ASAN test | Swift ASAN Test OS X Platform +macOS platform | @swift-ci Please ASAN test | Swift ASAN Test macOS Platform Ubuntu 18.04 | @swift-ci Please test Ubuntu 18.04 platform | Swift Test Ubuntu 18.04 Platform Ubuntu 20.04 | @swift-ci Please test Ubuntu 20.04 platform | Swift Test Ubuntu 20.04 Platform CentOS 7 | @swift-ci Please test CentOS 7 platform | Swift Test CentOS 7 Platform @@ -134,8 +134,8 @@ A validation test on Linux does the following: Platform | Comment | Check Status ------------ | ------- | ------------ -macOS platform | @swift-ci Please benchmark | Swift Benchmark on OS X Platform (many runs - rigorous) -macOS platform | @swift-ci Please smoke benchmark | Swift Benchmark on OS X Platform (few runs - sanity) +macOS platform | @swift-ci Please benchmark | Swift Benchmark on macOS Platform (many runs - rigorous) +macOS platform | @swift-ci Please smoke benchmark | Swift Benchmark on macOS Platform (few runs - sanity) ### Linting diff --git a/docs/DebuggingTheCompiler.md b/docs/DebuggingTheCompiler.md index 6f1d411698219..5baeea0957c65 100644 --- a/docs/DebuggingTheCompiler.md +++ b/docs/DebuggingTheCompiler.md @@ -875,14 +875,14 @@ well as cleanups/modernizations on a code-base. Swift's cmake invocation by default creates one of these json databases at the root path of the swift host build, for example on macOS: - $PATH_TO_BUILD/swift-macosx-x86_64/compile_commands.json + $PATH_TO_BUILD/swift-macosx-$(uname -m)/compile_commands.json Using this file, one invokes `clang-tidy` on a specific file in the codebase as follows: - clang-tidy -p=$PATH_TO_BUILD/swift-macosx-x86_64/compile_commands.json $FULL_PATH_TO_FILE + clang-tidy -p=$PATH_TO_BUILD/swift-macosx-$(uname -m)/compile_commands.json $FULL_PATH_TO_FILE One can also use shell regex to visit multiple files in the same directory. Example: - clang-tidy -p=$PATH_TO_BUILD/swift-macosx-x86_64/compile_commands.json $FULL_PATH_TO_DIR/*.cpp + clang-tidy -p=$PATH_TO_BUILD/swift-macosx-$(uname -m)/compile_commands.json $FULL_PATH_TO_DIR/*.cpp diff --git a/docs/DynamicCasting.md b/docs/DynamicCasting.md index 647230f883e28..3a596860da4bc 100644 --- a/docs/DynamicCasting.md +++ b/docs/DynamicCasting.md @@ -302,6 +302,8 @@ Casting from a function type F1 to a function type F2 will succeed iff the follo Note that it is _not_ sufficient for argument and return types to be castable; they must actually be identical. +Caveat: The current Swift compiler supports more general casts of function types in certain cases where the compiler has enough information to statically construct the necessary adapter function. Function types that must be cast at runtime follow the less permissive rules described above. + ## Existential Types Conceptually, an "existential type" is an opaque wrapper that carries a type and an instance of that type. @@ -372,7 +374,7 @@ For casting purposes, `AnyHashable` behaves like an existential type. It satisfies the weak existential invariant above. However, note that `AnyHashable` does not act like an existential for other purposes. -For example, it's metatype is named `AnyHashable.Type` and it does not have an existential metatype. +For example, its metatype is named `AnyHashable.Type` and it does not have an existential metatype. ### Protocol Witness types @@ -491,6 +493,12 @@ Non-protocol types do not have existential metatypes. For a generic variable `G`, the expression also refers to the regular metatype, even if the generic variable is bound to a protocol. There is no mechanism in Swift to refer to the existential metatype via a generic variable.) +In essence, an existential metatype simply defines an existential that can hold a metatype instance. +An instance of the existential metatype `Any.Type` can hold any metatype instance. +An instance of the existential metatype `P.Type` of a protocol `P` can hold a metatype instance of any type that conforms to `P`. +As with other existentials, casting _from_ an existential metatype is equivalent to casting the contents of the existential. +Casting _to_ an existential metatype succeeds whenever the source is a conforming metatype instance (or can be unwrapped to yield such a metatype instance). + Example ``` protocol P { diff --git a/docs/HowToGuides/GettingStarted.md b/docs/HowToGuides/GettingStarted.md index 0265874d6a207..a312e193498bc 100644 --- a/docs/HowToGuides/GettingStarted.md +++ b/docs/HowToGuides/GettingStarted.md @@ -245,13 +245,13 @@ Phew, that's a lot to digest! Now let's proceed to the actual build itself! - Via Ninja: ```sh utils/build-script --skip-build-benchmarks \ - --skip-ios --skip-watchos --skip-tvos --swift-darwin-supported-archs "x86_64" \ + --skip-ios --skip-watchos --skip-tvos --swift-darwin-supported-archs "$(uname -m)" \ --sccache --release-debuginfo --test ``` - Via Xcode: ```sh utils/build-script --skip-build-benchmarks \ - --skip-ios --skip-watchos --skip-tvos --swift-darwin-supported-archs "x86_64" \ + --skip-ios --skip-watchos --skip-tvos --swift-darwin-supported-archs "$(uname -m)" \ --sccache --release-debuginfo --test \ --xcode ``` @@ -350,7 +350,8 @@ git push --set-upstream my-remote my-branch ### First time Xcode setup If you used `--xcode` earlier, you will see an Xcode project generated under -`../build/Xcode-RelWithDebInfoAssert/swift-macosx-x86_64`. When you open the +`../build/Xcode-RelWithDebInfoAssert/swift-macosx-x86_64` (or +`../build/Xcode-RelWithDebInfoAssert/swift-macosx-arm64` on Apple Silicon Macs). When you open the project, Xcode might helpfully suggest "Automatically Create Schemes". Most of those schemes are not required in day-to-day work, so you can instead manually select the following schemes: @@ -375,12 +376,12 @@ Now that you have made some changes, you will need to rebuild... To rebuild the compiler: ```sh -ninja -C ../build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64 swift-frontend +ninja -C ../build/Ninja-RelWithDebInfoAssert/swift-macosx-$(uname -m) swift-frontend ``` To rebuild everything, including the standard library: ```sh -ninja -C ../build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64 +ninja -C ../build/Ninja-RelWithDebInfoAssert/swift-macosx-$(uname -m) ``` ### Incremental builds with Xcode @@ -396,7 +397,7 @@ build should be much faster than the from-scratch build at the beginning. Now check if the version string has been updated: ```sh -../build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/bin/swift-frontend --version +../build/Ninja-RelWithDebInfoAssert/swift-macosx-$(uname -m)/bin/swift-frontend --version ``` This should print your updated version string. @@ -439,22 +440,22 @@ There are two main ways to run tests: ```sh # Rebuild all test dependencies and run all tests under test/. utils/run-test --lit ../llvm-project/llvm/utils/lit/lit.py \ - ../build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64 + ../build/Ninja-RelWithDebInfoAssert/swift-macosx-$(uname -m)/test-macosx-$(uname -m) # Rebuild all test dependencies and run tests containing "MyTest". utils/run-test --lit ../llvm-project/llvm/utils/lit/lit.py \ - ../build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64 \ + ../build/Ninja-RelWithDebInfoAssert/swift-macosx-$(uname -m)/test-macosx-$(uname -m) \ --filter="MyTest" ``` 2. `lit.py`: lit doesn't know anything about dependencies. It just runs tests. ```sh # Run all tests under test/. ../llvm-project/llvm/utils/lit/lit.py -s -vv \ - ../build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64 + ../build/Ninja-RelWithDebInfoAssert/swift-macosx-$(uname -m)/test-macosx-$(uname -m) # Run tests containing "MyTest" ../llvm-project/llvm/utils/lit/lit.py -s -vv \ - ../build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64 \ + ../build/Ninja-RelWithDebInfoAssert/swift-macosx-$(uname -m)/test-macosx-$(uname -m) \ --filter="MyTest" ``` The `-s` and `-vv` flags print a progress bar and the executed commands diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index 23a380833516c..0166ef3545cc8 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -572,9 +572,10 @@ CONTEXTUAL_SIMPLE_DECL_ATTR(actor, Actor, 102) DECL_ATTR(actorIndependent, ActorIndependent, - OnFunc | OnVar | OnSubscript | ConcurrencyOnly | + OnClass | OnStruct | OnEnum | OnExtension | OnFunc | OnConstructor | + OnVar | OnSubscript | ConcurrencyOnly | ABIStableToAdd | ABIStableToRemove | - APIStableToAdd | APIBreakingToRemove, + APIBreakingToAdd | APIBreakingToRemove, 103) SIMPLE_DECL_ATTR(globalActor, GlobalActor, diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index c091580433301..20ecc973a4d7b 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -3786,8 +3786,8 @@ ERROR(expression_unused_closure,none, "closure expression is unused", ()) ERROR(expression_unused_function,none, "expression resolves to an unused function", ()) -ERROR(expression_unused_lvalue,none, - "expression resolves to an unused %select{variable|property|subscript}0", (unsigned)) +WARNING(expression_unused_lvalue,none, + "expression resolves to an unused %select{variable|property|subscript}0", (unsigned)) WARNING(expression_unused_result_call,none, "result of call to %0 is unused", (DeclName)) WARNING(expression_unused_result_operator,none, @@ -4281,13 +4281,6 @@ ERROR(actorindependent_mutable_storage,none, ERROR(actorindependent_local_var,none, "'@actorIndependent' can not be applied to local variables", ()) -ERROR(actorindependent_not_actor_member,none, - "'@actorIndependent' can only be applied to actor members and " - "global/static variables", - ()) -ERROR(actorindependent_not_actor_instance_member,none, - "'@actorIndependent' can only be applied to instance members of actors", - ()) ERROR(concurrency_lib_missing,none, "missing '%0' declaration, probably because the '_Concurrency' " diff --git a/include/swift/AST/Evaluator.h b/include/swift/AST/Evaluator.h index ed5e392406e11..9086403bada14 100644 --- a/include/swift/AST/Evaluator.h +++ b/include/swift/AST/Evaluator.h @@ -20,6 +20,7 @@ #include "swift/AST/AnyRequest.h" #include "swift/AST/EvaluatorDependencies.h" +#include "swift/AST/RequestCache.h" #include "swift/Basic/AnyValue.h" #include "swift/Basic/Debug.h" #include "swift/Basic/LangOptions.h" @@ -211,7 +212,7 @@ class Evaluator { llvm::SetVector activeRequests; /// A cache that stores the results of requests. - llvm::DenseMap cache; + evaluator::RequestCache cache; /// Track the dependencies of each request. /// @@ -314,14 +315,15 @@ class Evaluator { typename std::enable_if::type* = nullptr> void cacheOutput(const Request &request, typename Request::OutputType &&output) { - cache.insert({AnyRequest(request), std::move(output)}); + cache.insert(request, std::move(output)); } /// Do not introduce new callers of this function. template::type* = nullptr> void clearCachedOutput(const Request &request) { - cache.erase(AnyRequest(request)); + cache.erase(request); + recorder.clearRequest(request); } /// Clear the cache stored within this evaluator. @@ -372,21 +374,21 @@ class Evaluator { FrontendStatsTracer statsTracer = make_tracer(stats, request); if (stats) reportEvaluatedRequest(*stats, request); - recorder.beginRequest(activeReq); + recorder.beginRequest(); - auto &&r = getRequestFunction()(request, *this); + auto &&result = getRequestFunction()(request, *this); - recorder.endRequest(activeReq); + recorder.endRequest(request); handleDependencySourceRequest(request); - handleDependencySinkRequest(request, r); + handleDependencySinkRequest(request, result); // Make sure we remove this from the set of active requests once we're // done. assert(activeRequests.back() == activeReq); activeRequests.pop_back(); - return std::move(r); + return std::move(result); } /// Get the result of a request, consulting an external cache @@ -398,7 +400,7 @@ class Evaluator { getResultCached(const Request &request) { // If there is a cached result, return it. if (auto cached = request.getCachedResult()) { - recorder.replayCachedRequest(ActiveRequest(request)); + recorder.replayCachedRequest(request); handleDependencySinkRequest(request, *cached); return *cached; } @@ -424,12 +426,12 @@ class Evaluator { llvm::Expected getResultCached(const Request &request) { // If we already have an entry for this request in the cache, return it. - auto known = cache.find_as(request); - if (known != cache.end()) { - auto r = known->second.template castTo(); - recorder.replayCachedRequest(ActiveRequest(request)); - handleDependencySinkRequest(request, r); - return r; + auto known = cache.find_as(request); + if (known != cache.end()) { + auto result = known->second; + recorder.replayCachedRequest(request); + handleDependencySinkRequest(request, result); + return result; } // Compute the result. @@ -438,7 +440,7 @@ class Evaluator { return result; // Cache the result. - cache.insert({AnyRequest(request), *result}); + cache.insert(request, *result); return result; } @@ -465,7 +467,7 @@ class Evaluator { void handleDependencySourceRequest(const Request &r) { auto source = r.readDependencySource(recorder); if (!source.isNull() && source.get()->isPrimary()) { - recorder.handleDependencySourceRequest(ActiveRequest(r), source.get()); + recorder.handleDependencySourceRequest(r, source.get()); } } diff --git a/include/swift/AST/EvaluatorDependencies.h b/include/swift/AST/EvaluatorDependencies.h index 01f32b1a118a0..15be3f9a33bd6 100644 --- a/include/swift/AST/EvaluatorDependencies.h +++ b/include/swift/AST/EvaluatorDependencies.h @@ -20,6 +20,7 @@ #include "swift/AST/AnyRequest.h" #include "swift/AST/DependencyCollector.h" +#include "swift/AST/RequestCache.h" #include "swift/Basic/NullablePtr.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" @@ -63,15 +64,14 @@ struct DependencyRecorder { /// downstream request as well. Note that uncached requests don't appear as /// keys in this map; their references are charged to the innermost cached /// active request. - llvm::DenseMap> - requestReferences; + RequestReferences requestReferences; /// Stack of references from each cached active request. When evaluating a /// dependency sink request, we update the innermost set of references. /// Upon completion of a request, we union the completed request's references /// with the next innermost active request. - std::vector> + std::vector> activeRequestReferences; #ifndef NDEBUG @@ -83,22 +83,30 @@ struct DependencyRecorder { public: /// Push a new empty set onto the activeRequestReferences stack. - void beginRequest(const swift::ActiveRequest &req); + template + void beginRequest(); /// Pop the activeRequestReferences stack, and insert recorded references /// into the requestReferences map, as well as the next innermost entry in /// activeRequestReferences. - void endRequest(const swift::ActiveRequest &req); + template + void endRequest(const Request &req); /// When replaying a request whose value has already been cached, we need /// to update the innermost set in the activeRequestReferences stack. - void replayCachedRequest(const swift::ActiveRequest &req); + template + void replayCachedRequest(const Request &req); /// Upon completion of a dependency source request, we update the /// fileReferences map. - void handleDependencySourceRequest(const swift::ActiveRequest &req, + template + void handleDependencySourceRequest(const Request &req, SourceFile *source); + /// Clear the recorded dependencies of a request, if any. + template + void clearRequest(const Request &req); + private: /// Add an entry to the innermost set on the activeRequestReferences stack. /// Called from the DependencyCollector. @@ -120,6 +128,76 @@ struct DependencyRecorder { void enumerateReferencesInFile(const SourceFile *SF, ReferenceEnumerator f) const ; }; + +template +void evaluator::DependencyRecorder::beginRequest() { + if (!Request::isEverCached && !Request::isDependencySource) + return; + + activeRequestReferences.push_back({}); +} + +template +void evaluator::DependencyRecorder::endRequest(const Request &req) { + if (!Request::isEverCached && !Request::isDependencySource) + return; + + // Grab all the dependencies we've recorded so far, and pop + // the stack. + auto recorded = std::move(activeRequestReferences.back()); + activeRequestReferences.pop_back(); + + // If we didn't record anything, there is nothing to do. + if (recorded.empty()) + return; + + // Convert the set of dependencies into a vector. + std::vector + vec(recorded.begin(), recorded.end()); + + // The recorded dependencies bubble up to the parent request. + if (!activeRequestReferences.empty()) { + activeRequestReferences.back().insert(vec.begin(), + vec.end()); + } + + // Finally, record the dependencies so we can replay them + // later when the request is re-evaluated. + requestReferences.insert(std::move(req), std::move(vec)); +} + +template +void evaluator::DependencyRecorder::replayCachedRequest(const Request &req) { + assert(req.isCached()); + + if (activeRequestReferences.empty()) + return; + + auto found = requestReferences.find_as(req); + if (found == requestReferences.end()) + return; + + activeRequestReferences.back().insert(found->second.begin(), + found->second.end()); +} + +template +void evaluator::DependencyRecorder::handleDependencySourceRequest( + const Request &req, + SourceFile *source) { + auto found = requestReferences.find_as(req); + if (found != requestReferences.end()) { + fileReferences[source].insert(found->second.begin(), + found->second.end()); + } +} + +template +void evaluator::DependencyRecorder::clearRequest( + const Request &req) { + requestReferences.erase(req); +} + } // end namespace evaluator } // end namespace swift diff --git a/include/swift/AST/RequestCache.h b/include/swift/AST/RequestCache.h new file mode 100644 index 0000000000000..f7adfdcb13767 --- /dev/null +++ b/include/swift/AST/RequestCache.h @@ -0,0 +1,435 @@ +//===--- RequestCache.h - Per-request caching ----------------- -*- C++ -*-===// +// +// 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 file defines data structures to efficiently support the request +// evaluator's per-request caching and dependency tracking maps. +// +//===----------------------------------------------------------------------===// + +#include "swift/AST/DependencyCollector.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringRef.h" + +#ifndef SWIFT_AST_REQUEST_CACHE_H +#define SWIFT_AST_REQUEST_CACHE_H + +namespace swift { + +namespace evaluator { + +namespace detail { + +// Remove this when the compiler bumps to C++17. +template using void_t = void; +template> + +struct TupleHasDenseMapInfo {}; + +template +struct TupleHasDenseMapInfo< + std::tuple, + void_t::getEmptyKey)...>> { + using type = void_t<>; +}; + +} // end namespace detail + +namespace { + +/// Wrapper for a request with additional empty and tombstone states. +template> +class RequestKey { + friend struct llvm::DenseMapInfo; + union { + char Empty; + Request Req; + }; + + enum class StorageKind : uint8_t { + Normal, Empty, Tombstone + }; + StorageKind Kind; + + static RequestKey getEmpty() { + return RequestKey(StorageKind::Empty); + } + static RequestKey getTombstone() { + return RequestKey(StorageKind::Tombstone); + } + + RequestKey(StorageKind kind) : Empty(), Kind(kind) { + assert(kind != StorageKind::Normal); + } + +public: + explicit RequestKey(Request req) + : Req(std::move(req)), Kind(StorageKind::Normal) {} + + RequestKey(const RequestKey &other) : Empty(), Kind(other.Kind) { + if (Kind == StorageKind::Normal) + new (&Req) Request(other.Req); + } + RequestKey(RequestKey &&other) : Empty(), Kind(other.Kind) { + if (Kind == StorageKind::Normal) + new (&Req) Request(std::move(other.Req)); + } + RequestKey &operator=(const RequestKey &other) { + if (&other != this) { + this->~RequestKey(); + new (this) RequestKey(other); + } + return *this; + } + RequestKey &operator=(RequestKey &&other) { + if (&other != this) { + this->~RequestKey(); + new (this) RequestKey(std::move(other)); + } + return *this; + } + + ~RequestKey() { + if (Kind == StorageKind::Normal) + Req.~Request(); + } + + bool isStorageEqual(const Request &req) const { + if (Kind != StorageKind::Normal) + return false; + return Req == req; + } + friend bool operator==(const RequestKey &lhs, const RequestKey &rhs) { + if (lhs.Kind == StorageKind::Normal && rhs.Kind == StorageKind::Normal) { + return lhs.Req == rhs.Req; + } else { + return lhs.Kind == rhs.Kind; + } + } + friend bool operator!=(const RequestKey &lhs, const RequestKey &rhs) { + return !(lhs == rhs); + } + friend llvm::hash_code hash_value(const RequestKey &key) { + if (key.Kind != StorageKind::Normal) + return 1; + return hash_value(key.Req); + } +}; + +template +class RequestKey::type> { + friend struct llvm::DenseMapInfo; + using Info = llvm::DenseMapInfo; + + Request Req; + + static RequestKey getEmpty() { + return RequestKey(Request(Info::getEmptyKey())); + } + static RequestKey getTombstone() { + return RequestKey(Request(Info::getTombstoneKey())); + } + +public: + explicit RequestKey(Request req) : Req(std::move(req)) {} + + bool isStorageEqual(const Request &req) const { + return Req == req; + } + friend bool operator==(const RequestKey &lhs, const RequestKey &rhs) { + return lhs.Req == rhs.Req; + } + friend bool operator!=(const RequestKey &lhs, const RequestKey &rhs) { + return !(lhs == rhs); + } + friend llvm::hash_code hash_value(const RequestKey &key) { + return hash_value(key.Req); + } +}; + +} // end namespace + +/// Type-erased wrapper for caching results of a single type of request. +class PerRequestCache { + void *Storage; + std::function Deleter; + + PerRequestCache(void *storage, std::function deleter) + : Storage(storage), Deleter(deleter) {} + +public: + PerRequestCache() : Storage(nullptr), Deleter([](void *) {}) {} + PerRequestCache(PerRequestCache &&other) + : Storage(other.Storage), Deleter(std::move(other.Deleter)) { + other.Storage = nullptr; + } + + PerRequestCache &operator=(PerRequestCache &&other) { + if (&other != this) { + this->~PerRequestCache(); + new (this) PerRequestCache(std::move(other)); + } + return *this; + } + + PerRequestCache(const PerRequestCache &) = delete; + PerRequestCache &operator=(const PerRequestCache &) = delete; + + template + static PerRequestCache makeEmpty() { + using Map = + llvm::DenseMap, + typename Request::OutputType>; + return PerRequestCache(new Map(), + [](void *ptr) { delete static_cast(ptr); }); + } + + template + llvm::DenseMap, + typename Request::OutputType> * + get() const { + using Map = + llvm::DenseMap, + typename Request::OutputType>; + assert(Storage); + return static_cast(Storage); + } + + bool isNull() const { return !Storage; } + ~PerRequestCache() { + if (Storage) + Deleter(Storage); + } +}; + +/// Data structure for caching results of requests. Sharded by the type ID +/// zone and request kind, with a PerRequestCache for each request kind. +/// +/// Conceptually equivalent to DenseMap, but without +/// type erasure overhead for keys and values. +class RequestCache { + +#define SWIFT_TYPEID_ZONE(Name, Id) \ + std::vector Name##ZoneCache; \ + \ + template < \ + typename Request, typename ZoneTypes = TypeIDZoneTypes, \ + typename std::enable_if::zone == Zone::Name>::type * = \ + nullptr> \ + llvm::DenseMap, \ + typename Request::OutputType> * \ + getCache() { \ + auto &caches = Name##ZoneCache; \ + if (caches.empty()) { \ + caches.resize(ZoneTypes::Count); \ + } \ + auto idx = TypeID::localID; \ + if (caches[idx].isNull()) { \ + caches[idx] = PerRequestCache::makeEmpty(); \ + } \ + return caches[idx].template get(); \ + } +#include "swift/Basic/TypeIDZones.def" +#undef SWIFT_TYPEID_ZONE + +public: + template + typename llvm::DenseMap, + typename Request::OutputType>::const_iterator + find_as(const Request &req) { + auto *cache = getCache(); + return cache->find_as(req); + } + + template + typename llvm::DenseMap, + typename Request::OutputType>::const_iterator + end() { + auto *cache = getCache(); + return cache->end(); + } + + template + void insert(Request req, typename Request::OutputType val) { + auto *cache = getCache(); + auto result = cache->insert({RequestKey(std::move(req)), + std::move(val)}); + assert(result.second && "Request result was already cached"); + (void) result; + } + + template + void erase(Request req) { + auto *cache = getCache(); + cache->erase(RequestKey(std::move(req))); + } + + void clear() { +#define SWIFT_TYPEID_ZONE(Name, Id) Name##ZoneCache.clear(); +#include "swift/Basic/TypeIDZones.def" +#undef SWIFT_TYPEID_ZONE + } +}; + +/// Type-erased wrapper for caching dependencies from a single type of request. +class PerRequestReferences { + void *Storage; + std::function Deleter; + + PerRequestReferences(void *storage, std::function deleter) + : Storage(storage), Deleter(deleter) {} + +public: + PerRequestReferences() : Storage(nullptr), Deleter([](void *) {}) {} + PerRequestReferences(PerRequestReferences &&other) + : Storage(other.Storage), Deleter(std::move(other.Deleter)) { + other.Storage = nullptr; + } + + PerRequestReferences &operator=(PerRequestReferences &&other) { + if (&other != this) { + this->~PerRequestReferences(); + new (this) PerRequestReferences(std::move(other)); + } + return *this; + } + + PerRequestReferences(const PerRequestReferences &) = delete; + PerRequestReferences &operator=(const PerRequestReferences &) = delete; + + template + static PerRequestReferences makeEmpty() { + using Map = + llvm::DenseMap, + std::vector>; + return PerRequestReferences(new Map(), + [](void *ptr) { delete static_cast(ptr); }); + } + + template + llvm::DenseMap, + std::vector> * + get() const { + using Map = + llvm::DenseMap, + std::vector>; + assert(Storage); + return static_cast(Storage); + } + + bool isNull() const { return !Storage; } + ~PerRequestReferences() { + if (Storage) + Deleter(Storage); + } +}; + +/// Data structure for caching dependencies from requests. Sharded by the +/// type ID zone and request kind, with a PerRequestReferences for each +/// request kind. +/// +/// Conceptually equivalent to DenseMap>, but +/// without type erasure overhead for keys. +class RequestReferences { + +#define SWIFT_TYPEID_ZONE(Name, Id) \ + std::vector Name##ZoneRefs; \ + \ + template < \ + typename Request, typename ZoneTypes = TypeIDZoneTypes, \ + typename std::enable_if::zone == Zone::Name>::type * = \ + nullptr> \ + llvm::DenseMap, \ + std::vector> * \ + getRefs() { \ + auto &refs = Name##ZoneRefs; \ + if (refs.empty()) { \ + refs.resize(ZoneTypes::Count); \ + } \ + auto idx = TypeID::localID; \ + if (refs[idx].isNull()) { \ + refs[idx] = PerRequestReferences::makeEmpty(); \ + } \ + return refs[idx].template get(); \ + } +#include "swift/Basic/TypeIDZones.def" +#undef SWIFT_TYPEID_ZONE + +public: + template + typename llvm::DenseMap, + std::vector>::const_iterator + find_as(const Request &req) { + auto *refs = getRefs(); + return refs->find_as(req); + } + + template + typename llvm::DenseMap, + std::vector>::const_iterator + end() { + auto *refs = getRefs(); + return refs->end(); + } + + template + void insert(Request req, std::vector val) { + auto *refs = getRefs(); + refs->insert({RequestKey(std::move(req)), + std::move(val)}); + } + + template + void erase(Request req) { + auto *refs = getRefs(); + refs->erase(RequestKey(std::move(req))); + } + + void clear() { +#define SWIFT_TYPEID_ZONE(Name, Id) Name##ZoneRefs.clear(); +#include "swift/Basic/TypeIDZones.def" +#undef SWIFT_TYPEID_ZONE + } +}; + +} // end namespace evaluator + +} // end namespace swift + +namespace llvm { + +template +struct DenseMapInfo> { + using RequestKey = swift::evaluator::RequestKey; + static inline RequestKey getEmptyKey() { + return RequestKey::getEmpty(); + } + static inline RequestKey getTombstoneKey() { + return RequestKey::getTombstone(); + } + static unsigned getHashValue(const RequestKey &key) { + return hash_value(key); + } + static unsigned getHashValue(const Request &request) { + return hash_value(request); + } + static bool isEqual(const RequestKey &lhs, const RequestKey &rhs) { + return lhs == rhs; + } + static bool isEqual(const Request &lhs, const RequestKey &rhs) { + return rhs.isStorageEqual(lhs); + } +}; + +} // end namespace llvm + +#endif // SWIFT_AST_REQUEST_CACHE_H \ No newline at end of file diff --git a/include/swift/Basic/Compiler.h b/include/swift/Basic/Compiler.h index 5650ea9eaea4f..00a0b29b98c6b 100644 --- a/include/swift/Basic/Compiler.h +++ b/include/swift/Basic/Compiler.h @@ -103,4 +103,29 @@ #define SWIFT_CRASH_BUG_REPORT_MESSAGE \ "Please " SWIFT_BUG_REPORT_MESSAGE_BASE " and the crash backtrace." +// Conditionally exclude declarations or statements that are only needed for +// assertions from release builds (NDEBUG) without cluttering the surrounding +// code by #ifdefs. +// +// struct DoThings { +// SWIFT_ASSERT_ONLY_DECL(unsigned verifyCount = 0); +// DoThings() { +// SWIFT_ASSERT_ONLY(verifyCount = getNumberOfThingsToDo()); +// } +// void doThings() { +// do { +// // ... do each thing +// SWIFT_ASSERT_ONLY(--verifyCount); +// } while (!done()); +// assert(verifyCount == 0 && "did not do everything"); +// } +// }; +#ifdef NDEBUG +#define SWIFT_ASSERT_ONLY_DECL(X) +#define SWIFT_ASSERT_ONLY(X) do { } while (false) +#else +#define SWIFT_ASSERT_ONLY_DECL(X) X +#define SWIFT_ASSERT_ONLY(X) do { X; } while (false) +#endif + #endif // SWIFT_BASIC_COMPILER_H diff --git a/include/swift/Basic/DefineTypeIDZone.h b/include/swift/Basic/DefineTypeIDZone.h index 315a42a0aa9ed..ff11e36d9f9ea 100644 --- a/include/swift/Basic/DefineTypeIDZone.h +++ b/include/swift/Basic/DefineTypeIDZone.h @@ -45,14 +45,15 @@ template<> struct TypeIDZoneTypes { #undef SWIFT_TYPEID_NAMED #undef SWIFT_TYPEID_TEMPLATE1_NAMED #undef SWIFT_TYPEID_TEMPLATE2_NAMED + Count }; }; // Second pass: create specializations of TypeID for these types. #define SWIFT_TYPEID_NAMED(Type, Name) \ template<> struct TypeID { \ - static const uint8_t zoneID = \ - static_cast(Zone::SWIFT_TYPEID_ZONE); \ + static constexpr Zone zone = Zone::SWIFT_TYPEID_ZONE; \ + static const uint8_t zoneID = static_cast(zone); \ static const uint8_t localID = \ TypeIDZoneTypes::Name; \ \ diff --git a/include/swift/Basic/TypeID.h b/include/swift/Basic/TypeID.h index b7a4cc2bbc6ec..01b514e566661 100644 --- a/include/swift/Basic/TypeID.h +++ b/include/swift/Basic/TypeID.h @@ -31,22 +31,9 @@ namespace swift { enum class Zone : uint8_t { - C = 0, - AST = 1, - AccessControl = 11, - IDETypes = 136, - IDE = 137, - IDETypeChecking = 97, - NameLookup = 9, - Parse = 8, - TypeChecker = 10, - SILGen = 12, - SILOptimizer = 13, - TBDGen = 14, - IRGen = 20, - - // N.B. This is not a formal zone and exists solely to support the unit tests. - ArithmeticEvaluator = 255, +#define SWIFT_TYPEID_ZONE(Name, Id) Name = Id, +#include "swift/Basic/TypeIDZones.def" +#undef SWIFT_TYPEID_ZONE }; static_assert(std::is_same::type, uint8_t>::value, diff --git a/include/swift/Basic/TypeIDZones.def b/include/swift/Basic/TypeIDZones.def new file mode 100644 index 0000000000000..62282b6d681f6 --- /dev/null +++ b/include/swift/Basic/TypeIDZones.def @@ -0,0 +1,37 @@ +//===--- TypeIDZones.def - List of TypeID Zones -----------------*- C++ -*-===// + // + // 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 + // + //===----------------------------------------------------------------------===// + // + // This definition file describes the zones for TypeID. + // + //===----------------------------------------------------------------------===// + + SWIFT_TYPEID_ZONE(C, 0) + SWIFT_TYPEID_ZONE(AST, 1) + + SWIFT_TYPEID_ZONE(Parse, 8) + SWIFT_TYPEID_ZONE(NameLookup, 9) + + SWIFT_TYPEID_ZONE(TypeChecker, 10) + SWIFT_TYPEID_ZONE(AccessControl, 11) + SWIFT_TYPEID_ZONE(SILGen, 12) + SWIFT_TYPEID_ZONE(SILOptimizer, 13) + SWIFT_TYPEID_ZONE(TBDGen, 14) + + SWIFT_TYPEID_ZONE(IRGen, 20) + + SWIFT_TYPEID_ZONE(IDETypeChecking, 97) + + SWIFT_TYPEID_ZONE(IDETypes, 136) + SWIFT_TYPEID_ZONE(IDE, 137) + + // N.B. This is not a formal zone and exists solely to support the unit tests. + SWIFT_TYPEID_ZONE(ArithmeticEvaluator, 255) diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index b7d47642a3d90..186bc1cd1039f 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -123,7 +123,9 @@ PASS(ConstantEvaluableSubsetChecker, "test-constant-evaluable-subset", PASS(CopyForwarding, "copy-forwarding", "Copy Forwarding to Remove Redundant Copies") PASS(CopyPropagation, "copy-propagation", - "Copy propagation to Remove Redundant SSA Copies") + "Copy propagation to Remove Redundant SSA Copies, pruning debug info") +PASS(MandatoryCopyPropagation, "mandatory-copy-propagation", + "Copy propagation to Remove Redundant SSA Copies, preserving debug info") PASS(COWOpts, "cow-opts", "Optimize COW operations") PASS(Differentiation, "differentiation", diff --git a/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h b/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h new file mode 100644 index 0000000000000..7748d6597fa30 --- /dev/null +++ b/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h @@ -0,0 +1,293 @@ +//===--- CanonicalOSSALifetime.h - Canonicalize OSSA lifetimes --*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// Canonicalize the copies and destroys of a single owned or guaranteed OSSA +/// value. +/// +/// This top-level API rewrites the extended OSSA lifetime of a SILValue: +/// +/// void canonicalizeValueLifetime(SILValue def, CanonicalOSSALifetime &) +/// +/// The extended lifetime transitively includes the uses of `def` itself along +/// with the uses of any copies of `def`. Canonicalization provably minimizes +/// the OSSA lifetime and its copies by rewriting all copies and destroys. Only +/// consusming uses that are not on the liveness boundary require a copy. +/// +/// Example #1: Handle consuming and nonconsuming uses. +/// +/// bb0(%arg : @owned $T, %addr : @trivial $*T): +/// %copy = copy_value %arg : $T +/// debug_value %copy : $T +/// store %copy to [init] %addr : $*T +/// debug_value %arg : $T +/// debug_value_addr %addr : $*T +/// destroy_value %arg : $T +/// +/// Will be transformed to: +/// +/// bb0(%arg : @owned $T, %addr : @trivial $*T): +/// // The original copy is deleted. +/// debug_value %arg : $T +/// // A new copy_value is inserted before the consuming store. +/// %copy = copy_value %arg : $T +/// store %copy to [init] %addr : $*T +/// // The non-consuming use now uses the original value. +/// debug_value %arg : $T +/// // A new destroy is inserted after the last use. +/// destroy_value %arg : $T +/// debug_value_addr %addr : $*T +/// // The original destroy is deleted. +/// +/// Example #2: Handle control flow. +/// +/// bb0(%arg : @owned $T): +/// cond_br %_, bb1, bb2 +/// bb1: +/// br bb3 +/// bb2: +/// debug_value %arg : $T +/// %copy = copy_value %arg : $T +/// destroy_value %copy : $T +/// br bb3 +/// bb3: +/// destroy_value %arg : $T +/// +/// Will be transformed to: +/// +/// bb0(%arg : @owned $T): +/// cond_br %_, bb1, bb2 +/// bb1: +/// destroy_value %arg : $T +/// br bb3 +/// bb2: +/// // The original copy is deleted. +/// debug_value %arg : $T +/// destroy_value %arg : $T +/// br bb3 +/// bb2: +/// // The original destroy is deleted. +/// +/// FIXME: Canonicalization currently bails out if any uses of the def has +/// OperandOwnership::PointerEscape. Once project_box is protected by a borrow +/// scope and mark_dependence is associated with an end_dependence, +/// canonicalization will work everywhere as intended. The intention is to keep +/// the canonicalization algorithm as simple and robust, leaving the remaining +/// performance opportunities contingent on fixing the SIL representation. +/// +/// FIXME: Canonicalization currently fails to eliminate copies downstream of a +/// ForwardingBorrow. Aggregates should be fixed to be Reborrow instead of +/// ForwardingBorrow, then computeCanonicalLiveness() can be fixed to extend +/// liveness through ForwardingBorrows. +/// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SILOPTIMIZER_UTILS_CANONICALOSSALIFETIME_H +#define SWIFT_SILOPTIMIZER_UTILS_CANONICALOSSALIFETIME_H + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SetVector.h" +#include "swift/SIL/SILInstruction.h" +#include "swift/SILOptimizer/Utils/PrunedLiveness.h" + +namespace swift { + +/// Information about consumes on the extended-lifetime boundary. Consuming uses +/// within the lifetime are not included--they will consume a copy after +/// rewriting. For borrowed def values, the consumes do not include the end of +/// the borrow scope, rather the consumes transitively include consumes of any +/// owned copies of the borrowed value. +/// +/// This result remains valid during copy rewriting. The only instructions +/// referenced it contains are consumes that cannot be deleted. +class CanonicalOSSAConsumeInfo { + /// Map blocks on the lifetime boundary to the last consuming instruction. + llvm::SmallDenseMap finalBlockConsumes; + + /// Record any debug_value instructions found after a final consume. + SmallVector debugAfterConsume; + + /// For borrowed defs, track per-block copies of the borrowed value that only + /// have uses outside the borrow scope and will not be removed by + /// canonicalization. These copies are effectively distinct OSSA lifetimes + /// that should be canonicalized separately. + llvm::SmallDenseMap persistentCopies; + +public: + bool hasUnclaimedConsumes() const { return !finalBlockConsumes.empty(); } + + void clear() { + finalBlockConsumes.clear(); + debugAfterConsume.clear(); + persistentCopies.clear(); + } + + bool hasFinalConsumes() const { return !finalBlockConsumes.empty(); } + + void recordFinalConsume(SILInstruction *inst) { + assert(!finalBlockConsumes.count(inst->getParent())); + finalBlockConsumes[inst->getParent()] = inst; + } + + // Return true if this instruction is marked as a final consume point of the + // current def's live range. A consuming instruction can only be claimed once + // because instructions like `tuple` can consume the same value via multiple + // operands. + bool claimConsume(SILInstruction *inst) { + auto destroyPos = finalBlockConsumes.find(inst->getParent()); + if (destroyPos != finalBlockConsumes.end() && destroyPos->second == inst) { + finalBlockConsumes.erase(destroyPos); + return true; + } + return false; + } + + /// Record a debug_value that is known to be outside pruned liveness. Assumes + /// that instructions are only visited once. + void recordDebugAfterConsume(DebugValueInst *dvi) { + debugAfterConsume.push_back(dvi); + } + + ArrayRef getDebugInstsAfterConsume() const { + return debugAfterConsume; + } + + bool hasPersistentCopies() const { return !persistentCopies.empty(); } + + bool isPersistentCopy(CopyValueInst *copy) const { + auto iter = persistentCopies.find(copy->getParent()); + if (iter == persistentCopies.end()) { + return false; + } + return iter->second == copy; + } + + SWIFT_ASSERT_ONLY_DECL(void dump() const LLVM_ATTRIBUTE_USED); +}; + +/// Canonicalize OSSA lifetimes. +/// +/// Allows the allocation of analysis state to be reused across calls to +/// canonicalizeValueLifetime(). +class CanonicalizeOSSALifetime { +public: + /// Find the original definition of a potentially copied value. + static SILValue getCanonicalCopiedDef(SILValue v); + +private: + /// If true, then debug_value instructions outside of non-debug + /// liveness may be pruned during canonicalization. + bool pruneDebug; + + /// Current copied def for which this state describes the liveness. + SILValue currentDef; + + /// If an outer copy is created for uses outside the borrow scope + CopyValueInst *outerCopy = nullptr; + + /// Cumulatively, have any instructions been modified by canonicalization? + bool changed = false; + + /// Original points in the CFG where the current value's lifetime is consumed + /// or destroyed. For guaranteed values it remains empty. A backward walk from + /// these blocks must discover all uses on paths that lead to a return or + /// throw. + /// + /// These blocks are not necessarily in the pruned live blocks since + /// pruned liveness does not consider destroy_values. + SmallSetVector consumingBlocks; + + /// Record all interesting debug_value instructions here rather then treating + /// them like a normal use. An interesting debug_value is one that may lie + /// outisde the pruned liveness at the time it is discovered. + llvm::SmallDenseSet debugValues; + + /// Reuse a general worklist for def-use traversal. + SmallSetVector defUseWorklist; + + /// Reuse a general worklist for CFG traversal. + SmallSetVector blockWorklist; + + /// Pruned liveness for the extended live range including copies. For this + /// purpose, only consuming instructions are considered "lifetime + /// ending". end_borrows do not end a liverange that may include owned copies. + PrunedLiveness liveness; + + /// Information about consuming instructions discovered in this caonical OSSA + /// lifetime. + CanonicalOSSAConsumeInfo consumes; + +public: + CanonicalizeOSSALifetime(bool pruneDebug) : pruneDebug(pruneDebug) {} + + SILValue getCurrentDef() const { return currentDef; } + + void initDef(SILValue def) { + assert(consumingBlocks.empty() && debugValues.empty() && liveness.empty()); + consumes.clear(); + + currentDef = def; + outerCopy = nullptr; + liveness.initializeDefBlock(def->getParentBlock()); + } + + void clearLiveness() { + consumingBlocks.clear(); + debugValues.clear(); + liveness.clear(); + } + + bool hasChanged() const { return changed; } + + void setChanged() { changed = true; } + + SILValue createdOuterCopy() const { return outerCopy; } + + /// Top-Level API: rewrites copies and destroys within \p def's extended + /// lifetime. \p lifetime caches transient analysis state across multiple + /// calls. + /// + /// Return false if the OSSA structure cannot be recognized (with a proper + /// OSSA representation this will always return true). + /// + /// Upon returning, isChanged() indicates, cumulatively, whether any SIL + /// changes were made. + /// + /// Upon returning, createdOuterCopy() indicates whether a new copy was + /// created for uses outside the borrow scope. To canonicalize the new outer + /// lifetime, call this API again on the value defined by the new copy. + bool canonicalizeValueLifetime(SILValue def); + +protected: + void recordDebugValue(DebugValueInst *dvi) { + debugValues.insert(dvi); + } + + void recordConsumingUse(Operand *use) { + consumingBlocks.insert(use->getUser()->getParent()); + } + + bool computeBorrowLiveness(); + + void consolidateBorrowScope(); + + bool computeCanonicalLiveness(); + + void findOrInsertDestroyInBlock(SILBasicBlock *bb); + + void findOrInsertDestroys(); + + void rewriteCopies(); +}; + +} // end namespace swift + +#endif diff --git a/include/swift/SILOptimizer/Utils/PrunedLiveness.h b/include/swift/SILOptimizer/Utils/PrunedLiveness.h index 1dee95e0a9c95..7d0bb62ed459d 100644 --- a/include/swift/SILOptimizer/Utils/PrunedLiveness.h +++ b/include/swift/SILOptimizer/Utils/PrunedLiveness.h @@ -90,14 +90,6 @@ #include "swift/SIL/SILBasicBlock.h" -#ifdef NDEBUG -#define SWIFT_ASSERT_ONLY_MEMBER(X) -#define SWIFT_ASSERT_ONLY(X) do { } while (false) -#else -#define SWIFT_ASSERT_ONLY_MEMBER(X) X -#define SWIFT_ASSERT_ONLY(X) do { X; } while (false) -#endif - namespace swift { /// Discover "pruned" liveness for an arbitrary set of uses. The client builds @@ -140,13 +132,15 @@ class PrunedLiveBlocks { llvm::SmallDenseMap liveBlocks; // Once the first use has been seen, no definitions can be added. - SWIFT_ASSERT_ONLY_MEMBER(bool seenUse = false); + SWIFT_ASSERT_ONLY_DECL(bool seenUse = false); public: bool empty() const { return liveBlocks.empty(); } void clear() { liveBlocks.clear(); SWIFT_ASSERT_ONLY(seenUse = false); } + unsigned numLiveBlocks() const { return liveBlocks.size(); } + void initializeDefBlock(SILBasicBlock *defBB) { assert(!seenUse && "cannot initialize more defs with partial liveness"); markBlockLive(defBB, LiveWithin); @@ -212,10 +206,17 @@ class PrunedLiveness { users.clear(); } + unsigned numLiveBlocks() const { return liveBlocks.numLiveBlocks(); } + void initializeDefBlock(SILBasicBlock *defBB) { liveBlocks.initializeDefBlock(defBB); } + /// For flexibility, \p lifetimeEnding is provided by the + /// caller. PrunedLiveness makes no assumptions about the def-use + /// relationships that generate liveness. For example, use->isLifetimeEnding() + /// cannot distinguish the end of the borrow scope that defines this extended + /// live range vs. a nested borrow scope within the extended live range. void updateForUse(Operand *use, bool lifetimeEnding); PrunedLiveBlocks::IsLive getBlockLiveness(SILBasicBlock *bb) const { diff --git a/lib/AST/Evaluator.cpp b/lib/AST/Evaluator.cpp index 8886725aafc27..a4a78110c5f14 100644 --- a/lib/AST/Evaluator.cpp +++ b/lib/AST/Evaluator.cpp @@ -148,13 +148,6 @@ void Evaluator::printDependencies( out.resetColor(); } - // Print the cached value, if known. - auto cachedValue = cache.find(request); - if (cachedValue != cache.end()) { - out << " -> "; - printEscapedString(cachedValue->second.getAsString(), out); - } - if (!visitedAnywhere.insert(request).second) { // We've already seed this node. Check whether it's part of a cycle. if (std::find(visitedAlongPath.begin(), visitedAlongPath.end(), request) @@ -313,11 +306,6 @@ void Evaluator::printDependenciesGraphviz(llvm::raw_ostream &out) const { out << " [label=\""; printEscapedString(request.getAsString(), out); - auto cachedValue = cache.find(request); - if (cachedValue != cache.end()) { - out << " -> "; - printEscapedString(cachedValue->second.getAsString(), out); - } out << "\""; if (auto color = getColor(request)) { @@ -370,68 +358,6 @@ void Evaluator::dumpDependenciesGraphviz() const { printDependenciesGraphviz(llvm::dbgs()); } -void evaluator::DependencyRecorder::beginRequest( - const swift::ActiveRequest &req) { - if (!req.isCached() && !req.isDependencySource()) - return; - - activeRequestReferences.push_back({}); -} - -void evaluator::DependencyRecorder::endRequest( - const swift::ActiveRequest &req) { - if (!req.isCached() && !req.isDependencySource()) - return; - - // Grab all the dependencies we've recorded so far, and pop - // the stack. - auto recorded = std::move(activeRequestReferences.back()); - activeRequestReferences.pop_back(); - - // If we didn't record anything, there is nothing to do. - if (recorded.empty()) - return; - - // Convert the set of dependencies into a vector. - std::vector - vec(recorded.begin(), recorded.end()); - - // The recorded dependencies bubble up to the parent request. - if (!activeRequestReferences.empty()) { - activeRequestReferences.back().insert(vec.begin(), - vec.end()); - } - - // Finally, record the dependencies so we can replay them - // later when the request is re-evaluated. - requestReferences.insert({AnyRequest(req), vec}); -} - -void evaluator::DependencyRecorder::replayCachedRequest( - const swift::ActiveRequest &req) { - assert(req.isCached()); - - if (activeRequestReferences.empty()) - return; - - auto found = requestReferences.find_as(req); - if (found == requestReferences.end()) - return; - - activeRequestReferences.back().insert(found->second.begin(), - found->second.end()); -} - -void evaluator::DependencyRecorder::handleDependencySourceRequest( - const swift::ActiveRequest &req, - SourceFile *source) { - auto found = requestReferences.find_as(req); - if (found != requestReferences.end()) { - fileReferences[source].insert(found->second.begin(), - found->second.end()); - } -} - void evaluator::DependencyRecorder::recordDependency( const DependencyCollector::Reference &ref) { if (activeRequestReferences.empty()) diff --git a/lib/Parse/ParseIfConfig.cpp b/lib/Parse/ParseIfConfig.cpp index f0b97006cd8c1..9be9ae9782ac6 100644 --- a/lib/Parse/ParseIfConfig.cpp +++ b/lib/Parse/ParseIfConfig.cpp @@ -102,7 +102,7 @@ class ValidateIfConfigCondition : return nullptr; } - // Support '||' and '&&' operator. The procedence of '&&' is higher than '||'. + // Support '||' and '&&' operator. The precedence of '&&' is higher than '||'. // Invalid operator and the next operand are diagnosed and removed from AST. Expr *foldSequence(Expr *LHS, ArrayRef &S, bool isRecurse = false) { assert(!S.empty() && ((S.size() & 1) == 0)); diff --git a/lib/SIL/IR/OperandOwnership.cpp b/lib/SIL/IR/OperandOwnership.cpp index cec3757bcfc48..e59acc3eaa577 100644 --- a/lib/SIL/IR/OperandOwnership.cpp +++ b/lib/SIL/IR/OperandOwnership.cpp @@ -314,8 +314,7 @@ FORWARDING_OWNERSHIP(LinearFunction) #undef FORWARDING_OWNERSHIP // Arbitrary value casts are forwarding instructions that are also allowed to -// propagate Unowned values. If the result is Unowned, then the operand must -// also be Unowned. +// propagate Unowned values. #define FORWARDING_ANY_OWNERSHIP(INST) \ OperandOwnership OperandOwnershipClassifier::visit##INST##Inst( \ INST##Inst *i) { \ @@ -414,7 +413,7 @@ static OperandOwnership getFunctionArgOwnership(SILArgumentConvention argConv, // owned values may be passed to guaranteed arguments without an explicit // borrow scope in the caller. In contrast, a begin_apply /does/ have an // explicit borrow scope in the caller so we must treat arguments passed to it - // as being borrowed for the entire borrow scope. + // as being borrowed for the entire region of coroutine execution. case SILArgumentConvention::Indirect_In_Constant: case SILArgumentConvention::Indirect_In_Guaranteed: case SILArgumentConvention::Direct_Guaranteed: @@ -780,10 +779,8 @@ OperandOwnership OperandOwnershipClassifier::visitBuiltinInst(BuiltinInst *bi) { //===----------------------------------------------------------------------===// OperandOwnership Operand::getOperandOwnership() const { - // NOTE: NonUse distinguishes itself from InstantaneousUse because it does not - // require liveness. Discrimating such uses in the enum avoids the need to - // return an Optional::None, which could be confused with - // OwnershipKind::None. + // A type-dependent operant is a NonUse (as opposed to say an + // InstantaneousUse) because it does not require liveness. if (isTypeDependent()) return OperandOwnership::NonUse; diff --git a/lib/SILOptimizer/Mandatory/DataflowDiagnostics.cpp b/lib/SILOptimizer/Mandatory/DataflowDiagnostics.cpp index 1482a9c264e08..56ebbf813c089 100644 --- a/lib/SILOptimizer/Mandatory/DataflowDiagnostics.cpp +++ b/lib/SILOptimizer/Mandatory/DataflowDiagnostics.cpp @@ -63,7 +63,7 @@ static void diagnoseMissingReturn(const UnreachableInst *UI, if (!BS->empty()) { auto element = BS->getLastElement(); if (auto expr = element.dyn_cast()) { - if (expr->getType()->isEqual(ResTy)) { + if (expr->getType()->getRValueType()->isEqual(ResTy)) { Context.Diags.diagnose( expr->getStartLoc(), diag::missing_return_last_expr, ResTy, diff --git a/lib/SILOptimizer/Transforms/ArrayCountPropagation.cpp b/lib/SILOptimizer/Transforms/ArrayCountPropagation.cpp index 846037d58f657..4e17667d3fa1d 100644 --- a/lib/SILOptimizer/Transforms/ArrayCountPropagation.cpp +++ b/lib/SILOptimizer/Transforms/ArrayCountPropagation.cpp @@ -123,7 +123,7 @@ bool ArrayAllocation::recursivelyCollectUses(ValueBase *Def) { for (auto *Opd : Def->getUses()) { auto *User = Opd->getUser(); // Ignore reference counting and debug instructions. - if (isa(User) || + if (isa(User) || isa(User) || isa(User)) continue; @@ -195,11 +195,6 @@ class ArrayCountPropagation : public SILFunctionTransform { void run() override { auto &Fn = *getFunction(); - - // FIXME: Add ownership support. - if (Fn.hasOwnership()) - return; - bool Changed = false; SmallVector DeadArrayCountCalls; // Propagate the count of array allocations to array.count users. diff --git a/lib/SILOptimizer/Transforms/CopyPropagation.cpp b/lib/SILOptimizer/Transforms/CopyPropagation.cpp index f1ace6307d8dd..5ac4cb4cd7b49 100644 --- a/lib/SILOptimizer/Transforms/CopyPropagation.cpp +++ b/lib/SILOptimizer/Transforms/CopyPropagation.cpp @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// 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 @@ -13,667 +13,37 @@ /// SSA Copy propagation pass to remove unnecessary copy_value and destroy_value /// instructions. /// -/// Because this algorithm rewrites copies and destroys without attempting -/// to balance the retain count, it is only sound for ownership-SSA. +/// Because this algorithm rewrites copies and destroys without attempting to +/// balance the retain count, it is only sound when SIL is in ownership-SSA +/// form. The pass itself is mostly for testing the underlying functionality +/// which can also be invoked as a utility for any owned value. /// -/// [WIP] This complements -enable-sil-opaque-values. It is not yet enabled by -/// default. It can be enabled once the ownership properties, currently -/// specified as helpers at the top of this file, are formalized via a central -/// API. +/// TODO: Cleanup the resulting SIL by deleting instructions that produce dead +/// values (after removing its copies). /// -/// The pass has four steps: -/// -/// 1. Find SSA defs that have been copied. -/// -/// Then, separately, for each copied def, perform steps 2-4: -/// -/// 2. Compute "pruned" liveness of the def and its copies, ignoring original -/// destroys. -/// -/// 3. Find the SSA def's final destroy points based on its pruned liveness. -/// -/// 4. Rewrite the def's original copies and destroys, inserting new copies -/// where needed. -/// -/// The state used by this pass is encapsulated in CopyPropagationState, simply -/// refered to as `pass`: -/// -/// - Step 1 initializes `pass.currDef`. -/// -/// - Step 2 initializes `pass.liveness`. -/// -/// - Step 3 initializes `pass.destroys` and inserts destroy_value instructions. -/// -/// - Step 4 deletes original copies and destroys and inserts new copies. -/// -/// Example #1: Handle consuming and nonconsuming uses. -/// -/// bb0(%arg : @owned $T, %addr : @trivial $*T): -/// %copy = copy_value %arg : $T -/// debug_value %copy : $T -/// store %copy to [init] %addr : $*T -/// debug_value %arg : $T -/// debug_value_addr %addr : $*T -/// destroy_value %arg : $T -/// -/// Will be transformed to: -/// -/// bb0(%arg : @owned $T, %addr : @trivial $*T): -/// (The original copy is deleted.) -/// debug_value %arg : $T -/// (A new copy_value is inserted before the consuming store.) -/// %copy = copy_value %arg : $T -/// store %copy to [init] %addr : $*T -/// (The non-consuming use now uses the original value.) -/// debug_value %arg : $T -/// (A new destroy is inserted after the last use.) -/// destroy_value %arg : $T -/// debug_value_addr %addr : $*T -/// (The original destroy is deleted.) -/// -/// Example #2: Handle control flow. -/// -/// bb0(%arg : @owned $T): -/// cond_br %_, bb1, bxb2 -/// -/// bb1: -/// debug_value %arg : $T -/// %copy = copy_value %arg : $T -/// destroy_value %copy : $T -/// br bb2 -/// -/// bb2: -/// destroy_value %arg : $T -/// -/// Will be transformed to: -/// -/// bb0(%arg : @owned $T, %addr : @trivial $*T, %z : @trivial $Builtin.Int1): -/// cond_br %z, bb1, bb3 -/// -/// bb1: -/// (The critical edge is split.) -/// destroy_value %arg : $T -/// br bb2 -/// -/// bb3: -/// (The original copy is deleted.) -/// debug_value %arg : $T -/// destroy_value %arg : $T -/// br bb2 -/// -/// bb2: -/// (The original destroy is deleted.) -/// -/// FIXME: When we ban critical edges in SIL, this pass does not need to -/// invalidate any CFG analyses. -/// -/// FIXME: SILGen currently emits spurious borrow scopes which block -/// elimination of copies within a scope. -/// -/// TODO: Possibly extend this algorithm to better handle aggregates. If -/// ownership SIL consistently uses a `destructure` operation to split -/// aggregates, then the current scalar algorithm should work -/// naturally. However, if we instead need to handle borrow scopes, then this -/// becomes much harder. We would need a DI-style tracker to track the uses of a -/// particular copied def. The single boolean "live" state would be replaced -/// with a bitvector that tracks each tuple element. -/// -/// TODO: Cleanup the resulting SIL. Delete instructions with no side effects -/// that produce values which are immediately destroyed after copy propagation. /// ===----------------------------------------------------------------------=== #define DEBUG_TYPE "copy-propagation" -#include "swift/Basic/IndexTrie.h" -#include "swift/SIL/InstructionUtils.h" -#include "swift/SIL/Projection.h" + +#include "swift/SIL/BasicBlockUtils.h" #include "swift/SILOptimizer/PassManager/Passes.h" #include "swift/SILOptimizer/PassManager/Transforms.h" -#include "swift/SILOptimizer/Utils/CFGOptUtils.h" -#include "swift/SILOptimizer/Utils/InstOptUtils.h" -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/Statistic.h" +#include "swift/SILOptimizer/Utils/CanonicalOSSALifetime.h" using namespace swift; -using llvm::DenseMap; -using llvm::DenseSet; -using llvm::SmallSetVector; - -STATISTIC(NumCopiesEliminated, "number of copy_value instructions removed"); -STATISTIC(NumDestroysEliminated, - "number of destroy_value instructions removed"); -STATISTIC(NumCopiesGenerated, "number of copy_value instructions created"); -STATISTIC(NumDestroysGenerated, "number of destroy_value instructions created"); -STATISTIC(NumUnknownUsers, "number of functions with unknown users"); //===----------------------------------------------------------------------===// -// CopyPropagationState: shared state for the pass's analysis and transforms. +// CopyPropagation: Top-Level Function Transform. //===----------------------------------------------------------------------===// namespace { - -/// LiveWithin blocks have at least one use and/or def within the block, but are -/// not LiveOut. -/// -/// LiveOut blocks are live on at least one successor path. -/// -/// Dead blocks are either outside of the def's pruned liveness region, or they -/// have not yet been discovered by the liveness computation. -enum IsLive_t { LiveWithin, LiveOut, Dead }; - -/// Liveness information, which is produced by step #2, computeLiveness, and -/// consumed by step #3, findOrInsertDestroys. -class LivenessInfo { - // Map of all blocks in which current def is live. True if it is also liveout. - DenseMap liveBlocks; - // Set of all "interesting" users in this def's live range and whether their - // used value is consumed. (Non-consuming uses within a block that is already - // known to be live are uninteresting.) - DenseMap users; - - // Original points in the CFG where the current value was consumed or - // destroyed. - using BlockSetVec = SmallSetVector; - BlockSetVec originalDestroyBlocks; - -public: - bool empty() { - assert(!liveBlocks.empty() || users.empty()); - return liveBlocks.empty(); - } - - void clear() { - liveBlocks.clear(); - users.clear(); - originalDestroyBlocks.clear(); - } - - IsLive_t isBlockLive(SILBasicBlock *bb) const { - auto liveBlockIter = liveBlocks.find(bb); - if (liveBlockIter == liveBlocks.end()) - return Dead; - return liveBlockIter->second ? LiveOut : LiveWithin; - } - - void markBlockLive(SILBasicBlock *bb, IsLive_t isLive) { - assert(isLive != Dead && "erasing live blocks isn't implemented."); - liveBlocks[bb] = (isLive == LiveOut); - } - - // Return a valid result if the given user was identified as an interesting - // use of the current def. Returns true if the interesting use consumes the - // current def. - Optional isConsumingUser(SILInstruction *user) const { - auto useIter = users.find(user); - if (useIter == users.end()) - return None; - return useIter->second; - } - - // Record the user of this operand in the set of interesting users. - // - // Note that a user may use the current value from multiple operands. If any - // of the uses are non-consuming, then we must consider the use itself - // non-consuming; it cannot be a final destroy point because the value of the - // non-consuming operand must be kept alive until the end of the - // user. Consider a call that takes the same value using different - // conventions: - // - // apply %f(%val, %val) : $(@guaranteed, @owned) -> () - // - // This call cannot be allowed to destroy %val. - void recordUser(Operand *use) { - bool consume = use->isLifetimeEnding(); - auto iterAndSuccess = users.try_emplace(use->getUser(), consume); - if (!iterAndSuccess.second) - iterAndSuccess.first->second &= consume; - } - - void recordOriginalDestroy(Operand *use) { - originalDestroyBlocks.insert(use->getUser()->getParent()); - } - - iterator_range - getOriginalDestroyBlocks() const { - return originalDestroyBlocks; - } -}; - -/// Information about final destroy points, which is produced by step #3, -/// findOrInsertDestroys, and consumed by step #4, rewriteCopies. -/// -/// This remains valid during copy rewriting. The only instructions referenced -/// are destroys that cannot be deleted. -/// -/// For non-owned values, this remains empty. -class DestroyInfo { - // Map blocks that contain a final destroy to the destroying instruction. - DenseMap finalBlockDestroys; +class CopyPropagation : public SILFunctionTransform { + /// True if debug_value instructions should be pruned. + bool pruneDebug; public: - bool empty() const { return finalBlockDestroys.empty(); } - - void clear() { finalBlockDestroys.clear(); } - - bool hasFinalDestroy(SILBasicBlock *bb) const { - return finalBlockDestroys.count(bb); - } - - // Return true if this instruction is marked as a final destroy point of the - // current def's live range. A destroy can only be claimed once because - // instructions like `tuple` can consume the same value via multiple operands. - bool claimDestroy(SILInstruction *inst) { - auto destroyPos = finalBlockDestroys.find(inst->getParent()); - if (destroyPos != finalBlockDestroys.end() && destroyPos->second == inst) { - finalBlockDestroys.erase(destroyPos); - return true; - } - return false; - } - - void recordFinalDestroy(SILInstruction *inst) { - finalBlockDestroys[inst->getParent()] = inst; - } - - void invalidateFinalDestroy(SILInstruction *inst) { - finalBlockDestroys[inst->getParent()] = inst; - } -}; - -/// This pass' shared state. -struct CopyPropagationState { - SILFunction *func; - - // Per-function invalidation state. - unsigned invalidation; - - // Current copied def for which this state describes the liveness. - SILValue currDef; - - // computeLiveness result. - LivenessInfo liveness; - - // findOrInsertDestroys result. - DestroyInfo destroys; - - CopyPropagationState(SILFunction *F) - : func(F), invalidation(SILAnalysis::InvalidationKind::Nothing) {} - - bool isValueOwned() const { - return currDef.getOwnershipKind() == OwnershipKind::Owned; - } - - void markInvalid(SILAnalysis::InvalidationKind kind) { - invalidation |= (unsigned)kind; - } - - void resetDef(SILValue def) { - // Do not clear invalidation. It accumulates for an entire function before - // the PassManager is notified. - liveness.clear(); - destroys.clear(); - currDef = def; - } -}; -} // namespace - -//===----------------------------------------------------------------------===// -// Step 2. Discover "pruned" liveness for a copied def, ignoring copies and -// destroys. -// -// Generate pass.liveness. -// - marks blocks as LiveOut or LiveWithin. -// - records users that may become the last user on that path. -// - records blocks in which currDef may be destroyed. -// -// TODO: Make sure all dependencies are accounted for (mark_dependence, -// ref_element_addr, project_box?). We should have an ownership API so that the -// pass doesn't require any special knowledge of value dependencies. -//===----------------------------------------------------------------------===// - -/// Mark blocks live during a reverse CFG traversal from one specific block -/// containing a user. -static void computeUseBlockLiveness(SILBasicBlock *userBB, - CopyPropagationState &pass) { - - // If we are visiting this block, then it is not already LiveOut. Mark it - // LiveWithin to indicate a liveness boundary within the block. - pass.liveness.markBlockLive(userBB, LiveWithin); + CopyPropagation(bool pruneDebug): pruneDebug(pruneDebug) {} - SmallVector predBBWorklist({userBB}); - while (!predBBWorklist.empty()) { - SILBasicBlock *bb = predBBWorklist.pop_back_val(); - // The popped `bb` is live; mark all its predecessors LiveOut. - // pass.currDef was already marked LiveWithin, which terminates traversal. - for (auto *predBB : bb->getPredecessorBlocks()) { - switch (pass.liveness.isBlockLive(predBB)) { - case Dead: - predBBWorklist.push_back(predBB); - LLVM_FALLTHROUGH; - case LiveWithin: - pass.liveness.markBlockLive(predBB, LiveOut); - break; - case LiveOut: - break; - } - } - } -} - -/// Update the current def's liveness based on one specific use operand. -/// -/// Terminators consume their owned operands, so they are not live out of the -/// block. -static void computeUseLiveness(Operand *use, CopyPropagationState &pass) { - auto *bb = use->getUser()->getParent(); - auto isLive = pass.liveness.isBlockLive(bb); - switch (isLive) { - case LiveOut: - // Ignore uses within the pruned liveness. - return; - case LiveWithin: - // Record all uses of blocks on the liveness boundary. Whoever uses this - // liveness result determines the instruction-level liveness boundary to be - // the last use in the block. - pass.liveness.recordUser(use); - break; - case Dead: { - // This use block has not yet been marked live. Mark it and its predecessor - // blocks live. - computeUseBlockLiveness(bb, pass); - // Record all uses of blocks on the liveness boundary. This cannot be - // determined until after the `computeUseBlockLiveness` CFG traversal - // because a loop may result in this block being LiveOut. - if (pass.liveness.isBlockLive(bb) == LiveWithin) - pass.liveness.recordUser(use); - break; - } - } -} - -/// Generate pass.liveness. -/// Return true if successful. -/// -/// This finds the "pruned" liveness by recursing through copies and ignoring -/// the original destroys. -/// -/// Assumption: No users occur before 'def' in def's BB because this follows the -/// SSA def-use chains without "looking through" any terminators. -static bool computeLiveness(CopyPropagationState &pass) { - assert(pass.liveness.empty()); - - SILBasicBlock *defBB = pass.currDef->getParentBlock(); - - pass.liveness.markBlockLive(defBB, LiveWithin); - - SmallSetVector defUseWorkList; - defUseWorkList.insert(pass.currDef); - - while (!defUseWorkList.empty()) { - SILValue value = defUseWorkList.pop_back_val(); - for (Operand *use : value->getUses()) { - auto *user = use->getUser(); - - // Recurse through copies. - if (auto *copy = dyn_cast(user)) { - defUseWorkList.insert(copy); - continue; - } - - // An entire borrow scope is considered a single use that occurs at the - // point of the end_borrow. - if (auto *bbi = dyn_cast(user)) { - for (Operand *use : bbi->getUses()) { - if (isa(use->getUser())) - computeUseLiveness(use, pass); - } - continue; - } - - if (use->isLifetimeEnding()) { - pass.liveness.recordOriginalDestroy(use); - // Destroying a values does not force liveness. - if (isa(user)) - continue; - } - computeUseLiveness(use, pass); - } - } - return true; -} - -//===----------------------------------------------------------------------===// -// Step 3. Find the destroy points of the current def based on the pruned -// liveness computed in Step 2. -//===----------------------------------------------------------------------===// - -/// The liveness boundary is at a CFG edge `predBB` -> `succBB`, meaning that -/// `pass.currDef` is live out of at least one other `predBB` successor. -/// -/// Create and record a final destroy_value at the beginning of `succBB` -/// (assuming no critical edges). -static void insertDestroyOnCFGEdge(SILBasicBlock *predBB, SILBasicBlock *succBB, - CopyPropagationState &pass) { - assert(succBB->getSinglePredecessorBlock() == predBB && - "value is live-out on another predBB successor: critical edge?"); - - SILBuilderWithScope builder(succBB->begin()); - auto *di = - builder.createDestroyValue(succBB->begin()->getLoc(), pass.currDef); - - pass.destroys.recordFinalDestroy(di); - - ++NumDestroysGenerated; - LLVM_DEBUG(llvm::dbgs() << " Destroy on edge "; di->dump()); - - pass.markInvalid(SILAnalysis::InvalidationKind::Instructions); -} - -/// This liveness boundary is within a basic block at the given position. -/// -/// Create a final destroy, immediately after `pos`. -static void insertDestroyAtInst(SILBasicBlock::iterator pos, - CopyPropagationState &pass) { - SILBuilderWithScope builder(pos); - auto *di = builder.createDestroyValue((*pos).getLoc(), pass.currDef); - pass.destroys.recordFinalDestroy(di); - ++NumDestroysGenerated; - LLVM_DEBUG(llvm::dbgs() << " Destroy at last use "; di->dump()); - pass.markInvalid(SILAnalysis::InvalidationKind::Instructions); -} - -// The pruned liveness boundary is within the given basic block. Find the -// block's last use. If the last use consumes the value, record it as a -// destroy. Otherwise, insert a new destroy_value. -static void findOrInsertDestroyInBlock(SILBasicBlock *bb, - CopyPropagationState &pass) { - auto *defInst = pass.currDef->getDefiningInstruction(); - auto instIter = bb->getTerminator()->getIterator(); - while (true) { - auto *inst = &*instIter; - Optional isConsumingResult = pass.liveness.isConsumingUser(inst); - if (isConsumingResult.hasValue()) { - if (isConsumingResult.getValue()) { - // This consuming use becomes a final destroy. - pass.destroys.recordFinalDestroy(inst); - break; - } - // Insert a destroy after this non-consuming use. - assert(inst != bb->getTerminator() && "Terminator must consume operand."); - insertDestroyAtInst(std::next(instIter), pass); - break; - } - // This is not a potential last user. Keep scanning. - // If the original destroy is reached, this is a dead live range. Insert a - // destroy immediately after the def. - if (instIter == bb->begin()) { - assert(cast(pass.currDef)->getParent() == bb); - insertDestroyAtInst(instIter, pass); - break; - } - --instIter; - if (&*instIter == defInst) { - insertDestroyAtInst(std::next(instIter), pass); - break; - } - } -} - -/// Populate `pass.finalBlockDestroys` with the final destroy points once copies -/// are eliminated. This only applies to owned values. -/// -/// Observations: -/// - The pass.currDef must be postdominated by some subset of its -/// consuming uses, including destroys. -/// - The postdominating consumes cannot be within nested loops. -/// - Any blocks in nested loops are now marked LiveOut. -static void findOrInsertDestroys(CopyPropagationState &pass) { - assert(!pass.liveness.empty()); - if (!pass.isValueOwned()) - return; - - // Visit each original consuming use or destroy as the starting point for a - // backward CFG traversal. - for (auto *originalDestroyBB : pass.liveness.getOriginalDestroyBlocks()) { - SmallSetVector blockPredWorklist; - - // Process each visited block, where succBB == nullptr for the starting - // point of the CFG traversal. - auto visitBB = [&](SILBasicBlock *bb, SILBasicBlock *succBB) { - switch (pass.liveness.isBlockLive(bb)) { - case LiveOut: - assert(succBB && "value live-out of a block where it is consumed"); - insertDestroyOnCFGEdge(bb, succBB, pass); - break; - case LiveWithin: - // The liveness boundary is inside this block. Insert a final destroy - // inside the block if it doesn't already have one. - if (!pass.destroys.hasFinalDestroy(bb)) - findOrInsertDestroyInBlock(bb, pass); - break; - case Dead: { - // Continue searching upward to find the pruned liveness boundary. - blockPredWorklist.insert(bb); - } - } - }; - // Perform a backward CFG traversal up to the pruned liveness boundary, - // visiting each block. - visitBB(originalDestroyBB, nullptr); - while (!blockPredWorklist.empty()) { - auto *succBB = blockPredWorklist.pop_back_val(); - for (auto *predBB : succBB->getPredecessorBlocks()) - visitBB(predBB, succBB); - } - } -} - -//===----------------------------------------------------------------------===// -// Step 4. Rewrite copies and destroys for a single copied definition. -//===----------------------------------------------------------------------===// - -/// `pass.currDef` is live across the given consuming use. Copy the value. -static void copyLiveUse(Operand *use, CopyPropagationState &pass) { - SILInstruction *user = use->getUser(); - SILBuilder B(user->getIterator()); - B.setCurrentDebugScope(user->getDebugScope()); - - auto *copy = B.createCopyValue(user->getLoc(), use->get()); - use->set(copy); - ++NumCopiesGenerated; - LLVM_DEBUG(llvm::dbgs() << " Copying at last use "; copy->dump()); - pass.markInvalid(SILAnalysis::InvalidationKind::Instructions); -} - -/// Revisit the def-use chain of `pass.currDef`. Mark unneeded original copies -/// and destroys for deletion. Insert new copies for interior uses that require -/// ownership of the used operand. -/// -/// TODO: Avoid unnecessary rewrites. Identify copies and destroys that already -/// complement a non-consuming use. -static void rewriteCopies(CopyPropagationState &pass) { - SmallSetVector instsToDelete; - SmallSetVector defUseWorklist; - - // Visit each operand in the def-use chain. - auto visitUse = [&](Operand *use) { - auto *user = use->getUser(); - // Recurse through copies. - if (auto *copy = dyn_cast(user)) { - defUseWorklist.insert(copy); - return; - } - - if (auto *destroy = dyn_cast(user)) { - // If this destroy was marked as a final destroy, ignore it; otherwise, - // delete it. - if (!pass.destroys.claimDestroy(destroy)) { - instsToDelete.insert(destroy); - LLVM_DEBUG(llvm::dbgs() << " Removing "; destroy->dump()); - ++NumDestroysEliminated; - } - return; - } - - // Nonconsuming uses do not need copies and cannot be marked as destroys. - if (!use->isLifetimeEnding()) - return; - - // If this use was marked as a final destroy *and* this is the first - // consumed operand we have visited, then ignore it. Otherwise, treat it as - // an "interior" consuming use and insert a copy. - if (!pass.destroys.claimDestroy(user)) - copyLiveUse(use, pass); - }; - - // Perform a def-use traversal, visiting each use operand. - defUseWorklist.insert(pass.currDef); - while (!defUseWorklist.empty()) { - SILValue value = defUseWorklist.pop_back_val(); - // Recurse through copies then remove them. - if (auto *copy = dyn_cast(value)) { - for (auto *use : copy->getUses()) - visitUse(use); - copy->replaceAllUsesWith(copy->getOperand()); - instsToDelete.insert(copy); - LLVM_DEBUG(llvm::dbgs() << " Removing "; copy->dump()); - ++NumCopiesEliminated; - continue; - } - for (Operand *use : value->getUses()) - visitUse(use); - } - assert(pass.destroys.empty()); - - // Remove the leftover copy_value and destroy_value instructions. - if (!instsToDelete.empty()) { - recursivelyDeleteTriviallyDeadInstructions(instsToDelete.takeVector(), - /*force=*/true); - pass.markInvalid(SILAnalysis::InvalidationKind::Instructions); - } -} - -//===----------------------------------------------------------------------===// -// CopyPropagation: Top-Level Function Transform. -//===----------------------------------------------------------------------===// - -/// TODO: we could strip casts as well, then when recursing through users keep -/// track of the nearest non-copy def. For opaque values, we don't expect to see -/// casts. -static SILValue stripCopies(SILValue v) { - while (true) { - v = stripSinglePredecessorArgs(v); - - if (auto *srcCopy = dyn_cast(v)) { - v = srcCopy->getOperand(); - continue; - } - - return v; - } -} - -namespace { -class CopyPropagation : public SILFunctionTransform { /// The entry point to this function transformation. void run() override; }; @@ -681,36 +51,46 @@ class CopyPropagation : public SILFunctionTransform { /// Top-level pass driver. void CopyPropagation::run() { - LLVM_DEBUG(llvm::dbgs() << "*** CopyPropagation: " << getFunction()->getName() - << "\n"); + auto *f = getFunction(); + + // Debug label for unit testing. + LLVM_DEBUG(llvm::dbgs() << "*** CopyPropagation: " << f->getName() << "\n"); // This algorithm fundamentally assumes ownership. - if (!getFunction()->hasOwnership()) + if (!f->hasOwnership()) return; - // Step 1. Find all copied defs. - CopyPropagationState pass(getFunction()); - SmallSetVector copiedDefs; - for (auto &bb : *pass.func) { + // Driver: Find all copied defs. + llvm::SmallSetVector copiedDefs; + for (auto &bb : *f) { for (auto &i : bb) { if (auto *copy = dyn_cast(&i)) - copiedDefs.insert(stripCopies(copy)); + copiedDefs.insert( + CanonicalizeOSSALifetime::getCanonicalCopiedDef(copy)); } } - + // Perform copy propgation for each copied value. + CanonicalizeOSSALifetime canonicalizer(pruneDebug); for (auto &def : copiedDefs) { - pass.resetDef(def); - // Step 2: computeLiveness - if (computeLiveness(pass)) { - // Step 3: findOrInsertDestroys - findOrInsertDestroys(pass); - // Invalidate book-keeping before deleting instructions. - pass.liveness.clear(); - // Step 4: rewriteCopies - rewriteCopies(pass); + canonicalizer.canonicalizeValueLifetime(def); + if (SILValue outerCopy = canonicalizer.createdOuterCopy()) { + SILValue outerDef = canonicalizer.getCanonicalCopiedDef(outerCopy); + canonicalizer.canonicalizeValueLifetime(outerDef); } + // TODO: also canonicalize any lifetime.persistentCopies like separate owned + // live ranges. + } + if (canonicalizer.hasChanged()) { + invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); + DeadEndBlocks deBlocks(f); + f->verifyOwnership(&deBlocks); } - invalidateAnalysis(SILAnalysis::InvalidationKind(pass.invalidation)); } -SILTransform *swift::createCopyPropagation() { return new CopyPropagation(); } +SILTransform *swift::createCopyPropagation() { + return new CopyPropagation(/*pruneDebug*/ true); +} + +SILTransform *swift::createMandatoryCopyPropagation() { + return new CopyPropagation(/*pruneDebug*/ false); +} diff --git a/lib/SILOptimizer/Utils/CMakeLists.txt b/lib/SILOptimizer/Utils/CMakeLists.txt index b34bae3d536eb..4a9b218480e05 100644 --- a/lib/SILOptimizer/Utils/CMakeLists.txt +++ b/lib/SILOptimizer/Utils/CMakeLists.txt @@ -2,6 +2,7 @@ target_sources(swiftSILOptimizer PRIVATE BasicBlockOptUtils.cpp CFGOptUtils.cpp CanonicalizeInstruction.cpp + CanonicalOSSALifetime.cpp CastOptimizer.cpp CheckedCastBrJumpThreading.cpp ConstantFolding.cpp diff --git a/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp b/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp new file mode 100644 index 0000000000000..38ed6015bbe92 --- /dev/null +++ b/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp @@ -0,0 +1,650 @@ +//===--- CanonicalOSSALifetime.cpp - Canonicalize OSSA value lifetimes ----===// +// +// 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 top-level API rewrites the extended lifetime of a SILValue: +/// +/// bool CanonicalizeOSSALifetime::canonicalizeValueLifetime(SILValue def) +/// +/// Each time it's called on a single OSSA value, `def`, it performs three +/// steps: +/// +/// 1. Compute "pruned" liveness of def and its copies, ignoring original +/// destroys. Initializes `liveness`. +/// +/// 2. Find `def`s final destroy points based on its pruned +/// liveness. Initializes `consumes` and inserts new destroy_value +/// instructions. +/// +/// 3. Rewrite `def`s original copies and destroys, inserting new copies where +/// needed. Deletes original copies and destroys and inserts new copies. +/// +/// See CanonicalOSSALifetime.h for examples. +/// +/// TODO: Enable canonical-ossa-rewrite-borrows to rewrite single-block borrows. +/// Add support for multi-block borrows, load_borrow, and phi by using +/// persistentCopies. +/// +/// TODO: If all client passes can maintain block numbers, then the +/// SmallDenseMaps/SetVectors can be replaced with bitsets/sparsesets. +/// +//===----------------------------------------------------------------------===// + +#define DEBUG_TYPE "copy-propagation" + +#include "swift/SILOptimizer/Utils/CanonicalOSSALifetime.h" +#include "swift/SIL/InstructionUtils.h" +#include "swift/SIL/OwnershipUtils.h" +#include "swift/SILOptimizer/Utils/CFGOptUtils.h" +#include "swift/SILOptimizer/Utils/InstOptUtils.h" +#include "llvm/ADT/Statistic.h" + +using namespace swift; +using llvm::SmallSetVector; + +STATISTIC(NumCopiesEliminated, "number of copy_value instructions removed"); +STATISTIC(NumDestroysEliminated, + "number of destroy_value instructions removed"); +STATISTIC(NumCopiesGenerated, "number of copy_value instructions created"); +STATISTIC(NumDestroysGenerated, "number of destroy_value instructions created"); +STATISTIC(NumUnknownUsers, "number of functions with unknown users"); + +/// This use-def walk must be consistent with the def-use walks performed +/// within the canonicalizeValueLifetime() implementation. +SILValue CanonicalizeOSSALifetime::getCanonicalCopiedDef(SILValue v) { + while (auto *copy = dyn_cast(v)) { + auto def = copy->getOperand(); + if (def.getOwnershipKind() == OwnershipKind::Owned) { + v = def; + continue; + } + if (auto borrowedVal = BorrowedValue::get(def)) { + // Any def's that aren't filtered out here must be handled by + // computeBorrowLiveness. + switch (borrowedVal->kind) { + case BorrowedValueKind::SILFunctionArgument: + case BorrowedValueKind::BeginBorrow: + return def; + case BorrowedValueKind::LoadBorrow: + case BorrowedValueKind::Phi: + break; + } + } + // This guaranteed value cannot be handled, treat the copy as an owned + // live range def instead. + return copy; + } + return v; +} + +//===----------------------------------------------------------------------===// +// MARK: Rewrite borrow scopes +//===----------------------------------------------------------------------===// + +llvm::cl::opt + EnableRewriteBorrows("canonical-ossa-rewrite-borrows", llvm::cl::init(true), + llvm::cl::desc("Enable rewriting borrow scopes")); + +bool CanonicalizeOSSALifetime::computeBorrowLiveness() { + auto borrowedVal = BorrowedValue::get(currentDef); + if (!borrowedVal) { + return false; + } + switch (borrowedVal->kind) { + case BorrowedValueKind::SILFunctionArgument: + // For efficiency, function arguments skip liveness. + return true; + case BorrowedValueKind::LoadBorrow: + case BorrowedValueKind::Phi: + // TODO: Canonicalize load_borrow scope and phi once consolidateBorrowScope + // can handle persistentCopies. + return false; + case BorrowedValueKind::BeginBorrow: + break; + } + if (!EnableRewriteBorrows) { + return false; + } + borrowedVal->visitLocalScopeEndingUses([this](Operand *use) { + liveness.updateForUse(use, /*lifetimeEnding*/ true); + }); + + // TODO: Remove this check. Canonicalize multi-block borrow scopes only after + // consolidateBorrowScope can handle persistentCopies, otherwise we may end up + // generating more dynamic copies than the non-canonical form. + if (liveness.numLiveBlocks() > 1) { + return false; + } + return true; +} + +// Create a copy for outer uses of the borrow scope introduced by +// currentDef. This copy should only be used by outer uses in the same block +// as the borrow scope. +// +// To use an existing outer copy, we could find its earliest consume. But the +// new copy will immediately canonicalized and a canonical begin_borrow scope +// have no outside uses of its first block. +static CopyValueInst *createOuterCopy(BeginBorrowInst *beginBorrow) { + SILBuilderWithScope B(beginBorrow); + + auto loc = RegularLocation::getAutoGeneratedLocation( + beginBorrow->getLoc().getSourceLoc()); + auto *copy = B.createCopyValue(loc, beginBorrow->getOperand()); + + ++NumCopiesGenerated; + LLVM_DEBUG(llvm::dbgs() << " Outer copy " << *copy); + + return copy; +} + +// TODO: Canonicalize multi-block borrow scopes, load_borrow scope, and phi +// borrow scopes by adding one copy per block to persistentCopies for +// each block that dominates an outer use. +void CanonicalizeOSSALifetime::consolidateBorrowScope() { + if (isa(currentDef)) { + return; + } + // Gather all outer uses before rewriting any to avoid scanning any basic + // block more than once. + SmallVector outerUses; + llvm::SmallDenseSet outerUseInsts; + auto recordOuterUse = [&](Operand *use) { + outerUses.push_back(use); + outerUseInsts.insert(use->getUser()); + }; + + defUseWorklist.clear(); + defUseWorklist.insert(currentDef); + while (!defUseWorklist.empty()) { + SILValue value = defUseWorklist.pop_back_val(); + for (Operand *use : value->getUses()) { + auto *user = use->getUser(); + // Recurse through copies. + if (auto *copy = dyn_cast(user)) { + defUseWorklist.insert(copy); + continue; + } + // debug_value uses are handled like normal uses here. They should be + // stripped later if required when handling outerCopy or persistentCopies. + if (liveness.getBlockLiveness(user->getParent()) + == PrunedLiveBlocks::LiveOut) { + continue; + } + switch (use->getOperandOwnership()) { + case OperandOwnership::NonUse: + break; + case OperandOwnership::TrivialUse: + llvm_unreachable("this operand cannot handle ownership"); + + case OperandOwnership::InteriorPointer: + case OperandOwnership::ForwardingBorrow: + case OperandOwnership::EndBorrow: + case OperandOwnership::Reborrow: + // Ignore uses that must be within the borrow scope. + break; + + case OperandOwnership::ForwardingUnowned: + case OperandOwnership::PointerEscape: + case OperandOwnership::InstantaneousUse: + case OperandOwnership::UnownedInstantaneousUse: + case OperandOwnership::BitwiseEscape: + case OperandOwnership::ForwardingConsume: + case OperandOwnership::DestroyingConsume: + case OperandOwnership::Borrow: + recordOuterUse(use); + break; + } + } + } // end def-use traversal + + // Remove outer uses that occur before the end of the borrow scope. + auto *beginBorrow = cast(currentDef); + BorrowedValue::get(beginBorrow)->visitLocalScopeEndingUses([&](Operand *use) { + // Forward iterate until we find the end of the borrow scope. + auto *endScope = use->getUser(); + for (auto instIter = beginBorrow->getIterator(), + endIter = endScope->getIterator(); + instIter != endIter; ++instIter) { + outerUseInsts.erase(&*instIter); + } + }); + if (outerUseInsts.empty()) { + return; + } + // Rewrite the outer uses. + this->outerCopy = createOuterCopy(beginBorrow); + for (Operand *use : outerUses) { + if (!outerUseInsts.count(use->getUser())) { + continue; + } + LLVM_DEBUG(llvm::dbgs() << " Use of outer copy " << *use->getUser()); + use->set(outerCopy); + } +} + +//===----------------------------------------------------------------------===// +// MARK: Step 1. Compute pruned liveness +//===----------------------------------------------------------------------===// + +bool CanonicalizeOSSALifetime::computeCanonicalLiveness() { + defUseWorklist.clear(); + defUseWorklist.insert(currentDef); + while (!defUseWorklist.empty()) { + SILValue value = defUseWorklist.pop_back_val(); + for (Operand *use : value->getUses()) { + auto *user = use->getUser(); + + // Recurse through copies. + if (auto *copy = dyn_cast(user)) { + defUseWorklist.insert(copy); + continue; + } + // Handle debug_value instructions separately. + if (pruneDebug) { + if (auto *dvi = dyn_cast(user)) { + // Only instructions potentially outside current pruned liveness are + // interesting. + if (liveness.getBlockLiveness(dvi->getParent()) + != PrunedLiveBlocks::LiveOut) { + recordDebugValue(dvi); + } + continue; + } + } + switch (use->getOperandOwnership()) { + case OperandOwnership::NonUse: + break; + case OperandOwnership::TrivialUse: + llvm_unreachable("this operand cannot handle ownership"); + + // Conservatively treat a conversion to an unowned value as a pointer + // escape. Is it legal to canonicalize these? + case OperandOwnership::ForwardingUnowned: + case OperandOwnership::PointerEscape: + return false; + case OperandOwnership::InstantaneousUse: + case OperandOwnership::UnownedInstantaneousUse: + case OperandOwnership::BitwiseEscape: + liveness.updateForUse(use, /*lifetimeEnding*/ false); + break; + case OperandOwnership::ForwardingConsume: + recordConsumingUse(use); + liveness.updateForUse(use, /*lifetimeEnding*/ true); + break; + case OperandOwnership::DestroyingConsume: + // destroy_value does not force pruned liveness (but store etc. does). + if (!isa(user)) { + liveness.updateForUse(use, /*lifetimeEnding*/ true); + } + recordConsumingUse(use); + break; + case OperandOwnership::Borrow: + // An entire borrow scope is considered a single use that occurs at the + // point of the end_borrow. + BorrowingOperand(use).visitLocalEndScopeUses([this](Operand *end) { + liveness.updateForUse(end, /*lifetimeEnding*/ false); + return true; + }); + break; + case OperandOwnership::InteriorPointer: + case OperandOwnership::ForwardingBorrow: + case OperandOwnership::EndBorrow: + case OperandOwnership::Reborrow: + llvm_unreachable("operand kind cannot take an owned value"); + } + } + } + return true; +} + +//===----------------------------------------------------------------------===// +// MARK: Step 2. Find the destroy points of the current def based on the pruned +// liveness computed in Step 1. +//===----------------------------------------------------------------------===// + +/// The liveness boundary is at a CFG edge `predBB` -> `succBB`, meaning that +/// `currentDef` is live out of at least one other `predBB` successor. +/// +/// Create and record a final destroy_value at the beginning of `succBB` +/// (assuming no critical edges). +static void insertDestroyOnCFGEdge(SILBasicBlock *predBB, SILBasicBlock *succBB, + SILValue def, + CanonicalOSSAConsumeInfo &consumes) { + assert(succBB->getSinglePredecessorBlock() == predBB + && "value is live-out on another predBB successor: critical edge?"); + + SILBuilderWithScope builder(succBB->begin()); + auto loc = RegularLocation::getAutoGeneratedLocation( + succBB->begin()->getLoc().getSourceLoc()); + auto *di = builder.createDestroyValue(loc, def); + + consumes.recordFinalConsume(di); + + ++NumDestroysGenerated; + LLVM_DEBUG(llvm::dbgs() << " Destroy on edge " << *di); +} + +/// This liveness boundary is within a basic block at the given position. +/// +/// Create a final destroy, immediately after `pos`. +static void insertDestroyAtInst(SILBasicBlock::iterator pos, + DestroyValueInst *existingDestroy, SILValue def, + CanonicalOSSAConsumeInfo &consumes) { + if (existingDestroy) { + consumes.recordFinalConsume(existingDestroy); + return; + } + SILBuilderWithScope builder(pos); + auto loc = RegularLocation::getAutoGeneratedLocation( + (*pos).getLoc().getSourceLoc()); + auto *di = builder.createDestroyValue(loc, def); + consumes.recordFinalConsume(di); + + ++NumDestroysGenerated; + LLVM_DEBUG(llvm::dbgs() << " Destroy at last use " << *di); +} + +// The pruned liveness boundary is within the given basic block. Find the +// block's last use. If the last use consumes the value, record it as a +// destroy. Otherwise, insert a new destroy_value. +// +// Return true if a new destroy was inserted. +void CanonicalizeOSSALifetime::findOrInsertDestroyInBlock(SILBasicBlock *bb) { + auto *defInst = currentDef->getDefiningInstruction(); + DestroyValueInst *existingDestroy = nullptr; + auto instIter = bb->getTerminator()->getIterator(); + while (true) { + auto *inst = &*instIter; + + if (pruneDebug) { + if (auto *dvi = dyn_cast(inst)) { + if (debugValues.erase(dvi)) + consumes.recordDebugAfterConsume(dvi); + } + } + switch (liveness.isInterestingUser(inst)) { + case PrunedLiveness::NonUser: + break; + case PrunedLiveness::NonLifetimeEndingUse: + // Insert a destroy after this non-consuming use. + if (inst == bb->getTerminator()) { + for (auto &succ : bb->getSuccessors()) { + insertDestroyOnCFGEdge(bb, succ, currentDef, consumes); + setChanged(); + } + } else { + insertDestroyAtInst(std::next(instIter), existingDestroy, currentDef, + consumes); + setChanged(); + } + return; + case PrunedLiveness::LifetimeEndingUse: + // This consuming use becomes a final destroy. + consumes.recordFinalConsume(inst); + return; + } + // This is not a potential last user. Keep scanning. + // Allow lifetimes to be artificially extended up to the next call. + if (ApplySite::isa(inst)) { + existingDestroy = nullptr; + } else if (!existingDestroy) { + if (auto *destroy = dyn_cast(inst)) { + auto destroyDef = CanonicalizeOSSALifetime::getCanonicalCopiedDef( + destroy->getOperand()); + if (destroyDef == currentDef) { + existingDestroy = destroy; + } + } + } + if (instIter == bb->begin()) { + assert(cast(currentDef)->getParent() == bb); + insertDestroyAtInst(instIter, existingDestroy, currentDef, consumes); + setChanged(); + return; + } + --instIter; + // If the original def is reached, this is a dead live range. Insert a + // destroy immediately after the def. + if (&*instIter == defInst) { + insertDestroyAtInst(std::next(instIter), existingDestroy, currentDef, + consumes); + setChanged(); + return; + } + } +} + +/// Populate `consumes` with the final destroy points once copies are +/// eliminated. This only applies to owned values. +/// +/// Observations: +/// - currentDef must be postdominated by some subset of its +/// consuming uses, including destroys on all return paths. +/// - The postdominating consumes cannot be within nested loops. +/// - Any blocks in nested loops are now marked LiveOut. +void CanonicalizeOSSALifetime::findOrInsertDestroys() { + // Visit each original consuming use or destroy as the starting point for a + // backward CFG traversal. + blockWorklist.clear(); + blockWorklist.insert(consumingBlocks.begin(), consumingBlocks.end()); + // This worklist is also a visited set, so we never pop the entries. + for (unsigned blockIdx = 0; blockIdx < blockWorklist.size(); ++blockIdx) { + // Process each block that has not been visited and is not LiveOut. + SILBasicBlock *bb = blockWorklist[blockIdx]; + switch (liveness.getBlockLiveness(bb)) { + case PrunedLiveBlocks::LiveOut: + // A lifetimeEndBlock may be determined to be LiveOut after analyzing the + // extended It is irrelevent for finding the boundary. + break; + case PrunedLiveBlocks::LiveWithin: { + // The liveness boundary is inside this block. Insert a final destroy + // inside the block if it doesn't already have one. + findOrInsertDestroyInBlock(bb); + break; + } + case PrunedLiveBlocks::Dead: + // Continue searching upward to find the pruned liveness boundary. + for (auto *predBB : bb->getPredecessorBlocks()) { + if (liveness.getBlockLiveness(predBB) == PrunedLiveBlocks::LiveOut) { + insertDestroyOnCFGEdge(predBB, bb, currentDef, consumes); + setChanged(); + } else { + blockWorklist.insert(predBB); + } + } + break; + } + } + // Add any debug_values from Dead blocks into the debugAfterConsume set. + for (auto *dvi : debugValues) { + if (liveness.getBlockLiveness(dvi->getParent()) == PrunedLiveBlocks::Dead) { + consumes.recordDebugAfterConsume(dvi); + } + } +} + +//===----------------------------------------------------------------------===// +// MARK: Step 3. Rewrite copies and destroys +//===----------------------------------------------------------------------===// + +/// The lifetime extends beyond given consuming use. Copy the value. +static void copyLiveUse(Operand *use) { + SILInstruction *user = use->getUser(); + SILBuilderWithScope B(user->getIterator()); + + auto loc = RegularLocation::getAutoGeneratedLocation( + user->getLoc().getSourceLoc()); + auto *copy = B.createCopyValue(loc, use->get()); + use->set(copy); + + ++NumCopiesGenerated; + LLVM_DEBUG(llvm::dbgs() << " Copying at last use " << *copy); +} + +/// Revisit the def-use chain of currentDef. Mark unneeded original +/// copies and destroys for deletion. Insert new copies for interior uses that +/// require ownership of the used operand. +void CanonicalizeOSSALifetime::rewriteCopies() { + bool isOwned = currentDef.getOwnershipKind() == OwnershipKind::Owned; + assert((!isOwned || !consumes.hasPersistentCopies()) + && "persistent copies use borrowed values"); + + SmallSetVector instsToDelete; + defUseWorklist.clear(); + + // Visit each operand in the def-use chain. + // + // Return true if the operand can use the current definition. Return false if + // it requires a copy. + auto visitUse = [&](Operand *use) { + auto *user = use->getUser(); + // Recurse through copies. + if (auto *copy = dyn_cast(user)) { + if (!consumes.isPersistentCopy(copy)) { + defUseWorklist.insert(copy); + return true; + } + } + if (auto *destroy = dyn_cast(user)) { + // If this destroy was marked as a final destroy, ignore it; otherwise, + // delete it. + if (!consumes.claimConsume(destroy)) { + instsToDelete.insert(destroy); + LLVM_DEBUG(llvm::dbgs() << " Removing " << *destroy); + ++NumDestroysEliminated; + } + return true; + } + + // Nonconsuming uses do not need copies and cannot be marked as destroys. + // A lifetime-ending use here must be a consume because EndBorrow/Reborrow + // uses have been filtered out. + if (!use->isLifetimeEnding()) + return true; + + // If this use was marked as a final destroy *and* this is the first + // consumed operand we have visited, then ignore it. Otherwise, treat it as + // an "interior" consuming use and insert a copy. + return consumes.claimConsume(user); + }; + + // Perform a def-use traversal, visiting each use operand. + for (auto useIter = currentDef->use_begin(), endIter = currentDef->use_end(); + useIter != endIter;) { + Operand *use = *useIter++; + // A direct lifetime-ending use of a guaranteed value (EndBorrow or + // Reborrow), never needs a copy. + if (!isOwned && use->isLifetimeEnding()) { + continue; + } + if (!visitUse(use)) { + copyLiveUse(use); + setChanged(); + } + } + while (!defUseWorklist.empty()) { + CopyValueInst *srcCopy = cast(defUseWorklist.pop_back_val()); + // Recurse through copies while replacing their uses. + Operand *reusedCopyOp = nullptr; + for (auto useIter = srcCopy->use_begin(); useIter != srcCopy->use_end();) { + Operand *use = *useIter++; + if (!visitUse(use)) { + if (!reusedCopyOp && srcCopy->getParent() == use->getParentBlock()) { + reusedCopyOp = use; + } else { + copyLiveUse(use); + setChanged(); + } + } + } + if (!(reusedCopyOp && srcCopy->hasOneUse())) { + setChanged(); + srcCopy->replaceAllUsesWith(srcCopy->getOperand()); + if (reusedCopyOp) { + reusedCopyOp->set(srcCopy); + } else { + instsToDelete.insert(srcCopy); + LLVM_DEBUG(llvm::dbgs() << " Removing " << *srcCopy); + ++NumCopiesEliminated; + } + } + } + assert(!consumes.hasUnclaimedConsumes()); + + // Remove any dead debug_values. + for (auto *dvi : consumes.getDebugInstsAfterConsume()) { + LLVM_DEBUG(llvm::dbgs() << " Removing debug_value: " << *dvi); + dvi->eraseFromParent(); + } + consumes.clear(); + + // Remove the leftover copy_value and destroy_value instructions. + if (!instsToDelete.empty()) { + recursivelyDeleteTriviallyDeadInstructions(instsToDelete.takeVector(), + /*force=*/true); + setChanged(); + } +} + +//===----------------------------------------------------------------------===// +// MARK: Top-Level API +//===----------------------------------------------------------------------===// + +bool CanonicalizeOSSALifetime::canonicalizeValueLifetime(SILValue def) { + switch (def.getOwnershipKind()) { + case OwnershipKind::None: + case OwnershipKind::Unowned: + case OwnershipKind::Any: + return false; + case OwnershipKind::Guaranteed: + initDef(def); + if (!computeBorrowLiveness()) { + clearLiveness(); + return false; + } + // Set outerCopy and persistentCopies and rewrite uses + // outside the scope. + consolidateBorrowScope(); + // Invalidate book-keeping before deleting instructions. + clearLiveness(); + // Rewrite copies and delete extra destroys within the scope. + assert(!consumes.hasFinalConsumes()); + rewriteCopies(); + return true; + case OwnershipKind::Owned: + initDef(def); + // Step 1: compute liveness + if (!computeCanonicalLiveness()) { + clearLiveness(); + return false; + } + // Step 2: record final destroys + findOrInsertDestroys(); + // Invalidate book-keeping before deleting instructions. + clearLiveness(); + // Step 3: rewrite copies and delete extra destroys + rewriteCopies(); + return true; + } +} + +//===----------------------------------------------------------------------===// +// MARK: Debugging +//===----------------------------------------------------------------------===// + +SWIFT_ASSERT_ONLY_DECL( + void CanonicalOSSAConsumeInfo::dump() const { + llvm::dbgs() << "Consumes:"; + for (auto &blockAndInst : finalBlockConsumes) { + llvm::dbgs() << " " << *blockAndInst.getSecond(); + } + }) diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index ecd43489a108f..2b87eed617852 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -298,7 +298,7 @@ class AttributeChecker : public AttributeVisitor { case ActorIndependentKind::Safe: diagnoseAndRemoveAttr(attr, diag::actorindependent_mutable_storage); return; - + case ActorIndependentKind::Unsafe: break; } @@ -315,26 +315,11 @@ class AttributeChecker : public AttributeVisitor { (dc->isTypeContext() && var->isStatic())) { return; } - - // Otherwise, fall through to make sure we're in an appropriate - // context. } - // @actorIndependent only makes sense on an actor instance member. - if (!dc->getSelfClassDecl() || - !dc->getSelfClassDecl()->isActor()) { - diagnoseAndRemoveAttr(attr, diag::actorindependent_not_actor_member); - return; + if (auto VD = dyn_cast(D)) { + (void)getActorIsolation(VD); } - - auto VD = cast(D); - if (!VD->isInstanceMember()) { - diagnoseAndRemoveAttr( - attr, diag::actorindependent_not_actor_instance_member); - return; - } - - (void)getActorIsolation(VD); } void visitGlobalActorAttr(GlobalActorAttr *attr) { diff --git a/stdlib/public/Platform/winsdk.modulemap b/stdlib/public/Platform/winsdk.modulemap index 9bbc6e37b5e58..616154c020a0f 100644 --- a/stdlib/public/Platform/winsdk.modulemap +++ b/stdlib/public/Platform/winsdk.modulemap @@ -168,6 +168,8 @@ module WinSDK [system] { export * link "xaudio2.lib" + + requires cplusplus } // XInput 1.4 (Windows 10, XBox) is newer than the XInput 9.1.0 which was diff --git a/stdlib/public/runtime/DynamicCast.cpp b/stdlib/public/runtime/DynamicCast.cpp index 30642e253c784..47481f1a5199a 100644 --- a/stdlib/public/runtime/DynamicCast.cpp +++ b/stdlib/public/runtime/DynamicCast.cpp @@ -2185,17 +2185,6 @@ tryCast( /****************************** Main Entrypoint *******************************/ /******************************************************************************/ -// XXX REMOVE ME XXX TODO XXX -// Declare the old entrypoint -SWIFT_RUNTIME_EXPORT -bool -swift_dynamicCast_OLD(OpaqueValue *destLocation, - OpaqueValue *srcValue, - const Metadata *srcType, - const Metadata *destType, - DynamicCastFlags flags); -// XXX REMOVE ME XXX TODO XXX - /// ABI: Perform a dynamic cast to an arbitrary type. static bool swift_dynamicCastImpl(OpaqueValue *destLocation, diff --git a/test/Parse/consecutive_statements.swift b/test/Parse/consecutive_statements.swift index deb1eb1d94754..46700b62c6c98 100644 --- a/test/Parse/consecutive_statements.swift +++ b/test/Parse/consecutive_statements.swift @@ -6,13 +6,13 @@ func statement_starts() { f(0) f (0) - f // expected-error{{expression resolves to an unused variable}} + f // expected-warning{{expression resolves to an unused variable}} (0) // expected-warning {{integer literal is unused}} var a = [1,2,3] a[0] = 1 a [0] = 1 - a // expected-error{{expression resolves to an unused variable}} + a // expected-warning{{expression resolves to an unused variable}} [0, 1, 2] // expected-warning {{expression of type '[Int]' is unused}} } diff --git a/test/Parse/super.swift b/test/Parse/super.swift index a7acb18b2ad76..cd40fbb7d80a1 100644 --- a/test/Parse/super.swift +++ b/test/Parse/super.swift @@ -34,7 +34,7 @@ class D : B { } func super_calls() { - super.foo // expected-error {{expression resolves to an unused property}} + super.foo // expected-warning {{expression resolves to an unused property}} super.foo.bar // expected-error {{value of type 'Int' has no member 'bar'}} super.bar // expected-error {{expression resolves to an unused function}} super.bar() @@ -42,7 +42,7 @@ class D : B { super.init // expected-error{{no exact matches in reference to initializer}} super.init() // expected-error{{'super.init' cannot be called outside of an initializer}} super.init(0) // expected-error{{'super.init' cannot be called outside of an initializer}} // expected-error {{missing argument label 'x:' in call}} - super[0] // expected-error {{expression resolves to an unused subscript}} + super[0] // expected-warning {{expression resolves to an unused subscript}} super .bar() } diff --git a/test/Runtime/protocol_conformance_collision.swift b/test/Runtime/protocol_conformance_collision.swift index 515acf6a31cfe..865e6b7effbf3 100644 --- a/test/Runtime/protocol_conformance_collision.swift +++ b/test/Runtime/protocol_conformance_collision.swift @@ -14,6 +14,8 @@ // UNSUPPORTED: DARWIN_SIMULATOR=tvos // UNSUPPORTED: DARWIN_SIMULATOR=watchos +// UNSUPPORTED: use_os_stdlib + import Accelerate import Foundation import StdlibUnittest diff --git a/test/SILOptimizer/array_count_propagation_ossa.sil b/test/SILOptimizer/array_count_propagation_ossa.sil new file mode 100644 index 0000000000000..3fa5055294413 --- /dev/null +++ b/test/SILOptimizer/array_count_propagation_ossa.sil @@ -0,0 +1,184 @@ +// RUN: %target-sil-opt -enable-sil-verify-all -array-count-propagation %s | %FileCheck %s + +sil_stage canonical + +import Builtin +import Swift + +struct MyInt { + @_hasStorage var _value: Builtin.Int64 +} + +struct MyBool {} + +struct _MyBridgeStorage { + @_hasStorage var rawValue : Builtin.BridgeObject +} + +struct _MyArrayBuffer { + @_hasStorage var _storage : _MyBridgeStorage +} + +struct MyArray { + @_hasStorage var _buffer : _MyArrayBuffer +} + +class Klass { +} + +sil [ossa] @swift_bufferAllocate : $@convention(thin)() -> @owned AnyObject +sil [ossa] [_semantics "array.uninitialized"] @adoptStorage : $@convention(thin) (@owned AnyObject, MyInt, @thin MyArray.Type) -> @owned (MyArray, UnsafeMutablePointer) +sil [ossa] [_semantics "array.get_count"] @getCount : $@convention(method) (@guaranteed MyArray) -> MyInt +sil [ossa] [_semantics "array.get_element"] @getElement : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> @out MyInt +sil [ossa] [_semantics "array.uninitialized"] @allocateUninitialized : $@convention(thin) (MyInt, @thin MyArray.Type) -> @owned (MyArray, UnsafeMutablePointer) +sil [ossa] [_semantics "array.finalize_intrinsic"] @finalize : $@convention(thin) (@owned MyArray) -> @owned MyArray +sil [ossa] [_semantics "array.init"] @initRepeatedValueCount : $@convention(thin) (@in MyInt, MyInt, @thin MyArray.Type) -> @owned MyArray +sil [ossa] [_semantics "array.init"] @initEmpty : $@convention(thin) (@thin MyArray.Type) -> @owned MyArray + +// CHECK-LABEL: sil [ossa] @test_adoptStorage1 : +// CHECK: [[COUNTVAL:%.*]] = integer_literal $Builtin.Int64, 3 +// CHECK: [[COUNT:%.*]] = struct $MyInt ([[COUNTVAL]] +// CHECK: [[GETCOUNTFUN:%.*]] = function_ref @getCount +// CHECK-NOT: apply [[GETCOUNTFUN]] +// CHECK-LABEL: } // end sil function 'test_adoptStorage1' +sil [ossa] @test_adoptStorage1 : $@convention(thin) () -> MyInt { +bb0: + %0 = integer_literal $Builtin.Int64, 3 + %1 = function_ref @swift_bufferAllocate : $@convention(thin) () -> @owned AnyObject + %2 = apply %1() : $@convention(thin) () -> @owned AnyObject + %3 = struct $MyInt(%0 : $Builtin.Int64) + %4 = metatype $@thin MyArray.Type + %5 = function_ref @adoptStorage : $@convention(thin) (@owned AnyObject, MyInt, @thin MyArray.Type) -> @owned (MyArray, UnsafeMutablePointer) + %6 = apply %5(%2, %3, %4) : $@convention(thin) (@owned AnyObject, MyInt, @thin MyArray.Type) -> @owned (MyArray, UnsafeMutablePointer) + (%7, %8) = destructure_tuple %6 : $(MyArray, UnsafeMutablePointer) + debug_value %7 : $MyArray + %f = function_ref @finalize : $@convention(thin) (@owned MyArray) -> @owned MyArray + %a = apply %f(%7) : $@convention(thin) (@owned MyArray) -> @owned MyArray + %9 = function_ref @getCount : $@convention(method) (@guaranteed MyArray) -> MyInt + %10 = apply %9(%a) : $@convention(method) (@guaranteed MyArray) -> MyInt + destroy_value %a : $MyArray + return %10 : $MyInt +} + +// CHECK-LABEL: sil [ossa] @test_allocateUninitialized : +// CHECK: [[COUNTVAL:%.*]] = integer_literal $Builtin.Int64, 3 +// CHECK: [[COUNT:%.*]] = struct $MyInt ([[COUNTVAL]] +// CHECK: [[GETCOUNTFUN:%.*]] = function_ref @getCount +// CHECK-NOT: apply [[GETCOUNTFUN]] +// CHECK-LABEL: } // end sil function 'test_allocateUninitialized' +sil [ossa] @test_allocateUninitialized : $@convention(thin) () -> MyInt { +bb0: + %0 = integer_literal $Builtin.Int64, 3 + %3 = struct $MyInt(%0 : $Builtin.Int64) + %4 = metatype $@thin MyArray.Type + %5 = function_ref @allocateUninitialized : $@convention(thin) (MyInt, @thin MyArray.Type) -> @owned (MyArray, UnsafeMutablePointer) + %6 = apply %5(%3, %4) : $@convention(thin) (MyInt, @thin MyArray.Type) -> @owned (MyArray, UnsafeMutablePointer) + (%7, %8) = destructure_tuple %6 : $(MyArray, UnsafeMutablePointer) + debug_value %7 : $MyArray + %9 = function_ref @getCount : $@convention(method) (@guaranteed MyArray) -> MyInt + %10 = apply %9(%7) : $@convention(method) (@guaranteed MyArray) -> MyInt + %15 = function_ref @getElement : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> @out MyInt + %16 = alloc_stack $MyInt + %17 = struct $MyBool() + %18 = apply %15(%16, %3, %17, %7) : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> @out MyInt + destroy_value %7 : $MyArray + dealloc_stack %16 : $*MyInt + return %10 : $MyInt +} + +// CHECK-LABEL: sil [ossa] @test_initRepeatedValueCount : +// CHECK: [[COUNTVAL:%.*]] = integer_literal $Builtin.Int64, 3 +// CHECK: [[COUNT:%.*]] = struct $MyInt ([[COUNTVAL]] +// CHECK: [[GETCOUNTFUN:%.*]] = function_ref @getCount +// CHECK-NOT: apply [[GETCOUNTFUN]] +// CHECK-LABEL: } // end sil function 'test_initRepeatedValueCount' +sil [ossa] @test_initRepeatedValueCount : $@convention(thin) () -> MyInt { +bb0: + %0 = integer_literal $Builtin.Int64, 3 + %1 = alloc_stack $MyInt + %3 = struct $MyInt(%0 : $Builtin.Int64) + store %3 to [trivial] %1: $*MyInt + %4 = metatype $@thin MyArray.Type + %5 = function_ref @initRepeatedValueCount : $@convention(thin) (@in MyInt, MyInt, @thin MyArray.Type) -> @owned MyArray + %6 = apply %5(%1, %3, %4) : $@convention(thin) (@in MyInt, MyInt, @thin MyArray.Type) -> @owned MyArray + debug_value %6 : $MyArray + %9 = function_ref @getCount : $@convention(method) (@guaranteed MyArray) -> MyInt + %10 = apply %9(%6) : $@convention(method) (@guaranteed MyArray) -> MyInt + %15 = function_ref @getElement : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> @out MyInt + %16 = alloc_stack $MyInt + %17 = struct $MyBool() + %18 = apply %15(%16, %3, %17, %6) : $@convention(method) (MyInt, MyBool, @guaranteed MyArray) -> @out MyInt + destroy_value %6 : $MyArray + dealloc_stack %16 : $*MyInt + dealloc_stack %1 : $*MyInt + return %10 : $MyInt +} + +// CHECK-LABEL: sil [ossa] @test_escape : +// CHECK: [[GETCOUNTFUN:%.*]] = function_ref @getCount +// CHECK: [[RESULT:%.*]] = apply [[GETCOUNTFUN]] +// CHECK-LABEL: } // end sil function 'test_escape' +sil [ossa] @test_escape : $@convention(thin) () -> MyInt { +bb0: + %0 = integer_literal $Builtin.Int64, 3 + %15 = alloc_stack $MyArray + %1 = function_ref @swift_bufferAllocate : $@convention(thin) () -> @owned AnyObject + %2 = apply %1() : $@convention(thin) () -> @owned AnyObject + %3 = struct $MyInt(%0 : $Builtin.Int64) + %4 = metatype $@thin MyArray.Type + %5 = function_ref @adoptStorage : $@convention(thin) (@owned AnyObject, MyInt, @thin MyArray.Type) -> @owned (MyArray, UnsafeMutablePointer) + %6 = apply %5(%2, %3, %4) : $@convention(thin) (@owned AnyObject, MyInt, @thin MyArray.Type) -> @owned (MyArray, UnsafeMutablePointer) + (%7, %8) = destructure_tuple %6 : $(MyArray, UnsafeMutablePointer) + %copy7 = copy_value %7 : $MyArray + debug_value %7 : $MyArray + store %7 to [init] %15 : $*MyArray + %9 = function_ref @getCount : $@convention(method) (@guaranteed MyArray) -> MyInt + %10 = apply %9(%copy7) : $@convention(method) (@guaranteed MyArray) -> MyInt + destroy_value %copy7 : $MyArray + destroy_addr %15 : $*MyArray + dealloc_stack %15 : $*MyArray + return %10 : $MyInt +} + +sil [ossa] @mayWrite : $@convention(thin) (@guaranteed MyArray) -> () + +// CHECK-LABEL: sil [ossa] @test_mayWrite : +// CHECK: [[GETCOUNTFUN:%.*]] = function_ref @getCount +// CHECK: [[RESULT:%.*]] = apply [[GETCOUNTFUN]] +// CHECK-LABEL: } // end sil function 'test_mayWrite' +sil [ossa] @test_mayWrite : $@convention(thin) () -> MyInt { +bb0: + %0 = integer_literal $Builtin.Int64, 3 + %1 = function_ref @swift_bufferAllocate : $@convention(thin) () -> @owned AnyObject + %2 = apply %1() : $@convention(thin) () -> @owned AnyObject + %3 = struct $MyInt(%0 : $Builtin.Int64) + %4 = metatype $@thin MyArray.Type + %5 = function_ref @adoptStorage : $@convention(thin) (@owned AnyObject, MyInt, @thin MyArray.Type) -> @owned (MyArray, UnsafeMutablePointer) + %6 = apply %5(%2, %3, %4) : $@convention(thin) (@owned AnyObject, MyInt, @thin MyArray.Type) -> @owned (MyArray, UnsafeMutablePointer) + (%7, %8) = destructure_tuple %6 : $(MyArray, UnsafeMutablePointer) + debug_value %7 : $MyArray + %15 = function_ref @mayWrite : $@convention(thin) (@guaranteed MyArray) -> () + %16 = apply %15(%7) : $@convention(thin) (@guaranteed MyArray) -> () + %9 = function_ref @getCount : $@convention(method) (@guaranteed MyArray) -> MyInt + %10 = apply %9(%7) : $@convention(method) (@guaranteed MyArray) -> MyInt + destroy_value %7 : $MyArray + return %10 : $MyInt +} + +// We don't handle empty array allocations yet. +// CHECK-LABEL: sil [ossa] @test_initEmpty : +// CHECK: [[GETCOUNTFUN:%.*]] = function_ref @getCount +// CHECK: [[RESULT:%.*]] = apply [[GETCOUNTFUN]] +// CHECK-LABEL: } // end sil function 'test_initEmpty' +sil [ossa] @test_initEmpty : $@convention(thin) () -> MyInt { +bb0: + %4 = metatype $@thin MyArray.Type + %5 = function_ref @initEmpty : $@convention(thin) (@thin MyArray.Type) -> @owned MyArray + %6 = apply %5(%4) : $@convention(thin) (@thin MyArray.Type) -> @owned MyArray + debug_value %6 : $MyArray + %9 = function_ref @getCount : $@convention(method) (@guaranteed MyArray) -> MyInt + %10 = apply %9(%6) : $@convention(method) (@guaranteed MyArray) -> MyInt + destroy_value %6 : $MyArray + return %10 : $MyInt +} + diff --git a/test/SILOptimizer/copy_propagation.sil b/test/SILOptimizer/copy_propagation.sil index 196e118723fc3..420ac5fbb7a36 100644 --- a/test/SILOptimizer/copy_propagation.sil +++ b/test/SILOptimizer/copy_propagation.sil @@ -1,10 +1,28 @@ -// RUN: %target-sil-opt -copy-propagation -enable-sil-opaque-values -enable-sil-verify-all %s | %FileCheck %s +// RUN: %target-sil-opt -copy-propagation -canonical-ossa-rewrite-borrows -enable-sil-opaque-values -enable-sil-verify-all %s | %FileCheck %s --check-prefixes=CHECK,CHECK-OPT +// RUN: %target-sil-opt -mandatory-copy-propagation -canonical-ossa-rewrite-borrows -enable-sil-opaque-values -enable-sil-verify-all %s | %FileCheck %s --check-prefixes=CHECK,CHECK-DEBUG +// RUN: %target-sil-opt -copy-propagation -canonical-ossa-rewrite-borrows -enable-sil-opaque-values -debug-only=copy-propagation %s -o /dev/null 2>&1 | %FileCheck %s --check-prefix=CHECK-TRACE sil_stage canonical import Builtin import Swift +sil [ossa] @takeOwned : $@convention(thin) (@in T) -> () +sil [ossa] @takeMultipleOwned : $@convention(thin) (@in T, @in T) -> () +sil [ossa] @takeGuaranteed : $@convention(thin) (@in_guaranteed T) -> () +sil [ossa] @takeGuaranteedAndOwnedArg : $@convention(thin) (@in_guaranteed T, @in T) -> () + +class B { } + +class C { + var a: Int64 +} + +struct NativeObjectPair { + var obj1 : Builtin.NativeObject + var obj2 : Builtin.NativeObject +} + // Once Mem2Reg supports ownership, it will leave behind extra copies as // seen in the SIL test below for simple assignment: // public func testVarAssign(_ t: T) -> T { @@ -17,8 +35,8 @@ import Swift // CHECK: bb0(%0 : @guaranteed $T): // CHECK-NOT: destroy // CHECK: [[CPY:%.*]] = copy_value %0 : $T -// CHECK-NOT: destroy -// CHECK: return [[CPY]] : $T +// CHECK_CHECK-NOT: destroy +// CHECK_CHECK: return [[CPY]] : $T // CHECK-LABEL: } // end sil function 'testVarAssign' sil [ossa] @testVarAssign : $@convention(thin) (@in_guaranteed T) -> @out T { bb0(%0 : @guaranteed $T): @@ -28,16 +46,16 @@ bb0(%0 : @guaranteed $T): return %2 : $T } -// CHECK: sil [ossa] @multiReturnValue : $@convention(thin) (@in_guaranteed T) -> (@out T, @out T) { +// CHECK-LABEL: sil [ossa] @multiReturnValue : $@convention(thin) (@in_guaranteed T) -> (@out T, @out T) { // CHECK: bb0(%0 : @guaranteed $T): // CHECK-NOT: destroy // CHECK: [[CPY1:%.*]] = copy_value %0 : $T -// CHECK-NOT: destroy -// CHECK: [[CPY2:%.*]] = copy_value %0 : $T -// CHECK-NOT: destroy -// CHECK: [[R:%.*]] = tuple ([[CPY1]] : $T, [[CPY2]] : $T) -// CHECK-NOT: destroy -// CHECK: return [[R]] : $(T, T) +// CHECK_CHECK-NOT: destroy +// CHECK_CHECK: [[CPY2:%.*]] = copy_value %0 : $T +// CHECK_CHECK-NOT: destroy +// CHECK_CHECK: [[R:%.*]] = tuple ([[CPY1]] : $T, [[CPY2]] : $T) +// CHECK_CHECK-NOT: destroy +// CHECK_CHECK: return [[R]] : $(T, T) // CHECK-LABEL: } // end sil function 'multiReturnValue' sil [ossa] @multiReturnValue : $@convention(thin) (@in_guaranteed T) -> (@out T, @out T) { bb0(%0 : @guaranteed $T): @@ -51,12 +69,12 @@ bb0(%0 : @guaranteed $T): // CHECK-LABEL: sil [ossa] @multiCallResult : $@convention(thin) (@in_guaranteed T) -> @out T { // CHECK: bb0(%0 : @guaranteed $T): -// CHECK-NEXT: // function_ref multiReturnValue -// CHECK-NEXT: [[F:%.*]] = function_ref @multiReturnValue : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> (@out τ_0_0, @out τ_0_0) -// CHECK-NEXT: [[CALL:%.*]] = apply [[F]](%0) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> (@out τ_0_0, @out τ_0_0) -// CHECK-NEXT: ([[D1:%.*]], [[D2:%.*]]) = destructure_tuple [[CALL]] : $(T, T) -// CHECK-NEXT: destroy_value [[D2]] : $T -// CHECK-NEXT: return [[D1]] : $T +// CHECK_CHECK-NEXT: // function_ref multiReturnValue +// CHECK_CHECK-NEXT: [[F:%.*]] = function_ref @multiReturnValue : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> (@out τ_0_0, @out τ_0_0) +// CHECK_CHECK-NEXT: [[CALL:%.*]] = apply [[F]](%0) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> (@out τ_0_0, @out τ_0_0) +// CHECK_CHECK-NEXT: ([[D1:%.*]], [[D2:%.*]]) = destructure_tuple [[CALL]] : $(T, T) +// CHECK_CHECK-NEXT: destroy_value [[D2]] : $T +// CHECK_CHECK-NEXT: return [[D1]] : $T // CHECK-LABEL: } // end sil function 'multiCallResult' sil [ossa] @multiCallResult : $@convention(thin) (@in_guaranteed T) -> @out T { bb0(%0 : @guaranteed $T): @@ -75,19 +93,19 @@ bb0(%0 : @guaranteed $T): // CHECK-LABEL: sil [ossa] @testPhi : $@convention(thin) (@in_guaranteed T, @in_guaranteed T, Bool) -> @out T { // CHECK: bb0(%0 : @guaranteed $T, %1 : @guaranteed $T, %2 : $Bool): -// CHECK-NEXT: %3 = struct_extract %2 : $Bool, #Bool._value -// CHECK-NEXT: cond_br %3, bb1, bb2 +// CHECK_CHECK-NEXT: struct_extract %2 : $Bool, #Bool._value +// CHECK_CHECK-NEXT: cond_br %{{.*}}, bb1, bb2 // // CHECK: bb1: -// CHECK-NEXT: %5 = copy_value %0 : $T -// CHECK-NEXT: br bb3(%5 : $T) +// CHECK_CHECK-NEXT: copy_value %0 : $T +// CHECK_CHECK-NEXT: br bb3(% // // CHECK: bb2: -// CHECK-NEXT: %7 = copy_value %1 : $T -// CHECK-NEXT: br bb3(%7 : $T) +// CHECK_CHECK-NEXT: copy_value %1 : $T +// CHECK_CHECK-NEXT: br bb3(% // -// CHECK: bb3(%9 : @owned $T): -// CHECK-NEXT: return %9 : $T +// CHECK: bb3(% +// CHECK_CHECK-NEXT: return // CHECK-LABEL: } // end sil function 'testPhi' sil [ossa] @testPhi : $@convention(thin) (@in_guaranteed T, @in_guaranteed T, Bool) -> @out T { bb0(%0 : @guaranteed $T, %1 : @guaranteed $T, %2 : $Bool): @@ -113,23 +131,22 @@ bb3(%11 : @owned $T): // CHECK-LABEL: sil [ossa] @testConsume : $@convention(thin) (@in T, @inout T) -> () { // CHECK: bb0(%0 : @owned $T, %1 : $*T): // -// The original copy_value is deleted. -// CHECK-NEXT: debug_value %0 : $T +// Mandatory opt reuses the original copy for the consuming store. +// CHECK-DEBUG-NEXT: [[STOREVAL:%.*]] = copy_value %0 : $T // -// A new copy_value is inserted before the consuming store. -// CHECK-NEXT: %3 = copy_value %0 : $T -// CHECK-NEXT: store %3 to [assign] %1 : $*T +// CHECK-NEXT: debug_value %0 : $T +// CHECK-DEBUG-NEXT: store [[STOREVAL]] to [assign] %1 : $*T +// CHECK-OPT-NEXT: store %0 to [assign] %1 : $*T // // The non-consuming use now uses the original value. -// CHECK-NEXT: debug_value %0 : $T +// CHECK-DEBUG-NEXT: debug_value %0 : $T // -// A new destroy is inserted after the last use. -// CHECK-NEXT: destroy_value %0 : $T // CHECK-NEXT: debug_value_addr %1 : $*T // -// The original destroy is deleted. -// CHECK-NEXT: %8 = tuple () -// CHECK-NEXT: return %8 : $() +// The original destroy is deleted with optimizations enabled. +// CHECK-DEBUG-NEXT: destroy_value %0 : $T +// CHECK-NEXT: tuple () +// CHECK-NEXT: return // CHECK-LABEL: // end sil function 'testConsume' sil [ossa] @testConsume : $@convention(thin) (@in T, @inout T) -> () { bb0(%arg : @owned $T, %addr : $*T): @@ -143,29 +160,29 @@ bb0(%arg : @owned $T, %addr : $*T): return %v : $() } -// CHECK-LABEL: sil [ossa] @testDestroyEdge : $@convention(thin) (@in T, @inout T, Builtin.Int1) -> () { -// CHECK: bb0(%0 : @owned $T, %1 : $*T, %2 : $Builtin.Int1): -// CHECK-NEXT: cond_br %2, bb2, bb1 +// CHECK-LABEL: sil [ossa] @testDestroyEdge : $@convention(thin) (@in T, Builtin.Int1) -> () { +// CHECK: bb0(%0 : @owned $T, %1 : $Builtin.Int1): +// CHECK-OPT-NEXT: destroy_value %0 : $T +// CHECK-DEBUG-NEXT: cond_br %1, bb2, bb1 // // CHECK: bb1: -// -// The critical edge is split. -// CHECK-NEXT: destroy_value %0 : $T -// CHECK-NEXT: br bb3 +// Debug build inserts a new destroy +// CHECK-DEBUG-NEXT: destroy_value %0 : $T +// CHECK-NEXT: br bb3 // // CHECK: bb2: -// The original copy is deleted. -// CHECK-NEXT: debug_value %0 : $T -// CHECK-NEXT: destroy_value %0 : $T +// The original copy is deleted in both cases. +// CHECK-DEBUG-NEXT: debug_value %0 : $T +// CHECK-DEBUG-NEXT: destroy_value %0 : $T // CHECK-NEXT: br bb3 // // CHECK: bb3: -// The original destroy is deleted. -// CHECK-NEXT: %9 = tuple () -// CHECK-NEXT: return %9 : $() +// The original destroy is deleted in both cases. +// CHECK-NEXT: tuple () +// CHECK-NEXT: return // CHECK-LABEL: } // end sil function 'testDestroyEdge' -sil [ossa] @testDestroyEdge : $@convention(thin) (@in T, @inout T, Builtin.Int1) -> () { -bb0(%arg : @owned $T, %addr : $*T, %z : $Builtin.Int1): +sil [ossa] @testDestroyEdge : $@convention(thin) (@in T, Builtin.Int1) -> () { +bb0(%arg : @owned $T, %z : $Builtin.Int1): cond_br %z, bb2, bb1 bb1: @@ -183,16 +200,14 @@ bb3: return %10 : $() } -sil [ossa] @takeGuaranteedAndOwnedArg : $@convention(thin) (@in_guaranteed T, @in T) -> () - // Test the same user instruction with both @guaranteed and @owned operands taking the same copied value. // We need to keep the value alive to the end of the instruction. // // CHECK-LABEL: sil [ossa] @testGuaranteedAndOwnedArg : $@convention(thin) (@in T) -> () { // CHECK: bb0(%0 : @owned $T): +// CHECK-NEXT: [[CPY:%.*]] = copy_value %0 : $T // CHECK-NEXT: // function_ref takeGuaranteedAndOwnedArg // CHECK-NEXT: function_ref @takeGuaranteedAndOwnedArg : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0, @in τ_0_0) -> () -// CHECK-NEXT: [[CPY:%.*]] = copy_value %0 : $T // CHECK-NEXT: apply %{{.*}}(%0, [[CPY]]) : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0, @in τ_0_0) -> () // CHECK-NEXT: destroy_value %0 : $T // CHECK-NEXT: return %{{.*}} : $() @@ -205,3 +220,446 @@ bb(%0 : @owned $T): destroy_value %0 : $T return %call : $() } + +// Reuse one of the copies for the apply. Eliminate the other copy and destroy. +// Which copy is reused is unfortunately sensitive to the use list order. +// +// CHECK-TRACE-LABEL: CopyPropagation: testCopy2OperReuse +// CHECK-TRACE: Removing destroy_value %0 : $T +// CHECK-TRACE: Removing %{{.*}} = copy_value %0 : $T +// CHECK-TRACE-NOT: Removing +// +// CHECK-LABEL: sil [ossa] @testCopy2OperReuse : $@convention(thin) (@in T) -> () { +// CHECK: bb0(%0 : @owned $T): +// CHECK-NEXT: [[CP:%.*]] = copy_value %0 : $T +// CHECK-NEXT: // function_ref takeMultipleOwned +// CHECK-NEXT: function_ref @takeMultipleOwned : $@convention(thin) <τ_0_0> (@in τ_0_0, @in τ_0_0) -> () +// CHECK-NEXT: apply %{{.*}}(%0, [[CP]]) : $@convention(thin) <τ_0_0> (@in τ_0_0, @in τ_0_0) -> () +// CHECK-NEXT: tuple () +// CHECK-NEXT: return +// CHECK-LABEL: } // end sil function 'testCopy2OperReuse' +sil [ossa] @testCopy2OperReuse : $@convention(thin) (@in T) -> () { +bb0(%arg : @owned $T): + %copy1 = copy_value %arg : $T + %copy2 = copy_value %arg : $T + %f = function_ref @takeMultipleOwned : $@convention(thin) (@in T, @in T) -> () + %call = apply %f(%copy1, %copy2) : $@convention(thin) (@in T, @in T) -> () + destroy_value %arg : $T + %10 = tuple () + return %10 : $() +} + +// Reuse one copy and eliminate the other copy and destroy. +// +// CHECK-TRACE-LABEL: *** CopyPropagation: testCopy2CallReuse +// CHECK-TRACE: Removing destroy_value %0 : $T +// CHECK-TRACE: Removing %{{.*}} = copy_value %0 : $T +// CHECK-TRACE-NOT: Removing +// +// CHECK-LABEL: sil [ossa] @testCopy2CallReuse : $@convention(thin) (@in T) -> () { +// CHECK: bb0(%0 : @owned $T): +// CHECK-NEXT: [[CP:%.*]] = copy_value %0 : $T +// CHECK-NEXT: // function_ref +// CHECK-NEXT: function_ref +// CHECK-NEXT: apply %{{.*}}([[CP]]) +// CHECK-NEXT: apply %{{.*}}(%0) +// CHECK-NEXT: tuple +// CHECK-NEXT: return +// CHECK-LABEL: } // end sil function 'testCopy2CallReuse' +sil [ossa] @testCopy2CallReuse : $@convention(thin) (@in T) -> () { +bb0(%arg : @owned $T): + %copy1 = copy_value %arg : $T + %copy2 = copy_value %arg : $T + %f = function_ref @takeOwned : $@convention(thin) (@in T) -> () + %call1 = apply %f(%copy1) : $@convention(thin) (@in T) -> () + %call2 = apply %f(%copy2) : $@convention(thin) (@in T) -> () + destroy_value %arg : $T + %10 = tuple () + return %10 : $() +} + +// bb1 has a consuming instruction but is also live-out. Reuse the copy in bb1. +// +// CHECK-TRACE-LABEL: *** CopyPropagation: liveoutConsume +// CHECK-TRACE: Removing destroy_value %0 : $T +// CHECK-TRACE: Removing %{{.*}} = copy_value %0 : $T +// CHECK-TRACE-NOT: Removing +// +// CHECK-LABEL: sil [ossa] @liveoutConsume : $@convention(thin) (@owned T, Builtin.Int1) -> () { +// CHECK: bb0(%0 : @owned $T, %1 : $Builtin.Int1): +// CHECK-NOT: copy_value +// CHECK: cond_br %1, bb2, bb1 +// CHECK: bb1: +// CHECK: copy_value %0 : $T +// CHECK: apply +// CHECK: br bb3 +// CHECK: bb3: +// CHECK-NOT: copy_value +// CHECK: apply +// CHECK-NOT: destroy_value +// CHECK-LABEL: } // end sil function 'liveoutConsume' +sil [ossa] @liveoutConsume : $@convention(thin) (@owned T, Builtin.Int1) -> () { +bb0(%arg : @owned $T, %z : $Builtin.Int1): + %copy1 = copy_value %arg : $T + cond_br %z, bb2, bb1 + +bb1: + %copy2 = copy_value %arg : $T + %f1 = function_ref @takeOwned : $@convention(thin) (@in T) -> () + %call1 = apply %f1(%copy2) : $@convention(thin) (@in T) -> () + br bb3 + +bb2: + br bb3 + +bb3: + %f2 = function_ref @takeOwned : $@convention(thin) (@in T) -> () + %call2 = apply %f2(%copy1) : $@convention(thin) (@in T) -> () + destroy_value %arg : $T + %10 = tuple () + return %10 : $() +} + +// The LiveWithin block has a destroy, but it's before the first use. +// +// CHECK-TRACE-LABEL: *** CopyPropagation: testDestroyBeforeUse +// CHECK-TRACE: Removing destroy_value %1 : $T +// CHECK-TRACE: Removing %{{.*}} = copy_value %0 : $T +// +// CHECK-LABEL: sil [ossa] @testDestroyBeforeUse : $@convention(thin) (@in T) -> () { +// CHECK: bb0(%0 : @owned $T): +// CHECK-NOT: copy_value +// CHECK-NOT: destroy_value +// CHECK: apply +// CHECK-NOT: destroy_value +// CHECK: return +// CHECK-LABEL: } // end sil function 'testDestroyBeforeUse' +sil [ossa] @testDestroyBeforeUse : $@convention(thin) (@in T) -> () { +bb0(%arg : @owned $T): + %copy = copy_value %arg : $T + destroy_value %copy : $T + %f = function_ref @takeOwned : $@convention(thin) (@in T) -> () + %call2 = apply %f(%arg) : $@convention(thin) (@in T) -> () + %10 = tuple () + return %10 : $() +} + +// The LiveWithin block has a destroy, but it's after an unrelated call. +// +// CHECK-TRACE-LABEL: *** CopyPropagation: testDestroyAfterCall +// CHECK-TRACE-NOT: Removing +// +// CHECK-LABEL: sil [ossa] @testDestroyAfterCall : $@convention(thin) (@in T, @in T) -> () { +// CHECK: bb0(%0 : @owned $T, %1 : @owned $T): +// CHECK: apply %{{.*}}(%0) : $@convention(thin) <τ_0_0> (@in τ_0_0) -> () +// CHECK: destroy_value %1 : $T +// CHECK-LABEL: } // end sil function 'testDestroyAfterCall' +sil [ossa] @testDestroyAfterCall : $@convention(thin) (@in T, @in T) -> () { +bb0(%arg1 : @owned $T, %arg2 : @owned $T): + %f = function_ref @takeOwned : $@convention(thin) (@in T) -> () + %call = apply %f(%arg1) : $@convention(thin) (@in T) -> () + destroy_value %arg2 : $T + %10 = tuple () + return %10 : $() +} + +// A copy may have multiple uses +// +// CHECK-TRACE-LABEL: *** CopyPropagation: testSharedCopy +// CHECK-TRACE: Removing destroy_value %0 : $T +// CHECK-TRACE: Removing %1 = copy_value %0 : $T +// CHECK-TRACE-NOT: Removing +// +// CHECK-LABEL: sil [ossa] @testSharedCopy : $@convention(thin) (@in T) -> () { +// CHECK-NOT: copy_value +// CHECK: apply +// CHECK: apply +// CHECK-NOT: destroy_value +// CHECK-LABEL: } // end sil function 'testSharedCopy' +sil [ossa] @testSharedCopy : $@convention(thin) (@in T) -> () { +bb0(%arg : @owned $T): + %copy = copy_value %arg : $T + %f1 = function_ref @takeGuaranteed : $@convention(thin) (@in_guaranteed T) -> () + %call1 = apply %f1(%copy) : $@convention(thin) (@in_guaranteed T) -> () + %f2 = function_ref @takeOwned : $@convention(thin) (@in T) -> () + %call2 = apply %f2(%copy) : $@convention(thin) (@in T) -> () + destroy_value %arg : $T + %10 = tuple () + return %10 : $() +} + +// A copy within a borrow scope is not handled by CopyPropagation. An +// earlier pass should have hoisted the copy outside of the borrow +// scope. +// +// CHECK-TRACE-LABEL: *** CopyPropagation: testBorrowCopy +// CHECK-TRACE: Outer copy [[OUTERCOPY:%.*]] = copy_value %0 : $T +// CHECK-TRACE: Use of outer copy destroy_value +// CHECK-TRACE: Removing %{{.*}} = copy_value +// CHECK-TRACE: Removing destroy_value [[OUTERCOPY]] : $T +// CHECK-TRACE: Removing [[OUTERCOPY]] = copy_value %0 : $T +// +// CHECK-LABEL: sil [ossa] @testBorrowCopy : $@convention(thin) (@in T) -> () { +// CHECK-LABEL: bb0(%0 : @owned $T): +// CHECK-NEXT: begin_borrow %0 : $T +// CHECK-NEXT: end_borrow +// CHECK-NEXT: destroy_value %0 : $T +// CHECK-NEXT: tuple () +// CHECK-NEXT: return +// CHECK-LABEL: } +sil [ossa] @testBorrowCopy : $@convention(thin) (@in T) -> () { +bb0(%0 : @owned $T): + %3 = begin_borrow %0 : $T + %4 = copy_value %3 : $T + end_borrow %3 : $T + destroy_value %4 : $T + destroy_value %0 : $T + %17 = tuple () + return %17 : $() +} + +// CHECK-TRACE-LABEL: *** CopyPropagation: testCopyBorrow +// CHECK-TRACE: Removing destroy_value %1 : $T +// CHECK-TRACE: Removing %{{.*}} = copy_value %0 : $T +// CHECK-TRACE-NOT: Removing +// +// CHECK-LABEL: sil [ossa] @testCopyBorrow : $@convention(thin) (@in T) -> () { +// CHECK: bb0(%0 : @owned $T): +// CHECK-NEXT: %1 = begin_borrow %0 : $T +// CHECK-NEXT: end_borrow %1 : $T +// CHECK-NEXT: destroy_value %0 : $T +// CHECK-NEXT: tuple +// CHECK-NEXT: return +// CHECK-LABEL: } // end sil function 'testCopyBorrow' +sil [ossa] @testCopyBorrow : $@convention(thin) (@in T) -> () { +bb0(%0 : @owned $T): + %1 = copy_value %0 : $T + %2 = begin_borrow %1 : $T + end_borrow %2 : $T + destroy_value %1 : $T + destroy_value %0 : $T + %17 = tuple () + return %17 : $() +} + +sil @testThrows : $@convention(thin) <τ_0_0> (τ_0_0) -> (@error Error) + +// CHECK-TRACE-LABEL: *** CopyPropagation: testTryApply +// +// CHECK-LABEL: sil [ossa] @testTryApply : $@convention(thin) (@in T) -> @error Error { +// CHECK: bb0(%0 : @owned $T): +// CHECK: function_ref @testThrows : $@convention(thin) <τ_0_0> (τ_0_0) -> @error Error +// CHECK: try_apply %{{.*}}(%0) : $@convention(thin) <τ_0_0> (τ_0_0) -> @error Error, normal bb1, error bb2 +// CHECK: bb1(%3 : $()): +// CHECK: destroy_value %0 : $T +// CHECK: br bb3 +// CHECK: bb2(%{{.*}} : @owned $Error): +// CHECK: destroy_value %0 : $T +// CHECK: destroy_value %{{.*}} : $Error +// CHECK: br bb3 +// CHECK: bb3: +// CHECK-NOT: destroy +// CHECK: return +// CHECK-LABEL: } // end sil function 'testTryApply' +sil [ossa] @testTryApply : $@convention(thin) (@in T) -> (@error Error) { +bb0(%0 : @owned $T): + %1 = copy_value %0 : $T + destroy_value %0 : $T + %f = function_ref @testThrows : $@convention(thin) <τ_0_0> (τ_0_0) -> (@error Error) + try_apply %f(%1) : $@convention(thin) <τ_0_0> (τ_0_0) -> (@error Error), normal bb1, error bb2 + +bb1(%returnval : $()): + br bb3 + +bb2(%error : @owned $Error): + destroy_value %error : $Error + br bb3 + +bb3: + destroy_value %1 : $T + %17 = tuple () + return %17 : $() +} + +// ----------------------------------------------------------------------------- +// Test that convert_escape_to_noescape is a PointerEscape + +sil @closure : $@convention(thin) (@thick T.Type) -> @owned @callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> Bool for + +sil @takeClosure : $@convention(thin) (@noescape @callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for ) -> (@owned AnyObject, @error Error) + +// CHECK-TRACE-LABEL: *** CopyPropagation: testConvertFunction +// +// CHECK-LABEL: sil [ossa] @testConvertFunction : $@convention(thin) (@in_guaranteed T) -> @owned AnyObject { +// CHECK: bb0(%0 : @guaranteed $T): +// CHECK: [[CLOSURE:%.*]] = apply %{{.*}}(%{{.*}}) : $@convention(thin) <τ_0_0> (@thick τ_0_0.Type) -> @owned @callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> Bool for <τ_0_0, τ_0_0> +// CHECK: [[CONVERT:%.*]] = convert_function [[CLOSURE]] : $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> Bool for to $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for +// CHECK: [[COPY:%.*]] = copy_value [[CONVERT]] : $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for +// CHECK: [[NOESCAPE:%.*]] = convert_escape_to_noescape [[COPY]] : $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for to $@noescape @callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for +// CHECK: try_apply %{{.*}}([[NOESCAPE]]) : $@convention(thin) <τ_0_0> (@noescape @callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for <τ_0_0, τ_0_0>) -> (@owned AnyObject, @error Error), normal bb1, error bb2 +// CHECK: bb1 +// CHECK: destroy_value [[COPY]] : $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for +// CHECK: destroy_value [[CONVERT]] : $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for +// CHECK: return +// CHECK: bb2 +// CHECK: destroy_value [[COPY]] : $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for +// CHECK: unreachable +// CHECK-LABEL: } // end sil function 'testConvertFunction' +sil [ossa] @testConvertFunction : $@convention(thin) (@in_guaranteed T) -> @owned AnyObject { +bb0(%0 : @guaranteed $T): + %2 = metatype $@thick T.Type + %3 = function_ref @closure : $@convention(thin) <τ_0_0> (@thick τ_0_0.Type) -> @owned @callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> Bool for <τ_0_0, τ_0_0> + %4 = apply %3(%2) : $@convention(thin) <τ_0_0> (@thick τ_0_0.Type) -> @owned @callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> Bool for <τ_0_0, τ_0_0> + %5 = convert_function %4 : $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> Bool for to $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for + %6 = copy_value %5 : $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for + %7 = convert_escape_to_noescape %6 : $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for to $@noescape @callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for + %8 = function_ref @takeClosure : $@convention(thin) <τ_0_0> (@noescape @callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for <τ_0_0, τ_0_0>) -> (@owned AnyObject, @error Error) + try_apply %8(%7) : $@convention(thin) <τ_0_0> (@noescape @callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for <τ_0_0, τ_0_0>) -> (@owned AnyObject, @error Error), normal bb1, error bb2 + +bb1(%10 : @owned $AnyObject): + destroy_value %6 : $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for + destroy_value %5 : $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for + return %10 : $AnyObject + +bb2(%14 : @owned $Error): + destroy_value %6 : $@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_1) -> (Bool, @error Error) for + unreachable +} + +// ----------------------------------------------------------------------------- +// Test end_apply + +struct Struct { + var st: Int +} + +sil @swift_modifyAtWritableKeyPath : $@yield_once @convention(thin) <τ_0_0, τ_0_1> (@inout τ_0_0, @guaranteed WritableKeyPath<τ_0_0, τ_0_1>) -> @yields @inout τ_0_1 +sil @modifyInt : $@convention(thin) (@inout Int) -> () + +// CHECK-TRACE-LABEL: *** CopyPropagation: testBeginApply +// +// CHECK-LABEL: sil hidden [ossa] @testBeginApply : $@convention(thin) (@inout Struct) -> () { +// CHECK: begin_apply +// CHECK-NOT: destroy +// CHECK: apply +// CHECK-NOT: destroy +// CHECK: end_apply +// CHECK-NOT: destroy +// CHECK: destroy_value %{{.*}} : $WritableKeyPath +// CHECK-NOT: destroy +// CHECK-LABEL: } // end sil function 'testBeginApply' +sil hidden [ossa] @testBeginApply : $@convention(thin) (@inout Struct) -> () { +bb0(%0 : $*Struct): + %2 = keypath $WritableKeyPath, (root $Struct; stored_property #Struct.st : $Int) + debug_value %2 : $WritableKeyPath, let, name "kp" + %4 = copy_value %2 : $WritableKeyPath + %5 = function_ref @swift_modifyAtWritableKeyPath : $@yield_once @convention(thin) <τ_0_0, τ_0_1> (@inout τ_0_0, @guaranteed WritableKeyPath<τ_0_0, τ_0_1>) -> @yields @inout τ_0_1 + (%6, %7) = begin_apply %5(%0, %4) : $@yield_once @convention(thin) <τ_0_0, τ_0_1> (@inout τ_0_0, @guaranteed WritableKeyPath<τ_0_0, τ_0_1>) -> @yields @inout τ_0_1 + %8 = function_ref @modifyInt : $@convention(thin) (@inout Int) -> () + %9 = apply %8(%6) : $@convention(thin) (@inout Int) -> () + end_apply %7 + destroy_value %4 : $WritableKeyPath + destroy_value %2 : $WritableKeyPath + %13 = tuple () + return %13 : $() +} + +// FIXME: project_box is currently a PointerEscape, so box live ranges are not canonicalized. +// +// CHECK-TRACE-LABEL: *** CopyPropagation: testProjectBox +// +// CHECK-LABEL: sil [ossa] @testProjectBox : $@convention(thin) (@owned B) -> @owned B { +// CHECK: copy_value +// CHECK: destroy_value +// CHECK: destroy_value +// CHECK-LABEL: } // end sil function 'testProjectBox' +sil [ossa] @testProjectBox : $@convention(thin) (@owned B) -> @owned B { +bb0(%0 : @owned $B): + %box = alloc_box $<τ_0_0> { var τ_0_0 } + %boxadr = project_box %box : $<τ_0_0> { var τ_0_0 } , 0 + store %0 to [init] %boxadr : $*B + %load = load [copy] %boxadr : $*B + %copy = copy_value %box : $<τ_0_0> { var τ_0_0 } + destroy_value %box : $<τ_0_0> { var τ_0_0 } + destroy_value %copy : $<τ_0_0> { var τ_0_0 } + return %load : $B +} + +// FIXME: mark_dependence is currently a PointerEscape, so dependent live ranges are not canonicalized. +// +// CHECK-TRACE-LABEL: *** CopyPropagation: testMarkDependence +// +// CHECK-LABEL: sil [ossa] @testMarkDependence : $@convention(thin) (@inout Builtin.Int64, @owned B) -> Builtin.Int64 { +// CHECK: copy_value +// CHECK: destroy_value +// CHECK: destroy_value +// CHECK-LABEL: } // end sil function 'testMarkDependence' +sil [ossa] @testMarkDependence : $@convention(thin) (@inout Builtin.Int64, @owned B) -> Builtin.Int64 { +bb0(%0 : $*Builtin.Int64, %1 : @owned $B): + %ptr = mark_dependence %0 : $*Builtin.Int64 on %1 : $B + %val = load [trivial] %ptr : $*Builtin.Int64 + %copy = copy_value %1 : $B + destroy_value %1 : $B + destroy_value %copy : $B + return %val : $Builtin.Int64 +} + +// CHECK-TRACE-LABEL: *** CopyPropagation: testBitwiseEscape +// +// CHECK-LABEL: sil [ossa] @testBitwiseEscape : $@convention(thin) (@guaranteed C) -> Builtin.RawPointer { +// CHECK-NOT: copy_value +// CHECK-NOT: destroy_value +// CHECK-LABEL: } // end sil function 'testBitwiseEscape' +sil [ossa] @testBitwiseEscape : $@convention(thin) (@guaranteed C) -> Builtin.RawPointer { +bb0(%0 : @guaranteed $C): + %raw = ref_to_raw_pointer %0 : $C to $Builtin.RawPointer + %copy = copy_value %0 : $C + destroy_value %copy : $C + return %raw : $Builtin.RawPointer +} + +// CHECK-TRACE-LABEL: *** CopyPropagation: testInteriorPointer +// +// CHECK-LABEL: sil [ossa] @testInteriorPointer : $@convention(thin) (@guaranteed C) -> Int64 { +// CHECK: bb0(%0 : @guaranteed $C): +// CHECK-NEXT: begin_borrow +// CHECK-NEXT: ref_element_addr +// CHECK-NEXT: load +// CHECK-NEXT: end_borrow +// CHECK-NEXT: return +// CHECK-LABEL: } // end sil function 'testInteriorPointer' +sil [ossa] @testInteriorPointer : $@convention(thin) (@guaranteed C) -> Int64 { +bb0(%0 : @guaranteed $C): + %copy1 = copy_value %0 : $C + %borrow = begin_borrow %copy1 : $C + %adr = ref_element_addr %borrow : $C, #C.a + %val = load [trivial] %adr : $*Int64 + %copy2 = copy_value %borrow : $C + end_borrow %borrow : $C + destroy_value %copy1 : $C + destroy_value %copy2 : $C + return %val : $Int64 +} + +// CHECK-LABEL: sil [ossa] @testExtract : $@convention(thin) (@guaranteed NativeObjectPair) -> @owned Builtin.NativeObject { +// CHECK: bb0(%0 : @guaranteed $NativeObjectPair): +// CHECK-NEXT: [[B:%.*]] = begin_borrow %0 : $NativeObjectPair +// CHECK-NEXT: [[E:%.*]] = struct_extract [[B]] : $NativeObjectPair, #NativeObjectPair.obj1 +// CHECK-NEXT: [[C:%.*]] = copy_value [[E]] : $Builtin.NativeObject +// CHECK-NEXT: end_borrow +// CHECK-NEXT: return [[C]] : $Builtin.NativeObject +// CHECK-LABEL: } // end sil function 'testExtract' +sil [ossa] @testExtract : $@convention(thin) (@guaranteed NativeObjectPair) -> @owned Builtin.NativeObject { +bb0(%0 : @guaranteed $NativeObjectPair): + %copy1 = copy_value %0 : $NativeObjectPair + %borrow = begin_borrow %copy1 : $NativeObjectPair + %copy2 = copy_value %borrow : $NativeObjectPair + %val = struct_extract %borrow : $NativeObjectPair, #NativeObjectPair.obj1 + %copy3 = copy_value %val : $Builtin.NativeObject + end_borrow %borrow : $NativeObjectPair + %copy4 = copy_value %copy3 : $Builtin.NativeObject + destroy_value %copy1 : $NativeObjectPair + destroy_value %copy2 : $NativeObjectPair + destroy_value %copy3 : $Builtin.NativeObject + return %copy4 : $Builtin.NativeObject +} diff --git a/test/SILOptimizer/return.swift b/test/SILOptimizer/return.swift index e0192f812b29e..69931a32bf1b4 100644 --- a/test/SILOptimizer/return.swift +++ b/test/SILOptimizer/return.swift @@ -159,3 +159,57 @@ struct StructWithIUOinit : InitProtocol { init!(_ x: Int) { } // no missing-return error } +func testSR13753() { + // SR-13753 + let _ : () -> Int = { + var x : Int { + get { 0 } + set { } + } + x // expected-error {{missing return in a closure expected to return 'Int'; did you mean to return the last expression?}} {{5-5=return }} + // expected-warning@-1 {{setter argument 'newValue' was never used, but the property was accessed}} + // expected-note@-2 {{did you mean to use 'newValue' instead of accessing the property's current value?}} + // expected-warning@-3 {{expression resolves to an unused variable}} + } + + func f() -> Int { + var x : Int { + get { 0 } + set { } + } + x // expected-error {{missing return in a function expected to return 'Int'; did you mean to return the last expression?}} {{5-5=return }} + // expected-warning@-1 {{setter argument 'newValue' was never used, but the property was accessed}} + // expected-note@-2 {{did you mean to use 'newValue' instead of accessing the property's current value?}} + // expected-warning@-3 {{expression resolves to an unused variable}} + } + + let _ : () -> Int = { + var x : UInt { + get { 0 } + set { } + } + x + // expected-warning@-1 {{setter argument 'newValue' was never used, but the property was accessed}} + // expected-note@-2 {{did you mean to use 'newValue' instead of accessing the property's current value?}} + // expected-warning@-3 {{expression resolves to an unused variable}} + } // expected-error {{missing return in a closure expected to return 'Int'}} + + func f1() -> Int { + var x : UInt { + get { 0 } + set { } + } + x + // expected-warning@-1 {{setter argument 'newValue' was never used, but the property was accessed}} + // expected-note@-2 {{did you mean to use 'newValue' instead of accessing the property's current value?}} + // expected-warning@-3 {{expression resolves to an unused variable}} + } // expected-error {{missing return in a function expected to return 'Int'}} + + let _ : () -> Int = { + var x : Int = 0 // expected-warning {{variable 'x' was never mutated; consider changing to 'let' constant}} + var _ : Int = 0 + + x // expected-error{{missing return in a closure expected to return 'Int'; did you mean to return the last expression?}} {{5-5=return }} + //expected-warning@-1{{expression resolves to an unused variable}} + } +} diff --git a/test/attr/actorindependent.swift b/test/attr/actorindependent.swift index 157be2381bff3..facfcfc0f1c09 100644 --- a/test/attr/actorindependent.swift +++ b/test/attr/actorindependent.swift @@ -2,7 +2,6 @@ // REQUIRES: concurrency -// expected-error@+1{{'@actorIndependent' can only be applied to actor members and global/static variables}} @actorIndependent func globalFunction() { } @actorIndependent var globalComputedProperty1: Int { 17 } @@ -33,9 +32,11 @@ struct X { } class C { - // expected-error@+1{{'@actorIndependent' can only be applied to actor members and global/static variables}} @actorIndependent var property3: Int { 5 } + + @actorIndependent + func f() { } } actor class A { @@ -65,6 +66,8 @@ actor class A { set { } } + @actorIndependent init() { } + @actorIndependent func synchronousFunc() { } @@ -74,7 +77,6 @@ actor class A { @actorIndependent subscript(index: Int) -> String { "\(index)" } - // expected-error@+1{{'@actorIndependent' can only be applied to instance members of actors}} @actorIndependent static func staticFunc() { } } @@ -95,4 +97,6 @@ actor class FromProperty { // expected-error@+1{{actor-isolated property 'counter' can not be referenced from an '@actorIndependent' context}} set { counter = newValue } } -} \ No newline at end of file +} + +@actorIndependent extension FromProperty { } diff --git a/test/expr/expressions.swift b/test/expr/expressions.swift index 810a77116160b..c64a543a82f51 100644 --- a/test/expr/expressions.swift +++ b/test/expr/expressions.swift @@ -21,9 +21,9 @@ func1() _ = 4+7 var bind_test1 : () -> () = func1 -var bind_test2 : Int = 4; func1 // expected-error {{expression resolves to an unused variable}} +var bind_test2 : Int = 4; func1 // expected-warning {{expression resolves to an unused variable}} -(func1, func2) // expected-error {{expression resolves to an unused variable}} +(func1, func2) // expected-warning {{expression resolves to an unused variable}} func basictest() { // Simple integer variables. diff --git a/unittests/AST/ArithmeticEvaluator.cpp b/unittests/AST/ArithmeticEvaluator.cpp index c0377970a9e38..c04043d6f9260 100644 --- a/unittests/AST/ArithmeticEvaluator.cpp +++ b/unittests/AST/ArithmeticEvaluator.cpp @@ -265,8 +265,8 @@ TEST(ArithmeticEvaluator, Simple) { } EXPECT_EQ(productDependencies, - " `--InternallyCachedEvaluationRule(Binary: product) -> 2.461145e+02\n" - " `--InternallyCachedEvaluationRule(Binary: sum) -> 5.859870e+00\n" + " `--InternallyCachedEvaluationRule(Binary: product)\n" + " `--InternallyCachedEvaluationRule(Binary: sum)\n" " | `--InternallyCachedEvaluationRule(Literal: 3.141590e+00)\n" " | `--InternallyCachedEvaluationRule(Literal: 2.718280e+00)\n" " `--InternallyCachedEvaluationRule(Literal: 4.200000e+01)\n"); @@ -311,8 +311,8 @@ TEST(ArithmeticEvaluator, Simple) { " request_2 [label=\"ExternallyCachedEvaluationRule(Literal: 2.718280e+00)\"];\n" " request_3 [label=\"ExternallyCachedEvaluationRule(Literal: 3.141590e+00)\"];\n" " request_4 [label=\"ExternallyCachedEvaluationRule(Literal: 4.200000e+01)\"];\n" - " request_5 [label=\"InternallyCachedEvaluationRule(Binary: product) -> 2.461145e+02\"];\n" - " request_6 [label=\"InternallyCachedEvaluationRule(Binary: sum) -> 5.859870e+00\"];\n" + " request_5 [label=\"InternallyCachedEvaluationRule(Binary: product)\"];\n" + " request_6 [label=\"InternallyCachedEvaluationRule(Binary: sum)\"];\n" " request_7 [label=\"InternallyCachedEvaluationRule(Literal: 2.718280e+00)\"];\n" " request_8 [label=\"InternallyCachedEvaluationRule(Literal: 3.141590e+00)\"];\n" " request_9 [label=\"InternallyCachedEvaluationRule(Literal: 4.200000e+01)\"];\n" diff --git a/utils/build-script-impl b/utils/build-script-impl index d51920d40b707..268d23de155af 100755 --- a/utils/build-script-impl +++ b/utils/build-script-impl @@ -483,10 +483,6 @@ function set_build_options_for_host() { -DPython2_EXECUTABLE="$(xcrun -f python2.7)" -DPython3_EXECUTABLE="$(xcrun -f python3)" ) - lldb_cmake_options+=( - -DPython2_EXECUTABLE="$(xcrun -f python2.7)" - -DPython3_EXECUTABLE="$(xcrun -f python3)" - ) case ${host} in macosx-x86_64) SWIFT_HOST_TRIPLE="x86_64-apple-macosx${DARWIN_DEPLOYMENT_VERSION_OSX}" diff --git a/utils/coverage/coverage-generate-data b/utils/coverage/coverage-generate-data index 8f4fc1c58a4e5..947a26f107ea7 100755 --- a/utils/coverage/coverage-generate-data +++ b/utils/coverage/coverage-generate-data @@ -14,6 +14,7 @@ import logging import multiprocessing import os import pipes +import platform import subprocess import sys import timeit @@ -100,7 +101,7 @@ def dump_coverage_data(merged_file): """Dump coverage data of file at path `merged_file` using llvm-cov""" try: swift = os.path.join(global_build_subdir, - 'swift-macosx-x86_64/bin/swift') + 'swift-macosx-{}/bin/swift'.format(platform.machine())) coverage_log = os.path.join(os.path.dirname(merged_file), 'coverage.log') testname = os.path.basename(os.path.dirname(merged_file)) diff --git a/utils/vim/ftplugin/swift.vim b/utils/vim/ftplugin/swift.vim index 422a599e1b8bb..55d9e47b6398a 100644 --- a/utils/vim/ftplugin/swift.vim +++ b/utils/vim/ftplugin/swift.vim @@ -6,8 +6,16 @@ " See https://swift.org/LICENSE.txt for license information " See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +" Only do this when not done yet for this buffer +if exists("b:did_ftplugin") + finish +endif + +let b:did_ftplugin = 1 +let b:undo_ftplugin = "setlocal comments< expandtab< tabstop< shiftwidth< smartindent<" + setlocal comments=s1:/*,mb:*,ex:*/,:///,:// setlocal expandtab -setlocal ts=2 -setlocal sw=2 +setlocal tabstop=2 +setlocal shiftwidth=2 setlocal smartindent