From 504b76a7ab79aff5d1f164788f28646b2f38b9e4 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 16 Jun 2023 22:53:27 -0700 Subject: [PATCH 01/24] [Macros] Add test using observer accessors (didSet/willSet) with Observable At one point this gave us trouble, but it's since been fixed. Add the test to make sure we don't regress. Covers rdar://109231047. --- .../Observation/ObservableDidSetWillSet.swift | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/stdlib/Observation/ObservableDidSetWillSet.swift diff --git a/test/stdlib/Observation/ObservableDidSetWillSet.swift b/test/stdlib/Observation/ObservableDidSetWillSet.swift new file mode 100644 index 0000000000000..6004bdc56ce4e --- /dev/null +++ b/test/stdlib/Observation/ObservableDidSetWillSet.swift @@ -0,0 +1,44 @@ +// REQUIRES: swift_swift_parser, executable_test + +// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking -enable-experimental-feature InitAccessors -enable-experimental-feature Macros -Xfrontend -plugin-path -Xfrontend %swift-host-lib-dir/plugins) | %FileCheck %s + +// Asserts is required for '-enable-experimental-feature InitAccessors'. +// REQUIRES: asserts + +// REQUIRES: observation +// REQUIRES: concurrency +// REQUIRES: objc_interop +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: back_deployment_runtime + +import _Observation + +@Observable +public class Model { + public enum State { + case initializing + case running + case complete + } + + public var state: State = .initializing { + willSet { + print("new state=\(String(describing: newValue))") + } + + didSet { + guard oldValue != state else { return } + print("old state=\(String(describing: oldValue))") + } + } +} + + +let m = Model() + +// CHECK: new state=running +// CHECK: old state=initializing +m.state = .running +// CHECK: new state=complete +// CHECK: old state=running +m.state = .complete From b08eb625695d00b09ed1a148928e5a574addf40b Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Wed, 31 May 2023 13:33:02 -0700 Subject: [PATCH 02/24] Rename SinkAddressProjections::projections -> SinkAddressProjections::oldProjections --- .../SILOptimizer/Utils/BasicBlockOptUtils.h | 2 +- lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h b/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h index d9786c4f3458f..563b5453da713 100644 --- a/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h +++ b/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h @@ -135,7 +135,7 @@ bool canCloneTerminator(TermInst *termInst); /// BasicBlockCloner handles this internally. class SinkAddressProjections { // Projections ordered from last to first in the chain. - SmallVector projections; + SmallVector oldProjections; SmallSetVector inBlockDefs; // Transient per-projection data for use during cloning. diff --git a/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp b/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp index a404bb16ebc9b..b860ac7a9b9c5 100644 --- a/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp +++ b/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp @@ -223,7 +223,7 @@ void BasicBlockCloner::sinkAddressProjections() { // // Return true on success, even if projections is empty. bool SinkAddressProjections::analyzeAddressProjections(SILInstruction *inst) { - projections.clear(); + oldProjections.clear(); inBlockDefs.clear(); SILBasicBlock *bb = inst->getParent(); @@ -237,7 +237,7 @@ bool SinkAddressProjections::analyzeAddressProjections(SILInstruction *inst) { } if (auto *addressProj = dyn_cast(def)) { if (addressProj->isPure()) { - projections.push_back(addressProj); + oldProjections.push_back(addressProj); return true; } } @@ -252,12 +252,12 @@ bool SinkAddressProjections::analyzeAddressProjections(SILInstruction *inst) { return false; } // Recurse upward through address projections. - for (unsigned idx = 0; idx < projections.size(); ++idx) { + for (unsigned idx = 0; idx < oldProjections.size(); ++idx) { // Only one address result/operand can be handled per instruction. - if (projections.size() != idx + 1) + if (oldProjections.size() != idx + 1) return false; - for (SILValue operandVal : projections[idx]->getOperandValues()) + for (SILValue operandVal : oldProjections[idx]->getOperandValues()) if (!pushOperandVal(operandVal)) return false; } @@ -267,13 +267,13 @@ bool SinkAddressProjections::analyzeAddressProjections(SILInstruction *inst) { // Clone the projections gathered by 'analyzeAddressProjections' at // their use site outside this block. bool SinkAddressProjections::cloneProjections() { - if (projections.empty()) + if (oldProjections.empty()) return false; - SILBasicBlock *bb = projections.front()->getParent(); + SILBasicBlock *bb = oldProjections.front()->getParent(); // Clone projections in last-to-first order. - for (unsigned idx = 0; idx < projections.size(); ++idx) { - auto *oldProj = projections[idx]; + for (unsigned idx = 0; idx < oldProjections.size(); ++idx) { + auto *oldProj = oldProjections[idx]; assert(oldProj->getParent() == bb); // Reset transient per-projection sets. usesToReplace.clear(); From 56629e13006e6742c2989cb6bdf5e2f5e81715ef Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Wed, 31 May 2023 15:29:35 -0700 Subject: [PATCH 03/24] Verify we don't have any address phis --- lib/SIL/Verifier/SILVerifier.cpp | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index a37a3dffac796..2ec75058f195a 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -1065,24 +1065,6 @@ class SILVerifier : public SILVerifierBase { return InstNumbers[a] < InstNumbers[b]; } - // FIXME: For sanity, address-type phis should be prohibited at all SIL - // stages. However, the optimizer currently breaks the invariant in three - // places: - // 1. Normal Simplify CFG during conditional branch simplification - // (sneaky jump threading). - // 2. Simplify CFG via Jump Threading. - // 3. Loop Rotation. - // - // BasicBlockCloner::canCloneInstruction and sinkAddressProjections is - // designed to avoid this issue, we just need to make sure all passes use it - // correctly. - // - // Minimally, we must prevent address-type phis as long as access markers are - // preserved. A goal is to preserve access markers in OSSA. - bool prohibitAddressPhis() { - return F.hasOwnership(); - } - void visitSILPhiArgument(SILPhiArgument *arg) { // Verify that the `isPhiArgument` property is sound: // - Phi arguments come from branches. @@ -1106,7 +1088,7 @@ class SILVerifier : public SILVerifierBase { "All phi argument inputs must be from branches."); } } - if (arg->isPhi() && prohibitAddressPhis()) { + if (arg->isPhi()) { // As a property of well-formed SIL, we disallow address-type // phis. Supporting them would prevent reliably reasoning about the // underlying storage of memory access. This reasoning is important for From 37d858694d772fed1c7c64830e311f817f4d9587 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Wed, 21 Jun 2023 12:18:21 -0700 Subject: [PATCH 04/24] Update SILSSAUpdater::areIdentical to check for SILArgument --- lib/SILOptimizer/Utils/SILSSAUpdater.cpp | 26 ++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/SILOptimizer/Utils/SILSSAUpdater.cpp b/lib/SILOptimizer/Utils/SILSSAUpdater.cpp index b3f6084d42cc7..2badec4371fc8 100644 --- a/lib/SILOptimizer/Utils/SILSSAUpdater.cpp +++ b/lib/SILOptimizer/Utils/SILSSAUpdater.cpp @@ -86,20 +86,30 @@ areIdentical(llvm::DenseMap &availableValues) { return true; } - auto *mvir = - dyn_cast(availableValues.begin()->second); - if (!mvir) - return false; + if (auto *mvir = dyn_cast( + availableValues.begin()->second)) { + for (auto value : availableValues) { + auto *result = dyn_cast(value.second); + if (!result) + return false; + if (!result->getParent()->isIdenticalTo(mvir->getParent()) || + result->getIndex() != mvir->getIndex()) { + return false; + } + } + return true; + } + auto *firstArg = cast(availableValues.begin()->second); for (auto value : availableValues) { - auto *result = dyn_cast(value.second); - if (!result) + auto *arg = dyn_cast(value.second); + if (!arg) return false; - if (!result->getParent()->isIdenticalTo(mvir->getParent()) || - result->getIndex() != mvir->getIndex()) { + if (arg != firstArg) { return false; } } + return true; } From 46bed7c6170ac17d8e036ef0a9dab6d338774f85 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Wed, 21 Jun 2023 12:19:19 -0700 Subject: [PATCH 05/24] Add SinkAddressProjections::newProjections --- include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h | 7 +++++++ lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h b/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h index 563b5453da713..1ae6d8ebd359f 100644 --- a/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h +++ b/include/swift/SILOptimizer/Utils/BasicBlockOptUtils.h @@ -136,6 +136,8 @@ bool canCloneTerminator(TermInst *termInst); class SinkAddressProjections { // Projections ordered from last to first in the chain. SmallVector oldProjections; + // Cloned projections to avoid address phis. + SmallVectorImpl *newProjections; SmallSetVector inBlockDefs; // Transient per-projection data for use during cloning. @@ -143,6 +145,10 @@ class SinkAddressProjections { llvm::SmallDenseMap firstBlockUse; public: + SinkAddressProjections( + SmallVectorImpl *newProjections = nullptr) + : newProjections(newProjections) {} + /// Check for an address projection chain ending at \p inst. Return true if /// the given instruction is successfully analyzed. /// @@ -163,6 +169,7 @@ class SinkAddressProjections { ArrayRef getInBlockDefs() const { return inBlockDefs.getArrayRef(); } + /// Clone the chain of projections at their use sites. /// /// Return true if anything was done. diff --git a/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp b/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp index b860ac7a9b9c5..09c871fb683cb 100644 --- a/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp +++ b/lib/SILOptimizer/Utils/BasicBlockOptUtils.cpp @@ -297,9 +297,12 @@ bool SinkAddressProjections::cloneProjections() { auto *useBB = use->getUser()->getParent(); auto *firstUse = firstBlockUse.lookup(useBB); SingleValueInstruction *newProj; - if (use == firstUse) + if (use == firstUse) { newProj = cast(oldProj->clone(use->getUser())); - else { + if (newProjections) { + newProjections->push_back(newProj); + } + } else { newProj = cast(firstUse->get()); assert(newProj->getParent() == useBB); newProj->moveFront(useBB); From 1e89ad603520d86137159973326499afff1eea63 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Wed, 21 Jun 2023 12:20:33 -0700 Subject: [PATCH 06/24] Sink address projections in ArrayPropertyOpt so that there are no address phis --- .../LoopTransforms/ArrayPropertyOpt.cpp | 55 ++++++-- test/SILOptimizer/array_property_opt.sil | 133 ++++++++++++++++++ 2 files changed, 177 insertions(+), 11 deletions(-) diff --git a/lib/SILOptimizer/LoopTransforms/ArrayPropertyOpt.cpp b/lib/SILOptimizer/LoopTransforms/ArrayPropertyOpt.cpp index 92c5b607745eb..cb9badf559f8e 100644 --- a/lib/SILOptimizer/LoopTransforms/ArrayPropertyOpt.cpp +++ b/lib/SILOptimizer/LoopTransforms/ArrayPropertyOpt.cpp @@ -53,19 +53,19 @@ #define DEBUG_TYPE "array-property-opt" #include "ArrayOpt.h" +#include "swift/SIL/CFG.h" +#include "swift/SIL/DebugUtils.h" +#include "swift/SIL/InstructionUtils.h" +#include "swift/SIL/LoopInfo.h" +#include "swift/SIL/Projection.h" +#include "swift/SIL/SILCloner.h" #include "swift/SILOptimizer/Analysis/ArraySemantic.h" #include "swift/SILOptimizer/Analysis/LoopAnalysis.h" #include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h" #include "swift/SILOptimizer/Utils/CFGOptUtils.h" #include "swift/SILOptimizer/Utils/LoopUtils.h" #include "swift/SILOptimizer/Utils/SILSSAUpdater.h" -#include "swift/SIL/CFG.h" -#include "swift/SIL/DebugUtils.h" -#include "swift/SIL/InstructionUtils.h" -#include "swift/SIL/Projection.h" -#include "swift/SIL/LoopInfo.h" -#include "swift/SIL/BasicBlockBits.h" -#include "swift/SIL/SILCloner.h" #include "llvm/ADT/SmallSet.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" @@ -86,6 +86,8 @@ class ArrayPropertiesAnalysis { SILBasicBlock *Preheader; DominanceInfo *DomTree; + SinkAddressProjections sinkProj; + llvm::DenseMap InstCountCache; llvm::SmallSet HoistableArray; @@ -169,6 +171,7 @@ class ArrayPropertiesAnalysis { bool FoundHoistable = false; uint32_t LoopInstCount = 0; + for (auto *BB : Loop->getBlocks()) { for (auto &Inst : *BB) { // Can't clone alloc_stack instructions whose dealloc_stack is outside @@ -176,6 +179,10 @@ class ArrayPropertiesAnalysis { if (!canDuplicateLoopInstruction(Loop, &Inst)) return false; + if (!sinkProj.analyzeAddressProjections(&Inst)) { + return false; + } + ArraySemanticsCall ArrayPropsInst(&Inst, "array.props", true); if (!ArrayPropsInst) continue; @@ -512,10 +519,11 @@ class RegionCloner : public SILCloner { SILSSAUpdater &SSAUp) { // Collect outside uses. SmallVector UseList; - for (auto Use : V->getUses()) + for (auto Use : V->getUses()) { if (!isBlockCloned(Use->getUser()->getParent())) { UseList.push_back(UseWrapper(Use)); } + } if (UseList.empty()) return; @@ -532,15 +540,40 @@ class RegionCloner : public SILCloner { void updateSSAForm() { SILSSAUpdater SSAUp; + SmallVector newProjections; + SinkAddressProjections sinkProj(&newProjections); + for (auto *origBB : originalPreorderBlocks()) { // Update outside used phi values. - for (auto *arg : origBB->getArguments()) + for (auto *arg : origBB->getArguments()) { updateSSAForValue(origBB, arg, SSAUp); + } // Update outside used instruction values. for (auto &inst : *origBB) { - for (auto result : inst.getResults()) - updateSSAForValue(origBB, result, SSAUp); + for (auto result : inst.getResults()) { + bool success = sinkProj.analyzeAddressProjections(&inst); + assert(success); + // Sink address projections by cloning to avoid address phis. + sinkProj.cloneProjections(); + + // If no new projections were created, update ssa for the result only. + if (newProjections.empty()) { + updateSSAForValue(origBB, result, SSAUp); + continue; + } + + for (auto *newProj : newProjections) { + // Operand values of new projections may need ssa update. + for (auto opVal : newProj->getOperandValues()) { + if (!isBlockCloned(opVal->getParentBlock())) { + continue; + } + updateSSAForValue(origBB, opVal, SSAUp); + } + } + newProjections.clear(); + } } } } diff --git a/test/SILOptimizer/array_property_opt.sil b/test/SILOptimizer/array_property_opt.sil index 0af8872442a94..bffc6d3c9a0c9 100644 --- a/test/SILOptimizer/array_property_opt.sil +++ b/test/SILOptimizer/array_property_opt.sil @@ -1,5 +1,9 @@ // RUN: %target-sil-opt -parse-serialized-sil -enable-sil-verify-all %s -array-property-opt | %FileCheck %s +// Linux doesn't have the same symbol name for _ArrayBuffer, which is part of +// the ObjC runtime interop. Use `_ContiguousArrayBuffer instead. +// REQUIRES: objc_interop + sil_stage canonical import Builtin @@ -258,3 +262,132 @@ bb10: // Exit dominated by bb3 bb11: // Non-exit dominated by bb1 return %4 : $Builtin.Int1 } + +class Klass { + var val: Optional +} + +struct WrapperStruct { + var val: Klass +} + +sil @use_klass : $@convention(thin) (@in_guaranteed Klass) -> () + +sil @test_sink_address_proj : $@convention(thin) (@inout MyArray, @in_guaranteed WrapperStruct) -> () { +bb0(%0 : $*MyArray, %1 : $*WrapperStruct): + %3 = load %0 : $*MyArray + br bb1 + +bb1: + %2 = function_ref @arrayPropertyIsNative : $@convention(method) (@owned MyArray) -> Bool + retain_value %3 : $MyArray + %5 = apply %2(%3) : $@convention(method) (@owned MyArray) -> Bool + %ele = struct_element_addr %1 : $*WrapperStruct, #WrapperStruct.val + cond_br undef, bb5, bb2 + +bb2: + %6 = integer_literal $Builtin.Int1, -1 + cond_br %6, bb3, bb4 + +bb3: + br bb1 + +bb4: + br bb6 + +bb5: + %f = function_ref @use_klass : $@convention(thin) (@in_guaranteed Klass) -> () + %a = apply %f(%ele) : $@convention(thin) (@in_guaranteed Klass) -> () + br bb6 + +bb6: + %t = tuple () + return %t : $() +} + +sil [_semantics "array.props.isNativeTypeChecked"] @hoistableIsNativeTypeChecked : $@convention(method) (@guaranteed Array) -> Bool +sil [_semantics "array.get_element"] @getElement : $@convention(method) (Int, Bool, _DependenceToken, @guaranteed Array) -> @owned Klass +sil [_semantics "array.get_count"] @getCount : $@convention(method) (@guaranteed Array) -> Int + +sil hidden @test_array_prop_opt : $@convention(thin) (@guaranteed Optional>) -> Int { +bb0(%0 : $Optional>): + %4 = integer_literal $Builtin.Int64, 0 + switch_enum %0 : $Optional>, case #Optional.some!enumelt: bb2, case #Optional.none!enumelt: bb1 + +bb1: + br bb12(%4 : $Builtin.Int64) + +bb2(%12 : $Array): + %14 = function_ref @getCount : $@convention(method) (@guaranteed Array) -> Int + retain_value %0 : $Optional> + retain_value %0 : $Optional> + %17 = apply %14(%12) : $@convention(method) (@guaranteed Array) -> Int + %18 = struct_extract %17 : $Int, #Int._value + %19 = builtin "cmp_eq_Int64"(%18 : $Builtin.Int64, %4 : $Builtin.Int64) : $Builtin.Int1 + cond_br %19, bb3, bb4 + +bb3: + release_value %0 : $Optional> + %22 = unchecked_enum_data %0 : $Optional>, #Optional.some!enumelt + %23 = struct_extract %22 : $Array, #Array._buffer + %24 = struct_extract %23 : $_ArrayBuffer, #_ArrayBuffer._storage + %25 = struct_extract %24 : $_BridgeStorage<__ContiguousArrayStorageBase>, #_BridgeStorage.rawValue + strong_release %25 : $Builtin.BridgeObject + br bb12(%4 : $Builtin.Int64) + +bb4: + %28 = function_ref @hoistableIsNativeTypeChecked : $@convention(method) (@guaranteed Array) -> Bool + %29 = function_ref @getElement : $@convention(method) (Int, Bool, _DependenceToken, @guaranteed Array) -> @owned Klass + %30 = integer_literal $Builtin.Int64, 1 + %31 = integer_literal $Builtin.Int1, -1 + %32 = struct $_DependenceToken () + br bb5(%4 : $Builtin.Int64) + +bb5(%34 : $Builtin.Int64): + %35 = struct $Int (%34 : $Builtin.Int64) + %36 = apply %28(%12) : $@convention(method) (@guaranteed Array) -> Bool + %37 = apply %29(%35, %36, %32, %12) : $@convention(method) (Int, Bool, _DependenceToken, @guaranteed Array) -> @owned Klass + %38 = builtin "sadd_with_overflow_Int64"(%34 : $Builtin.Int64, %30 : $Builtin.Int64, %31 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1) + %39 = tuple_extract %38 : $(Builtin.Int64, Builtin.Int1), 0 + %40 = tuple_extract %38 : $(Builtin.Int64, Builtin.Int1), 1 + cond_fail %40 : $Builtin.Int1, "arithmetic overflow" + %43 = ref_element_addr %37 : $Klass, #Klass.val + %44 = begin_access [read] [dynamic] [no_nested_conflict] %43 : $*Optional + %45 = load %44 : $*Optional + end_access %44 : $*Optional + switch_enum %45 : $Optional, case #Optional.some!enumelt: bb9, case #Optional.none!enumelt: bb6 + +bb6: + strong_release %37 : $Klass + %49 = builtin "cmp_eq_Int64"(%39 : $Builtin.Int64, %18 : $Builtin.Int64) : $Builtin.Int1 + cond_br %49, bb7, bb8 + +bb7: + release_value %0 : $Optional> + release_value %0 : $Optional> + br bb12(%4 : $Builtin.Int64) + +bb8: + br bb5(%39 : $Builtin.Int64) + +bb9: + release_value %0 : $Optional> + %57 = begin_access [read] [dynamic] [no_nested_conflict] %43 : $*Optional + %58 = load %57 : $*Optional + end_access %57 : $*Optional + switch_enum %58 : $Optional, case #Optional.some!enumelt: bb11, case #Optional.none!enumelt: bb10 + +bb10: + cond_fail %31 : $Builtin.Int1, "Unexpectedly found nil while unwrapping an Optional value" + unreachable + +bb11(%63 : $Int): + release_value %0 : $Optional> + strong_release %37 : $Klass + %66 = struct_extract %63 : $Int, #Int._value + br bb12(%66 : $Builtin.Int64) + +bb12(%69 : $Builtin.Int64): + %70 = struct $Int (%69 : $Builtin.Int64) + return %70 : $Int +} From 7964918cb2c7faf5a209aad040215750d9debfc1 Mon Sep 17 00:00:00 2001 From: zoecarver Date: Wed, 31 May 2023 10:15:14 -0700 Subject: [PATCH 07/24] [cxx-interop] Fix sema lookup scope so we find NSNotification interface even in C++ mode. --- lib/ClangImporter/ClangAdapter.cpp | 2 +- .../Cxx/objc-correctness/Inputs/nsnotification-bridging.h | 3 +++ .../nsnotification-bridging-ide-test.swift | 8 ++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 test/Interop/Cxx/objc-correctness/Inputs/nsnotification-bridging.h create mode 100644 test/Interop/Cxx/objc-correctness/nsnotification-bridging-ide-test.swift diff --git a/lib/ClangImporter/ClangAdapter.cpp b/lib/ClangImporter/ClangAdapter.cpp index 06da77966d051..bbc4335ca9a4b 100644 --- a/lib/ClangImporter/ClangAdapter.cpp +++ b/lib/ClangImporter/ClangAdapter.cpp @@ -554,7 +554,7 @@ clang::TypedefNameDecl *importer::findSwiftNewtype(const clang::NamedDecl *decl, clang::LookupResult lookupResult(clangSema, notificationName, clang::SourceLocation(), clang::Sema::LookupOrdinaryName); - if (!clangSema.LookupName(lookupResult, nullptr)) + if (!clangSema.LookupName(lookupResult, clangSema.TUScope)) return nullptr; auto nsDecl = lookupResult.getAsSingle(); if (!nsDecl) diff --git a/test/Interop/Cxx/objc-correctness/Inputs/nsnotification-bridging.h b/test/Interop/Cxx/objc-correctness/Inputs/nsnotification-bridging.h new file mode 100644 index 0000000000000..47435b83b6ac8 --- /dev/null +++ b/test/Interop/Cxx/objc-correctness/Inputs/nsnotification-bridging.h @@ -0,0 +1,3 @@ +#import + +extern NSString * const SpaceShipNotification; diff --git a/test/Interop/Cxx/objc-correctness/nsnotification-bridging-ide-test.swift b/test/Interop/Cxx/objc-correctness/nsnotification-bridging-ide-test.swift new file mode 100644 index 0000000000000..db0527eff3d42 --- /dev/null +++ b/test/Interop/Cxx/objc-correctness/nsnotification-bridging-ide-test.swift @@ -0,0 +1,8 @@ +// RUN: %target-swift-ide-test -print-module -module-to-print=NSNofiticationBridging -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop -enable-objc-interop | %FileCheck %s + +// REQUIRES: objc_interop + +// CHECK: @_exported import Foundation + +// CHECK: @available(swift, obsoleted: 3, renamed: "NSNotification.Name.SpaceShip") +// CHECK: let SpaceShipNotification: NSNotification.Name From 7216be13fa560a44907b5c28d97d8a7284cf038c Mon Sep 17 00:00:00 2001 From: zoecarver Date: Wed, 31 May 2023 11:05:10 -0700 Subject: [PATCH 08/24] [cxx-interop] A little macros fix; breadcrumbs for future fixes. --- lib/ClangImporter/ImportMacro.cpp | 4 +++- test/ClangImporter/macros.swift | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/ClangImporter/ImportMacro.cpp b/lib/ClangImporter/ImportMacro.cpp index e50d9e82f8671..bc6f65dc95ac5 100644 --- a/lib/ClangImporter/ImportMacro.cpp +++ b/lib/ClangImporter/ImportMacro.cpp @@ -328,6 +328,8 @@ static Optional> } // Macro identifier. + // TODO: for some reason when in C++ mode, "hasMacroDefinition" is often + // false: rdar://110071334 } else if (token.is(clang::tok::identifier) && token.getIdentifierInfo()->hasMacroDefinition()) { @@ -422,7 +424,7 @@ static ValueDecl *importMacro(ClangImporter::Implementation &impl, auto diagState = impl.getClangSema().DelayedDiagnostics.push(diagPool); auto parsedType = impl.getClangSema().getTypeName(identifier, clang::SourceLocation(), - /*scope*/nullptr); + impl.getClangSema().TUScope); impl.getClangSema().DelayedDiagnostics.popWithoutEmitting(diagState); if (parsedType && diagPool.empty()) { diff --git a/test/ClangImporter/macros.swift b/test/ClangImporter/macros.swift index eeccd9fd80a02..6a9f07be9f54c 100644 --- a/test/ClangImporter/macros.swift +++ b/test/ClangImporter/macros.swift @@ -1,5 +1,8 @@ // RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -enable-objc-interop -typecheck -verify %s +// Most of these don't pass: rdar://110071334 +// %target-swift-frontend(mock-sdk: %clang-importer-sdk) -enable-experimental-cxx-interop -enable-objc-interop -typecheck -verify %s + @_exported import macros func circle_area(_ radius: CDouble) -> CDouble { From 7d47cb9fec9b9a828855a57871bcf7145e412003 Mon Sep 17 00:00:00 2001 From: zoecarver Date: Wed, 31 May 2023 11:20:34 -0700 Subject: [PATCH 09/24] [cxx-interop] fix how we import CF types. --- lib/ClangImporter/ClangImporter.cpp | 2 +- test/ClangImporter/cf.swift | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index c5038e7e1480a..56fce770ffad7 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -2757,7 +2757,7 @@ ClangImporter::Implementation::lookupTypedef(clang::DeclarationName name) { clang::SourceLocation(), clang::Sema::LookupOrdinaryName); - if (sema.LookupName(lookupResult, /*scope=*/nullptr)) { + if (sema.LookupName(lookupResult, sema.TUScope)) { for (auto decl : lookupResult) { if (auto typedefDecl = dyn_cast(decl->getUnderlyingDecl())) diff --git a/test/ClangImporter/cf.swift b/test/ClangImporter/cf.swift index a2e1967b80fb2..019b10f8e7680 100644 --- a/test/ClangImporter/cf.swift +++ b/test/ClangImporter/cf.swift @@ -1,5 +1,7 @@ // RUN: %target-swift-frontend -disable-objc-attr-requires-foundation-module -typecheck -verify -import-cf-types -I %S/Inputs/custom-modules %s +// RUN: %target-swift-frontend -disable-objc-attr-requires-foundation-module -typecheck -verify -import-cf-types -enable-experimental-cxx-interop -I %S/Inputs/custom-modules %s + // REQUIRES: objc_interop import CoreCooling From e5ece81cc90cc9767514c7f7cafcc621c64ec719 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Fri, 23 Jun 2023 16:03:26 +0100 Subject: [PATCH 10/24] [Freestanding] Remove uses of stat() and dlsym(). We shouldn't be using stat() or dlsym() in the freestanding runtime. rdar://111214571 rdar://106555012 --- CMakeLists.txt | 5 ++++ cmake/modules/AddSwiftUnittests.cmake | 9 ++++++- include/swift/Threading/ThreadSanitizer.h | 6 ++++- lib/Threading/ThreadSanitizer.cpp | 6 ++--- stdlib/cmake/modules/AddSwiftStdlib.cmake | 10 ++++++++ stdlib/cmake/modules/StdlibOptions.cmake | 10 +++++++- stdlib/public/runtime/Paths.cpp | 24 +++++++++++++++++++ utils/build-presets.ini | 2 ++ utils/build-script-impl | 4 ++++ utils/check_freestanding_dependencies.py | 1 - .../products/minimalstdlib.py | 2 ++ 11 files changed, 71 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 54597829e7218..ee70c474647bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -646,6 +646,11 @@ option(SWIFT_THREADING_PACKAGE Valid package names are 'pthreads', 'darwin', 'linux', 'win32', 'c11', 'none' or the empty string for the SDK default.") +option(SWIFT_THREADING_HAS_DLSYM + "Enable the use of the dlsym() function. This gets used to provide TSan + support on some platforms." + TRUE) + option(SWIFT_ENABLE_MACCATALYST "Build the Standard Library and overlays with MacCatalyst support" FALSE) diff --git a/cmake/modules/AddSwiftUnittests.cmake b/cmake/modules/AddSwiftUnittests.cmake index e7a98367337e9..a4573981c225c 100644 --- a/cmake/modules/AddSwiftUnittests.cmake +++ b/cmake/modules/AddSwiftUnittests.cmake @@ -49,12 +49,19 @@ function(add_swift_unittest test_dirname) endif() # some headers switch their inline implementations based on - # SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY and + # SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY, SWIFT_STDLIB_HAS_DLSYM and # SWIFT_THREADING_PACKAGE definitions if(SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY) target_compile_definitions("${test_dirname}" PRIVATE SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY) endif() + if(SWIFT_STDLIB_HAS_DLSYM) + target_compile_definitions("${test_dirname}" PRIVATE + "SWIFT_STDLIB_HAS_DLSYM=1") + else() + target_compile_definitions("${test_dirname}" PRIVATE + "SWIFT_STDLIB_HAS_DLSYM=0") + endif() string(TOUPPER "${SWIFT_SDK_${SWIFT_HOST_VARIANT_SDK}_THREADING_PACKAGE}" _threading_package) target_compile_definitions("${test_dirname}" PRIVATE diff --git a/include/swift/Threading/ThreadSanitizer.h b/include/swift/Threading/ThreadSanitizer.h index 0a6a8028ea183..a50a554f08c40 100644 --- a/include/swift/Threading/ThreadSanitizer.h +++ b/include/swift/Threading/ThreadSanitizer.h @@ -24,7 +24,10 @@ namespace swift { -#if defined(_WIN32) || defined(__wasi__) || !__has_include() +#if SWIFT_THREADING_NONE \ + || defined(_WIN32) || defined(__wasi__) \ + || !__has_include() \ + || (defined(SWIFT_STDLIB_HAS_DLSYM) && !SWIFT_STDLIB_HAS_DLSYM) #define SWIFT_THREADING_TSAN_SUPPORT 0 @@ -35,6 +38,7 @@ template T *acquire(T *ptr) { return ptr; } template T *release(T *ptr) { return ptr; } } // namespace tsan + #else #define SWIFT_THREADING_TSAN_SUPPORT 1 diff --git a/lib/Threading/ThreadSanitizer.cpp b/lib/Threading/ThreadSanitizer.cpp index a103c161dce91..36a64b166e799 100644 --- a/lib/Threading/ThreadSanitizer.cpp +++ b/lib/Threading/ThreadSanitizer.cpp @@ -23,6 +23,8 @@ #include "swift/shims/Visibility.h" +#include + #include namespace swift { @@ -32,9 +34,6 @@ SWIFT_THREADING_EXPORT bool _swift_tsan_enabled = false; SWIFT_THREADING_EXPORT void (*_swift_tsan_acquire)(const void *) = nullptr; SWIFT_THREADING_EXPORT void (*_swift_tsan_release)(const void *) = nullptr; -#if __has_include() -#include - // The TSan library code will call this function when it starts up extern "C" SWIFT_ATTRIBUTE_FOR_EXPORTS void __tsan_on_initialize() { @@ -53,7 +52,6 @@ void __tsan_on_initialize() { next_init(); } } -#endif // __has_include() } // namespace threading_impl } // namespace swift diff --git a/stdlib/cmake/modules/AddSwiftStdlib.cmake b/stdlib/cmake/modules/AddSwiftStdlib.cmake index f5a77c4c81a02..00a87fad69b94 100644 --- a/stdlib/cmake/modules/AddSwiftStdlib.cmake +++ b/stdlib/cmake/modules/AddSwiftStdlib.cmake @@ -352,6 +352,16 @@ function(_add_target_variant_c_compile_flags) list(APPEND result "-DSWIFT_STDLIB_HAS_DLADDR") endif() + if(SWIFT_STDLIB_HAS_DLSYM) + list(APPEND result "-DSWIFT_STDLIB_HAS_DLSYM=1") + else() + list(APPEND result "-DSWIFT_STDLIB_HAS_DLSYM=0") + endif() + + if(SWIFT_STDLIB_HAS_FILESYSTEM) + list(APPEND result "-DSWIFT_STDLIB_HAS_FILESYSTEM") + endif() + if(SWIFT_RUNTIME_STATIC_IMAGE_INSPECTION) list(APPEND result "-DSWIFT_RUNTIME_STATIC_IMAGE_INSPECTION") endif() diff --git a/stdlib/cmake/modules/StdlibOptions.cmake b/stdlib/cmake/modules/StdlibOptions.cmake index 2e946e7cdb927..5547ca1b6a4a0 100644 --- a/stdlib/cmake/modules/StdlibOptions.cmake +++ b/stdlib/cmake/modules/StdlibOptions.cmake @@ -135,7 +135,15 @@ option(SWIFT_STDLIB_BUILD_PRIVATE TRUE) option(SWIFT_STDLIB_HAS_DLADDR - "Build stdlib assuming the runtime environment runtime environment provides dladdr API." + "Build stdlib assuming the runtime environment provides the dladdr API." + TRUE) + +option(SWIFT_STDLIB_HAS_DLSYM + "Build stdlib assuming the runtime environment provides the dlsym API." + TRUE) + +option(SWIFT_STDLIB_HAS_FILESYSTEM + "Build stdlib assuming the runtime environment has a filesystem." TRUE) option(SWIFT_RUNTIME_STATIC_IMAGE_INSPECTION diff --git a/stdlib/public/runtime/Paths.cpp b/stdlib/public/runtime/Paths.cpp index 5ea45bd9fa166..bba1b6e2db7e4 100644 --- a/stdlib/public/runtime/Paths.cpp +++ b/stdlib/public/runtime/Paths.cpp @@ -53,6 +53,28 @@ #include #include +#if !SWIFT_STDLIB_HAS_FILESYSTEM + +SWIFT_RUNTIME_EXPORT +const char * +swift_getRuntimeLibraryPath() { + return nullptr; +} + +SWIFT_RUNTIME_EXPORT +const char * +swift_getRootPath() { + return nullptr; +} + +SWIFT_RUNTIME_EXPORT +char * +swift_copyAuxiliaryExecutablePath(const char *name) { + return nullptr; +} + +#else // SWIFT_STDLIB_HAS_FILESYSTEM + namespace { swift::once_t runtimePathToken; @@ -578,3 +600,5 @@ bool _swift_exists(const char *path) } } + +#endif // SWIFT_STDLIB_HAS_FILESYSTEM diff --git a/utils/build-presets.ini b/utils/build-presets.ini index a41f554a172d2..c28dfa05d40d4 100644 --- a/utils/build-presets.ini +++ b/utils/build-presets.ini @@ -2618,6 +2618,8 @@ swift-enable-reflection=0 swift-stdlib-reflection-metadata=debugger-only swift-stdlib-stable-abi=0 swift-stdlib-has-dladdr=0 +swift-stdlib-has-dlsym=0 +swift-stdlib-has-filesystem=0 swift-stdlib-supports-backtrace-reporting=0 swift-stdlib-has-darwin-libmalloc=0 swift-stdlib-has-asl=0 diff --git a/utils/build-script-impl b/utils/build-script-impl index 7569f806b1af1..2d4b0364a19c2 100755 --- a/utils/build-script-impl +++ b/utils/build-script-impl @@ -207,6 +207,8 @@ KNOWN_SETTINGS=( swift-enable-reflection "1" "whether to support reflection and mirrors" swift-stdlib-reflection-metadata "enabled" "whether to build stdlib with runtime metadata (valid options are 'enabled', 'disabled' and 'debugger-only')" swift-stdlib-has-dladdr "1" "whether to build stdlib assuming the runtime environment provides dladdr API" + swift-stdlib-has-dlsym "1" "whether to build stdlib assuming the runtime environment provides the dlsym API" + swift-stdlib-has-filesystem "1" "whether to build stdlib assuming the runtime environment provides a filesystem" swift-stdlib-supports-backtrace-reporting "" "whether to build stdlib assuming the runtime environment provides the backtrace(3) API, if not set defaults to true on all platforms except for Cygwin, Haiku and wasm" swift-runtime-static-image-inspection "0" "whether to build stdlib assuming the runtime environment only supports a single runtime image with Swift code" swift-threading-package "" "override the threading package for the host build; this is either a single package or a semicolon-separated list of sdk:package pairs. Valid packages are empty string (no override), 'pthreads', 'darwin', 'linux', 'win32', 'c11', 'none'" @@ -1831,6 +1833,8 @@ for host in "${ALL_HOSTS[@]}"; do -DSWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY:BOOL=$(true_false "${SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY}") -DSWIFT_ENABLE_RUNTIME_FUNCTION_COUNTERS:BOOL=$(true_false "${SWIFT_ENABLE_RUNTIME_FUNCTION_COUNTERS}") -DSWIFT_STDLIB_HAS_DLADDR:BOOL=$(true_false "${SWIFT_STDLIB_HAS_DLADDR}") + -DSWIFT_STDLIB_HAS_DLSYM:BOOL=$(true_false "${SWIFT_STDLIB_HAS_DLSYM}") + -DSWIFT_STDLIB_HAS_FILESYSTEM:BOOL=$(true_false "${SWIFT_STDLIB_HAS_FILESYSTEM}") -DSWIFT_RUNTIME_STATIC_IMAGE_INSPECTION:BOOL=$(true_false "${SWIFT_RUNTIME_STATIC_IMAGE_INSPECTION}") -DSWIFT_STDLIB_OS_VERSIONING:BOOL=$(true_false "${SWIFT_STDLIB_OS_VERSIONING}") -DSWIFT_STDLIB_HAS_COMMANDLINE:BOOL=$(true_false "${SWIFT_STDLIB_HAS_COMMANDLINE}") diff --git a/utils/check_freestanding_dependencies.py b/utils/check_freestanding_dependencies.py index f30142779d2ca..93ff06f8cd890 100755 --- a/utils/check_freestanding_dependencies.py +++ b/utils/check_freestanding_dependencies.py @@ -56,7 +56,6 @@ "_posix_memalign", "_putc", "_read", "_realloc", "_snprintf", "_strchr", "_strcmp", "_strdup", "_strlen", "_strncmp", "_strtod", "_strtof", "_strtol", "_strtold", "_vprintf", "_vsnprintf", "_write", - "_stat", "_stat$INODE64", ] + cxx_dependencies + math_dependencies vendor_apple_specific_dependencies = [ "___stack_chk_fail", "___stack_chk_guard", diff --git a/utils/swift_build_support/swift_build_support/products/minimalstdlib.py b/utils/swift_build_support/swift_build_support/products/minimalstdlib.py index 8d70554d6fe10..332049888e685 100644 --- a/utils/swift_build_support/swift_build_support/products/minimalstdlib.py +++ b/utils/swift_build_support/swift_build_support/products/minimalstdlib.py @@ -140,7 +140,9 @@ def build(self, host_target): self.cmake_options.define( 'SWIFT_STDLIB_HAS_DARWIN_LIBMALLOC:BOOL', 'FALSE') self.cmake_options.define('SWIFT_STDLIB_HAS_DLADDR:BOOL', 'FALSE') + self.cmake_options.define('SWIFT_STDLIB_HAS_DLSYM:BOOL', 'FALSE') self.cmake_options.define('SWIFT_STDLIB_HAS_ENVIRON:BOOL', 'FALSE') + self.cmake_options.define('SWIFT_STDLIB_HAS_FILESYSTEM:BOOL', 'FALSE') self.cmake_options.define('SWIFT_STDLIB_HAS_LOCALE:BOOL', 'FALSE') self.cmake_options.define('SWIFT_STDLIB_HAS_STDIN:BOOL', 'FALSE') self.cmake_options.define( From 92fd8da3c3896678179cb16ecedddc13f6c4bcd8 Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Mon, 19 Jun 2023 17:53:32 -0700 Subject: [PATCH 11/24] [FieldSensitivePL] Fixed use-before-def handling. rdar://111118843 --- .../swift/SIL/FieldSensitivePrunedLiveness.h | 55 +++++++-- .../Utils/FieldSensitivePrunedLiveness.cpp | 116 +++++++----------- test/Interpreter/moveonly_loop.swift | 32 +++++ test/SILOptimizer/discard_checking.swift | 5 +- .../field_sensitive_liverange_unit.sil | 27 +--- 5 files changed, 133 insertions(+), 102 deletions(-) create mode 100644 test/Interpreter/moveonly_loop.swift diff --git a/include/swift/SIL/FieldSensitivePrunedLiveness.h b/include/swift/SIL/FieldSensitivePrunedLiveness.h index a28019fc90492..eb42ee56e9135 100644 --- a/include/swift/SIL/FieldSensitivePrunedLiveness.h +++ b/include/swift/SIL/FieldSensitivePrunedLiveness.h @@ -571,19 +571,22 @@ class FieldSensitivePrunedLiveBlocks { } /// Update this liveness result for a single use. - IsLive updateForUse(SILInstruction *user, unsigned bitNo) { + IsLive updateForUse(SILInstruction *user, unsigned bitNo, + bool isUserBeforeDef) { assert(isInitialized()); auto *block = user->getParent(); - auto liveness = getBlockLiveness(block, bitNo); - if (liveness != Dead) - return liveness; + if (!isUserBeforeDef) { + auto liveness = getBlockLiveness(block, bitNo); + if (liveness != Dead) + return liveness; + } computeScalarUseBlockLiveness(block, bitNo); return getBlockLiveness(block, bitNo); } /// Update this range of liveness results for a single use. void updateForUse(SILInstruction *user, unsigned startBitNo, - unsigned endBitNo, + unsigned endBitNo, SmallBitVector const &useBeforeDefBits, SmallVectorImpl &resultingLiveness); IsLive getBlockLiveness(SILBasicBlock *bb, unsigned bitNo) const { @@ -854,10 +857,12 @@ class FieldSensitivePrunedLiveness { /// Also for flexibility, \p affectedAddress must be a derived projection from /// the base that \p user is affecting. void updateForUse(SILInstruction *user, TypeTreeLeafTypeRange span, - bool lifetimeEnding); + bool lifetimeEnding, + SmallBitVector const &useBeforeDefBits); void updateForUse(SILInstruction *user, SmallBitVector const &bits, - bool lifetimeEnding); + bool lifetimeEnding, + SmallBitVector const &useBeforeDefBits); void getBlockLiveness(SILBasicBlock *bb, TypeTreeLeafTypeRange span, SmallVectorImpl @@ -1160,6 +1165,12 @@ class FieldSensitiveSSAPrunedLiveRange return def.first->getParentBlock() == block && def.second->contains(bit); } + template + void isUserBeforeDef(SILInstruction *user, Iterable const &iterable, + SmallBitVector &useBeforeDefBits) const { + assert(useBeforeDefBits.none()); + } + void findBoundariesInBlock(SILBasicBlock *block, unsigned bitNo, bool isLiveOut, FieldSensitivePrunedLivenessBoundary &boundary) const; @@ -1243,6 +1254,36 @@ class FieldSensitiveMultiDefPrunedLiveRange }); } + bool isDefBlock(SILBasicBlock *block, SmallBitVector const &bits) const { + assert(isInitialized()); + auto iter = defBlocks.find(block); + if (!iter) + return false; + return llvm::any_of(*iter, [&](TypeTreeLeafTypeRange storedSpan) { + return storedSpan.intersects(bits); + }); + } + + /// Return true if \p user occurs before the first def in the same basic + /// block. In classical liveness dataflow terms, gen/kill conditions over all + /// users in 'bb' are: + /// + /// Gen(bb) |= !isDefBlock(bb) || isUserBeforeDef(bb) + /// Kill(bb) &= isDefBlock(bb) && !isUserBeforeDef(bb) + /// + /// If 'bb' has no users, it is neither a Gen nor Kill. Otherwise, Gen and + /// Kill are complements. + bool isUserBeforeDef(SILInstruction *user, unsigned element) const; + template + void isUserBeforeDef(SILInstruction *user, Iterable const &iterable, + SmallBitVector &useBeforeDefBits) const { + for (auto bit : iterable) { + if (isUserBeforeDef(user, bit)) { + useBeforeDefBits.set(bit); + } + } + } + bool isDef(SILInstruction *inst, unsigned bit) const { assert(isInitialized()); auto iter = defs.find(cast(inst)); diff --git a/lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp b/lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp index e75dcb22867fa..e3ca0be286da9 100644 --- a/lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp +++ b/lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp @@ -505,6 +505,7 @@ void FieldSensitivePrunedLiveBlocks::computeScalarUseBlockLiveness( /// Terminators are not live out of the block. void FieldSensitivePrunedLiveBlocks::updateForUse( SILInstruction *user, unsigned startBitNo, unsigned endBitNo, + SmallBitVector const &useBeforeDefBits, SmallVectorImpl &resultingLivenessInfo) { assert(isInitialized()); resultingLivenessInfo.clear(); @@ -517,10 +518,15 @@ void FieldSensitivePrunedLiveBlocks::updateForUse( for (unsigned index : indices(resultingLivenessInfo)) { unsigned specificBitNo = startBitNo + index; + auto isUseBeforeDef = useBeforeDefBits.test(specificBitNo); switch (resultingLivenessInfo[index]) { case LiveOut: case LiveWithin: - continue; + if (!isUseBeforeDef) { + continue; + } else { + LLVM_FALLTHROUGH; + } case Dead: { // This use block has not yet been marked live. Mark it and its // predecessor blocks live. @@ -599,21 +605,21 @@ void FieldSensitivePrunedLivenessBoundary::dump() const { // MARK: FieldSensitiveLiveness //===----------------------------------------------------------------------===// -void FieldSensitivePrunedLiveness::updateForUse(SILInstruction *user, - TypeTreeLeafTypeRange range, - bool lifetimeEnding) { +void FieldSensitivePrunedLiveness::updateForUse( + SILInstruction *user, TypeTreeLeafTypeRange range, bool lifetimeEnding, + SmallBitVector const &useBeforeDefBits) { SmallVector resultingLiveness; liveBlocks.updateForUse(user, range.startEltOffset, range.endEltOffset, - resultingLiveness); + useBeforeDefBits, resultingLiveness); addInterestingUser(user, range, lifetimeEnding); } -void FieldSensitivePrunedLiveness::updateForUse(SILInstruction *user, - SmallBitVector const &bits, - bool lifetimeEnding) { +void FieldSensitivePrunedLiveness::updateForUse( + SILInstruction *user, SmallBitVector const &bits, bool lifetimeEnding, + SmallBitVector const &useBeforeDefBits) { for (auto bit : bits.set_bits()) { - liveBlocks.updateForUse(user, bit); + liveBlocks.updateForUse(user, bit, useBeforeDefBits.test(bit)); } addInterestingUser(user, bits, lifetimeEnding); @@ -798,74 +804,46 @@ void FieldSensitivePrunedLiveRange::computeBoundary( } } +bool FieldSensitiveMultiDefPrunedLiveRange::isUserBeforeDef( + SILInstruction *user, unsigned element) const { + auto *block = user->getParent(); + if (!isDefBlock(block, element)) + return false; + + if (llvm::any_of(block->getArguments(), [this, element](SILArgument *arg) { + return isDef(arg, element); + })) { + return false; + } + + auto *current = user; + while (true) { + // If user is also a def, then the use is considered before the def. + current = current->getPreviousInstruction(); + if (!current) + return true; + + if (isDef(current, element)) + return false; + } +} + template void FieldSensitivePrunedLiveRange::updateForUse( SILInstruction *user, TypeTreeLeafTypeRange range, bool lifetimeEnding) { - PRUNED_LIVENESS_LOG( - llvm::dbgs() - << "Begin FieldSensitivePrunedLiveRange::updateForUse " - "for: " - << *user); - PRUNED_LIVENESS_LOG( - llvm::dbgs() << "Looking for def instruction earlier in the block!\n"); - - auto *parentBlock = user->getParent(); - for (auto ii = std::next(user->getReverseIterator()), - ie = parentBlock->rend(); - ii != ie; ++ii) { - // If we find the def, just mark this instruction as being an interesting - // instruction. - if (asImpl().isDef(&*ii, range)) { - PRUNED_LIVENESS_LOG(llvm::dbgs() << " Found def: " << *ii); - PRUNED_LIVENESS_LOG(llvm::dbgs() - << " Marking inst as interesting user and returning!\n"); - addInterestingUser(user, range, lifetimeEnding); - return; - } - } - - // Otherwise, just delegate to our parent class's update for use. This will - // update liveness for our predecessor blocks and add this instruction as an - // interesting user. - PRUNED_LIVENESS_LOG(llvm::dbgs() << "No defs found! Delegating to " - "FieldSensitivePrunedLiveness::updateForUse.\n"); - FieldSensitivePrunedLiveness::updateForUse(user, range, lifetimeEnding); + SmallBitVector useBeforeDefBits(getNumSubElements()); + asImpl().isUserBeforeDef(user, range.getRange(), useBeforeDefBits); + FieldSensitivePrunedLiveness::updateForUse(user, range, lifetimeEnding, + useBeforeDefBits); } template void FieldSensitivePrunedLiveRange::updateForUse( SILInstruction *user, SmallBitVector const &bits, bool lifetimeEnding) { - PRUNED_LIVENESS_LOG( - llvm::dbgs() - << "Begin FieldSensitivePrunedLiveRange::updateForUse " - "for: " - << *user); - PRUNED_LIVENESS_LOG(llvm::dbgs() - << "Looking for def instruction earlier in the block!\n"); - - auto *parentBlock = user->getParent(); - for (auto ii = std::next(user->getReverseIterator()), - ie = parentBlock->rend(); - ii != ie; ++ii) { - // If we find the def, just mark this instruction as being an interesting - // instruction. - if (asImpl().isDef(&*ii, bits)) { - PRUNED_LIVENESS_LOG(llvm::dbgs() << " Found def: " << *ii); - PRUNED_LIVENESS_LOG( - llvm::dbgs() - << " Marking inst as interesting user and returning!\n"); - addInterestingUser(user, bits, lifetimeEnding); - return; - } - } - - // Otherwise, just delegate to our parent class's update for use. This will - // update liveness for our predecessor blocks and add this instruction as an - // interesting user. - PRUNED_LIVENESS_LOG(llvm::dbgs() - << "No defs found! Delegating to " - "FieldSensitivePrunedLiveness::updateForUse.\n"); - FieldSensitivePrunedLiveness::updateForUse(user, bits, lifetimeEnding); + SmallBitVector useBeforeDefBits(getNumSubElements()); + asImpl().isUserBeforeDef(user, bits.set_bits(), useBeforeDefBits); + FieldSensitivePrunedLiveness::updateForUse(user, bits, lifetimeEnding, + useBeforeDefBits); } //===----------------------------------------------------------------------===// diff --git a/test/Interpreter/moveonly_loop.swift b/test/Interpreter/moveonly_loop.swift new file mode 100644 index 0000000000000..d631d5f138185 --- /dev/null +++ b/test/Interpreter/moveonly_loop.swift @@ -0,0 +1,32 @@ +// RUN: %target-run-simple-swift(-Xfrontend -sil-verify-all) | %FileCheck %s +// RUN: %target-run-simple-swift(-O -Xfrontend -sil-verify-all) | %FileCheck %s + +// REQUIRES: executable_test + +struct S: ~Copyable { + let s: String + init(_ s: String) { self.s = s } + deinit { print("deiniting \(s)") } +} + +func use(_ s: borrowing S) { + print("using: \(s.s)") +} + +@_silgen_name("f") +func f(_ c: consuming S) { + repeat { + use(c) + c = S("2") + } while false +} + +func doit() { + let s = S("1") + f(s) +} + +// CHECK: using: 1 +// CHECK-NEXT: deiniting 1 +// CHECK-NEXT: deiniting 2 +doit() diff --git a/test/SILOptimizer/discard_checking.swift b/test/SILOptimizer/discard_checking.swift index 69334b6283c36..98589911a7383 100644 --- a/test/SILOptimizer/discard_checking.swift +++ b/test/SILOptimizer/discard_checking.swift @@ -473,14 +473,11 @@ struct Basics: ~Copyable { } consuming func reinitAfterDiscard3_bad(_ c: Color) throws { - // expected-error@-1 {{must consume 'self' before exiting method that discards self}} - // FIXME: ^ this error is related to rdar://110239087 - repeat { self = Basics() // expected-error {{cannot reinitialize 'self' after 'discard self'}} discard self // expected-note 2{{discarded self here}} } while false - } + } // expected-error {{must consume 'self' before exiting method that discards self}} consuming func reinitAfterDiscard3_ok(_ c: Color) throws { self = Basics() diff --git a/test/SILOptimizer/field_sensitive_liverange_unit.sil b/test/SILOptimizer/field_sensitive_liverange_unit.sil index e8313afca86e0..d7f20e1648f12 100644 --- a/test/SILOptimizer/field_sensitive_liverange_unit.sil +++ b/test/SILOptimizer/field_sensitive_liverange_unit.sil @@ -64,28 +64,11 @@ bb4: // CHECK: MultiDef lifetime analysis: // CHECK: def in range [0, 1) instruction: store %{{.*}} to [init] [[ADDR:%.*]] : $*C // CHECK: def in range [0, 1) instruction: store %0 to [init] [[ADDR]] : $*C - -// FIXME: rdar://111118843 : bb0 is live-out, but FieldSensitivePL incorrectly -// determines it to be LiveWithin because of its mishandling of -// uses-before-defs. -// -// The following HECK lines are the correct CHECK lines. The subsequent -// CHECK lines capture the current erroneous behavior. -// HECK: bb0: LiveOut -// HECK: bb1: LiveWithin -// HECK: last user: %{{.*}} = load [copy] [[ADDR]] : $*C -// HECK: boundary edge: bb2 -// HECK: dead def: store %0 to [init] %1 : $*C - -// FIXME: bb0 is not LiveWithin, it is LiveOut -// CHECK: bb0: LiveWithin, -// CHECK: bb1: LiveWithin, -// CHECK: last user: %{{.*}} = load [copy] [[ADDR]] : $*C -// FIXME: the store of the copy is not a dead-def, it is used in bb1, by the -// only user added. -// CHECK: dead def: store %2 to [init] %1 : $*C -// CHECK: dead def: store %0 to [init] %1 : $*C - +// CHECK: bb0: LiveOut +// CHECK: bb1: LiveWithin +// CHECK: last user: %{{.*}} = load [copy] [[ADDR]] : $*C +// CHECK: boundary edge: bb2 +// CHECK: dead def: store %0 to [init] %1 : $*C // CHECK-LABEL: end running test 1 of 1 on testMultiDefUseAddressReinit sil [ossa] @testMultiDefUseAddressReinit : $@convention(thin) (@owned C) -> () { bb0(%0: @owned $C): From 7c1e5ec1d13b1b0ecc0e7ec28477795919aa1df1 Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Thu, 22 Jun 2023 17:56:12 -0700 Subject: [PATCH 12/24] [SIL] NFC: Added debug printing of block ids. --- include/swift/SIL/SILBasicBlock.h | 11 +++++++++++ lib/SIL/IR/SILPrinter.cpp | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/include/swift/SIL/SILBasicBlock.h b/include/swift/SIL/SILBasicBlock.h index b44c3c528616d..a44adee4c0164 100644 --- a/include/swift/SIL/SILBasicBlock.h +++ b/include/swift/SIL/SILBasicBlock.h @@ -516,6 +516,17 @@ public SwiftObjectHeader { void printAsOperand(raw_ostream &OS, bool PrintType = true); +#ifndef NDEBUG + /// Print the ID of the block, bbN. + void dumpID() const; + + /// Print the ID of the block with \p OS, bbN. + void printID(llvm::raw_ostream &OS) const; + + /// Print the ID of the block with \p Ctx, bbN. + void printID(SILPrintContext &Ctx) const; +#endif + /// getSublistAccess() - returns pointer to member of instruction list static InstListType SILBasicBlock::*getSublistAccess() { return &SILBasicBlock::InstList; diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index b8b49b32bb6ea..52e998014024e 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -854,6 +854,12 @@ class SILPrinter : public SILInstructionVisitor { *this << ')'; } +#ifndef NDEBUG + void printID(const SILBasicBlock *BB) { + *this << Ctx.getID(BB) << "\n"; + } +#endif + void print(const SILBasicBlock *BB) { // Output uses for BB arguments. These are put into place as comments before // the block header. @@ -3086,6 +3092,21 @@ void SILBasicBlock::print(SILPrintContext &Ctx) const { SILPrinter(Ctx).print(this); } +#ifndef NDEBUG +void SILBasicBlock::dumpID() const { + printID(llvm::errs()); +} + +void SILBasicBlock::printID(llvm::raw_ostream &OS) const { + SILPrintContext Ctx(OS); + printID(Ctx); +} + +void SILBasicBlock::printID(SILPrintContext &Ctx) const { + SILPrinter(Ctx).printID(this); +} +#endif + /// Pretty-print the SILFunction to errs. void SILFunction::dump(bool Verbose) const { SILPrintContext Ctx(llvm::errs(), Verbose); From 7322ccb25b18d29534961bb0ba29b29fd4bc2b04 Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Mon, 19 Jun 2023 16:23:42 -0700 Subject: [PATCH 13/24] [FieldSensitivePL] NFC: Added unit tests. Ported versions of the unit tests that Andy added to pruned liveness to FieldSensitivePL. rdar://110862719 --- .../UtilityPasses/UnitTestRunner.cpp | 81 +++++++ .../field_sensitive_liverange_unit.sil | 202 ++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 test/SILOptimizer/field_sensitive_liverange_unit.sil diff --git a/lib/SILOptimizer/UtilityPasses/UnitTestRunner.cpp b/lib/SILOptimizer/UtilityPasses/UnitTestRunner.cpp index ebf9a35bdd8e6..ea3ed526c58e2 100644 --- a/lib/SILOptimizer/UtilityPasses/UnitTestRunner.cpp +++ b/lib/SILOptimizer/UtilityPasses/UnitTestRunner.cpp @@ -73,6 +73,7 @@ #include "swift/SIL/OwnershipLiveness.h" #include "swift/SIL/OwnershipUtils.h" #include "swift/SIL/PrunedLiveness.h" +#include "swift/SIL/FieldSensitivePrunedLiveness.h" #include "swift/SIL/SILArgumentArrayRef.h" #include "swift/SIL/SILBasicBlock.h" #include "swift/SIL/SILBridging.h" @@ -494,6 +495,85 @@ struct MultiDefUseLivenessTest : UnitTest { } }; +// Arguments: +// - value: entity whose fields' livenesses are being computed +// - string: "defs:" +// - variadic list of triples consisting of +// - value: a live-range defining value +// - int: the beginning of the range of fields defined by the value +// - int: the end of the range of the fields defined by the value +// - the string "uses:" +// - variadic list of quadruples consisting of +// - instruction: a live-range user +// - bool: whether the user is lifetime-ending +// - int: the beginning of the range of fields used by the instruction +// - int: the end of the range of fields used by the instruction +// Dumps: +// - the liveness result and boundary +// +// Computes liveness for the specified def nodes by considering the +// specified uses. The actual uses of the def nodes are ignored. +// +// This is useful for testing non-ssa liveness, for example, of memory +// locations. In that case, the def nodes may be stores and the uses may be +// destroy_addrs. +struct FieldSensitiveMultiDefUseLiveRangeTest : UnitTest { + FieldSensitiveMultiDefUseLiveRangeTest(UnitTestRunner *pass) : UnitTest(pass) {} + + void invoke(Arguments &arguments) override { + SmallVector discoveredBlocks; + auto value = arguments.takeValue(); + FieldSensitiveMultiDefPrunedLiveRange liveness(getFunction(), value, &discoveredBlocks); + + llvm::outs() << "FieldSensitive MultiDef lifetime analysis:\n"; + if (arguments.takeString() != "defs:") { + llvm::report_fatal_error( + "test specification expects the 'defs:' label\n"); + } + while (true) { + auto argument = arguments.takeArgument(); + if (isa(argument)) { + if(cast(argument).getValue() != "uses:") { + llvm::report_fatal_error( + "test specification expects the 'uses:' label\n"); + } + break; + } + auto begin = arguments.takeUInt(); + auto end = arguments.takeUInt(); + TypeTreeLeafTypeRange range(begin, end); + if (isa(argument)) { + auto *instruction = cast(argument).getValue(); + llvm::outs() << " def in range [" << begin << ", " << end << ") instruction: " << *instruction; + liveness.initializeDef(instruction, range); + continue; + } + if (isa(argument)) { + SILValue value = cast(argument).getValue(); + llvm::outs() << " def in range [" << begin << ", " << end << ") value: " << value; + liveness.initializeDef(value, range); + continue; + } + llvm::report_fatal_error( + "test specification expects the 'uses:' label\n"); + } + liveness.finishedInitializationOfDefs(); + while (arguments.hasUntaken()) { + auto *inst = arguments.takeInstruction(); + auto lifetimeEnding = arguments.takeBool(); + auto begin = arguments.takeUInt(); + auto end = arguments.takeUInt(); + TypeTreeLeafTypeRange range(begin, end); + liveness.updateForUse(inst, range, lifetimeEnding); + } + liveness.print(llvm::errs()); + + FieldSensitivePrunedLivenessBoundary boundary(liveness.getNumSubElements()); + liveness.computeBoundary(boundary); + boundary.print(llvm::errs()); + } +}; + // Arguments: // - bool: pruneDebug // - bool: maximizeLifetimes @@ -921,6 +1001,7 @@ void UnitTestRunner::withTest(StringRef name, Doit doit) { ADD_UNIT_TEST_SUBCLASS("linear-liveness", LinearLivenessTest) ADD_UNIT_TEST_SUBCLASS("multidef-liveness", MultiDefLivenessTest) ADD_UNIT_TEST_SUBCLASS("multidefuse-liveness", MultiDefUseLivenessTest) + ADD_UNIT_TEST_SUBCLASS("fieldsensitive-multidefuse-liverange", FieldSensitiveMultiDefUseLiveRangeTest) ADD_UNIT_TEST_SUBCLASS("ossa-lifetime-completion", OSSALifetimeCompletionTest) ADD_UNIT_TEST_SUBCLASS("pruned-liveness-boundary-with-list-of-last-users-insertion-points", PrunedLivenessBoundaryWithListOfLastUsersInsertionPointsTest) ADD_UNIT_TEST_SUBCLASS("shrink-borrow-scope", ShrinkBorrowScopeTest) diff --git a/test/SILOptimizer/field_sensitive_liverange_unit.sil b/test/SILOptimizer/field_sensitive_liverange_unit.sil new file mode 100644 index 0000000000000..e8313afca86e0 --- /dev/null +++ b/test/SILOptimizer/field_sensitive_liverange_unit.sil @@ -0,0 +1,202 @@ +// RUN: %target-sil-opt -unit-test-runner %s -o /dev/null 2>&1 | %FileCheck %s + +class C {} +sil @getC : $@convention(thin) () -> (@owned C) + +// Test a live range that is extended through reborrows, +// considering them new defs. +// (e.g. BorrowedValue::visitTransitiveLifetimeEndingUses) +// +// This live range is not dominated by the original borrow. +// +// CHECK-LABEL: testReborrow: fieldsensitive-multidefuse-liverange +// CHECK: FieldSensitive MultiDef lifetime analysis: +// CHECK: def in range [0, 1) value: [[B:%.*]] = load_borrow %0 : $*C +// CHECK: def in range [0, 1) value: [[RB:%.*]] = argument of bb3 : $C +// CHECK-NEXT: bb2: LiveWithin +// CHECK-NEXT: bb3: LiveWithin +// CHECK-NEXT: last user: br bb3([[B]] : $C) +// CHECK-NEXT: at 1 +// CHECK-NEXT: last user: end_borrow [[RB]] : $C +// CHECK-NEXT: at 1 +sil [ossa] @testReborrow : $@convention(thin) () -> () { +bb0: + test_specification """ + fieldsensitive-multidefuse-liverange + @instruction + defs: + @trace[0] 0 1 + @trace[1] 0 1 + uses: + @block[2].instruction[3] true 0 1 + @block[3].instruction[0] true 0 1 + """ + %stack = alloc_stack $C + %getC = function_ref @getC : $@convention(thin) () -> (@owned C) + cond_br undef, bb1, bb2 + +bb1: + %c1 = apply %getC() : $@convention(thin) () -> (@owned C) + store %c1 to [init] %stack : $*C + %borrow1 = load_borrow %stack : $*C + br bb3(%borrow1 : $C) + +bb2: + %c2 = apply %getC() : $@convention(thin) () -> (@owned C) + store %c2 to [init] %stack : $*C + %borrow2 = load_borrow %stack : $*C + debug_value [trace] %borrow2 : $C + br bb3(%borrow2 : $C) + +bb3(%reborrow : @guaranteed $C): + debug_value [trace] %reborrow : $C + end_borrow %reborrow : $C + br bb4 + +bb4: + destroy_addr %stack : $*C + dealloc_stack %stack : $*C + %99 = tuple() + return %99 : $() +} + +// CHECK-LABEL: begin running test 1 of 1 on testMultiDefUseAddressReinit +// CHECK: MultiDef lifetime analysis: +// CHECK: def in range [0, 1) instruction: store %{{.*}} to [init] [[ADDR:%.*]] : $*C +// CHECK: def in range [0, 1) instruction: store %0 to [init] [[ADDR]] : $*C + +// FIXME: rdar://111118843 : bb0 is live-out, but FieldSensitivePL incorrectly +// determines it to be LiveWithin because of its mishandling of +// uses-before-defs. +// +// The following HECK lines are the correct CHECK lines. The subsequent +// CHECK lines capture the current erroneous behavior. +// HECK: bb0: LiveOut +// HECK: bb1: LiveWithin +// HECK: last user: %{{.*}} = load [copy] [[ADDR]] : $*C +// HECK: boundary edge: bb2 +// HECK: dead def: store %0 to [init] %1 : $*C + +// FIXME: bb0 is not LiveWithin, it is LiveOut +// CHECK: bb0: LiveWithin, +// CHECK: bb1: LiveWithin, +// CHECK: last user: %{{.*}} = load [copy] [[ADDR]] : $*C +// FIXME: the store of the copy is not a dead-def, it is used in bb1, by the +// only user added. +// CHECK: dead def: store %2 to [init] %1 : $*C +// CHECK: dead def: store %0 to [init] %1 : $*C + +// CHECK-LABEL: end running test 1 of 1 on testMultiDefUseAddressReinit +sil [ossa] @testMultiDefUseAddressReinit : $@convention(thin) (@owned C) -> () { +bb0(%0: @owned $C): + test_specification """ + fieldsensitive-multidefuse-liverange + @instruction + defs: + @instruction[+2] 0 1 + @block[1].instruction[2] 0 1 + uses: + @block[1].instruction[0] false 0 1 + """ + %1 = alloc_stack $C + %2 = copy_value %0 : $C + store %2 to [init] %1 : $*C + cond_br undef, bb1, bb2 + +bb1: + %5 = load [copy] %1 : $*C + destroy_addr %1 : $*C + store %0 to [init] %1 : $*C + destroy_value %5 : $C + br bb3 + +bb2: + destroy_value %0 : $C + br bb3 + +bb3: + destroy_addr %1 : $*C + dealloc_stack %1 : $*C + %9999 = tuple () + return %9999 : $() +} + +// A single instruction occurs twice on the same liverange +// boundary. Once as a last use, and once as a dead def. +// This is a particularly problematic corner case. +// +// CHECK-LABEL: testDeadSelfKill: fieldsensitive-multidefuse-liverange +// CHECK: FieldSensitive MultiDef lifetime analysis: +// CHECK: def in range [0, 1) instruction: store {{%[^,]+}} to [init] [[STACK:%[^,]+]] : +// CHECK: def in range [0, 1) instruction: store {{%[^,]+}} to [assign] [[STACK]] +// CHECK: bb1: LiveWithin +// CHECK: last user: store {{%[^,]+}} to [assign] [[STACK]] +// CHECK: dead def: store {{%[^,]+}} to [assign] [[STACK]] +sil [ossa] @testDeadSelfKill : $@convention(thin) () -> () { +bb0: + br bb3 + +bb1(%1 : @owned $C, %2 : @owned $C): + test_specification """ + fieldsensitive-multidefuse-liverange + @instruction + defs: + @instruction[+1] 0 1 + @instruction[+2] 0 1 + uses: + @instruction[+2] true 0 1 + """ + %stack = alloc_stack $C + store %1 to [init] %stack : $*C + store %2 to [assign] %stack : $*C + unreachable + +bb3: + %99 = tuple() + return %99 : $() +} + +// A dead-end block with a def can still be a boundary edge. This can +// only happen in OSSA with incomplete lifetimes. +// +// CHECK-LABEL: testMultiDefDeadDefBoundaryEdge: fieldsensitive-multidefuse-liverange +// CHECK: FieldSensitive MultiDef lifetime analysis: +// CHECK: def in range [0, 1) instruction: store {{%[^,]+}} to [init] [[STACK:%[^,]+]] : +// CHECK: def in range [0, 1) instruction: store {{%[^,]+}} to [assign] [[STACK]] +// CHECK: bb0: LiveOut +// CHECK: bb1: LiveWithin +// CHECK: bb2: LiveWithin +// CHECK: last user: destroy_addr [[STACK]] +// CHECK-NEXT: at 1 +// CHECK-NEXT: boundary edge: bb1 +// CHECK-NEXT: at 1 +// CHECK-NEXT: dead def: store {{%[^,]+}} to [assign] [[STACK]] +// CHECK-NEXT: at 1 +sil [ossa] @testMultiDefDeadDefBoundaryEdge : $@convention(thin) () -> () { +bb0: + %getC = function_ref @getC : $@convention(thin) () -> (@owned C) + %stack = alloc_stack $C + %c = apply %getC() : $@convention(thin) () -> (@owned C) + store %c to [init] %stack : $*C + test_specification """ + fieldsensitive-multidefuse-liverange + @instruction[-3] + defs: + @instruction[-1] 0 1 + @block[1].instruction[1] 0 1 + uses: + @block[2].instruction[0] true 0 1 + """ + cond_br undef, bb1, bb3 + +bb1: + %c2 = apply %getC() : $@convention(thin) () -> (@owned C) + store %c2 to [assign] %stack : $*C + unreachable + +bb3: + destroy_addr %stack : $*C + dealloc_stack %stack : $*C + %99 = tuple() + return %99 : $() +} From 9606a9acfd179a91bae5e784bafc46b465468c43 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 23 Jun 2023 10:00:23 -0700 Subject: [PATCH 14/24] [ASTPrinter] Stop unnecessary escaping of `init` in macro role attributes. The excessive escaping of `init` in macro role attributes was a workaround paired with https://github.com/apple/swift/pull/65442 to smooth things over when working across Swift compiler versions. However, it's causing problems for init accessors, so stop escaping. Fixes rdar://111190084. --- lib/AST/Attr.cpp | 5 +++-- test/ModuleInterface/macros.swift | 12 +++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index daee76ed386e6..4f5bd9e9f7f83 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -1392,8 +1392,9 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options, SmallString<32> buffer; StringRef nameText = name.getName().getString(buffer); bool shouldEscape = - escapeKeywordInContext(nameText, PrintNameContext::Normal) || - nameText == "$"; + !name.getName().isSpecial() && + (escapeKeywordInContext(nameText, PrintNameContext::Normal) || + nameText == "$"); Printer << "("; if (shouldEscape) Printer << "`"; diff --git a/test/ModuleInterface/macros.swift b/test/ModuleInterface/macros.swift index 63ea8332a7ad3..d100fee0288a2 100644 --- a/test/ModuleInterface/macros.swift +++ b/test/ModuleInterface/macros.swift @@ -35,9 +35,19 @@ @attached(accessor) public macro myWrapper() = #externalMacro(module: "SomeModule", type: "Wrapper") // CHECK: #if compiler(>=5.3) && $Macros && $AttachedMacros -// CHECK: @attached(member, names: named(`init`), prefixed(`$`)) public macro MemberwiseInit() = #externalMacro(module: "SomeModule", type: "MemberwiseInitMacro") +// CHECK: @attached(member, names: named(init), prefixed(`$`)) public macro MemberwiseInit() = #externalMacro(module: "SomeModule", type: "MemberwiseInitMacro") // CHECK-NEXT: #endif @attached(member, names: named(init), prefixed(`$`)) public macro MemberwiseInit() = #externalMacro(module: "SomeModule", type: "MemberwiseInitMacro") +// CHECK: #if compiler(>=5.3) && $Macros && $AttachedMacros +// CHECK: @attached(member, names: named(`init`), prefixed(`$`)) public macro MemberwiseInitFunc() = #externalMacro(module: "SomeModule", type: "MemberwiseInitFuncMacro") +// CHECK-NEXT: #endif +@attached(member, names: named(`init`), prefixed(`$`)) public macro MemberwiseInitFunc() = #externalMacro(module: "SomeModule", type: "MemberwiseInitFuncMacro") + +// CHECK: #if compiler(>=5.3) && $Macros && $AttachedMacros +// CHECK: @attached(accessor, names: named(init)) public macro AccessorInitFunc() = #externalMacro(module: "SomeModule", type: "AccessorInitFuncMacro") +// CHECK-NEXT: #endif +@attached(accessor, names: named(init)) public macro AccessorInitFunc() = #externalMacro(module: "SomeModule", type: "AccessorInitFuncMacro") + // CHECK-NOT: internalStringify @freestanding(expression) macro internalStringify(_ value: T) -> (T, String) = #externalMacro(module: "SomeModule", type: "StringifyMacro") From e89fdd3084d8730d3267e44acdf094d9930699fc Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 22 Jun 2023 12:51:01 -0700 Subject: [PATCH 15/24] Requestify the computation of the list of memberwise initialized properties. Fixes rdar://110776763, a case where initialized stored properties introduced by peer macros weren't working alongside declared stored properties. --- include/swift/AST/Decl.h | 4 +++ include/swift/AST/DeclContext.h | 16 ++++++++++- include/swift/AST/TypeCheckRequests.h | 22 +++++++++++++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 +++ include/swift/AST/TypeMemberVisitor.h | 5 +--- lib/AST/Decl.cpp | 10 +++++++ lib/AST/DeclContext.cpp | 4 +++ lib/Refactoring/Refactoring.cpp | 2 +- lib/SILGen/SILGenConstructor.cpp | 2 +- lib/Sema/CodeSynthesis.cpp | 9 +------ lib/Sema/TypeCheckStorage.cpp | 30 +++++++++++++++++++++ test/Macros/macro_expand_peers.swift | 3 +-- 12 files changed, 93 insertions(+), 17 deletions(-) diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 556ee0f456948..138ae8605f63a 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -3995,6 +3995,10 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext { /// this type. ArrayRef getInitAccessorProperties() const; + /// Return a collection of all properties that will be part of the memberwise + /// initializer. + ArrayRef getMemberwiseInitProperties() const; + /// Establish a mapping between properties that could be iniitalized /// via other properties by means of init accessors. This mapping is /// one-to-many because we allow intersecting `initializes(...)`. diff --git a/include/swift/AST/DeclContext.h b/include/swift/AST/DeclContext.h index 889ba60edf92a..bf6f4401c0149 100644 --- a/include/swift/AST/DeclContext.h +++ b/include/swift/AST/DeclContext.h @@ -850,9 +850,23 @@ class IterableDeclContext { HasNestedClassDeclarations = 1; } - /// Retrieve the set of members in this context. + /// Retrieve the current set of members in this context. + /// + /// NOTE: This operation is an alias of \c getCurrentMembers() that is considered + /// deprecated. Most clients should not use this or \c getCurrentMembers(), but + /// instead should use one of the projections of members that provides a semantic + /// view, e.g., \c getParsedMembers(), \c getABIMembers(), \c getAllMembers(), and so + /// on. DeclRange getMembers() const; + /// Retrieve the current set of members in this context, without triggering the + /// creation of new members via code synthesis, macro expansion, etc. + /// + /// This operation should only be used in narrow places where any side-effect + /// producing operations have been done earlier. For the most part, this means that + /// it should only be used in the implementation of + DeclRange getCurrentMembers() const; + /// Get the members that were syntactically present in the source code, /// and will not contain any members that are implicitly synthesized by /// the implementation. diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 80fcb212739e1..0a7ad45e021ba 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -1693,6 +1693,28 @@ class InitAccessorPropertiesRequest : bool isCached() const { return true; } }; +/// Request to obtain a list of properties that will be reflected in the parameters of a +/// memberwise initializer. +class MemberwiseInitPropertiesRequest : + public SimpleRequest(NominalTypeDecl *), + RequestFlags::Cached> { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + ArrayRef + evaluate(Evaluator &evaluator, NominalTypeDecl *decl) const; + + // Evaluation. + bool evaluate(Evaluator &evaluator, AbstractStorageDecl *decl) const; + +public: + bool isCached() const { return true; } +}; + class HasStorageRequest : public SimpleRequest(NominalTypeDecl *), Cached, NoLocationInfo) +SWIFT_REQUEST(TypeChecker, MemberwiseInitPropertiesRequest, + ArrayRef(NominalTypeDecl *), + Cached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, StructuralTypeRequest, Type(TypeAliasDecl *), Cached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, SuperclassTypeRequest, diff --git a/include/swift/AST/TypeMemberVisitor.h b/include/swift/AST/TypeMemberVisitor.h index c9952ddae652f..e2faced14ce91 100644 --- a/include/swift/AST/TypeMemberVisitor.h +++ b/include/swift/AST/TypeMemberVisitor.h @@ -61,10 +61,7 @@ class TypeMemberVisitor : public DeclVisitor { /// A convenience method to visit all the members. void visitMembers(NominalTypeDecl *D) { - for (Decl *member : D->getMembers()) { - member->visitAuxiliaryDecls([&](Decl *decl) { - asImpl().visit(decl); - }); + for (Decl *member : D->getAllMembers()) { asImpl().visit(member); } } diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index dd55ebe4529a1..0755059cd9d77 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -4920,6 +4920,16 @@ NominalTypeDecl::getInitAccessorProperties() const { {}); } +ArrayRef +NominalTypeDecl::getMemberwiseInitProperties() const { + auto &ctx = getASTContext(); + auto mutableThis = const_cast(this); + return evaluateOrDefault( + ctx.evaluator, + MemberwiseInitPropertiesRequest{mutableThis}, + {}); +} + void NominalTypeDecl::collectPropertiesInitializableByInitAccessors( std::multimap &result) const { for (auto *property : getInitAccessorProperties()) { diff --git a/lib/AST/DeclContext.cpp b/lib/AST/DeclContext.cpp index 88e9cf8da265b..6455e1ffff25f 100644 --- a/lib/AST/DeclContext.cpp +++ b/lib/AST/DeclContext.cpp @@ -920,6 +920,10 @@ DeclRange IterableDeclContext::getCurrentMembersWithoutLoading() const { } DeclRange IterableDeclContext::getMembers() const { + return getCurrentMembers(); +} + +DeclRange IterableDeclContext::getCurrentMembers() const { loadAllMembers(); return getCurrentMembersWithoutLoading(); diff --git a/lib/Refactoring/Refactoring.cpp b/lib/Refactoring/Refactoring.cpp index 5cda1ef0ccf58..db0d1090d759a 100644 --- a/lib/Refactoring/Refactoring.cpp +++ b/lib/Refactoring/Refactoring.cpp @@ -3415,7 +3415,7 @@ collectMembersForInit(ResolvedCursorInfoPtr CursorInfo, SourceManager &SM = nominalDecl->getASTContext().SourceMgr; - for (auto member : nominalDecl->getMembers()) { + for (auto member : nominalDecl->getMemberwiseInitProperties()) { auto varDecl = dyn_cast(member); if (!varDecl) { continue; diff --git a/lib/SILGen/SILGenConstructor.cpp b/lib/SILGen/SILGenConstructor.cpp index eab5c61a53d75..98f620866effa 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -406,7 +406,7 @@ static void emitImplicitValueConstructor(SILGenFunction &SGF, storedProperties.insert(properties.begin(), properties.end()); } - for (auto *member : decl->getMembers()) { + for (auto *member : decl->getAllMembers()) { auto *field = dyn_cast(member); if (!field) continue; diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index 53f73bbe6354f..f11447a8c0aac 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -297,14 +297,7 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl, std::multimap initializedViaAccessor; decl->collectPropertiesInitializableByInitAccessors(initializedViaAccessor); - for (auto member : decl->getMembers()) { - auto var = dyn_cast(member); - if (!var) - continue; - - if (!var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) - continue; - + for (auto var : decl->getMemberwiseInitProperties()) { if (initializedViaAccessor.count(var)) continue; diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 4e00cd1925eb5..778f35b94b552 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -354,6 +354,36 @@ InitAccessorPropertiesRequest::evaluate(Evaluator &evaluator, return decl->getASTContext().AllocateCopy(results); } +ArrayRef +MemberwiseInitPropertiesRequest::evaluate(Evaluator &evaluator, + NominalTypeDecl *decl) const { + IterableDeclContext *implDecl = decl->getImplementationContext(); + + if (!hasStoredProperties(decl, implDecl)) + return ArrayRef(); + + // Make sure we expand what we need to to get all of the properties. + computeLoweredProperties(decl, implDecl, LoweredPropertiesReason::Memberwise); + + SmallVector results; + auto maybeAddProperty = [&](VarDecl *var) { + if (var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) + results.push_back(var); + }; + + for (auto *member : decl->getCurrentMembers()) { + if (auto *var = dyn_cast(member)) + maybeAddProperty(var); + + member->visitAuxiliaryDecls([&](Decl *auxDecl) { + if (auto auxVar = dyn_cast(auxDecl)) + maybeAddProperty(auxVar); + }); + } + + return decl->getASTContext().AllocateCopy(results); +} + /// Validate the \c entryNumber'th entry in \c binding. const PatternBindingEntry *PatternBindingEntryRequest::evaluate( Evaluator &eval, PatternBindingDecl *binding, unsigned entryNumber, diff --git a/test/Macros/macro_expand_peers.swift b/test/Macros/macro_expand_peers.swift index 240a2a6e51f5c..9749c84f98bbe 100644 --- a/test/Macros/macro_expand_peers.swift +++ b/test/Macros/macro_expand_peers.swift @@ -221,8 +221,7 @@ macro AddPeerStoredProperty() = struct SomeStructWithPeerProperties { @AddPeerStoredProperty - - func foo() {} + var foo: String = "hello" } func testStructWithPeers() { From a1464cfc2eab1452dca14fe6cf048b8b861759ca Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 22 Jun 2023 14:53:04 -0700 Subject: [PATCH 16/24] Correctly remove properties subsumed by init accessors from memberwise inits The code to remove these subsumed properties was part of creating the memberwise initializer declaration, which could lead to inconsistencies with the newly-introduced request for gathering the memberwise-initialized properties. Move the code into the computation of the list of memberwise-initialized properties. Also update a few more places that were effectively recomputing the set of memberwise-initialized properties themselves over to the request. --- lib/Index/Index.cpp | 9 +-------- lib/Refactoring/Refactoring.cpp | 4 ---- lib/Sema/CodeSynthesis.cpp | 9 +-------- lib/Sema/TypeCheckStorage.cpp | 28 ++++++++++++++++++++++++++-- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/lib/Index/Index.cpp b/lib/Index/Index.cpp index 90c862a75a30e..e93e965d2b068 100644 --- a/lib/Index/Index.cpp +++ b/lib/Index/Index.cpp @@ -706,14 +706,7 @@ class IndexSwiftASTWalker : public SourceEntityWalker { return; unsigned CurLabel = 0; - for (auto Member : TypeContext->getMembers()) { - auto Prop = dyn_cast(Member); - if (!Prop) - continue; - - if (!Prop->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) - continue; - + for (auto Prop : TypeContext->getMemberwiseInitProperties()) { if (CurLabel == Args.size()) break; diff --git a/lib/Refactoring/Refactoring.cpp b/lib/Refactoring/Refactoring.cpp index db0d1090d759a..274d921c2ead7 100644 --- a/lib/Refactoring/Refactoring.cpp +++ b/lib/Refactoring/Refactoring.cpp @@ -3428,10 +3428,6 @@ collectMembersForInit(ResolvedCursorInfoPtr CursorInfo, continue; } - if (!varDecl->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) { - continue; - } - auto patternBinding = varDecl->getParentPatternBinding(); if (!patternBinding) continue; diff --git a/lib/Sema/CodeSynthesis.cpp b/lib/Sema/CodeSynthesis.cpp index f11447a8c0aac..e151060e3aab3 100644 --- a/lib/Sema/CodeSynthesis.cpp +++ b/lib/Sema/CodeSynthesis.cpp @@ -294,13 +294,7 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl, if (ICK == ImplicitConstructorKind::Memberwise) { assert(isa(decl) && "Only struct have memberwise constructor"); - std::multimap initializedViaAccessor; - decl->collectPropertiesInitializableByInitAccessors(initializedViaAccessor); - for (auto var : decl->getMemberwiseInitProperties()) { - if (initializedViaAccessor.count(var)) - continue; - accessLevel = std::min(accessLevel, var->getFormalAccess()); params.push_back(createMemberwiseInitParameter(decl, Loc, var)); } @@ -1401,8 +1395,7 @@ ResolveEffectiveMemberwiseInitRequest::evaluate(Evaluator &evaluator, auto accessLevel = AccessLevel::Public; for (auto *member : decl->getMembers()) { auto *var = dyn_cast(member); - if (!var || - !var->isMemberwiseInitialized(/*preferDeclaredProperties*/ true)) + if (!var || !var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) continue; accessLevel = std::min(accessLevel, var->getFormalAccess()); } diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index 778f35b94b552..b43c544b82843 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -366,9 +366,25 @@ MemberwiseInitPropertiesRequest::evaluate(Evaluator &evaluator, computeLoweredProperties(decl, implDecl, LoweredPropertiesReason::Memberwise); SmallVector results; + SmallPtrSet subsumedViaInitAccessor; + auto maybeAddProperty = [&](VarDecl *var) { - if (var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) - results.push_back(var); + // We only care about properties that are memberwise initialized. + if (!var->isMemberwiseInitialized(/*preferDeclaredProperties=*/true)) + return; + + // Add this property. + results.push_back(var); + + // If this property has an init accessor, it subsumes all of the stored properties + // that the accessor initializes. Mark those stored properties as being subsumed; we'll + // get back to them later. + if (auto initAccessor = var->getAccessor(AccessorKind::Init)) { + if (auto initAttr = initAccessor->getAttrs().getAttribute()) { + for (auto subsumed : initAttr->getPropertyDecls(initAccessor)) + subsumedViaInitAccessor.insert(subsumed); + } + } }; for (auto *member : decl->getCurrentMembers()) { @@ -381,6 +397,14 @@ MemberwiseInitPropertiesRequest::evaluate(Evaluator &evaluator, }); } + // If any properties were subsumed via init accessors, drop them from the list. + if (!subsumedViaInitAccessor.empty()) { + results.erase(std::remove_if(results.begin(), results.end(), [&](VarDecl *var) { + return subsumedViaInitAccessor.contains(var); + }), + results.end()); + } + return decl->getASTContext().AllocateCopy(results); } From 7ed59e1200c8597f4393947e1db1be9a77feb143 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 22 Jun 2023 15:04:20 -0700 Subject: [PATCH 17/24] Reimplement InitAccessorPropertiesRequest on top of MemberwiseInitPropertiesRequest Simplify the implementation and take advantage of request caching. --- lib/Sema/TypeCheckStorage.cpp | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/lib/Sema/TypeCheckStorage.cpp b/lib/Sema/TypeCheckStorage.cpp index b43c544b82843..db524bfc06e4f 100644 --- a/lib/Sema/TypeCheckStorage.cpp +++ b/lib/Sema/TypeCheckStorage.cpp @@ -333,22 +333,10 @@ bool HasInitAccessorRequest::evaluate(Evaluator &evaluator, ArrayRef InitAccessorPropertiesRequest::evaluate(Evaluator &evaluator, NominalTypeDecl *decl) const { - IterableDeclContext *implDecl = decl->getImplementationContext(); - - if (!hasStoredProperties(decl, implDecl)) - return ArrayRef(); - - // Make sure we expand what we need to to get all of the properties. - computeLoweredProperties(decl, implDecl, LoweredPropertiesReason::Memberwise); - SmallVector results; - for (auto *member : decl->getMembers()) { - auto *var = dyn_cast(member); - if (!var || var->isStatic() || !var->hasInitAccessor()) { - continue; - } - - results.push_back(var); + for (auto var : decl->getMemberwiseInitProperties()) { + if (var->hasInitAccessor()) + results.push_back(var); } return decl->getASTContext().AllocateCopy(results); From 2114833e2e278c4c86729cba113f723805afa04f Mon Sep 17 00:00:00 2001 From: zoecarver Date: Thu, 8 Jun 2023 09:40:13 -0700 Subject: [PATCH 18/24] [nfc] Fix notification test --- test/Interop/Cxx/objc-correctness/Inputs/module.modulemap | 6 ++++++ .../objc-correctness/nsnotification-bridging-ide-test.swift | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/test/Interop/Cxx/objc-correctness/Inputs/module.modulemap b/test/Interop/Cxx/objc-correctness/Inputs/module.modulemap index 0d922784daf2a..8f346bf3072d6 100644 --- a/test/Interop/Cxx/objc-correctness/Inputs/module.modulemap +++ b/test/Interop/Cxx/objc-correctness/Inputs/module.modulemap @@ -12,3 +12,9 @@ module CxxClassWithNSStringInit [extern_c] { module NSOptionsMangling { header "NSOptionsMangling.h" } + +module NSNofiticationBridging { + header "nsnotification-bridging.h" + requires objc + requires cplusplus +} diff --git a/test/Interop/Cxx/objc-correctness/nsnotification-bridging-ide-test.swift b/test/Interop/Cxx/objc-correctness/nsnotification-bridging-ide-test.swift index db0527eff3d42..8804fc6c455d7 100644 --- a/test/Interop/Cxx/objc-correctness/nsnotification-bridging-ide-test.swift +++ b/test/Interop/Cxx/objc-correctness/nsnotification-bridging-ide-test.swift @@ -2,7 +2,6 @@ // REQUIRES: objc_interop -// CHECK: @_exported import Foundation +// CHECK: import Foundation -// CHECK: @available(swift, obsoleted: 3, renamed: "NSNotification.Name.SpaceShip") -// CHECK: let SpaceShipNotification: NSNotification.Name +// CHECK: let SpaceShipNotification: String From 31e0252d96ba8fe4d4eb89b130ea246a4335aaaf Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 23 Jun 2023 12:31:41 -0700 Subject: [PATCH 19/24] Make sure we query `hasInitAccessor` before looking for the init accessor. This is a workaround for the lack of a request for getting accessors. --- lib/AST/Decl.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 0755059cd9d77..5461a4ca1d74c 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -7187,8 +7187,10 @@ bool VarDecl::isMemberwiseInitialized(bool preferDeclaredProperties) const { // If this is a computed property with `init` accessor, it's // memberwise initializable when it could be used to initialize // other stored properties. - if (auto *init = getAccessor(AccessorKind::Init)) - return init->getAttrs().hasAttribute(); + if (hasInitAccessor()) { + if (auto *init = getAccessor(AccessorKind::Init)) + return init->getAttrs().hasAttribute(); + } // If this is a computed property, it's not memberwise initialized unless // the caller has asked for the declared properties and it is either a From 8eff2e82991e9d7a023953ef85ffe4bb5a7c077a Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Thu, 22 Jun 2023 21:21:55 -0700 Subject: [PATCH 20/24] [LifetimeExtension] Handle use-before-def. The multi-def algorithm is based off the single-def algorithm. However, it differs from it in needing to handle "liveness holes"--uses before defs. When identifying blocks which are originally live, the algorithm starts from consuming blocks and walks backwards until a condition is met. Previously, that condition was finding blocks which were originally live. That makes sense in the single-def case: if a block is originally live, either it was already walked through or else it was one of the blocks discovered by liveness. In either case, its predecessors have already been visited if appropriate. However, in the multi-def case, this condition is too stringent. It fails to account for the possibility of a block with has a "liveness hole". Only stop the backwards walk if the predecessor not only (1) was in originally live but additionally (2) "kills liveness"--doesn't have a use before a def. rdar://111221183 --- .../Mandatory/MoveOnlyAddressCheckerUtils.cpp | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp index a7d7ed6bf798e..7f260a7c245a0 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp @@ -1414,6 +1414,8 @@ class ExtendUnconsumedLiveness { private: bool hasDefAfter(SILInstruction *inst, unsigned element); + bool isLiveAtBegin(SILBasicBlock *block, unsigned element, bool isLiveAtEnd, + DestroysCollection const &destroys); bool shouldAddDestroyToLiveness(SILInstruction *destroy, unsigned element, @@ -2967,7 +2969,14 @@ void ExtendUnconsumedLiveness::runOnField( // as the other consuming uses). BasicBlockWorklist worklist(currentDef->getFunction()); for (auto *consumingBlock : consumingBlocks) { - worklist.push(consumingBlock); + if (!originalLiveBlocks.insert(consumingBlock) + // Don't walk into the predecessors of blocks which kill liveness. + && !isLiveAtBegin(consumingBlock, element, /*isLiveAtEnd=*/true, destroys)) { + continue; + } + for (auto *predecessor : consumingBlock->getPredecessorBlocks()) { + worklist.pushIfNotVisited(predecessor); + } } // Walk backwards from consuming blocks. @@ -2976,10 +2985,6 @@ void ExtendUnconsumedLiveness::runOnField( continue; } for (auto *predecessor : block->getPredecessorBlocks()) { - // If the block was discovered by liveness, we already added it to the - // set. - if (originalLiveBlocks.contains(predecessor)) - continue; worklist.pushIfNotVisited(predecessor); } } @@ -3096,6 +3101,37 @@ bool ExtendUnconsumedLiveness::shouldAddDestroyToLiveness( return !consumedAtEntryBlocks.contains(block); } +/// Compute the block's effect on liveness and apply it to \p isLiveAtEnd. +bool ExtendUnconsumedLiveness::isLiveAtBegin(SILBasicBlock *block, + unsigned element, bool isLiveAtEnd, + DestroysCollection const &destroys) { + enum class Effect { + None, // 0 + Kill, // 1 + Gen, // 2 + }; + auto effect = Effect::None; + for (auto &instruction : llvm::reverse(*block)) { + // An instruction can be both a destroy and a def. If it is, its + // behavior is first to destroy and then to init. So when walking + // backwards, its last action is to destroy, so its effect is that of any + // destroy. + if (destroys.find(&instruction) != destroys.end()) { + effect = Effect::Gen; + } else if (liveness.isDef(&instruction, element)) { + effect = Effect::Kill; + } + } + switch (effect) { + case Effect::None: + return isLiveAtEnd; + case Effect::Kill: + return false; + case Effect::Gen: + return true; + } +} + bool ExtendUnconsumedLiveness::hasDefAfter(SILInstruction *start, unsigned element) { // NOTE: Start iteration at \p start, not its sequel, because From ff7aa3c99a055e35b42ed11946bae794d648bdfd Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Fri, 23 Jun 2023 10:16:03 -0700 Subject: [PATCH 21/24] [LifetimeExtension] Fixed reinit collection. During initializeLiveness, all blocks which contain destroys are to be recorded in consuming blocks. Previously, however, certain reinits were not being added. Here, all reinits are correctly added. --- lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp index 7f260a7c245a0..614bb37b12088 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp @@ -1070,11 +1070,11 @@ void UseState::initializeLiveness( // Now at this point, we have defined all of our defs so we can start adding // uses to the liveness. for (auto reinitInstAndValue : reinitInsts) { + recordConsumingBlock(reinitInstAndValue.first->getParent(), + reinitInstAndValue.second); if (!isReinitToInitConvertibleInst(reinitInstAndValue.first)) { liveness.updateForUse(reinitInstAndValue.first, reinitInstAndValue.second, false /*lifetime ending*/); - recordConsumingBlock(reinitInstAndValue.first->getParent(), - reinitInstAndValue.second); LLVM_DEBUG(llvm::dbgs() << "Added liveness for reinit: " << *reinitInstAndValue.first; liveness.print(llvm::dbgs())); From 270089f29798b73b3df74e7adad5f22dc074b21b Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Fri, 23 Jun 2023 18:10:55 -0700 Subject: [PATCH 22/24] [IRGen+Runtime] Layout string getEnumTag for fixed size enums subset (#66899) * [IRGen+Runtime] Layout string getEnumTag for fixed size enums subset getEnumTag impl for layout strings of fixed sized enums that use a function to fetch the enum tag * Fix potential UB in IRGen --- include/swift/Runtime/RuntimeFunctions.def | 13 ++++++- lib/IRGen/GenValueWitness.cpp | 39 ++++++++++++++++--- stdlib/public/runtime/BytecodeLayouts.cpp | 11 ++++++ stdlib/public/runtime/BytecodeLayouts.h | 3 ++ ...out_string_witnesses_types_resilient.swift | 25 +++++++++++- .../layout_string_witnesses_dynamic.swift | 29 +++++++++++++- 6 files changed, 111 insertions(+), 9 deletions(-) diff --git a/include/swift/Runtime/RuntimeFunctions.def b/include/swift/Runtime/RuntimeFunctions.def index bb47fbacc7ee6..4b4bfe48bda54 100644 --- a/include/swift/Runtime/RuntimeFunctions.def +++ b/include/swift/Runtime/RuntimeFunctions.def @@ -2315,6 +2315,16 @@ FUNCTION(GenericInitWithTake, ATTRS(NoUnwind), EFFECT(Refcounting)) +// unsigned swift_enumFn_getEnumTag(swift::OpaqueValue *address, +// const Metadata *metadata); +FUNCTION(EnumFnGetEnumTag, + swift_enumFn_getEnumTag, + C_CC, AlwaysAvailable, + RETURNS(Int32Ty), + ARGS(Int8PtrTy, TypeMetadataPtrTy), + ATTRS(NoUnwind), + EFFECT(NoEffect)) + // unsigned swift_multiPayloadEnumGeneric_getEnumTag(opaque* address, // const Metadata *type); FUNCTION(MultiPayloadEnumGenericGetEnumTag, @@ -2325,7 +2335,8 @@ FUNCTION(MultiPayloadEnumGenericGetEnumTag, ATTRS(NoUnwind), EFFECT(NoEffect)) -// void swift_generic_instantiateLayoutString(const uint8_t* layoutStr, Metadata* type); +// void swift_generic_instantiateLayoutString(const uint8_t* layoutStr, +// Metadata* type); FUNCTION(GenericInstantiateLayoutString, swift_generic_instantiateLayoutString, C_CC, AlwaysAvailable, diff --git a/lib/IRGen/GenValueWitness.cpp b/lib/IRGen/GenValueWitness.cpp index c5b823daf0ab3..73904106417cd 100644 --- a/lib/IRGen/GenValueWitness.cpp +++ b/lib/IRGen/GenValueWitness.cpp @@ -885,8 +885,8 @@ void addStride(ConstantStructBuilder &B, const TypeInfo *TI, IRGenModule &IGM) { } } // end anonymous namespace -bool isRuntimeInstatiatedLayoutString(IRGenModule &IGM, - const TypeLayoutEntry *typeLayoutEntry) { +static bool isRuntimeInstatiatedLayoutString(IRGenModule &IGM, + const TypeLayoutEntry *typeLayoutEntry) { if (IGM.Context.LangOpts.hasFeature(Feature::LayoutStringValueWitnesses) && IGM.Context.LangOpts.hasFeature( Feature::LayoutStringValueWitnessesInstantiation) && @@ -899,6 +899,36 @@ bool isRuntimeInstatiatedLayoutString(IRGenModule &IGM, return false; } +static llvm::Constant *getEnumTagFunction(IRGenModule &IGM, + const EnumTypeLayoutEntry *typeLayoutEntry) { + if (typeLayoutEntry->isSingleton()) { + return nullptr; + } else if (!typeLayoutEntry->isFixedSize(IGM)) { + if (typeLayoutEntry->isMultiPayloadEnum()) { + return IGM.getMultiPayloadEnumGenericGetEnumTagFn(); + } else { + // TODO: implement support for single payload generic enums + return nullptr; + } + } else if (typeLayoutEntry->isMultiPayloadEnum()) { + return IGM.getEnumFnGetEnumTagFn(); + } else { + auto &payloadTI = **(typeLayoutEntry->cases[0]->getFixedTypeInfo()); + auto mask = payloadTI.getFixedExtraInhabitantMask(IGM); + auto tzCount = mask.countTrailingZeros(); + auto shiftedMask = mask.lshr(tzCount); + auto toCount = shiftedMask.countTrailingOnes(); + if (payloadTI.mayHaveExtraInhabitants(IGM) && + (mask.countPopulation() > 64 || + toCount != mask.countPopulation() || + (tzCount % toCount != 0))) { + return IGM.getEnumFnGetEnumTagFn(); + } else { + return nullptr; + } + } +} + static bool valueWitnessRequiresCopyability(ValueWitness index) { switch (index) { @@ -1127,9 +1157,8 @@ static void addValueWitness(IRGenModule &IGM, ConstantStructBuilder &B, if (auto *typeLayoutEntry = typeInfo.buildTypeLayoutEntry( IGM, ty, /*useStructLayouts*/ true)) { if (auto *enumLayoutEntry = typeLayoutEntry->getAsEnum()) { - if (enumLayoutEntry->isMultiPayloadEnum() && - !typeLayoutEntry->isFixedSize(IGM)) { - return addFunction(IGM.getMultiPayloadEnumGenericGetEnumTagFn()); + if (auto *fn = getEnumTagFunction(IGM, enumLayoutEntry)) { + return addFunction(fn); } } } diff --git a/stdlib/public/runtime/BytecodeLayouts.cpp b/stdlib/public/runtime/BytecodeLayouts.cpp index ad3cb4123c35f..7ea93f63619fe 100644 --- a/stdlib/public/runtime/BytecodeLayouts.cpp +++ b/stdlib/public/runtime/BytecodeLayouts.cpp @@ -652,6 +652,17 @@ swift_generic_assignWithTake(swift::OpaqueValue *dest, swift::OpaqueValue *src, return swift_generic_initWithTake(dest, src, metadata); } +extern "C" +unsigned swift_enumFn_getEnumTag(swift::OpaqueValue *address, + const Metadata *metadata) { + auto addr = reinterpret_cast(address); + LayoutStringReader reader{metadata->getLayoutString(), + layoutStringHeaderSize + sizeof(uint64_t)}; + auto getEnumTag = readRelativeFunctionPointer(reader); + + return getEnumTag(addr); +} + extern "C" unsigned swift_multiPayloadEnumGeneric_getEnumTag(swift::OpaqueValue *address, const Metadata *metadata) { diff --git a/stdlib/public/runtime/BytecodeLayouts.h b/stdlib/public/runtime/BytecodeLayouts.h index 6a1b83f01f50c..82e1f50a43bc8 100644 --- a/stdlib/public/runtime/BytecodeLayouts.h +++ b/stdlib/public/runtime/BytecodeLayouts.h @@ -113,6 +113,9 @@ swift::OpaqueValue *swift_generic_initWithTake(swift::OpaqueValue *dest, swift::OpaqueValue *src, const Metadata *metadata); SWIFT_RUNTIME_EXPORT +unsigned swift_enumFn_getEnumTag(swift::OpaqueValue *address, + const Metadata *metadata); +SWIFT_RUNTIME_EXPORT unsigned swift_multiPayloadEnumGeneric_getEnumTag(swift::OpaqueValue *address, const Metadata *metadata); SWIFT_RUNTIME_EXPORT diff --git a/test/Interpreter/Inputs/layout_string_witnesses_types_resilient.swift b/test/Interpreter/Inputs/layout_string_witnesses_types_resilient.swift index 581c6058d87b2..b3ad4def44928 100644 --- a/test/Interpreter/Inputs/layout_string_witnesses_types_resilient.swift +++ b/test/Interpreter/Inputs/layout_string_witnesses_types_resilient.swift @@ -19,13 +19,34 @@ public struct GenericResilient { } } -public enum ResilientMultiPayloadEnum { +public enum ResilientMultiPayloadEnumGeneric { case empty0 case empty1 case nonEmpty0(AnyObject) case nonEmpty1(T) } -public func getResilientMultiPayloadEnumEmpty0(_ t: T.Type) -> ResilientMultiPayloadEnum { +public enum ResilientMultiPayloadEnum { + case empty0 + case empty1 + case nonEmpty0(AnyObject, Bool) + case nonEmpty1(AnyObject) +} + +public enum ResilientSinglePayloadEnumComplex { + case empty0 + case empty1 + case nonEmpty(ResilientMultiPayloadEnum) +} + +public func getResilientMultiPayloadEnumGenericEmpty0(_ t: T.Type) -> ResilientMultiPayloadEnumGeneric { + return .empty0 +} + +public func getResilientMultiPayloadEnumEmpty0() -> ResilientMultiPayloadEnum { + return .empty0 +} + +public func getResilientSinglePayloadEnumComplexEmpty0() -> ResilientSinglePayloadEnumComplex { return .empty0 } diff --git a/test/Interpreter/layout_string_witnesses_dynamic.swift b/test/Interpreter/layout_string_witnesses_dynamic.swift index 43e528be75bd0..bd357984a286b 100644 --- a/test/Interpreter/layout_string_witnesses_dynamic.swift +++ b/test/Interpreter/layout_string_witnesses_dynamic.swift @@ -516,8 +516,21 @@ func testGenericSinglePayloadEnumManyXI() { testGenericSinglePayloadEnumManyXI() +func testResilientSinglePayloadEnumComplexTag() { + let x = switch getResilientSinglePayloadEnumComplexEmpty0() { + case .nonEmpty: 0 + case .empty0: 1 + case .empty1: 2 + } + + // CHECK: Enum case: 1 + print("Enum case: \(x)") +} + +testResilientSinglePayloadEnumComplexTag() + func testResilientMultiPayloadEnumTag() { - let x = switch getResilientMultiPayloadEnumEmpty0(AnyObject.self) { + let x = switch getResilientMultiPayloadEnumEmpty0() { case .nonEmpty0: 0 case .nonEmpty1: 1 case .empty0: 2 @@ -530,6 +543,20 @@ func testResilientMultiPayloadEnumTag() { testResilientMultiPayloadEnumTag() +func testResilientMultiPayloadEnumGenericTag() { + let x = switch getResilientMultiPayloadEnumGenericEmpty0(AnyObject.self) { + case .nonEmpty0: 0 + case .nonEmpty1: 1 + case .empty0: 2 + case .empty1: 3 + } + + // CHECK: Enum case: 2 + print("Enum case: \(x)") +} + +testResilientMultiPayloadEnumGenericTag() + #if os(macOS) import Foundation From 0a2defed9889b3f4ddbafc6e6c27997b9053a70f Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sat, 24 Jun 2023 10:59:13 +0900 Subject: [PATCH 23/24] [Concurrency] Fix code snippet on task group docs Code snippet was missing required of: parameter. --- stdlib/public/Concurrency/TaskGroup.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/Concurrency/TaskGroup.swift b/stdlib/public/Concurrency/TaskGroup.swift index 35414ec9d2072..942cd48da6ca0 100644 --- a/stdlib/public/Concurrency/TaskGroup.swift +++ b/stdlib/public/Concurrency/TaskGroup.swift @@ -149,14 +149,14 @@ public func withTaskGroup( /// For example, in the code below, /// nothing is canceled and the group doesn't throw an error: /// -/// try await withThrowingTaskGroup { group in +/// try await withThrowingTaskGroup(of: Void.self) { group in /// group.addTask { throw SomeError() } /// } /// /// In contrast, this example throws `SomeError` /// and cancels all of the tasks in the group: /// -/// try await withThrowingTaskGroup { group in +/// try await withThrowingTaskGroup(of: Void.self) { group in /// group.addTask { throw SomeError() } /// try await group.next() /// } From 253d8fbf33dbc280598699beac86d0f2d5817d5e Mon Sep 17 00:00:00 2001 From: Dario Rexin Date: Fri, 23 Jun 2023 20:43:28 -0700 Subject: [PATCH 24/24] [IRGen+Runtime] Add getEnumTag for layout strings on generic single payload enum (#66911) --- include/swift/Runtime/RuntimeFunctions.def | 10 +++++ lib/IRGen/GenValueWitness.cpp | 2 +- stdlib/public/runtime/BytecodeLayouts.cpp | 45 +++++++++++++++++++ stdlib/public/runtime/BytecodeLayouts.h | 3 ++ ...out_string_witnesses_types_resilient.swift | 10 +++++ .../layout_string_witnesses_dynamic.swift | 13 ++++++ 6 files changed, 82 insertions(+), 1 deletion(-) diff --git a/include/swift/Runtime/RuntimeFunctions.def b/include/swift/Runtime/RuntimeFunctions.def index 4b4bfe48bda54..679c58cc1c779 100644 --- a/include/swift/Runtime/RuntimeFunctions.def +++ b/include/swift/Runtime/RuntimeFunctions.def @@ -2335,6 +2335,16 @@ FUNCTION(MultiPayloadEnumGenericGetEnumTag, ATTRS(NoUnwind), EFFECT(NoEffect)) +// unsigned swift_singlePayloadEnumGeneric_getEnumTag(swift::OpaqueValue *address, +// const Metadata *metadata); +FUNCTION(SinglePayloadEnumGenericGetEnumTag, + swift_singlePayloadEnumGeneric_getEnumTag, + C_CC, AlwaysAvailable, + RETURNS(Int32Ty), + ARGS(Int8PtrTy, TypeMetadataPtrTy), + ATTRS(NoUnwind), + EFFECT(NoEffect)) + // void swift_generic_instantiateLayoutString(const uint8_t* layoutStr, // Metadata* type); FUNCTION(GenericInstantiateLayoutString, diff --git a/lib/IRGen/GenValueWitness.cpp b/lib/IRGen/GenValueWitness.cpp index 73904106417cd..27d25c8fad3a5 100644 --- a/lib/IRGen/GenValueWitness.cpp +++ b/lib/IRGen/GenValueWitness.cpp @@ -908,7 +908,7 @@ static llvm::Constant *getEnumTagFunction(IRGenModule &IGM, return IGM.getMultiPayloadEnumGenericGetEnumTagFn(); } else { // TODO: implement support for single payload generic enums - return nullptr; + return IGM.getSinglePayloadEnumGenericGetEnumTagFn(); } } else if (typeLayoutEntry->isMultiPayloadEnum()) { return IGM.getEnumFnGetEnumTagFn(); diff --git a/stdlib/public/runtime/BytecodeLayouts.cpp b/stdlib/public/runtime/BytecodeLayouts.cpp index 7ea93f63619fe..4d9c255108a5b 100644 --- a/stdlib/public/runtime/BytecodeLayouts.cpp +++ b/stdlib/public/runtime/BytecodeLayouts.cpp @@ -692,6 +692,51 @@ swift_multiPayloadEnumGeneric_getEnumTag(swift::OpaqueValue *address, } } +extern "C" unsigned +swift_singlePayloadEnumGeneric_getEnumTag(swift::OpaqueValue *address, + const Metadata *metadata) { + auto addr = reinterpret_cast(address); + LayoutStringReader reader{metadata->getLayoutString(), + layoutStringHeaderSize + sizeof(uint64_t)}; + + auto tagBytesAndOffset = reader.readBytes(); + auto extraTagBytesPattern = (uint8_t)(tagBytesAndOffset >> 62); + auto xiTagBytesOffset = + tagBytesAndOffset & std::numeric_limits::max(); + const Metadata *xiType = nullptr; + + if (extraTagBytesPattern) { + auto extraTagBytes = 1 << (extraTagBytesPattern - 1); + auto payloadSize = reader.readBytes(); + auto tagBytes = readTagBytes(addr + payloadSize, extraTagBytes); + if (tagBytes) { + xiType = reader.readBytes(); + unsigned payloadNumExtraInhabitants = + xiType ? xiType->vw_getNumExtraInhabitants() : 0; + unsigned caseIndexFromExtraTagBits = + payloadSize >= 4 ? 0 : (tagBytes - 1U) << (payloadSize * 8U); + unsigned caseIndexFromValue = loadEnumElement(addr, payloadSize); + unsigned noPayloadIndex = + (caseIndexFromExtraTagBits | caseIndexFromValue) + + payloadNumExtraInhabitants; + return noPayloadIndex + 1; + } + } else { + reader.skip(sizeof(size_t)); + } + + xiType = reader.readBytes(); + + if (xiType) { + auto numEmptyCases = reader.readBytes(); + + return xiType->vw_getEnumTagSinglePayload( + (const OpaqueValue *)(addr + xiTagBytesOffset), numEmptyCases); + } + + return 0; +} + void swift::swift_resolve_resilientAccessors(uint8_t *layoutStr, size_t layoutStrOffset, const uint8_t *fieldLayoutStr, diff --git a/stdlib/public/runtime/BytecodeLayouts.h b/stdlib/public/runtime/BytecodeLayouts.h index 82e1f50a43bc8..0678697da0c20 100644 --- a/stdlib/public/runtime/BytecodeLayouts.h +++ b/stdlib/public/runtime/BytecodeLayouts.h @@ -119,6 +119,9 @@ SWIFT_RUNTIME_EXPORT unsigned swift_multiPayloadEnumGeneric_getEnumTag(swift::OpaqueValue *address, const Metadata *metadata); SWIFT_RUNTIME_EXPORT +unsigned swift_singlePayloadEnumGeneric_getEnumTag(swift::OpaqueValue *address, + const Metadata *metadata); +SWIFT_RUNTIME_EXPORT void swift_generic_instantiateLayoutString(const uint8_t *layoutStr, Metadata *type); diff --git a/test/Interpreter/Inputs/layout_string_witnesses_types_resilient.swift b/test/Interpreter/Inputs/layout_string_witnesses_types_resilient.swift index b3ad4def44928..cc4d1fddd841e 100644 --- a/test/Interpreter/Inputs/layout_string_witnesses_types_resilient.swift +++ b/test/Interpreter/Inputs/layout_string_witnesses_types_resilient.swift @@ -19,6 +19,12 @@ public struct GenericResilient { } } +public enum ResilientSinglePayloadEnumGeneric { + case empty0 + case empty1 + case nonEmpty0(T) +} + public enum ResilientMultiPayloadEnumGeneric { case empty0 case empty1 @@ -39,6 +45,10 @@ public enum ResilientSinglePayloadEnumComplex { case nonEmpty(ResilientMultiPayloadEnum) } +public func getResilientSinglePayloadEnumGenericEmpty0(_ t: T.Type) -> ResilientSinglePayloadEnumGeneric { + return .empty0 +} + public func getResilientMultiPayloadEnumGenericEmpty0(_ t: T.Type) -> ResilientMultiPayloadEnumGeneric { return .empty0 } diff --git a/test/Interpreter/layout_string_witnesses_dynamic.swift b/test/Interpreter/layout_string_witnesses_dynamic.swift index bd357984a286b..8a70436928b3c 100644 --- a/test/Interpreter/layout_string_witnesses_dynamic.swift +++ b/test/Interpreter/layout_string_witnesses_dynamic.swift @@ -543,6 +543,19 @@ func testResilientMultiPayloadEnumTag() { testResilientMultiPayloadEnumTag() +func testResilientSinglePayloadEnumGenericTag() { + let x = switch getResilientSinglePayloadEnumGenericEmpty0(AnyObject.self) { + case .nonEmpty0: 0 + case .empty0: 1 + case .empty1: 2 + } + + // CHECK: Enum case: 1 + print("Enum case: \(x)") +} + +testResilientSinglePayloadEnumGenericTag() + func testResilientMultiPayloadEnumGenericTag() { let x = switch getResilientMultiPayloadEnumGenericEmpty0(AnyObject.self) { case .nonEmpty0: 0