Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions crates/passes/src/monomorphization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,12 @@
//! cross-program edges are interpreted from the callee's perspective.
//! 4. **Carry through** external definitions the DFS did not reach (they are still needed for
//! stub assembly); drop current-program leftovers as dead code.
//! 5. **Assemble stubs** from the now-populated `reconstructed_*` maps. `FromLeo` stubs are
//! rebuilt directly; `FromLibrary` stubs are reconstructed so their items pick up any
//! monomorphized composite references.
//! 5. **Reconstruct constructors** in the current program and every `FromLeo` stub. Keep generic
//! functions available until all constructors have their specializations.
//! 6. **Prune originals**: an original generic is removed once every call to it has been
//! rewritten to a specialization. If unresolved calls remain, the original is kept so
//! subsequent runs of this pass (inside the `ConstPropUnrollAndMorphing` fixed-point loop)
//! can finish the job.
//! can finish the job. Then assemble all scopes, modules, and stubs from the reconstructed maps.

use crate::Pass;

Expand Down
152 changes: 83 additions & 69 deletions crates/passes/src/monomorphization/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,74 @@ impl UnitReconstructor for MonomorphizationVisitor<'_> {
}

fn reconstruct_program_scope(&mut self, input: ProgramScope) -> ProgramScope {
let top_level_program = input.program_id.as_symbol();
self.program = top_level_program;
self.program = input.program_id.as_symbol();

let mappings =
input.mappings.into_iter().map(|(id, mapping)| (id, self.reconstruct_mapping(mapping))).collect();
let storage_variables = input
.storage_variables
.into_iter()
.map(|(id, storage_variable)| (id, self.reconstruct_storage_variable(storage_variable)))
.collect();

let consts = input
.consts
.into_iter()
.map(|(i, c)| match self.reconstruct_const(c) {
(Statement::Const(declaration), _) => (i, declaration),
_ => panic!("`reconstruct_const` can only return `Statement::Const`"),
})
.collect();

// Collect only current-program top-level functions for this scope, then reorder so
// entry points precede finalize functions — the type checker expects that order.
let (entry_points, non_entry_points): (Vec<_>, Vec<_>) =
items_at_path(&self.reconstructed_functions, self.program, &[]).partition(|(_, f)| f.variant.is_entry());
let functions: Vec<_> = entry_points.into_iter().chain(non_entry_points).collect();

ProgramScope {
program_id: input.program_id,
parents: input.parents.into_iter().map(|(s, t)| (s, self.reconstruct_type(t).0)).collect(),
// Exclude generic composites that have been monomorphized — only their concrete
// specializations should appear in the output.
composites: items_at_path(&self.reconstructed_composites, self.program, &[])
.filter(|(_, c)| c.const_parameters.is_empty())
.collect(),
mappings,
storage_variables,
functions,
interfaces: input.interfaces.into_iter().map(|(i, int)| (i, self.reconstruct_interface(int))).collect(),
constructor: input.constructor,
consts,
span: input.span,
}
}

fn reconstruct_program(&mut self, mut input: Program) -> Program {
// Seed `function_map` and `composite_map` with every definition reachable from this
// program (stubs, libraries, current program). A single DFS from the current program's
// entry points then monomorphizes all of them in one pass; cross-program edges in the
// call graph make recursive per-stub passes unnecessary. Current-program inserts come
// last so they override any stub placeholders for overlapping keys.
self.program =
*input.program_scopes.first().expect("a program must have a single program scope at this stage").0;

for (_, stub) in &input.stubs {
for (loc, f) in stub_functions(stub) {
self.function_map.entry(loc).or_insert_with(|| f.clone());
}
for (loc, c) in stub_composites(stub) {
self.composite_map.entry(loc).or_insert_with(|| c.clone());
}
}
for (loc, f) in program_functions(&input) {
self.function_map.insert(loc, f.clone());
}
for (loc, c) in program_composites(&input) {
self.composite_map.insert(loc, c.clone());
}

let top_level_program = self.program;

// Composites first: a composite field may instantiate another generic composite, so
// post-order makes sure dependencies are monomorphized before their users.
Expand Down Expand Up @@ -116,25 +182,21 @@ impl UnitReconstructor for MonomorphizationVisitor<'_> {
}
}

