Skip to content

blockifier: add with-libfunc-profiling feature flag#13755

Open
avi-starkware wants to merge 1 commit intoavi/blockifier/benchmarking-instrumentationfrom
avi/blockifier/libfunc-profiling
Open

blockifier: add with-libfunc-profiling feature flag#13755
avi-starkware wants to merge 1 commit intoavi/blockifier/benchmarking-instrumentationfrom
avi/blockifier/libfunc-profiling

Conversation

@avi-starkware
Copy link
Copy Markdown
Collaborator

Adds the with-libfunc-profiling feature flag for execution profiling
support. When enabled, introduces an AotWithProgram variant on
ContractExecutor that collects per-entrypoint libfunc profiling data
into a global map keyed by transaction hash.

Zero behavior change without the flag enabled.

@reviewable-StarkWare
Copy link
Copy Markdown

This change is Reviewable

Copy link
Copy Markdown
Collaborator Author

avi-starkware commented Apr 14, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions
Copy link
Copy Markdown

Artifacts upload workflows:

@avi-starkware avi-starkware marked this pull request as ready for review April 14, 2026 11:01
@cursor
Copy link
Copy Markdown

cursor bot commented Apr 14, 2026

PR Summary

Medium Risk
Low impact by default (fully feature-gated), but when enabled it adds unsafe symbol-pointer manipulation and global, mutex-protected profiling state that could affect correctness/perf under concurrency.

Overview
Adds a new with-libfunc-profiling Cargo feature (and optional cairo-lang-sierra dependency) to enable libfunc-level profiling when executing contracts via Cairo native.

When enabled, native entrypoint execution conditionally routes through a new profiling::run_profiled wrapper that instruments AotContractExecutor::run, extracts per-libfunc profile data, and stores it in a global LIBFUNC_PROFILES_MAP keyed by transaction hash; NativeCompiledClassV1Inner gains an optional Sierra program handle used to decode profiles.

Reviewed by Cursor Bugbot for commit 545e953. Bugbot is set up for automated code reviews on this repo. Configure here.

@avi-starkware avi-starkware force-pushed the avi/blockifier/libfunc-profiling branch from 6a272ae to ceacfac Compare April 14, 2026 11:06
};

libfunc_profiling_old_trace_id = *libfunc_profiling_trace_id;
*libfunc_profiling_trace_id = counter;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unsafe mutable aliasing enables data race on trace ID

High Severity

The run method takes &self, and the ContractExecutor is shared across threads via Arc in NativeCompiledClassV1. The unsafe block creates a &mut u64 reference to the executor's trace ID symbol pointer. Since nothing prevents concurrent calls to run on the same executor, two threads can simultaneously obtain &mut u64 references to the same memory — a data race and undefined behavior. Additionally, the save/restore pattern for the old trace ID is not atomic, so interleaved concurrent calls corrupt each other's trace IDs.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ceacfac. Configure here.

}
};

*libfunc_profiling_trace_id = libfunc_profiling_old_trace_id;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Trace ID not restored if unwrap panics

Medium Severity

After setting the trace ID at the unsafe pointer on line 100, the restoration on line 131 is not panic-safe. Several unwrap() calls between these lines (on LIBFUNC_PROFILE.lock(), .remove(), and LIBFUNC_PROFILES_MAP.lock()) can panic — particularly if a mutex becomes poisoned from a prior panic. If any panics, the trace ID is never restored, leaving the executor's symbol in a stale state. An RAII guard or scopeguard pattern would ensure the restore happens on unwind.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ceacfac. Configure here.

@avi-starkware avi-starkware force-pushed the avi/blockifier/libfunc-profiling branch from ceacfac to eab2995 Compare April 14, 2026 11:33
selector,
profile: raw_profile,
program: program.clone(),
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Entire Sierra program cloned per entrypoint execution

Medium Severity

Each EntrypointProfile stores a full program.clone() of the Sierra program. Since program comes from the AotWithProgram variant and is the same for every call to the same contract, repeatedly cloning a potentially large Sierra program for every single entrypoint execution causes significant unnecessary memory consumption. For a contract called N times, N full copies of the program are stored in LIBFUNC_PROFILES_MAP.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eab2995. Configure here.

Copy link
Copy Markdown
Contributor

@TomerStarkware TomerStarkware left a comment

Choose a reason for hiding this comment

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

:lgtm:

@TomerStarkware partially reviewed 3 files and all commit messages, and made 1 comment.
Reviewable status: 2 of 3 files reviewed, 3 unresolved discussions (waiting on avi-starkware, noaov1, and Yoni-Starkware).

