//===----------------------------------------------------------------------===//
//
// 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

// Implementation Note: this file intentionally uses very LOW-LEVEL
// CONSTRUCTS, so that assert and fatal may be used liberally in
// building library abstractions without fear of infinite recursion.
//
// FIXME: We could go farther with this simplification, e.g. avoiding
// UnsafeMutablePointer

@_transparent
public // @testable
func _isDebugAssertConfiguration() -> Bool {
  // The values for the assert_configuration call are:
  // 0: Debug
  // 1: Release
  // 2: Fast
  return Int32(Builtin.assert_configuration()) == 0
}

@_transparent
public // @testable, used in _Concurrency executor preconditions
func _isReleaseAssertConfiguration() -> Bool {
  // The values for the assert_configuration call are:
  // 0: Debug
  // 1: Release
  // 2: Fast
  return Int32(Builtin.assert_configuration()) == 1
}

@_transparent
public // @testable
func _isFastAssertConfiguration() -> Bool {
  // The values for the assert_configuration call are:
  // 0: Debug
  // 1: Release
  // 2: Fast
  return Int32(Builtin.assert_configuration()) == 2
}

@_transparent
public // @testable
func _isStdlibInternalChecksEnabled() -> Bool {
#if INTERNAL_CHECKS_ENABLED
  return true
#else
  return false
#endif
}

@_transparent
@_alwaysEmitIntoClient // Introduced in 5.7
public // @testable
func _isStdlibDebugChecksEnabled() -> Bool {
#if SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE
  return !_isFastAssertConfiguration()
#else
  return _isDebugAssertConfiguration()
#endif
}

@usableFromInline @_transparent
internal func _fatalErrorFlags() -> UInt32 {
  // The current flags are:
  // (1 << 0): Report backtrace on fatal error
#if os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
  return 0
#else
  return _isDebugAssertConfiguration() ? 1 : 0
#endif
}

/// This function should be used only in the implementation of user-level
/// assertions.
///
/// This function should not be inlined in desktop Swift because it is cold and
/// inlining just bloats code. In Embedded Swift, we force inlining as this
/// function is typically just a trap (in release configurations).
@usableFromInline
#if !$Embedded
@inline(never)
#else
@inline(__always) @_transparent
#endif
@_semantics("programtermination_point")
internal func _assertionFailure(
  _ prefix: StaticString, _ message: StaticString,
  file: StaticString, line: UInt,
  flags: UInt32
) -> Never {
#if !$Embedded
  prefix.withUTF8Buffer {
    (prefix) -> Void in
    message.withUTF8Buffer {
      (message) -> Void in
      file.withUTF8Buffer {
        (file) -> Void in
        unsafe _swift_stdlib_reportFatalErrorInFile(
          prefix.baseAddress!, CInt(prefix.count),
          message.baseAddress!, CInt(message.count),
          file.baseAddress!, CInt(file.count), UInt32(line),
          flags)
        Builtin.int_trap()
      }
    }
  }
#else
  if _isDebugAssertConfiguration() {
    _embeddedReportFatalErrorInFile(prefix: prefix, message: message,
      file: file, line: line)
  }
#endif
  Builtin.condfail_message(false._value, message.unsafeRawPointer)
  Builtin.int_trap()
}

/// This function should be used only in the implementation of user-level
/// assertions.
///
/// This function should not be inlined in desktop Swift because it is cold and
/// inlining just bloats code. In Embedded Swift, we force inlining as this
/// function is typically just a trap (in release configurations).
@usableFromInline
#if !$Embedded
@inline(never)
#else
@_disfavoredOverload
@inline(__always)
#endif
@_semantics("programtermination_point")
internal func _assertionFailure(
  _ prefix: StaticString, _ message: String,
  file: StaticString, line: UInt,
  flags: UInt32
) -> Never {
#if !$Embedded
  prefix.withUTF8Buffer {
    (prefix) -> Void in
    var message = message
    message.withUTF8 {
      (messageUTF8) -> Void in
      file.withUTF8Buffer {
        (file) -> Void in
        unsafe _swift_stdlib_reportFatalErrorInFile(
          prefix.baseAddress!, CInt(prefix.count),
          messageUTF8.baseAddress!, CInt(messageUTF8.count),
          file.baseAddress!, CInt(file.count), UInt32(line),
          flags)
      }
    }
  }
#else
  if _isDebugAssertConfiguration() {
    var message = message
    message.withUTF8 { (messageUTF8) -> Void in
      unsafe _embeddedReportFatalErrorInFile(prefix: prefix, message: messageUTF8, file: file, line: line)
    }
  }
#endif

  Builtin.int_trap()
}

