Skip to content

Commit 384dd61

Browse files
authored
Replace Data with SwiftProtobufContiguousBytes in serialization path (apple#1391)
Usages of `Data` in the serialisation path have been replaced by usages of the new `SwiftProtobufContiguousBytes` protocol so that `[UInt8]` can also be used in the serialisation path, avoiding unnecessary translations between arrays of bytes and `Data`. Note: we have decided **not** to change the `bytes` field of the protos from `Data` to `[UInt8]`/`SwiftProtobufContiguousBytes`, so this change does not include this. This PR includes some new `Data` allocations, which are temporary to avoid growing the PR too much. I tried to restrict my changes to just the serialisation path, and plan on sending another one for deserialisation and a third one for JSON encoding/decoding.
1 parent 199dae7 commit 384dd61

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+250
-238
lines changed

Documentation/API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public struct Example: SwiftProtobuf.Message {
6868
// An extension map may be needed when decoding nested
6969
// proto2-format messages that utilize extensions.
7070
// See below for more details.
71-
func serializedData() throws -> Data
71+
func serializedBytes<Bytes: SwiftProtobufContiguousBytes>() throws -> Bytes
7272
init<Bytes: SwiftProtobufContiguousBytes>(serializedBytes: Bytes) throws {
7373
init<Bytes: SwiftProtobufContiguousBytes>(serializedBytes: Bytes, extensions: ExtensionMap? = nil, partial: Bool = false) throws
7474

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import Foundation
2-
31
import FuzzCommon
42

53
import SwiftProtobuf
@@ -10,14 +8,14 @@ public func FuzzBinary(_ start: UnsafeRawPointer, _ count: Int) -> CInt {
108
var msg: Fuzz_Testing_Message?
119
do {
1210
msg = try Fuzz_Testing_Message(
13-
serializedData: Data(bytes),
11+
serializedBytes: Array(bytes),
1412
extensions: Fuzz_Testing_FuzzTesting_Extensions)
1513
} catch {
1614
// Error parsing are to be expected since not all input will be well formed.
1715
}
1816
// Test serialization for completeness.
1917
// If a message was parsed, it should not fail to serialize, so assert as such.
20-
let _ = try! msg?.serializedData()
18+
let _: [UInt8]? = try! msg?.serializedBytes()
2119

2220
return 0
2321
}

Performance/generators/swift.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ extension Harness {
112112
113113
// Exercise binary serialization.
114114
let data = try measureSubtask("Encode binary") {
115-
return try message.serializedData()
115+
return try message.serializedBytes()
116116
}
117117
let message2 = try measureSubtask("Decode binary") {
118118
return try PerfMessage(serializedData: data)

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,17 @@ let info2 = BookInfo.with {
261261
$0.author = "Jane Q. Smith"
262262
}
263263

264-
// Serialize to binary protobuf format:
265-
let binaryData: Data = try info.serializedData()
264+
// Serialize to binary protobuf format: you can choose to serialize into
265+
// any type conforming to SwiftProtobufContiguousBytes. For example:
266+
let binaryData: Data = try info.serializedBytes()
267+
let binaryDataAsBytes: [UInt8] = try info.serializedBytes()
266268

267269
// Deserialize a received Data object from `binaryData`
268270
let decodedInfo = try BookInfo(serializedData: binaryData)
269271

272+
// Deserialize a received [UInt8] object from `binaryDataAsBytes`
273+
let decodedInfo = try BookInfo(serializedBytes: binaryDataAsBytes)
274+
270275
// Serialize to JSON format as a Data object
271276
let jsonData: Data = try info.jsonUTF8Data()
272277

Sources/Conformance/main.swift

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@ import Glibc
2121
import Darwin.C
2222
#endif
2323

24-
import Foundation
2524
import SwiftProtobuf
2625

27-
func readRequest() -> Data? {
26+
func readRequest() -> [UInt8]? {
2827
var rawCount: UInt32 = 0
2928
let read1 = fread(&rawCount, 1, 4, stdin)
3029
let count = Int(rawCount)
@@ -36,11 +35,10 @@ func readRequest() -> Data? {
3635
if read2 < count {
3736
return nil
3837
}
39-
return Data(buff)
38+
return buff
4039
}
4140

42-
func writeResponse(data: Data) {
43-
let bytes = [UInt8](data)
41+
func writeResponse(bytes: [UInt8]) {
4442
var count = UInt32(bytes.count)
4543
fwrite(&count, 4, 1, stdout)
4644
_ = bytes.withUnsafeBufferPointer { bp in
@@ -49,12 +47,12 @@ func writeResponse(data: Data) {
4947
fflush(stdout)
5048
}
5149

52-
func buildResponse(serializedData: Data) -> Conformance_ConformanceResponse {
50+
func buildResponse(serializedBytes: [UInt8]) -> Conformance_ConformanceResponse {
5351
var response = Conformance_ConformanceResponse()
5452

5553
let request: Conformance_ConformanceRequest
5654
do {
57-
request = try Conformance_ConformanceRequest(serializedData: serializedData)
55+
request = try Conformance_ConformanceRequest(serializedBytes: serializedBytes)
5856
} catch {
5957
response.runtimeError = "Failed to parse conformance request"
6058
return response
@@ -142,7 +140,7 @@ func buildResponse(serializedData: Data) -> Conformance_ConformanceResponse {
142140
switch request.requestedOutputFormat {
143141
case .protobuf:
144142
do {
145-
response.protobufPayload = try testMessage.serializedData()
143+
response.protobufPayload = try testMessage.serializedBytes()
146144
} catch let e {
147145
response.serializeError = "Failed to serialize: \(e)"
148146
}
@@ -170,9 +168,9 @@ func buildResponse(serializedData: Data) -> Conformance_ConformanceResponse {
170168

171169
func singleTest() throws -> Bool {
172170
if let indata = readRequest() {
173-
let response = buildResponse(serializedData: indata)
174-
let outdata = try response.serializedData()
175-
writeResponse(data: outdata)
171+
let response = buildResponse(serializedBytes: indata)
172+
let outdata: [UInt8] = try response.serializedBytes()
173+
writeResponse(bytes: outdata)
176174
return true
177175
} else {
178176
return false

Sources/SwiftProtobufCore/AnyMessageStorage.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ fileprivate func emitVerboseTextForm(visitor: inout TextFormatEncodingVisitor, m
4343
visitor.visitAnyVerbose(value: message, typeURL: url)
4444
}
4545

46-
fileprivate func asJSONObject(body: [UInt8]) -> [UInt8] {
46+
fileprivate func asJSONObject(body: [UInt8]) -> Data {
4747
let asciiOpenCurlyBracket = UInt8(ascii: "{")
4848
let asciiCloseCurlyBracket = UInt8(ascii: "}")
4949
var result = [asciiOpenCurlyBracket]
5050
result.append(contentsOf: body)
5151
result.append(asciiCloseCurlyBracket)
52-
return result
52+
return Data(result)
5353
}
5454

5555
fileprivate func unpack(contentJSON: [UInt8],
@@ -102,7 +102,7 @@ internal class AnyMessageStorage {
102102
return Data(value)
103103
case .message(let message):
104104
do {
105-
return try message.serializedData(partial: true)
105+
return try message.serializedBytes(partial: true)
106106
} catch {
107107
return Data()
108108
}
@@ -115,14 +115,14 @@ internal class AnyMessageStorage {
115115
extensions: SimpleExtensionMap(),
116116
options: options,
117117
as: messageType)
118-
return try m.serializedData(partial: true)
118+
return try m.serializedBytes(partial: true)
119119
} catch {
120120
return Data()
121121
}
122122
}
123123
}
124124
set {
125-
state = .binary(Array(newValue))
125+
state = .binary(newValue)
126126
}
127127
}
128128

@@ -133,13 +133,13 @@ internal class AnyMessageStorage {
133133
// blob, i.e. - when decoding from binary, the spec doesn't include decoding
134134
// the binary blob, it is pass through. Instead there is a public api for
135135
// unpacking that takes new options when a developer decides to decode it.
136-
case binary([UInt8])
136+
case binary(Data)
137137
// a message
138138
case message(Message)
139139
// parsed JSON with the @type removed and the decoding options.
140140
case contentJSON([UInt8], JSONDecodingOptions)
141141
}
142-
var state: InternalState = .binary([])
142+
var state: InternalState = .binary(Data())
143143

144144
static let defaultInstance = AnyMessageStorage()
145145

@@ -179,8 +179,8 @@ internal class AnyMessageStorage {
179179
target = message
180180
} else {
181181
// Different type, serialize and parse.
182-
let data = try msg.serializedData(partial: true)
183-
target = try M(serializedBytes: Array(data), extensions: extensions, partial: true)
182+
let bytes: [UInt8] = try msg.serializedBytes(partial: true)
183+
target = try M(serializedBytes: bytes, extensions: extensions, partial: true)
184184
}
185185

186186
case .contentJSON(let contentJSON, let options):
@@ -275,7 +275,7 @@ extension AnyMessageStorage {
275275
try! visitor.visitSingularStringField(value: _typeURL, fieldNumber: 1)
276276
}
277277
if !valueData.isEmpty {
278-
try! visitor.visitSingularBytesField(value: Data(valueData), fieldNumber: 2)
278+
try! visitor.visitSingularBytesField(value: valueData, fieldNumber: 2)
279279
}
280280

281281
case .message(let msg):
@@ -300,7 +300,7 @@ extension AnyMessageStorage {
300300
}
301301
// Build a readable form of the JSON:
302302
let contentJSONAsObject = asJSONObject(body: contentJSON)
303-
visitor.visitAnyJSONDataField(value: contentJSONAsObject)
303+
visitor.visitAnyJSONBytesField(value: contentJSONAsObject)
304304
}
305305
}
306306
}
@@ -434,7 +434,7 @@ extension AnyMessageStorage {
434434
jsonEncoder.append(staticText: ",")
435435
// NOTE: This doesn't really take `options` into account since it is
436436
// just reflecting out what was taken in originally.
437-
jsonEncoder.append(utf8Data: contentJSON)
437+
jsonEncoder.append(utf8Bytes: contentJSON)
438438
}
439439
jsonEncoder.endObject()
440440
return jsonEncoder.stringResult
@@ -451,7 +451,7 @@ extension AnyMessageStorage {
451451
try decoder.scanner.skipRequiredObjectStart()
452452
// Reset state
453453
_typeURL = String()
454-
state = .binary([])
454+
state = .binary(Data())
455455
if decoder.scanner.skipOptionalObjectEnd() {
456456
return
457457
}

Sources/SwiftProtobufCore/BinaryDecoder.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,16 @@ internal struct BinaryDecoder: Decoder {
787787
}
788788
}
789789

790+
internal mutating func decodeSingularBytesField(value: inout [UInt8]) throws {
791+
guard fieldWireFormat == WireFormat.lengthDelimited else {
792+
return
793+
}
794+
var n: Int = 0
795+
let p = try getFieldBodyBytes(count: &n)
796+
value = Array(UnsafeRawBufferPointer(start: p, count: n))
797+
consumed = true
798+
}
799+
790800
internal mutating func decodeSingularBytesField(value: inout Data) throws {
791801
guard fieldWireFormat == WireFormat.lengthDelimited else {
792802
return

Sources/SwiftProtobufCore/BinaryDelimited.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public enum BinaryDelimited {
3434
case tooLarge
3535
}
3636

37-
/// Serialize a single size-delimited message from the given stream. Delimited
37+
/// Serialize a single size-delimited message to the given stream. Delimited
3838
/// format allows a single file or stream to contain multiple messages,
3939
/// whereas normally writing multiple non-delimited messages to the same
4040
/// stream would cause them to be merged. A delimited message is a varint
@@ -57,18 +57,18 @@ public enum BinaryDelimited {
5757
partial: Bool = false
5858
) throws {
5959
// TODO: Revisit to avoid the extra buffering when encoding is streamed in general.
60-
let serialized = try message.serializedData(partial: partial)
60+
let serialized: [UInt8] = try message.serializedBytes(partial: partial)
6161
let totalSize = Varint.encodedSize(of: UInt64(serialized.count)) + serialized.count
62-
var data = Data(count: totalSize)
63-
data.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) in
62+
var bytes: [UInt8] = Array(repeating: 0, count: totalSize)
63+
bytes.withUnsafeMutableBytes { (body: UnsafeMutableRawBufferPointer) in
6464
if let baseAddress = body.baseAddress, body.count > 0 {
6565
var encoder = BinaryEncoder(forWritingInto: baseAddress)
6666
encoder.putBytesValue(value: serialized)
6767
}
6868
}
6969

7070
var written: Int = 0
71-
data.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
71+
bytes.withUnsafeBytes { (body: UnsafeRawBufferPointer) in
7272
if let baseAddress = body.baseAddress, body.count > 0 {
7373
// This assumingMemoryBound is technically unsafe, but without SR-11078
7474
// (https://bugs.swift.org/browse/SR-11087) we don't have another option.

Sources/SwiftProtobufCore/BinaryEncoder.swift

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ internal struct BinaryEncoder {
2828
pointer = pointer.advanced(by: 1)
2929
}
3030

31-
private mutating func append(contentsOf data: Data) {
32-
data.withUnsafeBytes { dataPointer in
31+
private mutating func append<Bytes: SwiftProtobufContiguousBytes>(contentsOf bytes: Bytes) {
32+
bytes.withUnsafeBytes { dataPointer in
3333
if let baseAddress = dataPointer.baseAddress, dataPointer.count > 0 {
3434
pointer.copyMemory(from: baseAddress, byteCount: dataPointer.count)
3535
pointer = pointer.advanced(by: dataPointer.count)
@@ -41,7 +41,7 @@ internal struct BinaryEncoder {
4141
private mutating func append(contentsOf bufferPointer: UnsafeRawBufferPointer) -> Int {
4242
let count = bufferPointer.count
4343
if let baseAddress = bufferPointer.baseAddress, count > 0 {
44-
memcpy(pointer, baseAddress, count)
44+
pointer.copyMemory(from: baseAddress, byteCount: count)
4545
}
4646
pointer = pointer.advanced(by: count)
4747
return count
@@ -92,34 +92,28 @@ internal struct BinaryEncoder {
9292
mutating func putFixedUInt64(value: UInt64) {
9393
var v = value.littleEndian
9494
let n = MemoryLayout<UInt64>.size
95-
memcpy(pointer, &v, n)
95+
pointer.copyMemory(from: &v, byteCount: n)
9696
pointer = pointer.advanced(by: n)
9797
}
9898

9999
mutating func putFixedUInt32(value: UInt32) {
100100
var v = value.littleEndian
101101
let n = MemoryLayout<UInt32>.size
102-
memcpy(pointer, &v, n)
102+
pointer.copyMemory(from: &v, byteCount: n)
103103
pointer = pointer.advanced(by: n)
104104
}
105105

106106
mutating func putFloatValue(value: Float) {
107107
let n = MemoryLayout<Float>.size
108-
var v = value
109-
var nativeBytes: UInt32 = 0
110-
memcpy(&nativeBytes, &v, n)
111-
var littleEndianBytes = nativeBytes.littleEndian
112-
memcpy(pointer, &littleEndianBytes, n)
108+
var v = value.bitPattern.littleEndian
109+
pointer.copyMemory(from: &v, byteCount: n)
113110
pointer = pointer.advanced(by: n)
114111
}
115112

116113
mutating func putDoubleValue(value: Double) {
117114
let n = MemoryLayout<Double>.size
118-
var v = value
119-
var nativeBytes: UInt64 = 0
120-
memcpy(&nativeBytes, &v, n)
121-
var littleEndianBytes = nativeBytes.littleEndian
122-
memcpy(pointer, &littleEndianBytes, n)
115+
var v = value.bitPattern.littleEndian
116+
pointer.copyMemory(from: &v, byteCount: n)
123117
pointer = pointer.advanced(by: n)
124118
}
125119

@@ -142,7 +136,7 @@ internal struct BinaryEncoder {
142136
}
143137
}
144138

145-
mutating func putBytesValue(value: Data) {
139+
mutating func putBytesValue<Bytes: SwiftProtobufContiguousBytes>(value: Bytes) {
146140
putVarInt(value: value.count)
147141
append(contentsOf: value)
148142
}

Sources/SwiftProtobufCore/Decoder.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ public protocol Decoder {
154154
mutating func decodeRepeatedStringField(value: inout [String]) throws
155155
/// Decode a bytes value to non-`Optional` field storage
156156
mutating func decodeSingularBytesField(value: inout Data) throws
157+
/// Decode a bytes value to non-`Optional` field storage
158+
mutating func decodeSingularBytesField(value: inout [UInt8]) throws
157159
/// Decode a bytes value to `Optional` field storage
158160
mutating func decodeSingularBytesField(value: inout Data?) throws
159161
/// Decode bytes values to repeated field storage

0 commit comments

Comments
 (0)