diff --git a/crates/passes/src/monomorphization/mod.rs b/crates/passes/src/monomorphization/mod.rs index 864e8c0610f..7eaf432a3f3 100644 --- a/crates/passes/src/monomorphization/mod.rs +++ b/crates/passes/src/monomorphization/mod.rs @@ -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; diff --git a/crates/passes/src/monomorphization/program.rs b/crates/passes/src/monomorphization/program.rs index d2c7a3d2cb5..b25cae507ec 100644 --- a/crates/passes/src/monomorphization/program.rs +++ b/crates/passes/src/monomorphization/program.rs @@ -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. @@ -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. @@ -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(); diff --git a/crates/passes/src/monomorphization/visitor.rs b/crates/passes/src/monomorphization/visitor.rs index e5df9a36082..a6495c1495e 100644 --- a/crates/passes/src/monomorphization/visitor.rs +++ b/crates/passes/src/monomorphization/visitor.rs @@ -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, @@ -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, } diff --git a/tests/expectations/compiler/constructor/calls_in_constructors.out b/tests/expectations/compiler/constructor/calls_in_constructors.out index 95fab83dc34..d12069c27d9 100644 --- a/tests/expectations/compiler/constructor/calls_in_constructors.out +++ b/tests/expectations/compiler/constructor/calls_in_constructors.out @@ -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`). +────╯ diff --git a/tests/expectations/compiler/function/program_core_functions_external_leo.out b/tests/expectations/compiler/function/program_core_functions_external_leo.out index a5ffdd22ff0..b65b41a36aa 100644 --- a/tests/expectations/compiler/function/program_core_functions_external_leo.out +++ b/tests/expectations/compiler/function/program_core_functions_external_leo.out @@ -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; @@ -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; diff --git a/tests/tests/compiler/constructor/calls_in_constructors.leo b/tests/tests/compiler/constructor/calls_in_constructors.leo index de54a538cb2..9f3e569a2ae 100644 --- a/tests/tests/compiler/constructor/calls_in_constructors.leo +++ b/tests/tests/compiler/constructor/calls_in_constructors.leo @@ -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() {} +} diff --git a/tests/tests/compiler/function/program_core_functions_external_leo.leo b/tests/tests/compiler/function/program_core_functions_external_leo.leo index 4235750a808..9f3fed4f296 100644 --- a/tests/tests/compiler/function/program_core_functions_external_leo.leo +++ b/tests/tests/compiler/function/program_core_functions_external_leo.leo @@ -1,3 +1,7 @@ +final fn check_edition::[N: u16]() { + assert_eq(std::ctx::edition(), N); +} + program child.aleo { mapping vals: u32 => u32; @@ -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 --- // @@ -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()); + } }