midenc_codegen_masm/
convert.rs

1use miden_assembly::LibraryPath;
2use midenc_hir::{
3    self as hir,
4    pass::{AnalysisManager, ConversionPass, ConversionResult},
5    ConversionPassRegistration, PassInfo,
6};
7use midenc_hir_analysis as analysis;
8use midenc_session::Session;
9
10use crate::{
11    codegen::{FunctionEmitter, OperandStack, Scheduler, TypedValue},
12    masm, MasmArtifact,
13};
14
15type ProgramGlobalVariableAnalysis = analysis::GlobalVariableAnalysis<hir::Program>;
16type ModuleGlobalVariableAnalysis = analysis::GlobalVariableAnalysis<hir::Module>;
17
18/// Convert an HIR program or module to Miden Assembly
19///
20/// This pass assumes the following statements are true, and may fail if any are not:
21///
22/// * The IR has been validated, or is known to be valid
23/// * If converting a single module, it must be self-contained
24/// * If converting multiple modules, they must be linked into a [Program], in order to ensure that
25///   there are no undefined symbols, and that the placement of global variables in linear memory
26///   has been fixed.
27/// * There are no critical edges in the control flow graph, or the [SplitCriticalEdges] rewrite has
28///   been applied.
29/// * The control flow graph is a tree, with the exception of loop header blocks. This means that
30///   the only blocks with more than one predecessor are loop headers. See the [Treeify] rewrite for
31///   more information.
32///
33/// Any further optimizations or rewrites are considered optional.
34#[derive(ConversionPassRegistration)]
35pub struct ConvertHirToMasm<T>(core::marker::PhantomData<T>);
36impl<T> Default for ConvertHirToMasm<T> {
37    fn default() -> Self {
38        Self(core::marker::PhantomData)
39    }
40}
41impl<T> PassInfo for ConvertHirToMasm<T> {
42    const DESCRIPTION: &'static str = "Convert an HIR module or program to Miden Assembly\n\nSee \
43                                       the module documentation for ConvertHirToMasm for more \
44                                       details";
45    const FLAG: &'static str = "convert-hir-to-masm";
46    const SUMMARY: &'static str = "Convert an HIR module or program to Miden Assembly";
47}
48
49impl ConversionPass for ConvertHirToMasm<hir::Program> {
50    type From = Box<hir::Program>;
51    type To = MasmArtifact;
52
53    fn convert(
54        &mut self,
55        mut program: Self::From,
56        analyses: &mut AnalysisManager,
57        session: &Session,
58    ) -> ConversionResult<Self::To> {
59        // Ensure global variable analysis is computed
60        let globals =
61            analyses.get_or_compute::<ProgramGlobalVariableAnalysis>(&program, session)?;
62
63        let mut artifact = if program.has_entrypoint() {
64            masm::Program::from_hir(&program, &globals)
65                .map(Box::new)
66                .map(MasmArtifact::Executable)?
67        } else {
68            MasmArtifact::Library(Box::new(masm::Library::from_hir(&program, &globals)))
69        };
70
71        // Move link libraries to artifact
72        let libraries = core::mem::take(program.libraries_mut());
73        for lib in libraries.into_values() {
74            artifact.link_library(lib);
75        }
76
77        // Remove the set of modules to compile from the program
78        let modules = program.modules_mut().take();
79
80        for module in modules.into_iter() {
81            // Convert the module
82            let mut convert_to_masm = ConvertHirToMasm::<hir::Module>::default();
83            let masm_module = convert_to_masm.convert(module, analyses, session)?;
84
85            // Add to the final Miden Assembly program
86            artifact.insert(masm_module);
87        }
88
89        Ok(artifact)
90    }
91}
92
93impl ConversionPass for ConvertHirToMasm<hir::Module> {
94    type From = Box<hir::Module>;
95    type To = Box<masm::Module>;
96
97    fn convert(
98        &mut self,
99        mut module: Self::From,
100        analyses: &mut AnalysisManager,
101        session: &Session,
102    ) -> ConversionResult<Self::To> {
103        use miden_assembly::ast::ModuleKind;
104        use midenc_hir::ProgramAnalysisKey;
105
106        let kind = if module.is_kernel() {
107            ModuleKind::Kernel
108        } else {
109            ModuleKind::Library
110        };
111        let name = LibraryPath::new(&module.name).unwrap_or_else(|err| {
112            panic!("invalid module name '{}': {}", module.name.as_str(), err)
113        });
114        let mut masm_module = Box::new(masm::Module::new(name, kind));
115
116        // Compute import information for this module
117        masm_module.imports = module.imports();
118
119        // If we don't have a program-wide global variable analysis, compute it using the module
120        // global table.
121        if !analyses.is_available::<ProgramGlobalVariableAnalysis>(&ProgramAnalysisKey) {
122            analyses.get_or_compute::<ModuleGlobalVariableAnalysis>(&module, session)?;
123        }
124
125        // Removing a function via this cursor will move the cursor to
126        // the next function in the module. Once the end of the module
127        // is reached, the cursor will point to the null object, and
128        // `remove` will return `None`.
129        while let Some(function) = module.pop_front() {
130            let mut convert_to_masm = ConvertHirToMasm::<&hir::Function>::default();
131            let masm_function = convert_to_masm.convert(&function, analyses, session)?;
132            masm_module.push_back(Box::new(masm_function));
133        }
134
135        Ok(masm_module)
136    }
137}
138
139impl<'a> ConversionPass for ConvertHirToMasm<&'a hir::Function> {
140    type From = &'a hir::Function;
141    type To = masm::Function;
142
143    fn convert(
144        &mut self,
145        f: Self::From,
146        analyses: &mut AnalysisManager,
147        session: &Session,
148    ) -> ConversionResult<Self::To> {
149        use midenc_hir::ProgramAnalysisKey;
150
151        let mut f_prime = masm::Function::new(f.id, f.signature.clone());
152
153        // Start at the function entry
154        {
155            let entry = f.dfg.entry_block();
156
157            let globals = analyses
158                .get::<ProgramGlobalVariableAnalysis>(&ProgramAnalysisKey)
159                .map(|result| result.layout().clone())
160                .unwrap_or_else(|| {
161                    let result = analyses.expect::<ModuleGlobalVariableAnalysis>(
162                        &f.id.module,
163                        "expected global variable analysis to be available",
164                    );
165                    result.layout().clone()
166                });
167
168            let domtree = analyses.get_or_compute::<analysis::DominatorTree>(f, session)?;
169            let loops = analyses.get_or_compute::<analysis::LoopAnalysis>(f, session)?;
170            let liveness = analyses.get_or_compute::<analysis::LivenessAnalysis>(f, session)?;
171
172            let mut stack = OperandStack::default();
173            for arg in f.dfg.block_args(entry).iter().rev().copied() {
174                let ty = f.dfg.value_type(arg).clone();
175                stack.push(TypedValue { value: arg, ty });
176            }
177
178            let scheduler = Scheduler::new(f, &mut f_prime, &domtree, &loops, &liveness);
179            let schedule = scheduler.build();
180
181            /*
182                       if f.id.function.as_str().contains("get_inputs") {
183                           dbg!(&schedule);
184                       }
185            */
186            let emitter =
187                FunctionEmitter::new(f, &mut f_prime, &domtree, &loops, &liveness, &globals);
188            emitter.emit(schedule, stack);
189        }
190
191        Ok(f_prime)
192    }
193}