Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cairo_native): add batcher compiler struct #2187

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

3 changes: 2 additions & 1 deletion crates/blockifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description = "The transaction-executing component in the Starknet sequencer."
workspace = true

[features]
cairo_native = ["dep:cairo-native"]
cairo_native = ["dep:cairo-native", "starknet_sierra_compile/cairo_native"]
jemalloc = ["dep:tikv-jemallocator"]
reexecution = ["transaction_serde"]
testing = ["rand", "rstest", "starknet_api/testing"]
Expand Down Expand Up @@ -50,6 +50,7 @@ serde_json = { workspace = true, features = ["arbitrary_precision"] }
sha2.workspace = true
starknet-types-core.workspace = true
starknet_api.workspace = true
starknet_sierra_compile = { workspace = true, optional = true }
strum.workspace = true
strum_macros.workspace = true
tempfile.workspace = true
Expand Down
2 changes: 2 additions & 0 deletions crates/blockifier/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub mod cached_state;
#[cfg(feature = "cairo_native")]
pub mod contract_class_manager;
#[cfg(test)]
pub mod error_format_test;
pub mod errors;
Expand Down
129 changes: 129 additions & 0 deletions crates/blockifier/src/state/contract_class_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use std::sync::mpsc::{sync_channel, Receiver, SyncSender, TrySendError};
use std::sync::Arc;

use log::{error, info};
use starknet_api::core::ClassHash;
use starknet_api::state::SierraContractClass;
use starknet_sierra_compile::command_line_compiler::CommandLineCompiler;
use starknet_sierra_compile::config::SierraToCasmCompilationConfig;
use starknet_sierra_compile::utils::into_contract_class_for_compilation;
use starknet_sierra_compile::SierraToNativeCompiler;

use crate::execution::contract_class::{CompiledClassV1, RunnableCompiledClass};
use crate::execution::native::contract_class::NativeCompiledClassV1;
use crate::state::global_cache::{CachedCairoNative, ContractCaches};

const CHANNEL_SIZE: usize = 1000;

/// Represents a request to compile a sierra contract class to a native compiled class.
///
/// # Fields:
/// * `class_hash` - used to identify the contract class in the cache.
/// * `sierra_contract_class` - the sierra contract class to be compiled.
/// * `casm_compiled_class` - stored in [`NativeCompiledClassV1`] to allow fallback to cairo_vm
/// execution in case of unxecpected failure during native execution.
type CompilationRequest = (ClassHash, Arc<SierraContractClass>, CompiledClassV1);

/// Manages the global cache of contract classes and handles sierra-to-native compilation requests.
struct ContractClassManager {
// The global cache of contract classes: casm, sierra, and native.
contract_caches: Arc<ContractCaches>,
// The sending half of the compilation request channel.
sender: SyncSender<CompilationRequest>,
}

#[allow(dead_code)]
impl ContractClassManager {
/// Creates a new contract class manager and spawns a thread that listens for compilation
/// requests and processes them (a.k.a. the compilation worker).
/// Returns the contract class manager.
pub fn start(contract_caches: ContractCaches) -> ContractClassManager {
// TODO(Avi, 15/12/2024): Add the size of the channel to the config.
let contract_caches = Arc::new(contract_caches);
let (sender, receiver) = sync_channel(CHANNEL_SIZE);
let compiler_config = SierraToCasmCompilationConfig::default();
let compiler = CommandLineCompiler::new(compiler_config);

std::thread::spawn({
let contract_caches = Arc::clone(&contract_caches);
let compiler = Arc::new(compiler);

move || run_compilation_worker(contract_caches, receiver, compiler)
});

ContractClassManager { contract_caches, sender }
}

/// Sends a compilation request to the compilation worker. Does not block the sender. Logs an
/// error is the channel is full.
pub fn send_compilation_request(&self, request: CompilationRequest) {
self.cache_request_contracts(&request);
// TODO(Avi, 15/12/2024): Check for duplicated requests.
self.sender.try_send(request).unwrap_or_else(|err| match err {
TrySendError::Full((class_hash, _, _)) => {
error!(
"Compilation request channel is full (size: {}). Compilation request for \
class hash {} was not sent.",
CHANNEL_SIZE, class_hash
)
}
TrySendError::Disconnected(_) => {
panic!("Compilation request channel is closed.")
}
});
}

/// Returns the native compiled class for the given class hash, if it exists in cache.
pub fn get_native(&self, class_hash: &ClassHash) -> Option<CachedCairoNative> {
self.contract_caches.get_native(class_hash)
}

/// Returns the Sierra contract class for the given class hash, if it exists in cache.
pub fn get_sierra(&self, class_hash: &ClassHash) -> Option<Arc<SierraContractClass>> {
self.contract_caches.get_sierra(class_hash)
}

/// Returns the casm compiled class for the given class hash, if it exists in cache.
pub fn get_casm(&self, class_hash: &ClassHash) -> Option<RunnableCompiledClass> {
self.contract_caches.get_casm(class_hash)
}

/// Caches the sierra and casm contract classes of a compilation request.
fn cache_request_contracts(&self, request: &CompilationRequest) {
let (class_hash, sierra, casm) = request.clone();
self.contract_caches.set_sierra(class_hash, sierra);
let cached_casm = RunnableCompiledClass::from(casm);
self.contract_caches.set_casm(class_hash, cached_casm);
}
}

