//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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
//
//===----------------------------------------------------------------------===//

import SwiftShims

/// Returns 1 if the running OS version is greater than or equal to
/// major.minor.patchVersion and 0 otherwise.
///
/// This is a magic entry point known to the compiler. It is called in
/// generated code for API availability checking.
///
/// This is marked @_transparent on iOS to work around broken availability
/// checking for iOS apps running on macOS (rdar://83378814). libswiftCore uses
/// the macOS platform identifier for its version check in that scenario,
/// causing all queries to return true. When this function is inlined into the
/// caller, the compiler embeds the correct platform identifier in the client
/// code, and we get the right answer.
///
/// @_transparent breaks the optimizer's ability to remove availability checks
/// that are unnecessary due to the current deployment target. We call through
/// to the _stdlib_isOSVersionAtLeast_AEIC function below to work around this,
/// as the optimizer is able to perform this optimization for a
/// @export(implementation) function. We can't use @export(implementation)
/// directly on this call because it would break ABI for existing apps.
///
/// `@_transparent` breaks the interpreter mode on macOS, as it creates a direct
/// reference to ___isPlatformVersionAtLeast from compiler-rt, and the
/// interpreter doesn't currently know how to load symbols from compiler-rt.
/// Since `@_transparent` is only necessary for iOS apps, we only apply it on
/// iOS, not any other which would inherit/remap iOS availability.
#if os(iOS) && !os(visionOS)
@_effects(readnone)
@_transparent
@_noLocks
public func _stdlib_isOSVersionAtLeast(
  _ major: Builtin.Word,
  _ minor: Builtin.Word,
  _ patch: Builtin.Word
) -> Builtin.Int1 {
  return _stdlib_isOSVersionAtLeast_AEIC(major, minor, patch)
}
#else
@_semantics("availability.osversion")
@_effects(readnone)
@_unavailableInEmbedded
#if hasFeature(Macros)
@_noLocks
#endif
public func _stdlib_isOSVersionAtLeast(
  _ major: Builtin.Word,
  _ minor: Builtin.Word,
  _ patch: Builtin.Word
) -> Builtin.Int1 {
  return _stdlib_isOSVersionAtLeast_AEIC(major, minor, patch)
}
#endif

@_semantics("availability.osversion")
@_effects(readnone)
@export(implementation)
#if hasFeature(Macros)
@_noLocks
#endif
public func _stdlib_isOSVersionAtLeast_AEIC(
  _ major: Builtin.Word,
  _ minor: Builtin.Word,
  _ patch: Builtin.Word
) -> Builtin.Int1 {
#if (os(anyAppleOS) || os(Android)) && SWIFT_RUNTIME_OS_VERSIONING
  if Int(major) == 9999 {
    return true._value
  }

  let queryVersion = (Int(major), Int(minor), Int(patch))
  let major32 = Int32(truncatingIfNeeded:Int(queryVersion.0))
  let minor32 = Int32(truncatingIfNeeded:Int(queryVersion.1))
  let patch32 = Int32(truncatingIfNeeded:Int(queryVersion.2))

  // Defer to a builtin that calls clang's version checking builtin from
  // compiler-rt.
  let result32 = Int32(Builtin.targetOSVersionAtLeast(major32._value,
                                                      minor32._value,
                                                      patch32._value))
  return (result32 != (0 as Int32))._value
#else
  // FIXME: As yet, there is no obvious versioning standard for platforms other
  // than Darwin-based OSes, so we just assume false for now. 
  // rdar://problem/18881232
  return false._value
#endif
}

// Performs an availability check in macCatalyst code to support back
// deployment. This entry point takes in a variant OS version
// (i.e, an iOS version).
//
// This is not inlinable because we
// don't want to inline the messy implementation details of the
// compiler-rt support into apps and expose those as ABI surface.
//
// This is a magic entry point known to the compiler. It is called in
// generated code for API availability checking.

#if (os(macOS) || os(iOS) && targetEnvironment(macCatalyst)) && SWIFT_RUNTIME_OS_VERSIONING
@_semantics("availability.osversion")
@_effects(readnone)
@available(macOS 10.15, iOS 13.0, *)
#if hasFeature(Macros)
@_noLocks
#endif
public func _stdlib_isVariantOSVersionAtLeast(
  _ major: Builtin.Word,
  _ minor: Builtin.Word,
  _ patch: Builtin.Word
  ) -> Builtin.Int1 {
  if Int(major) == 9999 {
    return true._value
  }
  let queryVersion = (Int(major), Int(minor), Int(patch))
  let major32 = Int32(truncatingIfNeeded:Int(queryVersion.0))
  let minor32 = Int32(truncatingIfNeeded:Int(queryVersion.1))
  let patch32 = Int32(truncatingIfNeeded:Int(queryVersion.2))

  // Defer to a builtin that calls clang's version checking builtin from
  // compiler-rt.
  let result32 = Int32(Builtin.targetVariantOSVersionAtLeast(major32._value,
                                                             minor32._value,
                                                             patch32._value))
  return (result32 != (0 as Int32))._value
}
#endif

// Performs an availability check in zippered code to support back
// deployment. This entry point takes in both a primary OS version
// (i.e., a macOS version) and a variant OS version  (i.e, an iOS version).
//
// In a normal macOS process it will return 1 if the running OS version is
// greater than or equal to major.minor.patchVersion and 0 otherwise. For an
// macCatalyst process it will return 1 if the running macCatalyst version is greater
// than or equal to the passed-in variant version.
//
// Unlike _stdlib_isOSVersionAtLeast, this is not inlinable because we
// don't want to inline the messy implementation details of the
// compiler-rt support into apps and expose those as ABI surface.
//
// This is a magic entry point known to the compiler. It is called in
// generated code for API availability checking.

