// 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/Untee.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 untee_summary() -> String {
  "Expand local.tee operations into explicit local.set plus local.get pairs."
}

///|
fn untee_rewrite_instr(
  instr : @lib.Instruction,
  preserve_initialization : Array[Bool],
) -> (@lib.Instruction, Bool) {
  pass_rewrite_structured_instr(instr, fn(body) {
    untee_rewrite_instrs(body, preserve_initialization)
  })
}

///|
fn untee_rewrite_instrs(
  instrs : Array[@lib.Instruction],
  preserve_initialization : Array[Bool],
) -> (Array[@lib.Instruction], Bool) {
  let mut out : Array[@lib.Instruction]? = None
  for idx in 0.. {
        let target = match out {
          Some(existing) => existing
          None => {
            let prefix : Array[@lib.Instruction] = []
            for prior in 0..= 0 &&
          local_id < preserve_initialization.length() &&
          preserve_initialization[local_id]
        match target.last() {
          Some(@lib.Unreachable) if !preserve_target_initialization => ()
          _ => {
            target.push(@lib.Instruction::local_set(local_idx))
            target.push(@lib.Instruction::local_get(local_idx))
          }
        }
      }
      _ =>
        match out {
          Some(existing) => existing.push(current)
          None if current_changed => {
            let prefix : Array[@lib.Instruction] = []
            for prior in 0.. ()
        }
    }
  }
  match out {
    Some(rewritten) => (rewritten, true)
    None => (instrs, false)
  }
}

///|
pub fn untee_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_
  }
  if funcs.is_empty() {
    return mod_
  }
  let mut rewritten : Array[@lib.Func]? = None
  let params_by_func : Array[Array[@lib.ValType]] = match
    (mod_.type_sec, mod_.func_sec) {
    (Some(_), Some(_)) => cl_defined_func_param_cache(mod_)
    _ => Array::make(funcs.length(), [])
  }
  for func_idx = 0; func_idx < funcs.length(); func_idx = func_idx + 1 {
    let func = funcs[func_idx]
    let @lib.Func(locals, @lib.Expr(body)) = func
    // Definite initialization is checked even in polymorphic unreachable code.
    let params = params_by_func[func_idx]
    let preserve_initialization = Array::make(
      params.length() + locals.length(),
      false,
    )
    for body_local = 0
        body_local < locals.length()
        body_local = body_local + 1 {
      match locals.get(body_local) {
        @lib.RefTypeValType(ref_type) if ref_type.is_non_nullable() =>
          preserve_initialization[params.length() + body_local] = true
        _ => ()
      }
    }
    let (next_body, func_changed) = untee_rewrite_instrs(
      body, preserve_initialization,
    )
    if func_changed {
      let target = match rewritten {
        Some(existing) => existing
        None => {
          let prefix : Array[@lib.Func] = []
          for prior in 0.. existing.push(func)
        None => ()
      }
    }
  }
  match rewritten {
    Some(funcs) => mod_.with_code_sec(@lib.CodeSec::new(funcs))
    None => mod_
  }
}