Skip to content
Draft
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.

376 changes: 188 additions & 188 deletions crates/cairo-lang-runner/src/profiling_test_data/major_test_cases

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/cairo-lang-sierra-generator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ cairo-lang-test-utils = { path = "../cairo-lang-test-utils", version = "=2.18.0"
] }
cairo-lang-utils = { path = "../cairo-lang-utils", version = "=2.18.0", features = ["tracing"] }
itertools = { workspace = true, default-features = true }
num-bigint = { workspace = true, default-features = true }
num-traits = { workspace = true }
rayon.workspace = true
salsa.workspace = true
Expand Down
69 changes: 64 additions & 5 deletions crates/cairo-lang-sierra-generator/src/block_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@
#[path = "block_generator_test.rs"]
mod test;

use cairo_lang_defs::ids::{NamedLanguageElementId, TopLevelLanguageElementId};
use cairo_lang_diagnostics::Maybe;
use cairo_lang_lowering as lowering;
use cairo_lang_lowering::BlockId;
use cairo_lang_lowering::db::LoweringGroup;
use cairo_lang_lowering::ids::LocationId;
use cairo_lang_semantic::items::constant::ConstValueId;
use cairo_lang_sierra as sierra;
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use itertools::{chain, enumerate, zip_eq};
use lowering::analysis::StatementLocation;
use lowering::{MatchArm, VarUsage};
use num_bigint::BigInt;
use sierra::extensions::lib_func::SierraApChange;
use sierra::program;

use crate::block_generator::sierra::ids::ConcreteLibfuncId;
use crate::db::SierraGenGroup;
use crate::db::{EXTERNALLY_PROVIDED_CONST, SierraGenGroup};
use crate::expr_generator_context::{ExprGenerationResult, ExprGeneratorContext};
use crate::lifetime::{DropLocation, SierraGenVar, UseLocation};
use crate::local_variables::MIN_SIZE_FOR_LOCAL_INTO_BOX;
Expand Down Expand Up @@ -331,15 +334,24 @@ fn generate_statement_call_code<'db>(
statement: &lowering::StatementCall<'db>,
statement_location: &StatementLocation,
) -> Maybe<()> {
let db = context.get_db();

// Handle the reserved `__externally_provided_const__` extern function: replace the call with a
// `const_as_immediate` of the value supplied by the installed `ExternalConstProvider`.
if let Some((extern_id, _)) = statement.function.get_extern(db)
&& extern_id.name(db).long(db) == EXTERNALLY_PROVIDED_CONST
{
return generate_externally_provided_const_code(context, statement);
}

// Check if this is a user defined function or a libfunc.
let (body, libfunc_id) =
get_concrete_libfunc_id(context.get_db(), statement.function, statement.with_coupon);
let (body, libfunc_id) = get_concrete_libfunc_id(db, statement.function, statement.with_coupon);
// Checks if the call invalidates ap tracking.
let libfunc_signature = get_libfunc_signature(context.get_db(), &libfunc_id);
let libfunc_signature = get_libfunc_signature(db, &libfunc_id);
let [branch_signature] = &libfunc_signature.branch_signatures[..] else {
panic!(
"Unexpected branches in '{}'.",
DebugReplacer { db: context.get_db() }.replace_libfunc_id(&libfunc_id)
DebugReplacer { db }.replace_libfunc_id(&libfunc_id)
);
};
if matches!(branch_signature.ap_change, SierraApChange::Unknown) {
Expand Down Expand Up @@ -393,6 +405,53 @@ fn generate_statement_call_code<'db>(
Ok(())
}

/// Generates Sierra code for a call to the reserved `__externally_provided_const__` extern
/// function, replacing it with a `const_as_immediate`.
///
/// When an [`crate::db::ExternalConstProvider`] is installed, the value is taken from it (keyed by
/// the full path of the extern function, so distinct declarations in different modules resolve
/// independently) and validated against the declared return type; a provider returning an error
/// fails Sierra generation. When no provider is installed (e.g. generic compilation, profiling or
/// size estimation, which don't use the value), it defaults to a zero constant of the declared
/// type — build flows that need the real value install a provider (e.g. the two-pass class-hash
/// injection in `starknet`'s `compile_path`).
fn generate_externally_provided_const_code<'db>(
context: &mut ExprGeneratorContext<'db, '_>,
statement: &lowering::StatementCall<'db>,
) -> Maybe<()> {
let db = context.get_db();
let [output] = statement.outputs[..] else {
panic!("`{EXTERNALLY_PROVIDED_CONST}` must have exactly one output.");
};
// The declared return type, which the provided value must match.
let return_type = context.get_lowered_variable(output).ty;

let value = match db.external_const_provider() {
Some(provider) => {
let (extern_id, _) = statement.function.get_extern(db).unwrap();
let value = provider(db, &extern_id.full_path(db), return_type)?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume extern_id.full_path will yield the externs' call site?
Don't we want more flexibility here and add some parameters to the method?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would emit the full path of the extern - which is basically the same path as the contract (+ a bit)

// The provider returns a value of an arbitrary type; ensure it matches the declared
// one, as a mismatch would produce a type-incorrect Sierra program.
if value.ty(db)? != return_type {
panic!(
"`{EXTERNALLY_PROVIDED_CONST}` provider returned a value whose type does not \
match the declared return type."
);
}
value
}
None => ConstValueId::from_int(db, return_type, &BigInt::ZERO),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should emit warnings imo

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

impossible to emit diagnotics at this stage - so at most - emit a log warning.

};

let output_var = context.get_sierra_variable(output);
context.push_statement(simple_basic_statement(
const_libfunc_id_by_type(db, value, false),
&[],
&[output_var],
));
Ok(())
}