#if (os(macOS) || os(iOS) && targetEnvironment(macCatalyst)) && SWIFT_RUNTIME_OS_VERSIONING
@_semantics("availability.osversion")
@_effects(readnone)
@_unavailableInEmbedded
#if hasFeature(Macros)
@_noLocks
#endif
public func _stdlib_isOSVersionAtLeastOrVariantVersionAtLeast(
  _ major: Builtin.Word,
  _ minor: Builtin.Word,
  _ patch: Builtin.Word,
  _ variantMajor: Builtin.Word,
  _ variantMinor: Builtin.Word,
  _ variantPatch: Builtin.Word
  ) -> Builtin.Int1 {
  if Int(major) == 9999 {
    return true._value
  }
  let queryVersion = (Int(major), Int(minor), Int(patch))
  let queryVariantVersion =
    (Int(variantMajor), Int(variantMinor), Int(variantPatch))

  let major32 = UInt32(truncatingIfNeeded:Int(queryVersion.0))
  let minor32 = UInt32(truncatingIfNeeded:Int(queryVersion.1))
  let patch32 = UInt32(truncatingIfNeeded:Int(queryVersion.2))

  let variantMajor32 = UInt32(truncatingIfNeeded:Int(queryVariantVersion.0))
  let variantMinor32 = UInt32(truncatingIfNeeded:Int(queryVariantVersion.1))
  let variantPatch32 = UInt32(truncatingIfNeeded:Int(queryVariantVersion.2))

  // Defer to a builtin that calls clang's version checking builtin from
  // compiler-rt.
  let result32 = Int32(Builtin.targetOSVersionOrVariantOSVersionAtLeast(
          major32._value, minor32._value, patch32._value,
          variantMajor32._value, variantMinor32._value, variantPatch32._value))

  return (result32 != (0 as UInt32))._value
}
#endif

public typealias _SwiftStdlibVersion = SwiftShims._SwiftStdlibVersion

/// This is a magic entry point known to the compiler. It is called in
/// generated code for Swift runtime availability checking, e.g.
///
///     if #available(Swift 6.2, *) { }
///
@available(SwiftStdlib 5.7, *)
@export(implementation)
internal func _isSwiftRuntimeVersionAtLeast(
  _ major: Builtin.Word,
  _ minor: Builtin.Word,
  _ patch: Builtin.Word
) -> Builtin.Int1 {
  let version = _SwiftStdlibVersion(major, minor, patch)
  return (_SwiftStdlibVersion.current._value <= version._value)._value
}

/// Return true if the main executable was linked with an SDK version
/// corresponding to the given Swift Stdlib release, or later. Otherwise, return
/// false.
///
/// This is useful to maintain compatibility with older binaries after a
/// behavioral change in the stdlib.
///
/// This function must not be called from inlinable code.
@inline(__always)
@_unavailableInEmbedded
internal func _isExecutableLinkedOnOrAfter(
  _ stdlibVersion: _SwiftStdlibVersion
) -> Bool {
#if SWIFT_RUNTIME_OS_VERSIONING
  return _swift_stdlib_isExecutableLinkedOnOrAfter(stdlibVersion)
#else
  return true
#endif
}

extension _SwiftStdlibVersion {
  @export(implementation)
  public static var v5_6_0: Self { Self(_value: 0x050600) }

  @export(implementation)
  public static var v5_7_0: Self { Self(_value: 0x050700) }

  // Note: As of now, there is no bincompat level defined for the versions
  // below. If you need to use one of these in a call to
  // `_isExecutableLinkedOnOrAfter`, then you'll need to define the
  // corresponding version in the runtime.
  @export(implementation)
  public static var v5_8_0: Self { Self(_value: 0x050800) }
  @export(implementation)
  public static var v5_9_0: Self { Self(_value: 0x050900) }
  @export(implementation)
  public static var v5_10_0: Self { Self(_value: 0x050A00) }
  @export(implementation)
  public static var v6_0_0: Self { Self(_value: 0x060000) }
  @export(implementation)
  public static var v6_1_0: Self { Self(_value: 0x060100) }
  @export(implementation)
  public static var v6_2_0: Self { Self(_value: 0x060200) }
  @export(implementation)
  public static var v6_3_0: Self { Self(_value: 0x060300) }
  @export(implementation)
  public static var v6_4_0: Self { Self(_value: 0x060400) }
  @export(implementation)
  public static var v6_5_0: Self { Self(_value: 0x060500) }

  private static var _current: Self { .v6_5_0 }

#if hasFeature(Macros)
  @available(SwiftStdlib 5.7, *)
  public static var current: Self {
    @_noLocks
    @_effects(readnone)
    get { ._current }
  }
#else
  @available(SwiftStdlib 5.7, *)
  public static var current: Self {
    @_effects(readnone)
    get { ._current }
  }
#endif

  @export(implementation)
  internal init(
    _ major: Builtin.Word,
    _ minor: Builtin.Word,
    _ patch: Builtin.Word
  ) {
    let version = (Int(major), Int(minor), Int(patch))
    var value: UInt32 = 0x0
    value |= ((UInt32(truncatingIfNeeded: version.0) & 0xffff) << 16)
    value |= ((UInt32(truncatingIfNeeded: version.1) & 0xff) << 8)
    value |= ((UInt32(truncatingIfNeeded: version.2) & 0xff))
    self = Self(_value: value)
  }
}

@available(SwiftStdlib 5.7, *)
@_unavailableInEmbedded
extension _SwiftStdlibVersion: CustomStringConvertible {
  @available(SwiftStdlib 5.7, *)
  public var description: String {
    let major = _value >> 16
    let minor = (_value >> 8) & 0xFF
    let patch = _value & 0xFF
    return "\(major).\(minor).\(patch)"
  }
}


