// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Copyright 2023 WebAssembly Community Group participants.
// Retained historical upstream notice; current derivation remains unconfirmed.
// Conservative attribution from pinned Binaryen version_130 counterpart:
// Copyright 2017 WebAssembly Community Group participants
// Counterpart: src/passes/AvoidReinterprets.cpp (5d704ad5); origin remains unconfirmed.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.

///|
pub fn avoid_reinterprets_summary() -> String {
  "Replace full-width numeric load plus reinterpret pairs with equivalent typed loads."
}

///|
fn ar_rewrite_load_reinterpret_pair(
  load : @lib.Instruction,
  reinterpret : @lib.Instruction,
) -> @lib.Instruction? {
  match (load, reinterpret) {
    (@lib.F32Load(memarg), @lib.I32ReinterpretF32) =>
      Some(@lib.Instruction::i32_load(memarg))
    (@lib.F64Load(memarg), @lib.I64ReinterpretF64) =>
      Some(@lib.Instruction::i64_load(memarg))
    (@lib.I32Load(memarg), @lib.F32ReinterpretI32) =>
      Some(@lib.Instruction::f32_load(memarg))
    (@lib.I64Load(memarg), @lib.F64ReinterpretI64) =>
      Some(@lib.Instruction::f64_load(memarg))
    _ => None
  }
}

///|
fn ar_rewrite_instr(instr : @lib.Instruction) -> (@lib.Instruction, Bool) {
  pass_rewrite_structured_instr(instr, ar_rewrite_instrs)
}

///|
fn ar_rewrite_instrs(
  instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
  let mut out : Array[@lib.Instruction]? = None
  let mut i = 0
  while i < instrs.length() {
    let (current, current_changed) = ar_rewrite_instr(instrs[i])
    let mut replacement : @lib.Instruction? = None
    if i + 1 < instrs.length() {
      match ar_rewrite_load_reinterpret_pair(current, instrs[i + 1]) {
        Some(next) => replacement = Some(next)
        None => ()
      }
    }
    if current_changed || replacement is Some(_) {
      let buffer = match out {
        Some(buffer) => buffer
        None => {
          let buffer : Array[@lib.Instruction] = []
          for prefix = 0; prefix < i; prefix = prefix + 1 {
            buffer.push(instrs[prefix])
          }
          out = Some(buffer)
          buffer
        }
      }
      buffer.push(
        match replacement {
          Some(next) => next
          None => current
        },
      )
    } else {
      match out {
        Some(buffer) => buffer.push(current)
        None => ()
      }
    }
    if replacement is Some(_) {
      i += 2
    } else {
      i += 1
    }
  }
  match out {
    Some(buffer) => (buffer, true)
    None => (instrs, false)
  }
}

///|
pub fn avoid_reinterprets_run_module_pass(mod_ : @lib.Module) -> @lib.Module {
  if pass_module_has_offset_sensitive_metadata(mod_) {
    return mod_
  }
  let funcs = match mod_.code_sec {
    Some(@lib.CodeSec(funcs)) => funcs
    None => return mod_
  }
  let rewritten : Array[@lib.Func] = []
  let mut changed = false
  for func in funcs {
    let @lib.Func(locals, @lib.Expr(body)) = func
    let (next_body, func_changed) = ar_rewrite_instrs(body)
    if func_changed {
      changed = true
      rewritten.push(@lib.Func::new(locals, @lib.Expr::new(next_body)))
    } else {
      rewritten.push(func)
    }
  }
  if changed {
    mod_.with_code_sec(@lib.CodeSec::new(rewritten))
  } else {
    mod_
  }
}