Skip to content

Commit

Permalink
Introduce a consteval_int macro to compute integers at compile-time (
Browse files Browse the repository at this point in the history
  • Loading branch information
wraitii authored Jun 13, 2023
1 parent a96569f commit 8e4e319
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/cairo-lang-plugins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ cairo-lang-syntax = { path = "../cairo-lang-syntax", version = "2.0.0-rc0" }
cairo-lang-utils = { path = "../cairo-lang-utils", version = "2.0.0-rc0" }
indoc.workspace = true
itertools.workspace = true
num-bigint.workspace = true
salsa.workspace = true
smol_str.workspace = true

Expand Down
5 changes: 4 additions & 1 deletion crates/cairo-lang-plugins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::sync::Arc;

use cairo_lang_semantic::plugin::SemanticPlugin;

use crate::plugins::{ConfigPlugin, DerivePlugin, GenerateTraitPlugin, PanicablePlugin};
use crate::plugins::{
ConfigPlugin, ConstevalIntMacroPlugin, DerivePlugin, GenerateTraitPlugin, PanicablePlugin,
};

pub mod plugins;

Expand All @@ -13,6 +15,7 @@ mod test;
/// Gets the list of default plugins to load into the Cairo compiler.
pub fn get_default_plugins() -> Vec<Arc<dyn SemanticPlugin>> {
vec![
Arc::new(ConstevalIntMacroPlugin::default()),
Arc::new(DerivePlugin::default()),
Arc::new(GenerateTraitPlugin::default()),
Arc::new(PanicablePlugin::default()),
Expand Down
170 changes: 170 additions & 0 deletions crates/cairo-lang-plugins/src/plugins/consteval_int.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
use std::sync::Arc;

use cairo_lang_defs::plugin::{
DynGeneratedFileAuxData, MacroPlugin, PluginDiagnostic, PluginGeneratedFile, PluginResult,
};
use cairo_lang_semantic::plugin::{AsDynMacroPlugin, SemanticPlugin, TrivialPluginAuxData};
use cairo_lang_syntax::node::db::SyntaxGroup;
use cairo_lang_syntax::node::{ast, Terminal, TypedSyntaxNode};
use num_bigint::BigInt;

#[derive(Debug, Default)]
#[non_exhaustive]
pub struct ConstevalIntMacroPlugin;

impl AsDynMacroPlugin for ConstevalIntMacroPlugin {
fn as_dyn_macro_plugin<'a>(self: Arc<Self>) -> Arc<dyn MacroPlugin + 'a>
where
Self: 'a,
{
self
}
}
impl SemanticPlugin for ConstevalIntMacroPlugin {}

impl MacroPlugin for ConstevalIntMacroPlugin {
fn generate_code(&self, db: &dyn SyntaxGroup, item_ast: ast::Item) -> PluginResult {
match item_ast {
ast::Item::Constant(constant_ast) => handle_constant(db, &constant_ast),
_ => PluginResult::default(),
}
}
}

/// Rewrite a constant declaration that contains a consteval_int macro
/// into a constant declaration with the computed value,
/// e.g. `const a: felt252 = consteval_int!(2 * 2 * 2);` into `const a: felt252 = 8;`.
fn handle_constant(db: &dyn SyntaxGroup, constant_ast: &ast::ItemConstant) -> PluginResult {
let constant_value = constant_ast.value(db);
if let ast::Expr::InlineMacro(inline_macro) = constant_value {
if inline_macro.path(db).as_syntax_node().get_text(db) != "consteval_int" {
return PluginResult::default();
}
let mut diagnostics = vec![];
let constant_expression =
extract_consteval_macro_expression(db, &inline_macro, &mut diagnostics);
if constant_expression.is_none() {
return PluginResult { diagnostics, ..Default::default() };
}
let new_value = compute_constant_expr(db, &constant_expression.unwrap(), &mut diagnostics);
if new_value.is_none() {
return PluginResult { diagnostics, ..Default::default() };
}
return PluginResult {
code: Some(PluginGeneratedFile {
name: "computed_constants".into(),
content: format!(
"const {}{}= {};",
constant_ast.name(db).text(db),
constant_ast.type_clause(db).as_syntax_node().get_text(db),
new_value.unwrap()
),
aux_data: DynGeneratedFileAuxData(Arc::new(TrivialPluginAuxData {})),
}),
diagnostics,
remove_original_item: true,
};
}
PluginResult::default()
}

/// Extract the actual expression from the consteval_int macro, or fail with diagnostics.
fn extract_consteval_macro_expression(
db: &dyn SyntaxGroup,
macro_ast: &ast::ExprInlineMacro,
diagnostics: &mut Vec<PluginDiagnostic>,
) -> Option<ast::Expr> {
let args = macro_ast.arguments(db).args(db).elements(db);
if args.len() != 1 {
diagnostics.push(PluginDiagnostic {
stable_ptr: macro_ast.stable_ptr().untyped(),
message: "consteval_int macro must have a single unnamed argument.".to_string(),
});
return None;
}
match args[0].arg_clause(db) {
ast::ArgClause::Unnamed(arg) => Some(arg.value(db)),
_ => {
diagnostics.push(PluginDiagnostic {
stable_ptr: macro_ast.stable_ptr().untyped(),
message: "consteval_int macro must have a single unnamed argument.".to_string(),
});
None
}
}
}

/// Compute the actual value of an integer expression, or fail with diagnostics.
/// This computation handles arbitrary integers, unlike regular Cairo math.
fn compute_constant_expr(
db: &dyn SyntaxGroup,
value: &ast::Expr,
diagnostics: &mut Vec<PluginDiagnostic>,
) -> Option<BigInt> {
match value {
ast::Expr::Literal(lit) => lit.numeric_value(db),
ast::Expr::Binary(bin_expr) => match bin_expr.op(db) {
ast::BinaryOperator::Plus(_) => Some(
compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)?
+ compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?,
),
ast::BinaryOperator::Mul(_) => Some(
compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)?
* compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?,
),
ast::BinaryOperator::Minus(_) => Some(
compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)?
- compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?,
),
ast::BinaryOperator::Div(_) => Some(
compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)?
/ compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?,
),
ast::BinaryOperator::Mod(_) => Some(
compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)?
% compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?,
),
ast::BinaryOperator::And(_) => Some(
compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)?
& compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?,
),
ast::BinaryOperator::Or(_) => Some(
compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)?
| compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?,
),
ast::BinaryOperator::Xor(_) => Some(
compute_constant_expr(db, &bin_expr.lhs(db), diagnostics)?
^ compute_constant_expr(db, &bin_expr.rhs(db), diagnostics)?,
),
_ => {
diagnostics.push(PluginDiagnostic {
stable_ptr: bin_expr.stable_ptr().untyped(),
message: "Unsupported binary operator in consteval_int macro".to_string(),
});
None
}
},
ast::Expr::Unary(un_expr) => match un_expr.op(db) {
ast::UnaryOperator::Minus(_) => {
Some(-compute_constant_expr(db, &un_expr.expr(db), diagnostics)?)
}
_ => {
diagnostics.push(PluginDiagnostic {
stable_ptr: un_expr.stable_ptr().untyped(),
message: "Unsupported unary operator in consteval_int macro".to_string(),
});
None
}
},
ast::Expr::Parenthesized(paren_expr) => {
compute_constant_expr(db, &paren_expr.expr(db), diagnostics)
}
_ => {
diagnostics.push(PluginDiagnostic {
stable_ptr: value.stable_ptr().untyped(),
message: "Unsupported expression in consteval_int macro".to_string(),
});
None
}
}
}
2 changes: 2 additions & 0 deletions crates/cairo-lang-plugins/src/plugins/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
pub use config::*;
pub use consteval_int::*;
pub use derive::*;
pub use generate_trait::*;
pub use panicable::*;