/// This function should be used only in the implementation of user-level
/// assertions.
///
/// This function should not be inlined in desktop Swift because it is cold and
/// inlining just bloats code. In Embedded Swift, we force inlining as this
/// function is typically just a trap (in release configurations).
@usableFromInline
#if !$Embedded
@inline(never)
#else
@inline(__always)
#endif
@_semantics("programtermination_point")
@_unavailableInEmbedded
internal func _assertionFailure(
  _ prefix: StaticString, _ message: String,
  flags: UInt32
) -> Never {
  prefix.withUTF8Buffer {
    (prefix) -> Void in
    var message = message
    message.withUTF8 {
      (messageUTF8) -> Void in
      unsafe _swift_stdlib_reportFatalError(
        prefix.baseAddress!, CInt(prefix.count),
        messageUTF8.baseAddress!, CInt(messageUTF8.count),
        flags)
    }
  }

  Builtin.int_trap()
}

#if $Embedded
@usableFromInline
@inline(never)
@_semantics("programtermination_point")
internal func _assertionFailure(
  _ prefix: StaticString, _ message: StaticString,
  flags: UInt32
) -> Never {
  if _isDebugAssertConfiguration() {
    _embeddedReportFatalError(prefix: prefix, message: message)
  }

  Builtin.condfail_message(false._value, message.unsafeRawPointer)
  Builtin.int_trap()
}
#endif

/// This function should be used only in the implementation of stdlib
/// assertions.
///
/// This function should not be inlined in desktop Swift because it is cold and
/// inlining just bloats code. In Embedded Swift, we force inlining as this
/// function is typically just a trap (in release configurations).
@usableFromInline
#if !$Embedded
@inline(never)
#else
@inline(__always) @_transparent
#endif
@_semantics("programtermination_point")
internal func _fatalErrorMessage(
  _ prefix: StaticString, _ message: StaticString,
  file: StaticString, line: UInt,
  flags: UInt32
) -> Never {
  _assertionFailure(prefix, message, file: file, line: line, flags: flags)
}

/// Prints a fatal error message when an unimplemented initializer gets
/// called by the Objective-C runtime.
@_transparent
public // COMPILER_INTRINSIC
func _unimplementedInitializer(className: StaticString,
                               initName: StaticString = #function,
                               file: StaticString = #file,
                               line: UInt = #line,
                               column: UInt = #column
) -> Never {
  // This function is marked @_transparent so that it is inlined into the caller
  // (the initializer stub), and, depending on the build configuration,
  // redundant parameter values (#file etc.) are eliminated, and don't leak
  // information about the user's source.

#if !$Embedded
  if _isDebugAssertConfiguration() {
    className.withUTF8Buffer {
      (className) in
      initName.withUTF8Buffer {
        (initName) in
        file.withUTF8Buffer {
          (file) in
          unsafe _swift_stdlib_reportUnimplementedInitializerInFile(
            className.baseAddress!, CInt(className.count),
            initName.baseAddress!, CInt(initName.count),
            file.baseAddress!, CInt(file.count),
            UInt32(line), UInt32(column),
            /*flags:*/ 0)
        }
      }
    }
  } else {
    className.withUTF8Buffer {
      (className) in
      initName.withUTF8Buffer {
        (initName) in
        unsafe _swift_stdlib_reportUnimplementedInitializer(
          className.baseAddress!, CInt(className.count),
          initName.baseAddress!, CInt(initName.count),
          /*flags:*/ 0)
      }
    }
  }
#endif

  Builtin.int_trap()
}

#if !$Embedded