/// Handles compilation requests from the channel, holding the receiver end of the channel.
/// If no request is available, non-busy-waits until a request is available.
/// When the sender is dropped, the worker processes all pending requests and terminates.
fn run_compilation_worker(
contract_caches: Arc<ContractCaches>,
receiver: Receiver<CompilationRequest>,
compiler: Arc<dyn SierraToNativeCompiler>,
) {
info!("Compilation worker started.");
for (class_hash, sierra, casm) in receiver.iter() {
if contract_caches.get_native(&class_hash).is_some() {
// The contract class is already compiled to native - skip the compilation.
continue;
}
let sierra_for_compilation = into_contract_class_for_compilation(sierra.as_ref());
let compilation_result = compiler.compile_to_native(sierra_for_compilation);
match compilation_result {
Ok(executor) => {
let native_compiled_class = NativeCompiledClassV1::new(executor, casm);
contract_caches
.set_native(class_hash, CachedCairoNative::Compiled(native_compiled_class));
}
Err(err) => {
error!("Error compiling contract class: {}", err);
contract_caches.set_native(class_hash, CachedCairoNative::CompilationFailed);
}
}
}
info!("Compilation worker terminated.");
}
28 changes: 14 additions & 14 deletions crates/blockifier/src/state/global_cache.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
use std::sync::{Arc, Mutex, MutexGuard};

use cached::{Cached, SizedCache};
#[cfg(feature = "cairo_native")]
use cairo_native::executor::AotContractExecutor;
use starknet_api::core::ClassHash;
#[cfg(feature = "cairo_native")]
use starknet_api::state::SierraContractClass;

#[cfg(feature = "cairo_native")]
use crate::execution::contract_class::RunnableCompiledClass;
#[cfg(feature = "cairo_native")]
use crate::execution::native::contract_class::NativeCompiledClassV1;

type ContractClassLRUCache<T> = SizedCache<ClassHash, T>;
pub type LockedContractClassCache<'a, T> = MutexGuard<'a, ContractClassLRUCache<T>>;
type ContractLRUCache<T> = SizedCache<ClassHash, T>;
pub type LockedClassCache<'a, T> = MutexGuard<'a, ContractLRUCache<T>>;
#[derive(Debug, Clone)]
// Thread-safe LRU cache for contract classes, optimized for inter-language sharing when
// `blockifier` compiles as a shared library.
// Thread-safe LRU cache for contract classes (Seirra or compiled Casm/Native), optimized for
// inter-language sharing when `blockifier` compiles as a shared library.
// TODO(Yoni, 1/1/2025): consider defining CachedStateReader.
pub struct GlobalContractCache<T: Clone>(pub Arc<Mutex<ContractClassLRUCache<T>>>);
pub struct GlobalContractCache<T: Clone>(pub Arc<Mutex<ContractLRUCache<T>>>);

#[cfg(feature = "cairo_native")]
#[derive(Debug, Clone)]
pub enum CachedCairoNative {
Compiled(AotContractExecutor),
Compiled(NativeCompiledClassV1),
CompilationFailed,
}

Expand All @@ -30,7 +30,7 @@ pub const GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST: usize = 400;
impl<T: Clone> GlobalContractCache<T> {
/// Locks the cache for atomic access. Although conceptually shared, writing to this cache is
/// only possible for one writer at a time.
pub fn lock(&self) -> LockedContractClassCache<'_, T> {
pub fn lock(&self) -> LockedClassCache<'_, T> {
self.0.lock().expect("Global contract cache is poisoned.")
}

Expand All @@ -47,25 +47,25 @@ impl<T: Clone> GlobalContractCache<T> {
}

pub fn new(cache_size: usize) -> Self {
Self(Arc::new(Mutex::new(ContractClassLRUCache::<T>::with_size(cache_size))))
Self(Arc::new(Mutex::new(ContractLRUCache::<T>::with_size(cache_size))))
}
}

#[cfg(feature = "cairo_native")]
pub struct GlobalContractCacheManager {
pub struct ContractCaches {
pub casm_cache: GlobalContractCache<RunnableCompiledClass>,
pub native_cache: GlobalContractCache<CachedCairoNative>,
pub sierra_cache: GlobalContractCache<Arc<SierraContractClass>>,
}

#[cfg(feature = "cairo_native")]
impl GlobalContractCacheManager {
impl ContractCaches {
pub fn get_casm(&self, class_hash: &ClassHash) -> Option<RunnableCompiledClass> {
self.casm_cache.get(class_hash)
}

pub fn set_casm(&self, class_hash: ClassHash, contract_class: RunnableCompiledClass) {
self.casm_cache.set(class_hash, contract_class);
pub fn set_casm(&self, class_hash: ClassHash, compiled_class: RunnableCompiledClass) {
self.casm_cache.set(class_hash, compiled_class);
}

pub fn get_native(&self, class_hash: &ClassHash) -> Option<CachedCairoNative> {
Expand Down
Loading