Skip to content

Commit 5d66984

Browse files
authored
Merge pull request #31277 from milseman/deconstring_5_3
[5.3] [string] Add _deconstructUTF8 for internal usage
2 parents e9ee200 + 41a8f21 commit 5d66984

File tree

3 files changed

+241
-1
lines changed

3 files changed

+241
-1
lines changed

stdlib/public/core/StringObject.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,14 @@ extension _StringObject {
856856
return Builtin.reinterpretCast(largeAddressBits)
857857
#endif
858858
}
859+
860+
@_alwaysEmitIntoClient
861+
@inlinable
862+
@inline(__always)
863+
internal var owner: AnyObject? {
864+
guard self.isMortal else { return nil }
865+
return Builtin.reinterpretCast(largeAddressBits)
866+
}
859867
}
860868

861869
// Aggregate queries / abstractions

stdlib/public/core/StringTesting.swift

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,20 @@ struct _StringRepresentation {
3939
extension String {
4040
public // @testable
4141
func _classify() -> _StringRepresentation { return _guts._classify() }
42+
43+
@_alwaysEmitIntoClient
44+
public // @testable
45+
func _deconstructUTF8<ToPointer: _Pointer>(
46+
scratch: UnsafeMutableRawBufferPointer?
47+
) -> (
48+
owner: AnyObject?,
49+
ToPointer,
50+
length: Int,
51+
usesScratch: Bool,
52+
allocatedMemory: Bool
53+
) {
54+
_guts._deconstructUTF8(scratch: scratch)
55+
}
4256
}
4357

4458
extension _StringGuts {
@@ -72,5 +86,92 @@ extension _StringGuts {
7286
}
7387
fatalError()
7488
}
75-
}
7689

