miden_assembly_syntax/sema/
mod.rs1mod context;
2mod errors;
3mod passes;
4#[cfg(test)]
5mod tests;
6
7use alloc::{
8 boxed::Box,
9 collections::{BTreeSet, VecDeque},
10 sync::Arc,
11 vec::Vec,
12};
13
14use miden_core::{Word, crypto::hash::Poseidon2};
15use miden_debug_types::{SourceFile, SourceManager, Span, Spanned};
16use smallvec::SmallVec;
17
18pub use self::{
19 context::AnalysisContext,
20 errors::{SemanticAnalysisError, SyntaxError},
21 passes::{ConstEvalVisitor, VerifyInvokeTargets, VerifyRepeatCounts},
22};
23use crate::{ast::*, parser::WordValue};
24
25pub fn analyze(
36 source: Arc<SourceFile>,
37 kind: ModuleKind,
38 path: &Path,
39 forms: Vec<Form>,
40 warnings_as_errors: bool,
41 source_manager: Arc<dyn SourceManager>,
42) -> Result<Box<Module>, SyntaxError> {
43 log::debug!(target: "sema", "starting semantic analysis for '{path}' (kind = {kind})");
44 let mut analyzer = AnalysisContext::new(source.clone(), source_manager);
45 analyzer.set_warnings_as_errors(warnings_as_errors);
46
47 let mut module = Box::new(Module::new(kind, path).with_span(source.source_span()));
48
49 let mut forms = VecDeque::from(forms);
50 let mut enums = SmallVec::<[EnumType; 1]>::new_const();
51 let mut docs = None;
52 while let Some(form) = forms.pop_front() {
53 match form {
54 Form::ModuleDoc(docstring) => {
55 assert!(docs.is_none());
56 module.set_docs(Some(docstring));
57 },
58 Form::Doc(docstring) => {
59 if let Some(unused) = docs.replace(docstring) {
60 analyzer.error(SemanticAnalysisError::UnusedDocstring { span: unused.span() });
61 }
62 },
63 Form::Type(ty) => {
64 if let Err(err) = module.define_type(ty.with_docs(docs.take())) {
65 analyzer.error(err);
66 }
67 },
68 Form::Enum(ty) => {
69 for variant in ty.variants() {
71 let Variant { span, name, discriminant, .. } = variant;
72 analyzer.register_constant(Constant {
73 span: *span,
74 docs: None,
75 visibility: ty.visibility(),
76 name: name.clone(),
77 value: discriminant.clone(),
78 });
79 }
80
81 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 analyzer.simplify_constants();
137 for item in module.items_mut().iter_mut() {
138 let Export::Constant(constant) = item else {
139 continue;
140 };
141 constant.value = analyzer
142 .get_constant(&constant.name)
143 .expect("semantic analysis tracks all module constants")
144 .clone();
145 }
146
147 for mut ty in enums {
149 for variant in ty.variants_mut() {
150 variant.discriminant = analyzer.get_constant(&variant.name).unwrap().clone();
151 }
152
153 if let Err(err) = module.define_enum(ty) {
154 analyzer.error(err);
155 }
156 }
157
158 if matches!(kind, ModuleKind::Executable) && !module.has_entrypoint() {
159 analyzer.error(SemanticAnalysisError::MissingEntrypoint);
160 }
161
162 analyzer.has_failed()?;
163
164 visit_items(&mut module, &mut analyzer)?;
166
167 for import in module.aliases() {
169 if !import.is_used() {
170 analyzer.error(SemanticAnalysisError::UnusedImport { span: import.span() });
171 }
172 }
173
174 analyzer.into_result().map(move |_| module)
175}
176
177fn visit_items(module: &mut Module, analyzer: &mut AnalysisContext) -> Result<(), SyntaxError> {
183 let is_kernel = module.is_kernel();
184 let locals = BTreeSet::from_iter(module.items().iter().map(|p| p.name().clone()));
185 let mut items = VecDeque::from(core::mem::take(&mut module.items));
186 while let Some(item) = items.pop_front() {
187 match item {
188 Export::Procedure(mut procedure) => {
189 if is_kernel && procedure.visibility().is_public() {
191 procedure.set_syscall(true);
192 }
193
194 log::debug!(target: "const-eval", "visiting procedure {}", procedure.name());
196 {
197 let mut visitor = ConstEvalVisitor::new(analyzer);
198 let _ = visitor.visit_mut_procedure(&mut procedure);
199 if let Err(errs) = visitor.into_result() {
200 for err in errs {
201 log::error!(target: "const-eval", "error found in procedure {}: {err}", procedure.name());
202 analyzer.error(err);
203 }
204 }
205 }
206
207 log::debug!(target: "verify-repeat", "visiting procedure {}", procedure.name());
209 {
210 let mut visitor = VerifyRepeatCounts::new(analyzer);
211 let _ = visitor.visit_procedure(&procedure);
212 }
213
214 log::debug!(target: "verify-invoke", "visiting procedure {}", procedure.name());
220 {
221 let mut visitor = VerifyInvokeTargets::new(
222 analyzer,
223 module,
224 &locals,
225 Some(procedure.name().clone()),
226 );
227 let _ = visitor.visit_mut_procedure(&mut procedure);
228 }
229 module.items.push(Export::Procedure(procedure));
230 },
231 Export::Alias(mut alias) => {
232 log::debug!(target: "verify-invoke", "visiting alias {}", alias.target());
233 {
234 let mut visitor = VerifyInvokeTargets::new(analyzer, module, &locals, None);
235 let _ = visitor.visit_mut_alias(&mut alias);
236 }
237 module.items.push(Export::Alias(alias));
238 },
239 Export::Constant(mut constant) => {
240 log::debug!(target: "verify-invoke", "visiting constant {}", constant.name());
241 {
242 let mut visitor = VerifyInvokeTargets::new(analyzer, module, &locals, None);
243 let _ = visitor.visit_mut_constant(&mut constant);
244 }
245 module.items.push(Export::Constant(constant));
246 },
247 Export::Type(mut ty) => {
248 log::debug!(target: "verify-invoke", "visiting type {}", ty.name());
249 {
250 let mut visitor = VerifyInvokeTargets::new(analyzer, module, &locals, None);
251 let _ = visitor.visit_mut_type_decl(&mut ty);
252 }
253 module.items.push(Export::Type(ty));
254 },
255 }
256 }
257
258 Ok(())
259}
260
261fn define_alias(
262 item: Alias,
263 module: &mut Module,
264 context: &mut AnalysisContext,
265) -> Result<(), SyntaxError> {
266 let name = item.name().clone();
267 if let Err(err) = module.define_alias(item, context.source_manager()) {
268 match err {
269 SemanticAnalysisError::SymbolConflict { .. } => {
270 context.error(err);
272 },
273 err => {
274 context.error(err);
276 context.has_failed()?;
277 },
278 }
279 }
280
281 context.register_imported_name(name);
282
283 Ok(())
284}
285
286fn define_procedure(
287 procedure: Procedure,
288 module: &mut Module,
289 context: &mut AnalysisContext,
290) -> Result<(), SyntaxError> {
291 let name = procedure.name().clone();
292 if let Err(err) = module.define_procedure(procedure, context.source_manager()) {
293 match err {
294 SemanticAnalysisError::SymbolConflict { .. } => {
295 context.error(err);
297 },
298 err => {
299 context.error(err);
301 context.has_failed()?;
302 },
303 }
304 }
305
306 context.register_procedure_name(name);
307
308 Ok(())
309}
310
311fn add_advice_map_entry(
316 module: &mut Module,
317 entry: AdviceMapEntry,
318 context: &mut AnalysisContext,
319) -> Result<(), SyntaxError> {
320 let key = match entry.key {
321 Some(key) => Word::from(key.inner().0),
322 None => Poseidon2::hash_elements(&entry.value),
323 };
324 let cst = Constant::new(
325 entry.span,
326 Visibility::Private,
327 entry.name.clone(),
328 ConstantExpr::Word(Span::new(entry.span, WordValue(*key))),
329 );
330 context.define_constant(module, cst);
331 match module.advice_map.get(&key) {
332 Some(_) => {
333 context.error(SemanticAnalysisError::AdvMapKeyAlreadyDefined { span: entry.span });
334 },
335 None => {
336 module.advice_map.insert(key, entry.value);
337 },
338 }
339 Ok(())
340}