Skip to content

Commit

Permalink
feat(cairo_native): integrate native compilation into CommandLineComp…
Browse files Browse the repository at this point in the history
…iler
  • Loading branch information
avi-starkware committed Nov 13, 2024
1 parent 580e598 commit 41d90e5
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 27 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion crates/bin/starknet-native-compile/Cargo.lock

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

3 changes: 2 additions & 1 deletion crates/bin/starknet-native-compile/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[package]
name = "starknet-native-compile"
version = "0.0.0"
# The version of this crate should equal the version of cairo-native.
version = "0.2.1-alpha.0"
edition = "2021"
repository = "https://github.com/starkware-libs/sequencer/"
license = "Apache-2.0"
Expand Down
5 changes: 5 additions & 0 deletions crates/starknet_sierra_compile/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,21 @@ name = "starknet_sierra_compile"
repository.workspace = true
version.workspace = true

[features]
cairo_native = ["dep:cairo-native"]

[lints]
workspace = true

[dependencies]
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
Expand Down
59 changes: 42 additions & 17 deletions crates/starknet_sierra_compile/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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_CAIRO_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 = CAIRO_NATIVE_BINARY_NAME;
let required_version = REQUIRED_CAIRO_NATIVE_VERSION;

let starknet_native_compile_crate_path = PathBuf::from("../bin").join(CAIRO_NATIVE_BINARY_NAME);
let cargo_install_args = &[
CAIRO_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
Expand All @@ -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.");
Expand Down
13 changes: 10 additions & 3 deletions crates/starknet_sierra_compile/src/build_utils.rs
Original file line number Diff line number Diff line change
@@ -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 CAIRO_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"))
Expand All @@ -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()
}
51 changes: 49 additions & 2 deletions crates/starknet_sierra_compile/src/command_line_compiler.rs
Original file line number Diff line number Diff line change
@@ -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, CAIRO_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(CAIRO_NATIVE_BINARY_NAME),
}
}
}

Expand Down Expand Up @@ -59,3 +74,35 @@ impl SierraToCasmCompiler for CommandLineCompiler {
Ok(serde_json::from_slice::<CasmContractClass>(&compile_output.stdout)?)
}
}

#[cfg(feature = "cairo_native")]
impl SierraToNativeCompiler for CommandLineCompiler {
fn compile_to_native(
&self,
contract_class: ContractClass,
) -> Result<AotContractExecutor, CompilationUtilError> {
// Create a temporary file to store the Sierra contract class.
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))?)
}
}
35 changes: 32 additions & 3 deletions crates/starknet_sierra_compile/src/compile_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,23 @@ 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 };

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);
Expand All @@ -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);
Expand All @@ -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(..)));
}
10 changes: 10 additions & 0 deletions crates/starknet_sierra_compile/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")]
Expand Down Expand Up @@ -33,3 +36,10 @@ impl From<std::io::Error> for CompilationUtilError {
CompilationUtilError::UnexpectedError(error.to_string())
}
}

#[cfg(feature = "cairo_native")]
impl From<cairo_native::error::Error> for CompilationUtilError {
fn from(error: cairo_native::error::Error) -> Self {
CompilationUtilError::CompilationError(error.to_string())
}
}
10 changes: 10 additions & 0 deletions crates/starknet_sierra_compile/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -24,3 +26,11 @@ pub trait SierraToCasmCompiler: Send + Sync {
contract_class: ContractClass,
) -> Result<CasmContractClass, CompilationUtilError>;
}

#[cfg(feature = "cairo_native")]
pub trait SierraToNativeCompiler: Send + Sync {
fn compile_to_native(
&self,
contract_class: ContractClass,
) -> Result<AotContractExecutor, CompilationUtilError>;
}

0 comments on commit 41d90e5

Please sign in to comment.