miden_assembly_syntax/sema/
mod.rs1mod 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
23pub 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 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 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
138 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 visit_items(&mut module, &mut analyzer)?;
157
158 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
168fn 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 if is_kernel && procedure.visibility().is_public() {
182 procedure.set_syscall(true);
183 }
184
185 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 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 context.error(err);
257 },
258 err => {
259 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 context.error(err);
282 },
283 err => {
284 context.error(err);
286 context.has_failed()?;
287 },
288 }
289 }
290
291 context.register_procedure_name(name);
292
293 Ok(())
294}
295
296fn 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}