/// Compiled function lifecycle support owned by Wasmoon JIT.

///|
const STACKMAP_I32_MAX : Int = 0x7FFFFFFF

///|
/// Information about a GC safepoint in generated code.
pub struct GcSafepoint {
  /// Offset in generated machine code where this safepoint is.
  code_offset : Int
  /// Which virtual registers contain live GC references at this point.
  live_refs : Array[Int]
  /// Additional metadata, such as return address for stack unwinding.
  metadata : Int64
} derive(Debug)

///|
pub fn GcSafepoint::GcSafepoint(
  code_offset : Int,
  live_refs : Array[Int],
  metadata : Int64,
) -> GcSafepoint {
  { code_offset, live_refs, metadata }
}

///|
fn compiled_append_u32_le(buf : Array[Byte], value : Int) -> Unit {
  buf.push((value & 0xFF).to_byte())
  buf.push(((value >> 8) & 0xFF).to_byte())
  buf.push(((value >> 16) & 0xFF).to_byte())
  buf.push(((value >> 24) & 0xFF).to_byte())
}

///|
fn safepoint_metadata_to_root_count(metadata : Int64) -> Int {
  if metadata <= 0L {
    return 0
  }
  if metadata > STACKMAP_I32_MAX.to_int64() {
    STACKMAP_I32_MAX
  } else {
    metadata.to_int()
  }
}

///|
fn compiled_encode_stackmap_root_count(root_count : Int) -> Int {
  if root_count <= 0 {
    0
  } else if root_count > STACKMAP_I32_MAX {
    STACKMAP_I32_MAX
  } else {
    root_count
  }
}

///|
/// Build stackmap blob v2 with optional per-safepoint root-index lists.
/// Format v2:
///   u32 version (=2)
///   u32 safepoint_count
///   repeated safepoint_count times:
///     u32 root_count
///     u32 root_index_count
///     u32 root_indices[root_index_count]
fn compiled_build_gc_stackmap_blob_v2(
  safepoints : Array[GcSafepoint],
  default_root_counts : Array[Int],
) -> Bytes {
  if safepoints.length() == 0 {
    return b""
  }
  let bytes : Array[Byte] = []
  compiled_append_u32_le(bytes, 2)
  compiled_append_u32_le(bytes, safepoints.length())
  for i, safepoint in safepoints {
    let default_root_count = if i < default_root_counts.length() {
      default_root_counts[i]
    } else {
      safepoint.live_refs.length()
    }
    let metadata_root_count = safepoint_metadata_to_root_count(
      safepoint.metadata,
    )
    let mut root_count = compiled_encode_stackmap_root_count(
      if metadata_root_count > 0 {
        metadata_root_count
      } else {
        default_root_count
      },
    )
    let valid_indices : Array[Int] = []
    for live_ref in safepoint.live_refs {
      if live_ref >= 0 && live_ref <= STACKMAP_I32_MAX {
        valid_indices.push(live_ref)
      }
    }
    if valid_indices.length() > 0 {
      if root_count <= 0 || root_count > valid_indices.length() {
        root_count = valid_indices.length()
      }
      compiled_append_u32_le(bytes, root_count)
      compiled_append_u32_le(bytes, root_count)
      for idx in 0..