diff --git a/Cargo.lock b/Cargo.lock index ba5969b57..0ac08e640 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -276,6 +276,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64ct" version = "1.6.0" @@ -462,7 +468,7 @@ dependencies = [ "rust-analyzer-salsa", "semver", "smol_str", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -546,7 +552,7 @@ dependencies = [ "itertools 0.12.1", "rust-analyzer-salsa", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -634,7 +640,7 @@ dependencies = [ "cairo-lang-utils", "serde", "smol_str", - "thiserror", + "thiserror 1.0.69", "toml", ] @@ -666,7 +672,7 @@ dependencies = [ "sha2", "smol_str", "starknet-types-core", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -719,7 +725,7 @@ dependencies = [ "sha3", "smol_str", "starknet-types-core", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -735,7 +741,7 @@ dependencies = [ "itertools 0.12.1", "num-bigint", "num-traits", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -751,7 +757,7 @@ dependencies = [ "itertools 0.12.1", "num-bigint", "num-traits", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -796,7 +802,7 @@ dependencies = [ "num-bigint", "num-traits", "starknet-types-core", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -836,7 +842,7 @@ dependencies = [ "serde_json", "smol_str", "starknet-types-core", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -859,7 +865,7 @@ dependencies = [ "sha3", "smol_str", "starknet-types-core", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -999,7 +1005,7 @@ dependencies = [ "stats_alloc", "tempfile", "test-case", - "thiserror", + "thiserror 1.0.69", "tracing", "tracing-subscriber", "utf8_iter", @@ -1010,11 +1016,15 @@ dependencies = [ name = "cairo-native-runtime" version = "0.2.3" dependencies = [ + "cairo-lang-sierra", "cairo-lang-sierra-gas", + "cairo-lang-utils", "itertools 0.13.0", "lazy_static", + "num-bigint", "num-traits", "rand", + "sierra-emu", "starknet-curve 0.5.1", "starknet-types-core", ] @@ -1044,7 +1054,7 @@ dependencies = [ "serde_json", "sha2", "sha3", - "starknet-crypto", + "starknet-crypto 0.6.2", "starknet-types-core", "thiserror-no-std", "zip", @@ -1080,7 +1090,7 @@ dependencies = [ "serde_json", "sha2", "sha3", - "starknet-crypto", + "starknet-crypto 0.6.2", "starknet-types-core", "thiserror-no-std", "wasm-bindgen", @@ -1278,6 +1288,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const_format" version = "0.2.33" @@ -1405,6 +1421,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", + "rand_core", "subtle", "zeroize", ] @@ -1480,6 +1497,17 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.11" @@ -1559,6 +1587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -1590,6 +1619,20 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + [[package]] name = "educe" version = "0.5.11" @@ -1608,6 +1651,26 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "ena" version = "0.14.3" @@ -1671,6 +1734,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1830,6 +1903,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1874,6 +1948,17 @@ dependencies = [ "microlp", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "half" version = "2.4.1" @@ -2163,6 +2248,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + [[package]] name = "keccak" version = "0.1.5" @@ -2553,6 +2652,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parity-scale-codec" version = "3.7.0" @@ -2638,6 +2749,15 @@ dependencies = [ "sha2", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "petgraph" version = "0.6.5" @@ -2675,6 +2795,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.31" @@ -2775,6 +2905,15 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -2940,7 +3079,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3149,7 +3288,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3200,6 +3339,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "1.0.23" @@ -3308,6 +3461,48 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "sierra-emu" +version = "0.1.0" +source = "git+https://github.com/lambdaclass/sierra-emu#73706c141b77073afc0403167ef11645c96fd183" +dependencies = [ + "cairo-lang-compiler", + "cairo-lang-filesystem", + "cairo-lang-sierra", + "cairo-lang-sierra-ap-change", + "cairo-lang-sierra-gas", + "cairo-lang-utils", + "clap", + "k256", + "keccak", + "num-bigint", + "num-traits", + "p256", + "rand", + "sec1", + "serde", + "serde_json", + "sha2", + "smallvec", + "starknet-crypto 0.7.3", + "starknet-curve 0.5.1", + "starknet-types-core", + "tempfile", + "thiserror 2.0.3", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -3354,6 +3549,16 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sprs" version = "0.11.2" @@ -3392,6 +3597,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "starknet-crypto" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded22ccf4cb9e572ce3f77de6066af53560cd2520d508876c83bb1e6b29d5cbc" +dependencies = [ + "crypto-bigint", + "hex", + "hmac", + "num-bigint", + "num-integer", + "num-traits", + "rfc6979", + "sha2", + "starknet-curve 0.5.1", + "starknet-types-core", + "zeroize", +] + [[package]] name = "starknet-crypto-codegen" version = "0.3.3" @@ -3516,7 +3740,7 @@ dependencies = [ "bindgen 0.69.5", "cc", "paste", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3582,7 +3806,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", ] [[package]] @@ -3596,6 +3829,17 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "thiserror-impl-no-std" version = "2.0.2" @@ -3821,7 +4065,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" dependencies = [ - "thiserror", + "thiserror 1.0.69", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 01020d53e..3dfba80cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ with-cheatcode = [] with-debug-utils = [] with-mem-tracing = [] with-runtime = ["dep:cairo-native-runtime"] +with-trace-dump = ["cairo-native-runtime/with-trace-dump"] # the aquamarine dep is only used in docs and cannot be detected as used by cargo udeps [package.metadata.cargo-udeps.ignore] diff --git a/Makefile b/Makefile index b028dbcd2..812b9abe0 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ build-dev: check-llvm .PHONY: check check: check-llvm cargo fmt --all -- --check - cargo clippy --all-targets --all-features -- -D warnings + cargo clippy --all-targets --features build-cli,with-cheatcode,with-runtime -- -D warnings .PHONY: test test: check-llvm needs-cairo2 build-alexandria runtime-ci @@ -88,11 +88,11 @@ coverage: check-llvm needs-cairo2 build-alexandria runtime-ci .PHONY: doc doc: check-llvm - cargo doc --all-features --no-deps --workspace + cargo doc --features build-cli,with-cheatcode,with-runtime --no-deps --workspace .PHONY: doc-open doc-open: check-llvm - cargo doc --all-features --no-deps --workspace --open + cargo doc --features build-cli,with-cheatcode,with-runtime --no-deps --workspace --open .PHONY: bench bench: needs-cairo2 runtime @@ -179,8 +179,11 @@ build-alexandria: .PHONY: runtime runtime: - cargo b --release --all-features -p cairo-native-runtime && cp target/release/libcairo_native_runtime.a . + cargo b --release -p cairo-native-runtime && cp target/release/libcairo_native_runtime.a . + +runtime-with-trace-dump: + cargo b -p cairo-native-runtime --features=with-trace-dump && cp target/debug/libcairo_native_runtime.a . .PHONY: runtime-ci runtime-ci: - cargo b --profile ci --all-features -p cairo-native-runtime && cp target/ci/libcairo_native_runtime.a . + cargo b --profile ci -p cairo-native-runtime && cp target/ci/libcairo_native_runtime.a . diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index ac403ac91..eb6c6f594 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -9,6 +9,14 @@ keywords = ["starknet", "cairo", "runtime"] [lib] crate-type = ["rlib", "cdylib", "staticlib"] +[features] +with-trace-dump = [ + "dep:cairo-lang-sierra", + "dep:cairo-lang-utils", + "dep:sierra-emu", + "dep:num-bigint", +] + [dependencies] starknet-types-core = { version = "0.1.7", default-features = false, features = [ "std", @@ -16,8 +24,13 @@ starknet-types-core = { version = "0.1.7", default-features = false, features = "hash", ] } cairo-lang-sierra-gas = "2.9.0-dev.0" -starknet-curve = "0.5.1" +itertools = "0.13.0" lazy_static = "1.5.0" rand = "0.8.5" -itertools = "0.13.0" -num-traits = "0.2" +starknet-curve = "0.5.1" + +num-bigint = { version = "0.4.4", optional = true } +num-traits = { version = "0.2" } +cairo-lang-sierra = { version = "2.9.0-dev.0", optional = true } +cairo-lang-utils = { version = "2.9.0-dev.0", optional = true } +sierra-emu = { git = "https://github.com/lambdaclass/sierra-emu", optional = true } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 4d55f2282..85768d3d6 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -680,6 +680,468 @@ pub fn as_cairo_short_string_ex(value: &Felt, length: usize) -> Option { Some(as_string) } +#[cfg(feature = "with-trace-dump")] +pub mod trace_dump { + use cairo_lang_sierra::{ + extensions::{ + bounded_int::BoundedIntConcreteType, + core::{CoreLibfunc, CoreType, CoreTypeConcrete}, + starknet::{secp256::Secp256PointTypeConcrete, StarkNetTypeConcrete}, + }, + ids::{ConcreteTypeId, VarId}, + program::StatementIdx, + program_registry::ProgramRegistry, + }; + use cairo_lang_utils::ordered_hash_map::OrderedHashMap; + use itertools::Itertools; + use num_bigint::BigInt; + 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::FeltDict; + + pub static TRACE_DUMP: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); + + pub struct TraceDump { + pub trace: ProgramTrace, + state: OrderedHashMap, + registry: ProgramRegistry, + + get_layout: fn(&CoreTypeConcrete, &ProgramRegistry) -> Layout, + } + + impl TraceDump { + pub fn new( + registry: ProgramRegistry, + get_layout: fn(&CoreTypeConcrete, &ProgramRegistry) -> Layout, + ) -> Self { + Self { + trace: ProgramTrace::default(), + state: OrderedHashMap::default(), + registry, + + get_layout, + } + } + } + + #[no_mangle] + pub extern "C" fn get_trace_dump_ptr() -> *const Mutex> { + &*TRACE_DUMP as *const _ + } + + #[no_mangle] + pub unsafe extern "C" fn cairo_native__trace_dump__state( + trace_id: u64, + var_id: u64, + type_id: u64, + value_ptr: NonNull<()>, + ) { + let mut trace_dump = TRACE_DUMP.lock().unwrap(); + let trace_dump = trace_dump.get_mut(&trace_id).unwrap(); + + let type_id = ConcreteTypeId::new(type_id); + let value = read_value_ptr( + &trace_dump.registry, + &type_id, + value_ptr, + trace_dump.get_layout, + ); + + trace_dump.state.insert(VarId::new(var_id), value); + } + + #[no_mangle] + pub unsafe extern "C" fn cairo_native__trace_dump__push(trace_id: u64, statement_idx: u64) { + let mut trace_dump = TRACE_DUMP.lock().unwrap(); + let trace_dump = trace_dump.get_mut(&trace_id).unwrap(); + + let mut items = OrderedHashMap::default(); + swap(&mut items, &mut trace_dump.state); + + trace_dump + .trace + .push(StateDump::new(StatementIdx(statement_idx as usize), items)); + } + + unsafe fn read_value_ptr( + registry: &ProgramRegistry, + type_id: &ConcreteTypeId, + value_ptr: NonNull<()>, + get_layout: fn(&CoreTypeConcrete, &ProgramRegistry) -> Layout, + ) -> 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) => read_value_ptr( + registry, + &info.ty, + value_ptr.cast::>().read(), + get_layout, + ), + CoreTypeConcrete::Array(info) => { + let layout = Layout::new::<()>(); + let (array_ptr, layout) = { + let (layout, offset) = layout.extend(Layout::new::<*mut ()>()).unwrap(); + (value_ptr.byte_add(offset).cast::<*mut ()>().read(), layout) + }; + let (array_begin, layout) = { + let (layout, offset) = layout.extend(Layout::new::()).unwrap(); + (value_ptr.byte_add(offset).cast::().read(), layout) + }; + let (array_end, _) = { + let (layout, offset) = layout.extend(Layout::new::()).unwrap(); + (value_ptr.byte_add(offset).cast::().read(), layout) + }; + + let layout = + get_layout(registry.get_type(&info.ty).unwrap(), registry).pad_to_align(); + + let mut data = Vec::with_capacity((array_end - array_begin) as usize); + for index in array_begin..array_end { + let index = index as usize; + + data.push(read_value_ptr( + registry, + &info.ty, + NonNull::new(array_ptr.byte_add(layout.size() * index)).unwrap(), + get_layout, + )); + } + + 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 = get_layout(type_info, registry); + + let offset; + (layout, offset) = layout.extend(member_layout).unwrap(); + + let current_ptr = value_ptr.byte_add(offset); + members.push(read_value_ptr(registry, member_ty, current_ptr, get_layout)); + } + + 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(get_layout( + registry.get_type(&info.variants[tag_value]).unwrap(), + registry, + )) + .unwrap(); + + read_value_ptr( + registry, + &info.variants[tag_value], + value_ptr.byte_add(offset), + get_layout, + ) + }; + + Value::Enum { + self_ty: type_id.clone(), + index: tag_value, + payload: Box::new(payload), + } + } + + CoreTypeConcrete::NonZero(info) | CoreTypeConcrete::Snapshot(info) => { + read_value_ptr(registry, &info.ty, value_ptr, get_layout) + } + + // Builtins and other unit types: + CoreTypeConcrete::Bitwise(_) + | CoreTypeConcrete::BuiltinCosts(_) + | CoreTypeConcrete::EcOp(_) + | CoreTypeConcrete::Pedersen(_) + | CoreTypeConcrete::Poseidon(_) + | CoreTypeConcrete::RangeCheck96(_) + | CoreTypeConcrete::RangeCheck(_) + | CoreTypeConcrete::SegmentArena(_) + | CoreTypeConcrete::StarkNet(StarkNetTypeConcrete::System(_)) + | CoreTypeConcrete::Uint128MulGuarantee(_) => Value::Unit, + + // TODO: + CoreTypeConcrete::Coupon(_) => todo!("CoreTypeConcrete::Coupon"), + CoreTypeConcrete::Circuit(_) => todo!("CoreTypeConcrete::Circuit"), + 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) => read_value_ptr(registry, &info.ty, inner_ptr, get_layout), + None => Value::Uninitialized { + ty: info.ty.clone(), + }, + } + } + + CoreTypeConcrete::SquashedFelt252Dict(info) | CoreTypeConcrete::Felt252Dict(info) => { + let value = value_ptr.cast::<&FeltDict>().read(); + + let data = value + .inner + .iter() + .map(|(k, &p)| { + let v = match NonNull::new(p) { + Some(value_ptr) => { + read_value_ptr(registry, &info.ty, value_ptr.cast(), get_layout) + } + None => Value::Uninitialized { + ty: info.ty.clone(), + }, + }; + let k = Felt::from_bytes_le(k); + (k, v) + }) + .collect::>(); + + Value::FeltDict { + ty: info.ty.clone(), + data, + } + } + CoreTypeConcrete::Felt252DictEntry(info) => { + let value = value_ptr.cast::().read(); + + let data = value + .dict + .inner + .iter() + .map(|(k, &p)| { + let v = match NonNull::new(p) { + Some(value_ptr) => { + read_value_ptr(registry, &info.ty, value_ptr.cast(), get_layout) + } + 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, + 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)) + } + } + } + + #[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, + } +} + #[cfg(test)] mod tests { use std::{ diff --git a/src/bin/cairo-native-run.rs b/src/bin/cairo-native-run.rs index 03f0f48fb..c9b456346 100644 --- a/src/bin/cairo-native-run.rs +++ b/src/bin/cairo-native-run.rs @@ -16,7 +16,7 @@ use utils::{find_function, result_to_runresult}; mod utils; -#[derive(Clone, Debug, ValueEnum)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)] enum RunMode { Aot, Jit, @@ -44,6 +44,10 @@ 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)] + trace_output: Option, } fn main() -> anyhow::Result<()> { @@ -101,6 +105,21 @@ fn main() -> anyhow::Result<()> { } }; + #[cfg(feature = "with-trace-dump")] + { + use cairo_lang_sierra::program_registry::ProgramRegistry; + use cairo_native::types::TypeBuilder; + use cairo_native_runtime::trace_dump::{TraceDump, TRACE_DUMP}; + + TRACE_DUMP.lock().unwrap().insert( + 0, + TraceDump::new( + ProgramRegistry::new(&sierra_program).unwrap(), + |ty, registry| ty.layout(registry).unwrap(), + ), + ); + } + let gas_metadata = GasMetadata::new(&sierra_program, Some(MetadataComputationConfig::default())).unwrap(); @@ -136,5 +155,24 @@ fn main() -> anyhow::Result<()> { println!("Remaining gas: {gas}"); } + #[cfg(feature = "with-trace-dump")] + if let Some(trace_output) = args.trace_output { + assert_eq!( + args.run_mode, + RunMode::Jit, + "AOT trace dump for programs is not yet supported" + ); + + let traces = cairo_native_runtime::trace_dump::TRACE_DUMP.lock().unwrap(); + assert_eq!(traces.len(), 1); + + let trace_dump = traces.values().next().unwrap(); + serde_json::to_writer( + std::fs::File::create(trace_output).unwrap(), + &trace_dump.trace, + ) + .unwrap(); + } + Ok(()) } diff --git a/src/compiler.rs b/src/compiler.rs index d7c35f235..ccdf21152 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -100,6 +100,7 @@ use std::{ collections::{hash_map::Entry, BTreeMap, HashMap, HashSet}, ops::Deref, }; +use tracing::debug; /// The [BlockStorage] type is used to map each statement into its own entry block (on the right), /// and its landing block (on the left) if required. @@ -385,40 +386,45 @@ fn compile_func( let pre_entry_block = region.insert_block_before(entry_block, Block::new(&pre_entry_block_args)); - let initial_state = edit_state::put_results(OrderedHashMap::<_, Value>::default(), { - let mut values = Vec::new(); + let initial_state = + edit_state::put_results(OrderedHashMap::<_, (&ConcreteTypeId, Value)>::default(), { + let mut values = Vec::new(); - let mut count = 0; - for param in &function.params { - let type_info = registry.get_type(¶m.ty)?; - let location = Location::new( - context, - "program.sierra", - sierra_stmt_start_offset + function.entry_point.0, - 0, - ); + let mut count = 0; + for param in &function.params { + let type_info = registry.get_type(¶m.ty)?; + let location = Location::new( + context, + "program.sierra", + sierra_stmt_start_offset + function.entry_point.0, + 0, + ); - values.push(( - ¶m.id, - if type_info.is_builtin() && type_info.is_zst(registry)? { - pre_entry_block - .append_operation(llvm::undef( - type_info.build(context, module, registry, metadata, ¶m.ty)?, - location, - )) - .result(0)? - .into() - } else { - let value = entry_block.argument(count)?.into(); - count += 1; + values.push(( + ¶m.id, + ( + ¶m.ty, + if type_info.is_builtin() && type_info.is_zst(registry)? { + pre_entry_block + .append_operation(llvm::undef( + type_info + .build(context, module, registry, metadata, ¶m.ty)?, + location, + )) + .result(0)? + .into() + } else { + let value = entry_block.argument(count)?.into(); + count += 1; - value - }, - )); - } + value + }, + ), + )); + } - values.into_iter() - })?; + values.into_iter() + })?; tracing::trace!("Implementing the entry block."); entry_block.append_operation(cf::br( @@ -428,7 +434,7 @@ fn compile_func( Statement::Return(x) => x, } .iter() - .map(|x| initial_state[x]) + .map(|x| initial_state[x].1) .collect::>(), { Location::new( @@ -460,10 +466,12 @@ fn compile_func( state = edit_state::put_results( OrderedHashMap::default(), state - .keys() - .sorted_by_key(|x| x.id) + .iter() + .sorted_by_key(|(x, _)| x.id) .enumerate() - .map(|(idx, var_id)| Ok((var_id, landing_block.argument(idx)?.into()))) + .map(|(idx, (var_id, (ty, _)))| { + Ok((var_id, (*ty, landing_block.argument(idx)?.into()))) + }) .collect::, Error>>()? .into_iter(), )?; @@ -478,7 +486,10 @@ fn compile_func( } .iter(), )? - .1, + .1 + .iter() + .map(|x| x.1) + .collect::>(), Location::name( context, &format!("landing_block(stmt_idx={})", statement_idx), @@ -522,6 +533,21 @@ fn compile_func( format!("{}(stmt_idx={})", libfunc_to_name(libf), statement_idx) }; + #[cfg(feature = "with-trace-dump")] + self::trace_dump::build_state_snapshot( + metadata.get_or_insert_with( + crate::metadata::trace_dump::TraceDumpMeta::default, + ), + context, + registry, + module, + &pre_entry_block, + block, + Location::unknown(context), + statement_idx, + &state, + ); + let (state, _) = edit_state::take_args(state, invocation.args.iter())?; let helper = LibfuncHelper { @@ -614,8 +640,9 @@ fn compile_func( invocation .branches .iter() + .zip(libfunc.branch_signatures()) .zip(helper.results()?) - .map(|(branch_info, result_values)| { + .map(|((branch_info, signature), result_values)| { assert_eq!( branch_info.results.len(), result_values.len(), @@ -624,7 +651,13 @@ fn compile_func( Ok(edit_state::put_results( state.clone(), - branch_info.results.iter().zip(result_values.into_iter()), + branch_info.results.iter().zip( + signature + .vars + .iter() + .map(|x| &x.ty) + .zip(result_values.iter().copied()), + ), )?) }) .collect::>()?, @@ -644,6 +677,23 @@ fn compile_func( ), ); + #[cfg(feature = "with-trace-dump")] + if !is_recursive || tailrec_state.is_some() { + self::trace_dump::build_state_snapshot( + metadata.get_or_insert_with( + crate::metadata::trace_dump::TraceDumpMeta::default, + ), + context, + registry, + module, + &pre_entry_block, + block, + Location::unknown(context), + statement_idx, + &state, + ); + } + let (_, mut values) = edit_state::take_args(state, var_ids.iter())?; let mut block = *block; @@ -714,7 +764,7 @@ fn compile_func( .ret_types .iter() .zip(&values) - .filter_map(|(type_id, value)| { + .filter_map(|(type_id, (_, value))| { let type_info = match registry.get_type(type_id) { Ok(x) => x, Err(e) => return Some(Err(e.into())), @@ -741,7 +791,7 @@ fn compile_func( .ret_types .iter() .zip(&values) - .filter_map(|(type_id, value)| { + .filter_map(|(type_id, (_, value))| { let type_info = match registry.get_type(type_id) { Ok(x) => x, Err(e) => return Some(Err(e.into())), @@ -785,11 +835,11 @@ fn compile_func( } // Store the return value in the return pointer, if there's one. - if Some(true) == has_return_ptr { + if has_return_ptr == Some(true) { let (_ret_type_id, ret_type_info) = return_type_infos[0]; let ret_layout = ret_type_info.layout(registry)?; - let ptr = values.remove(0); + let (_, ptr) = values.remove(0); block.append_operation(llvm::store( context, ptr, @@ -807,7 +857,7 @@ fn compile_func( let res_ty = llvm::r#type::r#struct(context, &return_types, false); values.iter().enumerate().try_fold( block.append_op_result(llvm::undef(res_ty, location))?, - |acc, (idx, x)| { + |acc, (idx, (_, x))| { block.append_op_result(llvm::insert_value( context, acc, @@ -1240,7 +1290,7 @@ where ); } StatementCompileResult::Deferred => { - tracing::trace!("Statement {statement_idx}'s compilation has been deferred."); + debug!("Statement {statement_idx}'s compilation has been deferred."); visited.remove(&statement_idx); queue.insert(0, (statement_idx, state)); @@ -1256,7 +1306,7 @@ fn generate_branching_targets<'ctx, 'this, 'a>( statements: &'this [Statement], statement_idx: StatementIdx, invocation: &'this Invocation, - state: &OrderedHashMap>, + state: &OrderedHashMap)>, ) -> Vec<(&'this Block<'ctx>, Vec>)> where 'this: 'ctx, @@ -1275,7 +1325,7 @@ where .map(|var_id| { match branch.results.iter().find_position(|id| *id == var_id) { Some((i, _)) => BranchArg::Returned(i), - None => BranchArg::External(state[var_id]), + None => BranchArg::External(state[var_id].1), } }) .collect::>(); @@ -1296,7 +1346,7 @@ where .find_map(|(i, id)| (id == var_id).then_some(i)) { Some(i) => BranchArg::Returned(i), - None => BranchArg::External(state[var_id]), + None => BranchArg::External(state[var_id].1), } }) .collect::>(); @@ -1397,3 +1447,50 @@ enum StatementCompileResult { /// The statement's processing has to be deferred until the end. Deferred, } + +#[cfg(feature = "with-trace-dump")] +mod trace_dump { + use crate::{metadata::trace_dump::TraceDumpMeta, types::TypeBuilder, utils::BlockExt}; + 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, + }; + + #[allow(clippy::too_many_arguments)] + pub fn build_state_snapshot( + trace_dump: &mut TraceDumpMeta, + context: &Context, + registry: &ProgramRegistry, + module: &Module, + init_block: &BlockRef, + block: &BlockRef, + location: Location, + statement_idx: StatementIdx, + state: &OrderedHashMap, + ) { + for (var_id, (type_id, value)) in state.iter() { + let type_info = registry.get_type(type_id).unwrap(); + let layout = type_info.layout(registry).unwrap(); + + let ptr_value = init_block + .alloca1(context, location, value.r#type(), layout.align()) + .unwrap(); + block.store(context, location, ptr_value, *value).unwrap(); + + trace_dump + .build_state(context, module, block, var_id, type_id, ptr_value, location) + .unwrap(); + } + + trace_dump + .build_push(context, module, block, statement_idx, location) + .unwrap(); + } +} diff --git a/src/executor/contract.rs b/src/executor/contract.rs index 06ed5040d..c33d14f73 100644 --- a/src/executor/contract.rs +++ b/src/executor/contract.rs @@ -77,7 +77,7 @@ use tempfile::NamedTempFile; #[educe(Debug)] pub struct AotContractExecutor { #[educe(Debug(ignore))] - library: Arc, + pub library: Arc, path: PathBuf, is_temp_path: bool, contract_info: NativeContractInfo, diff --git a/src/lib.rs b/src/lib.rs index e41db615e..0d75440dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,9 @@ pub use self::{ values::Value, }; +#[cfg(feature = "with-trace-dump")] +pub use cairo_native_runtime as runtime; + mod arch; pub mod cache; mod compiler; @@ -24,11 +27,11 @@ pub mod error; pub mod execution_result; pub mod executor; mod ffi; -mod libfuncs; +pub mod libfuncs; pub mod metadata; pub mod module; pub mod starknet; pub mod starknet_stub; -mod types; +pub mod types; pub mod utils; mod values; diff --git a/src/metadata.rs b/src/metadata.rs index 6f5038516..6cbb29b60 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -23,6 +23,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..2d3999ec0 --- /dev/null +++ b/src/metadata/trace_dump.rs @@ -0,0 +1,182 @@ +#![cfg(feature = "with-trace-dump")] + +use crate::{error::Result, utils::BlockExt}; +use cairo_lang_sierra::{ + ids::{ConcreteTypeId, VarId}, + program::StatementIdx, +}; +use melior::{ + dialect::{func, llvm, memref}, + ir::{ + attribute::{FlatSymbolRefAttribute, StringAttribute, TypeAttribute}, + r#type::{FunctionType, IntegerType, MemRefType}, + Block, Identifier, Location, Module, Region, Value, + }, + Context, ExecutionEngine, +}; +use std::collections::HashSet; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +enum TraceBinding { + State, + Push, + TraceId, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct TraceDumpMeta { + bindings: HashSet, +} + +impl TraceDumpMeta { + #[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<()> { + self.build_trace_id(context, module)?; + if self.bindings.insert(TraceBinding::State) { + module.body().append_operation(func::func( + context, + StringAttribute::new(context, "cairo_native__trace_dump__state"), + TypeAttribute::new( + FunctionType::new( + context, + &[ + IntegerType::new(context, 64).into(), // Trace ID. + IntegerType::new(context, 64).into(), // Var ID. + IntegerType::new(context, 64).into(), // Value type (`ConcreteTypeId`). + llvm::r#type::pointer(context, 0), // Value pointer. + ], + &[], + ) + .into(), + ), + Region::new(), + &[( + Identifier::new(context, "sym_visibility"), + StringAttribute::new(context, "private").into(), + )], + location, + )); + } + + let trace_id = block + .append_op_result(memref::get_global( + context, + "TRACE_DUMP__TRACE_ID", + MemRefType::new(IntegerType::new(context, 64).into(), &[], None, None), + location, + )) + .unwrap(); + let trace_id = block.append_op_result(memref::load(trace_id, &[], 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)?; + block.append_operation(func::call( + context, + FlatSymbolRefAttribute::new(context, "cairo_native__trace_dump__state"), + &[trace_id, var_id, value_ty, value_ptr], + &[], + location, + )); + + Ok(()) + } + + pub fn build_push( + &mut self, + context: &Context, + module: &Module, + block: &Block, + statement_idx: StatementIdx, + location: Location, + ) -> Result<()> { + self.build_trace_id(context, module)?; + if self.bindings.insert(TraceBinding::Push) { + module.body().append_operation(func::func( + context, + StringAttribute::new(context, "cairo_native__trace_dump__push"), + TypeAttribute::new( + FunctionType::new( + context, + &[ + IntegerType::new(context, 64).into(), // Trace ID. + IntegerType::new(context, 64).into(), // Statement index. + ], + &[], + ) + .into(), + ), + Region::new(), + &[( + Identifier::new(context, "sym_visibility"), + StringAttribute::new(context, "private").into(), + )], + Location::unknown(context), + )); + } + + let trace_id = block + .append_op_result(memref::get_global( + context, + "TRACE_DUMP__TRACE_ID", + MemRefType::new(IntegerType::new(context, 64).into(), &[], None, None), + location, + )) + .unwrap(); + let trace_id = block.append_op_result(memref::load(trace_id, &[], location))?; + let statement_idx = block.const_int(context, location, statement_idx.0, 64)?; + block.append_operation(func::call( + context, + FlatSymbolRefAttribute::new(context, "cairo_native__trace_dump__push"), + &[trace_id, statement_idx], + &[], + location, + )); + + Ok(()) + } + + fn build_trace_id(&mut self, context: &Context, module: &Module) -> Result<()> { + if self.bindings.insert(TraceBinding::TraceId) { + module.body().append_operation(memref::global( + context, + "TRACE_DUMP__TRACE_ID", + None, + MemRefType::new(IntegerType::new(context, 64).into(), &[], None, None), + None, + false, + None, + Location::unknown(context), + )); + } + + Ok(()) + } + + pub fn register_impls(&self, engine: &ExecutionEngine) { + if self.bindings.contains(&TraceBinding::State) { + unsafe { + engine.register_symbol( + "cairo_native__trace_dump__push", + cairo_native_runtime::trace_dump::cairo_native__trace_dump__push as *mut (), + ); + } + } + + if !self.bindings.is_empty() { + unsafe { + engine.register_symbol( + "cairo_native__trace_dump__state", + cairo_native_runtime::trace_dump::cairo_native__trace_dump__state as *mut (), + ); + } + } + } +}