Skip to content

Commit d1c5663

Browse files
Merge pull request swiftlang#26863 from ravikandhadai/oslog-strings
[oslog][stdlib-private] Add support for interpolating strings in the new os_log APIs.
2 parents 94f1d2c + 03c6365 commit d1c5663

File tree

7 files changed

+336
-14
lines changed

7 files changed

+336
-14
lines changed

stdlib/private/OSLog/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ add_swift_target_library(swiftOSLogPrototype
55
OSLog.swift
66
OSLogMessage.swift
77
OSLogIntegerTypes.swift
8+
OSLogStringTypes.swift
89

910
SWIFT_MODULE_DEPENDS_IOS Darwin os
1011
SWIFT_MODULE_DEPENDS_OSX Darwin os

stdlib/private/OSLog/OSLog.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ internal func osLog(
9090
bufferMemory,
9191
UInt32(bufferSize))
9292

93+
builder.destroy()
9394
bufferMemory.deallocate()
9495
}
9596

stdlib/private/OSLog/OSLogIntegerTypes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ extension OSLogInterpolation {
9999
// two bytes needed for the headers.
100100
totalBytesForSerializingArguments += byteCount + 2
101101

102-
preamble = getUpdatedPreamble(isPrivate: isPrivate)
102+
preamble = getUpdatedPreamble(isPrivate: isPrivate, isScalar: true)
103103
}
104104

105105
/// Construct an os_log format specifier from the given parameters.

stdlib/private/OSLog/OSLogMessage.swift

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,27 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
113113