90+
91+
/*
92+
93+
Deconstruct the string into contiguous UTF-8, allocating memory if necessary
94+
95+
┌────────────────────╥───────────────────────┬─────────────────────┬─────────────┬─────────────────┐
96+
│ Form ║ owner │ pointer+length │ usesScratch │ allocatedMemory │
97+
├────────────────────╫───────────────────────┼─────────────────────┼─────────────┼─────────────────┤
98+
│ small with scratch ║ nil │ `scratch` │ true │ false │
99+
├────────────────────╫───────────────────────┼─────────────────────┼─────────────┼─────────────────┤
100+
│ small w/o scratch ║ extra allocation │ `owner` pointer │ false │ true │
101+
╞════════════════════╬═══════════════════════╪═════════════════════╪═════════════╪═════════════════╡
102+
│ immortal, large ║ nil │ literal pointer │ false │ false │
103+
├────────────────────╫───────────────────────┼─────────────────────┼─────────────┼─────────────────┤
104+
│ native ║ __StringStorage │ tail alloc pointer │ false │ false │
105+
╞════════════════════╬═══════════════════════╪═════════════════════╪═════════════╪═════════════════╡
106+
│ shared ║ __SharedStringStorage │ shared pointer │ false │ false │
107+
├────────────────────╫───────────────────────┼─────────────────────┼─────────────┼─────────────────┤
108+
│ shared, bridged ║ _CocoaString │ cocoa ASCII pointer │ false │ false │
109+
╞════════════════════╬═══════════════════════╪═════════════════════╪═════════════╪═════════════════╡
110+
│ foreign ║ extra allocation │ `owner` pointer │ false │ true │
111+
└────────────────────╨───────────────────────┴─────────────────────┴─────────────┴─────────────────┘
112+
113+
*/
114+
@_alwaysEmitIntoClient
115+
internal // TODO: figure out if this works as a compiler intrinsic
116+
func _deconstructUTF8<ToPointer: _Pointer>(
117+
scratch: UnsafeMutableRawBufferPointer?
118+
) -> (
119+
owner: AnyObject?,
120+
ToPointer,
121+
length: Int,
122+
usesScratch: Bool,
123+
allocatedMemory: Bool
124+
) {
125+
126+
// If we're small, try to copy into the scratch space provided
127+
if self.isSmall {
128+
let smol = self.asSmall
129+
if let scratch = scratch, scratch.count > smol.count {
130+
let scratchStart =
131+
scratch.baseAddress!
132+
smol.withUTF8 { smolUTF8 -> () in
133+
scratchStart.initializeMemory(
134+
as: UInt8.self, from: smolUTF8.baseAddress!, count: smolUTF8.count)
135+
}
136+
scratch[smol.count] = 0
137+
return (
138+
owner: nil,
139+
_convertPointerToPointerArgument(scratchStart),
140+
length: smol.count,
141+
usesScratch: true, allocatedMemory: false)
142+
}
143+
} else if _fastPath(self.isFastUTF8) {
144+
let ptr: ToPointer =
145+
_convertPointerToPointerArgument(self._object.fastUTF8.baseAddress!)
146+
return (
147+
owner: self._object.owner,
148+
ptr,
149+
length: self._object.count,
150+
usesScratch: false, allocatedMemory: false)
151+
}
152+
153+
let (object, ptr, len) = self._allocateForDeconstruct()
154+
return (
155+
owner: object,
156+
_convertPointerToPointerArgument(ptr),
157+
length: len,
158+
usesScratch: false,
159+
allocatedMemory: true)
160+
}
161+
162+
@_alwaysEmitIntoClient
163+
@inline(never) // slow path
164+
internal
165+
func _allocateForDeconstruct() -> (
166+
owner: AnyObject,
167+
UnsafeRawPointer,
168+
length: Int
169+
) {
170+
let utf8 = Array(String(self).utf8) + [0]
171+
let (owner, ptr): (AnyObject?, UnsafeRawPointer) =
172+
_convertConstArrayToPointerArgument(utf8)
173+
174+
// Array's owner cannot be nil, even though it is declared optional...
175+
return (owner: owner!, ptr, length: utf8.count - 1)
176+
}
177+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
4+
import StdlibUnittest
5+
defer { runAllTests() }
6+
7+
var StringDeconstructTests = TestSuite("StringDeconstructTests")
8+
9+
enum ExpectedDeconstruction {
10+
case scratchIfAvailable
11+
case interiorPointer
12+
case extraAllocation
13+
}
14+
15+
func expectDeconstruct(
16+
_ str: String,
17+
_ expectDeconstruct: ExpectedDeconstruction,
18+
stackTrace: SourceLocStack = SourceLocStack(),
19+
showFrame: Bool = true,
20+
file: String = #file, line: UInt = #line
21+
) {
22+
var stackTrace = stackTrace.pushIf(showFrame, file: file, line: line)
23+
let expectBytes = Array(str.utf8)
24+
25+
_ = Array<UInt8>(unsafeUninitializedCapacity: 16) {
26+
buffer, initializedCount in
27+
// Deconstruct with a provided scratch space
28+
29+
// WS == with scratch, N == nil
30+
let scratch = UnsafeMutableRawBufferPointer(buffer)
31+
let (ownerWS, ptrWS, lengthWS, usedScratchWS, allocatedMemoryWS)
32+
: (AnyObject?, UnsafePointer<UInt8>, Int, Bool, Bool)
33+
= str._deconstructUTF8(scratch: scratch)
34+
let (ownerN, ptrN, lengthN, usedScratchN, allocatedMemoryN)
35+
: (AnyObject?, UnsafePointer<UInt8>, Int, Bool, Bool)
36+
= str._deconstructUTF8(scratch: nil)
37+
38+
let rawBytesWS = UnsafeRawBufferPointer(start: ptrWS, count: lengthWS)
39+
let rawBytesN = UnsafeRawBufferPointer(start: ptrN, count: lengthN)
40+
41+
expectEqualSequence(expectBytes, rawBytesWS, stackTrace: stackTrace)
42+
expectEqualSequence(rawBytesWS, rawBytesN, stackTrace: stackTrace)
43+
44+
switch expectDeconstruct {
45+
case .scratchIfAvailable:
46+
expectNil(ownerWS, stackTrace: stackTrace)
47+
expectNotNil(ownerN, stackTrace: stackTrace)
48+
49+
expectEqual(scratch.baseAddress, rawBytesWS.baseAddress,
50+
stackTrace: stackTrace)
51+
expectNotEqual(scratch.baseAddress, rawBytesN.baseAddress,
52+
stackTrace: stackTrace)
53+
54+
expectTrue(lengthWS < scratch.count, stackTrace: stackTrace)
55+
expectTrue(lengthN < scratch.count, stackTrace: stackTrace)
56+
57+
expectTrue(usedScratchWS, stackTrace: stackTrace)
58+
expectFalse(usedScratchN, stackTrace: stackTrace)
59+
60+
expectFalse(allocatedMemoryWS, stackTrace: stackTrace)
61+
expectTrue(allocatedMemoryN, stackTrace: stackTrace)
62+
63+
case .interiorPointer:
64+
// TODO: owner == (immortal ? nil : StringObject.largeAddress)
65+
expectTrue(str.isContiguousUTF8, stackTrace: stackTrace)
66+
var copy = str
67+
copy.withUTF8 {
68+
expectEqual($0.baseAddress, ptrWS, stackTrace: stackTrace)
69+
expectEqual($0.baseAddress, ptrN, stackTrace: stackTrace)
70+
expectEqual($0.count, lengthWS, stackTrace: stackTrace)
71+
expectEqual($0.count, lengthN, stackTrace: stackTrace)
72+
}
73+
74+
expectFalse(usedScratchWS, stackTrace: stackTrace)
75+
expectFalse(usedScratchN, stackTrace: stackTrace)
76+
expectFalse(allocatedMemoryWS, stackTrace: stackTrace)
77+
expectFalse(allocatedMemoryN, stackTrace: stackTrace)
78+
case .extraAllocation:
79+
expectFalse(str.isContiguousUTF8, stackTrace: stackTrace)
80+
expectNotNil(ownerWS, stackTrace: stackTrace)
81+
expectNotNil(ownerN, stackTrace: stackTrace)
82+
expectFalse(usedScratchWS, stackTrace: stackTrace)
83+
expectFalse(usedScratchN, stackTrace: stackTrace)
84+
expectTrue(allocatedMemoryWS, stackTrace: stackTrace)
85+
expectTrue(allocatedMemoryN, stackTrace: stackTrace)
86+
}
87+
}
88+
}
89+
90+
@inline(never)
91+
func id<T>(_ a: T) -> T { a }
92+
93+
StringDeconstructTests.test("deconstruct") {
94+
let smallASCII = "abcd"
95+
96+
#if arch(i386) || arch(arm) || arch(wasm32)
97+
let smallUTF8 = "ジッパ"
98+
#else
99+
let smallUTF8 = "ジッパー"
100+
#endif
101+
102+
let large = "the quick fox jumped over the lazy brown dog"
103+
104+
var largeMortal = large
105+
largeMortal.append(id("🧟‍♀️"))
106+
largeMortal.append(id(largeMortal.last!))
107+
108+
expectDeconstruct(smallASCII, .scratchIfAvailable)
109+
expectDeconstruct(smallUTF8, .scratchIfAvailable)
110+
expectDeconstruct(large, .interiorPointer)
111+
expectDeconstruct(largeMortal, .interiorPointer)
112+
}
113+
114+
#if _runtime(_ObjC)
115+
import Foundation
116+
StringDeconstructTests.test("deconstruct cocoa") {
117+
let smallCocoa: NSString = "aaa"
118+
let largeASCIICocoa: NSString = "the quick fox jumped over the lazy brown dog"
119+
let largeCocoa: NSString = "the quick 🧟‍♀️ ate the slow 🧠"
120+
121+
#if arch(i386) || arch(arm) || arch(wasm32)
122+
expectDeconstruct(smallCocoa as String, .interiorPointer)
123+
#else
124+
expectDeconstruct(smallCocoa as String, .scratchIfAvailable)
125+
#endif
126+
127+
expectDeconstruct(largeASCIICocoa as String, .interiorPointer)
128+
expectDeconstruct(largeCocoa as String, .extraAllocation)
129+
}
130+
#endif
131+

0 commit comments

Comments
 (0)