///|
priv enum AllocationBundleState {
  BundlePending
  BundleReg(PhysicalReg)
  BundleSpill
  BundleSplit
}

///|
priv struct AllocationBundle {
  id : Int
  segments : Array[Int]
  mut priority : Int
  mut weight : Int
  mut has_fixed_constraint : Bool
  split_count : Int
  mut state : AllocationBundleState
}

///|
priv struct BundleQueueEntry {
  bundle : Int
  hint : PhysicalReg?
}

///|
priv struct BundleQueue {
  bundles : Array[AllocationBundle]
  items : Array[BundleQueueEntry]
  in_queue : Array[Bool]
  production_priority : Bool
}

///|
fn BundleQueue::new(
  bundles : Array[AllocationBundle],
  production_priority : Bool,
) -> BundleQueue {
  {
    bundles,
    items: [],
    in_queue: Array::make(bundles.length(), false),
    production_priority,
  }
}

///|
fn BundleQueue::ensure_slot(self : BundleQueue, bundle : Int) -> Unit {
  while bundle >= self.in_queue.length() {
    self.in_queue.push(false)
  }
}

///|
fn BundleQueue::higher(
  self : BundleQueue,
  lhs : BundleQueueEntry,
  rhs : BundleQueueEntry,
) -> Bool {
  let left = self.bundles[lhs.bundle]
  let right = self.bundles[rhs.bundle]
  if self.production_priority && left.priority != right.priority {
    left.priority > right.priority
  } else {
    left.id < right.id
  }
}

///|
fn BundleQueue::push(
  self : BundleQueue,
  bundle : Int,
  hint : PhysicalReg?,
) -> Unit {
  self.ensure_slot(bundle)
  if self.in_queue[bundle] {
    return
  }
  self.in_queue[bundle] = true
  self.items.push({ bundle, hint })
  let mut index = self.items.length() - 1
  while index > 0 {
    let parent = (index - 1) / 2
    if !self.higher(self.items[index], self.items[parent]) {
      break
    }
    let temporary = self.items[index]
    self.items[index] = self.items[parent]
    self.items[parent] = temporary
    index = parent
  }
}

///|
fn BundleQueue::pop(self : BundleQueue) -> BundleQueueEntry? {
  if self.items.is_empty() {
    return None
  }
  let first = self.items[0]
  self.in_queue[first.bundle] = false
  let last = self.items.pop().unwrap()
  if !self.items.is_empty() {
    self.items[0] = last
    let mut index = 0
    while true {
      let left = index * 2 + 1
      if left >= self.items.length() {
        break
      }
      let right = left + 1
      let mut higher = left
      if right < self.items.length() &&
        self.higher(self.items[right], self.items[left]) {
        higher = right
      }
      if !self.higher(self.items[higher], self.items[index]) {
        break
      }
      let temporary = self.items[index]
      self.items[index] = self.items[higher]
      self.items[higher] = temporary
      index = higher
    }
  }
  Some(first)
}

///|
priv struct BundleRegisterProbe {
  conflicts : Array[Int]
  conflict_cost : Int
  first_conflict : ProgramPoint?
  clobbered : Bool
}