let mappings =
input.mappings.into_iter().map(|(id, mapping)| (id, self.reconstruct_mapping(mapping))).collect();
let storage_variables = input
.storage_variables
.into_iter()
.map(|(id, storage_variable)| (id, self.reconstruct_storage_variable(storage_variable)))
.collect();

let consts = input
.consts
.into_iter()
.map(|(i, c)| match self.reconstruct_const(c) {
(Statement::Const(declaration), _) => (i, declaration),
_ => panic!("`reconstruct_const` can only return `Statement::Const`"),
})
.collect();

// The constructor is reconstructed last because nothing can call it.
let constructor = input.constructor.map(|c| self.reconstruct_constructor(c));
// Reconstruct all constructors before removing generic functions or collecting scope items.
for (program_name, scope) in input.program_scopes.iter_mut().chain(
input
.stubs
.values_mut()
.filter_map(|stub| match stub {
Stub::FromLeo { program, .. } => Some(program),
_ => None,
})
.flat_map(|program| program.program_scopes.iter_mut()),
) {
self.program = *program_name;
scope.constructor = scope.constructor.take().map(|c| self.reconstruct_constructor(c));
}
self.program = top_level_program;

// Drop original generic functions whose monomorphized instances have replaced them,
// unless they are still referenced by unresolved calls that later passes will retry.
Expand All @@ -144,54 +206,6 @@ impl UnitReconstructor for MonomorphizationVisitor<'_> {
!is_monomorphized || is_still_called
});

// Collect only current-program top-level functions for this scope, then reorder so
// entry points precede finalize functions — the type checker expects that order.
let (entry_points, non_entry_points): (Vec<_>, Vec<_>) =
items_at_path(&self.reconstructed_functions, self.program, &[]).partition(|(_, f)| f.variant.is_entry());
let functions: Vec<_> = entry_points.into_iter().chain(non_entry_points).collect();

ProgramScope {
program_id: input.program_id,
parents: input.parents.into_iter().map(|(s, t)| (s, self.reconstruct_type(t).0)).collect(),
// Exclude generic composites that have been monomorphized — only their concrete
// specializations should appear in the output.
composites: items_at_path(&self.reconstructed_composites, self.program, &[])
.filter(|(_, c)| c.const_parameters.is_empty())
.collect(),
mappings,
storage_variables,
functions,
interfaces: input.interfaces.into_iter().map(|(i, int)| (i, self.reconstruct_interface(int))).collect(),
constructor,
consts,
span: input.span,
}
}

fn reconstruct_program(&mut self, input: Program) -> Program {
// Seed `function_map` and `composite_map` with every definition reachable from this
// program (stubs, libraries, current program). A single DFS from the current program's
// entry points then monomorphizes all of them in one pass; cross-program edges in the
// call graph make recursive per-stub passes unnecessary. Current-program inserts come
// last so they override any stub placeholders for overlapping keys.
self.program =
*input.program_scopes.first().expect("a program must have a single program scope at this stage").0;

for (_, stub) in &input.stubs {
for (loc, f) in stub_functions(stub) {
self.function_map.entry(loc).or_insert_with(|| f.clone());
}
for (loc, c) in stub_composites(stub) {
self.composite_map.entry(loc).or_insert_with(|| c.clone());
}
}
for (loc, f) in program_functions(&input) {
self.function_map.insert(loc, f.clone());
}
for (loc, c) in program_composites(&input) {
self.composite_map.insert(loc, c.clone());
}

// Type checking depends on stubs coming out in the original insertion order, so
// snapshot the keys before partitioning.
let stub_key_order: Vec<_> = input.stubs.keys().cloned().collect();
Expand Down
3 changes: 1 addition & 2 deletions crates/passes/src/monomorphization/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,6 @@ impl MonomorphizationVisitor<'_> {
_ => panic!("`reconstruct_const` can only return `Statement::Const`"),
})
.collect();
let constructor = input.constructor.map(|c| self.reconstruct_constructor(c));

