Skip to content

Commit 93c57f8

Browse files
committed
Implement a few silcombine transformations for arrays
- Useless existential_ref <-> class conversions. - mark_dependence_inst depending on uninteresting instructions. - release(init_existential_ref(x)) -> release(x) when hasOneUse(x) - Update COWArrayOpt to handle the new forms generated by this. these aren't massive performance wins, but do shrink the size of SIL when dealing with arrays.
1 parent 01f42c8 commit 93c57f8

File tree

6 files changed

+161
-23
lines changed

6 files changed

+161
-23
lines changed

include/swift/SIL/SILInstruction.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6000,6 +6000,13 @@ class MarkDependenceInst
60006000
SILValue getValue() const { return Operands[Value].get(); }
60016001
SILValue getBase() const { return Operands[Base].get(); }
60026002

6003+
void setValue(SILValue newVal) {
6004+
Operands[Value].set(newVal);
6005+
}
6006+
void setBase(SILValue newVal) {
6007+
Operands[Base].set(newVal);
6008+
}
6009+
60036010
ArrayRef<Operand> getAllOperands() const { return Operands.asArray(); }
60046011
MutableArrayRef<Operand> getAllOperands() { return Operands.asArray(); }
60056012
};

lib/SILOptimizer/LoopTransforms/COWArrayOpt.cpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ bool COWArrayOpt::checkSafeElementValueUses(UserOperList &ElementValueUsers) {
797797
static bool isArrayEltStore(StoreInst *SI) {
798798
SILValue Dest = stripAddressProjections(SI->getDest());
799799
if (auto *MD = dyn_cast<MarkDependenceInst>(Dest))
800-
Dest = MD->getOperand(0);
800+
Dest = MD->getValue();
801801

802802
if (auto *PtrToAddr =
803803
dyn_cast<PointerToAddressInst>(stripAddressProjections(Dest)))
@@ -1095,17 +1095,21 @@ struct HoistableMakeMutable {
10951095
DepInsts.push_back(StructExtractArrayAddr);
10961096

10971097
// Check the base the array element address is dependent on.
1098-
auto *EnumArrayAddr = dyn_cast<EnumInst>(MarkDependence->getBase());
1099-
if (!EnumArrayAddr)
1100-
return false;
1101-
DepInsts.push_back(EnumArrayAddr);
1102-
auto *UncheckedRefCast =
1103-
dyn_cast<UncheckedRefCastInst>(EnumArrayAddr->getOperand());
1104-
if (!UncheckedRefCast)
1105-
return false;
1106-
DepInsts.push_back(UncheckedRefCast);
1098+
SILValue base = MarkDependence->getBase();
1099+
1100+
// We can optionally have an enum instruction here.
1101+
if (auto *EnumArrayAddr = dyn_cast<EnumInst>(base)) {
1102+
DepInsts.push_back(EnumArrayAddr);
1103+
base = EnumArrayAddr->getOperand();
1104+
}
1105+
1106+
// We can optionally have an unchecked cast.
1107+
if (auto *UncheckedRefCast = dyn_cast<UncheckedRefCastInst>(base)) {
1108+
DepInsts.push_back(UncheckedRefCast);
1109+
base = UncheckedRefCast->getOperand();
1110+
}
11071111

1108-
SILValue ArrayBuffer = stripValueProjections(UncheckedRefCast->getOperand(), DepInsts);
1112+
SILValue ArrayBuffer = stripValueProjections(base, DepInsts);
11091113
auto *BaseLoad = dyn_cast<LoadInst>(ArrayBuffer);
11101114
if (!BaseLoad || Loop->contains(BaseLoad->getOperand()->getParentBlock()))
11111115
return false;

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ class SILCombiner :
240240
SILInstruction *visitUnreachableInst(UnreachableInst *UI);
241241
SILInstruction *visitAllocRefDynamicInst(AllocRefDynamicInst *ARDI);
242242
SILInstruction *visitEnumInst(EnumInst *EI);
243+
244+
SILInstruction *visitMarkDependenceInst(MarkDependenceInst *MDI);
245+
SILInstruction *visitInitExistentialRefInst(InitExistentialRefInst *IER);
243246
SILInstruction *visitConvertFunctionInst(ConvertFunctionInst *CFI);
244247

245248
/// Instruction visitor helpers.

lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,15 @@ SILInstruction *SILCombiner::visitStrongReleaseInst(StrongReleaseInst *SRI) {
11341134
isa<ObjCMetatypeToObjectInst>(SRI->getOperand()))
11351135
return eraseInstFromFunction(*SRI);
11361136

1137+
// Release of a classbound existential converted from a class is just a
1138+
// release of the class, squish the conversion.
1139+
if (auto ier = dyn_cast<InitExistentialRefInst>(SRI->getOperand()))
1140+
if (ier->hasOneUse()) {
1141+
SRI->setOperand(ier->getOperand());
1142+
eraseInstFromFunction(*ier);
1143+
return SRI;
1144+
}
1145+
11371146
return nullptr;
11381147
}
11391148

@@ -1406,3 +1415,63 @@ SILInstruction *SILCombiner::visitEnumInst(EnumInst *EI) {
14061415
return nullptr;
14071416
}
14081417

1418+
SILInstruction *SILCombiner::visitMarkDependenceInst(MarkDependenceInst *MDI) {
1419+
// Simplify the base operand of a MarkDependenceInst to eliminate unnecessary
1420+
// instructions that aren't adding value.
1421+
//
1422+
// Conversions to Optional.Some(x) often happen here, this isn't important
1423+
// for us, we can just depend on 'x' directly.
1424+
if (auto eiBase = dyn_cast<EnumInst>(MDI->getBase())) {
1425+
if (eiBase->hasOperand() && eiBase->hasOneUse()) {
1426+
MDI->setBase(eiBase->getOperand());
1427+
eraseInstFromFunction(*eiBase);
1428+
return MDI;
1429+
}
1430+
}
1431+
1432+
// Conversions from a class to AnyObject also happen a lot, we can just depend
1433+
// on the class reference.
1434+
if (auto ier = dyn_cast<InitExistentialRefInst>(MDI->getBase())) {
1435+
MDI->setBase(ier->getOperand());
1436+
if (ier->use_empty())
1437+
eraseInstFromFunction(*ier);
1438+
return MDI;
1439+
}
1440+
1441+
return nullptr;
1442+
}
1443+
1444+
1445+
SILInstruction *SILCombiner::
1446+
visitInitExistentialRefInst(InitExistentialRefInst *IER) {
1447+
// Arrays in particular end up with chains of init/open existential refs,
1448+
// which convert back and forth between a class reference and an existential
1449+
// reference e.g. like this:
1450+
//
1451+
// %a = init_existential_ref %x : $_ContiguousArrayStorageBase :
1452+
// $_ContiguousArrayStorageBase, $_NSArrayCore
1453+
// %b = open_existential_ref %a : $_NSArrayCore to
1454+
// $@opened("EA85...") _NSArrayCore
1455+
//
1456+
// %c = init_existential_ref %b : $@opened("EA85...") _NSArrayCore :
1457+
// $@opened("EA85...") _NSArrayCore, $AnyObject
1458+
// we can simplify this by having %c initialize itself from the %x reference
1459+
// directly.
1460+
if (auto *ORE = dyn_cast<OpenExistentialRefInst>(IER->getOperand())) {
1461+
if (auto *IER2 = dyn_cast<InitExistentialRefInst>(ORE->getOperand())) {
1462+
1463+
// We create a new instruction, instead of modifying the existing one
1464+
// in place, because we need the result type of "%c" but the operand list
1465+
// of "%a", and the number of dependent types could disagree.
1466+
return Builder.createInitExistentialRef(IER->getLoc(), IER->getType(),
1467+
IER2->getFormalConcreteType(),
1468+
IER2->getOperand(),
1469+
IER->getConformances());
1470+
1471+
}
1472+
}
1473+
1474+
return nullptr;
1475+
}
1476+
1477+

test/SILOptimizer/pointer_conversion.swift

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ func get<T>() -> T
2222
public func testArray() {
2323
let array: [Int] = get()
2424
takesConstRawPointer(array)
25-
// CHECK: [[OWNER:%.+]] = enum $Optional<AnyObject>, #Optional.some!enumelt.1,
26-
// CHECK-NEXT: [[POINTER:%.+]] = struct $UnsafeRawPointer (
27-
// CHECK-NEXT: [[DEP_POINTER:%.+]] = mark_dependence [[POINTER]] : $UnsafeRawPointer on [[OWNER]] : $Optional<AnyObject>
25+
// CHECK: [[OWNER:%.+]] = unchecked_ref_cast
26+
// CHECK: [[POINTER:%.+]] = struct $UnsafeRawPointer (
27+
// CHECK-NEXT: [[DEP_POINTER:%.+]] = mark_dependence [[POINTER]] : $UnsafeRawPointer on [[OWNER]] : $_ContiguousArrayStorageBase
2828
// CHECK: [[FN:%.+]] = function_ref @takesConstRawPointer
2929
// CHECK-NEXT: apply [[FN]]([[DEP_POINTER]])
3030
// CHECK-NOT: release
@@ -38,9 +38,9 @@ public func testArray() {
3838
public func testArrayToOptional() {
3939
let array: [Int] = get()
4040
takesOptConstRawPointer(array)
41-
// CHECK: [[OWNER:%.+]] = enum $Optional<AnyObject>, #Optional.some!enumelt.1,
42-
// CHECK-NEXT: [[POINTER:%.+]] = struct $UnsafeRawPointer (
43-
// CHECK-NEXT: [[DEP_POINTER:%.+]] = mark_dependence [[POINTER]] : $UnsafeRawPointer on [[OWNER]] : $Optional<AnyObject>
41+
// CHECK: [[OWNER:%.+]] = unchecked_ref_cast
42+
// CHECK: [[POINTER:%.+]] = struct $UnsafeRawPointer (
43+
// CHECK-NEXT: [[DEP_POINTER:%.+]] = mark_dependence [[POINTER]] : $UnsafeRawPointer on [[OWNER]] : $_ContiguousArrayStorageBase
4444
// CHECK-NEXT: [[OPT_POINTER:%.+]] = enum $Optional<UnsafeRawPointer>, #Optional.some!enumelt.1, [[DEP_POINTER]]
4545
// CHECK: [[FN:%.+]] = function_ref @takesOptConstRawPointer
4646
// CHECK-NEXT: apply [[FN]]([[OPT_POINTER]])
@@ -55,9 +55,8 @@ public func testArrayToOptional() {
5555
public func testMutableArray() {
5656
var array: [Int] = get()
5757
takesMutableRawPointer(&array)
58-
// CHECK: [[OWNER:%.+]] = enum $Optional<AnyObject>, #Optional.some!enumelt.1,
59-
// CHECK-NEXT: [[POINTER:%.+]] = struct $UnsafeMutableRawPointer (
60-
// CHECK-NEXT: [[DEP_POINTER:%.+]] = mark_dependence [[POINTER]] : $UnsafeMutableRawPointer on [[OWNER]] : $Optional<AnyObject>
58+
// CHECK: [[POINTER:%.+]] = struct $UnsafeMutableRawPointer (
59+
// CHECK-NEXT: [[DEP_POINTER:%.+]] = mark_dependence [[POINTER]] : $UnsafeMutableRawPointer on {{.*}} : $_ContiguousArrayStorageBase
6160
// CHECK: [[FN:%.+]] = function_ref @takesMutableRawPointer
6261
// CHECK-NEXT: apply [[FN]]([[DEP_POINTER]])
6362
// CHECK-NOT: release
@@ -72,9 +71,8 @@ public func testMutableArray() {
7271
public func testMutableArrayToOptional() {
7372
var array: [Int] = get()
7473
takesOptMutableRawPointer(&array)
75-
// CHECK: [[OWNER:%.+]] = enum $Optional<AnyObject>, #Optional.some!enumelt.1,
76-
// CHECK-NEXT: [[POINTER:%.+]] = struct $UnsafeMutableRawPointer (
77-
// CHECK-NEXT: [[DEP_POINTER:%.+]] = mark_dependence [[POINTER]] : $UnsafeMutableRawPointer on [[OWNER]] : $Optional<AnyObject>
74+
// CHECK: [[POINTER:%.+]] = struct $UnsafeMutableRawPointer (
75+
// CHECK-NEXT: [[DEP_POINTER:%.+]] = mark_dependence [[POINTER]] : $UnsafeMutableRawPointer on {{.*}} : $_ContiguousArrayStorageBase
7876
// CHECK-NEXT: [[OPT_POINTER:%.+]] = enum $Optional<UnsafeMutableRawPointer>, #Optional.some!enumelt.1, [[DEP_POINTER]]
7977
// CHECK: [[FN:%.+]] = function_ref @takesOptMutableRawPointer
8078
// CHECK-NEXT: apply [[FN]]([[OPT_POINTER]])
@@ -112,3 +110,28 @@ public func testOptionalArray() {
112110
// CHECK-NEXT: [[NO_OWNER:%.+]] = enum $Optional<AnyObject>, #Optional.none!enumelt
113111
// CHECK-NEXT: br [[CALL_BRANCH]]([[NO_POINTER]] : $Optional<UnsafeRawPointer>, [[NO_OWNER]] : $Optional<AnyObject>)
114112
} // CHECK: end sil function '_T018pointer_conversion17testOptionalArrayyyF'
113+
114+
115+
// CHECK-LABEL: sil @_T018pointer_conversion21arrayLiteralPromotionyyF
116+
public func arrayLiteralPromotion() {
117+
takesConstRawPointer([41,42,43,44])
118+
119+
// Stack allocate the array.
120+
// CHECK: alloc_ref [stack] [tail_elems $Int * {{.*}} : $Builtin.Word] $_ContiguousArrayStorage<Int>
121+
122+
// Store the elements.
123+
// CHECK: [[ELT:%.+]] = integer_literal $Builtin.Int{{.*}}, 41
124+
// CHECK: [[ELT:%.+]] = integer_literal $Builtin.Int{{.*}}, 42
125+
// CHECK: [[ELT:%.+]] = integer_literal $Builtin.Int{{.*}}, 43
126+
// CHECK: [[ELT:%.+]] = integer_literal $Builtin.Int{{.*}}, 44
127+
128+
// Call the function.
129+
// CHECK: [[PTR:%.+]] = mark_dependence
130+
131+
// CHECK: [[FN:%.+]] = function_ref @takesConstRawPointer
132+
// CHECK: apply [[FN]]([[PTR]])
133+
134+
// Release the heap value.
135+
// CHECK: strong_release
136+
}
137+

test/SILOptimizer/sil_combine.sil

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2083,6 +2083,19 @@ bb0(%0 : $B, %1 : $@sil_unowned B, %2 : $AnyObject, %3: $@sil_unmanaged AnyObjec
20832083
return %9999 : $(B, @sil_unowned B, AnyObject, @sil_unmanaged AnyObject)
20842084
}
20852085

2086+
// CHECK-LABEL: sil @collapse_init_open_init_ref_cast
2087+
// CHECK: bb0([[Ref:%.*]]: $MyClass):
2088+
// CHECK-NEXT: init_existential_ref
2089+
// CHECK-NEXT: return
2090+
sil @collapse_init_open_init_ref_cast : $@convention(thin) (MyClass) -> (AnyObject) {
2091+
bb0(%0: $MyClass):
2092+
%1 = init_existential_ref %0 : $MyClass : $MyClass, $AnyObject
2093+
%2 = open_existential_ref %1 : $AnyObject to $@opened("2CAE06CE-5F10-11E4-AF13-C82A1428F987") AnyObject
2094+
%3 = init_existential_ref %2 : $@opened("2CAE06CE-5F10-11E4-AF13-C82A1428F987") AnyObject : $@opened("2CAE06CE-5F10-11E4-AF13-C82A1428F987") AnyObject, $AnyObject
2095+
return %3 : $AnyObject
2096+
}
2097+
2098+
20862099
// CHECK-LABEL: sil @collapse_existential_pack_unpack_unchecked_ref_cast
20872100
// CHECK: bb0([[Ref:%.*]]: $MyClass):
20882101
// CHECK-NOT: init_existential_ref
@@ -3429,3 +3442,22 @@ bb1(%4 : $Builtin.Int32):
34293442
bb2(%5 : $MyErrorType):
34303443
throw %5 : $MyErrorType
34313444
}
3445+
3446+
// CHECK-LABEL: sil @mark_dependence_base
3447+
// CHECK: bb0(
3448+
// CHECK-NOT: init_existential_ref
3449+
// CHECK-NOT: enum
3450+
// CHECK-NEXT: mark_dependence
3451+
// CHECK-NEXT: load
3452+
// CHECK: return
3453+
sil @mark_dependence_base : $@convention(thin) (@inout Builtin.Int64, @owned B) -> Builtin.Int64 {
3454+
bb0(%0 : $*Builtin.Int64, %1 : $B):
3455+
%x = init_existential_ref %1 : $B : $B, $AnyObject
3456+
%2 = enum $Optional<AnyObject>, #Optional.some!enumelt.1, %x : $AnyObject
3457+
%3 = mark_dependence %0 : $*Builtin.Int64 on %2 : $Optional<AnyObject>
3458+
%4 = load %3 : $*Builtin.Int64
3459+
return %4 : $Builtin.Int64
3460+
}
3461+
3462+
3463+

0 commit comments

Comments
 (0)