profile
viewpoint
Ravi Kandhadai ravikandhadai Apple Inc

ravikandhadai/swift 0

The Swift Programming Language

ravikandhadai/swift-log 0

A Logging API for Swift

PullRequestReviewEvent

pull request commentapple/swift

[OSSignpost] Update apinotes to allow usage of os_signpost ABI entrypoint

Thanks Josh! It looks good to me! Just to clarify, SwiftPrivate: true actually means NS_REFINED_FOR_SWIFT which would prepend two underscores to the name of the symbol, essentially discouraging it from being directly used in Swift.

guitard0g

comment created time in 15 days

pull request commentapple/swift

[OSLog] Update compiler stubs and tests

@swift-ci Please smoke test Linux Platform

guitard0g

comment created time in 20 days

pull request commentapple/swift

[OSLog] Update compiler stubs and tests

@swift-ci please test Linux Platform

guitard0g

comment created time in 20 days

PR closed apple/swift

Reviewers
[SR-10155] Support string interpolations with binary value

I made OSLog support String interpolations with binary value, not only hex, octal and decimal.

See: https://github.com/apple/swift/pull/22914/files#diff-67e352846aa55b916212d76f5f4afda1R229 https://bugs.swift.org/browse/SR-10155

+6 -0

5 comments

2 changed files

S-Shimotori

pr closed time in 20 days

pull request commentapple/swift

[SR-10155] Support string interpolations with binary value

I think this PR can be closed. The code base has changed quite a bit, and the os_log overlay code in the compiler is just for testing the compiler optimizations. Thanks @S-Shimotori for your efforts. I would like to encourage you to continue your contributions to the mainstream Swift compiler code base.

S-Shimotori

comment created time in 20 days

PullRequestReviewEvent

Pull request review commentapple/swift

