// Note: emit_bounds_check and emit_load_memory_base removed
// Memory bounds checking is now done at IR translation time via FuncEnvironment

///|
fn context_slot_offset(ctx : @lower.LoweringContext, slot_id : Int) -> Int {
  let layout = ctx.embedding_context_layout()
  match layout.slot_offset(slot_id) {
    Some(offset) => offset
    None => abort("missing Wasm context slot \{slot_id}")
  }
}

///|
pub fn load_context_slot_pointer(
  ctx : @lower.LoweringContext,
  block : @block.Block,
  slot_id : Int,
  base : @abi.Reg,
) -> @abi.VReg {
  let cache_key = context_slot_cache_key(slot_id)
  if ctx.cached_context_pointer(cache_key) is Some(vreg) {
    return vreg
  }
  let result = ctx.machv_function().new_vreg(Int)
  let load_inst = @instr.Inst(Load(I64, context_slot_offset(ctx, slot_id)))
  load_inst.add_def({ reg: Virtual(result) })
  load_inst.add_use(base)
  block.add_inst(load_inst)
  ctx.cache_context_pointer(cache_key, result)
  result
}

///|
pub fn load_context_slot_pointer_from_pinned_context(
  ctx : @lower.LoweringContext,
  block : @block.Block,
  slot_id : Int,
) -> @abi.VReg {
  load_context_slot_pointer(
    ctx,
    block,
    slot_id,
    Physical({ index: @lower.context_index(ctx), class: Int }),
  )
}

///|
/// Emit instruction to load func_table from the Wasm context on-demand.
/// Returns a virtual register containing func_table pointer.
fn emit_load_func_table(
  ctx : @lower.LoweringContext,
  block : @block.Block,
  context_slots : WasmContextSlots,
) -> @abi.VReg {
  load_context_slot_pointer_from_pinned_context(
    ctx,
    block,
    context_slots.function_table,
  )
}

///|
/// Lower GetFuncRef - get tagged function pointer for storing in tables
/// Returns func_ptr | FUNCREF_TAG (bit 61) for ref.test detection
pub fn lower_get_func_ref(
  ctx : @lower.LoweringContext,
  inst : @milkir.Inst,
  block : @block.Block,
  func_idx : Int,
  context_slots : WasmContextSlots,
) -> Unit {
  guard inst.first_result() is Some(result) else { return }
  let result_vreg = ctx.get_vreg(result)

  // Step 1: Load func_table pointer from vmctx
  let func_table_vreg = emit_load_func_table(ctx, block, context_slots)

  // Step 2: Load func_ptr from func_table[func_idx * 8]
  let raw_func_ptr = ctx.machv_function().new_vreg(Int)
  let offset = func_idx * 8
  if @lower.is_valid_load_store_offset(ctx, offset.to_int64(), 8) {
    let load_ptr = @instr.Inst(Load(I64, offset))
    load_ptr.add_def({ reg: Virtual(raw_func_ptr) })
    load_ptr.add_use(Virtual(func_table_vreg))
    block.add_inst(load_ptr)
  } else {
    // Large func-table indices exceed AArch64 LDR unsigned-imm range.
    let offset_vreg = @lower.materialize_imm(ctx, block, offset.to_int64())
    let load_ptr = @instr.Inst(LoadPtrRegOffset(I64, None, 0, 0))
    load_ptr.add_def({ reg: Virtual(raw_func_ptr) })
    load_ptr.add_use(Virtual(func_table_vreg))
    load_ptr.add_use(Virtual(offset_vreg))
    block.add_inst(load_ptr)
  }

  // Step 3: OR with FUNCREF_TAG (0x2000000000000000)
  let tag_vreg = ctx.machv_function().new_vreg(Int)
  let load_tag = @instr.Inst(LoadConst(0x2000000000000000L))
  load_tag.add_def({ reg: Virtual(tag_vreg) })
  block.add_inst(load_tag)
  let or_inst = @instr.Inst(Or(true))
  or_inst.add_def({ reg: Virtual(result_vreg) })
  or_inst.add_use(Virtual(raw_func_ptr))
  or_inst.add_use(Virtual(tag_vreg))
  block.add_inst(or_inst)
}

// Note: lower_load, lower_store, lower_load_narrow, lower_store_narrow removed
// Memory bounds checking is now done at IR translation time via FuncEnvironment
// MachV lowering now uses LoadPtr/StorePtr/LoadPtrNarrow/StorePtrNarrow directly

///|
/// Lower memory-base access to generic pointer loads from the runtime context.
/// memidx: memory index for multi-memory support
pub fn lower_load_mem_base(
  ctx : @lower.LoweringContext,
  inst : @milkir.Inst,
  block : @block.Block,
  memidx : Int,
  context_slots : WasmContextSlots,
) -> Unit {
  guard inst.first_result() is Some(result) else { return }
  if inst.operands.length() != 1 {
    return
  }
  if ctx.cached_context_pointer(memidx) is Some(cached_vreg) {
    ctx.set_cached_vreg(result.id, cached_vreg)
    return
  }
  let result_vreg = ctx.get_vreg(result)
  let vmctx_reg : @abi.Reg = if ctx.embedding_abi().reserve_context_role {
    Physical(@lower.context_preg(ctx))
  } else {
    let vmctx_vreg = ctx.get_vreg_for_use(inst.operands[0], block)
    Virtual(vmctx_vreg)
  }
  if memidx == 0 {
    ctx.machv_function().mark_uses_context_cache_0_source()
    let cached_vreg = load_context_slot_pointer(
      ctx,
      block,
      context_slots.memory0_base,
      vmctx_reg,
    )
    ctx.set_cached_vreg(result.id, cached_vreg)
    ctx.cache_context_pointer(memidx, cached_vreg)
    return
  } else {
    let directory_vreg = ctx.machv_function().new_vreg(Int)
    let load_directory = @instr.Inst(
      Load(I64, context_slot_offset(ctx, context_slots.memory_directory)),
    )
    load_directory.add_def({ reg: Virtual(directory_vreg) })
    load_directory.add_use(vmctx_reg)
    block.add_inst(load_directory)

    let descriptor_vreg = ctx.machv_function().new_vreg(Int)
    let load_descriptor = @instr.Inst(
      Load(I64, memidx * context_slots.pointer_stride),
    )
    load_descriptor.add_def({ reg: Virtual(descriptor_vreg) })
    load_descriptor.add_use(Virtual(directory_vreg))
    block.add_inst(load_descriptor)

    let load_base = @instr.Inst(Load(I64, 0))
    load_base.add_def({ reg: Virtual(result_vreg) })
    load_base.add_use(Virtual(descriptor_vreg))
    block.add_inst(load_base)
  }
  ctx.cache_context_pointer(memidx, result_vreg)
}