Skip to content

Commit e57b9a5

Browse files
committed
Merge pull request swiftlang#1797 from aschwaighofer/stack_allocated_nscontiguousstring
Use a stack allocated _NSContiguousString for hashing and comparison
2 parents 9e9c80e + 7fb2cce commit e57b9a5

File tree

6 files changed

+151
-32
lines changed

6 files changed

+151
-32
lines changed

lib/SILOptimizer/Analysis/EscapeAnalysis.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,36 @@ void EscapeAnalysis::analyzeInstruction(SILInstruction *I,
11731173
break;
11741174
}
11751175

1176+
if (FAS.getReferencedFunction() &&
1177+
FAS.getReferencedFunction()->hasSemanticsAttr(
1178+
"self_no_escaping_closure") &&
1179+
((FAS.hasIndirectResults() && FAS.getNumArguments() == 3) ||
1180+
(!FAS.hasIndirectResults() && FAS.getNumArguments() == 2)) &&
1181+
FAS.hasSelfArgument()) {
1182+
// The programmer has guaranteed that the closure will not capture the
1183+
// self pointer passed to it or anything that is transitively reachable
1184+
// from the pointer.
1185+
auto Args = FAS.getArgumentsWithoutIndirectResults();
1186+
// The first not indirect result argument is the closure.
1187+
setEscapesGlobal(ConGraph, Args[0]);
1188+
return;
1189+
}
1190+
1191+
if (FAS.getReferencedFunction() &&
1192+
FAS.getReferencedFunction()->hasSemanticsAttr(
1193+
"pair_no_escaping_closure") &&
1194+
((FAS.hasIndirectResults() && FAS.getNumArguments() == 4) ||
1195+
(!FAS.hasIndirectResults() && FAS.getNumArguments() == 3)) &&
1196+
FAS.hasSelfArgument()) {
1197+
// The programmer has guaranteed that the closure will not capture the
1198+
// self pointer passed to it or anything that is transitively reachable
1199+
// from the pointer.
1200+
auto Args = FAS.getArgumentsWithoutIndirectResults();
1201+
// The second not indirect result argument is the closure.
1202+
setEscapesGlobal(ConGraph, Args[1]);
1203+
return;
1204+
}
1205+
11761206
if (RecursionDepth < MaxRecursionDepth) {
11771207
CalleeList Callees = BCA->getCalleeList(FAS);
11781208
if (Callees.allCalleesVisible()) {

stdlib/public/core/String.swift

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,11 @@ extension String {
314314
public func _stdlib_compareNSStringDeterministicUnicodeCollation(
315315
lhs: AnyObject, _ rhs: AnyObject
316316
) -> Int32
317+
318+
@_silgen_name("swift_stdlib_compareNSStringDeterministicUnicodeCollationPtr")
319+
public func _stdlib_compareNSStringDeterministicUnicodeCollationPointer(
320+
lhs: OpaquePointer, _ rhs: OpaquePointer
321+
) -> Int32
317322
#endif
318323

319324
extension String : Equatable {
@@ -372,6 +377,15 @@ extension String {
372377
// Note: this operation should be consistent with equality comparison of
373378
// Character.
374379
#if _runtime(_ObjC)
380+
if self._core.hasContiguousStorage && rhs._core.hasContiguousStorage {
381+
let lhsStr = _NSContiguousString(self._core)
382+
let rhsStr = _NSContiguousString(rhs._core)
383+
let res = lhsStr._unsafeWithNotEscapedSelfPointerPair(rhsStr) {
384+
return Int(
385+
_stdlib_compareNSStringDeterministicUnicodeCollationPointer($0, $1))
386+
}
387+
return res
388+
}
375389
return Int(_stdlib_compareNSStringDeterministicUnicodeCollation(
376390
_bridgeToObjectiveCImpl(), rhs._bridgeToObjectiveCImpl()))
377391
#else
@@ -445,12 +459,12 @@ extension String {
445459

446460
#if _runtime(_ObjC)
447461
@warn_unused_result
448-
@_silgen_name("swift_stdlib_NSStringNFDHashValue")
449-
func _stdlib_NSStringNFDHashValue(str: AnyObject) -> Int
462+
@_silgen_name("swift_stdlib_NSStringHashValue")
463+
func _stdlib_NSStringHashValue(str: AnyObject, _ isASCII: Bool) -> Int
450464

451465
@warn_unused_result
452-
@_silgen_name("swift_stdlib_NSStringASCIIHashValue")
453-
func _stdlib_NSStringASCIIHashValue(str: AnyObject) -> Int
466+
@_silgen_name("swift_stdlib_NSStringHashValuePointer")
467+
func _stdlib_NSStringHashValuePointer(str: OpaquePointer, _ isASCII: Bool) -> Int
454468
#endif
455469

456470
extension String : Hashable {
@@ -470,16 +484,18 @@ extension String : Hashable {
470484
#else
471485
let hashOffset = Int(bitPattern: 0x429b_1266_88dd_cc21)
472486
#endif
473-
// FIXME(performance): constructing a temporary NSString is extremely
474-
// wasteful and inefficient.
475-
let cocoaString = unsafeBitCast(
476-
self._bridgeToObjectiveCImpl(), to: _NSStringCore.self)
477-
478-
// If we have an ASCII string, we do not need to normalize.
479-
if self._core.isASCII {
480-
return hashOffset ^ _stdlib_NSStringASCIIHashValue(cocoaString)
487+
// If we have a contigous string then we can use the stack optimization.
488+
let core = self._core
489+
let isASCII = core.isASCII
490+
if core.hasContiguousStorage {
491+
let stackAllocated = _NSContiguousString(core)
492+
return hashOffset ^ stackAllocated._unsafeWithNotEscapedSelfPointer {
493+
return _stdlib_NSStringHashValuePointer($0, isASCII )
494+
}
481495
} else {
482-
return hashOffset ^ _stdlib_NSStringNFDHashValue(cocoaString)
496+
let cocoaString = unsafeBitCast(
497+
self._bridgeToObjectiveCImpl(), to: _NSStringCore.self)
498+
return hashOffset ^ _stdlib_NSStringHashValue(cocoaString, isASCII)
483499
}
484500
#else
485501
if self._core.isASCII {

stdlib/public/core/StringBridge.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,39 @@ public final class _NSContiguousString : _SwiftNativeNSString {
269269
return self
270270
}
271271

272+
/// The caller of this function guarantees that the closure 'body' does not
273+
/// escape the object referenced by the opaque pointer passed to it or
274+
/// anything transitively reachable form this object. Doing so
275+
/// will result in undefined behavior.
276+
@_semantics("self_no_escaping_closure")
277+
func _unsafeWithNotEscapedSelfPointer<Result>(
278+
@noescape body: (OpaquePointer) throws -> Result
279+
) rethrows -> Result {
280+
let selfAsPointer = unsafeBitCast(self, to: OpaquePointer.self)
281+
defer {
282+
_fixLifetime(self)
283+
}
284+
return try body(selfAsPointer)
285+
}
286+
287+
/// The caller of this function guarantees that the closure 'body' does not
288+
/// escape either object referenced by the opaque pointer pair passed to it or
289+
/// transitively reachable objects. Doing so will result in undefined
290+
/// behavior.
291+
@_semantics("pair_no_escaping_closure")
292+
func _unsafeWithNotEscapedSelfPointerPair<Result>(
293+
rhs: _NSContiguousString,
294+
@noescape body: (OpaquePointer, OpaquePointer) throws -> Result
295+
) rethrows -> Result {
296+
let selfAsPointer = unsafeBitCast(self, to: OpaquePointer.self)
297+
let rhsAsPointer = unsafeBitCast(rhs, to: OpaquePointer.self)
298+
defer {
299+
_fixLifetime(self)
300+
_fixLifetime(rhs)
301+
}
302+
return try body(selfAsPointer, rhsAsPointer)
303+
}
304+
272305
public let _core: _StringCore
273306
}
274307

stdlib/public/stubs/SwiftNativeNSXXXBase.mm.gyb

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,22 +117,37 @@ extern "C" int32_t swift_stdlib_compareNSStringDeterministicUnicodeCollation(
117117
}
118118

119119
SWIFT_RUNTIME_STDLIB_INTERFACE
120-
extern "C" size_t
121-
swift_stdlib_NSStringNFDHashValue(NSString *NS_RELEASES_ARGUMENT str) {
122-
size_t Result = str.decomposedStringWithCanonicalMapping.hash;
123-
swift_unknownRelease(str);
120+
extern "C" int32_t
121+
swift_stdlib_compareNSStringDeterministicUnicodeCollationPtr(void *Lhs,
122+
void *Rhs) {
123+
NSString *lhs = (NSString *)Lhs;
124+
NSString *rhs = (NSString *)Rhs;
125+
126+
// 'kCFCompareNonliteral' actually means "normalize to NFD".
127+
int Result = CFStringCompare((__bridge CFStringRef)lhs,
128+
(__bridge CFStringRef)rhs, kCFCompareNonliteral);
124129
return Result;
125130
}
126131

127-
// For strings we know only have ASCII
128132
SWIFT_RUNTIME_STDLIB_INTERFACE
129133
extern "C" size_t
130-
swift_stdlib_NSStringASCIIHashValue(NSString *NS_RELEASES_ARGUMENT str) {
131-
size_t Result = str.hash;
134+
swift_stdlib_NSStringHashValue(NSString *NS_RELEASES_ARGUMENT str,
135+
bool isASCII) {
136+
size_t Result =
137+
isASCII ? str.hash : str.decomposedStringWithCanonicalMapping.hash;
138+
132139
swift_unknownRelease(str);
133140
return Result;
134141
}
135142

143+
SWIFT_RUNTIME_STDLIB_INTERFACE
144+
extern "C" size_t
145+
swift_stdlib_NSStringHashValuePointer(void *opaque, bool isASCII) {
146+
NSString *str = (NSString *)opaque;
147+
return isASCII ? str.hash : str.decomposedStringWithCanonicalMapping.hash;
148+
}
149+
150+
136151
SWIFT_RUNTIME_STDLIB_INTERFACE
137152
extern "C" bool swift_stdlib_NSStringHasPrefixNFD(NSString *theString,
138153
NSString *prefix) {

test/1_stdlib/RuntimeObjC.swift

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -543,22 +543,12 @@ RuntimeFoundationWrappers.test(
543543
expectEqual(0, nsStringCanaryCount)
544544
}
545545

546-
RuntimeFoundationWrappers.test("_stdlib_NSStringNFDHashValue/NoLeak") {
546+
RuntimeFoundationWrappers.test("_stdlib_NSStringHashValue/NoLeak") {
547547
nsStringCanaryCount = 0
548548
autoreleasepool {
549549
let a = NSStringCanary()
550550
expectEqual(1, nsStringCanaryCount)
551-
_stdlib_NSStringNFDHashValue(a)
552-
}
553-
expectEqual(0, nsStringCanaryCount)
554-
}
555-
556-
RuntimeFoundationWrappers.test("_stdlib_NSStringASCIIHashValue/NoLeak") {
557-
nsStringCanaryCount = 0
558-
autoreleasepool {
559-
let a = NSStringCanary()
560-
expectEqual(1, nsStringCanaryCount)
561-
_stdlib_NSStringASCIIHashValue(a)
551+
_stdlib_NSStringHashValue(a, true)
562552
}
563553
expectEqual(0, nsStringCanaryCount)
564554
}

test/SILOptimizer/escape_analysis.sil

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,3 +1214,38 @@ sil [_semantics "array.get_element_address"] @get_element_address : $@convention
12141214
sil [_semantics "array.get_count"] @get_count : $@convention(method) (@guaranteed Array<X>) -> Int32
12151215
sil [_semantics "array.get_capacity"] @get_capacity : $@convention(method) (@guaranteed Array<X>) -> Int32
12161216

1217+
sil [_semantics "pair_no_escaping_closure"] @unsafeWithNotEscapedSelfPointerPair : $@convention(method) (@owned X, @owned @callee_owned (X, X) -> (@out X, @error ErrorType), @guaranteed X) -> (@out X, @error ErrorType)
1218+
sil [_semantics "self_no_escaping_closure"] @unsafeWithNotEscapedSelfPointer: $@convention(method) (@owned @callee_owned (X, X) -> (@out X, @error ErrorType), @guaranteed X) -> (@out X, @error ErrorType)
1219+
1220+
// CHECK-LABEL: CG of semantics_pair_no_escaping_closure
1221+
// CHECK-NEXT: Arg %0 Esc: A, Succ:
1222+
// CHECK-NEXT: Arg %1 Esc: A, Succ:
1223+
// CHECK-NEXT: Arg %2 Esc: G, Succ: (%2.1)
1224+
// CHECK-NEXT: Con %2.1 Esc: G, Succ:
1225+
// CHECK-NEXT: Val %4 Esc: %5, Succ:
1226+
// CHECK-NEXT: End
1227+
sil @semantics_pair_no_escaping_closure : $@convention(thin) (@owned X, @guaranteed X, @owned @callee_owned (X, X) -> (@out X, @error ErrorType)) -> () {
1228+
bb(%0 : $X, %1 : $X, %2: $@callee_owned (X, X) -> (@out X, @error ErrorType)):
1229+
%3 = function_ref @unsafeWithNotEscapedSelfPointerPair : $@convention(method) (@owned X, @owned @callee_owned (X, X) -> (@out X, @error ErrorType), @guaranteed X) -> (@out X, @error ErrorType)
1230+
%4 = alloc_stack $X
1231+
%6 = apply [nothrow] %3(%4, %0, %2, %1) : $@convention(method) (@owned X, @owned @callee_owned (X, X) -> (@out X, @error ErrorType), @guaranteed X) -> (@out X, @error ErrorType)
1232+
dealloc_stack %4 : $*X
1233+
%7 = tuple()
1234+
return %7 : $()
1235+
}
1236+
1237+
// CHECK-LABEL: CG of semantics_self_no_escaping_closure
1238+
// CHECK-NEXT: Arg %0 Esc: A, Succ:
1239+
// CHECK-NEXT: Arg %1 Esc: G, Succ: (%1.1)
1240+
// CHECK-NEXT: Con %1.1 Esc: G, Succ:
1241+
// CHECK-NEXT: Val %3 Esc: %4, Succ:
1242+
// CHECK-NEXT: End
1243+
sil @semantics_self_no_escaping_closure : $@convention(thin) (@guaranteed X, @owned @callee_owned (X, X) -> (@out X, @error ErrorType)) -> () {
1244+
bb(%0 : $X, %1: $@callee_owned (X, X) -> (@out X, @error ErrorType)):
1245+
%2 = function_ref @unsafeWithNotEscapedSelfPointer : $@convention(method) (@owned @callee_owned (X, X) -> (@out X, @error ErrorType), @guaranteed X) -> (@out X, @error ErrorType)
1246+
%3 = alloc_stack $X
1247+
%6 = apply [nothrow] %2(%3, %1, %0) : $@convention(method) (@owned @callee_owned (X, X) -> (@out X, @error ErrorType), @guaranteed X) -> (@out X, @error ErrorType)
1248+
dealloc_stack %3 : $*X
1249+
%7 = tuple()
1250+
return %7 : $()
1251+
}

0 commit comments

Comments
 (0)