miden_assembly_syntax/sema/passes/const_eval.rs
1use alloc::{sync::Arc, vec::Vec};
2use core::ops::ControlFlow;
3
4use miden_core::{events::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 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 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(EventImmediate::Immediate(Immediate::Constant(name)))
147 | Instruction::TraceImm(EventImmediate::Immediate(Immediate::Constant(name))) = &**inst
148 {
149 let span = name.span();
150 match self.env.get(name) {
151 Ok(Some(
152 CachedConstantValue::Miss(ConstantExpr::Hash(HashKind::Event, _))
153 | CachedConstantValue::Hit(ConstantValue::Hash(HashKind::Event, _)),
154 )) => {
155 // CHANGE: allow `emit.EVENT` / `trace.EVENT` when `EVENT` was defined via
156 // const.EVENT = event("...")
157 // NOTE: This function only validates the kind; the actual resolution to a Felt
158 // happens below in `visit_mut_immediate_felt` just like other Felt immediates.
159 // Enabled syntax:
160 // const.EVT = event("...")
161 // emit.EVT
162 // trace.EVT
163 },
164 Ok(Some(CachedConstantValue::Miss(expr @ ConstantExpr::Var(_)))) => {
165 // A reference to another constant was used, try to evaluate the expression
166 let expr = expr.clone();
167 match constants::eval::expr(&expr, self.env) {
168 Ok(ConstantExpr::Hash(HashKind::Event, _)) => (),
169 // Unable to evaluate in the current context
170 Ok(ConstantExpr::Var(_)) => return ControlFlow::Continue(()),
171 Ok(_) => {
172 self.errors.push(
173 ConstEvalError::InvalidConstant {
174 span,
175 expected: "an event name",
176 source_file: self.env.get_source_file_for(span),
177 }
178 .into(),
179 );
180 },
181 Err(err) => {
182 self.errors.push(err);
183 },
184 }
185 },
186 Ok(Some(_)) => {
187 // CHANGE: disallow `emit.CONST` / `trace.CONST` unless CONST is defined via
188 // `event("...")`.
189 // Examples which now error:
190 // const.BAD = 42
191 // emit.BAD
192 // trace.BAD
193 // const.W = word("foo")
194 // emit.W
195 // trace.W
196 self.errors.push(
197 ConstEvalError::InvalidConstant {
198 span,
199 expected: "an event name",
200 source_file: self.env.get_source_file_for(span),
201 }
202 .into(),
203 );
204 },
205 // The value is not yet available, proceed for now
206 Ok(None) => return ControlFlow::Continue(()),
207 Err(err) => {
208 self.errors.push(err);
209 },
210 }
211 }
212 visit::visit_mut_inst(self, inst)
213 }
214 fn visit_mut_immediate_u8(&mut self, imm: &mut Immediate<u8>) -> ControlFlow<()> {
215 self.eval_const(imm)
216 }
217 fn visit_mut_immediate_u16(&mut self, imm: &mut Immediate<u16>) -> ControlFlow<()> {
218 self.eval_const(imm)
219 }
220 fn visit_mut_immediate_u32(&mut self, imm: &mut Immediate<u32>) -> ControlFlow<()> {
221 self.eval_const(imm)
222 }
223 fn visit_mut_immediate_error_message(
224 &mut self,
225 imm: &mut Immediate<Arc<str>>,
226 ) -> ControlFlow<()> {
227 match imm {
228 Immediate::Value(_) => ControlFlow::Continue(()),
229 Immediate::Constant(name) => {
230 let span = name.span();
231 match self.env.get_error(name) {
232 Ok(Some(value)) => {
233 *imm = Immediate::Value(Span::new(span, value));
234 },
235 // The constant is externally-defined, and not available yet
236 Ok(None) => (),
237 Err(error) => {
238 self.errors.push(error);
239 },
240 }
241 ControlFlow::Continue(())
242 },
243 }
244 }
245 fn visit_mut_immediate_felt(&mut self, imm: &mut Immediate<Felt>) -> ControlFlow<()> {
246 match imm {
247 Immediate::Value(_) => ControlFlow::Continue(()),
248 Immediate::Constant(name) => {
249 let span = name.span();
250 match self.env.get(name) {
251 Ok(Some(
252 CachedConstantValue::Miss(ConstantExpr::Int(value))
253 | CachedConstantValue::Hit(ConstantValue::Int(value)),
254 )) => {
255 *imm = Immediate::Value(Span::new(
256 span,
257 Felt::new_unchecked(value.inner().as_int()),
258 ));
259 },
260 Ok(Some(
261 CachedConstantValue::Miss(ConstantExpr::Hash(HashKind::Event, string))
262 | CachedConstantValue::Hit(ConstantValue::Hash(HashKind::Event, string)),
263 )) => {
264 // CHANGE: resolve `event("...")` to a Felt when a Felt immediate is
265 // expected (e.g. enables `emit.EVENT`):
266 // const.EVT = event("...")
267 // emit.EVT
268 let event_id = EventId::from_name(string.as_str()).as_felt();
269 *imm = Immediate::Value(Span::new(span, event_id));
270 },
271 Ok(Some(CachedConstantValue::Miss(
272 expr @ (ConstantExpr::Var(_) | ConstantExpr::BinaryOp { .. }),
273 ))) => {
274 // A reference to another constant was used, try to evaluate the expression
275 let expr = expr.clone();
276 match constants::eval::expr(&expr, self.env) {
277 Ok(ConstantExpr::Int(value)) => {
278 *imm = Immediate::Value(Span::new(
279 span,
280 Felt::new_unchecked(value.inner().as_int()),
281 ));
282 },
283 Ok(ConstantExpr::Hash(HashKind::Event, value)) => {
284 // CHANGE: resolve `event("...")` to a Felt when a Felt immediate is
285 // expected (e.g. enables `emit.EVENT`):
286 // const.EVT = event("...")
287 // emit.EVT
288 let event_id = EventId::from_name(value.as_str()).as_felt();
289 *imm = Immediate::Value(Span::new(span, event_id));
290 },
291 // Unable to evaluate in the current context
292 Ok(ConstantExpr::Var(_) | ConstantExpr::BinaryOp { .. }) => (),
293 Ok(_) => {
294 self.errors.push(
295 ConstEvalError::InvalidConstant {
296 span,
297 expected: "a felt",
298 source_file: self.env.get_source_file_for(span),
299 }
300 .into(),
301 );
302 },
303 Err(err) => {
304 self.errors.push(err);
305 },
306 }
307 },
308 // Invalid value
309 Ok(Some(_)) => {
310 self.errors.push(
311 ConstEvalError::InvalidConstant {
312 span,
313 expected: "a felt",
314 source_file: self.env.get_source_file_for(span),
315 }
316 .into(),
317 );
318 },
319 // The constant expression references an externally-defined symbol which is
320 // not available yet, so ignore for now
321 Ok(None) => (),
322 Err(err) => {
323 self.errors.push(err);
324 },
325 }
326 ControlFlow::Continue(())
327 },
328 }
329 }
330
331 fn visit_mut_immediate_push_value(
332 &mut self,
333 imm: &mut Immediate<PushValue>,
334 ) -> ControlFlow<()> {
335 match imm {
336 Immediate::Value(_) => ControlFlow::Continue(()),
337 Immediate::Constant(name) => {
338 let span = name.span();
339 match self.env.get(name) {
340 Ok(Some(
341 CachedConstantValue::Miss(ConstantExpr::Int(value))
342 | CachedConstantValue::Hit(ConstantValue::Int(value)),
343 )) => {
344 *imm = Immediate::Value(Span::new(span, PushValue::Int(*value.inner())));
345 },
346 Ok(Some(
347 CachedConstantValue::Miss(ConstantExpr::Word(value))
348 | CachedConstantValue::Hit(ConstantValue::Word(value)),
349 )) => {
350 *imm = Immediate::Value(Span::new(span, PushValue::Word(*value.inner())));
351 },
352 Ok(Some(
353 CachedConstantValue::Miss(ConstantExpr::Hash(hash_kind, string))
354 | CachedConstantValue::Hit(ConstantValue::Hash(hash_kind, string)),
355 )) => match hash_kind {
356 HashKind::Word => {
357 // Existing behavior for `const.W = word("...")`:
358 // push.W # pushes a Word
359 let hash_word = hash_string_to_word(string.as_str());
360 *imm = Immediate::Value(Span::new(
361 span,
362 PushValue::Word(WordValue(*hash_word)),
363 ));
364 },
365 HashKind::Event => {
366 // CHANGE: allow `const.EVT = event("...")` with IntValue contexts by
367 // reducing to a Felt via word()[0]. Enables:
368 // const.EVT = event("...")
369 // push.EVT # pushes the Felt event id
370 let event_id = EventId::from_name(string.as_str()).as_felt();
371 *imm =
372 Immediate::Value(Span::new(span, IntValue::Felt(event_id).into()));
373 },
374 },
375 Ok(Some(CachedConstantValue::Miss(
376 expr @ (ConstantExpr::Var(_) | ConstantExpr::BinaryOp { .. }),
377 ))) => {
378 // A reference to another constant was used, try to evaluate the expression
379 let expr = expr.clone();
380 match constants::eval::expr(&expr, self.env) {
381 Ok(ConstantExpr::Int(value)) => {
382 *imm = Immediate::Value(Span::new(
383 span,
384 PushValue::Int(*value.inner()),
385 ));
386 },
387 Ok(ConstantExpr::Word(value)) => {
388 *imm = Immediate::Value(Span::new(
389 span,
390 PushValue::Word(*value.inner()),
391 ));
392 },
393 Ok(ConstantExpr::Hash(HashKind::Word, value)) => {
394 // Existing behavior for `const.W = word("...")`:
395 // push.W # pushes a Word
396 let hash_word = hash_string_to_word(value.as_str());
397 *imm = Immediate::Value(Span::new(
398 span,
399 PushValue::Word(WordValue(*hash_word)),
400 ));
401 },
402 Ok(ConstantExpr::Hash(HashKind::Event, value)) => {
403 // CHANGE: allow `const.EVT = event("...")` with IntValue contexts
404 // by reducing to a Felt via word()[0]. Enables:
405 // const.EVT = event("...")
406 // push.EVT # pushes the Felt event id
407 let event_id = EventId::from_name(value.as_str()).as_felt();
408 *imm = Immediate::Value(Span::new(
409 span,
410 IntValue::Felt(event_id).into(),
411 ));
412 },
413 // Unable to evaluate in the current context
414 Ok(ConstantExpr::Var(_) | ConstantExpr::BinaryOp { .. }) => (),
415 Ok(_) => {
416 self.errors.push(
417 ConstEvalError::InvalidConstant {
418 span,
419 expected: "an integer or word",
420 source_file: self.env.get_source_file_for(span),
421 }
422 .into(),
423 );
424 },
425 Err(err) => {
426 self.errors.push(err);
427 },
428 }
429 },
430 Ok(Some(_)) => {
431 self.errors.push(
432 ConstEvalError::InvalidConstant {
433 span,
434 expected: "an integer or word",
435 source_file: self.env.get_source_file_for(span),
436 }
437 .into(),
438 );
439 },
440 // The constant references an externally-defined symbol which is not yet
441 // available, so ignore for now
442 Ok(None) => (),
443 Err(err) => {
444 self.errors.push(err);
445 },
446 }
447 ControlFlow::Continue(())
448 },
449 }
450 }
451
452 fn visit_mut_immediate_word_value(
453 &mut self,
454 imm: &mut Immediate<WordValue>,
455 ) -> ControlFlow<()> {
456 match imm {
457 Immediate::Value(_) => ControlFlow::Continue(()),
458 Immediate::Constant(name) => {
459 let span = name.span();
460 match self.env.get(name) {
461 Ok(Some(
462 CachedConstantValue::Miss(ConstantExpr::Word(value))
463 | CachedConstantValue::Hit(ConstantValue::Word(value)),
464 )) => {
465 *imm = Immediate::Value(Span::new(span, *value.inner()));
466 },
467 Ok(Some(
468 CachedConstantValue::Miss(ConstantExpr::Hash(HashKind::Word, string))
469 | CachedConstantValue::Hit(ConstantValue::Hash(HashKind::Word, string)),
470 )) => {
471 // Existing behavior for `const.W = word("...")`:
472 // push.W # pushes a Word
473 let hash_word = hash_string_to_word(string.as_str());
474 *imm = Immediate::Value(Span::new(span, WordValue(*hash_word)));
475 },
476 Ok(Some(CachedConstantValue::Miss(expr @ ConstantExpr::Var(_)))) => {
477 // A reference to another constant was used, try to evaluate the expression
478 let expr = expr.clone();
479 match constants::eval::expr(&expr, self.env) {
480 Ok(ConstantExpr::Word(value)) => {
481 *imm = Immediate::Value(Span::new(span, *value.inner()));
482 },
483 Ok(ConstantExpr::Hash(HashKind::Word, value)) => {
484 // Existing behavior for `const.W = word("...")`:
485 // push.W # pushes a Word
486 let hash_word = hash_string_to_word(value.as_str());
487 *imm = Immediate::Value(Span::new(span, WordValue(*hash_word)));
488 },
489 // Unable to evaluate in the current context
490 Ok(ConstantExpr::Var(_)) => (),
491 Ok(_) => {
492 self.errors.push(
493 ConstEvalError::InvalidConstant {
494 span,
495 expected: "a word",
496 source_file: self.env.get_source_file_for(span),
497 }
498 .into(),
499 );
500 },
501 Err(err) => {
502 self.errors.push(err);
503 },
504 }
505 },
506 Ok(Some(_)) => {
507 self.errors.push(
508 ConstEvalError::InvalidConstant {
509 span,
510 expected: "a word",
511 source_file: self.env.get_source_file_for(span),
512 }
513 .into(),
514 );
515 },
516 // The constant references an externally-defined symbol which is not yet
517 // available, so ignore for now
518 Ok(None) => (),
519 Err(err) => {
520 self.errors.push(err);
521 },
522 }
523 ControlFlow::Continue(())
524 },
525 }
526 }
527}