///|
fn allocation_bundle_metrics(
  segment_ids : Array[Int],
  segments : Array[AllocationSegment],
) -> (Int, Int, Bool) {
  let mut priority = 0
  let mut weighted_cost = 0L
  let mut has_fixed_constraint = false
  for segment_id in segment_ids {
    let segment_priority = allocation_segment_priority(segments[segment_id])
    priority = priority + segment_priority
    weighted_cost = weighted_cost +
      segments[segment_id].weight.to_int64() * segment_priority.to_int64()
    has_fixed_constraint = has_fixed_constraint ||
      segments[segment_id].has_fixed_constraint
  }
  let normalized_priority = priority.max(1)
  let weight = ((weighted_cost + normalized_priority.to_int64() - 1L) /
    normalized_priority.to_int64())
    .min(2_147_483_647L)
    .to_int()
  let mut one_value = true
  let mut highest_active_cost = 0
  let mut second_active_cost = 0
  let mut saw_use = false
  let mut saw_gap_after_use = false
  let mut has_cold_gap = false
  let mut active_fragments = 0
  if !segment_ids.is_empty() {
    let first_value = segments[segment_ids[0]].value
    for segment_id in segment_ids {
      let segment = segments[segment_id]
      one_value = one_value && segment.value == first_value
      if segment.has_use {
        active_fragments = active_fragments + 1
        if segment.weight > highest_active_cost {
          second_active_cost = highest_active_cost
          highest_active_cost = segment.weight
        } else if segment.weight > second_active_cost {
          second_active_cost = segment.weight
        }
        has_cold_gap = has_cold_gap || saw_gap_after_use
        saw_use = true
      } else if saw_use {
        saw_gap_after_use = true
      }
    }
  }
  // Repeated active fragments of one value incur independent reload costs in
  // small CFG regions. Protect the two hottest fragments without letting long
  // bundles grow more expensive with every block. Coalesced SSA values,
  // two-fragment ranges, bundles spanning more than four fragments, and values
  // with a cold live-through gap retain density pricing so the allocator can
  // split cold regions away from a hot loop.
  let eviction_weight = if one_value &&
    !has_cold_gap &&
    segment_ids.length() >= 3 &&
    segment_ids.length() <= 4 &&
    active_fragments >= 2 {
    (highest_active_cost.to_int64() + second_active_cost.to_int64())
    .min(2_147_483_647L)
    .to_int()
    .max(1)
  } else {
    weight.max(1)
  }
  (normalized_priority, eviction_weight, has_fixed_constraint)
}

///|
fn allocation_bundle(
  id : Int,
  segment_ids : Array[Int],
  segments : Array[AllocationSegment],
  split_count? : Int = 0,
) -> AllocationBundle {
  let (priority, weight, has_fixed_constraint) = allocation_bundle_metrics(
    segment_ids, segments,
  )
  {
    id,
    segments: segment_ids.copy(),
    priority,
    weight,
    has_fixed_constraint,
    split_count,
    state: BundlePending,
  }
}

///|
/// Queue priority is live extent, while eviction uses local spill weight. The
/// two must remain distinct so a dense short bundle can evict a sparse long
/// bundle that was deliberately processed first.
fn allocation_segment_priority(segment : AllocationSegment) -> Int {
  if segment.range.start.block == segment.range.end.block {
    (segment.range.end.inst - segment.range.start.inst).abs() + 1
  } else {
    1
  }
}

///|
fn build_allocation_bundles(
  plan : ProductionBundlePlan,
  segments : Array[AllocationSegment],
) -> (Array[AllocationBundle], Array[Int]) {
  let bundles : Array[AllocationBundle] = []
  let segment_owner = Array::make(segments.length(), -1)
  for segment_ids in plan.bundle_segments {
    let bundle = allocation_bundle(bundles.length(), segment_ids, segments)
    bundles.push(bundle)
    for segment_id in segment_ids {
      segment_owner[segment_id] = bundle.id
    }
  }
  (bundles, segment_owner)
}

///|
fn bundle_class(
  bundle : AllocationBundle,
  segments : Array[AllocationSegment],
) -> RegClass {
  segments[bundle.segments[0]].value.class
}

///|
fn first_clobber_point(
  segment : AllocationSegment,
  reg : PhysicalReg,
  clobbers : RegisterClobberIndex,
) -> ProgramPoint? {
  clobbers.query(reg, segment.range).0
}

///|
fn earlier_conflict_point(
  current : ProgramPoint?,
  candidate : ProgramPoint,
  block_order : Array[Int],
) -> ProgramPoint {
  match current {
    Some(point) =>
      if candidate.compare_with_order(point, block_order) < 0 {
        candidate
      } else {
        point
      }
    None => candidate
  }
}

