From cf8efaa583c20a1c44e1791f197393afaa2934dd Mon Sep 17 00:00:00 2001 From: Hakeem Kazeem Date: Sat, 10 May 2025 11:15:45 +0100 Subject: [PATCH 01/28] Review TODO comments --- src/arch.rs | 8 ++++---- src/bin/cairo-native-stress/main.rs | 2 +- src/executor.rs | 2 +- src/executor/contract.rs | 2 +- src/libfuncs/bytes31.rs | 2 +- src/libfuncs/felt252.rs | 8 ++++---- src/starknet.rs | 4 ++-- src/types/enum.rs | 4 ++-- src/types/starknet.rs | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/arch.rs b/src/arch.rs index 15f84a65c..4affc94e2 100644 --- a/src/arch.rs +++ b/src/arch.rs @@ -114,7 +114,7 @@ impl AbiArgument for ValueWithInfoWrapper<'_> { .to_bytes(buffer, find_dict_drop_override)?, (Value::Array(_), CoreTypeConcrete::Array(_)) => { - // TODO: Assert that `info.ty` matches all the values' types. + // TODO: Assert that `info.ty` matches all the values' types. See: https://github.com/lambdaclass/cairo_native/issues/1216#issue-3052795891 let abi_ptr = self.value.to_ptr( self.arena, @@ -130,7 +130,7 @@ impl AbiArgument for ValueWithInfoWrapper<'_> { abi.capacity.to_bytes(buffer, find_dict_drop_override)?; } (Value::BoundedInt { .. }, CoreTypeConcrete::BoundedInt(_)) => { - native_panic!("todo: implement AbiArgument for Value::BoundedInt case") + native_panic!("todo: implement AbiArgument for Value::BoundedInt case") // See: https://github.com/lambdaclass/cairo_native/issues/1217#issue-3052805863 } (Value::Bytes31(value), CoreTypeConcrete::Bytes31(_)) => { value.to_bytes(buffer, find_dict_drop_override)? @@ -183,7 +183,7 @@ impl AbiArgument for ValueWithInfoWrapper<'_> { ), ) => value.to_bytes(buffer, find_dict_drop_override)?, (Value::Felt252Dict { .. }, CoreTypeConcrete::Felt252Dict(_)) => { - // TODO: Assert that `info.ty` matches all the values' types. + // TODO: Assert that `info.ty` matches all the values' types. See: https://github.com/lambdaclass/cairo_native/issues/1216#issue-3052795891 self.value .to_ptr( @@ -249,7 +249,7 @@ impl AbiArgument for ValueWithInfoWrapper<'_> { value.to_bytes(buffer, find_dict_drop_override)? } _ => native_panic!( - "todo: abi argument unimplemented for ({:?}, {:?})", + "todo: abi argument unimplemented for ({:?}, {:?})", // See: https://github.com/lambdaclass/cairo_native/issues/1218#issue-3052814195 self.value, self.type_id ), diff --git a/src/bin/cairo-native-stress/main.rs b/src/bin/cairo-native-stress/main.rs index 167ef1869..d10453391 100644 --- a/src/bin/cairo-native-stress/main.rs +++ b/src/bin/cairo-native-stress/main.rs @@ -103,7 +103,7 @@ fn main() { let before_round = Instant::now(); let program = modify_starknet_contract(program.clone(), UNIQUE_CONTRACT_VALUE, round); - // TODO: use the program hash instead of round number. + // TODO: use the program hash instead of round number. See: https://github.com/lambdaclass/cairo_native/issues/1215#issue-3052756022 let hash = round; debug!(hash, "obtained test program"); diff --git a/src/executor.rs b/src/executor.rs index 35f79fd96..f5fcad8c3 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -265,7 +265,7 @@ fn invoke_dynamic( _ if type_info.is_builtin() => { if !type_info.is_zst(registry)? { if let CoreTypeConcrete::BuiltinCosts(_) = type_info { - // todo: should we use this value? + // todo: should we use this value? See: https://github.com/lambdaclass/cairo_native/issues/1219#issue-3052919995 let _value = match &mut return_ptr { Some(return_ptr) => unsafe { *read_value::<*mut u64>(return_ptr) }, None => ret_registers[0] as *mut u64, diff --git a/src/executor/contract.rs b/src/executor/contract.rs index 465972535..72af2cdf2 100644 --- a/src/executor/contract.rs +++ b/src/executor/contract.rs @@ -681,7 +681,7 @@ mod tests { use rayon::iter::ParallelBridge; use rstest::*; - // todo add recursive contract test + // todo add recursive contract test See: https://github.com/lambdaclass/cairo_native/issues/1220#issue-3053012331 #[fixture] fn starknet_program() -> ContractClass { diff --git a/src/libfuncs/bytes31.rs b/src/libfuncs/bytes31.rs index 66006c87a..8c3b8cd34 100644 --- a/src/libfuncs/bytes31.rs +++ b/src/libfuncs/bytes31.rs @@ -170,7 +170,7 @@ mod test { use starknet_types_core::felt::Felt; lazy_static! { - // TODO: Test `bytes31_const` once the compiler supports it. + // TODO: Test `bytes31_const` once the compiler supports it. See: https://github.com/lambdaclass/cairo_native/issues/1224#issue-3053717697 static ref BYTES31_ROUNDTRIP: (String, Program) = load_cairo! { use core::bytes_31::{bytes31_try_from_felt252, bytes31_to_felt252}; diff --git a/src/libfuncs/felt252.rs b/src/libfuncs/felt252.rs index 65f079bdc..d52b34c3f 100644 --- a/src/libfuncs/felt252.rs +++ b/src/libfuncs/felt252.rs @@ -364,10 +364,10 @@ pub mod test { } }; - // TODO: Add test program for `felt252_add_const`. - // TODO: Add test program for `felt252_sub_const`. - // TODO: Add test program for `felt252_mul_const`. - // TODO: Add test program for `felt252_div_const`. + // TODO: Add test program for `felt252_add_const`. See: https://github.com/lambdaclass/cairo_native/issues/1214#issue-3052727376 + // TODO: Add test program for `felt252_sub_const`. See: https://github.com/lambdaclass/cairo_native/issues/1214#issue-3052727376 + // TODO: Add test program for `felt252_mul_const`. See: https://github.com/lambdaclass/cairo_native/issues/1214#issue-3052727376 + // TODO: Add test program for `felt252_div_const`. See: https://github.com/lambdaclass/cairo_native/issues/1214#issue-3052727376 static ref FELT252_CONST: (String, Program) = load_cairo! { extern fn felt252_const() -> felt252 nopanic; diff --git a/src/starknet.rs b/src/starknet.rs index dfc4e42cc..a76e31880 100644 --- a/src/starknet.rs +++ b/src/starknet.rs @@ -53,7 +53,7 @@ impl From<&Felt252Abi> for Felt { } /// Binary representation of a `u256` (in MLIR). -// TODO: This shouldn't need to be public. +// TODO: This shouldn't need to be public. See: https://github.com/lambdaclass/cairo_native/issues/1221#issue-3053023949 #[derive( Debug, Clone, @@ -557,7 +557,7 @@ impl StarknetSyscallHandler for DummySyscallHandler { } } -// TODO: Move to the correct place or remove if unused. +// TODO: Move to the correct place or remove if unused. See: https://github.com/lambdaclass/cairo_native/issues/1222#issue-3053029798 pub(crate) mod handler { use super::*; use crate::utils::{libc_free, libc_malloc}; diff --git a/src/types/enum.rs b/src/types/enum.rs index 213df2e8e..a3e102e92 100644 --- a/src/types/enum.rs +++ b/src/types/enum.rs @@ -760,8 +760,8 @@ pub fn get_layout_for_variants( /// Extract the type and layout for the default enum representation, its discriminant and all its /// payloads. -// TODO: Change this function to accept a slice of slices (for variants). Not all uses have a slice -// with one `ConcreteTypeId` per variant (deploy_syscalls has two types for the Ok() variant). +// TODO: Change this function to accept a slice of slices (for variants). Not all uses have a slice See: https://github.com/lambdaclass/cairo_native/issues/1225#issue-3053744788 +// with one `ConcreteTypeId` per variant (deploy_syscalls has two types for the Ok() variant). pub fn get_type_for_variants<'ctx>( context: &'ctx Context, module: &Module<'ctx>, diff --git a/src/types/starknet.rs b/src/types/starknet.rs index c99a47363..7995b7267 100644 --- a/src/types/starknet.rs +++ b/src/types/starknet.rs @@ -19,7 +19,7 @@ //! ## Secp256Point //! TODO -// TODO: Maybe the types used here can be i251 instead of i252. +// TODO: Maybe the types used here can be i251 instead of i252. See https://github.com/lambdaclass/cairo_native/issues/1226#issue-3053977191 use super::WithSelf; use crate::{ From 53d911cb3e548589caff1884273491b2d1087a68 Mon Sep 17 00:00:00 2001 From: Julian Gonzalez Calderon Date: Tue, 6 May 2025 12:24:19 -0300 Subject: [PATCH 02/28] Minor improvements to Sierra Emu crate (#1202) * Move cairo-lang dependencies to workspace * Remove sierra-emu/.gitignore * Update lock * Add fibonacci.cairo example for sierra emulator * Simplify Makefile * Simplify README and improve documentation on how to use the binary * Add make check * Improve examples * Document make check * Remove unused script * Add fibonacci_contract.cairo * Fix typo in Makefile * Fix CI * Use cairo2/corelib instead of corelib for symlinking in sierra emu * Improve Sierra Emulator descritopn * Move more dependencies to workspace * Fix whitespace * Fix whitespace (again) --------- Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> --- .github/workflows/ci.yml | 8 +- Cargo.toml | 62 ++++++---- debug_utils/sierra-emu/Cargo.toml | 36 +++--- debug_utils/sierra-emu/Makefile | 72 +++--------- debug_utils/sierra-emu/README.md | 108 ++++-------------- debug_utils/sierra-emu/examples/contract.rs | 56 +++++++++ debug_utils/sierra-emu/examples/program.rs | 45 ++++++++ .../sierra-emu/programs/fibonacci.cairo | 14 +++ .../programs/fibonacci_contract.cairo | 17 +++ .../scripts/check-corelib-version.sh | 10 -- 10 files changed, 231 insertions(+), 197 deletions(-) create mode 100644 debug_utils/sierra-emu/examples/contract.rs create mode 100644 debug_utils/sierra-emu/examples/program.rs create mode 100644 debug_utils/sierra-emu/programs/fibonacci.cairo create mode 100644 debug_utils/sierra-emu/programs/fibonacci_contract.cairo delete mode 100755 debug_utils/sierra-emu/scripts/check-corelib-version.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7fd1ec483..bb18171b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -350,9 +350,11 @@ jobs: - uses: actions/checkout@v4 - name: Retreive cached dependecies uses: Swatinem/rust-cache@v2 - - name: Deps - working-directory: ./debug_utils/sierra-emu - run: make deps + - name: Install dependencies + run: | + make build-cairo-2-compiler + cd ./debug_utils/sierra-emu + make corelib - name: Build working-directory: ./debug_utils/sierra-emu run: cargo build --all-features --verbose diff --git a/Cargo.toml b/Cargo.toml index c4501d9ce..2b5a631f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,14 +63,14 @@ normal = ["aquamarine"] [dependencies] aquamarine = "0.6.0" bumpalo = "3.16.0" -cairo-lang-compiler = "=2.12.0-dev.1" -cairo-lang-defs = "=2.12.0-dev.1" -cairo-lang-filesystem = "=2.12.0-dev.1" -cairo-lang-runner = "=2.12.0-dev.1" -cairo-lang-semantic = "=2.12.0-dev.1" -cairo-lang-sierra = "=2.12.0-dev.1" -cairo-lang-sierra-generator = "=2.12.0-dev.1" -cairo-lang-sierra-to-casm = "=2.12.0-dev.1" +cairo-lang-compiler.workspace = true +cairo-lang-defs.workspace = true +cairo-lang-filesystem.workspace = true +cairo-lang-runner.workspace = true +cairo-lang-semantic.workspace = true +cairo-lang-sierra.workspace = true +cairo-lang-sierra-generator.workspace = true +cairo-lang-sierra-to-casm.workspace = true educe = "0.5.11" # can't update until https://github.com/magiclen/educe/issues/27 itertools = "0.14.0" lazy_static = "1.5" @@ -78,9 +78,9 @@ libc = "0.2" llvm-sys = "191.0.0" melior = { version = "0.21.0", features = ["ods-dialects", "helpers"] } mlir-sys = { version = "0.4.1" } -num-bigint = "0.4.6" -num-traits = "0.2" -starknet-types-core = { version = "0.1.7", default-features = false, features = [ +num-bigint.workspace = true +num-traits.workspace = true +starknet-types-core = { workspace = true, default-features = false, features = [ "std", "serde", "num-traits", @@ -92,11 +92,11 @@ utf8_iter = "1.0.4" # CLI dependencies -cairo-lang-sierra-ap-change = "=2.12.0-dev.1" -cairo-lang-sierra-gas = "=2.12.0-dev.1" -cairo-lang-starknet = "=2.12.0-dev.1" -cairo-lang-utils = "=2.12.0-dev.1" -cairo-lang-starknet-classes = "=2.12.0-dev.1" +cairo-lang-sierra-ap-change.workspace = true +cairo-lang-sierra-gas.workspace = true +cairo-lang-starknet.workspace = true +cairo-lang-utils.workspace = true +cairo-lang-starknet-classes.workspace = true clap = { version = "4.5.23", features = ["derive"], optional = true } libloading = "0.8.6" tracing-subscriber = { version = "0.3.19", features = [ @@ -106,7 +106,7 @@ tracing-subscriber = { version = "0.3.19", features = [ ], optional = true } serde = { version = "1.0", features = ["derive"] } anyhow = { version = "1.0", optional = true } -cairo-lang-test-plugin = { version = "=2.12.0-dev.1", optional = true } +cairo-lang-test-plugin = { workspace = true, optional = true } colored = { version = "2.1.0", optional = true } # needed to interface with cairo-lang-* keccak = "0.1.5" @@ -121,15 +121,15 @@ ark-secp256k1 = "0.5.0" ark-secp256r1 = "0.5.0" ark-ec = "0.5.0" ark-ff = "0.5.0" -num-integer = "0.1.46" +num-integer.workspace = true # Runtime functions rand = "0.9.0" -starknet-curve = "0.5.1" +starknet-curve.workspace = true [dev-dependencies] cairo-vm = { git = "https://github.com/lambdaclass/cairo-vm", rev = "368e3fb311601a33ff8945e784eaa332f1fd499a", features = ["cairo-1-hints"] } -cairo-lang-semantic = { version = "=2.12.0-dev.1", features = ["testing"] } +cairo-lang-semantic = { workspace = true, features = ["testing"] } criterion = { version = "0.5.1", features = ["html_reports"] } lambdaworks-math = "0.11.0" pretty_assertions_sorted = "1.2.3" @@ -178,3 +178,25 @@ harness = false [workspace] members = ["debug_utils/sierra-emu", "debug_utils/sierra2casm-dbg"] + +[workspace.dependencies] +cairo-lang-compiler = "=2.12.0-dev.1" +cairo-lang-defs = "=2.12.0-dev.1" +cairo-lang-filesystem = "=2.12.0-dev.1" +cairo-lang-runner = "=2.12.0-dev.1" +cairo-lang-semantic = "=2.12.0-dev.1" +cairo-lang-sierra = "=2.12.0-dev.1" +cairo-lang-sierra-ap-change = "=2.12.0-dev.1" +cairo-lang-sierra-gas = "=2.12.0-dev.1" +cairo-lang-sierra-generator = "=2.12.0-dev.1" +cairo-lang-sierra-to-casm = "=2.12.0-dev.1" +cairo-lang-starknet = "=2.12.0-dev.1" +cairo-lang-starknet-classes = "=2.12.0-dev.1" +cairo-lang-test-plugin = "=2.12.0-dev.1" +cairo-lang-utils = "=2.12.0-dev.1" +starknet-crypto = "0.7.3" +starknet-curve = "0.5.1" +starknet-types-core = { version = "0.1.7", default-features = false } +num-bigint = "0.4.6" +num-integer = "0.1.46" +num-traits = "0.2" diff --git a/debug_utils/sierra-emu/Cargo.toml b/debug_utils/sierra-emu/Cargo.toml index 71face6be..fa8034215 100644 --- a/debug_utils/sierra-emu/Cargo.toml +++ b/debug_utils/sierra-emu/Cargo.toml @@ -9,22 +9,22 @@ readme = "README.md" keywords = ["starknet", "cairo", "compiler", "mlir"] [dependencies] -cairo-lang-compiler = "=2.12.0-dev.1" -cairo-lang-filesystem = "=2.12.0-dev.1" -cairo-lang-runner = "=2.12.0-dev.1" -cairo-lang-sierra = "=2.12.0-dev.1" -cairo-lang-sierra-generator = "=2.12.0-dev.1" -cairo-lang-sierra-to-casm = "=2.12.0-dev.1" -cairo-lang-sierra-ap-change = "=2.12.0-dev.1" -cairo-lang-sierra-gas = "=2.12.0-dev.1" -cairo-lang-starknet-classes = "=2.12.0-dev.1" -cairo-lang-utils = "=2.12.0-dev.1" -cairo-lang-test-plugin = "=2.12.0-dev.1" +cairo-lang-compiler.workspace = true +cairo-lang-filesystem.workspace = true +cairo-lang-runner.workspace = true +cairo-lang-sierra-ap-change.workspace = true +cairo-lang-sierra-gas.workspace = true +cairo-lang-sierra-generator.workspace = true +cairo-lang-sierra-to-casm.workspace = true +cairo-lang-sierra.workspace = true +cairo-lang-starknet-classes.workspace = true +cairo-lang-test-plugin.workspace = true +cairo-lang-utils.workspace = true clap = { version = "4.5.26", features = ["derive"] } k256 = "0.13.4" keccak = "0.1.5" -num-bigint = "0.4.6" -num-traits = "0.2.19" +num-bigint.workspace = true +num-traits.workspace = true p256 = "0.13.2" rand = "0.8.5" sec1 = { version = "0.7.3", features = ["std"] } @@ -32,9 +32,9 @@ serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.133" sha2 = { version = "0.10.8", features = ["compress"] } smallvec = "1.13.2" -starknet-crypto = "0.7.3" -starknet-curve = "0.5.1" -starknet-types-core = "0.1.7" +starknet-crypto.workspace = true +starknet-curve.workspace = true +starknet-types-core.workspace = true tempfile = "3.14.0" thiserror = "2.0.3" tracing = "0.1.41" @@ -42,5 +42,5 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } rayon = "1.10.0" [dev-dependencies] -cairo-lang-compiler = "=2.12.0-dev.1" -cairo-lang-starknet = "=2.12.0-dev.1" +cairo-lang-compiler.workspace = true +cairo-lang-starknet.workspace = true diff --git a/debug_utils/sierra-emu/Makefile b/debug_utils/sierra-emu/Makefile index 436fbeeb9..c064f7a20 100644 --- a/debug_utils/sierra-emu/Makefile +++ b/debug_utils/sierra-emu/Makefile @@ -1,79 +1,33 @@ -.PHONY: usage deps build check needs-cairo2 deps-macos build-cairo-2-compiler-macos decompress-cairo install-scarb clean - -UNAME := $(shell uname) - -CAIRO_2_VERSION= 2.12.0-dev.1 -SCARB_VERSION = 2.11.2 - -needs-cairo2: -ifeq ($(wildcard ./cairo2/.),) - $(error You are missing the Starknet Cairo 1 compiler, please run 'make deps' to install the necessary dependencies.) -endif - ./scripts/check-corelib-version.sh $(CAIRO_2_VERSION) - usage: @echo "Usage:" - @echo " deps: Installs the necesarry dependencies." - @echo " build: Builds the cairo-native library and binaries." + @echo " corelib: Install the corelib." + @echo " build: Builds the sierra emu." @echo " check: Checks format and lints." @echo " test: Runs all tests." @echo " test-no-corelib: Runs all tests except for the ones from the cairo's corelib." @echo " clean: Cleans the built artifacts." +corelib: ../../cairo2/corelib + ln -s ../../cairo2/corelib corelib + build: cargo build --release --all-features +.PHONY: build check: cargo fmt --all -- --check cargo clippy --all-targets --all-features -- -D warnings +.PHONY: check -test: needs-cairo2 +test: corelib cargo test --all-features +.PHONY: test -test-no-corelib: needs-cairo2 +test-no-corelib: corelib cargo test --all-features -- --skip test_corelib +.PHONY: test-no-corelib clean: + rm corelib cargo clean - -deps: -ifeq ($(UNAME), Linux) -deps: build-cairo-2-compiler install-scarb -endif -ifeq ($(UNAME), Darwin) -deps: deps-macos -endif - -rm -rf corelib - -ln -s cairo2/corelib corelib - -deps-macos: build-cairo-2-compiler-macos install-scarb-macos - -cairo-repo-2-dir = cairo2 -cairo-repo-2-dir-macos = cairo2-macos - -build-cairo-2-compiler-macos: | $(cairo-repo-2-dir-macos) - -$(cairo-repo-2-dir-macos): cairo-${CAIRO_2_VERSION}-macos.tar - $(MAKE) decompress-cairo SOURCE=$< TARGET=cairo2/ - -build-cairo-2-compiler: | $(cairo-repo-2-dir) - -$(cairo-repo-2-dir): cairo-${CAIRO_2_VERSION}.tar - $(MAKE) decompress-cairo SOURCE=$< TARGET=cairo2/ - -decompress-cairo: - rm -rf $(TARGET) \ - && tar -xzvf $(SOURCE) \ - && mv cairo/ $(TARGET) - -cairo-%-macos.tar: - curl -L -o "$@" "https://github.com/starkware-libs/cairo/releases/download/v$*/release-aarch64-apple-darwin.tar" - -cairo-%.tar: - curl -L -o "$@" "https://github.com/starkware-libs/cairo/releases/download/v$*/release-x86_64-unknown-linux-musl.tar.gz" - -install-scarb: - curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh| sh -s -- --no-modify-path --version $(SCARB_VERSION) - -install-scarb-macos: - curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh| sh -s -- --version $(SCARB_VERSION) +.PHONY: clean diff --git a/debug_utils/sierra-emu/README.md b/debug_utils/sierra-emu/README.md index 36c00411d..0c16ebf36 100644 --- a/debug_utils/sierra-emu/README.md +++ b/debug_utils/sierra-emu/README.md @@ -1,102 +1,36 @@ -
+# ⚡ Cairo Sierra Emulator ⚡ -### ⚡ Cairo Sierra Emulator ⚡ +An Cairo emulator directly using the Cairo's intermediate representation "Sierra" instead of CASM. An useful usecase is to aid in debugging Cairo Native. -An Cairo emulator directly using the Cairo's intermediate representation "Sierra" instead of CASM.
-An useful usecase is to aid in debugging other Cairo related VMs. - -[Report Bug](https://github.com/lambdaclass/sierra-emu/issues/new) · [Request Feature](https://github.com/lambdaclass/sierra-emu/issues/new) - -[![Telegram Chat][tg-badge]][tg-url] -[![rust](https://github.com/lambdaclass/sierra-emu/actions/workflows/ci.yml/badge.svg)](https://github.com/lambdaclass/sierra-emu/actions/workflows/ci.yml) -[![codecov](https://img.shields.io/codecov/c/github/lambdaclass/sierra-emu)](https://codecov.io/gh/lambdaclass/sierra-emu) -[![license](https://img.shields.io/github/license/lambdaclass/sierra-emu)](/LICENSE) -[![pr-welcome]](#-contributing) - - -[tg-badge]: https://img.shields.io/endpoint?url=https%3A%2F%2Ftg.sumanjay.workers.dev%2FLambdaStarkNet%2F&logo=telegram&label=chat&color=neon -[tg-url]: https://t.me/LambdaStarkNet -[pr-welcome]: https://img.shields.io/static/v1?color=orange&label=PRs&style=flat&message=welcome - -
+## Dependencies +First, make sure to have all the dependencies from Cairo Native setup. +Then, you can use the corelib recipe to make a symlink in the current directory. +```bash +make corelib +``` ## Running the Program -`cargo run ` - -## Using the API - -With a contract: - -```rust -use std::path::Path; - -use cairo_lang_compiler::CompilerConfig; -use cairo_lang_starknet::compile::compile_path; -use sierra_emu::{ - starknet::StubSyscallHandler, ContractExecutionResult, - VirtualMachine, -}; -let path = Path::new("programs/hello_starknet.cairo"); -let contract = compile_path( - path, - None, - CompilerConfig { - replace_ids: true, - ..Default::default() - }, -) -.unwrap(); +To use the sierra emulator binary, we must first compile the target cairo program. -let sierra_program = contract.extract_sierra_program().unwrap(); - -let entry_point = contract.entry_points_by_type.external.first().unwrap(); - -let mut vm = VirtualMachine::new_starknet( - sierra_program.clone().into(), - &contract.entry_points_by_type, -); - -let calldata = [2.into()]; -let initial_gas = 1000000; - -// Change the StubSyscallHandler with your own implementation. -let syscall_handler = &mut StubSyscallHandler::default(); - -vm.call_contract(entry_point.selector.clone().into(), initial_gas, calldata); - -let _race = vm.run_with_trace(syscall_handler); -let trace_str = serde_json::to_string_pretty(&trace).unwrap(); -std::fs::write("contract_trace.json", trace_str).unwrap(); +```bash +../../cairo2/bin/cairo-compile -rs ./programs/fibonacci.cairo > ./programs/fibonacci.sierra ``` -With a program: +Then, we can generate the ejecution trace with the sierra emulator: -```rust -let path = Path::new(path); - -let sierra_program = Arc::new( - compile_cairo_project_at_path( - path, - CompilerConfig { - replace_ids: true, - ..Default::default() - }, - ) - .unwrap(), -); - -let function = find_entry_point_by_name(&sierra_program, func_name).unwrap(); +```bash +cargo run -- ./programs/fibonacci.sierra fibonacci::fibonacci::main \ + --available-gas 100000 --output ./programs/fibonacci.trace.json +``` -let mut vm = VirtualMachine::new(sierra_program.clone()); +The program trace will be generated to `./programs/fibonacci.trace.json`. -let args = args.iter().cloned(); -let initial_gas = 1000000; +## Using the API -vm.call_program(function, initial_gas, args); +The sierra emulator can also be used as a library. -let syscall_handler = &mut StubSyscallHandler::default(); -let trace = vm.run_with_trace(syscall_handler); -``` +- See [examples/program.rs](examples/program.rs) for an example on how to execute a cairo program +- See [examples/contract.rs](examples/contract.rs) for an example on how to execute a cairo contract diff --git a/debug_utils/sierra-emu/examples/contract.rs b/debug_utils/sierra-emu/examples/contract.rs new file mode 100644 index 000000000..b07683b30 --- /dev/null +++ b/debug_utils/sierra-emu/examples/contract.rs @@ -0,0 +1,56 @@ +use std::{error::Error, sync::Arc}; + +use std::path::Path; + +use cairo_lang_compiler::CompilerConfig; +use cairo_lang_starknet::compile::compile_path; +use cairo_lang_starknet_classes::contract_class::version_id_from_serialized_sierra_program; +use sierra_emu::{starknet::StubSyscallHandler, ContractExecutionResult, VirtualMachine}; +use starknet_crypto::Felt; + +fn main() -> Result<(), Box> { + // INPUT ARGUMENTS + let cairo_path = Path::new("programs/fibonacci_contract.cairo"); + let arguments: [Felt; 1] = [Felt::from(10)]; + let initial_gas = 100000; + + // Compile cairo to sierra + let contract = compile_path( + cairo_path, + None, + CompilerConfig { + replace_ids: true, + ..Default::default() + }, + )?; + let program = contract.extract_sierra_program()?; + let (version_id, _) = version_id_from_serialized_sierra_program(&contract.sierra_program)?; + + // Find entrypoint to execute + let entrypoint = contract + .entry_points_by_type + .external + .first() + .ok_or("contract should contain at least one external entrypoint")? + .clone(); + + // Build virtual machine + let mut vm = VirtualMachine::new_starknet( + Arc::new(program), + &contract.entry_points_by_type, + version_id, + ); + vm.call_contract(entrypoint.selector.into(), initial_gas, arguments, None); + + // Execute the virtual machine. + let syscall_handler = &mut StubSyscallHandler::default(); + let trace = vm.run_with_trace(syscall_handler); + + // Obtain execution result from last frame + let result = + ContractExecutionResult::from_trace(&trace).ok_or("failed to obtain execution result")?; + + println!("{result:?}"); + + Ok(()) +} diff --git a/debug_utils/sierra-emu/examples/program.rs b/debug_utils/sierra-emu/examples/program.rs new file mode 100644 index 000000000..fcd481135 --- /dev/null +++ b/debug_utils/sierra-emu/examples/program.rs @@ -0,0 +1,45 @@ +use std::{error::Error, path::Path, sync::Arc}; + +use cairo_lang_compiler::{compile_cairo_project_at_path, CompilerConfig}; +use sierra_emu::{ + find_entry_point_by_name, starknet::StubSyscallHandler, ContractExecutionResult, Value, + VirtualMachine, +}; + +fn main() -> Result<(), Box> { + // INPUT ARGUMENTS + let cairo_path = Path::new("programs/fibonacci.cairo"); + let entrypoint = "fibonacci::fibonacci::main"; + let arguments: [Value; 0] = []; + let initial_gas = 100000; + + // Compile cairo to sierra + let program = compile_cairo_project_at_path( + cairo_path, + CompilerConfig { + replace_ids: true, + ..Default::default() + }, + )?; + + // Find entrypoint to execute + let function = find_entry_point_by_name(&program, entrypoint) + .ok_or("failed to find main entrypoint")? + .clone(); + + // Build virtual machine + let mut vm = VirtualMachine::new(Arc::new(program)); + vm.call_program(&function, initial_gas, arguments); + + // Execute the virtual machine. + let syscall_handler = &mut StubSyscallHandler::default(); + let trace = vm.run_with_trace(syscall_handler); + + // Obtain execution result from last frame + let result = + ContractExecutionResult::from_trace(&trace).ok_or("failed to obtain execution result")?; + + println!("{result:?}"); + + Ok(()) +} diff --git a/debug_utils/sierra-emu/programs/fibonacci.cairo b/debug_utils/sierra-emu/programs/fibonacci.cairo new file mode 100644 index 000000000..fc07c34ba --- /dev/null +++ b/debug_utils/sierra-emu/programs/fibonacci.cairo @@ -0,0 +1,14 @@ +use core::felt252; + +fn main() -> felt252 { + let n = 10; + let result = fib(1, 1, n); + result +} + +fn fib(a: felt252, b: felt252, n: felt252) -> felt252 { + match n { + 0 => a, + _ => fib(b, a + b, n - 1), + } +} diff --git a/debug_utils/sierra-emu/programs/fibonacci_contract.cairo b/debug_utils/sierra-emu/programs/fibonacci_contract.cairo new file mode 100644 index 000000000..83a94e7ef --- /dev/null +++ b/debug_utils/sierra-emu/programs/fibonacci_contract.cairo @@ -0,0 +1,17 @@ +#[starknet::contract] +mod Fibonacci { + #[storage] + struct Storage { } + + #[external(v0)] + fn fibonacci(ref self: ContractState, value: felt252) -> felt252 { + return super::fibonacci(1, 1, value); + } +} + +fn fibonacci(a: felt252, b: felt252, n: felt252) -> felt252 { + match n { + 0 => a, + _ => fibonacci(b, a + b, n - 1), + } +} diff --git a/debug_utils/sierra-emu/scripts/check-corelib-version.sh b/debug_utils/sierra-emu/scripts/check-corelib-version.sh deleted file mode 100755 index 2458d6755..000000000 --- a/debug_utils/sierra-emu/scripts/check-corelib-version.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -# Script to check the corelib version matches. - -_result=$(grep "version = \"$1\"" corelib/Scarb.toml) - -if [ $? -ne 0 ]; then - echo "corelib version mismatch, please re-run 'make deps'" - exit 1 -fi From 1b9a370e48d22fe1973e5cb19220bad193536c26 Mon Sep 17 00:00:00 2001 From: Julian Gonzalez Calderon Date: Tue, 6 May 2025 13:46:38 -0300 Subject: [PATCH 03/28] Add Trace Dump feature (#1199) * Add trace dump support in compiler * Add sierra emu dependency with trace-dump feature * Add trace dump metadata * Register trace dump symbols on executors * Use trace dump feature in cairo-native-run * Remove unused feature * Rename trace_id symbol * Don't generate trace dump on return when no tail recursion target has been found * Update lock * Derive Eq,PartialEq on TraceDump * Substract initial required gas in sierra emu * Print error when trace dump is not found, but ignore * Add trace dump test * Fix clippy * Remove `get_layout` from trace dump * Rename variables * Minor improvements * Add documentation * Improve documentation * Improve documentation on trace dump runtime functions * Fix typo Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> * Fix warning * Update usage docs * Document trace dump feature for programs * Add txt to blockquote to avoid executing in doc test --------- Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> --- Cargo.lock | 116 ++-- Cargo.toml | 2 + debug_utils/sierra-emu/src/dump.rs | 4 +- debug_utils/sierra-emu/src/value.rs | 1 + debug_utils/sierra-emu/src/vm.rs | 3 + docs/debugging.md | 55 ++ src/bin/cairo-native-run.rs | 64 +++ src/compiler.rs | 45 ++ src/executor/aot.rs | 3 + src/executor/contract.rs | 3 + src/executor/jit.rs | 3 + src/metadata.rs | 1 + src/metadata/trace_dump.rs | 793 ++++++++++++++++++++++++++++ src/utils.rs | 1 + src/utils/trace_dump.rs | 225 ++++++++ 15 files changed, 1256 insertions(+), 63 deletions(-) create mode 100644 src/metadata/trace_dump.rs create mode 100644 src/utils/trace_dump.rs diff --git a/Cargo.lock b/Cargo.lock index d065958dc..abcf7265d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,7 +108,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -189,7 +189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -215,7 +215,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -287,7 +287,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -385,7 +385,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -675,7 +675,7 @@ checksum = "b1e4872352761cf6d7f47eeb1626e3b1d84a514017fb4251173148d8c04f36d5" dependencies = [ "cairo-lang-debug", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -1071,6 +1071,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "sierra-emu", "starknet-curve", "starknet-types-core", "stats_alloc", @@ -1177,9 +1178,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.20" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "shlex", ] @@ -1274,7 +1275,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -1528,9 +1529,9 @@ dependencies = [ [[package]] name = "deunicode" -version = "1.6.2" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" +checksum = "dc55fe0d1f6c107595572ec8b107c0999bb1a2e0b75e37429a4fb0d6474a0e7d" [[package]] name = "diff" @@ -1588,7 +1589,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -1600,7 +1601,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -1667,7 +1668,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -1760,7 +1761,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -1808,7 +1809,7 @@ checksum = "43eaff6bbc0b3a878361aced5ec6a2818ee7c541c5b33b5880dfa9a86c23e9e7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -1994,7 +1995,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -2330,7 +2331,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.101", + "syn 2.0.100", "tblgen", "unindent", ] @@ -2561,7 +2562,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -2704,7 +2705,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.25", + "zerocopy 0.8.24", ] [[package]] @@ -2740,7 +2741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -3033,7 +3034,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.101", + "syn 2.0.100", "unicode-ident", ] @@ -3063,7 +3064,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -3195,7 +3196,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -3244,7 +3245,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -3255,7 +3256,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -3281,9 +3282,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.9" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -3545,9 +3546,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -3613,7 +3614,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -3624,7 +3625,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", "test-case-core", ] @@ -3654,7 +3655,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -3665,7 +3666,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -3725,9 +3726,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.8.22" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -3737,33 +3738,26 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", - "toml_write", "winnow", ] -[[package]] -name = "toml_write" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" - [[package]] name = "tracing" version = "0.1.41" @@ -3783,7 +3777,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -3887,7 +3881,7 @@ checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -4042,7 +4036,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -4064,7 +4058,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4213,9 +4207,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.7" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] @@ -4270,11 +4264,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "zerocopy-derive 0.8.25", + "zerocopy-derive 0.8.24", ] [[package]] @@ -4285,18 +4279,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] @@ -4316,7 +4310,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.100", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2b5a631f2..87547af34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ with-cheatcode = [] with-debug-utils = [] with-mem-tracing = [] with-segfault-catcher = [] +with-trace-dump = ["dep:sierra-emu"] # the aquamarine dep is only used in docs and cannot be detected as used by cargo udeps [package.metadata.cargo-udeps.ignore] @@ -89,6 +90,7 @@ tempfile = "3.15.0" thiserror = "2.0.9" tracing = "0.1" utf8_iter = "1.0.4" +sierra-emu = { path = "debug_utils/sierra-emu", optional = true } # CLI dependencies diff --git a/debug_utils/sierra-emu/src/dump.rs b/debug_utils/sierra-emu/src/dump.rs index a7e2b668b..591d57266 100644 --- a/debug_utils/sierra-emu/src/dump.rs +++ b/debug_utils/sierra-emu/src/dump.rs @@ -5,7 +5,7 @@ use serde::{ser::SerializeMap, Serialize}; use starknet_crypto::Felt; use std::collections::BTreeMap; -#[derive(Clone, Debug, Default, Serialize)] +#[derive(Clone, Debug, Default, Serialize, Eq, PartialEq)] pub struct ProgramTrace { pub states: Vec, // TODO: Syscall data. @@ -34,7 +34,7 @@ impl ProgramTrace { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct StateDump { pub statement_idx: StatementIdx, pub items: BTreeMap, diff --git a/debug_utils/sierra-emu/src/value.rs b/debug_utils/sierra-emu/src/value.rs index 6f37a2d71..aea97667c 100644 --- a/debug_utils/sierra-emu/src/value.rs +++ b/debug_utils/sierra-emu/src/value.rs @@ -15,6 +15,7 @@ use std::{collections::HashMap, ops::Range}; use crate::{debug::type_to_name, gas::BuiltinCosts}; +/// TODO: Can we reuse `cairo_native::Value::from_ptr`? #[derive(Clone, Debug, Eq, PartialEq, Serialize)] pub enum Value { Array { diff --git a/debug_utils/sierra-emu/src/vm.rs b/debug_utils/sierra-emu/src/vm.rs index b82b71c69..56caa04e6 100644 --- a/debug_utils/sierra-emu/src/vm.rs +++ b/debug_utils/sierra-emu/src/vm.rs @@ -233,6 +233,9 @@ impl VirtualMachine { I: IntoIterator, I::IntoIter: ExactSizeIterator, { + let required_gas = self.gas.initial_required_gas(&function.id).unwrap(); + let initial_gas = initial_gas.checked_sub(required_gas).unwrap(); + let mut iter = args.into_iter(); self.push_frame( function.id.clone(), diff --git a/docs/debugging.md b/docs/debugging.md index f10624bdc..88dc44f23 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -74,6 +74,61 @@ export RUST_LOG="cairo_native=trace" } ``` +## Trace Dump Feature + +The `with-trace-dump` feature is used to generate the execution trace of a sierra program. + +First, make sure to compile with the feature enabled: +```bash +cargo build --release --features with-trace-dump +``` + +Then, use the `trace_output` flag to save the trace dump to disk: + +```bash +target/release/cairo-native-run -s programs/recursion.cairo --trace-output programs/recursion.trace --available-gas 10000000 +``` + +The generated file will contain the state of all variables in the current scope, for every statement executed: + +```json +{ + "states": [ + { + "statementIdx": 25, + "preStateDump": { + "0": "Unit", + "1": { "U64": 9993660 } + } + }, + { + "statementIdx": 26, + "preStateDump": { + "0": "Unit", + "1": { "U64": 9993660 } + } + }, + { + "statementIdx": 27, + "preStateDump": { + "0": "Unit", + "1": { "U64": 9993660 }, + "2": { "Felt": "0x3e8" } + } + }, + ... + ] +} +``` + +It is sometimes useful to take a look at the sierra program. You can use the `--sierra-output` flag to save the sierra program to disk. + +```txt +disable_ap_tracking() -> (); // 25 +const_as_immediate>() -> ([2]); // 26 +store_temp([0]) -> ([0]); // 27 +``` + ## Debugging Contracts Contracts are difficult to debug for various reasons, including: diff --git a/src/bin/cairo-native-run.rs b/src/bin/cairo-native-run.rs index 2a42ae8c7..2214c9bd6 100644 --- a/src/bin/cairo-native-run.rs +++ b/src/bin/cairo-native-run.rs @@ -45,6 +45,16 @@ struct Args { /// Optimization level, Valid: 0, 1, 2, 3. Values higher than 3 are considered as 3. #[arg(short = 'O', long, default_value_t = 0)] opt_level: u8, + + #[cfg(feature = "with-trace-dump")] + #[arg(long)] + /// The output path for the execution trace + trace_output: Option, + + #[cfg(feature = "with-trace-dump")] + #[arg(long)] + /// The output path for the compiled sierra code + sierra_output: Option, } fn main() -> anyhow::Result<()> { @@ -70,6 +80,14 @@ fn main() -> anyhow::Result<()> { )? .program; + #[cfg(feature = "with-trace-dump")] + if let Some(sierra_output) = args.sierra_output { + use std::fs::File; + use std::io::Write; + let mut file = File::create(sierra_output).unwrap(); + write!(file, "{}", &sierra_program).unwrap(); + } + let native_context = NativeContext::new(); // Compile the sierra program into a MLIR module. @@ -81,6 +99,16 @@ fn main() -> anyhow::Result<()> { RunMode::Aot => { let executor = AotNativeExecutor::from_native_module(native_module, args.opt_level.into())?; + + #[cfg(feature = "with-trace-dump")] + { + use cairo_native::metadata::trace_dump::TraceBinding; + if let Some(trace_id) = executor.find_symbol_ptr(TraceBinding::TraceId.symbol()) { + let trace_id = trace_id.cast::(); + unsafe { *trace_id = 0 }; + } + } + Box::new(move |function_id, args, gas, syscall_handler| { executor.invoke_dynamic_with_syscall_handler( function_id, @@ -93,6 +121,16 @@ fn main() -> anyhow::Result<()> { RunMode::Jit => { let executor = JitNativeExecutor::from_native_module(native_module, args.opt_level.into())?; + + #[cfg(feature = "with-trace-dump")] + { + use cairo_native::metadata::trace_dump::TraceBinding; + if let Some(trace_id) = executor.find_symbol_ptr(TraceBinding::TraceId.symbol()) { + let trace_id = trace_id.cast::(); + unsafe { *trace_id = 0 }; + } + } + Box::new(move |function_id, args, gas, syscall_handler| { executor.invoke_dynamic_with_syscall_handler( function_id, @@ -104,6 +142,17 @@ fn main() -> anyhow::Result<()> { } }; + #[cfg(feature = "with-trace-dump")] + { + use cairo_lang_sierra::program_registry::ProgramRegistry; + use cairo_native::metadata::trace_dump::trace_dump_runtime::{TraceDump, TRACE_DUMP}; + + TRACE_DUMP.lock().unwrap().insert( + 0, + TraceDump::new(ProgramRegistry::new(&sierra_program).unwrap()), + ); + } + let gas_metadata = GasMetadata::new(&sierra_program, Some(MetadataComputationConfig::default())).unwrap(); @@ -139,5 +188,20 @@ fn main() -> anyhow::Result<()> { println!("Remaining gas: {gas}"); } + #[cfg(feature = "with-trace-dump")] + if let Some(trace_output) = args.trace_output { + let traces = cairo_native::metadata::trace_dump::trace_dump_runtime::TRACE_DUMP + .lock() + .unwrap(); + assert_eq!(traces.len(), 1); + + let trace_dump = traces.values().next().unwrap(); + serde_json::to_writer_pretty( + std::fs::File::create(trace_output).unwrap(), + &trace_dump.trace, + ) + .unwrap(); + } + Ok(()) } diff --git a/src/compiler.rs b/src/compiler.rs index b6c0b3b94..13b7060d5 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -212,6 +212,9 @@ fn compile_func( ) .collect::, _>>()?; + #[cfg(feature = "with-trace-dump")] + let mut var_types: HashMap = HashMap::new(); + // Replace memory-allocated arguments with pointers. for (ty, type_info) in arg_types @@ -415,6 +418,9 @@ fn compile_func( value }, )); + + #[cfg(feature = "with-trace-dump")] + var_types.insert(param.id.clone(), param.ty.clone()); } values.into_iter() @@ -522,6 +528,19 @@ fn compile_func( format!("{}(stmt_idx={})", libfunc_to_name(libf), statement_idx) }; + #[cfg(feature = "with-trace-dump")] + crate::utils::trace_dump::build_state_snapshot( + context, + registry, + module, + block, + location, + metadata, + statement_idx, + &state, + &var_types, + ); + let (state, _) = edit_state::take_args(state, invocation.args.iter())?; let helper = LibfuncHelper { @@ -614,6 +633,17 @@ fn compile_func( } } + #[cfg(feature = "with-trace-dump")] + for (branch_signature, branch_info) in + libfunc.branch_signatures().iter().zip(&invocation.branches) + { + for (var_info, var_id) in + branch_signature.vars.iter().zip(&branch_info.results) + { + var_types.insert(var_id.clone(), var_info.ty.clone()); + } + } + StatementCompileResult::Processed( invocation .branches @@ -647,6 +677,21 @@ fn compile_func( ), ); + #[cfg(feature = "with-trace-dump")] + if !is_recursive || tailrec_state.is_some() { + crate::utils::trace_dump::build_state_snapshot( + context, + registry, + module, + block, + location, + metadata, + statement_idx, + &state, + &var_types, + ); + } + let (_, mut values) = edit_state::take_args(state, var_ids.iter())?; let mut block = *block; diff --git a/src/executor/aot.rs b/src/executor/aot.rs index f88ea582b..7de2cb4f8 100644 --- a/src/executor/aot.rs +++ b/src/executor/aot.rs @@ -57,6 +57,9 @@ impl AotNativeExecutor { #[cfg(feature = "with-debug-utils")] crate::metadata::debug_utils::setup_runtime(|name| executor.find_symbol_ptr(name)); + #[cfg(feature = "with-trace-dump")] + crate::metadata::trace_dump::setup_runtime(|name| executor.find_symbol_ptr(name)); + executor } diff --git a/src/executor/contract.rs b/src/executor/contract.rs index 72af2cdf2..cc93d1f5c 100644 --- a/src/executor/contract.rs +++ b/src/executor/contract.rs @@ -282,6 +282,9 @@ impl AotContractExecutor { #[cfg(feature = "with-debug-utils")] crate::metadata::debug_utils::setup_runtime(|name| executor.find_symbol_ptr(name)); + #[cfg(feature = "with-trace-dump")] + crate::metadata::trace_dump::setup_runtime(|name| executor.find_symbol_ptr(name)); + Ok(Some(executor)) } diff --git a/src/executor/jit.rs b/src/executor/jit.rs index 13edfab1d..899c955db 100644 --- a/src/executor/jit.rs +++ b/src/executor/jit.rs @@ -68,6 +68,9 @@ impl<'m> JitNativeExecutor<'m> { #[cfg(feature = "with-debug-utils")] crate::metadata::debug_utils::setup_runtime(|name| executor.find_symbol_ptr(name)); + #[cfg(feature = "with-trace-dump")] + crate::metadata::trace_dump::setup_runtime(|name| executor.find_symbol_ptr(name)); + Ok(executor) } diff --git a/src/metadata.rs b/src/metadata.rs index 9a05f369a..b4328d69c 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -24,6 +24,7 @@ pub mod gas; pub mod realloc_bindings; pub mod runtime_bindings; pub mod tail_recursion; +pub mod trace_dump; /// Metadata container. #[cfg_attr(not(feature = "with-debug-utils"), derive(Default))] diff --git a/src/metadata/trace_dump.rs b/src/metadata/trace_dump.rs new file mode 100644 index 000000000..92e89aa0e --- /dev/null +++ b/src/metadata/trace_dump.rs @@ -0,0 +1,793 @@ +#![cfg(feature = "with-trace-dump")] + +use crate::{ + error::{Error, Result}, + utils::BlockExt, +}; +use cairo_lang_sierra::{ + ids::{ConcreteTypeId, VarId}, + program::StatementIdx, +}; +use melior::{ + dialect::{llvm, memref, ods}, + ir::{ + attribute::{FlatSymbolRefAttribute, StringAttribute, TypeAttribute}, + operation::OperationBuilder, + r#type::{IntegerType, MemRefType}, + Attribute, Block, BlockLike, Location, Module, Region, Value, + }, + Context, +}; +use std::{collections::HashSet, ffi::c_void, ptr}; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum TraceBinding { + State, + Push, + TraceId, +} + +impl TraceBinding { + pub const fn symbol(self) -> &'static str { + match self { + TraceBinding::State => "cairo_native__trace_dump__add_variable_to_state", + TraceBinding::Push => "cairo_native__trace_dump__push_state_to_trace_dump", + TraceBinding::TraceId => "cairo_native__trace_dump__trace_id", + } + } + + const fn function_ptr(self) -> *const () { + match self { + TraceBinding::State => trace_dump_runtime::add_variable_to_state as *const (), + TraceBinding::Push => trace_dump_runtime::push_state_to_trace_dump as *const (), + // it has no function pointer, as its a global constant + TraceBinding::TraceId => ptr::null(), + } + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct TraceDumpMeta { + active_map: HashSet, +} + +impl TraceDumpMeta { + /// Register the global for the given binding, if not yet registered, and return + /// a pointer to the stored value. + /// + /// For the function to be available, `setup_runtime` must be called before running the module + fn build_function<'c, 'a>( + &mut self, + context: &'c Context, + module: &Module, + block: &'a Block<'c>, + location: Location<'c>, + binding: TraceBinding, + ) -> Result> { + if self.active_map.insert(binding) { + module.body().append_operation( + ods::llvm::mlir_global( + context, + Region::new(), + TypeAttribute::new(llvm::r#type::pointer(context, 0)), + StringAttribute::new(context, binding.symbol()), + Attribute::parse(context, "#llvm.linkage") + .ok_or(Error::ParseAttributeError)?, + location, + ) + .into(), + ); + } + + let global_address = block.append_op_result( + ods::llvm::mlir_addressof( + context, + llvm::r#type::pointer(context, 0), + FlatSymbolRefAttribute::new(context, binding.symbol()), + location, + ) + .into(), + )?; + + block.load( + context, + location, + global_address, + llvm::r#type::pointer(context, 0), + ) + } + + #[allow(clippy::too_many_arguments)] + pub fn build_state( + &mut self, + context: &Context, + module: &Module, + block: &Block, + var_id: &VarId, + value_ty: &ConcreteTypeId, + value_ptr: Value, + location: Location, + ) -> Result<()> { + let trace_id = self.build_trace_id(context, module, block, location)?; + + let var_id = block.const_int(context, location, var_id.id, 64)?; + let value_ty = block.const_int(context, location, value_ty.id, 64)?; + + let function = + self.build_function(context, module, block, location, TraceBinding::State)?; + block.append_operation( + OperationBuilder::new("llvm.call", location) + .add_operands(&[function]) + .add_operands(&[trace_id, var_id, value_ty, value_ptr]) + .build()?, + ); + + Ok(()) + } + + pub fn build_push( + &mut self, + context: &Context, + module: &Module, + block: &Block, + statement_idx: StatementIdx, + location: Location, + ) -> Result<()> { + let trace_id = self.build_trace_id(context, module, block, location)?; + let statement_idx = block.const_int(context, location, statement_idx.0, 64)?; + + let function = self.build_function(context, module, block, location, TraceBinding::Push)?; + + block.append_operation( + OperationBuilder::new("llvm.call", location) + .add_operands(&[function]) + .add_operands(&[trace_id, statement_idx]) + .build()?, + ); + + Ok(()) + } + + pub fn build_trace_id<'c, 'a>( + &mut self, + context: &'c Context, + module: &Module, + block: &'a Block<'c>, + location: Location<'c>, + ) -> Result> { + if self.active_map.insert(TraceBinding::TraceId) { + module.body().append_operation(memref::global( + context, + TraceBinding::TraceId.symbol(), + None, + MemRefType::new(IntegerType::new(context, 64).into(), &[], None, None), + None, + false, + None, + location, + )); + } + + let trace_id_ptr = block + .append_op_result(memref::get_global( + context, + TraceBinding::TraceId.symbol(), + MemRefType::new(IntegerType::new(context, 64).into(), &[], None, None), + location, + )) + .unwrap(); + + block.append_op_result(memref::load(trace_id_ptr, &[], location)) + } +} + +pub fn setup_runtime(find_symbol_ptr: impl Fn(&str) -> Option<*mut c_void>) { + let bindings = &[TraceBinding::State, TraceBinding::Push]; + + for binding in bindings { + if let Some(global) = find_symbol_ptr(binding.symbol()) { + let global = global.cast::<*const ()>(); + unsafe { *global = binding.function_ptr() }; + } + } +} + +pub mod trace_dump_runtime { + #![allow(non_snake_case)] + + use cairo_lang_sierra::{ + extensions::{ + bounded_int::BoundedIntConcreteType, + circuit::CircuitTypeConcrete, + core::{CoreLibfunc, CoreType, CoreTypeConcrete}, + starknet::{secp256::Secp256PointTypeConcrete, StarknetTypeConcrete}, + }, + ids::{ConcreteTypeId, VarId}, + program::{GenericArg, StatementIdx}, + program_registry::ProgramRegistry, + }; + use cairo_lang_utils::ordered_hash_map::OrderedHashMap; + use itertools::Itertools; + use num_bigint::{BigInt, BigUint}; + use num_traits::One; + use sierra_emu::{ + starknet::{ + Secp256k1Point as EmuSecp256k1Point, Secp256r1Point as EmuSecp256r1Point, + U256 as EmuU256, + }, + ProgramTrace, StateDump, Value, + }; + use starknet_types_core::felt::Felt; + use std::{ + alloc::Layout, + collections::HashMap, + mem::swap, + ops::Range, + ptr::NonNull, + sync::{LazyLock, Mutex}, + }; + + use crate::{starknet::ArrayAbi, types::TypeBuilder}; + + use crate::runtime::FeltDict; + + pub static TRACE_DUMP: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + + /// An in-progress trace dump for a particular execution + pub struct TraceDump { + pub trace: ProgramTrace, + /// Represents the latest state. All values are added to + /// this state until pushed to the trace. + state: OrderedHashMap, + registry: ProgramRegistry, + } + + impl TraceDump { + pub fn new(registry: ProgramRegistry) -> Self { + Self { + trace: ProgramTrace::default(), + state: OrderedHashMap::default(), + registry, + } + } + } + + /// Adds a new variable to the current state of the trace dump with the + /// given identifier. + /// + /// Receives a pointer to the value, even if the value is a pointer itself. + pub unsafe extern "C" fn add_variable_to_state( + trace_id: u64, + var_id: u64, + type_id: u64, + value_ptr: NonNull<()>, + ) { + let mut trace_dump = TRACE_DUMP.lock().unwrap(); + let Some(trace_dump) = trace_dump.get_mut(&trace_id) else { + eprintln!("Could not find trace dump!"); + return; + }; + + let type_id = ConcreteTypeId::new(type_id); + let value = value_from_ptr(&trace_dump.registry, &type_id, value_ptr); + + trace_dump.state.insert(VarId::new(var_id), value); + } + + /// Pushes the latest state to the trace dump with the given identifier. + /// + /// It is called after all variables have been added with `add_variable_to_state`. + pub unsafe extern "C" fn push_state_to_trace_dump(trace_id: u64, statement_idx: u64) { + let mut trace_dump = TRACE_DUMP.lock().unwrap(); + let Some(trace_dump) = trace_dump.get_mut(&trace_id) else { + eprintln!("Could not find trace dump!"); + return; + }; + + let mut items = OrderedHashMap::default(); + swap(&mut items, &mut trace_dump.state); + + trace_dump + .trace + .push(StateDump::new(StatementIdx(statement_idx as usize), items)); + } + + /// TODO: Can we reuse `cairo_native::Value::from_ptr`? + unsafe fn value_from_ptr( + registry: &ProgramRegistry, + type_id: &ConcreteTypeId, + value_ptr: NonNull<()>, + ) -> Value { + let type_info = registry.get_type(type_id).unwrap(); + match type_info { + CoreTypeConcrete::Felt252(_) + | CoreTypeConcrete::Starknet(StarknetTypeConcrete::ContractAddress(_)) + | CoreTypeConcrete::Starknet(StarknetTypeConcrete::ClassHash(_)) + | CoreTypeConcrete::Starknet(StarknetTypeConcrete::StorageAddress(_)) + | CoreTypeConcrete::Starknet(StarknetTypeConcrete::StorageBaseAddress(_)) => { + Value::Felt(Felt::from_bytes_le(value_ptr.cast().as_ref())) + } + CoreTypeConcrete::Uint8(_) => Value::U8(value_ptr.cast().read()), + CoreTypeConcrete::Uint16(_) => Value::U16(value_ptr.cast().read()), + CoreTypeConcrete::Uint32(_) => Value::U32(value_ptr.cast().read()), + CoreTypeConcrete::Uint64(_) | CoreTypeConcrete::GasBuiltin(_) => { + Value::U64(value_ptr.cast().read()) + } + CoreTypeConcrete::Uint128(_) => Value::U128(value_ptr.cast().read()), + + CoreTypeConcrete::BoundedInt(BoundedIntConcreteType { range, .. }) => { + let n_bits = ((range.size() - BigInt::one()).bits() as u32).max(1); + let n_bytes = n_bits.next_multiple_of(8) >> 3; + + let data = NonNull::slice_from_raw_parts(value_ptr.cast::(), n_bytes as usize); + + let value = BigInt::from_bytes_le(num_bigint::Sign::Plus, data.as_ref()); + + Value::BoundedInt { + range: Range { + start: range.lower.clone(), + end: range.upper.clone(), + }, + value: value + &range.lower, + } + } + + CoreTypeConcrete::EcPoint(_) => { + let layout = Layout::new::<()>(); + let (x, layout) = { + let (layout, offset) = layout.extend(Layout::new::<[u128; 2]>()).unwrap(); + ( + Felt::from_bytes_le(value_ptr.byte_add(offset).cast().as_ref()), + layout, + ) + }; + let (y, _) = { + let (layout, offset) = layout.extend(Layout::new::<[u128; 2]>()).unwrap(); + ( + Felt::from_bytes_le(value_ptr.byte_add(offset).cast().as_ref()), + layout, + ) + }; + + Value::EcPoint { x, y } + } + CoreTypeConcrete::EcState(_) => { + let layout = Layout::new::<()>(); + let (x0, layout) = { + let (layout, offset) = layout.extend(Layout::new::<[u128; 2]>()).unwrap(); + ( + Felt::from_bytes_le(value_ptr.byte_add(offset).cast().as_ref()), + layout, + ) + }; + let (y0, layout) = { + let (layout, offset) = layout.extend(Layout::new::<[u128; 2]>()).unwrap(); + ( + Felt::from_bytes_le(value_ptr.byte_add(offset).cast().as_ref()), + layout, + ) + }; + let (x1, layout) = { + let (layout, offset) = layout.extend(Layout::new::<[u128; 2]>()).unwrap(); + ( + Felt::from_bytes_le(value_ptr.byte_add(offset).cast().as_ref()), + layout, + ) + }; + let (y1, _) = { + let (layout, offset) = layout.extend(Layout::new::<[u128; 2]>()).unwrap(); + ( + Felt::from_bytes_le(value_ptr.byte_add(offset).cast().as_ref()), + layout, + ) + }; + + Value::EcState { x0, y0, x1, y1 } + } + + CoreTypeConcrete::Uninitialized(info) => Value::Uninitialized { + ty: info.ty.clone(), + }, + CoreTypeConcrete::Box(info) => { + value_from_ptr(registry, &info.ty, value_ptr.cast::>().read()) + } + CoreTypeConcrete::Array(info) => { + let array = value_ptr.cast::>().read(); + + let layout = registry + .get_type(&info.ty) + .unwrap() + .layout(registry) + .unwrap() + .pad_to_align(); + + let mut data = Vec::with_capacity((array.until - array.since) as usize); + + if !array.ptr.is_null() { + let data_ptr = array.ptr.read(); + for index in (array.since)..array.until { + let index = index as usize; + + data.push(value_from_ptr( + registry, + &info.ty, + NonNull::new(data_ptr.byte_add(layout.size() * index)).unwrap(), + )); + } + } + + Value::Array { + ty: info.ty.clone(), + data, + } + } + + CoreTypeConcrete::Struct(info) => { + let mut layout = Layout::new::<()>(); + let mut members = Vec::with_capacity(info.members.len()); + for member_ty in &info.members { + let type_info = registry.get_type(member_ty).unwrap(); + let member_layout = type_info.layout(registry).unwrap(); + + let offset; + (layout, offset) = layout.extend(member_layout).unwrap(); + + let current_ptr = value_ptr.byte_add(offset); + members.push(value_from_ptr(registry, member_ty, current_ptr)); + } + + Value::Struct(members) + } + CoreTypeConcrete::Enum(info) => { + let tag_bits = info.variants.len().next_power_of_two().trailing_zeros(); + let (tag_value, layout) = match tag_bits { + 0 => (0, Layout::new::<()>()), + width if width <= 8 => { + (value_ptr.cast::().read() as usize, Layout::new::()) + } + width if width <= 16 => ( + value_ptr.cast::().read() as usize, + Layout::new::(), + ), + width if width <= 32 => ( + value_ptr.cast::().read() as usize, + Layout::new::(), + ), + width if width <= 64 => ( + value_ptr.cast::().read() as usize, + Layout::new::(), + ), + width if width <= 128 => ( + value_ptr.cast::().read() as usize, + Layout::new::(), + ), + _ => todo!(), + }; + + let payload = { + let (_, offset) = layout + .extend( + registry + .get_type(&info.variants[tag_value]) + .unwrap() + .layout(registry) + .unwrap(), + ) + .unwrap(); + + value_from_ptr( + registry, + &info.variants[tag_value], + value_ptr.byte_add(offset), + ) + }; + + Value::Enum { + self_ty: type_id.clone(), + index: tag_value, + payload: Box::new(payload), + } + } + + CoreTypeConcrete::NonZero(info) | CoreTypeConcrete::Snapshot(info) => { + value_from_ptr(registry, &info.ty, value_ptr) + } + + // Builtins and other unit types: + CoreTypeConcrete::Bitwise(_) + | CoreTypeConcrete::EcOp(_) + | CoreTypeConcrete::Pedersen(_) + | CoreTypeConcrete::Poseidon(_) + | CoreTypeConcrete::RangeCheck96(_) + | CoreTypeConcrete::RangeCheck(_) + | CoreTypeConcrete::SegmentArena(_) + | CoreTypeConcrete::Starknet(StarknetTypeConcrete::System(_)) + | CoreTypeConcrete::Uint128MulGuarantee(_) => Value::Unit, + + CoreTypeConcrete::BuiltinCosts(_) => { + let builtin_costs = value_ptr.cast::<&[u64; 7]>().read(); + Value::BuiltinCosts(sierra_emu::BuiltinCosts { + r#const: builtin_costs[0], + pedersen: builtin_costs[1], + bitwise: builtin_costs[2], + ecop: builtin_costs[3], + poseidon: builtin_costs[4], + add_mod: builtin_costs[5], + mul_mod: builtin_costs[6], + }) + } + + // TODO: + CoreTypeConcrete::Coupon(_) => todo!("CoreTypeConcrete::Coupon"), + CoreTypeConcrete::Circuit(circuit) => match circuit { + CircuitTypeConcrete::AddMod(_) => Value::Unit, + CircuitTypeConcrete::MulMod(_) => Value::Unit, + CircuitTypeConcrete::AddModGate(_) => Value::Unit, + CircuitTypeConcrete::Circuit(_) => Value::Unit, + CircuitTypeConcrete::CircuitData(info) => { + let Some(GenericArg::Type(circuit_type_id)) = + info.info.long_id.generic_args.first() + else { + panic!("generic arg should be a type"); + }; + let CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(circuit)) = + registry.get_type(circuit_type_id).unwrap() + else { + panic!("generic arg should be a Circuit"); + }; + + let u384_layout = Layout::from_size_align(48, 16).unwrap(); + + let n_inputs = circuit.circuit_info.n_inputs; + let mut values = Vec::with_capacity(n_inputs); + + let value_ptr = value_ptr.cast::<[u8; 48]>(); + + for i in 0..n_inputs { + let size = u384_layout.pad_to_align().size(); + let current_ptr = value_ptr.byte_add(size * i); + let current_value = current_ptr.as_ref(); + values.push(BigUint::from_bytes_le(current_value)); + } + + Value::Circuit(values) + } + CircuitTypeConcrete::CircuitOutputs(info) => { + let Some(GenericArg::Type(circuit_type_id)) = + info.info.long_id.generic_args.first() + else { + panic!("generic arg should be a type"); + }; + let CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(circuit)) = + registry.get_type(circuit_type_id).unwrap() + else { + panic!("generic arg should be a Circuit"); + }; + + let u384_layout = Layout::from_size_align(48, 16).unwrap(); + + let n_outputs = circuit.circuit_info.values.len(); + let mut values = Vec::with_capacity(n_outputs); + + let value_ptr = value_ptr.cast::<[u8; 48]>(); + + for i in 0..n_outputs { + let size = u384_layout.pad_to_align().size(); + let current_ptr = value_ptr.byte_add(size * i); + let current_value = current_ptr.as_ref(); + values.push(BigUint::from_bytes_le(current_value)); + } + + Value::CircuitOutputs(values) + } + CircuitTypeConcrete::CircuitPartialOutputs(_) => { + todo!("CircuitTypeConcrete::CircuitPartialOutputs") + } + CircuitTypeConcrete::CircuitDescriptor(_) => Value::Unit, + CircuitTypeConcrete::CircuitFailureGuarantee(_) => { + todo!("CircuitTypeConcrete::CircuitFailureGuarantee") + } + CircuitTypeConcrete::CircuitInput(_) => { + todo!("CircuitTypeConcrete::CircuitInput") + } + CircuitTypeConcrete::CircuitInputAccumulator(info) => { + let Some(GenericArg::Type(circuit_type_id)) = + info.info.long_id.generic_args.first() + else { + panic!("generic arg should be a type"); + }; + let CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(_)) = + registry.get_type(circuit_type_id).unwrap() + else { + panic!("generic arg should be a Circuit"); + }; + + let u64_layout = Layout::new::(); + let u384_layout = Layout::from_size_align(48, 16).unwrap(); + + let length = unsafe { *value_ptr.cast::().as_ptr() }; + + let (_, input_start_offset) = u64_layout.extend(u384_layout).unwrap(); + let start_ptr = value_ptr.byte_add(input_start_offset).cast::<[u8; 48]>(); + + let mut values = Vec::with_capacity(length as usize); + + for i in 0..length { + let size = u384_layout.pad_to_align().size(); + let current_ptr = start_ptr.byte_add(size * i as usize); + let current_value = current_ptr.as_ref(); + values.push(BigUint::from_bytes_le(current_value)); + } + + Value::Circuit(values) + } + CircuitTypeConcrete::CircuitModulus(_) => { + let value_ptr = value_ptr.cast::<[u8; 48]>(); + let value = unsafe { value_ptr.as_ref() }; + Value::CircuitModulus(BigUint::from_bytes_le(value)) + } + + CircuitTypeConcrete::InverseGate(_) => Value::Unit, + CircuitTypeConcrete::MulModGate(_) => Value::Unit, + CircuitTypeConcrete::SubModGate(_) => Value::Unit, + CircuitTypeConcrete::U96Guarantee(_) => { + let value_ptr = value_ptr.cast::<[u8; 12]>(); + let value = unsafe { value_ptr.as_ref() }; + + let mut array_value = [0u8; 16]; + array_value[..12].clone_from_slice(value); + + Value::U128(u128::from_le_bytes(array_value)) + } + CircuitTypeConcrete::U96LimbsLessThanGuarantee(_) => Value::Unit, + }, + CoreTypeConcrete::Const(_) => todo!("CoreTypeConcrete::Const"), + CoreTypeConcrete::Sint8(_) => Value::I8(value_ptr.cast().read()), + CoreTypeConcrete::Sint16(_) => todo!("CoreTypeConcrete::Sint16"), + CoreTypeConcrete::Sint32(_) => Value::I32(value_ptr.cast().read()), + CoreTypeConcrete::Sint64(_) => todo!("CoreTypeConcrete::Sint64"), + CoreTypeConcrete::Sint128(_) => Value::I128(value_ptr.cast().read()), + CoreTypeConcrete::Nullable(info) => { + let inner_ptr = value_ptr.cast::<*mut ()>().read(); + match NonNull::new(inner_ptr) { + Some(inner_ptr) => value_from_ptr(registry, &info.ty, inner_ptr), + None => Value::Uninitialized { + ty: info.ty.clone(), + }, + } + } + + CoreTypeConcrete::SquashedFelt252Dict(info) | CoreTypeConcrete::Felt252Dict(info) => { + let value = value_ptr.cast::<&FeltDict>().read(); + + let data = value + .mappings + .iter() + .map(|(k, &i)| { + let p = value + .elements + .byte_offset((value.layout.size() * i) as isize); + let v = match NonNull::new(p) { + Some(value_ptr) => value_from_ptr(registry, &info.ty, value_ptr.cast()), + None => Value::Uninitialized { + ty: info.ty.clone(), + }, + }; + let k = Felt::from_bytes_le(k); + (k, v) + }) + .collect::>(); + + Value::FeltDict { + ty: info.ty.clone(), + count: value.count, + data, + } + } + CoreTypeConcrete::Felt252DictEntry(info) => { + let value = value_ptr.cast::().read(); + + let data = value + .dict + .mappings + .iter() + .map(|(k, &i)| { + let p = value + .dict + .elements + .byte_offset((value.dict.layout.size() * i) as isize); + let v = match NonNull::new(p) { + Some(value_ptr) => value_from_ptr(registry, &info.ty, value_ptr.cast()), + None => Value::Uninitialized { + ty: info.ty.clone(), + }, + }; + let k = Felt::from_bytes_le(k); + (k, v) + }) + .collect::>(); + let key = Felt::from_bytes_le(value.key); + + Value::FeltDictEntry { + ty: info.ty.clone(), + data, + count: value.dict.count, + key, + } + } + CoreTypeConcrete::Span(_) => todo!("CoreTypeConcrete::Span"), + CoreTypeConcrete::Starknet(selector) => match selector { + StarknetTypeConcrete::Secp256Point(selector) => match selector { + Secp256PointTypeConcrete::K1(_) => { + let point: Secp256Point = value_ptr.cast().read(); + let emu_point = EmuSecp256k1Point { + x: EmuU256 { + lo: point.x.lo, + hi: point.x.hi, + }, + y: EmuU256 { + lo: point.y.lo, + hi: point.y.hi, + }, + }; + emu_point.into_value() + } + Secp256PointTypeConcrete::R1(_) => { + let point: Secp256Point = value_ptr.cast().read(); + let emu_point = EmuSecp256r1Point { + x: EmuU256 { + lo: point.x.lo, + hi: point.x.hi, + }, + y: EmuU256 { + lo: point.y.lo, + hi: point.y.hi, + }, + }; + emu_point.into_value() + } + }, + StarknetTypeConcrete::Sha256StateHandle(_) => { + let raw_data = value_ptr.cast::>().read().read(); + let data = raw_data.into_iter().map(Value::U32).collect_vec(); + Value::Struct(data) + } + _ => unreachable!(), + }, + CoreTypeConcrete::Bytes31(_) => { + let original_data: [u8; 31] = value_ptr.cast().read(); + let mut data = [0u8; 32]; + for (i, v) in original_data.into_iter().enumerate() { + data[i] = v + } + + Value::Bytes31(Felt::from_bytes_le(&data)) + } + CoreTypeConcrete::IntRange(_) + | CoreTypeConcrete::Blake(_) + | CoreTypeConcrete::QM31(_) => { + todo!() + } + } + } + + #[derive(Debug)] + struct FeltDictEntry<'a> { + dict: &'a FeltDict, + key: &'a [u8; 32], + } + + #[repr(C, align(16))] + pub struct Secp256Point { + pub x: U256, + pub y: U256, + pub is_infinity: bool, + } + + #[repr(C, align(16))] + pub struct U256 { + pub lo: u128, + pub hi: u128, + } +} diff --git a/src/utils.rs b/src/utils.rs index ecfecf7a8..1dda89185 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -37,6 +37,7 @@ mod range_ext; #[cfg(feature = "with-segfault-catcher")] pub mod safe_runner; pub mod sierra_gen; +pub mod trace_dump; #[cfg(target_os = "macos")] pub const SHARED_LIBRARY_EXT: &str = "dylib"; diff --git a/src/utils/trace_dump.rs b/src/utils/trace_dump.rs new file mode 100644 index 000000000..afa489e03 --- /dev/null +++ b/src/utils/trace_dump.rs @@ -0,0 +1,225 @@ +#![cfg(feature = "with-trace-dump")] +//! The trace dump feature is used to generate the execution trace of a sierra program. +//! +//! Take, for example, the following sierra code: +//! +//! ```sierra +//! const_as_immediate>() -> ([0]); +//! const_as_immediate>() -> ([1]); +//! store_temp([0]) -> ([0]); +//! felt252_add([0], [1]) -> ([2]); +//! store_temp([2]) -> ([2]); +//! return([2]); +//! ``` +//! +//! The compiler will call `build_state_snapshot` right before each statement. +//! Iterating every variable on the current scope and saving its value on a global +//! static variable. +//! +//! At the end of the execution, the full trace dump can be retrieved, which +//! looks something like this: +//! +//! ```json +//! { +//! "states": [ +//! { +//! "statementIdx": 0, +//! "preStateDump": {} +//! }, +//! { +//! "statementIdx": 1, +//! "preStateDump": { +//! "0": { "Felt": "0xa" } +//! } +//! }, +//! { +//! "statementIdx": 2, +//! "preStateDump": { +//! "0": { "Felt": "0xa" }, +//! "1": { "Felt": "0x14" } +//! } +//! }, +//! ... +//! ] +//! } +//! ``` +//! +//! To support this feature even on the context of starknet contracts, then we +//! must support building a trace dump for multiple programs at the same time, as +//! starknet contracts can themselves call another contracts. To achieve this, we +//! need two important elements. +//! +//! 1. The global static variable must me able to store multiple trace dumps at the +//! same time. We have a global static hashmap from trace id to trace dump content. +//! See `TRACE_DUMP`. +//! +//! 2. We must store somewhere the ID of the current trace dump, and update it +//! acordingly when switching between contract executors +//! +//! Both these elements must be properly setup before running the executor. See +//! `cairo-native-run` for an example on how to do it. You can also check on +//! this file's integration tests. +//! +//! When executing starknet contracts, the trace id must be set right +//! before each execution, restoring the previous trace id after the execution. + +use std::collections::HashMap; + +use cairo_lang_sierra::{ + extensions::core::{CoreLibfunc, CoreType}, + ids::{ConcreteTypeId, VarId}, + program::StatementIdx, + program_registry::ProgramRegistry, +}; +use cairo_lang_utils::ordered_hash_map::OrderedHashMap; +use melior::{ + ir::{BlockRef, Location, Module, Value, ValueLike}, + Context, +}; + +use crate::{ + metadata::{trace_dump::TraceDumpMeta, MetadataStorage}, + types::TypeBuilder, +}; + +use super::BlockExt; + +#[allow(clippy::too_many_arguments)] +pub fn build_state_snapshot( + context: &Context, + registry: &ProgramRegistry, + module: &Module, + block: &BlockRef, + location: Location, + metadata: &mut MetadataStorage, + statement_idx: StatementIdx, + state: &OrderedHashMap, + var_types: &HashMap, +) { + let trace_dump = metadata.get_or_insert_with(TraceDumpMeta::default); + + for (var_id, value) in state.iter() { + let value_type_id = var_types.get(var_id).unwrap(); + let value_type = registry.get_type(value_type_id).unwrap(); + + let layout = value_type.layout(registry).unwrap(); + + let value_ptr = block + .alloca1(context, location, value.r#type(), layout.align()) + .unwrap(); + block.store(context, location, value_ptr, *value).unwrap(); + + trace_dump + .build_state( + context, + module, + block, + var_id, + value_type_id, + value_ptr, + location, + ) + .unwrap(); + } + + trace_dump + .build_push(context, module, block, statement_idx, location) + .unwrap(); +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use cairo_lang_sierra::{program::Program, program_registry::ProgramRegistry}; + use pretty_assertions_sorted::assert_eq_sorted; + use rstest::{fixture, rstest}; + use sierra_emu::{starknet::StubSyscallHandler, VirtualMachine}; + + use crate::{ + context::NativeContext, + executor::AotNativeExecutor, + metadata::trace_dump::{ + trace_dump_runtime::{TraceDump, TRACE_DUMP}, + TraceBinding, + }, + utils::test::load_cairo, + OptLevel, + }; + + #[fixture] + fn program() -> Program { + let (_, program) = load_cairo! { + use core::felt252; + + fn main() -> felt252 { + let n = 10; + let result = fib(1, 1, n); + result + } + + fn fib(a: felt252, b: felt252, n: felt252) -> felt252 { + match n { + 0 => a, + _ => fib(b, a + b, n - 1), + } + } + }; + program + } + + #[rstest] + fn test_program(program: Program) { + let entrypoint_function = &program + .funcs + .iter() + .find(|x| { + x.id.debug_name + .as_ref() + .map(|x| x.contains("main")) + .unwrap_or_default() + }) + .unwrap() + .clone(); + + let native_context = NativeContext::new(); + let module = native_context + .compile(&program, false, Some(Default::default())) + .expect("failed to compile context"); + let executor = AotNativeExecutor::from_native_module(module, OptLevel::default()).unwrap(); + + if let Some(trace_id) = executor.find_symbol_ptr(TraceBinding::TraceId.symbol()) { + let trace_id = trace_id.cast::(); + unsafe { *trace_id = 0 }; + } + + TRACE_DUMP + .lock() + .unwrap() + .insert(0, TraceDump::new(ProgramRegistry::new(&program).unwrap())); + + executor + .invoke_dynamic(&entrypoint_function.id, &[], Some(u64::MAX)) + .unwrap(); + + let native_trace = TRACE_DUMP + .lock() + .unwrap() + .values() + .next() + .unwrap() + .trace + .clone(); + + let mut vm = VirtualMachine::new(Arc::new(program)); + + let initial_gas = u64::MAX; + let args = []; + vm.call_program(entrypoint_function, initial_gas, args.into_iter()); + + let syscall_handler = &mut StubSyscallHandler::default(); + let emu_trace = vm.run_with_trace(syscall_handler); + + assert_eq_sorted!(emu_trace, native_trace); + } +} From 6268f57bbb4924ad0a04a8bf153af12b8a398f93 Mon Sep 17 00:00:00 2001 From: Franco Giachetta Date: Wed, 7 May 2025 12:50:40 -0300 Subject: [PATCH 04/28] [Sierra-Emu] Implement missing syscall and nullable libfuncs (#1205) * Add sierra-emu as a debug utility * Change working dir in sierra-emu job * Remove repeated lines in sierra-emu gitignore * Exclude debug_utils from coverage job * remove some todos from value and implement Coupon libfunc * start adding corelib test * run tests from corelib * remove unwanted file * remove not related things * get return_value from trace * replace ids for debug info * add common folder for integration tests * fix tests * remove todo * ignore test for now * filter ignored tests * Update debug_utils/sierra-emu/tests/common/mod.rs Co-authored-by: Julian Gonzalez Calderon * run ignored test from the ci * better ignoring * remove debug_name in enums * refactor * remove unused dependency * More expressive naming for the workflow Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> * reviews * reviews * fmt * fmt ci * revert last commit * fmt * accidentally removed rayon usage * avoid map's short-circuiting * catch panics the avoid loosing the test name * log ignored tests * clippy * Add "(WIP)" to corelib's test job Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> * print progress as the test runs * fmt * format makefile Co-authored-by: Julian Gonzalez Calderon * fix name test in ci * add dependencies removed in merge * dependencies * clippy * remove unused import * better indent in makefile * uppercase tests summary comment for better sight * add starknet missing libfuncs * cargo.lock * add null, nullable_from_box and match_nullable libfuncs * clippyt * add forward_snapshot libfunc + fix is() for nullable * refactor syscall handler * add some felt types to match * remove type_to_name() call in is() function --------- Co-authored-by: gabrielbosio Co-authored-by: Julian Gonzalez Calderon Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> --- debug_utils/sierra-emu/src/lib.rs | 6 + debug_utils/sierra-emu/src/starknet.rs | 19 ++ debug_utils/sierra-emu/src/value.rs | 6 +- debug_utils/sierra-emu/src/vm.rs | 3 +- debug_utils/sierra-emu/src/vm/nullable.rs | 74 +++++ debug_utils/sierra-emu/src/vm/starknet.rs | 327 ++++++++++++++++----- debug_utils/sierra-emu/tests/common/mod.rs | 2 +- 7 files changed, 365 insertions(+), 72 deletions(-) create mode 100644 debug_utils/sierra-emu/src/vm/nullable.rs diff --git a/debug_utils/sierra-emu/src/lib.rs b/debug_utils/sierra-emu/src/lib.rs index c0579403c..2a55372fa 100644 --- a/debug_utils/sierra-emu/src/lib.rs +++ b/debug_utils/sierra-emu/src/lib.rs @@ -123,6 +123,12 @@ pub fn run_program( ) => Value::Unit, CoreTypeConcrete::Starknet(inner) => match inner { StarknetTypeConcrete::System(_) => Value::Unit, + StarknetTypeConcrete::ClassHash(_) + | StarknetTypeConcrete::ContractAddress(_) + | StarknetTypeConcrete::StorageBaseAddress(_) + | StarknetTypeConcrete::StorageAddress(_) => { + Value::parse_felt(&iter.next().unwrap()) + } _ => todo!(), }, _ => todo!(), diff --git a/debug_utils/sierra-emu/src/starknet.rs b/debug_utils/sierra-emu/src/starknet.rs index 1673eab28..5423a3af4 100644 --- a/debug_utils/sierra-emu/src/starknet.rs +++ b/debug_utils/sierra-emu/src/starknet.rs @@ -169,6 +169,25 @@ pub trait StarknetSyscallHandler { remaining_gas: &mut u64, ) -> SyscallResult<[u32; 8]>; + fn meta_tx_v0( + &mut self, + _address: Felt, + _entry_point_selector: Felt, + _calldata: Vec, + _signature: Vec, + _remaining_gas: &mut u64, + ) -> SyscallResult> { + unimplemented!(); + } + + fn get_class_hash_at( + &mut self, + _contract_address: Felt, + _remaining_gas: &mut u64, + ) -> SyscallResult { + unimplemented!() + } + fn cheatcode(&mut self, _selector: Felt, _input: Vec) -> Vec { unimplemented!() } diff --git a/debug_utils/sierra-emu/src/value.rs b/debug_utils/sierra-emu/src/value.rs index aea97667c..09b924b8d 100644 --- a/debug_utils/sierra-emu/src/value.rs +++ b/debug_utils/sierra-emu/src/value.rs @@ -73,6 +73,7 @@ pub enum Value { ty: ConcreteTypeId, }, BuiltinCosts(BuiltinCosts), + Null, Unit, } @@ -99,6 +100,7 @@ impl Value { .map(|member| Value::default_for_type(registry, member)) .collect(), ), + CoreTypeConcrete::Nullable(info) => Value::default_for_type(registry, &info.ty), x => panic!("type {:?} has no default value implementation", x.info()), } } @@ -197,7 +199,9 @@ impl Value { CoreTypeConcrete::Uint128MulGuarantee(_) => matches!(self, Self::Unit), CoreTypeConcrete::Sint16(_) => matches!(self, Self::I16(_)), CoreTypeConcrete::Sint64(_) => matches!(self, Self::I64(_)), - CoreTypeConcrete::Nullable(info) => self.is(registry, &info.ty), + CoreTypeConcrete::Nullable(info) => { + matches!(self, Value::Null) || self.is(registry, &info.ty) + } CoreTypeConcrete::Uninitialized(_) => matches!(self, Self::Uninitialized { .. }), CoreTypeConcrete::Felt252DictEntry(info) => { matches!(self, Self::FeltDictEntry { ty, .. } if *ty == info.ty) diff --git a/debug_utils/sierra-emu/src/vm.rs b/debug_utils/sierra-emu/src/vm.rs index 56caa04e6..13ffe92d2 100644 --- a/debug_utils/sierra-emu/src/vm.rs +++ b/debug_utils/sierra-emu/src/vm.rs @@ -51,6 +51,7 @@ mod gas; mod int128; mod jump; mod mem; +mod nullable; mod pedersen; mod poseidon; mod snapshot_take; @@ -492,7 +493,7 @@ fn eval<'a>( self::gas::eval(registry, selector, args, gas, *statement_idx, builtin_costs) } CoreConcreteLibfunc::Mem(selector) => self::mem::eval(registry, selector, args), - CoreConcreteLibfunc::Nullable(_) => todo!(), + CoreConcreteLibfunc::Nullable(selector) => self::nullable::eval(registry, selector, args), CoreConcreteLibfunc::Pedersen(selector) => self::pedersen::eval(registry, selector, args), CoreConcreteLibfunc::Poseidon(selector) => self::poseidon::eval(registry, selector, args), CoreConcreteLibfunc::Sint128(selector) => self::int128::eval(registry, selector, args), diff --git a/debug_utils/sierra-emu/src/vm/nullable.rs b/debug_utils/sierra-emu/src/vm/nullable.rs new file mode 100644 index 000000000..536689b45 --- /dev/null +++ b/debug_utils/sierra-emu/src/vm/nullable.rs @@ -0,0 +1,74 @@ +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType}, + lib_func::{SignatureAndTypeConcreteLibfunc, SignatureOnlyConcreteLibfunc}, + nullable::NullableConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use smallvec::smallvec; + +use crate::Value; + +use super::EvalAction; + +pub fn eval( + registry: &ProgramRegistry, + selector: &NullableConcreteLibfunc, + args: Vec, +) -> EvalAction { + match selector { + NullableConcreteLibfunc::Null(info) => eval_null(registry, info, args), + NullableConcreteLibfunc::NullableFromBox(info) => { + eval_nullable_from_box(registry, info, args) + } + NullableConcreteLibfunc::MatchNullable(info) => eval_match_nullable(registry, info, args), + NullableConcreteLibfunc::ForwardSnapshot(info) => { + eval_forward_snapshot(registry, info, args) + } + } +} + +fn eval_null( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [] = args.try_into().unwrap(); + + EvalAction::NormalBranch(0, smallvec![Value::Null]) +} + +fn eval_nullable_from_box( + _registry: &ProgramRegistry, + _info: &SignatureAndTypeConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [value]: [Value; 1] = args.try_into().unwrap(); + + EvalAction::NormalBranch(0, smallvec![value]) +} + +fn eval_match_nullable( + _registry: &ProgramRegistry, + _info: &SignatureAndTypeConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [value]: [Value; 1] = args.try_into().unwrap(); + + if matches!(value, Value::Null) { + EvalAction::NormalBranch(0, smallvec![]) + } else { + EvalAction::NormalBranch(1, smallvec![value]) + } +} + +fn eval_forward_snapshot( + _registry: &ProgramRegistry, + _info: &SignatureAndTypeConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [value]: [Value; 1] = args.try_into().unwrap(); + + EvalAction::NormalBranch(0, smallvec![value]) +} diff --git a/debug_utils/sierra-emu/src/vm/starknet.rs b/debug_utils/sierra-emu/src/vm/starknet.rs index 4d039ebd9..1ddf45a22 100644 --- a/debug_utils/sierra-emu/src/vm/starknet.rs +++ b/debug_utils/sierra-emu/src/vm/starknet.rs @@ -1,6 +1,6 @@ use super::EvalAction; use crate::{ - starknet::{Secp256r1Point, StarknetSyscallHandler, U256}, + starknet::{Secp256k1Point, Secp256r1Point, StarknetSyscallHandler, U256}, Value, }; use cairo_lang_sierra::{ @@ -17,6 +17,11 @@ use num_traits::One; use smallvec::smallvec; use starknet_types_core::felt::Felt; +enum SecpPointType { + K1, + R1, +} + pub fn eval( registry: &ProgramRegistry, selector: &StarknetConcreteLibfunc, @@ -101,83 +106,38 @@ pub fn eval( StarknetConcreteLibfunc::SendMessageToL1(info) => { eval_send_message_to_l1(registry, info, args, syscall_handler) } - StarknetConcreteLibfunc::Testing(_info) => todo!(), + StarknetConcreteLibfunc::Testing(_) => todo!(), StarknetConcreteLibfunc::Secp256(info) => match info { cairo_lang_sierra::extensions::starknet::secp256::Secp256ConcreteLibfunc::K1(info) => { match info { - cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::New(_) => todo!(), - cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::Add(_) => todo!(), - cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::Mul(_) => todo!(), - cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::GetPointFromX(_) => todo!(), - cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::GetXy(_) => todo!(), + cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::New(info) => eval_secp256_new(registry, info, args, syscall_handler, SecpPointType::K1), + cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::Add(info) => eval_secp256_add(registry, info, args, syscall_handler, SecpPointType::K1), + cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::Mul(info) => eval_secp256_mul(registry, info, args, syscall_handler, SecpPointType::K1), + cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::GetPointFromX(info) => eval_secp256_get_point_from_x(registry, info, args, syscall_handler, SecpPointType::K1), + cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::GetXy(info) => eval_secp256_get_xy(registry, info, args, syscall_handler, SecpPointType::K1), } } cairo_lang_sierra::extensions::starknet::secp256::Secp256ConcreteLibfunc::R1(info) => { match info { - cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::New(info) => eval_secp_r_new(registry, info, args, syscall_handler), - cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::Add(info) => eval_secp_r_add(registry, info, args, syscall_handler), - cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::Mul(info) => eval_secp_r_mul(registry, info, args, syscall_handler), - cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::GetPointFromX(info) => eval_secp_r_get_point_from_x(registry, info, args, syscall_handler), - cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::GetXy(info) => secp_r_get_xy(registry, info, args, syscall_handler), + cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::New(info) => eval_secp256_new(registry, info, args, syscall_handler, SecpPointType::R1), + cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::Add(info) => eval_secp256_add(registry, info, args, syscall_handler, SecpPointType::R1), + cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::Mul(info) => eval_secp256_mul(registry, info, args, syscall_handler, SecpPointType::R1), + cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::GetPointFromX(info) => eval_secp256_get_point_from_x(registry, info, args, syscall_handler, SecpPointType::R1), + cairo_lang_sierra::extensions::starknet::secp256::Secp256OpConcreteLibfunc::GetXy(info) => eval_secp256_get_xy(registry, info, args, syscall_handler, SecpPointType::R1), } } }, - StarknetConcreteLibfunc::GetClassHashAt(_) => todo!(), - StarknetConcreteLibfunc::MetaTxV0(_) => todo!(), - } -} - -fn eval_secp_r_add( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, - syscall_handler: &mut impl StarknetSyscallHandler, -) -> EvalAction { - let [Value::U64(mut gas), system @ Value::Unit, x, y]: [Value; 4] = args.try_into().unwrap() - else { - panic!() - }; - - let x = Secp256r1Point::from_value(x); - let y = Secp256r1Point::from_value(y); - - match syscall_handler.secp256r1_add(x, y, &mut gas) { - Ok(x) => EvalAction::NormalBranch(0, smallvec![Value::U64(gas), system, x.into_value()]), - Err(r) => { - let r = Value::Struct(r.into_iter().map(Value::Felt).collect::>()); - EvalAction::NormalBranch(1, smallvec![Value::U64(gas), system, r]) - } - } -} - -fn eval_secp_r_mul( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, - syscall_handler: &mut impl StarknetSyscallHandler, -) -> EvalAction { - let [Value::U64(mut gas), system @ Value::Unit, x, n]: [Value; 4] = args.try_into().unwrap() - else { - panic!() - }; - - let x = Secp256r1Point::from_value(x); - let n = U256::from_value(n); - - match syscall_handler.secp256r1_mul(x, n, &mut gas) { - Ok(x) => EvalAction::NormalBranch(0, smallvec![Value::U64(gas), system, x.into_value()]), - Err(r) => { - let r = Value::Struct(r.into_iter().map(Value::Felt).collect::>()); - EvalAction::NormalBranch(1, smallvec![Value::U64(gas), system, r]) - } + StarknetConcreteLibfunc::GetClassHashAt(info) => eval_get_class_hash_at(registry, info, args, syscall_handler), + StarknetConcreteLibfunc::MetaTxV0(info) => eval_meta_tx_v0(registry, info, args, syscall_handler), } } -fn eval_secp_r_new( +fn eval_secp256_new( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, syscall_handler: &mut impl StarknetSyscallHandler, + secp_type: SecpPointType, ) -> EvalAction { let [Value::U64(mut gas), system @ Value::Unit, Value::Struct(x), Value::Struct(y)]: [Value; 4] = args.try_into().unwrap() @@ -192,14 +152,23 @@ fn eval_secp_r_new( let Value::U128(y_hi) = y[1] else { panic!() }; let y = U256 { lo: y_lo, hi: y_hi }; - match syscall_handler.secp256r1_new(x, y, &mut gas) { + let syscall_result = match secp_type { + SecpPointType::K1 => syscall_handler + .secp256k1_new(x, y, &mut gas) + .map(|res| res.map(|op| op.into_value())), + SecpPointType::R1 => syscall_handler + .secp256r1_new(x, y, &mut gas) + .map(|res| res.map(|op| op.into_value())), + }; + + match syscall_result { Ok(p) => { let enum_ty = &info.branch_signatures()[0].vars[2].ty; let value = match p { Some(p) => Value::Enum { self_ty: enum_ty.clone(), index: 0, - payload: Box::new(p.into_value()), + payload: Box::new(p), }, None => Value::Enum { self_ty: enum_ty.clone(), @@ -238,11 +207,92 @@ fn eval_secp_r_new( } } -fn eval_secp_r_get_point_from_x( +fn eval_secp256_add( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, + secp_type: SecpPointType, +) -> EvalAction { + let [Value::U64(mut gas), system @ Value::Unit, x, y]: [Value; 4] = args.try_into().unwrap() + else { + panic!() + }; + + let syscall_result = match secp_type { + SecpPointType::K1 => { + let x = Secp256k1Point::from_value(x); + let y = Secp256k1Point::from_value(y); + + syscall_handler + .secp256k1_add(x, y, &mut gas) + .map(|res| res.into_value()) + } + SecpPointType::R1 => { + let x = Secp256r1Point::from_value(x); + let y = Secp256r1Point::from_value(y); + + syscall_handler + .secp256r1_add(x, y, &mut gas) + .map(|res| res.into_value()) + } + }; + + match syscall_result { + Ok(x) => EvalAction::NormalBranch(0, smallvec![Value::U64(gas), system, x]), + Err(r) => { + let r = Value::Struct(r.into_iter().map(Value::Felt).collect::>()); + EvalAction::NormalBranch(1, smallvec![Value::U64(gas), system, r]) + } + } +} + +fn eval_secp256_mul( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, + secp_type: SecpPointType, +) -> EvalAction { + let [Value::U64(mut gas), system @ Value::Unit, x, n]: [Value; 4] = args.try_into().unwrap() + else { + panic!() + }; + + let n = U256::from_value(n); + + let syscall_result = match secp_type { + SecpPointType::K1 => { + let x = Secp256k1Point::from_value(x); + + syscall_handler + .secp256k1_mul(x, n, &mut gas) + .map(|res| res.into_value()) + } + SecpPointType::R1 => { + let x = Secp256r1Point::from_value(x); + + syscall_handler + .secp256r1_mul(x, n, &mut gas) + .map(|res| res.into_value()) + } + }; + + match syscall_result { + Ok(x) => EvalAction::NormalBranch(0, smallvec![Value::U64(gas), system, x]), + Err(r) => { + let r = Value::Struct(r.into_iter().map(Value::Felt).collect::>()); + EvalAction::NormalBranch(1, smallvec![Value::U64(gas), system, r]) + } + } +} + +fn eval_secp256_get_point_from_x( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, syscall_handler: &mut impl StarknetSyscallHandler, + secp_type: SecpPointType, ) -> EvalAction { let [Value::U64(mut gas), system @ Value::Unit, Value::Struct(x), Value::Enum { index: y_parity, .. @@ -256,14 +306,23 @@ fn eval_secp_r_get_point_from_x( let x = U256 { lo: x_lo, hi: x_hi }; let y_parity = y_parity.is_one(); - match syscall_handler.secp256r1_get_point_from_x(x, y_parity, &mut gas) { + let syscall_result = match secp_type { + SecpPointType::K1 => syscall_handler + .secp256k1_get_point_from_x(x, y_parity, &mut gas) + .map(|res| res.map(|op| op.into_value())), + SecpPointType::R1 => syscall_handler + .secp256r1_get_point_from_x(x, y_parity, &mut gas) + .map(|res| res.map(|op| op.into_value())), + }; + + match syscall_result { Ok(p) => { let enum_ty = &info.branch_signatures()[0].vars[2].ty; let value = match p { Some(p) => Value::Enum { self_ty: enum_ty.clone(), index: 0, - payload: Box::new(p.into_value()), + payload: Box::new(p), }, None => Value::Enum { self_ty: enum_ty.clone(), @@ -287,6 +346,7 @@ fn eval_secp_r_get_point_from_x( }; let value = payload.into_iter().map(Value::Felt).collect::>(); + EvalAction::NormalBranch( 1, smallvec![ @@ -302,11 +362,12 @@ fn eval_secp_r_get_point_from_x( } } -fn secp_r_get_xy( +fn eval_secp256_get_xy( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, syscall_handler: &mut impl StarknetSyscallHandler, + secp_type: SecpPointType, ) -> EvalAction { let [Value::U64(mut gas), system @ Value::Unit, secp_value]: [Value; 3] = args.try_into().unwrap() @@ -314,9 +375,24 @@ fn secp_r_get_xy( panic!() }; - let secp_value = Secp256r1Point::from_value(secp_value); + let syscall_result = match secp_type { + SecpPointType::K1 => { + let secp_value = Secp256k1Point::from_value(secp_value); + + syscall_handler + .secp256k1_get_xy(secp_value, &mut gas) + .map(|res| (res.0, res.1)) + } + SecpPointType::R1 => { + let secp_value = Secp256r1Point::from_value(secp_value); + + syscall_handler + .secp256r1_get_xy(secp_value, &mut gas) + .map(|res| (res.0, res.1)) + } + }; - match syscall_handler.secp256r1_get_xy(secp_value, &mut gas) { + match syscall_result { Ok(payload) => { let (x, y) = (payload.0.into_value(), payload.1.into_value()); EvalAction::NormalBranch(0, smallvec![Value::U64(gas), system, x, y]) @@ -1263,3 +1339,116 @@ fn eval_sha256_process_block( ), } } + +fn eval_get_class_hash_at( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, + args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, +) -> EvalAction { + let [Value::U64(mut gas), system @ Value::Unit, Value::Felt(contract_address)]: [Value; 3] = + args.try_into().unwrap() + else { + panic!() + }; + + // get felt type from the error branch array + let felt_ty = { + match registry + .get_type(&info.branch_signatures()[1].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Array(info) => info.ty.clone(), + _ => unreachable!(), + } + }; + + match syscall_handler.get_class_hash_at(contract_address, &mut gas) { + Ok(payload) => { + EvalAction::NormalBranch(0, smallvec![Value::U64(gas), system, Value::Felt(payload)]) + } + Err(payload) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U64(gas), + system, + Value::Array { + ty: felt_ty, + data: payload.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + } +} + +fn eval_meta_tx_v0( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, + args: Vec, + syscall_handler: &mut impl StarknetSyscallHandler, +) -> EvalAction { + let [Value::U64(mut gas), system @ Value::Unit, Value::Felt(address), Value::Felt(entry_point_selector), Value::Array { + ty: calldata_ty, + data: calldata, + }, Value::Array { + ty: signature_ty, + data: signature, + }]: [Value; 6] = args.try_into().unwrap() + else { + panic!() + }; + + assert_eq!(info.signature.param_signatures[4].ty, calldata_ty); + assert_eq!(info.signature.param_signatures[5].ty, signature_ty); + + let calldata = calldata + .into_iter() + .map(|x| match x { + Value::Felt(x) => x, + _ => unreachable!(), + }) + .collect(); + + let signature = signature + .into_iter() + .map(|x| match x { + Value::Felt(x) => x, + _ => unreachable!(), + }) + .collect(); + + let felt_ty = { + match registry + .get_type(&info.branch_signatures()[0].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Array(info) => info.ty.clone(), + _ => unreachable!(), + } + }; + + match syscall_handler.meta_tx_v0(address, entry_point_selector, calldata, signature, &mut gas) { + Ok(res) => EvalAction::NormalBranch( + 0, + smallvec![ + Value::U64(gas), + system, + Value::Array { + ty: felt_ty, + data: res.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + Err(err) => EvalAction::NormalBranch( + 1, + smallvec![ + Value::U64(gas), + system, + Value::Array { + ty: felt_ty, + data: err.into_iter().map(Value::Felt).collect::>(), + } + ], + ), + } +} diff --git a/debug_utils/sierra-emu/tests/common/mod.rs b/debug_utils/sierra-emu/tests/common/mod.rs index fa77478ad..8acea99e7 100644 --- a/debug_utils/sierra-emu/tests/common/mod.rs +++ b/debug_utils/sierra-emu/tests/common/mod.rs @@ -83,6 +83,6 @@ pub fn value_to_felt(value: &Value) -> Vec { Value::U64(x) => vec![(*x).into()], Value::U128(x) => vec![(*x).into()], Value::U256(x, y) => vec![(*x).into(), (*y).into()], - Value::Unit | Value::Uninitialized { .. } => vec![0.into()], + Value::Unit | Value::Null | Value::Uninitialized { .. } => vec![0.into()], } } From 9fab389eb278404b9bfdc6ad32d956458e7e498e Mon Sep 17 00:00:00 2001 From: Franco Giachetta Date: Wed, 7 May 2025 16:38:53 -0300 Subject: [PATCH 05/28] [Sierra-Emu] implement some todos from `const` and `cast` libfuncs (#1191) * Add sierra-emu as a debug utility * Change working dir in sierra-emu job * Remove repeated lines in sierra-emu gitignore * Exclude debug_utils from coverage job * remove some todos from value and implement Coupon libfunc * start adding corelib test * run tests from corelib * remove unwanted file * remove not related things * get return_value from trace * replace ids for debug info * add common folder for integration tests * fix tests * remove todo * ignore test for now * add enum in const::inner * remove todos and fix some failures * filter ignored tests * Update debug_utils/sierra-emu/tests/common/mod.rs Co-authored-by: Julian Gonzalez Calderon * run ignored test from the ci * better ignoring * ignore test * fmt * remove debug_name in enums * refactor * remove unused dependency * More expressive naming for the workflow Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> * reviews * reviews * fmt * fmt ci * revert last commit * fmt * accidentally removed rayon usage * avoid map's short-circuiting * catch panics the avoid loosing the test name * log ignored tests * clippy * Add "(WIP)" to corelib's test job Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> * print progress as the test runs * fmt * format makefile Co-authored-by: Julian Gonzalez Calderon * fix name test in ci * add dependencies removed in merge * dependencies * clippy * remove unused import * better indent in makefile * uppercase tests summary comment for better sight * fix clippy * clippy * remove unnecesary pubs * better docs * better panic message in get_numberic_args_as_bigints() Co-authored-by: Julian Gonzalez Calderon * reviews * remove unnecessary comment Co-authored-by: Julian Gonzalez Calderon * use slices in get_numeric_args_as_bigints * fmt * remove unnecesary to_vec * fmt * use iter instead of into_iter * reviews changes * better error message * fix typo --------- Co-authored-by: gabrielbosio Co-authored-by: Julian Gonzalez Calderon Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> --- debug_utils/sierra-emu/src/lib.rs | 1 + debug_utils/sierra-emu/src/utils.rs | 55 ++++++++++++++ debug_utils/sierra-emu/src/vm/bounded_int.rs | 33 ++------- debug_utils/sierra-emu/src/vm/cast.rs | 77 +++++--------------- debug_utils/sierra-emu/src/vm/const.rs | 61 +++++++++++++--- 5 files changed, 127 insertions(+), 100 deletions(-) create mode 100644 debug_utils/sierra-emu/src/utils.rs diff --git a/debug_utils/sierra-emu/src/lib.rs b/debug_utils/sierra-emu/src/lib.rs index 2a55372fa..9aab69f83 100644 --- a/debug_utils/sierra-emu/src/lib.rs +++ b/debug_utils/sierra-emu/src/lib.rs @@ -19,6 +19,7 @@ mod dump; mod gas; pub mod starknet; mod test_utils; +mod utils; mod value; mod vm; diff --git a/debug_utils/sierra-emu/src/utils.rs b/debug_utils/sierra-emu/src/utils.rs new file mode 100644 index 000000000..fe618ed90 --- /dev/null +++ b/debug_utils/sierra-emu/src/utils.rs @@ -0,0 +1,55 @@ +use cairo_lang_sierra::{ + extensions::core::{CoreLibfunc, CoreType, CoreTypeConcrete}, + program_registry::ProgramRegistry, +}; +use num_bigint::BigInt; +use num_traits::ToPrimitive; + +use crate::Value; + +/// Receives a vector of values, filters any which is non numeric and returns a `Vec` +/// Useful when a binary operation takes generic values (like with bounded ints). +pub fn get_numeric_args_as_bigints(args: &[Value]) -> Vec { + args.iter() + .map(|v| match v { + Value::BoundedInt { value, .. } => value.to_owned(), + Value::I8(value) => BigInt::from(*value), + Value::I16(value) => BigInt::from(*value), + Value::I32(value) => BigInt::from(*value), + Value::I64(value) => BigInt::from(*value), + Value::I128(value) => BigInt::from(*value), + Value::U8(value) => BigInt::from(*value), + Value::U16(value) => BigInt::from(*value), + Value::U32(value) => BigInt::from(*value), + Value::U64(value) => BigInt::from(*value), + Value::U128(value) => BigInt::from(*value), + Value::Felt(value) => value.to_bigint(), + Value::Bytes31(value) => value.to_bigint(), + value => panic!("argument should be an integer: {:?}", value), + }) + .collect() +} + +pub fn get_value_from_integer( + registry: &ProgramRegistry, + ty: &CoreTypeConcrete, + value: BigInt, +) -> Value { + match ty { + CoreTypeConcrete::NonZero(info) => { + let ty = registry.get_type(&info.ty).unwrap(); + get_value_from_integer(registry, ty, value) + } + CoreTypeConcrete::Sint8(_) => Value::I8(value.to_i8().unwrap()), + CoreTypeConcrete::Sint16(_) => Value::I16(value.to_i16().unwrap()), + CoreTypeConcrete::Sint32(_) => Value::I32(value.to_i32().unwrap()), + CoreTypeConcrete::Sint64(_) => Value::I64(value.to_i64().unwrap()), + CoreTypeConcrete::Sint128(_) => Value::I128(value.to_i128().unwrap()), + CoreTypeConcrete::Uint8(_) => Value::U8(value.to_u8().unwrap()), + CoreTypeConcrete::Uint16(_) => Value::U16(value.to_u16().unwrap()), + CoreTypeConcrete::Uint32(_) => Value::U32(value.to_u32().unwrap()), + CoreTypeConcrete::Uint64(_) => Value::U64(value.to_u64().unwrap()), + CoreTypeConcrete::Uint128(_) => Value::U128(value.to_u128().unwrap()), + _ => panic!("cannot get integer value for a non-integer type"), + } +} diff --git a/debug_utils/sierra-emu/src/vm/bounded_int.rs b/debug_utils/sierra-emu/src/vm/bounded_int.rs index 675d2a6f1..55d4955d5 100644 --- a/debug_utils/sierra-emu/src/vm/bounded_int.rs +++ b/debug_utils/sierra-emu/src/vm/bounded_int.rs @@ -1,5 +1,5 @@ use super::EvalAction; -use crate::Value; +use crate::{utils::get_numeric_args_as_bigints, Value}; use cairo_lang_sierra::{ extensions::{ bounded_int::{ @@ -15,29 +15,6 @@ use cairo_lang_sierra::{ use num_bigint::BigInt; use smallvec::smallvec; -// All binary operations have generic arguments, this function takes their values -// and builds bigints out of them (since Bigints are used to represent bounded ints' values) -fn get_numberic_args_as_bigints(args: Vec) -> Vec { - args.into_iter() - // remove builtins, if any - .filter(|v| !matches!(v, Value::Unit)) - .map(|v| match v { - Value::BoundedInt { value, .. } => value, - Value::I8(value) => BigInt::from(value), - Value::I16(value) => BigInt::from(value), - Value::I32(value) => BigInt::from(value), - Value::I64(value) => BigInt::from(value), - Value::I128(value) => BigInt::from(value), - Value::U8(value) => BigInt::from(value), - Value::U16(value) => BigInt::from(value), - Value::U32(value) => BigInt::from(value), - Value::U64(value) => BigInt::from(value), - Value::U128(value) => BigInt::from(value), - _ => panic!("Not a numeric value"), - }) - .collect() -} - pub fn eval( registry: &ProgramRegistry, selector: &BoundedIntConcreteLibfunc, @@ -62,7 +39,7 @@ pub fn eval_add( info: &SignatureOnlyConcreteLibfunc, args: Vec, ) -> EvalAction { - let [lhs, rhs]: [BigInt; 2] = get_numberic_args_as_bigints(args).try_into().unwrap(); + let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args).try_into().unwrap(); let range = match registry .get_type(&info.signature.branch_signatures[0].vars[0].ty) @@ -91,7 +68,7 @@ pub fn eval_sub( info: &SignatureOnlyConcreteLibfunc, args: Vec, ) -> EvalAction { - let [lhs, rhs]: [BigInt; 2] = get_numberic_args_as_bigints(args).try_into().unwrap(); + let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args).try_into().unwrap(); let range = match registry .get_type(&info.signature.branch_signatures[0].vars[0].ty) @@ -120,7 +97,7 @@ pub fn eval_mul( info: &SignatureOnlyConcreteLibfunc, args: Vec, ) -> EvalAction { - let [lhs, rhs]: [BigInt; 2] = get_numberic_args_as_bigints(args).try_into().unwrap(); + let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args).try_into().unwrap(); let range = match registry .get_type(&info.signature.branch_signatures[0].vars[0].ty) @@ -149,7 +126,7 @@ pub fn eval_div_rem( info: &BoundedIntDivRemConcreteLibfunc, args: Vec, ) -> EvalAction { - let [lhs, rhs]: [BigInt; 2] = get_numberic_args_as_bigints(args).try_into().unwrap(); + let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args).try_into().unwrap(); let quo = &lhs / &rhs; let rem = lhs % rhs; diff --git a/debug_utils/sierra-emu/src/vm/cast.rs b/debug_utils/sierra-emu/src/vm/cast.rs index fd1f15f6f..78330a5fc 100644 --- a/debug_utils/sierra-emu/src/vm/cast.rs +++ b/debug_utils/sierra-emu/src/vm/cast.rs @@ -1,15 +1,17 @@ use super::EvalAction; -use crate::Value; +use crate::{ + utils::{get_numeric_args_as_bigints, get_value_from_integer}, + Value, +}; use cairo_lang_sierra::{ extensions::{ casts::{CastConcreteLibfunc, DowncastConcreteLibfunc}, - core::{CoreLibfunc, CoreType, CoreTypeConcrete}, + core::{CoreLibfunc, CoreType}, lib_func::SignatureOnlyConcreteLibfunc, - ConcreteType, + ConcreteLibfunc, }, program_registry::ProgramRegistry, }; -use num_bigint::BigInt; use smallvec::smallvec; pub fn eval( @@ -23,85 +25,40 @@ pub fn eval( } } -pub fn eval_downcast( +fn eval_downcast( registry: &ProgramRegistry, info: &DowncastConcreteLibfunc, args: Vec, ) -> EvalAction { - let [range_check @ Value::Unit, value]: [Value; 2] = args.try_into().unwrap() else { + let range_check @ Value::Unit: Value = args[0].clone() else { panic!() }; + let [value] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); - let value = match value { - Value::BoundedInt { value, .. } => value, - Value::U128(value) => BigInt::from(value), - Value::U64(value) => BigInt::from(value), - Value::U32(value) => BigInt::from(value), - Value::U16(value) => BigInt::from(value), - Value::U8(value) => BigInt::from(value), - _ => todo!(), - }; - + let int_ty = registry.get_type(&info.to_ty).unwrap(); let range = info.to_range.lower.clone()..info.to_range.upper.clone(); if range.contains(&value) { EvalAction::NormalBranch( 0, - smallvec![ - range_check, - match registry.get_type(&info.to_ty).unwrap() { - CoreTypeConcrete::Sint8(_) => Value::I8(value.try_into().unwrap()), - CoreTypeConcrete::Sint128(_) => Value::I128(value.try_into().unwrap()), - CoreTypeConcrete::Uint8(_) => Value::U8(value.try_into().unwrap()), - CoreTypeConcrete::Uint16(_) => Value::U16(value.try_into().unwrap()), - CoreTypeConcrete::Uint32(_) => Value::U32(value.try_into().unwrap()), - CoreTypeConcrete::Uint64(_) => Value::U64(value.try_into().unwrap()), - CoreTypeConcrete::Uint128(_) => Value::U128(value.try_into().unwrap()), - CoreTypeConcrete::BoundedInt(_) => Value::BoundedInt { range, value }, - x => todo!("{:?}", x.info()), - } - ], + smallvec![range_check, get_value_from_integer(registry, int_ty, value)], ) } else { EvalAction::NormalBranch(1, smallvec![range_check]) } } -pub fn eval_upcast( +fn eval_upcast( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, ) -> EvalAction { - let [value] = args.try_into().unwrap(); - - let value = match value { - Value::BoundedInt { value, .. } => value, - Value::U128(value) => BigInt::from(value), - Value::U64(value) => BigInt::from(value), - Value::U32(value) => BigInt::from(value), - Value::U16(value) => BigInt::from(value), - Value::U8(value) => BigInt::from(value), - _ => todo!(), - }; + let [value] = get_numeric_args_as_bigints(&args).try_into().unwrap(); + let int_ty = registry + .get_type(&info.branch_signatures()[0].vars[0].ty) + .unwrap(); EvalAction::NormalBranch( 0, - smallvec![match registry - .get_type(&info.signature.branch_signatures[0].vars[0].ty) - .unwrap() - { - CoreTypeConcrete::Sint8(_) => Value::I8(value.try_into().unwrap()), - CoreTypeConcrete::Sint32(_) => Value::I32(value.try_into().unwrap()), - CoreTypeConcrete::Sint128(_) => Value::I128(value.try_into().unwrap()), - CoreTypeConcrete::Uint8(_) => Value::U8(value.try_into().unwrap()), - CoreTypeConcrete::Uint16(_) => Value::U16(value.try_into().unwrap()), - CoreTypeConcrete::Uint32(_) => Value::U32(value.try_into().unwrap()), - CoreTypeConcrete::Uint64(_) => Value::U64(value.try_into().unwrap()), - CoreTypeConcrete::Uint128(_) => Value::U128(value.try_into().unwrap()), - CoreTypeConcrete::Felt252(_) => Value::Felt(value.into()), - CoreTypeConcrete::Sint16(_) => todo!("Sint16"), - CoreTypeConcrete::Sint64(_) => todo!("Sint64"), - CoreTypeConcrete::BoundedInt(_) => todo!("BoundedInt"), - _ => todo!(), - }], + smallvec![get_value_from_integer(registry, int_ty, value)], ) } diff --git a/debug_utils/sierra-emu/src/vm/const.rs b/debug_utils/sierra-emu/src/vm/const.rs index be6c24e78..17aac2b97 100644 --- a/debug_utils/sierra-emu/src/vm/const.rs +++ b/debug_utils/sierra-emu/src/vm/const.rs @@ -6,11 +6,13 @@ use cairo_lang_sierra::{ ConstAsBoxConcreteLibfunc, ConstAsImmediateConcreteLibfunc, ConstConcreteLibfunc, }, core::{CoreLibfunc, CoreType, CoreTypeConcrete}, + starknet::StarknetTypeConcrete, }, ids::ConcreteTypeId, program::GenericArg, program_registry::ProgramRegistry, }; +use num_traits::ToPrimitive; use smallvec::smallvec; pub fn eval( @@ -24,7 +26,7 @@ pub fn eval( } } -pub fn eval_as_immediate( +fn eval_as_immediate( registry: &ProgramRegistry, info: &ConstAsImmediateConcreteLibfunc, args: Vec, @@ -41,7 +43,7 @@ pub fn eval_as_immediate( ) } -pub fn eval_as_box( +fn eval_as_box( registry: &ProgramRegistry, info: &ConstAsBoxConcreteLibfunc, args: Vec, @@ -89,28 +91,32 @@ fn inner( }, _ => unreachable!(), }, + CoreTypeConcrete::Bytes31(_) => match inner_data { + [GenericArg::Value(value)] => Value::Bytes31(value.into()), + _ => unreachable!(), + }, CoreTypeConcrete::Sint128(_) => match inner_data { - [GenericArg::Value(value)] => Value::I128(value.try_into().unwrap()), + [GenericArg::Value(value)] => Value::I128(value.to_i128().unwrap()), _ => unreachable!(), }, CoreTypeConcrete::Sint64(_) => match inner_data { - [GenericArg::Value(value)] => Value::U64(value.try_into().unwrap()), + [GenericArg::Value(value)] => Value::I64(value.to_i64().unwrap()), _ => unreachable!(), }, CoreTypeConcrete::Sint32(_) => match inner_data { - [GenericArg::Value(value)] => Value::I32(value.try_into().unwrap()), + [GenericArg::Value(value)] => Value::I32(value.to_i32().unwrap()), _ => unreachable!(), }, CoreTypeConcrete::Sint16(_) => match inner_data { - [GenericArg::Value(value)] => Value::I16(value.try_into().unwrap()), + [GenericArg::Value(value)] => Value::I16(value.to_i16().unwrap()), _ => unreachable!(), }, CoreTypeConcrete::Sint8(_) => match inner_data { - [GenericArg::Value(value)] => Value::I8(value.try_into().unwrap()), + [GenericArg::Value(value)] => Value::I8(value.to_i8().unwrap()), _ => unreachable!(), }, CoreTypeConcrete::Uint128(_) => match inner_data { - [GenericArg::Value(value)] => Value::U128(value.try_into().unwrap()), + [GenericArg::Value(value)] => Value::U128(value.to_u128().unwrap()), [GenericArg::Type(type_id)] => match registry.get_type(type_id).unwrap() { CoreTypeConcrete::Const(info) => inner(registry, &info.inner_ty, &info.inner_data), _ => unreachable!(), @@ -118,11 +124,11 @@ fn inner( _ => unreachable!(), }, CoreTypeConcrete::Uint64(_) => match inner_data { - [GenericArg::Value(value)] => Value::U64(value.try_into().unwrap()), + [GenericArg::Value(value)] => Value::U64(value.to_u64().unwrap()), _ => unreachable!(), }, CoreTypeConcrete::Uint32(_) => match inner_data { - [GenericArg::Value(value)] => Value::U32(value.try_into().unwrap()), + [GenericArg::Value(value)] => Value::U32(value.to_u32().unwrap()), [GenericArg::Type(type_id)] => match registry.get_type(type_id).unwrap() { CoreTypeConcrete::Const(info) => inner(registry, &info.inner_ty, &info.inner_data), _ => unreachable!(), @@ -130,11 +136,11 @@ fn inner( _ => unreachable!(), }, CoreTypeConcrete::Uint16(_) => match inner_data { - [GenericArg::Value(value)] => Value::U16(value.try_into().unwrap()), + [GenericArg::Value(value)] => Value::U16(value.to_u16().unwrap()), _ => unreachable!(), }, CoreTypeConcrete::Uint8(_) => match inner_data { - [GenericArg::Value(value)] => Value::U8(value.try_into().unwrap()), + [GenericArg::Value(value)] => Value::U8(value.to_u8().unwrap()), _ => unreachable!(), }, CoreTypeConcrete::Struct(_) => { @@ -160,6 +166,37 @@ fn inner( Value::Struct(fields) } + CoreTypeConcrete::Enum(_) => match inner_data { + [GenericArg::Value(value_idx), GenericArg::Type(payload_ty)] => { + let payload_type = registry.get_type(payload_ty).unwrap(); + let const_payload_type = match payload_type { + CoreTypeConcrete::Const(inner) => inner, + _ => { + panic!("matched an unexpected CoreTypeConcrete that is not a Const") + } + }; + let payload = inner(registry, payload_ty, &const_payload_type.inner_data); + let index: usize = value_idx.to_usize().unwrap(); + + Value::Enum { + self_ty: type_id.clone(), + index, + payload: Box::new(payload), + } + } + _ => panic!("const data mismatch"), + }, + CoreTypeConcrete::Const(info) => inner(registry, &info.inner_ty, &info.inner_data), + CoreTypeConcrete::Starknet(selector) => match selector { + StarknetTypeConcrete::ClassHash(_) + | StarknetTypeConcrete::ContractAddress(_) + | StarknetTypeConcrete::StorageAddress(_) + | StarknetTypeConcrete::StorageBaseAddress(_) => match inner_data { + [GenericArg::Value(value)] => Value::Felt(value.into()), + _ => unreachable!(), + }, + _ => todo!(""), + }, _ => todo!("{}", type_id), } } From a70680f019e92911d8c22a33656b8b15217d296f Mon Sep 17 00:00:00 2001 From: Franco Giachetta Date: Wed, 7 May 2025 19:07:08 -0300 Subject: [PATCH 06/28] [Sierra-Emu] implement `get_available_gas` libfunc (#1210) * implement las gas libfunc * remove unnecesary pubs --- debug_utils/sierra-emu/src/vm/gas.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/debug_utils/sierra-emu/src/vm/gas.rs b/debug_utils/sierra-emu/src/vm/gas.rs index 28bb661a5..525ddc1d0 100644 --- a/debug_utils/sierra-emu/src/vm/gas.rs +++ b/debug_utils/sierra-emu/src/vm/gas.rs @@ -29,7 +29,7 @@ pub fn eval( GasConcreteLibfunc::RedepositGas(info) => { eval_redeposit_gas(registry, info, args, gas, statement_idx, builtin_costs) } - GasConcreteLibfunc::GetAvailableGas(_) => todo!(), + GasConcreteLibfunc::GetAvailableGas(info) => eval_get_available_gas(registry, info, args), GasConcreteLibfunc::BuiltinWithdrawGas(info) => { eval_builtin_withdraw_gas(registry, info, args, gas, statement_idx) } @@ -40,7 +40,7 @@ pub fn eval( } } -pub fn eval_builtin_withdraw_gas( +fn eval_builtin_withdraw_gas( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -87,7 +87,7 @@ pub fn eval_builtin_withdraw_gas( } } -pub fn eval_withdraw_gas( +fn eval_withdraw_gas( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -133,7 +133,7 @@ pub fn eval_withdraw_gas( } } -pub fn eval_redeposit_gas( +fn eval_redeposit_gas( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -174,7 +174,19 @@ pub fn eval_redeposit_gas( EvalAction::NormalBranch(0, smallvec![Value::U64(new_gas)]) } -pub fn eval_get_builtin_costs( +fn eval_get_available_gas( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [gas_val @ Value::U64(gas)]: [Value; 1] = args.try_into().unwrap() else { + panic!(); + }; + + EvalAction::NormalBranch(0, smallvec![gas_val, Value::U128(gas as u128)]) +} + +fn eval_get_builtin_costs( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, _args: Vec, From 36fbc0ae53f2f80c7f34391adacb823c3696c56f Mon Sep 17 00:00:00 2001 From: Julian Gonzalez Calderon Date: Thu, 8 May 2025 16:30:16 -0300 Subject: [PATCH 07/28] Improve sierra2casm-dbg (#1206) * Add sierra2casm-dbg * Sync cairo version between sierra2casm-dbg and root * Bump Cairo VM version in sierra2casm-dbg * Fix compilation and linter errors * Bump Cairo in sierra2casm-dbg * Remove unused serde dep in sierra2casm-dbg * Remove Rust toolchain in sierra2casm-dbg * Add sierra2casm-dbg CI job (#1201) * Move cairo-lang dependencies to workspace * Remove sierra-emu/.gitignore * Update lock * Add fibonacci.cairo example for sierra emulator * Simplify Makefile * Simplify README and improve documentation on how to use the binary * Add make check * Improve examples * Document make check * Remove unused script * Add fibonacci_contract.cairo * Fix typo in Makefile * Fix CI * Use cairo2/corelib instead of corelib for symlinking in sierra emu * Improve Sierra Emulator descritopn * Move more dependencies to workspace * Rename to casm-data-flow * Add Makefile * Add basic run-contract example * Add fibonacci_starknet.cairo * Fix hint format * Add basic README.md * Update dependencies * Print gas usage * Finish README.md * Add .PHONY * Fix paragraph * Move deps to workspace * Fix whitespace * Fix whitespace * Fix README.md --------- Co-authored-by: gabrielbosio Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> --- .github/workflows/ci.yml | 4 +- Cargo.lock | 63 +++++-- Cargo.toml | 3 +- debug_utils/casm-data-flow/Cargo.toml | 14 ++ debug_utils/casm-data-flow/Makefile | 19 ++ debug_utils/casm-data-flow/README.md | 52 ++++++ .../examples/basic.rs | 2 +- .../casm-data-flow/examples/run-contract.rs | 174 ++++++++++++++++++ .../programs/fibonacci_starknet.cairo | 17 ++ .../src/lib.rs | 0 .../src/main.rs | 25 +-- .../src/mappings.rs | 0 .../src/memory.rs | 0 .../src/program.rs | 0 .../src/search.rs | 0 .../src/trace.rs | 0 debug_utils/sierra2casm-dbg/.gitignore | 19 -- debug_utils/sierra2casm-dbg/Cargo.toml | 15 -- 18 files changed, 338 insertions(+), 69 deletions(-) create mode 100644 debug_utils/casm-data-flow/Cargo.toml create mode 100644 debug_utils/casm-data-flow/Makefile create mode 100644 debug_utils/casm-data-flow/README.md rename debug_utils/{sierra2casm-dbg => casm-data-flow}/examples/basic.rs (93%) create mode 100644 debug_utils/casm-data-flow/examples/run-contract.rs create mode 100644 debug_utils/casm-data-flow/programs/fibonacci_starknet.cairo rename debug_utils/{sierra2casm-dbg => casm-data-flow}/src/lib.rs (100%) rename debug_utils/{sierra2casm-dbg => casm-data-flow}/src/main.rs (88%) rename debug_utils/{sierra2casm-dbg => casm-data-flow}/src/mappings.rs (100%) rename debug_utils/{sierra2casm-dbg => casm-data-flow}/src/memory.rs (100%) rename debug_utils/{sierra2casm-dbg => casm-data-flow}/src/program.rs (100%) rename debug_utils/{sierra2casm-dbg => casm-data-flow}/src/search.rs (100%) rename debug_utils/{sierra2casm-dbg => casm-data-flow}/src/trace.rs (100%) delete mode 100644 debug_utils/sierra2casm-dbg/.gitignore delete mode 100644 debug_utils/sierra2casm-dbg/Cargo.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb18171b5..28de76a45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -375,8 +375,8 @@ jobs: - name: Retreive cached dependecies uses: Swatinem/rust-cache@v2 - name: Build - working-directory: ./debug_utils/sierra2casm-dbg + working-directory: ./debug_utils/casm-data-flow run: cargo build --all-features --verbose - name: Run tests - working-directory: ./debug_utils/sierra2casm-dbg + working-directory: ./debug_utils/casm-data-flow run: cargo test diff --git a/Cargo.lock b/Cargo.lock index abcf7265d..d5cbec337 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1152,6 +1152,42 @@ dependencies = [ "zip", ] +[[package]] +name = "cairo-vm" +version = "2.0.1" +source = "git+https://github.com/lambdaclass/cairo-vm/?rev=3b36cd92f68f3dc26ddf5aba6c1f9a50ff3d303f#3b36cd92f68f3dc26ddf5aba6c1f9a50ff3d303f" +dependencies = [ + "anyhow", + "ark-ff 0.4.2", + "ark-std 0.4.0", + "bincode 2.0.1", + "bitvec", + "cairo-lang-casm", + "cairo-lang-starknet", + "cairo-lang-starknet-classes", + "generic-array", + "hashbrown 0.15.2", + "hex", + "indoc", + "keccak", + "lazy_static", + "nom", + "num-bigint", + "num-integer", + "num-prime", + "num-traits", + "rand 0.8.5", + "rust_decimal", + "serde", + "serde_json", + "sha2", + "sha3", + "starknet-crypto", + "starknet-types-core", + "thiserror 2.0.12", + "zip", +] + [[package]] name = "camino" version = "1.1.9" @@ -1170,6 +1206,20 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "casm-data-flow" +version = "0.1.0" +dependencies = [ + "bincode 2.0.1", + "cairo-lang-casm", + "cairo-lang-starknet-classes", + "cairo-lang-utils", + "cairo-vm 2.0.1 (git+https://github.com/lambdaclass/cairo-vm/?rev=3b36cd92f68f3dc26ddf5aba6c1f9a50ff3d303f)", + "clap", + "serde_json", + "starknet-types-core", +] + [[package]] name = "cast" version = "0.3.0" @@ -3354,19 +3404,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "sierra2casm-dbg" -version = "0.1.0" -dependencies = [ - "bincode 2.0.1", - "cairo-lang-casm", - "cairo-lang-utils", - "cairo-vm 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "clap", - "serde_json", - "starknet-types-core", -] - [[package]] name = "signature" version = "2.2.0" diff --git a/Cargo.toml b/Cargo.toml index 87547af34..6c22f06bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,9 +179,10 @@ name = "libfuncs" harness = false [workspace] -members = ["debug_utils/sierra-emu", "debug_utils/sierra2casm-dbg"] +members = ["debug_utils/sierra-emu", "debug_utils/casm-data-flow"] [workspace.dependencies] +cairo-lang-casm = "=2.12.0-dev.1" cairo-lang-compiler = "=2.12.0-dev.1" cairo-lang-defs = "=2.12.0-dev.1" cairo-lang-filesystem = "=2.12.0-dev.1" diff --git a/debug_utils/casm-data-flow/Cargo.toml b/debug_utils/casm-data-flow/Cargo.toml new file mode 100644 index 000000000..8c0b59622 --- /dev/null +++ b/debug_utils/casm-data-flow/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "casm-data-flow" +version = "0.1.0" +edition = "2021" + +[dependencies] +bincode = { version = "2.0.0-rc.3", default-features = false } +clap = { version = "4.5.27", features = ["derive"] } +serde_json = "1.0.138" +starknet-types-core.workspace = true +cairo-lang-casm = { workspace = true, features = [ "serde", ] } +cairo-lang-utils.workspace = true +cairo-lang-starknet-classes.workspace = true +cairo-vm = { git = "https://github.com/lambdaclass/cairo-vm/", rev = "3b36cd92f68f3dc26ddf5aba6c1f9a50ff3d303f", features = ["cairo-1-hints"] } diff --git a/debug_utils/casm-data-flow/Makefile b/debug_utils/casm-data-flow/Makefile new file mode 100644 index 000000000..780b076bd --- /dev/null +++ b/debug_utils/casm-data-flow/Makefile @@ -0,0 +1,19 @@ +usage: + @echo "Usage:" + @echo " build: Builds casm-data-flow." + @echo " check: Checks format and lints." + @echo " clean: Cleans the built artifacts." +.PHONY: usage + +build: + cargo build --release --all-features +.PHONY: build + +check: + cargo fmt --all -- --check + cargo clippy --all-targets --all-features -- -D warnings +.PHONY: check + +clean: + cargo clean +.PHONY: clean diff --git a/debug_utils/casm-data-flow/README.md b/debug_utils/casm-data-flow/README.md new file mode 100644 index 000000000..02f7bad29 --- /dev/null +++ b/debug_utils/casm-data-flow/README.md @@ -0,0 +1,52 @@ +# Casm Data Flow + +A data flow analyzer for a Cairo VM. Currently, it only supports starknet contracts. + +It is useful when trying to debug a gas difference between Cairo Native and Cairo VM. + +## Usage + +First, we need to compile the contract: + +```bash +../../cairo2/bin/starknet-compile -s programs/fibonacci_starknet.cairo > programs/fibonacci_starknet.sierra.json +../../cairo2/bin/starknet-sierra-compile programs/fibonacci_starknet.sierra.json programs/fibonacci_starknet.casm.json +``` + +Then, we execute the contract. In this example, we are using [our Cairo VM](https://github.com/lambdaclass/cairo-vm). + +```bash +cargo run --example run-contract -- programs/fibonacci_starknet.casm.json programs/fibonacci_starknet.memory programs/fibonacci_starknet.trace +``` + +The example prints the starting and final gas: + +``` +Starting Gas: 18446744073709551615 +Final Gas: 18446744073709544205 +``` + +We will need the trace and memory files generated, to find a path from the starting gas to the final gas. + +```bash +cargo run -- --program-path programs/fibonacci_starknet.casm.json --trace-path programs/fibonacci_starknet.trace --memory-path programs/fibonacci_starknet.memory -s 18446744073709551615 -t 18446744073709544205 +``` + +The binary prints the full path from the starting gas to the final gas. + +``` + [184] = 18446744073709551615 + [212] = 18446744073709550345 (Δ-1270) + [222] = 18446744073709549075 (Δ-1270) + [232] = 18446744073709547805 (Δ-1270) + [242] = 18446744073709546535 (Δ-1270) + [252] = 18446744073709545265 (Δ-1270) + [262] = 18446744073709543995 (Δ-1270) + [272] = 18446744073709542725 (Δ-1270) + [282] = 18446744073709541455 (Δ-1270) + [292] = 18446744073709540185 (Δ-1270) + [302] = 18446744073709538915 (Δ-1270) + [312] = 18446744073709537645 (Δ-1270) + [315] = 18446744073709539715 (Δ2070) + [321] = 18446744073709544205 (Δ4490) +``` diff --git a/debug_utils/sierra2casm-dbg/examples/basic.rs b/debug_utils/casm-data-flow/examples/basic.rs similarity index 93% rename from debug_utils/sierra2casm-dbg/examples/basic.rs rename to debug_utils/casm-data-flow/examples/basic.rs index 95fb70bb9..40e3815d9 100644 --- a/debug_utils/sierra2casm-dbg/examples/basic.rs +++ b/debug_utils/casm-data-flow/examples/basic.rs @@ -1,5 +1,5 @@ use bincode::de::read::SliceReader; -use sierra2casm_dbg::{decode_instruction, GraphMappings, Memory, Trace, ValueId}; +use casm_data_flow::{decode_instruction, GraphMappings, Memory, Trace, ValueId}; use std::{collections::HashMap, fs}; fn main() { diff --git a/debug_utils/casm-data-flow/examples/run-contract.rs b/debug_utils/casm-data-flow/examples/run-contract.rs new file mode 100644 index 000000000..3e4198889 --- /dev/null +++ b/debug_utils/casm-data-flow/examples/run-contract.rs @@ -0,0 +1,174 @@ +use std::{fs::File, io::Write, path::PathBuf}; + +use bincode::enc::write::Writer; +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; +use cairo_vm::{ + cairo_run::{write_encoded_memory, write_encoded_trace}, + hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, + types::{ + builtin_name::BuiltinName, layout_name::LayoutName, program::Program, + relocatable::MaybeRelocatable, + }, + vm::runners::cairo_runner::{CairoArg, CairoRunner, RunResources}, +}; +use clap::Parser; +use starknet_types_core::felt::Felt; + +#[derive(Debug, Parser)] +struct Args { + contract_path: PathBuf, + memory_path: PathBuf, + trace_path: PathBuf, +} + +pub fn main() { + let cli_args = Args::parse(); + let calldata_args = [MaybeRelocatable::from(10)]; + let expected_retdata = [Felt::from(89)]; + + let contract_file = File::open(cli_args.contract_path).expect("failed to open contract path"); + let contract: CasmContractClass = + serde_json::from_reader(contract_file).expect("failed to parse contract file"); + + let program = Program::try_from(contract.clone()) + .expect("failed to build vm program from contract class"); + + let mut runner = + CairoRunner::new(&program, LayoutName::all_cairo, None, false, true, false).unwrap(); + + let program_builtins = contract + .entry_points_by_type + .external + .iter() + .find(|e| e.offset == 0) + .unwrap() + .builtins + .iter() + .map(|s| BuiltinName::from_str(s).expect("Invalid builtin name")) + .collect::>(); + runner + .initialize_function_runner_cairo_1(&program_builtins) + .unwrap(); + + let syscall_segment = MaybeRelocatable::from(runner.vm.add_memory_segment()); + + let builtins = runner.get_program_builtins(); + + let builtin_segment: Vec = runner + .vm + .get_builtin_runners() + .iter() + .filter(|b| builtins.contains(&b.name())) + .flat_map(|b| b.initial_stack()) + .collect(); + + let initial_gas = MaybeRelocatable::from(usize::MAX); + println!("Starting Gas: {}", &initial_gas); + + let mut implicit_args = builtin_segment; + implicit_args.extend([initial_gas]); + implicit_args.extend([syscall_segment]); + + let builtin_costs: Vec = + vec![0.into(), 0.into(), 0.into(), 0.into(), 0.into()]; + let builtin_costs_ptr = runner.vm.add_memory_segment(); + runner + .vm + .load_data(builtin_costs_ptr, &builtin_costs) + .unwrap(); + + let core_program_end_ptr = + (runner.program_base.unwrap() + runner.get_program().data_len()).unwrap(); + let program_extra_data: Vec = + vec![0x208B7FFF7FFF7FFE.into(), builtin_costs_ptr.into()]; + runner + .vm + .load_data(core_program_end_ptr, &program_extra_data) + .unwrap(); + + let calldata_start = runner.vm.add_memory_segment(); + let calldata_end = runner.vm.load_data(calldata_start, &calldata_args).unwrap(); + + let mut entrypoint_args: Vec = implicit_args + .iter() + .map(|m| CairoArg::from(m.clone())) + .collect(); + entrypoint_args.extend([ + MaybeRelocatable::from(calldata_start).into(), + MaybeRelocatable::from(calldata_end).into(), + ]); + let entrypoint_args: Vec<&CairoArg> = entrypoint_args.iter().collect(); + + let mut hint_processor = + Cairo1HintProcessor::new(&contract.hints, RunResources::new(621), false); + + runner + .run_from_entrypoint( + 0, + &entrypoint_args, + true, + Some(runner.get_program().data_len() + program_extra_data.len()), + &mut hint_processor, + ) + .expect("failed to execute contract"); + + let return_values = runner.vm.get_return_values(5).unwrap(); + let final_gas = return_values[0].get_int().unwrap(); + let retdata_start = return_values[3].get_relocatable().unwrap(); + let retdata_end = return_values[4].get_relocatable().unwrap(); + let retdata: Vec = runner + .vm + .get_integer_range(retdata_start, (retdata_end - retdata_start).unwrap()) + .unwrap() + .iter() + .map(|c| c.clone().into_owned()) + .collect(); + + println!("Final Gas: {}", final_gas); + + assert_eq!(retdata, expected_retdata); + + runner.relocate(true).expect("failed to relocate trace"); + + let trace_file = File::create(cli_args.trace_path).expect("failed to create trace file"); + let mut trace_writer = FileWriter::new(trace_file); + write_encoded_trace( + &runner.relocated_trace.expect("trace should exist"), + &mut trace_writer, + ) + .expect("failed to write trace"); + + let memory_file = File::create(cli_args.memory_path).expect("failed to create memory file"); + let mut memory_writer = FileWriter::new(memory_file); + write_encoded_memory(&runner.relocated_memory, &mut memory_writer) + .expect("failed to write memory"); +} + +struct FileWriter { + buf_writer: File, + bytes_written: usize, +} + +impl Writer for FileWriter { + fn write(&mut self, bytes: &[u8]) -> Result<(), bincode::error::EncodeError> { + self.buf_writer + .write_all(bytes) + .map_err(|e| bincode::error::EncodeError::Io { + inner: e, + index: self.bytes_written, + })?; + + self.bytes_written += bytes.len(); + + Ok(()) + } +} + +impl FileWriter { + fn new(buf_writer: File) -> Self { + Self { + buf_writer, + bytes_written: 0, + } + } +} diff --git a/debug_utils/casm-data-flow/programs/fibonacci_starknet.cairo b/debug_utils/casm-data-flow/programs/fibonacci_starknet.cairo new file mode 100644 index 000000000..83a94e7ef --- /dev/null +++ b/debug_utils/casm-data-flow/programs/fibonacci_starknet.cairo @@ -0,0 +1,17 @@ +#[starknet::contract] +mod Fibonacci { + #[storage] + struct Storage { } + + #[external(v0)] + fn fibonacci(ref self: ContractState, value: felt252) -> felt252 { + return super::fibonacci(1, 1, value); + } +} + +fn fibonacci(a: felt252, b: felt252, n: felt252) -> felt252 { + match n { + 0 => a, + _ => fibonacci(b, a + b, n - 1), + } +} diff --git a/debug_utils/sierra2casm-dbg/src/lib.rs b/debug_utils/casm-data-flow/src/lib.rs similarity index 100% rename from debug_utils/sierra2casm-dbg/src/lib.rs rename to debug_utils/casm-data-flow/src/lib.rs diff --git a/debug_utils/sierra2casm-dbg/src/main.rs b/debug_utils/casm-data-flow/src/main.rs similarity index 88% rename from debug_utils/sierra2casm-dbg/src/main.rs rename to debug_utils/casm-data-flow/src/main.rs index 36e0361ae..349d918ae 100644 --- a/debug_utils/sierra2casm-dbg/src/main.rs +++ b/debug_utils/casm-data-flow/src/main.rs @@ -1,12 +1,12 @@ use bincode::de::read::SliceReader; -use cairo_vm::serde::deserialize_program::HintParams; -use clap::Parser; -use serde_json::Value; -use sierra2casm_dbg::{ +use cairo_lang_casm::hints::Hint; +use casm_data_flow::{ run_search_algorithm, search::{DfsQueue, NodeId}, GraphMappings, Memory, Trace, }; +use clap::Parser; +use serde_json::Value; use starknet_types_core::felt::Felt; use std::{collections::HashMap, fs, path::PathBuf, str::FromStr}; @@ -53,20 +53,9 @@ fn main() { .remove("hints") .unwrap(); - let hints: HashMap> = serde_json::from_value(hints).unwrap(); - hints - .into_iter() - .map(|(key, value)| { - // - ( - key, - value - .into_iter() - .map(|hint_params| serde_json::from_str(&hint_params.code).unwrap()) - .collect(), - ) - }) - .collect() + let hints: Vec<(usize, Vec)> = serde_json::from_value(hints).unwrap(); + + hints.into_iter().collect() } }; diff --git a/debug_utils/sierra2casm-dbg/src/mappings.rs b/debug_utils/casm-data-flow/src/mappings.rs similarity index 100% rename from debug_utils/sierra2casm-dbg/src/mappings.rs rename to debug_utils/casm-data-flow/src/mappings.rs diff --git a/debug_utils/sierra2casm-dbg/src/memory.rs b/debug_utils/casm-data-flow/src/memory.rs similarity index 100% rename from debug_utils/sierra2casm-dbg/src/memory.rs rename to debug_utils/casm-data-flow/src/memory.rs diff --git a/debug_utils/sierra2casm-dbg/src/program.rs b/debug_utils/casm-data-flow/src/program.rs similarity index 100% rename from debug_utils/sierra2casm-dbg/src/program.rs rename to debug_utils/casm-data-flow/src/program.rs diff --git a/debug_utils/sierra2casm-dbg/src/search.rs b/debug_utils/casm-data-flow/src/search.rs similarity index 100% rename from debug_utils/sierra2casm-dbg/src/search.rs rename to debug_utils/casm-data-flow/src/search.rs diff --git a/debug_utils/sierra2casm-dbg/src/trace.rs b/debug_utils/casm-data-flow/src/trace.rs similarity index 100% rename from debug_utils/sierra2casm-dbg/src/trace.rs rename to debug_utils/casm-data-flow/src/trace.rs diff --git a/debug_utils/sierra2casm-dbg/.gitignore b/debug_utils/sierra2casm-dbg/.gitignore deleted file mode 100644 index afde32e00..000000000 --- a/debug_utils/sierra2casm-dbg/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -### Rust ### -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - - -corelib/ -run/ diff --git a/debug_utils/sierra2casm-dbg/Cargo.toml b/debug_utils/sierra2casm-dbg/Cargo.toml deleted file mode 100644 index 804a6a6e8..000000000 --- a/debug_utils/sierra2casm-dbg/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "sierra2casm-dbg" -version = "0.1.0" -edition = "2021" - -[dependencies] -bincode = { version = "2.0.0-rc.3", default-features = false } -clap = { version = "4.5.27", features = ["derive"] } -cairo-lang-casm = { version = "=2.12.0-dev.1", default-features = false, features = [ - "serde", -] } -cairo-lang-utils = { version = "=2.12.0-dev.1", default-features = false } -cairo-vm = { version = "2.0.1", default-features = false } -serde_json = "1.0.138" -starknet-types-core = { version = "0.1.7", default-features = false } From 04a34828f7b3ee6bb76e8fa03fa9432cebb930be Mon Sep 17 00:00:00 2001 From: Franco Giachetta Date: Thu, 8 May 2025 18:18:02 -0300 Subject: [PATCH 08/28] [Sierra-Emu] implement missing libfuncs from `int_range` and `array` (#1200) * Add sierra-emu as a debug utility * Change working dir in sierra-emu job * Remove repeated lines in sierra-emu gitignore * Exclude debug_utils from coverage job * remove some todos from value and implement Coupon libfunc * start adding corelib test * run tests from corelib * remove unwanted file * remove not related things * get return_value from trace * replace ids for debug info * add common folder for integration tests * fix tests * remove todo * ignore test for now * filter ignored tests * Update debug_utils/sierra-emu/tests/common/mod.rs Co-authored-by: Julian Gonzalez Calderon * run ignored test from the ci * better ignoring * remove debug_name in enums * refactor * remove unused dependency * More expressive naming for the workflow Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> * reviews * reviews * fmt * fmt ci * revert last commit * fmt * accidentally removed rayon usage * avoid map's short-circuiting * catch panics the avoid loosing the test name * log ignored tests * clippy * Add "(WIP)" to corelib's test job Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> * print progress as the test runs * fmt * format makefile Co-authored-by: Julian Gonzalez Calderon * fix name test in ci * add dependencies removed in merge * dependencies * clippy * remove unused import * better indent in makefile * uppercase tests summary comment for better sight * implement int_range libfuncs * implement missing array libfuncs * fmt * add is() for IntRange * remove unnecesary pubs * reviews * reviews * fix branching in int_range_try_new * clippy * reviews * fmt --------- Co-authored-by: gabrielbosio Co-authored-by: Julian Gonzalez Calderon Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> --- debug_utils/sierra-emu/src/value.rs | 6 +- debug_utils/sierra-emu/src/vm.rs | 3 +- debug_utils/sierra-emu/src/vm/array.rs | 92 ++++++++++++++++--- debug_utils/sierra-emu/src/vm/int_range.rs | 101 +++++++++++++++++++++ debug_utils/sierra-emu/tests/common/mod.rs | 7 ++ 5 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 debug_utils/sierra-emu/src/vm/int_range.rs diff --git a/debug_utils/sierra-emu/src/value.rs b/debug_utils/sierra-emu/src/value.rs index 09b924b8d..d7adfa842 100644 --- a/debug_utils/sierra-emu/src/value.rs +++ b/debug_utils/sierra-emu/src/value.rs @@ -69,6 +69,10 @@ pub enum Value { U32(u32), U64(u64), U8(u8), + IntRange { + x: Box, + y: Box, + }, Uninitialized { ty: ConcreteTypeId, }, @@ -220,7 +224,7 @@ impl Value { StarknetTypeConcrete::Secp256Point(_) => matches!(self, Self::Struct(_)), StarknetTypeConcrete::Sha256StateHandle(_) => matches!(self, Self::Struct { .. }), }, - CoreTypeConcrete::IntRange(_) => todo!(), + CoreTypeConcrete::IntRange(_) => matches!(self, Self::IntRange { .. }), CoreTypeConcrete::Blake(_) => todo!(), CoreTypeConcrete::QM31(_) => todo!(), }; diff --git a/debug_utils/sierra-emu/src/vm.rs b/debug_utils/sierra-emu/src/vm.rs index 13ffe92d2..c1443aab3 100644 --- a/debug_utils/sierra-emu/src/vm.rs +++ b/debug_utils/sierra-emu/src/vm.rs @@ -49,6 +49,7 @@ mod felt252_dict_entry; mod function_call; mod gas; mod int128; +mod int_range; mod jump; mod mem; mod nullable; @@ -519,7 +520,7 @@ fn eval<'a>( EvalAction::NormalBranch(0, smallvec![value]) } - CoreConcreteLibfunc::IntRange(_) => todo!(), + CoreConcreteLibfunc::IntRange(selector) => self::int_range::eval(registry, selector, args), CoreConcreteLibfunc::Blake(_) => todo!(), CoreConcreteLibfunc::QM31(_) => todo!(), CoreConcreteLibfunc::Felt252SquashedDict(_) => todo!(), diff --git a/debug_utils/sierra-emu/src/vm/array.rs b/debug_utils/sierra-emu/src/vm/array.rs index d5eecf9b6..d857892dc 100644 --- a/debug_utils/sierra-emu/src/vm/array.rs +++ b/debug_utils/sierra-emu/src/vm/array.rs @@ -19,10 +19,10 @@ pub fn eval( match selector { ArrayConcreteLibfunc::New(info) => eval_new(registry, info, args), ArrayConcreteLibfunc::SpanFromTuple(info) => eval_span_from_tuple(registry, info, args), - ArrayConcreteLibfunc::TupleFromSpan(_) => todo!(), + ArrayConcreteLibfunc::TupleFromSpan(info) => eval_tuple_from_span(registry, info, args), ArrayConcreteLibfunc::Append(info) => eval_append(registry, info, args), ArrayConcreteLibfunc::PopFront(info) => eval_pop_front(registry, info, args), - ArrayConcreteLibfunc::PopFrontConsume(_) => todo!(), + ArrayConcreteLibfunc::PopFrontConsume(info) => eval_pop_front_consume(registry, info, args), ArrayConcreteLibfunc::Get(info) => eval_get(registry, info, args), ArrayConcreteLibfunc::Slice(info) => eval_slice(registry, info, args), ArrayConcreteLibfunc::Len(info) => eval_len(registry, info, args), @@ -33,7 +33,9 @@ pub fn eval( ArrayConcreteLibfunc::SnapshotMultiPopFront(info) => { eval_snapshot_multi_pop_front(registry, info, args) } - ArrayConcreteLibfunc::SnapshotMultiPopBack(_) => todo!(), + ArrayConcreteLibfunc::SnapshotMultiPopBack(info) => { + eval_snapshot_multi_pop_back(registry, info, args) + } } } @@ -61,7 +63,31 @@ fn eval_span_from_tuple( EvalAction::NormalBranch(0, smallvec![value]) } -pub fn eval_new( +fn eval_tuple_from_span( + registry: &ProgramRegistry, + info: &SignatureAndTypeConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Array { data, .. }]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + let tuple_len = { + let CoreTypeConcrete::Struct(param) = registry.get_type(&info.ty).unwrap() else { + panic!() + }; + + param.members.len() + }; + + if data.len() == tuple_len { + EvalAction::NormalBranch(0, smallvec![Value::Struct(data)]) + } else { + EvalAction::NormalBranch(1, smallvec![]) + } +} + +fn eval_new( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -85,7 +111,7 @@ pub fn eval_new( ) } -pub fn eval_append( +fn eval_append( registry: &ProgramRegistry, info: &SignatureAndTypeConcreteLibfunc, args: Vec, @@ -101,7 +127,7 @@ pub fn eval_append( EvalAction::NormalBranch(0, smallvec![Value::Array { ty, data }]) } -pub fn eval_get( +fn eval_get( _registry: &ProgramRegistry, _info: &SignatureAndTypeConcreteLibfunc, args: Vec, @@ -118,7 +144,7 @@ pub fn eval_get( } } -pub fn eval_slice( +fn eval_slice( _registry: &ProgramRegistry, _info: &SignatureAndTypeConcreteLibfunc, args: Vec, @@ -144,7 +170,7 @@ pub fn eval_slice( } } -pub fn eval_len( +fn eval_len( _registry: &ProgramRegistry, _info: &SignatureAndTypeConcreteLibfunc, args: Vec, @@ -157,7 +183,7 @@ pub fn eval_len( EvalAction::NormalBranch(0, smallvec![Value::U32(array_len)]) } -pub fn eval_pop_front( +fn eval_pop_front( _registry: &ProgramRegistry, _info: &SignatureAndTypeConcreteLibfunc, args: Vec, @@ -175,7 +201,25 @@ pub fn eval_pop_front( } } -pub fn eval_snapshot_pop_front( +fn eval_pop_front_consume( + _registry: &ProgramRegistry, + _info: &SignatureAndTypeConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::Array { mut data, ty }]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + + if !data.is_empty() { + let new_data = data.split_off(1); + let value = data[0].clone(); + EvalAction::NormalBranch(0, smallvec![Value::Array { data: new_data, ty }, value]) + } else { + EvalAction::NormalBranch(1, smallvec![]) + } +} + +fn eval_snapshot_pop_front( registry: &ProgramRegistry, info: &SignatureAndTypeConcreteLibfunc, args: Vec, @@ -194,7 +238,7 @@ pub fn eval_snapshot_pop_front( } } -pub fn eval_snapshot_pop_back( +fn eval_snapshot_pop_back( registry: &ProgramRegistry, info: &SignatureAndTypeConcreteLibfunc, args: Vec, @@ -238,3 +282,29 @@ fn eval_snapshot_multi_pop_front( EvalAction::NormalBranch(1, smallvec![rangecheck, Value::Array { data, ty }]) } } + +fn eval_snapshot_multi_pop_back( + registry: &ProgramRegistry, + info: &cairo_lang_sierra::extensions::array::ConcreteMultiPopLibfunc, + args: Vec, +) -> EvalAction { + let [rangecheck, Value::Array { mut data, ty }]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + let CoreTypeConcrete::Struct(popped_cty) = registry.get_type(&info.popped_ty).unwrap() else { + panic!() + }; + + if data.len() >= popped_cty.members.len() { + let new_data = data.split_off(data.len() - popped_cty.members.len()); + let value = Value::Struct(data); + assert!(value.is(registry, &info.popped_ty)); + EvalAction::NormalBranch( + 0, + smallvec![rangecheck, Value::Array { data: new_data, ty }, value], + ) + } else { + EvalAction::NormalBranch(1, smallvec![rangecheck, Value::Array { data, ty }]) + } +} diff --git a/debug_utils/sierra-emu/src/vm/int_range.rs b/debug_utils/sierra-emu/src/vm/int_range.rs new file mode 100644 index 000000000..50b794bd2 --- /dev/null +++ b/debug_utils/sierra-emu/src/vm/int_range.rs @@ -0,0 +1,101 @@ +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType}, + lib_func::SignatureOnlyConcreteLibfunc, + range::IntRangeConcreteLibfunc, + ConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use num_bigint::BigInt; +use smallvec::smallvec; + +use crate::{ + utils::{get_numeric_args_as_bigints, get_value_from_integer}, + Value, +}; + +use super::EvalAction; + +pub fn eval( + registry: &ProgramRegistry, + selector: &IntRangeConcreteLibfunc, + args: Vec, +) -> EvalAction { + match selector { + IntRangeConcreteLibfunc::TryNew(info) => eval_try_new(registry, info, args), + IntRangeConcreteLibfunc::PopFront(info) => eval_pop_front(registry, info, args), + } +} + +fn eval_try_new( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let range_check @ Value::Unit: Value = args[0].clone() else { + panic!() + }; + let [x, y]: [BigInt; 2] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); + + let int_ty = registry.get_type(&info.param_signatures()[1].ty).unwrap(); + + // if x >= y then the range is not valid and we return [y, y) (empty range) + if x < y { + let x = get_value_from_integer(registry, int_ty, x); + let y = get_value_from_integer(registry, int_ty, y); + EvalAction::NormalBranch( + 0, + smallvec![ + range_check, + Value::IntRange { + x: Box::new(x), + y: Box::new(y), + } + ], + ) + } else { + let y = get_value_from_integer(registry, int_ty, y); + EvalAction::NormalBranch( + 1, + smallvec![ + range_check, + Value::IntRange { + x: Box::new(y.clone()), + y: Box::new(y), + } + ], + ) + } +} + +fn eval_pop_front( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::IntRange { x, y }]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + let [x, y]: [BigInt; 2] = get_numeric_args_as_bigints(&[*x, *y]).try_into().unwrap(); + let int_ty = registry.get_type(&info.param_signatures()[1].ty).unwrap(); + + if x < y { + let x_plus_1 = get_value_from_integer(registry, int_ty, &x + 1); + let x = get_value_from_integer(registry, int_ty, x); + let y = get_value_from_integer(registry, int_ty, y); + + EvalAction::NormalBranch( + 0, + smallvec![ + Value::IntRange { + x: Box::new(x_plus_1), + y: Box::new(y) + }, + x + ], + ) + } else { + EvalAction::NormalBranch(1, smallvec![]) + } +} diff --git a/debug_utils/sierra-emu/tests/common/mod.rs b/debug_utils/sierra-emu/tests/common/mod.rs index 8acea99e7..0f9a0befe 100644 --- a/debug_utils/sierra-emu/tests/common/mod.rs +++ b/debug_utils/sierra-emu/tests/common/mod.rs @@ -83,6 +83,13 @@ pub fn value_to_felt(value: &Value) -> Vec { Value::U64(x) => vec![(*x).into()], Value::U128(x) => vec![(*x).into()], Value::U256(x, y) => vec![(*x).into(), (*y).into()], + Value::IntRange { x, y } => { + let felt = value_to_felt(x); + felts.extend(felt); + let felt = value_to_felt(y); + felts.extend(felt); + felts + } Value::Unit | Value::Null | Value::Uninitialized { .. } => vec![0.into()], } } From e31caaebe3ceac46bb07cb302b9f5bec9049b1df Mon Sep 17 00:00:00 2001 From: Franco Giachetta Date: Fri, 9 May 2025 16:15:34 -0300 Subject: [PATCH 09/28] [Sierra-Emu] Implement missing libfuncs from int (#1192) * Add sierra-emu as a debug utility * Change working dir in sierra-emu job * Remove repeated lines in sierra-emu gitignore * Exclude debug_utils from coverage job * remove some todos from value and implement Coupon libfunc * start adding corelib test * run tests from corelib * remove unwanted file * remove not related things * get return_value from trace * replace ids for debug info * add common folder for integration tests * fix tests * remove todo * ignore test for now * filter ignored tests * Update debug_utils/sierra-emu/tests/common/mod.rs Co-authored-by: Julian Gonzalez Calderon * run ignored test from the ci * better ignoring * remove debug_name in enums * refactor * remove unused dependency * implement missing libfuncs from int * fmt * More expressive naming for the workflow Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> * reviews * reviews * fmt * fmt ci * revert last commit * fmt * accidentally removed rayon usage * avoid map's short-circuiting * catch panics the avoid loosing the test name * log ignored tests * clippy * Add "(WIP)" to corelib's test job Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> * add general libfuncs: const, equal, to_felt, from_felt * add diff libfunc * print progress as the test runs * fmt * fix from_felt * remove unused files + fix issue with non-integer * format makefile Co-authored-by: Julian Gonzalez Calderon * try fix from_felt * fix name test in ci * add dependencies removed in merge * dependencies * clippy * remove unused import * better indent in makefile * uppercase tests summary comment for better sight * simplify a little apply_wrapping_op * merge test-corelib * remove unnecesary pubs * also match builtins instead of filtering them * format * use slices in get_numeric_args_as_bigints * fmt * remove unnecesary to_vec * fmt * use iter instead of into_iter * clippy * reviews + remove uint128 file * rename uint252 file to uint256 * fix clippy --------- Co-authored-by: gabrielbosio Co-authored-by: Julian Gonzalez Calderon Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> --- debug_utils/sierra-emu/src/utils.rs | 54 ++- debug_utils/sierra-emu/src/value.rs | 35 ++ debug_utils/sierra-emu/src/vm.rs | 35 +- debug_utils/sierra-emu/src/vm/int.rs | 391 ++++++++++++++++++ debug_utils/sierra-emu/src/vm/int128.rs | 193 --------- debug_utils/sierra-emu/src/vm/uint128.rs | 257 ------------ debug_utils/sierra-emu/src/vm/uint16.rs | 176 -------- .../src/vm/{uint252.rs => uint256.rs} | 0 debug_utils/sierra-emu/src/vm/uint32.rs | 176 -------- debug_utils/sierra-emu/src/vm/uint512.rs | 2 +- debug_utils/sierra-emu/src/vm/uint64.rs | 176 -------- debug_utils/sierra-emu/src/vm/uint8.rs | 176 -------- 12 files changed, 494 insertions(+), 1177 deletions(-) create mode 100644 debug_utils/sierra-emu/src/vm/int.rs delete mode 100644 debug_utils/sierra-emu/src/vm/int128.rs delete mode 100644 debug_utils/sierra-emu/src/vm/uint128.rs delete mode 100644 debug_utils/sierra-emu/src/vm/uint16.rs rename debug_utils/sierra-emu/src/vm/{uint252.rs => uint256.rs} (100%) delete mode 100644 debug_utils/sierra-emu/src/vm/uint32.rs delete mode 100644 debug_utils/sierra-emu/src/vm/uint64.rs delete mode 100644 debug_utils/sierra-emu/src/vm/uint8.rs diff --git a/debug_utils/sierra-emu/src/utils.rs b/debug_utils/sierra-emu/src/utils.rs index fe618ed90..4c2af0093 100644 --- a/debug_utils/sierra-emu/src/utils.rs +++ b/debug_utils/sierra-emu/src/utils.rs @@ -1,9 +1,13 @@ use cairo_lang_sierra::{ - extensions::core::{CoreLibfunc, CoreType, CoreTypeConcrete}, + extensions::{ + core::{CoreLibfunc, CoreType, CoreTypeConcrete}, + utils::Range, + }, program_registry::ProgramRegistry, }; use num_bigint::BigInt; -use num_traits::ToPrimitive; +use num_traits::{Bounded, One, ToPrimitive}; +use starknet_types_core::felt::CAIRO_PRIME_BIGINT; use crate::Value; @@ -53,3 +57,49 @@ pub fn get_value_from_integer( _ => panic!("cannot get integer value for a non-integer type"), } } + +pub fn integer_range( + ty: &CoreTypeConcrete, + registry: &ProgramRegistry, +) -> Range { + fn range_of() -> Range + where + T: Bounded + Into, + { + Range { + lower: T::min_value().into(), + upper: T::max_value().into() + BigInt::one(), + } + } + + match ty { + CoreTypeConcrete::Uint8(_) => range_of::(), + CoreTypeConcrete::Uint16(_) => range_of::(), + CoreTypeConcrete::Uint32(_) => range_of::(), + CoreTypeConcrete::Uint64(_) => range_of::(), + CoreTypeConcrete::Uint128(_) => range_of::(), + CoreTypeConcrete::Felt252(_) => Range { + lower: BigInt::ZERO, + upper: CAIRO_PRIME_BIGINT.clone(), + }, + CoreTypeConcrete::Sint8(_) => range_of::(), + CoreTypeConcrete::Sint16(_) => range_of::(), + CoreTypeConcrete::Sint32(_) => range_of::(), + CoreTypeConcrete::Sint64(_) => range_of::(), + CoreTypeConcrete::Sint128(_) => range_of::(), + CoreTypeConcrete::BoundedInt(info) => info.range.clone(), + CoreTypeConcrete::Bytes31(_) => Range { + lower: BigInt::ZERO, + upper: BigInt::one() << 248, + }, + CoreTypeConcrete::Const(info) => { + let ty = registry.get_type(&info.inner_ty).unwrap(); + integer_range(ty, registry) + } + CoreTypeConcrete::NonZero(info) => { + let ty = registry.get_type(&info.ty).unwrap(); + integer_range(ty, registry) + } + _ => panic!("cannot get integer range value for a non-integer type"), + } +} diff --git a/debug_utils/sierra-emu/src/value.rs b/debug_utils/sierra-emu/src/value.rs index d7adfa842..ed96be281 100644 --- a/debug_utils/sierra-emu/src/value.rs +++ b/debug_utils/sierra-emu/src/value.rs @@ -250,3 +250,38 @@ impl Value { }) } } + +macro_rules! impl_conversions { + ( $( $t:ty as $i:ident ; )+ ) => { $( + impl From<$t> for Value { + fn from(value: $t) -> Self { + Self::$i(value) + } + } + + impl TryFrom for $t { + type Error = Value; + + fn try_from(value: Value) -> Result { + match value { + Value::$i(value) => Ok(value), + _ => Err(value), + } + } + } + )+ }; +} + +impl_conversions! { + Felt as Felt; + u8 as U8; + u16 as U16; + u32 as U32; + u64 as U64; + u128 as U128; + i8 as I8; + i16 as I16; + i32 as I32; + i64 as I64; + i128 as I128; +} diff --git a/debug_utils/sierra-emu/src/vm.rs b/debug_utils/sierra-emu/src/vm.rs index c1443aab3..e445f2fcd 100644 --- a/debug_utils/sierra-emu/src/vm.rs +++ b/debug_utils/sierra-emu/src/vm.rs @@ -48,7 +48,7 @@ mod felt252_dict; mod felt252_dict_entry; mod function_call; mod gas; -mod int128; +mod int; mod int_range; mod jump; mod mem; @@ -58,13 +58,8 @@ mod poseidon; mod snapshot_take; mod starknet; mod r#struct; -mod uint128; -mod uint16; -mod uint252; -mod uint32; +mod uint256; mod uint512; -mod uint64; -mod uint8; #[derive(Clone)] pub struct VirtualMachine { @@ -497,23 +492,23 @@ fn eval<'a>( CoreConcreteLibfunc::Nullable(selector) => self::nullable::eval(registry, selector, args), CoreConcreteLibfunc::Pedersen(selector) => self::pedersen::eval(registry, selector, args), CoreConcreteLibfunc::Poseidon(selector) => self::poseidon::eval(registry, selector, args), - CoreConcreteLibfunc::Sint128(selector) => self::int128::eval(registry, selector, args), - CoreConcreteLibfunc::Sint16(_) => todo!(), - CoreConcreteLibfunc::Sint32(_) => todo!(), - CoreConcreteLibfunc::Sint64(_) => todo!(), - CoreConcreteLibfunc::Sint8(_) => todo!(), + CoreConcreteLibfunc::Sint8(selector) => self::int::eval_signed(registry, selector, args), + CoreConcreteLibfunc::Sint16(selector) => self::int::eval_signed(registry, selector, args), + CoreConcreteLibfunc::Sint32(selector) => self::int::eval_signed(registry, selector, args), + CoreConcreteLibfunc::Sint64(selector) => self::int::eval_signed(registry, selector, args), + CoreConcreteLibfunc::Sint128(selector) => self::int::eval_i128(registry, selector, args), + CoreConcreteLibfunc::Uint8(selector) => self::int::eval_unsigned(registry, selector, args), + CoreConcreteLibfunc::Uint16(selector) => self::int::eval_unsigned(registry, selector, args), + CoreConcreteLibfunc::Uint32(selector) => self::int::eval_unsigned(registry, selector, args), + CoreConcreteLibfunc::Uint64(selector) => self::int::eval_unsigned(registry, selector, args), + CoreConcreteLibfunc::Uint128(selector) => self::int::eval_uint128(registry, selector, args), + CoreConcreteLibfunc::Uint256(selector) => self::uint256::eval(registry, selector, args), + CoreConcreteLibfunc::Uint512(selector) => self::uint512::eval(registry, selector, args), + CoreConcreteLibfunc::Struct(selector) => self::r#struct::eval(registry, selector, args), CoreConcreteLibfunc::SnapshotTake(info) => self::snapshot_take::eval(registry, info, args), CoreConcreteLibfunc::Starknet(selector) => { self::starknet::eval(registry, selector, args, syscall_handler) } - CoreConcreteLibfunc::Struct(selector) => self::r#struct::eval(registry, selector, args), - CoreConcreteLibfunc::Uint128(selector) => self::uint128::eval(registry, selector, args), - CoreConcreteLibfunc::Uint16(selector) => self::uint16::eval(registry, selector, args), - CoreConcreteLibfunc::Uint256(selector) => self::uint252::eval(registry, selector, args), - CoreConcreteLibfunc::Uint32(selector) => self::uint32::eval(registry, selector, args), - CoreConcreteLibfunc::Uint512(selector) => self::uint512::eval(registry, selector, args), - CoreConcreteLibfunc::Uint64(selector) => self::uint64::eval(registry, selector, args), - CoreConcreteLibfunc::Uint8(selector) => self::uint8::eval(registry, selector, args), CoreConcreteLibfunc::UnconditionalJump(info) => self::jump::eval(registry, info, args), CoreConcreteLibfunc::UnwrapNonZero(_info) => { let [value] = args.try_into().unwrap(); diff --git a/debug_utils/sierra-emu/src/vm/int.rs b/debug_utils/sierra-emu/src/vm/int.rs new file mode 100644 index 000000000..88d360ef0 --- /dev/null +++ b/debug_utils/sierra-emu/src/vm/int.rs @@ -0,0 +1,391 @@ +use std::fmt::Debug; + +use cairo_lang_sierra::{ + extensions::{ + core::{CoreLibfunc, CoreType, CoreTypeConcrete}, + int::{ + signed::{SintConcrete, SintTraits}, + signed128::Sint128Concrete, + unsigned::{UintConcrete, UintTraits}, + unsigned128::Uint128Concrete, + IntConstConcreteLibfunc, IntMulTraits, IntOperationConcreteLibfunc, IntOperator, + IntTraits, + }, + is_zero::IsZeroTraits, + lib_func::SignatureOnlyConcreteLibfunc, + }, + program_registry::ProgramRegistry, +}; +use num_bigint::{BigInt, BigUint}; +use num_traits::ops::overflowing::{OverflowingAdd, OverflowingSub}; +use smallvec::smallvec; +use starknet_crypto::Felt; + +use crate::{ + utils::{get_numeric_args_as_bigints, get_value_from_integer, integer_range}, + Value, +}; + +use super::EvalAction; + +fn apply_overflowing_op_for_type( + ty: &CoreTypeConcrete, + lhs: BigInt, + rhs: BigInt, + op: IntOperator, +) -> (BigInt, bool) { + fn overflowing_op(lhs: BigInt, rhs: BigInt, op: IntOperator) -> (BigInt, bool) + where + T: OverflowingAdd + OverflowingSub + Into + TryFrom, + >::Error: Debug, + { + let lhs: T = lhs.try_into().unwrap(); + let rhs: T = rhs.try_into().unwrap(); + + let (result, had_overflow) = match op { + IntOperator::OverflowingAdd => OverflowingAdd::overflowing_add(&lhs, &rhs), + IntOperator::OverflowingSub => OverflowingSub::overflowing_sub(&lhs, &rhs), + }; + + (result.into(), had_overflow) + } + + match ty { + CoreTypeConcrete::Sint8(_) => overflowing_op::(lhs, rhs, op), + CoreTypeConcrete::Sint16(_) => overflowing_op::(lhs, rhs, op), + CoreTypeConcrete::Sint32(_) => overflowing_op::(lhs, rhs, op), + CoreTypeConcrete::Sint64(_) => overflowing_op::(lhs, rhs, op), + CoreTypeConcrete::Sint128(_) => overflowing_op::(lhs, rhs, op), + CoreTypeConcrete::Uint8(_) => overflowing_op::(lhs, rhs, op), + CoreTypeConcrete::Uint16(_) => overflowing_op::(lhs, rhs, op), + CoreTypeConcrete::Uint32(_) => overflowing_op::(lhs, rhs, op), + CoreTypeConcrete::Uint64(_) => overflowing_op::(lhs, rhs, op), + CoreTypeConcrete::Uint128(_) => overflowing_op::(lhs, rhs, op), + _ => panic!("cannot apply integer operation to non-integer type"), + } +} + +pub fn eval_signed( + registry: &ProgramRegistry, + selector: &SintConcrete, + args: Vec, +) -> EvalAction +where + T: IntMulTraits + SintTraits, +{ + match selector { + SintConcrete::Const(info) => eval_const(registry, info, args), + SintConcrete::Diff(info) => eval_diff(registry, info, args), + SintConcrete::Equal(info) => eval_equal(registry, info, args), + SintConcrete::FromFelt252(info) => eval_from_felt(registry, info, args), + SintConcrete::Operation(info) => eval_operation(registry, info, args), + SintConcrete::ToFelt252(info) => eval_to_felt(registry, info, args), + SintConcrete::WideMul(info) => eval_widemul(registry, info, args), + } +} + +pub fn eval_i128( + registry: &ProgramRegistry, + selector: &Sint128Concrete, + args: Vec, +) -> EvalAction { + match selector { + Sint128Concrete::Const(info) => eval_const(registry, info, args), + Sint128Concrete::Diff(info) => eval_diff(registry, info, args), + Sint128Concrete::Equal(info) => eval_equal(registry, info, args), + Sint128Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), + Sint128Concrete::Operation(info) => eval_operation(registry, info, args), + Sint128Concrete::ToFelt252(info) => eval_to_felt(registry, info, args), + } +} + +pub fn eval_unsigned( + registry: &ProgramRegistry, + selector: &UintConcrete, + args: Vec, +) -> EvalAction +where + T: IntMulTraits + IsZeroTraits + UintTraits, +{ + match selector { + UintConcrete::Const(info) => eval_const(registry, info, args), + UintConcrete::Bitwise(info) => eval_bitwise(registry, info, args), + UintConcrete::Divmod(info) => eval_divmod(registry, info, args), + UintConcrete::Equal(info) => eval_equal(registry, info, args), + UintConcrete::FromFelt252(info) => eval_from_felt(registry, info, args), + UintConcrete::Operation(info) => eval_operation(registry, info, args), + UintConcrete::IsZero(info) => eval_is_zero(registry, info, args), + UintConcrete::SquareRoot(info) => eval_square_root(registry, info, args), + UintConcrete::ToFelt252(info) => eval_to_felt(registry, info, args), + UintConcrete::WideMul(info) => eval_widemul(registry, info, args), + } +} + +pub fn eval_uint128( + registry: &ProgramRegistry, + selector: &Uint128Concrete, + args: Vec, +) -> EvalAction { + match selector { + Uint128Concrete::Const(info) => eval_const(registry, info, args), + Uint128Concrete::Operation(info) => eval_operation(registry, info, args), + Uint128Concrete::SquareRoot(info) => eval_square_root(registry, info, args), + Uint128Concrete::Equal(info) => eval_equal(registry, info, args), + Uint128Concrete::ToFelt252(info) => eval_to_felt(registry, info, args), + Uint128Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), + Uint128Concrete::IsZero(info) => eval_is_zero(registry, info, args), + Uint128Concrete::Divmod(info) => eval_divmod(registry, info, args), + Uint128Concrete::Bitwise(info) => eval_bitwise(registry, info, args), + Uint128Concrete::GuaranteeMul(info) => eval_guarantee_mul(registry, info, args), + Uint128Concrete::MulGuaranteeVerify(info) => eval_guarantee_verify(registry, info, args), + Uint128Concrete::ByteReverse(info) => eval_byte_reverse(registry, info, args), + } +} + +fn eval_const( + registry: &ProgramRegistry, + info: &IntConstConcreteLibfunc, + _args: Vec, +) -> EvalAction { + let int_ty = registry + .get_type(&info.signature.branch_signatures[0].vars[0].ty) + .unwrap(); + + EvalAction::NormalBranch( + 0, + smallvec![get_value_from_integer(registry, int_ty, info.c.into())], + ) +} + +fn eval_bitwise( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let bitwise @ Value::Unit: Value = args[0].clone() else { + panic!() + }; + let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); + + let int_ty = registry + .get_type(&info.signature.branch_signatures[0].vars[1].ty) + .unwrap(); + + let and = get_value_from_integer(registry, int_ty, &lhs & &rhs); + let or = get_value_from_integer(registry, int_ty, &lhs | &rhs); + let xor = get_value_from_integer(registry, int_ty, &lhs ^ &rhs); + + EvalAction::NormalBranch(0, smallvec![bitwise, and, xor, or]) +} + +fn eval_diff( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let range_check @ Value::Unit: Value = args[0].clone() else { + panic!() + }; + let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); + + let int_ty = registry + .get_type(&info.signature.branch_signatures[0].vars[1].ty) + .unwrap(); + + let overflow = (lhs >= rhs) as usize; + let (res, _) = apply_overflowing_op_for_type(int_ty, lhs, rhs, IntOperator::OverflowingSub); + let res = get_value_from_integer(registry, int_ty, res); + + EvalAction::NormalBranch(overflow, smallvec![range_check, res]) +} + +fn eval_divmod( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let range_check @ Value::Unit: Value = args[0].clone() else { + panic!() + }; + let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); + + let int_ty = registry + .get_type(&info.signature.branch_signatures[0].vars[1].ty) + .unwrap(); + + let res = &lhs / &rhs; + let rem = lhs % rhs; + + let res = get_value_from_integer(registry, int_ty, res); + let rem = get_value_from_integer(registry, int_ty, rem); + + EvalAction::NormalBranch(0, smallvec![range_check, res, rem]) +} + +fn eval_equal( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [lhs, rhs] = args.try_into().unwrap(); + + EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) +} + +fn eval_from_felt( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::Felt(value_felt)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + let int_ty = registry + .get_type(&info.signature.branch_signatures[0].vars[1].ty) + .unwrap(); + + let range = integer_range(int_ty, registry); + + let value = value_felt.to_bigint(); + + if value > range.lower && value <= range.upper { + let value = get_value_from_integer(registry, int_ty, value); + EvalAction::NormalBranch(0, smallvec![range_check, value]) + } else { + EvalAction::NormalBranch(1, smallvec![range_check]) + } +} + +fn eval_is_zero( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [value]: [BigInt; 1] = get_numeric_args_as_bigints(&args).try_into().unwrap(); + + let int_ty = registry + .get_type(&info.signature.branch_signatures[1].vars[0].ty) + .unwrap(); + + if value == 0.into() { + EvalAction::NormalBranch(0, smallvec![]) + } else { + EvalAction::NormalBranch( + 1, + smallvec![get_value_from_integer(registry, int_ty, value)], + ) + } +} + +fn eval_operation( + registry: &ProgramRegistry, + info: &IntOperationConcreteLibfunc, + args: Vec, +) -> EvalAction { + let range_check @ Value::Unit: Value = args[0].clone() else { + panic!() + }; + let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); + let int_ty = registry + .get_type(&info.signature.branch_signatures[0].vars[1].ty) + .unwrap(); + + let (res, had_overflow) = apply_overflowing_op_for_type(int_ty, lhs, rhs, info.operator); + let res = get_value_from_integer(registry, int_ty, res); + + EvalAction::NormalBranch(had_overflow as usize, smallvec![range_check, res]) +} + +fn eval_square_root( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let range_check @ Value::Unit: Value = args[0].clone() else { + panic!() + }; + let [value]: [BigInt; 1] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); + + let int_ty = registry + .get_type(&info.signature.branch_signatures[0].vars[1].ty) + .unwrap(); + + let res = value.sqrt(); + + EvalAction::NormalBranch( + 0, + smallvec![range_check, get_value_from_integer(registry, int_ty, res)], + ) +} + +fn eval_to_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [val]: [BigInt; 1] = get_numeric_args_as_bigints(&args).try_into().unwrap(); + + EvalAction::NormalBranch(0, smallvec![Value::Felt(Felt::from(val))]) +} + +fn eval_widemul( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args).try_into().unwrap(); + + let int_ty = registry + .get_type(&info.signature.branch_signatures[0].vars[0].ty) + .unwrap(); + + let res = lhs * rhs; + + EvalAction::NormalBranch(0, smallvec![get_value_from_integer(registry, int_ty, res)]) +} + +fn eval_guarantee_mul( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [Value::U128(lhs), Value::U128(rhs)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + let mask128 = BigUint::from(u128::MAX); + let result = BigUint::from(lhs) * BigUint::from(rhs); + let high = Value::U128((&result >> 128u32).try_into().unwrap()); + let low = Value::U128((result & mask128).try_into().unwrap()); + + EvalAction::NormalBranch(0, smallvec![high, low, Value::Unit]) +} + +fn eval_guarantee_verify( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, _verify @ Value::Unit]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + EvalAction::NormalBranch(0, smallvec![range_check]) +} + +fn eval_byte_reverse( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [bitwise @ Value::Unit, Value::U128(value)]: [Value; 2] = args.try_into().unwrap() else { + panic!() + }; + + let value = value.swap_bytes(); + + EvalAction::NormalBranch(0, smallvec![bitwise, Value::U128(value)]) +} diff --git a/debug_utils/sierra-emu/src/vm/int128.rs b/debug_utils/sierra-emu/src/vm/int128.rs deleted file mode 100644 index 09f6a91e5..000000000 --- a/debug_utils/sierra-emu/src/vm/int128.rs +++ /dev/null @@ -1,193 +0,0 @@ -use cairo_lang_sierra::{ - extensions::{ - core::{CoreLibfunc, CoreType}, - int::{signed128::Sint128Concrete, IntOperationConcreteLibfunc, IntOperator}, - lib_func::SignatureOnlyConcreteLibfunc, - }, - program_registry::ProgramRegistry, -}; -use num_bigint::{BigInt, BigUint, ToBigInt}; -use smallvec::smallvec; -use starknet_crypto::Felt; - -use crate::Value; - -use super::EvalAction; - -pub fn eval( - registry: &ProgramRegistry, - selector: &Sint128Concrete, - args: Vec, -) -> EvalAction { - match selector { - Sint128Concrete::Const(_) => todo!("int128 const"), - Sint128Concrete::Operation(info) => eval_operation(registry, info, args), - Sint128Concrete::Equal(info) => eval_equal(registry, info, args), - Sint128Concrete::ToFelt252(info) => eval_to_felt(registry, info, args), - Sint128Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), - Sint128Concrete::Diff(info) => eval_diff(registry, info, args), - } -} - -fn eval_diff( - _registry: &ProgramRegistry, - _selector: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::I128(lhs), Value::I128(rhs)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - if lhs >= rhs { - EvalAction::NormalBranch( - 0, - smallvec![range_check, Value::U128((lhs - rhs).try_into().unwrap())], - ) - } else { - EvalAction::NormalBranch( - 1, - smallvec![ - range_check, - Value::U128(lhs.wrapping_sub(rhs).try_into().unwrap()) - ], - ) - } -} - -fn eval_operation( - _registry: &ProgramRegistry, - selector: &IntOperationConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::I128(lhs), Value::I128(rhs)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let (result, overflow) = match selector.operator { - IntOperator::OverflowingAdd => lhs.overflowing_add(rhs), - IntOperator::OverflowingSub => lhs.overflowing_sub(rhs), - }; - - EvalAction::NormalBranch( - overflow as usize, - smallvec![range_check, Value::I128(result)], - ) -} - -fn eval_equal( - _registry: &ProgramRegistry, - _selector: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::I128(lhs), Value::I128(rhs)]: [Value; 2] = args.try_into().unwrap() else { - panic!() - }; - - EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) -} - -pub fn eval_to_felt( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::I128(value)]: [Value; 1] = args.try_into().unwrap() else { - panic!() - }; - - EvalAction::NormalBranch(0, smallvec![Value::Felt(value.into())]) -} - -pub fn eval_from_felt( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::Felt(value_felt)]: [Value; 2] = args.try_into().unwrap() - else { - panic!() - }; - let prime = Felt::prime(); - let half_prime = &prime / BigUint::from(2u8); - - let min = Felt::from(i128::MIN).to_bigint(); - let max = Felt::from(i128::MAX).to_bigint(); - - let value = { - if value_felt.to_biguint() > half_prime { - (prime - value_felt.to_biguint()).to_bigint().unwrap() * BigInt::from(-1) - } else { - value_felt.to_bigint() - } - }; - - if value >= min || value <= max { - let value: i128 = value.try_into().unwrap(); - EvalAction::NormalBranch(0, smallvec![range_check, Value::I128(value)]) - } else { - EvalAction::NormalBranch(1, smallvec![range_check]) - } -} - -#[cfg(test)] -mod tests { - use crate::{load_cairo, test_utils::run_test_program, Value}; - - #[test] - fn test_eval_diff() { - let (_, program) = load_cairo!( - use core::integer; - - fn main() -> u128 { - integer::i128_diff(2, 1).unwrap() - } - ); - - let result = run_test_program(program); - - let Value::Enum { - self_ty: _, - index: _, - payload, - } = result.last().unwrap() - else { - panic!("No output"); - }; - - let expected = Value::Struct(vec![Value::U128(1)]); - - assert_eq!(**payload, expected); - } - - #[test] - fn test_eval_from_felt() { - let (_, program) = load_cairo!( - use core::integer; - - fn main() -> i128 { - 0x7fffffffffffffffffffffffffffffff_felt252 - .try_into() - .unwrap() - } - ); - - let result = run_test_program(program); - - let Value::Enum { - self_ty: _, - index: _, - payload, - } = result.last().unwrap() - else { - panic!("No output"); - }; - - let expected = Value::Struct(vec![Value::I128(0x7fffffffffffffffffffffffffffffff)]); - - assert_eq!(**payload, expected); - } -} diff --git a/debug_utils/sierra-emu/src/vm/uint128.rs b/debug_utils/sierra-emu/src/vm/uint128.rs deleted file mode 100644 index 3da80ec7a..000000000 --- a/debug_utils/sierra-emu/src/vm/uint128.rs +++ /dev/null @@ -1,257 +0,0 @@ -use super::EvalAction; -use crate::Value; -use cairo_lang_sierra::{ - extensions::{ - core::{CoreLibfunc, CoreType}, - int::{ - unsigned128::{Uint128Concrete, Uint128Traits}, - IntConstConcreteLibfunc, IntOperationConcreteLibfunc, IntOperator, - }, - lib_func::SignatureOnlyConcreteLibfunc, - }, - program_registry::ProgramRegistry, -}; -use num_bigint::BigUint; -use smallvec::smallvec; -use starknet_crypto::Felt; -use starknet_types_core::felt::NonZeroFelt; - -pub fn eval( - registry: &ProgramRegistry, - selector: &Uint128Concrete, - args: Vec, -) -> EvalAction { - match selector { - Uint128Concrete::Const(info) => eval_const(registry, info, args), - Uint128Concrete::Operation(info) => eval_operation(registry, info, args), - Uint128Concrete::SquareRoot(info) => eval_square_root(registry, info, args), - Uint128Concrete::Equal(info) => eval_equal(registry, info, args), - Uint128Concrete::ToFelt252(info) => eval_to_felt(registry, info, args), - Uint128Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), - Uint128Concrete::IsZero(info) => eval_is_zero(registry, info, args), - Uint128Concrete::Divmod(info) => eval_divmod(registry, info, args), - Uint128Concrete::Bitwise(info) => eval_bitwise(registry, info, args), - Uint128Concrete::GuaranteeMul(info) => eval_guarantee_mul(registry, info, args), - Uint128Concrete::MulGuaranteeVerify(info) => eval_guarantee_verify(registry, info, args), - Uint128Concrete::ByteReverse(info) => eval_byte_reverse(registry, info, args), - } -} - -pub fn eval_guarantee_mul( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U128(lhs), Value::U128(rhs)]: [Value; 2] = args.try_into().unwrap() else { - panic!() - }; - - let mask128 = BigUint::from(u128::MAX); - let result = BigUint::from(lhs) * BigUint::from(rhs); - let high = Value::U128((&result >> 128u32).try_into().unwrap()); - let low = Value::U128((result & mask128).try_into().unwrap()); - - EvalAction::NormalBranch(0, smallvec![high, low, Value::Unit]) -} - -pub fn eval_square_root( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::U128(value)]: [Value; 2] = args.try_into().unwrap() - else { - panic!() - }; - - let value_big = BigUint::from(value); - - let result: u64 = value_big.sqrt().try_into().unwrap(); - - EvalAction::NormalBranch(0, smallvec![range_check, Value::U64(result)]) -} - -pub fn eval_guarantee_verify( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, _verify @ Value::Unit]: [Value; 2] = args.try_into().unwrap() - else { - panic!() - }; - - EvalAction::NormalBranch(0, smallvec![range_check]) -} - -pub fn eval_divmod( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::U128(x), Value::U128(y)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let val = Value::U128(x / y); - let rem = Value::U128(x % y); - - EvalAction::NormalBranch(0, smallvec![range_check, val, rem]) -} - -pub fn eval_operation( - _registry: &ProgramRegistry, - info: &IntOperationConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::U128(lhs), Value::U128(rhs)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let (result, has_overflow) = match info.operator { - IntOperator::OverflowingAdd => lhs.overflowing_add(rhs), - IntOperator::OverflowingSub => lhs.overflowing_sub(rhs), - }; - - EvalAction::NormalBranch( - has_overflow as usize, - smallvec![range_check, Value::U128(result)], - ) -} - -pub fn eval_equal( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U128(lhs), Value::U128(rhs)]: [Value; 2] = args.try_into().unwrap() else { - panic!() - }; - - EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) -} - -pub fn eval_is_zero( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [vm_value @ Value::U128(value)]: [Value; 1] = args.try_into().unwrap() else { - panic!() - }; - - if value == 0 { - EvalAction::NormalBranch(0, smallvec![]) - } else { - EvalAction::NormalBranch(1, smallvec![vm_value]) - } -} - -pub fn eval_bitwise( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [bitwise @ Value::Unit, Value::U128(lhs), Value::U128(rhs)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let and = lhs & rhs; - let or = lhs | rhs; - let xor = lhs ^ rhs; - - EvalAction::NormalBranch( - 0, - smallvec![bitwise, Value::U128(and), Value::U128(xor), Value::U128(or)], - ) -} - -pub fn eval_const( - _registry: &ProgramRegistry, - info: &IntConstConcreteLibfunc, - _args: Vec, -) -> EvalAction { - EvalAction::NormalBranch(0, smallvec![Value::U128(info.c)]) -} - -pub fn eval_to_felt( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U128(value)]: [Value; 1] = args.try_into().unwrap() else { - panic!() - }; - - EvalAction::NormalBranch(0, smallvec![Value::Felt(value.into())]) -} - -pub fn eval_from_felt( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() - else { - panic!() - }; - - let bound = Felt::from(u128::MAX) + 1; - - if value < bound { - let value: u128 = value.to_biguint().try_into().unwrap(); - EvalAction::NormalBranch(0, smallvec![range_check, Value::U128(value)]) - } else { - let (new_value, overflow) = value.div_rem(&NonZeroFelt::try_from(bound).unwrap()); - - let overflow: u128 = overflow.to_biguint().try_into().unwrap(); - let new_value: u128 = new_value.to_biguint().try_into().unwrap(); - EvalAction::NormalBranch( - 1, - smallvec![range_check, Value::U128(new_value), Value::U128(overflow)], - ) - } -} - -pub fn eval_byte_reverse( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [bitwise @ Value::Unit, Value::U128(value)]: [Value; 2] = args.try_into().unwrap() else { - panic!() - }; - - let value = value.swap_bytes(); - - EvalAction::NormalBranch(0, smallvec![bitwise, Value::U128(value)]) -} - -#[cfg(test)] -mod test { - use crate::{load_cairo, test_utils::run_test_program, Value}; - - #[test] - fn test_square_root() { - let (_, program) = load_cairo!( - use core::num::traits::Sqrt; - fn main() -> u64 { - 0xffffffffffffffffffffffffffffffff_u128.sqrt() - } - ); - - let result = run_test_program(program); - - let Value::U64(payload) = result.last().unwrap() else { - panic!("No output"); - }; - - assert_eq!(*payload, 0xffffffffffffffff); - } -} diff --git a/debug_utils/sierra-emu/src/vm/uint16.rs b/debug_utils/sierra-emu/src/vm/uint16.rs deleted file mode 100644 index 29d9269c5..000000000 --- a/debug_utils/sierra-emu/src/vm/uint16.rs +++ /dev/null @@ -1,176 +0,0 @@ -use super::EvalAction; -use crate::Value; -use cairo_lang_sierra::{ - extensions::{ - core::{CoreLibfunc, CoreType}, - int::{ - unsigned::{Uint16Concrete, Uint16Traits}, - IntConstConcreteLibfunc, IntOperationConcreteLibfunc, IntOperator, - }, - lib_func::SignatureOnlyConcreteLibfunc, - }, - program_registry::ProgramRegistry, -}; -use smallvec::smallvec; -use starknet_crypto::Felt; - -pub fn eval( - registry: &ProgramRegistry, - selector: &Uint16Concrete, - args: Vec, -) -> EvalAction { - match selector { - Uint16Concrete::Const(info) => eval_const(registry, info, args), - Uint16Concrete::Operation(info) => eval_operation(registry, info, args), - Uint16Concrete::SquareRoot(_) => todo!(), - Uint16Concrete::Equal(info) => eval_equal(registry, info, args), - Uint16Concrete::ToFelt252(info) => eval_to_felt252(registry, info, args), - Uint16Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), - Uint16Concrete::IsZero(info) => eval_is_zero(registry, info, args), - Uint16Concrete::Divmod(info) => eval_divmod(registry, info, args), - Uint16Concrete::WideMul(info) => eval_widemul(registry, info, args), - Uint16Concrete::Bitwise(info) => eval_bitwise(registry, info, args), - } -} - -pub fn eval_divmod( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::U16(x), Value::U16(y)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let val = Value::U16(x / y); - let rem = Value::U16(x % y); - - EvalAction::NormalBranch(0, smallvec![range_check, val, rem]) -} - -pub fn eval_operation( - _registry: &ProgramRegistry, - info: &IntOperationConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::U16(lhs), Value::U16(rhs)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let (result, has_overflow) = match info.operator { - IntOperator::OverflowingAdd => lhs.overflowing_add(rhs), - IntOperator::OverflowingSub => lhs.overflowing_sub(rhs), - }; - - EvalAction::NormalBranch( - has_overflow as usize, - smallvec![range_check, Value::U16(result)], - ) -} - -pub fn eval_from_felt( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() - else { - panic!() - }; - - let max = Felt::from(u16::MAX); - - if value <= max { - let value: u16 = value.to_biguint().try_into().unwrap(); - EvalAction::NormalBranch(0, smallvec![range_check, Value::U16(value)]) - } else { - EvalAction::NormalBranch(1, smallvec![range_check]) - } -} - -pub fn eval_equal( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U16(lhs), Value::U16(rhs)]: [Value; 2] = args.try_into().unwrap() else { - panic!() - }; - - EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) -} - -pub fn eval_bitwise( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [bitwise @ Value::Unit, Value::U16(lhs), Value::U16(rhs)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let and = lhs & rhs; - let or = lhs | rhs; - let xor = lhs ^ rhs; - - EvalAction::NormalBranch( - 0, - smallvec![bitwise, Value::U16(and), Value::U16(xor), Value::U16(or)], - ) -} - -pub fn eval_is_zero( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [vm_value @ Value::U16(value)]: [Value; 1] = args.try_into().unwrap() else { - panic!() - }; - - if value == 0 { - EvalAction::NormalBranch(0, smallvec![]) - } else { - EvalAction::NormalBranch(1, smallvec![vm_value]) - } -} - -pub fn eval_to_felt252( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U16(value)]: [Value; 1] = args.try_into().unwrap() else { - panic!() - }; - - EvalAction::NormalBranch(0, smallvec![Value::Felt(value.into())]) -} - -pub fn eval_const( - _registry: &ProgramRegistry, - info: &IntConstConcreteLibfunc, - _args: Vec, -) -> EvalAction { - EvalAction::NormalBranch(0, smallvec![Value::U16(info.c)]) -} - -pub fn eval_widemul( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U16(lhs), Value::U16(rhs)]: [Value; 2] = args.try_into().unwrap() else { - panic!() - }; - - let result = (lhs as u32) * (rhs as u32); - - EvalAction::NormalBranch(0, smallvec![Value::U32(result)]) -} diff --git a/debug_utils/sierra-emu/src/vm/uint252.rs b/debug_utils/sierra-emu/src/vm/uint256.rs similarity index 100% rename from debug_utils/sierra-emu/src/vm/uint252.rs rename to debug_utils/sierra-emu/src/vm/uint256.rs diff --git a/debug_utils/sierra-emu/src/vm/uint32.rs b/debug_utils/sierra-emu/src/vm/uint32.rs deleted file mode 100644 index 6c9e2c3eb..000000000 --- a/debug_utils/sierra-emu/src/vm/uint32.rs +++ /dev/null @@ -1,176 +0,0 @@ -use super::EvalAction; -use crate::Value; -use cairo_lang_sierra::{ - extensions::{ - core::{CoreLibfunc, CoreType}, - int::{ - unsigned::{Uint32Concrete, Uint32Traits}, - IntConstConcreteLibfunc, IntOperationConcreteLibfunc, IntOperator, - }, - lib_func::SignatureOnlyConcreteLibfunc, - }, - program_registry::ProgramRegistry, -}; -use smallvec::smallvec; -use starknet_crypto::Felt; - -pub fn eval( - registry: &ProgramRegistry, - selector: &Uint32Concrete, - args: Vec, -) -> EvalAction { - match selector { - Uint32Concrete::Const(info) => eval_const(registry, info, args), - Uint32Concrete::Operation(info) => eval_operation(registry, info, args), - Uint32Concrete::SquareRoot(_) => todo!(), - Uint32Concrete::Equal(info) => eval_equal(registry, info, args), - Uint32Concrete::ToFelt252(info) => eval_to_felt252(registry, info, args), - Uint32Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), - Uint32Concrete::IsZero(info) => eval_is_zero(registry, info, args), - Uint32Concrete::Divmod(info) => eval_divmod(registry, info, args), - Uint32Concrete::WideMul(info) => eval_widemul(registry, info, args), - Uint32Concrete::Bitwise(info) => eval_bitwise(registry, info, args), - } -} - -pub fn eval_divmod( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::U32(x), Value::U32(y)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let val = Value::U32(x / y); - let rem = Value::U32(x % y); - - EvalAction::NormalBranch(0, smallvec![range_check, val, rem]) -} - -pub fn eval_operation( - _registry: &ProgramRegistry, - info: &IntOperationConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::U32(lhs), Value::U32(rhs)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let (result, has_overflow) = match info.operator { - IntOperator::OverflowingAdd => lhs.overflowing_add(rhs), - IntOperator::OverflowingSub => lhs.overflowing_sub(rhs), - }; - - EvalAction::NormalBranch( - has_overflow as usize, - smallvec![range_check, Value::U32(result)], - ) -} - -pub fn eval_from_felt( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() - else { - panic!() - }; - - let max = Felt::from(u32::MAX); - - if value <= max { - let value: u32 = value.to_biguint().try_into().unwrap(); - EvalAction::NormalBranch(0, smallvec![range_check, Value::U32(value)]) - } else { - EvalAction::NormalBranch(1, smallvec![range_check]) - } -} - -pub fn eval_equal( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U32(lhs), Value::U32(rhs)]: [Value; 2] = args.try_into().unwrap() else { - panic!() - }; - - EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) -} - -pub fn eval_bitwise( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [bitwise @ Value::Unit, Value::U32(lhs), Value::U32(rhs)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let and = lhs & rhs; - let or = lhs | rhs; - let xor = lhs ^ rhs; - - EvalAction::NormalBranch( - 0, - smallvec![bitwise, Value::U32(and), Value::U32(xor), Value::U32(or)], - ) -} - -pub fn eval_is_zero( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [vm_value @ Value::U32(value)]: [Value; 1] = args.try_into().unwrap() else { - panic!() - }; - - if value == 0 { - EvalAction::NormalBranch(0, smallvec![]) - } else { - EvalAction::NormalBranch(1, smallvec![vm_value]) - } -} - -pub fn eval_to_felt252( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U32(value)]: [Value; 1] = args.try_into().unwrap() else { - panic!() - }; - - EvalAction::NormalBranch(0, smallvec![Value::Felt(value.into())]) -} - -pub fn eval_const( - _registry: &ProgramRegistry, - info: &IntConstConcreteLibfunc, - _args: Vec, -) -> EvalAction { - EvalAction::NormalBranch(0, smallvec![Value::U32(info.c)]) -} - -pub fn eval_widemul( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U32(lhs), Value::U32(rhs)]: [Value; 2] = args.try_into().unwrap() else { - panic!() - }; - - let result = (lhs as u64) * (rhs as u64); - - EvalAction::NormalBranch(0, smallvec![Value::U64(result)]) -} diff --git a/debug_utils/sierra-emu/src/vm/uint512.rs b/debug_utils/sierra-emu/src/vm/uint512.rs index 03a626874..84ffbb8f7 100644 --- a/debug_utils/sierra-emu/src/vm/uint512.rs +++ b/debug_utils/sierra-emu/src/vm/uint512.rs @@ -1,6 +1,6 @@ use super::EvalAction; use crate::{ - vm::uint252::{u256_to_biguint, u256_to_value, u516_to_value}, + vm::uint256::{u256_to_biguint, u256_to_value, u516_to_value}, Value, }; use cairo_lang_sierra::{ diff --git a/debug_utils/sierra-emu/src/vm/uint64.rs b/debug_utils/sierra-emu/src/vm/uint64.rs deleted file mode 100644 index 81622690c..000000000 --- a/debug_utils/sierra-emu/src/vm/uint64.rs +++ /dev/null @@ -1,176 +0,0 @@ -use super::EvalAction; -use crate::Value; -use cairo_lang_sierra::{ - extensions::{ - core::{CoreLibfunc, CoreType}, - int::{ - unsigned::{Uint64Concrete, Uint64Traits}, - IntConstConcreteLibfunc, IntOperationConcreteLibfunc, IntOperator, - }, - lib_func::SignatureOnlyConcreteLibfunc, - }, - program_registry::ProgramRegistry, -}; -use smallvec::smallvec; -use starknet_crypto::Felt; - -pub fn eval( - registry: &ProgramRegistry, - selector: &Uint64Concrete, - args: Vec, -) -> EvalAction { - match selector { - Uint64Concrete::Const(info) => eval_const(registry, info, args), - Uint64Concrete::Operation(info) => eval_operation(registry, info, args), - Uint64Concrete::SquareRoot(_) => todo!(), - Uint64Concrete::Equal(info) => eval_equal(registry, info, args), - Uint64Concrete::ToFelt252(info) => eval_to_felt252(registry, info, args), - Uint64Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), - Uint64Concrete::IsZero(info) => eval_is_zero(registry, info, args), - Uint64Concrete::Divmod(info) => eval_divmod(registry, info, args), - Uint64Concrete::WideMul(info) => eval_widemul(registry, info, args), - Uint64Concrete::Bitwise(info) => eval_bitwise(registry, info, args), - } -} - -pub fn eval_divmod( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::U64(x), Value::U64(y)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let val = Value::U64(x / y); - let rem = Value::U64(x % y); - - EvalAction::NormalBranch(0, smallvec![range_check, val, rem]) -} - -pub fn eval_from_felt( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() - else { - panic!() - }; - - let max = Felt::from(u64::MAX); - - if value <= max { - let value: u64 = value.to_biguint().try_into().unwrap(); - EvalAction::NormalBranch(0, smallvec![range_check, Value::U64(value)]) - } else { - EvalAction::NormalBranch(1, smallvec![range_check]) - } -} - -pub fn eval_operation( - _registry: &ProgramRegistry, - info: &IntOperationConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::U64(lhs), Value::U64(rhs)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let (result, has_overflow) = match info.operator { - IntOperator::OverflowingAdd => lhs.overflowing_add(rhs), - IntOperator::OverflowingSub => lhs.overflowing_sub(rhs), - }; - - EvalAction::NormalBranch( - has_overflow as usize, - smallvec![range_check, Value::U64(result)], - ) -} - -pub fn eval_equal( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U64(lhs), Value::U64(rhs)]: [Value; 2] = args.try_into().unwrap() else { - panic!() - }; - - EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) -} - -pub fn eval_bitwise( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [bitwise @ Value::Unit, Value::U64(lhs), Value::U64(rhs)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let and = lhs & rhs; - let or = lhs | rhs; - let xor = lhs ^ rhs; - - EvalAction::NormalBranch( - 0, - smallvec![bitwise, Value::U64(and), Value::U64(xor), Value::U64(or)], - ) -} - -pub fn eval_is_zero( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [vm_value @ Value::U64(value)]: [Value; 1] = args.try_into().unwrap() else { - panic!() - }; - - if value == 0 { - EvalAction::NormalBranch(0, smallvec![]) - } else { - EvalAction::NormalBranch(1, smallvec![vm_value]) - } -} - -pub fn eval_to_felt252( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U64(value)]: [Value; 1] = args.try_into().unwrap() else { - panic!() - }; - - EvalAction::NormalBranch(0, smallvec![Value::Felt(value.into())]) -} - -pub fn eval_const( - _registry: &ProgramRegistry, - info: &IntConstConcreteLibfunc, - _args: Vec, -) -> EvalAction { - EvalAction::NormalBranch(0, smallvec![Value::U64(info.c)]) -} - -pub fn eval_widemul( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U64(lhs), Value::U64(rhs)]: [Value; 2] = args.try_into().unwrap() else { - panic!() - }; - - let result = (lhs as u128) * (rhs as u128); - - EvalAction::NormalBranch(0, smallvec![Value::U128(result)]) -} diff --git a/debug_utils/sierra-emu/src/vm/uint8.rs b/debug_utils/sierra-emu/src/vm/uint8.rs deleted file mode 100644 index b4c06fb77..000000000 --- a/debug_utils/sierra-emu/src/vm/uint8.rs +++ /dev/null @@ -1,176 +0,0 @@ -use super::EvalAction; -use crate::Value; -use cairo_lang_sierra::{ - extensions::{ - core::{CoreLibfunc, CoreType}, - int::{ - unsigned::{Uint8Concrete, Uint8Traits}, - IntConstConcreteLibfunc, IntOperationConcreteLibfunc, IntOperator, - }, - lib_func::SignatureOnlyConcreteLibfunc, - }, - program_registry::ProgramRegistry, -}; -use smallvec::smallvec; -use starknet_crypto::Felt; - -pub fn eval( - registry: &ProgramRegistry, - selector: &Uint8Concrete, - args: Vec, -) -> EvalAction { - match selector { - Uint8Concrete::Const(info) => eval_const(registry, info, args), - Uint8Concrete::Operation(info) => eval_operation(registry, info, args), - Uint8Concrete::SquareRoot(_) => todo!(), - Uint8Concrete::Equal(info) => eval_equal(registry, info, args), - Uint8Concrete::ToFelt252(info) => eval_to_felt252(registry, info, args), - Uint8Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), - Uint8Concrete::IsZero(info) => eval_is_zero(registry, info, args), - Uint8Concrete::Divmod(info) => eval_divmod(registry, info, args), - Uint8Concrete::WideMul(info) => eval_widemul(registry, info, args), - Uint8Concrete::Bitwise(info) => eval_bitwise(registry, info, args), - } -} - -pub fn eval_divmod( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::U8(x), Value::U8(y)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let val = Value::U8(x / y); - let rem = Value::U8(x % y); - - EvalAction::NormalBranch(0, smallvec![range_check, val, rem]) -} - -pub fn eval_to_felt252( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U8(value)]: [Value; 1] = args.try_into().unwrap() else { - panic!() - }; - - EvalAction::NormalBranch(0, smallvec![Value::Felt(value.into())]) -} - -pub fn eval_operation( - _registry: &ProgramRegistry, - info: &IntOperationConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::U8(lhs), Value::U8(rhs)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let (result, has_overflow) = match info.operator { - IntOperator::OverflowingAdd => lhs.overflowing_add(rhs), - IntOperator::OverflowingSub => lhs.overflowing_sub(rhs), - }; - - EvalAction::NormalBranch( - has_overflow as usize, - smallvec![range_check, Value::U8(result)], - ) -} - -pub fn eval_from_felt( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() - else { - panic!() - }; - - let max = Felt::from(u8::MAX); - - if value <= max { - let value: u8 = value.to_biguint().try_into().unwrap(); - EvalAction::NormalBranch(0, smallvec![range_check, Value::U8(value)]) - } else { - EvalAction::NormalBranch(1, smallvec![range_check]) - } -} - -pub fn eval_equal( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U8(lhs), Value::U8(rhs)]: [Value; 2] = args.try_into().unwrap() else { - panic!() - }; - - EvalAction::NormalBranch((lhs == rhs) as usize, smallvec![]) -} - -pub fn eval_bitwise( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [bitwise @ Value::Unit, Value::U8(lhs), Value::U8(rhs)]: [Value; 3] = - args.try_into().unwrap() - else { - panic!() - }; - - let and = lhs & rhs; - let or = lhs | rhs; - let xor = lhs ^ rhs; - - EvalAction::NormalBranch( - 0, - smallvec![bitwise, Value::U8(and), Value::U8(xor), Value::U8(or)], - ) -} - -pub fn eval_is_zero( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [vm_value @ Value::U8(value)]: [Value; 1] = args.try_into().unwrap() else { - panic!() - }; - - if value == 0 { - EvalAction::NormalBranch(0, smallvec![]) - } else { - EvalAction::NormalBranch(1, smallvec![vm_value]) - } -} - -pub fn eval_const( - _registry: &ProgramRegistry, - info: &IntConstConcreteLibfunc, - _args: Vec, -) -> EvalAction { - EvalAction::NormalBranch(0, smallvec![Value::U8(info.c)]) -} - -pub fn eval_widemul( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, - args: Vec, -) -> EvalAction { - let [Value::U8(lhs), Value::U8(rhs)]: [Value; 2] = args.try_into().unwrap() else { - panic!() - }; - - let result = (lhs as u16) * (rhs as u16); - - EvalAction::NormalBranch(0, smallvec![Value::U16(result)]) -} From f42fd0520f8bc5c1e52aa1b147b851b829efb432 Mon Sep 17 00:00:00 2001 From: Kazeem Hakeem Date: Mon, 12 May 2025 20:09:51 +0100 Subject: [PATCH 10/28] Update arch.rs --- src/arch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arch.rs b/src/arch.rs index 4affc94e2..59263cd5e 100644 --- a/src/arch.rs +++ b/src/arch.rs @@ -183,7 +183,7 @@ impl AbiArgument for ValueWithInfoWrapper<'_> { ), ) => value.to_bytes(buffer, find_dict_drop_override)?, (Value::Felt252Dict { .. }, CoreTypeConcrete::Felt252Dict(_)) => { - // TODO: Assert that `info.ty` matches all the values' types. See: https://github.com/lambdaclass/cairo_native/issues/1216#issue-3052795891 + // TODO: Assert that `info.ty` matches all the values' types. self.value .to_ptr( From 59b6f618e10d9ac5a920fe95557186ae2cb7f036 Mon Sep 17 00:00:00 2001 From: Kazeem Hakeem Date: Mon, 12 May 2025 20:10:50 +0100 Subject: [PATCH 11/28] Update main.rs --- src/bin/cairo-native-stress/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bin/cairo-native-stress/main.rs b/src/bin/cairo-native-stress/main.rs index d10453391..2ca7c3cd2 100644 --- a/src/bin/cairo-native-stress/main.rs +++ b/src/bin/cairo-native-stress/main.rs @@ -103,7 +103,6 @@ fn main() { let before_round = Instant::now(); let program = modify_starknet_contract(program.clone(), UNIQUE_CONTRACT_VALUE, round); - // TODO: use the program hash instead of round number. See: https://github.com/lambdaclass/cairo_native/issues/1215#issue-3052756022 let hash = round; debug!(hash, "obtained test program"); From aaa77ef50bce48bbd1d16370252e7fc6c51955db Mon Sep 17 00:00:00 2001 From: Kazeem Hakeem Date: Mon, 12 May 2025 20:12:20 +0100 Subject: [PATCH 12/28] Update arch.rs --- src/arch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arch.rs b/src/arch.rs index 59263cd5e..cc427cad6 100644 --- a/src/arch.rs +++ b/src/arch.rs @@ -249,7 +249,7 @@ impl AbiArgument for ValueWithInfoWrapper<'_> { value.to_bytes(buffer, find_dict_drop_override)? } _ => native_panic!( - "todo: abi argument unimplemented for ({:?}, {:?})", // See: https://github.com/lambdaclass/cairo_native/issues/1218#issue-3052814195 + "todo: abi argument unimplemented for ({:?}, {:?})", self.value, self.type_id ), From cffb1ee5667606522b5219a240e9e8c521644fac Mon Sep 17 00:00:00 2001 From: Kazeem Hakeem Date: Mon, 12 May 2025 20:13:19 +0100 Subject: [PATCH 13/28] Update enum.rs --- src/types/enum.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/enum.rs b/src/types/enum.rs index a3e102e92..238e562f0 100644 --- a/src/types/enum.rs +++ b/src/types/enum.rs @@ -760,7 +760,7 @@ pub fn get_layout_for_variants( /// Extract the type and layout for the default enum representation, its discriminant and all its /// payloads. -// TODO: Change this function to accept a slice of slices (for variants). Not all uses have a slice See: https://github.com/lambdaclass/cairo_native/issues/1225#issue-3053744788 +// TODO: Change this function to accept a slice of slices (for variants). Not all uses have a slice See: https://github.com/lambdaclass/cairo_native/issues/1187 // with one `ConcreteTypeId` per variant (deploy_syscalls has two types for the Ok() variant). pub fn get_type_for_variants<'ctx>( context: &'ctx Context, From 4b83428b16c2829b0cc5850c1ce19c2ee81763ab Mon Sep 17 00:00:00 2001 From: Kazeem Hakeem Date: Thu, 15 May 2025 19:27:19 +0100 Subject: [PATCH 14/28] Update arch.rs --- src/arch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/arch.rs b/src/arch.rs index cc427cad6..4ca45ebc9 100644 --- a/src/arch.rs +++ b/src/arch.rs @@ -114,7 +114,7 @@ impl AbiArgument for ValueWithInfoWrapper<'_> { .to_bytes(buffer, find_dict_drop_override)?, (Value::Array(_), CoreTypeConcrete::Array(_)) => { - // TODO: Assert that `info.ty` matches all the values' types. See: https://github.com/lambdaclass/cairo_native/issues/1216#issue-3052795891 + // TODO: Assert that `info.ty` matches all the values' types. See: https://github.com/lambdaclass/cairo_native/issues/1216 let abi_ptr = self.value.to_ptr( self.arena, @@ -130,7 +130,7 @@ impl AbiArgument for ValueWithInfoWrapper<'_> { abi.capacity.to_bytes(buffer, find_dict_drop_override)?; } (Value::BoundedInt { .. }, CoreTypeConcrete::BoundedInt(_)) => { - native_panic!("todo: implement AbiArgument for Value::BoundedInt case") // See: https://github.com/lambdaclass/cairo_native/issues/1217#issue-3052805863 + native_panic!("todo: implement AbiArgument for Value::BoundedInt case") // See: https://github.com/lambdaclass/cairo_native/issues/1217 } (Value::Bytes31(value), CoreTypeConcrete::Bytes31(_)) => { value.to_bytes(buffer, find_dict_drop_override)? From 4ec9601fe241ec6ca68fc8fb3faab862f9f1a12f Mon Sep 17 00:00:00 2001 From: Kazeem Hakeem Date: Thu, 15 May 2025 19:28:06 +0100 Subject: [PATCH 15/28] Update executor.rs --- src/executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/executor.rs b/src/executor.rs index f5fcad8c3..f5db4b3d9 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -265,7 +265,7 @@ fn invoke_dynamic( _ if type_info.is_builtin() => { if !type_info.is_zst(registry)? { if let CoreTypeConcrete::BuiltinCosts(_) = type_info { - // todo: should we use this value? See: https://github.com/lambdaclass/cairo_native/issues/1219#issue-3052919995 + // todo: should we use this value? See: https://github.com/lambdaclass/cairo_native/issues/1219 let _value = match &mut return_ptr { Some(return_ptr) => unsafe { *read_value::<*mut u64>(return_ptr) }, None => ret_registers[0] as *mut u64, From d915f8298e00557f00013962653fd328fd98533c Mon Sep 17 00:00:00 2001 From: Kazeem Hakeem Date: Thu, 15 May 2025 19:29:02 +0100 Subject: [PATCH 16/28] Update contract.rs --- src/executor/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/executor/contract.rs b/src/executor/contract.rs index cc93d1f5c..12c13bde1 100644 --- a/src/executor/contract.rs +++ b/src/executor/contract.rs @@ -684,7 +684,7 @@ mod tests { use rayon::iter::ParallelBridge; use rstest::*; - // todo add recursive contract test See: https://github.com/lambdaclass/cairo_native/issues/1220#issue-3053012331 + // todo add recursive contract test See: https://github.com/lambdaclass/cairo_native/issues/1220 #[fixture] fn starknet_program() -> ContractClass { From 53ad154fd223819cf52d7718325acb4dc9ab4b14 Mon Sep 17 00:00:00 2001 From: Kazeem Hakeem Date: Thu, 15 May 2025 19:30:16 +0100 Subject: [PATCH 17/28] Update bytes31.rs --- src/libfuncs/bytes31.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libfuncs/bytes31.rs b/src/libfuncs/bytes31.rs index 8c3b8cd34..d64cbeb3f 100644 --- a/src/libfuncs/bytes31.rs +++ b/src/libfuncs/bytes31.rs @@ -170,7 +170,7 @@ mod test { use starknet_types_core::felt::Felt; lazy_static! { - // TODO: Test `bytes31_const` once the compiler supports it. See: https://github.com/lambdaclass/cairo_native/issues/1224#issue-3053717697 + // TODO: Test `bytes31_const` once the compiler supports it. See: https://github.com/lambdaclass/cairo_native/issues/1224 static ref BYTES31_ROUNDTRIP: (String, Program) = load_cairo! { use core::bytes_31::{bytes31_try_from_felt252, bytes31_to_felt252}; From f3223c2719734a5322d53abf86800c3d8b608217 Mon Sep 17 00:00:00 2001 From: Kazeem Hakeem Date: Thu, 15 May 2025 19:31:01 +0100 Subject: [PATCH 18/28] Update felt252.rs --- src/libfuncs/felt252.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libfuncs/felt252.rs b/src/libfuncs/felt252.rs index d52b34c3f..7604ce09d 100644 --- a/src/libfuncs/felt252.rs +++ b/src/libfuncs/felt252.rs @@ -364,10 +364,10 @@ pub mod test { } }; - // TODO: Add test program for `felt252_add_const`. See: https://github.com/lambdaclass/cairo_native/issues/1214#issue-3052727376 - // TODO: Add test program for `felt252_sub_const`. See: https://github.com/lambdaclass/cairo_native/issues/1214#issue-3052727376 - // TODO: Add test program for `felt252_mul_const`. See: https://github.com/lambdaclass/cairo_native/issues/1214#issue-3052727376 - // TODO: Add test program for `felt252_div_const`. See: https://github.com/lambdaclass/cairo_native/issues/1214#issue-3052727376 + // TODO: Add test program for `felt252_add_const`. See: https://github.com/lambdaclass/cairo_native/issues/1214 + // TODO: Add test program for `felt252_sub_const`. See: https://github.com/lambdaclass/cairo_native/issues/1214 + // TODO: Add test program for `felt252_mul_const`. See: https://github.com/lambdaclass/cairo_native/issues/1214 + // TODO: Add test program for `felt252_div_const`. See: https://github.com/lambdaclass/cairo_native/issues/1214 static ref FELT252_CONST: (String, Program) = load_cairo! { extern fn felt252_const() -> felt252 nopanic; From e9c3235ef76438c3ac1f760517824e59f29ce895 Mon Sep 17 00:00:00 2001 From: Kazeem Hakeem Date: Thu, 15 May 2025 19:31:48 +0100 Subject: [PATCH 19/28] Update starknet.rs --- src/starknet.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/starknet.rs b/src/starknet.rs index a76e31880..131439f63 100644 --- a/src/starknet.rs +++ b/src/starknet.rs @@ -53,7 +53,7 @@ impl From<&Felt252Abi> for Felt { } /// Binary representation of a `u256` (in MLIR). -// TODO: This shouldn't need to be public. See: https://github.com/lambdaclass/cairo_native/issues/1221#issue-3053023949 +// TODO: This shouldn't need to be public. See: https://github.com/lambdaclass/cairo_native/issues/1221 #[derive( Debug, Clone, @@ -557,7 +557,7 @@ impl StarknetSyscallHandler for DummySyscallHandler { } } -// TODO: Move to the correct place or remove if unused. See: https://github.com/lambdaclass/cairo_native/issues/1222#issue-3053029798 +// TODO: Move to the correct place or remove if unused. See: https://github.com/lambdaclass/cairo_native/issues/1222 pub(crate) mod handler { use super::*; use crate::utils::{libc_free, libc_malloc}; From 6c81a6bf01e0c509c6b7f1f03ddb19cac6274163 Mon Sep 17 00:00:00 2001 From: Kazeem Hakeem Date: Thu, 15 May 2025 19:32:27 +0100 Subject: [PATCH 20/28] Update starknet.rs --- src/types/starknet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/starknet.rs b/src/types/starknet.rs index 7995b7267..6593cf48c 100644 --- a/src/types/starknet.rs +++ b/src/types/starknet.rs @@ -19,7 +19,7 @@ //! ## Secp256Point //! TODO -// TODO: Maybe the types used here can be i251 instead of i252. See https://github.com/lambdaclass/cairo_native/issues/1226#issue-3053977191 +// TODO: Maybe the types used here can be i251 instead of i252. See https://github.com/lambdaclass/cairo_native/issues/1226 use super::WithSelf; use crate::{ From 8e23a2715ccaeea813f3378405be278be537575d Mon Sep 17 00:00:00 2001 From: Franco Giachetta Date: Mon, 12 May 2025 18:38:31 -0300 Subject: [PATCH 21/28] implement circuit_single_limb_less_than_guarantee_verify (#1212) * implement circuit_single_limb_less_than_guarantee_verify * fmt * clippy * Fix comment Co-authored-by: Julian Gonzalez Calderon --------- Co-authored-by: Julian Gonzalez Calderon Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> --- src/libfuncs/circuit.rs | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/libfuncs/circuit.rs b/src/libfuncs/circuit.rs index 1e03e8448..0db7e041b 100644 --- a/src/libfuncs/circuit.rs +++ b/src/libfuncs/circuit.rs @@ -71,10 +71,12 @@ pub fn build<'ctx, 'this>( CircuitConcreteLibfunc::IntoU96Guarantee(info) => { build_into_u96_guarantee(context, registry, entry, location, helper, metadata, info) } - CircuitConcreteLibfunc::U96SingleLimbLessThanGuaranteeVerify( - SignatureOnlyConcreteLibfunc { signature, .. }, - ) - | CircuitConcreteLibfunc::U96GuaranteeVerify(SignatureOnlyConcreteLibfunc { signature }) => { + CircuitConcreteLibfunc::U96SingleLimbLessThanGuaranteeVerify(info) => { + build_u96_single_limb_less_than_guarantee_verify( + context, registry, entry, location, helper, metadata, info, + ) + } + CircuitConcreteLibfunc::U96GuaranteeVerify(SignatureOnlyConcreteLibfunc { signature }) => { super::build_noop::<1, true>( context, registry, @@ -821,6 +823,37 @@ fn build_u96_limbs_less_than_guarantee_verify<'ctx, 'this>( Ok(()) } +fn build_u96_single_limb_less_than_guarantee_verify<'ctx, 'this>( + context: &'ctx Context, + _registry: &ProgramRegistry, + entry: &'this Block<'ctx>, + location: Location<'ctx>, + helper: &LibfuncHelper<'ctx, 'this>, + _metadata: &mut MetadataStorage, + _info: &SignatureOnlyConcreteLibfunc, +) -> Result<()> { + let guarantee = entry.arg(0)?; + + let u96_type = IntegerType::new(context, 96).into(); + // this libfunc will always receive gate and modulus with single limb + let limb_struct_type = llvm::r#type::r#struct(context, &[u96_type; 1], false); + + // extract gate and modulus from input value + let gate = entry.extract_value(context, location, guarantee, limb_struct_type, 0)?; + let modulus = entry.extract_value(context, location, guarantee, limb_struct_type, 1)?; + + // extract the only limb from gate and modulus + let gate_limb = entry.extract_value(context, location, gate, u96_type, 0)?; + let modulus_limb = entry.extract_value(context, location, modulus, u96_type, 0)?; + + // calcualte diff between limbs + let diff = entry.append_op_result(arith::subi(modulus_limb, gate_limb, location))?; + + entry.append_operation(helper.br(0, &[diff], location)); + + Ok(()) +} + /// Generate MLIR operations for the `get_circuit_output` libfunc. #[allow(clippy::too_many_arguments)] fn build_get_output<'ctx, 'this>( From 8fa3382ced0a4b55152eb1878b8f645b55bdc4b6 Mon Sep 17 00:00:00 2001 From: Franco Giachetta Date: Tue, 13 May 2025 10:50:32 -0300 Subject: [PATCH 22/28] [Sierra-Emu] Fix felt-dict's overflow, circuits libfuncs and implement missing EcPoint libfunc (#1208) * change initial gas due to overflows * fix some libfuncs in circuits * fix is() for circuits * remove Value::Unit from return values in get_output * match U96LimbsLessThanGuarantee correctly * some corrections to circuits * some corrections to circuits * fix failure_guarantee_verify to avoid panicking * fix circuits * more fixes to circuits + implement ec_zero libfunc * clippy * remove unnecesary tests * remove unnecesary file * fix branch index * add comment * fix eval_u96_limbs_less_than_guarantee_verify * remove circuit failure test * update trace dump with new Value::CircitOutputs * fmt * reviews + fix modulus in trace dump * remove unnecesary file * fix clippy * implement circuit_single_limb_less_than_guarantee_verify * fix trace dump for circuit outputs * fix felt_dict_get * fix felt_dict_get * implement U96LimbsLessThanGuarantee for trace dump * remove unnecessary code * fmt * fix felt_dict_entry_get * revert change in circuits.rs * remove unwanted file * revert unwanted change * revert unwanted change * make trace dump for circuit outputs cleaner * remove unwanted code * increase felt dict count always during get() * doc function + merge main * reviews * use elemts stride for offseting u384 structs * forgot to update one stride --------- Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> --- Cargo.lock | 232 +++++++------- debug_utils/sierra-emu/Cargo.toml | 1 + debug_utils/sierra-emu/src/utils.rs | 8 + debug_utils/sierra-emu/src/value.rs | 35 ++- debug_utils/sierra-emu/src/vm/cast.rs | 1 + debug_utils/sierra-emu/src/vm/circuit.rs | 283 ++++++++++++------ debug_utils/sierra-emu/src/vm/ec.rs | 34 ++- .../sierra-emu/src/vm/felt252_dict_entry.rs | 12 +- debug_utils/sierra-emu/tests/common/mod.rs | 10 +- debug_utils/sierra-emu/tests/corelib.rs | 3 +- debug_utils/sierra-emu/tests/libfuncs.rs | 10 - .../tests/tests/circuits_failure.cairo | 20 -- src/metadata/trace_dump.rs | 84 +++++- 13 files changed, 443 insertions(+), 290 deletions(-) delete mode 100644 debug_utils/sierra-emu/tests/tests/circuits_failure.cairo diff --git a/Cargo.lock b/Cargo.lock index d5cbec337..7a0220f8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,14 +10,14 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -108,7 +108,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -124,7 +124,7 @@ dependencies = [ "ark-std 0.5.0", "educe 0.6.0", "fnv", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "itertools 0.13.0", "num-bigint", "num-integer", @@ -189,7 +189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -215,7 +215,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -230,7 +230,7 @@ dependencies = [ "ark-std 0.5.0", "educe 0.6.0", "fnv", - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] [[package]] @@ -287,7 +287,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -385,7 +385,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -504,7 +504,7 @@ dependencies = [ "rust-analyzer-salsa", "semver", "smol_str", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -591,7 +591,7 @@ dependencies = [ "itertools 0.14.0", "rust-analyzer-salsa", "serde", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -675,7 +675,7 @@ checksum = "b1e4872352761cf6d7f47eeb1626e3b1d84a514017fb4251173148d8c04f36d5" dependencies = [ "cairo-lang-debug", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -687,7 +687,7 @@ dependencies = [ "cairo-lang-filesystem", "cairo-lang-utils", "serde", - "thiserror 2.0.12", + "thiserror", "toml", ] @@ -706,7 +706,7 @@ dependencies = [ "cairo-lang-utils", "cairo-vm 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.14.0", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -736,7 +736,7 @@ dependencies = [ "sha2", "smol_str", "starknet-types-core", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -790,7 +790,7 @@ dependencies = [ "sha3", "smol_str", "starknet-types-core", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -806,7 +806,7 @@ dependencies = [ "itertools 0.14.0", "num-bigint", "num-traits", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -822,7 +822,7 @@ dependencies = [ "itertools 0.14.0", "num-bigint", "num-traits", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -867,7 +867,7 @@ dependencies = [ "num-bigint", "num-traits", "starknet-types-core", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -907,7 +907,7 @@ dependencies = [ "serde_json", "smol_str", "starknet-types-core", - "thiserror 2.0.12", + "thiserror", "typetag", ] @@ -931,7 +931,7 @@ dependencies = [ "sha3", "smol_str", "starknet-types-core", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -1008,7 +1008,7 @@ version = "2.12.0-dev.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f043065d60a8a2510bfacb6c91767298fed50ed9abbd69ff7698322b7cb1e65" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.3", "indexmap 2.9.0", "itertools 0.14.0", "num-bigint", @@ -1077,7 +1077,7 @@ dependencies = [ "stats_alloc", "tempfile", "test-case", - "thiserror 2.0.12", + "thiserror", "tracing", "tracing-subscriber", "utf8_iter", @@ -1094,7 +1094,7 @@ dependencies = [ "bincode 2.0.1", "bitvec", "generic-array", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "hex", "indoc", "keccak", @@ -1130,7 +1130,7 @@ dependencies = [ "cairo-lang-starknet", "cairo-lang-starknet-classes", "generic-array", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "hex", "indoc", "keccak", @@ -1148,7 +1148,7 @@ dependencies = [ "sha3", "starknet-crypto", "starknet-types-core", - "thiserror 2.0.12", + "thiserror", "zip", ] @@ -1166,7 +1166,7 @@ dependencies = [ "cairo-lang-starknet", "cairo-lang-starknet-classes", "generic-array", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "hex", "indoc", "keccak", @@ -1184,7 +1184,7 @@ dependencies = [ "sha3", "starknet-crypto", "starknet-types-core", - "thiserror 2.0.12", + "thiserror", "zip", ] @@ -1228,9 +1228,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.19" +version = "1.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" +checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" dependencies = [ "shlex", ] @@ -1325,7 +1325,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1579,9 +1579,9 @@ dependencies = [ [[package]] name = "deunicode" -version = "1.6.1" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc55fe0d1f6c107595572ec8b107c0999bb1a2e0b75e37429a4fb0d6474a0e7d" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" [[package]] name = "diff" @@ -1639,7 +1639,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1651,7 +1651,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1718,7 +1718,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1811,7 +1811,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1859,7 +1859,7 @@ checksum = "43eaff6bbc0b3a878361aced5ec6a2818ee7c541c5b33b5880dfa9a86c23e9e7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -1954,9 +1954,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", @@ -1978,9 +1978,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" [[package]] name = "hex" @@ -2045,7 +2045,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2091,7 +2091,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "serde", ] @@ -2338,7 +2338,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] [[package]] @@ -2381,7 +2381,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.100", + "syn 2.0.101", "tblgen", "unindent", ] @@ -2612,7 +2612,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2755,7 +2755,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.24", + "zerocopy", ] [[package]] @@ -2791,7 +2791,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" dependencies = [ "proc-macro2", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -2985,9 +2985,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags", ] @@ -3084,7 +3084,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.100", + "syn 2.0.101", "unicode-ident", ] @@ -3114,7 +3114,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3150,9 +3150,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags", "errno", @@ -3204,7 +3204,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -3246,7 +3246,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3295,7 +3295,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3306,7 +3306,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3332,9 +3332,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -3386,6 +3386,7 @@ dependencies = [ "k256", "keccak", "num-bigint", + "num-integer", "num-traits", "p256", "rand 0.8.5", @@ -3399,7 +3400,7 @@ dependencies = [ "starknet-curve", "starknet-types-core", "tempfile", - "thiserror 2.0.12", + "thiserror", "tracing", "tracing-subscriber", ] @@ -3583,9 +3584,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" dependencies = [ "proc-macro2", "quote", @@ -3607,7 +3608,7 @@ dependencies = [ "bindgen", "cc", "paste", - "thiserror 2.0.12", + "thiserror", ] [[package]] @@ -3651,7 +3652,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3662,37 +3663,17 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "test-case-core", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.12", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "thiserror-impl", ] [[package]] @@ -3703,7 +3684,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3763,9 +3744,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.8.20" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", @@ -3775,26 +3756,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap 2.9.0", "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tracing" version = "0.1.41" @@ -3814,7 +3802,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3918,7 +3906,7 @@ checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -3929,11 +3917,11 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unescaper" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" +checksum = "c01d12e3a56a4432a8b436f293c25f4808bdf9e9f9f98f9260bba1f1bc5a1f26" dependencies = [ - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -4073,7 +4061,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "wasm-bindgen-shared", ] @@ -4095,7 +4083,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4244,9 +4232,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.6" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] @@ -4292,42 +4280,22 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ - "zerocopy-derive 0.8.24", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.100", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] @@ -4347,7 +4315,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.101", ] [[package]] diff --git a/debug_utils/sierra-emu/Cargo.toml b/debug_utils/sierra-emu/Cargo.toml index fa8034215..71fbc3484 100644 --- a/debug_utils/sierra-emu/Cargo.toml +++ b/debug_utils/sierra-emu/Cargo.toml @@ -23,6 +23,7 @@ cairo-lang-utils.workspace = true clap = { version = "4.5.26", features = ["derive"] } k256 = "0.13.4" keccak = "0.1.5" +num-integer.workspace = true num-bigint.workspace = true num-traits.workspace = true p256 = "0.13.2" diff --git a/debug_utils/sierra-emu/src/utils.rs b/debug_utils/sierra-emu/src/utils.rs index 4c2af0093..a2b5cc141 100644 --- a/debug_utils/sierra-emu/src/utils.rs +++ b/debug_utils/sierra-emu/src/utils.rs @@ -54,6 +54,14 @@ pub fn get_value_from_integer( CoreTypeConcrete::Uint32(_) => Value::U32(value.to_u32().unwrap()), CoreTypeConcrete::Uint64(_) => Value::U64(value.to_u64().unwrap()), CoreTypeConcrete::Uint128(_) => Value::U128(value.to_u128().unwrap()), + CoreTypeConcrete::BoundedInt(info) => { + let range = &info.range; + Value::BoundedInt { + range: range.lower.clone()..range.upper.clone(), + value, + } + } + CoreTypeConcrete::Felt252(_) => Value::Felt(value.into()), _ => panic!("cannot get integer value for a non-integer type"), } } diff --git a/debug_utils/sierra-emu/src/value.rs b/debug_utils/sierra-emu/src/value.rs index ed96be281..7c3f64c61 100644 --- a/debug_utils/sierra-emu/src/value.rs +++ b/debug_utils/sierra-emu/src/value.rs @@ -27,8 +27,11 @@ pub enum Value { value: BigInt, }, Circuit(Vec), + CircuitOutputs { + circuits: Vec, + modulus: BigUint, + }, CircuitModulus(BigUint), - CircuitOutputs(Vec), Enum { self_ty: ConcreteTypeId, index: usize, @@ -173,25 +176,31 @@ impl Value { // Circuit related types CoreTypeConcrete::Circuit(selector) => match selector { - CircuitTypeConcrete::Circuit(_) => matches!(self, Self::Circuit(_)), - CircuitTypeConcrete::CircuitData(_) => matches!(self, Self::Circuit(_)), - CircuitTypeConcrete::CircuitOutputs(_) => matches!(self, Self::CircuitOutputs(_)), - CircuitTypeConcrete::CircuitInput(_) => matches!(self, Self::Unit), - CircuitTypeConcrete::CircuitInputAccumulator(_) => matches!(self, Self::Circuit(_)), + CircuitTypeConcrete::Circuit(_) + | CircuitTypeConcrete::CircuitData(_) + | CircuitTypeConcrete::CircuitInputAccumulator(_) => { + matches!(self, Self::Circuit(_)) + } + CircuitTypeConcrete::CircuitOutputs(_) => { + matches!(self, Self::CircuitOutputs { .. }) + } CircuitTypeConcrete::CircuitModulus(_) => matches!(self, Self::CircuitModulus(_)), CircuitTypeConcrete::U96Guarantee(_) => matches!(self, Self::U128(_)), - CircuitTypeConcrete::CircuitDescriptor(_) - | CircuitTypeConcrete::CircuitFailureGuarantee(_) - | CircuitTypeConcrete::AddMod(_) + CircuitTypeConcrete::CircuitInput(_) => { + matches!(self, Self::Struct(_)) + } + CircuitTypeConcrete::U96LimbsLessThanGuarantee(_) => { + matches!(self, Self::Struct(_)) + } + CircuitTypeConcrete::AddMod(_) | CircuitTypeConcrete::MulMod(_) + | CircuitTypeConcrete::CircuitDescriptor(_) + | CircuitTypeConcrete::CircuitFailureGuarantee(_) | CircuitTypeConcrete::AddModGate(_) | CircuitTypeConcrete::CircuitPartialOutputs(_) | CircuitTypeConcrete::InverseGate(_) | CircuitTypeConcrete::MulModGate(_) - | CircuitTypeConcrete::SubModGate(_) - | CircuitTypeConcrete::U96LimbsLessThanGuarantee(_) => { - matches!(self, Self::Unit) - } + | CircuitTypeConcrete::SubModGate(_) => matches!(self, Self::Unit), }, CoreTypeConcrete::Const(info) => self.is(registry, &info.inner_ty), CoreTypeConcrete::EcOp(_) => matches!(self, Self::Unit), diff --git a/debug_utils/sierra-emu/src/vm/cast.rs b/debug_utils/sierra-emu/src/vm/cast.rs index 78330a5fc..8718dca64 100644 --- a/debug_utils/sierra-emu/src/vm/cast.rs +++ b/debug_utils/sierra-emu/src/vm/cast.rs @@ -36,6 +36,7 @@ fn eval_downcast( let [value] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); let int_ty = registry.get_type(&info.to_ty).unwrap(); + let range = info.to_range.lower.clone()..info.to_range.upper.clone(); if range.contains(&value) { EvalAction::NormalBranch( diff --git a/debug_utils/sierra-emu/src/vm/circuit.rs b/debug_utils/sierra-emu/src/vm/circuit.rs index 17166148d..3f154688a 100644 --- a/debug_utils/sierra-emu/src/vm/circuit.rs +++ b/debug_utils/sierra-emu/src/vm/circuit.rs @@ -12,8 +12,74 @@ use cairo_lang_sierra::{ program_registry::ProgramRegistry, }; use num_bigint::{BigInt, BigUint, Sign, ToBigInt}; +use num_integer::{ExtendedGcd, Integer}; +use num_traits::{One, ToPrimitive, Zero}; use smallvec::smallvec; +fn u384_to_struct(num: BigUint) -> Value { + let output_big = num.to_bigint().unwrap(); + + let mask: BigInt = BigInt::from_bytes_be(Sign::Plus, &[255; 12]); + + let l0: BigInt = &output_big & &mask; + let l1: BigInt = (&output_big >> 96) & &mask; + let l2: BigInt = (&output_big >> 192) & &mask; + let l3: BigInt = (output_big >> 288) & &mask; + + let range = BigInt::ZERO..(BigInt::from(1) << 96); + Value::Struct(vec![ + Value::BoundedInt { + range: range.clone(), + value: l0, + }, + Value::BoundedInt { + range: range.clone(), + value: l1, + }, + Value::BoundedInt { + range: range.clone(), + value: l2, + }, + Value::BoundedInt { range, value: l3 }, + ]) +} + +fn struct_to_u384(struct_members: Vec) -> BigUint { + let [Value::U128(l0), Value::U128(l1), Value::U128(l2), Value::U128(l3)]: [Value; 4] = + struct_members.try_into().unwrap() + else { + panic!() + }; + + let l0 = l0.to_le_bytes(); + let l1 = l1.to_le_bytes(); + let l2 = l2.to_le_bytes(); + let l3 = l3.to_le_bytes(); + + BigUint::from_bytes_le(&[ + l0[0], l0[1], l0[2], l0[3], l0[4], l0[5], l0[6], l0[7], l0[8], l0[9], l0[10], + l0[11], // + l1[0], l1[1], l1[2], l1[3], l1[4], l1[5], l1[6], l1[7], l1[8], l1[9], l1[10], + l1[11], // + l2[0], l2[1], l2[2], l2[3], l2[4], l2[5], l2[6], l2[7], l2[8], l2[9], l2[10], + l2[11], // + l3[0], l3[1], l3[2], l3[3], l3[4], l3[5], l3[6], l3[7], l3[8], l3[9], l3[10], + l3[11], // + ]) +} + +/// If the value is non-invertible a nullifier is returned instead. A nullifier +/// is value which satisfies this following equation: num * nullifier ≡ 0(modulus). +fn find_nullifier(num: &BigUint, modulus: &BigUint) -> BigUint { + let ExtendedGcd { gcd, .. } = num + .to_bigint() + .unwrap() + .extended_gcd(&modulus.to_bigint().unwrap()); + let gcd = gcd.to_biguint().unwrap(); + + modulus / gcd +} + pub fn eval( registry: &ProgramRegistry, selector: &CircuitConcreteLibfunc, @@ -48,45 +114,30 @@ pub fn eval( } } -pub fn eval_add_input( - _registry: &ProgramRegistry, - _info: &SignatureAndTypeConcreteLibfunc, +fn eval_add_input( + registry: &ProgramRegistry, + info: &SignatureAndTypeConcreteLibfunc, args: Vec, ) -> EvalAction { let [Value::Circuit(mut values), Value::Struct(members)]: [Value; 2] = args.try_into().unwrap() else { panic!() }; - assert_ne!(values.len(), values.capacity()); - let [Value::U128(l0), Value::U128(l1), Value::U128(l2), Value::U128(l3)]: [Value; 4] = - members.try_into().unwrap() - else { - panic!() + let n_inputs = match registry.get_type(&info.ty).unwrap() { + CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(info)) => info.circuit_info.n_inputs, + _ => panic!(), }; - let l0 = l0.to_le_bytes(); - let l1 = l1.to_le_bytes(); - let l2 = l2.to_le_bytes(); - let l3 = l3.to_le_bytes(); - values.push(BigUint::from_bytes_le(&[ - l0[0], l0[1], l0[2], l0[3], l0[4], l0[5], l0[6], l0[7], l0[8], l0[9], l0[10], - l0[11], // - l1[0], l1[1], l1[2], l1[3], l1[4], l1[5], l1[6], l1[7], l1[8], l1[9], l1[10], - l1[11], // - l2[0], l2[1], l2[2], l2[3], l2[4], l2[5], l2[6], l2[7], l2[8], l2[9], l2[10], - l2[11], // - l3[0], l3[1], l3[2], l3[3], l3[4], l3[5], l3[6], l3[7], l3[8], l3[9], l3[10], - l3[11], // - ])); + values.push(struct_to_u384(members)); EvalAction::NormalBranch( - (values.len() != values.capacity()) as usize, + (values.len() != n_inputs) as usize, smallvec![Value::Circuit(values)], ) } -pub fn eval_eval( +fn eval_eval( _registry: &ProgramRegistry, _info: &SignatureAndTypeConcreteLibfunc, _args: Vec, @@ -144,14 +195,16 @@ pub fn eval_eval( outputs[mul_gate.output] = Some((l * r) % &modulus); } (None, Some(r)) => { - let res = match r.modinv(&modulus) { - Some(inv) => inv, + match r.modinv(&modulus) { + // if it is a inv_gate the output index is store in lhs + Some(r) => outputs[mul_gate.lhs] = Some(r), + // Since we don't calculate CircuitPartialOutputs + // perform an early break None => { - panic!("attempt to divide by 0"); + outputs[mul_gate.lhs] = Some(find_nullifier(&r, &modulus)); + break false; } - }; - // if it is a inv_gate the output index is store in lhs - outputs[mul_gate.lhs] = Some(res); + } } // this state should not be reached since it would mean that // not all the circuit's inputs where filled @@ -162,28 +215,39 @@ pub fn eval_eval( } }; - let values = outputs - .into_iter() - .skip(1 + circ_info.n_inputs) - .collect::>>() - .expect("The circuit cannot be calculated"); - if success { + let values = outputs + .into_iter() + .skip(1 + circ_info.n_inputs) + .collect::>>() + .expect("The circuit cannot be calculated"); + EvalAction::NormalBranch( 0, - smallvec![add_mod, mul_mod, Value::CircuitOutputs(values)], + smallvec![ + add_mod, + mul_mod, + Value::CircuitOutputs { + circuits: values, + modulus + } + ], ) } else { EvalAction::NormalBranch(1, smallvec![add_mod, mul_mod, Value::Unit, Value::Unit]) } } -pub fn eval_get_output( +fn eval_get_output( _registry: &ProgramRegistry, _info: &ConcreteGetOutputLibFunc, args: Vec, ) -> EvalAction { - let [Value::CircuitOutputs(outputs)]: [Value; 1] = args.try_into().unwrap() else { + let [Value::CircuitOutputs { + circuits: outputs, + modulus, + }]: [Value; 1] = args.try_into().unwrap() + else { panic!() }; let circuit_info = match _registry.get_type(&_info.circuit_ty).unwrap() { @@ -193,65 +257,86 @@ pub fn eval_get_output( let gate_offset = *circuit_info.values.get(&_info.output_ty).unwrap(); let output_idx = gate_offset - 1 - circuit_info.n_inputs; let output = outputs[output_idx].to_owned(); - let output_big = output.to_bigint().unwrap(); - - let mask: BigInt = BigInt::from_bytes_be(Sign::Plus, &[255; 12]); - let l0: BigInt = &output_big & &mask; - let l1: BigInt = (&output_big >> 96) & &mask; - let l2: BigInt = (&output_big >> 192) & &mask; - let l3: BigInt = (output_big >> 288) & &mask; - - let range = BigInt::ZERO..(BigInt::from(1) << 96); - let vec_values = vec![ - Value::BoundedInt { - range: range.clone(), - value: l0, - }, - Value::BoundedInt { - range: range.clone(), - value: l1, - }, - Value::BoundedInt { - range: range.clone(), - value: l2, - }, - Value::BoundedInt { range, value: l3 }, - ]; + let output_struct = u384_to_struct(output); + let modulus = u384_to_struct(modulus); - EvalAction::NormalBranch(0, smallvec![Value::Struct(vec_values), Value::Unit]) + EvalAction::NormalBranch( + 0, + smallvec![ + output_struct.clone(), + Value::Struct(vec![output_struct, modulus]), + ], + ) } -pub fn eval_u96_limbs_less_than_guarantee_verify( +fn eval_u96_limbs_less_than_guarantee_verify( _registry: &ProgramRegistry, - _info: &ConcreteU96LimbsLessThanGuaranteeVerifyLibfunc, - _args: Vec, + info: &ConcreteU96LimbsLessThanGuaranteeVerifyLibfunc, + args: Vec, ) -> EvalAction { - EvalAction::NormalBranch(0, smallvec![Value::Unit]) + let [Value::Struct(guarantee)]: [Value; 1] = args.try_into().unwrap() else { + panic!() + }; + let limb_count = info.limb_count; + let Value::Struct(gate) = &guarantee[0] else { + panic!(); + }; + let Value::Struct(modulus) = &guarantee[1] else { + panic!(); + }; + let Value::BoundedInt { + value: gate_last_limb, + .. + } = &gate[limb_count - 1] + else { + panic!(); + }; + let Value::BoundedInt { + value: modulus_last_limb, + .. + } = &modulus[limb_count - 1] + else { + panic!(); + }; + let diff = (modulus_last_limb - gate_last_limb).to_u128().unwrap(); + + if diff != 0 { + EvalAction::NormalBranch(1, smallvec![Value::U128(diff)]) + } else { + // if there is no diff, build a new guarantee, skipping the last limb + let new_gate = Value::Struct(gate[0..limb_count].to_vec()); + let new_modulus = Value::Struct(modulus[0..limb_count].to_vec()); + + EvalAction::NormalBranch(0, smallvec![Value::Struct(vec![new_gate, new_modulus])]) + } } -pub fn eval_u96_single_limb_less_than_guarantee_verify( +fn eval_u96_single_limb_less_than_guarantee_verify( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, - _args: Vec, + args: Vec, ) -> EvalAction { + let [_guarantee]: [Value; 1] = args.try_into().unwrap(); + EvalAction::NormalBranch(0, smallvec![Value::U128(0)]) } -pub fn eval_u96_guarantee_verify( +fn eval_u96_guarantee_verify( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, - _args: Vec, + args: Vec, ) -> EvalAction { - let [range_check_96 @ Value::Unit, _]: [Value; 2] = _args.try_into().unwrap() else { - panic!() + let [range_check_96 @ Value::Unit, _]: [Value; 2] = args.try_into().unwrap() else { + panic!(); }; + EvalAction::NormalBranch(0, smallvec![range_check_96]) } -pub fn eval_failure_guarantee_verify( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, +fn eval_failure_guarantee_verify( + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, _args: Vec, ) -> EvalAction { let [rc96 @ Value::Unit, mul_mod @ Value::Unit, _, _, _]: [Value; 5] = @@ -260,10 +345,34 @@ pub fn eval_failure_guarantee_verify( panic!() }; - EvalAction::NormalBranch(0, smallvec![rc96, mul_mod, Value::Unit]) + let limbs_count = match registry + .get_type(&info.signature.branch_signatures[0].vars[2].ty) + .unwrap() + { + CoreTypeConcrete::Circuit(CircuitTypeConcrete::U96LimbsLessThanGuarantee(info)) => { + info.limb_count + } + _ => panic!(), + }; + + let zero_u96 = Value::BoundedInt { + range: BigInt::zero()..BigInt::one() << 96, + value: 0.into(), + }; + // This should be changed with it correct value when we implement this libfunc in native + let limbs_struct = Value::Struct(vec![zero_u96; limbs_count]); + + EvalAction::NormalBranch( + 0, + smallvec![ + rc96, + mul_mod, + Value::Struct(vec![limbs_struct.clone(), limbs_struct]) + ], + ) } -pub fn eval_get_descriptor( +fn eval_get_descriptor( _registry: &ProgramRegistry, _info: &SignatureAndTypeConcreteLibfunc, _args: Vec, @@ -271,7 +380,7 @@ pub fn eval_get_descriptor( EvalAction::NormalBranch(0, smallvec![Value::Unit]) } -pub fn eval_init_circuit_data( +fn eval_init_circuit_data( _registry: &ProgramRegistry, info: &SignatureAndTypeConcreteLibfunc, args: Vec, @@ -294,7 +403,7 @@ pub fn eval_init_circuit_data( ) } -pub fn eval_try_into_circuit_modulus( +fn eval_try_into_circuit_modulus( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -331,14 +440,14 @@ pub fn eval_try_into_circuit_modulus( let value = l0 | (l1 << 96) | (l2 << 192) | (l3 << 288); - // a CircuitModulus must not be neither 0 nor 1 - assert_ne!(value, 0_u8.into()); - assert_ne!(value, 1_u8.into()); - - EvalAction::NormalBranch(0, smallvec![Value::CircuitModulus(value)]) + if value > BigUint::one() { + EvalAction::NormalBranch(0, smallvec![Value::CircuitModulus(value)]) + } else { + EvalAction::NormalBranch(1, smallvec![]) + } } -pub fn eval_into_u96_guarantee( +fn eval_into_u96_guarantee( _registry: &ProgramRegistry, _info: &SignatureAndTypeConcreteLibfunc, args: Vec, diff --git a/debug_utils/sierra-emu/src/vm/ec.rs b/debug_utils/sierra-emu/src/vm/ec.rs index 64ab9828a..b9ab8933d 100644 --- a/debug_utils/sierra-emu/src/vm/ec.rs +++ b/debug_utils/sierra-emu/src/vm/ec.rs @@ -34,11 +34,11 @@ pub fn eval( EcConcreteLibfunc::StateAddMul(info) => eval_state_add_mul(registry, info, args), EcConcreteLibfunc::PointFromX(info) => eval_point_from_x(registry, info, args), EcConcreteLibfunc::UnwrapPoint(info) => eval_unwrap_point(registry, info, args), - EcConcreteLibfunc::Zero(_) => todo!(), + EcConcreteLibfunc::Zero(info) => eval_zero(registry, info, args), } } -pub fn eval_is_zero( +fn eval_is_zero( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -55,7 +55,7 @@ pub fn eval_is_zero( } } -pub fn eval_unwrap_point( +fn eval_unwrap_point( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -66,7 +66,7 @@ pub fn eval_unwrap_point( EvalAction::NormalBranch(0, smallvec![Value::Felt(x), Value::Felt(y)]) } -pub fn eval_neg( +fn eval_neg( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -86,7 +86,7 @@ pub fn eval_neg( ) } -pub fn eval_new( +fn eval_new( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -107,7 +107,7 @@ pub fn eval_new( } } -pub fn eval_state_init( +fn eval_state_init( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, _args: Vec, @@ -125,7 +125,7 @@ pub fn eval_state_init( ) } -pub fn eval_state_add( +fn eval_state_add( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -153,7 +153,7 @@ pub fn eval_state_add( ) } -pub fn eval_state_add_mul( +fn eval_state_add_mul( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -184,7 +184,7 @@ pub fn eval_state_add_mul( ) } -pub fn eval_state_finalize( +fn eval_state_finalize( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -211,7 +211,7 @@ pub fn eval_state_finalize( } } -pub fn eval_point_from_x( +fn eval_point_from_x( _registry: &ProgramRegistry, _info: &SignatureOnlyConcreteLibfunc, args: Vec, @@ -258,3 +258,17 @@ fn random_ec_point() -> AffinePoint { AffinePoint::new(random_x, random_y).unwrap() } + +fn eval_zero( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + _args: Vec, +) -> EvalAction { + EvalAction::NormalBranch( + 0, + smallvec![Value::EcPoint { + x: 0.into(), + y: 0.into() + }], + ) +} diff --git a/debug_utils/sierra-emu/src/vm/felt252_dict_entry.rs b/debug_utils/sierra-emu/src/vm/felt252_dict_entry.rs index a93132580..1f0da1a01 100644 --- a/debug_utils/sierra-emu/src/vm/felt252_dict_entry.rs +++ b/debug_utils/sierra-emu/src/vm/felt252_dict_entry.rs @@ -29,17 +29,19 @@ pub fn eval_get( let [Value::FeltDict { ty, mut data, - count, + mut count, }, Value::Felt(key)]: [Value; 2] = args.try_into().unwrap() else { panic!() }; assert_eq!(info.ty, ty); - let default_value = Value::default_for_type(registry, &info.ty); - data.insert(key, default_value.clone()); + let value = data + .entry(key) + .or_insert(Value::default_for_type(registry, &ty)) + .clone(); - let count = count + 1; + count += 1; EvalAction::NormalBranch( 0, @@ -50,7 +52,7 @@ pub fn eval_get( count, key }, - default_value, + value ], ) } diff --git a/debug_utils/sierra-emu/tests/common/mod.rs b/debug_utils/sierra-emu/tests/common/mod.rs index 0f9a0befe..b1dbec34e 100644 --- a/debug_utils/sierra-emu/tests/common/mod.rs +++ b/debug_utils/sierra-emu/tests/common/mod.rs @@ -20,7 +20,15 @@ pub fn value_to_felt(value: &Value) -> Vec { costs.mul_mod.into(), ], Value::CircuitModulus(value) => vec![value.into()], - Value::Circuit(data) | Value::CircuitOutputs(data) => data.iter().map(Felt::from).collect(), + Value::Circuit(data) => data.iter().map(Felt::from).collect(), + Value::CircuitOutputs { + circuits: data, + modulus, + } => { + let mut felts = data.iter().map(Felt::from).collect::>(); + felts.push(modulus.into()); + felts + } Value::EcPoint { x, y } => { vec![*x, *y] } diff --git a/debug_utils/sierra-emu/tests/corelib.rs b/debug_utils/sierra-emu/tests/corelib.rs index 50bd5d5ea..1b032abcc 100644 --- a/debug_utils/sierra-emu/tests/corelib.rs +++ b/debug_utils/sierra-emu/tests/corelib.rs @@ -65,6 +65,7 @@ fn test_corelib() { "core::test::dict_test::test_array_from_squash_dict", "core::test::hash_test::test_blake2s", "core::test::testing_test::test_get_unspent_gas", + "core::test::qm31_test::", ]; let compiled = compile_tests( @@ -104,7 +105,7 @@ fn run_tests(compiled: TestCompilation) -> Vec { program.clone(), EntryPoint::String(name.clone()), vec![], - u64::MAX, + test.available_gas.map(|g| g as u64).unwrap_or_default(), ) }); diff --git a/debug_utils/sierra-emu/tests/libfuncs.rs b/debug_utils/sierra-emu/tests/libfuncs.rs index e7181885a..ff7a5ad82 100644 --- a/debug_utils/sierra-emu/tests/libfuncs.rs +++ b/debug_utils/sierra-emu/tests/libfuncs.rs @@ -145,13 +145,3 @@ fn test_run_full_circuit() { assert_eq!(**payload, expected_output); } - -#[test] -#[should_panic(expected = "attempt to divide by 0")] -fn test_circuit_failure() { - run_program( - "tests/tests/circuits_failure.cairo", - "circuits_failure::circuits_failure::main", - &[], - ); -} diff --git a/debug_utils/sierra-emu/tests/tests/circuits_failure.cairo b/debug_utils/sierra-emu/tests/tests/circuits_failure.cairo deleted file mode 100644 index fcf2684d0..000000000 --- a/debug_utils/sierra-emu/tests/tests/circuits_failure.cairo +++ /dev/null @@ -1,20 +0,0 @@ -use core::circuit::{ - RangeCheck96, AddMod, MulMod, u96, CircuitElement, CircuitInput, circuit_add, circuit_sub, - circuit_mul, circuit_inverse, EvalCircuitTrait, u384, CircuitOutputsTrait, CircuitModulus, - AddInputResultTrait, CircuitInputs, -}; - -fn main() { - let in1 = CircuitElement::> {}; - let inv = circuit_inverse(in1); - - let modulus = TryInto::<_, CircuitModulus>::try_into([7, 0, 0, 0]).unwrap(); - let outputs = (inv,) - .new_inputs() - .next([0, 0, 0, 0]) - .done() - .eval(modulus) - .unwrap(); - - outputs.get_output(inv); -} diff --git a/src/metadata/trace_dump.rs b/src/metadata/trace_dump.rs index 92e89aa0e..c0fb53d87 100644 --- a/src/metadata/trace_dump.rs +++ b/src/metadata/trace_dump.rs @@ -208,7 +208,7 @@ pub mod trace_dump_runtime { }; use cairo_lang_utils::ordered_hash_map::OrderedHashMap; use itertools::Itertools; - use num_bigint::{BigInt, BigUint}; + use num_bigint::{BigInt, BigUint, Sign}; use num_traits::One; use sierra_emu::{ starknet::{ @@ -227,7 +227,11 @@ pub mod trace_dump_runtime { sync::{LazyLock, Mutex}, }; - use crate::{starknet::ArrayAbi, types::TypeBuilder}; + use crate::{ + starknet::ArrayAbi, + types::TypeBuilder, + utils::{get_integer_layout, layout_repeat}, + }; use crate::runtime::FeltDict; @@ -565,21 +569,33 @@ pub mod trace_dump_runtime { panic!("generic arg should be a Circuit"); }; - let u384_layout = Layout::from_size_align(48, 16).unwrap(); + let u96_layout = get_integer_layout(96); let n_outputs = circuit.circuit_info.values.len(); let mut values = Vec::with_capacity(n_outputs); - let value_ptr = value_ptr.cast::<[u8; 48]>(); + let (u384_struct_layout, _) = layout_repeat(&u96_layout, 4).unwrap(); + let (gates_array_layout, gate_stride) = + layout_repeat(&u384_struct_layout, n_outputs).unwrap(); + let (_, modulus_offset) = + gates_array_layout.extend(u384_struct_layout).unwrap(); + + let value_ptr = value_ptr.cast::<[u8; 12]>(); + // get gate values for i in 0..n_outputs { - let size = u384_layout.pad_to_align().size(); - let current_ptr = value_ptr.byte_add(size * i); - let current_value = current_ptr.as_ref(); - values.push(BigUint::from_bytes_le(current_value)); + let gate_ptr = value_ptr.byte_add(gate_stride * i); + values.push(u384_struct_to_bigint(gate_ptr, 4)); } - Value::CircuitOutputs(values) + // get modulus value + let modulus_ptr = value_ptr.byte_add(modulus_offset); + let modulus = u384_struct_to_bigint(modulus_ptr, 4); + + Value::CircuitOutputs { + circuits: values, + modulus, + } } CircuitTypeConcrete::CircuitPartialOutputs(_) => { todo!("CircuitTypeConcrete::CircuitPartialOutputs") @@ -627,7 +643,6 @@ pub mod trace_dump_runtime { let value = unsafe { value_ptr.as_ref() }; Value::CircuitModulus(BigUint::from_bytes_le(value)) } - CircuitTypeConcrete::InverseGate(_) => Value::Unit, CircuitTypeConcrete::MulModGate(_) => Value::Unit, CircuitTypeConcrete::SubModGate(_) => Value::Unit, @@ -640,7 +655,40 @@ pub mod trace_dump_runtime { Value::U128(u128::from_le_bytes(array_value)) } - CircuitTypeConcrete::U96LimbsLessThanGuarantee(_) => Value::Unit, + CircuitTypeConcrete::U96LimbsLessThanGuarantee(info) => { + let value_ptr = value_ptr.cast::<[u8; 12]>(); + + let u96_layout = get_integer_layout(96); + let (u384_struct_layout, struct_stride) = + layout_repeat(&u96_layout, info.limb_count).unwrap(); + + let output_limbs = (0..info.limb_count) + .map(|i| { + let current_ptr = value_ptr.byte_add(struct_stride * i); + Value::BoundedInt { + range: 0.into()..BigInt::one() << 96, + value: BigInt::from_bytes_le(Sign::Plus, current_ptr.as_ref()), + } + }) + .collect::>(); + + let modulus_ptr = value_ptr.byte_add(u384_struct_layout.size()); + + let modulus_limbs = (0..info.limb_count) + .map(|i| { + let current_ptr = modulus_ptr.byte_add(struct_stride * i); + Value::BoundedInt { + range: 0.into()..BigInt::one() << 96, + value: BigInt::from_bytes_le(Sign::Plus, current_ptr.as_ref()), + } + }) + .collect::>(); + + Value::Struct(vec![ + Value::Struct(output_limbs), + Value::Struct(modulus_limbs), + ]) + } }, CoreTypeConcrete::Const(_) => todo!("CoreTypeConcrete::Const"), CoreTypeConcrete::Sint8(_) => Value::I8(value_ptr.cast().read()), @@ -772,6 +820,20 @@ pub mod trace_dump_runtime { } } + unsafe fn u384_struct_to_bigint(value_ptr: NonNull<[u8; 12]>, limbs_count: usize) -> BigUint { + let u96_layout = get_integer_layout(96); + let (_, elem_stride) = layout_repeat(&u96_layout, 4).unwrap(); + + let output_limbs = (0..limbs_count) + .flat_map(|i| { + let offset = elem_stride * i; + *value_ptr.byte_add(offset).as_ref() + }) + .collect::>(); + + BigUint::from_bytes_le(&output_limbs) + } + #[derive(Debug)] struct FeltDictEntry<'a> { dict: &'a FeltDict, From b3f218ef3a00bf8e87806c3bf85093b648a1e4f2 Mon Sep 17 00:00:00 2001 From: Julian Gonzalez Calderon Date: Mon, 26 May 2025 16:33:31 -0300 Subject: [PATCH 23/28] cherry: Use dynamic arrays instead of static arrays for circuit data (#1233) (#1237) --- src/libfuncs/circuit.rs | 308 +++++++++++++++++++---------------- src/types/circuit.rs | 352 +++++++++++++++++++++++++++++++--------- 2 files changed, 441 insertions(+), 219 deletions(-) diff --git a/src/libfuncs/circuit.rs b/src/libfuncs/circuit.rs index 0db7e041b..54ff0cb53 100644 --- a/src/libfuncs/circuit.rs +++ b/src/libfuncs/circuit.rs @@ -6,10 +6,12 @@ use super::{increment_builtin_counter_by, LibfuncHelper}; use crate::{ error::{Result, SierraAssertError}, libfuncs::r#struct::build_struct_value, - metadata::MetadataStorage, + metadata::{ + drop_overrides::DropOverridesMeta, realloc_bindings::ReallocBindingsMeta, MetadataStorage, + }, native_panic, types::{circuit::build_u384_struct_type, TypeBuilder}, - utils::{get_integer_layout, layout_repeat, BlockExt, ProgramRegistryExt}, + utils::{get_integer_layout, layout_repeat, BlockExt, GepIndex, ProgramRegistryExt}, }; use cairo_lang_sierra::{ extensions::{ @@ -28,10 +30,7 @@ use melior::{ arith::{self, CmpiPredicate}, cf, llvm, }, - ir::{ - attribute::DenseI32ArrayAttribute, r#type::IntegerType, Block, BlockLike, Location, Type, - Value, ValueLike, - }, + ir::{r#type::IntegerType, Block, BlockLike, Location, Type, Value, ValueLike}, Context, }; use num_traits::Signed; @@ -106,14 +105,39 @@ fn build_init_circuit_data<'ctx, 'this>( metadata: &mut MetadataStorage, info: &SignatureAndTypeConcreteLibfunc, ) -> Result<()> { - let rc_usage = match registry.get_type(&info.ty)? { - CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(info)) => { - info.circuit_info.rc96_usage() - } + let circuit_info = match registry.get_type(&info.ty)? { + CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(info)) => &info.circuit_info, _ => return Err(SierraAssertError::BadTypeInfo.into()), }; - let rc = increment_builtin_counter_by(context, entry, location, entry.arg(0)?, rc_usage)?; + let rc = increment_builtin_counter_by( + context, + entry, + location, + entry.arg(0)?, + circuit_info.rc96_usage(), + )?; + + // Calculate full capacity for array. + let capacity = circuit_info.n_inputs; + let u384_layout = get_integer_layout(384); + let capacity_bytes = layout_repeat(&u384_layout, capacity)? + .0 + .pad_to_align() + .size(); + let capacity_bytes_value = entry.const_int(context, location, capacity_bytes, 64)?; + + // Alloc memory for array. + let ptr_ty = llvm::r#type::pointer(context, 0); + let ptr = entry.append_op_result(llvm::zero(ptr_ty, location))?; + let ptr = entry.append_op_result(ReallocBindingsMeta::realloc( + context, + ptr, + capacity_bytes_value, + location, + )?)?; + + // Create accumulator struct. let k0 = entry.const_int(context, location, 0, 64)?; let accumulator_ty = &info.branch_signatures()[0].vars[1].ty; let accumulator = build_struct_value( @@ -124,7 +148,7 @@ fn build_init_circuit_data<'ctx, 'this>( helper, metadata, accumulator_ty, - &[k0], + &[k0, ptr], )?; entry.append_operation(helper.br(0, &[rc, accumulator], location)); @@ -140,16 +164,13 @@ fn build_add_input<'ctx, 'this>( entry: &'this Block<'ctx>, location: Location<'ctx>, helper: &LibfuncHelper<'ctx, 'this>, - metadata: &mut MetadataStorage, + _metadata: &mut MetadataStorage, info: &SignatureAndTypeConcreteLibfunc, ) -> Result<()> { let n_inputs = match registry.get_type(&info.ty)? { CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(info)) => info.circuit_info.n_inputs, _ => return Err(SierraAssertError::BadTypeInfo.into()), }; - let accumulator_type_id = &info.param_signatures()[0].ty; - let accumulator_ctype = registry.get_type(accumulator_type_id)?; - let accumulator_layout = accumulator_ctype.layout(registry)?; let accumulator: Value = entry.arg(0)?; @@ -161,14 +182,42 @@ fn build_add_input<'ctx, 'this>( IntegerType::new(context, 64).into(), 0, )?; + // Calculate next length: next_length = current_length + 1 + let k1 = entry.const_int(context, location, 1, 64)?; + let next_length = entry.addi(current_length, k1, location)?; + // Insert next_length into accumulator + let accumulator = entry.insert_value(context, location, accumulator, next_length, 0)?; + + // Get pointer to inputs array + let inputs_ptr = entry.extract_value( + context, + location, + accumulator, + llvm::r#type::pointer(context, 0), + 1, + )?; + // Get pointer to next input to insert + let next_input_ptr = entry.gep( + context, + location, + inputs_ptr, + &[GepIndex::Value(current_length)], + IntegerType::new(context, 384).into(), + )?; + + // Interpret u384 struct (input) as u384 integer + let u384_struct = entry.arg(1)?; + let new_input = u384_struct_to_integer(context, entry, location, u384_struct)?; + // Store the u384 into next input pointer + entry.store(context, location, next_input_ptr, new_input)?; - // Check if last_insert: current_length == number_of_inputs - 1 - let n_inputs_minus_1 = entry.const_int(context, location, n_inputs - 1, 64)?; + // Check if last_insert: next_length == number_of_inputs + let n_inputs = entry.const_int(context, location, n_inputs, 64)?; let last_insert = entry.cmpi( context, arith::CmpiPredicate::Eq, - current_length, - n_inputs_minus_1, + next_length, + n_inputs, location, )?; @@ -184,124 +233,23 @@ fn build_add_input<'ctx, 'this>( location, )); - // If not last insert, then: + // If not last insert, then return accumulator { - // Calculate next length: next_length = current_length + 1 - let k1 = middle_insert_block.const_int(context, location, 1, 64)?; - let next_length = middle_insert_block.addi(current_length, k1, location)?; - - // Insert next_length into accumulator - let accumulator = - middle_insert_block.insert_value(context, location, accumulator, next_length, 0)?; - - // Get pointer to accumulator with alloc and store - let accumulator_ptr = helper.init_block().alloca1( - context, - location, - accumulator.r#type(), - accumulator_layout.align(), - )?; - middle_insert_block.store(context, location, accumulator_ptr, accumulator)?; - - // Get pointer to next input to insert - let k0 = middle_insert_block.const_int(context, location, 0, 64)?; - let next_input_ptr = - middle_insert_block.append_op_result(llvm::get_element_ptr_dynamic( - context, - accumulator_ptr, - &[k0, k1, current_length], - accumulator.r#type(), - llvm::r#type::pointer(context, 0), - location, - ))?; - - // Interpret u384 struct (input) as u384 integer - let u384_struct = entry.arg(1)?; - let new_input = - u384_struct_to_integer(context, middle_insert_block, location, u384_struct)?; - - // Store the u384 into next input pointer - middle_insert_block.store(context, location, next_input_ptr, new_input)?; - - // Load accumulator from pointer - let accumulator = - middle_insert_block.load(context, location, accumulator_ptr, accumulator.r#type())?; - middle_insert_block.append_operation(helper.br(1, &[accumulator], location)); } - // If is last insert, then: + // If is last insert, then return accumulator.pointer { - let data_type_id = &info.branch_signatures()[0].vars[0].ty; - let (data_type, data_layout) = - registry.build_type_with_layout(context, helper, metadata, data_type_id)?; - - // Alloc return data - let data_ptr = - helper - .init_block() - .alloca1(context, location, data_type, data_layout.align())?; - - // Get pointer to accumulator with alloc and store - let accumulator_ptr = helper.init_block().alloca1( - context, - location, - accumulator.r#type(), - accumulator_layout.align(), - )?; - last_insert_block.store(context, location, accumulator_ptr, accumulator)?; - - // Get pointer to accumulator input - let k0 = last_insert_block.const_int(context, location, 0, 64)?; - let k1 = last_insert_block.const_int(context, location, 1, 64)?; - let accumulator_input_ptr = - last_insert_block.append_op_result(llvm::get_element_ptr_dynamic( - context, - accumulator_ptr, - &[k0, k1], - accumulator.r#type(), - llvm::r#type::pointer(context, 0), - location, - ))?; - - // Copy accumulator input into return data - let accumulator_input_length = last_insert_block.const_int( + // Get pointer to inputs array + let inputs_ptr = last_insert_block.extract_value( context, location, - layout_repeat(&get_integer_layout(384), n_inputs - 1)? - .0 - .size(), - 64, - )?; - last_insert_block.memcpy( - context, - location, - accumulator_input_ptr, - data_ptr, - accumulator_input_length, - ); - - // Interpret u384 struct (input) as u384 integer - let u384_struct = entry.arg(1)?; - let new_input = u384_struct_to_integer(context, last_insert_block, location, u384_struct)?; - - // Get pointer to data end - let data_end_ptr = last_insert_block.append_op_result(llvm::get_element_ptr( - context, - data_ptr, - DenseI32ArrayAttribute::new(context, &[0, n_inputs as i32 - 1]), - data_type, + accumulator, llvm::r#type::pointer(context, 0), - location, - ))?; - - // Store the u384 into next input pointer - last_insert_block.store(context, location, data_end_ptr, new_input)?; - - // Load data from pointer - let data = last_insert_block.load(context, location, data_ptr, data_type)?; + 1, + )?; - last_insert_block.append_operation(helper.br(0, &[data], location)); + last_insert_block.append_operation(helper.br(0, &[inputs_ptr], location)); } Ok(()) @@ -394,6 +342,17 @@ fn build_eval<'ctx, 'this>( // Ok case { + // We drop circuit_data, as its consumed by this libfunc. + if let Some(drop_overrides_meta) = metadata.get::() { + drop_overrides_meta.invoke_override( + context, + ok_block, + location, + &info.signature.param_signatures[3].ty, + circuit_data, + )?; + } + let mul_mod = increment_builtin_counter_by( context, ok_block, @@ -408,12 +367,36 @@ fn build_eval<'ctx, 'this>( .map(|value| u384_integer_to_struct(context, ok_block, location, value)) .collect::>>()?; - let n_gates = circuit_info.values.len(); - let gates_array = ok_block.append_op_result(llvm::undef( - llvm::r#type::array(build_u384_struct_type(context), n_gates as u32), + // Calculate full capacity for array. + let outputs_capacity = circuit_info.values.len(); + let u384_struct_layout = layout_repeat(&get_integer_layout(96), 4)?.0; + let outputs_capacity_bytes = layout_repeat(&u384_struct_layout, outputs_capacity)? + .0 + .pad_to_align() + .size(); + let outputs_capacity_bytes_value = + ok_block.const_int(context, location, outputs_capacity_bytes, 64)?; + + // Alloc memory for array. + let ptr_ty = llvm::r#type::pointer(context, 0); + let outputs_ptr = ok_block.append_op_result(llvm::zero(ptr_ty, location))?; + let outputs_ptr = ok_block.append_op_result(ReallocBindingsMeta::realloc( + context, + outputs_ptr, + outputs_capacity_bytes_value, location, - ))?; - let gates_array = ok_block.insert_values(context, location, gates_array, &gates)?; + )?)?; + + for (i, gate) in gates.into_iter().enumerate() { + let value_ptr = ok_block.gep( + context, + location, + outputs_ptr, + &[GepIndex::Const(i as i32)], + build_u384_struct_type(context), + )?; + ok_block.store(context, location, value_ptr, gate)?; + } let modulus_struct = u384_integer_to_struct(context, ok_block, location, circuit_modulus)?; @@ -427,7 +410,7 @@ fn build_eval<'ctx, 'this>( helper, metadata, outputs_type_id, - &[gates_array, modulus_struct], + &[outputs_ptr, modulus_struct], )?; ok_block.append_operation(helper.br(0, &[add_mod, mul_mod, outputs], location)); @@ -435,6 +418,17 @@ fn build_eval<'ctx, 'this>( // Error case { + // We drop circuit_data, as its consumed by this libfunc. + if let Some(drop_overrides_meta) = metadata.get::() { + drop_overrides_meta.invoke_override( + context, + err_block, + location, + &info.signature.param_signatures[3].ty, + circuit_data, + )?; + } + // We only consider mul gates evaluated before failure let mul_mod = { let mul_mod_usage = err_block.muli( @@ -483,12 +477,18 @@ fn build_gate_evaluation<'ctx, 'this>( let mut values = vec![None; 1 + circuit_info.n_inputs + circuit_info.values.len()]; values[0] = Some(block.const_int(context, location, 1, 384)?); for i in 0..circuit_info.n_inputs { - values[i + 1] = Some(block.extract_value( + let value_ptr = block.gep( context, location, circuit_data, + &[GepIndex::Const(i as i32)], + IntegerType::new(context, 384).into(), + )?; + values[i + 1] = Some(block.load( + context, + location, + value_ptr, IntegerType::new(context, 384).into(), - i, )?); } @@ -880,12 +880,11 @@ fn build_get_output<'ctx, 'this>( let outputs = entry.arg(0)?; - let n_gates = circuit_info.values.len(); - let output_gates = entry.extract_value( + let values_ptr = entry.extract_value( context, location, outputs, - llvm::r#type::array(build_u384_struct_type(context), n_gates as u32), + llvm::r#type::pointer(context, 0), 0, )?; let modulus_struct = entry.extract_value( @@ -895,12 +894,20 @@ fn build_get_output<'ctx, 'this>( build_u384_struct_type(context), 1, )?; - let output_struct = entry.extract_value( + + let output_struct_ptr = entry.gep( + context, + location, + values_ptr, + &[GepIndex::Const(output_idx as i32)], + build_u384_struct_type(context), + )?; + + let output_struct = entry.load( context, location, - output_gates, + output_struct_ptr, build_u384_struct_type(context), - output_idx, )?; let guarantee_type_id = &info.branch_signatures()[0].vars[1].ty; @@ -915,6 +922,21 @@ fn build_get_output<'ctx, 'this>( &[output_struct, modulus_struct], )?; + // We drop the circuit outputs value, as its consumed by this libfunc. + // NOTE: As this libfunc consumes circuit_outputs, this implies that + // calling it multiple times involves duplicating the circuit outputs + // each time. This could be fixed by implementing a reference counter, + // like we do with regular arrays. + if let Some(drop_overrides_meta) = metadata.get::() { + drop_overrides_meta.invoke_override( + context, + entry, + location, + &info.signature.param_signatures[0].ty, + outputs, + )?; + } + entry.append_operation(helper.br(0, &[output_struct, guarantee], location)); Ok(()) diff --git a/src/types/circuit.rs b/src/types/circuit.rs index 613113a40..107506aaf 100644 --- a/src/types/circuit.rs +++ b/src/types/circuit.rs @@ -2,11 +2,14 @@ use std::alloc::Layout; -use super::WithSelf; +use super::{BlockExt, WithSelf}; use crate::{ error::{Result, SierraAssertError}, - metadata::MetadataStorage, - utils::{get_integer_layout, layout_repeat}, + metadata::{ + drop_overrides::DropOverridesMeta, dup_overrides::DupOverridesMeta, + realloc_bindings::ReallocBindingsMeta, MetadataStorage, + }, + utils::{get_integer_layout, layout_repeat, ProgramRegistryExt}, }; use cairo_lang_sierra::{ extensions::{ @@ -18,8 +21,8 @@ use cairo_lang_sierra::{ program_registry::ProgramRegistry, }; use melior::{ - dialect::llvm, - ir::{r#type::IntegerType, Module, Type}, + dialect::{func, llvm}, + ir::{r#type::IntegerType, Block, BlockLike, Location, Module, Region, Type, Value}, Context, }; @@ -88,11 +91,23 @@ pub fn build<'ctx>( } } +/// Builds the circuit accumulator type. +/// +/// ## Layout: +/// +/// Holds up to N_INPUTS elements. Where each element is an u384 integer. +/// +/// ```txt +/// type = struct { +/// size: u64, +/// data: *u384, +/// } +/// ``` pub fn build_circuit_accumulator<'ctx>( context: &'ctx Context, - _module: &Module<'ctx>, + module: &Module<'ctx>, registry: &ProgramRegistry, - _metadata: &mut MetadataStorage, + metadata: &mut MetadataStorage, info: WithSelf, ) -> Result> { let Some(GenericArg::Type(circuit_type_id)) = info.info.long_id.generic_args.first() else { @@ -104,21 +119,95 @@ pub fn build_circuit_accumulator<'ctx>( return Err(SierraAssertError::BadTypeInfo.into()); }; - let n_inputs = circuit.circuit_info.n_inputs; + DupOverridesMeta::register_with( + context, + module, + registry, + metadata, + info.self_ty(), + |metadata| { + let location = Location::unknown(context); + let region = Region::new(); + let value_ty = registry.build_type(context, module, metadata, info.self_ty())?; + let entry = region.append_block(Block::new(&[(value_ty, location)])); + + let accumulator = entry.arg(0)?; + let inputs_ptr = entry.extract_value( + context, + location, + accumulator, + llvm::r#type::pointer(context, 0), + 1, + )?; + + let u384_layout = get_integer_layout(384); + + let new_inputs_ptr = build_array_dup( + context, + &entry, + location, + inputs_ptr, + circuit.circuit_info.n_inputs, + u384_layout, + )?; + + let new_accumulator = + entry.insert_value(context, location, accumulator, new_inputs_ptr, 1)?; + entry.append_operation(func::r#return(&[accumulator, new_accumulator], location)); + + Ok(Some(region)) + }, + )?; + DropOverridesMeta::register_with( + context, + module, + registry, + metadata, + info.self_ty(), + |metadata| { + let location = Location::unknown(context); + let region = Region::new(); + let value_ty = registry.build_type(context, module, metadata, info.self_ty())?; + let entry = region.append_block(Block::new(&[(value_ty, location)])); + + let accumulator = entry.arg(0)?; + let inputs_ptr = entry.extract_value( + context, + location, + accumulator, + llvm::r#type::pointer(context, 0), + 1, + )?; + + entry.append_operation(ReallocBindingsMeta::free(context, inputs_ptr, location)?); + entry.append_operation(func::r#return(&[], location)); + + Ok(Some(region)) + }, + )?; let fields = vec![ IntegerType::new(context, 64).into(), - llvm::r#type::array(IntegerType::new(context, 384).into(), n_inputs as u32 - 1), + llvm::r#type::pointer(context, 0), ]; Ok(llvm::r#type::r#struct(context, &fields, false)) } +/// Builds the circuit data type. +/// +/// ## Layout: +/// +/// Holds N_INPUTS elements. Where each element is an u384. +/// +/// ```txt +/// type = *u384 +/// ``` pub fn build_circuit_data<'ctx>( context: &'ctx Context, - _module: &Module<'ctx>, + module: &Module<'ctx>, registry: &ProgramRegistry, - _metadata: &mut MetadataStorage, + metadata: &mut MetadataStorage, info: WithSelf, ) -> Result> { let Some(GenericArg::Type(circuit_type_id)) = info.info.long_id.generic_args.first() else { @@ -130,19 +219,85 @@ pub fn build_circuit_data<'ctx>( return Err(SierraAssertError::BadTypeInfo.into()); }; - let n_inputs = circuit.circuit_info.n_inputs; + DupOverridesMeta::register_with( + context, + module, + registry, + metadata, + info.self_ty(), + |metadata| { + let location = Location::unknown(context); + let region = Region::new(); + let value_ty = registry.build_type(context, module, metadata, info.self_ty())?; + let entry = region.append_block(Block::new(&[(value_ty, location)])); + + let data_ptr = entry.arg(0)?; - Ok(llvm::r#type::array( - IntegerType::new(context, 384).into(), - n_inputs as u32, - )) + let u384_layout = get_integer_layout(384); + + let new_data_ptr = build_array_dup( + context, + &entry, + location, + data_ptr, + circuit.circuit_info.n_inputs, + u384_layout, + )?; + + entry.append_operation(func::r#return(&[data_ptr, new_data_ptr], location)); + + Ok(Some(region)) + }, + )?; + DropOverridesMeta::register_with( + context, + module, + registry, + metadata, + info.self_ty(), + |metadata| { + let location = Location::unknown(context); + let region = Region::new(); + let value_ty = registry.build_type(context, module, metadata, info.self_ty())?; + let entry = region.append_block(Block::new(&[(value_ty, location)])); + + let data_ptr = entry.arg(0)?; + + entry.append_operation(ReallocBindingsMeta::free(context, data_ptr, location)?); + entry.append_operation(func::r#return(&[], location)); + + Ok(Some(region)) + }, + )?; + + Ok(llvm::r#type::pointer(context, 0)) } +/// Builds the circuit outputs type. +/// +/// ## Layout: +/// +/// Holds N_VALUES elements, where each element is a u384 struct, +/// A u384 struct contains 4 limbs, each a u96 integer. +/// +/// ```txt +/// type = struct { +/// data: *u384s, +/// modulus: u384s, +/// }; +/// +/// u384s = struct { +/// limb1: u96, +/// limb2: u96, +/// limb3: u96, +/// limb4: u96, +/// } +/// ``` pub fn build_circuit_outputs<'ctx>( context: &'ctx Context, - _module: &Module<'ctx>, + module: &Module<'ctx>, registry: &ProgramRegistry, - _metadata: &mut MetadataStorage, + metadata: &mut MetadataStorage, info: WithSelf, ) -> Result> { let Some(GenericArg::Type(circuit_type_id)) = info.info.long_id.generic_args.first() else { @@ -154,12 +309,76 @@ pub fn build_circuit_outputs<'ctx>( return Err(SierraAssertError::BadTypeInfo.into()); }; - let n_gates = circuit.circuit_info.values.len(); + DupOverridesMeta::register_with( + context, + module, + registry, + metadata, + info.self_ty(), + |metadata| { + let location = Location::unknown(context); + let region = Region::new(); + let value_ty = registry.build_type(context, module, metadata, info.self_ty())?; + let entry = region.append_block(Block::new(&[(value_ty, location)])); + + let outputs = entry.arg(0)?; + let gates_ptr = entry.extract_value( + context, + location, + outputs, + llvm::r#type::pointer(context, 0), + 0, + )?; + + let u384_struct_layout = layout_repeat(&get_integer_layout(96), 4)?.0; + + let new_gates_ptr = build_array_dup( + context, + &entry, + location, + gates_ptr, + circuit.circuit_info.values.len(), + u384_struct_layout, + )?; + + let new_outputs = entry.insert_value(context, location, outputs, new_gates_ptr, 0)?; + entry.append_operation(func::r#return(&[outputs, new_outputs], location)); + + Ok(Some(region)) + }, + )?; + DropOverridesMeta::register_with( + context, + module, + registry, + metadata, + info.self_ty(), + |metadata| { + let location = Location::unknown(context); + let region = Region::new(); + let value_ty = registry.build_type(context, module, metadata, info.self_ty())?; + let entry = region.append_block(Block::new(&[(value_ty, location)])); + + let outputs = entry.arg(0)?; + let gates_ptr = entry.extract_value( + context, + location, + outputs, + llvm::r#type::pointer(context, 0), + 0, + )?; + + entry.append_operation(ReallocBindingsMeta::free(context, gates_ptr, location)?); + entry.append_operation(func::r#return(&[], location)); + + Ok(Some(region)) + }, + )?; Ok(llvm::r#type::r#struct( context, &[ - llvm::r#type::array(build_u384_struct_type(context), n_gates as u32), + llvm::r#type::pointer(context, 0), build_u384_struct_type(context), ], false, @@ -185,6 +404,33 @@ pub fn build_u96_limbs_less_than_guarantee<'ctx>( )) } +pub fn build_array_dup<'ctx, 'this>( + context: &'ctx Context, + block: &'this Block<'ctx>, + location: Location<'ctx>, + ptr: Value<'ctx, 'this>, + capacity: usize, + layout: Layout, +) -> Result> { + let capacity_bytes = layout_repeat(&layout, capacity)?.0.pad_to_align().size(); + let capacity_bytes_value = block.const_int(context, location, capacity_bytes, 64)?; + + let new_inputs_ptr = { + let ptr_ty = llvm::r#type::pointer(context, 0); + let new_inputs_ptr = block.append_op_result(llvm::zero(ptr_ty, location))?; + block.append_op_result(ReallocBindingsMeta::realloc( + context, + new_inputs_ptr, + capacity_bytes_value, + location, + )?)? + }; + + block.memcpy(context, location, ptr, new_inputs_ptr, capacity_bytes_value); + + Ok(new_inputs_ptr) +} + pub const fn is_complex(info: &CircuitTypeConcrete) -> bool { match *info { CircuitTypeConcrete::AddMod(_) @@ -232,7 +478,7 @@ pub const fn is_zst(info: &CircuitTypeConcrete) -> bool { } pub fn layout( - registry: &ProgramRegistry, + _registry: &ProgramRegistry, info: &CircuitTypeConcrete, ) -> Result { match info { @@ -252,64 +498,18 @@ pub fn layout( | CircuitTypeConcrete::CircuitDescriptor(_) | CircuitTypeConcrete::CircuitFailureGuarantee(_) => Ok(Layout::new::<()>()), - CircuitTypeConcrete::CircuitData(info) => { - let Some(GenericArg::Type(circuit_type_id)) = info.info.long_id.generic_args.first() - else { - return Err(SierraAssertError::BadTypeInfo.into()); - }; - let CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(circuit)) = - registry.get_type(circuit_type_id)? - else { - return Err(SierraAssertError::BadTypeInfo.into()); - }; - - let n_inputs = circuit.circuit_info.n_inputs; + CircuitTypeConcrete::CircuitData(_) => Ok(Layout::new::<*mut ()>()), + CircuitTypeConcrete::CircuitOutputs(_) => { + let u384_struct_layout = layout_repeat(&get_integer_layout(96), 4)?.0; + let pointer_layout = Layout::new::<*mut ()>(); - let u384_layout = get_integer_layout(384); - - let layout = layout_repeat(&u384_layout, n_inputs)?.0; - - Ok(layout) + Ok(pointer_layout.extend(u384_struct_layout)?.0) } - CircuitTypeConcrete::CircuitOutputs(info) => { - let Some(GenericArg::Type(circuit_type_id)) = info.info.long_id.generic_args.first() - else { - return Err(SierraAssertError::BadTypeInfo.into()); - }; - let CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(circuit)) = - registry.get_type(circuit_type_id)? - else { - return Err(SierraAssertError::BadTypeInfo.into()); - }; - - let n_gates = circuit.circuit_info.values.len(); - - let u384_layout = get_integer_layout(384); - - let layout = layout_repeat(&u384_layout, n_gates)?.0; - - Ok(layout) - } - CircuitTypeConcrete::CircuitInputAccumulator(info) => { - let Some(GenericArg::Type(circuit_type_id)) = info.info.long_id.generic_args.first() - else { - return Err(SierraAssertError::BadTypeInfo.into()); - }; - let CoreTypeConcrete::Circuit(CircuitTypeConcrete::Circuit(circuit)) = - registry.get_type(circuit_type_id)? - else { - return Err(SierraAssertError::BadTypeInfo.into()); - }; - - let n_inputs = circuit.circuit_info.n_inputs; - - let length_layout = get_integer_layout(64); - - let u384_layout = get_integer_layout(384); - let inputs_layout = layout_repeat(&u384_layout, n_inputs - 1)?.0; - let layout = length_layout.extend(inputs_layout)?.0; + CircuitTypeConcrete::CircuitInputAccumulator(_) => { + let integer_layout = get_integer_layout(64); + let pointer_layout = Layout::new::<*mut ()>(); - Ok(layout) + Ok(integer_layout.extend(pointer_layout)?.0) } CircuitTypeConcrete::CircuitPartialOutputs(_) => Ok(Layout::new::<()>()), } From 1c0488a6f0201470d9c4cd8c27689c6122489364 Mon Sep 17 00:00:00 2001 From: Franco Giachetta Date: Fri, 30 May 2025 10:56:51 -0300 Subject: [PATCH 24/28] [Serra-Emu] Fix some libfuncs (#1223) * change initial gas due to overflows * fix some libfuncs in circuits * fix is() for circuits * remove Value::Unit from return values in get_output * match U96LimbsLessThanGuarantee correctly * some corrections to circuits * some corrections to circuits * fix failure_guarantee_verify to avoid panicking * fix circuits * more fixes to circuits + implement ec_zero libfunc * clippy * remove unnecesary tests * remove unnecesary file * fix branch index * add comment * fix eval_u96_limbs_less_than_guarantee_verify * remove circuit failure test * update trace dump with new Value::CircitOutputs * fmt * reviews + fix modulus in trace dump * remove unnecesary file * fix clippy * implement circuit_single_limb_less_than_guarantee_verify * fix trace dump for circuit outputs * fix felt_dict_get * fix felt_dict_get * implement U96LimbsLessThanGuarantee for trace dump * remove unnecessary code * fmt * fix felt_dict_entry_get * revert change in circuits.rs * remove unwanted file * revert unwanted change * revert unwanted change * fix cast and int range libfuncs * make trace dump for circuit outputs cleaner * remove unwanted code * increase felt dict count always during get() * doc function + merge main * fix some libfuncs * implement CouponCall not working * all tests passing * remove WIP for corelib tests in sierra-emu's ci * remove WIP for corelib tests in sierra-emu's ci * fmt! * pop unused argument in coupon_call * better error messages in utils --- .github/workflows/ci.yml | 3 +- debug_utils/sierra-emu/src/lib.rs | 4 +- debug_utils/sierra-emu/src/utils.rs | 38 +++---- debug_utils/sierra-emu/src/value.rs | 6 +- debug_utils/sierra-emu/src/vm.rs | 8 +- debug_utils/sierra-emu/src/vm/array.rs | 9 +- debug_utils/sierra-emu/src/vm/bounded_int.rs | 35 +++--- debug_utils/sierra-emu/src/vm/cast.rs | 14 +-- .../sierra-emu/src/vm/function_call.rs | 19 +++- debug_utils/sierra-emu/src/vm/int.rs | 100 +++++++++++------- debug_utils/sierra-emu/src/vm/int_range.rs | 26 ++--- debug_utils/sierra-emu/src/vm/uint256.rs | 11 +- 12 files changed, 165 insertions(+), 108 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28de76a45..cd4ec77e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -361,10 +361,9 @@ jobs: - name: Run tests working-directory: ./debug_utils/sierra-emu run: make test-no-corelib - - name: Run corelib's tests (WIP) + - name: Run corelib's tests working-directory: ./debug_utils/sierra-emu run: cargo test test_corelib -- --nocapture - continue-on-error: true # ignore result for now. When sierra-emu is fully implemented this should be removed build-sierra2casm-dbg: name: Build sierra2casm-dbg diff --git a/debug_utils/sierra-emu/src/lib.rs b/debug_utils/sierra-emu/src/lib.rs index 9aab69f83..bf1656dfa 100644 --- a/debug_utils/sierra-emu/src/lib.rs +++ b/debug_utils/sierra-emu/src/lib.rs @@ -10,6 +10,7 @@ use cairo_lang_sierra::{ program::{GenFunction, Program, StatementIdx}, program_registry::ProgramRegistry, }; +use debug::type_to_name; use starknet::StubSyscallHandler; pub use self::{dump::*, gas::BuiltinCosts, value::*, vm::VirtualMachine}; @@ -132,7 +133,8 @@ pub fn run_program( } _ => todo!(), }, - _ => todo!(), + CoreTypeConcrete::EcOp(_) => Value::Unit, + _ => todo!("{}", type_to_name(type_id, vm.registry())), } }) .collect::>(), diff --git a/debug_utils/sierra-emu/src/utils.rs b/debug_utils/sierra-emu/src/utils.rs index a2b5cc141..029a88ece 100644 --- a/debug_utils/sierra-emu/src/utils.rs +++ b/debug_utils/sierra-emu/src/utils.rs @@ -3,13 +3,14 @@ use cairo_lang_sierra::{ core::{CoreLibfunc, CoreType, CoreTypeConcrete}, utils::Range, }, + ids::ConcreteTypeId, program_registry::ProgramRegistry, }; use num_bigint::BigInt; use num_traits::{Bounded, One, ToPrimitive}; use starknet_types_core::felt::CAIRO_PRIME_BIGINT; -use crate::Value; +use crate::{debug::type_to_name, Value}; /// Receives a vector of values, filters any which is non numeric and returns a `Vec` /// Useful when a binary operation takes generic values (like with bounded ints). @@ -29,21 +30,20 @@ pub fn get_numeric_args_as_bigints(args: &[Value]) -> Vec { Value::U128(value) => BigInt::from(*value), Value::Felt(value) => value.to_bigint(), Value::Bytes31(value) => value.to_bigint(), - value => panic!("argument should be an integer: {:?}", value), + value => panic!("Argument should be an integer: {:?}", value), }) .collect() } pub fn get_value_from_integer( registry: &ProgramRegistry, - ty: &CoreTypeConcrete, + ty_id: &ConcreteTypeId, value: BigInt, ) -> Value { + let ty = registry.get_type(ty_id).unwrap(); + match ty { - CoreTypeConcrete::NonZero(info) => { - let ty = registry.get_type(&info.ty).unwrap(); - get_value_from_integer(registry, ty, value) - } + CoreTypeConcrete::NonZero(info) => get_value_from_integer(registry, &info.ty, value), CoreTypeConcrete::Sint8(_) => Value::I8(value.to_i8().unwrap()), CoreTypeConcrete::Sint16(_) => Value::I16(value.to_i16().unwrap()), CoreTypeConcrete::Sint32(_) => Value::I32(value.to_i32().unwrap()), @@ -62,12 +62,15 @@ pub fn get_value_from_integer( } } CoreTypeConcrete::Felt252(_) => Value::Felt(value.into()), - _ => panic!("cannot get integer value for a non-integer type"), + _ => panic!( + "Cannot get integer value for a non-integer type: {}", + type_to_name(ty_id, registry) + ), } } pub fn integer_range( - ty: &CoreTypeConcrete, + ty_id: &ConcreteTypeId, registry: &ProgramRegistry, ) -> Range { fn range_of() -> Range @@ -80,6 +83,8 @@ pub fn integer_range( } } + let ty = registry.get_type(ty_id).unwrap(); + match ty { CoreTypeConcrete::Uint8(_) => range_of::(), CoreTypeConcrete::Uint16(_) => range_of::(), @@ -100,14 +105,11 @@ pub fn integer_range( lower: BigInt::ZERO, upper: BigInt::one() << 248, }, - CoreTypeConcrete::Const(info) => { - let ty = registry.get_type(&info.inner_ty).unwrap(); - integer_range(ty, registry) - } - CoreTypeConcrete::NonZero(info) => { - let ty = registry.get_type(&info.ty).unwrap(); - integer_range(ty, registry) - } - _ => panic!("cannot get integer range value for a non-integer type"), + CoreTypeConcrete::Const(info) => integer_range(&info.inner_ty, registry), + CoreTypeConcrete::NonZero(info) => integer_range(&info.ty, registry), + _ => panic!( + "Cannot get integer range value for a non-integer type {}", + type_to_name(ty_id, registry) + ), } } diff --git a/debug_utils/sierra-emu/src/value.rs b/debug_utils/sierra-emu/src/value.rs index 7c3f64c61..2689ab0fa 100644 --- a/debug_utils/sierra-emu/src/value.rs +++ b/debug_utils/sierra-emu/src/value.rs @@ -101,13 +101,17 @@ impl Value { index: 0, payload: Box::new(Value::default_for_type(registry, &info.variants[0])), }, + CoreTypeConcrete::Array(info) => Value::Array { + ty: info.ty.clone(), + data: vec![], + }, CoreTypeConcrete::Struct(info) => Value::Struct( info.members .iter() .map(|member| Value::default_for_type(registry, member)) .collect(), ), - CoreTypeConcrete::Nullable(info) => Value::default_for_type(registry, &info.ty), + CoreTypeConcrete::Nullable(_) => Value::Null, x => panic!("type {:?} has no default value implementation", x.info()), } } diff --git a/debug_utils/sierra-emu/src/vm.rs b/debug_utils/sierra-emu/src/vm.rs index e445f2fcd..24a83ebba 100644 --- a/debug_utils/sierra-emu/src/vm.rs +++ b/debug_utils/sierra-emu/src/vm.rs @@ -471,7 +471,6 @@ fn eval<'a>( CoreConcreteLibfunc::Circuit(selector) => self::circuit::eval(registry, selector, args), CoreConcreteLibfunc::Const(selector) => self::r#const::eval(registry, selector, args), CoreConcreteLibfunc::Coupon(selector) => self::coupon::eval(registry, selector, args), - CoreConcreteLibfunc::CouponCall(_) => todo!(), CoreConcreteLibfunc::Debug(_) => todo!(), CoreConcreteLibfunc::Drop(info) => self::drop::eval(registry, info, args), CoreConcreteLibfunc::Dup(info) => self::dup::eval(registry, info, args), @@ -484,7 +483,12 @@ fn eval<'a>( CoreConcreteLibfunc::Felt252DictEntry(selector) => { self::felt252_dict_entry::eval(registry, selector, args) } - CoreConcreteLibfunc::FunctionCall(info) => self::function_call::eval(registry, info, args), + CoreConcreteLibfunc::FunctionCall(info) => { + self::function_call::eval_function_call(registry, info, args) + } + CoreConcreteLibfunc::CouponCall(info) => { + self::function_call::eval_coupon_call(registry, info, args) + } CoreConcreteLibfunc::Gas(selector) => { self::gas::eval(registry, selector, args, gas, *statement_idx, builtin_costs) } diff --git a/debug_utils/sierra-emu/src/vm/array.rs b/debug_utils/sierra-emu/src/vm/array.rs index d857892dc..4391b1807 100644 --- a/debug_utils/sierra-emu/src/vm/array.rs +++ b/debug_utils/sierra-emu/src/vm/array.rs @@ -297,13 +297,10 @@ fn eval_snapshot_multi_pop_back( }; if data.len() >= popped_cty.members.len() { - let new_data = data.split_off(data.len() - popped_cty.members.len()); - let value = Value::Struct(data); + let popped_data = data.split_off(data.len() - popped_cty.members.len()); + let value = Value::Struct(popped_data); assert!(value.is(registry, &info.popped_ty)); - EvalAction::NormalBranch( - 0, - smallvec![rangecheck, Value::Array { data: new_data, ty }, value], - ) + EvalAction::NormalBranch(0, smallvec![rangecheck, Value::Array { data, ty }, value]) } else { EvalAction::NormalBranch(1, smallvec![rangecheck, Value::Array { data, ty }]) } diff --git a/debug_utils/sierra-emu/src/vm/bounded_int.rs b/debug_utils/sierra-emu/src/vm/bounded_int.rs index 55d4955d5..31ac13750 100644 --- a/debug_utils/sierra-emu/src/vm/bounded_int.rs +++ b/debug_utils/sierra-emu/src/vm/bounded_int.rs @@ -1,5 +1,8 @@ use super::EvalAction; -use crate::{utils::get_numeric_args_as_bigints, Value}; +use crate::{ + utils::{get_numeric_args_as_bigints, get_value_from_integer}, + Value, +}; use cairo_lang_sierra::{ extensions::{ bounded_int::{ @@ -126,8 +129,10 @@ pub fn eval_div_rem( info: &BoundedIntDivRemConcreteLibfunc, args: Vec, ) -> EvalAction { - let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args).try_into().unwrap(); - + let range_check @ Value::Unit: Value = args[0].clone() else { + panic!() + }; + let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); let quo = &lhs / &rhs; let rem = lhs % rhs; @@ -151,7 +156,7 @@ pub fn eval_div_rem( EvalAction::NormalBranch( 0, smallvec![ - Value::Unit, // range_check + range_check, Value::BoundedInt { range: quo_range, value: quo, @@ -169,14 +174,10 @@ pub fn eval_constrain( info: &BoundedIntConstrainConcreteLibfunc, args: Vec, ) -> EvalAction { - let [range_check @ Value::Unit, value]: [Value; 2] = args.try_into().unwrap() else { + let range_check @ Value::Unit: Value = args[0].clone() else { panic!() }; - - let value = match value { - Value::I8(value) => value.into(), - _ => todo!(), - }; + let [value]: [BigInt; 1] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); if value < info.boundary { let range = match registry @@ -224,19 +225,19 @@ pub fn eval_constrain( } pub fn eval_is_zero( - _registry: &ProgramRegistry, - _info: &SignatureOnlyConcreteLibfunc, + registry: &ProgramRegistry, + info: &SignatureOnlyConcreteLibfunc, args: Vec, ) -> EvalAction { - let [value] = args.try_into().unwrap(); - let is_zero = match value { - Value::I8(value) => value == 0, - _ => todo!(), - }; + let [value] = get_numeric_args_as_bigints(&args).try_into().unwrap(); + let is_zero = value == 0.into(); + + let int_ty = &info.branch_signatures()[1].vars[0].ty; if is_zero { EvalAction::NormalBranch(0, smallvec![]) } else { + let value = get_value_from_integer(registry, int_ty, value); EvalAction::NormalBranch(1, smallvec![value]) } } diff --git a/debug_utils/sierra-emu/src/vm/cast.rs b/debug_utils/sierra-emu/src/vm/cast.rs index 8718dca64..4fac4867a 100644 --- a/debug_utils/sierra-emu/src/vm/cast.rs +++ b/debug_utils/sierra-emu/src/vm/cast.rs @@ -35,13 +35,15 @@ fn eval_downcast( }; let [value] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); - let int_ty = registry.get_type(&info.to_ty).unwrap(); - let range = info.to_range.lower.clone()..info.to_range.upper.clone(); + if range.contains(&value) { EvalAction::NormalBranch( 0, - smallvec![range_check, get_value_from_integer(registry, int_ty, value)], + smallvec![ + range_check, + get_value_from_integer(registry, &info.to_ty, value) + ], ) } else { EvalAction::NormalBranch(1, smallvec![range_check]) @@ -54,12 +56,10 @@ fn eval_upcast( args: Vec, ) -> EvalAction { let [value] = get_numeric_args_as_bigints(&args).try_into().unwrap(); - let int_ty = registry - .get_type(&info.branch_signatures()[0].vars[0].ty) - .unwrap(); + let int_ty_id = &info.branch_signatures()[0].vars[0].ty; EvalAction::NormalBranch( 0, - smallvec![get_value_from_integer(registry, int_ty, value)], + smallvec![get_value_from_integer(registry, int_ty_id, value)], ) } diff --git a/debug_utils/sierra-emu/src/vm/function_call.rs b/debug_utils/sierra-emu/src/vm/function_call.rs index 3b7298544..df70eae0a 100644 --- a/debug_utils/sierra-emu/src/vm/function_call.rs +++ b/debug_utils/sierra-emu/src/vm/function_call.rs @@ -8,7 +8,7 @@ use cairo_lang_sierra::{ program_registry::ProgramRegistry, }; -pub fn eval( +pub fn eval_function_call( registry: &ProgramRegistry, info: &SignatureAndFunctionConcreteLibfunc, args: Vec, @@ -21,3 +21,20 @@ pub fn eval( EvalAction::FunctionCall(info.function.id.clone(), args.into_iter().collect()) } + +pub fn eval_coupon_call( + registry: &ProgramRegistry, + info: &SignatureAndFunctionConcreteLibfunc, + mut args: Vec, +) -> EvalAction { + // Don't check the last arg since it is not actually an argument for the function call itself + assert_eq!(args.len() - 1, info.function.params.len()); + assert!(args + .iter() + .zip(&info.function.params) + .all(|(value, param)| value.is(registry, ¶m.ty))); + + args.pop(); + + EvalAction::FunctionCall(info.function.id.clone(), args.into_iter().collect()) +} diff --git a/debug_utils/sierra-emu/src/vm/int.rs b/debug_utils/sierra-emu/src/vm/int.rs index 88d360ef0..1a56c2ba3 100644 --- a/debug_utils/sierra-emu/src/vm/int.rs +++ b/debug_utils/sierra-emu/src/vm/int.rs @@ -14,12 +14,14 @@ use cairo_lang_sierra::{ is_zero::IsZeroTraits, lib_func::SignatureOnlyConcreteLibfunc, }, + ids::ConcreteTypeId, program_registry::ProgramRegistry, }; -use num_bigint::{BigInt, BigUint}; +use num_bigint::{BigInt, BigUint, ToBigInt}; use num_traits::ops::overflowing::{OverflowingAdd, OverflowingSub}; use smallvec::smallvec; use starknet_crypto::Felt; +use starknet_types_core::felt::NonZeroFelt; use crate::{ utils::{get_numeric_args_as_bigints, get_value_from_integer, integer_range}, @@ -29,7 +31,8 @@ use crate::{ use super::EvalAction; fn apply_overflowing_op_for_type( - ty: &CoreTypeConcrete, + registry: &ProgramRegistry, + ty: &ConcreteTypeId, lhs: BigInt, rhs: BigInt, op: IntOperator, @@ -50,8 +53,10 @@ fn apply_overflowing_op_for_type( (result.into(), had_overflow) } + let ty = registry.get_type(ty).unwrap(); + match ty { - CoreTypeConcrete::Sint8(_) => overflowing_op::(lhs, rhs, op), + CoreTypeConcrete::Sint8(_) => overflowing_op::(lhs, rhs, op), CoreTypeConcrete::Sint16(_) => overflowing_op::(lhs, rhs, op), CoreTypeConcrete::Sint32(_) => overflowing_op::(lhs, rhs, op), CoreTypeConcrete::Sint64(_) => overflowing_op::(lhs, rhs, op), @@ -132,7 +137,7 @@ pub fn eval_uint128( Uint128Concrete::SquareRoot(info) => eval_square_root(registry, info, args), Uint128Concrete::Equal(info) => eval_equal(registry, info, args), Uint128Concrete::ToFelt252(info) => eval_to_felt(registry, info, args), - Uint128Concrete::FromFelt252(info) => eval_from_felt(registry, info, args), + Uint128Concrete::FromFelt252(info) => eval_u128_from_felt(registry, info, args), Uint128Concrete::IsZero(info) => eval_is_zero(registry, info, args), Uint128Concrete::Divmod(info) => eval_divmod(registry, info, args), Uint128Concrete::Bitwise(info) => eval_bitwise(registry, info, args), @@ -147,9 +152,7 @@ fn eval_const( info: &IntConstConcreteLibfunc, _args: Vec, ) -> EvalAction { - let int_ty = registry - .get_type(&info.signature.branch_signatures[0].vars[0].ty) - .unwrap(); + let int_ty = &info.signature.branch_signatures[0].vars[0].ty; EvalAction::NormalBranch( 0, @@ -167,9 +170,7 @@ fn eval_bitwise( }; let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); - let int_ty = registry - .get_type(&info.signature.branch_signatures[0].vars[1].ty) - .unwrap(); + let int_ty = &info.signature.branch_signatures[0].vars[1].ty; let and = get_value_from_integer(registry, int_ty, &lhs & &rhs); let or = get_value_from_integer(registry, int_ty, &lhs | &rhs); @@ -188,15 +189,13 @@ fn eval_diff( }; let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); - let int_ty = registry - .get_type(&info.signature.branch_signatures[0].vars[1].ty) - .unwrap(); + let int_ty = &info.signature.branch_signatures[0].vars[1].ty; - let overflow = (lhs >= rhs) as usize; - let (res, _) = apply_overflowing_op_for_type(int_ty, lhs, rhs, IntOperator::OverflowingSub); + let (res, had_overflow) = + apply_overflowing_op_for_type(registry, int_ty, lhs, rhs, IntOperator::OverflowingSub); let res = get_value_from_integer(registry, int_ty, res); - EvalAction::NormalBranch(overflow, smallvec![range_check, res]) + EvalAction::NormalBranch(had_overflow as usize, smallvec![range_check, res]) } fn eval_divmod( @@ -209,9 +208,7 @@ fn eval_divmod( }; let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); - let int_ty = registry - .get_type(&info.signature.branch_signatures[0].vars[1].ty) - .unwrap(); + let int_ty = &info.signature.branch_signatures[0].vars[1].ty; let res = &lhs / &rhs; let rem = lhs % rhs; @@ -242,15 +239,22 @@ fn eval_from_felt( panic!() }; - let int_ty = registry - .get_type(&info.signature.branch_signatures[0].vars[1].ty) - .unwrap(); + let prime = Felt::prime(); + let half_prime = &prime / BigUint::from(2u8); - let range = integer_range(int_ty, registry); + let int_ty = &info.signature.branch_signatures[0].vars[1].ty; - let value = value_felt.to_bigint(); + let range = integer_range(int_ty, registry); - if value > range.lower && value <= range.upper { + let value = { + let value_bigint = value_felt.to_biguint(); + if value_bigint > half_prime { + (prime - value_bigint).to_bigint().unwrap() * BigInt::from(-1) + } else { + value_felt.to_bigint() + } + }; + if value >= range.lower && value < range.upper { let value = get_value_from_integer(registry, int_ty, value); EvalAction::NormalBranch(0, smallvec![range_check, value]) } else { @@ -258,6 +262,33 @@ fn eval_from_felt( } } +pub fn eval_u128_from_felt( + _registry: &ProgramRegistry, + _info: &SignatureOnlyConcreteLibfunc, + args: Vec, +) -> EvalAction { + let [range_check @ Value::Unit, Value::Felt(value)]: [Value; 2] = args.try_into().unwrap() + else { + panic!() + }; + + let bound = Felt::from(u128::MAX) + 1; + + if value < bound { + let value: u128 = value.to_biguint().try_into().unwrap(); + EvalAction::NormalBranch(0, smallvec![range_check, Value::U128(value)]) + } else { + let (new_value, overflow) = value.div_rem(&NonZeroFelt::try_from(bound).unwrap()); + + let overflow: u128 = overflow.to_biguint().try_into().unwrap(); + let new_value: u128 = new_value.to_biguint().try_into().unwrap(); + EvalAction::NormalBranch( + 1, + smallvec![range_check, Value::U128(new_value), Value::U128(overflow)], + ) + } +} + fn eval_is_zero( registry: &ProgramRegistry, info: &SignatureOnlyConcreteLibfunc, @@ -265,9 +296,7 @@ fn eval_is_zero( ) -> EvalAction { let [value]: [BigInt; 1] = get_numeric_args_as_bigints(&args).try_into().unwrap(); - let int_ty = registry - .get_type(&info.signature.branch_signatures[1].vars[0].ty) - .unwrap(); + let int_ty = &info.signature.branch_signatures[1].vars[0].ty; if value == 0.into() { EvalAction::NormalBranch(0, smallvec![]) @@ -288,11 +317,10 @@ fn eval_operation( panic!() }; let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); - let int_ty = registry - .get_type(&info.signature.branch_signatures[0].vars[1].ty) - .unwrap(); + let int_ty = &info.signature.branch_signatures[0].vars[1].ty; - let (res, had_overflow) = apply_overflowing_op_for_type(int_ty, lhs, rhs, info.operator); + let (res, had_overflow) = + apply_overflowing_op_for_type(registry, int_ty, lhs, rhs, info.operator); let res = get_value_from_integer(registry, int_ty, res); EvalAction::NormalBranch(had_overflow as usize, smallvec![range_check, res]) @@ -308,9 +336,7 @@ fn eval_square_root( }; let [value]: [BigInt; 1] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); - let int_ty = registry - .get_type(&info.signature.branch_signatures[0].vars[1].ty) - .unwrap(); + let int_ty = &info.signature.branch_signatures[0].vars[1].ty; let res = value.sqrt(); @@ -337,9 +363,7 @@ fn eval_widemul( ) -> EvalAction { let [lhs, rhs]: [BigInt; 2] = get_numeric_args_as_bigints(&args).try_into().unwrap(); - let int_ty = registry - .get_type(&info.signature.branch_signatures[0].vars[0].ty) - .unwrap(); + let int_ty = &info.signature.branch_signatures[0].vars[0].ty; let res = lhs * rhs; diff --git a/debug_utils/sierra-emu/src/vm/int_range.rs b/debug_utils/sierra-emu/src/vm/int_range.rs index 50b794bd2..e71bb6cd4 100644 --- a/debug_utils/sierra-emu/src/vm/int_range.rs +++ b/debug_utils/sierra-emu/src/vm/int_range.rs @@ -1,6 +1,6 @@ use cairo_lang_sierra::{ extensions::{ - core::{CoreLibfunc, CoreType}, + core::{CoreLibfunc, CoreType, CoreTypeConcrete}, lib_func::SignatureOnlyConcreteLibfunc, range::IntRangeConcreteLibfunc, ConcreteLibfunc, @@ -38,12 +38,12 @@ fn eval_try_new( }; let [x, y]: [BigInt; 2] = get_numeric_args_as_bigints(&args[1..]).try_into().unwrap(); - let int_ty = registry.get_type(&info.param_signatures()[1].ty).unwrap(); + let int_ty_id = &info.param_signatures()[1].ty; // if x >= y then the range is not valid and we return [y, y) (empty range) if x < y { - let x = get_value_from_integer(registry, int_ty, x); - let y = get_value_from_integer(registry, int_ty, y); + let x = get_value_from_integer(registry, int_ty_id, x); + let y = get_value_from_integer(registry, int_ty_id, y); EvalAction::NormalBranch( 0, smallvec![ @@ -55,7 +55,7 @@ fn eval_try_new( ], ) } else { - let y = get_value_from_integer(registry, int_ty, y); + let y = get_value_from_integer(registry, int_ty_id, y); EvalAction::NormalBranch( 1, smallvec![ @@ -78,15 +78,17 @@ fn eval_pop_front( panic!() }; let [x, y]: [BigInt; 2] = get_numeric_args_as_bigints(&[*x, *y]).try_into().unwrap(); - let int_ty = registry.get_type(&info.param_signatures()[1].ty).unwrap(); + let int_ty_id = match registry.get_type(&info.param_signatures()[0].ty).unwrap() { + CoreTypeConcrete::IntRange(info) => &info.ty, + _ => panic!(), + }; if x < y { - let x_plus_1 = get_value_from_integer(registry, int_ty, &x + 1); - let x = get_value_from_integer(registry, int_ty, x); - let y = get_value_from_integer(registry, int_ty, y); - + let x_plus_1 = get_value_from_integer(registry, int_ty_id, &x + 1); + let x = get_value_from_integer(registry, int_ty_id, x); + let y = get_value_from_integer(registry, int_ty_id, y); EvalAction::NormalBranch( - 0, + 1, smallvec![ Value::IntRange { x: Box::new(x_plus_1), @@ -96,6 +98,6 @@ fn eval_pop_front( ], ) } else { - EvalAction::NormalBranch(1, smallvec![]) + EvalAction::NormalBranch(0, smallvec![]) } } diff --git a/debug_utils/sierra-emu/src/vm/uint256.rs b/debug_utils/sierra-emu/src/vm/uint256.rs index b2e37359a..6e25cf465 100644 --- a/debug_utils/sierra-emu/src/vm/uint256.rs +++ b/debug_utils/sierra-emu/src/vm/uint256.rs @@ -48,6 +48,10 @@ fn eval_inv_mod_n( let modulo = u256_to_biguint(mod_lo, mod_hi); match x.modinv(&modulo) { + None => EvalAction::NormalBranch(1, smallvec![range_check, Value::Unit, Value::Unit]), + Some(r) if r == 0u8.into() => { + EvalAction::NormalBranch(1, smallvec![range_check, Value::Unit, Value::Unit]) + } Some(r) => EvalAction::NormalBranch( 0, smallvec![ @@ -63,7 +67,6 @@ fn eval_inv_mod_n( Value::Unit ], ), - None => EvalAction::NormalBranch(1, smallvec![range_check, Value::Unit, Value::Unit]), } } @@ -104,8 +107,10 @@ pub fn u516_to_value(value: BigUint) -> Value { let upper_u256: BigUint = &value >> 256u32; let hi1: u128 = (&upper_u256 >> 128u32).try_into().unwrap(); let lo1: u128 = (upper_u256 & BigUint::from(u128::MAX)).try_into().unwrap(); - let hi: u128 = (&value >> 128u32).try_into().unwrap(); - let lo: u128 = (value & BigUint::from(u128::MAX)).try_into().unwrap(); + let lower_mask = BigUint::from_bytes_le(&[0xFF; 32]); + let lower_u256: BigUint = value & lower_mask; + let hi: u128 = (&lower_u256 >> 128u32).try_into().unwrap(); + let lo: u128 = (lower_u256 & BigUint::from(u128::MAX)).try_into().unwrap(); Value::Struct(vec![ Value::U128(lo), Value::U128(hi), From c5155b5f0b346999f48256f6da53c5962c42bd5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Gonz=C3=A1lez=20Calder=C3=B3n?= Date: Tue, 10 Jun 2025 17:50:30 -0300 Subject: [PATCH 25/28] Fix format --- src/arch.rs | 3 ++- src/types/enum.rs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/arch.rs b/src/arch.rs index 4ca45ebc9..86835fb30 100644 --- a/src/arch.rs +++ b/src/arch.rs @@ -130,7 +130,8 @@ impl AbiArgument for ValueWithInfoWrapper<'_> { abi.capacity.to_bytes(buffer, find_dict_drop_override)?; } (Value::BoundedInt { .. }, CoreTypeConcrete::BoundedInt(_)) => { - native_panic!("todo: implement AbiArgument for Value::BoundedInt case") // See: https://github.com/lambdaclass/cairo_native/issues/1217 + // See: https://github.com/lambdaclass/cairo_native/issues/1217 + native_panic!("todo: implement AbiArgument for Value::BoundedInt case") } (Value::Bytes31(value), CoreTypeConcrete::Bytes31(_)) => { value.to_bytes(buffer, find_dict_drop_override)? diff --git a/src/types/enum.rs b/src/types/enum.rs index 238e562f0..98768fd4b 100644 --- a/src/types/enum.rs +++ b/src/types/enum.rs @@ -760,8 +760,9 @@ pub fn get_layout_for_variants( /// Extract the type and layout for the default enum representation, its discriminant and all its /// payloads. -// TODO: Change this function to accept a slice of slices (for variants). Not all uses have a slice See: https://github.com/lambdaclass/cairo_native/issues/1187 -// with one `ConcreteTypeId` per variant (deploy_syscalls has two types for the Ok() variant). +// TODO: Change this function to accept a slice of slices (for variants). Not all uses have a slice +// with one `ConcreteTypeId` per variant (deploy_syscalls has two types for the Ok() variant). +// See: https://github.com/lambdaclass/cairo_native/issues/1187/ pub fn get_type_for_variants<'ctx>( context: &'ctx Context, module: &Module<'ctx>, From f27a64d569a4ed00fede6c49336d18fa76c4719d Mon Sep 17 00:00:00 2001 From: Julian Gonzalez Calderon Date: Mon, 2 Jun 2025 11:03:45 -0300 Subject: [PATCH 26/28] Gather compilation statistics (#1236) * Add basic Statistics struct and its builder * Add Serialzie to Statistics * Generate basic sierra statistics * Add stats argument to compile * Save sierra to mlir and mlir passes time * Add stats argument to object_to_shared_lib * Save linking time * Add stats argument to module_to_object * Save llvm time * Save total compilation time * Save object size in statistics * Remove StatisticsBuilder * Add libfunc frequency * Remove work * Assert frequency map is not empty * Save mlir operation count * Save llvmir instructions and opcode frequency * Save llvmir virtual register count * Adapt trace_dump.rs * Don't rely on transmute * Move MLIR walking logic to walk_ir module * Move LLVMIR walking to walk_ir module * Improve comments * Pass statistics to compile stats * Save MLIR operations by libfunc * Add clone_option_mut macro * Validate statistics * Use clone_option_mut * Document statistics * Document walk_ir module * Add API to statistics * Add support for saving stats on `starknet-native-compile` * Add documentation * Fix clippy * Update starknet-blocks.yml ref * Rephrase docs Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> * Update script for comparing state dumps --------- Co-authored-by: Gabriel Bosio <38794644+gabrielbosio@users.noreply.github.com> --- .github/workflows/daily.yml | 3 +- .github/workflows/starknet-blocks.yml | 7 +- benches/compile_time.rs | 16 ++-- benches/libfuncs.rs | 8 +- examples/easy_api.rs | 2 +- examples/erc20.rs | 2 +- examples/invoke.rs | 2 +- examples/starknet.rs | 2 +- scripts/cmp_state_dumps.py | 97 ++++++++++++++++++++ scripts/cmp_state_dumps.sh | 51 ----------- src/bin/cairo-native-compile.rs | 7 +- src/bin/cairo-native-dump.rs | 2 +- src/bin/cairo-native-run.rs | 2 +- src/bin/cairo-native-stress/main.rs | 6 +- src/bin/scarb-native-dump.rs | 1 + src/bin/starknet-native-compile.rs | 15 +++ src/bin/utils/test.rs | 2 +- src/cache/aot.rs | 6 +- src/cache/jit.rs | 2 +- src/compiler.rs | 26 +++++- src/context.rs | 45 +++++---- src/executor.rs | 8 +- src/executor/aot.rs | 10 +- src/executor/contract.rs | 55 ++++++++++- src/ffi.rs | 85 +++++++++++++---- src/lib.rs | 1 + src/libfuncs/bounded_int.rs | 8 +- src/libfuncs/enum.rs | 2 +- src/libfuncs/int.rs | 28 +++--- src/statistics.rs | 93 +++++++++++++++++++ src/utils.rs | 3 +- src/utils/trace_dump.rs | 2 +- src/utils/walk_ir.rs | 127 ++++++++++++++++++++++++++ tests/common.rs | 5 +- tests/tests/compile_library.rs | 6 +- tests/tests/trampoline.rs | 2 +- 36 files changed, 577 insertions(+), 162 deletions(-) create mode 100755 scripts/cmp_state_dumps.py delete mode 100755 scripts/cmp_state_dumps.sh create mode 100644 src/statistics.rs create mode 100644 src/utils/walk_ir.rs diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index 976b07e40..0ceea0b10 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -199,8 +199,7 @@ jobs: continue-on-error: true - name: Compare states - run: | - ./scripts/cmp_state_dumps.sh | tee output + run: python ./scripts/cmp_state_dumps.py | tee output - name: Upload Compare Results id: upload_compare_results diff --git a/.github/workflows/starknet-blocks.yml b/.github/workflows/starknet-blocks.yml index 9c3d3e320..5513f8b29 100644 --- a/.github/workflows/starknet-blocks.yml +++ b/.github/workflows/starknet-blocks.yml @@ -32,7 +32,7 @@ jobs: with: repository: lambdaclass/starknet-replay path: starknet-replay - ref: 135ba7cd5b45fe137b11b7f75654dcd472363033 + ref: 1b8e2e0be21a8df9f5f6b8f8514d1a40b456ef58 # We need native to use the linux deps ci action - name: Checkout Native uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: with: repository: lambdaclass/sequencer path: sequencer - ref: b61262980394dab0e0a4c4cab85f12d31d0ce878 + ref: 40331042c1149f5cb84b27f9dd8d47994a010bbe - name: Cache RPC Calls uses: actions/cache@v4.2.0 @@ -126,5 +126,4 @@ jobs: continue-on-error: true - name: Compare states - run: | - ./scripts/cmp_state_dumps.sh + run: python ./scripts/cmp_state_dumps.py diff --git a/benches/compile_time.rs b/benches/compile_time.rs index 5ec301c50..d6caa5a7c 100644 --- a/benches/compile_time.rs +++ b/benches/compile_time.rs @@ -15,7 +15,7 @@ pub fn bench_compile_time(c: &mut Criterion) { b.iter(|| { let native_context = NativeContext::new(); native_context - .compile(program, false, Some(Default::default())) + .compile(program, false, Some(Default::default()), None) .unwrap(); // pass manager internally verifies the MLIR output is correct. }) @@ -32,7 +32,7 @@ pub fn bench_compile_time(c: &mut Criterion) { c.bench_with_input(BenchmarkId::new(filename, 1), &program, |b, program| { b.iter(|| { native_context - .compile(program, false, Some(Default::default())) + .compile(program, false, Some(Default::default()), None) .unwrap(); // pass manager internally verifies the MLIR output is correct. }) @@ -48,9 +48,9 @@ pub fn bench_compile_time(c: &mut Criterion) { b.iter(|| { let native_context = NativeContext::new(); let module = native_context - .compile(black_box(program), false, Some(Default::default())) + .compile(black_box(program), false, Some(Default::default()), None) .unwrap(); - let object = module_to_object(module.module(), OptLevel::None) + let object = module_to_object(module.module(), OptLevel::None, None) .expect("to compile correctly to a object file"); black_box(object) }) @@ -67,9 +67,9 @@ pub fn bench_compile_time(c: &mut Criterion) { c.bench_with_input(BenchmarkId::new(filename, 1), &program, |b, program| { b.iter(|| { let module = native_context - .compile(black_box(program), false, Some(Default::default())) + .compile(black_box(program), false, Some(Default::default()), None) .unwrap(); - let object = module_to_object(module.module(), OptLevel::None) + let object = module_to_object(module.module(), OptLevel::None, None) .expect("to compile correctly to a object file"); black_box(object) }) @@ -86,9 +86,9 @@ pub fn bench_compile_time(c: &mut Criterion) { c.bench_with_input(BenchmarkId::new(filename, 1), &program, |b, program| { b.iter(|| { let module = native_context - .compile(black_box(program), false, Some(Default::default())) + .compile(black_box(program), false, Some(Default::default()), None) .unwrap(); - let object = module_to_object(module.module(), OptLevel::Aggressive) + let object = module_to_object(module.module(), OptLevel::Aggressive, None) .expect("to compile correctly to a object file"); black_box(object) }) diff --git a/benches/libfuncs.rs b/benches/libfuncs.rs index 706920420..537a64a6e 100644 --- a/benches/libfuncs.rs +++ b/benches/libfuncs.rs @@ -55,7 +55,7 @@ pub fn bench_libfuncs(c: &mut Criterion) { let native_context = NativeContext::new(); b.iter(|| { let module = native_context - .compile(program, false, Some(Default::default())) + .compile(program, false, Some(Default::default()), None) .unwrap(); // pass manager internally verifies the MLIR output is correct. let native_executor = @@ -77,7 +77,7 @@ pub fn bench_libfuncs(c: &mut Criterion) { |b, program| { let native_context = NativeContext::new(); let module = native_context - .compile(program, false, Some(Default::default())) + .compile(program, false, Some(Default::default()), None) .unwrap(); // pass manager internally verifies the MLIR output is correct. let native_executor = @@ -108,7 +108,7 @@ pub fn bench_libfuncs(c: &mut Criterion) { let native_context = NativeContext::new(); b.iter(|| { let module = native_context - .compile(program, false, Some(Default::default())) + .compile(program, false, Some(Default::default()), None) .unwrap(); // pass manager internally verifies the MLIR output is correct. let native_executor = @@ -130,7 +130,7 @@ pub fn bench_libfuncs(c: &mut Criterion) { |b, program| { let native_context = NativeContext::new(); let module = native_context - .compile(program, false, Some(Default::default())) + .compile(program, false, Some(Default::default()), None) .unwrap(); // pass manager internally verifies the MLIR output is correct. let native_executor = diff --git a/examples/easy_api.rs b/examples/easy_api.rs index fef4ab8ea..c2e2a7d46 100644 --- a/examples/easy_api.rs +++ b/examples/easy_api.rs @@ -16,7 +16,7 @@ fn main() { // Compile the sierra program into a MLIR module. let native_program = native_context - .compile(&sierra_program, false, Some(Default::default())) + .compile(&sierra_program, false, Some(Default::default()), None) .unwrap(); // The parameters of the entry point. diff --git a/examples/erc20.rs b/examples/erc20.rs index 8e444854e..c1cffd6a1 100644 --- a/examples/erc20.rs +++ b/examples/erc20.rs @@ -322,7 +322,7 @@ fn main() { let native_context = NativeContext::new(); let native_program = native_context - .compile(&sierra_program, false, Some(Default::default())) + .compile(&sierra_program, false, Some(Default::default()), None) .unwrap(); let entry_point_fn = diff --git a/examples/invoke.rs b/examples/invoke.rs index 2ac4a2494..5ea86c925 100644 --- a/examples/invoke.rs +++ b/examples/invoke.rs @@ -21,7 +21,7 @@ fn main() { let native_context = NativeContext::new(); let native_program = native_context - .compile(&sierra_program, false, Some(Default::default())) + .compile(&sierra_program, false, Some(Default::default()), None) .unwrap(); // Call the echo function from the contract using the generated wrapper. diff --git a/examples/starknet.rs b/examples/starknet.rs index ab66c6eb7..bae24586c 100644 --- a/examples/starknet.rs +++ b/examples/starknet.rs @@ -456,7 +456,7 @@ fn main() { let native_context = NativeContext::new(); let native_program = native_context - .compile(&sierra_program, false, Some(Default::default())) + .compile(&sierra_program, false, Some(Default::default()), None) .unwrap(); // Call the echo function from the contract using the generated wrapper. diff --git a/scripts/cmp_state_dumps.py b/scripts/cmp_state_dumps.py new file mode 100755 index 000000000..f28b03569 --- /dev/null +++ b/scripts/cmp_state_dumps.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# +# usage: cmp-state-dumps [-h] [-d] +# Compare all files in the state_dumps directory and outputs a summary +# options: +# -h, --help show this help message and exit +# -d, --delete removes matching files +# +# Uses a pool of worker threads that compare each state dump. +# possible improvements: use a pool of workers for file removing. + +import argparse +import glob +import re +import multiprocessing as mp +import os +from collections import defaultdict + +POOL_SIZE = 16 + +STATE_DUMPS_PATH = "state_dumps" +VM_DIRECTORY = "vm" +NATIVE_DIRECTORY = "native" + +LOG_PATH = "state_dumps/matching.log" + + +def compare(vm_dump_path: str): + native_dump_path = re.sub(VM_DIRECTORY, NATIVE_DIRECTORY, vm_dump_path, count=1) + + if not (m := re.findall(r"/(0x.*).json", vm_dump_path)): + raise Exception("bad path") + tx = m[0] + + if not (m := re.findall(r"block(\d+)", vm_dump_path)): + raise Exception("bad path") + block = m[0] + + try: + with open(native_dump_path) as f: + native_dump = f.read() + with open(vm_dump_path) as f: + vm_dump = f.read() + except: # noqa: E722 + return ("MISS", block, tx) + + native_dump = re.sub(r".*reverted.*", "", native_dump, count=1) + vm_dump = re.sub(r".*reverted.*", "", vm_dump, count=1) + + if native_dump == vm_dump: + return ("MATCH", block, tx, vm_dump_path, native_dump_path) + else: + return ("DIFF", block, tx) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + prog="cmp-state-dumps", + description="Compare all files in the state_dumps directory and outputs a summary", + ) + parser.add_argument( + "-d", "--delete", action="store_true", help="removes matching files" + ) + config = parser.parse_args() + + files = glob.glob(f"{STATE_DUMPS_PATH}/{VM_DIRECTORY}/*/*.json") + files.sort(key=os.path.getmtime) + + print(f"Starting comparison with {POOL_SIZE} workers") + + stats = defaultdict(int) + with mp.Pool(POOL_SIZE) as pool, open(LOG_PATH, mode="a") as log: + for status, *info in pool.imap(compare, files): + stats[status] += 1 + + if status != "MATCH": + (block, tx) = info + print(status, block, tx) + + elif status == "MATCH" and config.delete: + (block, tx, vm_dump_path, native_dump_path) = info + + log.write(f"{block} {tx}\n") + log.flush() + os.remove(native_dump_path) + os.remove(vm_dump_path) + + print("Finished comparison") + + print() + for key, count in stats.items(): + print(key, count) + + if stats["DIFF"] != 0 or stats["MISS"] != 0: + exit(1) + else: + exit(0) diff --git a/scripts/cmp_state_dumps.sh b/scripts/cmp_state_dumps.sh deleted file mode 100755 index 5719a3228..000000000 --- a/scripts/cmp_state_dumps.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash - -# Compares state dump files between two directories: 'state_dumps/vm' and 'state_dumps/native'. -# It iterates over all JSON files in the 'state_dumps/vm' directory and checks if the corresponding -# file exists in 'state_dumps/native'. -# If the corresponding file does not exist, it skips the comparison and counts the missing files. -# For existing pairs, it compares the contents, ignoring the lines containing the "reverted" field, because of error message diference in Native and VM. -# It counts and displays the number of matching, differing, and missing state dumps. - -matching=0 -diffing=0 -missing=0 - -# Iterate over state_dumps/vm dumps -for vm_dump in state_dumps/vm/*/*.json; do - [ -f "$vm_dump" ] || continue - - native_dump="${vm_dump//vm/native}" - - # Check if the corresponding native_dump file exists, if not, skip - if [ ! -f "$native_dump" ]; then - echo "Missing: $native_dump (file not found)" - missing=$((missing+1)) - continue - fi - - tx_name=$(basename "$vm_dump") - tx=${tx_name//.*/} - block_name=$(basename "$(dirname "$vm_dump")") - block=${block_name//block/} - - if ! cmp -s \ - <(sed '/"revert_error": /d' "$native_dump") \ - <(sed '/"revert_error": /d' "$vm_dump") - then - echo "Diff at block $block, tx $tx" - diffing=$((diffing+1)) - else - matching=$((matching+1)) - fi -done - -echo -echo "Finished comparison" -echo "- Matching: $matching" -echo "- Diffing: $diffing" -echo "- Missing: $missing" - -if ! [[ $diffing -eq 0 && $missing -eq 0 ]] ; then - exit 1 -fi diff --git a/src/bin/cairo-native-compile.rs b/src/bin/cairo-native-compile.rs index 1fceffd37..bac328a85 100644 --- a/src/bin/cairo-native-compile.rs +++ b/src/bin/cairo-native-compile.rs @@ -56,7 +56,7 @@ fn main() -> anyhow::Result<()> { // Compile the sierra program into a MLIR module. let native_module = native_context - .compile(&sierra_program, false, Some(Default::default())) + .compile(&sierra_program, false, Some(Default::default()), None) .unwrap(); let output_mlir = args @@ -79,9 +79,10 @@ fn main() -> anyhow::Result<()> { }) }); - let object_data = module_to_object(native_module.module(), args.opt_level.into()) + let object_data = module_to_object(native_module.module(), args.opt_level.into(), None) .context("Failed to convert module to object.")?; - object_to_shared_lib(&object_data, &output_lib).context("Failed to write shared library.")?; + object_to_shared_lib(&object_data, &output_lib, None) + .context("Failed to write shared library.")?; Ok(()) } diff --git a/src/bin/cairo-native-dump.rs b/src/bin/cairo-native-dump.rs index 2361530b3..21f3dc6b8 100644 --- a/src/bin/cairo-native-dump.rs +++ b/src/bin/cairo-native-dump.rs @@ -35,7 +35,7 @@ fn main() -> Result<(), Box> { let program = load_program(Path::new(&args.input), args.starknet)?; // Compile the program. - let module = context.compile(&program, false, Some(Default::default()))?; + let module = context.compile(&program, false, Some(Default::default()), None)?; // Write the output. let output_str = module diff --git a/src/bin/cairo-native-run.rs b/src/bin/cairo-native-run.rs index 2214c9bd6..4440026ff 100644 --- a/src/bin/cairo-native-run.rs +++ b/src/bin/cairo-native-run.rs @@ -92,7 +92,7 @@ fn main() -> anyhow::Result<()> { // Compile the sierra program into a MLIR module. let native_module = native_context - .compile(&sierra_program, false, Some(Default::default())) + .compile(&sierra_program, false, Some(Default::default()), None) .unwrap(); let native_executor: Box _> = match args.run_mode { diff --git a/src/bin/cairo-native-stress/main.rs b/src/bin/cairo-native-stress/main.rs index 2ca7c3cd2..b6757b1a7 100644 --- a/src/bin/cairo-native-stress/main.rs +++ b/src/bin/cairo-native-stress/main.rs @@ -279,7 +279,7 @@ where ) -> Arc { let native_module = self .context - .compile(program, false, Some(Default::default())) + .compile(program, false, Some(Default::default()), None) .expect("failed to compile program"); let registry = ProgramRegistry::new(program).expect("failed to get program registry"); @@ -290,7 +290,7 @@ where .expect("module should have gas metadata"); let shared_library = { - let object_data = module_to_object(native_module.module(), opt_level) + let object_data = module_to_object(native_module.module(), opt_level, None) .expect("failed to convert MLIR to object"); let shared_library_dir = Path::new(AOT_CACHE_DIR); @@ -298,7 +298,7 @@ where let shared_library_name = format!("lib{key}{SHARED_LIBRARY_EXT}"); let shared_library_path = shared_library_dir.join(shared_library_name); - object_to_shared_lib(&object_data, &shared_library_path) + object_to_shared_lib(&object_data, &shared_library_path, None) .expect("failed to link object into shared library"); unsafe { diff --git a/src/bin/scarb-native-dump.rs b/src/bin/scarb-native-dump.rs index ab779d4f4..6e2591a36 100644 --- a/src/bin/scarb-native-dump.rs +++ b/src/bin/scarb-native-dump.rs @@ -41,6 +41,7 @@ fn main() -> anyhow::Result<()> { &compiled.into_v1().unwrap().program, false, Some(Default::default()), + None, ) .unwrap(); diff --git a/src/bin/starknet-native-compile.rs b/src/bin/starknet-native-compile.rs index 8be9f5077..be9155c38 100644 --- a/src/bin/starknet-native-compile.rs +++ b/src/bin/starknet-native-compile.rs @@ -1,4 +1,6 @@ use anyhow::{anyhow, bail, Context}; +use cairo_native::statistics::Statistics; +use std::fs; use std::path::PathBuf; use cairo_lang_sierra::program::Program; @@ -20,6 +22,10 @@ struct Args { opt_level: u8, /// The output file path. output: PathBuf, + + #[arg(long)] + /// Output path for compilation statistics + stats: Option, } fn main() -> anyhow::Result<()> { @@ -27,15 +33,24 @@ fn main() -> anyhow::Result<()> { let (contract_class, sierra_program, sierra_version) = load_sierra_program_from_file(&args.path)?; + let mut stats_with_path = args.stats.map(|path| (Statistics::default(), path)); + let stats = stats_with_path.as_mut().map(|v| &mut v.0); + AotContractExecutor::new_into( &sierra_program, &contract_class.entry_points_by_type, sierra_version, args.output.clone(), args.opt_level.into(), + stats, ) .context("Error compiling Sierra program.")? .with_context(|| format!("Failed to take lock on path {}", args.output.display()))?; + + if let Some((stats, path)) = stats_with_path { + fs::write(path.with_extension("json"), serde_json::to_string(&stats)?)?; + } + Ok(()) } diff --git a/src/bin/utils/test.rs b/src/bin/utils/test.rs index 2b1b3cfc8..aee1bdb3f 100644 --- a/src/bin/utils/test.rs +++ b/src/bin/utils/test.rs @@ -138,7 +138,7 @@ pub fn run_tests( // Compile the sierra program into a MLIR module. let native_module = native_context - .compile(&sierra_program, false, Some(Default::default())) + .compile(&sierra_program, false, Some(Default::default()), None) .unwrap(); let native_executor: Box _> = match args.run_mode { diff --git a/src/cache/aot.rs b/src/cache/aot.rs index f939b131f..d14389583 100644 --- a/src/cache/aot.rs +++ b/src/cache/aot.rs @@ -47,10 +47,10 @@ where mut metadata, } = self .context - .compile(program, false, Some(Default::default()))?; + .compile(program, false, Some(Default::default()), None)?; // Compile module into an object. - let object_data = crate::ffi::module_to_object(&module, opt_level)?; + let object_data = crate::ffi::module_to_object(&module, opt_level, None)?; // Compile object into a shared library. let shared_library_path = tempfile::Builder::new() @@ -58,7 +58,7 @@ where .suffix(SHARED_LIBRARY_EXT) .tempfile()? .into_temp_path(); - crate::ffi::object_to_shared_lib(&object_data, &shared_library_path)?; + crate::ffi::object_to_shared_lib(&object_data, &shared_library_path, None)?; let shared_library = unsafe { Library::new(shared_library_path)? }; let executor = AotNativeExecutor::new( diff --git a/src/cache/jit.rs b/src/cache/jit.rs index 975f692f5..ae64ec338 100644 --- a/src/cache/jit.rs +++ b/src/cache/jit.rs @@ -48,7 +48,7 @@ where ) -> Result>> { let module = self .context - .compile(program, false, Some(Default::default()))?; + .compile(program, false, Some(Default::default()), None)?; let executor = JitNativeExecutor::from_native_module(module, opt_level)?; let executor = Arc::new(executor); diff --git a/src/compiler.rs b/src/compiler.rs index 13b7060d5..2e998d241 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -45,6 +45,7 @@ //! [BFS algorithm]: https://en.wikipedia.org/wiki/Breadth-first_search use crate::{ + clone_option_mut, debug::libfunc_to_name, error::{panic::ToNativeAssertError, Error}, libfuncs::{BranchArg, LibfuncBuilder, LibfuncHelper}, @@ -54,8 +55,9 @@ use crate::{ MetadataStorage, }, native_assert, native_panic, + statistics::Statistics, types::TypeBuilder, - utils::{generate_function_name, BlockExt}, + utils::{generate_function_name, walk_ir::walk_mlir_block, BlockExt}, }; use bumpalo::Bump; use cairo_lang_sierra::{ @@ -98,6 +100,7 @@ use mlir_sys::{ use std::{ cell::Cell, collections::{hash_map::Entry, BTreeMap, HashMap, HashSet}, + ffi::c_void, ops::Deref, }; @@ -121,6 +124,7 @@ type BlockStorage<'c, 'a> = /// /// Additionally, it needs a reference to the MLIR context, the output module and the metadata /// storage. The last one is passed externally so that stuff can be initialized if necessary. +#[allow(clippy::too_many_arguments)] pub fn compile( context: &Context, module: &Module, @@ -129,6 +133,7 @@ pub fn compile( metadata: &mut MetadataStorage, di_compile_unit_id: Attribute, ignore_debug_names: bool, + stats: Option<&mut Statistics>, ) -> Result<(), Error> { if let Ok(x) = std::env::var("NATIVE_DEBUG_DUMP") { if x == "1" || x == "true" { @@ -158,6 +163,7 @@ pub fn compile( di_compile_unit_id, sierra_stmt_start_offset, ignore_debug_names, + clone_option_mut!(stats), )?; } @@ -184,6 +190,7 @@ fn compile_func( di_compile_unit_id: Attribute, sierra_stmt_start_offset: usize, ignore_debug_names: bool, + stats: Option<&mut Statistics>, ) -> Result<(), Error> { let fn_location = Location::new( context, @@ -621,6 +628,23 @@ fn compile_func( &helper, metadata, )?; + + // When statistics are enabled, we iterate from the start + // to the end block of the compiled libfunc, and count all the operations. + if let Some(&mut ref mut stats) = stats { + unsafe extern "C" fn callback( + _: mlir_sys::MlirOperation, + data: *mut c_void, + ) -> mlir_sys::MlirWalkResult { + let data = data.cast::().as_mut().unwrap(); + *data += 1; + 0 + } + let data = walk_mlir_block(*block, *helper.last_block.get(), callback, 0); + let name = libfunc_to_name(libfunc).to_string(); + *stats.mlir_operations_by_libfunc.entry(name).or_insert(0) += data; + } + native_assert!( block.terminator().is_some(), "libfunc {} had no terminator", diff --git a/src/context.rs b/src/context.rs index 87d141c9f..de8802c43 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,10 +1,12 @@ use crate::{ + clone_option_mut, error::{panic::ToNativeAssertError, Error}, ffi::{get_data_layout_rep, get_target_triple}, metadata::{gas::GasMetadata, runtime_bindings::RuntimeBindingsMeta, MetadataStorage}, module::NativeModule, native_assert, - utils::run_pass_manager, + statistics::Statistics, + utils::{run_pass_manager, walk_ir::walk_mlir_operations}, }; use cairo_lang_sierra::{ extensions::core::{CoreLibfunc, CoreType}, @@ -31,8 +33,7 @@ use mlir_sys::{ mlirLLVMDIModuleAttrGet, MlirLLVMDIEmissionKind_MlirLLVMDIEmissionKindFull, MlirLLVMDINameTableKind_MlirLLVMDINameTableKindDefault, }; -use std::{sync::OnceLock, time::Instant}; -use tracing::trace; +use std::{ffi::c_void, sync::OnceLock, time::Instant}; /// Context of IRs, dialects and passes for Cairo programs compilation. #[derive(Debug, Eq, PartialEq)] @@ -69,10 +70,8 @@ impl NativeContext { program: &Program, ignore_debug_names: bool, gas_metadata_config: Option, + stats: Option<&mut Statistics>, ) -> Result { - trace!("starting sierra to mlir compilation"); - let pre_sierra_compilation_instant = Instant::now(); - static INITIALIZED: OnceLock<()> = OnceLock::new(); INITIALIZED.get_or_init(|| unsafe { LLVM_InitializeAllTargets(); @@ -167,6 +166,7 @@ impl NativeContext { // Create the Sierra program registry let registry = ProgramRegistry::::new(program)?; + let pre_sierra_to_mlir_instant = Instant::now(); crate::compile( &self.context, &module, @@ -175,13 +175,12 @@ impl NativeContext { &mut metadata, unsafe { Attribute::from_raw(di_unit_id) }, ignore_debug_names, + clone_option_mut!(stats), )?; - - let sierra_compilation_time = pre_sierra_compilation_instant.elapsed().as_millis(); - trace!( - time = sierra_compilation_time, - "sierra to mlir compilation finished" - ); + let sierra_to_mlir_time = pre_sierra_to_mlir_instant.elapsed().as_millis(); + if let Some(&mut ref mut stats) = stats { + stats.compilation_sierra_to_mlir_time_ms = Some(sierra_to_mlir_time); + } if let Ok(x) = std::env::var("NATIVE_DEBUG_DUMP") { if x == "1" || x == "true" { @@ -201,11 +200,25 @@ impl NativeContext { } } - trace!("starting mlir passes"); - let pre_passes_instant = Instant::now(); + if let Some(&mut ref mut stats) = stats { + unsafe extern "C" fn callback( + _: mlir_sys::MlirOperation, + data: *mut c_void, + ) -> mlir_sys::MlirWalkResult { + let data = data.cast::().as_mut().unwrap(); + *data += 1; + 0 + } + let data = walk_mlir_operations(module.as_operation(), callback, 0); + stats.mlir_operation_count = Some(data) + } + + let pre_mlir_passes_instant = Instant::now(); run_pass_manager(&self.context, &mut module)?; - let passes_time = pre_passes_instant.elapsed().as_millis(); - trace!(time = passes_time, "mlir passes finished"); + let mlir_passes_time = pre_mlir_passes_instant.elapsed().as_millis(); + if let Some(&mut ref mut stats) = stats { + stats.compilation_mlir_passes_time_ms = Some(mlir_passes_time); + } if let Ok(x) = std::env::var("NATIVE_DEBUG_DUMP") { if x == "1" || x == "true" { diff --git a/src/executor.rs b/src/executor.rs index f5db4b3d9..10894629e 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -720,7 +720,7 @@ mod tests { fn test_invoke_dynamic_aot_native_executor(program: Program) { let native_context = NativeContext::new(); let module = native_context - .compile(&program, false, Some(Default::default())) + .compile(&program, false, Some(Default::default()), None) .expect("failed to compile context"); let executor = AotNativeExecutor::from_native_module(module, OptLevel::default()).unwrap(); @@ -738,7 +738,7 @@ mod tests { fn test_invoke_dynamic_jit_native_executor(program: Program) { let native_context = NativeContext::new(); let module = native_context - .compile(&program, false, None) + .compile(&program, false, None, None) .expect("failed to compile context"); let executor = JitNativeExecutor::from_native_module(module, OptLevel::default()).unwrap(); @@ -756,7 +756,7 @@ mod tests { fn test_invoke_contract_dynamic_aot(starknet_program: Program) { let native_context = NativeContext::new(); let module = native_context - .compile(&starknet_program, false, Some(Default::default())) + .compile(&starknet_program, false, Some(Default::default()), None) .expect("failed to compile context"); let executor = AotNativeExecutor::from_native_module(module, OptLevel::default()).unwrap(); @@ -788,7 +788,7 @@ mod tests { fn test_invoke_contract_dynamic_jit(starknet_program: Program) { let native_context = NativeContext::new(); let module = native_context - .compile(&starknet_program, false, Some(Default::default())) + .compile(&starknet_program, false, Some(Default::default()), None) .expect("failed to compile context"); let executor = JitNativeExecutor::from_native_module(module, OptLevel::default()).unwrap(); diff --git a/src/executor/aot.rs b/src/executor/aot.rs index 7de2cb4f8..a792b8c2b 100644 --- a/src/executor/aot.rs +++ b/src/executor/aot.rs @@ -76,8 +76,8 @@ impl AotNativeExecutor { .keep() .map_err(io::Error::from)?; - let object_data = crate::module_to_object(&module, opt_level)?; - crate::object_to_shared_lib(&object_data, &library_path)?; + let object_data = crate::module_to_object(&module, opt_level, None)?; + crate::object_to_shared_lib(&object_data, &library_path, None)?; Ok(Self::new( unsafe { Library::new(&library_path)? }, @@ -257,7 +257,7 @@ mod tests { fn test_invoke_dynamic(program: Program, #[case] optlevel: OptLevel) { let native_context = NativeContext::new(); let module = native_context - .compile(&program, false, Some(Default::default())) + .compile(&program, false, Some(Default::default()), None) .expect("failed to compile context"); let executor = AotNativeExecutor::from_native_module(module, optlevel).unwrap(); @@ -278,7 +278,7 @@ mod tests { fn test_invoke_dynamic_with_syscall_handler(program: Program, #[case] optlevel: OptLevel) { let native_context = NativeContext::new(); let module = native_context - .compile(&program, false, Some(Default::default())) + .compile(&program, false, Some(Default::default()), None) .expect("failed to compile context"); let executor = AotNativeExecutor::from_native_module(module, optlevel).unwrap(); @@ -317,7 +317,7 @@ mod tests { fn test_invoke_contract_dynamic(starknet_program: Program, #[case] optlevel: OptLevel) { let native_context = NativeContext::new(); let module = native_context - .compile(&starknet_program, false, Some(Default::default())) + .compile(&starknet_program, false, Some(Default::default()), None) .expect("failed to compile context"); let executor = AotNativeExecutor::from_native_module(module, optlevel).unwrap(); diff --git a/src/executor/contract.rs b/src/executor/contract.rs index 12c13bde1..b53e06a2e 100644 --- a/src/executor/contract.rs +++ b/src/executor/contract.rs @@ -33,7 +33,9 @@ use crate::{ arch::AbiArgument, + clone_option_mut, context::NativeContext, + debug::libfunc_to_name, error::{panic::ToNativeAssertError, Error, Result}, execution_result::{BuiltinStats, ContractExecutionResult}, executor::{invoke_trampoline, BuiltinCostsGuard}, @@ -41,6 +43,7 @@ use crate::{ module::NativeModule, native_assert, native_panic, starknet::{handler::StarknetSyscallHandlerCallbacks, StarknetSyscallHandler}, + statistics::Statistics, types::TypeBuilder, utils::{ decode_error_message, generate_function_name, get_integer_layout, libc_free, libc_malloc, @@ -57,7 +60,7 @@ use cairo_lang_sierra::{ starknet::StarknetTypeConcrete, }, ids::FunctionId, - program::{GenFunction, Program, StatementIdx}, + program::{GenFunction, GenStatement, Program, StatementIdx}, program_registry::ProgramRegistry, }; use cairo_lang_sierra_to_casm::metadata::MetadataComputationConfig; @@ -80,6 +83,7 @@ use std::{ path::{Path, PathBuf}, ptr::{self, NonNull}, sync::Arc, + time::Instant, }; use tempfile::NamedTempFile; @@ -134,11 +138,15 @@ impl BuiltinType { impl AotContractExecutor { /// Compile and load a program using a temporary shared library. + /// + /// When enabled, compilation stats will be saved to the `stats`. The + /// initial statistics can be build using the default builder. pub fn new( program: &Program, entry_points: &ContractEntryPoints, sierra_version: VersionId, opt_level: OptLevel, + stats: Option<&mut Statistics>, ) -> Result { let output_path = NamedTempFile::new()? .into_temp_path() @@ -151,6 +159,7 @@ impl AotContractExecutor { sierra_version, output_path, opt_level, + stats, )? .to_native_assert_error("temporary contract path collision")?; @@ -165,12 +174,16 @@ impl AotContractExecutor { /// attempt to compile a program while the `output_path` is already locked will result in /// `Ok(None)` being returned. When this happens, the user should wait until the lock is /// released, at which point they can use `AotContractExecutor::from_path` to load it. + /// + /// When enabled, compilation stats will be saved to the `stats`. The + /// initial statistics can be build using the default builder. pub fn new_into( program: &Program, entry_points: &ContractEntryPoints, sierra_version: VersionId, output_path: impl Into, opt_level: OptLevel, + stats: Option<&mut Statistics>, ) -> Result> { let output_path = output_path.into(); let lock_file = match LockFile::new(&output_path)? { @@ -178,6 +191,8 @@ impl AotContractExecutor { None => return Ok(None), }; + let pre_compilation_instant = Instant::now(); + let context = NativeContext::new(); let no_eq_solver = match sierra_version.major.cmp(&1) { @@ -186,6 +201,13 @@ impl AotContractExecutor { Ordering::Greater => true, }; + if let Some(&mut ref mut stats) = stats { + stats.sierra_type_count = Some(program.type_declarations.len()); + stats.sierra_libfunc_count = Some(program.libfunc_declarations.len()); + stats.sierra_statement_count = Some(program.statements.len()); + stats.sierra_func_count = Some(program.funcs.len()); + } + // Compile the Sierra program. let NativeModule { module, registry, .. @@ -210,8 +232,19 @@ impl AotContractExecutor { skip_non_linear_solver_comparisons: false, compute_runtime_costs: false, }), + clone_option_mut!(stats), )?; + if let Some(&mut ref mut stats) = stats { + for statement in &program.statements { + if let GenStatement::Invocation(invocation) = statement { + let libfunc = registry.get_libfunc(&invocation.libfunc_id)?; + let name = libfunc_to_name(libfunc).to_string(); + *stats.sierra_libfunc_frequency.entry(name).or_insert(0) += 1; + } + } + } + // Generate mappings between the entry point's selectors and their function indexes. let entry_point_mappings = chain!( entry_points.constructor.iter(), @@ -236,10 +269,18 @@ impl AotContractExecutor { }) .collect::>>()?; - let object_data = crate::module_to_object(&module, opt_level)?; + let object_data = crate::module_to_object(&module, opt_level, clone_option_mut!(stats))?; + if let Some(&mut ref mut stats) = stats { + stats.object_size_bytes = Some(object_data.len()); + } // Build the shared library into the lockfile, to avoid using a tmp file. - crate::object_to_shared_lib(&object_data, &lock_file.0)?; + crate::object_to_shared_lib(&object_data, &lock_file.0, clone_option_mut!(stats))?; + + let compilation_time = pre_compilation_instant.elapsed().as_millis(); + if let Some(&mut ref mut stats) = stats { + stats.compilation_total_time_ms = Some(compilation_time); + } // Write the contract info. fs::write( @@ -250,6 +291,10 @@ impl AotContractExecutor { })?, )?; + if let Some(&mut ref mut stats) = stats { + native_assert!(stats.validate(), "some statistics are missing"); + } + // Atomically move the built shared library to the correct path. This will avoid data races // when loading contracts. lock_file.rename(&output_path)?; @@ -781,6 +826,7 @@ mod tests { &starknet_program.entry_points_by_type, sierra_version, optlevel, + None, ) .unwrap(), ); @@ -820,6 +866,7 @@ mod tests { &starknet_program.entry_points_by_type, sierra_version, optlevel, + None, ) .unwrap(); @@ -859,6 +906,7 @@ mod tests { &starknet_program_factorial.entry_points_by_type, sierra_version, optlevel, + None, ) .unwrap(); @@ -899,6 +947,7 @@ mod tests { &starknet_program_empty.entry_points_by_type, sierra_version, optlevel, + None, ) .unwrap(); diff --git a/src/ffi.rs b/src/ffi.rs index ead1d033b..040b602e6 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -3,11 +3,16 @@ //! This is a "hotfix" for missing Rust interfaces to the C/C++ libraries we use, namely LLVM/MLIR //! APIs that are missing from melior. -use crate::error::{panic::ToNativeAssertError, Error, Result}; +use crate::{ + error::{panic::ToNativeAssertError, Error, Result}, + statistics::Statistics, + utils::walk_ir::walk_llvm_instructions, +}; use llvm_sys::{ core::{ LLVMContextCreate, LLVMContextDispose, LLVMDisposeMemoryBuffer, LLVMDisposeMessage, - LLVMDisposeModule, LLVMGetBufferSize, LLVMGetBufferStart, + LLVMDisposeModule, LLVMGetBufferSize, LLVMGetBufferStart, LLVMGetFirstUse, + LLVMGetInstructionOpcode, }, error::LLVMGetErrorMessage, prelude::LLVMMemoryBufferRef, @@ -95,7 +100,11 @@ impl From for OptLevel { } /// Converts a MLIR module to a compile object, that can be linked with a linker. -pub fn module_to_object(module: &Module<'_>, opt_level: OptLevel) -> Result> { +pub fn module_to_object( + module: &Module<'_>, + opt_level: OptLevel, + stats: Option<&mut Statistics>, +) -> Result> { static INITIALIZED: OnceLock<()> = OnceLock::new(); INITIALIZED.get_or_init(|| unsafe { @@ -111,11 +120,42 @@ pub fn module_to_object(module: &Module<'_>, opt_level: OptLevel) -> Result, opt_level: OptLevel) -> Result")) .to_native_assert_error("only fails if the hardcoded string contains a null byte")?; - trace!("starting llvm passes"); - let pre_passes_instant = Instant::now(); + let pre_llvm_passes_instant = Instant::now(); let error = LLVMRunPasses(llvm_module, passes.as_ptr(), machine, opts); - let passes_time = pre_passes_instant.elapsed().as_millis(); - trace!(time = passes_time, "llvm passes finished"); + let llvm_passes_time = pre_llvm_passes_instant.elapsed().as_millis(); + if let Some(&mut ref mut stats) = stats { + stats.compilation_llvm_passes_time_ms = Some(llvm_passes_time); + } if !error.is_null() { let msg = LLVMGetErrorMessage(error); @@ -184,7 +225,7 @@ pub fn module_to_object(module: &Module<'_>, opt_level: OptLevel) -> Result = MaybeUninit::uninit(); trace!("starting llvm to object compilation"); - let pre_llvm_compilation_instant = Instant::now(); + let pre_llvm_to_object_instant = Instant::now(); let ok = LLVMTargetMachineEmitToMemoryBuffer( machine, llvm_module, @@ -192,11 +233,10 @@ pub fn module_to_object(module: &Module<'_>, opt_level: OptLevel) -> Result, opt_level: OptLevel) -> Result Result<()> { +pub fn object_to_shared_lib( + object: &[u8], + output_filename: &Path, + stats: Option<&mut Statistics>, +) -> Result<()> { // linker seems to need a file and doesn't accept stdin let mut file = NamedTempFile::new()?; file.write_all(object)?; @@ -289,11 +333,12 @@ pub fn object_to_shared_lib(object: &[u8], output_filename: &Path) -> Result<()> let mut linker = std::process::Command::new("ld"); - trace!("starting linking"); let pre_linking_instant = Instant::now(); let proc = linker.args(args.iter().map(|x| x.as_ref())).output()?; let linking_time = pre_linking_instant.elapsed().as_millis(); - trace!(time = linking_time, "linking finished"); + if let Some(&mut ref mut stats) = stats { + stats.compilation_linking_time_ms = Some(linking_time); + } if proc.status.success() { Ok(()) diff --git a/src/lib.rs b/src/lib.rs index a3f5c9898..b76c13932 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ pub mod module; mod runtime; pub mod starknet; pub mod starknet_stub; +pub mod statistics; mod types; pub mod utils; mod values; diff --git a/src/libfuncs/bounded_int.rs b/src/libfuncs/bounded_int.rs index b5d744dcb..3e048a31f 100644 --- a/src/libfuncs/bounded_int.rs +++ b/src/libfuncs/bounded_int.rs @@ -841,7 +841,7 @@ mod test { } ); let ctx = NativeContext::new(); - let module = ctx.compile(&program, false, None).unwrap(); + let module = ctx.compile(&program, false, None, None).unwrap(); let executor = JitNativeExecutor::from_native_module(module, OptLevel::Default).unwrap(); let ExecutionResult { remaining_gas: _, @@ -874,7 +874,7 @@ mod test { } ); let ctx = NativeContext::new(); - let module = ctx.compile(&program, false, None).unwrap(); + let module = ctx.compile(&program, false, None, None).unwrap(); let executor = JitNativeExecutor::from_native_module(module, OptLevel::Default).unwrap(); let ExecutionResult { remaining_gas: _, @@ -907,7 +907,7 @@ mod test { } ); let ctx = NativeContext::new(); - let module = ctx.compile(&program, false, None).unwrap(); + let module = ctx.compile(&program, false, None, None).unwrap(); let executor = JitNativeExecutor::from_native_module(module, OptLevel::Default).unwrap(); let ExecutionResult { remaining_gas: _, @@ -940,7 +940,7 @@ mod test { } ); let ctx = NativeContext::new(); - let module = ctx.compile(&program, false, None).unwrap(); + let module = ctx.compile(&program, false, None, None).unwrap(); let executor = JitNativeExecutor::from_native_module(module, OptLevel::Default).unwrap(); let ExecutionResult { remaining_gas: _, diff --git a/src/libfuncs/enum.rs b/src/libfuncs/enum.rs index 2a774b835..694f33054 100644 --- a/src/libfuncs/enum.rs +++ b/src/libfuncs/enum.rs @@ -647,7 +647,7 @@ mod test { let native_context = NativeContext::new(); native_context - .compile(&program, false, Some(Default::default())) + .compile(&program, false, Some(Default::default()), None) .unwrap(); } } diff --git a/src/libfuncs/int.rs b/src/libfuncs/int.rs index 0b800eccc..63b532cd4 100644 --- a/src/libfuncs/int.rs +++ b/src/libfuncs/int.rs @@ -947,7 +947,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [T::min_value(), T::zero(), T::one(), T::max_value()]; @@ -993,7 +993,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [0u128, 1u128, u128::MAX]; @@ -1073,7 +1073,7 @@ mod test { }; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; if min.is_zero() { @@ -1161,7 +1161,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [T::min_value(), T::zero(), T::one(), T::max_value()]; @@ -1238,7 +1238,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [T::min_value(), T::zero(), T::one(), T::max_value()]; @@ -1305,7 +1305,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [T::min_value(), T::zero(), T::one(), T::max_value()]; @@ -1374,7 +1374,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [ @@ -1447,7 +1447,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [0u128, 1u128, u128::MAX]; @@ -1528,7 +1528,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [T::min_value(), T::zero(), T::one(), T::max_value()]; @@ -1619,7 +1619,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [T::min_value(), T::zero(), T::one(), T::max_value()]; @@ -1691,7 +1691,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [T::min_value(), T::zero(), T::one(), T::max_value()]; @@ -1750,7 +1750,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [T::min_value(), T::zero(), T::one(), T::max_value()]; @@ -1794,7 +1794,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [ @@ -1864,7 +1864,7 @@ mod test { .map_err(|e| e.to_string())?; let context = NativeContext::new(); - let module = context.compile(&program, false, None)?; + let module = context.compile(&program, false, None, None)?; let executor = JitNativeExecutor::from_native_module(module, OptLevel::default())?; let data = [T::min_value(), T::zero(), T::one(), T::max_value()]; diff --git a/src/statistics.rs b/src/statistics.rs new file mode 100644 index 000000000..132ad85d8 --- /dev/null +++ b/src/statistics.rs @@ -0,0 +1,93 @@ +use std::collections::BTreeMap; + +use serde::Serialize; + +/// A set of compilation statistics gathered during the compilation. +/// It should be completely filled at the end of the compilation. +#[derive(Default, Serialize)] +pub struct Statistics { + /// Number of types defined in the Sierra code. + pub sierra_type_count: Option, + /// Number of libfuncs defined in the Sierra code. + pub sierra_libfunc_count: Option, + /// Number of statements contained in the Sierra code. + pub sierra_statement_count: Option, + /// Number of user functions defined in the Sierra code. + pub sierra_func_count: Option, + /// Number of statements for each distinct libfunc. + pub sierra_libfunc_frequency: BTreeMap, + /// Number of MLIR operations generated. + pub mlir_operation_count: Option, + /// Number of MLIR operations generated for each distinct libfunc. + pub mlir_operations_by_libfunc: BTreeMap, + /// Number of LLVMIR instructions generated. + pub llvmir_instruction_count: Option, + /// Number of LLVMIR virtual registers defined. + pub llvmir_virtual_register_count: Option, + /// Number of LLVMIR instructions for each distinct opcode. + pub llvmir_opcode_frequency: BTreeMap, + /// Total compilation time. + pub compilation_total_time_ms: Option, + /// Time spent at Sierra to MLIR. + pub compilation_sierra_to_mlir_time_ms: Option, + /// Time spent at MLIR passes. + pub compilation_mlir_passes_time_ms: Option, + /// Time spent at MLIR to LLVMIR translation. + pub compilation_mlir_to_llvm_time_ms: Option, + /// Time spent at LLVM passes. + pub compilation_llvm_passes_time_ms: Option, + /// Time spent at LLVM to object compilation. + pub compilation_llvm_to_object_time_ms: Option, + /// Time spent at linking the shared library. + pub compilation_linking_time_ms: Option, + /// Size of the compiled object. + pub object_size_bytes: Option, +} + +impl Statistics { + pub fn validate(&self) -> bool { + self.sierra_type_count.is_some() + && self.sierra_libfunc_count.is_some() + && self.sierra_statement_count.is_some() + && self.sierra_func_count.is_some() + && !self.sierra_libfunc_frequency.is_empty() + && self.mlir_operation_count.is_some() + && !self.mlir_operations_by_libfunc.is_empty() + && self.llvmir_instruction_count.is_some() + && self.llvmir_virtual_register_count.is_some() + && !self.llvmir_opcode_frequency.is_empty() + && self.compilation_total_time_ms.is_some() + && self.compilation_sierra_to_mlir_time_ms.is_some() + && self.compilation_mlir_passes_time_ms.is_some() + && self.compilation_mlir_to_llvm_time_ms.is_some() + && self.compilation_llvm_passes_time_ms.is_some() + && self.compilation_llvm_to_object_time_ms.is_some() + && self.compilation_linking_time_ms.is_some() + && self.object_size_bytes.is_some() + } +} + +/// Clones a variable of type `Option<&mut T>` without consuming self +/// +/// # Example +/// +/// The following example would fail to compile otherwise. +/// +/// ``` +/// # use cairo_native::clone_option_mut; +/// fn consume(v: Option<&mut Vec>) {} +/// +/// let mut vec = Vec::new(); +/// let option = Some(&mut vec); +/// consume(clone_option_mut!(option)); +/// consume(option); +/// ``` +#[macro_export] +macro_rules! clone_option_mut { + ( $var:ident ) => { + match $var { + None => None, + Some(&mut ref mut s) => Some(s), + } + }; +} diff --git a/src/utils.rs b/src/utils.rs index 1dda89185..78ae71733 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -38,6 +38,7 @@ mod range_ext; pub mod safe_runner; pub mod sierra_gen; pub mod trace_dump; +pub mod walk_ir; #[cfg(target_os = "macos")] pub const SHARED_LIBRARY_EXT: &str = "dylib"; @@ -608,7 +609,7 @@ pub mod test { let context = NativeContext::new(); let module = context - .compile(program, false, Some(Default::default())) + .compile(program, false, Some(Default::default()), None) .expect("Could not compile test program to MLIR."); let executor = JitNativeExecutor::from_native_module(module, OptLevel::Less).unwrap(); diff --git a/src/utils/trace_dump.rs b/src/utils/trace_dump.rs index afa489e03..3307d5b2b 100644 --- a/src/utils/trace_dump.rs +++ b/src/utils/trace_dump.rs @@ -184,7 +184,7 @@ mod tests { let native_context = NativeContext::new(); let module = native_context - .compile(&program, false, Some(Default::default())) + .compile(&program, false, Some(Default::default()), None) .expect("failed to compile context"); let executor = AotNativeExecutor::from_native_module(module, OptLevel::default()).unwrap(); diff --git a/src/utils/walk_ir.rs b/src/utils/walk_ir.rs new file mode 100644 index 000000000..b1563341c --- /dev/null +++ b/src/utils/walk_ir.rs @@ -0,0 +1,127 @@ +use std::ffi::c_void; + +use llvm_sys::{ + core::{ + LLVMGetFirstBasicBlock, LLVMGetFirstFunction, LLVMGetFirstInstruction, + LLVMGetNextBasicBlock, LLVMGetNextFunction, LLVMGetNextInstruction, + }, + prelude::{LLVMModuleRef, LLVMValueRef}, + LLVMBasicBlock, LLVMValue, +}; +use melior::ir::{BlockLike, BlockRef, OperationRef}; +use mlir_sys::{MlirOperation, MlirWalkResult}; + +type OperationWalkCallback = + unsafe extern "C" fn(MlirOperation, *mut ::std::os::raw::c_void) -> MlirWalkResult; + +/// Traverses the given operation tree in preorder. +/// +/// Calls `f` on each operation encountered. The second argument to `f` should +/// be interpreted as a pointer to a value of type `T`. +/// +/// TODO: Can we receive a closure instead? +/// We may need to save a pointer to the closure +/// inside of the callback data. +pub fn walk_mlir_operations( + top_op: OperationRef, + f: OperationWalkCallback, + initial: T, +) -> T { + let mut data = Box::new(initial); + unsafe { + mlir_sys::mlirOperationWalk( + top_op.to_raw(), + Some(f), + data.as_mut() as *mut _ as *mut c_void, + mlir_sys::MlirWalkOrder_MlirWalkPreOrder, + ); + }; + *data +} + +/// Traverses from start block to end block (including) in preorder. +/// +/// Calls `f` on each operation encountered. The second argument to `f` should +/// be interpreted as a pointer to a value of type `T`. +/// +/// TODO: Can we receive a closure instead? +/// We may need to save a pointer to the closure +/// inside of the callback data. +pub fn walk_mlir_block( + start_block: BlockRef, + end_block: BlockRef, + f: OperationWalkCallback, + initial: T, +) -> T { + let mut data = Box::new(initial); + + let mut current_block = start_block; + loop { + let mut next_operation = current_block.first_operation(); + + while let Some(operation) = next_operation { + unsafe { + mlir_sys::mlirOperationWalk( + operation.to_raw(), + Some(f), + data.as_mut() as *mut _ as *mut c_void, + mlir_sys::MlirWalkOrder_MlirWalkPreOrder, + ); + }; + + // we have to convert it to raw, and back to ref to bypass borrow checker. + next_operation = unsafe { + operation + .next_in_block() + .map(OperationRef::to_raw) + .map(|op| OperationRef::from_raw(op)) + } + } + + if current_block == end_block { + break; + } + + current_block = current_block + .next_in_region() + .expect("should always reach `end_block`"); + } + + *data +} + +/// Traverses the whole LLVM Module, calling `f` on each instruction. +/// +/// As this function receives a closure rather than a function, there is no need +/// to receive initial data, and can instead modify the captured environment. +pub unsafe fn walk_llvm_instructions(llvm_module: LLVMModuleRef, mut f: impl FnMut(LLVMValueRef)) { + let new_value = |function_ptr: *mut LLVMValue| { + if function_ptr.is_null() { + None + } else { + Some(function_ptr) + } + }; + let new_block = |function_ptr: *mut LLVMBasicBlock| { + if function_ptr.is_null() { + None + } else { + Some(function_ptr) + } + }; + + let mut current_function = new_value(LLVMGetFirstFunction(llvm_module)); + while let Some(function) = current_function { + let mut current_block = new_block(LLVMGetFirstBasicBlock(function)); + while let Some(block) = current_block { + let mut current_instruction = new_value(LLVMGetFirstInstruction(block)); + while let Some(instruction) = current_instruction { + f(instruction); + + current_instruction = new_value(LLVMGetNextInstruction(instruction)); + } + current_block = new_block(LLVMGetNextBasicBlock(block)); + } + current_function = new_value(LLVMGetNextFunction(function)); + } +} diff --git a/tests/common.rs b/tests/common.rs index 320f1ec21..d32063bf3 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -233,7 +233,7 @@ pub fn run_native_program( let context = NativeContext::new(); let module = context - .compile(program, false, Some(Default::default())) + .compile(program, false, Some(Default::default()), None) .expect("Could not compile test program to MLIR."); assert!( @@ -430,7 +430,7 @@ pub fn run_native_starknet_contract( let native_context = NativeContext::new(); let native_program = native_context - .compile(sierra_program, false, Some(Default::default())) + .compile(sierra_program, false, Some(Default::default()), None) .unwrap(); let entry_point_fn = find_entry_point_by_idx(sierra_program, entry_point_function_idx).unwrap(); @@ -456,6 +456,7 @@ pub fn run_native_starknet_aot_contract( &contract.entry_points_by_type, sierra_version, Default::default(), + None, ) .unwrap(); native_executor diff --git a/tests/tests/compile_library.rs b/tests/tests/compile_library.rs index cbd5a014c..eabd3a9c2 100644 --- a/tests/tests/compile_library.rs +++ b/tests/tests/compile_library.rs @@ -14,12 +14,12 @@ pub fn compile_library() -> Result<(), Box> { } }; - let module = context.compile(&program.1, false, Some(Default::default()))?; + let module = context.compile(&program.1, false, Some(Default::default()), None)?; - let object = cairo_native::module_to_object(module.module(), Default::default())?; + let object = cairo_native::module_to_object(module.module(), Default::default(), None)?; let file = NamedTempFile::new()?.into_temp_path(); - cairo_native::object_to_shared_lib(&object, &file)?; + cairo_native::object_to_shared_lib(&object, &file, None)?; Ok(()) } diff --git a/tests/tests/trampoline.rs b/tests/tests/trampoline.rs index 846172c08..d7f3a6b47 100644 --- a/tests/tests/trampoline.rs +++ b/tests/tests/trampoline.rs @@ -14,7 +14,7 @@ fn run_program(program: &Program, entry_point: &str, args: &[Value]) -> Executio let context = NativeContext::new(); let module = context - .compile(program, false, Some(Default::default())) + .compile(program, false, Some(Default::default()), None) .unwrap(); // FIXME: There are some bugs with non-zero LLVM optimization levels. let executor = JitNativeExecutor::from_native_module(module, OptLevel::None).unwrap(); From 07fd5c87d017a47d482ea449cdc22d243e4b36e2 Mon Sep 17 00:00:00 2001 From: Julian Gonzalez Calderon Date: Thu, 5 Jun 2025 18:35:52 -0300 Subject: [PATCH 27/28] Don't fail on empty programs (#1248) --- src/statistics.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/statistics.rs b/src/statistics.rs index 132ad85d8..18dadb910 100644 --- a/src/statistics.rs +++ b/src/statistics.rs @@ -50,12 +50,9 @@ impl Statistics { && self.sierra_libfunc_count.is_some() && self.sierra_statement_count.is_some() && self.sierra_func_count.is_some() - && !self.sierra_libfunc_frequency.is_empty() && self.mlir_operation_count.is_some() - && !self.mlir_operations_by_libfunc.is_empty() && self.llvmir_instruction_count.is_some() && self.llvmir_virtual_register_count.is_some() - && !self.llvmir_opcode_frequency.is_empty() && self.compilation_total_time_ms.is_some() && self.compilation_sierra_to_mlir_time_ms.is_some() && self.compilation_mlir_passes_time_ms.is_some() From cbf93e768b573700439b65ddf148da4f78de07a9 Mon Sep 17 00:00:00 2001 From: Julian Gonzalez Calderon Date: Tue, 10 Jun 2025 15:17:43 -0300 Subject: [PATCH 28/28] Update version (#1249) --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a0220f8b..2b81047cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1020,7 +1020,7 @@ dependencies = [ [[package]] name = "cairo-native" -version = "0.5.0-rc.5" +version = "0.5.0-rc.6" dependencies = [ "anyhow", "aquamarine", diff --git a/Cargo.toml b/Cargo.toml index 6c22f06bf..61435a933 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cairo-native" -version = "0.5.0-rc.5" +version = "0.5.0-rc.6" edition = "2021" license = "Apache-2.0" description = "A compiler to convert Cairo's IR Sierra code to MLIR and execute it." diff --git a/README.md b/README.md index dff68b952..6f50d3e79 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ often so use it at your own risk. 🚧 For versions under `1.0` `cargo` doesn't comply with [semver](https://semver.org/), so we advise to pin the version you -use. This can be done by adding `cairo-native = "0.5.0-rc.5"` to your Cargo.toml +use. This can be done by adding `cairo-native = "0.5.0-rc.6"` to your Cargo.toml ## Getting Started