// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// 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 dae2_rewrite_raw_function(
module_graph : Dae2Graph,
infos : Array[Dae2Function],
index : Int32,
original : @lib.Func,
context : @ir.HotModuleContext,
kept_params : Array[@lib.ValType],
removed_params : Array[@lib.ValType],
) -> (@lib.Func, Array[Int32])? {
let @lib.Func(_, @lib.Expr(body)) = original
if body.any(fn(instr) {
instr is @lib.Block(_, _) ||
instr is @lib.Loop(_, _) ||
instr is @lib.If(_, _, _)
}) {
// Flattening a block first loses the control boundary that lets HOT defer
// a pure parent operand until after the block's effects.
match
dae2_rewrite_raw_structured_function(
module_graph, infos, index, original, context, kept_params, removed_params,
) {
Some(result) => return Some(result)
None => ()
}
return dae2_rewrite_raw_flat_function(
module_graph, infos, index, original, context, kept_params, removed_params,
)
}
match
dae2_rewrite_raw_flat_function(
module_graph, infos, index, original, context, kept_params, removed_params,
) {
Some(result) => Some(result)
None =>
dae2_rewrite_raw_structured_function(
module_graph, infos, index, original, context, kept_params, removed_params,
)
}
}
///|
// A qualified scalar stack slot has one consumer. Keep original instruction
// order while projecting that stack onto demanded values. Observable producers
// still run; unused retained results are dropped at their production point.
fn dae2_rewrite_raw_flat_function(
module_graph : Dae2Graph,
infos : Array[Dae2Function],
index : Int32,
original : @lib.Func,
context : @ir.HotModuleContext,
kept_params : Array[@lib.ValType],
removed_params : Array[@lib.ValType],
) -> (@lib.Func, Array[Int32])? {
let info = infos[index]
if !info.raw_analysis || info.results.length() > 1 {
return None
}
let @lib.Func(locals, @lib.Expr(original_instructions)) = original
let instructions = match
dae2_raw_branchless_instructions(original_instructions) {
Some(instructions) => instructions
None => return None
}
// Reject unsupported mutation shapes before allocating expression data.
for instr in instructions {
match instr {
@lib.CallIndirect(_, _) | @lib.CallRef(_) => return None
@lib.Call(@lib.FuncIdx(raw)) => {
let callee = raw.reinterpret_as_int()
if callee < 0 ||
callee >= infos.length() ||
infos[callee].results.length() > 1 {
return None
}
}
_ => ()
}
}
let graph = Dae2Graph::new()
ignore(graph.locations(instructions.length()))
let kinds = Array::make(instructions.length(), -1)
let stack : Array[Int32] = []
let definitions = Array::make(info.params.length() + locals.length(), -1)
let mut tee_writes : Array[Int32]? = None
for position in 0..= 0 {
(
if instr is @lib.MemorySize(_) || instr is @lib.TableSize(_) {
2
} else {
0
},
pure,
1,
)
} else if observer >= 0 {
(1, observer, 0)
} else if producer >= 0 {
(2, producer, 1)
} else {
match instr {
@lib.LocalSet(_) => (3, 1, 0)
@lib.LocalTee(_) => (8, 1, 1)
@lib.Call(@lib.FuncIdx(raw)) => {
let callee = infos[raw.reinterpret_as_int()]
(4, callee.params.length(), callee.results.length())
}
@lib.Drop => (5, 1, 0)
@lib.Return if position == instructions.length() - 1 =>
(6, info.results.length(), 0)
@lib.Nop => (7, 0, 0)
_ => return None
}
}
if stack.length() < consumed {
return None
}
let start = stack.length() - consumed
if start > 0 && (kind == 1 || kind == 2 || kind == 4) {
match instructions[stack[start - 1]] {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local_id = raw.reinterpret_as_int()
if local_id < info.params.length() && definitions[local_id] < 0 {
// HOT can defer this parameter read until its actual consumer.
// Let structured replay prove that no emitted local write crosses
// that read. The lexical flat emitter cannot change its order.
return None
}
}
_ => ()
}
}
kinds[position] = kind
match kind {
0 | 3 | 8 => {
for slot in start.. {
let local = raw.reinterpret_as_int()
if local < 0 || local >= definitions.length() {
return None
}
graph.edge(position, definitions[local])
}
@lib.LocalSet(@lib.LocalIdx(raw)) => {
let local = raw.reinterpret_as_int()
if local < 0 || local >= definitions.length() {
return None
}
definitions[local] = position
}
@lib.LocalTee(@lib.LocalIdx(raw)) => {
let local = raw.reinterpret_as_int()
if local < 0 || local >= definitions.length() {
return None
}
// The write can be demanded by a later get even when the tee's
// original stack value is discarded. Keep those demands separate
// so each original stack slot is projected exactly once.
let writes = match tee_writes {
Some(writes) => writes
None => {
let writes = Array::make(instructions.length(), -1)
tee_writes = Some(writes)
writes
}
}
let write = graph.location()
graph.edge(write, stack[start])
writes[position] = write
definitions[local] = write
}
_ => ()
}
}
1 | 2 =>
for slot in start.. {
let raw = match instr {
@lib.Call(@lib.FuncIdx(raw)) => raw
_ => return None
}
let callee = infos[raw.reinterpret_as_int()]
for arg in 0.. graph.edge(position, stack[start])
6 => {
if start != 0 {
return None
}
if module_graph.used[info.result_location] {
for slot in start.. ()
}
stack.truncate(start)
if produced == 1 {
stack.push(position)
}
}
if instructions.last() is Some(@lib.Return) {
if !stack.is_empty() {
return None
}
} else {
if stack.length() != info.results.length() {
return None
}
if module_graph.used[info.result_location] {
for value in stack {
graph.observe(value)
}
}
}
graph.solve()
// HOT lowering removes local.tee writes only when no emitted local.get
// reads that slot. Reuse the finished definition row for these read facts;
// a read before an overwrite still keeps the original tee shape.
if tee_writes is Some(_) {
for local in 0..
definitions[raw.reinterpret_as_int()] = 1
_ => ()
}
}
}
}
let local_map = Array::make(info.params.length() + locals.length(), -1)
let mut kept = 0
let mut removed = 0
for param in 0..
if used {
match instr {
@lib.LocalGet(@lib.LocalIdx(raw)) => {
let local = raw.reinterpret_as_int()
if local < info.params.length() &&
!module_graph.used[info.param_locations[local]] &&
graph.first_edge[position] < 0 {
return None
}
out.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(local_map[local].to_uint()),
),
)
}
@lib.LocalSet(@lib.LocalIdx(raw)) =>
out.push(
@lib.Instruction::local_set(
@lib.LocalIdx::new(
local_map[raw.reinterpret_as_int()].to_uint(),
),
),
)
_ => out.push(instr)
}
}
1 | 6 => out.push(instr)
2 => {
out.push(instr)
if !used {
out.push(@lib.Instruction::drop())
}
}
4 => {
let raw = match instr {
@lib.Call(@lib.FuncIdx(raw)) => raw
_ => return None
}
let callee = infos[raw.reinterpret_as_int()]
let retains_result = !callee.results.is_empty() &&
module_graph.used[callee.result_location]
if used && !retains_result {
return None
}
out.push(instr)
if retains_result && !used {
out.push(@lib.Instruction::drop())
}
}
5 => {
let edge = graph.first_edge[position]
if edge < 0 || graph.used[graph.edges[edge]] {
return None
}
}
8 => {
let write = match tee_writes {
Some(writes) => writes[position]
None => return None
}
let local = match instr {
@lib.LocalTee(@lib.LocalIdx(raw)) => raw.reinterpret_as_int()
_ => return None
}
// Match hot_lower_impl_prune_dead_local_tees_in_body using emitted
// reads, independently of whether this particular write reaches one.
if graph.used[write] || (used && definitions[local] != 0) {
out.push(
@lib.Instruction::local_tee(
@lib.LocalIdx::new(local_map[local].to_uint()),
),
)
if !used {
// HOT keeps the demanded write as tee/drop, even when its value
// disappears. Drop here before any later ordered producer.
out.push(@lib.Instruction::drop())
}
}
}
_ => ()
}
}
let new_locals = locals.copy()
for slot = removed_params.length() - 1; slot >= 0; slot = slot - 1 {
new_locals.push(removed_params[slot])
}
let (rewritten, capture_map) = pass_lower_compact_captures_with_map(
@lib.Func::new(new_locals, @lib.Expr::new(out)),
original,
kept_params,
)
Some((rewritten, local_map.map(fn(local) { capture_map[local] })))
}