///|
fn probe_bundle_register(
  bundle : AllocationBundle,
  register : Int,
  reg : PhysicalReg,
  bundles : Array[AllocationBundle],
  segments : Array[AllocationSegment],
  segment_owner : Array[Int],
  occupied : Array[Array[Int]],
  clobbers : RegisterClobberIndex,
  block_order : Array[Int],
) -> BundleRegisterProbe {
  let conflicts : Array[Int] = []
  let mut conflict_cost = 0
  let mut first_conflict : ProgramPoint? = None
  let mut clobbered = false
  for segment_id in bundle.segments {
    let segment = segments[segment_id]
    if first_clobber_point(segment, reg, clobbers) is Some(point) {
      clobbered = true
      first_conflict = Some(
        earlier_conflict_point(first_conflict, point, block_order),
      )
    }
    for
      conflict_segment in conflicting_segments(
        segments,
        occupied[register],
        segment,
        block_order,
      ) {
      let owner = segment_owner[conflict_segment]
      if owner == bundle.id || conflicts.contains(owner) {
        continue
      }
      conflicts.push(owner)
      conflict_cost = conflict_cost.max(bundles[owner].weight)
      let point = later_point(
        segment.range.start,
        segments[conflict_segment].range.start,
        block_order,
      )
      first_conflict = Some(
        earlier_conflict_point(first_conflict, point, block_order),
      )
    }
  }
  { conflicts, conflict_cost, first_conflict, clobbered }
}

///|
fn push_bundle_register_candidates(
  order : Array[Int],
  registers : Array[PhysicalReg],
  candidate : PhysicalReg?,
) -> Unit {
  if candidate is Some(reg) {
    push_register_candidate(order, register_index(registers, reg))
  }
}

