From aec8d88af4ba7a5510ffb06aae910874e1b38e11 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Sat, 20 Jun 2026 11:47:51 +0800 Subject: [PATCH 01/35] Forward repeated storage reads --- crates/compiler/src/compiler.rs | 2 + crates/passes/src/lib.rs | 3 + .../passes/src/storage_read_forwarding/ast.rs | 126 ++++++++++++++++++ .../passes/src/storage_read_forwarding/mod.rs | 52 ++++++++ .../src/storage_read_forwarding/program.rs | 50 +++++++ .../src/storage_read_forwarding/visitor.rs | 98 ++++++++++++++ crates/passes/src/test_passes.rs | 12 ++ .../repeated_mapping_reads.out | 39 ++++++ .../repeated_vector_len.out | 31 +++++ .../external_same_named_mapping_reads.leo | 41 ++++++ .../repeated_mapping_reads.leo | 35 +++++ .../repeated_vector_len.leo | 24 ++++ 12 files changed, 513 insertions(+) create mode 100644 crates/passes/src/storage_read_forwarding/ast.rs create mode 100644 crates/passes/src/storage_read_forwarding/mod.rs create mode 100644 crates/passes/src/storage_read_forwarding/program.rs create mode 100644 crates/passes/src/storage_read_forwarding/visitor.rs create mode 100644 tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out create mode 100644 tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out create mode 100644 tests/tests/compiler/storage/external_same_named_mapping_reads.leo create mode 100644 tests/tests/passes/storage_read_forwarding/repeated_mapping_reads.leo create mode 100644 tests/tests/passes/storage_read_forwarding/repeated_vector_len.leo diff --git a/crates/compiler/src/compiler.rs b/crates/compiler/src/compiler.rs index f37758ef88..6fa71a8037 100644 --- a/crates/compiler/src/compiler.rs +++ b/crates/compiler/src/compiler.rs @@ -456,6 +456,8 @@ impl Compiler { self.do_pass::(())?; + self.do_pass::(())?; + self.do_pass::(())?; Ok(abis) diff --git a/crates/passes/src/lib.rs b/crates/passes/src/lib.rs index e9fd51b085..ba14619458 100644 --- a/crates/passes/src/lib.rs +++ b/crates/passes/src/lib.rs @@ -94,6 +94,9 @@ pub use static_single_assignment::*; mod ssa_const_propagation; pub use ssa_const_propagation::*; +mod storage_read_forwarding; +pub use storage_read_forwarding::*; + mod storage_lowering; pub use storage_lowering::*; diff --git a/crates/passes/src/storage_read_forwarding/ast.rs b/crates/passes/src/storage_read_forwarding/ast.rs new file mode 100644 index 0000000000..fd96dbf66c --- /dev/null +++ b/crates/passes/src/storage_read_forwarding/ast.rs @@ -0,0 +1,126 @@ +// Copyright (C) 2019-2026 Provable Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use super::StorageReadForwardingVisitor; + +use leo_ast::*; + +impl AstReconstructor for StorageReadForwardingVisitor<'_> { + type AdditionalInput = (); + type AdditionalOutput = (); + + fn reconstruct_path(&mut self, input: Path, _additional: &()) -> (Expression, Self::AdditionalOutput) { + if let Some(alias) = input.try_local_symbol().and_then(|name| self.local_alias(name)) { + let ty = self.state.type_table.get(&input.id()); + let path = Path::from(Identifier::new(alias, self.state.node_builder.next_id())).to_local(); + if let Some(ty) = ty { + self.state.type_table.insert(path.id(), ty); + } + (path.into(), ()) + } else { + (input.into(), ()) + } + } + + fn reconstruct_intrinsic( + &mut self, + mut input: IntrinsicExpression, + _additional: &(), + ) -> (Expression, Self::AdditionalOutput) { + input.arguments = input.arguments.into_iter().map(|arg| self.reconstruct_expression(arg, &()).0).collect(); + if Self::is_effect_boundary(&input) { + self.clear_reads(); + } + (input.into(), ()) + } + + fn reconstruct_call( + &mut self, + mut input: CallExpression, + _additional: &(), + ) -> (Expression, Self::AdditionalOutput) { + input.arguments = input.arguments.into_iter().map(|arg| self.reconstruct_expression(arg, &()).0).collect(); + self.clear_reads(); + (input.into(), ()) + } + + fn reconstruct_dynamic_op( + &mut self, + mut input: DynamicOpExpression, + _additional: &(), + ) -> (Expression, Self::AdditionalOutput) { + input.interface = self.reconstruct_type(input.interface).0; + input.target_program = self.reconstruct_expression(input.target_program, &()).0; + input.network = input.network.map(|network| self.reconstruct_expression(network, &()).0); + match &mut input.kind { + DynamicOpKind::Call { arguments, .. } | DynamicOpKind::Op { arguments, .. } => { + *arguments = + std::mem::take(arguments).into_iter().map(|arg| self.reconstruct_expression(arg, &()).0).collect(); + } + DynamicOpKind::Read { .. } => {} + } + self.clear_reads(); + (input.into(), ()) + } + + fn reconstruct_conditional(&mut self, mut input: ConditionalStatement) -> (Statement, Self::AdditionalOutput) { + input.condition = self.reconstruct_expression(input.condition, &()).0; + + let aliases = self.aliases.clone(); + self.clear_reads(); + self.aliases = aliases.clone(); + input.then = self.reconstruct_block(input.then).0; + + self.clear_reads(); + self.aliases = aliases.clone(); + input.otherwise = input.otherwise.map(|statement| Box::new(self.reconstruct_statement(*statement).0)); + + self.clear_reads(); + self.aliases = aliases; + + (input.into(), ()) + } + + fn reconstruct_definition(&mut self, mut input: DefinitionStatement) -> (Statement, Self::AdditionalOutput) { + input.value = self.reconstruct_expression(input.value, &()).0; + + let DefinitionPlace::Single(place) = &input.place else { + return (input.into(), ()); + }; + + let Expression::Intrinsic(intrinsic) = &input.value else { + return (input.into(), ()); + }; + + if let Some(read) = Self::storage_read(intrinsic) { + if let Some(existing) = self.reads.get(&read).copied() { + self.aliases.insert(place.name, existing); + return (Statement::dummy(), ()); + } + self.reads.insert(read, place.name); + } + + (input.into(), ()) + } + + fn reconstruct_assign(&mut self, _input: AssignStatement) -> (Statement, Self::AdditionalOutput) { + panic!("`AssignStatement`s should not exist in the AST at this phase of compilation."); + } + + fn reconstruct_iteration(&mut self, _input: IterationStatement) -> (Statement, Self::AdditionalOutput) { + panic!("`IterationStatement`s should not exist in the AST at this phase of compilation."); + } +} diff --git a/crates/passes/src/storage_read_forwarding/mod.rs b/crates/passes/src/storage_read_forwarding/mod.rs new file mode 100644 index 0000000000..29477b415e --- /dev/null +++ b/crates/passes/src/storage_read_forwarding/mod.rs @@ -0,0 +1,52 @@ +// Copyright (C) 2019-2026 Provable Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +//! Forward repeated finalize storage reads until a conservative effect boundary. + +use crate::{CompilerState, Pass}; + +use leo_ast::UnitReconstructor as _; +use leo_errors::Result; + +mod ast; +mod program; +mod visitor; + +use visitor::StorageReadForwardingVisitor; + +pub struct StorageReadForwarding; + +impl Pass for StorageReadForwarding { + type Input = (); + type Output = (); + + const NAME: &str = "StorageReadForwarding"; + + fn do_pass(_input: Self::Input, state: &mut CompilerState) -> Result { + let ast = std::mem::take(&mut state.ast); + let mut visitor = + StorageReadForwardingVisitor { state, reads: Default::default(), aliases: Default::default() }; + + let ast = ast.map( + |program| visitor.reconstruct_program(program), + |library| library, // no-op for libraries + ); + + visitor.state.handler.last_err()?; + visitor.state.ast = ast; + Ok(()) + } +} diff --git a/crates/passes/src/storage_read_forwarding/program.rs b/crates/passes/src/storage_read_forwarding/program.rs new file mode 100644 index 0000000000..2dc9ff7175 --- /dev/null +++ b/crates/passes/src/storage_read_forwarding/program.rs @@ -0,0 +1,50 @@ +// Copyright (C) 2019-2026 Provable Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use super::StorageReadForwardingVisitor; + +use leo_ast::{AstReconstructor, Constructor, Function, Library, Module, UnitReconstructor}; + +impl UnitReconstructor for StorageReadForwardingVisitor<'_> { + fn reconstruct_library(&mut self, input: Library) -> Library { + input + } + + fn reconstruct_program_scope(&mut self, mut input: leo_ast::ProgramScope) -> leo_ast::ProgramScope { + input.functions = input.functions.into_iter().map(|(i, f)| (i, self.reconstruct_function(f))).collect(); + input.constructor = input.constructor.map(|c| self.reconstruct_constructor(c)); + input + } + + fn reconstruct_function(&mut self, mut input: Function) -> Function { + self.clear_function_state(); + input.block = self.reconstruct_block(input.block).0; + self.clear_function_state(); + input + } + + fn reconstruct_constructor(&mut self, mut input: Constructor) -> Constructor { + self.clear_function_state(); + input.block = self.reconstruct_block(input.block).0; + self.clear_function_state(); + input + } + + fn reconstruct_module(&mut self, mut input: Module) -> Module { + input.functions = input.functions.into_iter().map(|(i, f)| (i, self.reconstruct_function(f))).collect(); + input + } +} diff --git a/crates/passes/src/storage_read_forwarding/visitor.rs b/crates/passes/src/storage_read_forwarding/visitor.rs new file mode 100644 index 0000000000..18e3429094 --- /dev/null +++ b/crates/passes/src/storage_read_forwarding/visitor.rs @@ -0,0 +1,98 @@ +// Copyright (C) 2019-2026 Provable Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::CompilerState; + +use leo_ast::{Expression, IntrinsicExpression, LiteralVariant, Location}; +use leo_span::{Symbol, sym}; + +use indexmap::IndexMap; + +#[derive(Clone, Eq, PartialEq, Hash)] +pub(super) enum Atom { + Local(Symbol), + Global(Location), + Literal(LiteralVariant), +} + +#[derive(Eq, PartialEq, Hash)] +pub(super) enum StorageRead { + Get { mapping: Atom, key: Atom }, + GetOrUse { mapping: Atom, key: Atom, default: Atom }, + Contains { mapping: Atom, key: Atom }, +} + +pub struct StorageReadForwardingVisitor<'a> { + pub state: &'a mut CompilerState, + pub(super) reads: IndexMap, + pub(super) aliases: IndexMap, +} + +impl StorageReadForwardingVisitor<'_> { + pub(super) fn clear_reads(&mut self) { + self.reads.clear(); + } + + pub(super) fn clear_function_state(&mut self) { + self.reads.clear(); + self.aliases.clear(); + } + + pub(super) fn local_alias(&self, name: Symbol) -> Option { + let mut current = name; + while let Some(next) = self.aliases.get(¤t).copied() { + if next == current { + return Some(current); + } + current = next; + } + (current != name).then_some(current) + } + + pub(super) fn atom(expr: &Expression) -> Option { + match expr { + Expression::Literal(lit) => Some(Atom::Literal(lit.variant.clone())), + Expression::Path(path) => path + .try_local_symbol() + .map(Atom::Local) + .or_else(|| path.try_global_location().cloned().map(Atom::Global)), + _ => None, + } + } + + pub(super) fn storage_read(intrinsic: &IntrinsicExpression) -> Option { + match intrinsic.name { + sym::_mapping_get => Some(StorageRead::Get { + mapping: Self::atom(intrinsic.arguments.first()?)?, + key: Self::atom(intrinsic.arguments.get(1)?)?, + }), + sym::_mapping_get_or_use => Some(StorageRead::GetOrUse { + mapping: Self::atom(intrinsic.arguments.first()?)?, + key: Self::atom(intrinsic.arguments.get(1)?)?, + default: Self::atom(intrinsic.arguments.get(2)?)?, + }), + sym::_mapping_contains => Some(StorageRead::Contains { + mapping: Self::atom(intrinsic.arguments.first()?)?, + key: Self::atom(intrinsic.arguments.get(1)?)?, + }), + _ => None, + } + } + + pub(super) fn is_effect_boundary(intrinsic: &IntrinsicExpression) -> bool { + matches!(intrinsic.name, sym::_mapping_set | sym::_mapping_remove | sym::_final_run) + } +} diff --git a/crates/passes/src/test_passes.rs b/crates/passes/src/test_passes.rs index 640d86cb88..d4fa3212a8 100644 --- a/crates/passes/src/test_passes.rs +++ b/crates/passes/src/test_passes.rs @@ -211,6 +211,18 @@ macro_rules! compiler_passes { (Disambiguate, ()), (StorageLowering, (TypeCheckingInput::new(NetworkName::TestnetV0))) ]), + (storage_read_forwarding_runner, [ + (GlobalVarsCollection, ()), + (PathResolution, ()), + (GlobalItemsCollection, ()), + (TypeChecking, (TypeCheckingInput::new(NetworkName::TestnetV0))), + (Disambiguate, ()), + (StorageLowering, (TypeCheckingInput::new(NetworkName::TestnetV0))), + (SsaForming, (SsaFormingInput { rename_defs: true })), + (Flattening, ()), + (SsaForming, (SsaFormingInput { rename_defs: false })), + (StorageReadForwarding, ()) + ]), (write_transforming_runner, [ (GlobalVarsCollection, ()), (PathResolution, ()), diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out new file mode 100644 index 0000000000..2c445d2b1e --- /dev/null +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -0,0 +1,39 @@ +program test.aleo { + @noupgrade + async constructor() { + return; + } + mapping data: u32 => u8; + mapping out: bool => u8; + fn reuse_before_write(key$$0: u32) -> Final { + let $var$1 = async { + let a = _mapping_get_or_use(test.aleo::data, key, 0u8); + { + } + _mapping_set(test.aleo::out, false, a + a); + }; + return $var$1; + } + fn keep_after_write(key$$2: u32) -> Final { + let $var$3 = async { + let a = _mapping_get_or_use(test.aleo::data, key, 0u8); + _mapping_set(test.aleo::data, key, 1u8); + let b = _mapping_get_or_use(test.aleo::data, key, 0u8); + _mapping_set(test.aleo::out, false, a + b); + }; + return $var$3; + } + fn reuse_contains(key$$4: u32) -> Final { + let $var$5 = async { + let a = _mapping_contains(test.aleo::data, key); + { + } + let one = 1u8; + let zero = 0u8; + let value = a ? one : zero; + _mapping_set(test.aleo::out, a, value); + }; + return $var$5; + } +} + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out new file mode 100644 index 0000000000..87dc8d811d --- /dev/null +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -0,0 +1,31 @@ +program test.aleo { + @noupgrade + async constructor() { + return; + } + mapping out: bool => u32; + mapping data__: u32 => u8; + mapping data__len__: bool => u32; + fn reuse_len() -> Final { + let $var$1 = async { + let a = _mapping_get_or_use(test.aleo::data__len__, false, 0u32); + { + } + _mapping_set(test.aleo::out, false, a + a); + }; + return $var$1; + } + fn keep_after_push() -> Final { + let $var$2 = async { + let a = _mapping_get_or_use(test.aleo::data__len__, false, 0u32); + { + } + _mapping_set(test.aleo::data__len__, false, a + 1u32); + _mapping_set(test.aleo::data__, a, 1u8); + let b = _mapping_get_or_use(test.aleo::data__len__, false, 0u32); + _mapping_set(test.aleo::out, false, a + b); + }; + return $var$2; + } +} + diff --git a/tests/tests/compiler/storage/external_same_named_mapping_reads.leo b/tests/tests/compiler/storage/external_same_named_mapping_reads.leo new file mode 100644 index 0000000000..22587a5d21 --- /dev/null +++ b/tests/tests/compiler/storage/external_same_named_mapping_reads.leo @@ -0,0 +1,41 @@ +program left.aleo { + mapping data: u32 => u8; + + fn noop() -> Final { + return final {}; + } + + @noupgrade + constructor() {} +} + +// --- Next Program --- // +program right.aleo { + mapping data: u32 => u8; + + fn noop() -> Final { + return final {}; + } + + @noupgrade + constructor() {} +} + +// --- Next Program --- // +import left.aleo; +import right.aleo; + +program caller.aleo { + mapping out: bool => u8; + + fn read_both(key: u32) -> Final { + return final { + let a: u8 = Mapping::get_or_use(left.aleo::data, key, 1u8); + let b: u8 = Mapping::get_or_use(right.aleo::data, key, 2u8); + Mapping::set(out, false, a + b); + }; + } + + @noupgrade + constructor() {} +} diff --git a/tests/tests/passes/storage_read_forwarding/repeated_mapping_reads.leo b/tests/tests/passes/storage_read_forwarding/repeated_mapping_reads.leo new file mode 100644 index 0000000000..8c0d676c09 --- /dev/null +++ b/tests/tests/passes/storage_read_forwarding/repeated_mapping_reads.leo @@ -0,0 +1,35 @@ +program test.aleo { + mapping data: u32 => u8; + mapping out: bool => u8; + + fn reuse_before_write(key: u32) -> Final { + return final { + let a: u8 = Mapping::get_or_use(data, key, 0u8); + let b: u8 = Mapping::get_or_use(data, key, 0u8); + Mapping::set(out, false, a + b); + }; + } + + fn keep_after_write(key: u32) -> Final { + return final { + let a: u8 = Mapping::get_or_use(data, key, 0u8); + Mapping::set(data, key, 1u8); + let b: u8 = Mapping::get_or_use(data, key, 0u8); + Mapping::set(out, false, a + b); + }; + } + + fn reuse_contains(key: u32) -> Final { + return final { + let a: bool = Mapping::contains(data, key); + let b: bool = Mapping::contains(data, key); + let one: u8 = 1u8; + let zero: u8 = 0u8; + let value: u8 = a ? one : zero; + Mapping::set(out, b, value); + }; + } + + @noupgrade + constructor() {} +} diff --git a/tests/tests/passes/storage_read_forwarding/repeated_vector_len.leo b/tests/tests/passes/storage_read_forwarding/repeated_vector_len.leo new file mode 100644 index 0000000000..3b80a65819 --- /dev/null +++ b/tests/tests/passes/storage_read_forwarding/repeated_vector_len.leo @@ -0,0 +1,24 @@ +program test.aleo { + storage data: [u8]; + mapping out: bool => u32; + + fn reuse_len() -> Final { + return final { + let a: u32 = data.len(); + let b: u32 = data.len(); + Mapping::set(out, false, a + b); + }; + } + + fn keep_after_push() -> Final { + return final { + let a: u32 = data.len(); + data.push(1u8); + let b: u32 = data.len(); + Mapping::set(out, false, a + b); + }; + } + + @noupgrade + constructor() {} +} From 5f6a38393b820f935b42b1c8f449b47ebef8eee6 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Sat, 20 Jun 2026 13:01:25 +0800 Subject: [PATCH 02/35] Clean storage read snapshots EOF --- .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 - .../passes/storage_read_forwarding/repeated_vector_len.out | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index 2c445d2b1e..ea48431de8 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -36,4 +36,3 @@ program test.aleo { return $var$5; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index 87dc8d811d..34e70fed5d 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -28,4 +28,3 @@ program test.aleo { return $var$2; } } - From d143643aa5d3af4fa6793827f5e7d0c2397b9bd0 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Sat, 20 Jun 2026 13:06:00 +0800 Subject: [PATCH 03/35] Add external mapping read expectation --- .../external_same_named_mapping_reads.out | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/expectations/compiler/storage/external_same_named_mapping_reads.out diff --git a/tests/expectations/compiler/storage/external_same_named_mapping_reads.out b/tests/expectations/compiler/storage/external_same_named_mapping_reads.out new file mode 100644 index 0000000000..16a28bc74a --- /dev/null +++ b/tests/expectations/compiler/storage/external_same_named_mapping_reads.out @@ -0,0 +1,54 @@ +program left.aleo; + +mapping data: + key as u32.public; + value as u8.public; + +function noop: + async noop into r0; + output r0 as left.aleo/noop.future; + +finalize noop: + assert.eq true true; + +constructor: + assert.eq edition 0u16; +// --- Next Program --- // +program right.aleo; + +mapping data: + key as u32.public; + value as u8.public; + +function noop: + async noop into r0; + output r0 as right.aleo/noop.future; + +finalize noop: + assert.eq true true; + +constructor: + assert.eq edition 0u16; +// --- Next Program --- // +import left.aleo; +import right.aleo; +program caller.aleo; + +mapping out: + key as boolean.public; + value as u8.public; + +function read_both: + input r0 as u32.private; + async read_both r0 into r1; + output r1 as caller.aleo/read_both.future; + +finalize read_both: + input r0 as u32.public; + get.or_use left.aleo/data[r0] 1u8 into r1; + get.or_use right.aleo/data[r0] 2u8 into r2; + add r1 r2 into r3; + set r3 into out[false]; + +constructor: + assert.eq edition 0u16; From 4717e1669cdf676a593684b3f42f1fd1be737887 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Sat, 20 Jun 2026 13:15:13 +0800 Subject: [PATCH 04/35] Cover branch-local storage read forwarding --- .../passes/src/storage_read_forwarding/mod.rs | 6 +++++- .../conditional_branch_reads.out | 21 +++++++++++++++++++ .../conditional_branch_reads.leo | 20 ++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out create mode 100644 tests/tests/passes/storage_read_forwarding/conditional_branch_reads.leo diff --git a/crates/passes/src/storage_read_forwarding/mod.rs b/crates/passes/src/storage_read_forwarding/mod.rs index 29477b415e..eed00672d8 100644 --- a/crates/passes/src/storage_read_forwarding/mod.rs +++ b/crates/passes/src/storage_read_forwarding/mod.rs @@ -14,7 +14,11 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -//! Forward repeated finalize storage reads until a conservative effect boundary. +//! Forward repeated local finalize storage reads until a conservative effect boundary. +//! +//! This pass only handles lowered static mapping intrinsics. Dynamic storage +//! reads carry target program and network operands and are left to a separate +//! optimization so their invalidation model can be reviewed independently. use crate::{CompilerState, Pass}; diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out new file mode 100644 index 0000000000..a2552ff228 --- /dev/null +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -0,0 +1,21 @@ +program test.aleo { + @noupgrade + async constructor() { + return; + } + mapping data: u32 => u8; + mapping out: bool => u8; + fn branch_local(key$$0: u32, flag$$1: bool) -> Final { + let $var$2 = async { + if flag { + let a = _mapping_get_or_use(test.aleo::data, key, 0u8); + { + } + _mapping_set(test.aleo::out, false, a + a); + } + let c = _mapping_get_or_use(test.aleo::data, key, 0u8); + _mapping_set(test.aleo::out, true, c); + }; + return $var$2; + } +} diff --git a/tests/tests/passes/storage_read_forwarding/conditional_branch_reads.leo b/tests/tests/passes/storage_read_forwarding/conditional_branch_reads.leo new file mode 100644 index 0000000000..655a733188 --- /dev/null +++ b/tests/tests/passes/storage_read_forwarding/conditional_branch_reads.leo @@ -0,0 +1,20 @@ +program test.aleo { + mapping data: u32 => u8; + mapping out: bool => u8; + + fn branch_local(key: u32, flag: bool) -> Final { + return final { + if flag { + let a: u8 = Mapping::get_or_use(data, key, 0u8); + let b: u8 = Mapping::get_or_use(data, key, 0u8); + Mapping::set(out, false, a + b); + } + + let c: u8 = Mapping::get_or_use(data, key, 0u8); + Mapping::set(out, true, c); + }; + } + + @noupgrade + constructor() {} +} From d527354d9e7aeba02c1e68102975ee3b71da6e2a Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Sat, 20 Jun 2026 13:55:16 +0800 Subject: [PATCH 05/35] Treat finalize assertions as storage read barriers --- .../passes/src/storage_read_forwarding/ast.rs | 17 +++++++++++++++++ .../assert_barrier.out | 19 +++++++++++++++++++ .../assert_barrier.leo | 17 +++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 tests/expectations/passes/storage_read_forwarding/assert_barrier.out create mode 100644 tests/tests/passes/storage_read_forwarding/assert_barrier.leo diff --git a/crates/passes/src/storage_read_forwarding/ast.rs b/crates/passes/src/storage_read_forwarding/ast.rs index fd96dbf66c..82d12847a1 100644 --- a/crates/passes/src/storage_read_forwarding/ast.rs +++ b/crates/passes/src/storage_read_forwarding/ast.rs @@ -94,6 +94,23 @@ impl AstReconstructor for StorageReadForwardingVisitor<'_> { (input.into(), ()) } + fn reconstruct_assert(&mut self, mut input: AssertStatement) -> (Statement, Self::AdditionalOutput) { + input.variant = match input.variant { + AssertVariant::Assert(expr) => AssertVariant::Assert(self.reconstruct_expression(expr, &()).0), + AssertVariant::AssertEq(left, right) => AssertVariant::AssertEq( + self.reconstruct_expression(left, &()).0, + self.reconstruct_expression(right, &()).0, + ), + AssertVariant::AssertNeq(left, right) => AssertVariant::AssertNeq( + self.reconstruct_expression(left, &()).0, + self.reconstruct_expression(right, &()).0, + ), + }; + + self.clear_reads(); + (input.into(), ()) + } + fn reconstruct_definition(&mut self, mut input: DefinitionStatement) -> (Statement, Self::AdditionalOutput) { input.value = self.reconstruct_expression(input.value, &()).0; diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out new file mode 100644 index 0000000000..a16464cae9 --- /dev/null +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -0,0 +1,19 @@ +program test.aleo { + @noupgrade + async constructor() { + return; + } + mapping data: u32 => u8; + mapping out: bool => u8; + fn keep_after_assert(key$$0: u32) -> Final { + let $var$1 = async { + let a = _mapping_get_or_use(test.aleo::data, key, 0u8); + { + } + assert_eq(a, a); + let c = _mapping_get_or_use(test.aleo::data, key, 0u8); + _mapping_set(test.aleo::out, false, a + c); + }; + return $var$1; + } +} diff --git a/tests/tests/passes/storage_read_forwarding/assert_barrier.leo b/tests/tests/passes/storage_read_forwarding/assert_barrier.leo new file mode 100644 index 0000000000..48137c33a3 --- /dev/null +++ b/tests/tests/passes/storage_read_forwarding/assert_barrier.leo @@ -0,0 +1,17 @@ +program test.aleo { + mapping data: u32 => u8; + mapping out: bool => u8; + + fn keep_after_assert(key: u32) -> Final { + return final { + let a: u8 = Mapping::get_or_use(data, key, 0u8); + let b: u8 = Mapping::get_or_use(data, key, 0u8); + assert_eq(b, a); + let c: u8 = Mapping::get_or_use(data, key, 0u8); + Mapping::set(out, false, b + c); + }; + } + + @noupgrade + constructor() {} +} From c3410a64fef8776770dda915f945d781483b4882 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Sat, 20 Jun 2026 14:10:52 +0800 Subject: [PATCH 06/35] Limit storage read forwarding to finalize contexts --- .../passes/src/storage_read_forwarding/ast.rs | 18 +++++++++++++++++- .../passes/src/storage_read_forwarding/mod.rs | 8 ++++++-- .../src/storage_read_forwarding/program.rs | 6 ++++++ .../src/storage_read_forwarding/visitor.rs | 1 + 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/crates/passes/src/storage_read_forwarding/ast.rs b/crates/passes/src/storage_read_forwarding/ast.rs index 82d12847a1..a73812aa6e 100644 --- a/crates/passes/src/storage_read_forwarding/ast.rs +++ b/crates/passes/src/storage_read_forwarding/ast.rs @@ -76,6 +76,20 @@ impl AstReconstructor for StorageReadForwardingVisitor<'_> { (input.into(), ()) } + fn reconstruct_async( + &mut self, + mut input: AsyncExpression, + _additional: &(), + ) -> (Expression, Self::AdditionalOutput) { + let previous_context = self.in_finalize_context; + self.in_finalize_context = true; + self.clear_function_state(); + input.block = self.reconstruct_block(input.block).0; + self.clear_function_state(); + self.in_finalize_context = previous_context; + (input.into(), ()) + } + fn reconstruct_conditional(&mut self, mut input: ConditionalStatement) -> (Statement, Self::AdditionalOutput) { input.condition = self.reconstruct_expression(input.condition, &()).0; @@ -122,7 +136,9 @@ impl AstReconstructor for StorageReadForwardingVisitor<'_> { return (input.into(), ()); }; - if let Some(read) = Self::storage_read(intrinsic) { + if self.in_finalize_context + && let Some(read) = Self::storage_read(intrinsic) + { if let Some(existing) = self.reads.get(&read).copied() { self.aliases.insert(place.name, existing); return (Statement::dummy(), ()); diff --git a/crates/passes/src/storage_read_forwarding/mod.rs b/crates/passes/src/storage_read_forwarding/mod.rs index eed00672d8..735f063bb0 100644 --- a/crates/passes/src/storage_read_forwarding/mod.rs +++ b/crates/passes/src/storage_read_forwarding/mod.rs @@ -41,8 +41,12 @@ impl Pass for StorageReadForwarding { fn do_pass(_input: Self::Input, state: &mut CompilerState) -> Result { let ast = std::mem::take(&mut state.ast); - let mut visitor = - StorageReadForwardingVisitor { state, reads: Default::default(), aliases: Default::default() }; + let mut visitor = StorageReadForwardingVisitor { + state, + reads: Default::default(), + aliases: Default::default(), + in_finalize_context: false, + }; let ast = ast.map( |program| visitor.reconstruct_program(program), diff --git a/crates/passes/src/storage_read_forwarding/program.rs b/crates/passes/src/storage_read_forwarding/program.rs index 2dc9ff7175..2c21b1f996 100644 --- a/crates/passes/src/storage_read_forwarding/program.rs +++ b/crates/passes/src/storage_read_forwarding/program.rs @@ -30,16 +30,22 @@ impl UnitReconstructor for StorageReadForwardingVisitor<'_> { } fn reconstruct_function(&mut self, mut input: Function) -> Function { + let previous_context = self.in_finalize_context; + self.in_finalize_context = input.variant.is_finalize_context(); self.clear_function_state(); input.block = self.reconstruct_block(input.block).0; self.clear_function_state(); + self.in_finalize_context = previous_context; input } fn reconstruct_constructor(&mut self, mut input: Constructor) -> Constructor { + let previous_context = self.in_finalize_context; + self.in_finalize_context = true; self.clear_function_state(); input.block = self.reconstruct_block(input.block).0; self.clear_function_state(); + self.in_finalize_context = previous_context; input } diff --git a/crates/passes/src/storage_read_forwarding/visitor.rs b/crates/passes/src/storage_read_forwarding/visitor.rs index 18e3429094..1451416e46 100644 --- a/crates/passes/src/storage_read_forwarding/visitor.rs +++ b/crates/passes/src/storage_read_forwarding/visitor.rs @@ -39,6 +39,7 @@ pub struct StorageReadForwardingVisitor<'a> { pub state: &'a mut CompilerState, pub(super) reads: IndexMap, pub(super) aliases: IndexMap, + pub(super) in_finalize_context: bool, } impl StorageReadForwardingVisitor<'_> { From 2e965bb6ef7027b795886d8e0feccd46e229a7ae Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Sat, 20 Jun 2026 14:25:52 +0800 Subject: [PATCH 07/35] Align storage read forwarding branch snapshot --- .../conditional_branch_reads.out | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index a2552ff228..03b6f30c9d 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -7,11 +7,12 @@ program test.aleo { mapping out: bool => u8; fn branch_local(key$$0: u32, flag$$1: bool) -> Final { let $var$2 = async { - if flag { - let a = _mapping_get_or_use(test.aleo::data, key, 0u8); - { - } - _mapping_set(test.aleo::out, false, a + a); + let condition$3 = flag; + let a = _mapping_get_or_use(test.aleo::data, key, 0u8); + { + } + _mapping_set(test.aleo::out, false, a + a); + { } let c = _mapping_get_or_use(test.aleo::data, key, 0u8); _mapping_set(test.aleo::out, true, c); From 170809fbd2b83c076c2cb06f612c816872e2c5ab Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Sat, 20 Jun 2026 14:36:51 +0800 Subject: [PATCH 08/35] Restore storage forwarding snapshot EOFs --- .../passes/storage_read_forwarding/assert_barrier.out | 1 + .../passes/storage_read_forwarding/conditional_branch_reads.out | 1 + .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 + .../passes/storage_read_forwarding/repeated_vector_len.out | 1 + 4 files changed, 4 insertions(+) diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index a16464cae9..cbf440fcb4 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -17,3 +17,4 @@ program test.aleo { return $var$1; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index 03b6f30c9d..ec1b28d85c 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -20,3 +20,4 @@ program test.aleo { return $var$2; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index ea48431de8..2c445d2b1e 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -36,3 +36,4 @@ program test.aleo { return $var$5; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index 34e70fed5d..87dc8d811d 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -28,3 +28,4 @@ program test.aleo { return $var$2; } } + From 94524ba4432042baae151cc1fc236a7195fd5743 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Sat, 20 Jun 2026 16:08:29 +0800 Subject: [PATCH 09/35] Clean storage forwarding snapshots --- .../passes/storage_read_forwarding/assert_barrier.out | 1 - .../passes/storage_read_forwarding/conditional_branch_reads.out | 1 - .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 - .../passes/storage_read_forwarding/repeated_vector_len.out | 1 - 4 files changed, 4 deletions(-) diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index cbf440fcb4..a16464cae9 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -17,4 +17,3 @@ program test.aleo { return $var$1; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index ec1b28d85c..03b6f30c9d 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -20,4 +20,3 @@ program test.aleo { return $var$2; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index 2c445d2b1e..ea48431de8 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -36,4 +36,3 @@ program test.aleo { return $var$5; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index 87dc8d811d..34e70fed5d 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -28,4 +28,3 @@ program test.aleo { return $var$2; } } - From ff2deab5b0b19545d740ec1a2510e8a7b82a0a00 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Sat, 20 Jun 2026 16:11:25 +0800 Subject: [PATCH 10/35] Update AST snapshot pass list --- tests/expectations/cli/test_ast_snapshots_program/STDOUT | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/expectations/cli/test_ast_snapshots_program/STDOUT b/tests/expectations/cli/test_ast_snapshots_program/STDOUT index 9b67a5d9dd..0b555e57c0 100644 --- a/tests/expectations/cli/test_ast_snapshots_program/STDOUT +++ b/tests/expectations/cli/test_ast_snapshots_program/STDOUT @@ -56,6 +56,8 @@ StaticAnalyzing.ast StaticAnalyzing.json StorageLowering.ast StorageLowering.json +StorageReadForwarding.ast +StorageReadForwarding.json TypeChecking.ast TypeChecking.json WriteTransforming.ast @@ -108,6 +110,8 @@ StaticAnalyzing.ast StaticAnalyzing.json StorageLowering.ast StorageLowering.json +StorageReadForwarding.ast +StorageReadForwarding.json TypeChecking.ast TypeChecking.json WriteTransforming.ast From 7b95860e33755fa494948df0b216cc36d818402e Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Sat, 20 Jun 2026 16:25:46 +0800 Subject: [PATCH 11/35] Restore storage pass snapshot endings --- .../passes/storage_read_forwarding/assert_barrier.out | 1 + .../passes/storage_read_forwarding/conditional_branch_reads.out | 1 + .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 + .../passes/storage_read_forwarding/repeated_vector_len.out | 1 + 4 files changed, 4 insertions(+) diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index a16464cae9..cbf440fcb4 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -17,3 +17,4 @@ program test.aleo { return $var$1; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index 03b6f30c9d..ec1b28d85c 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -20,3 +20,4 @@ program test.aleo { return $var$2; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index ea48431de8..2c445d2b1e 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -36,3 +36,4 @@ program test.aleo { return $var$5; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index 34e70fed5d..87dc8d811d 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -28,3 +28,4 @@ program test.aleo { return $var$2; } } + From a80b26cb2f664ddd20971bd04d88c6bb52d20b9a Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Sun, 21 Jun 2026 20:26:57 +0800 Subject: [PATCH 12/35] Clean storage read forwarding expectations --- .../passes/storage_read_forwarding/assert_barrier.out | 1 - .../passes/storage_read_forwarding/conditional_branch_reads.out | 1 - .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 - .../passes/storage_read_forwarding/repeated_vector_len.out | 1 - 4 files changed, 4 deletions(-) diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index cbf440fcb4..a16464cae9 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -17,4 +17,3 @@ program test.aleo { return $var$1; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index ec1b28d85c..03b6f30c9d 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -20,4 +20,3 @@ program test.aleo { return $var$2; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index 2c445d2b1e..ea48431de8 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -36,4 +36,3 @@ program test.aleo { return $var$5; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index 87dc8d811d..34e70fed5d 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -28,4 +28,3 @@ program test.aleo { return $var$2; } } - From d7f652c8f4c09d4bbe76c228c0e96cc01eeeae3e Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Mon, 22 Jun 2026 02:14:59 +0800 Subject: [PATCH 13/35] Match storage forwarding snapshots --- .../passes/storage_read_forwarding/assert_barrier.out | 1 + .../passes/storage_read_forwarding/conditional_branch_reads.out | 1 + .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 + .../passes/storage_read_forwarding/repeated_vector_len.out | 1 + 4 files changed, 4 insertions(+) diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index a16464cae9..cbf440fcb4 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -17,3 +17,4 @@ program test.aleo { return $var$1; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index 03b6f30c9d..ec1b28d85c 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -20,3 +20,4 @@ program test.aleo { return $var$2; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index ea48431de8..2c445d2b1e 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -36,3 +36,4 @@ program test.aleo { return $var$5; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index 34e70fed5d..87dc8d811d 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -28,3 +28,4 @@ program test.aleo { return $var$2; } } + From d2f19090a605a69c6cc0910a3dd41c0ddcf7f777 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Mon, 22 Jun 2026 10:32:52 +0800 Subject: [PATCH 14/35] Normalize storage forwarding expectation EOFs --- .../passes/storage_read_forwarding/assert_barrier.out | 1 - .../passes/storage_read_forwarding/conditional_branch_reads.out | 1 - .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 - .../passes/storage_read_forwarding/repeated_vector_len.out | 1 - 4 files changed, 4 deletions(-) diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index cbf440fcb4..a16464cae9 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -17,4 +17,3 @@ program test.aleo { return $var$1; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index ec1b28d85c..03b6f30c9d 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -20,4 +20,3 @@ program test.aleo { return $var$2; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index 2c445d2b1e..ea48431de8 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -36,4 +36,3 @@ program test.aleo { return $var$5; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index 87dc8d811d..34e70fed5d 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -28,4 +28,3 @@ program test.aleo { return $var$2; } } - From 83076aa9aa67348fe98e167a7a7ac0ca745165d2 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Mon, 22 Jun 2026 10:40:52 +0800 Subject: [PATCH 15/35] Revert "Normalize storage forwarding expectation EOFs" This reverts commit d2f19090a605a69c6cc0910a3dd41c0ddcf7f777. --- .../passes/storage_read_forwarding/assert_barrier.out | 1 + .../passes/storage_read_forwarding/conditional_branch_reads.out | 1 + .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 + .../passes/storage_read_forwarding/repeated_vector_len.out | 1 + 4 files changed, 4 insertions(+) diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index a16464cae9..cbf440fcb4 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -17,3 +17,4 @@ program test.aleo { return $var$1; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index 03b6f30c9d..ec1b28d85c 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -20,3 +20,4 @@ program test.aleo { return $var$2; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index ea48431de8..2c445d2b1e 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -36,3 +36,4 @@ program test.aleo { return $var$5; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index 34e70fed5d..87dc8d811d 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -28,3 +28,4 @@ program test.aleo { return $var$2; } } + From 35ce271a7e115bed1b3ecde483756a9cedf16165 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Thu, 25 Jun 2026 13:33:33 +0800 Subject: [PATCH 16/35] Fix storage read forwarding expectation EOFs --- .../passes/storage_read_forwarding/assert_barrier.out | 1 - .../passes/storage_read_forwarding/conditional_branch_reads.out | 1 - .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 - .../passes/storage_read_forwarding/repeated_vector_len.out | 1 - 4 files changed, 4 deletions(-) diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index cbf440fcb4..a16464cae9 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -17,4 +17,3 @@ program test.aleo { return $var$1; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index ec1b28d85c..03b6f30c9d 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -20,4 +20,3 @@ program test.aleo { return $var$2; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index 2c445d2b1e..ea48431de8 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -36,4 +36,3 @@ program test.aleo { return $var$5; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index 87dc8d811d..34e70fed5d 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -28,4 +28,3 @@ program test.aleo { return $var$2; } } - From fa3c32729c4867a7ce99235d371e46a141ea9e01 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Thu, 25 Jun 2026 13:49:10 +0800 Subject: [PATCH 17/35] Restore storage forwarding expectation EOFs --- .../passes/storage_read_forwarding/assert_barrier.out | 1 + .../passes/storage_read_forwarding/conditional_branch_reads.out | 1 + .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 + .../passes/storage_read_forwarding/repeated_vector_len.out | 1 + 4 files changed, 4 insertions(+) diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index a16464cae9..cbf440fcb4 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -17,3 +17,4 @@ program test.aleo { return $var$1; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index 03b6f30c9d..ec1b28d85c 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -20,3 +20,4 @@ program test.aleo { return $var$2; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index ea48431de8..2c445d2b1e 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -36,3 +36,4 @@ program test.aleo { return $var$5; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index 34e70fed5d..87dc8d811d 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -28,3 +28,4 @@ program test.aleo { return $var$2; } } + From 54d4ab3abdd5d6d18e5d2178ea7006c75b6146ee Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 14:11:13 +0800 Subject: [PATCH 18/35] Preserve storage read aliases across conditionals --- .../passes/src/storage_read_forwarding/ast.rs | 4 ++ crates/passes/src/test_passes.rs | 54 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/crates/passes/src/storage_read_forwarding/ast.rs b/crates/passes/src/storage_read_forwarding/ast.rs index a73812aa6e..a02d026068 100644 --- a/crates/passes/src/storage_read_forwarding/ast.rs +++ b/crates/passes/src/storage_read_forwarding/ast.rs @@ -97,13 +97,17 @@ impl AstReconstructor for StorageReadForwardingVisitor<'_> { self.clear_reads(); self.aliases = aliases.clone(); input.then = self.reconstruct_block(input.then).0; + let then_aliases = self.aliases.clone(); self.clear_reads(); self.aliases = aliases.clone(); input.otherwise = input.otherwise.map(|statement| Box::new(self.reconstruct_statement(*statement).0)); + let otherwise_aliases = self.aliases.clone(); self.clear_reads(); self.aliases = aliases; + self.aliases.extend(then_aliases); + self.aliases.extend(otherwise_aliases); (input.into(), ()) } diff --git a/crates/passes/src/test_passes.rs b/crates/passes/src/test_passes.rs index d4fa3212a8..fcb97c5a3a 100644 --- a/crates/passes/src/test_passes.rs +++ b/crates/passes/src/test_passes.rs @@ -311,6 +311,60 @@ macro_rules! make_all_runners { } compiler_passes!(make_all_runners); +make_runner!( + storage_read_forwarding_pre_flatten_runner, + [ + (GlobalVarsCollection, ()), + (PathResolution, ()), + (GlobalItemsCollection, ()), + (TypeChecking, (TypeCheckingInput::new(NetworkName::TestnetV0))), + (Disambiguate, ()), + (StorageLowering, (TypeCheckingInput::new(NetworkName::TestnetV0))), + (SsaForming, (SsaFormingInput { rename_defs: true })), + (StorageReadForwarding, ()) + ] +); + +#[test] +#[serial] +fn storage_read_forwarding_preserves_branch_alias_for_ssa_join() { + let output = storage_read_forwarding_pre_flatten_runner( + r#" +program test.aleo { + mapping data: u32 => u8; + mapping out: bool => u8; + + fn branch_phi(key: u32, flag: bool) -> Final { + return final { + let mut x: u8 = 0u8; + + if flag { + x = Mapping::get_or_use(data, key, 0u8); + x = Mapping::get_or_use(data, key, 0u8); + } + + Mapping::set(out, false, x); + }; + } + + @noupgrade + constructor() {} +} +"#, + ); + + assert_eq!( + output.matches("_mapping_get_or_use").count(), + 1, + "expected the repeated branch read to be forwarded:\n{output}" + ); + assert!( + output.contains("let x$6 = flag$$1 ? x$#4 : x$#3;"), + "expected the SSA join to use the surviving branch definition:\n{output}" + ); + assert!(!output.contains("x$#5"), "removed SSA definition is still referenced:\n{output}"); +} + /// Macro to generate `#[test]` functions for all compiler passes. /// /// Each test function: From 5b664d489d4ca03cdbc89e037c54d9e1cfa2dc7f Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 14:17:21 +0800 Subject: [PATCH 19/35] Fix storage forwarding regression style --- crates/passes/src/test_passes.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/crates/passes/src/test_passes.rs b/crates/passes/src/test_passes.rs index fcb97c5a3a..4e8e987050 100644 --- a/crates/passes/src/test_passes.rs +++ b/crates/passes/src/test_passes.rs @@ -311,19 +311,16 @@ macro_rules! make_all_runners { } compiler_passes!(make_all_runners); -make_runner!( - storage_read_forwarding_pre_flatten_runner, - [ - (GlobalVarsCollection, ()), - (PathResolution, ()), - (GlobalItemsCollection, ()), - (TypeChecking, (TypeCheckingInput::new(NetworkName::TestnetV0))), - (Disambiguate, ()), - (StorageLowering, (TypeCheckingInput::new(NetworkName::TestnetV0))), - (SsaForming, (SsaFormingInput { rename_defs: true })), - (StorageReadForwarding, ()) - ] -); +make_runner!(storage_read_forwarding_pre_flatten_runner, [ + (GlobalVarsCollection, ()), + (PathResolution, ()), + (GlobalItemsCollection, ()), + (TypeChecking, TypeCheckingInput::new(NetworkName::TestnetV0)), + (Disambiguate, ()), + (StorageLowering, TypeCheckingInput::new(NetworkName::TestnetV0)), + (SsaForming, SsaFormingInput { rename_defs: true }), + (StorageReadForwarding, ()) +]); #[test] #[serial] From 27242e7095cc609799cf3947104925da3958dfbc Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 14:25:06 +0800 Subject: [PATCH 20/35] Use valid Leo syntax in storage forwarding test --- crates/passes/src/test_passes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/passes/src/test_passes.rs b/crates/passes/src/test_passes.rs index 4e8e987050..1f04d50f99 100644 --- a/crates/passes/src/test_passes.rs +++ b/crates/passes/src/test_passes.rs @@ -333,7 +333,7 @@ program test.aleo { fn branch_phi(key: u32, flag: bool) -> Final { return final { - let mut x: u8 = 0u8; + let x: u8 = 0u8; if flag { x = Mapping::get_or_use(data, key, 0u8); From 57b33548204fb9c56e815dc5b12d4ebebbfff929 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 14:35:25 +0800 Subject: [PATCH 21/35] Focus storage forwarding regression runner --- crates/passes/src/test_passes.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/passes/src/test_passes.rs b/crates/passes/src/test_passes.rs index 1f04d50f99..43cfd61b09 100644 --- a/crates/passes/src/test_passes.rs +++ b/crates/passes/src/test_passes.rs @@ -312,12 +312,6 @@ macro_rules! make_all_runners { compiler_passes!(make_all_runners); make_runner!(storage_read_forwarding_pre_flatten_runner, [ - (GlobalVarsCollection, ()), - (PathResolution, ()), - (GlobalItemsCollection, ()), - (TypeChecking, TypeCheckingInput::new(NetworkName::TestnetV0)), - (Disambiguate, ()), - (StorageLowering, TypeCheckingInput::new(NetworkName::TestnetV0)), (SsaForming, SsaFormingInput { rename_defs: true }), (StorageReadForwarding, ()) ]); @@ -350,6 +344,7 @@ program test.aleo { "#, ); + assert!(!output.contains("Error:"), "unexpected parse or pass error:\n{output}"); assert_eq!( output.matches("_mapping_get_or_use").count(), 1, From 85c75ba3f359c7d607f69800d2a9fbdeb5706d4e Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 14:44:19 +0800 Subject: [PATCH 22/35] Use direct SSA shape in storage forwarding test --- crates/passes/src/test_passes.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/passes/src/test_passes.rs b/crates/passes/src/test_passes.rs index 43cfd61b09..34636cc1d4 100644 --- a/crates/passes/src/test_passes.rs +++ b/crates/passes/src/test_passes.rs @@ -312,7 +312,6 @@ macro_rules! make_all_runners { compiler_passes!(make_all_runners); make_runner!(storage_read_forwarding_pre_flatten_runner, [ - (SsaForming, SsaFormingInput { rename_defs: true }), (StorageReadForwarding, ()) ]); @@ -327,14 +326,15 @@ program test.aleo { fn branch_phi(key: u32, flag: bool) -> Final { return final { - let x: u8 = 0u8; + let x0: u8 = 0u8; if flag { - x = Mapping::get_or_use(data, key, 0u8); - x = Mapping::get_or_use(data, key, 0u8); + let x1: u8 = Mapping::get_or_use(data, key, 0u8); + let x2: u8 = Mapping::get_or_use(data, key, 0u8); } - Mapping::set(out, false, x); + let x3: u8 = flag ? x2 : x0; + Mapping::set(out, false, x3); }; } @@ -351,10 +351,10 @@ program test.aleo { "expected the repeated branch read to be forwarded:\n{output}" ); assert!( - output.contains("let x$6 = flag$$1 ? x$#4 : x$#3;"), + output.contains("flag ? x1 : x0"), "expected the SSA join to use the surviving branch definition:\n{output}" ); - assert!(!output.contains("x$#5"), "removed SSA definition is still referenced:\n{output}"); + assert!(!output.contains("x2"), "removed SSA definition is still referenced:\n{output}"); } /// Macro to generate `#[test]` functions for all compiler passes. From b6d93ae650c22074c42367bd37cfde9b7bd8b5b4 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 14:50:28 +0800 Subject: [PATCH 23/35] Format storage forwarding regression runner --- crates/passes/src/test_passes.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/passes/src/test_passes.rs b/crates/passes/src/test_passes.rs index 34636cc1d4..dd9904162f 100644 --- a/crates/passes/src/test_passes.rs +++ b/crates/passes/src/test_passes.rs @@ -311,9 +311,7 @@ macro_rules! make_all_runners { } compiler_passes!(make_all_runners); -make_runner!(storage_read_forwarding_pre_flatten_runner, [ - (StorageReadForwarding, ()) -]); +make_runner!(storage_read_forwarding_pre_flatten_runner, [(StorageReadForwarding, ())]); #[test] #[serial] From cdc39cb78b6e9052bf5e9d4c3bb24bc077a66b98 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 15:01:17 +0800 Subject: [PATCH 24/35] Use definitions in storage forwarding regression --- crates/passes/src/test_passes.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/passes/src/test_passes.rs b/crates/passes/src/test_passes.rs index dd9904162f..fdcf56ea55 100644 --- a/crates/passes/src/test_passes.rs +++ b/crates/passes/src/test_passes.rs @@ -324,14 +324,14 @@ program test.aleo { fn branch_phi(key: u32, flag: bool) -> Final { return final { - let x0: u8 = 0u8; + let x0 = 0u8; if flag { - let x1: u8 = Mapping::get_or_use(data, key, 0u8); - let x2: u8 = Mapping::get_or_use(data, key, 0u8); + let x1 = Mapping::get_or_use(data, key, 0u8); + let x2 = Mapping::get_or_use(data, key, 0u8); } - let x3: u8 = flag ? x2 : x0; + let x3 = flag ? x2 : x0; Mapping::set(out, false, x3); }; } From 4dea567d735de399a2b41161882c0ea76a0d9239 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 15:14:36 +0800 Subject: [PATCH 25/35] Test storage forwarding aliases at AST level --- .../passes/src/storage_read_forwarding/ast.rs | 112 ++++++++++++++++++ crates/passes/src/test_passes.rs | 44 ------- 2 files changed, 112 insertions(+), 44 deletions(-) diff --git a/crates/passes/src/storage_read_forwarding/ast.rs b/crates/passes/src/storage_read_forwarding/ast.rs index a02d026068..53863e2a91 100644 --- a/crates/passes/src/storage_read_forwarding/ast.rs +++ b/crates/passes/src/storage_read_forwarding/ast.rs @@ -161,3 +161,115 @@ impl AstReconstructor for StorageReadForwardingVisitor<'_> { panic!("`IterationStatement`s should not exist in the AST at this phase of compilation."); } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::CompilerState; + + use leo_span::{Symbol, sym}; + use std::rc::Rc; + + fn ident(state: &mut CompilerState, name: &str) -> Identifier { + Identifier::new(Symbol::intern(name), state.node_builder.next_id()) + } + + fn local(state: &mut CompilerState, name: &str) -> Expression { + Path::from(ident(state, name)).to_local().into() + } + + fn u8_lit(state: &mut CompilerState, value: &str) -> Expression { + Literal::integer(IntegerType::U8, value.into(), Default::default(), state.node_builder.next_id()).into() + } + + fn definition(state: &mut CompilerState, name: &str, value: Expression) -> Statement { + DefinitionStatement { + place: DefinitionPlace::Single(ident(state, name)), + type_: None, + value, + span: Default::default(), + id: state.node_builder.next_id(), + } + .into() + } + + fn storage_read(state: &mut CompilerState) -> Expression { + let mut arguments = Vec::new(); + arguments.push(local(state, "data")); + arguments.push(local(state, "key")); + arguments.push(u8_lit(state, "0")); + IntrinsicExpression { + name: sym::_mapping_get_or_use, + type_parameters: Vec::new(), + input_types: Vec::new(), + return_types: Vec::new(), + arguments, + span: Default::default(), + id: state.node_builder.next_id(), + } + .into() + } + + #[test] + fn preserves_branch_alias_for_ssa_join_operand() { + let mut state = CompilerState { node_builder: Rc::new(NodeBuilder::default()), ..Default::default() }; + + let condition = local(&mut state, "flag"); + let true_operand = local(&mut state, "x2"); + let false_operand = local(&mut state, "x0"); + let join = TernaryExpression { + condition: local(&mut state, "flag"), + if_true: true_operand, + if_false: false_operand, + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let initial_value = u8_lit(&mut state, "0"); + let first_read = storage_read(&mut state); + let second_read = storage_read(&mut state); + let block = Block { + statements: vec![ + definition(&mut state, "x0", initial_value), + ConditionalStatement { + condition, + then: Block { + statements: vec![ + definition(&mut state, "x1", first_read), + definition(&mut state, "x2", second_read), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }, + otherwise: None, + span: Default::default(), + id: state.node_builder.next_id(), + } + .into(), + definition(&mut state, "x3", join.into()), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let mut visitor = StorageReadForwardingVisitor { + state: &mut state, + reads: Default::default(), + aliases: Default::default(), + in_finalize_context: true, + }; + + let output = visitor.reconstruct_block(block).0.to_string(); + + assert_eq!( + output.matches("_mapping_get_or_use").count(), + 1, + "expected the repeated branch read to be forwarded:\n{output}" + ); + assert!( + output.contains("let x3 = flag ? x1 : x0"), + "expected the SSA join to use the surviving branch definition:\n{output}" + ); + assert!(!output.contains("x2"), "removed SSA definition is still referenced:\n{output}"); + } +} diff --git a/crates/passes/src/test_passes.rs b/crates/passes/src/test_passes.rs index fdcf56ea55..d4fa3212a8 100644 --- a/crates/passes/src/test_passes.rs +++ b/crates/passes/src/test_passes.rs @@ -311,50 +311,6 @@ macro_rules! make_all_runners { } compiler_passes!(make_all_runners); -make_runner!(storage_read_forwarding_pre_flatten_runner, [(StorageReadForwarding, ())]); - -#[test] -#[serial] -fn storage_read_forwarding_preserves_branch_alias_for_ssa_join() { - let output = storage_read_forwarding_pre_flatten_runner( - r#" -program test.aleo { - mapping data: u32 => u8; - mapping out: bool => u8; - - fn branch_phi(key: u32, flag: bool) -> Final { - return final { - let x0 = 0u8; - - if flag { - let x1 = Mapping::get_or_use(data, key, 0u8); - let x2 = Mapping::get_or_use(data, key, 0u8); - } - - let x3 = flag ? x2 : x0; - Mapping::set(out, false, x3); - }; - } - - @noupgrade - constructor() {} -} -"#, - ); - - assert!(!output.contains("Error:"), "unexpected parse or pass error:\n{output}"); - assert_eq!( - output.matches("_mapping_get_or_use").count(), - 1, - "expected the repeated branch read to be forwarded:\n{output}" - ); - assert!( - output.contains("flag ? x1 : x0"), - "expected the SSA join to use the surviving branch definition:\n{output}" - ); - assert!(!output.contains("x2"), "removed SSA definition is still referenced:\n{output}"); -} - /// Macro to generate `#[test]` functions for all compiler passes. /// /// Each test function: From 8684c6f2bb99ac7848b650f7f148c8f55651bab0 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 15:17:51 +0800 Subject: [PATCH 26/35] Satisfy clippy in storage forwarding test --- crates/passes/src/storage_read_forwarding/ast.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/passes/src/storage_read_forwarding/ast.rs b/crates/passes/src/storage_read_forwarding/ast.rs index 53863e2a91..547dbb110f 100644 --- a/crates/passes/src/storage_read_forwarding/ast.rs +++ b/crates/passes/src/storage_read_forwarding/ast.rs @@ -194,10 +194,7 @@ mod tests { } fn storage_read(state: &mut CompilerState) -> Expression { - let mut arguments = Vec::new(); - arguments.push(local(state, "data")); - arguments.push(local(state, "key")); - arguments.push(u8_lit(state, "0")); + let arguments = vec![local(state, "data"), local(state, "key"), u8_lit(state, "0")]; IntrinsicExpression { name: sym::_mapping_get_or_use, type_parameters: Vec::new(), From 41a82664c43494321a19f875cbd74bd95d306744 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 15:25:23 +0800 Subject: [PATCH 27/35] Wrap storage forwarding unit test in session --- .../passes/src/storage_read_forwarding/ast.rs | 118 +++++++++--------- 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/crates/passes/src/storage_read_forwarding/ast.rs b/crates/passes/src/storage_read_forwarding/ast.rs index 547dbb110f..89cd5b1c44 100644 --- a/crates/passes/src/storage_read_forwarding/ast.rs +++ b/crates/passes/src/storage_read_forwarding/ast.rs @@ -167,7 +167,7 @@ mod tests { use super::*; use crate::CompilerState; - use leo_span::{Symbol, sym}; + use leo_span::{Symbol, create_session_if_not_set_then, sym}; use std::rc::Rc; fn ident(state: &mut CompilerState, name: &str) -> Identifier { @@ -209,64 +209,66 @@ mod tests { #[test] fn preserves_branch_alias_for_ssa_join_operand() { - let mut state = CompilerState { node_builder: Rc::new(NodeBuilder::default()), ..Default::default() }; - - let condition = local(&mut state, "flag"); - let true_operand = local(&mut state, "x2"); - let false_operand = local(&mut state, "x0"); - let join = TernaryExpression { - condition: local(&mut state, "flag"), - if_true: true_operand, - if_false: false_operand, - span: Default::default(), - id: state.node_builder.next_id(), - }; - - let initial_value = u8_lit(&mut state, "0"); - let first_read = storage_read(&mut state); - let second_read = storage_read(&mut state); - let block = Block { - statements: vec![ - definition(&mut state, "x0", initial_value), - ConditionalStatement { - condition, - then: Block { - statements: vec![ - definition(&mut state, "x1", first_read), - definition(&mut state, "x2", second_read), - ], + create_session_if_not_set_then(|_| { + let mut state = CompilerState { node_builder: Rc::new(NodeBuilder::default()), ..Default::default() }; + + let condition = local(&mut state, "flag"); + let true_operand = local(&mut state, "x2"); + let false_operand = local(&mut state, "x0"); + let join = TernaryExpression { + condition: local(&mut state, "flag"), + if_true: true_operand, + if_false: false_operand, + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let initial_value = u8_lit(&mut state, "0"); + let first_read = storage_read(&mut state); + let second_read = storage_read(&mut state); + let block = Block { + statements: vec![ + definition(&mut state, "x0", initial_value), + ConditionalStatement { + condition, + then: Block { + statements: vec![ + definition(&mut state, "x1", first_read), + definition(&mut state, "x2", second_read), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }, + otherwise: None, span: Default::default(), id: state.node_builder.next_id(), - }, - otherwise: None, - span: Default::default(), - id: state.node_builder.next_id(), - } - .into(), - definition(&mut state, "x3", join.into()), - ], - span: Default::default(), - id: state.node_builder.next_id(), - }; - - let mut visitor = StorageReadForwardingVisitor { - state: &mut state, - reads: Default::default(), - aliases: Default::default(), - in_finalize_context: true, - }; - - let output = visitor.reconstruct_block(block).0.to_string(); - - assert_eq!( - output.matches("_mapping_get_or_use").count(), - 1, - "expected the repeated branch read to be forwarded:\n{output}" - ); - assert!( - output.contains("let x3 = flag ? x1 : x0"), - "expected the SSA join to use the surviving branch definition:\n{output}" - ); - assert!(!output.contains("x2"), "removed SSA definition is still referenced:\n{output}"); + } + .into(), + definition(&mut state, "x3", join.into()), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let mut visitor = StorageReadForwardingVisitor { + state: &mut state, + reads: Default::default(), + aliases: Default::default(), + in_finalize_context: true, + }; + + let output = visitor.reconstruct_block(block).0.to_string(); + + assert_eq!( + output.matches("_mapping_get_or_use").count(), + 1, + "expected the repeated branch read to be forwarded:\n{output}" + ); + assert!( + output.contains("let x3 = flag ? x1 : x0"), + "expected the SSA join to use the surviving branch definition:\n{output}" + ); + assert!(!output.contains("x2"), "removed SSA definition is still referenced:\n{output}"); + }); } } From a58112d14ebe310b989a52c11a74d42891829069 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 16:26:26 +0800 Subject: [PATCH 28/35] Limit branch storage read aliases to SSA joins --- .../passes/src/storage_read_forwarding/ast.rs | 176 +++++++++++++++++- .../passes/src/storage_read_forwarding/mod.rs | 8 + .../src/storage_read_forwarding/visitor.rs | 26 +++ 3 files changed, 208 insertions(+), 2 deletions(-) diff --git a/crates/passes/src/storage_read_forwarding/ast.rs b/crates/passes/src/storage_read_forwarding/ast.rs index 89cd5b1c44..867c5c68a6 100644 --- a/crates/passes/src/storage_read_forwarding/ast.rs +++ b/crates/passes/src/storage_read_forwarding/ast.rs @@ -92,27 +92,67 @@ impl AstReconstructor for StorageReadForwardingVisitor<'_> { fn reconstruct_conditional(&mut self, mut input: ConditionalStatement) -> (Statement, Self::AdditionalOutput) { input.condition = self.reconstruct_expression(input.condition, &()).0; + let join_condition = input.condition.clone(); let aliases = self.aliases.clone(); self.clear_reads(); + self.clear_join_aliases(); self.aliases = aliases.clone(); input.then = self.reconstruct_block(input.then).0; let then_aliases = self.aliases.clone(); self.clear_reads(); + self.clear_join_aliases(); self.aliases = aliases.clone(); input.otherwise = input.otherwise.map(|statement| Box::new(self.reconstruct_statement(*statement).0)); let otherwise_aliases = self.aliases.clone(); self.clear_reads(); self.aliases = aliases; - self.aliases.extend(then_aliases); - self.aliases.extend(otherwise_aliases); + + self.then_join_aliases = then_aliases + .into_iter() + .filter(|(alias, target)| self.aliases.get(alias).copied() != Some(*target)) + .collect(); + self.otherwise_join_aliases = otherwise_aliases + .into_iter() + .filter(|(alias, target)| self.aliases.get(alias).copied() != Some(*target)) + .collect(); + self.join_condition = + (!self.then_join_aliases.is_empty() || !self.otherwise_join_aliases.is_empty()).then_some(join_condition); + + (input.into(), ()) + } + + fn reconstruct_ternary( + &mut self, + mut input: TernaryExpression, + _additional: &(), + ) -> (Expression, Self::AdditionalOutput) { + if !self.same_join_condition(&input.condition) { + self.clear_join_aliases(); + input.condition = self.reconstruct_expression(input.condition, &()).0; + input.if_true = self.reconstruct_expression(input.if_true, &()).0; + input.if_false = self.reconstruct_expression(input.if_false, &()).0; + return (input.into(), ()); + } + + let aliases = self.aliases.clone(); + input.condition = self.reconstruct_expression(input.condition, &()).0; + + self.aliases.extend(self.then_join_aliases.clone()); + input.if_true = self.reconstruct_expression(input.if_true, &()).0; + self.aliases = aliases.clone(); + + self.aliases.extend(self.otherwise_join_aliases.clone()); + input.if_false = self.reconstruct_expression(input.if_false, &()).0; + self.aliases = aliases; (input.into(), ()) } fn reconstruct_assert(&mut self, mut input: AssertStatement) -> (Statement, Self::AdditionalOutput) { + self.clear_join_aliases(); input.variant = match input.variant { AssertVariant::Assert(expr) => AssertVariant::Assert(self.reconstruct_expression(expr, &()).0), AssertVariant::AssertEq(left, right) => AssertVariant::AssertEq( @@ -153,6 +193,15 @@ impl AstReconstructor for StorageReadForwardingVisitor<'_> { (input.into(), ()) } + fn reconstruct_expression_statement( + &mut self, + mut input: ExpressionStatement, + ) -> (Statement, Self::AdditionalOutput) { + self.clear_join_aliases(); + input.expression = self.reconstruct_expression(input.expression, &()).0; + (input.into(), ()) + } + fn reconstruct_assign(&mut self, _input: AssignStatement) -> (Statement, Self::AdditionalOutput) { panic!("`AssignStatement`s should not exist in the AST at this phase of compilation."); } @@ -254,6 +303,9 @@ mod tests { state: &mut state, reads: Default::default(), aliases: Default::default(), + then_join_aliases: Default::default(), + otherwise_join_aliases: Default::default(), + join_condition: None, in_finalize_context: true, }; @@ -271,4 +323,124 @@ mod tests { assert!(!output.contains("x2"), "removed SSA definition is still referenced:\n{output}"); }); } + + #[test] + fn does_not_apply_branch_alias_to_different_condition() { + create_session_if_not_set_then(|_| { + let mut state = CompilerState { node_builder: Rc::new(NodeBuilder::default()), ..Default::default() }; + + let condition = local(&mut state, "flag"); + let unrelated_join = TernaryExpression { + condition: local(&mut state, "other"), + if_true: local(&mut state, "x2"), + if_false: local(&mut state, "x0"), + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let initial_value = u8_lit(&mut state, "0"); + let first_read = storage_read(&mut state); + let second_read = storage_read(&mut state); + let block = Block { + statements: vec![ + definition(&mut state, "x0", initial_value), + ConditionalStatement { + condition, + then: Block { + statements: vec![ + definition(&mut state, "x1", first_read), + definition(&mut state, "x2", second_read), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }, + otherwise: None, + span: Default::default(), + id: state.node_builder.next_id(), + } + .into(), + definition(&mut state, "x3", unrelated_join.into()), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let mut visitor = StorageReadForwardingVisitor { + state: &mut state, + reads: Default::default(), + aliases: Default::default(), + then_join_aliases: Default::default(), + otherwise_join_aliases: Default::default(), + join_condition: None, + in_finalize_context: true, + }; + + let output = visitor.reconstruct_block(block).0.to_string(); + + assert!( + output.contains("let x3 = other ? x2 : x0"), + "branch-local alias was applied outside the matching SSA join:\n{output}" + ); + }); + } + + #[test] + fn does_not_apply_then_branch_alias_to_otherwise_join_operand() { + create_session_if_not_set_then(|_| { + let mut state = CompilerState { node_builder: Rc::new(NodeBuilder::default()), ..Default::default() }; + + let condition = local(&mut state, "flag"); + let join = TernaryExpression { + condition: local(&mut state, "flag"), + if_true: local(&mut state, "x0"), + if_false: local(&mut state, "x2"), + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let initial_value = u8_lit(&mut state, "0"); + let first_read = storage_read(&mut state); + let second_read = storage_read(&mut state); + let block = Block { + statements: vec![ + definition(&mut state, "x0", initial_value), + ConditionalStatement { + condition, + then: Block { + statements: vec![ + definition(&mut state, "x1", first_read), + definition(&mut state, "x2", second_read), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }, + otherwise: None, + span: Default::default(), + id: state.node_builder.next_id(), + } + .into(), + definition(&mut state, "x3", join.into()), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let mut visitor = StorageReadForwardingVisitor { + state: &mut state, + reads: Default::default(), + aliases: Default::default(), + then_join_aliases: Default::default(), + otherwise_join_aliases: Default::default(), + join_condition: None, + in_finalize_context: true, + }; + + let output = visitor.reconstruct_block(block).0.to_string(); + + assert!( + output.contains("let x3 = flag ? x0 : x2"), + "then-branch alias was applied to the opposite ternary arm:\n{output}" + ); + }); + } } diff --git a/crates/passes/src/storage_read_forwarding/mod.rs b/crates/passes/src/storage_read_forwarding/mod.rs index 735f063bb0..767024ff19 100644 --- a/crates/passes/src/storage_read_forwarding/mod.rs +++ b/crates/passes/src/storage_read_forwarding/mod.rs @@ -19,6 +19,11 @@ //! This pass only handles lowered static mapping intrinsics. Dynamic storage //! reads carry target program and network operands and are left to a separate //! optimization so their invalidation model can be reviewed independently. +//! +//! Read facts are always cleared at branch joins. Aliases created inside a +//! branch are only exposed to the matching arm of same-condition SSA join +//! ternaries emitted immediately after the branch, so branch-local definitions +//! are not treated as globally available after the join. use crate::{CompilerState, Pass}; @@ -45,6 +50,9 @@ impl Pass for StorageReadForwarding { state, reads: Default::default(), aliases: Default::default(), + then_join_aliases: Default::default(), + otherwise_join_aliases: Default::default(), + join_condition: None, in_finalize_context: false, }; diff --git a/crates/passes/src/storage_read_forwarding/visitor.rs b/crates/passes/src/storage_read_forwarding/visitor.rs index 1451416e46..c1e9cf42ac 100644 --- a/crates/passes/src/storage_read_forwarding/visitor.rs +++ b/crates/passes/src/storage_read_forwarding/visitor.rs @@ -39,6 +39,9 @@ pub struct StorageReadForwardingVisitor<'a> { pub state: &'a mut CompilerState, pub(super) reads: IndexMap, pub(super) aliases: IndexMap, + pub(super) then_join_aliases: IndexMap, + pub(super) otherwise_join_aliases: IndexMap, + pub(super) join_condition: Option, pub(super) in_finalize_context: bool, } @@ -50,6 +53,13 @@ impl StorageReadForwardingVisitor<'_> { pub(super) fn clear_function_state(&mut self) { self.reads.clear(); self.aliases.clear(); + self.clear_join_aliases(); + } + + pub(super) fn clear_join_aliases(&mut self) { + self.then_join_aliases.clear(); + self.otherwise_join_aliases.clear(); + self.join_condition = None; } pub(super) fn local_alias(&self, name: Symbol) -> Option { @@ -63,6 +73,22 @@ impl StorageReadForwardingVisitor<'_> { (current != name).then_some(current) } + pub(super) fn same_join_condition(&self, condition: &Expression) -> bool { + let Some(join_condition) = &self.join_condition else { + return false; + }; + + match (condition, join_condition) { + (Expression::Path(left), Expression::Path(right)) => { + let left = left.try_local_symbol(); + let right = right.try_local_symbol(); + left == right && left.is_some() + } + (Expression::Literal(left), Expression::Literal(right)) => left.variant == right.variant, + _ => false, + } + } + pub(super) fn atom(expr: &Expression) -> Option { match expr { Expression::Literal(lit) => Some(Atom::Literal(lit.variant.clone())), From 3776839399e5e2d3b4fb153dcede881f2b036209 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 17:35:29 +0800 Subject: [PATCH 29/35] Retry CI From 3331039a4eab6b7bcd782950b6d8fb1341f7e6e5 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 19:16:42 +0800 Subject: [PATCH 30/35] Preserve forwarded storage read definitions --- .../passes/src/storage_read_forwarding/ast.rs | 164 +++++++++++++++++- .../passes/src/storage_read_forwarding/mod.rs | 5 + .../src/storage_read_forwarding/visitor.rs | 46 +++-- .../assert_barrier.out | 4 +- .../conditional_branch_reads.out | 4 +- .../repeated_mapping_reads.out | 7 +- .../repeated_vector_len.out | 7 +- 7 files changed, 200 insertions(+), 37 deletions(-) diff --git a/crates/passes/src/storage_read_forwarding/ast.rs b/crates/passes/src/storage_read_forwarding/ast.rs index 867c5c68a6..446618b18a 100644 --- a/crates/passes/src/storage_read_forwarding/ast.rs +++ b/crates/passes/src/storage_read_forwarding/ast.rs @@ -176,16 +176,22 @@ impl AstReconstructor for StorageReadForwardingVisitor<'_> { return (input.into(), ()); }; - let Expression::Intrinsic(intrinsic) = &input.value else { - return (input.into(), ()); - }; + if self.in_finalize_context + && let Expression::Path(path) = &input.value + && let Some(target) = path.try_local_symbol() + { + self.insert_alias(place.name, target); + } if self.in_finalize_context - && let Some(read) = Self::storage_read(intrinsic) + && let Expression::Intrinsic(intrinsic) = &input.value + && let Some(read) = self.storage_read(intrinsic) { if let Some(existing) = self.reads.get(&read).copied() { - self.aliases.insert(place.name, existing); - return (Statement::dummy(), ()); + let existing = self.canonical_local(existing); + self.insert_alias(place.name, existing); + input.value = self.local_expression_like(existing, &input.value); + return (input.into(), ()); } self.reads.insert(read, place.name); } @@ -243,7 +249,11 @@ mod tests { } fn storage_read(state: &mut CompilerState) -> Expression { - let arguments = vec![local(state, "data"), local(state, "key"), u8_lit(state, "0")]; + storage_read_with_key(state, "key") + } + + fn storage_read_with_key(state: &mut CompilerState, key: &str) -> Expression { + let arguments = vec![local(state, "data"), local(state, key), u8_lit(state, "0")]; IntrinsicExpression { name: sym::_mapping_get_or_use, type_parameters: Vec::new(), @@ -256,6 +266,20 @@ mod tests { .into() } + fn storage_contains(state: &mut CompilerState) -> Expression { + let arguments = vec![local(state, "data"), local(state, "key")]; + IntrinsicExpression { + name: sym::_mapping_contains, + type_parameters: Vec::new(), + input_types: Vec::new(), + return_types: Vec::new(), + arguments, + span: Default::default(), + id: state.node_builder.next_id(), + } + .into() + } + #[test] fn preserves_branch_alias_for_ssa_join_operand() { create_session_if_not_set_then(|_| { @@ -320,7 +344,10 @@ mod tests { output.contains("let x3 = flag ? x1 : x0"), "expected the SSA join to use the surviving branch definition:\n{output}" ); - assert!(!output.contains("x2"), "removed SSA definition is still referenced:\n{output}"); + assert!( + output.contains("let x2 = x1"), + "expected the repeated read definition to remain as a copy:\n{output}" + ); }); } @@ -381,6 +408,10 @@ mod tests { output.contains("let x3 = other ? x2 : x0"), "branch-local alias was applied outside the matching SSA join:\n{output}" ); + assert!( + output.contains("let x2 = x1"), + "non-matching join kept a reference to x2 without preserving its definition:\n{output}" + ); }); } @@ -441,6 +472,123 @@ mod tests { output.contains("let x3 = flag ? x0 : x2"), "then-branch alias was applied to the opposite ternary arm:\n{output}" ); + assert!( + output.contains("let x2 = x1"), + "opposite-arm join kept a reference to x2 without preserving its definition:\n{output}" + ); + }); + } + + #[test] + fn canonicalizes_aliased_storage_read_keys() { + create_session_if_not_set_then(|_| { + let mut state = CompilerState { node_builder: Rc::new(NodeBuilder::default()), ..Default::default() }; + + let key_alias = local(&mut state, "key"); + let first_read = storage_read_with_key(&mut state, "key"); + let second_read = storage_read_with_key(&mut state, "key2"); + let block = Block { + statements: vec![ + definition(&mut state, "key2", key_alias), + definition(&mut state, "x1", first_read), + definition(&mut state, "x2", second_read), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let mut visitor = StorageReadForwardingVisitor { + state: &mut state, + reads: Default::default(), + aliases: Default::default(), + then_join_aliases: Default::default(), + otherwise_join_aliases: Default::default(), + join_condition: None, + in_finalize_context: true, + }; + + let output = visitor.reconstruct_block(block).0.to_string(); + + assert_eq!( + output.matches("_mapping_get_or_use").count(), + 1, + "expected aliased storage-read keys to share the same read fact:\n{output}" + ); + assert!( + output.contains("let x2 = x1"), + "expected the aliased-key duplicate read to remain as a copy:\n{output}" + ); + }); + } + + #[test] + fn canonicalizes_aliased_join_conditions() { + create_session_if_not_set_then(|_| { + let mut state = CompilerState { node_builder: Rc::new(NodeBuilder::default()), ..Default::default() }; + + let join = TernaryExpression { + condition: local(&mut state, "cond2"), + if_true: local(&mut state, "x2"), + if_false: local(&mut state, "x0"), + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let initial_value = u8_lit(&mut state, "0"); + let first_condition = storage_contains(&mut state); + let second_condition = storage_contains(&mut state); + let first_read = storage_read(&mut state); + let second_read = storage_read(&mut state); + let block = Block { + statements: vec![ + definition(&mut state, "x0", initial_value), + definition(&mut state, "cond1", first_condition), + definition(&mut state, "cond2", second_condition), + ConditionalStatement { + condition: local(&mut state, "cond2"), + then: Block { + statements: vec![ + definition(&mut state, "x1", first_read), + definition(&mut state, "x2", second_read), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }, + otherwise: None, + span: Default::default(), + id: state.node_builder.next_id(), + } + .into(), + definition(&mut state, "x3", join.into()), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let mut visitor = StorageReadForwardingVisitor { + state: &mut state, + reads: Default::default(), + aliases: Default::default(), + then_join_aliases: Default::default(), + otherwise_join_aliases: Default::default(), + join_condition: None, + in_finalize_context: true, + }; + + let output = visitor.reconstruct_block(block).0.to_string(); + + assert!( + output.contains("let cond2 = cond1"), + "expected the repeated branch condition read to remain as a copy:\n{output}" + ); + assert!( + output.contains("let x3 = cond1 ? x1 : x0"), + "expected same-condition SSA join matching to use canonical condition aliases:\n{output}" + ); + assert!( + output.contains("let x2 = x1"), + "expected the branch-local repeated read to remain as a copy:\n{output}" + ); }); } } diff --git a/crates/passes/src/storage_read_forwarding/mod.rs b/crates/passes/src/storage_read_forwarding/mod.rs index 767024ff19..1e338fc54c 100644 --- a/crates/passes/src/storage_read_forwarding/mod.rs +++ b/crates/passes/src/storage_read_forwarding/mod.rs @@ -24,6 +24,11 @@ //! branch are only exposed to the matching arm of same-condition SSA join //! ternaries emitted immediately after the branch, so branch-local definitions //! are not treated as globally available after the join. +//! +//! Duplicate storage reads are rewritten to explicit local copies instead of +//! removed in this pass. This keeps the transformed AST well-formed even when a +//! later path-sensitive use is intentionally not rewritten; the following DCE +//! pass is responsible for deleting copies whose uses were fully forwarded. use crate::{CompilerState, Pass}; diff --git a/crates/passes/src/storage_read_forwarding/visitor.rs b/crates/passes/src/storage_read_forwarding/visitor.rs index c1e9cf42ac..9a28bff704 100644 --- a/crates/passes/src/storage_read_forwarding/visitor.rs +++ b/crates/passes/src/storage_read_forwarding/visitor.rs @@ -16,7 +16,7 @@ use crate::CompilerState; -use leo_ast::{Expression, IntrinsicExpression, LiteralVariant, Location}; +use leo_ast::{Expression, Identifier, IntrinsicExpression, LiteralVariant, Location, Node as _, Path}; use leo_span::{Symbol, sym}; use indexmap::IndexMap; @@ -73,6 +73,17 @@ impl StorageReadForwardingVisitor<'_> { (current != name).then_some(current) } + pub(super) fn canonical_local(&self, name: Symbol) -> Symbol { + self.local_alias(name).unwrap_or(name) + } + + pub(super) fn insert_alias(&mut self, alias: Symbol, target: Symbol) { + let target = self.canonical_local(target); + if alias != target { + self.aliases.insert(alias, target); + } + } + pub(super) fn same_join_condition(&self, condition: &Expression) -> bool { let Some(join_condition) = &self.join_condition else { return false; @@ -80,8 +91,8 @@ impl StorageReadForwardingVisitor<'_> { match (condition, join_condition) { (Expression::Path(left), Expression::Path(right)) => { - let left = left.try_local_symbol(); - let right = right.try_local_symbol(); + let left = left.try_local_symbol().map(|name| self.canonical_local(name)); + let right = right.try_local_symbol().map(|name| self.canonical_local(name)); left == right && left.is_some() } (Expression::Literal(left), Expression::Literal(right)) => left.variant == right.variant, @@ -89,36 +100,45 @@ impl StorageReadForwardingVisitor<'_> { } } - pub(super) fn atom(expr: &Expression) -> Option { + pub(super) fn atom(&self, expr: &Expression) -> Option { match expr { Expression::Literal(lit) => Some(Atom::Literal(lit.variant.clone())), Expression::Path(path) => path .try_local_symbol() - .map(Atom::Local) + .map(|name| Atom::Local(self.canonical_local(name))) .or_else(|| path.try_global_location().cloned().map(Atom::Global)), _ => None, } } - pub(super) fn storage_read(intrinsic: &IntrinsicExpression) -> Option { + pub(super) fn storage_read(&self, intrinsic: &IntrinsicExpression) -> Option { match intrinsic.name { sym::_mapping_get => Some(StorageRead::Get { - mapping: Self::atom(intrinsic.arguments.first()?)?, - key: Self::atom(intrinsic.arguments.get(1)?)?, + mapping: self.atom(intrinsic.arguments.first()?)?, + key: self.atom(intrinsic.arguments.get(1)?)?, }), sym::_mapping_get_or_use => Some(StorageRead::GetOrUse { - mapping: Self::atom(intrinsic.arguments.first()?)?, - key: Self::atom(intrinsic.arguments.get(1)?)?, - default: Self::atom(intrinsic.arguments.get(2)?)?, + mapping: self.atom(intrinsic.arguments.first()?)?, + key: self.atom(intrinsic.arguments.get(1)?)?, + default: self.atom(intrinsic.arguments.get(2)?)?, }), sym::_mapping_contains => Some(StorageRead::Contains { - mapping: Self::atom(intrinsic.arguments.first()?)?, - key: Self::atom(intrinsic.arguments.get(1)?)?, + mapping: self.atom(intrinsic.arguments.first()?)?, + key: self.atom(intrinsic.arguments.get(1)?)?, }), _ => None, } } + pub(super) fn local_expression_like(&mut self, symbol: Symbol, old_value: &Expression) -> Expression { + let ty = self.state.type_table.get(&old_value.id()); + let path = Path::from(Identifier::new(symbol, self.state.node_builder.next_id())).to_local(); + if let Some(ty) = ty { + self.state.type_table.insert(path.id(), ty); + } + path.into() + } + pub(super) fn is_effect_boundary(intrinsic: &IntrinsicExpression) -> bool { matches!(intrinsic.name, sym::_mapping_set | sym::_mapping_remove | sym::_final_run) } diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index cbf440fcb4..d6415de009 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -8,8 +8,7 @@ program test.aleo { fn keep_after_assert(key$$0: u32) -> Final { let $var$1 = async { let a = _mapping_get_or_use(test.aleo::data, key, 0u8); - { - } + let b = a; assert_eq(a, a); let c = _mapping_get_or_use(test.aleo::data, key, 0u8); _mapping_set(test.aleo::out, false, a + c); @@ -17,4 +16,3 @@ program test.aleo { return $var$1; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index ec1b28d85c..9067590e42 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -9,8 +9,7 @@ program test.aleo { let $var$2 = async { let condition$3 = flag; let a = _mapping_get_or_use(test.aleo::data, key, 0u8); - { - } + let b = a; _mapping_set(test.aleo::out, false, a + a); { } @@ -20,4 +19,3 @@ program test.aleo { return $var$2; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index 2c445d2b1e..5fab8a93b2 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -8,8 +8,7 @@ program test.aleo { fn reuse_before_write(key$$0: u32) -> Final { let $var$1 = async { let a = _mapping_get_or_use(test.aleo::data, key, 0u8); - { - } + let b = a; _mapping_set(test.aleo::out, false, a + a); }; return $var$1; @@ -26,8 +25,7 @@ program test.aleo { fn reuse_contains(key$$4: u32) -> Final { let $var$5 = async { let a = _mapping_contains(test.aleo::data, key); - { - } + let b = a; let one = 1u8; let zero = 0u8; let value = a ? one : zero; @@ -36,4 +34,3 @@ program test.aleo { return $var$5; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index 87dc8d811d..168842bd9d 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -9,8 +9,7 @@ program test.aleo { fn reuse_len() -> Final { let $var$1 = async { let a = _mapping_get_or_use(test.aleo::data__len__, false, 0u32); - { - } + let b = a; _mapping_set(test.aleo::out, false, a + a); }; return $var$1; @@ -18,8 +17,7 @@ program test.aleo { fn keep_after_push() -> Final { let $var$2 = async { let a = _mapping_get_or_use(test.aleo::data__len__, false, 0u32); - { - } + let $len_var$3 = a; _mapping_set(test.aleo::data__len__, false, a + 1u32); _mapping_set(test.aleo::data__, a, 1u8); let b = _mapping_get_or_use(test.aleo::data__len__, false, 0u32); @@ -28,4 +26,3 @@ program test.aleo { return $var$2; } } - From c301bf415dbef7f3a19fe277823b5fe29d68ad1d Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 19:29:24 +0800 Subject: [PATCH 31/35] Fix storage read forwarding expectation --- .../passes/storage_read_forwarding/repeated_vector_len.out | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index 168842bd9d..b6bb6f5eb2 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -17,7 +17,7 @@ program test.aleo { fn keep_after_push() -> Final { let $var$2 = async { let a = _mapping_get_or_use(test.aleo::data__len__, false, 0u32); - let $len_var$3 = a; + let $len_var$0 = a; _mapping_set(test.aleo::data__len__, false, a + 1u32); _mapping_set(test.aleo::data__, a, 1u8); let b = _mapping_get_or_use(test.aleo::data__len__, false, 0u32); From 43709658697f0a787c1e512cc56b220e0704a637 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 19:39:25 +0800 Subject: [PATCH 32/35] Match storage forwarding fixture output --- .../passes/storage_read_forwarding/assert_barrier.out | 1 + .../passes/storage_read_forwarding/conditional_branch_reads.out | 1 + .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 + .../passes/storage_read_forwarding/repeated_vector_len.out | 1 + 4 files changed, 4 insertions(+) diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index d6415de009..5fcdde411a 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -16,3 +16,4 @@ program test.aleo { return $var$1; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index 9067590e42..c844d864e5 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -19,3 +19,4 @@ program test.aleo { return $var$2; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index 5fab8a93b2..1beed075c2 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -34,3 +34,4 @@ program test.aleo { return $var$5; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index b6bb6f5eb2..db98f1a43c 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -26,3 +26,4 @@ program test.aleo { return $var$2; } } + From 43cea5c76e273c815315031ed66e34619ce50846 Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 20:09:43 +0800 Subject: [PATCH 33/35] Clean storage forwarding expectation EOFs --- .../passes/storage_read_forwarding/assert_barrier.out | 1 - .../passes/storage_read_forwarding/conditional_branch_reads.out | 1 - .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 - .../passes/storage_read_forwarding/repeated_vector_len.out | 1 - 4 files changed, 4 deletions(-) diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index 5fcdde411a..d6415de009 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -16,4 +16,3 @@ program test.aleo { return $var$1; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index c844d864e5..9067590e42 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -19,4 +19,3 @@ program test.aleo { return $var$2; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index 1beed075c2..5fab8a93b2 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -34,4 +34,3 @@ program test.aleo { return $var$5; } } - diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index db98f1a43c..b6bb6f5eb2 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -26,4 +26,3 @@ program test.aleo { return $var$2; } } - From 76f162b1585a9991facd7e66bcf2013090d0c94d Mon Sep 17 00:00:00 2001 From: "Cyne Jarvis J. Zarceno" Date: Fri, 26 Jun 2026 20:19:00 +0800 Subject: [PATCH 34/35] Revert "Clean storage forwarding expectation EOFs" This reverts commit 43cea5c76e273c815315031ed66e34619ce50846. --- .../passes/storage_read_forwarding/assert_barrier.out | 1 + .../passes/storage_read_forwarding/conditional_branch_reads.out | 1 + .../passes/storage_read_forwarding/repeated_mapping_reads.out | 1 + .../passes/storage_read_forwarding/repeated_vector_len.out | 1 + 4 files changed, 4 insertions(+) diff --git a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out index d6415de009..5fcdde411a 100644 --- a/tests/expectations/passes/storage_read_forwarding/assert_barrier.out +++ b/tests/expectations/passes/storage_read_forwarding/assert_barrier.out @@ -16,3 +16,4 @@ program test.aleo { return $var$1; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out index 9067590e42..c844d864e5 100644 --- a/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/conditional_branch_reads.out @@ -19,3 +19,4 @@ program test.aleo { return $var$2; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out index 5fab8a93b2..1beed075c2 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_mapping_reads.out @@ -34,3 +34,4 @@ program test.aleo { return $var$5; } } + diff --git a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out index b6bb6f5eb2..db98f1a43c 100644 --- a/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out +++ b/tests/expectations/passes/storage_read_forwarding/repeated_vector_len.out @@ -26,3 +26,4 @@ program test.aleo { return $var$2; } } + From 3997cf33615c0dbfa23bd2bee9068455f687d1f6 Mon Sep 17 00:00:00 2001 From: Kuhai9801 <165563006+Kuhai9801@users.noreply.github.com> Date: Sat, 27 Jun 2026 15:20:32 +0800 Subject: [PATCH 35/35] Tighten storage read join alias lifetime --- .../passes/src/storage_read_forwarding/ast.rs | 83 +++++++++++++++++++ .../passes/src/storage_read_forwarding/mod.rs | 3 +- .../src/storage_read_forwarding/visitor.rs | 4 + 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/crates/passes/src/storage_read_forwarding/ast.rs b/crates/passes/src/storage_read_forwarding/ast.rs index 446618b18a..ebb2002149 100644 --- a/crates/passes/src/storage_read_forwarding/ast.rs +++ b/crates/passes/src/storage_read_forwarding/ast.rs @@ -169,7 +169,18 @@ impl AstReconstructor for StorageReadForwardingVisitor<'_> { (input.into(), ()) } + fn reconstruct_const(&mut self, mut input: ConstDeclaration) -> (Statement, Self::AdditionalOutput) { + self.clear_join_aliases(); + input.type_ = self.reconstruct_type(input.type_).0; + input.value = self.reconstruct_expression(input.value, &()).0; + (input.into(), ()) + } + fn reconstruct_definition(&mut self, mut input: DefinitionStatement) -> (Statement, Self::AdditionalOutput) { + if !self.is_matching_join_ternary(&input.value) { + self.clear_join_aliases(); + } + input.value = self.reconstruct_expression(input.value, &()).0; let DefinitionPlace::Single(place) = &input.place else { @@ -208,6 +219,12 @@ impl AstReconstructor for StorageReadForwardingVisitor<'_> { (input.into(), ()) } + fn reconstruct_return(&mut self, mut input: ReturnStatement) -> (Statement, Self::AdditionalOutput) { + self.clear_join_aliases(); + input.expression = self.reconstruct_expression(input.expression, &()).0; + (input.into(), ()) + } + fn reconstruct_assign(&mut self, _input: AssignStatement) -> (Statement, Self::AdditionalOutput) { panic!("`AssignStatement`s should not exist in the AST at this phase of compilation."); } @@ -415,6 +432,72 @@ mod tests { }); } + #[test] + fn does_not_apply_branch_alias_after_intervening_definition() { + create_session_if_not_set_then(|_| { + let mut state = CompilerState { node_builder: Rc::new(NodeBuilder::default()), ..Default::default() }; + + let condition = local(&mut state, "flag"); + let join = TernaryExpression { + condition: local(&mut state, "flag"), + if_true: local(&mut state, "x2"), + if_false: local(&mut state, "x0"), + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let initial_value = u8_lit(&mut state, "0"); + let first_read = storage_read(&mut state); + let second_read = storage_read(&mut state); + let intervening_value = u8_lit(&mut state, "1"); + let block = Block { + statements: vec![ + definition(&mut state, "x0", initial_value), + ConditionalStatement { + condition, + then: Block { + statements: vec![ + definition(&mut state, "x1", first_read), + definition(&mut state, "x2", second_read), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }, + otherwise: None, + span: Default::default(), + id: state.node_builder.next_id(), + } + .into(), + definition(&mut state, "tmp", intervening_value), + definition(&mut state, "x3", join.into()), + ], + span: Default::default(), + id: state.node_builder.next_id(), + }; + + let mut visitor = StorageReadForwardingVisitor { + state: &mut state, + reads: Default::default(), + aliases: Default::default(), + then_join_aliases: Default::default(), + otherwise_join_aliases: Default::default(), + join_condition: None, + in_finalize_context: true, + }; + + let output = visitor.reconstruct_block(block).0.to_string(); + + assert!( + output.contains("let x3 = flag ? x2 : x0"), + "branch-local alias was applied after a non-join definition:\n{output}" + ); + assert!( + output.contains("let x2 = x1"), + "later same-condition join kept a reference to x2 without preserving its definition:\n{output}" + ); + }); + } + #[test] fn does_not_apply_then_branch_alias_to_otherwise_join_operand() { create_session_if_not_set_then(|_| { diff --git a/crates/passes/src/storage_read_forwarding/mod.rs b/crates/passes/src/storage_read_forwarding/mod.rs index 1e338fc54c..cc7f211f97 100644 --- a/crates/passes/src/storage_read_forwarding/mod.rs +++ b/crates/passes/src/storage_read_forwarding/mod.rs @@ -23,7 +23,8 @@ //! Read facts are always cleared at branch joins. Aliases created inside a //! branch are only exposed to the matching arm of same-condition SSA join //! ternaries emitted immediately after the branch, so branch-local definitions -//! are not treated as globally available after the join. +//! are not treated as globally available after the join. Pending branch aliases +//! are discarded as soon as a non-join statement is encountered. //! //! Duplicate storage reads are rewritten to explicit local copies instead of //! removed in this pass. This keeps the transformed AST well-formed even when a diff --git a/crates/passes/src/storage_read_forwarding/visitor.rs b/crates/passes/src/storage_read_forwarding/visitor.rs index 9a28bff704..66b55917b8 100644 --- a/crates/passes/src/storage_read_forwarding/visitor.rs +++ b/crates/passes/src/storage_read_forwarding/visitor.rs @@ -100,6 +100,10 @@ impl StorageReadForwardingVisitor<'_> { } } + pub(super) fn is_matching_join_ternary(&self, expression: &Expression) -> bool { + matches!(expression, Expression::Ternary(ternary) if self.same_join_condition(&ternary.condition)) + } + pub(super) fn atom(&self, expr: &Expression) -> Option { match expr { Expression::Literal(lit) => Some(Atom::Literal(lit.variant.clone())),