miden_assembly_syntax/sema/passes/const_eval.rs
1use alloc::{sync::Arc, vec::Vec};
2use core::ops::ControlFlow;
3
4use miden_core::{EventId, utils::hash_string_to_word};
5use miden_debug_types::{Span, Spanned};
6
7use crate::{
8 Felt,
9 ast::{
10 constants::{ConstEnvironment, ConstEvalError, eval::CachedConstantValue},
11 *,
12 },
13 parser::{IntValue, PushValue, WordValue},
14};
15
16/// This visitor evaluates all constant expressions and folds them to literals.
17///
18/// This visitor is abstracted over the const-evaluation environment so that it's implementation
19/// can be reused for both module-local rewrites, and link-time rewrites where we have all the
20/// symbols available to resolve foreign constants.
21pub struct ConstEvalVisitor<'env, Env>
22where
23 Env: ?Sized + ConstEnvironment,
24{
25 env: &'env mut Env,
26 errors: Vec<<Env as ConstEnvironment>::Error>,
27}
28
29impl<'env, Env> ConstEvalVisitor<'env, Env>
30where
31 Env: ?Sized + ConstEnvironment,
32 <Env as ConstEnvironment>::Error: From<ConstEvalError>,
33{
34 pub fn new(env: &'env mut Env) -> Self {
35 Self { env, errors: Default::default() }
36 }
37
38 pub fn into_result(self) -> Result<(), Vec<<Env as ConstEnvironment>::Error>> {
39 if self.errors.is_empty() {
40 Ok(())
41 } else {
42 Err(self.errors)
43 }
44 }
45
46 fn eval_const<T>(&mut self, imm: &mut Immediate<T>) -> ControlFlow<()>
47 where
48 T: TryFrom<u64>,
49 {
50 match imm {
51 Immediate::Value(_) => ControlFlow::Continue(()),
52 Immediate::Constant(name) => {
53 let span = name.span();
54 let value = match self.env.get(name) {
55 Ok(Some(
56 CachedConstantValue::Hit(ConstantValue::Int(value))
57 | CachedConstantValue::Miss(ConstantExpr::Int(value)),
58 )) => *value,
59 Ok(Some(CachedConstantValue::Miss(
60 expr @ (ConstantExpr::Var(_) | ConstantExpr::BinaryOp { .. }),
61 ))) => {
62 // A reference to another constant was used, try to evaluate the expression
63 let expr = expr.clone();
64 match crate::ast::constants::eval::expr(&expr, self.env) {
65 Ok(ConstantExpr::Int(value)) => value,
66 // Unable to evaluate in the current context
67 Ok(ConstantExpr::Var(_) | ConstantExpr::BinaryOp { .. }) => {
68 return ControlFlow::Continue(());
69 },
70 Ok(_) => {
71 self.errors.push(
72 ConstEvalError::InvalidConstant {
73 span,
74 expected: "an integer",
75 source_file: self.env.get_source_file_for(span),
76 }
77 .into(),
78 );
79 return ControlFlow::Continue(());
80 },
81 Err(err) => {
82 self.errors.push(err);
83 return ControlFlow::Continue(());
84 },
85 }
86 },
87 Ok(Some(_)) => {
88 self.errors.push(
89 ConstEvalError::InvalidConstant {
90 span,
91 expected: core::any::type_name::<T>(),
92 source_file: self.env.get_source_file_for(span),
93 }
94 .into(),
95 );
96 return ControlFlow::Continue(());
97 },
98 Ok(None) => return ControlFlow::Continue(()),
99 Err(err) => {
100 self.errors.push(err);
101 return ControlFlow::Continue(());
102 },
103 };
104 match T::try_from(value.as_int()) {
105 Ok(value) => {
106 *imm = Immediate::Value(Span::new(span, value));
107 },
108 Err(_) => {
109 self.errors.push(
110 ConstEvalError::ImmediateOverflow {
111 span,
112 source_file: self.env.get_source_file_for(span),
113 }
114 .into(),
115 );
116 },
117 }
118 ControlFlow::Continue(())
119 },
120 }
121 }
122}
123
124impl<'env, Env> VisitMut for ConstEvalVisitor<'env, Env>
125where
126 Env: ?Sized + ConstEnvironment,
127 <Env as ConstEnvironment>::Error: From<ConstEvalError>,
128{
129 fn visit_mut_constant(&mut self, constant: &mut Constant) -> ControlFlow<()> {
130 if constant.value.is_value() {
131 return ControlFlow::Continue(());
132 }
133
134 match crate::ast::constants::eval::expr(&constant.value, self.env) {
135 Ok(evaluated) => {
136 constant.value = evaluated;
137 },
138 Err(err) => {
139 self.errors.push(err);
140 },
141 }
142 ControlFlow::Continue(())
143 }
144 fn visit_mut_inst(&mut self, inst: &mut Span<Instruction>) -> ControlFlow<()> {
145 use crate::ast::Instruction;
146 if let Instruction::EmitImm(Immediate::Constant(name)) = &**inst {
147 let span = name.span();
148 match self.env.get(name) {
149 Ok(Some(
150 CachedConstantValue::Miss(ConstantExpr::Hash(HashKind::Event, _))
151 | CachedConstantValue::Hit(ConstantValue::Hash(HashKind::Event, _)),
152 )) => {
153 // CHANGE: allow `emit.EVENT` when `EVENT` was defined via
154 // const.EVENT = event("...")
155 // NOTE: This function only validates the kind; the actual resolution to a Felt
156 // happens below in `visit_mut_immediate_felt` just like other Felt immediates.
157 // Enabled syntax:
158 // const.EVT = event("...")
159 // emit.EVT
160 },
161 Ok(Some(CachedConstantValue::Miss(expr @ ConstantExpr::Var(_)))) => {
162 // A reference to another constant was used, try to evaluate the expression
163 let expr = expr.clone();
164 match crate::ast::constants::eval::expr(&expr, self.env) {
165 Ok(ConstantExpr::Hash(HashKind::Event, _)) => (),
166 // Unable to evaluate in the current context
167 Ok(ConstantExpr::Var(_)) => return ControlFlow::Continue(()),
168 Ok(_) => {
169 self.errors.push(
170 ConstEvalError::InvalidConstant {
171 span,
172 expected: "an event name",
173 source_file: self.env.get_source_file_for(span),
174 }
175 .into(),
176 );
177 },
178 Err(err) => {
179 self.errors.push(err);
180 },
181 }
182 },
183 Ok(Some(_)) => {
184 // CHANGE: disallow `emit.CONST` unless CONST is defined via `event("...")`.
185 // Examples which now error:
186 // const.BAD = 42
187 // emit.BAD
188 // const.W = word("foo")
189 // emit.W
190 self.errors.push(
191 ConstEvalError::InvalidConstant {
192 span,
193 expected: "an event name",
194 source_file: self.env.get_source_file_for(span),
195 }
196 .into(),
197 );
198 },
199 // The value is not yet available, proceed for now
200 Ok(None) => return ControlFlow::Continue(()),
201 Err(err) => {
202 self.errors.push(err);
203 },
204 }
205 }
206 crate::ast::visit::visit_mut_inst(self, inst)
207 }
208 fn visit_mut_immediate_u8(&mut self, imm: &mut Immediate<u8>) -> ControlFlow<()> {
209 self.eval_const(imm)
210 }
211 fn visit_mut_immediate_u16(&mut self, imm: &mut Immediate<u16>) -> ControlFlow<()> {
212 self.eval_const(imm)
213 }
214 fn visit_mut_immediate_u32(&mut self, imm: &mut Immediate<u32>) -> ControlFlow<()> {
215 self.eval_const(imm)
216 }
217 fn visit_mut_immediate_error_message(
218 &mut self,
219 imm: &mut Immediate<Arc<str>>,
220 ) -> ControlFlow<()> {
221 match imm {
222 Immediate::Value(_) => ControlFlow::Continue(()),
223 Immediate::Constant(name) => {
224 let span = name.span();
225 match self.env.get_error(name) {
226 Ok(Some(value)) => {
227 *imm = Immediate::Value(Span::new(span, value.clone()));
228 },
229 // The constant is externally-defined, and not available yet
230 Ok(None) => (),
231 Err(error) => {
232 self.errors.push(error);
233 },
234 }
235 ControlFlow::Continue(())
236 },
237 }
238 }
239 fn visit_mut_immediate_felt(&mut self, imm: &mut Immediate<Felt>) -> ControlFlow<()> {
240 match imm {
241 Immediate::Value(_) => ControlFlow::Continue(()),
242 Immediate::Constant(name) => {
243 let span = name.span();
244 match self.env.get(name) {
245 Ok(Some(
246 CachedConstantValue::Miss(ConstantExpr::Int(value))
247 | CachedConstantValue::Hit(ConstantValue::Int(value)),
248 )) => {
249 *imm = Immediate::Value(Span::new(span, Felt::new(value.inner().as_int())));
250 },
251 Ok(Some(
252 CachedConstantValue::Miss(ConstantExpr::Hash(HashKind::Event, string))
253 | CachedConstantValue::Hit(ConstantValue::Hash(HashKind::Event, string)),
254 )) => {
255 // CHANGE: resolve `event("...")` to a Felt when a Felt immediate is
256 // expected (e.g. enables `emit.EVENT`):
257 // const.EVT = event("...")
258 // emit.EVT
259 let event_id = EventId::from_name(string.as_str()).as_felt();
260 *imm = Immediate::Value(Span::new(span, event_id));
261 },
262 Ok(Some(CachedConstantValue::Miss(
263 expr @ (ConstantExpr::Var(_) | ConstantExpr::BinaryOp { .. }),
264 ))) => {
265 // A reference to another constant was used, try to evaluate the expression
266 let expr = expr.clone();
267 match crate::ast::constants::eval::expr(&expr, self.env) {
268 Ok(ConstantExpr::Int(value)) => {
269 *imm = Immediate::Value(Span::new(
270 span,
271 Felt::new(value.inner().as_int()),
272 ));
273 },
274 Ok(ConstantExpr::Hash(HashKind::Event, value)) => {
275 // CHANGE: resolve `event("...")` to a Felt when a Felt immediate is
276 // expected (e.g. enables `emit.EVENT`):
277 // const.EVT = event("...")
278 // emit.EVT
279 let event_id = EventId::from_name(value.as_str()).as_felt();
280 *imm = Immediate::Value(Span::new(span, event_id));
281 },
282 // Unable to evaluate in the current context
283 Ok(ConstantExpr::Var(_) | ConstantExpr::BinaryOp { .. }) => (),
284 Ok(_) => {
285 self.errors.push(
286 ConstEvalError::InvalidConstant {
287 span,
288 expected: "a felt",
289 source_file: self.env.get_source_file_for(span),
290 }
291 .into(),
292 );
293 },
294 Err(err) => {
295 self.errors.push(err);
296 },
297 }
298 },
299 // Invalid value
300 Ok(Some(_)) => {
301 self.errors.push(
302 ConstEvalError::InvalidConstant {
303 span,
304 expected: "a felt",
305 source_file: self.env.get_source_file_for(span),
306 }
307 .into(),
308 );
309 },
310 // The constant expression references an externally-defined symbol which is
311 // not available yet, so ignore for now
312 Ok(None) => (),
313 Err(err) => {
314 self.errors.push(err);
315 },
316 }
317 ControlFlow::Continue(())
318 },
319 }
320 }
321
322 fn visit_mut_immediate_push_value(
323 &mut self,
324 imm: &mut Immediate<PushValue>,
325 ) -> ControlFlow<()> {
326 match imm {
327 Immediate::Value(_) => ControlFlow::Continue(()),
328 Immediate::Constant(name) => {
329 let span = name.span();
330 match self.env.get(name) {
331 Ok(Some(
332 CachedConstantValue::Miss(ConstantExpr::Int(value))
333 | CachedConstantValue::Hit(ConstantValue::Int(value)),
334 )) => {
335 *imm = Immediate::Value(Span::new(span, PushValue::Int(*value.inner())));
336 },
337 Ok(Some(
338 CachedConstantValue::Miss(ConstantExpr::Word(value))
339 | CachedConstantValue::Hit(ConstantValue::Word(value)),
340 )) => {
341 *imm = Immediate::Value(Span::new(span, PushValue::Word(*value.inner())));
342 },
343 Ok(Some(
344 CachedConstantValue::Miss(ConstantExpr::Hash(hash_kind, string))
345 | CachedConstantValue::Hit(ConstantValue::Hash(hash_kind, string)),
346 )) => match hash_kind {
347 HashKind::Word => {
348 // Existing behavior for `const.W = word("...")`:
349 // push.W # pushes a Word
350 let hash_word = hash_string_to_word(string.as_str());
351 *imm = Immediate::Value(Span::new(
352 span,
353 PushValue::Word(WordValue(*hash_word)),
354 ));
355 },
356 HashKind::Event => {
357 // CHANGE: allow `const.EVT = event("...")` with IntValue contexts by
358 // reducing to a Felt via word()[0]. Enables:
359 // const.EVT = event("...")
360 // push.EVT # pushes the Felt event id
361 let event_id = EventId::from_name(string.as_str()).as_felt();
362 *imm =
363 Immediate::Value(Span::new(span, IntValue::Felt(event_id).into()));
364 },
365 },
366 Ok(Some(CachedConstantValue::Miss(
367 expr @ (ConstantExpr::Var(_) | ConstantExpr::BinaryOp { .. }),
368 ))) => {
369 // A reference to another constant was used, try to evaluate the expression
370 let expr = expr.clone();
371 match crate::ast::constants::eval::expr(&expr, self.env) {
372 Ok(ConstantExpr::Int(value)) => {
373 *imm = Immediate::Value(Span::new(
374 span,
375 PushValue::Int(*value.inner()),
376 ));
377 },
378 Ok(ConstantExpr::Word(value)) => {
379 *imm = Immediate::Value(Span::new(
380 span,
381 PushValue::Word(*value.inner()),
382 ));
383 },
384 Ok(ConstantExpr::Hash(HashKind::Word, value)) => {
385 // Existing behavior for `const.W = word("...")`:
386 // push.W # pushes a Word
387 let hash_word = hash_string_to_word(value.as_str());
388 *imm = Immediate::Value(Span::new(
389 span,
390 PushValue::Word(WordValue(*hash_word)),
391 ));
392 },
393 Ok(ConstantExpr::Hash(HashKind::Event, value)) => {
394 // CHANGE: allow `const.EVT = event("...")` with IntValue contexts
395 // by reducing to a Felt via word()[0]. Enables:
396 // const.EVT = event("...")
397 // push.EVT # pushes the Felt event id
398 let event_id = EventId::from_name(value.as_str()).as_felt();
399 *imm = Immediate::Value(Span::new(
400 span,
401 IntValue::Felt(event_id).into(),
402 ));
403 },
404 // Unable to evaluate in the current context
405 Ok(ConstantExpr::Var(_) | ConstantExpr::BinaryOp { .. }) => (),
406 Ok(_) => {
407 self.errors.push(
408 ConstEvalError::InvalidConstant {
409 span,
410 expected: "an integer or word",
411 source_file: self.env.get_source_file_for(span),
412 }
413 .into(),
414 );
415 },
416 Err(err) => {
417 self.errors.push(err);
418 },
419 }
420 },
421 Ok(Some(_)) => {
422 self.errors.push(
423 ConstEvalError::InvalidConstant {
424 span,
425 expected: "an integer or word",
426 source_file: self.env.get_source_file_for(span),
427 }
428 .into(),
429 );
430 },
431 // The constant references an externally-defined symbol which is not yet
432 // available, so ignore for now
433 Ok(None) => (),
434 Err(err) => {
435 self.errors.push(err);
436 },
437 }
438 ControlFlow::Continue(())
439 },
440 }
441 }
442
443 fn visit_mut_immediate_word_value(
444 &mut self,
445 imm: &mut Immediate<WordValue>,
446 ) -> ControlFlow<()> {
447 match imm {
448 Immediate::Value(_) => ControlFlow::Continue(()),
449 Immediate::Constant(name) => {
450 let span = name.span();
451 match self.env.get(name) {
452 Ok(Some(
453 CachedConstantValue::Miss(ConstantExpr::Word(value))
454 | CachedConstantValue::Hit(ConstantValue::Word(value)),
455 )) => {
456 *imm = Immediate::Value(Span::new(span, *value.inner()));
457 },
458 Ok(Some(
459 CachedConstantValue::Miss(ConstantExpr::Hash(HashKind::Word, string))
460 | CachedConstantValue::Hit(ConstantValue::Hash(HashKind::Word, string)),
461 )) => {
462 // Existing behavior for `const.W = word("...")`:
463 // push.W # pushes a Word
464 let hash_word = hash_string_to_word(string.as_str());
465 *imm = Immediate::Value(Span::new(span, WordValue(*hash_word)));
466 },
467 Ok(Some(CachedConstantValue::Miss(expr @ ConstantExpr::Var(_)))) => {
468 // A reference to another constant was used, try to evaluate the expression
469 let expr = expr.clone();
470 match crate::ast::constants::eval::expr(&expr, self.env) {
471 Ok(ConstantExpr::Word(value)) => {
472 *imm = Immediate::Value(Span::new(span, *value.inner()));
473 },
474 Ok(ConstantExpr::Hash(HashKind::Word, value)) => {
475 // Existing behavior for `const.W = word("...")`:
476 // push.W # pushes a Word
477 let hash_word = hash_string_to_word(value.as_str());
478 *imm = Immediate::Value(Span::new(span, WordValue(*hash_word)));
479 },
480 // Unable to evaluate in the current context
481 Ok(ConstantExpr::Var(_)) => (),
482 Ok(_) => {
483 self.errors.push(
484 ConstEvalError::InvalidConstant {
485 span,
486 expected: "a word",
487 source_file: self.env.get_source_file_for(span),
488 }
489 .into(),
490 );
491 },
492 Err(err) => {
493 self.errors.push(err);
494 },
495 }
496 },
497 Ok(Some(_)) => {
498 self.errors.push(
499 ConstEvalError::InvalidConstant {
500 span,
501 expected: "a word",
502 source_file: self.env.get_source_file_for(span),
503 }
504 .into(),
505 );
506 },
507 // The constant references an externally-defined symbol which is not yet
508 // available, so ignore for now
509 Ok(None) => (),
510 Err(err) => {
511 self.errors.push(err);
512 },
513 }
514 ControlFlow::Continue(())
515 },
516 }
517 }
518}