/// Returns if the variable at the given location should be duplicated.
fn should_dup<'db>(
context: &mut ExprGeneratorContext<'db, '_>,
Expand Down
13 changes: 13 additions & 0 deletions crates/cairo-lang-sierra-generator/src/block_generator_test.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
use std::sync::Arc;

use cairo_lang_debug::DebugWithDb;
use cairo_lang_filesystem::flag::{Flag, FlagsGroup};
use cairo_lang_filesystem::ids::FlagLongId;
use cairo_lang_lowering::db::LoweringGroup;
use cairo_lang_lowering::{self as lowering, LoweringStage, ids};
use cairo_lang_semantic::items::constant::ConstValueId;
use cairo_lang_semantic::test_utils::setup_test_function;
use cairo_lang_test_utils::parse_test_file::TestRunnerResult;
use cairo_lang_utils::Intern;
use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
use lowering::fmt::LoweredFormatter;
use lowering::ids::ConcreteFunctionWithBodyId;
use num_bigint::BigInt;

use super::generate_function_result;
use crate::db::SierraGenGroup;
use crate::expr_generator_context::ExprGeneratorContext;
use crate::lifetime::find_variable_lifetime;
use crate::replace_ids::replace_sierra_ids;
Expand All @@ -28,6 +33,7 @@ cairo_lang_test_utils::test_file_test!(
serialization: "serialization",
early_return: "early_return",
panic: "panic",
externally_provided_const: "externally_provided_const",
},
block_generator_test
);
Expand All @@ -43,6 +49,13 @@ fn block_generator_test(
let add_withdraw_gas_flag_id = FlagLongId(Flag::ADD_WITHDRAW_GAS.into());
db.set_flag(add_withdraw_gas_flag_id, Some(Flag::AddWithdrawGas(false)));

// Install a test provider for the `__externally_provided_const__` extern function. It is inert
// unless that extern is actually called, and resolves every call to the constant `7777` of the
// declared return type.
db.set_external_const_provider(Some(Arc::new(|db, _full_path, ty| {
Ok(ConstValueId::from_int(db, ty, &BigInt::from(7777)))
})));

// Parse code and create semantic model.
let (test_function, semantic_diagnostics) = setup_test_function(db, inputs).split();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! > Test that a call to `__externally_provided_const__` becomes a const_as_immediate.

//! > test_runner_name
block_generator_test

//! > function_code
extern fn __externally_provided_const__() -> felt252 nopanic;

fn foo() -> felt252 {
__externally_provided_const__()
}

//! > function_name
foo

//! > semantic_diagnostics

//! > lowering_diagnostics

//! > sierra_gen_diagnostics

//! > sierra_code
const_as_immediate<Const<felt252, 7777>>() -> ([0])
PushValues([0]: felt252) -> ([0])
return([0])

//! > lowering_flat
Parameters:
blk0 (root):
Statements:
(v0: core::felt252) <- test::__externally_provided_const__()
End:
Return(v0)
50 changes: 49 additions & 1 deletion crates/cairo-lang-sierra-generator/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ use cairo_lang_lowering as lowering;
use cairo_lang_lowering::db::LoweringGroup;
use cairo_lang_lowering::panic::PanicSignatureInfo;
use cairo_lang_semantic as semantic;
use cairo_lang_semantic::items::constant::ConstValueId;
use cairo_lang_sierra::extensions::lib_func::SierraApChange;
use cairo_lang_sierra::extensions::{ConcreteType, GenericTypeEx};
use cairo_lang_sierra::ids::ConcreteTypeId;
use lowering::ids::ConcreteFunctionWithBodyId;
use salsa::plumbing::FromId;
use salsa::{Database, Id};
use salsa::{Database, Id, Setter};

use crate::program_generator::{self, SierraProgramWithDebug};
use crate::replace_ids::SierraIdReplacer;
Expand All @@ -33,6 +34,39 @@ pub enum SierraGeneratorTypeLongId<'db> {
Phantom(semantic::TypeId<'db>),
}

/// The reserved name of the extern function whose calls are replaced, at the Sierra generation
/// stage, by a constant value supplied by the installed [`ExternalConstProvider`]. It may be
/// declared anywhere; it is recognized solely by this name and never reaches Sierra.
pub const EXTERNALLY_PROVIDED_CONST: &str = "__externally_provided_const__";

/// A callback supplying the constant value for a call to the reserved
/// `__externally_provided_const__` extern function.
///
/// It is given the full path of the (concrete) extern function being called and its declared
/// return type, and returns the [`ConstValueId`] that the call should be replaced with at the
/// Sierra generation stage. The provider is responsible for deciding what happens when no value is
/// available for the given path (e.g. returning an error). The returned value's type is validated
/// against the declared return type by the caller.
///
/// Installed on the database by its constructor (e.g. by the build driver); it is not set by
/// default.
pub type ExternalConstProvider = Arc<
dyn for<'db> Fn(&'db dyn Database, &str, semantic::TypeId<'db>) -> Maybe<ConstValueId<'db>>
+ Send
+ Sync,
>;

#[salsa::input]
pub struct SierraGenGroupInput {
#[returns(ref)]
pub external_const_provider: Option<ExternalConstProvider>,
}

#[salsa::tracked]
pub fn sierra_gen_group_input(db: &dyn Database) -> SierraGenGroupInput {
SierraGenGroupInput::new(db, None)
}

/// Wrapper for the concrete libfunc long id, providing a unique id for each libfunc.
#[salsa::interned(revisions = usize::MAX)]
struct ConcreteLibfuncIdLongWrapper {
Expand Down Expand Up @@ -272,6 +306,20 @@ pub trait SierraGenGroup: Database {
program_generator::get_sierra_program(self.as_dyn_database(), (), requested_crate_ids)
.maybe_as_ref()
}

/// Returns the installed [`ExternalConstProvider`], if any.
fn external_const_provider(&self) -> Option<ExternalConstProvider> {
sierra_gen_group_input(self.as_dyn_database())
.external_const_provider(self.as_dyn_database())
.clone()
}

/// Installs the [`ExternalConstProvider`] used to resolve calls to the reserved
/// `__externally_provided_const__` extern function.
fn set_external_const_provider(&mut self, provider: Option<ExternalConstProvider>) {
let input = sierra_gen_group_input(self.as_dyn_database());
input.set_external_const_provider(self).to(provider);
}
}
impl<T: Database + ?Sized> SierraGenGroup for T {}

Expand Down
27 changes: 20 additions & 7 deletions crates/cairo-lang-sierra-generator/src/local_variables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#[path = "local_variables_test.rs"]
mod test;

use cairo_lang_defs::ids::NamedLanguageElementId;
use cairo_lang_diagnostics::Maybe;
use cairo_lang_lowering as lowering;
use cairo_lang_lowering::db::LoweringGroup;
Expand All @@ -23,7 +24,7 @@ use lowering::{Lowered, MatchInfo, Statement, VarRemapping, VarUsage};
use salsa::Database;

use crate::ap_tracking::{ApTrackingConfiguration, get_ap_tracking_configuration};
use crate::db::SierraGenGroup;
use crate::db::{EXTERNALLY_PROVIDED_CONST, SierraGenGroup};
use crate::replace_ids::{DebugReplacer, SierraIdReplacer};
use crate::utils::{
enum_init_libfunc_id, get_concrete_libfunc_id, get_libfunc_signature, match_enum_libfunc_id,
Expand Down Expand Up @@ -390,13 +391,25 @@ impl<'db, 'a> FindLocalsContext<'db, 'a> {
BranchInfo { known_ap_change: true }
}
lowering::Statement::Call(statement_call) => {
let (_, concrete_function_id) = get_concrete_libfunc_id(
self.db,
statement_call.function,
statement_call.with_coupon,
);
// The reserved `__externally_provided_const__` extern is replaced by a
// `const_as_immediate` at code generation, so treat it as a constant here (known
// ap-change, constant output) rather than resolving a (non-existent) libfunc.
if let Some((extern_id, _)) = statement_call.function.get_extern(self.db)
&& extern_id.name(self.db).long(self.db) == EXTERNALLY_PROVIDED_CONST
{
if let [output] = statement_call.outputs[..] {
self.constants.insert(output);
}
BranchInfo { known_ap_change: true }
} else {
let (_, concrete_function_id) = get_concrete_libfunc_id(
self.db,
statement_call.function,
statement_call.with_coupon,
);

self.analyze_call(concrete_function_id, inputs, outputs)
self.analyze_call(concrete_function_id, inputs, outputs)
}
}
lowering::Statement::StructConstruct(statement_struct_construct) => {
let ty = self.db.get_concrete_type_id(
Expand Down
Loading