///|
fn bundle_register_order(
  bundle : AllocationBundle,
  hint : PhysicalReg?,
  segments : Array[AllocationSegment],
  ranges : LiveRangeSet,
  environment : MachineEnv,
  preferred_by_value : Array[PhysicalReg?],
  edge_affinities : Array[Array[Int]],
  is_edge_argument : Array[Bool],
  edge_target_registers : Array[Bool],
) -> Array[Int] {
  let order : Array[Int] = []
  push_bundle_register_candidates(order, environment.allocatable_regs, hint)
  for segment_id in bundle.segments {
    let segment = segments[segment_id]
    push_bundle_register_candidates(
      order,
      environment.allocatable_regs,
      segment_fixed_hint(ranges, segment),
    )
    push_bundle_register_candidates(
      order,
      environment.allocatable_regs,
      segment_preference_hint(ranges, segment, environment.allocatable_regs),
    )
    push_bundle_register_candidates(
      order,
      environment.allocatable_regs,
      preferred_by_value[segment.value.id],
    )
    for related in edge_affinities[segment.value.id] {
      push_bundle_register_candidates(
        order,
        environment.allocatable_regs,
        preferred_by_value[related],
      )
    }
  }
  let mut has_edge_argument = false
  for segment_id in bundle.segments {
    has_edge_argument = has_edge_argument ||
      is_edge_argument[segments[segment_id].value.id]
  }
  if has_edge_argument {
    for register in 0.. Unit {
  guard bundle.state is BundleReg(reg) else { return }
  let register = register_index(environment.allocatable_regs, reg).unwrap()
  for segment_id in bundle.segments {
    remove_occupied_segment(occupied, register, segment_id)
    segments[segment_id].location = None
  }
  bundle.state = BundlePending
}

///|
fn bundle_can_evict(
  enable_eviction : Bool,
  bundle : AllocationBundle,
  probe : BundleRegisterProbe,
) -> Bool {
  enable_eviction &&
  !probe.clobbered &&
  !probe.conflicts.is_empty() &&
  probe.conflict_cost < bundle.weight
}

///|
fn evict_bundle_conflicts(
  conflicts : Array[Int],
  bundles : Array[AllocationBundle],
  environment : MachineEnv,
  segments : Array[AllocationSegment],
  occupied : Array[Array[Int]],
  queue : BundleQueue,
) -> Unit {
  for conflict in conflicts {
    let evicted = bundles[conflict]
    remove_bundle_allocation(evicted, environment, segments, occupied)
    queue.push(evicted.id, None)
  }
}

///|
fn record_bundle_allocation(
  bundle : AllocationBundle,
  register : Int,
  environment : MachineEnv,
  segments : Array[AllocationSegment],
  occupied : Array[Array[Int]],
  block_order : Array[Int],
  preferred_by_value : Array[PhysicalReg?],
  is_edge_target : Array[Bool],
  edge_target_registers : Array[Bool],
) -> Unit {
  let reg = environment.allocatable_regs[register]
  for segment_id in bundle.segments {
    let segment = segments[segment_id]
    segment.location = Some(SegmentReg(reg))
    preferred_by_value[segment.value.id] = Some(reg)
    if is_edge_target[segment.value.id] {
      edge_target_registers[register] = true
    }
    insert_occupied_segment(
      segments,
      occupied[register],
      segment_id,
      block_order,
    )
  }
  bundle.state = BundleReg(reg)
}

///|
fn force_spill_bundle(
  bundle : AllocationBundle,
  segments : Array[AllocationSegment],
) -> Unit {
  for segment_id in bundle.segments {
    segments[segment_id].location = Some(SegmentSpill)
  }
  bundle.state = BundleSpill
}

///|
fn enqueue_child_bundle(
  bundles : Array[AllocationBundle],
  segment_owner : Array[Int],
  queue : BundleQueue,
  segments : Array[AllocationSegment],
  segment_ids : Array[Int],
  hint : PhysicalReg?,
  split_count : Int,
) -> Unit {
  let child = allocation_bundle(
    bundles.length(),
    segment_ids,
    segments,
    split_count~,
  )
  bundles.push(child)
  for segment_id in segment_ids {
    segment_owner[segment_id] = child.id
  }
  queue.push(child.id, hint)
}

///|
fn split_bundle_at_conflict(
  bundle : AllocationBundle,
  split_point : ProgramPoint?,
  hint : PhysicalReg?,
  bundles : Array[AllocationBundle],
  segment_owner : Array[Int],
  queue : BundleQueue,
  segments : Array[AllocationSegment],
  block_order : Array[Int],
) -> Unit {
  if bundle.segments.length() <= 1 {
    force_spill_bundle(bundle, segments)
    return
  }
  let before : Array[Int] = []
  let after : Array[Int] = []
  if split_point is Some(point) {
    for segment_id in bundle.segments {
      if segments[segment_id].range.end.compare_with_order(point, block_order) <=
        0 {
        before.push(segment_id)
      } else {
        after.push(segment_id)
      }
    }
  }
  bundle.state = BundleSplit
  let child_split_count = bundle.split_count + 1
  if bundle.split_count < 2 && !before.is_empty() && !after.is_empty() {
    enqueue_child_bundle(
      bundles, segment_owner, queue, segments, before, hint, child_split_count,
    )
    enqueue_child_bundle(
      bundles, segment_owner, queue, segments, after, hint, child_split_count,
    )
    return
  }
  let mut first = true
  for segment_id in bundle.segments {
    enqueue_child_bundle(
      bundles,
      segment_owner,
      queue,
      segments,
      [segment_id],
      if first {
        hint
      } else {
        None
      },
      child_split_count,
    )
    first = false
  }
}

///|
fn hoist_split_to_loop_entry(
  bundle : AllocationBundle,
  split_point : ProgramPoint?,
  segments : Array[AllocationSegment],
  loop_depths : Array[Int],
  block_order : Array[Int],
) -> ProgramPoint? {
  guard split_point is Some(point) &&
    point.block >= 0 &&
    point.block < loop_depths.length() &&
    !bundle.segments.is_empty() else {
    return split_point
  }
  let mut start = segments[bundle.segments[0]].range.start
  for segment_id in bundle.segments {
    let candidate = segments[segment_id].range.start
    if candidate.compare_with_order(start, block_order) < 0 {
      start = candidate
    }
  }
  guard start.block >= 0 &&
    start.block < loop_depths.length() &&
    loop_depths[point.block] > loop_depths[start.block] else {
    return split_point
  }
  let start_depth = loop_depths[start.block]
  for block in (start.block + 1)..<=point.block {
    if block >= 0 &&
      block < loop_depths.length() &&
      loop_depths[block] > start_depth {
      return Some(ProgramPoint(block, -1))
    }
  }
  split_point
}

///|
fn should_take_bundle_split(
  best_cost : Int?,
  best_point : ProgramPoint?,
  candidate_cost : Int,
  candidate_point : ProgramPoint,
  block_order : Array[Int],
) -> Bool {
  match (best_cost, best_point) {
    (None, None) => true
    (Some(cost), Some(point)) =>
      candidate_cost < cost ||
      (
        candidate_cost == cost &&
        candidate_point.compare_with_order(point, block_order) > 0
      )
    _ => false
  }
}

///|
/// Check that allocation left every bundle resolved and every segment owned.
///
/// Like `ProductionBundlePlan::validate`, a violation here is a defect in this
/// allocator rather than bad input, and it reports through
/// `VerifyError::InvalidPlan` so the caller can see which invariant broke.
fn validate_bundle_allocations(
  bundles : Array[AllocationBundle],
  segments : Array[AllocationSegment],
  segment_owner : Array[Int],
) -> Unit raise VerifyError {
  for bundle in bundles {
    if bundle.state is BundlePending {
      raise InvalidPlan(message="pending allocation bundle after allocation")
    }
    let (priority, weight, has_fixed_constraint) = allocation_bundle_metrics(
      bundle.segments,
      segments,
    )
    if bundle.priority != priority {
      raise InvalidPlan(
        message="allocation bundle lost queue priority: recorded \{bundle.priority}, recomputed \{priority}",
      )
    }
    if bundle.weight != weight {
      raise InvalidPlan(
        message="allocation bundle lost use weight: recorded \{bundle.weight}, recomputed \{weight}",
      )
    }
    if has_fixed_constraint != bundle.has_fixed_constraint {
      raise InvalidPlan(
        message="allocation bundle lost fixed constraints: recorded \{bundle.has_fixed_constraint}, recomputed \{has_fixed_constraint}",
      )
    }
    if bundle.state is BundleSplit {
      continue
    }
  }
  for segment in segments {
    let owner = segment_owner[segment.id]
    if owner < 0 ||
      owner >= bundles.length() ||
      !bundles[owner].segments.contains(segment.id) {
      raise InvalidPlan(
        message="invalid allocation bundle ownership for segment \{segment.id}: owner \{owner}",
      )
    }
    match (bundles[owner].state, segment.location) {
      (BundleReg(expected), Some(SegmentReg(actual))) if expected == actual =>
        ()
      (BundleSpill, Some(SegmentSpill)) => ()
      _ =>
        raise InvalidPlan(
          message="incomplete atomic bundle allocation for segment \{segment.id} in bundle \{owner}",
        )
    }
  }
}

///|
fn[F : FunctionView] bridge_predecessors(function : F) -> Array[Array[Int]] {
  let predecessors : Array[Array[Int]] = Array::makei(function.block_count(), _ => {
    []
  })
  for block in 0..= 0 && successor < predecessors.length() {
        predecessors[successor].push(block)
      }
    }
  }
  predecessors
}

