Skip to content

[oslog][stdlib-private] Add support for interpolating strings in the new os_log APIs. #26863

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions stdlib/private/OSLog/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ add_swift_target_library(swiftOSLogPrototype
OSLog.swift
OSLogMessage.swift
OSLogIntegerTypes.swift
OSLogStringTypes.swift

SWIFT_MODULE_DEPENDS_IOS Darwin os
SWIFT_MODULE_DEPENDS_OSX Darwin os
Expand Down
1 change: 1 addition & 0 deletions stdlib/private/OSLog/OSLog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ internal func osLog(
bufferMemory,
UInt32(bufferSize))

builder.destroy()
bufferMemory.deallocate()
}

Expand Down
2 changes: 1 addition & 1 deletion stdlib/private/OSLog/OSLogIntegerTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ extension OSLogInterpolation {
// two bytes needed for the headers.
totalBytesForSerializingArguments += byteCount + 2

preamble = getUpdatedPreamble(isPrivate: isPrivate)
preamble = getUpdatedPreamble(isPrivate: isPrivate, isScalar: true)
}

/// Construct an os_log format specifier from the given parameters.
Expand Down
40 changes: 35 additions & 5 deletions stdlib/private/OSLog/OSLogMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,27 @@ public struct OSLogInterpolation : StringInterpolationProtocol {

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

@inlinable
internal var rawValue: UInt8 {
switch self {
case .scalar:
return 0
case .count:
return 1
case .string:
return 2
case .pointer:
return 3
case .object:
return 4
}
}
}
Expand Down Expand Up @@ -242,9 +251,16 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
@_semantics("oslog.interpolation.getUpdatedPreamble")
@_effects(readonly)
@_optimize(none)
internal func getUpdatedPreamble(isPrivate: Bool) -> UInt8 {
internal func getUpdatedPreamble(
isPrivate: Bool,
isScalar: Bool
) -> UInt8 {
var preamble = self.preamble
if isPrivate {
return preamble | PreambleBitMask.privateBitMask.rawValue
preamble |= PreambleBitMask.privateBitMask.rawValue
}
if !isScalar {
preamble |= PreambleBitMask.nonScalarBitMask.rawValue
}
return preamble
}
Expand Down Expand Up @@ -350,12 +366,19 @@ internal struct OSLogArguments {
internal struct OSLogByteBufferBuilder {
internal var position: UnsafeMutablePointer<UInt8>

/// Objects denoting storage created by the serialize methods. Such storage
/// is created while serializing strings as os_log requires stable pointers to
/// Swift strings, which may require copying them to a in-memory buffer.
/// The lifetime of this auxiliary storage is same as the lifetime of `self`.
internal var auxiliaryStorage: [AnyObject]

/// Initializer that accepts a pointer to a preexisting buffer.
/// - Parameter bufferStart: the starting pointer to a byte buffer
/// that must contain the serialized bytes.
@usableFromInline
internal init(_ bufferStart: UnsafeMutablePointer<UInt8>) {
position = bufferStart
auxiliaryStorage = []
}

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

/// `serialize` for other other types must be implemented by extensions.

/// This function exists so that clients can control the lifetime of a stack-
/// allocated instance of OSLogByteBufferBuilder.
@usableFromInline
internal mutating func destroy() {
auxiliaryStorage = []
}
}
130 changes: 130 additions & 0 deletions stdlib/private/OSLog/OSLogStringTypes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//===----------------- OSLogStringTypes.swift -----------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

// This file defines extensions for interpolating strings into a OSLogMesage.
// It defines `appendInterpolation` function for String type. It also defines
// extensions for serializing strings into the argument buffer passed to
// os_log ABIs. Note that os_log requires passing a stable pointer to an
// interpolated string. The SPI: `_convertConstStringToUTF8PointerArgument`
// is used to construct a stable pointer to a (dynamic) string.
//
// The `appendInterpolation` function defined in this file accept formatting
// and privacy options along with the interpolated expression as shown below:
//
// "\(x, privacy: .public\)"
//
// TODO: support formatting options such as left and right padding
// (e.g. %10s, %-10s).

extension OSLogInterpolation {

/// Define interpolation for expressions of type String.
/// - Parameters:
/// - argumentString: the interpolated expression of type String, which is autoclosured.
/// - privacy: a privacy qualifier which is either private or public. Default is private.
/// TODO: create a specifier to denote auto-inferred privacy level and make it default.
@_transparent
@_optimize(none)
public mutating func appendInterpolation(
_ argumentString: @autoclosure @escaping () -> String,
privacy: Privacy = .private
) {
guard argumentCount < maxOSLogArgumentCount else { return }

let isPrivateArgument = isPrivate(privacy)
formatString += getStringFormatSpecifier(isPrivateArgument)
addStringHeaders(isPrivateArgument)

arguments.append(argumentString)
argumentCount += 1
}

/// Update preamble and append argument headers based on the parameters of
/// the interpolation.
@_transparent
@_optimize(none)
@usableFromInline
internal mutating func addStringHeaders(_ isPrivate: Bool) {
// Append argument header.
let header = getArgumentHeader(isPrivate: isPrivate, type: .string)
arguments.append(header)

// Append number of bytes needed to serialize the argument.
let byteCount = sizeForEncoding()
arguments.append(UInt8(byteCount))

// Increment total byte size by the number of bytes needed for this
// argument, which is the sum of the byte size of the argument and
// two bytes needed for the headers.
totalBytesForSerializingArguments += byteCount + 2

preamble = getUpdatedPreamble(isPrivate: isPrivate, isScalar: false)
}

/// Construct an os_log format specifier from the given parameters.
/// This function must be constant evaluable and all its arguments
/// must be known at compile time.
@inlinable
@_semantics("oslog.interpolation.getFormatSpecifier")
@_effects(readonly)
@_optimize(none)
internal func getStringFormatSpecifier(_ isPrivate: Bool) -> String {
// TODO: create a specifier to denote auto-inferred privacy.
return isPrivate ? "%{private}s" : "%{public}s"
}
}

extension OSLogArguments {
/// Append an (autoclosured) interpolated expression of String type, passed to
/// `OSLogMessage.appendInterpolation`, to the array of closures tracked
/// by this instance.
@usableFromInline
internal mutating func append(_ value: @escaping () -> String) {
argumentClosures!.append({ $0.serialize(value()) })
}
}

/// Return the byte size of a pointer as strings are passed to the C os_log ABIs by
/// a stable pointer to its UTF8 bytes. Since pointers do not have a public
/// bitWidth property, and since MemoryLayout is not supported by the constant
/// evaluator, this function returns the byte size of Int, which must equal the
/// word length of the target architecture and hence the pointer size.
/// This function must be constant evaluable.
@inlinable
@_optimize(none)
@_effects(readonly)
@_semantics("oslog.string.sizeForEncoding")
internal func sizeForEncoding() -> Int {
return Int.bitWidth &>> logBitsPerByte
}

extension OSLogByteBufferBuilder {
/// Serialize a string at the buffer location pointed to by `position`.
/// Record any auxiliary storage created for getting a stable pointer to the
/// parameter string in the `self.auxiliaryStorage` property, so that the
/// storage is alive for the lifetime of `self`.
@usableFromInline
internal mutating func serialize(_ stringValue: String) {
let (optionalStorage, bytePointer): (AnyObject?, UnsafeRawPointer) =
_convertConstStringToUTF8PointerArgument(
stringValue)

if let storage = optionalStorage {
auxiliaryStorage.append(storage)
}

let byteCount = sizeForEncoding()
let dest = UnsafeMutableRawBufferPointer(start: position, count: byteCount)
withUnsafeBytes(of: bytePointer) { dest.copyMemory(from: $0) }
position += byteCount
}
}
40 changes: 40 additions & 0 deletions test/SILOptimizer/OSLogPrototypeCompileTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,45 @@ if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
// CHECK-DAG: [[ARGCOUNT]] = struct $UInt8 ([[ARGCOUNTLIT:%[0-9]+]] : $Builtin.Int8)
// CHECK-DAG: [[ARGCOUNTLIT]] = integer_literal $Builtin.Int8, 1
}

// CHECK-LABEL: @$s25OSLogPrototypeCompileTest26testDynamicStringArgumentsL_1hy0aB06LoggerV_tF
func testDynamicStringArguments(h: Logger) {
let concatString = "hello" + " - " + "world"
let interpolatedString = "\(31) trillion digits of pi are known so far"

h.log("""
concat: \(concatString, privacy: .public) \
interpolated: \(interpolatedString, privacy: .private)
""")

// Check if there is a call to _os_log_impl with a literal format string.
// CHECK-DAG is used here as it is easier to perform the checks backwards
// from uses to the definitions.

// CHECK-DAG: [[OS_LOG_IMPL:%[0-9]+]] = function_ref @_os_log_impl : $@convention(c)
// CHECK-DAG: apply [[OS_LOG_IMPL]]({{%.*}}, {{%.*}}, {{%.*}}, [[CHARPTR:%[0-9]+]], {{%.*}}, {{%.*}})
// CHECK-DAG: [[CHARPTR]] = struct $UnsafePointer<Int8> ([[LIT:%[0-9]+]] : $Builtin.RawPointer)
// CHECK-DAG: [[LIT]] = string_literal utf8 "concat: %{public}s interpolated: %{private}s"

// Check if the size of the argument buffer is a constant.

// CHECK-DAG: [[ALLOCATE:%[0-9]+]] = function_ref @$sSp8allocate8capacitySpyxGSi_tFZ
// CHECK-DAG: apply [[ALLOCATE]]<UInt8>([[BUFFERSIZE:%[0-9]+]], {{%.*}})
// CHECK-DAG: [[BUFFERSIZE]] = struct $Int ([[BUFFERSIZELIT:%[0-9]+]]
// CHECK-64-DAG: [[BUFFERSIZELIT]] = integer_literal $Builtin.Int64, 22
// CHECK-32-DAG: [[BUFFERSIZELIT]] = integer_literal $Builtin.Int32, 14

// Check whether the header bytes: premable and argument count are constants.

// CHECK-DAG: [[SERIALIZE:%[0-9]+]] = function_ref @$s14OSLogPrototype0A17ByteBufferBuilderV9serializeyys5UInt8VF
// CHECK-DAG: apply [[SERIALIZE]]([[PREAMBLE:%[0-9]+]], {{%.*}})
// CHECK-DAG: [[PREAMBLE]] = struct $UInt8 ([[PREAMBLELIT:%[0-9]+]] : $Builtin.Int8)
// CHECK-DAG: [[PREAMBLELIT]] = integer_literal $Builtin.Int8, 3

// CHECK-DAG: [[SERIALIZE:%[0-9]+]] = function_ref @$s14OSLogPrototype0A17ByteBufferBuilderV9serializeyys5UInt8VF
// CHECK-DAG: apply [[SERIALIZE]]([[ARGCOUNT:%[0-9]+]], {{%.*}})
// CHECK-DAG: [[ARGCOUNT]] = struct $UInt8 ([[ARGCOUNTLIT:%[0-9]+]] : $Builtin.Int8)
// CHECK-DAG: [[ARGCOUNTLIT]] = integer_literal $Builtin.Int8, 2
}
}

Loading