Skip to content

Commit

Permalink
Added basic runnable to textual casm compilation. (#6595)
Browse files Browse the repository at this point in the history
  • Loading branch information
orizi authored Nov 12, 2024
1 parent 226c364 commit 0fcdfdf
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 12 deletions.
5 changes: 5 additions & 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-runnable-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ cairo-lang-sierra-to-casm = { path = "../cairo-lang-sierra-to-casm", version = "
cairo-lang-sierra-type-size = { path = "../cairo-lang-sierra-type-size", version = "~2.8.4" }
cairo-lang-utils = { path = "../cairo-lang-utils", version = "~2.8.4" }
cairo-vm.workspace = true
itertools.workspace = true
thiserror.workspace = true

[dev-dependencies]
22 changes: 22 additions & 0 deletions crates/cairo-lang-runnable-utils/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use cairo_lang_utils::casts::IntoOrPanic;
use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
use cairo_vm::types::builtin_name::BuiltinName;
use itertools::{Itertools, chain};
use thiserror::Error;

#[derive(Debug, Error)]
Expand Down Expand Up @@ -166,6 +167,27 @@ impl RunnableBuilder {
Ok((assembled_cairo_program, builtins))
}

/// CASM style string representation of the program.
pub fn casm_function_program(&self, func: &Function) -> Result<String, BuildError> {
let (header, builtins) = self.create_entry_code(func)?;
let footer = create_code_footer();

Ok(chain!(
[
format!("# builtins: {}\n", builtins.into_iter().map(|b| b.to_str()).join(", ")),
"# header #\n".to_string()
],
header.into_iter().map(|i| format!("{i};\n")),
[
"# sierra based code #\n".to_string(),
self.casm_program.to_string(),
"# footer #\n".to_string()
],
footer.into_iter().map(|i| format!("{i};\n")),
)
.join(""))
}

/// Returns the instructions to add to the beginning of the code to successfully call the main
/// function, as well as the builtins required to execute the program.
fn create_entry_code(
Expand Down
11 changes: 7 additions & 4 deletions crates/cairo-lang-runnable/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ license-file.workspace = true
description = "Cairo runnable provable artifact."

[dependencies]
anyhow.workspace = true
cairo-lang-compiler = { path = "../cairo-lang-compiler", version = "~2.8.4" }
cairo-lang-defs = { path = "../cairo-lang-defs", version = "~2.8.4" }
cairo-lang-filesystem = { path = "../cairo-lang-filesystem", version = "~2.8.4" }
cairo-lang-lowering = { path = "../cairo-lang-lowering", version = "~2.8.4" }
cairo-lang-plugins = { path = "../cairo-lang-plugins", version = "~2.8.4" }
cairo-lang-runnable-utils = { path = "../cairo-lang-runnable-utils", version = "~2.8.4" }
cairo-lang-semantic = { path = "../cairo-lang-semantic", version = "~2.8.4" }
cairo-lang-sierra-generator = { path = "../cairo-lang-sierra-generator", version = "~2.8.4" }
cairo-lang-syntax = { path = "../cairo-lang-syntax", version = "~2.8.4" }
cairo-lang-utils = { path = "../cairo-lang-utils", version = "~2.8.4" }
itertools = { workspace = true, default-features = true }
Expand All @@ -19,14 +26,10 @@ serde.workspace = true
cairo-lang-compiler = { path = "../cairo-lang-compiler" }
cairo-lang-debug = { path = "../cairo-lang-debug" }
cairo-lang-diagnostics = { path = "../cairo-lang-diagnostics" }
cairo-lang-filesystem = { path = "../cairo-lang-filesystem" }
cairo-lang-plugins = { path = "../cairo-lang-plugins", features = ["testing"] }
cairo-lang-semantic = { path = "../cairo-lang-semantic", features = ["testing"] }
cairo-lang-test-utils = { path = "../cairo-lang-test-utils", features = ["testing"] }
env_logger.workspace = true
indoc.workspace = true
test-case.workspace = true
test-log.workspace = true

[package.metadata.cargo-machete]
ignored = ["ark-secp256k1", "ark-secp256r1"]
103 changes: 103 additions & 0 deletions crates/cairo-lang-runnable/src/compile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use std::path::Path;
use std::sync::Arc;

use anyhow::{Context, Result};
use cairo_lang_compiler::db::RootDatabase;
use cairo_lang_compiler::diagnostics::DiagnosticsReporter;
use cairo_lang_compiler::project::setup_project;
use cairo_lang_filesystem::ids::CrateId;
use cairo_lang_lowering::ids::ConcreteFunctionWithBodyId;
use cairo_lang_runnable_utils::builder::RunnableBuilder;
use cairo_lang_semantic::plugin::PluginSuite;
use cairo_lang_sierra_generator::db::SierraGenGroup;
use cairo_lang_sierra_generator::executables::find_executable_function_ids;
use cairo_lang_sierra_generator::program_generator::SierraProgramWithDebug;
use cairo_lang_utils::Upcast;
use itertools::Itertools;

use crate::plugin::{RUNNABLE_ATTR, RunnablePlugin};

/// Compile the function given by path.
/// Errors if there is ambiguity.
pub fn compile_runnable(
path: &Path,
runnable_path: Option<&str>,
diagnostics_reporter: DiagnosticsReporter<'_>,
) -> Result<String> {
let mut db = RootDatabase::builder()
.detect_corelib()
.with_plugin_suite(std::mem::take(PluginSuite::default().add_plugin::<RunnablePlugin>()))
.build()?;

let main_crate_ids = setup_project(&mut db, Path::new(&path))?;

compile_runnable_in_prepared_db(&db, runnable_path, main_crate_ids, diagnostics_reporter)
}

/// Runs compiler on the specified runnable function.
/// If no runnable was specified, verify that there is only one.
/// Otherwise, return an error.
pub fn compile_runnable_in_prepared_db(
db: &RootDatabase,
runnable_path: Option<&str>,
main_crate_ids: Vec<CrateId>,
mut diagnostics_reporter: DiagnosticsReporter<'_>,
) -> Result<String> {
let mut runnables: Vec<_> = find_executable_function_ids(db, main_crate_ids)
.into_iter()
.filter_map(|(id, labels)| labels.into_iter().any(|l| l == RUNNABLE_ATTR).then_some(id))
.collect();

// TODO(ilya): Add contract names.
if let Some(runnable_path) = runnable_path {
runnables.retain(|runnable| {
runnable.base_semantic_function(db).full_path(db.upcast()) == runnable_path
});
};
let runnable = match runnables.len() {
0 => {
// Report diagnostics as they might reveal the reason why no runnable was found.
diagnostics_reporter.ensure(db)?;
anyhow::bail!("Requested `#[runnable]` not found.");
}
1 => runnables[0],
_ => {
let runnable_names = runnables
.iter()
.map(|runnable| runnable.base_semantic_function(db).full_path(db.upcast()))
.join("\n ");
anyhow::bail!(
"More than one runnable found in the main crate: \n {}\nUse --runnable to \
specify which to compile.",
runnable_names
);
}
};

compile_runnable_function_in_prepared_db(db, runnable, diagnostics_reporter)
}

/// Runs compiler for a runnable function.
///
/// # Arguments
/// * `db` - Preloaded compilation database.
/// * `runnable` - [`ConcreteFunctionWithBodyId`]s to compile.
/// * `compiler_config` - The compiler configuration.
/// # Returns
/// * `Ok(Vec<String>)` - The result artifact of the compilation.
/// * `Err(anyhow::Error)` - Compilation failed.
pub fn compile_runnable_function_in_prepared_db(
db: &RootDatabase,
runnable: ConcreteFunctionWithBodyId,
mut diagnostics_reporter: DiagnosticsReporter<'_>,
) -> Result<String> {
diagnostics_reporter.ensure(db)?;
let SierraProgramWithDebug { program: sierra_program, debug_info: _ } = Arc::unwrap_or_clone(
db.get_sierra_program_for_functions(vec![runnable])
.ok()
.with_context(|| "Compilation failed without any diagnostics.")?,
);
let runnable_func = sierra_program.funcs[0].clone();
let builder = RunnableBuilder::new(sierra_program, None)?;
Ok(builder.casm_function_program(&runnable_func)?)
}
130 changes: 130 additions & 0 deletions crates/cairo-lang-runnable/src/compile_test_data/basic
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//! > Basic runnable.

//! > test_runner_name
CompileRunnableTestRunner(expect_diagnostics: false)

//! > cairo_code
#[runnable]
fn main() {}

//! > generated_casm_code
# builtins:
# header #
ap += 0;
call rel 3;
ret;
# sierra based code #
ret;
# footer #
ret;

//! > expected_diagnostics

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

//! > Test runnable with arguments.

//! > test_runner_name
CompileRunnableTestRunner(expect_diagnostics: false)

//! > cairo_code
#[runnable]
fn main(a: felt252, b: felt252) -> felt252 {
a + b
}

//! > generated_casm_code
# builtins:
# header #
ap += 0;
%{ raise NotImplementedError %}
%{ raise NotImplementedError %}
ap += 2;
call rel 3;
ret;
# sierra based code #
[ap + 0] = [fp + -4] + [fp + -3], ap++;
ret;
# footer #
ret;

//! > expected_diagnostics

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

//! > Test runnable with recursion.

//! > test_runner_name
CompileRunnableTestRunner(expect_diagnostics: false)

//! > cairo_code
#[runnable]
fn fib(a: u128, b: u128, n: u128) -> u128 {
if n == 0 {
a
} else {
fib(b, a + b, n - 1)
}
}

//! > generated_casm_code
# builtins: range_check
# header #
ap += 0;
[ap + 0] = [fp + -3], ap++;
%{ raise NotImplementedError %}
%{ raise NotImplementedError %}
%{ raise NotImplementedError %}
ap += 3;
call rel 3;
ret;
# sierra based code #
jmp rel 9 if [fp + -3] != 0;
[ap + 0] = [fp + -6], ap++;
[ap + 0] = 0, ap++;
[ap + 0] = 0, ap++;
[ap + 0] = [fp + -5], ap++;
ret;
[ap + 1] = [fp + -5] + [fp + -4], ap++;
%{ memory[ap + -1] = memory[ap + 0] < 340282366920938463463374607431768211456 %}
jmp rel 7 if [ap + -1] != 0, ap++;
[ap + -1] = [ap + 0] + 340282366920938463463374607431768211456, ap++;
[ap + -1] = [[fp + -6] + 0];
jmp rel 35;
[ap + -1] = [[fp + -6] + 0];
[ap + 0] = 1, ap++;
[fp + -3] = [ap + 1] + [ap + -1], ap++;
%{ memory[ap + -1] = memory[ap + 0] < 340282366920938463463374607431768211456 %}
jmp rel 7 if [ap + -1] != 0, ap++;
[ap + 0] = [ap + -1] + 340282366920938463463374607431768211456, ap++;
[ap + -1] = [[fp + -6] + 1];
jmp rel 11;
[ap + -1] = [[fp + -6] + 1];
[ap + 0] = [fp + -6] + 2, ap++;
[ap + 0] = [fp + -4], ap++;
[ap + 0] = [ap + -6], ap++;
[ap + 0] = [ap + -4], ap++;
call rel -34;
ret;
%{ memory[ap + 0] = segments.add() %}
ap += 1;
[ap + 0] = 39878429859763533771555484554338820190071, ap++;
[ap + -1] = [[ap + -2] + 0];
[ap + 0] = [fp + -6] + 2, ap++;
[ap + 0] = 1, ap++;
[ap + 0] = [ap + -4], ap++;
[ap + 0] = [ap + -5] + 1, ap++;
ret;
%{ memory[ap + 0] = segments.add() %}
ap += 1;
[ap + 0] = 39878429859757942499084499860145094553463, ap++;
[ap + -1] = [[ap + -2] + 0];
[ap + 0] = [fp + -6] + 1, ap++;
[ap + 0] = 1, ap++;
[ap + 0] = [ap + -4], ap++;
[ap + 0] = [ap + -5] + 1, ap++;
ret;
# footer #
ret;

//! > expected_diagnostics
4 changes: 4 additions & 0 deletions crates/cairo-lang-runnable/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
pub mod compile;
pub mod plugin;

#[cfg(test)]
mod test;
6 changes: 1 addition & 5 deletions crates/cairo-lang-runnable/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ use cairo_lang_syntax::node::helpers::{OptionWrappedGenericParamListHelper, Quer
use indoc::formatdoc;
use itertools::Itertools;

#[cfg(test)]
#[path = "plugin_test.rs"]
mod test;

const RUNNABLE_ATTR: &str = "runnable";
pub const RUNNABLE_ATTR: &str = "runnable";
const RUNNABLE_PREFIX: &str = "__runnable_wrapper__";
const IMPLICIT_PRECEDENCE: &[&str] = &[
"core::pedersen::Pedersen",
Expand Down
Loading

0 comments on commit 0fcdfdf

Please sign in to comment.