@avi-starkware avi-starkware changed the base branch from avi/blockifier/contract-executor-abstraction to graphite-base/13755 April 14, 2026 12:36
@avi-starkware avi-starkware force-pushed the avi/blockifier/libfunc-profiling branch from eab2995 to 71c0167 Compare April 14, 2026 12:37
@avi-starkware avi-starkware changed the base branch from graphite-base/13755 to avi/blockifier/benchmarking-instrumentation April 14, 2026 12:37
};

let old_trace_id = *trace_id;
*trace_id = counter;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unsafe data race on shared executor's trace_id symbol

High Severity

The trace_id raw pointer targets a global symbol in the executor's shared library. Since AotContractExecutor is shared across threads via Arc<NativeCompiledClassV1Inner>, concurrent calls to run_profiled for the same contract class race on reading/writing trace_id without synchronization. The save-modify-run-restore pattern on lines 66–67 and 93 is inherently non-atomic, causing undefined behavior and incorrect profile attribution when two threads interleave.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 71c0167. Configure here.

}
};

*trace_id = old_trace_id;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing panic safety for trace_id and profiler cleanup

Medium Severity

If executor.run() on line 69 panics (or unwinds), the cleanup code on lines 71–93 never executes. The LIBFUNC_PROFILE entry for counter leaks permanently, trace_id remains set to counter instead of being restored to old_trace_id, and the LIBFUNC_PROFILES_MAP lock is never updated. A RAII guard pattern would ensure cleanup runs even during unwinding.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 71c0167. Configure here.

type ProfilesByBlockTx = HashMap<String, TransactionProfile>;

pub static LIBFUNC_PROFILES_MAP: LazyLock<Mutex<ProfilesByBlockTx>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unbounded growth of global LIBFUNC_PROFILES_MAP without cleanup

Medium Severity

LIBFUNC_PROFILES_MAP is a global Mutex<HashMap> that accumulates an EntrypointProfile (which includes a cloned Program) for every profiled entrypoint call, keyed by transaction hash. No code in the codebase ever reads from or drains this map. In a long-running node with profiling enabled, this grows without bound, leaking memory proportional to the number of executed transactions.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 71c0167. Configure here.

#[cfg(feature = "with-libfunc-profiling")]
program: None,
casm,
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Program field always None, profiling path never executes

High Severity

The program field on NativeCompiledClassV1Inner is always initialized to None in the constructor, and no code anywhere in the codebase ever sets it to Some(...). In entry_point_execution.rs, the profiling branch matches on &compiled_class.program — since it's always None, the run_profiled path is dead code and profiling never activates, even with the with-libfunc-profiling feature flag enabled. The entire feature is non-functional.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 71c0167. Configure here.

@avi-starkware avi-starkware force-pushed the avi/blockifier/benchmarking-instrumentation branch from 7293ba2 to c9f42dd Compare April 14, 2026 12:42
@avi-starkware avi-starkware force-pushed the avi/blockifier/libfunc-profiling branch from 71c0167 to 0381287 Compare April 14, 2026 12:42
Copy link
Copy Markdown
Collaborator

@Yoni-Starkware Yoni-Starkware left a comment

Choose a reason for hiding this comment

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

:lgtm:

@Yoni-Starkware reviewed 7 files and all commit messages, and made 1 comment.
Reviewable status: all files reviewed, 7 unresolved discussions (waiting on avi-starkware and noaov1).

Adds the `with-libfunc-profiling` feature flag for execution profiling
support. When enabled, wraps AotContractExecutor::run() to collect
per-entrypoint libfunc profiling data into a global map keyed by
transaction hash.

Stores an optional Sierra program alongside the executor in
NativeCompiledClassV1Inner to enable profiling instrumentation.

Zero behavior change without the flag enabled.
@avi-starkware avi-starkware force-pushed the avi/blockifier/libfunc-profiling branch from 0381287 to 545e953 Compare April 19, 2026 09:29
@avi-starkware avi-starkware force-pushed the avi/blockifier/benchmarking-instrumentation branch from c9f42dd to bfe0a84 Compare April 19, 2026 09:29
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 8 total unresolved issues (including 7 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 545e953. Configure here.

let mut profiles_map = LIBFUNC_PROFILES_MAP.lock().unwrap();

let profile =
EntrypointProfile { class_hash, selector, profile: raw_profile, program: program.clone() };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Full Sierra Program cloned per entrypoint invocation

Medium Severity

EntrypointProfile stores a full owned program: cairo_lang_sierra::program::Program and each profiled entry point call does program.clone(). A Sierra program can be very large (thousands of type/libfunc declarations and statements). When the same contract is called many times, this creates many redundant deep copies. Storing an Arc<Program> instead would avoid the clones entirely.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 545e953. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants