# Intrinsics as exported by LLVM.
# Use `Intrinsics` to have a unified API across LLVM versions.
lib LibIntrinsics
  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_debugtrap)] {% end %}
  fun debugtrap = "llvm.debugtrap"

  {% if flag?(:avr) %}
    {% if compare_versions(Crystal::LLVM_VERSION, "15.0.0") < 0 %}
      fun memcpy = "llvm.memcpy.p0i8.p0i8.i16"(dest : Void*, src : Void*, len : UInt16, is_volatile : Bool)
      fun memmove = "llvm.memmove.p0i8.p0i8.i16"(dest : Void*, src : Void*, len : UInt16, is_volatile : Bool)
      fun memset = "llvm.memset.p0i8.i16"(dest : Void*, val : UInt8, len : UInt16, is_volatile : Bool)
    {% else %}
      fun memcpy = "llvm.memcpy.p0.p0.i16"(dest : Void*, src : Void*, len : UInt16, is_volatile : Bool)
      fun memmove = "llvm.memmove.p0.p0.i16"(dest : Void*, src : Void*, len : UInt16, is_volatile : Bool)
      fun memset = "llvm.memset.p0.i16"(dest : Void*, val : UInt8, len : UInt16, is_volatile : Bool)
    {% end %}
  {% else %}
    {% if flag?(:bits64) %}
      {% if compare_versions(Crystal::LLVM_VERSION, "15.0.0") < 0 %}
        {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %}
        fun memcpy = "llvm.memcpy.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool)

        {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %}
        fun memmove = "llvm.memmove.p0i8.p0i8.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool)

        {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %}
        fun memset = "llvm.memset.p0i8.i64"(dest : Void*, val : UInt8, len : UInt64, is_volatile : Bool)
      {% else %}
        {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %}
        fun memcpy = "llvm.memcpy.p0.p0.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool)

        {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %}
        fun memmove = "llvm.memmove.p0.p0.i64"(dest : Void*, src : Void*, len : UInt64, is_volatile : Bool)

        {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %}
        fun memset = "llvm.memset.p0.i64"(dest : Void*, val : UInt8, len : UInt64, is_volatile : Bool)
      {% end %}
    {% else %}
      {% if compare_versions(Crystal::LLVM_VERSION, "15.0.0") < 0 %}
        {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %}
        fun memcpy = "llvm.memcpy.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool)

        {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %}
        fun memmove = "llvm.memmove.p0i8.p0i8.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool)

        {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %}
        fun memset = "llvm.memset.p0i8.i32"(dest : Void*, val : UInt8, len : UInt32, is_volatile : Bool)
      {% else %}
        {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memcpy)] {% end %}
        fun memcpy = "llvm.memcpy.p0.p0.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool)

        {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memmove)] {% end %}
        fun memmove = "llvm.memmove.p0.p0.i32"(dest : Void*, src : Void*, len : UInt32, is_volatile : Bool)

        {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_memset)] {% end %}
        fun memset = "llvm.memset.p0.i32"(dest : Void*, val : UInt8, len : UInt32, is_volatile : Bool)
      {% end %}
    {% end %}
  {% end %}

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_read_cycle_counter)] {% end %}
  fun read_cycle_counter = "llvm.readcyclecounter" : UInt64

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bitreverse8)] {% end %}
  fun bitreverse8 = "llvm.bitreverse.i8"(id : UInt8) : UInt8

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bitreverse16)] {% end %}
  fun bitreverse16 = "llvm.bitreverse.i16"(id : UInt16) : UInt16

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bitreverse32)] {% end %}
  fun bitreverse32 = "llvm.bitreverse.i32"(id : UInt32) : UInt32

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bitreverse64)] {% end %}
  fun bitreverse64 = "llvm.bitreverse.i64"(id : UInt64) : UInt64

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bitreverse128)] {% end %}
  fun bitreverse128 = "llvm.bitreverse.i128"(id : UInt128) : UInt128

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bswap16)] {% end %}
  fun bswap16 = "llvm.bswap.i16"(id : UInt16) : UInt16

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bswap32)] {% end %}
  fun bswap32 = "llvm.bswap.i32"(id : UInt32) : UInt32

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bswap64)] {% end %}
  fun bswap64 = "llvm.bswap.i64"(id : UInt64) : UInt64

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_bswap128)] {% end %}
  fun bswap128 = "llvm.bswap.i128"(id : UInt128) : UInt128

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_popcount8)] {% end %}
  fun popcount8 = "llvm.ctpop.i8"(src : Int8) : Int8

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_popcount16)] {% end %}
  fun popcount16 = "llvm.ctpop.i16"(src : Int16) : Int16

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_popcount32)] {% end %}
  fun popcount32 = "llvm.ctpop.i32"(src : Int32) : Int32

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_popcount64)] {% end %}
  fun popcount64 = "llvm.ctpop.i64"(src : Int64) : Int64

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_popcount128)] {% end %}
  fun popcount128 = "llvm.ctpop.i128"(src : Int128) : Int128

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_countleading8)] {% end %}
  fun countleading8 = "llvm.ctlz.i8"(src : Int8, zero_is_undef : Bool) : Int8

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_countleading16)] {% end %}
  fun countleading16 = "llvm.ctlz.i16"(src : Int16, zero_is_undef : Bool) : Int16

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_countleading32)] {% end %}
  fun countleading32 = "llvm.ctlz.i32"(src : Int32, zero_is_undef : Bool) : Int32

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_countleading64)] {% end %}
  fun countleading64 = "llvm.ctlz.i64"(src : Int64, zero_is_undef : Bool) : Int64

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_countleading128)] {% end %}
  fun countleading128 = "llvm.ctlz.i128"(src : Int128, zero_is_undef : Bool) : Int128

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_counttrailing8)] {% end %}
  fun counttrailing8 = "llvm.cttz.i8"(src : Int8, zero_is_undef : Bool) : Int8

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_counttrailing16)] {% end %}
  fun counttrailing16 = "llvm.cttz.i16"(src : Int16, zero_is_undef : Bool) : Int16

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_counttrailing32)] {% end %}
  fun counttrailing32 = "llvm.cttz.i32"(src : Int32, zero_is_undef : Bool) : Int32

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_counttrailing64)] {% end %}
  fun counttrailing64 = "llvm.cttz.i64"(src : Int64, zero_is_undef : Bool) : Int64

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_counttrailing128)] {% end %}
  fun counttrailing128 = "llvm.cttz.i128"(src : Int128, zero_is_undef : Bool) : Int128

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl8)] {% end %}
  fun fshl8 = "llvm.fshl.i8"(a : UInt8, b : UInt8, count : UInt8) : UInt8

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl16)] {% end %}
  fun fshl16 = "llvm.fshl.i16"(a : UInt16, b : UInt16, count : UInt16) : UInt16

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl32)] {% end %}
  fun fshl32 = "llvm.fshl.i32"(a : UInt32, b : UInt32, count : UInt32) : UInt32

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl64)] {% end %}
  fun fshl64 = "llvm.fshl.i64"(a : UInt64, b : UInt64, count : UInt64) : UInt64

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshl128)] {% end %}
  fun fshl128 = "llvm.fshl.i128"(a : UInt128, b : UInt128, count : UInt128) : UInt128

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr8)] {% end %}
  fun fshr8 = "llvm.fshr.i8"(a : UInt8, b : UInt8, count : UInt8) : UInt8

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr16)] {% end %}
  fun fshr16 = "llvm.fshr.i16"(a : UInt16, b : UInt16, count : UInt16) : UInt16

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr32)] {% end %}
  fun fshr32 = "llvm.fshr.i32"(a : UInt32, b : UInt32, count : UInt32) : UInt32

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr64)] {% end %}
  fun fshr64 = "llvm.fshr.i64"(a : UInt64, b : UInt64, count : UInt64) : UInt64

  {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_fshr128)] {% end %}
  fun fshr128 = "llvm.fshr.i128"(a : UInt128, b : UInt128, count : UInt128) : UInt128

  {% if compare_versions(Crystal::LLVM_VERSION, "19.1.0") < 0 %}
    fun va_start = "llvm.va_start"(ap : Void*)
    fun va_end = "llvm.va_end"(ap : Void*)
  {% else %}
    fun va_start = "llvm.va_start.p0"(ap : Void*)
    fun va_end = "llvm.va_end.p0"(ap : Void*)
  {% end %}

  {% if flag?(:i386) || flag?(:x86_64) %}
    {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_pause)] {% end %}
    fun pause = "llvm.x86.sse2.pause"
  {% end %}

  {% if flag?(:aarch64) %}
    {% if flag?(:interpreted) %} @[Primitive(:interpreter_intrinsics_pause)] {% end %}
    fun arm_hint = "llvm.aarch64.hint"(hint : Int32)
  {% end %}
