1#![allow(unused_assignments)]
3
4use alloc::{sync::Arc, vec::Vec};
5
6use smallvec::SmallVec;
7
8use crate::{
9 Felt,
10 ast::*,
11 debuginfo::{SourceFile, SourceSpan, Span, Spanned},
12 diagnostics::{Diagnostic, RelatedLabel, miette},
13 parser::IntValue,
14};
15
16#[derive(Debug, thiserror::Error, Diagnostic)]
18pub enum ConstEvalError {
19 #[error("undefined constant '{symbol}'")]
20 #[diagnostic(help("are you missing an import?"))]
21 UndefinedSymbol {
22 #[label("the constant referenced here is not defined in the current scope")]
23 symbol: Ident,
24 #[source_code]
25 source_file: Option<Arc<SourceFile>>,
26 },
27 #[error("undefined constant '{path}'")]
28 #[diagnostic(help(
29 "is the constant exported from its containing module? if the referenced module \
30 is in another library, make sure you provided it to the assembler"
31 ))]
32 UndefinedPath {
33 path: Arc<Path>,
34 #[label("this reference is invalid: no such definition found")]
35 span: SourceSpan,
36 #[source_code]
37 source_file: Option<Arc<SourceFile>>,
38 },
39 #[error("invalid immediate: value is larger than expected range")]
40 #[diagnostic()]
41 ImmediateOverflow {
42 #[label]
43 span: SourceSpan,
44 #[source_code]
45 source_file: Option<Arc<SourceFile>>,
46 },
47 #[error("invalid constant expression: value is larger than expected range")]
48 #[diagnostic()]
49 ConstExprOverflow {
50 #[label]
51 span: SourceSpan,
52 #[source_code]
53 source_file: Option<Arc<SourceFile>>,
54 },
55 #[error("invalid constant expression: division by zero")]
56 DivisionByZero {
57 #[label]
58 span: SourceSpan,
59 #[source_code]
60 source_file: Option<Arc<SourceFile>>,
61 },
62 #[error("invalid constant")]
63 #[diagnostic(help("this constant does not resolve to a value of the right type"))]
64 InvalidConstant {
65 expected: &'static str,
66 #[label("expected {expected}")]
67 span: SourceSpan,
68 #[source_code]
69 source_file: Option<Arc<SourceFile>>,
70 },
71 #[error("constant evaluation failed")]
72 #[diagnostic(help("this constant cannot be evaluated, due to operands of incorrect type"))]
73 InvalidConstExprOperand {
74 #[label]
75 span: SourceSpan,
76 #[label("expected this operand to produce an integer value, but it does not")]
77 operand: SourceSpan,
78 #[source_code]
79 source_file: Option<Arc<SourceFile>>,
80 },
81 #[error("constant evaluation terminated due to infinite recursion")]
82 #[diagnostic(help("dependencies between constants must form an acyclic graph"))]
83 ConstEvalCycle {
84 #[label("occurs while evaluating this expression")]
85 start: SourceSpan,
86 #[source_code]
87 source_file: Option<Arc<SourceFile>>,
88 #[related]
89 detected: [RelatedLabel; 1],
90 },
91}
92
93impl ConstEvalError {
94 #[inline]
95 pub fn undefined<Env>(symbol: Ident, 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(symbol.span());
101 Self::UndefinedSymbol { symbol, source_file }
102 }
103
104 #[inline]
105 pub fn invalid_constant<Env>(span: SourceSpan, expected: &'static str, env: &Env) -> Self
106 where
107 Env: ?Sized + ConstEnvironment,
108 <Env as ConstEnvironment>::Error: From<Self>,
109 {
110 let source_file = env.get_source_file_for(span);
111 Self::InvalidConstant { expected, span, source_file }
112 }
113
114 #[inline]
115 pub fn eval_cycle<Env>(start: SourceSpan, detected: SourceSpan, env: &Env) -> Self
116 where
117 Env: ?Sized + ConstEnvironment,
118 <Env as ConstEnvironment>::Error: From<Self>,
119 {
120 let start_file = env.get_source_file_for(start);
121 let detected_file = env.get_source_file_for(detected);
122 let detected = [RelatedLabel::error("related error")
123 .with_labeled_span(
124 detected,
125 "cycle occurs because we attempt to eval this constant recursively",
126 )
127 .with_source_file(detected_file)];
128 Self::ConstEvalCycle { start, source_file: start_file, detected }
129 }
130}
131
132#[derive(Debug)]
133pub enum CachedConstantValue<'a> {
134 Hit(&'a ConstantValue),
136 Miss(&'a ConstantExpr),
138}
139
140impl CachedConstantValue<'_> {
141 pub fn into_expr(self) -> ConstantExpr {
142 match self {
143 Self::Hit(value) => value.clone().into(),
144 Self::Miss(expr) => expr.clone(),
145 }
146 }
147}
148
149impl Spanned for CachedConstantValue<'_> {
150 fn span(&self) -> SourceSpan {
151 match self {
152 Self::Hit(value) => value.span(),
153 Self::Miss(expr) => expr.span(),
154 }
155 }
156}
157
158pub trait ConstEnvironment {
164 type Error: From<ConstEvalError>;
168
169 fn get_source_file_for(&self, span: SourceSpan) -> Option<Arc<SourceFile>>;
171
172 fn get(&mut self, name: &Ident) -> Result<Option<CachedConstantValue<'_>>, Self::Error>;
177
178 fn get_by_path(
189 &mut self,
190 path: Span<&Path>,
191 ) -> Result<Option<CachedConstantValue<'_>>, Self::Error>;
192
193 fn get_error(&mut self, name: &Ident) -> Result<Option<Arc<str>>, Self::Error> {
197 let mut seen = Vec::new();
198 let start = name.span();
199 match self.get(name)?.map(CachedConstantValue::into_expr) {
200 Some(expr) => resolve_error_expr(self, expr, start, &mut seen),
201 None => Ok(None),
202 }
203 }
204
205 fn get_error_by_path(&mut self, path: Span<&Path>) -> Result<Option<Arc<str>>, Self::Error> {
209 let mut seen = Vec::new();
210 let start = path.span();
211 match self.get_by_path(path)?.map(CachedConstantValue::into_expr) {
212 Some(expr) => resolve_error_expr(self, expr, start, &mut seen),
213 None => Ok(None),
214 }
215 }
216
217 #[inline]
219 #[allow(unused_variables)]
220 fn on_eval_start(&mut self, path: Span<&Path>) {}
221
222 #[inline]
226 #[allow(unused_variables)]
227 fn on_eval_completed(&mut self, name: Span<&Path>, value: &ConstantExpr) {}
228}
229
230fn resolve_error_expr<Env>(
231 env: &mut Env,
232 expr: ConstantExpr,
233 start: SourceSpan,
234 seen: &mut Vec<Arc<Path>>,
235) -> Result<Option<Arc<str>>, <Env as ConstEnvironment>::Error>
236where
237 Env: ?Sized + ConstEnvironment,
238 <Env as ConstEnvironment>::Error: From<ConstEvalError>,
239{
240 match expr {
241 ConstantExpr::String(spanned) => Ok(Some(spanned.into_inner())),
242 ConstantExpr::Var(path) => {
243 let path_ref = path.inner().as_ref();
244 let path_span = path.span();
245 resolve_error_path(env, Span::new(path_span, path_ref), start, seen)
246 },
247 other => Err(ConstEvalError::invalid_constant(other.span(), "a string", env).into()),
248 }
249}
250
251fn resolve_error_path<Env>(
252 env: &mut Env,
253 path: Span<&Path>,
254 start: SourceSpan,
255 seen: &mut Vec<Arc<Path>>,
256) -> Result<Option<Arc<str>>, <Env as ConstEnvironment>::Error>
257where
258 Env: ?Sized + ConstEnvironment,
259 <Env as ConstEnvironment>::Error: From<ConstEvalError>,
260{
261 let path_span = path.span();
262 let path_ref = path.into_inner();
263 if seen.iter().any(|seen_path| seen_path.as_ref() == path_ref) {
264 return Err(ConstEvalError::eval_cycle(start, path_span, env).into());
265 }
266 seen.push(Arc::<Path>::from(path_ref));
267
268 let path = Span::new(path_span, path_ref);
269 match env.get_by_path(path)?.map(CachedConstantValue::into_expr) {
270 Some(expr) => resolve_error_expr(env, expr, start, seen),
271 None => Ok(None),
272 }
273}
274
275pub fn expr<Env>(
285 value: &ConstantExpr,
286 env: &mut Env,
287) -> Result<ConstantExpr, <Env as ConstEnvironment>::Error>
288where
289 Env: ?Sized + ConstEnvironment,
290 <Env as ConstEnvironment>::Error: From<ConstEvalError>,
291{
292 enum Cont {
294 Eval(ConstantExpr),
296 Apply(Span<ConstantOp>),
299 Return(Span<Arc<Path>>),
302 }
303
304 if let Some(value) = value.as_value() {
306 return Ok(value.into());
307 }
308
309 let mut stack = Vec::with_capacity(8);
311 let mut continuations = Vec::with_capacity(8);
313 continuations.push(Cont::Eval(value.clone()));
315 let mut evaluating = SmallVec::<[_; 8]>::new_const();
321
322 while let Some(next) = continuations.pop() {
323 match next {
324 Cont::Eval(
325 expr @ (ConstantExpr::Int(_)
326 | ConstantExpr::String(_)
327 | ConstantExpr::Word(_)
328 | ConstantExpr::Hash(..)),
329 ) => {
330 stack.push(expr);
331 },
332 Cont::Eval(ConstantExpr::Var(path)) => {
333 if evaluating.contains(&path) {
334 return Err(
335 ConstEvalError::eval_cycle(evaluating[0].span(), path.span(), env).into()
336 );
337 }
338
339 if let Some(name) = path.as_ident() {
340 let name = name.with_span(path.span());
341 if let Some(expr) = env.get(&name)?.map(CachedConstantValue::into_expr) {
342 env.on_eval_start(path.as_deref());
343 evaluating.push(path.clone());
344 continuations.push(Cont::Return(path.clone()));
345 continuations.push(Cont::Eval(expr));
346 } else {
347 stack.push(ConstantExpr::Var(path));
348 }
349 } else if let Some(expr) = env.get_by_path(path.as_deref())? {
350 let expr = expr.into_expr();
351 env.on_eval_start(path.as_deref());
352 evaluating.push(path.clone());
353 continuations.push(Cont::Return(path.clone()));
354 continuations.push(Cont::Eval(expr));
355 } else {
356 stack.push(ConstantExpr::Var(path));
357 }
358 },
359 Cont::Eval(ConstantExpr::BinaryOp { span, op, lhs, rhs, .. }) => {
360 continuations.push(Cont::Apply(Span::new(span, op)));
361 continuations.push(Cont::Eval(*lhs));
362 continuations.push(Cont::Eval(*rhs));
363 },
364 Cont::Apply(op) => {
365 let lhs = stack.pop().unwrap();
366 let rhs = stack.pop().unwrap();
367 let (span, op) = op.into_parts();
368 match (lhs, rhs) {
369 (ConstantExpr::Int(lhs), ConstantExpr::Int(rhs)) => {
370 let lhs = lhs.into_inner();
371 let rhs = rhs.into_inner();
372 let result = match op {
373 ConstantOp::Add => lhs.checked_add(rhs).ok_or_else(|| {
374 ConstEvalError::ConstExprOverflow {
375 span,
376 source_file: env.get_source_file_for(span),
377 }
378 })?,
379 ConstantOp::Sub => lhs.checked_sub(rhs).ok_or_else(|| {
380 ConstEvalError::ConstExprOverflow {
381 span,
382 source_file: env.get_source_file_for(span),
383 }
384 })?,
385 ConstantOp::Mul => lhs.checked_mul(rhs).ok_or_else(|| {
386 ConstEvalError::ConstExprOverflow {
387 span,
388 source_file: env.get_source_file_for(span),
389 }
390 })?,
391 ConstantOp::IntDiv => lhs.checked_div(rhs).ok_or_else(|| {
392 ConstEvalError::DivisionByZero {
393 span,
394 source_file: env.get_source_file_for(span),
395 }
396 })?,
397 ConstantOp::Div => {
398 if rhs.as_int() == 0 {
399 return Err(ConstEvalError::DivisionByZero {
400 span,
401 source_file: env.get_source_file_for(span),
402 }
403 .into());
404 }
405 let lhs = Felt::new_unchecked(lhs.as_int());
406 let rhs = Felt::new_unchecked(rhs.as_int());
407 IntValue::from(lhs / rhs)
408 },
409 };
410 stack.push(ConstantExpr::Int(Span::new(span, result)));
411 },
412 operands @ ((
413 ConstantExpr::Int(_) | ConstantExpr::Var(_),
414 ConstantExpr::Var(_),
415 )
416 | (ConstantExpr::Var(_), ConstantExpr::Int(_))) => {
417 let (lhs, rhs) = operands;
418 stack.push(ConstantExpr::BinaryOp {
419 span,
420 op,
421 lhs: lhs.into(),
422 rhs: rhs.into(),
423 });
424 },
425 (ConstantExpr::Int(_) | ConstantExpr::Var(_), rhs) => {
426 let operand = rhs.span();
427 return Err(ConstEvalError::InvalidConstExprOperand {
428 span,
429 operand,
430 source_file: env.get_source_file_for(operand),
431 }
432 .into());
433 },
434 (lhs, _) => {
435 let operand = lhs.span();
436 return Err(ConstEvalError::InvalidConstExprOperand {
437 span,
438 operand,
439 source_file: env.get_source_file_for(operand),
440 }
441 .into());
442 },
443 }
444 },
445 Cont::Return(from) => {
446 debug_assert!(
447 !stack.is_empty(),
448 "returning from evaluating a constant reference is expected to produce at least one output"
449 );
450 evaluating.pop();
451
452 env.on_eval_completed(from.as_deref(), stack.last().unwrap());
453 },
454 }
455 }
456
457 assert_eq!(stack.len(), 1, "expected constant evaluation to produce exactly one output");
459 Ok(unsafe { stack.pop().unwrap_unchecked() })
462}