// 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 2022 WebAssembly Community Group participants
// Counterpart: src/passes/GlobalEffects.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.

///|
fn ge_body_cannot_suspend(body : @lib.Expr) -> Bool {
  let @lib.Expr(instrs) = body
  for instr in instrs {
    match instr {
      @lib.Call(_)
      | @lib.CallIndirect(_, _)
      | @lib.CallRef(_)
      | @lib.ReturnCall(_)
      | @lib.ReturnCallIndirect(_, _)
      | @lib.ReturnCallRef(_)
      | @lib.Suspend(_)
      | @lib.Resume(_, _)
      | @lib.ResumeThrow(_, _, _)
      | @lib.ResumeThrowRef(_, _)
      | @lib.StackSwitch(_, _) => return false
      @lib.Block(_, child) | @lib.Loop(_, child) =>
        if !ge_body_cannot_suspend(child) {
          return false
        }
      @lib.If(_, then_body, else_body) => {
        if !ge_body_cannot_suspend(@lib.Expr::new(then_body)) {
          return false
        }
        match else_body {
          Some(body) =>
            if !ge_body_cannot_suspend(@lib.Expr::new(body)) {
              return false
            }
          None => ()
        }
      }
      @lib.Try(_, _, _, _) | @lib.TryTable(_, _, _) => return false
      _ => ()
    }
  }
  true
}

///|
fn ge_target_cannot_suspend(
  funcs : Array[@lib.Func],
  defined_idx : Int32,
  summaries : Array[Bool?],
) -> Bool {
  match summaries[defined_idx] {
    Some(result) => result
    None => {
      let @lib.Func(_, body) = funcs[defined_idx]
      let result = ge_body_cannot_suspend(body)
      summaries[defined_idx] = Some(result)
      result
    }
  }
}

///|
fn ge_body_has_rewritable_resume(body : @lib.Expr) -> Bool {
  let @lib.Expr(instrs) = body
  for instr in instrs {
    match instr {
      @lib.Resume(_, _) => return true
      @lib.Block(_, nested) | @lib.Loop(_, nested) =>
        if ge_body_has_rewritable_resume(nested) {
          return true
        }
      @lib.If(_, then_body, else_body) => {
        if ge_body_has_rewritable_resume(@lib.Expr::new(then_body)) {
          return true
        }
        match else_body {
          Some(nested) =>
            if ge_body_has_rewritable_resume(@lib.Expr::new(nested)) {
              return true
            }
          None => ()
        }
      }
      @lib.Try(_, body, catches, _) => {
        if ge_body_has_rewritable_resume(body) {
          return true
        }
        for catch_ in catches {
          let catch_body = match catch_ {
            @lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
          }
          if ge_body_has_rewritable_resume(catch_body) {
            return true
          }
        }
      }
      @lib.TryTable(_, _, body) =>
        if ge_body_has_rewritable_resume(body) {
          return true
        }
      _ => ()
    }
  }
  false
}

///|
fn ge_rewrite_fresh_resumes(
  body : @lib.Expr,
  funcs : Array[@lib.Func],
  summaries : Array[Bool?],
  imported_funcs : Int32,
  changed : Array[Bool],
) -> @lib.Expr {
  let @lib.Expr(instrs) = body
  let out : Array[@lib.Instruction] = []
  for instr in instrs {
    match instr {
      @lib.Resume(type_idx, handlers) if handlers.is_empty() &&
        out.length() >= 2 => {
        let n = out.length()
        match (out[n - 2], out[n - 1]) {
          (@lib.RefFunc(target), @lib.ContNew(new_type)) if new_type == type_idx => {
            let @lib.FuncIdx(raw) = target
            let defined_idx = raw.to_int() - imported_funcs
            if defined_idx >= 0 && defined_idx < funcs.length() {
              if ge_target_cannot_suspend(funcs, defined_idx, summaries) {
                ignore(out.pop())
                ignore(out.pop())
                out.push(@lib.Instruction::call(target))
                changed[0] = true
              } else {
                out.push(instr)
              }
            } else {
              out.push(instr)
            }
          }
          _ => out.push(instr)
        }
      }
      @lib.Block(block_type, nested) =>
        out.push(
          @lib.Instruction::block(
            block_type,
            ge_rewrite_fresh_resumes(
              nested, funcs, summaries, imported_funcs, changed,
            ),
          ),
        )
      @lib.Loop(block_type, nested) =>
        out.push(
          @lib.Instruction::loop_(
            block_type,
            ge_rewrite_fresh_resumes(
              nested, funcs, summaries, imported_funcs, changed,
            ),
          ),
        )
      @lib.If(block_type, then_body, else_body) => {
        let @lib.Expr(next_then) = ge_rewrite_fresh_resumes(
          @lib.Expr::new(then_body),
          funcs,
          summaries,
          imported_funcs,
          changed,
        )
        let next_else = match else_body {
          Some(body) => {
            let @lib.Expr(next_body) = ge_rewrite_fresh_resumes(
              @lib.Expr::new(body),
              funcs,
              summaries,
              imported_funcs,
              changed,
            )
            Some(next_body)
          }
          None => None
        }
        out.push(@lib.Instruction::if_(block_type, next_then, next_else))
      }
      @lib.Try(block_type, body, catches, delegate) =>
        out.push(
          @lib.Instruction::try_(
            block_type,
            ge_rewrite_fresh_resumes(
              body, funcs, summaries, imported_funcs, changed,
            ),
            catches.map(fn(catch_) {
              match catch_ {
                @lib.LegacyCatch(tag, catch_body) =>
                  @lib.LegacyCatch::tagged(
                    tag,
                    ge_rewrite_fresh_resumes(
                      catch_body, funcs, summaries, imported_funcs, changed,
                    ),
                  )
                @lib.LegacyCatchAll(catch_body) =>
                  @lib.LegacyCatch::all(
                    ge_rewrite_fresh_resumes(
                      catch_body, funcs, summaries, imported_funcs, changed,
                    ),
                  )
              }
            }),
            delegate~,
          ),
        )
      @lib.TryTable(block_type, catches, body) =>
        out.push(
          @lib.Instruction::try_table(
            block_type,
            catches,
            ge_rewrite_fresh_resumes(
              body, funcs, summaries, imported_funcs, changed,
            ),
          ),
        )
      _ => out.push(instr)
    }
  }
  @lib.Expr::new(out)
}

///|
fn global_effects_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.any(fn(func) {
      let @lib.Func(_, body) = func
      ge_body_has_rewritable_resume(body)
    }) {
    return mod_
  }
  let mut imported_funcs = 0
  match mod_.import_sec {
    Some(@lib.ImportSec(imports)) =>
      for import_ in imports {
        if import_ is @lib.Import(_, _, @lib.FuncExternType(_)) {
          imported_funcs += 1
        }
      }
    None => ()
  }
  let changed = [false]
  let summaries : Array[Bool?] = Array::make(funcs.length(), None)
  let rewritten : Array[@lib.Func] = []
  for func in funcs {
    let @lib.Func(locals, body) = func
    if !ge_body_has_rewritable_resume(body) {
      rewritten.push(func)
      continue
    }
    rewritten.push(
      @lib.Func::new(
        locals,
        ge_rewrite_fresh_resumes(
          body, funcs, summaries, imported_funcs, changed,
        ),
      ),
    )
  }
  if !changed[0] {
    return mod_
  }
  let candidate = mod_.with_code_sec(@lib.CodeSec::new(rewritten))
  if @validate.validate_module(candidate).is_ok() {
    candidate
  } else {
    mod_
  }
}