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 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(detected);
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(&mut self, name: &Ident) -> Result<Option<CachedConstantValue<'_>>, Self::Error>;
167
168 fn get_by_path(
179 &mut self,
180 path: Span<&Path>,
181 ) -> Result<Option<CachedConstantValue<'_>>, Self::Error>;
182
183 fn get_error(&mut self, name: &Ident) -> Result<Option<Arc<str>>, Self::Error> {
187 let mut seen = Vec::new();
188 let start = name.span();
189 match self.get(name)?.map(CachedConstantValue::into_expr) {
190 Some(expr) => resolve_error_expr(self, expr, start, &mut seen),
191 None => Ok(None),
192 }
193 }
194
195 fn get_error_by_path(&mut self, path: Span<&Path>) -> Result<Option<Arc<str>>, Self::Error> {
199 let mut seen = Vec::new();
200 let start = path.span();
201 match self.get_by_path(path)?.map(CachedConstantValue::into_expr) {
202 Some(expr) => resolve_error_expr(self, expr, start, &mut seen),
203 None => Ok(None),
204 }
205 }
206
207 #[inline]
209 #[allow(unused_variables)]
210 fn on_eval_start(&mut self, path: Span<&Path>) {}
211
212 #[inline]
216 #[allow(unused_variables)]
217 fn on_eval_completed(&mut self, name: Span<&Path>, value: &ConstantExpr) {}
218}
219
220fn resolve_error_expr<Env>(
221 env: &mut Env,
222 expr: ConstantExpr,
223 start: SourceSpan,
224 seen: &mut Vec<Arc<Path>>,
225) -> Result<Option<Arc<str>>, <Env as ConstEnvironment>::Error>
226where
227 Env: ?Sized + ConstEnvironment,
228 <Env as ConstEnvironment>::Error: From<ConstEvalError>,
229{
230 match expr {
231 ConstantExpr::String(spanned) => Ok(Some(spanned.into_inner())),
232 ConstantExpr::Var(path) => {
233 let path_ref = path.inner().as_ref();
234 let path_span = path.span();
235 resolve_error_path(env, Span::new(path_span, path_ref), start, seen)
236 },
237 other => Err(ConstEvalError::invalid_constant(other.span(), "a string", env).into()),
238 }
239}
240
241fn resolve_error_path<Env>(
242 env: &mut Env,
243 path: Span<&Path>,
244 start: SourceSpan,
245 seen: &mut Vec<Arc<Path>>,
246) -> Result<Option<Arc<str>>, <Env as ConstEnvironment>::Error>
247where
248 Env: ?Sized + ConstEnvironment,
249 <Env as ConstEnvironment>::Error: From<ConstEvalError>,
250{
251 let path_span = path.span();
252 let path_ref = path.into_inner();
253 if seen.iter().any(|seen_path| seen_path.as_ref() == path_ref) {
254 return Err(ConstEvalError::eval_cycle(start, path_span, env).into());
255 }
256 seen.push(Arc::<Path>::from(path_ref));
257
258 let path = Span::new(path_span, path_ref);
259 match env.get_by_path(path)?.map(CachedConstantValue::into_expr) {
260 Some(expr) => resolve_error_expr(env, expr, start, seen),
261 None => Ok(None),
262 }
263}
264
265pub fn expr<Env>(
275 value: &ConstantExpr,
276 env: &mut Env,
277) -> Result<ConstantExpr, <Env as ConstEnvironment>::Error>
278where
279 Env: ?Sized + ConstEnvironment,
280 <Env as ConstEnvironment>::Error: From<ConstEvalError>,
281{
282 enum Cont {
284 Eval(ConstantExpr),
286 Apply(Span<ConstantOp>),
289 Return(Span<Arc<Path>>),
292 }
293
294 if let Some(value) = value.as_value() {
296 return Ok(value.into());
297 }
298
299 let mut stack = Vec::with_capacity(8);
301 let mut continuations = Vec::with_capacity(8);
303 continuations.push(Cont::Eval(value.clone()));
305 let mut evaluating = SmallVec::<[_; 8]>::new_const();
311
312 while let Some(next) = continuations.pop() {
313 match next {
314 Cont::Eval(
315 expr @ (ConstantExpr::Int(_)
316 | ConstantExpr::String(_)
317 | ConstantExpr::Word(_)
318 | ConstantExpr::Hash(..)),
319 ) => {
320 stack.push(expr);
321 },
322 Cont::Eval(ConstantExpr::Var(path)) => {
323 if evaluating.contains(&path) {
324 return Err(
325 ConstEvalError::eval_cycle(evaluating[0].span(), path.span(), env).into()
326 );
327 }
328
329 if let Some(name) = path.as_ident() {
330 let name = name.with_span(path.span());
331 if let Some(expr) = env.get(&name)?.map(CachedConstantValue::into_expr) {
332 env.on_eval_start(path.as_deref());
333 evaluating.push(path.clone());
334 continuations.push(Cont::Return(path.clone()));
335 continuations.push(Cont::Eval(expr));
336 } else {
337 stack.push(ConstantExpr::Var(path));
338 }
339 } else if let Some(expr) = env.get_by_path(path.as_deref())? {
340 let expr = expr.into_expr();
341 env.on_eval_start(path.as_deref());
342 evaluating.push(path.clone());
343 continuations.push(Cont::Return(path.clone()));
344 continuations.push(Cont::Eval(expr));
345 } else {
346 stack.push(ConstantExpr::Var(path));
347 }
348 },
349 Cont::Eval(ConstantExpr::BinaryOp { span, op, lhs, rhs, .. }) => {
350 continuations.push(Cont::Apply(Span::new(span, op)));
351 continuations.push(Cont::Eval(*lhs));
352 continuations.push(Cont::Eval(*rhs));
353 },
354 Cont::Apply(op) => {
355 let lhs = stack.pop().unwrap();
356 let rhs = stack.pop().unwrap();
357 let (span, op) = op.into_parts();
358 match (lhs, rhs) {
359 (ConstantExpr::Int(lhs), ConstantExpr::Int(rhs)) => {
360 let lhs = lhs.into_inner();
361 let rhs = rhs.into_inner();
362 let result = match op {
363 ConstantOp::Add => lhs.checked_add(rhs).ok_or_else(|| {
364 ConstEvalError::ConstExprOverflow {
365 span,
366 source_file: env.get_source_file_for(span),
367 }
368 })?,
369 ConstantOp::Sub => lhs.checked_sub(rhs).ok_or_else(|| {
370 ConstEvalError::ConstExprOverflow {
371 span,
372 source_file: env.get_source_file_for(span),
373 }
374 })?,
375 ConstantOp::Mul => lhs.checked_mul(rhs).ok_or_else(|| {
376 ConstEvalError::ConstExprOverflow {
377 span,
378 source_file: env.get_source_file_for(span),
379 }
380 })?,
381 ConstantOp::IntDiv => lhs.checked_div(rhs).ok_or_else(|| {
382 ConstEvalError::DivisionByZero {
383 span,
384 source_file: env.get_source_file_for(span),
385 }
386 })?,
387 ConstantOp::Div => {
388 if rhs.as_int() == 0 {
389 return Err(ConstEvalError::DivisionByZero {
390 span,
391 source_file: env.get_source_file_for(span),
392 }
393 .into());
394 }
395 let lhs = Felt::new_unchecked(lhs.as_int());
396 let rhs = Felt::new_unchecked(rhs.as_int());
397 IntValue::from(lhs / rhs)
398 },
399 };
400 stack.push(ConstantExpr::Int(Span::new(span, result)));
401 },
402 operands @ ((
403 ConstantExpr::Int(_) | ConstantExpr::Var(_),
404 ConstantExpr::Var(_),
405 )
406 | (ConstantExpr::Var(_), ConstantExpr::Int(_))) => {
407 let (lhs, rhs) = operands;
408 stack.push(ConstantExpr::BinaryOp {
409 span,
410 op,
411 lhs: lhs.into(),
412 rhs: rhs.into(),
413 });
414 },
415 (ConstantExpr::Int(_) | ConstantExpr::Var(_), rhs) => {
416 let operand = rhs.span();
417 return Err(ConstEvalError::InvalidConstExprOperand {
418 span,
419 operand,
420 source_file: env.get_source_file_for(operand),
421 }
422 .into());
423 },
424 (lhs, _) => {
425 let operand = lhs.span();
426 return Err(ConstEvalError::InvalidConstExprOperand {
427 span,
428 operand,
429 source_file: env.get_source_file_for(operand),
430 }
431 .into());
432 },
433 }
434 },
435 Cont::Return(from) => {
436 debug_assert!(
437 !stack.is_empty(),
438 "returning from evaluating a constant reference is expected to produce at least one output"
439 );
440 evaluating.pop();
441
442 env.on_eval_completed(from.as_deref(), stack.last().unwrap());
443 },
444 }
445 }
446
447 assert_eq!(stack.len(), 1, "expected constant evaluation to produce exactly one output");
449 Ok(unsafe { stack.pop().unwrap_unchecked() })
452}