114114
/// The possible values for the argument type, as defined by the os_log ABI,
115115
/// which occupies four most significant bits of the first byte of the
116-
/// argument header.
116+
/// argument header. The rawValue of this enum must be constant evaluable.
117+
/// (Note that an auto-generated rawValue is not constant evaluable because
118+
/// it cannot be annotated so.)
117119
@usableFromInline
118120
@_frozen
119121
internal enum ArgumentType {
120-
case scalar
121-
// TODO: more types will be added here.
122+
case scalar, count, string, pointer, object
122123

123124
@inlinable
124125
internal var rawValue: UInt8 {
125126
switch self {
126127
case .scalar:
127128
return 0
129+
case .count:
130+
return 1
131+
case .string:
132+
return 2
133+
case .pointer:
134+
return 3
135+
case .object:
136+
return 4
128137
}
129138
}
130139
}
@@ -242,9 +251,16 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
242251
@_semantics("oslog.interpolation.getUpdatedPreamble")
243252
@_effects(readonly)
244253
@_optimize(none)
245-
internal func getUpdatedPreamble(isPrivate: Bool) -> UInt8 {
254+
internal func getUpdatedPreamble(
255+
isPrivate: Bool,
256+
isScalar: Bool
257+
) -> UInt8 {
258+
var preamble = self.preamble
246259
if isPrivate {
247-
return preamble | PreambleBitMask.privateBitMask.rawValue
260+
preamble |= PreambleBitMask.privateBitMask.rawValue
261+
}
262+
if !isScalar {
263+
preamble |= PreambleBitMask.nonScalarBitMask.rawValue
248264
}
249265
return preamble
250266
}
@@ -350,12 +366,19 @@ internal struct OSLogArguments {
350366
internal struct OSLogByteBufferBuilder {
351367
internal var position: UnsafeMutablePointer<UInt8>
352368

369+
/// Objects denoting storage created by the serialize methods. Such storage
370+
/// is created while serializing strings as os_log requires stable pointers to
371+
/// Swift strings, which may require copying them to a in-memory buffer.
372+
/// The lifetime of this auxiliary storage is same as the lifetime of `self`.
373+
internal var auxiliaryStorage: [AnyObject]
374+
353375
/// Initializer that accepts a pointer to a preexisting buffer.
354376
/// - Parameter bufferStart: the starting pointer to a byte buffer
355377
/// that must contain the serialized bytes.
356378
@usableFromInline
357379
internal init(_ bufferStart: UnsafeMutablePointer<UInt8>) {
358380
position = bufferStart
381+
auxiliaryStorage = []
359382
}
360383

361384
/// Serialize a UInt8 value at the buffer location pointed to by `position`.
@@ -366,4 +389,11 @@ internal struct OSLogByteBufferBuilder {
366389
}
367390

368391
/// `serialize` for other other types must be implemented by extensions.
392+
393+
/// This function exists so that clients can control the lifetime of a stack-
394+
/// allocated instance of OSLogByteBufferBuilder.
395+
@usableFromInline
396+
internal mutating func destroy() {
397+
auxiliaryStorage = []
398+
}
369399
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//===----------------- OSLogStringTypes.swift -----------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 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+
// This file defines extensions for interpolating strings into a OSLogMesage.
14+
// It defines `appendInterpolation` function for String type. It also defines
15+
// extensions for serializing strings into the argument buffer passed to
16+
// os_log ABIs. Note that os_log requires passing a stable pointer to an
17+
// interpolated string. The SPI: `_convertConstStringToUTF8PointerArgument`
18+
// is used to construct a stable pointer to a (dynamic) string.
19+
//
20+
// The `appendInterpolation` function defined in this file accept formatting
21+
// and privacy options along with the interpolated expression as shown below:
22+
//
23+
// "\(x, privacy: .public\)"
24+
//
25+
// TODO: support formatting options such as left and right padding
26+
// (e.g. %10s, %-10s).
27+
28+
extension OSLogInterpolation {
29+
30+
/// Define interpolation for expressions of type String.
31+
/// - Parameters:
32+
/// - argumentString: the interpolated expression of type String, which is autoclosured.
33+
/// - privacy: a privacy qualifier which is either private or public. Default is private.
34+
/// TODO: create a specifier to denote auto-inferred privacy level and make it default.
35+
@_transparent
36+
@_optimize(none)
37+
public mutating func appendInterpolation(
38+
_ argumentString: @autoclosure @escaping () -> String,
39+
privacy: Privacy = .private
40+
) {
41+
guard argumentCount < maxOSLogArgumentCount else { return }
42+
43+
let isPrivateArgument = isPrivate(privacy)
44+
formatString += getStringFormatSpecifier(isPrivateArgument)
45+
addStringHeaders(isPrivateArgument)
46+
47+
arguments.append(argumentString)
48+
argumentCount += 1
49+
}
50+
51+
/// Update preamble and append argument headers based on the parameters of
52+
/// the interpolation.
53+
@_transparent
54+
@_optimize(none)
55+
@usableFromInline
56+
internal mutating func addStringHeaders(_ isPrivate: Bool) {
57+
// Append argument header.
58+
let header = getArgumentHeader(isPrivate: isPrivate, type: .string)
59+
arguments.append(header)
60+
61+
// Append number of bytes needed to serialize the argument.
62+
let byteCount = sizeForEncoding()
63+
arguments.append(UInt8(byteCount))
64+
65+
// Increment total byte size by the number of bytes needed for this
66+
// argument, which is the sum of the byte size of the argument and
67+
// two bytes needed for the headers.
68+
totalBytesForSerializingArguments += byteCount + 2
69+
70+
preamble = getUpdatedPreamble(isPrivate: isPrivate, isScalar: false)
71+
}
72+
73+
/// Construct an os_log format specifier from the given parameters.
74+
/// This function must be constant evaluable and all its arguments
75+
/// must be known at compile time.
76+
@inlinable
77+
@_semantics("oslog.interpolation.getFormatSpecifier")
78+
@_effects(readonly)
79+
@_optimize(none)
80+
internal func getStringFormatSpecifier(_ isPrivate: Bool) -> String {
81+
// TODO: create a specifier to denote auto-inferred privacy.
82+
return isPrivate ? "%{private}s" : "%{public}s"
83+
}
84+
}
85+
86+
extension OSLogArguments {
87+
/// Append an (autoclosured) interpolated expression of String type, passed to
88+
/// `OSLogMessage.appendInterpolation`, to the array of closures tracked
89+
/// by this instance.
90+
@usableFromInline
91+
internal mutating func append(_ value: @escaping () -> String) {
92+
argumentClosures!.append({ $0.serialize(value()) })
93+
}
94+
}
95+
96+
/// Return the byte size of a pointer as strings are passed to the C os_log ABIs by
97+
/// a stable pointer to its UTF8 bytes. Since pointers do not have a public
98+
/// bitWidth property, and since MemoryLayout is not supported by the constant
99+
/// evaluator, this function returns the byte size of Int, which must equal the
100+
/// word length of the target architecture and hence the pointer size.
101+
/// This function must be constant evaluable.
102+
@inlinable
103+
@_optimize(none)
104+
@_effects(readonly)
105+
@_semantics("oslog.string.sizeForEncoding")
106+
internal func sizeForEncoding() -> Int {
107+
return Int.bitWidth &>> logBitsPerByte
108+
}
109+
110+
extension OSLogByteBufferBuilder {
111+
/// Serialize a string at the buffer location pointed to by `position`.
112+
/// Record any auxiliary storage created for getting a stable pointer to the
113+
/// parameter string in the `self.auxiliaryStorage` property, so that the
114+
/// storage is alive for the lifetime of `self`.
115+
@usableFromInline
116+
internal mutating func serialize(_ stringValue: String) {
117+
let (optionalStorage, bytePointer): (AnyObject?, UnsafeRawPointer) =
118+
_convertConstStringToUTF8PointerArgument(
119+
stringValue)
120+
121+
if let storage = optionalStorage {
122+
auxiliaryStorage.append(storage)
123+
}
124+
125+
let byteCount = sizeForEncoding()
126+
let dest = UnsafeMutableRawBufferPointer(start: position, count: byteCount)
127+
withUnsafeBytes(of: bytePointer) { dest.copyMemory(from: $0) }
128+
position += byteCount
129+
}
130+
}

test/SILOptimizer/OSLogPrototypeCompileTest.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,5 +294,45 @@ if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
294294
// CHECK-DAG: [[ARGCOUNT]] = struct $UInt8 ([[ARGCOUNTLIT:%[0-9]+]] : $Builtin.Int8)
295295
// CHECK-DAG: [[ARGCOUNTLIT]] = integer_literal $Builtin.Int8, 1
296296
}
297+
298+
// CHECK-LABEL: @$s25OSLogPrototypeCompileTest26testDynamicStringArgumentsL_1hy0aB06LoggerV_tF
299+
func testDynamicStringArguments(h: Logger) {
300+
let concatString = "hello" + " - " + "world"
301+
let interpolatedString = "\(31) trillion digits of pi are known so far"
302+
303+
h.log("""
304+
concat: \(concatString, privacy: .public) \
305+
interpolated: \(interpolatedString, privacy: .private)
306+
""")
307+
308+
// Check if there is a call to _os_log_impl with a literal format string.
309+
// CHECK-DAG is used here as it is easier to perform the checks backwards
310+
// from uses to the definitions.
311+
312+
// CHECK-DAG: [[OS_LOG_IMPL:%[0-9]+]] = function_ref @_os_log_impl : $@convention(c)
313+
// CHECK-DAG: apply [[OS_LOG_IMPL]]({{%.*}}, {{%.*}}, {{%.*}}, [[CHARPTR:%[0-9]+]], {{%.*}}, {{%.*}})
314+
// CHECK-DAG: [[CHARPTR]] = struct $UnsafePointer<Int8> ([[LIT:%[0-9]+]] : $Builtin.RawPointer)
315+
// CHECK-DAG: [[LIT]] = string_literal utf8 "concat: %{public}s interpolated: %{private}s"
316+
317+
// Check if the size of the argument buffer is a constant.
318+
319+
// CHECK-DAG: [[ALLOCATE:%[0-9]+]] = function_ref @$sSp8allocate8capacitySpyxGSi_tFZ
320+
// CHECK-DAG: apply [[ALLOCATE]]<UInt8>([[BUFFERSIZE:%[0-9]+]], {{%.*}})
321+
// CHECK-DAG: [[BUFFERSIZE]] = struct $Int ([[BUFFERSIZELIT:%[0-9]+]]
322+
// CHECK-64-DAG: [[BUFFERSIZELIT]] = integer_literal $Builtin.Int64, 22
323+
// CHECK-32-DAG: [[BUFFERSIZELIT]] = integer_literal $Builtin.Int32, 14
324+
325+
// Check whether the header bytes: premable and argument count are constants.
326+
327+
// CHECK-DAG: [[SERIALIZE:%[0-9]+]] = function_ref @$s14OSLogPrototype0A17ByteBufferBuilderV9serializeyys5UInt8VF
328+
// CHECK-DAG: apply [[SERIALIZE]]([[PREAMBLE:%[0-9]+]], {{%.*}})
329+
// CHECK-DAG: [[PREAMBLE]] = struct $UInt8 ([[PREAMBLELIT:%[0-9]+]] : $Builtin.Int8)
330+
// CHECK-DAG: [[PREAMBLELIT]] = integer_literal $Builtin.Int8, 3
331+
332+
// CHECK-DAG: [[SERIALIZE:%[0-9]+]] = function_ref @$s14OSLogPrototype0A17ByteBufferBuilderV9serializeyys5UInt8VF
333+
// CHECK-DAG: apply [[SERIALIZE]]([[ARGCOUNT:%[0-9]+]], {{%.*}})
334+
// CHECK-DAG: [[ARGCOUNT]] = struct $UInt8 ([[ARGCOUNTLIT:%[0-9]+]] : $Builtin.Int8)
335+
// CHECK-DAG: [[ARGCOUNTLIT]] = integer_literal $Builtin.Int8, 2
336+
}
297337
}
298338

0 commit comments

Comments
 (0)