Skip to content

Commit 85228f2

Browse files
committed
Optimizer: do mark_dependence simplification at Onone
* re-implement the SILCombine peephole as a Swift instruction simplification * run this simplification also in the OnoneSimplification pass
1 parent d10602e commit 85228f2

File tree

15 files changed

+419
-118
lines changed

15 files changed

+419
-118
lines changed

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ swift_compiler_sources(Optimizer
2929
SimplifyInitEnumDataAddr.swift
3030
SimplifyKeyPath.swift
3131
SimplifyLoad.swift
32+
SimplifyMarkDependence.swift
3233
SimplifyMisc.swift
3334
SimplifyPartialApply.swift
3435
SimplifyPointerToAddress.swift
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//===--- SimplifyMarkDependence.swift -------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SIL
14+
15+
// Note: this simplification cannot run before dependency diagnostics.
16+
// See `var isRedundant` below.
17+
18+
extension MarkDependenceInst : OnoneSimplifiable, SILCombineSimplifiable {
19+
func simplify(_ context: SimplifyContext) {
20+
if isRedundant ||
21+
// A literal lives forever, so no mark_dependence is needed.
22+
// This pattern can occur after StringOptimization when a utf8CString of a literal is replace
23+
// by the string_literal itself.
24+
value.isLiteral
25+
{
26+
replace(with: value, context)
27+
return
28+
}
29+
simplifyBaseOperand(context)
30+
}
31+
}
32+
33+
extension MarkDependenceAddrInst : OnoneSimplifiable, SILCombineSimplifiable {
34+
func simplify(_ context: SimplifyContext) {
35+
if isRedundant {
36+
context.erase(instruction: self)
37+
return
38+
}
39+
simplifyBaseOperand(context)
40+
}
41+
}
42+
43+
private extension MarkDependenceInstruction {
44+
var isRedundant: Bool {
45+
if base.type.isObject && base.type.isTrivial(in: base.parentFunction) {
46+
// Sometimes due to specialization/builtins, we can get a mark_dependence whose base is a trivial
47+
// typed object. Trivial values live forever. Therefore the mark_dependence does not have a meaning.
48+
// Note: the mark_dependence is still needed for lifetime diagnostics. So it's important that this
49+
// simplification does not run before the lifetime diagnostic pass.
50+
return true
51+
}
52+
// If the value is an address projection from the base the mark_dependence is not needed because the
53+
// base cannot be destroyed before the accessing the value, anyway.
54+
if valueOrAddress.type.isAddress, base.type.isAddress,
55+
// But we still need to keep the mark_dependence for non-escapable types because a non-escapable
56+
// value can be copied and copies must not outlive the base.
57+
valueOrAddress.type.isEscapable(in: parentFunction),
58+
base.accessPath.isEqualOrContains(valueOrAddress.accessPath)
59+
{
60+
return true
61+
}
62+
return false
63+
}
64+
65+
func simplifyBaseOperand(_ context: SimplifyContext) {
66+
/// In OSSA, the `base` is a borrow introducing operand. It is pretty complicated to change the base.
67+
/// So, for simplicity, we only do this optimization when OSSA is already lowered.
68+
if parentFunction.hasOwnership {
69+
return
70+
}
71+
// Replace the base operand with the operand of the base value if it's a certain kind of forwarding
72+
// instruction.
73+
let rootBase = base.lookThroughEnumAndExistentialRef
74+
if rootBase != base {
75+
baseOperand.set(to: rootBase, context)
76+
}
77+
}
78+
}
79+
80+
private extension Value {
81+
/// True, if this is a literal instruction or a struct of a literal instruction.
82+
/// What we want to catch here is a `UnsafePointer<Int8>` of a string literal.
83+
var isLiteral: Bool {
84+
switch self {
85+
case let s as StructInst:
86+
if let singleOperand = s.operands.singleElement {
87+
return singleOperand.value.isLiteral
88+
}
89+
return false
90+
case is IntegerLiteralInst, is FloatLiteralInst, is StringLiteralInst:
91+
return true
92+
default:
93+
return false
94+
}
95+
}
96+
97+
var lookThroughEnumAndExistentialRef: Value {
98+
switch self {
99+
case let e as EnumInst:
100+
if let payload = e.payload {
101+
return payload.lookThroughEnumAndExistentialRef
102+
}
103+
return self
104+
case let ier as InitExistentialRefInst:
105+
return ier.instance.lookThroughEnumAndExistentialRef
106+
case let oer as OpenExistentialRefInst:
107+
return oer.existential.lookThroughEnumAndExistentialRef
108+
default:
109+
return self
110+
}
111+
}
112+
}

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ private func registerSwiftPasses() {
122122
registerForSILCombine(DestructureTupleInst.self, { run(DestructureTupleInst.self, $0) })
123123
registerForSILCombine(TypeValueInst.self, { run(TypeValueInst.self, $0) })
124124
registerForSILCombine(ClassifyBridgeObjectInst.self, { run(ClassifyBridgeObjectInst.self, $0) })
125+
registerForSILCombine(MarkDependenceInst.self, { run(MarkDependenceInst.self, $0) })
126+
registerForSILCombine(MarkDependenceAddrInst.self, { run(MarkDependenceAddrInst.self, $0) })
125127
registerForSILCombine(PointerToAddressInst.self, { run(PointerToAddressInst.self, $0) })
126128
registerForSILCombine(UncheckedEnumDataInst.self, { run(UncheckedEnumDataInst.self, $0) })
127129
registerForSILCombine(WitnessMethodInst.self, { run(WitnessMethodInst.self, $0) })

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,6 @@ class SILCombiner :
291291
SILInstruction *visitUnreachableInst(UnreachableInst *UI);
292292
SILInstruction *visitAllocRefDynamicInst(AllocRefDynamicInst *ARDI);
293293

294-
SILInstruction *visitMarkDependenceInst(MarkDependenceInst *MDI);
295-
SILInstruction *visitMarkDependenceAddrInst(MarkDependenceAddrInst *MDI);
296294
SILInstruction *visitConvertFunctionInst(ConvertFunctionInst *CFI);
297295
SILInstruction *
298296
visitConvertEscapeToNoEscapeInst(ConvertEscapeToNoEscapeInst *Cvt);

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 0 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,90 +1641,6 @@ visitAllocRefDynamicInst(AllocRefDynamicInst *ARDI) {
16411641
return NewInst;
16421642
}
16431643

1644-
/// Returns true if \p val is a literal instruction or a struct of a literal
1645-
/// instruction.
1646-
/// What we want to catch here is a UnsafePointer<Int8> of a string literal.
1647-
static bool isLiteral(SILValue val) {
1648-
while (auto *str = dyn_cast<StructInst>(val)) {
1649-
if (str->getNumOperands() != 1)
1650-
return false;
1651-
val = str->getOperand(0);
1652-
}
1653-
return isa<LiteralInst>(val);
1654-
}
1655-
1656-
template<SILInstructionKind Opc, typename Derived>
1657-
static SILInstruction *combineMarkDependenceBaseInst(
1658-
MarkDependenceInstBase<Opc, Derived> *mdi,
1659-
SILCombiner *C) {
1660-
1661-
if (!mdi->getFunction()->hasOwnership()) {
1662-
// Simplify the base operand of a MarkDependenceInst to eliminate
1663-
// unnecessary instructions that aren't adding value.
1664-
//
1665-
// Conversions to Optional.Some(x) often happen here, this isn't important
1666-
// for us, we can just depend on 'x' directly.
1667-
if (auto *eiBase = dyn_cast<EnumInst>(mdi->getBase())) {
1668-
if (eiBase->hasOperand()) {
1669-
mdi->setBase(eiBase->getOperand());
1670-
if (eiBase->use_empty()) {
1671-
C->eraseInstFromFunction(*eiBase);
1672-
}
1673-
return mdi;
1674-
}
1675-
}
1676-
1677-
// Conversions from a class to AnyObject also happen a lot, we can just
1678-
// depend on the class reference.
1679-
if (auto *ier = dyn_cast<InitExistentialRefInst>(mdi->getBase())) {
1680-
mdi->setBase(ier->getOperand());
1681-
if (ier->use_empty())
1682-
C->eraseInstFromFunction(*ier);
1683-
return mdi;
1684-
}
1685-
1686-
// Conversions from a class to AnyObject also happen a lot, we can just
1687-
// depend on the class reference.
1688-
if (auto *oeri = dyn_cast<OpenExistentialRefInst>(mdi->getBase())) {
1689-
mdi->setBase(oeri->getOperand());
1690-
if (oeri->use_empty())
1691-
C->eraseInstFromFunction(*oeri);
1692-
return mdi;
1693-
}
1694-
}
1695-
1696-
// Sometimes due to specialization/builtins, we can get a mark_dependence
1697-
// whose base is a trivial typed object. In such a case, the mark_dependence
1698-
// does not have a meaning, so just eliminate it.
1699-
{
1700-
SILType baseType = mdi->getBase()->getType();
1701-
if (baseType.getObjectType().isTrivial(*mdi->getFunction())) {
1702-
if (auto mdValue = dyn_cast<MarkDependenceInst>(mdi)) {
1703-
auto &valOper = mdi->getAllOperands()[MarkDependenceInst::Dependent];
1704-
mdValue->replaceAllUsesWith(valOper.get());
1705-
}
1706-
return C->eraseInstFromFunction(*mdi);
1707-
}
1708-
}
1709-
return nullptr;
1710-
}
1711-
1712-
SILInstruction *SILCombiner::visitMarkDependenceInst(MarkDependenceInst *mdi) {
1713-
if (isLiteral(mdi->getValue())) {
1714-
// A literal lives forever, so no mark_dependence is needed.
1715-
// This pattern can occur after StringOptimization when a utf8CString of
1716-
// a literal is replace by the string_literal itself.
1717-
replaceInstUsesWith(*mdi, mdi->getValue());
1718-
return eraseInstFromFunction(*mdi);
1719-
}
1720-
return combineMarkDependenceBaseInst(mdi, this);
1721-
}
1722-
1723-
SILInstruction *
1724-
SILCombiner::visitMarkDependenceAddrInst(MarkDependenceAddrInst *mdi) {
1725-
return combineMarkDependenceBaseInst(mdi, this);
1726-
}
1727-
17281644
/// Returns true if reference counting and debug_value users of a global_value
17291645
/// can be deleted.
17301646
static bool checkGlobalValueUsers(SILValue val,

lib/SILOptimizer/SILCombiner/Simplifications.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ INSTRUCTION_SIMPLIFICATION(CopyBlockInst)
4545
INSTRUCTION_SIMPLIFICATION(DestroyValueInst)
4646
INSTRUCTION_SIMPLIFICATION(DestructureStructInst)
4747
INSTRUCTION_SIMPLIFICATION(DestructureTupleInst)
48+
INSTRUCTION_SIMPLIFICATION(MarkDependenceInst)
49+
INSTRUCTION_SIMPLIFICATION(MarkDependenceAddrInst)
4850
INSTRUCTION_SIMPLIFICATION(PointerToAddressInst)
4951
INSTRUCTION_SIMPLIFICATION(TypeValueInst)
5052
INSTRUCTION_SIMPLIFICATION(UncheckedAddrCastInst)

test/SILGen/addressors.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ func test0() {
6565
// CHECK: [[T1:%.*]] = apply [[T0]]({{%.*}}, [[AVAL]])
6666
// CHECK: [[T2:%.*]] = struct_extract [[T1]] : $UnsafePointer<Int32>, #UnsafePointer._rawValue
6767
// CHECK: [[T3:%.*]] = pointer_to_address [[T2]] : $Builtin.RawPointer to [strict] $*Int32
68-
// CHECK: [[MD:%.*]] = mark_dependence [nonescaping] [[T3]] : $*Int32 on [[AVAL]] : $A
69-
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unsafe] [[MD]] : $*Int32
68+
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unsafe] [[T3]] : $*Int32
7069
// CHECK: [[Z:%.*]] = load [[ACCESS]] : $*Int32
7170
let z = a[10]
7271

@@ -102,8 +101,7 @@ func test1() -> Int32 {
102101
// CHECK: [[PTR:%.*]] = apply [[ACCESSOR]]({{%.*}}, [[A]]) : $@convention(method) (Int32, A) -> UnsafePointer<Int32>
103102
// CHECK: [[T0:%.*]] = struct_extract [[PTR]] : $UnsafePointer<Int32>, #UnsafePointer._rawValue
104103
// CHECK: [[T1:%.*]] = pointer_to_address [[T0]] : $Builtin.RawPointer to [strict] $*Int32
105-
// CHECK: [[MD:%.*]] = mark_dependence [nonescaping] [[T1]] : $*Int32 on [[A]] : $A
106-
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unsafe] [[MD]] : $*Int32
104+
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unsafe] [[T1]] : $*Int32
107105
// CHECK: [[T2:%.*]] = load [[ACCESS]] : $*Int32
108106
// CHECK: return [[T2]] : $Int32
109107
return A()[0]
@@ -198,9 +196,8 @@ func test_carray(_ array: inout CArray<(Int32) -> Int32>) -> Int32 {
198196
// CHECK: [[T2:%.*]] = apply [[T1]]<(Int32) -> Int32>({{%.*}}, [[T0]])
199197
// CHECK: [[T3:%.*]] = struct_extract [[T2]] : $UnsafePointer<(Int32) -> Int32>, #UnsafePointer._rawValue
200198
// CHECK: [[T4:%.*]] = pointer_to_address [[T3]] : $Builtin.RawPointer to [strict] $*@callee_guaranteed @substituted <τ_0_0, τ_0_1> (@in_guaranteed τ_0_0) -> @out τ_0_1 for <Int32, Int32>
201-
// CHECK: [[MD:%.*]] = mark_dependence [nonescaping] [[T4]] : $*@callee_guaranteed @substituted <τ_0_0, τ_0_1>
202199
// (@in_guaranteed τ_0_0) -> @out τ_0_1 for <Int32, Int32> on [[T0]] : $CArray<(Int32) -> Int32>
203-
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unsafe] [[MD]]
200+
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unsafe] [[T4]]
204201
// CHECK: [[T5:%.*]] = load [[ACCESS]]
205202
return array[1](5)
206203
}
@@ -289,8 +286,7 @@ struct E {
289286
// CHECK: [[T1:%.*]] = apply [[T0]]([[E]])
290287
// CHECK: [[T2:%.*]] = struct_extract [[T1]]
291288
// CHECK: [[T3:%.*]] = pointer_to_address [[T2]]
292-
// CHECK: [[MD:%.*]] = mark_dependence [nonescaping] [[T3]] : $*Int32 on %0 : $E
293-
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [unsafe] [[MD]] : $*Int32
289+
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [unsafe] [[T3]] : $*Int32
294290
// CHECK: store {{%.*}} to [[ACCESS]] : $*Int32
295291
func test_e(_ e: E) {
296292
e.value = 0

test/SILOptimizer/definite-init-convert-to-escape.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public func returnOptionalEscape() -> (() ->())?
9595
// NOPEEPHOLE-NEXT: strong_retain [[V2]]
9696
// NOPEEPHOLE-NEXT: [[CVT:%.*]] = convert_escape_to_noescape [[V2]]
9797
// NOPEEPHOLE-NEXT: [[SOME:%.*]] = enum $Optional<{{.*}}>, #Optional.some!enumelt, [[V2]]
98-
// NOPEEPHOLE-NEXT: [[MDI:%.*]] = mark_dependence [[CVT]] : $@noescape @callee_guaranteed () -> () on [[SOME]] : $Optional<@callee_guaranteed () -> ()>
98+
// NOPEEPHOLE-NEXT: [[MDI:%.*]] = mark_dependence [[CVT]] : $@noescape @callee_guaranteed () -> () on [[V2]]
9999
// NOPEEPHOLE-NEXT: [[NOESCAPE_SOME:%.*]] = enum $Optional<{{.*}}>, #Optional.some!enumelt, [[MDI]]
100100
// NOPEEPHOLE-NEXT: strong_release [[V2]]
101101
// NOPEEPHOLE-NEXT: br bb2([[NOESCAPE_SOME]] : $Optional<{{.*}}>, [[SOME]] : $Optional<{{.*}}>, [[SOME]] : $Optional<{{.*}}>)

test/SILOptimizer/lifetime_dependence/coroutine.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,7 @@ func use(_ o : borrowing View) {}
8686
// let wrapper = nc.wrapper
8787
// CHECK: [[ACCESS:%.*]] = begin_access [read] [static] [[NC]]
8888
// CHECK: [[NCVAL:%.*]] = load [[ACCESS]]
89-
// CHECK: ([[YIELD1:%.*]], [[TOKEN1:%.*]]) = begin_apply %{{.*}}([[NCVAL]]) : $@yield_once @convention(method) (@guaranteed NCContainer) -> @lifetime(borrow 0) @yields @guaranteed Wrapper
90-
// CHECK: [[WRAPPER:%.*]] = mark_dependence [nonescaping] [[YIELD1]] on [[TOKEN1]]
89+
// CHECK: ([[WRAPPER:%.*]], [[TOKEN1:%.*]]) = begin_apply %{{.*}}([[NCVAL]]) : $@yield_once @convention(method) (@guaranteed NCContainer) -> @lifetime(borrow 0) @yields @guaranteed Wrapper
9190
// CHECK: retain_value [[WRAPPER]]
9291
// CHECK: debug_value [[WRAPPER]], let, name "wrapper"
9392
// let view = wrapper.view
@@ -99,7 +98,7 @@ func use(_ o : borrowing View) {}
9998
// CHECK: apply %{{.*}}([[VIEW]]) : $@convention(thin) (@guaranteed View) -> ()
10099
// CHECK: release_value [[VIEW]]
101100
// CHECK: release_value [[WRAPPER]]
102-
// CHECK: %24 = end_apply [[TOKEN1]] as $()
101+
// CHECK: end_apply [[TOKEN1]] as $()
103102
// CHECK: end_access [[ACCESS]]
104103
// CHECK: destroy_addr [[NC]]
105104
// CHECK-LABEL: } // end sil function '$s9coroutine20testDoubleNestedRead2ncyAA11NCContainerVn_tF'

test/SILOptimizer/lifetime_dependence/diagnostic_passes.sil

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// RUN: %target-sil-opt %s \
2-
// RUN: -diagnostics -sil-verify-all \
2+
// RUN: -diagnostics -sil-verify-all -sil-disable-pass=onone-simplification \
33
// RUN: -enable-experimental-feature LifetimeDependence \
44
// RUN: | %FileCheck %s
55

test/SILOptimizer/lifetime_dependence/lifetime_dependence_scope_fixup.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend %s -Xllvm -sil-print-types -emit-sil \
1+
// RUN: %target-swift-frontend %s -Xllvm -sil-print-types -Xllvm -sil-disable-pass=onone-simplification -emit-sil \
22
// RUN: -enable-experimental-feature LifetimeDependence \
33
// RUN: | %FileCheck %s
44

test/SILOptimizer/sil_combine.sil

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3759,12 +3759,9 @@ bb2(%5 : $MyErrorType):
37593759
}
37603760

37613761
// CHECK-LABEL: sil @mark_dependence_base
3762-
// CHECK: bb0(
3763-
// CHECK-NOT: init_existential_ref
3764-
// CHECK-NOT: enum
3765-
// CHECK-NEXT: mark_dependence
3766-
// CHECK-NEXT: load
3767-
// CHECK: return
3762+
// CHECK: [[MD:%.*]] = mark_dependence %0 : $*Builtin.Int64 on %1 : $B
3763+
// CHECK-NEXT: load [[MD]]
3764+
// CHECK: } // end sil function 'mark_dependence_base'
37683765
sil @mark_dependence_base : $@convention(thin) (@inout Builtin.Int64, @owned B) -> Builtin.Int64 {
37693766
bb0(%0 : $*Builtin.Int64, %1 : $B):
37703767
%x = init_existential_ref %1 : $B : $B, $AnyObject
@@ -3788,10 +3785,8 @@ bb0(%0 : $B):
37883785
}
37893786

37903787
// CHECK-LABEL: sil @mark_dependence_trivial_address_base :
3791-
// CHECK: bb0(
3792-
// CHECK-NEXT: strong_retain
3793-
// CHECK-NEXT: return
3794-
// CHECK: } // end sil function 'mark_dependence_trivial_address_base'
3788+
// CHECK: mark_dependence
3789+
// CHECK: } // end sil function 'mark_dependence_trivial_address_base'
37953790
sil @mark_dependence_trivial_address_base : $@convention(thin) (@guaranteed B) -> @owned B {
37963791
bb0(%0 : $B):
37973792
strong_retain %0 : $B
@@ -3804,12 +3799,9 @@ bb0(%0 : $B):
38043799
protocol _NSArrayCore {}
38053800

38063801
// CHECK-LABEL: sil @mark_dependence_base2
3807-
// CHECK: bb0(
3808-
// CHECK-NOT: open_existential_ref
3809-
// CHECK-NOT: enum
3810-
// CHECK-NEXT: mark_dependence
3811-
// CHECK-NEXT: load
3812-
// CHECK: return
3802+
// CHECK: [[MD:%.*]] = mark_dependence %0 : $*Builtin.Int64 on %1 : $B
3803+
// CHECK-NEXT: load [[MD]]
3804+
// CHECK: } // end sil function 'mark_dependence_base2'
38133805
sil @mark_dependence_base2 : $@convention(thin) (@inout Builtin.Int64, @owned B) -> Builtin.Int64 {
38143806
bb0(%0 : $*Builtin.Int64, %1 : $B):
38153807
%2 = init_existential_ref %1 : $B : $B, $AnyObject

0 commit comments

Comments
 (0)