miden_assembly_syntax/ast/constants/
eval.rs1#![allow(unused_assignments)]
3
4use alloc::{sync::Arc, vec::Vec};
5
6use smallvec::SmallVec;
7
8use crate::{
9 ast::*,
10 debuginfo::{SourceFile, SourceSpan, Span, Spanned},
11 diagnostics::{Diagnostic, RelatedLabel, miette},
12};
13
14#[derive(Debug, thiserror::Error, Diagnostic)]
16pub enum ConstEvalError {
17 #[error("undefined constant '{symbol}'")]
18 #[diagnostic(help("are you missing an import?"))]
19 UndefinedSymbol {
20 #[label("the constant referenced here is not defined in the current scope")]
21 symbol: Ident,
22 #[source_code]
23 source_file: Option<Arc<SourceFile>>,
24 },
25 #[error("undefined constant '{path}'")]
26 #[diagnostic(help(
27 "is the constant exported from its containing module? if the referenced module \
28 is in another library, make sure you provided it to the assembler"
29 ))]
30 UndefinedPath {
31 path: Arc<Path>,
32 #[label("this reference is invalid: no such definition found")]
33 span: SourceSpan,
34 #[source_code]
35 source_file: Option<Arc<SourceFile>>,
36 },
37 #[error("invalid immediate: value is larger than expected range")]
38 #[diagnostic()]
39 ImmediateOverflow {
40 #[label]
41 span: SourceSpan,
42 #[source_code]
43 source_file: Option<Arc<SourceFile>>,
44 },
45 #[error("invalid constant expression: division by zero")]
46 DivisionByZero {
47 #[label]
48 span: SourceSpan,
49 #[source_code]
50 source_file: Option<Arc<SourceFile>>,
51 },
52 #[error("invalid constant")]
53 #[diagnostic(help("this constant does not resolve to a value of the right type"))]
54 InvalidConstant {
55 expected: &'static str,
56 #[label("expected {expected}")]
57 span: SourceSpan,
58 #[source_code]
59 source_file: Option<Arc<SourceFile>>,
60 },
61 #[error("constant evaluation failed")]
62 #[diagnostic(help("this constant cannot be evaluated, due to operands of incorrect type"))]
63 InvalidConstExprOperand {
64 #[label]
65 span: SourceSpan,
66 #[label("expected this operand to produce an integer value, but it does not")]
67 operand: SourceSpan,
68 #[source_code]
69 source_file: Option<Arc<SourceFile>>,
70 },
71 #[error("constant evaluation terminated due to infinite recursion")]
72 #[diagnostic(help("dependencies between constants must form an acyclic graph"))]
73 ConstEvalCycle {
74 #[label("occurs while evaluating this expression")]
75 start: SourceSpan,
76 #[source_code]
77 source_file: Option<Arc<SourceFile>>,
78 #[related]
79 detected: [RelatedLabel; 1],
80 },
81}
82
83impl ConstEvalError {
84 #[inline]
85 pub fn undefined<Env>(symbol: Ident, env: &Env) -> Self
86 where
87 Env: ?Sized + ConstEnvironment,
88 <Env as ConstEnvironment>::Error: From<Self>,
89 {
90 let source_file = env.get_source_file_for(symbol.span());
91 Self::UndefinedSymbol { symbol, source_file }
92 }
93
94 #[inline]
95 pub fn invalid_constant<Env>(span: SourceSpan, expected: &'static str, env: &Env) -> Self
96 where
97 Env: ?Sized + ConstEnvironment,
98 <Env as ConstEnvironment>::Error: From<Self>,
99 {
100 let source_file = env.get_source_file_for(span);
101 Self::InvalidConstant { expected, span, source_file }
102 }
103
104 #[inline]
105 pub fn eval_cycle<Env>(start: SourceSpan, detected: SourceSpan, env: &Env) -> Self
106 where
107 Env: ?Sized + ConstEnvironment,
108 <Env as ConstEnvironment>::Error: From<Self>,
109 {
110 let start_file = env.get_source_file_for(start);
111 let detected_file = env.get_source_file_for(start);
112 let detected = [RelatedLabel::error("related error")
113 .with_labeled_span(
114 detected,
115 "cycle occurs because we attempt to eval this constant recursively",
116 )
117 .with_source_file(detected_file)];
118 Self::ConstEvalCycle { start, source_file: start_file, detected }
119 }
120}
121
122#[derive(Debug)]
123pub enum CachedConstantValue<'a> {
124 Hit(&'a ConstantValue),
126 Miss(&'a ConstantExpr),
128}
129
130impl CachedConstantValue<'_> {
131 pub fn into_expr(self) -> ConstantExpr {
132 match self {
133 Self::Hit(value) => value.clone().into(),
134 Self::Miss(expr) => expr.clone(),
135 }
136 }
137}
138
139impl Spanned for CachedConstantValue<'_> {
140 fn span(&self) -> SourceSpan {
141 match self {
142 Self::Hit(value) => value.span(),
143 Self::Miss(expr) => expr.span(),
144 }
145 }
146}
147
148pub trait ConstEnvironment {
154 type Error: From<ConstEvalError>;
158
159 fn get_source_file_for(&self, span: SourceSpan) -> Option<Arc<SourceFile>>;
161
162 fn get(&self, name: &Ident) -> Result<Option<CachedConstantValue<'_>>, Self::Error>;
167
168 fn get_by_path(
179 &self,
180 path: Span<&Path>,
181 ) -> Result<Option<CachedConstantValue<'_>>, Self::Error>;
182
183 fn get_error(&self, name: &Ident) -> Result<Option<Arc<str>>, Self::Error> {
187 match self.get(name)? {
188 Some(expr) => match expr {
189 CachedConstantValue::Hit(ConstantValue::String(spanned)) => {
190 Ok(Some(spanned.clone().into_inner()))
191 },
192 CachedConstantValue::Miss(ConstantExpr::String(spanned)) => {
193 Ok(Some(spanned.clone().into_inner()))
194 },
195 CachedConstantValue::Miss(ConstantExpr::Var(_)) => Ok(None),
196 other => {
197 Err(ConstEvalError::invalid_constant(other.span(), "a string", self).into())
198 },
199 },
200 None => Ok(None),
201 }
202 }
203
204 fn get_error_by_path(&self, path: Span<&Path>) -> Result<Option<Arc<str>>, Self::Error> {
208 match self.get_by_path(path)? {
209 Some(expr) => match expr {
210 CachedConstantValue::Hit(ConstantValue::String(spanned)) => {
211 Ok(Some(spanned.clone().into_inner()))
212 },
213 CachedConstantValue::Miss(ConstantExpr::String(spanned)) => {
214 Ok(Some(spanned.clone().into_inner()))
215 },
216 CachedConstantValue::Miss(ConstantExpr::Var(_)) => Ok(None),
217 other => {
218 Err(ConstEvalError::invalid_constant(other.span(), "a string", self).into())
219 },
220 },
221 None => Ok(None),
222 }
223 }
224
225 #[inline]
227 #[allow(unused_variables)]
228 fn on_eval_start(&mut self, path: Span<&Path>) {}
229
230 #[inline]
234 #[allow(unused_variables)]
235 fn on_eval_completed(&mut self, name: Span<&Path>, value: &ConstantExpr) {}
236}
237
238pub fn expr<Env>(
248 value: &ConstantExpr,
249 env: &mut Env,
250) -> Result<ConstantExpr, <Env as ConstEnvironment>::Error>
251where
252 Env: ?Sized + ConstEnvironment,
253 <Env as ConstEnvironment>::Error: From<ConstEvalError>,
254{
255 enum Cont {
257 Eval(ConstantExpr),
259 Apply(Span<ConstantOp>),
262 Return(Span<Arc<Path>>),
265 }
266
267 if let Some(value) = value.as_value() {
269 return Ok(value.into());
270 }
271
272 let mut stack = Vec::with_capacity(8);
274 let mut continuations = Vec::with_capacity(8);
276 continuations.push(Cont::Eval(value.clone()));
278 let mut evaluating = SmallVec::<[_; 8]>::new_const();
284
285 while let Some(next) = continuations.pop() {
286 match next {
287 Cont::Eval(
288 expr @ (ConstantExpr::Int(_)
289 | ConstantExpr::String(_)
290 | ConstantExpr::Word(_)
291 | ConstantExpr::Hash(..)),
292 ) => {
293 stack.push(expr);
294 },
295 Cont::Eval(ConstantExpr::Var(path)) => {
296 if evaluating.contains(&path) {
297 return Err(
298 ConstEvalError::eval_cycle(evaluating[0].span(), path.span(), env).into()
299 );
300 }
301
302 if let Some(name) = path.as_ident() {
303 let name = name.with_span(path.span());
304 if let Some(expr) = env.get(&name)?.map(|e| e.into_expr()) {
305 env.on_eval_start(path.as_deref());
306 evaluating.push(path.clone());
307 continuations.push(Cont::Return(path.clone()));
308 continuations.push(Cont::Eval(expr));
309 } else {
310 stack.push(ConstantExpr::Var(path));
311 }
312 } else if let Some(expr) = env.get_by_path(path.as_deref())? {
313 let expr = expr.into_expr();
314 env.on_eval_start(path.as_deref());
315 evaluating.push(path.clone());
316 continuations.push(Cont::Return(path.clone()));
317 continuations.push(Cont::Eval(expr));
318 } else {
319 stack.push(ConstantExpr::Var(path));
320 }
321 },
322 Cont::Eval(ConstantExpr::BinaryOp { span, op, lhs, rhs, .. }) => {
323 continuations.push(Cont::Apply(Span::new(span, op)));
324 continuations.push(Cont::Eval(*lhs));
325 continuations.push(Cont::Eval(*rhs));
326 },
327 Cont::Apply(op) => {
328 let lhs = stack.pop().unwrap();
329 let rhs = stack.pop().unwrap();
330 let (span, op) = op.into_parts();
331 match (lhs, rhs) {
332 (ConstantExpr::Int(lhs), ConstantExpr::Int(rhs)) => {
333 let lhs = lhs.into_inner();
334 let rhs = rhs.into_inner();
335 let result = match op {
336 ConstantOp::Add => lhs.checked_add(rhs).ok_or_else(|| {
337 ConstEvalError::ImmediateOverflow {
338 span,
339 source_file: env.get_source_file_for(span),
340 }
341 })?,
342 ConstantOp::Sub => lhs.checked_sub(rhs).ok_or_else(|| {
343 ConstEvalError::ImmediateOverflow {
344 span,
345 source_file: env.get_source_file_for(span),
346 }
347 })?,
348 ConstantOp::Mul => lhs.checked_mul(rhs).ok_or_else(|| {
349 ConstEvalError::ImmediateOverflow {
350 span,
351 source_file: env.get_source_file_for(span),
352 }
353 })?,
354 ConstantOp::Div | ConstantOp::IntDiv => lhs
355 .checked_div(rhs)
356 .ok_or_else(|| ConstEvalError::DivisionByZero {
357 span,
358 source_file: env.get_source_file_for(span),
359 })?,
360 };
361 stack.push(ConstantExpr::Int(Span::new(span, result)));
362 },
363 operands @ ((ConstantExpr::Int(_), ConstantExpr::Var(_))
364 | (ConstantExpr::Var(_), ConstantExpr::Int(_))
365 | (ConstantExpr::Var(_), ConstantExpr::Var(_))) => {
366 let (lhs, rhs) = operands;
367 stack.push(ConstantExpr::BinaryOp {
368 span,
369 op,
370 lhs: lhs.into(),
371 rhs: rhs.into(),
372 });
373 },
374 (ConstantExpr::Int(_) | ConstantExpr::Var(_), rhs) => {
375 let operand = rhs.span();
376 return Err(ConstEvalError::InvalidConstExprOperand {
377 span,
378 operand,
379 source_file: env.get_source_file_for(operand),
380 }
381 .into());
382 },
383 (lhs, _) => {
384 let operand = lhs.span();
385 return Err(ConstEvalError::InvalidConstExprOperand {
386 span,
387 operand,
388 source_file: env.get_source_file_for(operand),
389 }
390 .into());
391 },
392 }
393 },
394 Cont::Return(from) => {
395 debug_assert!(
396 !stack.is_empty(),
397 "returning from evaluating a constant reference is expected to produce at least one output"
398 );
399 evaluating.pop();
400
401 env.on_eval_completed(from.as_deref(), stack.last().unwrap());
402 },
403 }
404 }
405
406 assert_eq!(stack.len(), 1, "expected constant evaluation to produce exactly one output");
408 Ok(unsafe { stack.pop().unwrap_unchecked() })
411}