///|
pub(all) enum NativeTarget {
  X64
  AArch64
} derive(Eq, Debug)

///|
priv struct JitFuncAddrFixup {
  offset : Int
  target : @code_object.RelocationTarget
  reg : Int
} derive(Debug)

///|
priv struct JitCallFixup {
  offset : Int
  target : @code_object.RelocationTarget
  veneer_offset : Int
} derive(Debug)

///|
pub struct JitCodeObject {
  priv code : Array[Int]
  priv frame_size : Int
  priv func_addr_fixups : Array[JitFuncAddrFixup]
  priv call_fixups : Array[JitCallFixup]
  priv gc_safepoints : Array[GcSafepoint]
} derive(Debug)

///|
fn JitCodeObject::new(
  code : Array[Int],
  frame_size : Int,
  func_addr_fixups? : Array[JitFuncAddrFixup] = [],
  call_fixups? : Array[JitCallFixup] = [],
  gc_safepoints? : Array[GcSafepoint] = [],
) -> JitCodeObject {
  {
    code: code.copy(),
    frame_size,
    func_addr_fixups: func_addr_fixups.copy(),
    call_fixups: call_fixups.copy(),
    gc_safepoints: gc_safepoints.copy(),
  }
}

///|
pub fn JitCodeObject::get_bytes(self : JitCodeObject) -> Array[Int] {
  self.code.copy()
}

///|
fn code_object_gc_safepoints(
  object : @code_object.UnlinkedCodeObject,
) -> Array[GcSafepoint] {
  object
  .safepoints()
  .map(site => {
    let root_count = match site.stack_map {
      Some(stack_map) => stack_map.argument_root_count
      None => 0
    }
    GcSafepoint(
      site.offset,
      Array::makei(root_count, index => index),
      root_count.to_int64(),
    )
  })
}

///|
fn jit_code_object_from_x64(
  object : @code_object.UnlinkedCodeObject,
  frame_size : Int,
) -> JitCodeObject raise JitPipelineError {
  let plan = @x64_target.prepare_x64_link(object) catch {
    error => raise X64LinkPreparationFailed(cause=error)
  }
  let addresses : Array[JitFuncAddrFixup] = []
  let calls : Array[JitCallFixup] = []
  let veneer_offsets = plan.veneer_offsets()
  for index, relocation in object.relocations() {
    if relocation.addend != 0L {
      raise InvalidJitRelocation(
        target=X64,
        index~,
        message="x64 JIT relocation \{index} has a non-zero addend",
      )
    }
    match relocation.kind {
      X64PcRelative32 => {
        if relocation.offset < 1 {
          raise InvalidJitRelocation(
            target=X64,
            index~,
            message="x64 rel32 relocation has invalid offset",
          )
        }
        calls.push({
          offset: relocation.offset - 1,
          target: relocation.target,
          veneer_offset: veneer_offsets[index],
        })
      }
      Absolute64 => {
        if relocation.offset < 2 {
          raise InvalidJitRelocation(
            target=X64,
            index~,
            message="x64 absolute relocation has invalid offset",
          )
        }
        addresses.push({
          offset: relocation.offset - 2,
          target: relocation.target,
          reg: 0,
        })
      }
      AArch64Call26 | AArch64Jump26 | AArch64Page21 | AArch64PageOffset12 =>
        raise InvalidJitRelocation(
          target=X64,
          index~,
          message="x64 code object contains AArch64 relocation \{index}",
        )
    }
  }
  JitCodeObject::new(
    plan.code().map(byte => byte.to_int()),
    frame_size,
    func_addr_fixups=addresses,
    call_fixups=calls,
    gc_safepoints=code_object_gc_safepoints(object),
  )
}

///|
fn jit_code_object_from_aarch64(
  object : @code_object.UnlinkedCodeObject,
  frame_size : Int,
) -> JitCodeObject raise JitPipelineError {
  let plan = @aarch64_target.prepare_aarch64_link(object) catch {
    error => raise AArch64LinkPreparationFailed(cause=error)
  }
  let calls : Array[JitCallFixup] = []
  for index, relocation in object.relocations() {
    if relocation.addend != 0L {
      raise InvalidJitRelocation(
        target=AArch64,
        index~,
        message="AArch64 JIT relocation \{index} has a non-zero addend",
      )
    }
    match relocation.kind {
      AArch64Call26 | AArch64Jump26 =>
        calls.push({
          offset: relocation.offset,
          target: relocation.target,
          veneer_offset: plan.veneer_offsets()[index],
        })
      AArch64Page21 | AArch64PageOffset12 | Absolute64 | X64PcRelative32 =>
        raise InvalidJitRelocation(
          target=AArch64,
          index~,
          message="unsupported AArch64 JIT relocation \{index}",
        )
    }
  }
  JitCodeObject::new(
    plan.code().map(byte => byte.to_int()),
    frame_size,
    call_fixups=calls,
    gc_safepoints=code_object_gc_safepoints(object),
  )
}