Skip to main content

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}