ProgramScope {
program_id: input.program_id,
Expand All @@ -219,7 +218,7 @@ impl MonomorphizationVisitor<'_> {
storage_variables,
functions,
interfaces: input.interfaces.into_iter().map(|(i, int)| (i, self.reconstruct_interface(int))).collect(),
constructor,
constructor: input.constructor,
consts,
span: input.span,
}
Expand Down
30 changes: 30 additions & 0 deletions tests/expectations/compiler/constructor/calls_in_constructors.out
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,33 @@ function food:
constructor:
get foo[0u8] into r0;
assert.eq r0 0u8;
assert.neq edition 1u16;
// --- Next Program --- //
import test.aleo;
program child.aleo;

function main:

constructor:
assert.neq edition 2u16;
// --- Next Program --- //
import child.aleo;
import test.aleo;
program parent.aleo;

function main:

constructor:
assert.eq edition 0u16;


---
Note: Treating dependencies as Aleo produces different results:

[ETYC0372005] Error: unknown function `test.aleo::checks::check_edition`
╭─[ compiler-test:10:9 ]
10 │ test.aleo::checks::check_edition::[2u16]();
│ Help: Check `test.aleo::checks::check_edition` for typos and confirm it is declared in this scope. If it lives in another program, import it with the program-qualified name (e.g. `credits.aleo::credits`).
────╯
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ view peek:
output r1 as u32.public;

constructor:
assert.neq entry/checksum peek/checksum;
assert.eq checksum checksum;
assert.eq edition 0u16;
assert.eq edition 0u16;
// --- Next Program --- //
import child.aleo;
Expand All @@ -27,4 +30,5 @@ finalize bar:
assert.neq child.aleo/entry/checksum child.aleo/peek/checksum;

constructor:
assert.eq edition 0u16;
assert.neq child.aleo/entry/checksum bar/checksum;
assert.eq checksum checksum;
31 changes: 31 additions & 0 deletions tests/tests/compiler/constructor/calls_in_constructors.leo
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,36 @@ program test.aleo {
constructor() {
let entry: u8 = foo.get(0u8);
check_first_entry_is_zero(entry);
checks::check_edition::[1u16]();
}
}

// --- Next Module: checks.leo --- //

export final fn check_edition::[N: u16]() {
assert_neq(std::ctx::edition(), N);
}

// --- Next Program --- //

import test.aleo;

program child.aleo {
fn main() {}

@custom
constructor() {
test.aleo::checks::check_edition::[2u16]();
}
}

// --- Next Program --- //

import child.aleo;

program parent.aleo {
fn main() {}

@noupgrade
constructor() {}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
final fn check_edition::[N: u16]() {
assert_eq(std::ctx::edition(), N);
}

program child.aleo {
mapping vals: u32 => u32;

Expand All @@ -9,8 +13,19 @@ program child.aleo {
return vals.get_or_use(k, 0u32);
}

@noupgrade
constructor() {}
@custom
constructor() {
assert_neq(
std::prog::function_checksum::[child.aleo, 'entry'](),
std::prog::function_checksum::[child.aleo, 'peek']()
);
assert_eq(std::prog::checksum::[child.aleo](), std::ctx::checksum());
check_edition::[0u16]();
// The computed bound delays this call until the next loop unrolling pass.
for i in 0u16..(1u16 + 0u16) {
check_edition::[i]();
}
}
}

// --- Next Program --- //
Expand All @@ -28,6 +43,12 @@ program parent.aleo {
return final { foo(); };
}

@noupgrade
constructor() {}
@custom
constructor() {
assert_neq(
std::prog::function_checksum::[child.aleo, 'entry'](),
std::prog::function_checksum::[parent.aleo, 'bar']()
);
assert_eq(std::prog::checksum::[parent.aleo](), std::ctx::checksum());
}
}
Loading