diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c30a95b2b..a043fe3749 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Cairo-VM Changelog #### Upcoming Changes +* feat: add a `--tracer` option which hosts a web server that shows the line by line execution of cairo code along with memoery registers [#1265](https://github.com/lambdaclass/cairo-vm/pull/1265) * fix: Handle error in hint `UINT256_MUL_DIV_MOD` when divides by zero [#1367](https://github.com/lambdaclass/cairo-vm/pull/1367) diff --git a/Cargo.lock b/Cargo.lock index 6f0790dd33..a32b6f1f36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.7.6" @@ -34,65 +40,37 @@ dependencies = [ ] [[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - -[[package]] -name = "anes" -version = "0.1.6" +name = "alloc-no-stdlib" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] -name = "anstream" -version = "0.3.2" +name = "alloc-stdlib" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is-terminal", - "utf8parse", + "alloc-no-stdlib", ] [[package]] -name = "anstyle" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" - -[[package]] -name = "anstyle-parse" -version = "0.2.1" +name = "allocator-api2" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" -dependencies = [ - "utf8parse", -] +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] -name = "anstyle-query" -version = "1.0.0" +name = "anes" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" -dependencies = [ - "windows-sys", -] +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] -name = "anstyle-wincon" +name = "anstyle" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" -dependencies = [ - "anstyle", - "windows-sys", -] +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" [[package]] name = "anyhow" @@ -194,6 +172,33 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-compression" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "atomic-polyfill" version = "0.1.11" @@ -203,12 +208,78 @@ dependencies = [ "critical-section", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + [[package]] name = "bincode" version = "2.0.0-rc.3" @@ -274,6 +345,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.13.0" @@ -292,6 +384,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + [[package]] name = "cairo-felt" version = "0.8.5" @@ -781,13 +879,33 @@ dependencies = [ "assert_matches", "bincode", "cairo-vm", - "clap", + "cairo-vm-tracer", + "clap 3.2.25", "mimalloc", "nom", "rstest", "thiserror", ] +[[package]] +name = "cairo-vm-tracer" +version = "0.8.5" +dependencies = [ + "axum", + "cairo-vm", + "include_dir", + "mime_guess", + "num-bigint", + "num-traits 0.2.16", + "serde", + "thiserror-no-std", + "tokio", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", +] + [[package]] name = "cast" version = "0.3.0" @@ -799,6 +917,9 @@ name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -833,6 +954,23 @@ dependencies = [ "half", ] +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_derive", + "clap_lex 0.2.4", + "indexmap 1.9.3", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + [[package]] name = "clap" version = "4.3.19" @@ -840,8 +978,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d" dependencies = [ "clap_builder", - "clap_derive", - "once_cell", ] [[package]] @@ -850,35 +986,37 @@ version = "4.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1" dependencies = [ - "anstream", "anstyle", - "clap_lex", - "strsim", + "clap_lex 0.5.0", ] [[package]] name = "clap_derive" -version = "4.3.12" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck 0.4.1", + "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.28", + "syn 1.0.109", ] [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] [[package]] -name = "colorchoice" -version = "1.0.0" +name = "clap_lex" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "colored" @@ -925,6 +1063,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "criterion" version = "0.5.1" @@ -934,7 +1081,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap", + "clap 4.3.19", "criterion-plot", "is-terminal", "itertools 0.10.5", @@ -1157,12 +1304,31 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + [[package]] name = "funty" version = "2.0.0" @@ -1382,6 +1548,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.2" @@ -1412,6 +1587,69 @@ dependencies = [ "digest", ] +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "iai-callgrind" version = "0.3.1" @@ -1435,6 +1673,25 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indent" version = "0.1.1" @@ -1478,13 +1735,23 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "iri-string" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21859b667d66a4c1dacd9df0863b3efb65785474255face87f5bca39dd8407c0" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.2", "rustix", "windows-sys", ] @@ -1513,6 +1780,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -1635,6 +1911,12 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "matrixmultiply" version = "0.2.4" @@ -1668,6 +1950,22 @@ dependencies = [ "libmimalloc-sys", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minilp" version = "0.2.2" @@ -1684,6 +1982,26 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + [[package]] name = "ndarray" version = "0.13.1" @@ -1713,6 +2031,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -1799,7 +2127,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.2", "libc", ] @@ -1815,6 +2143,18 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "os_str_bytes" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parity-scale-codec" version = "3.6.4" @@ -1900,6 +2240,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + [[package]] name = "petgraph" version = "0.6.3" @@ -1925,6 +2271,26 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pin-project" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "pin-project-lite" version = "0.2.10" @@ -1937,6 +2303,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "plotters" version = "0.3.5" @@ -1987,6 +2359,30 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.66" @@ -2385,6 +2781,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0" +dependencies = [ + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.3" @@ -2394,6 +2799,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.7" @@ -2415,6 +2832,15 @@ dependencies = [ "keccak", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "siphasher" version = "0.3.10" @@ -2445,6 +2871,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "spin" version = "0.5.2" @@ -2585,6 +3021,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "tap" version = "1.0.1" @@ -2615,6 +3057,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + [[package]] name = "thiserror" version = "1.0.44" @@ -2655,6 +3112,16 @@ dependencies = [ "thiserror-impl-no-std", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -2674,6 +3141,47 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tokio" +version = "1.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.7.6" @@ -2708,6 +3216,129 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" +dependencies = [ + "async-compression", + "base64", + "bitflags 1.3.2", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "httpdate", + "iri-string", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "typenum" version = "1.16.0" @@ -2729,6 +3360,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-ident" version = "1.0.11" @@ -2748,10 +3388,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] -name = "utf8parse" -version = "0.2.1" +name = "uuid" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "0fa2982af2eec27de306107c027578ff7f423d65f7250e40ce0fea8f45248b81" +dependencies = [ + "getrandom", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" @@ -2784,6 +3433,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3050,3 +3708,33 @@ dependencies = [ "quote", "syn 2.0.28", ] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index bbaa7c6a23..2be6ee9755 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "vm", "hint_accountant", "examples/wasm-demo", + "cairo-vm-tracer", ] exclude = ["ensure-no_std"] @@ -25,6 +26,7 @@ felt = { package = "cairo-felt", path = "./felt", version = "0.8.5", default-fea "alloc", ] } cairo-vm = { path = "./vm", version = "0.8.5", default-features = false } +cairo-vm-tracer = { path = "./cairo-vm-tracer", version = "0.8.5", default-features = false } mimalloc = { version = "0.1.37", default-features = false } num-bigint = { version = "0.4", default-features = false, features = [ "serde", diff --git a/Makefile b/Makefile index 1644851d62..9f312a51ce 100644 --- a/Makefile +++ b/Makefile @@ -123,14 +123,14 @@ build-cairo-1-compiler-macos: | $(cairo-repo-1-dir-macos) $(cairo-repo-1-dir-macos): curl -L -o cairo-1.1.1.tar https://github.com/starkware-libs/cairo/releases/download/v1.1.1/release-aarch64-apple-darwin.tar \ && tar -xzvf cairo-1.1.1.tar \ - && mv cairo/ cairo1/ + && cp -R cairo cairo1 && rm -r cairo build-cairo-1-compiler: | $(cairo-repo-1-dir) $(cairo-repo-1-dir): curl -L -o cairo-1.1.1.tar https://github.com/starkware-libs/cairo/releases/download/v1.1.1/release-x86_64-unknown-linux-musl.tar.gz \ && tar -xzvf cairo-1.1.1.tar \ - && mv cairo/ cairo1/ + && cp -R cairo cairo1 && rm -r cairo # ====================== # Test Cairo 2 Contracts @@ -162,14 +162,14 @@ CAIRO_2_VERSION=2.1.0-rc1 $(cairo-repo-2-dir-macos): curl -L -o cairo-${CAIRO_2_VERSION}.tar https://github.com/starkware-libs/cairo/releases/download/v${CAIRO_2_VERSION}/release-aarch64-apple-darwin.tar \ && tar -xzvf cairo-${CAIRO_2_VERSION}.tar \ - && mv cairo/ cairo2/ + && cp -R cairo cairo2 && rm -r cairo build-cairo-2-compiler: | $(cairo-repo-2-dir) $(cairo-repo-2-dir): curl -L -o cairo-${CAIRO_2_VERSION}.tar https://github.com/starkware-libs/cairo/releases/download/v${CAIRO_2_VERSION}/release-x86_64-unknown-linux-musl.tar.gz \ && tar -xzvf cairo-${CAIRO_2_VERSION}.tar \ - && mv cairo/ cairo2/ + && cp -R cairo cairo2 && rm -r cairo cargo-deps: cargo install --version 0.3.1 iai-callgrind-runner diff --git a/README.md b/README.md index f49f8fb79f..923a47d9b9 100644 --- a/README.md +++ b/README.md @@ -21,29 +21,37 @@ A faster and safer implementation of the Cairo VM in Rust ## Table of Contents -- [Disclaimer](#%EF%B8%8F-disclaimer) -- [About](#-about) +- [Table of Contents](#table-of-contents) +- [⚠️ Disclaimer](#️-disclaimer) +- [📖 About](#-about) - [The Cairo language](#the-cairo-language) -- [Getting Started](#-getting-started) +- [🌅 Getting Started](#-getting-started) - [Dependencies](#dependencies) -- [Usage](#-usage) + - [Required](#required) + - [Optional](#optional) + - [Installation script](#installation-script) +- [🚀 Usage](#-usage) - [Adding cairo-vm as a dependency](#adding-cairo-vm-as-a-dependency) - - [Running cairo-vm from the CLI](#running-cairo-vm-from-cli) + - [Running cairo-vm from CLI](#running-cairo-vm-from-cli) - [Using hints](#using-hints) - [Running a function in a Cairo program with arguments](#running-a-function-in-a-cairo-program-with-arguments) - [WebAssembly Demo](#webassembly-demo) - [Testing](#testing) -- [Benchmarks](#-benchmarks) -- [Changelog](#-changelog) -- [Contributing](#-contributing) -- [Related Projects](#-related-projects) -- [Documentation](#-documentation) + - [Tracer](#tracer) +- [📊 Benchmarks](#-benchmarks) +- [📜 Changelog](#-changelog) +- [🛠 Contributing](#-contributing) +- [🌞 Related Projects](#-related-projects) +- [📚 Documentation](#-documentation) - [Cairo](#cairo) - [Original Cairo VM Internals](#original-cairo-vm-internals) - [Compilers and Interpreters](#compilers-and-interpreters) - [StarkNet](#starknet) - - [Computational Integrity and Zero-Knowledge Proofs](#computational-integrity-and-zero-knowledge-proofs) -- [License](#%EF%B8%8F-license) + - [Computational Integrity and Zero Knowledge Proofs](#computational-integrity-and-zero-knowledge-proofs) + - [Basics](#basics) + - [ZK SNARKs](#zk-snarks) + - [STARKs](#starks) +- [⚖️ License](#️-license) ## ⚠️ Disclaimer @@ -213,6 +221,10 @@ Now that you have the dependencies necessary to run the test suite you can run: make test ``` +### Tracer + +Cairo-vm offers a tracer which gives you a visualization of how your memory and registers change line after line as the VM executes the code. You can read more about it [here](./docs/tracer/README.md) + ## 📊 Benchmarks Running a [Cairo program](./cairo_programs/benchmarks/big_fibonacci.cairo) that gets the 1.5 millionth Fibonacci number we got the following benchmarks: diff --git a/cairo-vm-cli/Cargo.toml b/cairo-vm-cli/Cargo.toml index 149e5619e8..bb9a4b76d4 100644 --- a/cairo-vm-cli/Cargo.toml +++ b/cairo-vm-cli/Cargo.toml @@ -10,8 +10,9 @@ keywords.workspace = true [dependencies] bincode = { version = "2.0.0-rc.3", tag = "v2.0.0-rc.3", git = "https://github.com/bincode-org/bincode.git" } cairo-vm = { workspace = true, features = ["std"] } -clap = { version = "4.3.10", features = ["derive"] } -mimalloc = { version = "0.1.37", default-features = false, optional = true } +cairo-vm-tracer = { workspace = true, optional = true } +clap = { version = "3.2.5", features = ["derive"] } +mimalloc = { version = "0.1.29", default-features = false, optional = true } nom = "7" thiserror = { version = "1.0.40" } @@ -23,3 +24,4 @@ rstest = "0.17.0" default = ["with_mimalloc"] with_mimalloc = ["cairo-vm/with_mimalloc", "mimalloc"] lambdaworks-felt = ["cairo-vm/lambdaworks-felt"] +with_tracer = ["cairo-vm/with_tracer", "cairo-vm-tracer"] diff --git a/cairo-vm-cli/src/main.rs b/cairo-vm-cli/src/main.rs index 06a51c713e..33c6f9d40d 100644 --- a/cairo-vm-cli/src/main.rs +++ b/cairo-vm-cli/src/main.rs @@ -4,9 +4,19 @@ use bincode::enc::write::Writer; use cairo_vm::air_public_input::PublicInputError; use cairo_vm::cairo_run::{self, EncodeTraceError}; use cairo_vm::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; +#[cfg(feature = "with_tracer")] +use cairo_vm::serde::deserialize_program::DebugInfo; use cairo_vm::vm::errors::cairo_run_errors::CairoRunError; use cairo_vm::vm::errors::trace_errors::TraceError; use cairo_vm::vm::errors::vm_errors::VirtualMachineError; +#[cfg(feature = "with_tracer")] +use cairo_vm::vm::runners::cairo_runner::CairoRunner; +#[cfg(feature = "with_tracer")] +use cairo_vm::vm::vm_core::VirtualMachine; +#[cfg(feature = "with_tracer")] +use cairo_vm_tracer::error::trace_data_errors::TraceDataError; +#[cfg(feature = "with_tracer")] +use cairo_vm_tracer::tracer::run_tracer; use clap::{CommandFactory, Parser, ValueHint}; use std::io::{self, Write}; use std::path::PathBuf; @@ -40,6 +50,9 @@ struct Args { secure_run: Option, #[clap(long = "air_public_input")] air_public_input: Option, + #[structopt(long = "--tracer")] + #[cfg(feature = "with_tracer")] + tracer: Option, } fn validate_layout(value: &str) -> Result { @@ -73,6 +86,9 @@ enum Error { Trace(#[from] TraceError), #[error(transparent)] PublicInput(#[from] PublicInputError), + #[error(transparent)] + #[cfg(feature = "with_tracer")] + TraceDataError(#[from] TraceDataError), } struct FileWriter { @@ -108,6 +124,30 @@ impl FileWriter { } } +#[cfg(feature = "with_tracer")] +fn start_tracer(cairo_runner: &CairoRunner, vm: &VirtualMachine) -> Result<(), TraceDataError> { + let relocation_table = vm + .relocate_segments() + .map_err(TraceDataError::FailedToGetRelocationTable)?; + let instruction_locations = cairo_runner + .get_program() + .get_relocated_instruction_locations(relocation_table.as_ref()); + let debug_info = instruction_locations.map(DebugInfo::new); + + let relocated_trace = vm + .get_relocated_trace() + .map_err(TraceDataError::FailedToRelocateTrace)?; + + run_tracer( + cairo_runner.get_program().clone(), + cairo_runner.relocated_memory.clone(), + relocated_trace.clone(), + 1, + debug_info, + )?; + Ok(()) +} + fn run(args: impl Iterator) -> Result<(), Error> { let args = Args::try_parse_from(args)?; @@ -172,6 +212,11 @@ fn run(args: impl Iterator) -> Result<(), Error> { std::fs::write(file_path, json)?; } + #[cfg(feature = "with_tracer")] + if args.tracer.unwrap_or(false) { + start_tracer(&cairo_runner, &vm)?; + } + Ok(()) } diff --git a/cairo-vm-tracer/Cargo.toml b/cairo-vm-tracer/Cargo.toml new file mode 100644 index 0000000000..95eb6699b8 --- /dev/null +++ b/cairo-vm-tracer/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "cairo-vm-tracer" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +readme.workspace = true + +[features] +default = ["std"] +std = [] +alloc = [] +tracer = [] + +[dependencies] +cairo-vm = { workspace = true } +thiserror-no-std = { workspace = true } +num-bigint = { workspace = true } +num-traits = { workspace = true } +axum = "0.6.18" +tokio = {version = "1.28.2", features = ["rt", "macros","rt-multi-thread"]} +serde = { workspace = true } +tower = { version = "0.4.13", features = ["util"] } +tower-http = { version = "0.4.0", features = ["full"] } +tracing = "0.1.37" +tracing-subscriber = "0.3.17" +include_dir = "0.7.3" +mime_guess = "2.0.4" diff --git a/cairo-vm-tracer/src/error/mod.rs b/cairo-vm-tracer/src/error/mod.rs new file mode 100644 index 0000000000..67535674a0 --- /dev/null +++ b/cairo-vm-tracer/src/error/mod.rs @@ -0,0 +1 @@ +pub mod trace_data_errors; diff --git a/cairo-vm-tracer/src/error/trace_data_errors.rs b/cairo-vm-tracer/src/error/trace_data_errors.rs new file mode 100644 index 0000000000..5c652e64b9 --- /dev/null +++ b/cairo-vm-tracer/src/error/trace_data_errors.rs @@ -0,0 +1,26 @@ +use cairo_vm::vm::errors::{ + memory_errors::MemoryError, trace_errors::TraceError, vm_errors::VirtualMachineError, +}; +use thiserror_no_std::Error; + +#[derive(Debug, Error)] +pub enum TraceDataError { + #[error("Instruction is None at pc {0} when encoding")] + InstructionIsNone(String), + #[error(transparent)] + InstructionDecodeError(#[from] VirtualMachineError), + #[error(transparent)] + FailedToGetRelocationTable(#[from] MemoryError), + #[error(transparent)] + FailedToRelocateTrace(#[from] TraceError), + #[error("Failed to read file {0}")] + FailedToReadFile(String), + #[error("Input file is None {0}")] + InputFileIsNone(String), + #[error("Instruction encoding must be convertible to a u64")] + FailedToConvertInstructionEncoding, + #[error("Offset must be convertible to a usize")] + FailedToConvertOffset, + #[error("Imm address {0} must be convertible to a usize")] + FailedToImmAddress(String), +} diff --git a/cairo-vm-tracer/src/lib.rs b/cairo-vm-tracer/src/lib.rs new file mode 100644 index 0000000000..a17ac87737 --- /dev/null +++ b/cairo-vm-tracer/src/lib.rs @@ -0,0 +1,4 @@ +pub mod error; +pub mod tracer; +mod tracer_data; +mod types; diff --git a/cairo-vm-tracer/src/tracer.rs b/cairo-vm-tracer/src/tracer.rs new file mode 100644 index 0000000000..a98b1c136e --- /dev/null +++ b/cairo-vm-tracer/src/tracer.rs @@ -0,0 +1,137 @@ +use std::{collections::HashMap, net::SocketAddr}; + +use axum::{ + body::{self, Empty, Full}, + extract::{Path, State}, + http::{header, HeaderValue, Response, StatusCode}, + response::IntoResponse, + routing::get, + Json, Router, +}; +use cairo_vm::{ + felt::{Felt252, PRIME_STR}, + serde::deserialize_program::DebugInfo, + types::program::Program, + vm::trace::trace_entry::TraceEntry, +}; +use include_dir::{include_dir, Dir}; +use num_bigint::BigInt; +use num_traits::{One, Signed}; +use serde::Serialize; +use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer}; +use tracing::Level; + +use crate::{ + error::trace_data_errors::TraceDataError, tracer_data::TracerData, + types::memory_access::MemoryAccess, +}; + +static STATIC_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/static"); + +#[tokio::main] +pub async fn run_tracer( + program: Program, + memory: Vec>, + trace: Vec, + program_base: u64, + debug_info: Option, +) -> Result<(), TraceDataError> { + let tracer_data = TracerData::new(program, memory, trace, program_base, debug_info)?; + + tracing_subscriber::fmt::init(); + let app = Router::new() + .route("/static/data.json", get(get_data)) + .route("/static/eval.json", get(get_eval)) + .route("/static/*path", get(static_path)) + .with_state(tracer_data) + .layer( + TraceLayer::new_for_http() + .make_span_with(DefaultMakeSpan::new().level(Level::INFO)) + .on_response(DefaultOnResponse::new().level(Level::INFO)), + ); + + let addr = SocketAddr::from(([127, 0, 0, 1], 8100)); + tracing::info!("listening on http://{}/static/index.html", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); + Ok(()) +} + +async fn get_data(tracer_data: State) -> Json { + let data_response = DataReponse { + code: tracer_data + .input_files + .iter() + .map(|(k, v)| (k.clone(), v.to_html())) + .collect(), + trace: tracer_data.trace.clone(), + memory: tracer_data + .memory + .iter() + .filter_map(|x| x.as_ref().map(|_| x.clone().unwrap())) + .map(|x| { + field_element_repr( + &x.to_bigint(), + &BigInt::parse_bytes(PRIME_STR[2..].as_bytes(), 16).unwrap(), + ) + }) + .enumerate() + .map(|(i, v)| (i + 1, v)) + .collect(), + memory_accesses: tracer_data.memory_accesses.clone(), + public_memory: vec![], + }; + + // filter a vector of options to remove none values + + Json(data_response) +} + +async fn get_eval(_tracer_data: State) {} + +async fn static_path(Path(path): Path) -> impl IntoResponse { + let path = path.trim_start_matches('/'); + let mime_type = mime_guess::from_path(path).first_or_text_plain(); + + match STATIC_DIR.get_file(path) { + None => Response::builder() + .status(StatusCode::NOT_FOUND) + .body(body::boxed(Empty::new())) + .unwrap(), + Some(file) => Response::builder() + .status(StatusCode::OK) + .header( + header::CONTENT_TYPE, + HeaderValue::from_str(mime_type.as_ref()).unwrap(), + ) + .body(body::boxed(Full::from(file.contents()))) + .unwrap(), + } +} + +fn field_element_repr(val: &BigInt, prime: &BigInt) -> String { + // Shift val to the range (-prime / 2, prime / 2). + let shifted_val: BigInt = (val.clone() + prime.clone() / 2) % prime.clone() - prime.clone() / 2; + // If shifted_val is small, use decimal representation. + let two_pow_40: BigInt = BigInt::one() << 40; + if shifted_val.abs() < two_pow_40 { + return shifted_val.to_string(); + } + // Otherwise, use hex representation (allowing a sign if the number is close to prime). + let two_pow_100: BigInt = BigInt::one() << 100; + if shifted_val.abs() < two_pow_100 { + return format!("0x{:x}", shifted_val); + } + format!("0x{:x}", val) +} + +#[derive(Serialize)] +struct DataReponse { + code: HashMap, + trace: Vec, + memory: HashMap, + public_memory: Vec, + memory_accesses: Vec, +} diff --git a/cairo-vm-tracer/src/tracer_data.rs b/cairo-vm-tracer/src/tracer_data.rs new file mode 100644 index 0000000000..9f72f1ef49 --- /dev/null +++ b/cairo-vm-tracer/src/tracer_data.rs @@ -0,0 +1,223 @@ +use std::collections::{BTreeMap, HashMap}; + +use cairo_vm::{ + felt::Felt252, + serde::deserialize_program::{DebugInfo, InstructionLocation}, + types::{ + instruction::Op1Addr, + program::Program, + relocatable::{MaybeRelocatable, Relocatable}, + }, + vm::{ + context::run_context::RunContext, decoding::decoder::decode_instruction, + trace::trace_entry::TraceEntry, + }, +}; +use num_bigint::BigUint; +use num_traits::ToPrimitive; + +use crate::{error::trace_data_errors::TraceDataError, types::memory_access::MemoryAccess}; + +#[derive(Clone)] +pub struct InputCodeFile { + content: String, + lines: Vec, + tags: Vec<(usize, isize, String)>, +} + +impl InputCodeFile { + fn new(content: &str) -> Self { + let lines: Vec = content.lines().map(|line| line.to_string()).collect(); + InputCodeFile { + content: content.to_string(), + lines, + tags: Vec::new(), + } + } + + fn mark_text( + &mut self, + line_start: usize, + col_start: usize, + line_end: usize, + col_end: usize, + classes: &[&str], + ) { + let offset_start = self + .lines + .iter() + .take(line_start - 1) + .map(|line| line.len()) + .sum::() + + line_start + + col_start + - 2; + + let offset_end = self + .lines + .iter() + .take(line_end - 1) + .map(|line| line.len()) + .sum::() + + line_end + + col_end + - 2; + + self.tags.push(( + offset_start, + -(offset_end as isize), + format!("", classes.join(" ")), + )); + self.tags + .push((offset_end, -std::isize::MAX, "".to_string())); + } + + pub fn to_html(&self) -> String { + let mut res = self.content.replace(' ', "\0"); + let mut sorted_tags = self.tags.clone(); + sorted_tags.sort_by_key(|&(key, _, _)| key); + for &(pos, _, ref tag_content) in sorted_tags.iter().rev() { + res.insert_str(pos, tag_content); + } + res.replace('\0', " ").replace('\n', "
\n") + } +} + +// TODO: add support for taking air_public_input as an argument +#[derive(Clone)] +pub struct TracerData { + pub(crate) _program: Program, + pub(crate) memory: Vec>, + pub(crate) trace: Vec, + pub(crate) _program_base: u64, // TODO: adjust size based on maximum instructions possible + pub(crate) _debug_info: Option, + pub(crate) memory_accesses: Vec, + pub(crate) input_files: HashMap, +} + +impl TracerData { + pub fn new( + program: Program, + memory: Vec>, + trace: Vec, + program_base: u64, + debug_info: Option, + ) -> Result { + let mut input_files = HashMap::::new(); + + if let Some(debug_info) = debug_info.clone() { + // loop over debug_info + + //sort hashmap by key + let instruction_locations: BTreeMap = + debug_info.get_instruction_locations().into_iter().collect(); + for (pc_offset, instruction_location) in instruction_locations.iter() { + let loc = &instruction_location.inst; + let filename = &loc.input_file.filename; + let content = loc + .input_file + .get_content() + .map_err(|_| TraceDataError::FailedToReadFile(filename.clone()))?; + if !input_files.contains_key(filename) { + input_files.insert(filename.clone(), InputCodeFile::new(content.as_str())); + } + let input_file = input_files.get_mut(filename); + if input_file.is_none() { + return Err(TraceDataError::InputFileIsNone(filename.clone())); + } + let input_file = input_file.unwrap(); + + input_file.mark_text( + loc.start_line as usize, + loc.start_col as usize, + loc.end_line as usize, + loc.end_col as usize, + &[format!("inst{}", pc_offset).as_str(), "instruction"], + ); + } + } + + let mut memory_accesses: Vec = vec![]; + //loop of trace + for entry in trace.iter() { + let run_context = RunContext::new(Relocatable::from((0, entry.pc)), entry.ap, entry.fp); + + let (instruction_encoding, _) = + get_instruction_encoding(entry.pc, &memory, program.prime())?; + + let instruction_encoding = instruction_encoding.to_u64(); + if instruction_encoding.is_none() { + return Err(TraceDataError::FailedToConvertInstructionEncoding); + } + let instruction_encoding = instruction_encoding.unwrap(); + let instruction = decode_instruction(instruction_encoding)?; + + // get dst_addr + let dst_addr = run_context.compute_dst_addr(&instruction)?.offset; + + // get op0_addr + let op0_addr = run_context.compute_op0_addr(&instruction)?.offset; + + // get op1_addr + let mut op0: Result, TraceDataError> = Ok(None); + if instruction.op1_addr == Op1Addr::Op0 { + let op0_memory = &memory[op0_addr]; + op0 = match op0_memory { + None => Ok(None), + Some(felt) => { + let offset = felt.clone().to_usize(); + if offset.is_none() { + return Err(TraceDataError::FailedToConvertOffset); + } + let offset = offset.unwrap(); + Ok(Some(MaybeRelocatable::RelocatableValue(Relocatable { + segment_index: 1_isize, + offset, + }))) + } + }; + } + let op0 = op0?; + let op1_addr = run_context + .compute_op1_addr(&instruction, op0.as_ref())? + .offset; + + // add to memory access + memory_accesses.push(MemoryAccess { + dst: dst_addr, + op0: op0_addr, + op1: op1_addr, + }); + } + + Ok(TracerData { + _program: program, + memory, + trace, + _program_base: program_base, + _debug_info: debug_info, + memory_accesses, + input_files, + }) + } +} + +// Returns the encoded instruction (the value at pc) and the immediate value (the value at +// pc + 1, if it exists in the memory). +pub fn get_instruction_encoding( + pc: usize, + memory: &[Option], + prime: &str, +) -> Result<(Felt252, Option), TraceDataError> { + if memory[pc].is_none() { + return Err(TraceDataError::InstructionIsNone(pc.to_string())); + } + let instruction_encoding = memory[pc].clone().unwrap(); + let prime = BigUint::parse_bytes(prime[2..].as_bytes(), 16).unwrap(); + + let imm_addr = BigUint::from(pc + 1) % prime; + let imm_addr = usize::try_from(imm_addr.clone()) + .map_err(|_| TraceDataError::FailedToImmAddress(imm_addr.to_string()))?; + let optional_imm = memory[imm_addr].clone(); + Ok((instruction_encoding, optional_imm)) +} diff --git a/cairo-vm-tracer/src/types/memory_access.rs b/cairo-vm-tracer/src/types/memory_access.rs new file mode 100644 index 0000000000..21507b313f --- /dev/null +++ b/cairo-vm-tracer/src/types/memory_access.rs @@ -0,0 +1,9 @@ +use serde::Serialize; + +// TODO: check if the sizes are corect +#[derive(Serialize, Clone)] +pub struct MemoryAccess { + pub(crate) dst: usize, + pub(crate) op0: usize, + pub(crate) op1: usize, +} diff --git a/cairo-vm-tracer/src/types/mod.rs b/cairo-vm-tracer/src/types/mod.rs new file mode 100644 index 0000000000..8b7ec4be21 --- /dev/null +++ b/cairo-vm-tracer/src/types/mod.rs @@ -0,0 +1 @@ +pub mod memory_access; diff --git a/cairo-vm-tracer/static/index.html b/cairo-vm-tracer/static/index.html new file mode 100644 index 0000000000..9606f87125 --- /dev/null +++ b/cairo-vm-tracer/static/index.html @@ -0,0 +1,56 @@ + + + + + + Cairo Tracer + + + + + + + +
+
+
+
+
+
+ Follow: + +
+
+
+
+ (s) Step
+ (S) Previous step
+ (n) Step over
+ (N) Previous step over
+ (o) Step out
+ (b) Run until next breakpoint
+ (B) Run until previous breakpoint
+
+ pc =
+ ap =
+ fp =
+
+ dst_addr =
+ op0_addr =
+ op1_addr =
+
+ Stack trace: +
+
+ + +
+ + + diff --git a/cairo-vm-tracer/static/tracer.css b/cairo-vm-tracer/static/tracer.css new file mode 100644 index 0000000000..c4ec672eed --- /dev/null +++ b/cairo-vm-tracer/static/tracer.css @@ -0,0 +1,81 @@ +html, body { + margin: 0px; + padding: 0px; + height: 100%; +} + +#code_div, #info_td, #memory_div_parent { + font-family: monospace; +} + +.current_instruction, .current_pc_mem { + background: yellow !important; +} + +.highlight_instruction { + background: lightblue; +} + +.breakpoint { + background: #99ccff; +} + +#code_div_parent, #memory_div_parent { + flex-flow: column; + height: 100%; + vertical-align: top; +} + +#code_div_parent { + background: rgb(238, 255, 255); + overflow-y: scroll; +} + +#memory_div_parent { + background: rgb(255, 255, 250); + display: flex; +} + +#memory_div { + overflow-y: scroll; + flex-flow: row; +} + +.mem_info { + display: inline-block; + width: 40px; + text-align: right; + font-size: 70%; +} + +#main_table { + height: 100%; +} + +#code_div { + margin: 10px; +} + +#memory_div>table { + height: 100%; + margin: 10px; +} + +.table_with_border { + border-collapse: collapse; +} + +.table_with_border>tr>td, .table_with_border>tr>th { + border: 1px black solid; +} + +.filename { + font-weight: bold; + text-decoration: underline; + margin-top: 10px; + margin-bottom: 5px; +} + +.public_memory { + background-color: #f0fff8; +} diff --git a/cairo-vm-tracer/static/tracer.js b/cairo-vm-tracer/static/tracer.js new file mode 100644 index 0000000000..61b97a60b5 --- /dev/null +++ b/cairo-vm-tracer/static/tracer.js @@ -0,0 +1,387 @@ +/* + The number of the current step being viewed in the tracer. +*/ +var current_step; + +var trace; +var memory; +var memory_accesses; + +/* + A list of objects {watch_expr: ..., watch_result: ...} where watch_expr is the element + and watch_result is the result element. +*/ +var watch_exprs = []; + +/* + The maximum number of entries shown in the stack trace. +*/ +const MAX_STACK_TRACE = 100; + +function load_json() { + $.getJSON("data.json", function (data) { + trace = data.trace; + memory = data.memory; + memory_accesses = data.memory_accesses; + for (const filename in data.code) { + $("#code_div") + .append($("
").addClass("filename").text(filename)) + .append($("
").html(data.code[filename])); + } + $("#slider_div").append(create_slider()); + $("#memory_div").append(create_memory_table()); + $("#watch_table").append(create_watch_row()); + mark_public_memory(data.public_memory); + goto_step(0); + + $(".instruction").dblclick(toggle_breakpoint); + $(".mem_row").dblclick(toggle_breakpoint); + }); +} + +/* + Adds a slider that tracks the progress of the program. +*/ +function create_slider() { + const slider = $("").attr({ + id: "slider", + type: "range", + min: 0, + max: trace.length - 1, + value: 0, + }); + + slider[0].oninput = function () { + goto_step(parseInt($("#slider").val())); + }; + + return slider; +} + +function create_memory_table() { + const table = $("").addClass("table_with_border"); + for (const addr in memory) { + table.append( + $("") + .attr({ id: "mem_row" + addr }) + .addClass("mem_row") + .append( + $("") + .append($("").append(fp_cell).append(pc_cell); + + pc_cell.mouseenter(function () { + $(".inst" + pc).addClass("highlight_instruction"); + }); + pc_cell.mouseleave(function () { + $(".inst" + pc).removeClass("highlight_instruction"); + }); + + return row; +} + +function create_watch_row() { + const watch_expr = $("").attr({ type: "text" }); + const watch_result = $(""); + + watch_exprs.push({ watch_expr: watch_expr, watch_result: watch_result }); + + watch_expr.keyup(function (event) { + if (event.key == "Enter") { + update_watch(); + } + }); + + const n_watches = watch_exprs.length; + watch_expr.change(function (event) { + // Add new watch if needed. + if (watch_expr.val() != "" && watch_exprs.length == n_watches) { + $("#watch_table").append(create_watch_row()); + } + + update_watch(); + }); + + // Make sure navigation keys (s, n, ...) will not work when watch_expr is focused. + watch_expr.keypress(function (event) { + event.stopPropagation(); + }); + + return $("") + .append($("
").append( + $("") + .attr({ id: "mem_info" + addr }) + .addClass("mem_info") + ) + ) + .append($("").text(addr)) + .append($("").text(memory[addr])) + ); + } + return table; +} + +function mark_public_memory(public_memory) { + for (const addr of public_memory) { + $("#mem_row" + addr).addClass("public_memory"); + } +} + +function update_current_instruction_view() { + const entry = trace[current_step]; + const pc = entry.pc; + const ap = entry.ap; + const fp = entry.fp; + + $(".instruction").removeClass("current_instruction"); + $(".inst" + pc).addClass("current_instruction"); + $("#pc").text(pc); + $("#ap").text(ap); + $("#fp").text(fp); + + const mem_accesses = memory_accesses[current_step]; + $("#dst_addr").text(mem_accesses.dst); + $("#op0_addr").text(mem_accesses.op0); + $("#op1_addr").text(mem_accesses.op1); + + $(".mem_row").removeClass("current_pc_mem"); + $("#mem_row" + pc).addClass("current_pc_mem"); + + $(".mem_info").text(""); + $("#mem_info" + ap).append("ap "); + $("#mem_info" + fp).append("fp "); + + update_stack_trace_view(); + + if ($("#slider").val() != current_step) { + $("#slider").val(current_step); + } + + scrollIntoViewIfNeeded($(".inst" + pc)[0]); + + if ($("#memory_follow").val() == "pc") { + scrollIntoViewIfNeeded($("#mem_row" + pc)[0]); + } else if ($("#memory_follow").val() == "ap") { + scrollIntoViewIfNeeded($("#mem_row" + ap)[0]); + } else if ($("#memory_follow").val() == "fp") { + scrollIntoViewIfNeeded($("#mem_row" + fp)[0]); + } + + update_watch(); +} + +function scrollIntoViewIfNeeded(target) { + if (target === undefined) { + return; + } + + const rect = target.getBoundingClientRect(); + const scrollParent = getScrollParent(target); + const scrollParentRect = scrollParent.getBoundingClientRect(); + if (rect.bottom > scrollParentRect.bottom) { + // Scroll down. + const scrollMargin = 64; + const targetBottom = scrollParent.scrollTop + rect.bottom; + scrollParent.scrollTo({ + top: targetBottom - scrollParent.clientHeight + scrollMargin, + behavior: "smooth", + }); + } else if (rect.top < scrollParentRect.top) { + // Scroll up. + const scrollMargin = 32; + const targetTop = scrollParent.scrollTop + rect.top; + scrollParent.scrollTo({ + top: targetTop - scrollMargin, + behavior: "smooth", + }); + } +} + +function getScrollParent(node) { + node = node.parentNode; + while (getComputedStyle(node)["overflow-y"] != "scroll") { + node = node.parentNode; + } + return node; +} + +function update_stack_trace_view() { + const entry = trace[current_step]; + const initial_fp = trace[0].fp; + + $(".mem_row").css("border-top", ""); + + const stack_trace = $("#stack_trace"); + stack_trace + .empty() + .append( + $("
").append("fp")) + .append($("").append("return pc")) + ); + + var fp = entry.fp; + for (var i = 0; i < MAX_STACK_TRACE && fp != initial_fp; i++) { + const pc = memory[fp - 1]; + stack_trace.append(create_stack_trace_row(fp, pc)); + $("#mem_row" + fp).css("border-top", "2px black solid"); + fp = memory[fp - 2]; + } +} + +function create_stack_trace_row(fp, pc) { + const fp_cell = $("").append(fp); + const pc_cell = $("").append(pc); + const row = $("
").append(watch_expr)) + .append($("").append(watch_result)); +} + +var update_watch_ajax = { abort: function () {} }; + +function update_watch() { + // REMOVE RETURN TO EVALUATION EXPRESSION + return; + var query_str = "eval.json?step=" + encodeURIComponent(current_step); + for (const entry of watch_exprs) { + const expr_txt = entry.watch_expr.val(); + query_str += + "&expr=" + encodeURIComponent(expr_txt == "" ? "null" : expr_txt); + } + // Abort previous AJAX request if exists. + update_watch_ajax.abort(); + update_watch_ajax = $.getJSON(query_str, function (data) { + for (var idx = 0; idx < data.length; ++idx) { + watch_exprs[idx].watch_result.text(data[idx]); + } + }); +} + +/* + Clears the text selection in the window. + This function was copied from + https://stackoverflow.com/questions/880512/prevent-text-selection-after-double-click. +*/ +function clearSelection() { + if (document.selection && document.selection.empty) { + document.selection.empty(); + } else if (window.getSelection) { + var sel = window.getSelection(); + sel.removeAllRanges(); + } +} + +function toggle_breakpoint(event) { + $(this).toggleClass("breakpoint"); + clearSelection(); + event.stopPropagation(); +} + +function has_breakpoint(step) { + return ( + $("#mem_row" + trace[step].pc).hasClass("breakpoint") || + $("#mem_row" + memory_accesses[step].dst).hasClass("breakpoint") || + $("#mem_row" + memory_accesses[step].op0).hasClass("breakpoint") || + $("#mem_row" + memory_accesses[step].op1).hasClass("breakpoint") + ); +} + +function goto_step(i) { + // Update global variable. + current_step = i; + update_current_instruction_view(); +} + +function step() { + if (current_step < trace.length - 1) { + goto_step(current_step + 1); + } +} + +function previous_step() { + if (current_step > 0) { + goto_step(current_step - 1); + } +} + +function step_over() { + const current_fp = trace[current_step].fp; + for (var i = current_step + 1; i < trace.length; i++) { + if (trace[i].fp == current_fp || has_breakpoint(i)) { + goto_step(i); + return; + } + } +} + +function previous_step_over() { + const current_fp = trace[current_step].fp; + for (var i = current_step - 1; i >= 0; i--) { + if (trace[i].fp == current_fp) { + goto_step(i); + return; + } + } +} + +function step_out() { + const current_fp = trace[current_step].fp; + const previous_fp = memory[current_fp - 2]; + for (var i = current_step + 1; i < trace.length; i++) { + if (trace[i].fp == previous_fp) { + goto_step(i); + return; + } + } +} + +function next_breakpoint() { + for (var i = current_step + 1; i < trace.length; i++) { + if (has_breakpoint(i)) { + goto_step(i); + return; + } + } +} + +function previous_breakpoint() { + const current_fp = trace[current_step].fp; + for (var i = current_step - 1; i >= 0; i--) { + if (has_breakpoint(i)) { + goto_step(i); + return; + } + } +} + +$(document).ready(function () { + load_json(); +}); + +$(document).keypress(function (event) { + if (event.key == "s") { + step(); + event.stopPropagation(); + } + if (event.key == "S") { + previous_step(); + event.stopPropagation(); + } + if (event.key == "n") { + step_over(); + event.stopPropagation(); + } + if (event.key == "N") { + previous_step_over(); + event.stopPropagation(); + } + if (event.key == "o") { + step_out(); + event.stopPropagation(); + } + if (event.key == "b") { + next_breakpoint(); + event.stopPropagation(); + } + if (event.key == "B") { + previous_breakpoint(); + event.stopPropagation(); + } +}); diff --git a/docs/README.md b/docs/README.md index dd1d40c420..f25a1c762a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,3 +4,4 @@ * [Custom Hint Processor](./hint_processor/) * [How to run a cairo program with custom hints](./hint_processor/builtin_hint_processor) * [References parsing](./references_parsing/) +* [Tracer](./tracer/) diff --git a/docs/tracer/README.md b/docs/tracer/README.md new file mode 100644 index 0000000000..0f99829bfe --- /dev/null +++ b/docs/tracer/README.md @@ -0,0 +1,39 @@ +# Cairo Tracer + +Cairo-vm offers a tracer which gives you a visualization of how your memory and registers change line after line as the VM executes the code. + +## Setting up the tracer + +To use the tracer, you need to build your the VM using the `with_tracer` feature flag. + +```bash +cargo build --release --features with_tracer +``` + +## Running the tracer + +Once the build in the above step is complete, you can use the `cairo-vm-cli` as you do normally with an additional argument `--tracer true`. This tells the VM to start a server where you can visualize the exection of your cairo program. + +```bash +target/release/cairo-vm-cli --layout --memory_file --trace_file --tracer true +``` + +### NOTE +> The `--memory_file` and `--trace_file` arguments are compulsary at the moment as the current code relocates the memory and trace only when these arguments are supplied. However, edits can be made in future versions to relocate if only the `--tracer` option is specified. + + +## Using the tracer + +![tracer](tracer.png) + +You can go to [http://127.0.0.1:8100/static/index.html](http://127.0.0.1:8100/static/index.html) to see the tracer. + +Use the following commands to visualize the code execution + +- `s` to go to the next step +- `Shift + s` to go to the previous step +- `n` to step over +- `N` to previous step over +- `o` to step out +- `b` to run until you reach the next breakpoint +- `B` to run until you reach the previous breakpoint diff --git a/docs/tracer/tracer.png b/docs/tracer/tracer.png new file mode 100644 index 0000000000..9cb29af0d9 Binary files /dev/null and b/docs/tracer/tracer.png differ diff --git a/felt/src/lib_bigint_felt.rs b/felt/src/lib_bigint_felt.rs index 1c9f3e0479..87472acfd1 100644 --- a/felt/src/lib_bigint_felt.rs +++ b/felt/src/lib_bigint_felt.rs @@ -377,7 +377,7 @@ impl Add<&Felt252> for u64 { }; // A single digit means this is effectively the sum of two `u64` numbers. let Some(h0) = rhs_digits.next() else { - return self.checked_add(low) + return self.checked_add(low); }; // Now we need to compare the 3 most significant digits. // There are two relevant cases from now on, either `rhs` behaves like a diff --git a/vm/Cargo.toml b/vm/Cargo.toml index d1d5a77ab3..395ff740cd 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -10,6 +10,7 @@ keywords.workspace = true [features] default = ["std", "with_mimalloc"] +with_tracer = ["tracer"] with_mimalloc = ["mimalloc"] std = [ "serde_json/std", @@ -28,6 +29,7 @@ cairo-1-hints = [ ] arbitrary = ["dep:arbitrary", "felt/arbitrary", "felt/std", "std"] lambdaworks-felt = ["felt/lambdaworks-felt"] +tracer = [] # Note that these features are not retro-compatible with the cairo Python VM. test_utils = [ diff --git a/vm/src/serde/deserialize_program.rs b/vm/src/serde/deserialize_program.rs index ee361a7d91..8f143f970b 100644 --- a/vm/src/serde/deserialize_program.rs +++ b/vm/src/serde/deserialize_program.rs @@ -196,13 +196,24 @@ fn arbitrary_parent_location(u: &mut Unstructured, depth: u8) -> arbitrary::Resu #[cfg_attr( all(feature = "arbitrary", feature = "std"), - derive(Arbitrary, Clone, Serialize) + derive(Arbitrary, Serialize) )] -#[derive(Deserialize, Debug, PartialEq, Eq)] +#[derive(Deserialize, Debug, PartialEq, Eq, Clone)] pub struct DebugInfo { instruction_locations: HashMap, } +impl DebugInfo { + pub fn new(instruction_locations: HashMap) -> Self { + Self { + instruction_locations, + } + } + pub fn get_instruction_locations(&self) -> HashMap { + self.instruction_locations.clone() + } +} + #[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct InstructionLocation { @@ -216,6 +227,17 @@ pub struct InputFile { pub filename: String, } +impl InputFile { + #[cfg(feature = "std")] + pub fn get_content(&self) -> Result { + let content = std::fs::read_to_string(self.filename.clone()); + if let Ok(content) = content { + return Ok(content); + } + Err(format!("Failed to read file {}", self.filename.clone())) + } +} + #[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] pub struct HintLocation { diff --git a/vm/src/types/program.rs b/vm/src/types/program.rs index aefd91c7b0..2b84ec94f5 100644 --- a/vm/src/types/program.rs +++ b/vm/src/types/program.rs @@ -223,6 +223,22 @@ impl Program { self.shared_program_data.identifiers.get(id) } + pub fn get_relocated_instruction_locations( + &self, + relocation_table: &[usize], + ) -> Option> { + self.shared_program_data.instruction_locations.as_ref()?; + let relocated_instructions = self + .shared_program_data + .instruction_locations + .as_ref() + .unwrap() + .iter() + .map(|(k, v)| (k + relocation_table[0], v.clone())) + .collect(); + Some(relocated_instructions) + } + pub fn iter_identifiers(&self) -> impl Iterator { self.shared_program_data .identifiers @@ -312,8 +328,9 @@ impl TryFrom for Program { #[cfg(test)] mod tests { + use super::*; - use crate::serde::deserialize_program::{ApTracking, FlowTrackingData}; + use crate::serde::deserialize_program::{ApTracking, FlowTrackingData, InputFile, Location}; use crate::utils::test_utils::*; use felt::felt_str; use num_traits::Zero; @@ -668,6 +685,61 @@ mod tests { ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn get_relocated_instruction_locations() { + fn build_instruction_location_for_test(start_line: u32) -> InstructionLocation { + InstructionLocation { + inst: Location { + end_line: 0, + end_col: 0, + input_file: InputFile { + filename: String::from("test"), + }, + parent_location: None, + start_line, + start_col: 0, + }, + hints: vec![], + } + } + + let reference_manager = ReferenceManager { + references: Vec::new(), + }; + let builtins: Vec = Vec::new(); + let data: Vec = vec![]; + let identifiers: HashMap = HashMap::new(); + let mut instruction_locations: HashMap = HashMap::new(); + + let il_1 = build_instruction_location_for_test(0); + let il_2 = build_instruction_location_for_test(2); + let il_3 = build_instruction_location_for_test(3); + instruction_locations.insert(5, il_1.clone()); + instruction_locations.insert(10, il_2.clone()); + instruction_locations.insert(12, il_3.clone()); + + let program = Program::new( + builtins, + data, + None, + HashMap::new(), + reference_manager, + identifiers, + Vec::new(), + Some(instruction_locations), + ) + .unwrap(); + + let relocated_instructions = program.get_relocated_instruction_locations(&[2]); + assert!(relocated_instructions.is_some()); + let relocated_instructions = relocated_instructions.unwrap(); + assert_eq!(relocated_instructions.len(), 3); + assert_eq!(relocated_instructions.get(&7), Some(&il_1)); + assert_eq!(relocated_instructions.get(&12), Some(&il_2)); + assert_eq!(relocated_instructions.get(&14), Some(&il_3)); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn iter_identifiers() { diff --git a/vm/src/vm/context/run_context.rs b/vm/src/vm/context/run_context.rs index 4867a154de..89a4814fb9 100644 --- a/vm/src/vm/context/run_context.rs +++ b/vm/src/vm/context/run_context.rs @@ -26,6 +26,10 @@ impl RunContext { self.pc } + pub fn new(pc: Relocatable, ap: usize, fp: usize) -> Self { + RunContext { pc, ap, fp } + } + pub fn compute_dst_addr( &self, instruction: &Instruction, diff --git a/vm/src/vm/trace/mod.rs b/vm/src/vm/trace/mod.rs index faab7ae879..d300003ac6 100644 --- a/vm/src/vm/trace/mod.rs +++ b/vm/src/vm/trace/mod.rs @@ -7,7 +7,7 @@ pub mod trace_entry { /// Register values are represented as their offsets, as their indexes will always be 0,1,1 respectively /// The index of the last pc will not be equal to 0, but it is not appended to the trace /// After relocation the value of each register will be a single integer - #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] + #[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)] pub struct TraceEntry { pub pc: usize, pub ap: usize, diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index c1c8aea0a7..7d1e591c25 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -891,6 +891,11 @@ impl VirtualMachine { self.trace = None } + #[cfg(feature = "with_tracer")] + pub fn relocate_segments(&self) -> Result, MemoryError> { + self.segments.relocate_segments() + } + #[doc(hidden)] pub fn skip_next_instruction_execution(&mut self) { self.skip_instruction_execution = true;