[OSLog] Update compiler stubs and tests

 func testDynamicPrecisionAndAlignment() {     // CHECK-NEXT: br label %[[NOT_ENABLED]] } -// TODO: add test for String. It is more complicated due to more complex logic-// in string serialization.+// CHECK-LABEL: define hidden swiftcc void @"${{.*}}testStringInterpolation+func testStringInterpolation(stringValue: String) {+  _osLogTestHelper("String value: \(stringValue)")+    // CHECK: entry:+    // CHECK: call %swift.bridge* @swift_bridgeObjectRetain_n(%swift.bridge* %1+    // CHECK: tail call swiftcc i1 @"${{.*}}isLoggingEnabled{{.*}}"()+    // CHECK-NEXT: br i1 {{%.*}}, label %[[ENABLED:[0-9]+]], label %[[NOT_ENABLED:[0-9]+]]++    // CHECK: [[NOT_ENABLED]]:+    // CHECK: call void @swift_bridgeObjectRelease_n(%swift.bridge* %1+    // CHECK-NEXT: br label %[[EXIT:[0-9]+]]++    // CHECK: [[ENABLED]]:+    //+    // Header bytes.+    //+    // CHECK-64: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 12+    // CHECK-32: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 8+    // CHECK-64-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 32+    // CHECK-32-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 16+    // CHECK: call void @llvm.lifetime.start{{.*}}({{.*}}, i8* {{(nonnull )?}}[[STR_STORAGE_PTR:%.*]]

Thanks for clarifying it.

guitard0g

comment created time in 21 days

PullRequestReviewEvent

Pull request review commentapple/swift

[OSLog] Update compiler stubs and tests

 func testDynamicPrecisionAndAlignment() {     // First argument bytes.     //     // CHECK-NEXT: [[OFFSET2:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 2-    // CHECK-NEXT: store i8 16, i8* [[OFFSET2]], align 1+    // TODO: Figure out why "store i8 16" turned into "store i8 0"

I think that this comment appears in the other places and is possibly a copy-paste error. If I remember correctly, there was a bug here that got fixed. I guess type count (represented by the value 16) is used only for precision but not for alignment. So the current patch has the right logic and the earlier one was a bug. So I guess you can remove the TODO here and also in other places.

guitard0g

comment created time in 22 days

PullRequestReviewEvent

Pull request review commentapple/swift

[OSLog] Update compiler stubs and tests

 func testDynamicPrecisionAndAlignment() {     // CHECK-NEXT: br label %[[NOT_ENABLED]] } -// TODO: add test for String. It is more complicated due to more complex logic-// in string serialization.+// CHECK-LABEL: define hidden swiftcc void @"${{.*}}testStringInterpolation+func testStringInterpolation(stringValue: String) {+  _osLogTestHelper("String value: \(stringValue)")+    // CHECK: entry:+    // CHECK: call %swift.bridge* @swift_bridgeObjectRetain_n(%swift.bridge* %1+    // CHECK: tail call swiftcc i1 @"${{.*}}isLoggingEnabled{{.*}}"()+    // CHECK-NEXT: br i1 {{%.*}}, label %[[ENABLED:[0-9]+]], label %[[NOT_ENABLED:[0-9]+]]++    // CHECK: [[NOT_ENABLED]]:+    // CHECK: call void @swift_bridgeObjectRelease_n(%swift.bridge* %1+    // CHECK-NEXT: br label %[[EXIT:[0-9]+]]++    // CHECK: [[ENABLED]]:+    //+    // Header bytes.+    //+    // CHECK-64: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 12+    // CHECK-32: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 8+    // CHECK-64-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 32+    // CHECK-32-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 16+    // CHECK: call void @llvm.lifetime.start{{.*}}({{.*}}, i8* {{(nonnull )?}}[[STR_STORAGE_PTR:%.*]]+    // CHECK: store i8 2, i8* [[BUFFER]], align 1+    // CHECK-NEXT: [[OFFSET1:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 1+    // CHECK-NEXT: store i8 1, i8* [[OFFSET1]], align 1++    // Argument bytes.+    //+    // CHECK-NEXT: [[OFFSET2:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 2+    // TODO: Figure out why "store i8 16" turned into "store i8 0"

Is this TODO: up to date? Looks like we are now we storing a 32 into the buffer.

guitard0g

comment created time in 22 days

Pull request review commentapple/swift

[OSLog] Update compiler stubs and tests

 func testDynamicPrecisionAndAlignment() {     // CHECK-NEXT: br label %[[NOT_ENABLED]] } -// TODO: add test for String. It is more complicated due to more complex logic-// in string serialization.+// CHECK-LABEL: define hidden swiftcc void @"${{.*}}testStringInterpolation+func testStringInterpolation(stringValue: String) {+  _osLogTestHelper("String value: \(stringValue)")+    // CHECK: entry:+    // CHECK: call %swift.bridge* @swift_bridgeObjectRetain_n(%swift.bridge* %1+    // CHECK: tail call swiftcc i1 @"${{.*}}isLoggingEnabled{{.*}}"()+    // CHECK-NEXT: br i1 {{%.*}}, label %[[ENABLED:[0-9]+]], label %[[NOT_ENABLED:[0-9]+]]++    // CHECK: [[NOT_ENABLED]]:+    // CHECK: call void @swift_bridgeObjectRelease_n(%swift.bridge* %1+    // CHECK-NEXT: br label %[[EXIT:[0-9]+]]++    // CHECK: [[ENABLED]]:+    //+    // Header bytes.+    //+    // CHECK-64: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 12+    // CHECK-32: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 8+    // CHECK-64-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 32+    // CHECK-32-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 16+    // CHECK: call void @llvm.lifetime.start{{.*}}({{.*}}, i8* {{(nonnull )?}}[[STR_STORAGE_PTR:%.*]]+    // CHECK: store i8 2, i8* [[BUFFER]], align 1+    // CHECK-NEXT: [[OFFSET1:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 1+    // CHECK-NEXT: store i8 1, i8* [[OFFSET1]], align 1++    // Argument bytes.+    //+    // CHECK-NEXT: [[OFFSET2:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 2+    // TODO: Figure out why "store i8 16" turned into "store i8 0"

A similar TODO is also present in the next test.

guitard0g

comment created time in 22 days

Pull request review commentapple/swift

[OSLog] Update compiler stubs and tests

+//===----------------- OSLogPrivacy.swift ---------------------------------===//+//+// This source file is part of the Swift.org open source project+//+// Copyright (c) 2014 - 2020 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 the APIs for specifying privacy in the log messages and also+// the logic for encoding them in the byte buffer passed to the libtrace library.++/// Privacy options for specifying privacy level of the interpolated expressions+/// in the string interpolations passed to the log APIs.+@frozen+public struct OSLogPrivacy {++  @usableFromInline+  internal enum PrivacyOption {+    case `private`+    case `public`+    case sensitive+    case auto+  }++  public enum Mask {+    /// Applies a salted hashing transformation to an interpolated value to redact it in the logs.+    ///+    /// Its purpose is to permit the correlation of identical values across multiple log lines+    /// without revealing the value itself.+    case hash+    case none+  }++  @usableFromInline+  internal var privacy: PrivacyOption++  @usableFromInline+  internal var mask: Mask++  @_transparent+  @usableFromInline+  internal init(privacy: PrivacyOption, mask: Mask) {+    self.privacy = privacy+    self.mask = mask+  }++  /// Sets the privacy level of an interpolated value to public.+  ///+  /// When the privacy level is public, the value will be displayed+  /// normally without any redaction in the logs.+  @_semantics("constant_evaluable")+  @_optimize(none)+  @inlinable+  public static var `public`: OSLogPrivacy {+    OSLogPrivacy(privacy: .public, mask: .none)+  }++  /// Sets the privacy level of an interpolated value to private.+  ///+  /// When the privacy level is private, the value will be redacted in the logs,+  /// subject to the privacy configuration of the logging system.+  @_semantics("constant_evaluable")+  @_optimize(none)+  @inlinable+  public static var `private`: OSLogPrivacy {+    OSLogPrivacy(privacy: .private, mask: .none)+  }++  /// Sets the privacy level of an interpolated value to private and+  /// applies a `mask` to the interpolated value to redacted it.+  ///+  /// When the privacy level is private, the value will be redacted in the logs,+  /// subject to the privacy configuration of the logging system.+  ///+  /// If the value need not be redacted in the logs, its full value is captured as normal.+  /// Otherwise (i.e. if the value would be redacted) the `mask` is applied to+  /// the argument value and the result of the transformation is recorded instead.+  ///+  /// - Parameters:+  ///   - mask: Mask to use with the privacy option.+  @_semantics("constant_evaluable")+  @_optimize(none)+  @inlinable+  public static func `private`(mask: Mask) -> OSLogPrivacy {+    OSLogPrivacy(privacy: .private, mask: mask)+  }++  /// Sets the privacy level of an interpolated value to sensitive.+  ///+  /// When the privacy level is sensitive, the value will be redacted in the logs,+  /// subject to the privacy configuration of the logging system.+  @_semantics("constant_evaluable")+  @_optimize(none)+  @inlinable+  public static var sensitive: OSLogPrivacy {+    OSLogPrivacy(privacy: .sensitive, mask: .none)+  }+

I think we can remove the sensitive options. This property declaration and the following static function declaration: sensitive(mask:) and all references to it.

guitard0g

comment created time in 22 days

Pull request review commentapple/swift

[OSLog] Update compiler stubs and tests

 func testNSObjectInterpolation(nsArray: NSArray) {     // CHECK-NEXT: [[OFFSET3:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 3     // CHECK-64-NEXT: store i8 8, i8* [[OFFSET3]], align 1     // CHECK-32-NEXT: store i8 4, i8* [[OFFSET3]], align 1-    // CHECK-NEXT: tail call void @llvm.objc.release+    // DOESN'T HAPPEN: tail call void @llvm.objc.release

Feel free to delete the "Doesn't happen" line

guitard0g

comment created time in 22 days

Pull request review commentapple/swift

[OSLog] Update compiler stubs and tests

 func testDynamicPrecisionAndAlignment() {     // CHECK-NEXT: br label %[[NOT_ENABLED]] } -// TODO: add test for String. It is more complicated due to more complex logic-// in string serialization.+// CHECK-LABEL: define hidden swiftcc void @"${{.*}}testStringInterpolation+func testStringInterpolation(stringValue: String) {+  _osLogTestHelper("String value: \(stringValue)")+    // CHECK: entry:+    // CHECK: call %swift.bridge* @swift_bridgeObjectRetain_n(%swift.bridge* %1+    // CHECK: tail call swiftcc i1 @"${{.*}}isLoggingEnabled{{.*}}"()+    // CHECK-NEXT: br i1 {{%.*}}, label %[[ENABLED:[0-9]+]], label %[[NOT_ENABLED:[0-9]+]]++    // CHECK: [[NOT_ENABLED]]:+    // CHECK: call void @swift_bridgeObjectRelease_n(%swift.bridge* %1+    // CHECK-NEXT: br label %[[EXIT:[0-9]+]]++    // CHECK: [[ENABLED]]:+    //+    // Header bytes.+    //+    // CHECK-64: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 12+    // CHECK-32: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 8+    // CHECK-64-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 32+    // CHECK-32-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 16+    // CHECK: call void @llvm.lifetime.start{{.*}}({{.*}}, i8* {{(nonnull )?}}[[STR_STORAGE_PTR:%.*]]

I wonder what STR_STORAGE_PTR corresponds to. It doesn't quite correspond to the unsafe storage buffers we are creating. Is this SILValue derived from the input argument %0 in the actual SIL?

guitard0g

comment created time in 22 days

Pull request review commentapple/swift

[OSLog] Update compiler stubs and tests

 func testNSObjectInterpolation(nsArray: NSArray) {     // CHECK-NEXT: [[OFFSET3:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 3     // CHECK-64-NEXT: store i8 8, i8* [[OFFSET3]], align 1     // CHECK-32-NEXT: store i8 4, i8* [[OFFSET3]], align 1-    // CHECK-NEXT: tail call void @llvm.objc.release+    // DOESN'T HAPPEN: tail call void @llvm.objc.release     // CHECK-NEXT: bitcast %swift.refcounted* %{{.*}} to %swift.opaque*     // CHECK-NEXT: [[OFFSET4:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 4     // CHECK-NEXT: [[BITCASTED_DEST:%.+]] = bitcast i8* [[OFFSET4]] to %TSo7NSArrayC**     // CHECK-NEXT: [[BITCASTED_SRC:%.+]] = bitcast i8* [[NSARRAY_ARG]] to %TSo7NSArrayC*     // CHECK-NEXT: store %TSo7NSArrayC*  [[BITCASTED_SRC]], %TSo7NSArrayC** [[BITCASTED_DEST]], align 1+    // CHECK-NEXT: [[BITCASTED_DEST2:%.+]] = bitcast i8* [[OBJ_STORAGE]] to %TSo7NSArrayC**+    // CHECK-NEXT: [[BITCASTED_SRC2:%.+]] = bitcast i8* [[NSARRAY_ARG]] to %TSo7NSArrayC*+    // CHECK-64-NEXT: store %TSo7NSArrayC* [[BITCASTED_SRC2]], %TSo7NSArrayC** [[BITCASTED_DEST2]], align 8+    // CHECK-32-NEXT: store %TSo7NSArrayC* [[BITCASTED_SRC2]], %TSo7NSArrayC** [[BITCASTED_DEST2]], align 4      // CHECK-64-NEXT: tail call swiftcc void @"${{.*}}_os_log_impl_test{{.*}}"({{.*}}, {{.*}}, {{.*}}, {{.*}}, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @{{.*}}, i64 0, i64 0), i8* {{(nonnull )?}}[[BUFFER]], i32 12)     // CHECK-32-NEXT: tail call swiftcc void @"${{.*}}_os_log_impl_test{{.*}}"({{.*}}, {{.*}}, {{.*}}, {{.*}}, i8* getelementptr inbounds ([20 x i8], [20 x i8]* @{{.*}}, i32 0, i32 0), i8* {{(nonnull )?}}[[BUFFER]], i32 8)+    // CHECK-NEXT: [[BITCASTED_OBJ_STORAGE:%.+]] = bitcast i8* [[OBJ_STORAGE]] to %swift.opaque*+    // CHECK-NEXT: tail call void @swift_arrayDestroy(%swift.opaque* [[BITCASTED_OBJ_STORAGE]], i64 1

Does this SIL instruction use i64 even in 32 bit platforms? You can omit the i64 and the rest if that is not the case.

guitard0g

comment created time in 22 days

Pull request review commentapple/swift

[OSLog] Update compiler stubs and tests

 func intValueWithPrecisionTest() -> OSLogMessage {      \(10, format: .decimal(minDigits: 25), align: .right(columns: 10))     """ }++@_semantics("test_driver")+func intValueWithPrivacyTest() -> OSLogMessage {+    return "An integer value \(10, privacy: .sensitive(mask: .hash))"

This test is good to have. Thanks for adding it. We can change .sensitive here by .private.

guitard0g

comment created time in 22 days

Pull request review commentapple/swift

[OSLog] Update compiler stubs and tests

 func testDynamicPrecisionAndAlignment() {     // CHECK-NEXT: br label %[[NOT_ENABLED]] } -// TODO: add test for String. It is more complicated due to more complex logic-// in string serialization.+// CHECK-LABEL: define hidden swiftcc void @"${{.*}}testStringInterpolation+func testStringInterpolation(stringValue: String) {+  _osLogTestHelper("String value: \(stringValue)")+    // CHECK: entry:+    // CHECK: call %swift.bridge* @swift_bridgeObjectRetain_n(%swift.bridge* %1+    // CHECK: tail call swiftcc i1 @"${{.*}}isLoggingEnabled{{.*}}"()+    // CHECK-NEXT: br i1 {{%.*}}, label %[[ENABLED:[0-9]+]], label %[[NOT_ENABLED:[0-9]+]]++    // CHECK: [[NOT_ENABLED]]:+    // CHECK: call void @swift_bridgeObjectRelease_n(%swift.bridge* %1+    // CHECK-NEXT: br label %[[EXIT:[0-9]+]]++    // CHECK: [[ENABLED]]:+    //+    // Header bytes.+    //+    // CHECK-64: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 12+    // CHECK-32: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 8+    // CHECK-64-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 32+    // CHECK-32-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 16+    // CHECK: call void @llvm.lifetime.start{{.*}}({{.*}}, i8* {{(nonnull )?}}[[STR_STORAGE_PTR:%.*]]+    // CHECK: store i8 2, i8* [[BUFFER]], align 1+    // CHECK-NEXT: [[OFFSET1:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 1+    // CHECK-NEXT: store i8 1, i8* [[OFFSET1]], align 1++    // Argument bytes.+    //+    // CHECK-NEXT: [[OFFSET2:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 2+    // TODO: Figure out why "store i8 16" turned into "store i8 0"+    // CHECK-NEXT: store i8 32, i8* [[OFFSET2]], align 1+    // CHECK-NEXT: [[OFFSET3:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 3+    // CHECK-NEXT: store i8 8, i8* [[OFFSET3]], align 1+    +    // CHECK: [[STR_POINTER:%.*]] = call swiftcc i8* @"${{.*}}getNullTerminatedUTF8Pointer{{.*}}"(i{{.*}} %0, {{.*}} %1++    // os_log_impl call.+    // CHECK-64: tail call swiftcc void @"${{.*}}_os_log_impl_test{{.*}}"({{.*}}, {{.*}}, {{.*}}, {{.*}}, i8* getelementptr inbounds ([17 x i8], [17 x i8]* @{{.*}}, i64 0, i64 0), i8* {{(nonnull )?}}[[BUFFER]], i32 12)+    // CHECK-32: tail call swiftcc void @"${{.*}}_os_log_impl_test{{.*}}"({{.*}}, {{.*}}, {{.*}}, {{.*}}, i8* getelementptr inbounds ([17 x i8], [17 x i8]* @{{.*}}, i32 0, i32 0), i8* {{(nonnull )?}}[[BUFFER]], i32 8)+    // CHECK-NEXT: [[BITCASTED_STR_STORAGE:%.*]] = bitcast i8* [[STR_STORAGE]] to %swift.opaque*+    // CHECK-NEXT: tail call void @swift_arrayDestroy(%swift.opaque* [[BITCASTED_STR_STORAGE]]+    // CHECK-NEXT: tail call void @swift_slowDealloc(i8* {{(nonnull )?}}[[STR_STORAGE]]+    // CHECK-NEXT: tail call void @swift_slowDealloc(i8* {{(nonnull )?}}[[BUFFER]]+    // CHECK: call void @llvm.lifetime.end{{.*}}({{.*}}, i8* {{(nonnull )?}}[[STR_STORAGE_PTR]]+    // CHECK-NEXT: br label %[[EXIT]]+  +    // CHECK: [[EXIT]]:+    // CHECK-NEXT: ret void+}++// CHECK-LABEL: define hidden swiftcc void @"${{.*}}testMetatypeInterpolation+func testMetatypeInterpolation<T>(of type: T.Type) {+    _osLogTestHelper("Metatype value: \(type)")+    // CHECK: entry:+    // CHECK: tail call swiftcc i1 @"${{.*}}isLoggingEnabled{{.*}}"()+    // CHECK-NEXT: br i1 {{%.*}}, label %[[ENABLED:[0-9]+]], label %[[NOT_ENABLED:[0-9]+]]++    // CHECK: [[NOT_ENABLED]]:+    // CHECK-NEXT: call void @swift_release+    // CHECK-NEXT: br label %[[EXIT:[0-9]+]]++    // CHECK: [[ENABLED]]:+    //+    // Header bytes.+    //+    // CHECK-64: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 12+    // CHECK-32: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 8+    // CHECK-64-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 32+    // CHECK-32-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 16+    // CHECK: call void @llvm.lifetime.start{{.*}}({{.*}}, i8* {{(nonnull )?}}[[STR_STORAGE_PTR:%.*]]+    // CHECK: store i8 2, i8* [[BUFFER]], align 1+    // CHECK-NEXT: [[OFFSET1:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 1+    // CHECK-NEXT: store i8 1, i8* [[OFFSET1]], align 1++    // Argument bytes.+    //+    // CHECK-NEXT: [[OFFSET2:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 2+    // TODO: Figure out why "store i8 16" turned into "store i8 0"+    // CHECK-NEXT: store i8 32, i8* [[OFFSET2]], align 1+    // CHECK-NEXT: [[OFFSET3:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 3+    // CHECK-NEXT: store i8 8, i8* [[OFFSET3]], align 1+    // CHECK-NEXT: [[TYPENAME:%.+]] = tail call swiftcc { i{{.*}}, %swift.bridge* } @"${{.*}}_typeName{{.*}}"({{.*}} %0

It is great that we see a _typeName(%0) here. It would be good to add a case for constant meta types like Int.self and a FIXME there to indicate that Erik's optimization should apply.

guitard0g

comment created time in 22 days

Pull request review commentapple/swift

[OSLog] Update compiler stubs and tests

 func testDynamicPrecisionAndAlignment() {     // CHECK-NEXT: br label %[[NOT_ENABLED]] } -// TODO: add test for String. It is more complicated due to more complex logic-// in string serialization.+// CHECK-LABEL: define hidden swiftcc void @"${{.*}}testStringInterpolation+func testStringInterpolation(stringValue: String) {+  _osLogTestHelper("String value: \(stringValue)")+    // CHECK: entry:+    // CHECK: call %swift.bridge* @swift_bridgeObjectRetain_n(%swift.bridge* %1+    // CHECK: tail call swiftcc i1 @"${{.*}}isLoggingEnabled{{.*}}"()+    // CHECK-NEXT: br i1 {{%.*}}, label %[[ENABLED:[0-9]+]], label %[[NOT_ENABLED:[0-9]+]]++    // CHECK: [[NOT_ENABLED]]:+    // CHECK: call void @swift_bridgeObjectRelease_n(%swift.bridge* %1+    // CHECK-NEXT: br label %[[EXIT:[0-9]+]]++    // CHECK: [[ENABLED]]:+    //+    // Header bytes.+    //+    // CHECK-64: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 12+    // CHECK-32: [[BUFFER:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 8+    // CHECK-64-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 32+    // CHECK-32-NEXT: [[STR_STORAGE:%.+]] = tail call noalias i8* @swift_slowAlloc(i{{.*}} 16+    // CHECK: call void @llvm.lifetime.start{{.*}}({{.*}}, i8* {{(nonnull )?}}[[STR_STORAGE_PTR:%.*]]+    // CHECK: store i8 2, i8* [[BUFFER]], align 1+    // CHECK-NEXT: [[OFFSET1:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 1+    // CHECK-NEXT: store i8 1, i8* [[OFFSET1]], align 1++    // Argument bytes.+    //+    // CHECK-NEXT: [[OFFSET2:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 2+    // TODO: Figure out why "store i8 16" turned into "store i8 0"+    // CHECK-NEXT: store i8 32, i8* [[OFFSET2]], align 1+    // CHECK-NEXT: [[OFFSET3:%.+]] = getelementptr inbounds i8, i8* [[BUFFER]], i{{.*}} 3+    // CHECK-NEXT: store i8 8, i8* [[OFFSET3]], align 1+    +    // CHECK: [[STR_POINTER:%.*]] = call swiftcc i8* @"${{.*}}getNullTerminatedUTF8Pointer{{.*}}"(i{{.*}} %0, {{.*}} %1

It is also good to check that STR_POINTER is stored into the [[BUFFER]] at the right offset.

guitard0g

comment created time in 22 days

PullRequestReviewEvent
PullRequestReviewEvent

push eventravikandhadai/swift

Ravi

commit sha a917f112defb13d1992ce6e86ea51753727dd44e

Updating stdlib files

view details

push time in a month

push eventravikandhadai/swift

Ravi

commit sha da84e249a95cb7e225304a92b038e790d4453d1e

Making a simple test case work

view details

push time in a month

push eventravikandhadai/swift

Ravi

commit sha 1f93396a21f928982449b3877a1bd1622a1b5d3e

Making constant folding pass fold constant evaluable functions

view details

push time in a month

push eventravikandhadai/swift

Ravi

commit sha 4fdb71201e160aa0b4c0a0c13196da9c48593ce1

Making constant folding pass use constant evaluator

view details

push time in a month

create barnchravikandhadai/swift

branch : improved-constant-folding

created branch time in a month

pull request commentapple/swift

SIL optimizer: Add a new string optimization

In the case of os_log the appendInterpolation calls take an escaping autoclosure, which gets stored in an array and later used, if and only if logging is enabled. (This means that when logging is disabled for a certain log level, e.g. debug level is generally disabled outside of Xcode, the interpolated values are not even evaluated and the log calls are almost like noops.) But, all of this complexity is optimized away by the OSLogOptimization pass. So what remains after that pass is a chain of calls that eventually applies _typename to the interpolated meta type (which could be a constant like Int.self or a non-constant SILValue coming from the context). The chains of calls should also be optimized and inlined in -O mode and the optimization of this PR should apply eventually. This is what I would expect. But, since in the os_log case there is more interconnection between optimization for this to happen, I would be curious to know if this indeed happens.

There is a test suite that checks the combined effect of all the -O optimizations (OSLogFullOptTest.swift). The resulting LLVM IR should be similar to C os_log calls, where there are no closures, no arrays, no StringInterpolation type or appendInterpolation calls. There should only be a runtime check for whether logging is enabled or not, code to construct the argument, and a sequence of "stores" of the argument bytes into a byte buffer. @guitard0g and I are planning to add one more test suite to check if this PR's optimization will also apply and eventually we are just left a string literal payload when interpolating constant meta types.

Note that _typeName constant propagation is done, regardless if it's in an interpolation. So even with this PR it should create a constant String argument if used in osLog.

That is right. But like here, in the os_log case also, one can just do logger.log("\(Int.self)") and _typename would be wrapped inside an appendInterpolation definition. In fact, Int.self would also be autoclosured, and _typename will apply within a closure. None of these will be evaluated until when logging is enabled. But, all of those closures will be optimized away because of OSLogOptimization, which essentially links the closure definition i.e., partial_applies to their use i.e., indirect calls, so that partial_apply to full_apply optimizations can completely eliminate the closure and convert it to a direct call. All of these already happen. So after all the optimization what we should be left with is conceptually an expression like _typename(Int.self) (though it is far from this to start with at the source level), so this optimization should apply and reduce it to a string literal.

Not propagating constant osLog string arguments is probably not so bad, because it leaves a "%s" in the format string and passes an additional argument. I think this is not so much overhead. If the argument is used in many places, it can even save some data size.

Yes that is right. The format string would be "%s" and there would be a payload that is a pointer (like char *) to the string's (UTF-8 encoded) bytes. But since this optimization reduces it to a string literal like "Int", ideally the payload must just be a pointer to the string literal (at the SIL level just a %ptr where %ptr = string_literal utf8 "Int"). Ideally, the creation of an intermediate Swift.String type should be completely avoided. But that may not happen as such. But, getting a pointer from a Swift.String is highly optimized at runtime due to os_log specific String SPIs.

Propagation of constant String (or other) arguments could be done as a post-process, by scanning the format string and the arguments.

Doing a post processing and combining the payload string literal into the format string is an interesting idea. Looks like in principle it is as an optimization on this own, that applies not just to _typename but to all static payloads (strings, numerics etc). That would be a nice optimization. It could save code size and performance.

eeckstein

comment created time in 3 months

pull request commentapple/swift

SIL optimizer: Add a new string optimization

@eeckstein Just a quick question. If I understand correctly, this constant folding works for interpolation of metatypes because it is expecting the appendInterpolation function to be inlined right? So it looks like it does depend on the inlining heuristics. I am asking this as in the os_log case , the appendInterpolation implementation is slightly more complicated. So that is something I have to check.

eeckstein

comment created time in 3 months

Pull request review commentapple/swift

SIL optimizer: Add a new string optimization

+//===--- StringOptimization.cpp - Optimize string operations --------------===//+//+// This source file is part of the Swift.org open source project+//+// Copyright (c) 2014 - 2020 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 pass performs several optimizations on String operations.+//+//===----------------------------------------------------------------------===//++#define DEBUG_TYPE "string-optimization"+#include "swift/SILOptimizer/PassManager/Transforms.h"+#include "swift/SILOptimizer/Analysis/ValueTracking.h"+#include "swift/SIL/SILFunction.h"+#include "swift/SIL/SILBasicBlock.h"+#include "swift/SIL/SILBuilder.h"+#include "swift/AST/SemanticAttrs.h"+#include "swift/AST/ParameterList.h"+#include "llvm/Support/Debug.h"++using namespace swift;++namespace {++/// Optimizes String operations with constant operands.++/// Specifically:+///   * Replaces x.append(y) with x = y if x is empty.+///   * Removes x.append("")+///   * Replaces x.append(y) with x = x + y if x and y are constant strings.+///   * Replaces _typeName(T.self) with a constant string if T is statically+///     known.+///+/// This pass must run on high-level SIL, where semantic calls are still in+/// place.+///+/// The optimization is implemented in a simple way. Therfore it cannot handle+/// complicated patterns, e.g. the dataflow analysis for the String.append self+/// argument is only done within a single block.+/// But this is totally sufficient to be able to constant propagate strings in+/// string interpolations.+///+/// If we want to make this optimization more powerful it's best done by using+/// the ConstExprStepEvaluator (which is currently lacking a few features to be+/// used for this optimization).+class StringOptimization {++  struct StringInfo {+    /// The string+    StringRef str;+    +    /// Negative means: not constant+    int numCodeUnits = -1;+    +    /// Not 0 for the empty-string initializer which reserves a capacity.+    int reservedCapacity = 0;+    +    bool isConstant() const { return numCodeUnits >= 0; }+    bool isEmpty() const { return isConstant() && str.empty(); }+  };++  /// The stdlib's String type.+  SILType stringType;+  +  /// The String initializer which takes an UTF8 string literal as argument.+  SILFunction *makeUTF8Func = nullptr;+  +  /// Caches the analysis result for an alloc_stack or an inout function+  /// argument, whether it is an "identifyable" object.+  /// See mayWriteToIdentifyableObject().+  llvm::DenseMap<SILValue, bool> identifyableObjectsCache;++public:+  bool run(SILFunction *F);++private:++  bool optimizeBlock(SILBasicBlock &block);+  +  bool optimizeStringAppend(ApplyInst *appendCall,+                            llvm::DenseMap<SILValue, SILValue> &storedStrings);++  bool optimizeTypeName(ApplyInst *typeNameCall);++  static ApplyInst *isSemanticCall(SILInstruction *inst, StringRef attr,+                                   unsigned numArgs);+  StoreInst *isStringStoreToIdentifyableObject(SILInstruction *inst);+  static void invalidateModifiedObjects(SILInstruction *inst,+                            llvm::DenseMap<SILValue, SILValue> &storedStrings);+  static StringInfo getStringInfo(SILValue value);+  static Optional<int> getIntConstant(SILValue value);+  static void replaceAppendWith(ApplyInst *appendCall, SILValue newValue,+                                bool copyNewValue);+  ApplyInst *createStringInit(StringRef str, SILInstruction *beforeInst);+};++/// The main entry point of the optimization.+bool StringOptimization::run(SILFunction *F) {+  NominalTypeDecl *stringDecl = F->getModule().getASTContext().getStringDecl();+  if (!stringDecl)+    return false;+  stringType = SILType::getPrimitiveObjectType(+                 CanType(stringDecl->getDeclaredType()));++  bool changed = false;+  +  for (SILBasicBlock &block : *F) {+    changed |= optimizeBlock(block);+  }+  return changed;+}++/// Run the optimization on a basic block.+bool StringOptimization::optimizeBlock(SILBasicBlock &block) {+  bool changed = false;+  +  /// Maps identifyable objects (alloc_stack, inout parameters) to string values+  /// which are stored in those objects.+  llvm::DenseMap<SILValue, SILValue> storedStrings;+  +  for (auto iter = block.begin(); iter != block.end();) {+    SILInstruction *inst = &*iter++;++    if (StoreInst *store = isStringStoreToIdentifyableObject(inst)) {+      storedStrings[store->getDest()] = store->getSrc();+      continue;+    }+    if (ApplyInst *append = isSemanticCall(inst, semantics::STRING_APPEND, 2)) {+      if (optimizeStringAppend(append, storedStrings)) {+        changed = true;+        continue;+      }+    }+    if (ApplyInst *typeName = isSemanticCall(inst, semantics::TYPENAME, 2)) {+      if (optimizeTypeName(typeName)) {+        changed = true;+        continue;+      }+    }+    // Remove items from storedStrings if inst overwrites (or potentially+    // overwrites) a stored String in an identifyable object.+    invalidateModifiedObjects(inst, storedStrings);+  }+  return changed;+}++/// Optimize String.append in case anything is known about the parameters.+bool StringOptimization::optimizeStringAppend(ApplyInst *appendCall,+                            llvm::DenseMap<SILValue, SILValue> &storedStrings) {+  SILValue rhs = appendCall->getArgument(0);+  StringInfo rhsString = getStringInfo(rhs);+  +  // Remove lhs.append(rhs) if rhs is empty.+  if (rhsString.isEmpty()) {+    appendCall->eraseFromParent();+    return true;+  }+  +  SILValue lhsAddr = appendCall->getArgument(1);+  StringInfo lhsString = getStringInfo(storedStrings[lhsAddr]);++  // The following two optimizations are a trade-off: Performance-wise it may be+  // benefitial to initialize an empty string with reserved capacity and then+  // append multiple other string components.+  // Removing the empty string (with the reserved capacity) might result in more+  // allocations.+  // So we just do this optimization up to a certain capacity limit (found by+  // experiment).+  if (lhsString.reservedCapacity > 50)+    return false;++  // Replace lhs.append(rhs) with 'lhs = rhs' if lhs is empty.+  if (lhsString.isEmpty()) {+    replaceAppendWith(appendCall, rhs, /*copyNewValue*/ true);+    storedStrings[lhsAddr] = rhs;+    return true;+  }+  +  // Replace lhs.append(rhs) with "lhs = lhs + rhs" if both lhs and rhs are+  // constant.+  if (lhsString.isConstant() && rhsString.isConstant()) {+    std::string concat = lhsString.str;+    concat += rhsString.str;+    if (ApplyInst *stringInit = createStringInit(concat, appendCall)) {+      replaceAppendWith(appendCall, stringInit, /*copyNewValue*/ false);+      storedStrings[lhsAddr] = stringInit;+      return true;+    }+  }+  +  return false;+}

I have noticed that String.makeUTF8 init may not get DCE'ed some times. Otherwise, there is a InstructionDeleter utility that can deleted instructions and track their operands and can DCE them later.

eeckstein

comment created time in 3 months

Pull request review commentapple/swift

SIL optimizer: Add a new string optimization

+//===--- StringOptimization.cpp - Optimize string operations --------------===//+//+// This source file is part of the Swift.org open source project+//+// Copyright (c) 2014 - 2020 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 pass performs several optimizations on String operations.+//+//===----------------------------------------------------------------------===//++#define DEBUG_TYPE "string-optimization"+#include "swift/SILOptimizer/PassManager/Transforms.h"+#include "swift/SILOptimizer/Analysis/ValueTracking.h"+#include "swift/SIL/SILFunction.h"+#include "swift/SIL/SILBasicBlock.h"+#include "swift/SIL/SILBuilder.h"+#include "swift/AST/SemanticAttrs.h"+#include "swift/AST/ParameterList.h"+#include "llvm/Support/Debug.h"++using namespace swift;++namespace {++/// Optimizes String operations with constant operands.++/// Specifically:+///   * Replaces x.append(y) with x = y if x is empty.+///   * Removes x.append("")+///   * Replaces x.append(y) with x = x + y if x and y are constant strings.+///   * Replaces _typeName(T.self) with a constant string if T is statically+///     known.+///+/// This pass must run on high-level SIL, where semantic calls are still in+/// place.+///+/// The optimization is implemented in a simple way. Therfore it cannot handle+/// complicated patterns, e.g. the dataflow analysis for the String.append self+/// argument is only done within a single block.+/// But this is totally sufficient to be able to constant propagate strings in+/// string interpolations.+///+/// If we want to make this optimization more powerful it's best done by using+/// the ConstExprStepEvaluator (which is currently lacking a few features to be+/// used for this optimization).+class StringOptimization {++  struct StringInfo {+    /// The string+    StringRef str;+    +    /// Negative means: not constant+    int numCodeUnits = -1;+    +    /// Not 0 for the empty-string initializer which reserves a capacity.+    int reservedCapacity = 0;+    +    bool isConstant() const { return numCodeUnits >= 0; }+    bool isEmpty() const { return isConstant() && str.empty(); }+  };++  /// The stdlib's String type.+  SILType stringType;+  +  /// The String initializer which takes an UTF8 string literal as argument.+  SILFunction *makeUTF8Func = nullptr;+  +  /// Caches the analysis result for an alloc_stack or an inout function+  /// argument, whether it is an "identifyable" object.+  /// See mayWriteToIdentifyableObject().+  llvm::DenseMap<SILValue, bool> identifyableObjectsCache;++public:+  bool run(SILFunction *F);++private:++  bool optimizeBlock(SILBasicBlock &block);+  +  bool optimizeStringAppend(ApplyInst *appendCall,+                            llvm::DenseMap<SILValue, SILValue> &storedStrings);++  bool optimizeTypeName(ApplyInst *typeNameCall);++  static ApplyInst *isSemanticCall(SILInstruction *inst, StringRef attr,+                                   unsigned numArgs);+  StoreInst *isStringStoreToIdentifyableObject(SILInstruction *inst);+  static void invalidateModifiedObjects(SILInstruction *inst,+                            llvm::DenseMap<SILValue, SILValue> &storedStrings);+  static StringInfo getStringInfo(SILValue value);+  static Optional<int> getIntConstant(SILValue value);+  static void replaceAppendWith(ApplyInst *appendCall, SILValue newValue,+                                bool copyNewValue);+  ApplyInst *createStringInit(StringRef str, SILInstruction *beforeInst);+};++/// The main entry point of the optimization.+bool StringOptimization::run(SILFunction *F) {+  NominalTypeDecl *stringDecl = F->getModule().getASTContext().getStringDecl();+  if (!stringDecl)+    return false;+  stringType = SILType::getPrimitiveObjectType(+                 CanType(stringDecl->getDeclaredType()));++  bool changed = false;+  +  for (SILBasicBlock &block : *F) {+    changed |= optimizeBlock(block);+  }+  return changed;+}++/// Run the optimization on a basic block.+bool StringOptimization::optimizeBlock(SILBasicBlock &block) {+  bool changed = false;+  +  /// Maps identifyable objects (alloc_stack, inout parameters) to string values+  /// which are stored in those objects.+  llvm::DenseMap<SILValue, SILValue> storedStrings;+  +  for (auto iter = block.begin(); iter != block.end();) {+    SILInstruction *inst = &*iter++;++    if (StoreInst *store = isStringStoreToIdentifyableObject(inst)) {+      storedStrings[store->getDest()] = store->getSrc();+      continue;+    }+    if (ApplyInst *append = isSemanticCall(inst, semantics::STRING_APPEND, 2)) {+      if (optimizeStringAppend(append, storedStrings)) {+        changed = true;+        continue;+      }+    }+    if (ApplyInst *typeName = isSemanticCall(inst, semantics::TYPENAME, 2)) {+      if (optimizeTypeName(typeName)) {+        changed = true;+        continue;+      }+    }+    // Remove items from storedStrings if inst overwrites (or potentially+    // overwrites) a stored String in an identifyable object.+    invalidateModifiedObjects(inst, storedStrings);+  }+  return changed;+}++/// Optimize String.append in case anything is known about the parameters.+bool StringOptimization::optimizeStringAppend(ApplyInst *appendCall,+                            llvm::DenseMap<SILValue, SILValue> &storedStrings) {+  SILValue rhs = appendCall->getArgument(0);+  StringInfo rhsString = getStringInfo(rhs);+  +  // Remove lhs.append(rhs) if rhs is empty.+  if (rhsString.isEmpty()) {+    appendCall->eraseFromParent();+    return true;+  }+  +  SILValue lhsAddr = appendCall->getArgument(1);+  StringInfo lhsString = getStringInfo(storedStrings[lhsAddr]);++  // The following two optimizations are a trade-off: Performance-wise it may be+  // benefitial to initialize an empty string with reserved capacity and then+  // append multiple other string components.+  // Removing the empty string (with the reserved capacity) might result in more+  // allocations.+  // So we just do this optimization up to a certain capacity limit (found by+  // experiment).+  if (lhsString.reservedCapacity > 50)+    return false;++  // Replace lhs.append(rhs) with 'lhs = rhs' if lhs is empty.+  if (lhsString.isEmpty()) {+    replaceAppendWith(appendCall, rhs, /*copyNewValue*/ true);+    storedStrings[lhsAddr] = rhs;+    return true;+  }+  +  // Replace lhs.append(rhs) with "lhs = lhs + rhs" if both lhs and rhs are+  // constant.+  if (lhsString.isConstant() && rhsString.isConstant()) {+    std::string concat = lhsString.str;+    concat += rhsString.str;+    if (ApplyInst *stringInit = createStringInit(concat, appendCall)) {+      replaceAppendWith(appendCall, stringInit, /*copyNewValue*/ false);+      storedStrings[lhsAddr] = stringInit;+      return true;+    }+  }+  +  return false;+}

I was wondering if there are any dead operands after this optimization, whether they all will be cleaned up by a later dead code elimination.

eeckstein

comment created time in 3 months

pull request commentapple/swift

SIL optimizer: Add a new string optimization

@ravikandhadai This constant-folding for string interpolation works for the DefaultStringInterpolation. It would be cool if we could also make it work for the OSLog interpolation. Any thoughts on this?

Making os_log use _typename would be similar to DefaultStringInterpolation implementation. It is tracked by rdar://65636690. So, I think this should enable constant fold the _typename in that case as well.

eeckstein

comment created time in 3 months

Pull request review commentapple/swift

SIL optimizer: Add a new string optimization

 FuncDecl *ASTContext::getArrayReserveCapacityDecl() const {   return nullptr; } +ConstructorDecl *ASTContext::getMakeUTF8StringDecl() const {+  if (getImpl().MakeUTF8StringDecl)+    return getImpl().MakeUTF8StringDecl;++  auto initializers =+    getStringDecl()->lookupDirect(DeclBaseName::createConstructor());++  for (Decl *initializer : initializers) {+    auto *constructor = cast<ConstructorDecl>(initializer);+    auto Attrs = constructor->getAttrs();+    for (auto *A : Attrs.getAttributes<SemanticsAttr, false>()) {+      if (A->Value != "string.makeUTF8")

Should this string "string.makeUTF8" be replaced by the macro: semantics::STRING_MAKE_UTF8

eeckstein

comment created time in 3 months

more