///|
fn bridge_register_candidate(
  segment : AllocationSegment,
  predecessors : Array[Array[Int]],
  segments : Array[AllocationSegment],
  segments_by_value : Array[Array[Int]],
) -> PhysicalReg? {
  let block = segment.range.start.block
  if block < 0 ||
    block >= predecessors.length() ||
    predecessors[block].is_empty() ||
    segment.range.start.inst != -1 {
    return None
  }
  let mut candidate : PhysicalReg? = None
  for predecessor in predecessors[block] {
    guard segment_in_block(
        segments,
        segments_by_value,
        segment.value,
        predecessor,
      )
      is Some(predecessor_segment) else {
      return None
    }
    guard segments[predecessor_segment].location is Some(SegmentReg(reg)) else {
      return None
    }
    if segments[predecessor_segment].range.start.inst < 0 &&
      !segments[predecessor_segment].defer_home {
      let mut stable_home = true
      for related in segments_by_value[segment.value.id] {
        match segments[related].location {
          Some(SegmentReg(related_reg)) =>
            if related_reg != reg {
              stable_home = false
              break
            }
          _ => {
            stable_home = false
            break
          }
        }
      }
      if !stable_home {
        return None
      }
    }
    match candidate {
      None => candidate = Some(reg)
      Some(previous) => if previous != reg { return None }
    }
  }
  candidate
}

