diff --git a/codegen/masm/CHANGELOG.md b/codegen/masm/CHANGELOG.md index a41b7c728..019dd8c77 100644 --- a/codegen/masm/CHANGELOG.md +++ b/codegen/masm/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- keep internal component procedures out of compiled MASM package exports so library packages expose + only lifted Component Model wrappers +- preserve generated component initializer callees in MASM procedure metadata + ## [0.5.1](https://github.com/0xMiden/compiler/compare/midenc-codegen-masm-v0.5.0...midenc-codegen-masm-v0.5.1) - 2025-11-13 ### Other diff --git a/codegen/masm/src/artifact.rs b/codegen/masm/src/artifact.rs index dc2540a0e..829fbfbd6 100644 --- a/codegen/masm/src/artifact.rs +++ b/codegen/masm/src/artifact.rs @@ -1,16 +1,13 @@ use alloc::sync::Arc; use core::fmt; -use miden_assembly::{Path, ast::InvocationTarget}; +use miden_assembly::Path; use miden_core::Word; use miden_mast_package::Package; use midenc_hir::{constants::ConstantData, dialects::builtin, interner::Symbol}; -use midenc_session::{ - Emit, OutputMode, OutputType, Session, Writer, - diagnostics::{IntoDiagnostic, Report, SourceSpan, Span, WrapErr}, -}; +use midenc_session::{Emit, OutputMode, OutputType, Session, Writer, diagnostics::Report}; -use crate::{TraceEvent, lower::NativePtr, masm}; +use crate::{lower::NativePtr, masm}; mod project_support; @@ -20,12 +17,14 @@ pub struct MasmComponent { /// /// All components must have a canonical root module, even if empty pub root: Arc, - /// The symbol name of the component initializer function + /// Whether this component requires an initializer procedure. /// - /// This function is responsible for initializing global variables and writing data segments + /// The initializer is responsible for initializing global variables and writing data segments /// into memory at program startup, and at cross-context call boundaries (in callee prologue). - pub init: Option, - /// The symbol name of the program entrypoint, if this component is executable. + /// When set, a private root-local `init` procedure is generated and invoked by the lifted + /// export wrappers (and the generated executable entrypoint) via a same-module symbol. + pub requires_init: bool, + /// The invocation target for the program entrypoint, if this component is executable. /// /// If unset, it indicates that the component is a library, even if it could be made executable. pub entrypoint: Option, @@ -37,6 +36,14 @@ pub struct MasmComponent { pub heap_base: u32, /// The address of the `__stack_pointer` global, if such a global has been defined pub stack_pointer: Option, + /// Whether non-root support modules are implementation details of the component package. + /// + /// When true, support modules are statically linked into library packages instead of being + /// surfaced as public package modules. This static linking — not procedure visibility — is + /// what prunes the lowered core Wasm procedures from the package export surface in the common + /// (non-synthetic-wrapper) component-library case, because MASM currently lowers `Internal` + /// visibility to public. + pub link_support_modules_privately: bool, /// The set of modules in this component pub modules: Vec>, } @@ -177,6 +184,17 @@ impl fmt::Display for MasmComponent { } impl MasmComponent { + /// Get a mutable reference to the component root module (`modules[0]`). + /// + /// The root module is never cloned during lowering, so the unique reference is guaranteed. + pub(crate) fn root_module_mut(&mut self) -> &mut masm::Module { + debug_assert!( + self.modules[0].path() == self.root.as_ref(), + "modules[0] must be the component root module" + ); + Arc::get_mut(&mut self.modules[0]).expect("expected unique reference") + } + /// Assemble this component into a Miden package. pub fn assemble( &self, @@ -200,137 +218,6 @@ impl MasmComponent { registry, ) } - - /// Generate an executable module which when run expects the raw data segment data to be - /// provided on the advice stack in the same order as initialization, and the operands of - /// the entrypoint function on the operand stack. - fn generate_main( - &self, - entrypoint: &InvocationTarget, - emit_test_harness: bool, - source_manager: Arc, - ) -> Result, Report> { - use masm::{Instruction as Inst, Op}; - - let mut exe = Box::new(masm::Module::new_executable()); - let span = SourceSpan::default(); - let mut invoked = Vec::new(); - let body = { - let mut block = masm::Block::new(span, Vec::with_capacity(64)); - // Invoke component initializer, if present - if let Some(init) = self.init.as_ref() { - invoked.push(masm::Invoke::new(masm::InvokeKind::Exec, init.clone())); - block.push(Op::Inst(Span::new(span, Inst::Exec(init.clone())))); - } - - // Initialize test harness, if requested - if emit_test_harness { - self.emit_test_harness(&mut block); - } - - // Invoke the program entrypoint - block.push(Op::Inst(Span::new( - span, - Inst::Trace(TraceEvent::FrameStart.as_u32().into()), - ))); - invoked.push(masm::Invoke::new(masm::InvokeKind::Exec, entrypoint.clone())); - block.push(Op::Inst(Span::new(span, Inst::Exec(entrypoint.clone())))); - block - .push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameEnd.as_u32().into())))); - - // Truncate the stack to 16 elements on exit - let truncate_stack = { - let name = masm::ProcedureName::new("truncate_stack").unwrap(); - let module = masm::LibraryPath::new("::miden::core::sys").unwrap(); - let qualified = masm::QualifiedProcedureName::new(module.as_path(), name); - InvocationTarget::Path(Span::new(span, qualified.into_inner())) - }; - invoked.push(masm::Invoke::new(masm::InvokeKind::Exec, truncate_stack.clone())); - block.push(Op::Inst(Span::new(span, Inst::Exec(truncate_stack)))); - block - }; - let mut start = masm::Procedure::new( - span, - masm::Visibility::Public, - masm::ProcedureName::main(), - 0, - body, - ); - start.extend_invoked(invoked); - exe.define_procedure(start, source_manager) - .into_diagnostic() - .wrap_err("failed to define executable `main` procedure")?; - Ok(exe) - } - - fn emit_test_harness(&self, block: &mut masm::Block) { - use masm::{Instruction as Inst, IntValue, Op, PushValue}; - use miden_core::Felt; - - let span = SourceSpan::default(); - - let pipe_words_to_memory = { - let name = masm::ProcedureName::new("pipe_words_to_memory").unwrap(); - let module = masm::LibraryPath::new("::miden::core::mem").unwrap(); - let qualified = masm::QualifiedProcedureName::new(module.as_path(), name); - InvocationTarget::Path(Span::new(span, qualified.into_inner())) - }; - - // Step 1: Get the number of initializers to run - // => [inits] on operand stack - block.push(Op::Inst(Span::new(span, Inst::AdvPush))); - - // Step 2: Evaluate the initial state of the loop condition `inits > 0` - // => [inits, inits] - block.push(Op::Inst(Span::new(span, Inst::Dup0))); - // => [inits > 0, inits] - block.push(Op::Inst(Span::new(span, Inst::Push(PushValue::Int(IntValue::U8(0)).into())))); - block.push(Op::Inst(Span::new(span, Inst::Gt))); - - // Step 3: Loop until `inits == 0` - let mut loop_body = Vec::with_capacity(16); - - // State of operand stack on entry to `loop_body`: [inits] - // State of advice stack on entry to `loop_body`: [dest_ptr, num_words, ...] - // - // Step 3a: Compute next value of `inits`, i.e. `inits'` - // => [inits - 1] - loop_body.push(Op::Inst(Span::new(span, Inst::SubImm(Felt::ONE.into())))); - - // Step 3b: Copy initializer data to memory - // => [num_words, dest_ptr, inits'] - loop_body.push(Op::Inst(Span::new(span, Inst::AdvPush))); - loop_body.push(Op::Inst(Span::new(span, Inst::AdvPush))); - // => [C, B, A, dest_ptr, inits'] on operand stack - loop_body - .push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameStart.as_u32().into())))); - loop_body.push(Op::Inst(Span::new(span, Inst::Exec(pipe_words_to_memory)))); - loop_body - .push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameEnd.as_u32().into())))); - // Drop C, B, A - loop_body.push(Op::Inst(Span::new(span, Inst::DropW))); - loop_body.push(Op::Inst(Span::new(span, Inst::DropW))); - loop_body.push(Op::Inst(Span::new(span, Inst::DropW))); - // => [inits'] - loop_body.push(Op::Inst(Span::new(span, Inst::Drop))); - - // Step 3c: Evaluate loop condition `inits' > 0` - // => [inits', inits'] - loop_body.push(Op::Inst(Span::new(span, Inst::Dup0))); - // => [inits' > 0, inits'] - loop_body - .push(Op::Inst(Span::new(span, Inst::Push(PushValue::Int(IntValue::U8(0)).into())))); - loop_body.push(Op::Inst(Span::new(span, Inst::Gt))); - - // Step 4: Enter (or skip) loop - block.push(Op::While { - span, - body: masm::Block::new(span, loop_body), - }); - - // Step 5: Drop `inits` after loop is evaluated - block.push(Op::Inst(Span::new(span, Inst::Drop))); - } } #[cfg(test)] diff --git a/codegen/masm/src/artifact/project_support.rs b/codegen/masm/src/artifact/project_support.rs index f45603a5c..4ef0cbae3 100644 --- a/codegen/masm/src/artifact/project_support.rs +++ b/codegen/masm/src/artifact/project_support.rs @@ -62,18 +62,8 @@ pub(super) fn assemble_with_registry( } } - let is_executable_target = session.options.target_type.is_some_and(|tt| tt.is_executable()) - || project_package.library_target().is_none() - || session.options.target.as_deref().is_some_and(|tname| { - project_package.executable_targets().iter().any(|t| tname == &**t.name) - }); - let sources = prepare_sources( - component, - &mut assembler, - session.get_flag("test_harness"), - session, - is_executable_target, - )?; + let is_executable_target = session.is_executable_target(); + let sources = prepare_sources(component, &mut assembler, is_executable_target)?; let mut project_assembler = assembler.for_project(project_package.clone(), registry)?; let selector = if is_executable_target { @@ -111,20 +101,28 @@ fn selected_executable_target_name<'a>( Ok(session.name.as_ref()) } -/// Prepare the synthetic project target and source inputs used to assemble compiler-generated MASM. +/// Partition the component's modules into the project root, surfaced support sources, and modules +/// statically linked into the assembler, producing the source inputs for project assembly. fn prepare_sources( component: &MasmComponent, assembler: &mut Assembler, - emit_test_harness: bool, - session: &Session, - generate_executable_main: bool, + is_executable_target: bool, ) -> Result { - // Intrinsics must be linked into the assembler context directly so they do not become part of - // the assembled package surface. + // Non-root support modules hold the lowered core Wasm procedures called by lifted wrappers. + // For a component library they are implementation details, so statically link them into the + // assembler (below) rather than surfacing them as package source. This static linking — not + // procedure visibility — is what keeps them off the package export surface, because MASM + // currently lowers `Internal` to public. Executable targets and synthetic-wrapper/standalone + // components keep them as source inputs instead. + let link_support_modules_privately = + !is_executable_target && component.link_support_modules_privately; + let mut support = Vec::with_capacity(component.modules.len()); let mut root = None; for module in component.modules.iter() { if is_intrinsics_module(module) { + // Intrinsics must be linked into the assembler context directly so they do not become + // part of the assembled package surface. log::debug!( target: "assembly", "adding intrinsics '{}' to assembler", @@ -139,18 +137,19 @@ fn prepare_sources( continue; } - support.push(Box::new(Arc::unwrap_or_clone(module.clone()))); - } + // Component library support modules contain the lowered core Wasm procedures called by + // lifted wrappers, but they are not part of the component package export surface. + if link_support_modules_privately { + log::debug!( + target: "assembly", + "adding component support module '{}' to assembler", + module.path() + ); + assembler.compile_and_statically_link(module.clone())?; + continue; + } - if generate_executable_main && let Some(entrypoint) = component.entrypoint.as_ref() { - // Our generated main module takes precedence here, so move the root module into support - support.extend(root); - let root = component.generate_main( - entrypoint, - emit_test_harness, - session.source_manager.clone(), - )?; - return Ok(ProjectSourceInputs { root, support }); + support.push(Box::new(Arc::unwrap_or_clone(module.clone()))); } let root = root.expect("components must always have a root module"); diff --git a/codegen/masm/src/linker.rs b/codegen/masm/src/linker.rs index dbf8bbae8..480ebdbd1 100644 --- a/codegen/masm/src/linker.rs +++ b/codegen/masm/src/linker.rs @@ -43,6 +43,12 @@ impl LinkInfo { !self.segment_layout.is_empty() } + /// Returns true if the component needs an initializer procedure to populate global variables + /// and data segments into memory before its exports run. + pub fn requires_init(&self) -> bool { + self.has_globals() || self.has_data_segments() + } + pub fn globals_layout(&self) -> &GlobalVariableLayout { &self.globals_layout } diff --git a/codegen/masm/src/lower/component.rs b/codegen/masm/src/lower/component.rs index be28bffe8..69444f9c6 100644 --- a/codegen/masm/src/lower/component.rs +++ b/codegen/masm/src/lower/component.rs @@ -70,17 +70,7 @@ impl ToMasmComponent for builtin::World { // If we have global variables or data segments, we will require a component initializer // function, as well as a module to hold component-level functions such as init - let requires_init = link_info.has_globals() || link_info.has_data_segments(); - let init = if requires_init { - let name = masm::ProcedureName::new("init").unwrap(); - let qualified = masm::QualifiedProcedureName::new("::init", name); - Some(masm::InvocationTarget::Path(Span::new( - SourceSpan::default(), - qualified.into_inner(), - ))) - } else { - None - }; + let requires_init = link_info.requires_init(); // Define the initial component modules set // @@ -97,6 +87,8 @@ impl ToMasmComponent for builtin::World { } else { None }; + let emit_executable_main = context.session().is_executable_target(); + let emit_test_harness = context.session().get_flag("test_harness"); // Compute the first page boundary after the end of the globals table (or reserved memory // if no globals) to use as the start of the dynamic heap when the program is executed @@ -110,12 +102,16 @@ impl ToMasmComponent for builtin::World { let mut masm_component = MasmComponent { id: None, root, - init, + requires_init, entrypoint, kernel, rodata, heap_base, stack_pointer, + // A World is the standalone/whole-program path: its non-root modules are the package + // surface itself, with no lifted-wrapper layer to hide them behind, so they are never + // linked privately. + link_support_modules_privately: false, modules, }; let builder = MasmComponentBuilder { @@ -125,6 +121,8 @@ impl ToMasmComponent for builtin::World { source_manager: context.session().source_manager.clone(), init_body: Default::default(), invoked_from_init: Default::default(), + emit_executable_main, + emit_test_harness, }; builder.build(self.as_operation())?; @@ -190,18 +188,7 @@ impl ToMasmComponent for builtin::Component { // If we have global variables or data segments, we will require a component initializer // function, as well as a module to hold component-level functions such as init - let requires_init = link_info.has_globals() || link_info.has_data_segments(); - let init = if requires_init { - let name = masm::ProcedureName::new("init").unwrap(); - let qualified = - masm::QualifiedProcedureName::new(component_path.as_path().to_absolute(), name); - Some(masm::InvocationTarget::Path(Span::new( - SourceSpan::default(), - qualified.into_inner(), - ))) - } else { - None - }; + let requires_init = link_info.requires_init(); // Define the initial component modules set // @@ -217,6 +204,8 @@ impl ToMasmComponent for builtin::Component { } else { None }; + let emit_executable_main = context.session().is_executable_target(); + let emit_test_harness = context.session().get_flag("test_harness"); // Compute the first page boundary after the end of the globals table (or reserved memory // if no globals) to use as the start of the dynamic heap when the program is executed @@ -227,15 +216,17 @@ impl ToMasmComponent for builtin::Component { let heap_base = u32::try_from(heap_base) .expect("unable to allocate dynamic heap: global table too large"); let stack_pointer = link_info.globals_layout().stack_pointer_offset(); + let link_support_modules_privately = !id.is_synthetic_wrapper(); let mut masm_component = MasmComponent { id: Some(id), root, - init, + requires_init, entrypoint, kernel, rodata, heap_base, stack_pointer, + link_support_modules_privately, modules, }; let builder = MasmComponentBuilder { @@ -245,6 +236,8 @@ impl ToMasmComponent for builtin::Component { source_manager: context.session().source_manager.clone(), init_body: Default::default(), invoked_from_init: Default::default(), + emit_executable_main, + emit_test_harness, }; builder.build(self.as_operation())?; @@ -293,16 +286,18 @@ struct MasmComponentBuilder<'a> { source_manager: Arc, init_body: Vec, invoked_from_init: BTreeSet, + emit_executable_main: bool, + emit_test_harness: bool, } impl MasmComponentBuilder<'_> { /// Convert the component body to Miden Assembly pub fn build(mut self, component: &midenc_hir::Operation) -> Result<(), Report> { - use masm::{Instruction as Inst, InvocationTarget, Op}; + use masm::{Instruction as Inst, Op}; // If a component-level init is required, emit code to initialize the heap before any other // initialization code. - if self.component.init.is_some() { + if self.component.requires_init { let span = component.span(); // Heap metadata initialization @@ -311,12 +306,7 @@ impl MasmComponentBuilder<'_> { span, Inst::Push(masm::Immediate::Value(Span::unknown(heap_base.into()))), ))); - let heap_init = { - let name = masm::ProcedureName::new("heap_init").unwrap(); - let module = masm::LibraryPath::new("::intrinsics::mem").unwrap(); - let qualified = masm::QualifiedProcedureName::new(module.as_path(), name); - InvocationTarget::Path(Span::new(span, qualified.into_inner())) - }; + let heap_init = qualified_procedure_target("::intrinsics::mem", "heap_init", span); self.init_body.push(Op::Inst(Span::new( span, Inst::Trace(TraceEvent::FrameStart.as_u32().into()), @@ -348,29 +338,19 @@ impl MasmComponentBuilder<'_> { } // Finalize the component-level init, if required - if self.component.init.is_some() { - let module = - Arc::get_mut(&mut self.component.modules[0]).expect("expected unique reference"); - - let init_name = masm::ProcedureName::new("init").unwrap(); + if self.component.requires_init { let init_body = core::mem::take(&mut self.init_body); - let init = masm::Procedure::new( - Default::default(), - masm::Visibility::Public, - init_name, - 0, - masm::Block::new(component.span(), init_body), + let invoked_from_init = core::mem::take(&mut self.invoked_from_init); + let root_module = self.component.root_module_mut(); + + define_init_procedure( + root_module, + init_body, + invoked_from_init, + component.span(), + self.source_manager.clone(), ) - .with_signature(masm::FunctionType::new( - midenc_hir::CallConv::Fast, - vec![], - vec![], - )); - - module - .define_procedure(init, self.source_manager.clone()) - .into_diagnostic() - .wrap_err("failed to define component `init` procedure")?; + .wrap_err("failed to define component `init` procedure")?; } else { assert!( self.init_body.is_empty(), @@ -378,6 +358,26 @@ impl MasmComponentBuilder<'_> { ); } + if self.emit_executable_main + && let Some(entrypoint) = self.component.entrypoint.clone() + { + let span = component.span(); + let root = self.component.root.clone(); + let init = self.component.requires_init.then(|| local_procedure_target("init", span)); + let entrypoint = localize_root_invocation_target(&entrypoint, root.as_ref()); + let root_module = self.component.root_module_mut(); + + define_main_procedure( + root_module, + init, + entrypoint, + self.emit_test_harness, + span, + self.source_manager.clone(), + ) + .wrap_err("failed to define executable `main` procedure")?; + } + Ok(()) } @@ -434,14 +434,18 @@ impl MasmComponentBuilder<'_> { fn define_function(&mut self, function: &builtin::Function) -> Result<(), Report> { let builder = MasmFunctionBuilder::new(function)?; + let init = self + .component + .requires_init + .then(|| local_procedure_target("init", function.span())); let procedure = builder.build( function, self.analysis_manager.nest(function.as_operation_ref()), self.link_info, + init, )?; - let module = - Arc::get_mut(&mut self.component.modules[0]).expect("expected unique reference"); + let module = self.component.root_module_mut(); let expected_path_len = if module.path().is_absolute() { 2 } else { 1 }; assert_eq!( module.path().len(), @@ -462,7 +466,7 @@ impl MasmComponentBuilder<'_> { /// populate the global heap with the data segments of this component, verifying that the /// commitments match. fn emit_data_segment_initialization(&mut self) { - use masm::{Instruction as Inst, InvocationTarget, Op}; + use masm::{Instruction as Inst, Op}; // Emit data segment initialization code // @@ -470,12 +474,8 @@ impl MasmComponentBuilder<'_> { // having been placed in the advice map with the same commitment and encoding used here. // The program will fail to execute if this is not set up correctly. let span = SourceSpan::default(); - let pipe_preimage_to_memory = { - let name = masm::ProcedureName::new("pipe_preimage_to_memory").unwrap(); - let module = masm::LibraryPath::new("::miden::core::mem").unwrap(); - let qualified = masm::QualifiedProcedureName::new(module.as_path(), name); - InvocationTarget::Path(Span::new(span, qualified.into_inner())) - }; + let pipe_preimage_to_memory = + qualified_procedure_target("::miden::core::mem", "pipe_preimage_to_memory", span); for rodata in self.component.rodata.iter() { // Push the commitment hash (`COM`) for this data onto the operand stack @@ -518,6 +518,173 @@ impl MasmComponentBuilder<'_> { } } +/// Define the private component initializer in `module`. +fn define_init_procedure( + module: &mut masm::Module, + body: Vec, + invoked: BTreeSet, + span: SourceSpan, + source_manager: Arc, +) -> Result<(), Report> { + let init_name = masm::ProcedureName::new("init").unwrap(); + let mut init = masm::Procedure::new( + Default::default(), + masm::Visibility::Private, + init_name, + 0, + masm::Block::new(span, body), + ) + .with_signature(masm::FunctionType::new(midenc_hir::CallConv::Fast, vec![], vec![])); + init.extend_invoked(invoked); + module.define_procedure(init, source_manager).into_diagnostic()?; + Ok(()) +} + +/// Define the generated executable `main` procedure in the component root module. +/// +/// The generated entry procedure invokes the component initializer, optional VM test harness +/// initialization, selected entrypoint, and stack truncation shim in order. +fn define_main_procedure( + module: &mut masm::Module, + init: Option, + entrypoint: masm::InvocationTarget, + emit_test_harness: bool, + span: SourceSpan, + source_manager: Arc, +) -> Result<(), Report> { + use masm::{Instruction as Inst, Op}; + + let mut invoked = Vec::new(); + let body = { + let mut block = masm::Block::new(span, Vec::with_capacity(64)); + + // Invoke component initializer, if present + if let Some(init) = init { + invoked.push(masm::Invoke::new(masm::InvokeKind::Exec, init.clone())); + block.push(Op::Inst(Span::new(span, Inst::Exec(init)))); + } + + // Initialize test harness, if requested + if emit_test_harness { + emit_test_harness_initialization(&mut block); + } + + // Invoke the program entrypoint + block.push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameStart.as_u32().into())))); + invoked.push(masm::Invoke::new(masm::InvokeKind::Exec, entrypoint.clone())); + block.push(Op::Inst(Span::new(span, Inst::Exec(entrypoint)))); + block.push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameEnd.as_u32().into())))); + + // Truncate the stack to 16 elements on exit + let truncate_stack = + qualified_procedure_target("::miden::core::sys", "truncate_stack", span); + invoked.push(masm::Invoke::new(masm::InvokeKind::Exec, truncate_stack.clone())); + block.push(Op::Inst(Span::new(span, Inst::Exec(truncate_stack)))); + block + }; + let mut main = + masm::Procedure::new(span, masm::Visibility::Public, masm::ProcedureName::main(), 0, body); + main.extend_invoked(invoked); + module.define_procedure(main, source_manager).into_diagnostic()?; + Ok(()) +} + +/// Emit VM test harness initializer loading into the generated executable entry block. +fn emit_test_harness_initialization(block: &mut masm::Block) { + use masm::{Instruction as Inst, IntValue, Op, PushValue}; + use miden_core::Felt; + + let span = SourceSpan::default(); + + let pipe_words_to_memory = + qualified_procedure_target("::miden::core::mem", "pipe_words_to_memory", span); + + // Step 1: Get the number of initializers to run + // => [inits] on operand stack + block.push(Op::Inst(Span::new(span, Inst::AdvPush))); + + // Step 2: Evaluate the initial state of the loop condition `inits > 0` + // => [inits, inits] + block.push(Op::Inst(Span::new(span, Inst::Dup0))); + // => [inits > 0, inits] + block.push(Op::Inst(Span::new(span, Inst::Push(PushValue::Int(IntValue::U8(0)).into())))); + block.push(Op::Inst(Span::new(span, Inst::Gt))); + + // Step 3: Loop until `inits == 0` + let mut loop_body = Vec::with_capacity(16); + + // State of operand stack on entry to `loop_body`: [inits] + // State of advice stack on entry to `loop_body`: [dest_ptr, num_words, ...] + // + // Step 3a: Compute next value of `inits`, i.e. `inits'` + // => [inits - 1] + loop_body.push(Op::Inst(Span::new(span, Inst::SubImm(Felt::ONE.into())))); + + // Step 3b: Copy initializer data to memory + // => [num_words, dest_ptr, inits'] + loop_body.push(Op::Inst(Span::new(span, Inst::AdvPush))); + loop_body.push(Op::Inst(Span::new(span, Inst::AdvPush))); + // => [C, B, A, dest_ptr, inits'] on operand stack + loop_body.push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameStart.as_u32().into())))); + loop_body.push(Op::Inst(Span::new(span, Inst::Exec(pipe_words_to_memory)))); + loop_body.push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameEnd.as_u32().into())))); + // Drop C, B, A + loop_body.push(Op::Inst(Span::new(span, Inst::DropW))); + loop_body.push(Op::Inst(Span::new(span, Inst::DropW))); + loop_body.push(Op::Inst(Span::new(span, Inst::DropW))); + // => [inits'] + loop_body.push(Op::Inst(Span::new(span, Inst::Drop))); + + // Step 3c: Evaluate loop condition `inits' > 0` + // => [inits', inits'] + loop_body.push(Op::Inst(Span::new(span, Inst::Dup0))); + // => [inits' > 0, inits'] + loop_body.push(Op::Inst(Span::new(span, Inst::Push(PushValue::Int(IntValue::U8(0)).into())))); + loop_body.push(Op::Inst(Span::new(span, Inst::Gt))); + + // Step 4: Enter (or skip) loop + block.push(Op::While { + span, + body: masm::Block::new(span, loop_body), + }); + + // Step 5: Drop `inits` after loop is evaluated + block.push(Op::Inst(Span::new(span, Inst::Drop))); +} + +/// Convert a root-module absolute invocation target into a local target. +fn localize_root_invocation_target( + target: &masm::InvocationTarget, + root: &masm::LibraryPathRef, +) -> masm::InvocationTarget { + if let masm::InvocationTarget::Path(path) = target + && path.parent().is_some_and(|parent| parent == root) + && let Some(name) = path.last() + { + return local_procedure_target(name, target.span()); + } + + target.clone() +} + +/// Build an invocation target for a procedure in the current module. +fn local_procedure_target(name: &str, span: SourceSpan) -> masm::InvocationTarget { + masm::InvocationTarget::Symbol(masm::Ident::from_raw_parts(Span::new(span, name.into()))) +} + +/// Build an invocation target for a fully-qualified procedure in another module (e.g. a stdlib or +/// intrinsics procedure referenced by absolute path). +fn qualified_procedure_target( + module: &str, + name: &str, + span: SourceSpan, +) -> masm::InvocationTarget { + let name = masm::ProcedureName::new(name).unwrap(); + let module = masm::LibraryPath::new(module).unwrap(); + let qualified = masm::QualifiedProcedureName::new(module.as_path(), name); + InvocationTarget::Path(Span::new(span, qualified.into_inner())) +} + struct MasmModuleBuilder<'a> { module: &'a mut masm::Module, analysis_manager: AnalysisManager, @@ -573,6 +740,7 @@ impl MasmModuleBuilder<'_> { function, self.analysis_manager.nest(function.as_operation_ref()), self.link_info, + None, )?; self.module @@ -690,6 +858,7 @@ impl MasmFunctionBuilder { function: &builtin::Function, analysis_manager: AnalysisManager, link_info: &LinkInfo, + init_target: Option, ) -> Result { use alloc::collections::BTreeSet; @@ -721,22 +890,13 @@ impl MasmFunctionBuilder { trace_target, }; - // For component export functions, invoke the `init` procedure first if needed. - // It loads the data segments and global vars into memory. + // Component export wrappers (Component Model calling convention) invoke the component + // `init` procedure first to load data segments and global vars into memory. The caller + // threads a root-local `init` target exactly when initialization is required; support + // module functions are never Component Model exports and always pass `None`. if function.signature().cc.is_wasm_canonical_abi() - && (link_info.has_globals() || link_info.has_data_segments()) + && let Some(init) = init_target { - let init = if let Some(id) = link_info.component() { - let component_path = id.to_library_path(); - let name = masm::ProcedureName::new("init").unwrap(); - let qualified = - masm::QualifiedProcedureName::new(component_path.as_path().to_absolute(), name); - InvocationTarget::Path(Span::new(SourceSpan::default(), qualified.into_inner())) - } else { - let name = masm::ProcedureName::new("init").unwrap(); - let qualified = masm::QualifiedProcedureName::new("::init", name); - InvocationTarget::Path(Span::new(SourceSpan::default(), qualified.into_inner())) - }; let span = SourceSpan::default(); // Add init call to the emitter's target before emitting the function body emitter.invoked.insert(masm::Invoke::new(masm::InvokeKind::Exec, init.clone())); @@ -755,12 +915,11 @@ impl MasmFunctionBuilder { // Since the VM's `drop` instruction not letting stack size go beyond the 16 elements // we most likely end up with stack size > 16 elements at the end. // See https://github.com/0xPolygonMiden/miden-vm/blob/c4acf49510fda9ba80f20cee1a9fb1727f410f47/processor/src/stack/mod.rs?plain=1#L226-L253 - let truncate_stack = { - let name = masm::ProcedureName::new("truncate_stack").unwrap(); - let module = masm::LibraryPath::new("::miden::core::sys").unwrap(); - let qualified = masm::QualifiedProcedureName::new(module.as_path(), name); - InvocationTarget::Path(Span::new(SourceSpan::default(), qualified.into_inner())) - }; + let truncate_stack = qualified_procedure_target( + "::miden::core::sys", + "truncate_stack", + SourceSpan::default(), + ); let span = SourceSpan::default(); invoked.insert(masm::Invoke::new(masm::InvokeKind::Exec, truncate_stack.clone())); body.push(masm::Op::Inst(Span::new(span, masm::Instruction::Exec(truncate_stack)))); diff --git a/frontend/wasm/CHANGELOG.md b/frontend/wasm/CHANGELOG.md index f3e89fb3e..c84a5b7d2 100644 --- a/frontend/wasm/CHANGELOG.md +++ b/frontend/wasm/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- treat core Wasm exports inside components as implementation details until lifted into Component + Model export wrappers + ## [0.5.1](https://github.com/0xMiden/compiler/compare/midenc-frontend-wasm-v0.5.0...midenc-frontend-wasm-v0.5.1) - 2025-11-13 ### Added diff --git a/frontend/wasm/src/component/lift_exports.rs b/frontend/wasm/src/component/lift_exports.rs index f99fe5620..9dcfbf855 100644 --- a/frontend/wasm/src/component/lift_exports.rs +++ b/frontend/wasm/src/component/lift_exports.rs @@ -82,9 +82,9 @@ pub fn generate_export_lifting_function( let export_func_span = core_export_func_ref.borrow().span(); let export_func_ident = Ident::new(midenc_hir::interner::Symbol::intern(export_func_name), export_func_span); - // Make the lowered core WASM export internal so only the lifted wrapper is - // publicly exported from the component, while still allowing the wrapper to - // call across the nested core module symbol table boundary. + // Promote the lowered core WASM export to internal so the lifted wrapper can call across the + // nested core module symbol table boundary. Package export pruning happens during MASM package + // assembly, since MASM currently lowers internal visibility to public procedure visibility. core_module_builder .set_function_visibility(core_export_func_path.name().as_str(), Visibility::Internal); let core_export_func_sig = core_export_func_ref.borrow().get_signature().clone(); diff --git a/frontend/wasm/src/component/translator.rs b/frontend/wasm/src/component/translator.rs index 82739fd60..a02960936 100644 --- a/frontend/wasm/src/component/translator.rs +++ b/frontend/wasm/src/component/translator.rs @@ -4,7 +4,7 @@ use cranelift_entity::PrimaryMap; use midenc_frontend_wasm_metadata::{FrontendMetadata, ProtocolExportKind}; use midenc_hir::{ self as hir2, BuilderExt, CallConv, Context, FunctionType, FxHashMap, FxHashSet, Ident, - SymbolNameComponent, SymbolPath, + SymbolNameComponent, SymbolPath, Visibility, diagnostics::Report, dialects::builtin::{self, ComponentBuilder, ModuleBuilder, World, WorldBuilder}, formatter::DisplayValues, @@ -654,12 +654,14 @@ impl<'a> ComponentTranslator<'a> { let module_name = parsed_module.module.name().as_str(); let module_ref = self.result.define_module(Ident::from(module_name)).unwrap(); let mut module_builder = ModuleBuilder::new(module_ref); + // Core exports inside a Wasm component are implementation details until lifted. let mut module_state = ModuleTranslationState::new( &parsed_module.module, &mut module_builder, &mut self.world_builder, module_types, import_canon_lower_args, + Visibility::Private, self.context.diagnostics(), )?; diff --git a/frontend/wasm/src/module/build_ir.rs b/frontend/wasm/src/module/build_ir.rs index b2ed0bb1c..518fcb998 100644 --- a/frontend/wasm/src/module/build_ir.rs +++ b/frontend/wasm/src/module/build_ir.rs @@ -72,12 +72,14 @@ pub fn translate_module_as_component( let module_ref = cb.define_module(Ident::from(module_name)).unwrap(); let mut module_builder = ModuleBuilder::new(module_ref); + // Standalone Wasm module exports are the package surface. let mut module_state = ModuleTranslationState::new( &parsed_module.module, &mut module_builder, &mut world_builder, &module_types, FxHashMap::default(), + Visibility::Public, context.diagnostics(), )?; build_ir_module(&mut parsed_module, &module_types, &mut module_state, config, context)?; diff --git a/frontend/wasm/src/module/module_translation_state.rs b/frontend/wasm/src/module/module_translation_state.rs index 317b2047a..bae1ff33f 100644 --- a/frontend/wasm/src/module/module_translation_state.rs +++ b/frontend/wasm/src/module/module_translation_state.rs @@ -32,12 +32,15 @@ impl<'a> ModuleTranslationState<'a> { /// `world_builder` - the Miden IR World builder /// `mod_types` - the Miden IR module types builder /// `module_args` - the module instantiation arguments, i.e. entities to "fill" module imports + /// `exported_func_visibility` - the visibility assigned to functions exported by the core + /// Wasm module pub fn new( module: &Module, module_builder: &'a mut ModuleBuilder, world_builder: &'a mut WorldBuilder, mod_types: &ModuleTypesBuilder, module_args: FxHashMap, + exported_func_visibility: Visibility, diagnostics: &DiagnosticsHandler, ) -> WasmResult { let mut functions = FxHashMap::default(); @@ -53,7 +56,7 @@ impl<'a> ModuleTranslationState<'a> { ], }; let visibility = if module.is_exported(index.into()) { - Visibility::Public + exported_func_visibility } else { Visibility::Private }; diff --git a/midenc-session/src/lib.rs b/midenc-session/src/lib.rs index 4d856fddf..554dc6bb5 100644 --- a/midenc-session/src/lib.rs +++ b/midenc-session/src/lib.rs @@ -131,7 +131,7 @@ impl Session { }; options.target_type = Some(target_type); } - let is_executable_target = + let is_executable_target_type = options.target_type.is_some_and(|ty| ty.is_executable()); let project = { let package = project.package(); @@ -144,13 +144,13 @@ impl Session { }); if has_virtual_executable_target - || (is_cargo_project && is_executable_target) + || (is_cargo_project && is_executable_target_type) { // HACK(pauls): Workaround bug with virtual bin targets until // 0.24.x. See https://github.com/0xMiden/miden-vm/pull/3156 miden_project::Project::Package(fixup_targets( package, - is_cargo_project && is_executable_target, + is_cargo_project && is_executable_target_type, )) } else { project @@ -479,6 +479,19 @@ impl Session { self.options.output_types.should_assemble() && !self.options.link_only } + /// Returns true if this session is compiling a project executable target. + pub fn is_executable_target(&self) -> bool { + let project_package = self.project.package(); + self.options.target_type.is_some_and(|target_type| target_type.is_executable()) + || project_package.library_target().is_none() + || self.options.target.as_deref().is_some_and(|target_name| { + project_package + .executable_targets() + .iter() + .any(|target| target_name == &**target.name) + }) + } + /// Returns true if the given [OutputType] should be emitted as an output pub fn should_emit(&self, ty: OutputType) -> bool { self.options.output_types.contains_key(&ty) diff --git a/tests/integration-network/src/mockchain/notes/basic_wallet.rs b/tests/integration-network/src/mockchain/notes/basic_wallet.rs index 76dfa217f..749e34e59 100644 --- a/tests/integration-network/src/mockchain/notes/basic_wallet.rs +++ b/tests/integration-network/src/mockchain/notes/basic_wallet.rs @@ -109,7 +109,7 @@ pub fn basic_wallet_p2id_transfers_asset_with_custom_tx_script() { .unwrap() .foreign_accounts(vec![faucet_inputs]); let tx_measurements = execute_tx(&mut chain, consume_tx_context_builder); - expect!["3327"].assert_eq(prologue_cycles(&tx_measurements)); + expect!["3309"].assert_eq(prologue_cycles(&tx_measurements)); expect!["9323"].assert_eq(single_note_cycles(&tx_measurements)); eprintln!("\n=== Checking Alice's account has the minted asset ==="); diff --git a/tests/integration/src/assert_helpers.rs b/tests/integration/src/assert_helpers.rs index a02f212fd..d7fd60ff7 100644 --- a/tests/integration/src/assert_helpers.rs +++ b/tests/integration/src/assert_helpers.rs @@ -1,3 +1,7 @@ +use std::collections::BTreeSet; + +use miden_mast_package::PackageExport; + /// Asserts that the exported procedure carrying `attribute` is unique and preserves its leaf /// export name. pub(crate) fn assert_unique_protocol_export( @@ -28,3 +32,72 @@ pub(crate) fn assert_unique_protocol_export( "expected the `{attribute}` export to preserve the user-defined procedure name", ); } + +/// Assert that every procedure exported by `package` is a lifted Component Model wrapper. +/// +/// Internal procedures (lowered core Wasm functions, the component `init`, `cabi_*`, intrinsics) +/// do not use the Component Model calling convention, so this catches such procedures leaking into +/// the package export surface without having to enumerate the expected export names. +pub(crate) fn assert_all_exports_are_lifted_wrappers(package: &miden_mast_package::Package) { + for export in package.mast.exports() { + // A library package should expose nothing but lifted wrappers, so a non-procedure + // (constant/type) export is itself a leak. + let Some(proc_export) = export.as_procedure() else { + panic!( + "package should export only lifted Component Model wrappers, but `{}` is a \ + non-procedure export", + export.path().as_ref().as_str(), + ); + }; + // Only lifted wrappers carry the Component Model calling convention; internal procedures + // never do, so the calling convention is a reliable proxy for "is a public package export". + let is_lifted_wrapper = proc_export + .signature + .as_ref() + .is_some_and(|signature| signature.calling_convention().is_wasm_canonical_abi()); + assert!( + is_lifted_wrapper, + "package should export only lifted Component Model wrappers, but `{}` is not one; \ + internal procedures must not leak into the package export surface", + proc_export.path, + ); + } +} + +/// Assert that a package exposes exactly the expected lifted Component Model procedure wrappers. +pub(crate) fn assert_lifted_component_exports( + package: &miden_mast_package::Package, + expected_exports: &[&str], +) { + let expected_exports = expected_exports + .iter() + .map(|export| (*export).to_string()) + .collect::>(); + + let mast_exports = package + .mast + .exports() + .filter_map(|export| export.as_procedure()) + .map(|export| export.path.as_ref().as_str().to_string()) + .collect::>(); + + assert_eq!( + mast_exports, expected_exports, + "package should only export lifted Component Model wrappers", + ); + + assert_all_exports_are_lifted_wrappers(package); + + let manifest_exports = package + .manifest + .exports() + .filter_map(|export| match export { + PackageExport::Procedure(export) => Some(export.path.as_ref().as_str().to_string()), + PackageExport::Constant(_) | PackageExport::Type(_) => None, + }) + .collect::>(); + assert_eq!( + manifest_exports, expected_exports, + "package manifest exports should match MAST exports", + ); +} diff --git a/tests/integration/src/end_to_end/examples/auth_component_no_auth.rs b/tests/integration/src/end_to_end/examples/auth_component_no_auth.rs index d7a067909..f44606581 100644 --- a/tests/integration/src/end_to_end/examples/auth_component_no_auth.rs +++ b/tests/integration/src/end_to_end/examples/auth_component_no_auth.rs @@ -1,7 +1,10 @@ use miden_core::serde::{Deserializable, Serializable}; use midenc_frontend_wasm::WasmTranslationConfig; -use crate::{CompilerTest, assert_helpers::assert_unique_protocol_export}; +use crate::{ + CompilerTest, + assert_helpers::{assert_all_exports_are_lifted_wrappers, assert_unique_protocol_export}, +}; #[test] fn auth_component_no_auth() { @@ -11,6 +14,7 @@ fn auth_component_no_auth() { let auth_comp_package = test.compile_package(); assert!(auth_comp_package.is_library()); assert_unique_protocol_export(auth_comp_package.as_ref(), "auth_script", "auth-procedure"); + assert_all_exports_are_lifted_wrappers(auth_comp_package.as_ref()); // Test that the package loads let bytes = auth_comp_package.to_bytes(); diff --git a/tests/integration/src/end_to_end/examples/auth_component_rpo_falcon512.rs b/tests/integration/src/end_to_end/examples/auth_component_rpo_falcon512.rs index ce1264b4f..cf1a32ceb 100644 --- a/tests/integration/src/end_to_end/examples/auth_component_rpo_falcon512.rs +++ b/tests/integration/src/end_to_end/examples/auth_component_rpo_falcon512.rs @@ -1,7 +1,10 @@ use miden_core::serde::{Deserializable, Serializable}; use midenc_frontend_wasm::WasmTranslationConfig; -use crate::{CompilerTest, assert_helpers::assert_unique_protocol_export}; +use crate::{ + CompilerTest, + assert_helpers::{assert_all_exports_are_lifted_wrappers, assert_unique_protocol_export}, +}; #[test] fn auth_component_rpo_falcon512() { @@ -14,6 +17,7 @@ fn auth_component_rpo_falcon512() { let auth_comp_package = test.compile_package(); assert!(auth_comp_package.is_library()); assert_unique_protocol_export(auth_comp_package.as_ref(), "auth_script", "check-signature"); + assert_all_exports_are_lifted_wrappers(auth_comp_package.as_ref()); // Test that the package loads let bytes = auth_comp_package.to_bytes(); diff --git a/tests/integration/src/end_to_end/examples/basic_wallet_package_sizes.rs b/tests/integration/src/end_to_end/examples/basic_wallet_package_sizes.rs index c8610a0b7..a4ba0384f 100644 --- a/tests/integration/src/end_to_end/examples/basic_wallet_package_sizes.rs +++ b/tests/integration/src/end_to_end/examples/basic_wallet_package_sizes.rs @@ -2,7 +2,9 @@ use midenc_expect_test::expect; use midenc_frontend_wasm::WasmTranslationConfig; use super::persist_cargo_miden_dependency; -use crate::{CompilerTest, testing::stripped_mast_size_str}; +use crate::{ + CompilerTest, assert_helpers::assert_lifted_component_exports, testing::stripped_mast_size_str, +}; fn no_debug_flags() -> [String; 2] { ["--debug".to_string(), "none".to_string()] @@ -18,7 +20,14 @@ fn basic_wallet_and_p2id() { ); let account_package = account_test.compile_package(); assert!(account_package.is_library(), "expected library"); - expect!["15614"].assert_eq(stripped_mast_size_str(&account_package)); + assert_lifted_component_exports( + account_package.as_ref(), + &[ + r#"::"miden:basic-wallet/miden-basic-wallet@0.1.0"::"move-asset-to-note""#, + r#"::"miden:basic-wallet/miden-basic-wallet@0.1.0"::"receive-asset""#, + ], + ); + expect!["8045"].assert_eq(stripped_mast_size_str(&account_package)); persist_cargo_miden_dependency("../../examples/basic-wallet", account_package.as_ref()); let mut tx_script_test = CompilerTest::rust_source_cargo_miden( @@ -28,7 +37,11 @@ fn basic_wallet_and_p2id() { ); let tx_script_package = tx_script_test.compile_package(); assert!(tx_script_package.is_library(), "expected library"); - expect!["19620"].assert_eq(stripped_mast_size_str(&tx_script_package)); + assert_lifted_component_exports( + tx_script_package.as_ref(), + &[r#"::"miden:base/transaction-script@1.0.0"::run"#], + ); + expect!["16636"].assert_eq(stripped_mast_size_str(&tx_script_package)); let mut p2id_test = CompilerTest::rust_source_cargo_miden( "../../examples/p2id-note", @@ -37,7 +50,11 @@ fn basic_wallet_and_p2id() { ); let note_package = p2id_test.compile_package(); assert!(note_package.is_library(), "expected library"); - expect!["19606"].assert_eq(stripped_mast_size_str(¬e_package)); + assert_lifted_component_exports( + note_package.as_ref(), + &[r#"::"miden:p2id/miden-p2id@0.1.0"::script"#], + ); + expect!["16681"].assert_eq(stripped_mast_size_str(¬e_package)); let mut p2ide_test = CompilerTest::rust_source_cargo_miden( "../../examples/p2ide-note", @@ -46,5 +63,9 @@ fn basic_wallet_and_p2id() { ); let p2ide_package = p2ide_test.compile_package(); assert!(p2ide_package.is_library(), "expected library"); - expect!["22748"].assert_eq(stripped_mast_size_str(&p2ide_package)); + assert_lifted_component_exports( + p2ide_package.as_ref(), + &[r#"::"miden:p2ide/miden-p2ide@0.1.0"::run"#], + ); + expect!["19823"].assert_eq(stripped_mast_size_str(&p2ide_package)); } diff --git a/tests/integration/src/end_to_end/examples/counter_note.rs b/tests/integration/src/end_to_end/examples/counter_note.rs index c06c7720b..ba08574ea 100644 --- a/tests/integration/src/end_to_end/examples/counter_note.rs +++ b/tests/integration/src/end_to_end/examples/counter_note.rs @@ -2,7 +2,10 @@ use miden_protocol::note::NoteScript; use midenc_frontend_wasm::WasmTranslationConfig; use super::persist_cargo_miden_dependency; -use crate::{CompilerTestBuilder, assert_helpers::assert_unique_protocol_export}; +use crate::{ + CompilerTestBuilder, + assert_helpers::{assert_lifted_component_exports, assert_unique_protocol_export}, +}; #[test] fn counter_note() { @@ -14,6 +17,14 @@ fn counter_note() { ); let mut counter_contract = counter_contract_builder.build(); let counter_contract_package = counter_contract.compile_package(); + assert!(counter_contract_package.is_library(), "expected library"); + assert_lifted_component_exports( + counter_contract_package.as_ref(), + &[ + r#"::"miden:counter-contract/miden-counter-contract@0.1.0"::"get-count""#, + r#"::"miden:counter-contract/miden-counter-contract@0.1.0"::"increment-count""#, + ], + ); persist_cargo_miden_dependency( "../../examples/counter-contract", counter_contract_package.as_ref(), diff --git a/tests/integration/src/sdk/mod.rs b/tests/integration/src/sdk/mod.rs index 519d5f2cf..6f4df5c33 100644 --- a/tests/integration/src/sdk/mod.rs +++ b/tests/integration/src/sdk/mod.rs @@ -11,6 +11,7 @@ use midenc_frontend_wasm::WasmTranslationConfig; use crate::{ CompilerTest, CompilerTestBuilder, + assert_helpers::assert_all_exports_are_lifted_wrappers, cargo_proj::project, compiler_test::{sdk_alloc_crate_path, sdk_crate_path}, testing::executor_with_std, @@ -286,17 +287,13 @@ fn rust_sdk_cross_ctx_account_and_note() { ); assert!(account_package.is_library()); assert_manifest_exports_match_library(account_package.as_ref()); + assert_all_exports_are_lifted_wrappers(account_package.as_ref()); let lib = account_package.mast.clone(); let exports = lib .exports() .filter(|e| !e.path().as_ref().as_str().starts_with("intrinsics")) .map(|e| e.path().as_ref().as_str().to_string()) .collect::>(); - assert!( - !lib.exports() - .any(|export| export.path().as_ref().as_str().starts_with("intrinsics")), - "expected no intrinsics in the exports" - ); let expected_module_prefix = "::\"miden:cross-ctx-account/"; let expected_function_suffix = "\"process-felt\""; assert!( @@ -345,6 +342,7 @@ fn rust_sdk_cross_ctx_account_and_note_word() { assert!(account_package.is_library()); let lib = account_package.mast.clone(); assert_component_export_signatures_match_wit(account_package.as_ref()); + assert_all_exports_are_lifted_wrappers(account_package.as_ref()); let expected_module_prefix = "::\"miden:cross-ctx-account-word/"; let expected_function_suffix = "\"process-word\""; let exports = lib @@ -398,6 +396,7 @@ fn rust_sdk_cross_ctx_word_arg_account_and_note() { assert!(account_package.is_library()); let lib = account_package.mast.clone(); + assert_all_exports_are_lifted_wrappers(account_package.as_ref()); let expected_module_prefix = "::\"miden:cross-ctx-account-word-arg/"; let expected_function_suffix = "\"process-word\""; let exports = lib diff --git a/tests/support/src/testing/eval.rs b/tests/support/src/testing/eval.rs index 7b9cc2f50..9b017fa26 100644 --- a/tests/support/src/testing/eval.rs +++ b/tests/support/src/testing/eval.rs @@ -86,9 +86,9 @@ where { // Provide initializer data and any user-supplied advice inputs via the advice stack. // - // NOTE: This relies on MasmComponent emitting a test harness via `emit_test_harness` during - // assembly of the package. The test harness consumes initializer inputs in FIFO order from the - // advice stack (top = index 0). + // NOTE: This relies on MasmComponent emitting a test harness via + // `emit_test_harness_initialization` during lowering. The test harness consumes initializer + // inputs in FIFO order from the advice stack (top = index 0). let user_advice_stack: Vec = advice_stack.into_iter().collect(); let mut advice_stack = Vec::new(); let mut num_initializers = 0u64;