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}