/// Previously used to evaluate editor placeholders.
@usableFromInline // COMPILER_INTRINSIC
internal func _undefined<T>(
  _ message: @autoclosure () -> String = String(),
  file: StaticString = #file, line: UInt = #line
) -> T {
  _assertionFailure("Fatal error", message(), file: file, line: line, flags: 0)
}

#else

/// Previously used to evaluate editor placeholders.
@usableFromInline // COMPILER_INTRINSIC
internal func _undefined<T>(
  _ message: @autoclosure () -> StaticString = StaticString(),
  file: StaticString = #file, line: UInt = #line
) -> T {
  _assertionFailure("Fatal error", message(), file: file, line: line, flags: 0)
}

#endif

/// Called when evaluating an editor placeholder in a playground.
///
/// We always export this into the client since it should never be used in an
/// actual shipping binary and keeps it a pure compiler implementation detail.
///
/// This function should not be inlined in desktop Swift because it is cold and
/// inlining just bloats code. In Embedded Swift, we force inlining as this
/// function is typically just a trap (in release configurations).
#if !$Embedded
@inline(never)
#else
@inline(__always)
#endif
@_alwaysEmitIntoClient // COMPILER_INTRINSIC
internal func _undefinedEditorPlaceholder(
  _filenameStart: Builtin.RawPointer,
  _filenameLength: Builtin.Word,
  _filenameIsASCII: Builtin.Int1,
  _line: Builtin.Word
) -> Never {
  _assertionFailure(
    "Fatal error",
    "attempt to evaluate editor placeholder",
    file: StaticString(
            _start: _filenameStart,
            utf8CodeUnitCount: _filenameLength,
            isASCII: _filenameIsASCII),
    line: UInt(_line),
    flags: 0
  )
}

/// Called when falling off the end of a switch and the type can be represented
/// as a raw value.
///
/// This function should not be inlined in desktop Swift because it is cold and
/// inlining just bloats code. In Embedded Swift, we force inlining as this
/// function is typically just a trap (in release configurations).
///
/// It doesn't take a source location because it's most important
/// in release builds anyway (old apps that are run on new OSs).
#if !$Embedded
@inline(never)
#else
@inline(__always)
#endif
@usableFromInline // COMPILER_INTRINSIC
internal func _diagnoseUnexpectedEnumCaseValue<SwitchedValue, RawValue>(
  type: SwitchedValue.Type,
  rawValue: RawValue
) -> Never {
#if !$Embedded
  _assertionFailure("Fatal error",
                    "unexpected enum case '\(type)(rawValue: \(rawValue))'",
                    flags: _fatalErrorFlags())
#else
  Builtin.int_trap()
#endif
}

/// Called when falling off the end of a switch and the value is not safe to
/// print.
///
/// This function should not be inlined in desktop Swift because it is cold and
/// inlining just bloats code. In Embedded Swift, we force inlining as this
/// function is typically just a trap (in release configurations).
///
/// It doesn't take a source location because it's most important
/// in release builds anyway (old apps that are run on new OSs).
#if !$Embedded
@inline(never)
#else
@inline(__always)
#endif
@usableFromInline // COMPILER_INTRINSIC
internal func _diagnoseUnexpectedEnumCase<SwitchedValue>(
  type: SwitchedValue.Type
) -> Never {
#if !$Embedded
  _assertionFailure(
    "Fatal error",
    "unexpected enum case while switching on value of type '\(type)'",
    flags: _fatalErrorFlags())
#else
  Builtin.int_trap()
#endif
}

/// Called when a function marked `unavailable` with `@available` is invoked
/// and the module containing the unavailable function was compiled with
/// `-unavailable-decl-optimization=stub`.
///
/// This function should not be inlined in desktop Swift because it is cold and
/// inlining just bloats code. In Embedded Swift, we force inlining as this
/// function is typically just a trap (in release configurations).
@backDeployed(before: SwiftStdlib 5.9)
#if !$Embedded
@inline(never)
#else
@inline(__always)
#endif
@_semantics("unavailable_code_reached")
@usableFromInline // COMPILER_INTRINSIC
internal func _diagnoseUnavailableCodeReached() -> Never {
  _assertionFailure(
    "Fatal error", "Unavailable code reached", flags: _fatalErrorFlags())
}
