From 6b16bfb4d9f32eef82023b09eaef0dd1855d59dd Mon Sep 17 00:00:00 2001 From: Avi Cohen Date: Tue, 12 Nov 2024 17:25:19 +0200 Subject: [PATCH] feat(cairo_native): integrate native compilation into CommandLineCompiler --- Cargo.lock | 13 ++++ crates/bin/starknet-native-compile/Cargo.lock | 2 +- crates/bin/starknet-native-compile/Cargo.toml | 3 +- crates/starknet_sierra_compile/Cargo.toml | 5 ++ crates/starknet_sierra_compile/build.rs | 59 +++++++++++++------ .../src/build_utils.rs | 13 +++- .../src/command_line_compiler.rs | 55 ++++++++++++++++- .../src/compile_test.rs | 35 ++++++++++- crates/starknet_sierra_compile/src/errors.rs | 10 ++++ crates/starknet_sierra_compile/src/lib.rs | 10 ++++ 10 files changed, 178 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3e33346a9..5462a73d46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10047,6 +10047,17 @@ dependencies = [ "serde", ] +[[package]] +name = "starknet-native-compile" +version = "0.2.0-alpha.4" +dependencies = [ + "cairo-lang-sierra", + "cairo-lang-starknet-classes", + "cairo-native", + "clap", + "serde_json", +] + [[package]] name = "starknet-types-core" version = "0.1.7" @@ -10488,11 +10499,13 @@ dependencies = [ "cairo-lang-sierra", "cairo-lang-starknet-classes", "cairo-lang-utils", + "cairo-native", "mempool_test_utils", "papyrus_config", "rstest", "serde", "serde_json", + "starknet-native-compile", "starknet-types-core", "starknet_api", "tempfile", diff --git a/crates/bin/starknet-native-compile/Cargo.lock b/crates/bin/starknet-native-compile/Cargo.lock index 99a6639a16..d85c509173 100644 --- a/crates/bin/starknet-native-compile/Cargo.lock +++ b/crates/bin/starknet-native-compile/Cargo.lock @@ -2947,7 +2947,7 @@ dependencies = [ [[package]] name = "starknet-native-compile" -version = "0.0.0" +version = "0.2.0-alpha.4" dependencies = [ "cairo-lang-sierra", "cairo-lang-starknet-classes", diff --git a/crates/bin/starknet-native-compile/Cargo.toml b/crates/bin/starknet-native-compile/Cargo.toml index 81cfb1f7a0..080fc2dfd5 100644 --- a/crates/bin/starknet-native-compile/Cargo.toml +++ b/crates/bin/starknet-native-compile/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "starknet-native-compile" -version = "0.0.0" +# The version of this crate should equal the version of cario-native. +version = "0.2.0-alpha.4" edition = "2021" repository = "https://github.com/starkware-libs/sequencer/" license = "Apache-2.0" diff --git a/crates/starknet_sierra_compile/Cargo.toml b/crates/starknet_sierra_compile/Cargo.toml index 97e211e96a..c36afdf148 100644 --- a/crates/starknet_sierra_compile/Cargo.toml +++ b/crates/starknet_sierra_compile/Cargo.toml @@ -5,6 +5,9 @@ name = "starknet_sierra_compile" repository.workspace = true version.workspace = true +[features] +cairo_native = ["dep:cairo-native"] + [lints] workspace = true @@ -12,9 +15,11 @@ workspace = true cairo-lang-sierra.workspace = true cairo-lang-starknet-classes.workspace = true cairo-lang-utils.workspace = true +cairo-native = { workspace = true, optional = true } papyrus_config.workspace = true serde.workspace = true serde_json.workspace = true +starknet-native-compile = { path = "../bin/starknet-native-compile/", optional = true } starknet-types-core.workspace = true starknet_api.workspace = true tempfile.workspace = true diff --git a/crates/starknet_sierra_compile/build.rs b/crates/starknet_sierra_compile/build.rs index ff1873ecf1..829036bd54 100644 --- a/crates/starknet_sierra_compile/build.rs +++ b/crates/starknet_sierra_compile/build.rs @@ -7,60 +7,85 @@ fn main() { println!("cargo:rerun-if-changed=build.rs"); install_starknet_sierra_compile(); + #[cfg(feature = "cairo_native")] + install_starknet_native_compile(); } -const REQUIRED_VERSION: &str = "2.7.1"; +const REQUIRED_CAIRO_LANG_VERSION: &str = "2.7.1"; +#[cfg(feature = "cairo_native")] +const REQUIRED_NATIVE_VERSION: &str = "0.2.0-alpha.4"; /// Downloads the Cairo crate from StarkWare's release page and extracts its contents into the /// `target` directory. This crate includes the `starknet-sierra-compile` binary, which is used to /// compile Sierra to Casm. The binary is executed as a subprocess whenever Sierra compilation is /// required. fn install_starknet_sierra_compile() { - let binary_path = binary_path(); + let binary_name = CAIRO_LANG_BINARY_NAME; + let required_version = REQUIRED_CAIRO_LANG_VERSION; + let cargo_install_args = &[CAIRO_LANG_BINARY_NAME, "--version", REQUIRED_CAIRO_LANG_VERSION]; + install_compiler_binary(binary_name, required_version, cargo_install_args); +} + +/// Installs the `starknet-native-compile` crate from the current repository and moves the binary +/// to the shared executables folder. This crate includes the `starknet-native-compile` binary, +/// which is used to compile Sierra to 0x86. The binary is executed as a subprocess whenever Sierra +/// compilation is required. +#[cfg(feature = "cairo_native")] +fn install_starknet_native_compile() { + let binary_name = NATIVE_BINARY_NAME; + let required_version = REQUIRED_NATIVE_VERSION; + + let starknet_native_compile_crate_path = PathBuf::from("../bin").join(NATIVE_BINARY_NAME); + let cargo_install_args = &[ + NATIVE_BINARY_NAME, + "--path", + starknet_native_compile_crate_path + .to_str() + .expect("Failed to convert the crate path to str"), + ]; + install_compiler_binary(binary_name, required_version, cargo_install_args); +} + +fn install_compiler_binary(binary_name: &str, required_version: &str, cargo_install_args: &[&str]) { + let binary_path = binary_path(binary_name); match Command::new(&binary_path).args(["--version"]).output() { Ok(binary_version) => { let binary_version = String::from_utf8(binary_version.stdout) .expect("Failed to convert the binary version to a string."); - if binary_version.contains(REQUIRED_VERSION) { - println!("The starknet-sierra-compile binary is up to date."); + if binary_version.contains(required_version) { + println!("The {binary_name} binary is up to date."); return; } else { println!( - "The starknet-sierra-compile binary is not up to date. Installing the \ - required version." + "The {binary_name} binary is not up to date. Installing the required version." ); std::fs::remove_file(&binary_path).expect("Failed to remove the old binary."); } } Err(_) => { - println!( - "The starknet-sierra-compile binary is not installed. Installing the required \ - version." - ); + println!("The {binary_name} binary is not installed. Installing the required version."); } } let out_dir = out_dir(); let temp_cargo_path = out_dir.join("cargo"); - let post_install_file_path = temp_cargo_path.join("bin").join(BINARY_NAME); + let post_install_file_path = temp_cargo_path.join("bin").join(binary_name); // Create the temporary cargo directory if it doesn't exist std::fs::create_dir_all(&temp_cargo_path).expect("Failed to create cargo directory"); let install_command_status = Command::new("cargo") .args([ "install", - BINARY_NAME, "--root", temp_cargo_path.to_str().expect("Failed to convert cargo_path to str"), - "--version", - REQUIRED_VERSION, ]) + .args(cargo_install_args) .status() - .expect("Failed to install starknet-sierra-compile"); + .unwrap_or_else(|_| panic!("Failed to install {binary_name}")); if !install_command_status.success() { - panic!("Failed to install starknet-sierra-compile"); + panic!("Failed to install {}", binary_name); } // Move the 'starknet-sierra-compile' executable to a shared location @@ -72,7 +97,7 @@ fn install_starknet_sierra_compile() { .expect("Failed to perform mv command."); if !move_command_status.success() { - panic!("Failed to move the starknet-sierra-compile binary to the shared folder."); + panic!("Failed to move the {} binary to the shared folder.", binary_name); } std::fs::remove_dir_all(temp_cargo_path).expect("Failed to remove the cargo directory."); diff --git a/crates/starknet_sierra_compile/src/build_utils.rs b/crates/starknet_sierra_compile/src/build_utils.rs index fdb7958cc7..f7f991f48a 100644 --- a/crates/starknet_sierra_compile/src/build_utils.rs +++ b/crates/starknet_sierra_compile/src/build_utils.rs @@ -1,6 +1,8 @@ use std::path::{Path, PathBuf}; -const BINARY_NAME: &str = "starknet-sierra-compile"; +pub(crate) const CAIRO_LANG_BINARY_NAME: &str = "starknet-sierra-compile"; +#[cfg(feature = "cairo_native")] +pub(crate) const NATIVE_BINARY_NAME: &str = "starknet-native-compile"; fn out_dir() -> PathBuf { Path::new(&std::env::var("OUT_DIR").expect("Failed to get the OUT_DIR environment variable")) @@ -23,6 +25,11 @@ fn shared_folder_dir() -> PathBuf { target_dir().join("shared_executables") } -pub fn binary_path() -> PathBuf { - shared_folder_dir().join(BINARY_NAME) +pub fn binary_path(binary_name: &str) -> PathBuf { + shared_folder_dir().join(binary_name) +} + +#[cfg(feature = "cairo_native")] +pub fn output_file_path() -> String { + out_dir().join("output.so").to_str().unwrap().into() } diff --git a/crates/starknet_sierra_compile/src/command_line_compiler.rs b/crates/starknet_sierra_compile/src/command_line_compiler.rs index 2a59932ba3..65f9a92b48 100644 --- a/crates/starknet_sierra_compile/src/command_line_compiler.rs +++ b/crates/starknet_sierra_compile/src/command_line_compiler.rs @@ -1,25 +1,40 @@ use std::io::Write; +#[cfg(feature = "cairo_native")] +use std::path::Path; use std::path::PathBuf; use std::process::Command; use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use cairo_lang_starknet_classes::contract_class::ContractClass; +#[cfg(feature = "cairo_native")] +use cairo_native::executor::AotContractExecutor; use tempfile::NamedTempFile; -use crate::build_utils::binary_path; +use crate::build_utils::{binary_path, CAIRO_LANG_BINARY_NAME}; +#[cfg(feature = "cairo_native")] +use crate::build_utils::{output_file_path, NATIVE_BINARY_NAME}; use crate::config::SierraToCasmCompilationConfig; use crate::errors::CompilationUtilError; use crate::SierraToCasmCompiler; +#[cfg(feature = "cairo_native")] +use crate::SierraToNativeCompiler; #[derive(Clone)] pub struct CommandLineCompiler { pub config: SierraToCasmCompilationConfig, path_to_starknet_sierra_compile_binary: PathBuf, + #[cfg(feature = "cairo_native")] + path_to_starknet_native_compile_binary: PathBuf, } impl CommandLineCompiler { pub fn new(config: SierraToCasmCompilationConfig) -> Self { - Self { config, path_to_starknet_sierra_compile_binary: binary_path() } + Self { + config, + path_to_starknet_sierra_compile_binary: binary_path(CAIRO_LANG_BINARY_NAME), + #[cfg(feature = "cairo_native")] + path_to_starknet_native_compile_binary: binary_path(NATIVE_BINARY_NAME), + } } } @@ -59,3 +74,39 @@ impl SierraToCasmCompiler for CommandLineCompiler { Ok(serde_json::from_slice::(&compile_output.stdout)?) } } + +#[cfg(feature = "cairo_native")] +impl SierraToNativeCompiler for CommandLineCompiler { + fn compile_to_native( + &self, + contract_class: ContractClass, + ) -> Result { + // Create a temporary file to store the Sierra contract class. + println!( + "Path to starknet native compile binary: {:?}", + self.path_to_starknet_native_compile_binary.as_os_str() + ); + let serialized_contract_class = serde_json::to_string(&contract_class)?; + + let mut temp_file = NamedTempFile::new()?; + temp_file.write_all(serialized_contract_class.as_bytes())?; + let temp_file_path = temp_file.path().to_str().ok_or( + CompilationUtilError::UnexpectedError("Failed to get temporary file path".to_owned()), + )?; + + let output_file_path = output_file_path(); + + // Set the parameters for the compile process. + let mut command = Command::new(self.path_to_starknet_native_compile_binary.as_os_str()); + command.args([temp_file_path, &output_file_path]); + + let compile_output = command.output()?; + + if !compile_output.status.success() { + let stderr_output = String::from_utf8(compile_output.stderr) + .unwrap_or("Failed to get stderr output".into()); + return Err(CompilationUtilError::CompilationError(stderr_output)); + }; + Ok(AotContractExecutor::load(Path::new(&output_file_path))?) + } +} diff --git a/crates/starknet_sierra_compile/src/compile_test.rs b/crates/starknet_sierra_compile/src/compile_test.rs index 675f911343..cb59c4a716 100644 --- a/crates/starknet_sierra_compile/src/compile_test.rs +++ b/crates/starknet_sierra_compile/src/compile_test.rs @@ -11,6 +11,8 @@ use crate::config::SierraToCasmCompilationConfig; use crate::errors::CompilationUtilError; use crate::test_utils::contract_class_from_file; use crate::SierraToCasmCompiler; +#[cfg(feature = "cairo_native")] +use crate::SierraToNativeCompiler; const SIERRA_TO_CASM_COMPILATION_CONFIG: SierraToCasmCompilationConfig = SierraToCasmCompilationConfig { max_bytecode_size: 81920 }; @@ -18,14 +20,14 @@ const SIERRA_TO_CASM_COMPILATION_CONFIG: SierraToCasmCompilationConfig = fn cairo_lang_compiler() -> CairoLangSierraToCasmCompiler { CairoLangSierraToCasmCompiler { config: SIERRA_TO_CASM_COMPILATION_CONFIG } } -fn commnad_line_compiler() -> CommandLineCompiler { +fn command_line_compiler() -> CommandLineCompiler { CommandLineCompiler::new(SIERRA_TO_CASM_COMPILATION_CONFIG) } // TODO: use the other compiler as well. #[rstest] #[case::cairo_lang_compiler(cairo_lang_compiler())] -#[case::command_line_compiler(commnad_line_compiler())] +#[case::command_line_compiler(command_line_compiler())] fn test_compile_sierra_to_casm(#[case] compiler: impl SierraToCasmCompiler) { env::set_current_dir(get_absolute_path(TEST_FILES_FOLDER)).expect("Failed to set current dir."); let sierra_path = Path::new(FAULTY_ACCOUNT_CLASS_FILE); @@ -41,7 +43,7 @@ fn test_compile_sierra_to_casm(#[case] compiler: impl SierraToCasmCompiler) { // TODO(Arni, 1/5/2024): Add a test for panic result test. #[rstest] #[case::cairo_lang_compiler(cairo_lang_compiler())] -#[case::command_line_compiler(commnad_line_compiler())] +#[case::command_line_compiler(command_line_compiler())] fn test_negative_flow_compile_sierra_to_casm(#[case] compiler: impl SierraToCasmCompiler) { env::set_current_dir(get_absolute_path(TEST_FILES_FOLDER)).expect("Failed to set current dir."); let sierra_path = Path::new(FAULTY_ACCOUNT_CLASS_FILE); @@ -53,3 +55,30 @@ fn test_negative_flow_compile_sierra_to_casm(#[case] compiler: impl SierraToCasm let result = compiler.compile(contract_class); assert_matches!(result, Err(CompilationUtilError::CompilationError(..))); } + +#[cfg(feature = "cairo_native")] +#[test] +fn test_compile_sierra_to_native() { + let compiler = command_line_compiler(); + env::set_current_dir(get_absolute_path(TEST_FILES_FOLDER)).expect("Failed to set current dir."); + let sierra_path = Path::new(FAULTY_ACCOUNT_CLASS_FILE); + // TODO(Avi, 1/1/2025): Check size/memory/time limits. + + let contract_class = contract_class_from_file(sierra_path); + let _native_contract_executor = compiler.compile_to_native(contract_class).unwrap(); +} + +#[cfg(feature = "cairo_native")] +#[test] +fn test_negative_flow_compile_sierra_to_native() { + let compiler = command_line_compiler(); + env::set_current_dir(get_absolute_path(TEST_FILES_FOLDER)).expect("Failed to set current dir."); + let sierra_path = Path::new(FAULTY_ACCOUNT_CLASS_FILE); + + let mut contract_class = contract_class_from_file(sierra_path); + // Truncate the sierra program to trigger an error. + contract_class.sierra_program = contract_class.sierra_program[..100].to_vec(); + + let result = compiler.compile_to_native(contract_class); + assert_matches!(result, Err(CompilationUtilError::CompilationError(..))); +} diff --git a/crates/starknet_sierra_compile/src/errors.rs b/crates/starknet_sierra_compile/src/errors.rs index 85ca1823c2..cab3c0a4cc 100644 --- a/crates/starknet_sierra_compile/src/errors.rs +++ b/crates/starknet_sierra_compile/src/errors.rs @@ -2,6 +2,9 @@ use cairo_lang_starknet_classes::allowed_libfuncs::AllowedLibfuncsError; use cairo_lang_starknet_classes::casm_contract_class::StarknetSierraCompilationError; use thiserror::Error; +#[cfg(feature = "cairo_native")] +use cairo_native; + #[derive(Debug, Error)] pub enum CompilationUtilError { #[error("Starknet Sierra compilation error: {0}")] @@ -33,3 +36,10 @@ impl From for CompilationUtilError { CompilationUtilError::UnexpectedError(error.to_string()) } } + +#[cfg(feature = "cairo_native")] +impl From for CompilationUtilError { + fn from(error: cairo_native::error::Error) -> Self { + CompilationUtilError::CompilationError(error.to_string()) + } +} diff --git a/crates/starknet_sierra_compile/src/lib.rs b/crates/starknet_sierra_compile/src/lib.rs index 8734f4d9c2..d8974bb05c 100644 --- a/crates/starknet_sierra_compile/src/lib.rs +++ b/crates/starknet_sierra_compile/src/lib.rs @@ -1,6 +1,8 @@ //! A lib for compiling Sierra into Casm. use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use cairo_lang_starknet_classes::contract_class::ContractClass; +#[cfg(feature = "cairo_native")] +use cairo_native::executor::AotContractExecutor; use crate::errors::CompilationUtilError; @@ -24,3 +26,11 @@ pub trait SierraToCasmCompiler: Send + Sync { contract_class: ContractClass, ) -> Result; } + +#[cfg(feature = "cairo_native")] +pub trait SierraToNativeCompiler: Send + Sync { + fn compile_to_native( + &self, + contract_class: ContractClass, + ) -> Result; +}