From b5a7ed4325fa0f1ef17197e8c26cf08201fa9a15 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sun, 19 Jul 2026 16:18:10 -0700 Subject: [PATCH] Fix treeify soundness: never treeify (sink) effectful ops at all. The `last_non_pure` adjacency rule let a non-pure value (e.g. a load) be owned by its adjacent consumer; when that consumer's pure single-use chain was itself treeified under a later use, the load's code was emitted at the final owner's position, possibly sinking past intervening effects, including stores to the same address. Rather than build a full reordering analysis, this PR simply forces all non-pure ops to stay in-place as their own tree. This increases Wasm code size slightly (traffic through allocated locals to consume the result of every load), but in an optimizing Wasm engine the end result will be the same. --- src/backend/treeify.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/backend/treeify.rs b/src/backend/treeify.rs index bcf39b8..ff0fcc7 100644 --- a/src/backend/treeify.rs +++ b/src/backend/treeify.rs @@ -42,7 +42,6 @@ impl Trees { let mut multi_use = HashSet::default(); for block_def in body.blocks.values() { - let mut last_non_pure = None; for &value in &block_def.insts { match &body.values[value] { &ValueDef::Operator(op, args, _) => { @@ -71,17 +70,13 @@ impl Trees { } else if let Some(old_owner) = owner.remove(&arg) { owned.remove(&old_owner); multi_use.insert(arg); - } else if Self::is_movable(body, arg) || Some(arg) == last_non_pure { + } else if Self::is_movable(body, arg) { let pos = u16::try_from(i).unwrap(); let value_arg = ValueArg(value, pos); owner.insert(arg, value_arg); owned.insert(value_arg, arg); } } - - if !op.is_pure() { - last_non_pure = Some(value); - } } &ValueDef::PickOutput(..) => { // Can ignore use: multi-arity values are never treeified.