miden_assembly_syntax/sema/
mod.rs

1mod context;
2mod errors;
3mod passes;
4
5use alloc::{
6    boxed::Box,
7    collections::{BTreeSet, VecDeque},
8    sync::Arc,
9    vec::Vec,
10};
11
12use miden_core::{Word, crypto::hash::Rpo256};
13use miden_debug_types::{SourceFile, SourceManager, Span, Spanned};
14use smallvec::SmallVec;
15
16pub use self::{
17    context::AnalysisContext,
18    errors::{SemanticAnalysisError, SyntaxError},
19    passes::{ConstEvalVisitor, VerifyInvokeTargets},
20};
21use crate::{ast::*, parser::WordValue};
22
23/// Constructs and validates a [Module], given the forms constituting the module body.
24///
25/// As part of this process, the following is also done:
26///
27/// * Documentation comments are attached to items they decorate
28/// * Import table is constructed
29/// * Symbol resolution is performed:
30///   * Constants referenced by name are replaced with the value of that constant.
31///   * Calls to imported procedures are resolved concretely
32/// * Semantic analysis is performed on the module to validate it
33pub fn analyze(
34    source: Arc<SourceFile>,
35    kind: ModuleKind,
36    path: &Path,
37    forms: Vec<Form>,
38    warnings_as_errors: bool,
39    source_manager: Arc<dyn SourceManager>,
40) -> Result<Box<Module>, SyntaxError> {
41    let mut analyzer = AnalysisContext::new(source.clone(), source_manager);
42    analyzer.set_warnings_as_errors(warnings_as_errors);
43
44    let mut module = Box::new(Module::new(kind, path).with_span(source.source_span()));
45
46    let mut forms = VecDeque::from(forms);
47    let mut enums = SmallVec::<[EnumType; 1]>::new_const();
48    let mut docs = None;
49    while let Some(form) = forms.pop_front() {
50        match form {
51            Form::ModuleDoc(docstring) => {
52                assert!(docs.is_none());
53                module.set_docs(Some(docstring));
54            },
55            Form::Doc(docstring) => {
56                if let Some(unused) = docs.replace(docstring) {
57                    analyzer.error(SemanticAnalysisError::UnusedDocstring { span: unused.span() });
58                }
59            },
60            Form::Type(ty) => {
61                if let Err(err) = module.define_type(ty.with_docs(docs.take())) {
62                    analyzer.error(err);
63                }
64            },
65            Form::Enum(ty) => {
66                // Ensure the constants defined by the enum are made known to the analyzer
67                for variant in ty.variants() {
68                    let Variant { span, name, discriminant, .. } = variant;
69                    analyzer.define_constant(
70                        &mut module,
71                        Constant {
72                            span: *span,
73                            docs: None,
74                            visibility: ty.visibility(),
75                            name: name.clone(),
76                            value: discriminant.clone(),
77                        },
78                    );
79                }
80
81                // Defer definition of the enum until we discover all constants
82                enums.push(ty.with_docs(docs.take()));
83            },
84            Form::Constant(constant) => {
85                analyzer.define_constant(&mut module, constant.with_docs(docs.take()));
86            },
87            Form::Alias(item) if item.visibility().is_public() => match kind {
88                ModuleKind::Kernel if module.is_kernel() => {
89                    docs.take();
90                    analyzer.error(SemanticAnalysisError::ReexportFromKernel { span: item.span() });
91                },
92                ModuleKind::Executable => {
93                    docs.take();
94                    analyzer.error(SemanticAnalysisError::UnexpectedExport { span: item.span() });
95                },
96                _ => {
97                    define_alias(item.with_docs(docs.take()), &mut module, &mut analyzer)?;
98                },
99            },
100            Form::Alias(item) => {
101                define_alias(item.with_docs(docs.take()), &mut module, &mut analyzer)?
102            },
103            Form::Procedure(export) => match kind {
104                ModuleKind::Executable
105                    if export.visibility().is_public() && !export.is_entrypoint() =>
106                {
107                    docs.take();
108                    analyzer.error(SemanticAnalysisError::UnexpectedExport { span: export.span() });
109                },
110                _ => {
111                    define_procedure(export.with_docs(docs.take()), &mut module, &mut analyzer)?;
112                },
113            },
114            Form::Begin(body) if matches!(kind, ModuleKind::Executable) => {
115                let docs = docs.take();
116                let procedure =
117                    Procedure::new(body.span(), Visibility::Public, ProcedureName::main(), 0, body)
118                        .with_docs(docs);
119                define_procedure(procedure, &mut module, &mut analyzer)?;
120            },
121            Form::Begin(body) => {
122                docs.take();
123                analyzer.error(SemanticAnalysisError::UnexpectedEntrypoint { span: body.span() });
124            },
125            Form::AdviceMapEntry(entry) => {
126                add_advice_map_entry(&mut module, entry.with_docs(docs.take()), &mut analyzer)?;
127            },
128        }
129    }
130
131    if let Some(unused) = docs.take() {
132        analyzer.error(SemanticAnalysisError::UnusedDocstring { span: unused.span() });
133    }
134
135    // Simplify all constant declarations
136    analyzer.simplify_constants();
137
138    // Define enums now that all constant declarations have been discovered
139    for mut ty in enums {
140        for variant in ty.variants_mut() {
141            variant.discriminant = analyzer.get_constant(&variant.name).unwrap().clone();
142        }
143
144        if let Err(err) = module.define_enum(ty) {
145            analyzer.error(err);
146        }
147    }
148
149    if matches!(kind, ModuleKind::Executable) && !module.has_entrypoint() {
150        analyzer.error(SemanticAnalysisError::MissingEntrypoint);
151    }
152
153    analyzer.has_failed()?;
154
155    // Run item checks
156    visit_items(&mut module, &mut analyzer)?;
157
158    // Check unused imports
159    for import in module.aliases() {
160        if !import.is_used() {
161            analyzer.error(SemanticAnalysisError::UnusedImport { span: import.span() });
162        }
163    }
164
165    analyzer.into_result().map(move |_| module)
166}
167
168/// Visit all of the items of the current analysis context, and apply various transformation and
169/// analysis passes.
170///
171/// When this function returns, all local analysis is complete, and all that remains is construction
172/// of a module graph and global program analysis to perform any remaining transformations.
173fn visit_items(module: &mut Module, analyzer: &mut AnalysisContext) -> Result<(), SyntaxError> {
174    let is_kernel = module.is_kernel();
175    let locals = BTreeSet::from_iter(module.items().iter().map(|p| p.name().clone()));
176    let mut items = VecDeque::from(core::mem::take(&mut module.items));
177    while let Some(item) = items.pop_front() {
178        match item {
179            Export::Procedure(mut procedure) => {
180                // Rewrite visibility for exported kernel procedures
181                if is_kernel && procedure.visibility().is_public() {
182                    procedure.set_syscall(true);
183                }
184
185                // Evaluate all named immediates to their concrete values
186                log::debug!(target: "const-eval", "visiting procedure {}", procedure.name());
187                {
188                    let mut visitor = ConstEvalVisitor::new(analyzer);
189                    let _ = visitor.visit_mut_procedure(&mut procedure);
190                    if let Err(errs) = visitor.into_result() {
191                        for err in errs {
192                            log::error!(target: "const-eval", "error found in procedure {}: {err}", procedure.name());
193                            analyzer.error(err);
194                        }
195                    }
196                }
197
198                // Next, verify invoke targets:
199                //
200                // * Kernel procedures cannot use `syscall` or `call`
201                // * Mark imports as used if they have at least one call to a procedure defined in
202                //   that module
203                // * Verify that all external callees have a matching import
204                log::debug!(target: "verify-invoke", "visiting procedure {}", procedure.name());
205                {
206                    let mut visitor = VerifyInvokeTargets::new(
207                        analyzer,
208                        module,
209                        &locals,
210                        Some(procedure.name().clone()),
211                    );
212                    let _ = visitor.visit_mut_procedure(&mut procedure);
213                }
214                module.items.push(Export::Procedure(procedure));
215            },
216            Export::Alias(mut alias) => {
217                log::debug!(target: "verify-invoke", "visiting alias {}", alias.target());
218                {
219                    let mut visitor = VerifyInvokeTargets::new(analyzer, module, &locals, None);
220                    let _ = visitor.visit_mut_alias(&mut alias);
221                }
222                module.items.push(Export::Alias(alias));
223            },
224            Export::Constant(mut constant) => {
225                log::debug!(target: "verify-invoke", "visiting constant {}", constant.name());
226                {
227                    let mut visitor = VerifyInvokeTargets::new(analyzer, module, &locals, None);
228                    let _ = visitor.visit_mut_constant(&mut constant);
229                }
230                module.items.push(Export::Constant(constant));
231            },
232            Export::Type(mut ty) => {
233                log::debug!(target: "verify-invoke", "visiting type {}", ty.name());
234                {
235                    let mut visitor = VerifyInvokeTargets::new(analyzer, module, &locals, None);
236                    let _ = visitor.visit_mut_type_decl(&mut ty);
237                }
238                module.items.push(Export::Type(ty));
239            },
240        }
241    }
242
243    Ok(())
244}
245
246fn define_alias(
247    item: Alias,
248    module: &mut Module,
249    context: &mut AnalysisContext,
250) -> Result<(), SyntaxError> {
251    let name = item.name().clone();
252    if let Err(err) = module.define_alias(item, context.source_manager()) {
253        match err {
254            SemanticAnalysisError::SymbolConflict { .. } => {
255                // Proceed anyway, to try and capture more errors
256                context.error(err);
257            },
258            err => {
259                // We can't proceed without producing a bunch of errors
260                context.error(err);
261                context.has_failed()?;
262            },
263        }
264    }
265
266    context.register_imported_name(name);
267
268    Ok(())
269}
270
271fn define_procedure(
272    procedure: Procedure,
273    module: &mut Module,
274    context: &mut AnalysisContext,
275) -> Result<(), SyntaxError> {
276    let name = procedure.name().clone();
277    if let Err(err) = module.define_procedure(procedure, context.source_manager()) {
278        match err {
279            SemanticAnalysisError::SymbolConflict { .. } => {
280                // Proceed anyway, to try and capture more errors
281                context.error(err);
282            },
283            err => {
284                // We can't proceed without producing a bunch of errors
285                context.error(err);
286                context.has_failed()?;
287            },
288        }
289    }
290
291    context.register_procedure_name(name);
292
293    Ok(())
294}
295
296/// Inserts a new entry in the Advice Map and defines a constant corresposnding to the entry's
297/// key.
298///
299/// Returns `Err` if the symbol is already defined
300fn add_advice_map_entry(
301    module: &mut Module,
302    entry: AdviceMapEntry,
303    context: &mut AnalysisContext,
304) -> Result<(), SyntaxError> {
305    let key = match entry.key {
306        Some(key) => Word::from(key.inner().0),
307        None => Rpo256::hash_elements(&entry.value),
308    };
309    let cst = Constant::new(
310        entry.span,
311        Visibility::Private,
312        entry.name.clone(),
313        ConstantExpr::Word(Span::new(entry.span, WordValue(*key))),
314    );
315    context.define_constant(module, cst);
316    match module.advice_map.get(&key) {
317        Some(_) => {
318            context.error(SemanticAnalysisError::AdvMapKeyAlreadyDefined { span: entry.span });
319        },
320        None => {
321            module.advice_map.insert(key, entry.value);
322        },
323    }
324    Ok(())
325}