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: division by zero")]
48 DivisionByZero {
49 #[label]
50 span: SourceSpan,
51 #[source_code]
52 source_file: Option<Arc<SourceFile>>,
53 },
54 #[error("invalid constant")]
55 #[diagnostic(help("this constant does not resolve to a value of the right type"))]
56 InvalidConstant {
57 expected: &'static str,
58 #[label("expected {expected}")]
59 span: SourceSpan,
60 #[source_code]
61 source_file: Option<Arc<SourceFile>>,
62 },
63 #[error("constant evaluation failed")]
64 #[diagnostic(help("this constant cannot be evaluated, due to operands of incorrect type"))]
65 InvalidConstExprOperand {
66 #[label]
67 span: SourceSpan,
68 #[label("expected this operand to produce an integer value, but it does not")]
69 operand: SourceSpan,
70 #[source_code]
71 source_file: Option<Arc<SourceFile>>,
72 },
73 #[error("constant evaluation terminated due to infinite recursion")]
74 #[diagnostic(help("dependencies between constants must form an acyclic graph"))]
75 ConstEvalCycle {
76 #[label("occurs while evaluating this expression")]
77 start: SourceSpan,
78 #[source_code]
79 source_file: Option<Arc<SourceFile>>,
80 #[related]
81 detected: [RelatedLabel; 1],
82 },
83}
84
85impl ConstEvalError {
86 #[inline]
87 pub fn undefined<Env>(symbol: Ident, env: &Env) -> Self
88 where
89 Env: ?Sized + ConstEnvironment,
90 <Env as ConstEnvironment>::Error: From<Self>,
91 {
92 let source_file = env.get_source_file_for(symbol.span());
93 Self::UndefinedSymbol { symbol, source_file }
94 }
95
96 #[inline]
97 pub fn invalid_constant<Env>(span: SourceSpan, expected: &'static str, env: &Env) -> Self
98 where
99 Env: ?Sized + ConstEnvironment,
100 <Env as ConstEnvironment>::Error: From<Self>,
101 {
102 let source_file = env.get_source_file_for(span);
103 Self::InvalidConstant { expected, span, source_file }
104 }
105
106 #[inline]
107 pub fn eval_cycle<Env>(start: SourceSpan, detected: SourceSpan, env: &Env) -> Self
108 where
109 Env: ?Sized + ConstEnvironment,
110 <Env as ConstEnvironment>::Error: From<Self>,
111 {
112 let start_file = env.get_source_file_for(start);
113 let detected_file = env.get_source_file_for(detected);
114 let detected = [RelatedLabel::error("related error")
115 .with_labeled_span(
116 detected,
117 "cycle occurs because we attempt to eval this constant recursively",
118 )
119 .with_source_file(detected_file)];
120 Self::ConstEvalCycle { start, source_file: start_file, detected }
121 }
122}
123
124#[derive(Debug)]
125pub enum CachedConstantValue<'a> {
126 Hit(&'a ConstantValue),
128 Miss(&'a ConstantExpr),
130}
131
132impl CachedConstantValue<'_> {
133 pub fn into_expr(self) -> ConstantExpr {
134 match self {
135 Self::Hit(value) => value.clone().into(),
136 Self::Miss(expr) => expr.clone(),
137 }
138 }
139}
140
141impl Spanned for CachedConstantValue<'_> {
142 fn span(&self) -> SourceSpan {
143 match self {
144 Self::Hit(value) => value.span(),
145 Self::Miss(expr) => expr.span(),
146 }
147 }
148}
149
150pub trait ConstEnvironment {
156 type Error: From<ConstEvalError>;
160
161 fn get_source_file_for(&self, span: SourceSpan) -> Option<Arc<SourceFile>>;
163
164 fn get(&mut self, name: &Ident) -> Result<Option<CachedConstantValue<'_>>, Self::Error>;
169
170 fn get_by_path(
181 &mut self,
182 path: Span<&Path>,
183 ) -> Result<Option<CachedConstantValue<'_>>, Self::Error>;
184
185 fn get_error(&mut self, name: &Ident) -> Result<Option<Arc<str>>, Self::Error> {
189 let mut seen = Vec::new();
190 let start = name.span();
191 match self.get(name)?.map(CachedConstantValue::into_expr) {
192 Some(expr) => resolve_error_expr(self, expr, start, &mut seen),
193 None => Ok(None),
194 }
195 }
196
197 fn get_error_by_path(&mut self, path: Span<&Path>) -> Result<Option<Arc<str>>, Self::Error> {
201 let mut seen = Vec::new();
202 let start = path.span();
203 match self.get_by_path(path)?.map(CachedConstantValue::into_expr) {
204 Some(expr) => resolve_error_expr(self, expr, start, &mut seen),
205 None => Ok(None),
206 }
207 }
208
209 #[inline]
211 #[allow(unused_variables)]
212 fn on_eval_start(&mut self, path: Span<&Path>) {}
213
214 #[inline]
218 #[allow(unused_variables)]
219 fn on_eval_completed(&mut self, name: Span<&Path>, value: &ConstantExpr) {}
220}
221
222fn resolve_error_expr<Env>(
223 env: &mut Env,
224 expr: ConstantExpr,
225 start: SourceSpan,
226 seen: &mut Vec<Arc<Path>>,
227) -> Result<Option<Arc<str>>, <Env as ConstEnvironment>::Error>
228where
229 Env: ?Sized + ConstEnvironment,
230 <Env as ConstEnvironment>::Error: From<ConstEvalError>,
231{
232 match expr {
233 ConstantExpr::String(spanned) => Ok(Some(spanned.into_inner())),
234 ConstantExpr::Var(path) => {
235 let path_ref = path.inner().as_ref();
236 let path_span = path.span();
237 resolve_error_path(env, Span::new(path_span, path_ref), start, seen)
238 },
239 other => Err(ConstEvalError::invalid_constant(other.span(), "a string", env).into()),
240 }
241}
242
243fn resolve_error_path<Env>(
244 env: &mut Env,
245 path: Span<&Path>,
246 start: SourceSpan,
247 seen: &mut Vec<Arc<Path>>,
248) -> Result<Option<Arc<str>>, <Env as ConstEnvironment>::Error>
249where
250 Env: ?Sized + ConstEnvironment,
251 <Env as ConstEnvironment>::Error: From<ConstEvalError>,
252{
253 let path_span = path.span();
254 let path_ref = path.into_inner();
255 if seen.iter().any(|seen_path| seen_path.as_ref() == path_ref) {
256 return Err(ConstEvalError::eval_cycle(start, path_span, env).into());
257 }
258 seen.push(Arc::<Path>::from(path_ref));
259
260 let path = Span::new(path_span, path_ref);
261 match env.get_by_path(path)?.map(CachedConstantValue::into_expr) {
262 Some(expr) => resolve_error_expr(env, expr, start, seen),
263 None => Ok(None),
264 }
265}
266
267pub fn expr<Env>(
277 value: &ConstantExpr,
278 env: &mut Env,
279) -> Result<ConstantExpr, <Env as ConstEnvironment>::Error>
280where
281 Env: ?Sized + ConstEnvironment,
282 <Env as ConstEnvironment>::Error: From<ConstEvalError>,
283{
284 enum Cont {
286 Eval(ConstantExpr),
288 Apply(Span<ConstantOp>),
291 Return(Span<Arc<Path>>),
294 }
295
296 if let Some(value) = value.as_value() {
298 return Ok(value.into());
299 }
300
301 let mut stack = Vec::with_capacity(8);
303 let mut continuations = Vec::with_capacity(8);
305 continuations.push(Cont::Eval(value.clone()));
307 let mut evaluating = SmallVec::<[_; 8]>::new_const();
313
314 while let Some(next) = continuations.pop() {
315 match next {
316 Cont::Eval(
317 expr @ (ConstantExpr::Int(_)
318 | ConstantExpr::String(_)
319 | ConstantExpr::Word(_)
320 | ConstantExpr::Hash(..)),
321 ) => {
322 stack.push(expr);
323 },
324 Cont::Eval(ConstantExpr::Var(path)) => {
325 if evaluating.contains(&path) {
326 return Err(
327 ConstEvalError::eval_cycle(evaluating[0].span(), path.span(), env).into()
328 );
329 }
330
331 if let Some(name) = path.as_ident() {
332 let name = name.with_span(path.span());
333 if let Some(expr) = env.get(&name)?.map(|e| e.into_expr()) {
334 env.on_eval_start(path.as_deref());
335 evaluating.push(path.clone());
336 continuations.push(Cont::Return(path.clone()));
337 continuations.push(Cont::Eval(expr));
338 } else {
339 stack.push(ConstantExpr::Var(path));
340 }
341 } else if let Some(expr) = env.get_by_path(path.as_deref())? {
342 let expr = expr.into_expr();
343 env.on_eval_start(path.as_deref());
344 evaluating.push(path.clone());
345 continuations.push(Cont::Return(path.clone()));
346 continuations.push(Cont::Eval(expr));
347 } else {
348 stack.push(ConstantExpr::Var(path));
349 }
350 },
351 Cont::Eval(ConstantExpr::BinaryOp { span, op, lhs, rhs, .. }) => {
352 continuations.push(Cont::Apply(Span::new(span, op)));
353 continuations.push(Cont::Eval(*lhs));
354 continuations.push(Cont::Eval(*rhs));
355 },
356 Cont::Apply(op) => {
357 let lhs = stack.pop().unwrap();
358 let rhs = stack.pop().unwrap();
359 let (span, op) = op.into_parts();
360 match (lhs, rhs) {
361 (ConstantExpr::Int(lhs), ConstantExpr::Int(rhs)) => {
362 let lhs = lhs.into_inner();
363 let rhs = rhs.into_inner();
364 let result = match op {
365 ConstantOp::Add => lhs.checked_add(rhs).ok_or_else(|| {
366 ConstEvalError::ImmediateOverflow {
367 span,
368 source_file: env.get_source_file_for(span),
369 }
370 })?,
371 ConstantOp::Sub => lhs.checked_sub(rhs).ok_or_else(|| {
372 ConstEvalError::ImmediateOverflow {
373 span,
374 source_file: env.get_source_file_for(span),
375 }
376 })?,
377 ConstantOp::Mul => lhs.checked_mul(rhs).ok_or_else(|| {
378 ConstEvalError::ImmediateOverflow {
379 span,
380 source_file: env.get_source_file_for(span),
381 }
382 })?,
383 ConstantOp::IntDiv => lhs.checked_div(rhs).ok_or_else(|| {
384 ConstEvalError::DivisionByZero {
385 span,
386 source_file: env.get_source_file_for(span),
387 }
388 })?,
389 ConstantOp::Div => {
390 if rhs.as_int() == 0 {
391 return Err(ConstEvalError::DivisionByZero {
392 span,
393 source_file: env.get_source_file_for(span),
394 }
395 .into());
396 }
397 let lhs = Felt::new(lhs.as_int());
398 let rhs = Felt::new(rhs.as_int());
399 IntValue::from(lhs / rhs)
400 },
401 };
402 stack.push(ConstantExpr::Int(Span::new(span, result)));
403 },
404 operands @ ((
405 ConstantExpr::Int(_) | ConstantExpr::Var(_),
406 ConstantExpr::Var(_),
407 )
408 | (ConstantExpr::Var(_), ConstantExpr::Int(_))) => {
409 let (lhs, rhs) = operands;
410 stack.push(ConstantExpr::BinaryOp {
411 span,
412 op,
413 lhs: lhs.into(),
414 rhs: rhs.into(),
415 });
416 },
417 (ConstantExpr::Int(_) | ConstantExpr::Var(_), rhs) => {
418 let operand = rhs.span();
419 return Err(ConstEvalError::InvalidConstExprOperand {
420 span,
421 operand,
422 source_file: env.get_source_file_for(operand),
423 }
424 .into());
425 },
426 (lhs, _) => {
427 let operand = lhs.span();
428 return Err(ConstEvalError::InvalidConstExprOperand {
429 span,
430 operand,
431 source_file: env.get_source_file_for(operand),
432 }
433 .into());
434 },
435 }
436 },
437 Cont::Return(from) => {
438 debug_assert!(
439 !stack.is_empty(),
440 "returning from evaluating a constant reference is expected to produce at least one output"
441 );
442 evaluating.pop();
443
444 env.on_eval_completed(from.as_deref(), stack.last().unwrap());
445 },
446 }
447 }
448
449 assert_eq!(stack.len(), 1, "expected constant evaluation to produce exactly one output");
451 Ok(unsafe { stack.pop().unwrap_unchecked() })
454}