end

module Intrinsics
  macro debugtrap
    ::LibIntrinsics.debugtrap
  end

  def self.pause
    {% if flag?(:i386) || flag?(:x86_64) %}
      LibIntrinsics.pause
    {% elsif flag?(:aarch64) %}
      LibIntrinsics.arm_hint(1) # YIELD
    {% end %}
  end

  macro memcpy(dest, src, len, is_volatile)
    ::LibIntrinsics.memcpy({{dest}}, {{src}}, {{len}}, {{is_volatile}})
  end

  macro memmove(dest, src, len, is_volatile)
    ::LibIntrinsics.memmove({{dest}}, {{src}}, {{len}}, {{is_volatile}})
  end

  macro memset(dest, val, len, is_volatile)
    ::LibIntrinsics.memset({{dest}}, {{val}}, {{len}}, {{is_volatile}})
  end

  def self.read_cycle_counter
    LibIntrinsics.read_cycle_counter
  end

  def self.bitreverse8(id) : UInt8
    LibIntrinsics.bitreverse8(id)
  end

  def self.bitreverse16(id) : UInt16
    LibIntrinsics.bitreverse16(id)
  end

  def self.bitreverse32(id) : UInt32
    LibIntrinsics.bitreverse32(id)
  end

  def self.bitreverse64(id) : UInt64
    LibIntrinsics.bitreverse64(id)
  end

  def self.bitreverse128(id) : UInt128
    LibIntrinsics.bitreverse128(id)
  end

  def self.bswap16(id) : UInt16
    LibIntrinsics.bswap16(id)
  end

  def self.bswap32(id) : UInt32
    LibIntrinsics.bswap32(id)
  end

  def self.bswap64(id) : UInt64
    LibIntrinsics.bswap64(id)
  end

  def self.bswap128(id) : UInt128
    LibIntrinsics.bswap128(id)
  end

  def self.popcount8(src) : Int8
    LibIntrinsics.popcount8(src)
  end

  def self.popcount16(src) : Int16
    LibIntrinsics.popcount16(src)
  end

  def self.popcount32(src) : Int32
    LibIntrinsics.popcount32(src)
  end

  def self.popcount64(src) : Int64
    LibIntrinsics.popcount64(src)
  end

  def self.popcount128(src)
    LibIntrinsics.popcount128(src)
  end

  macro countleading8(src, zero_is_undef)
    ::LibIntrinsics.countleading8({{src}}, {{zero_is_undef}})
  end

  macro countleading16(src, zero_is_undef)
    ::LibIntrinsics.countleading16({{src}}, {{zero_is_undef}})
  end

  macro countleading32(src, zero_is_undef)
    ::LibIntrinsics.countleading32({{src}}, {{zero_is_undef}})
  end

  macro countleading64(src, zero_is_undef)
    ::LibIntrinsics.countleading64({{src}}, {{zero_is_undef}})
  end

  macro countleading128(src, zero_is_undef)
    ::LibIntrinsics.countleading128({{src}}, {{zero_is_undef}})
  end

  macro counttrailing8(src, zero_is_undef)
    ::LibIntrinsics.counttrailing8({{src}}, {{zero_is_undef}})
  end

  macro counttrailing16(src, zero_is_undef)
    ::LibIntrinsics.counttrailing16({{src}}, {{zero_is_undef}})
  end

  macro counttrailing32(src, zero_is_undef)
    ::LibIntrinsics.counttrailing32({{src}}, {{zero_is_undef}})
  end

  macro counttrailing64(src, zero_is_undef)
    ::LibIntrinsics.counttrailing64({{src}}, {{zero_is_undef}})
  end

  macro counttrailing128(src, zero_is_undef)
    ::LibIntrinsics.counttrailing128({{src}}, {{zero_is_undef}})
  end

  def self.fshl8(a, b, count) : UInt8
    LibIntrinsics.fshl8(a, b, count)
  end

  def self.fshl16(a, b, count) : UInt16
    LibIntrinsics.fshl16(a, b, count)
  end

  def self.fshl32(a, b, count) : UInt32
    LibIntrinsics.fshl32(a, b, count)
  end

  def self.fshl64(a, b, count) : UInt64
    LibIntrinsics.fshl64(a, b, count)
  end

  def self.fshl128(a, b, count) : UInt128
    LibIntrinsics.fshl128(a, b, count)
  end

  def self.fshr8(a, b, count) : UInt8
    LibIntrinsics.fshr8(a, b, count)
  end

  def self.fshr16(a, b, count) : UInt16
    LibIntrinsics.fshr16(a, b, count)
  end

  def self.fshr32(a, b, count) : UInt32
    LibIntrinsics.fshr32(a, b, count)
  end

  def self.fshr64(a, b, count) : UInt64
    LibIntrinsics.fshr64(a, b, count)
  end

  def self.fshr128(a, b, count) : UInt128
    LibIntrinsics.fshr128(a, b, count)
  end

  macro va_start(ap)
    ::LibIntrinsics.va_start({{ap}})
  end

  macro va_end(ap)
    ::LibIntrinsics.va_end({{ap}})
  end

  # Should codegen to the following LLVM IR (before being inlined):
  # ```
  # define internal void @"*Intrinsics::unreachable:NoReturn"() #12 {
  # entry:
  #   unreachable
  # }
  # ```
  #
  # Can be used like `@llvm.assume(i1 cond)` as `unreachable unless (assumption)`.
  #
  # WARNING: the behaviour of the program is undefined if the assumption is broken!
  @[AlwaysInline]
  def self.unreachable : NoReturn
    x = uninitialized NoReturn
    x
  end
end

# Invokes an execution trap to catch the attention of a debugger. This has the
# same effect as placing a breakpoint in debuggers or IDEs supporting them.
#
# Execution is allowed to continue if the debugger instructs so. If no debugger
# is attached, usually the current process terminates with a status that
# corresponds to `Process::ExitReason::Breakpoint`.
#
# Inside an interpreter session, this drops into the REPL's pry mode instead of
# a system debugger.
macro debugger
  ::Intrinsics.debugtrap
end
