Skip to content

Commit f3affcd

Browse files
committed
Rerun SSA const propagation after final SSA
1 parent 8a39047 commit f3affcd

59 files changed

Lines changed: 1863 additions & 1744 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.circleci/continue_config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ jobs:
495495
cargo-doc:
496496
docker:
497497
- image: "cimg/rust:1.96.0"
498-
resource_class: large
498+
resource_class: xlarge
499499
steps:
500500
- only-run-if:
501501
scope-is: "full"

crates/compiler/src/compiler.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,13 @@ impl Compiler {
450450
// Flattening may produce ternary expressions not in SSA form.
451451
self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
452452

453-
self.do_pass::<SsaConstPropagation>(())?;
453+
self.do_pass::<SsaConstPropagation>(SsaConstPropagationMode::Full)?;
454+
455+
self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
456+
457+
// SSA repair can expose lowered Optional unwraps after const propagation.
458+
// Let the existing const-prop owner erase only that late pattern before CSE/DCE.
459+
self.do_pass::<SsaConstPropagation>(SsaConstPropagationMode::LateOptionalUnwrap)?;
454460

455461
self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
456462

crates/passes/src/ssa_const_propagation/ast.rs

Lines changed: 85 additions & 102 deletions
Large diffs are not rendered by default.

crates/passes/src/ssa_const_propagation/mod.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,32 @@ mod program;
3333
mod visitor;
3434
pub use visitor::SsaConstPropagationVisitor;
3535

36+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37+
pub enum SsaConstPropagationMode {
38+
Full,
39+
LateOptionalUnwrap,
40+
}
41+
3642
pub struct SsaConstPropagation;
3743

3844
impl Pass for SsaConstPropagation {
39-
type Input = ();
45+
type Input = SsaConstPropagationMode;
4046
type Output = ();
4147

4248
const NAME: &str = "SsaConstPropagation";
4349

44-
fn do_pass(_input: Self::Input, state: &mut crate::CompilerState) -> Result<Self::Output> {
50+
fn do_pass(input: Self::Input, state: &mut crate::CompilerState) -> Result<Self::Output> {
4551
// Run the pass in a loop until no changes are made.
4652
for _ in 0..1024 {
4753
let ast = std::mem::take(&mut state.ast);
4854
let mut visitor = SsaConstPropagationVisitor {
4955
state,
56+
mode: input,
5057
program: Symbol::intern(""),
5158
constants: Default::default(),
5259
atom_fielded_composites: Default::default(),
60+
aliases: Default::default(),
61+
composites: Default::default(),
5362
changed: false,
5463
};
5564

crates/passes/src/ssa_const_propagation/program.rs

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
use super::SsaConstPropagationVisitor;
1818

19-
use leo_ast::{AstReconstructor, Constructor, Function, Library, ProgramScope, Statement, UnitReconstructor};
19+
use leo_ast::{AstReconstructor, Constructor, Function, Library, Location, ProgramScope, Statement, UnitReconstructor};
2020

2121
impl UnitReconstructor for SsaConstPropagationVisitor<'_> {
2222
fn reconstruct_library(&mut self, input: Library) -> Library {
@@ -27,6 +27,11 @@ impl UnitReconstructor for SsaConstPropagationVisitor<'_> {
2727

2828
fn reconstruct_program_scope(&mut self, input: ProgramScope) -> ProgramScope {
2929
self.program = input.program_id.as_symbol();
30+
self.composites = input
31+
.composites
32+
.iter()
33+
.map(|(_, composite)| (Location::new(self.program, vec![composite.identifier.name]), composite.clone()))
34+
.collect();
3035

3136
ProgramScope {
3237
program_id: input.program_id,
@@ -54,25 +59,28 @@ impl UnitReconstructor for SsaConstPropagationVisitor<'_> {
5459
}
5560

5661
fn reconstruct_function(&mut self, mut input: Function) -> Function {
57-
// Reset the per-function maps.
58-
// In SSA form, each function has its own scope, so we can clear the maps.
59-
self.constants.clear();
60-
self.atom_fielded_composites.clear();
62+
if self.tracks_optional_unwraps() && !input.variant.is_finalize_context() {
63+
return input;
64+
}
65+
66+
// In SSA form, each function has its own scope, so analysis facts are local.
67+
self.clear_tracked_values();
6168
// Traverse the function body.
6269
input.block = self.reconstruct_block(input.block).0;
63-
self.constants.clear();
64-
self.atom_fielded_composites.clear();
70+
self.clear_tracked_values();
6571
input
6672
}
6773

6874
fn reconstruct_constructor(&mut self, mut input: Constructor) -> Constructor {
69-
// Reset the per-constructor maps.
70-
self.constants.clear();
71-
self.atom_fielded_composites.clear();
75+
if self.tracks_optional_unwraps() {
76+
return input;
77+
}
78+
79+
// Constructors also have their own SSA scope.
80+
self.clear_tracked_values();
7281
// Traverse the constructor body.
7382
input.block = self.reconstruct_block(input.block).0;
74-
self.constants.clear();
75-
self.atom_fielded_composites.clear();
83+
self.clear_tracked_values();
7684
input
7785
}
7886
}

crates/passes/src/ssa_const_propagation/visitor.rs

Lines changed: 110 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,23 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
1616

17+
use super::SsaConstPropagationMode;
18+
1719
use crate::CompilerState;
1820

19-
use leo_ast::{Expression, FromStrRadix, Literal, LiteralVariant, Location, Node, NodeID, const_eval::Value};
21+
use leo_ast::{
22+
Composite,
23+
CompositeExpression,
24+
Expression,
25+
FromStrRadix,
26+
Literal,
27+
LiteralVariant,
28+
Location,
29+
Node,
30+
NodeID,
31+
Type,
32+
const_eval::Value,
33+
};
2034
use leo_errors::Formatted;
2135
use leo_span::{Span, Symbol};
2236

@@ -25,16 +39,23 @@ use indexmap::IndexMap;
2539
/// Visitor that propagates constant values through the program.
2640
pub struct SsaConstPropagationVisitor<'a> {
2741
pub state: &'a mut CompilerState,
42+
/// Which subset of SSA const-prop rewrites this invocation is allowed to perform.
43+
pub mode: SsaConstPropagationMode,
2844
/// Current program being analyzed.
2945
pub program: Symbol,
3046
/// Maps variable names to their constant values.
3147
/// Only variables assigned constant values are tracked here.
3248
pub constants: IndexMap<Symbol, Value>,
33-
/// Maps variable names bound to a composite RHS where every field
34-
/// initializer is an atom to the atom each field was initialized with.
49+
/// Maps variable names bound to a composite RHS - where every field
50+
/// initializer is an atom - to the atom each field was initialized with.
3551
/// Used to forward `x.field` to the stored atom without rematerializing
36-
/// the enclosing struct effectively scalarizing short-lived aggregates.
52+
/// the enclosing struct - effectively scalarizing short-lived aggregates.
3753
pub atom_fielded_composites: IndexMap<Symbol, IndexMap<Symbol, Expression>>,
54+
/// Maps local variables that are simple aliases of atom-fielded composites.
55+
pub aliases: IndexMap<Symbol, Symbol>,
56+
/// Struct definitions visible in the current program scope, including
57+
/// compiler-generated Optional wrappers added after the symbol table.
58+
pub composites: IndexMap<Location, Composite>,
3859
/// Have we actually modified the program at all?
3960
pub changed: bool,
4061
}
@@ -46,6 +67,25 @@ pub fn is_atom(expr: &Expression) -> bool {
4667
matches!(expr, Expression::Path(_) | Expression::Literal(_))
4768
}
4869

70+
pub(super) fn optional_composite_atoms(composite: &CompositeExpression) -> Option<IndexMap<Symbol, Expression>> {
71+
if composite.base.is_some() || composite.members.len() != 2 {
72+
return None;
73+
}
74+
75+
let mut fields = IndexMap::with_capacity(composite.members.len());
76+
for member in &composite.members {
77+
let expr = member.expression.as_ref()?;
78+
if !is_atom(expr) {
79+
return None;
80+
}
81+
fields.insert(member.identifier.name, expr.clone());
82+
}
83+
84+
fields.get(&Symbol::intern("is_some"))?;
85+
fields.get(&Symbol::intern("val"))?;
86+
Some(fields)
87+
}
88+
4989
/// Parse a numeric literal string, handling underscores and radix prefixes (0x, 0o, 0b).
5090
fn parse_literal_value(s: &str) -> Option<i128> {
5191
let clean = s.replace('_', "");
@@ -77,20 +117,75 @@ pub fn is_one_literal(lit: &Literal) -> bool {
77117
}
78118
}
79119

80-
/// Check if two expressions refer to the same SSA variable.
81-
/// In SSA form, each variable name is unique, so name equality implies value equality.
82-
pub fn same_ssa_atom(a: &Expression, b: &Expression) -> bool {
83-
match (a, b) {
84-
(Expression::Path(pa), Expression::Path(pb)) => {
85-
let sa = pa.try_local_symbol();
86-
let sb = pb.try_local_symbol();
87-
sa == sb && sa.is_some()
120+
impl SsaConstPropagationVisitor<'_> {
121+
pub(super) fn runs_full_const_prop(&self) -> bool {
122+
self.mode == SsaConstPropagationMode::Full
123+
}
124+
125+
pub(super) fn forwards_composite_members(&self) -> bool {
126+
self.runs_full_const_prop()
127+
}
128+
129+
pub(super) fn tracks_optional_unwraps(&self) -> bool {
130+
self.mode == SsaConstPropagationMode::LateOptionalUnwrap
131+
}
132+
133+
/// Clear analysis state that is only valid within one SSA function body.
134+
pub(super) fn clear_tracked_values(&mut self) {
135+
self.constants.clear();
136+
self.atom_fielded_composites.clear();
137+
self.aliases.clear();
138+
}
139+
140+
pub(super) fn is_optional_wrapper_type(&self, ty: &Type) -> bool {
141+
let Type::Composite(composite_ty) = ty else {
142+
return false;
143+
};
144+
let location = composite_ty.path.expect_global_location();
145+
let Some(composite) =
146+
self.state.symbol_table.lookup_struct(self.program, location).or_else(|| self.composites.get(location))
147+
else {
148+
return false;
149+
};
150+
let [is_some, val] = composite.members.as_slice() else {
151+
return false;
152+
};
153+
is_some.identifier.name == Symbol::intern("is_some")
154+
&& matches!(is_some.type_, Type::Boolean)
155+
&& val.identifier.name == Symbol::intern("val")
156+
}
157+
158+
pub(super) fn is_optional_composite_expression(&self, composite: &CompositeExpression) -> bool {
159+
self.state.type_table.get(&composite.id()).is_some_and(|ty| self.is_optional_wrapper_type(&ty))
160+
}
161+
162+
pub(super) fn resolve_composite_alias(&self, mut name: Symbol) -> Symbol {
163+
for _ in 0..self.aliases.len() {
164+
let Some(alias) = self.aliases.get(&name).copied() else {
165+
break;
166+
};
167+
if alias == name {
168+
break;
169+
}
170+
name = alias;
88171
}
89-
_ => false,
172+
name
173+
}
174+
175+
pub(super) fn atom_for_composite_member(
176+
&self,
177+
expression: &Expression,
178+
field: Symbol,
179+
) -> Option<(Symbol, Expression)> {
180+
let Expression::Path(path) = expression else {
181+
return None;
182+
};
183+
let original_name = path.try_local_symbol()?;
184+
let name = self.resolve_composite_alias(original_name);
185+
let atom = self.atom_fielded_composites.get(&name)?.get(&field)?.clone();
186+
Some((name, atom))
90187
}
91-
}
92188

93-
impl SsaConstPropagationVisitor<'_> {
94189
/// Emit a `StaticAnalyzerError`.
95190
pub fn emit_err(&self, err: Formatted) {
96191
self.state.handler.emit_err(err);

crates/passes/src/test_passes.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ macro_rules! compiler_passes {
229229
(TypeChecking, (TypeCheckingInput::new(NetworkName::TestnetV0))),
230230
(Disambiguate, ()),
231231
(SsaForming, (SsaFormingInput { rename_defs: true })),
232-
(SsaConstPropagation, ()),
232+
(SsaConstPropagation, (SsaConstPropagationMode::Full)),
233233
]),
234234
(disambiguate_runner, [
235235
(GlobalVarsCollection, ()),

tests/expectations/cli/test_storage_from_unit_test/STDOUT

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
⚠️ No endpoint specified, defaulting to 'https://api.explorer.provable.com/v1'.
33

44
Leo 🔨 Compiling 'storage_demo.aleo'
5-
Leo Program size: 5.43 KB / 2000.00 KB
5+
Leo Program size: 5.37 KB / 2000.00 KB
66
Leo ✅ Compiled 'storage_demo.aleo' into Aleo instructions.
77
Leo ✅ Generated ABI for program 'storage_demo.aleo'.
88

99
Leo 🔨 Compiling 'test_storage_demo.aleo'
10-
Leo Program size: 20.35 KB / 2000.00 KB
10+
Leo Program size: 18.28 KB / 2000.00 KB
1111
Leo ✅ Compiled 'test_storage_demo.aleo' into Aleo instructions.
12-
Leo Import 'storage_demo.aleo': checksum = '[226u8, 104u8, 115u8, 137u8, 3u8, 83u8, 131u8, 89u8, 56u8, 167u8, 125u8, 100u8, 155u8, 220u8, 210u8, 53u8, 15u8, 25u8, 15u8, 152u8, 34u8, 88u8, 82u8, 31u8, 208u8, 157u8, 23u8, 145u8, 128u8, 36u8, 118u8, 40u8]'
12+
Leo Import 'storage_demo.aleo': checksum = '[181u8, 196u8, 11u8, 13u8, 176u8, 224u8, 84u8, 194u8, 122u8, 47u8, 204u8, 224u8, 175u8, 181u8, 20u8, 6u8, 165u8, 4u8, 170u8, 248u8, 93u8, 190u8, 224u8, 136u8, 197u8, 69u8, 42u8, 144u8, 52u8, 166u8, 103u8, 25u8]'
1313
Leo Loading the ledger from storage...
1414
Leo Loading the ledger from storage...
1515
Leo Loading the ledger from storage...

tests/expectations/cli/test_storage_from_unit_test/contents/build/storage_demo/storage_demo.aleo

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,9 @@ finalize bump_counter:
8282
contains counter__[false] into r0;
8383
get.or_use counter__[false] 0u64 into r1;
8484
ternary r0 r1 0u64 into r2;
85-
cast r0 r2 into r3 as Optional__DmN5CQ9hzeK;
86-
ternary r3.is_some r3.val 0u64 into r4;
87-
add r4 1u64 into r5;
88-
set r5 into counter__[false];
85+
ternary r0 r2 0u64 into r3;
86+
add r3 1u64 into r4;
87+
set r4 into counter__[false];
8988

9089
function clear_counter:
9190
async clear_counter into r0;

0 commit comments

Comments
 (0)