mod config;
mod consteval_int;
mod derive;
mod generate_trait;
mod panicable;
1 change: 1 addition & 0 deletions crates/cairo-lang-plugins/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ cairo_lang_test_utils::test_file_test!(
expand_plugin,
"src/test_data",
{
consteval_int: "consteval_int",
config: "config",
derive: "derive",
generate_trait: "generate_trait",
Expand Down
107 changes: 107 additions & 0 deletions crates/cairo-lang-plugins/src/test_data/consteval_int
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//! > Test consteval_int! macro

//! > test_runner_name
test_expand_plugin

//! > cairo_code
const a: felt252 = 0;

const b: felt252 = consteval_int!(4 + 5);

const c: felt252 = 4 + 5;

const d: felt252 = consteval_int!(23 + 4 * 5 + (4 + 5) / 2);

const e: u8 = consteval_int!(255 + 1 - 1);

//! > generated_cairo_code
const a: felt252 = 0;

const b: felt252 = 9;

const c: felt252 = 4 + 5;

const d: felt252 = 47;
const e: u8 = 255;

//! > expected_diagnostics

//! > ==========================================================================

//! > Test bad consteval_int! macros

//! > test_runner_name
test_expand_plugin

//! > cairo_code
const a: felt252 = consteval_int!(func_call(24));

const b: felt252 = consteval_int!('some string');

const c: felt252 = consteval_int!(*24);

const d: felt252 = consteval_int!(~24);

const e: felt252 = consteval_int!(234 < 5);

//! > generated_cairo_code
const a: felt252 = consteval_int!(func_call(24));


const b: felt252 = consteval_int!('some string');


const c: felt252 = consteval_int!(*24);


const d: felt252 = consteval_int!(~24);


const e: felt252 = consteval_int!(234 < 5);

//! > expected_diagnostics
error: Unsupported expression in consteval_int macro
--> dummy_file.cairo:1:35
const a: felt252 = consteval_int!(func_call(24));
^***********^

error: Unsupported expression in consteval_int macro
--> dummy_file.cairo:3:35
const b: felt252 = consteval_int!('some string');
^***********^

error: Unsupported unary operator in consteval_int macro
--> dummy_file.cairo:5:35
const c: felt252 = consteval_int!(*24);
^*^

error: Unsupported unary operator in consteval_int macro
--> dummy_file.cairo:7:35
const d: felt252 = consteval_int!(~24);
^*^

error: Unsupported binary operator in consteval_int macro
--> dummy_file.cairo:9:35
const e: felt252 = consteval_int!(234 < 5);
^*****^

//! > ==========================================================================

//! > Test consteval_int! inside functions (currently does nothing)

//! > test_runner_name
test_expand_plugin

//! > cairo_code
fn some_func()
{
return consteval_int!(4 + 5);
}

//! > generated_cairo_code
fn some_func()
{
return consteval_int!(4 + 5);
}

//! > expected_diagnostics
7 changes: 7 additions & 0 deletions tests/bug_samples/issue3130.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const a: felt252 = consteval_int!((4 + 2 * 3) * 256);
const b: felt252 = consteval_int!(0xff & (24 + 5 * 2));
const c: felt252 = consteval_int!(-0xff & (24 + 5 * 2));
const d: felt252 = consteval_int!(0xff | (24 + 5 * 2));

#[test]
fn main() {}
1 change: 1 addition & 0 deletions tests/bug_samples/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod issue2939;
mod issue2961;
mod issue2964;
mod issue2995;
mod issue3130;
mod issue3153;
mod issue3192;
mod issue3211;
Expand Down

0 comments on commit 8e4e319

Please sign in to comment.