///|
fn refresh_allocation_bundle(
  bundle : AllocationBundle,
  segments : Array[AllocationSegment],
) -> Unit {
  if bundle.segments.is_empty() {
    bundle.priority = 1
    bundle.weight = 1
    bundle.has_fixed_constraint = false
    bundle.state = BundleSplit
    return
  }
  let (priority, weight, has_fixed_constraint) = allocation_bundle_metrics(
    bundle.segments,
    segments,
  )
  bundle.priority = priority
  bundle.weight = weight
  bundle.has_fixed_constraint = has_fixed_constraint
}

///|
fn[F : FunctionView] segment_reaches_bridge_edge(
  function : F,
  segment : AllocationSegment,
) -> Bool {
  let block = segment.range.start.block
  let instructions = function.block_instructions(block)
  if segment.range.end.inst == instructions.length() * 2 {
    return true
  }
  if instructions.is_empty() ||
    segment.range.end.inst != (instructions.length() - 1) * 2 {
    return false
  }
  for successor in 0.. Bool {
  let block = segment.range.start.block
  for successor in 0.. Unit {
  if segment.location is Some(SegmentReg(previous)) &&
    register_index(environment.allocatable_regs, previous) is Some(register) {
    remove_occupied_segment(occupied, register, segment.id)
  }
  let previous_bundle = bundles[segment_owner[segment.id]]
  let mut index = 0
  while index < previous_bundle.segments.length() {
    if previous_bundle.segments[index] == segment.id {
      previous_bundle.segments.remove(index) |> ignore
    } else {
      index = index + 1
    }
  }
  refresh_allocation_bundle(previous_bundle, segments)
}

///|
fn assign_bridge_segment(
  segment : AllocationSegment,
  reg : PhysicalReg,
  bundles : Array[AllocationBundle],
  segments : Array[AllocationSegment],
  segment_owner : Array[Int],
  occupied : Array[Array[Int]],
  environment : MachineEnv,
  block_order : Array[Int],
) -> Unit {
  let previous_owner = segment_owner[segment.id]
  let promoted = allocation_bundle(
    bundles.length(),
    [segment.id],
    segments,
    split_count=bundles[previous_owner].split_count + 1,
  )
  promoted.state = BundleReg(reg)
  bundles.push(promoted)
  segment_owner[segment.id] = promoted.id
  segment.location = Some(SegmentReg(reg))
  let register = register_index(environment.allocatable_regs, reg).unwrap()
  insert_occupied_segment(segments, occupied[register], segment.id, block_order)
}

///|
fn assign_spilled_bridge_segment(
  segment : AllocationSegment,
  bundles : Array[AllocationBundle],
  segments : Array[AllocationSegment],
  segment_owner : Array[Int],
) -> Unit {
  let previous_owner = segment_owner[segment.id]
  let spilled = allocation_bundle(
    bundles.length(),
    [segment.id],
    segments,
    split_count=bundles[previous_owner].split_count + 1,
  )
  spilled.state = BundleSpill
  bundles.push(spilled)
  segment_owner[segment.id] = spilled.id
  segment.location = Some(SegmentSpill)
}

///|
fn[F : FunctionView] coalesce_bridge_segments(
  function : F,
  environment : MachineEnv,
  bundles : Array[AllocationBundle],
  segments : Array[AllocationSegment],
  segments_by_value : Array[Array[Int]],
  segment_owner : Array[Int],
  occupied : Array[Array[Int]],
  clobbers : RegisterClobberIndex,
  block_order : Array[Int],
) -> Unit {
  // Deferred homes are currently limited to call-free functions, where no
  // safepoint or call clobber can require the canonical stack-map home.
  for block in 0.. (Array[AllocationBundle], Array[Int]) raise VerifyError {
  let enable_eviction = strategy is Backtracking
  let (bundles, segment_owner) = build_allocation_bundles(bundle_plan, segments)
  let occupied : Array[Array[Int]] = []
  for _ in environment.allocatable_regs {
    occupied.push([])
  }
  let clobbers = build_register_clobber_index(function, ranges.block_order)
  let preferred_by_value : Array[PhysicalReg?] = Array::make(
    function.value_count(),
    None,
  )
  let (edge_affinities, is_edge_argument, is_edge_target) = build_edge_affinities(
    function,
  )
  let edge_target_registers = Array::make(
    environment.allocatable_regs.length(),
    false,
  )
  let queue = BundleQueue::new(bundles, enable_eviction)
  for bundle in bundles {
    queue.push(bundle.id, None)
  }
  while queue.pop() is Some(entry) {
    let bundle = bundles[entry.bundle]
    if !(bundle.state is BundlePending) {
      continue
    }
    let register_order = bundle_register_order(
      bundle,
      entry.hint,
      segments,
      ranges,
      environment,
      preferred_by_value,
      edge_affinities,
      is_edge_argument,
      edge_target_registers,
    )
    let mut free_register = -1
    let mut eviction_register = -1
    let eviction_conflicts : Array[Int] = []
    let mut eviction_cost : Int? = None
    let mut split_register = -1
    let mut split_cost : Int? = None
    let mut split_point : ProgramPoint? = None
    for register in register_order {
      let reg = environment.allocatable_regs[register]
      if reg.class != bundle_class(bundle, segments) {
        continue
      }
      let probe = probe_bundle_register(
        bundle,
        register,
        reg,
        bundles,
        segments,
        segment_owner,
        occupied,
        clobbers,
        ranges.block_order,
      )
      if !probe.clobbered && probe.conflicts.is_empty() {
        free_register = register
        break
      }
      if bundle_can_evict(enable_eviction, bundle, probe) &&
        (eviction_cost is None || probe.conflict_cost < eviction_cost.unwrap()) {
        eviction_register = register
        eviction_cost = Some(probe.conflict_cost)
        eviction_conflicts.clear()
        for conflict in probe.conflicts {
          eviction_conflicts.push(conflict)
        }
      }
      if probe.first_conflict is Some(point) &&
        should_take_bundle_split(
          split_cost,
          split_point,
          probe.conflict_cost,
          point,
          ranges.block_order,
        ) {
        split_register = register
        split_cost = Some(probe.conflict_cost)
        split_point = Some(point)
      }
    }
    if free_register >= 0 {
      record_bundle_allocation(
        bundle,
        free_register,
        environment,
        segments,
        occupied,
        ranges.block_order,
        preferred_by_value,
        is_edge_target,
        edge_target_registers,
      )
      continue
    }
    if eviction_register >= 0 {
      evict_bundle_conflicts(
        eviction_conflicts, bundles, environment, segments, occupied, queue,
      )
      record_bundle_allocation(
        bundle,
        eviction_register,
        environment,
        segments,
        occupied,
        ranges.block_order,
        preferred_by_value,
        is_edge_target,
        edge_target_registers,
      )
      continue
    }
    let split_hint = if split_register >= 0 {
      Some(environment.allocatable_regs[split_register])
    } else {
      None
    }
    split_bundle_at_conflict(
      bundle,
      hoist_split_to_loop_entry(
        bundle,
        split_point,
        segments,
        loop_depths,
        ranges.block_order,
      ),
      split_hint,
      bundles,
      segment_owner,
      queue,
      segments,
      ranges.block_order,
    )
  }
  let segments_by_value : Array[Array[Int]] = Array::makei(
    function.value_count(),
    _ => [],
  )
  for segment in segments {
    segments_by_value[segment.value.id].push(segment.id)
  }
  coalesce_bridge_segments(
    function,
    environment,
    bundles,
    segments,
    segments_by_value,
    segment_owner,
    occupied,
    clobbers,
    ranges.block_order,
  )
  validate_bundle_allocations(bundles, segments, segment_owner)
  (bundles, segment_owner)
}