// 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_
}
}