From 0b86753236456d18efb620fe1cf9c2fb3ec15b25 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 8 Dec 2020 14:30:47 +0000 Subject: [PATCH 01/27] CMake: fix builds for Apple Silicon hosts --- CMakeLists.txt | 2 +- docs/HowToGuides/GettingStarted.md | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 99937910af2bc..5fac215b9eccd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -758,7 +758,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/docs/HowToGuides/GettingStarted.md b/docs/HowToGuides/GettingStarted.md index 98eb50c093400..bc61e0ad0e0ed 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 From 33bc25be80baea456a75f92f2e651adca2313893 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 8 Dec 2020 17:12:23 +0000 Subject: [PATCH 02/27] Fix remaining hardcoded references to x86_64 --- benchmark/README.md | 11 ++++++----- docs/DebuggingTheCompiler.md | 6 +++--- utils/coverage/coverage-generate-data | 3 ++- 3 files changed, 11 insertions(+), 9 deletions(-) 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/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/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)) From d24d264469d8b2f7a44c847203a16e6c4e439384 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Thu, 17 Dec 2020 22:25:34 -0800 Subject: [PATCH 03/27] Enable ArrayCountPropagation on OSSA --- .../Transforms/ArrayCountPropagation.cpp | 7 +- .../array_count_propagation_ossa.sil | 184 ++++++++++++++++++ 2 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 test/SILOptimizer/array_count_propagation_ossa.sil 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/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 +} + From 6c38a6b82aa6e1c83521bab48adbff240dfbc8ec Mon Sep 17 00:00:00 2001 From: Akshay Hegde Date: Thu, 24 Dec 2020 00:36:42 -0800 Subject: [PATCH 04/27] Conform to existing conventions for Vim ftplugin This commit adds standard conventions for Vim filetype plugins: - Allows users to opt out of using the provided ftplugin file, if they choose to install and use another set of runtime files for Swift (which offers its own version of ftplugin for Swift), It also prevents this ftplugin file from being sourced again if a ftplugin file for Swift was already sourced. Vim's documentation on this recommends offering users this option as well, under the "DISABLING" section of `:help write-filetype-plugin`). - Adds the `b:undo_ftplugin` buffer local variable, which is used to undo the filetype settings when the `:setfiletype` command is used (See :help `undo_ftplugin`). Also prefer using the full names for Vim settings instead of short ones as they are more readable. The above conventions are in place in many of the ftplugin files shipped with Vim, so they can be used as a reference, as well. --- utils/vim/ftplugin/swift.vim | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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 From 990b4ee32abcddae363d17d5f5b7a367aa01db0b Mon Sep 17 00:00:00 2001 From: Luciano Almeida Date: Mon, 28 Dec 2020 22:02:10 -0300 Subject: [PATCH 05/27] [Diagnostics] Downgrade l-value resolves to an unused variable to warning --- include/swift/AST/DiagnosticsSema.def | 4 ++-- lib/SILOptimizer/Mandatory/DataflowDiagnostics.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index c091580433301..437fc3f7a9733 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, 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, From 0638a9af3378cd01f159475cc064e2abe0352c4e Mon Sep 17 00:00:00 2001 From: Luciano Almeida Date: Mon, 28 Dec 2020 22:03:03 -0300 Subject: [PATCH 06/27] [test] Adjusting l-value resolves to unused variable test cases --- test/Parse/consecutive_statements.swift | 4 ++-- test/Parse/super.swift | 4 ++-- test/expr/expressions.swift | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) 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/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. From 634ce7de1fde38773b65effed68f405f55a76f10 Mon Sep 17 00:00:00 2001 From: Luciano Almeida Date: Mon, 28 Dec 2020 22:03:31 -0300 Subject: [PATCH 07/27] [test] Add SR-13753 test cases --- test/SILOptimizer/return.swift | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) 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}} + } +} From d2bbdb8743de3074e7e9995c1c420d70f0d972e3 Mon Sep 17 00:00:00 2001 From: Kk Shinkai Date: Sun, 3 Jan 2021 17:28:27 +0800 Subject: [PATCH 08/27] Fix a misspelling --- lib/Parse/ParseIfConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)); From a6bce7e3083ccdb40156289ffa20456629cf39f9 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 29 Dec 2020 23:52:09 -0800 Subject: [PATCH 09/27] Move SWIFT_ASSERT_ONLY to Compiler.h --- include/swift/Basic/Compiler.h | 25 +++++++++++++++++++ .../swift/SILOptimizer/Utils/PrunedLiveness.h | 10 +------- 2 files changed, 26 insertions(+), 9 deletions(-) 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/SILOptimizer/Utils/PrunedLiveness.h b/include/swift/SILOptimizer/Utils/PrunedLiveness.h index 1dee95e0a9c95..c6100e08aa54c 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,7 +132,7 @@ 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(); } From 715d81cf269ba77b9e580924621c1df97487f0e6 Mon Sep 17 00:00:00 2001 From: Saleem Abdulrasool Date: Tue, 5 Jan 2021 14:47:02 -0800 Subject: [PATCH 10/27] Platform: add a `cplusplus` requirement to XAudio In some cases when building the `XAudio` module, we would end up going down C++ paths: ``` C:\Program Files (x86)\Windows Kits\10\/Include/10.0.17763.0/um/xaudio2.h:61:26: error: 'uuid' attribute is not supported in C interface __declspec(uuid("2B02E3CF-2E0B-4ec3-BE45-1B2A3FE7210D")) IXAudio2; ^ :29:10: note: in file included from :29: ^ ``` Although this works with newer SDKs, it does not work with older SDKs. Filter out the module for the time being with a requirement on `C++`. This should be possible to use with `-enable-cxx-interop`. --- stdlib/public/Platform/winsdk.modulemap | 2 ++ 1 file changed, 2 insertions(+) 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 From 7257f2918d36e46e9f7da0efe522467b4252a106 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 22 Dec 2020 19:37:45 -0500 Subject: [PATCH 11/27] Basic: Introduce an x-macro for enumerating TypeIDZones --- include/swift/Basic/DefineTypeIDZone.h | 5 ++-- include/swift/Basic/TypeID.h | 19 +++---------- include/swift/Basic/TypeIDZones.def | 37 ++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 include/swift/Basic/TypeIDZones.def 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) From 0314f7314a4306b4b54b16a6257c2c94ed27580e Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 22 Dec 2020 19:19:10 -0500 Subject: [PATCH 12/27] AST: Use a SmallDenseSet for active request references --- include/swift/AST/EvaluatorDependencies.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/swift/AST/EvaluatorDependencies.h b/include/swift/AST/EvaluatorDependencies.h index 01f32b1a118a0..1619f24263cb8 100644 --- a/include/swift/AST/EvaluatorDependencies.h +++ b/include/swift/AST/EvaluatorDependencies.h @@ -70,8 +70,8 @@ struct DependencyRecorder { /// 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 From e4437d21875f9225d3c2d4cb010bc29708619f7f Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 22 Dec 2020 21:13:52 -0500 Subject: [PATCH 13/27] AST: Don't print cached request values when dumping dependencies This becomes tricky with the new per-request cache representation. We can add it back later if we need it, but I feel like this code path isn't particularly useful right now anyway. --- lib/AST/Evaluator.cpp | 12 ------------ unittests/AST/ArithmeticEvaluator.cpp | 8 ++++---- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/lib/AST/Evaluator.cpp b/lib/AST/Evaluator.cpp index 8886725aafc27..fd6cb3a3f7e94 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)) { 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" From 4dd781d8b4c82be31fc47d7cd76cdb9fc1506044 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 22 Dec 2020 21:14:53 -0500 Subject: [PATCH 14/27] AST: Per-request caches This is based on an earlier patch by @hamishknight. The idea is that instead of caching results in a single DenseMap that maps AnyRequest to AnyValue, we instead define a separate DenseMap for each request kind that directly uses the request as the key, and the request value as the value. This avoids type erasure and memory allocation overhead arising from the use of AnyRequest and AnyValue. There are no remaining usages of AnyValue, and the only usage of AnyRequest is now in the reference dependency tracking, which can be refactored to use a similar strategy of storing per-request maps as well. --- include/swift/AST/Evaluator.h | 19 +- include/swift/AST/RequestCache.h | 313 +++++++++++++++++++++++++++++++ 2 files changed, 323 insertions(+), 9 deletions(-) create mode 100644 include/swift/AST/RequestCache.h diff --git a/include/swift/AST/Evaluator.h b/include/swift/AST/Evaluator.h index ed5e392406e11..b8a2fa470a0cf 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,14 @@ 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); } /// Clear the cache stored within this evaluator. @@ -424,12 +425,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(); + auto known = cache.find_as(request); + if (known != cache.end()) { + auto result = known->second; recorder.replayCachedRequest(ActiveRequest(request)); - handleDependencySinkRequest(request, r); - return r; + handleDependencySinkRequest(request, result); + return result; } // Compute the result. @@ -438,7 +439,7 @@ class Evaluator { return result; // Cache the result. - cache.insert({AnyRequest(request), *result}); + cache.insert(request, *result); return result; } diff --git a/include/swift/AST/RequestCache.h b/include/swift/AST/RequestCache.h new file mode 100644 index 0000000000000..b50e0cc5f1c1d --- /dev/null +++ b/include/swift/AST/RequestCache.h @@ -0,0 +1,313 @@ +//===--- 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 request caching. +// +//===----------------------------------------------------------------------===// + +#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 + } +}; + +} // 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 From f6cd8f269625c4486575a6bba9b2621482645eb5 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 22 Dec 2020 22:26:02 -0500 Subject: [PATCH 15/27] AST: Convert some methods on DependencyRecorder to templates ... instead of passing around an ActiveRequest. --- include/swift/AST/Evaluator.h | 16 ++--- include/swift/AST/EvaluatorDependencies.h | 76 +++++++++++++++++++++-- lib/AST/Evaluator.cpp | 62 ------------------ 3 files changed, 80 insertions(+), 74 deletions(-) diff --git a/include/swift/AST/Evaluator.h b/include/swift/AST/Evaluator.h index b8a2fa470a0cf..fb40d3409bf9e 100644 --- a/include/swift/AST/Evaluator.h +++ b/include/swift/AST/Evaluator.h @@ -373,21 +373,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 @@ -399,7 +399,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; } @@ -428,7 +428,7 @@ class Evaluator { auto known = cache.find_as(request); if (known != cache.end()) { auto result = known->second; - recorder.replayCachedRequest(ActiveRequest(request)); + recorder.replayCachedRequest(request); handleDependencySinkRequest(request, result); return result; } @@ -466,7 +466,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 1619f24263cb8..d39e243472dd8 100644 --- a/include/swift/AST/EvaluatorDependencies.h +++ b/include/swift/AST/EvaluatorDependencies.h @@ -83,20 +83,24 @@ 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); private: @@ -120,6 +124,70 @@ 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({AnyRequest(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()); + } +} + } // end namespace evaluator } // end namespace swift diff --git a/lib/AST/Evaluator.cpp b/lib/AST/Evaluator.cpp index fd6cb3a3f7e94..a4a78110c5f14 100644 --- a/lib/AST/Evaluator.cpp +++ b/lib/AST/Evaluator.cpp @@ -358,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()) From 0bfa8689784a366fd7a286437010f0293c9be4cd Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 22 Dec 2020 22:44:03 -0500 Subject: [PATCH 16/27] AST: Per-request dependency maps Just as with the result cache, instead of a single DenseMap with type-erased AnyRequest keys, we can use per-request maps for a nice performance improvement. --- include/swift/AST/Evaluator.h | 1 + include/swift/AST/EvaluatorDependencies.h | 24 +++-- include/swift/AST/RequestCache.h | 124 +++++++++++++++++++++- 3 files changed, 141 insertions(+), 8 deletions(-) diff --git a/include/swift/AST/Evaluator.h b/include/swift/AST/Evaluator.h index fb40d3409bf9e..9086403bada14 100644 --- a/include/swift/AST/Evaluator.h +++ b/include/swift/AST/Evaluator.h @@ -323,6 +323,7 @@ class Evaluator { typename std::enable_if::type* = nullptr> void clearCachedOutput(const Request &request) { cache.erase(request); + recorder.clearRequest(request); } /// Clear the cache stored within this evaluator. diff --git a/include/swift/AST/EvaluatorDependencies.h b/include/swift/AST/EvaluatorDependencies.h index d39e243472dd8..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,8 +64,7 @@ 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. @@ -103,6 +103,10 @@ struct DependencyRecorder { 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. @@ -159,7 +163,7 @@ void evaluator::DependencyRecorder::endRequest(const Request &req) { // Finally, record the dependencies so we can replay them // later when the request is re-evaluated. - requestReferences.insert({AnyRequest(req), std::move(vec)}); + requestReferences.insert(std::move(req), std::move(vec)); } template @@ -169,8 +173,8 @@ void evaluator::DependencyRecorder::replayCachedRequest(const Request &req) { if (activeRequestReferences.empty()) return; - auto found = requestReferences.find_as(req); - if (found == requestReferences.end()) + auto found = requestReferences.find_as(req); + if (found == requestReferences.end()) return; activeRequestReferences.back().insert(found->second.begin(), @@ -181,13 +185,19 @@ template void evaluator::DependencyRecorder::handleDependencySourceRequest( const Request &req, SourceFile *source) { - auto found = requestReferences.find_as(req); - if (found != requestReferences.end()) { + 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 index b50e0cc5f1c1d..f7adfdcb13767 100644 --- a/include/swift/AST/RequestCache.h +++ b/include/swift/AST/RequestCache.h @@ -11,10 +11,11 @@ //===----------------------------------------------------------------------===// // // This file defines data structures to efficiently support the request -// evaluator's request caching. +// evaluator's per-request caching and dependency tracking maps. // //===----------------------------------------------------------------------===// +#include "swift/AST/DependencyCollector.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringRef.h" @@ -279,6 +280,127 @@ class RequestCache { } }; +/// 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 From 7d686cde77fe9c5bca131280acfabb66d03193ac Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Tue, 5 Jan 2021 18:02:09 -0800 Subject: [PATCH 17/27] Remove no-longer-needed external declaration of swift_dynamicCast_OLD --- stdlib/public/runtime/DynamicCast.cpp | 11 ----------- 1 file changed, 11 deletions(-) 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, From 0630981e2c2f05957681afbdebe3183a2c54cb35 Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Tue, 5 Jan 2021 18:28:39 -0800 Subject: [PATCH 18/27] Clarify existential metatype discussion, add a caveat about function casting --- docs/DynamicCasting.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 { From f22d0855df15f91691367bf8bbf5d4b35287a1a5 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Wed, 30 Dec 2020 00:12:56 -0800 Subject: [PATCH 19/27] Add a CanonicalOSSALifetime utility. Canonicalizing OSSA provably minimizes the number of retains and releases within the boundaries of that lifetime. This eliminates the need for ad-hoc optimization of OSSA copies. This initial implementation only canonicalizes owned values, but canonicalizing guaranteed values is a simple extension. This was originally part of the CopyPropagation prototype years ago. Now OSSA is specified completely enough that it can be turned into a simple utility instead. CanonicalOSSALifetime uses PrunedLiveness to find the extended live range and identify the consumes on the boundary. All other consumes need their own copy. No other copies are needed. By running this after other transformations that affect OSSA lifetimes, we can avoid the need to run pattern-matching optimization to SemanticARC to recover from suboptimal patterns, which is not robust, maintainable, or efficient. --- .../Utils/CanonicalOSSALifetime.h | 304 ++++++++ .../swift/SILOptimizer/Utils/PrunedLiveness.h | 9 + lib/SIL/IR/OperandOwnership.cpp | 11 +- .../Transforms/CopyPropagation.cpp | 698 +----------------- lib/SILOptimizer/Utils/CMakeLists.txt | 1 + .../Utils/CanonicalOSSALifetime.cpp | 622 ++++++++++++++++ 6 files changed, 973 insertions(+), 672 deletions(-) create mode 100644 include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h create mode 100644 lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp diff --git a/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h b/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h new file mode 100644 index 0000000000000..828819010dfd6 --- /dev/null +++ b/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h @@ -0,0 +1,304 @@ +//===--- 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. + /// + /// This use-def walk must be consistent with the def-use walks performed + /// within the canonicalizeValueLifetime() implementation. + static SILValue getCanonicalCopiedDef(SILValue v) { + while (true) { + if (auto *copy = dyn_cast(v)) { + v = copy->getOperand(); + continue; + } + return 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 c6100e08aa54c..7d0bb62ed459d 100644 --- a/include/swift/SILOptimizer/Utils/PrunedLiveness.h +++ b/include/swift/SILOptimizer/Utils/PrunedLiveness.h @@ -139,6 +139,8 @@ class PrunedLiveBlocks { 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); @@ -204,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/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/Transforms/CopyPropagation.cpp b/lib/SILOptimizer/Transforms/CopyPropagation.cpp index f1ace6307d8dd..2e2a281698a19 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,665 +13,29 @@ /// 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. -//===----------------------------------------------------------------------===// - -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; - -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); - - 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. @@ -681,36 +45,40 @@ 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*/ true); 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(); } 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..cdfa01aaa4656 --- /dev/null +++ b/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp @@ -0,0 +1,622 @@ +//===--- 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"); + +//===----------------------------------------------------------------------===// +// 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 + // insteresting. + 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(); + } + }) From 02784a2dc299bb792e10bf54ec69fc6aa341cb94 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 4 Jan 2021 17:59:19 -0800 Subject: [PATCH 20/27] Add a variation on CopyPropagation to handle debug_value correctly. MandatoryCopyPropagation must be a separate pass in order to preserve all debug_value instructions. CopyPropagation cannot preserve debug_value because, as a rule, debug information cannot affect -O behavior. --- .../swift/SILOptimizer/PassManager/Passes.def | 4 +++- lib/SILOptimizer/Transforms/CopyPropagation.cpp | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) 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/lib/SILOptimizer/Transforms/CopyPropagation.cpp b/lib/SILOptimizer/Transforms/CopyPropagation.cpp index 2e2a281698a19..5ac4cb4cd7b49 100644 --- a/lib/SILOptimizer/Transforms/CopyPropagation.cpp +++ b/lib/SILOptimizer/Transforms/CopyPropagation.cpp @@ -38,6 +38,12 @@ using namespace swift; namespace { class CopyPropagation : public SILFunctionTransform { + /// True if debug_value instructions should be pruned. + bool pruneDebug; + +public: + CopyPropagation(bool pruneDebug): pruneDebug(pruneDebug) {} + /// The entry point to this function transformation. void run() override; }; @@ -64,7 +70,7 @@ void CopyPropagation::run() { } } // Perform copy propgation for each copied value. - CanonicalizeOSSALifetime canonicalizer(/*pruneDebug*/ true); + CanonicalizeOSSALifetime canonicalizer(pruneDebug); for (auto &def : copiedDefs) { canonicalizer.canonicalizeValueLifetime(def); if (SILValue outerCopy = canonicalizer.createdOuterCopy()) { @@ -81,4 +87,10 @@ void CopyPropagation::run() { } } -SILTransform *swift::createCopyPropagation() { return new CopyPropagation(); } +SILTransform *swift::createCopyPropagation() { + return new CopyPropagation(/*pruneDebug*/ true); +} + +SILTransform *swift::createMandatoryCopyPropagation() { + return new CopyPropagation(/*pruneDebug*/ false); +} From af8d931486504bf78f6761c33c903f93dc0cbd18 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 5 Jan 2021 21:58:18 -0800 Subject: [PATCH 21/27] [Concurrency] Loosen constraints on @actorIndependent placement. @actorIndependent is needed on declarations outside of actors to (e.g.) disable inference of a global actor. It is also effectively the default, so allow it to be specified explicitly. --- include/swift/AST/Attr.def | 5 +++-- include/swift/AST/DiagnosticsSema.def | 7 ------- lib/Sema/TypeCheckAttr.cpp | 21 +++------------------ test/attr/actorindependent.swift | 12 ++++++++---- 4 files changed, 14 insertions(+), 31 deletions(-) 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..de45fc0f84dc2 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -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/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/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 { } From f2ccd171aa00953c5552e53343e164a66761fde5 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 5 Jan 2021 22:15:11 -0800 Subject: [PATCH 22/27] CanonicalOSSALifetime: canonicalize guaranteed values. If a guaranteed value is not a recognized and handled borrow introducer, then treat the copy as a separate owned live range. --- .../Utils/CanonicalOSSALifetime.h | 13 +------ .../Utils/CanonicalOSSALifetime.cpp | 38 ++++++++++++++++--- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h b/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h index 828819010dfd6..7748d6597fa30 100644 --- a/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h +++ b/include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h @@ -180,18 +180,7 @@ class CanonicalOSSAConsumeInfo { class CanonicalizeOSSALifetime { public: /// Find the original definition of a potentially copied value. - /// - /// This use-def walk must be consistent with the def-use walks performed - /// within the canonicalizeValueLifetime() implementation. - static SILValue getCanonicalCopiedDef(SILValue v) { - while (true) { - if (auto *copy = dyn_cast(v)) { - v = copy->getOperand(); - continue; - } - return v; - } - } + static SILValue getCanonicalCopiedDef(SILValue v); private: /// If true, then debug_value instructions outside of non-debug diff --git a/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp b/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp index cdfa01aaa4656..38ed6015bbe92 100644 --- a/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp +++ b/lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp @@ -21,11 +21,11 @@ /// destroys. Initializes `liveness`. /// /// 2. Find `def`s final destroy points based on its pruned -/// liveness. Initializes `consumes` and inserts new destroy_value -/// instructions. +/// 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. +/// 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. /// @@ -57,6 +57,34 @@ 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 //===----------------------------------------------------------------------===// @@ -224,7 +252,7 @@ bool CanonicalizeOSSALifetime::computeCanonicalLiveness() { if (pruneDebug) { if (auto *dvi = dyn_cast(user)) { // Only instructions potentially outside current pruned liveness are - // insteresting. + // interesting. if (liveness.getBlockLiveness(dvi->getParent()) != PrunedLiveBlocks::LiveOut) { recordDebugValue(dvi); From 16b90b7b90abb7d7ad8c8323d0468f70fb23b198 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 4 Jan 2021 17:54:49 -0800 Subject: [PATCH 23/27] Update and add unit tests for CopyPropagation coverage. --- test/SILOptimizer/copy_propagation.sil | 564 ++++++++++++++++++++++--- 1 file changed, 511 insertions(+), 53 deletions(-) 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 +} From d8d3c482b82312abdcf0c1efe1f893f40dac32bb Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Wed, 6 Jan 2021 09:14:57 -0800 Subject: [PATCH 24/27] Update the job name in the README from OS X to macOS --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)| From 6a61d119e8ba9b9d75ee6985496383595d3082e2 Mon Sep 17 00:00:00 2001 From: Mishal Shah Date: Wed, 6 Jan 2021 09:32:16 -0800 Subject: [PATCH 25/27] Update the CI doc with new trigger info OS X -> macOS --- docs/ContinuousIntegration.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) 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 From 47df40df1ed19e3651df282397212cee2ac7f7f3 Mon Sep 17 00:00:00 2001 From: Mike Ash Date: Wed, 6 Jan 2021 13:21:46 -0500 Subject: [PATCH 26/27] [Test] Don't run protocol_conformance_collision.swift against older runtimes. rdar://72856188 --- test/Runtime/protocol_conformance_collision.swift | 2 ++ 1 file changed, 2 insertions(+) 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 From d4c17cd9816f8f27bfdcece9358d5379f3bf4365 Mon Sep 17 00:00:00 2001 From: Jonas Devlieghere Date: Wed, 6 Jan 2021 15:01:00 -0800 Subject: [PATCH 27/27] Revert "[build] Use Xcode toolchain Python executables" --- utils/build-script-impl | 4 ---- 1 file changed, 4 deletions(-) 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}"