diff --git a/CHANGELOG.md b/CHANGELOG.md index d37a550c67..d9e0af972a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## 0.12.0 (2025-01-22) + +#### Highlights +- [BREAKING] Refactored memory to be element-addressable (#1598). + +#### Changes +- [BREAKING] Resolved flag collision in `--verify` command and added functionality for optional input/output files (#1513). +- [BREAKING] Refactored `MastForest` serialization/deserialization to put decorator data at the end of the binary (#1531). +- [BREAKING] Refactored `Process` struct to no longer take ownership of the `Host` (#1571). +- [BREAKING] Converted `ProcessState` from a trait to a struct (#1571). +- [BREAKING] Simplified `Host` and `AdviceProvider` traits (#1572). +- [BREAKING] Updated Winterfell dependency to v0.11 (#1586). +- [BREAKING] Cleaned up benchmarks and examples in the `miden-vm` crate (#1587) +- [BREAKING] Switched to `thiserror` 2.0 derive errors and refactored errors (#1588). +- Moved handling of `FalconSigToStack` event from system event handlers to the `DefaultHost` (#1630). + +#### Enhancements +- Added options `--kernel`, `--debug` and `--output` to `miden bundle` (#1447). +- Added `miden_core::mast::MastForest::advice_map` to load it into the advice provider before the `MastForest` execution (#1574). +- Optimized the computation of the DEEP queries in the recursive verifier (#1594). +- Added validity checks for the inputs to the recursive verifier (#1596). + ## 0.11.0 (2024-11-04) #### Enhancements @@ -9,6 +31,7 @@ - Debug instructions can be enabled in the cli `run` command using `--debug` flag (#1502). - Added support for procedure annotation (attribute) syntax to Miden Assembly (#1510). - Make `miden-prover::prove()` method conditionally asynchronous (#1563). +- Update and sync the recursive verifier (#1575). #### Changes @@ -27,6 +50,7 @@ - [BREAKING] `DYNCALL` operation fixed, and now expects a memory address pointing to the procedure hash (#1535). - Permit child `MastNodeId`s to exceed the `MastNodeId`s of their parents (#1542). - Don't validate export names on `Library` deserialization (#1554) +- Compile advice injectors down to `Emit` operations (#1581) #### Fixes @@ -36,6 +60,7 @@ - Fixed the construction of the chiplets virtual table (#1514) (#1556) - Fixed the construction of the chiplets bus (#1516) (#1525) - Decorators are now allowed in empty basic blocks (#1466) +- Return an error if an instruction performs 2 memory accesses at the same memory address in the same cycle (#1561) ## 0.10.6 (2024-09-12) - `miden-processor` crate only diff --git a/Cargo.lock b/Cargo.lock index 0ecc546d00..5dc6cfb597 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,11 +82,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] @@ -163,7 +164,16 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "bit-vec", + "bit-vec 0.6.3", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", ] [[package]] @@ -172,6 +182,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -180,15 +196,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "blake3" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" dependencies = [ "arrayref", "arrayvec", @@ -214,12 +230,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "serde", ] @@ -243,9 +259,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.34" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "jobserver", "libc", @@ -287,9 +303,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -297,9 +313,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -309,9 +325,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", @@ -321,9 +337,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clipboard-win" @@ -375,9 +391,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -420,9 +436,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -439,9 +455,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -546,12 +562,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -574,9 +590,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fixedbitset" @@ -586,9 +602,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "float-cmp" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" dependencies = [ "num-traits", ] @@ -689,9 +705,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb949699c3e4df3a183b1d2142cb24277057055ed23c68ed58894f76c517223" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" dependencies = [ "cfg-if", "libc", @@ -729,9 +745,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "half" @@ -745,9 +761,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -775,9 +791,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", @@ -785,13 +801,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "3f187290c0ed3dfe3f7c85bedddd320949b68fc86ca0ceb71adfb05b3dc3af2a" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -826,9 +842,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -841,10 +857,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -864,7 +881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" dependencies = [ "ascii-canvas", - "bit-set", + "bit-set 0.5.3", "ena", "itertools 0.11.0", "lalrpop-util", @@ -892,9 +909,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.161" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libm" @@ -908,15 +925,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", ] [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "lock_api" @@ -930,9 +947,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" dependencies = [ "value-bag", ] @@ -990,7 +1007,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3572083504c43e14aec05447f8a3d57cce0f66d7a3c1b9058572eca4d70ab9" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block", "core-graphics-types", "foreign-types", @@ -1001,12 +1018,12 @@ dependencies = [ [[package]] name = "miden-air" -version = "0.11.0" +version = "0.12.0" dependencies = [ "criterion", "miden-core", - "miden-thiserror", "proptest", + "thiserror 2.0.11", "winter-air", "winter-prover", "winter-rand-utils", @@ -1014,25 +1031,25 @@ dependencies = [ [[package]] name = "miden-assembly" -version = "0.11.0" +version = "0.12.0" dependencies = [ "aho-corasick", "lalrpop", "lalrpop-util", "miden-core", "miden-miette", - "miden-thiserror", "pretty_assertions", "regex", "rustc_version 0.4.1", "smallvec", + "thiserror 2.0.11", "tracing", "unicode-width 0.2.0", ] [[package]] name = "miden-core" -version = "0.11.0" +version = "0.12.0" dependencies = [ "lock_api", "loom", @@ -1040,11 +1057,11 @@ dependencies = [ "miden-crypto", "miden-formatting", "miden-miette", - "miden-thiserror", "num-derive", "num-traits", "parking_lot", "proptest", + "thiserror 2.0.11", "winter-math", "winter-rand-utils", "winter-utils", @@ -1052,9 +1069,9 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50a68deed96cde1f51eb623f75828e320f699e0d798f11592f8958ba8b512c3" +checksum = "a06bf3ad2a85f3f8f0da73b6357c77e482b1ceb36cacda8a2d85caae3bd1f702" dependencies = [ "blake3", "cc", @@ -1064,6 +1081,7 @@ dependencies = [ "rand", "rand_core", "sha3", + "thiserror 2.0.11", "winter-crypto", "winter-math", "winter-utils", @@ -1080,9 +1098,9 @@ dependencies = [ [[package]] name = "miden-gpu" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271d375ea8bfdb0995f30d27d5eccc1468326e502e6ad6bdb0f9ee2d91ade50c" +checksum = "04742d5184bd76e7b92afcd9ca36b7c587a555d5fc4b27bb8f9d64ac57151f9c" dependencies = [ "metal", "once_cell", @@ -1091,9 +1109,9 @@ dependencies = [ [[package]] name = "miden-miette" -version = "7.1.1" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c532250422d933f15b148fb81e4522a5d649c178ab420d0d596c86228da35570" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" dependencies = [ "backtrace", "backtrace-ext", @@ -1102,7 +1120,6 @@ dependencies = [ "indenter", "lazy_static", "miden-miette-derive", - "miden-thiserror", "owo-colors", "regex", "rustc_version 0.2.3", @@ -1116,15 +1133,16 @@ dependencies = [ "syn", "terminal_size", "textwrap", + "thiserror 2.0.11", "trybuild", "unicode-width 0.1.14", ] [[package]] name = "miden-miette-derive" -version = "7.1.0" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cc759f0a2947acae217a2f32f722105cacc57d17d5f93bc16362142943a4edd" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" dependencies = [ "proc-macro2", "quote", @@ -1133,13 +1151,14 @@ dependencies = [ [[package]] name = "miden-processor" -version = "0.11.0" +version = "0.12.0" dependencies = [ "logtest", "miden-air", "miden-assembly", "miden-core", "miden-test-utils", + "thiserror 2.0.11", "tracing", "winter-fri", "winter-prover", @@ -1148,7 +1167,7 @@ dependencies = [ [[package]] name = "miden-prover" -version = "0.11.0" +version = "0.12.0" dependencies = [ "elsa", "miden-air", @@ -1162,12 +1181,13 @@ dependencies = [ [[package]] name = "miden-stdlib" -version = "0.11.0" +version = "0.12.0" dependencies = [ "blake3", "criterion", "miden-air", "miden-assembly", + "miden-core", "miden-processor", "miden-test-utils", "num", @@ -1198,39 +1218,20 @@ dependencies = [ "winter-rand-utils", ] -[[package]] -name = "miden-thiserror" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183ff8de338956ecfde3a38573241eb7a6f3d44d73866c210e5629c07fa00253" -dependencies = [ - "miden-thiserror-impl", -] - -[[package]] -name = "miden-thiserror-impl" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee4176a0f2e7d29d2a8ee7e60b6deb14ce67a20e94c3e2c7275cdb8804e1862" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "miden-verifier" -version = "0.11.0" +version = "0.12.0" dependencies = [ "miden-air", "miden-core", + "thiserror 2.0.11", "tracing", "winter-verifier", ] [[package]] name = "miden-vm" -version = "0.11.0" +version = "0.12.0" dependencies = [ "assert_cmd", "blake3", @@ -1255,14 +1256,15 @@ dependencies = [ "tracing", "tracing-forest", "tracing-subscriber", + "walkdir", "winter-fri", ] [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] @@ -1279,7 +1281,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "libc", ] @@ -1396,9 +1398,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -1477,9 +1479,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1538,9 +1540,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "predicates" -version = "3.1.2" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" dependencies = [ "anstyle", "difflib", @@ -1552,15 +1554,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" [[package]] name = "predicates-tree" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" dependencies = [ "predicates-core", "termtree", @@ -1578,22 +1580,22 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.6.0", + "bit-set 0.8.0", + "bit-vec 0.8.0", + "bitflags 2.8.0", "lazy_static", "num-traits", "rand", @@ -1613,9 +1615,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -1681,11 +1683,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -1696,7 +1698,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -1707,7 +1709,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -1722,9 +1724,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1764,27 +1766,27 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.23", + "semver 1.0.25", ] [[package]] name = "rustix" -version = "0.38.39" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rusty-fork" @@ -1804,7 +1806,7 @@ version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "clipboard-win", "libc", @@ -1855,9 +1857,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "semver-parser" @@ -1867,18 +1869,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -1887,9 +1889,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" dependencies = [ "itoa", "memchr", @@ -1987,9 +1989,9 @@ dependencies = [ [[package]] name = "strip-ansi-escapes" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" dependencies = [ "vte", ] @@ -2002,18 +2004,18 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "supports-color" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" dependencies = [ "is_ci", ] [[package]] name = "supports-hyperlinks" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee" +checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" [[package]] name = "supports-unicode" @@ -2023,9 +2025,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.87" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -2040,12 +2042,13 @@ checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" [[package]] name = "tempfile" -version = "3.13.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2083,9 +2086,9 @@ dependencies = [ [[package]] name = "termtree" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "test-case" @@ -2133,18 +2136,38 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", @@ -2216,9 +2239,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2227,9 +2250,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", @@ -2238,9 +2261,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -2254,7 +2277,7 @@ checksum = "ee40835db14ddd1e3ba414292272eddde9dad04d3d4b65509656414d1c42592f" dependencies = [ "ansi_term", "smallvec", - "thiserror", + "thiserror 1.0.69", "tracing", "tracing-subscriber", ] @@ -2272,9 +2295,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -2290,9 +2313,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dcd332a5496c026f1e14b7f3d2b7bd98e509660c04239c58b0ba38a12daded4" +checksum = "9f14b5c02a137632f68194ec657ecb92304138948e8957c932127eb1b58c23be" dependencies = [ "dissimilar", "glob", @@ -2318,9 +2341,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" @@ -2360,9 +2383,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-bag" @@ -2378,22 +2401,11 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vte" -version = "0.11.1" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" dependencies = [ - "utf8parse", - "vte_generate_state_changes", -] - -[[package]] -name = "vte_generate_state_changes" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" -dependencies = [ - "proc-macro2", - "quote", + "memchr", ] [[package]] @@ -2423,24 +2435,24 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -2449,9 +2461,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2459,9 +2471,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -2472,15 +2484,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -2590,15 +2605,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.59.0" @@ -2731,18 +2737,18 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] [[package]] name = "winter-air" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bec0b06b741543f43e3a6677b95b200d4cad2daab76e6721e14345345bfd0e" +checksum = "3a8fdb702503625f54dcaf9222aa2c7a0b2e868b3eb84b90d1837d68034bf999" dependencies = [ "libm", "winter-crypto", @@ -2753,9 +2759,9 @@ dependencies = [ [[package]] name = "winter-crypto" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "163da45f1d4d65cac361b8df4835a6daa95b3399154e16eb0305c178c6f6c1f4" +checksum = "67c57748fd2da77742be601f03eda639ff6046879738fd1faae86e80018263cb" dependencies = [ "blake3", "sha3", @@ -2765,9 +2771,9 @@ dependencies = [ [[package]] name = "winter-fri" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7b394670d68979a4cc21a37a95ef8ef350cf84be9256c53effe3052df50d26" +checksum = "3f9f999bc248c6254138b627035cb6d2c319580eb37dadcab6672298dbf00e41" dependencies = [ "winter-crypto", "winter-math", @@ -2776,18 +2782,18 @@ dependencies = [ [[package]] name = "winter-math" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8ba832121679e79b004b0003018c85873956d742a39c348c247f680fe15e00" +checksum = "6020c17839fa107ce4a7cc178e407ebbc24adfac1980f4fa2111198e052700ab" dependencies = [ "winter-utils", ] [[package]] name = "winter-maybe-async" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be43529f43f70306437d2c2c9f9e2b3a4d39b42e86702d8d7577f2357ea32fa6" +checksum = "91ce144fde121b98523bb8a6c15a311773e1d534d33c1cb47f5580bba9cff8e7" dependencies = [ "quote", "syn", @@ -2795,9 +2801,9 @@ dependencies = [ [[package]] name = "winter-prover" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f55f0153d26691caaf969066a13a824bcf3c98719d71b0f569bf8dc40a06fb9" +checksum = "b6c2f3cf80955f084fd614c86883195331116b6c96dc88532d43d6836bd7adee" dependencies = [ "tracing", "winter-air", @@ -2810,9 +2816,9 @@ dependencies = [ [[package]] name = "winter-rand-utils" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a7616d11fcc26552dada45c803a884ac97c253218835b83a2c63e1c2a988639" +checksum = "226e4c455f6eb72f64ac6eeb7642df25e21ff2280a4f6b09db75392ad6b390ef" dependencies = [ "rand", "winter-utils", @@ -2820,18 +2826,18 @@ dependencies = [ [[package]] name = "winter-utils" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b116c8ade0172506f8bda32dc674cf6b230adc8516e5138a0173ae69158a4f" +checksum = "1507ef312ea5569d54c2c7446a18b82143eb2a2e21f5c3ec7cfbe8200c03bd7c" dependencies = [ "rayon", ] [[package]] name = "winter-verifier" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ae1648768f96f5e6321a48a5bff5cc3101d2e51b23a6a095c6c9c9e133ecb61" +checksum = "56d949eab6c26328733477ec56ca054fdc69c0c1b8267fa03d8b618ffe2193c4" dependencies = [ "winter-air", "winter-crypto", diff --git a/Cargo.toml b/Cargo.toml index 30d5cdbdff..c043adbf7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,6 @@ inherits = "release" debug = true debug-assertions = true overflow-checks = true + +[workspace.dependencies] +thiserror = { version = "2.0", default-features = false } diff --git a/LICENSE b/LICENSE index 29e2918f33..6355ccf436 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Polygon (previously Matic) +Copyright (c) 2025 Polygon (previously Matic) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 7a60dc155a..92636ec556 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DEBUG_ASSERTIONS=RUSTFLAGS="-C debug-assertions" FEATURES_CONCURRENT_EXEC=--features concurrent,executable FEATURES_LOG_TREE=--features concurrent,executable,tracing-forest FEATURES_METAL_EXEC=--features concurrent,executable,metal -ALL_FEATURES_BUT_ASYNC=--features concurrent,executable,metal,testing,with-debug-info +ALL_FEATURES_BUT_ASYNC=--features concurrent,executable,metal,testing,with-debug-info,internal # -- linting -------------------------------------------------------------------------------------- @@ -116,4 +116,4 @@ exec-info: ## Builds an executable with log tree enabled .PHONY: bench bench: ## Runs benchmarks - cargo bench --profile optimized + cargo bench --profile optimized --features internal diff --git a/README.md b/README.md index 18a9782b2d..b745b6312b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Miden VM is a zero-knowledge virtual machine written in Rust. For any program ex ### Status and features -Miden VM is currently on release v0.11. In this release, most of the core features of the VM have been stabilized, and most of the STARK proof generation has been implemented. While we expect to keep making changes to the VM internals, the external interfaces should remain relatively stable, and we will do our best to minimize the amount of breaking changes going forward. +Miden VM is currently on release v0.12. In this release, most of the core features of the VM have been stabilized, and most of the STARK proof generation has been implemented. While we expect to keep making changes to the VM internals, the external interfaces should remain relatively stable, and we will do our best to minimize the amount of breaking changes going forward. The next version of the VM is being developed in the [next](https://github.com/0xPolygonMiden/miden-vm/tree/next) branch. There is also a documentation for the latest features and changes in the next branch [documentation next branch](https://0xpolygonmiden.github.io/miden-vm/intro/main.html). diff --git a/air/Cargo.toml b/air/Cargo.toml index ad70f6b999..924a0e5d56 100644 --- a/air/Cargo.toml +++ b/air/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "miden-air" -version = "0.11.0" +version = "0.12.0" description = "Algebraic intermediate representation of Miden VM processor" -documentation = "https://docs.rs/miden-air/0.11.0" +documentation = "https://docs.rs/miden-air/0.12.0" readme = "README.md" categories = ["cryptography", "no-std"] keywords = ["air", "arithmetization", "crypto", "miden"] @@ -31,12 +31,12 @@ std = ["vm-core/std", "winter-air/std", "thiserror/std"] testing = [] [dependencies] -thiserror = { package = "miden-thiserror", version = "1.0", default-features = false } -vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false } -winter-air = { package = "winter-air", version = "0.10", default-features = false } -winter-prover = { package = "winter-prover", version = "0.10", default-features = false } +thiserror = { workspace = true } +vm-core = { package = "miden-core", path = "../core", version = "0.12", default-features = false } +winter-air = { package = "winter-air", version = "0.11", default-features = false } +winter-prover = { package = "winter-prover", version = "0.11", default-features = false } [dev-dependencies] criterion = "0.5" proptest = "1.5" -rand-utils = { package = "winter-rand-utils", version = "0.10" } +rand-utils = { package = "winter-rand-utils", version = "0.11" } diff --git a/air/src/constraints/chiplets/memory/mod.rs b/air/src/constraints/chiplets/memory/mod.rs index 1f1289be8e..d4c93c1bc7 100644 --- a/air/src/constraints/chiplets/memory/mod.rs +++ b/air/src/constraints/chiplets/memory/mod.rs @@ -5,9 +5,10 @@ use winter_air::TransitionConstraintDegree; use super::{EvaluationFrame, FieldElement}; use crate::{ trace::chiplets::{ - memory::NUM_ELEMENTS, MEMORY_ADDR_COL_IDX, MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, - MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX, MEMORY_D_INV_COL_IDX, MEMORY_TRACE_OFFSET, - MEMORY_V_COL_RANGE, + MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX, + MEMORY_D_INV_COL_IDX, MEMORY_FLAG_SAME_CONTEXT_AND_WORD, MEMORY_IDX0_COL_IDX, + MEMORY_IDX1_COL_IDX, MEMORY_IS_READ_COL_IDX, MEMORY_IS_WORD_ACCESS_COL_IDX, + MEMORY_V_COL_RANGE, MEMORY_WORD_COL_IDX, }, utils::{binary_not, is_binary, EvaluationResult}, }; @@ -19,16 +20,16 @@ mod tests; // ================================================================================================ /// The number of constraints on the management of the memory chiplet. -pub const NUM_CONSTRAINTS: usize = 17; +pub const NUM_CONSTRAINTS: usize = 18; /// The degrees of constraints on the management of the memory chiplet. All constraint degrees are /// increased by 3 due to the selectors for the memory chiplet. pub const CONSTRAINT_DEGREES: [usize; NUM_CONSTRAINTS] = [ - 5, 5, // Enforce that the memory selectors are binary. - 9, 8, // Enforce s1 is set to 1 when reading existing memory and 0 otherwise. + 5, 5, 5, 5, // Enforce that rw, ew, idx0 and idx1 are binary. 7, 6, 9, 8, // Constrain the values in the d inverse column. - 8, // Enforce values in ctx, addr, clk transition correctly. - 6, 6, 6, 6, // Enforce correct memory initialization when reading from new memory. - 5, 5, 5, 5, // Enforce correct memory copy when reading from existing memory + 8, // Enforce values in ctx, word, clk transition correctly. + 7, // Enforce the correct value for the f_scw flag. + 9, 9, 9, 9, // Constrain the values in the first row of the chiplet. + 9, 9, 9, 9, // Constrain the values in all rows of the chiplet except the first. ]; // MEMORY TRANSITION CONSTRAINTS @@ -48,56 +49,52 @@ pub fn get_transition_constraint_count() -> usize { } /// Enforces constraints for the memory chiplet. +/// +/// The flags are: +/// - `memory_flag_all_rows`: a flag that is set to 1 when the current row is part of the memory +/// chiplet, +/// - `memory_flag_not_last_row`: a flag that is set to 1 when the current row is part of the memory +/// chiplet, but excludes the last row of the chiplet, +/// - `memory_flag_first_row`: a flag that is set to 1 when the *next* row is the first row of the +/// memory chiplet. pub fn enforce_constraints( frame: &EvaluationFrame, result: &mut [E], - memory_flag: E, + memory_flag_all_rows: E, + memory_flag_not_last_row: E, + memory_flag_first_row: E, ) { - // Constrain the operation selectors. - let mut index = enforce_selectors(frame, result, memory_flag); + // Constrain the binary columns. + let mut index = enforce_binary_columns(frame, result, memory_flag_all_rows); // Constrain the values in the d inverse column. - index += enforce_d_inv(frame, &mut result[index..], memory_flag); + index += enforce_d_inv(frame, &mut result[index..], memory_flag_not_last_row); + + // Enforce values in ctx, word_addr, clk transition correctly. + index += enforce_delta(frame, &mut result[index..], memory_flag_not_last_row); - // Enforce values in ctx, addr, clk transition correctly. - index += enforce_delta(frame, &mut result[index..], memory_flag); + // Enforce the correct value for the f_scw flag. + index += + enforce_flag_same_context_and_word(frame, &mut result[index..], memory_flag_not_last_row); // Constrain the memory values. - enforce_values(frame, &mut result[index..], memory_flag); + enforce_values(frame, &mut result[index..], memory_flag_not_last_row, memory_flag_first_row); } // TRANSITION CONSTRAINT HELPERS // ================================================================================================ -fn enforce_selectors( +fn enforce_binary_columns( frame: &EvaluationFrame, result: &mut [E], memory_flag: E, ) -> usize { - let mut index = 0; - - // s0 and s1 are binary. - result[index] = memory_flag * is_binary(frame.selector(0)); - index += 1; - result[index] = memory_flag * is_binary(frame.selector(1)); - index += 1; - - // s1 is set to 1 when existing memory is being read. this happens when ctx and addr haven't - // changed, and the next operation is a read (s0 is set). - result[index] = memory_flag - * frame.reaccess_flag() - * frame.selector_next(0) - * binary_not(frame.selector_next(1)); - index += 1; - - // s1 is set to 0 in all other cases. this happens when ctx changed, or ctx stayed the same but - // addr changed, or the operation was a write. - result[index] = memory_flag - * (frame.n0() + frame.not_n0() * frame.n1() + binary_not(frame.selector_next(0))) - * frame.selector_next(1); - index += 1; - - index + result[0] = memory_flag * is_binary(frame.is_read()); + result[1] = memory_flag * is_binary(frame.is_word_access()); + result[2] = memory_flag * is_binary(frame.idx0()); + result[3] = memory_flag * is_binary(frame.idx1()); + + 4 } /// A constraint evaluation function to enforce that the `d_inv` "delta inverse" column used to @@ -106,14 +103,19 @@ fn enforce_selectors( fn enforce_d_inv( frame: &EvaluationFrame, result: &mut [E], - memory_flag: E, + memory_flag_not_last_row: E, ) -> usize { let constraint_count = 4; - result.agg_constraint(0, memory_flag, is_binary(frame.n0())); - result.agg_constraint(1, memory_flag * frame.not_n0(), frame.ctx_change()); - result.agg_constraint(2, memory_flag * frame.not_n0(), is_binary(frame.n1())); - result.agg_constraint(3, memory_flag * frame.reaccess_flag(), frame.addr_change()); + // n0 is binary + result[0] = memory_flag_not_last_row * is_binary(frame.n0()); + // when the context changes, n0 should be set to 1. + result[1] = memory_flag_not_last_row * frame.not_n0() * frame.ctx_change(); + // when n0 is 0, n1 is binary. + result[2] = memory_flag_not_last_row * frame.not_n0() * is_binary(frame.n1()); + // when n0 and n1 are 0, then `word_addr` doesn't change. + result[3] = + memory_flag_not_last_row * frame.not_n0() * frame.not_n1() * frame.word_addr_change(); constraint_count } @@ -123,47 +125,111 @@ fn enforce_d_inv( fn enforce_delta( frame: &EvaluationFrame, result: &mut [E], - memory_flag: E, + memory_flag_not_last_row: E, ) -> usize { let constraint_count = 1; // If the context changed, include the difference. - result.agg_constraint(0, memory_flag * frame.n0(), frame.ctx_change()); - // If the context is the same, include the address difference if it changed or else include the - // clock change. + result[0] = memory_flag_not_last_row * frame.n0() * frame.ctx_change(); + // If the context is the same, include the word address difference if it changed or else include + // the clock change. result.agg_constraint( 0, - memory_flag * frame.not_n0(), - frame.n1() * frame.addr_change() + frame.not_n1() * frame.clk_change(), + memory_flag_not_last_row * frame.not_n0(), + frame.n1() * frame.word_addr_change() + frame.not_n1() * frame.clk_change(), ); // Always subtract the delta. It should offset the other changes. - result[0] -= memory_flag * frame.delta_next(); + result[0] -= memory_flag_not_last_row * frame.delta_next(); constraint_count } +/// A constraint evaluation function to enforce that the `f_scw` flag is set to 1 when the next row +/// is in the same context and word, and 0 otherwise. +fn enforce_flag_same_context_and_word( + frame: &EvaluationFrame, + result: &mut [E], + memory_flag_not_last_row: E, +) -> usize { + result[0] = memory_flag_not_last_row * (frame.f_scw_next() - frame.not_n0() * frame.not_n1()); + + 1 +} + /// A constraint evaluation function to enforce that memory is initialized to zero when it is read /// before being written and that when existing memory values are read they remain unchanged. +/// +/// The constraints on the values depend on a few factors: +/// - When in the first row of a new context or word, any of the 4 values of the word that are not +/// written to must be set to 0. This is because the memory is initialized to 0 when a new context +/// or word is started. +/// - When we remain in the same context and word, then this is when we want to enforce the "memory +/// property" that what was previously written must be read. Therefore, the values that are not +/// being written need to be equal to the values in the previous row (i.e. were either previously +/// written or are still initialized to 0). fn enforce_values( frame: &EvaluationFrame, result: &mut [E], - memory_flag: E, + memory_flag_no_last: E, + memory_flag_first_row: E, ) -> usize { - let mut index = 0; - - // initialize memory to zero when reading from new context and address pair. - for i in 0..NUM_ELEMENTS { - result[index] = memory_flag * frame.init_read_flag() * frame.v(i); - index += 1; - } - - // copy previous values when reading memory that was previously accessed. - for i in 0..NUM_ELEMENTS { - result[index] = memory_flag * frame.copy_read_flag() * (frame.v_next(i) - frame.v(i)); - index += 1; - } - - index + // c_i is set to 1 when `v'[i]` is not written to, and 0 otherwise. + // + // In other words, c_i is set to 1 when `v'[i]` needs to be constrained (to either 0 or `v[i]`). + // + // Note that `c_i` only uses values in the "next" row. This is because it must be used to + // constrain the first row of the memory chiplet, where that row sits in the "next" position of + // the frame, and the "current" row belongs to the previous chiplet (and hence the "current" row + // must not be accessed). + // + // As a result, `c_i` does not include the constraint of being in the memory chiplet, or in the + // same context and word - these must be enforced separately. + let (c0, c1, c2, c3) = { + // intuition: the i'th `f` flag is set to 1 when `i == 2 * idx1 + idx0` + let f0 = binary_not(frame.idx1_next()) * binary_not(frame.idx0_next()); + let f1 = binary_not(frame.idx1_next()) * frame.idx0_next(); + let f2 = frame.idx1_next() * binary_not(frame.idx0_next()); + let f3 = frame.idx1_next() * frame.idx0_next(); + + let c_i = |f_i| { + // z_i is set to 1 when we are operating on elements but not the i-th element + let z_i = binary_not(frame.is_word_access_next()) * binary_not(f_i); + let is_read_next = frame.is_read_next(); + + is_read_next + binary_not(is_read_next) * z_i + }; + + (c_i(f0), c_i(f1), c_i(f2), c_i(f3)) + }; + + // first row constraints: when row' is the first row, and v'[i] is not written to, then v'[i] + // must be 0. + result[0] = memory_flag_first_row * c0 * frame.v_next(0); + result[1] = memory_flag_first_row * c1 * frame.v_next(1); + result[2] = memory_flag_first_row * c2 * frame.v_next(2); + result[3] = memory_flag_first_row * c3 * frame.v_next(3); + + // non-first row constraints: if v[i] is not written to, + // - (f_scw' = 1) then its value needs to be copied over from the previous row, + // - (f_scw' = 0) then its value needs to be set to 0. + result[4] = memory_flag_no_last + * c0 + * (frame.f_scw_next() * (frame.v_next(0) - frame.v(0)) + + binary_not(frame.f_scw_next()) * frame.v_next(0)); + result[5] = memory_flag_no_last + * c1 + * (frame.f_scw_next() * (frame.v_next(1) - frame.v(1)) + + binary_not(frame.f_scw_next()) * frame.v_next(1)); + result[6] = memory_flag_no_last + * c2 + * (frame.f_scw_next() * (frame.v_next(2) - frame.v(2)) + + binary_not(frame.f_scw_next()) * frame.v_next(2)); + result[7] = memory_flag_no_last + * c3 + * (frame.f_scw_next() * (frame.v_next(3) - frame.v(3)) + + binary_not(frame.f_scw_next()) * frame.v_next(3)); + + 8 } // MEMORY FRAME EXTENSION TRAIT @@ -174,22 +240,30 @@ fn enforce_values( trait EvaluationFrameExt { // --- Column accessors ----------------------------------------------------------------------- - /// Gets the value of the specified selector column in the current row. - fn selector(&self, idx: usize) -> E; - /// Gets the value of the specified selector column in the next row. - fn selector_next(&self, idx: usize) -> E; - /// The current context value. - #[allow(dead_code)] - fn ctx(&self) -> E; - /// The current address. - #[allow(dead_code)] - fn addr(&self) -> E; - /// The current clock cycle. - #[allow(dead_code)] - fn clk(&self) -> E; - /// The next clock cycle. - #[allow(dead_code)] - fn clk_next(&self) -> E; + /// The value of the read/write column in the current row. + /// + /// 0: write, 1: read + fn is_read(&self) -> E; + /// The value of the read/write column in the next row. + /// + /// 0: write, 1: read + fn is_read_next(&self) -> E; + /// The value of the element/word column in the current row. + /// + /// 0: element, 1: word + fn is_word_access(&self) -> E; + /// The value of the element/word column in the next row. + /// + /// 0: element, 1: word + fn is_word_access_next(&self) -> E; + /// The 0'th bit of the index of the memory address in the current word. + fn idx0(&self) -> E; + /// The 0'th bit of the index of the memory address in the next word. + fn idx0_next(&self) -> E; + /// The 1st bit of the index of the memory address in the current word. + fn idx1(&self) -> E; + /// The 1st bit of the index of the memory address in the next word. + fn idx1_next(&self) -> E; /// The value from the specified index of the values (0, 1, 2, 3) in the current row. fn v(&self, index: usize) -> E; /// The value from the specified index of the values (0, 1, 2, 3) in the next row. @@ -203,6 +277,10 @@ trait EvaluationFrameExt { /// The next value of the column tracking the inverse delta used for constraint evaluations. fn d_inv_next(&self) -> E; + // The flag that indicates whether the next row is in the same word and context as the current + // row. + fn f_scw_next(&self) -> E; + // --- Intermediate variables & helpers ------------------------------------------------------- /// The change between the current value in the specified column and the next value, calculated @@ -220,59 +298,55 @@ trait EvaluationFrameExt { fn not_n1(&self) -> E; /// The difference between the next context and the current context. fn ctx_change(&self) -> E; - /// The difference between the next address and the current address. - fn addr_change(&self) -> E; + /// The difference between the next word address and the current word address. + fn word_addr_change(&self) -> E; /// The difference between the next clock value and the current one, minus 1. fn clk_change(&self) -> E; /// The delta between two consecutive context IDs, addresses, or clock cycles. fn delta_next(&self) -> E; - - // --- Flags ---------------------------------------------------------------------------------- - - /// A flag to indicate that previously assigned memory is being accessed. In other words, the - /// context and address have not changed. - fn reaccess_flag(&self) -> E; - - /// A flag to indicate that there is a read in the current row which requires the values to be - /// initialized to zero. - fn init_read_flag(&self) -> E; - - /// A flag to indicate that the operation in the next row is a read which requires copying the - /// values from the current row to the next row. - fn copy_read_flag(&self) -> E; } impl EvaluationFrameExt for &EvaluationFrame { // --- Column accessors ----------------------------------------------------------------------- #[inline(always)] - fn selector(&self, idx: usize) -> E { - self.current()[MEMORY_TRACE_OFFSET + idx] + fn is_read(&self) -> E { + self.current()[MEMORY_IS_READ_COL_IDX] + } + + #[inline(always)] + fn is_read_next(&self) -> E { + self.next()[MEMORY_IS_READ_COL_IDX] + } + + #[inline(always)] + fn is_word_access(&self) -> E { + self.current()[MEMORY_IS_WORD_ACCESS_COL_IDX] } #[inline(always)] - fn selector_next(&self, idx: usize) -> E { - self.next()[MEMORY_TRACE_OFFSET + idx] + fn is_word_access_next(&self) -> E { + self.next()[MEMORY_IS_WORD_ACCESS_COL_IDX] } #[inline(always)] - fn ctx(&self) -> E { - self.current()[MEMORY_CTX_COL_IDX] + fn idx0(&self) -> E { + self.current()[MEMORY_IDX0_COL_IDX] } #[inline(always)] - fn addr(&self) -> E { - self.next()[MEMORY_ADDR_COL_IDX] + fn idx0_next(&self) -> E { + self.next()[MEMORY_IDX0_COL_IDX] } #[inline(always)] - fn clk(&self) -> E { - self.current()[MEMORY_CLK_COL_IDX] + fn idx1(&self) -> E { + self.current()[MEMORY_IDX1_COL_IDX] } #[inline(always)] - fn clk_next(&self) -> E { - self.next()[MEMORY_CLK_COL_IDX] + fn idx1_next(&self) -> E { + self.next()[MEMORY_IDX1_COL_IDX] } #[inline(always)] @@ -300,6 +374,11 @@ impl EvaluationFrameExt for &EvaluationFrame { self.next()[MEMORY_D_INV_COL_IDX] } + #[inline(always)] + fn f_scw_next(&self) -> E { + self.next()[MEMORY_FLAG_SAME_CONTEXT_AND_WORD] + } + // --- Intermediate variables & helpers ------------------------------------------------------- #[inline(always)] @@ -319,7 +398,7 @@ impl EvaluationFrameExt for &EvaluationFrame { #[inline(always)] fn n1(&self) -> E { - self.change(MEMORY_ADDR_COL_IDX) * self.d_inv_next() + self.change(MEMORY_WORD_COL_IDX) * self.d_inv_next() } #[inline(always)] @@ -333,8 +412,8 @@ impl EvaluationFrameExt for &EvaluationFrame { } #[inline(always)] - fn addr_change(&self) -> E { - self.change(MEMORY_ADDR_COL_IDX) + fn word_addr_change(&self) -> E { + self.change(MEMORY_WORD_COL_IDX) } #[inline(always)] @@ -346,21 +425,4 @@ impl EvaluationFrameExt for &EvaluationFrame { fn delta_next(&self) -> E { E::from(2_u32.pow(16)) * self.d1_next() + self.d0_next() } - - // --- Flags ---------------------------------------------------------------------------------- - - #[inline(always)] - fn reaccess_flag(&self) -> E { - self.not_n0() * self.not_n1() - } - - #[inline(always)] - fn init_read_flag(&self) -> E { - self.selector(0) * binary_not(self.selector(1)) - } - - #[inline(always)] - fn copy_read_flag(&self) -> E { - self.selector_next(1) - } } diff --git a/air/src/constraints/chiplets/memory/tests.rs b/air/src/constraints/chiplets/memory/tests.rs index cdb34b117d..fb0cacf934 100644 --- a/air/src/constraints/chiplets/memory/tests.rs +++ b/air/src/constraints/chiplets/memory/tests.rs @@ -1,21 +1,23 @@ use alloc::vec::Vec; use rand_utils::rand_value; +use vm_core::{Felt, FieldElement, WORD_SIZE}; use super::{ - EvaluationFrame, MEMORY_ADDR_COL_IDX, MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, - MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX, MEMORY_D_INV_COL_IDX, MEMORY_V_COL_RANGE, NUM_ELEMENTS, + EvaluationFrame, MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, MEMORY_D0_COL_IDX, MEMORY_D1_COL_IDX, + MEMORY_D_INV_COL_IDX, MEMORY_V_COL_RANGE, MEMORY_WORD_COL_IDX, }; use crate::{ chiplets::memory, trace::{ chiplets::{ - memory::{Selectors, MEMORY_COPY_READ, MEMORY_INIT_READ, MEMORY_WRITE}, - MEMORY_TRACE_OFFSET, + memory::{MEMORY_ACCESS_WORD, MEMORY_READ, MEMORY_WRITE}, + MEMORY_FLAG_SAME_CONTEXT_AND_WORD, MEMORY_IDX0_COL_IDX, MEMORY_IDX1_COL_IDX, + MEMORY_IS_READ_COL_IDX, MEMORY_IS_WORD_ACCESS_COL_IDX, }, TRACE_WIDTH, }, - Felt, FieldElement, ONE, ZERO, + ONE, ZERO, }; // UNIT TESTS @@ -23,37 +25,25 @@ use crate::{ #[test] fn test_memory_write() { - let expected = [ZERO; memory::NUM_CONSTRAINTS]; + let expected_constraint_evals = [ZERO; memory::NUM_CONSTRAINTS]; - let old_values = vec![0, 0, 0, 0]; - let new_values = vec![1, 0, 0, 0]; + let old_word = vec![0, 0, 0, 0]; + let new_word = vec![1, 0, 0, 0]; // Write to a new context. - let result = get_constraint_evaluation( - MEMORY_WRITE, - MemoryTestDeltaType::Context, - &old_values, - &new_values, - ); - assert_eq!(expected, result); + let result = + get_constraint_evaluation(MEMORY_WRITE, MemoryTestDeltaType::Context, &old_word, &new_word); + assert_eq!(expected_constraint_evals, result); // Write to a new address in the same context. - let result = get_constraint_evaluation( - MEMORY_WRITE, - MemoryTestDeltaType::Address, - &old_values, - &new_values, - ); - assert_eq!(expected, result); + let result = + get_constraint_evaluation(MEMORY_WRITE, MemoryTestDeltaType::Word, &old_word, &new_word); + assert_eq!(expected_constraint_evals, result); // Write to the same context and address at a new clock cycle. - let result = get_constraint_evaluation( - MEMORY_WRITE, - MemoryTestDeltaType::Clock, - &old_values, - &new_values, - ); - assert_eq!(expected, result); + let result = + get_constraint_evaluation(MEMORY_WRITE, MemoryTestDeltaType::Clock, &old_word, &new_word); + assert_eq!(expected_constraint_evals, result); } #[test] @@ -65,7 +55,7 @@ fn test_memory_read() { // Read from a new context. let result = get_constraint_evaluation( - MEMORY_INIT_READ, + MEMORY_READ, MemoryTestDeltaType::Context, &old_values, &init_values, @@ -74,8 +64,8 @@ fn test_memory_read() { // Read from a new address in the same context. let result = get_constraint_evaluation( - MEMORY_INIT_READ, - MemoryTestDeltaType::Address, + MEMORY_READ, + MemoryTestDeltaType::Word, &old_values, &init_values, ); @@ -83,7 +73,7 @@ fn test_memory_read() { // Read from the same context and address at a new clock cycle. let result = get_constraint_evaluation( - MEMORY_COPY_READ, + MEMORY_READ, MemoryTestDeltaType::Clock, &old_values, &old_values, @@ -100,7 +90,7 @@ fn test_memory_read() { /// - Clock: when the delta occurs in the clock column, context and address must stay fixed. enum MemoryTestDeltaType { Context, - Address, + Word, Clock, } @@ -113,17 +103,17 @@ enum MemoryTestDeltaType { /// - To test a valid read, the `delta_type` must be Clock and the `old_values` and `new_values` /// must be equal. fn get_constraint_evaluation( - selectors: Selectors, + read_write: Felt, delta_type: MemoryTestDeltaType, old_values: &[u32], new_values: &[u32], ) -> [Felt; memory::NUM_CONSTRAINTS] { let delta_row = get_test_delta_row(&delta_type); - let frame = get_test_frame(selectors, &delta_type, &delta_row, old_values, new_values); + let frame = get_test_frame(read_write, &delta_type, &delta_row, old_values, new_values); let mut result = [ZERO; memory::NUM_CONSTRAINTS]; - memory::enforce_constraints(&frame, &mut result, ONE); + memory::enforce_constraints(&frame, &mut result, ONE, ONE, ZERO); result } @@ -141,7 +131,7 @@ fn get_constraint_evaluation( /// row. /// - `new_values`: specifies the new values, which are placed in the value columns of the next row. fn get_test_frame( - selectors: Selectors, + read_write: Felt, delta_type: &MemoryTestDeltaType, delta_row: &[u64], old_values: &[u32], @@ -151,16 +141,16 @@ fn get_test_frame( let mut next = vec![ZERO; TRACE_WIDTH]; // Set the operation in the next row. - next[MEMORY_TRACE_OFFSET] = selectors[0]; - next[MEMORY_TRACE_OFFSET + 1] = selectors[1]; + next[MEMORY_IS_READ_COL_IDX] = read_write; + next[MEMORY_IS_WORD_ACCESS_COL_IDX] = MEMORY_ACCESS_WORD; // Set the context, addr, and clock columns in the next row to the values in the delta row. next[MEMORY_CTX_COL_IDX] = Felt::new(delta_row[0]); - next[MEMORY_ADDR_COL_IDX] = Felt::new(delta_row[1]); + next[MEMORY_WORD_COL_IDX] = Felt::new(delta_row[1]); next[MEMORY_CLK_COL_IDX] = Felt::new(delta_row[2]); // Set the old and new values. - for idx in 0..NUM_ELEMENTS { + for idx in 0..WORD_SIZE { let old_value = Felt::new(old_values[idx] as u64); // Add a write for the old values to the current row. current[MEMORY_V_COL_RANGE.start + idx] = old_value; @@ -177,27 +167,41 @@ fn get_test_frame( let delta: u64 = match delta_type { MemoryTestDeltaType::Clock => delta_row[MemoryTestDeltaType::Clock as usize] - 1, MemoryTestDeltaType::Context => delta_row[MemoryTestDeltaType::Context as usize], - MemoryTestDeltaType::Address => delta_row[MemoryTestDeltaType::Address as usize], + MemoryTestDeltaType::Word => delta_row[MemoryTestDeltaType::Word as usize], }; next[MEMORY_D0_COL_IDX] = Felt::new(delta as u16 as u64); next[MEMORY_D1_COL_IDX] = Felt::new(delta >> 16); next[MEMORY_D_INV_COL_IDX] = (Felt::new(delta)).inv(); + // since we're always writing a word, the idx0 and idx1 columns should be zero + next[MEMORY_IDX0_COL_IDX] = ZERO; + next[MEMORY_IDX1_COL_IDX] = ZERO; + + // If the context or word columns are changed, the "same context and word" flag should be + // zero. + if delta_row[MemoryTestDeltaType::Word as usize] > 0 + || delta_row[MemoryTestDeltaType::Context as usize] > 0 + { + next[MEMORY_FLAG_SAME_CONTEXT_AND_WORD] = ZERO; + } else { + next[MEMORY_FLAG_SAME_CONTEXT_AND_WORD] = ONE; + } + EvaluationFrame::::from_rows(current, next) } -/// Generates a row of valid test values for the context, address, and clock columns according to +/// Generates a row of valid test values for the context, word, and clock columns according to /// the specified delta type, which determines the column over which the delta and delta inverse /// values of the trace would be calculated. /// -/// - When the delta type is Context, the address and clock columns can be anything. -/// - When the delta type is Address, the context must remain unchanged but the clock can change. -/// - When the delta type is Clock, both the context and address columns must remain unchanged. +/// - When the delta type is Context, the word and clock columns can be anything. +/// - When the delta type is Word, the context must remain unchanged but the clock can change. +/// - When the delta type is Clock, both the context and word columns must remain unchanged. fn get_test_delta_row(delta_type: &MemoryTestDeltaType) -> Vec { - let delta_value = rand_value::() as u64; + let delta_value = word_aligned_rand_value() as u64; let mut row = vec![0; 3]; let ctx_idx = MemoryTestDeltaType::Context as usize; - let addr_idx = MemoryTestDeltaType::Address as usize; + let word_addr_idx = MemoryTestDeltaType::Word as usize; let clk_idx = MemoryTestDeltaType::Clock as usize; // Set the context, addr, and clock columns according to the specified delta type. @@ -207,13 +211,13 @@ fn get_test_delta_row(delta_type: &MemoryTestDeltaType) -> Vec { row[ctx_idx] = delta_value; // Set addr and clock in the row column to random values. - row[addr_idx] = rand_value::() as u64; + row[word_addr_idx] = word_aligned_rand_value() as u64; row[clk_idx] = rand_value::() as u64; }, - MemoryTestDeltaType::Address => { + MemoryTestDeltaType::Word => { // Keep the context value the same in current and row rows (leave it as ZERO). // Set the row value for the address. - row[addr_idx] = delta_value; + row[word_addr_idx] = delta_value; // Set clock in the row column to a random value. row[clk_idx] = rand_value::() as u64; @@ -227,3 +231,9 @@ fn get_test_delta_row(delta_type: &MemoryTestDeltaType) -> Vec { row } + +/// Returns a random value that is divisible by 4 (i.e. "word aligned" when treated as an address). +fn word_aligned_rand_value() -> u32 { + let value = rand_value::(); + value - (value % 4) +} diff --git a/air/src/constraints/chiplets/mod.rs b/air/src/constraints/chiplets/mod.rs index ea950755fc..3ecbcae95d 100644 --- a/air/src/constraints/chiplets/mod.rs +++ b/air/src/constraints/chiplets/mod.rs @@ -87,7 +87,13 @@ pub fn enforce_constraints>( constraint_offset += bitwise::get_transition_constraint_count(); // memory transition constraints - memory::enforce_constraints(frame, &mut result[constraint_offset..], frame.memory_flag(false)); + memory::enforce_constraints( + frame, + &mut result[constraint_offset..], + frame.memory_flag(), + frame.memory_flag_not_last_row(), + frame.memory_flag_first_row(), + ); } // TRANSITION CONSTRAINT HELPERS @@ -143,12 +149,21 @@ trait EvaluationFrameExt { /// Flag to indicate whether the frame is in the bitwise portion of the Chiplets trace. fn bitwise_flag(&self) -> E; - /// Flag to indicate whether the frame is in the memory portion of the Chiplets trace. - /// When `include_last_row` is true, the memory flag is true for every row where the memory - /// selectors are set. When false, the last row is excluded. When this flag is used for - /// transition constraints with `include_last_row = false`, they will not be applied to the - /// final row of the memory trace. - fn memory_flag(&self, include_last_row: bool) -> E; + /// Flag to indicate whether the current row of the frame is in the memory portion of the + /// Chiplets trace. + fn memory_flag(&self) -> E; + + /// Flag to indicate whether the current row of the frame is in the memory portion of the + /// Chiplets trace, except for the last memory chiplet row. + fn memory_flag_not_last_row(&self) -> E; + + /// Flag to indicate whether the next row of the frame is in the memory portion of the Chiplets + /// trace. + fn memory_flag_next(&self) -> E; + + /// Flag to indicate whether the next row of the frame is the first row of the memory portion of + /// the Chiplets trace. + fn memory_flag_first_row(&self) -> E; } impl EvaluationFrameExt for &EvaluationFrame { @@ -175,12 +190,23 @@ impl EvaluationFrameExt for &EvaluationFrame { } #[inline(always)] - fn memory_flag(&self, include_last_row: bool) -> E { - if include_last_row { - self.s(0) * self.s(1) * binary_not(self.s(2)) - } else { - self.s(0) * self.s(1) * binary_not(self.s_next(2)) - } + fn memory_flag(&self) -> E { + self.s(0) * self.s(1) * binary_not(self.s(2)) + } + + #[inline(always)] + fn memory_flag_not_last_row(&self) -> E { + self.s(0) * self.s(1) * binary_not(self.s_next(2)) + } + + #[inline(always)] + fn memory_flag_next(&self) -> E { + self.s_next(0) * self.s_next(1) * binary_not(self.s_next(2)) + } + + #[inline(always)] + fn memory_flag_first_row(&self) -> E { + self.hasher_flag() * self.memory_flag_next() } } @@ -196,6 +222,6 @@ pub trait ChipletsFrameExt { impl ChipletsFrameExt for &EvaluationFrame { #[inline(always)] fn chiplets_memory_flag(&self) -> E { - self.memory_flag(true) + self.memory_flag() } } diff --git a/air/src/errors.rs b/air/src/errors.rs index af884215c3..03439cad4c 100644 --- a/air/src/errors.rs +++ b/air/src/errors.rs @@ -1,33 +1,12 @@ -use alloc::string::String; -use core::fmt::{Display, Formatter}; - use crate::trace::MIN_TRACE_LEN; -// EXECUTION ERROR +// EXECUTION OPTIONS ERROR // ================================================================================================ -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum ExecutionOptionsError { - ExpectedCyclesTooBig(u32, u32), + #[error("expected number of cycles {expected_cycles} must be smaller than the maximum number of cycles {max_cycles}")] + ExpectedCyclesTooBig { max_cycles: u32, expected_cycles: u32 }, + #[error("maximum number of cycles {0} must be greater than the minimum number of cycles {MIN_TRACE_LEN}")] MaxCycleNumTooSmall(u32), - OtherErrors(String), } - -impl Display for ExecutionOptionsError { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> { - use ExecutionOptionsError::*; - - match self { - ExpectedCyclesTooBig(max, expected) => { - write!(f, "The expected number of cycles must be smaller than the maximum number of cycles: maximum is {max}, but expectd is {expected}") - }, - MaxCycleNumTooSmall(max) => { - write!(f, "The maximum number of cycles must be greater than the minimum number of cycles: minimum is {MIN_TRACE_LEN}, but maximum is {max}") - }, - OtherErrors(error) => write!(f, "{error}"), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ExecutionOptionsError {} diff --git a/air/src/options.rs b/air/src/options.rs index fbbbbb31ce..bd1a5e6e56 100644 --- a/air/src/options.rs +++ b/air/src/options.rs @@ -199,7 +199,10 @@ impl ExecutionOptions { return Err(ExecutionOptionsError::MaxCycleNumTooSmall(expected_cycles)); } if max_cycles < expected_cycles { - return Err(ExecutionOptionsError::ExpectedCyclesTooBig(max_cycles, expected_cycles)); + return Err(ExecutionOptionsError::ExpectedCyclesTooBig { + max_cycles, + expected_cycles, + }); } // Round up the expected number of cycles to the next power of two. If it is smaller than diff --git a/air/src/trace/chiplets/memory.rs b/air/src/trace/chiplets/memory.rs index 6e531d30d1..f7e1af37ac 100644 --- a/air/src/trace/chiplets/memory.rs +++ b/air/src/trace/chiplets/memory.rs @@ -1,53 +1,63 @@ +use vm_core::WORD_SIZE; + use super::{create_range, Felt, Range, ONE, ZERO}; // CONSTANTS // ================================================================================================ /// Number of columns needed to record an execution trace of the memory chiplet. -pub const TRACE_WIDTH: usize = 12; +pub const TRACE_WIDTH: usize = 15; -/// Number of selector columns in the trace. -pub const NUM_SELECTORS: usize = 2; +// --- OPERATION SELECTORS ------------------------------------------------------------------------ -/// Type for Memory trace selectors. -/// -/// These selectors are used to define which operation and memory state update (init & read / copy & -/// read / write) is to be applied at a specific row of the memory execution trace. -pub type Selectors = [Felt; NUM_SELECTORS]; +/// Specifies the value of the `READ_WRITE` column when the operation is a write. +pub const MEMORY_WRITE: Felt = ZERO; +/// Specifies the value of the `READ_WRITE` column when the operation is a read. +pub const MEMORY_READ: Felt = ONE; +/// Specifies the value of the `ELEMENT_OR_WORD` column when the operation is over an element. +pub const MEMORY_ACCESS_ELEMENT: Felt = ZERO; +/// Specifies the value of the `ELEMENT_OR_WORD` column when the operation is over a word. +pub const MEMORY_ACCESS_WORD: Felt = ONE; -// --- OPERATION SELECTORS ------------------------------------------------------------------------ +// --- BUS LABELS ------------------------------------------------------------------------ -/// Specifies an operation that initializes new memory and then reads it. -pub const MEMORY_INIT_READ: Selectors = [ONE, ZERO]; +// All bus labels encode the chiplet selector (1, 1, 0), as well as the read/write and element/word +// columns. The purpose of the label is to force the chiplet to assign the correct values to the +// read/write and element/word columns. We also include the chiplet selector as a unique identifier +// for memory chiplet labels (to ensure they don't collide with labels from other chiplets). -/// Specifies an operation that copies existing memory and then reads it. -pub const MEMORY_COPY_READ: Selectors = [ONE, ONE]; +/// Unique label when r/w=0 and e/w=0. +pub const MEMORY_WRITE_ELEMENT_LABEL: u8 = 0b11000; -/// Specifies a memory write operation. -pub const MEMORY_WRITE: Selectors = [ZERO, ZERO]; +/// Unique label when r/w=0 and e/w=1. +pub const MEMORY_WRITE_WORD_LABEL: u8 = 0b11001; -/// Unique label computed as 1 plus the full chiplet selector with the bits reversed. -/// mem_read selector=[1, 1, 0, 1], rev(selector)=[1, 0, 1, 1], +1=[1, 1, 0, 0] -pub const MEMORY_READ_LABEL: u8 = 0b1100; +/// Unique label when r/w=1 and e/w=0. +pub const MEMORY_READ_ELEMENT_LABEL: u8 = 0b11010; -/// Unique label computed as 1 plus the full chiplet selector with the bits reversed. -/// mem_write selector=[1, 1, 0, 0] rev(selector)=[0, 0, 1, 1] +1=[0, 1, 0, 0] -pub const MEMORY_WRITE_LABEL: u8 = 0b0100; +/// Unique label when r/w=1 and e/w=1. +pub const MEMORY_READ_WORD_LABEL: u8 = 0b11011; // --- COLUMN ACCESSOR INDICES WITHIN THE CHIPLET ------------------------------------------------- -/// The number of elements accessible in one read or write memory access. -pub const NUM_ELEMENTS: usize = 4; - +/// Column to hold whether the operation is a read or write. +pub const IS_READ_COL_IDX: usize = 0; +/// Column to hold the whether the operation was over an element or a word. +pub const IS_WORD_ACCESS_COL_IDX: usize = IS_READ_COL_IDX + 1; /// Column to hold the context ID of the current memory context. -pub const CTX_COL_IDX: usize = NUM_SELECTORS; -/// Column to hold the memory address. -pub const ADDR_COL_IDX: usize = CTX_COL_IDX + 1; +pub const CTX_COL_IDX: usize = IS_WORD_ACCESS_COL_IDX + 1; +/// Column to hold the word (i.e. group of 4 memory slots, referred to by the address of the first +/// slot in the word). +pub const WORD_COL_IDX: usize = CTX_COL_IDX + 1; +/// Column to hold the first bit of the index of the address in the word. +pub const IDX0_COL_IDX: usize = WORD_COL_IDX + 1; +/// Column to hold the second bit of the index of the address in the word. +pub const IDX1_COL_IDX: usize = IDX0_COL_IDX + 1; /// Column for the clock cycle in which the memory operation occurred. -pub const CLK_COL_IDX: usize = ADDR_COL_IDX + 1; -/// Columns to hold the values stored at a given memory context, address, and clock cycle after -/// the memory operation. When reading from a new address, these are initialized to zero. -pub const V_COL_RANGE: Range = create_range(CLK_COL_IDX + 1, NUM_ELEMENTS); +pub const CLK_COL_IDX: usize = IDX1_COL_IDX + 1; +/// Columns to hold the values stored at a given memory context, word, and clock cycle after +/// the memory operation. When reading from a new word, these are initialized to zero. +pub const V_COL_RANGE: Range = create_range(CLK_COL_IDX + 1, WORD_SIZE); /// Column for the lower 16-bits of the delta between two consecutive context IDs, addresses, or /// clock cycles. pub const D0_COL_IDX: usize = V_COL_RANGE.end; @@ -57,3 +67,6 @@ pub const D1_COL_IDX: usize = D0_COL_IDX + 1; /// Column for the inverse of the delta between two consecutive context IDs, addresses, or clock /// cycles, used to enforce that changes are correctly constrained. pub const D_INV_COL_IDX: usize = D1_COL_IDX + 1; +/// Column to hold the flag indicating whether the current memory operation is in the same word and +/// same context as the previous operation. +pub const FLAG_SAME_CONTEXT_AND_WORD: usize = D_INV_COL_IDX + 1; diff --git a/air/src/trace/chiplets/mod.rs b/air/src/trace/chiplets/mod.rs index d892c67e36..5186254c0a 100644 --- a/air/src/trace/chiplets/mod.rs +++ b/air/src/trace/chiplets/mod.rs @@ -86,13 +86,19 @@ pub const BITWISE_OUTPUT_COL_IDX: usize = BITWISE_TRACE_OFFSET + bitwise::OUTPUT // --- GLOBALLY-INDEXED CHIPLET COLUMN ACCESSORS: MEMORY ------------------------------------------ -/// The index within the main trace of the column containing the first memory selector, which -/// indicates the operation (read or write). -pub const MEMORY_SELECTORS_COL_IDX: usize = MEMORY_TRACE_OFFSET; +/// The index within the main trace of the column containing the memory read/write column. +pub const MEMORY_IS_READ_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::IS_READ_COL_IDX; +/// The index within the main trace of the column containing the memory element/word column. +pub const MEMORY_IS_WORD_ACCESS_COL_IDX: usize = + MEMORY_TRACE_OFFSET + memory::IS_WORD_ACCESS_COL_IDX; /// The index within the main trace of the column containing the memory context. pub const MEMORY_CTX_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::CTX_COL_IDX; /// The index within the main trace of the column containing the memory address. -pub const MEMORY_ADDR_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::ADDR_COL_IDX; +pub const MEMORY_WORD_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::WORD_COL_IDX; +/// The index within the main trace of the column containing the 0'th memory index. +pub const MEMORY_IDX0_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::IDX0_COL_IDX; +/// The index within the main trace of the column containing the 1st memory index. +pub const MEMORY_IDX1_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::IDX1_COL_IDX; /// The index within the main trace of the column containing the clock cycle of the memory /// access. pub const MEMORY_CLK_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::CLK_COL_IDX; @@ -111,3 +117,7 @@ pub const MEMORY_D1_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::D1_COL_IDX; /// memory context IDs, addresses, or clock cycles, used to enforce that changes are correctly /// constrained. pub const MEMORY_D_INV_COL_IDX: usize = MEMORY_TRACE_OFFSET + memory::D_INV_COL_IDX; +/// Column to hold the flag indicating whether the current memory operation is in the same context +/// and same word as the previous operation. +pub const MEMORY_FLAG_SAME_CONTEXT_AND_WORD: usize = + MEMORY_TRACE_OFFSET + memory::FLAG_SAME_CONTEXT_AND_WORD; diff --git a/air/src/trace/main_trace.rs b/air/src/trace/main_trace.rs index eebc8e2779..9c0d101ff0 100644 --- a/air/src/trace/main_trace.rs +++ b/air/src/trace/main_trace.rs @@ -9,8 +9,8 @@ use super::{ chiplets::{ hasher::{DIGEST_LEN, HASH_CYCLE_LEN, STATE_WIDTH}, BITWISE_A_COL_IDX, BITWISE_B_COL_IDX, BITWISE_OUTPUT_COL_IDX, HASHER_NODE_INDEX_COL_IDX, - HASHER_STATE_COL_RANGE, MEMORY_ADDR_COL_IDX, MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, - MEMORY_V_COL_RANGE, + HASHER_STATE_COL_RANGE, MEMORY_CLK_COL_IDX, MEMORY_CTX_COL_IDX, MEMORY_IDX0_COL_IDX, + MEMORY_IDX1_COL_IDX, MEMORY_V_COL_RANGE, MEMORY_WORD_COL_IDX, }, decoder::{ GROUP_COUNT_COL_IDX, HASHER_STATE_OFFSET, IN_SPAN_COL_IDX, IS_CALL_FLAG_COL_IDX, @@ -371,8 +371,18 @@ impl MainTrace { } /// Returns the i-th row of the chiplet column containing memory address. - pub fn chiplet_memory_addr(&self, i: RowIndex) -> Felt { - self.columns.get_column(MEMORY_ADDR_COL_IDX)[i] + pub fn chiplet_memory_word(&self, i: RowIndex) -> Felt { + self.columns.get_column(MEMORY_WORD_COL_IDX)[i] + } + + /// Returns the i-th row of the chiplet column containing 0th bit of the word index. + pub fn chiplet_memory_idx0(&self, i: RowIndex) -> Felt { + self.columns.get_column(MEMORY_IDX0_COL_IDX)[i] + } + + /// Returns the i-th row of the chiplet column containing 1st bit of the word index. + pub fn chiplet_memory_idx1(&self, i: RowIndex) -> Felt { + self.columns.get_column(MEMORY_IDX1_COL_IDX)[i] } /// Returns the i-th row of the chiplet column containing clock cycle. diff --git a/air/src/trace/mod.rs b/air/src/trace/mod.rs index 62c7a910f5..6c2c109268 100644 --- a/air/src/trace/mod.rs +++ b/air/src/trace/mod.rs @@ -19,7 +19,7 @@ pub const MIN_TRACE_LEN: usize = 64; // ------------------------------------------------------------------------------------------------ // system decoder stack range checks chiplets -// (8 columns) (24 columns) (19 columns) (3 columns) (17 columns) +// (8 columns) (24 columns) (19 columns) (2 columns) (18 columns) // ├───────────────┴───────────────┴───────────────┴───────────────┴─────────────────┤ pub const SYS_TRACE_OFFSET: usize = 0; @@ -51,7 +51,7 @@ pub const RANGE_CHECK_TRACE_RANGE: Range = // Chiplets trace pub const CHIPLETS_OFFSET: usize = RANGE_CHECK_TRACE_RANGE.end; -pub const CHIPLETS_WIDTH: usize = 17; +pub const CHIPLETS_WIDTH: usize = 18; pub const CHIPLETS_RANGE: Range = range(CHIPLETS_OFFSET, CHIPLETS_WIDTH); pub const TRACE_WIDTH: usize = CHIPLETS_OFFSET + CHIPLETS_WIDTH; diff --git a/air/src/trace/rows.rs b/air/src/trace/rows.rs index 25f1f9e338..8f3a9a22a2 100644 --- a/air/src/trace/rows.rs +++ b/air/src/trace/rows.rs @@ -1,3 +1,4 @@ +use alloc::boxed::Box; use core::{ fmt::{Display, Formatter}, ops::{Add, AddAssign, Bound, Index, IndexMut, Mul, RangeBounds, Sub, SubAssign}, @@ -7,10 +8,11 @@ use vm_core::Felt; /// Represents the types of errors that can occur when converting from and into [`RowIndex`] and /// using its operations. -#[derive(Debug, thiserror::Error, PartialEq, Eq)] -pub enum RowIndexError { - #[error("value is too large to be converted into RowIndex: {0}")] - InvalidSize(T), +#[derive(Debug, thiserror::Error)] +pub enum RowIndexError { + // This uses Box rather than String because its stack size is 8 bytes smaller. + #[error("value {0} is larger than u32::MAX so it cannot be converted into a RowIndex")] + InvalidSize(Box), } // ROW INDEX @@ -71,25 +73,28 @@ impl From for Felt { /// # Panics /// /// This function will panic if the number represented by the usize is greater than the maximum -/// [`RowIndex`] value, `u32::MAX`. +/// [`RowIndex`] value, [`u32::MAX`]. impl From for RowIndex { fn from(value: usize) -> Self { - let value = u32::try_from(value).map_err(|_| RowIndexError::InvalidSize(value)).unwrap(); + let value = u32::try_from(value) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_usize", value).into())) + .unwrap(); value.into() } } /// Converts a u64 value into a [`RowIndex`]. /// -/// # Panics +/// # Errors /// -/// This function will panic if the number represented by the u64 is greater than the maximum -/// [`RowIndex`] value, `u32::MAX`. +/// This function returns an error if the number represented by the u64 is greater than the +/// maximum [`RowIndex`] value, [`u32::MAX`]. impl TryFrom for RowIndex { - type Error = RowIndexError; + type Error = RowIndexError; fn try_from(value: u64) -> Result { - let value = u32::try_from(value).map_err(|_| RowIndexError::InvalidSize(value))?; + let value = u32::try_from(value) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_u64", value).into()))?; Ok(RowIndex::from(value)) } } @@ -107,7 +112,9 @@ impl From for RowIndex { /// This function will panic if the number represented by the i32 is less than 0. impl From for RowIndex { fn from(value: i32) -> Self { - let value = u32::try_from(value).map_err(|_| RowIndexError::InvalidSize(value)).unwrap(); + let value = u32::try_from(value) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_i32", value).into())) + .unwrap(); RowIndex(value) } } @@ -125,7 +132,9 @@ impl Sub for RowIndex { type Output = RowIndex; fn sub(self, rhs: usize) -> Self::Output { - let rhs = u32::try_from(rhs).map_err(|_| RowIndexError::InvalidSize(rhs)).unwrap(); + let rhs = u32::try_from(rhs) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_usize", rhs).into())) + .unwrap(); RowIndex(self.0 - rhs) } } @@ -164,7 +173,9 @@ impl Add for RowIndex { type Output = RowIndex; fn add(self, rhs: usize) -> Self::Output { - let rhs = u32::try_from(rhs).map_err(|_| RowIndexError::InvalidSize(rhs)).unwrap(); + let rhs = u32::try_from(rhs) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_usize", rhs).into())) + .unwrap(); RowIndex(self.0 + rhs) } } @@ -209,7 +220,10 @@ impl PartialEq for RowIndex { impl PartialEq for RowIndex { fn eq(&self, rhs: &usize) -> bool { - self.0 == u32::try_from(*rhs).map_err(|_| RowIndexError::InvalidSize(*rhs)).unwrap() + self.0 + == u32::try_from(*rhs) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_usize", *rhs).into())) + .unwrap() } } @@ -221,7 +235,9 @@ impl PartialEq for i32 { impl PartialOrd for RowIndex { fn partial_cmp(&self, rhs: &usize) -> Option { - let rhs = u32::try_from(*rhs).map_err(|_| RowIndexError::InvalidSize(*rhs)).unwrap(); + let rhs = u32::try_from(*rhs) + .map_err(|_| RowIndexError::InvalidSize(format!("{}_usize", *rhs).into())) + .unwrap(); self.0.partial_cmp(&rhs) } } diff --git a/assembly/Cargo.toml b/assembly/Cargo.toml index e72f780fb6..45c55ce249 100644 --- a/assembly/Cargo.toml +++ b/assembly/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "miden-assembly" -version = "0.11.0" +version = "0.12.0" description = "Miden VM assembly language" -documentation = "https://docs.rs/miden-assembly/0.11.0" +documentation = "https://docs.rs/miden-assembly/0.12.0" readme = "README.md" categories = ["compilers", "no-std"] keywords = ["assembler", "assembly", "language", "miden"] @@ -19,22 +19,22 @@ doctest = false [features] default = ["std"] -std = ["aho-corasick/std", "miette/fancy", "miette/std", "thiserror/std", "vm-core/std"] +std = ["aho-corasick/std", "miette/fancy", "miette/std", "vm-core/std", "thiserror/std"] testing = ["dep:regex"] [dependencies] aho-corasick = { version = "1.1", default-features = false } lalrpop-util = { version = "0.20", default-features = false } -miette = { package = "miden-miette", version = "7.1", default-features = false, features = [ +miette = { package = "miden-miette", version = "8.0", default-features = false, features = [ "fancy-no-syscall", "derive" ] } regex = { version = "1.10", optional = true, default-features = false, features = ["unicode", "perf"] } smallvec = { version = "1.13", features = ["union", "const_generics", "const_new"] } -thiserror = { package = "miden-thiserror", version = "1.0", default-features = false } +thiserror = { workspace = true } tracing = { version = "0.1", default-features = false, features = ["attributes"] } unicode-width = { version = "0.2", features = ["no_std"] } -vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false, features = [ +vm-core = { package = "miden-core", path = "../core", version = "0.12", default-features = false, features = [ "diagnostics", ] } diff --git a/assembly/src/assembler/basic_block_builder.rs b/assembly/src/assembler/basic_block_builder.rs index 3246484d21..4d924b58bd 100644 --- a/assembly/src/assembler/basic_block_builder.rs +++ b/assembly/src/assembler/basic_block_builder.rs @@ -2,7 +2,8 @@ use alloc::{borrow::Borrow, string::ToString, vec::Vec}; use vm_core::{ mast::{DecoratorId, MastNodeId}, - AdviceInjector, AssemblyOp, Decorator, Operation, + sys_events::SystemEvent, + AssemblyOp, Decorator, Operation, }; use super::{mast_forest_builder::MastForestBuilder, BodyWrapper, DecoratorList, ProcedureContext}; @@ -93,6 +94,12 @@ impl BasicBlockBuilder<'_> { let new_len = self.ops.len() + n; self.ops.resize(new_len, op); } + + /// Converts the system event into its corresponding event ID, and adds an `Emit` operation + /// to the list of basic block operations. + pub fn push_system_event(&mut self, sys_event: SystemEvent) { + self.push_op(Operation::Emit(sys_event.into_event_id())) + } } /// Decorators @@ -105,11 +112,6 @@ impl BasicBlockBuilder<'_> { Ok(()) } - /// Adds the specified advice injector to the list of basic block decorators. - pub fn push_advice_injector(&mut self, injector: AdviceInjector) -> Result<(), AssemblyError> { - self.push_decorator(Decorator::Advice(injector)) - } - /// Adds an AsmOp decorator to the list of basic block decorators. /// /// This indicates that the provided instruction should be tracked and the cycle count for diff --git a/assembly/src/assembler/instruction/adv_ops.rs b/assembly/src/assembler/instruction/adv_ops.rs index 86a38c1fa8..399913946b 100644 --- a/assembly/src/assembler/instruction/adv_ops.rs +++ b/assembly/src/assembler/instruction/adv_ops.rs @@ -1,7 +1,7 @@ use vm_core::Operation; use super::{validate_param, BasicBlockBuilder}; -use crate::{ast::AdviceInjectorNode, AssemblyError, ADVICE_READ_LIMIT}; +use crate::{AssemblyError, ADVICE_READ_LIMIT}; // NON-DETERMINISTIC (ADVICE) INPUTS // ================================================================================================ @@ -18,14 +18,3 @@ pub fn adv_push(block_builder: &mut BasicBlockBuilder, n: u8) -> Result<(), Asse block_builder.push_op_many(Operation::AdvPop, n as usize); Ok(()) } - -// ADVICE INJECTORS -// ================================================================================================ - -/// Appends advice injector decorator to the span. -pub fn adv_inject( - block_builder: &mut BasicBlockBuilder, - injector: &AdviceInjectorNode, -) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(injector.into()) -} diff --git a/assembly/src/assembler/instruction/crypto_ops.rs b/assembly/src/assembler/instruction/crypto_ops.rs index 54e6a2acff..7efed9ee9a 100644 --- a/assembly/src/assembler/instruction/crypto_ops.rs +++ b/assembly/src/assembler/instruction/crypto_ops.rs @@ -1,4 +1,4 @@ -use vm_core::{AdviceInjector, Felt, Operation::*}; +use vm_core::{sys_events::SystemEvent, Felt, Operation::*}; use super::BasicBlockBuilder; use crate::AssemblyError; @@ -113,10 +113,10 @@ pub(super) fn hmerge(block_builder: &mut BasicBlockBuilder) { /// - root of the tree, 4 elements. /// /// This operation takes 9 VM cycles. -pub(super) fn mtree_get(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { +pub(super) fn mtree_get(block_builder: &mut BasicBlockBuilder) { // stack: [d, i, R, ...] // pops the value of the node we are looking for from the advice stack - read_mtree_node(block_builder)?; + read_mtree_node(block_builder); #[rustfmt::skip] let ops = [ // verify the node V for root R with depth d and index i @@ -128,8 +128,6 @@ pub(super) fn mtree_get(block_builder: &mut BasicBlockBuilder) -> Result<(), Ass MovUp4, Drop, MovUp4, Drop, ]; block_builder.push_ops(ops); - - Ok(()) } /// Appends the MRUPDATE op with a parameter of "false" and stack manipulations to the span block @@ -165,18 +163,16 @@ pub(super) fn mtree_set(block_builder: &mut BasicBlockBuilder) -> Result<(), Ass /// It is not checked whether the provided roots exist as Merkle trees in the advide providers. /// /// This operation takes 16 VM cycles. -pub(super) fn mtree_merge(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { +pub(super) fn mtree_merge(block_builder: &mut BasicBlockBuilder) { // stack input: [R_rhs, R_lhs, ...] // stack output: [R_merged, ...] // invoke the advice provider function to merge 2 Merkle trees defined by the roots on the top // of the operand stack - block_builder.push_advice_injector(AdviceInjector::MerkleNodeMerge)?; + block_builder.push_system_event(SystemEvent::MerkleNodeMerge); // perform the `hmerge`, updating the operand stack - hmerge(block_builder); - - Ok(()) + hmerge(block_builder) } // MERKLE TREES - HELPERS @@ -199,20 +195,18 @@ pub(super) fn mtree_merge(block_builder: &mut BasicBlockBuilder) -> Result<(), A /// - new value of the node, 4 elements (only in the case of mtree_set) /// /// This operation takes 4 VM cycles. -fn read_mtree_node(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { +fn read_mtree_node(block_builder: &mut BasicBlockBuilder) { // The stack should be arranged in the following way: [d, i, R, ...] so that the decorator // can fetch the node value from the root. In the `mtree.get` operation we have the stack in // the following format: [d, i, R], whereas in the case of `mtree.set` we would also have the // new node value post the tree root: [d, i, R, V_new] // // pops the value of the node we are looking for from the advice stack - block_builder.push_advice_injector(AdviceInjector::MerkleNodeToStack)?; + block_builder.push_system_event(SystemEvent::MerkleNodeToStack); // pops the old node value from advice the stack => MPVERIFY: [V_old, d, i, R, ...] // MRUPDATE: [V_old, d, i, R, V_new, ...] block_builder.push_op_many(AdvPop, 4); - - Ok(()) } /// Update a node in the merkle tree. This operation will always copy the tree into a new instance, @@ -225,7 +219,7 @@ fn update_mtree(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyErr // Inject the old node value onto the stack for the call to MRUPDATE. // stack: [V_old, d, i, R_old, V_new, ...] (4 cycles) - read_mtree_node(block_builder)?; + read_mtree_node(block_builder); #[rustfmt::skip] let ops = [ diff --git a/assembly/src/assembler/instruction/env_ops.rs b/assembly/src/assembler/instruction/env_ops.rs index 2026af71f5..8688bc89ce 100644 --- a/assembly/src/assembler/instruction/env_ops.rs +++ b/assembly/src/assembler/instruction/env_ops.rs @@ -44,7 +44,7 @@ pub fn locaddr( index: u16, proc_ctx: &ProcedureContext, ) -> Result<(), AssemblyError> { - local_to_absolute_addr(block_builder, index, proc_ctx.num_locals()) + local_to_absolute_addr(block_builder, index, proc_ctx.num_locals(), true) } /// Appends CALLER operation to the span which puts the hash of the function which initiated the diff --git a/assembly/src/assembler/instruction/ext2_ops.rs b/assembly/src/assembler/instruction/ext2_ops.rs index 6cc3ae88a4..b3d2cfc766 100644 --- a/assembly/src/assembler/instruction/ext2_ops.rs +++ b/assembly/src/assembler/instruction/ext2_ops.rs @@ -1,4 +1,4 @@ -use vm_core::{AdviceInjector::Ext2Inv, Operation::*}; +use vm_core::{sys_events::SystemEvent::Ext2Inv, Operation::*}; use super::BasicBlockBuilder; use crate::AssemblyError; @@ -53,8 +53,8 @@ pub fn ext2_mul(block_builder: &mut BasicBlockBuilder) { /// operations outputs the result c = (c1, c0) where c = a * b^-1. /// /// This operation takes 11 VM cycles. -pub fn ext2_div(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(Ext2Inv)?; +pub fn ext2_div(block_builder: &mut BasicBlockBuilder) { + block_builder.push_system_event(Ext2Inv); #[rustfmt::skip] let ops = [ AdvPop, // [b0', b1, b0, a1, a0, ...] @@ -70,8 +70,6 @@ pub fn ext2_div(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyErr Drop // [a1*b1', a0*b0'...] ]; block_builder.push_ops(ops); - - Ok(()) } /// Given a stack with initial configuration given by [a1, a0, ...] where a = (a0, a1) represents @@ -116,7 +114,7 @@ pub fn ext2_neg(block_builder: &mut BasicBlockBuilder) { /// /// This operation takes 8 VM cycles. pub fn ext2_inv(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(Ext2Inv)?; + block_builder.push_system_event(Ext2Inv); #[rustfmt::skip] let ops = [ AdvPop, // [a0', a1, a0, ...] diff --git a/assembly/src/assembler/instruction/field_ops.rs b/assembly/src/assembler/instruction/field_ops.rs index 74063bb489..e4797c441d 100644 --- a/assembly/src/assembler/instruction/field_ops.rs +++ b/assembly/src/assembler/instruction/field_ops.rs @@ -1,4 +1,4 @@ -use vm_core::{AdviceInjector, FieldElement, Operation::*}; +use vm_core::{sys_events::SystemEvent, FieldElement, Operation::*}; use super::{validate_param, BasicBlockBuilder}; use crate::{ @@ -259,8 +259,8 @@ fn perform_exp_for_small_power(span_builder: &mut BasicBlockBuilder, pow: u64) { /// /// # Errors /// Returns an error if the logarithm argument (top stack element) equals ZERO. -pub fn ilog2(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(AdviceInjector::ILog2)?; +pub fn ilog2(block_builder: &mut BasicBlockBuilder) { + block_builder.push_system_event(SystemEvent::ILog2); block_builder.push_op(AdvPop); // [ilog2, n, ...] // compute the power-of-two for the value given in the advice tape (17 cycles) @@ -290,8 +290,6 @@ pub fn ilog2(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> ]; block_builder.push_ops(ops); - - Ok(()) } // COMPARISON OPERATIONS diff --git a/assembly/src/assembler/instruction/mem_ops.rs b/assembly/src/assembler/instruction/mem_ops.rs index cdfbdc79c5..c3eaa36332 100644 --- a/assembly/src/assembler/instruction/mem_ops.rs +++ b/assembly/src/assembler/instruction/mem_ops.rs @@ -33,7 +33,7 @@ pub fn mem_read( if let Some(addr) = addr { if is_local { let num_locals = proc_ctx.num_locals(); - local_to_absolute_addr(block_builder, addr as u16, num_locals)?; + local_to_absolute_addr(block_builder, addr as u16, num_locals, is_single)?; } else { push_u32_value(block_builder, addr); } @@ -81,7 +81,7 @@ pub fn mem_write_imm( is_single: bool, ) -> Result<(), AssemblyError> { if is_local { - local_to_absolute_addr(block_builder, addr as u16, proc_ctx.num_locals())?; + local_to_absolute_addr(block_builder, addr as u16, proc_ctx.num_locals(), is_single)?; } else { push_u32_value(block_builder, addr); } @@ -100,8 +100,8 @@ pub fn mem_write_imm( // ================================================================================================ /// Appends a sequence of operations to the span needed for converting procedure local index to -/// absolute memory address. This consists of putting index onto the stack and then executing -/// LOCADDR operation. +/// absolute memory address. This consists in calculating the offset of the local value from the +/// frame pointer and pushing the result onto the stack. /// /// This operation takes: /// - 3 VM cycles if index == 1 @@ -111,8 +111,9 @@ pub fn mem_write_imm( /// Returns an error if index is greater than the number of procedure locals. pub fn local_to_absolute_addr( block_builder: &mut BasicBlockBuilder, - index: u16, + index_of_local: u16, num_proc_locals: u16, + is_single: bool, ) -> Result<(), AssemblyError> { if num_proc_locals == 0 { return Err(AssemblyError::Other( @@ -124,10 +125,21 @@ pub fn local_to_absolute_addr( )); } - let max = num_proc_locals - 1; - validate_param(index, 0..=max)?; + // If a single local value is being accessed, then the index can take the full range + // [0, num_proc_locals - 1]. Otherwise, the index can take the range [0, num_proc_locals - 4] + // to account for the fact that a full word is being accessed. + let max = if is_single { + num_proc_locals - 1 + } else { + num_proc_locals - 4 + }; + validate_param(index_of_local, 0..=max)?; - push_felt(block_builder, -Felt::from(max - index)); + // Local values are placed under the frame pointer, so we need to calculate the offset of the + // local value from the frame pointer. + // The offset is in the range [1, num_proc_locals], which is then subtracted from `fmp`. + let fmp_offset_of_local = num_proc_locals - index_of_local; + push_felt(block_builder, -Felt::from(fmp_offset_of_local)); block_builder.push_op(FmpAdd); Ok(()) diff --git a/assembly/src/assembler/instruction/mod.rs b/assembly/src/assembler/instruction/mod.rs index 49e8bec164..889e7c3685 100644 --- a/assembly/src/assembler/instruction/mod.rs +++ b/assembly/src/assembler/instruction/mod.rs @@ -1,7 +1,7 @@ use core::ops::RangeBounds; use miette::miette; -use vm_core::{mast::MastNodeId, Decorator, ONE, ZERO}; +use vm_core::{debuginfo::Spanned, mast::MastNodeId, Decorator, ONE, WORD_SIZE, ZERO}; use super::{ast::InvokeKind, Assembler, BasicBlockBuilder, Felt, Operation, ProcedureContext}; use crate::{ast::Instruction, utils::bound_into_included_u64, AssemblyError, Span}; @@ -89,7 +89,7 @@ impl Assembler { Instruction::ExpBitLength(num_pow_bits) => { field_ops::exp(block_builder, *num_pow_bits)? }, - Instruction::ILog2 => field_ops::ilog2(block_builder)?, + Instruction::ILog2 => field_ops::ilog2(block_builder), Instruction::Not => block_builder.push_op(Not), Instruction::And => block_builder.push_op(And), @@ -111,7 +111,7 @@ impl Assembler { Instruction::Ext2Add => ext2_ops::ext2_add(block_builder), Instruction::Ext2Sub => ext2_ops::ext2_sub(block_builder), Instruction::Ext2Mul => ext2_ops::ext2_mul(block_builder), - Instruction::Ext2Div => ext2_ops::ext2_div(block_builder)?, + Instruction::Ext2Div => ext2_ops::ext2_div(block_builder), Instruction::Ext2Neg => ext2_ops::ext2_neg(block_builder), Instruction::Ext2Inv => ext2_ops::ext2_inv(block_builder)?, @@ -190,10 +190,10 @@ impl Assembler { Instruction::U32Rotr => u32_ops::u32rotr(block_builder, None)?, Instruction::U32RotrImm(v) => u32_ops::u32rotr(block_builder, Some(v.expect_value()))?, Instruction::U32Popcnt => u32_ops::u32popcnt(block_builder), - Instruction::U32Clz => u32_ops::u32clz(block_builder)?, - Instruction::U32Ctz => u32_ops::u32ctz(block_builder)?, - Instruction::U32Clo => u32_ops::u32clo(block_builder)?, - Instruction::U32Cto => u32_ops::u32cto(block_builder)?, + Instruction::U32Clz => u32_ops::u32clz(block_builder), + Instruction::U32Ctz => u32_ops::u32ctz(block_builder), + Instruction::U32Clo => u32_ops::u32clo(block_builder), + Instruction::U32Cto => u32_ops::u32cto(block_builder), Instruction::U32Lt => u32_ops::u32lt(block_builder), Instruction::U32Lte => u32_ops::u32lte(block_builder), Instruction::U32Gt => u32_ops::u32gt(block_builder), @@ -331,13 +331,21 @@ impl Assembler { true, true, )?, - Instruction::LocLoadW(v) => mem_ops::mem_read( - block_builder, - proc_ctx, - Some(v.expect_value() as u32), - true, - false, - )?, + Instruction::LocLoadW(v) => { + let local_addr = v.expect_value(); + if local_addr % WORD_SIZE as u16 != 0 { + return Err(AssemblyError::InvalidLocalWordIndex { + span: instruction.span(), + source_file: proc_ctx + .source_manager() + .get(proc_ctx.span().source_id()) + .ok(), + local_addr, + }); + } + + mem_ops::mem_read(block_builder, proc_ctx, Some(local_addr as u32), true, false)? + }, Instruction::MemStore => block_builder.push_ops([MStore, Drop]), Instruction::MemStoreW => block_builder.push_ops([MStoreW]), Instruction::MemStoreImm(v) => { @@ -353,23 +361,32 @@ impl Assembler { true, true, )?, - Instruction::LocStoreW(v) => mem_ops::mem_write_imm( - block_builder, - proc_ctx, - v.expect_value() as u32, - true, - false, - )?, + Instruction::LocStoreW(v) => { + let local_addr = v.expect_value(); + if local_addr % WORD_SIZE as u16 != 0 { + return Err(AssemblyError::InvalidLocalWordIndex { + span: instruction.span(), + source_file: proc_ctx + .source_manager() + .get(proc_ctx.span().source_id()) + .ok(), + local_addr, + }); + } - Instruction::AdvInject(injector) => adv_ops::adv_inject(block_builder, injector)?, + mem_ops::mem_write_imm(block_builder, proc_ctx, local_addr as u32, true, false)? + }, + Instruction::SysEvent(system_event) => { + block_builder.push_system_event(system_event.into()) + }, // ----- cryptographic instructions --------------------------------------------------- Instruction::Hash => crypto_ops::hash(block_builder), Instruction::HPerm => block_builder.push_op(HPerm), Instruction::HMerge => crypto_ops::hmerge(block_builder), - Instruction::MTreeGet => crypto_ops::mtree_get(block_builder)?, + Instruction::MTreeGet => crypto_ops::mtree_get(block_builder), Instruction::MTreeSet => crypto_ops::mtree_set(block_builder)?, - Instruction::MTreeMerge => crypto_ops::mtree_merge(block_builder)?, + Instruction::MTreeMerge => crypto_ops::mtree_merge(block_builder), Instruction::MTreeVerify => block_builder.push_op(MpVerify(0)), Instruction::MTreeVerifyWithError(err_code) => { block_builder.push_op(MpVerify(err_code.expect_value())) diff --git a/assembly/src/assembler/instruction/u32_ops.rs b/assembly/src/assembler/instruction/u32_ops.rs index 8826d05939..42159babff 100644 --- a/assembly/src/assembler/instruction/u32_ops.rs +++ b/assembly/src/assembler/instruction/u32_ops.rs @@ -1,5 +1,6 @@ use vm_core::{ - AdviceInjector, Felt, + sys_events::SystemEvent, + Felt, Operation::{self, *}, }; @@ -298,12 +299,11 @@ pub fn u32popcnt(span_builder: &mut BasicBlockBuilder) { /// provider). /// /// This operation takes 42 VM cycles. -pub fn u32clz(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(AdviceInjector::U32Clz)?; +pub fn u32clz(block_builder: &mut BasicBlockBuilder) { + block_builder.push_system_event(SystemEvent::U32Clz); block_builder.push_op(AdvPop); // [clz, n, ...] verify_clz(block_builder); - Ok(()) } /// Translates `u32ctz` assembly instruction to VM operations. `u32ctz` counts the number of @@ -311,12 +311,11 @@ pub fn u32clz(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError /// provider). /// /// This operation takes 34 VM cycles. -pub fn u32ctz(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(AdviceInjector::U32Ctz)?; +pub fn u32ctz(block_builder: &mut BasicBlockBuilder) { + block_builder.push_system_event(SystemEvent::U32Ctz); block_builder.push_op(AdvPop); // [ctz, n, ...] verify_ctz(block_builder); - Ok(()) } /// Translates `u32clo` assembly instruction to VM operations. `u32clo` counts the number of @@ -324,12 +323,11 @@ pub fn u32ctz(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError /// provider). /// /// This operation takes 41 VM cycles. -pub fn u32clo(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(AdviceInjector::U32Clo)?; +pub fn u32clo(block_builder: &mut BasicBlockBuilder) { + block_builder.push_system_event(SystemEvent::U32Clo); block_builder.push_op(AdvPop); // [clo, n, ...] verify_clo(block_builder); - Ok(()) } /// Translates `u32cto` assembly instruction to VM operations. `u32cto` counts the number of @@ -337,12 +335,11 @@ pub fn u32clo(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError /// provider). /// /// This operation takes 33 VM cycles. -pub fn u32cto(block_builder: &mut BasicBlockBuilder) -> Result<(), AssemblyError> { - block_builder.push_advice_injector(AdviceInjector::U32Cto)?; +pub fn u32cto(block_builder: &mut BasicBlockBuilder) { + block_builder.push_system_event(SystemEvent::U32Cto); block_builder.push_op(AdvPop); // [cto, n, ...] verify_cto(block_builder); - Ok(()) } /// Specifically handles these specific inputs per the spec. @@ -419,7 +416,7 @@ fn prepare_bitwise( } /// Appends relevant operations to the span block for the correctness check of the `U32Clz` -/// injector. +/// system event. /// The idea is to compare the actual value with a bitmask consisting of `clz` leading ones to /// check that every bit in `clz` leading bits is zero and `1` additional one to check that /// `clz + 1`'th leading bit is one: @@ -500,7 +497,7 @@ fn verify_clz(block_builder: &mut BasicBlockBuilder) { } /// Appends relevant operations to the span block for the correctness check of the `U32Clo` -/// injector. +/// system event. /// The idea is to compare the actual value with a bitmask consisting of `clo` leading ones to /// check that every bit in `clo` leading bits is one and `1` additional one to check that /// `clo + 1`'th leading bit is zero: @@ -575,7 +572,7 @@ fn verify_clo(block_builder: &mut BasicBlockBuilder) { } /// Appends relevant operations to the span block for the correctness check of the `U32Ctz` -/// injector. +/// system event. /// The idea is to compare the actual value with a bitmask consisting of `ctz` trailing ones to /// check that every bit in `ctz` trailing bits is zero and `1` additional one to check that /// `ctz + 1`'th trailing bit is one: @@ -649,7 +646,7 @@ fn verify_ctz(block_builder: &mut BasicBlockBuilder) { } /// Appends relevant operations to the span block for the correctness check of the `U32Cto` -/// injector. +/// system event. /// The idea is to compare the actual value with a bitmask consisting of `cto` trailing ones to /// check that every bit in `cto` trailing bits is one and `1` additional one to check that /// `cto + 1`'th trailing bit is zero: diff --git a/assembly/src/assembler/mast_forest_builder.rs b/assembly/src/assembler/mast_forest_builder.rs index 96e7d51b52..0a17bd07c7 100644 --- a/assembly/src/assembler/mast_forest_builder.rs +++ b/assembly/src/assembler/mast_forest_builder.rs @@ -347,7 +347,9 @@ impl MastForestBuilder { // decorator already exists in the forest; return previously assigned id Ok(*decorator_id) } else { - let new_decorator_id = self.mast_forest.add_decorator(decorator)?; + let new_decorator_id = self.mast_forest.add_decorator(decorator).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new decorator", source) + })?; self.decorator_id_by_fingerprint.insert(decorator_hash, new_decorator_id); Ok(new_decorator_id) @@ -366,7 +368,9 @@ impl MastForestBuilder { // node already exists in the forest; return previously assigned id Ok(*node_id) } else { - let new_node_id = self.mast_forest.add_node(node)?; + let new_node_id = self.mast_forest.add_node(node).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new node", source) + })?; self.node_id_by_fingerprint.insert(node_fingerprint, new_node_id); self.hash_by_node_id.insert(new_node_id, node_fingerprint); @@ -380,7 +384,9 @@ impl MastForestBuilder { operations: Vec, decorators: Option, ) -> Result { - let block = MastNode::new_basic_block(operations, decorators)?; + let block = MastNode::new_basic_block(operations, decorators).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new basic block node", source) + })?; self.ensure_node(block) } @@ -390,7 +396,10 @@ impl MastForestBuilder { left_child: MastNodeId, right_child: MastNodeId, ) -> Result { - let join = MastNode::new_join(left_child, right_child, &self.mast_forest)?; + let join = + MastNode::new_join(left_child, right_child, &self.mast_forest).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new join node", source) + })?; self.ensure_node(join) } @@ -400,25 +409,34 @@ impl MastForestBuilder { if_branch: MastNodeId, else_branch: MastNodeId, ) -> Result { - let split = MastNode::new_split(if_branch, else_branch, &self.mast_forest)?; + let split = + MastNode::new_split(if_branch, else_branch, &self.mast_forest).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new split node", source) + })?; self.ensure_node(split) } /// Adds a loop node to the forest, and returns the [`MastNodeId`] associated with it. pub fn ensure_loop(&mut self, body: MastNodeId) -> Result { - let loop_node = MastNode::new_loop(body, &self.mast_forest)?; + let loop_node = MastNode::new_loop(body, &self.mast_forest).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new loop node", source) + })?; self.ensure_node(loop_node) } /// Adds a call node to the forest, and returns the [`MastNodeId`] associated with it. pub fn ensure_call(&mut self, callee: MastNodeId) -> Result { - let call = MastNode::new_call(callee, &self.mast_forest)?; + let call = MastNode::new_call(callee, &self.mast_forest).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new call node", source) + })?; self.ensure_node(call) } /// Adds a syscall node to the forest, and returns the [`MastNodeId`] associated with it. pub fn ensure_syscall(&mut self, callee: MastNodeId) -> Result { - let syscall = MastNode::new_syscall(callee, &self.mast_forest)?; + let syscall = MastNode::new_syscall(callee, &self.mast_forest).map_err(|source| { + AssemblyError::forest_error("assembler failed to add new syscall node", source) + })?; self.ensure_node(syscall) } diff --git a/assembly/src/assembler/mod.rs b/assembly/src/assembler/mod.rs index 2ddc94e31a..25df78c870 100644 --- a/assembly/src/assembler/mod.rs +++ b/assembly/src/assembler/mod.rs @@ -7,7 +7,7 @@ use vm_core::{ crypto::hash::RpoDigest, debuginfo::SourceSpan, mast::{DecoratorId, MastNodeId}, - DecoratorList, Felt, Kernel, Operation, Program, + DecoratorList, Felt, Kernel, Operation, Program, WORD_SIZE, }; use crate::{ @@ -158,7 +158,7 @@ impl Assembler { /// /// The given module must be a library module, or an error will be returned. #[inline] - pub fn add_module(&mut self, module: impl Compile) -> Result<(), Report> { + pub fn add_module(&mut self, module: impl Compile) -> Result { self.add_module_with_options(module, CompileOptions::for_library()) } @@ -169,22 +169,36 @@ impl Assembler { &mut self, module: impl Compile, options: CompileOptions, - ) -> Result<(), Report> { + ) -> Result { + let ids = self.add_modules_with_options(vec![module], options)?; + Ok(ids[0]) + } + + pub fn add_modules_with_options( + &mut self, + modules: impl IntoIterator, + options: CompileOptions, + ) -> Result, Report> { let kind = options.kind; - if kind != ModuleKind::Library { - return Err(Report::msg( - "only library modules are supported by `add_module_with_options`", - )); + if kind == ModuleKind::Executable { + return Err(Report::msg("Executables are not supported by `add_module_with_options`")); } - let module = module.compile_with_options(&self.source_manager, options)?; - assert_eq!(module.kind(), kind, "expected module kind to match compilation options"); - - self.module_graph.add_ast_module(module)?; - - Ok(()) + let modules = modules + .into_iter() + .map(|module| { + let module = module.compile_with_options(&self.source_manager, options.clone())?; + assert_eq!( + module.kind(), + kind, + "expected module kind to match compilation options" + ); + Ok(module) + }) + .collect::, Report>>()?; + let ids = self.module_graph.add_ast_modules(modules.into_iter())?; + Ok(ids) } - /// Adds all modules (defined by ".masm" files) from the specified directory to the module /// of this assembler graph. /// @@ -202,10 +216,8 @@ impl Assembler { namespace: crate::LibraryNamespace, dir: &std::path::Path, ) -> Result<(), Report> { - for module in crate::parser::read_modules_from_dir(namespace, dir, &self.source_manager)? { - self.module_graph.add_ast_module(module)?; - } - + let modules = crate::parser::read_modules_from_dir(namespace, dir, &self.source_manager)?; + self.module_graph.add_ast_modules(modules)?; Ok(()) } @@ -281,24 +293,12 @@ impl Assembler { /// # Errors /// /// Returns an error if parsing or compilation of the specified modules fails. - pub fn assemble_library( + pub fn assemble_common( mut self, modules: impl IntoIterator, + options: CompileOptions, ) -> Result { - let ast_module_indices = - modules.into_iter().try_fold(Vec::default(), |mut acc, module| { - module - .compile_with_options(&self.source_manager, CompileOptions::for_library()) - .and_then(|module| { - self.module_graph.add_ast_module(module).map_err(Report::from) - }) - .map(move |module_id| { - acc.push(module_id); - acc - }) - })?; - - self.module_graph.recompute()?; + let ast_module_indices = self.add_modules_with_options(modules, options)?; let mut mast_forest_builder = MastForestBuilder::default(); @@ -325,7 +325,6 @@ impl Assembler { exports }; - // TODO: show a warning if library exports are empty? let (mast_forest, id_remappings) = mast_forest_builder.build(); if let Some(id_remappings) = id_remappings { for (_proc_name, node_id) in exports.iter_mut() { @@ -338,53 +337,30 @@ impl Assembler { Ok(Library::new(mast_forest.into(), exports)?) } + pub fn assemble_library( + self, + modules: impl IntoIterator, + ) -> Result { + let options = CompileOptions { + kind: ModuleKind::Library, + warnings_as_errors: self.warnings_as_errors, + path: None, + }; + self.assemble_common(modules, options) + } + /// Assembles the provided module into a [KernelLibrary] intended to be used as a Kernel. /// /// # Errors /// /// Returns an error if parsing or compilation of the specified modules fails. - pub fn assemble_kernel(mut self, module: impl Compile) -> Result { + pub fn assemble_kernel(self, module: impl Compile) -> Result { let options = CompileOptions { kind: ModuleKind::Kernel, warnings_as_errors: self.warnings_as_errors, path: Some(LibraryPath::from(LibraryNamespace::Kernel)), }; - - let module = module.compile_with_options(&self.source_manager, options)?; - let module_idx = self.module_graph.add_ast_module(module)?; - - self.module_graph.recompute()?; - - let mut mast_forest_builder = MastForestBuilder::default(); - - // Note: it is safe to use `unwrap_ast()` here, since all modules looped over are - // AST (we just added them to the module graph) - let ast_module = self.module_graph[module_idx].unwrap_ast().clone(); - - let mut exports = ast_module - .exported_procedures() - .map(|(proc_idx, fqn)| { - let gid = module_idx + proc_idx; - self.compile_subgraph(gid, &mut mast_forest_builder)?; - - let proc_root_node_id = mast_forest_builder - .get_procedure(gid) - .expect("compilation succeeded but root not found in cache") - .body_node_id(); - Ok((fqn, proc_root_node_id)) - }) - .collect::, Report>>()?; - - // TODO: show a warning if library exports are empty? - let (mast_forest, id_remappings) = mast_forest_builder.build(); - if let Some(id_remappings) = id_remappings { - for (_proc_name, node_id) in exports.iter_mut() { - if let Some(&new_node_id) = id_remappings.get(node_id) { - *node_id = new_node_id; - } - } - } - let library = Library::new(mast_forest.into(), exports)?; + let library = self.assemble_common([module], options)?; Ok(library.try_into()?) } @@ -407,7 +383,6 @@ impl Assembler { // Recompute graph with executable module, and start compiling let ast_module_index = self.module_graph.add_ast_module(program)?; - self.module_graph.recompute()?; // Find the executable entrypoint Note: it is safe to use `unwrap_ast()` here, since this is // the module we just added, which is in AST representation. @@ -567,19 +542,21 @@ impl Assembler { ) -> Result { // Make sure the current procedure context is available during codegen let gid = proc_ctx.id(); + let num_locals = proc_ctx.num_locals(); let wrapper_proc = self.module_graph.get_procedure_unsafe(gid); let proc = wrapper_proc.unwrap_ast().unwrap_procedure(); let proc_body_id = if num_locals > 0 { - // for procedures with locals, we need to update fmp register before and after the - // procedure body is executed. specifically: + // For procedures with locals, we need to update fmp register before and after the + // procedure body is executed. Specifically: // - to allocate procedure locals we need to increment fmp by the number of locals - // - to deallocate procedure locals we need to decrement it by the same amount - let num_locals = Felt::from(num_locals); + // (rounded up to the word size), and + // - to deallocate procedure locals we need to decrement it by the same amount. + let locals_frame = Felt::from(num_locals.next_multiple_of(WORD_SIZE as u16)); let wrapper = BodyWrapper { - prologue: vec![Operation::Push(num_locals), Operation::FmpUpdate], - epilogue: vec![Operation::Push(-num_locals), Operation::FmpUpdate], + prologue: vec![Operation::Push(locals_frame), Operation::FmpUpdate], + epilogue: vec![Operation::Push(-locals_frame), Operation::FmpUpdate], }; self.compile_body(proc.iter(), &mut proc_ctx, Some(wrapper), mast_forest_builder)? } else { diff --git a/assembly/src/assembler/module_graph/mod.rs b/assembly/src/assembler/module_graph/mod.rs index f8116f9985..6c02e89f7b 100644 --- a/assembly/src/assembler/module_graph/mod.rs +++ b/assembly/src/assembler/module_graph/mod.rs @@ -223,7 +223,9 @@ impl ModuleGraph { /// This function will panic if the number of modules exceeds the maximum representable /// [ModuleIndex] value, `u16::MAX`. pub fn add_ast_module(&mut self, module: Box) -> Result { - self.add_module(PendingWrappedModule::Ast(module)) + let res = self.add_module(PendingWrappedModule::Ast(module))?; + self.recompute()?; + Ok(res) } fn add_module(&mut self, module: PendingWrappedModule) -> Result { @@ -238,6 +240,17 @@ impl ModuleGraph { Ok(module_id) } + pub fn add_ast_modules( + &mut self, + modules: impl Iterator>, + ) -> Result, AssemblyError> { + let idx = modules + .into_iter() + .map(|m| self.add_module(PendingWrappedModule::Ast(m))) + .collect::, _>>()?; + self.recompute()?; + Ok(idx) + } fn is_pending(&self, path: &LibraryPath) -> bool { self.pending.iter().any(|m| m.path() == path) } @@ -329,7 +342,7 @@ impl ModuleGraph { /// NOTE: This will return `Err` if we detect a validation error, a cycle in the graph, or an /// operation not supported by the current configuration. Basically, for any reason that would /// cause the resulting graph to represent an invalid program. - pub fn recompute(&mut self) -> Result<(), AssemblyError> { + fn recompute(&mut self) -> Result<(), AssemblyError> { // It is acceptable for there to be no changes, but if the graph is empty and no changes // are being made, we treat that as an error if self.modules.is_empty() && self.pending.is_empty() { diff --git a/assembly/src/assembler/procedure.rs b/assembly/src/assembler/procedure.rs index 167a25806e..591cd5ac76 100644 --- a/assembly/src/assembler/procedure.rs +++ b/assembly/src/assembler/procedure.rs @@ -44,6 +44,7 @@ impl ProcedureContext { } } + /// Sets the number of locals to allocate for the procedure. pub fn with_num_locals(mut self, num_locals: u16) -> Self { self.num_locals = num_locals; self diff --git a/assembly/src/ast/ident.rs b/assembly/src/ast/ident.rs index ea2d13a238..399f63a18f 100644 --- a/assembly/src/ast/ident.rs +++ b/assembly/src/ast/ident.rs @@ -8,7 +8,7 @@ use core::{ use crate::{SourceSpan, Span, Spanned}; /// Represents the types of errors that can occur when parsing/validating an [Ident] -#[derive(Debug, thiserror::Error, PartialEq, Eq)] +#[derive(Debug, thiserror::Error)] pub enum IdentError { #[error("invalid identifier: cannot be empty")] Empty, @@ -24,7 +24,7 @@ pub enum IdentError { /// Represents the various types of casing errors that can occur, e.g. using an identifier /// with `SCREAMING_CASE` where one with `snake_case` is expected. -#[derive(Debug, thiserror::Error, PartialEq, Eq)] +#[derive(Debug, thiserror::Error)] pub enum CaseKindError { #[error("only uppercase characters or underscores are allowed, and must start with an alphabetic character")] Screaming, diff --git a/assembly/src/ast/instruction/advice.rs b/assembly/src/ast/instruction/advice.rs index c0f13cbeb9..f6bfe807d0 100644 --- a/assembly/src/ast/instruction/advice.rs +++ b/assembly/src/ast/instruction/advice.rs @@ -1,10 +1,8 @@ use core::fmt; -use vm_core::AdviceInjector; +use vm_core::sys_events::SystemEvent; -use crate::{ast::ImmU8, Felt, ZERO}; - -// ADVICE INJECTOR NODE +// SYSTEM EVENT NODE // ================================================================================================ /// Instructions which inject data into the advice provider. @@ -13,80 +11,59 @@ use crate::{ast::ImmU8, Felt, ZERO}; /// - Push new data onto the advice stack. /// - Insert new data into the advice map. #[derive(Clone, PartialEq, Eq, Debug)] -pub enum AdviceInjectorNode { +pub enum SystemEventNode { PushU64Div, PushExt2intt, - PushSmtGet, - PushSmtSet, PushSmtPeek, PushMapVal, - PushMapValImm { offset: ImmU8 }, PushMapValN, - PushMapValNImm { offset: ImmU8 }, PushMtNode, InsertMem, InsertHdword, - InsertHdwordImm { domain: ImmU8 }, + InsertHdwordWithDomain, InsertHperm, PushSignature { kind: SignatureKind }, } -impl From<&AdviceInjectorNode> for AdviceInjector { - fn from(value: &AdviceInjectorNode) -> Self { - use AdviceInjectorNode::*; +impl From<&SystemEventNode> for SystemEvent { + fn from(value: &SystemEventNode) -> Self { + use SystemEventNode::*; match value { PushU64Div => Self::U64Div, PushExt2intt => Self::Ext2Intt, - PushSmtGet => Self::SmtGet, - PushSmtSet => Self::SmtSet, PushSmtPeek => Self::SmtPeek, - PushMapVal => Self::MapValueToStack { include_len: false, key_offset: 0 }, - PushMapValImm { offset: ImmU8::Value(offset) } => Self::MapValueToStack { - include_len: false, - key_offset: offset.into_inner() as usize, - }, - PushMapValImm { offset } => panic!("unresolved constant '{offset}'"), - PushMapValN => Self::MapValueToStack { include_len: true, key_offset: 0 }, - PushMapValNImm { offset: ImmU8::Value(offset) } => Self::MapValueToStack { - include_len: true, - key_offset: offset.into_inner() as usize, - }, - PushMapValNImm { offset } => panic!("unresolved constant '{offset}'"), + PushMapVal => Self::MapValueToStack, + PushMapValN => Self::MapValueToStackN, PushMtNode => Self::MerkleNodeToStack, InsertMem => Self::MemToMap, - InsertHdword => Self::HdwordToMap { domain: ZERO }, - InsertHdwordImm { domain: ImmU8::Value(domain) } => { - Self::HdwordToMap { domain: Felt::from(domain.into_inner()) } - }, - InsertHdwordImm { domain } => panic!("unresolved constant '{domain}'"), + InsertHdword => Self::HdwordToMap, + InsertHdwordWithDomain => Self::HdwordToMapWithDomain, InsertHperm => Self::HpermToMap, - PushSignature { kind } => Self::SigToStack { kind: (*kind).into() }, + PushSignature { kind } => match kind { + SignatureKind::RpoFalcon512 => Self::FalconSigToStack, + }, } } } -impl crate::prettier::PrettyPrint for AdviceInjectorNode { +impl crate::prettier::PrettyPrint for SystemEventNode { fn render(&self) -> crate::prettier::Document { crate::prettier::display(self) } } -impl fmt::Display for AdviceInjectorNode { +impl fmt::Display for SystemEventNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::PushU64Div => write!(f, "push_u64div"), Self::PushExt2intt => write!(f, "push_ext2intt"), - Self::PushSmtGet => write!(f, "push_smtget"), - Self::PushSmtSet => write!(f, "push_smtset"), Self::PushSmtPeek => write!(f, "push_smtpeek"), Self::PushMapVal => write!(f, "push_mapval"), - Self::PushMapValImm { offset } => write!(f, "push_mapval.{offset}"), Self::PushMapValN => write!(f, "push_mapvaln"), - Self::PushMapValNImm { offset } => write!(f, "push_mapvaln.{offset}"), Self::PushMtNode => write!(f, "push_mtnode"), Self::InsertMem => write!(f, "insert_mem"), Self::InsertHdword => write!(f, "insert_hdword"), - Self::InsertHdwordImm { domain } => write!(f, "insert_hdword.{domain}"), + Self::InsertHdwordWithDomain => write!(f, "insert_hdword_d"), Self::InsertHperm => writeln!(f, "insert_hperm"), Self::PushSignature { kind } => write!(f, "push_sig.{kind}"), } diff --git a/assembly/src/ast/instruction/mod.rs b/assembly/src/ast/instruction/mod.rs index a88441d89f..b42d976d8d 100644 --- a/assembly/src/ast/instruction/mod.rs +++ b/assembly/src/ast/instruction/mod.rs @@ -4,7 +4,7 @@ mod print; use alloc::vec::Vec; -pub use self::{advice::AdviceInjectorNode, debug::DebugOptions}; +pub use self::{advice::SystemEventNode, debug::DebugOptions}; use crate::{ ast::{immediate::*, InvocationTarget}, Felt, Word, @@ -239,7 +239,7 @@ pub enum Instruction { AdvPush(ImmU8), AdvLoadW, - AdvInject(AdviceInjectorNode), + SysEvent(SystemEventNode), // ----- cryptographic operations ------------------------------------------------------------ Hash, diff --git a/assembly/src/ast/instruction/print.rs b/assembly/src/ast/instruction/print.rs index b7ca11c37c..07cecbab09 100644 --- a/assembly/src/ast/instruction/print.rs +++ b/assembly/src/ast/instruction/print.rs @@ -248,7 +248,7 @@ impl PrettyPrint for Instruction { Self::AdvPush(value) => inst_with_imm("adv_push", value), Self::AdvLoadW => const_text("adv_loadw"), - Self::AdvInject(injector) => inst_with_imm("adv", injector), + Self::SysEvent(sys_event) => inst_with_imm("adv", sys_event), // ----- cryptographic operations ----------------------------------------------------- Self::Hash => const_text("hash"), diff --git a/assembly/src/ast/mod.rs b/assembly/src/ast/mod.rs index a606d9e906..19d8c50e51 100644 --- a/assembly/src/ast/mod.rs +++ b/assembly/src/ast/mod.rs @@ -27,7 +27,7 @@ pub use self::{ ident::{CaseKindError, Ident, IdentError}, immediate::{ErrorCode, ImmFelt, ImmU16, ImmU32, ImmU8, Immediate}, imports::Import, - instruction::{advice::SignatureKind, AdviceInjectorNode, DebugOptions, Instruction}, + instruction::{advice::SignatureKind, DebugOptions, Instruction, SystemEventNode}, invocation_target::{InvocationTarget, Invoke, InvokeKind}, module::{Module, ModuleKind}, op::Op, diff --git a/assembly/src/ast/tests.rs b/assembly/src/ast/tests.rs index 7fff35c587..a708143e2e 100644 --- a/assembly/src/ast/tests.rs +++ b/assembly/src/ast/tests.rs @@ -467,18 +467,14 @@ fn test_ast_parsing_adv_ops() -> Result<(), Report> { #[test] fn test_ast_parsing_adv_injection() -> Result<(), Report> { - use super::AdviceInjectorNode::*; + use super::SystemEventNode::*; let context = TestContext::new(); - let source = source_file!( - &context, - "begin adv.push_u64div adv.push_mapval adv.push_smtget adv.insert_mem end" - ); + let source = source_file!(&context, "begin adv.push_u64div adv.push_mapval adv.insert_mem end"); let forms = module!(begin!( - inst!(AdvInject(PushU64Div)), - inst!(AdvInject(PushMapVal)), - inst!(AdvInject(PushSmtGet)), - inst!(AdvInject(InsertMem)) + inst!(SysEvent(PushU64Div)), + inst!(SysEvent(PushMapVal)), + inst!(SysEvent(InsertMem)) )); assert_eq!(context.parse_forms(source)?, forms); Ok(()) @@ -1124,7 +1120,7 @@ fn assert_parsing_line_invalid_op() { while.true push.5.7 u32wrapping_add - loc_store.1 + loc_store.4 push.0 end diff --git a/assembly/src/ast/visit.rs b/assembly/src/ast/visit.rs index ecd11fb86e..083a4d4fe6 100644 --- a/assembly/src/ast/visit.rs +++ b/assembly/src/ast/visit.rs @@ -98,8 +98,8 @@ pub trait Visit { fn visit_inst(&mut self, inst: &Span) -> ControlFlow { visit_inst(self, inst) } - fn visit_advice_injector(&mut self, injector: Span<&AdviceInjectorNode>) -> ControlFlow { - visit_advice_injector(self, injector) + fn visit_system_event(&mut self, sys_event: Span<&SystemEventNode>) -> ControlFlow { + visit_system_event(self, sys_event) } fn visit_debug_options(&mut self, options: Span<&DebugOptions>) -> ControlFlow { visit_debug_options(self, options) @@ -167,8 +167,8 @@ where fn visit_inst(&mut self, inst: &Span) -> ControlFlow { (**self).visit_inst(inst) } - fn visit_advice_injector(&mut self, injector: Span<&AdviceInjectorNode>) -> ControlFlow { - (**self).visit_advice_injector(injector) + fn visit_system_event(&mut self, sys_event: Span<&SystemEventNode>) -> ControlFlow { + (**self).visit_system_event(sys_event) } fn visit_debug_options(&mut self, options: Span<&DebugOptions>) -> ControlFlow { (**self).visit_debug_options(options) @@ -315,7 +315,7 @@ where | MemStoreWImm(ref imm) | Emit(ref imm) | Trace(ref imm) => visitor.visit_immediate_u32(imm), - AdvInject(ref injector) => visitor.visit_advice_injector(Span::new(span, injector)), + SysEvent(ref sys_event) => visitor.visit_system_event(Span::new(span, sys_event)), Exec(ref target) => visitor.visit_exec(target), Call(ref target) => visitor.visit_call(target), SysCall(ref target) => visitor.visit_syscall(target), @@ -346,32 +346,11 @@ where } } -pub fn visit_advice_injector( - visitor: &mut V, - node: Span<&AdviceInjectorNode>, -) -> ControlFlow +pub fn visit_system_event(_visitor: &mut V, _node: Span<&SystemEventNode>) -> ControlFlow where V: ?Sized + Visit, { - match node.into_inner() { - AdviceInjectorNode::PushMapValImm { offset: ref imm } - | AdviceInjectorNode::PushMapValNImm { offset: ref imm } - | AdviceInjectorNode::InsertHdwordImm { domain: ref imm } => { - visitor.visit_immediate_u8(imm) - }, - AdviceInjectorNode::PushU64Div - | AdviceInjectorNode::PushExt2intt - | AdviceInjectorNode::PushSmtGet - | AdviceInjectorNode::PushSmtSet - | AdviceInjectorNode::PushSmtPeek - | AdviceInjectorNode::PushMapVal - | AdviceInjectorNode::PushMapValN - | AdviceInjectorNode::PushMtNode - | AdviceInjectorNode::InsertMem - | AdviceInjectorNode::InsertHdword - | AdviceInjectorNode::InsertHperm - | AdviceInjectorNode::PushSignature { .. } => ControlFlow::Continue(()), - } + ControlFlow::Continue(()) } pub fn visit_debug_options(visitor: &mut V, options: Span<&DebugOptions>) -> ControlFlow @@ -531,11 +510,8 @@ pub trait VisitMut { fn visit_mut_inst(&mut self, inst: &mut Span) -> ControlFlow { visit_mut_inst(self, inst) } - fn visit_mut_advice_injector( - &mut self, - injector: Span<&mut AdviceInjectorNode>, - ) -> ControlFlow { - visit_mut_advice_injector(self, injector) + fn visit_mut_system_event(&mut self, sys_event: Span<&mut SystemEventNode>) -> ControlFlow { + visit_mut_system_event(self, sys_event) } fn visit_mut_debug_options(&mut self, options: Span<&mut DebugOptions>) -> ControlFlow { visit_mut_debug_options(self, options) @@ -603,11 +579,8 @@ where fn visit_mut_inst(&mut self, inst: &mut Span) -> ControlFlow { (**self).visit_mut_inst(inst) } - fn visit_mut_advice_injector( - &mut self, - injector: Span<&mut AdviceInjectorNode>, - ) -> ControlFlow { - (**self).visit_mut_advice_injector(injector) + fn visit_mut_system_event(&mut self, sys_event: Span<&mut SystemEventNode>) -> ControlFlow { + (**self).visit_mut_system_event(sys_event) } fn visit_mut_debug_options(&mut self, options: Span<&mut DebugOptions>) -> ControlFlow { (**self).visit_mut_debug_options(options) @@ -767,7 +740,7 @@ where | MemStoreWImm(ref mut imm) | Emit(ref mut imm) | Trace(ref mut imm) => visitor.visit_mut_immediate_u32(imm), - AdvInject(ref mut injector) => visitor.visit_mut_advice_injector(Span::new(span, injector)), + SysEvent(ref mut sys_event) => visitor.visit_mut_system_event(Span::new(span, sys_event)), Exec(ref mut target) => visitor.visit_mut_exec(target), Call(ref mut target) => visitor.visit_mut_call(target), SysCall(ref mut target) => visitor.visit_mut_syscall(target), @@ -798,32 +771,14 @@ where } } -pub fn visit_mut_advice_injector( - visitor: &mut V, - node: Span<&mut AdviceInjectorNode>, +pub fn visit_mut_system_event( + _visitor: &mut V, + _node: Span<&mut SystemEventNode>, ) -> ControlFlow where V: ?Sized + VisitMut, { - match node.into_inner() { - AdviceInjectorNode::PushMapValImm { offset: ref mut imm } - | AdviceInjectorNode::PushMapValNImm { offset: ref mut imm } - | AdviceInjectorNode::InsertHdwordImm { domain: ref mut imm } => { - visitor.visit_mut_immediate_u8(imm) - }, - AdviceInjectorNode::PushU64Div - | AdviceInjectorNode::PushExt2intt - | AdviceInjectorNode::PushSmtGet - | AdviceInjectorNode::PushSmtSet - | AdviceInjectorNode::PushSmtPeek - | AdviceInjectorNode::PushMapVal - | AdviceInjectorNode::PushMapValN - | AdviceInjectorNode::PushMtNode - | AdviceInjectorNode::InsertMem - | AdviceInjectorNode::InsertHdword - | AdviceInjectorNode::InsertHperm - | AdviceInjectorNode::PushSignature { .. } => ControlFlow::Continue(()), - } + ControlFlow::Continue(()) } pub fn visit_mut_debug_options( diff --git a/assembly/src/errors.rs b/assembly/src/errors.rs index 4fd06609ef..06ff9ab3b9 100644 --- a/assembly/src/errors.rs +++ b/assembly/src/errors.rs @@ -4,7 +4,7 @@ use vm_core::mast::MastForestError; use crate::{ ast::QualifiedProcedureName, - diagnostics::{Diagnostic, RelatedError, RelatedLabel, Report, SourceFile}, + diagnostics::{Diagnostic, RelatedError, RelatedLabel, SourceFile}, LibraryNamespace, LibraryPath, SourceSpan, }; @@ -61,6 +61,15 @@ pub enum AssemblyError { source_file: Option>, callee: QualifiedProcedureName, }, + #[error("invalid local word index: {local_addr}")] + #[diagnostic(help("the index to a local word must be a multiple of 4"))] + InvalidLocalWordIndex { + #[label] + span: SourceSpan, + #[source_code] + source_file: Option>, + local_addr: u16, + }, #[error("invalid use of 'caller' instruction outside of kernel")] #[diagnostic(help( "the 'caller' instruction is only allowed in procedures defined in a kernel" @@ -81,13 +90,16 @@ pub enum AssemblyError { }, #[error(transparent)] #[diagnostic(transparent)] - Other(#[from] RelatedError), - #[error(transparent)] - Forest(#[from] MastForestError), + Other(RelatedError), + // Technically MastForestError is the source error here, but since AssemblyError is converted + // into a Report and that doesn't implement core::error::Error, treating MastForestError as a + // source error would effectively swallow it, so we include it in the error message instead. + #[error("{0}: {1}")] + Forest(&'static str, MastForestError), } -impl From for AssemblyError { - fn from(report: Report) -> Self { - Self::Other(RelatedError::new(report)) +impl AssemblyError { + pub(super) fn forest_error(message: &'static str, source: MastForestError) -> Self { + Self::Forest(message, source) } } diff --git a/assembly/src/library/error.rs b/assembly/src/library/error.rs index 3df795ca3a..9d3c58361f 100644 --- a/assembly/src/library/error.rs +++ b/assembly/src/library/error.rs @@ -4,13 +4,17 @@ use crate::{ast::QualifiedProcedureName, diagnostics::Diagnostic}; #[derive(Debug, thiserror::Error, Diagnostic)] pub enum LibraryError { - #[error("kernel library must contain at least one exported procedure")] + #[error("library must contain at least one exported procedure")] #[diagnostic()] - EmptyKernel, + NoExport, #[error("invalid export in kernel library: {procedure_path}")] InvalidKernelExport { procedure_path: QualifiedProcedureName }, - #[error(transparent)] - Kernel(#[from] KernelError), + // Technically KernelError is the source error here, but since LibraryError is sometimes + // converted into a Report and that doesn't implement core::error::Error, treating + // KernelError as a source error would effectively swallow it, so we include it in the + // error message instead. + #[error("failed to convert library into kernel library: {0}")] + KernelConversion(KernelError), #[error("invalid export: no procedure root for {procedure_path} procedure")] NoProcedureRootForExport { procedure_path: QualifiedProcedureName }, } diff --git a/assembly/src/library/mod.rs b/assembly/src/library/mod.rs index 7e2d35789a..38cadb3ae5 100644 --- a/assembly/src/library/mod.rs +++ b/assembly/src/library/mod.rs @@ -72,12 +72,16 @@ impl Library { /// Constructs a new [`Library`] from the provided MAST forest and a set of exports. /// /// # Errors + /// Returns an error if the set of exports is empty. /// Returns an error if any of the specified exports do not have a corresponding procedure root /// in the provided MAST forest. pub fn new( mast_forest: Arc, exports: BTreeMap, ) -> Result { + if exports.is_empty() { + return Err(LibraryError::NoExport); + } for (fqn, &proc_body_id) in exports.iter() { if !mast_forest.is_procedure_root(proc_body_id) { return Err(LibraryError::NoProcedureRootForExport { procedure_path: fqn.clone() }); @@ -177,6 +181,9 @@ impl Deserializable for Library { let mast_forest = Arc::new(MastForest::read_from(source)?); let num_exports = source.read_usize()?; + if num_exports == 0 { + return Err(DeserializationError::InvalidValue(String::from("No exported procedures"))); + }; let mut exports = BTreeMap::new(); for _ in 0..num_exports { let proc_module = source.read()?; @@ -353,10 +360,6 @@ impl TryFrom for KernelLibrary { type Error = LibraryError; fn try_from(library: Library) -> Result { - if library.exports.is_empty() { - return Err(LibraryError::EmptyKernel); - } - let kernel_path = LibraryPath::from(LibraryNamespace::Kernel); let mut proc_digests = Vec::with_capacity(library.exports.len()); @@ -375,7 +378,7 @@ impl TryFrom for KernelLibrary { kernel_module.add_procedure(proc_path.name.clone(), proc_digest); } - let kernel = Kernel::new(&proc_digests)?; + let kernel = Kernel::new(&proc_digests).map_err(LibraryError::KernelConversion)?; Ok(Self { kernel, diff --git a/assembly/src/library/namespace.rs b/assembly/src/library/namespace.rs index 4a0510618c..e9a41e191e 100644 --- a/assembly/src/library/namespace.rs +++ b/assembly/src/library/namespace.rs @@ -13,9 +13,9 @@ use crate::{ // ================================================================================================ /// Represents an error when parsing or validating a library namespace -#[derive(Debug, thiserror::Error, Diagnostic, PartialEq, Eq)] +#[derive(Debug, thiserror::Error, Diagnostic)] pub enum LibraryNamespaceError { - #[error("invalid library namespace name: cannot be a empty")] + #[error("invalid library namespace name: cannot be empty")] #[diagnostic()] Empty, #[error("invalid library namespace name: too many characters")] @@ -112,7 +112,7 @@ impl LibraryNamespace { if matches!(source, Self::KERNEL_PATH | Self::EXEC_PATH | Self::ANON_PATH) { return Ok(()); } - if source.as_bytes().len() > Self::MAX_LENGTH { + if source.len() > Self::MAX_LENGTH { return Err(LibraryNamespaceError::Length); } if !source.starts_with(|c: char| c.is_ascii_lowercase() && c.is_ascii_alphabetic()) { diff --git a/assembly/src/library/path.rs b/assembly/src/library/path.rs index 8175339dd2..4c8f110072 100644 --- a/assembly/src/library/path.rs +++ b/assembly/src/library/path.rs @@ -18,18 +18,18 @@ use crate::{ }; /// Represents errors that can occur when creating, parsing, or manipulating [LibraryPath]s -#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[derive(Debug, thiserror::Error)] pub enum PathError { #[error("invalid library path: cannot be empty")] Empty, #[error("invalid library path component: cannot be empty")] EmptyComponent, #[error("invalid library path component: {0}")] - InvalidComponent(#[from] crate::ast::IdentError), + InvalidComponent(crate::ast::IdentError), #[error("invalid library path: contains invalid utf8 byte sequences")] InvalidUtf8, #[error(transparent)] - InvalidNamespace(#[from] crate::library::LibraryNamespaceError), + InvalidNamespace(crate::library::LibraryNamespaceError), #[error("cannot join a path with reserved name to other paths")] UnsupportedJoin, } @@ -192,8 +192,8 @@ impl LibraryPath { /// Return the size in bytes of this path when displayed as a string pub fn byte_len(&self) -> usize { - self.inner.components.iter().map(|c| c.as_bytes().len()).sum::() - + self.inner.ns.as_str().as_bytes().len() + self.inner.components.iter().map(|c| c.len()).sum::() + + self.inner.ns.as_str().len() + (self.inner.components.len() * 2) } @@ -429,7 +429,9 @@ impl<'a> TryFrom>> for LibraryPath { let ns = match iter.next() { None => return Err(PathError::Empty), Some(LibraryPathComponent::Namespace(ns)) => ns.clone(), - Some(LibraryPathComponent::Normal(ident)) => LibraryNamespace::try_from(ident.clone())?, + Some(LibraryPathComponent::Normal(ident)) => { + LibraryNamespace::try_from(ident.clone()).map_err(PathError::InvalidNamespace)? + }, }; let mut components = Components::default(); for component in iter { diff --git a/assembly/src/parser/error.rs b/assembly/src/parser/error.rs index 4b0fd1bf2b..1f6278b58e 100644 --- a/assembly/src/parser/error.rs +++ b/assembly/src/parser/error.rs @@ -91,7 +91,7 @@ impl fmt::Display for BinErrorKind { // PARSING ERROR // ================================================================================================ -#[derive(Debug, Default, Clone, thiserror::Error, Diagnostic)] +#[derive(Debug, Default, thiserror::Error, Diagnostic)] #[repr(u8)] pub enum ParsingError { #[default] diff --git a/assembly/src/parser/grammar.lalrpop b/assembly/src/parser/grammar.lalrpop index 65b78ae427..378ae1ec50 100644 --- a/assembly/src/parser/grammar.lalrpop +++ b/assembly/src/parser/grammar.lalrpop @@ -43,6 +43,7 @@ extern { "add" => Token::Add, "adv" => Token::Adv, "insert_hdword" => Token::InsertHdword, + "insert_hdword_d" => Token::InsertHdwordWithDomain, "insert_hperm" => Token::InsertHperm, "insert_mem" => Token::InsertMem, "adv_loadw" => Token::AdvLoadw, @@ -54,8 +55,6 @@ extern { "push_mtnode" => Token::PushMtnode, "push_sig" => Token::PushSig, "push_smtpeek" => Token::PushSmtpeek, - "push_smtget" => Token::PushSmtget, - "push_smtset" => Token::PushSmtset, "push_u64div" => Token::PushU64Div, "and" => Token::And, "assert" => Token::Assert, @@ -599,7 +598,7 @@ MacroInst: SmallOpsVec = { #[inline] Inst: Instruction = { - AdviceInjector, + SystemEvent, Call, Debug, InstWithBitSizeImmediate, @@ -666,28 +665,18 @@ Inst: Instruction = { } #[inline] -AdviceInjector: Instruction = { - "adv" "." "insert_hdword" > => { - domain.map(|domain| Instruction::AdvInject(AdviceInjectorNode::InsertHdwordImm { domain })) - .unwrap_or(Instruction::AdvInject(AdviceInjectorNode::InsertHdword)) - }, - "adv" "." "insert_hperm" => Instruction::AdvInject(AdviceInjectorNode::InsertHperm), - "adv" "." "insert_mem" => Instruction::AdvInject(AdviceInjectorNode::InsertMem), - "adv" "." "push_ext2intt" => Instruction::AdvInject(AdviceInjectorNode::PushExt2intt), - "adv" "." "push_mapval" > => { - i.map(|offset| Instruction::AdvInject(AdviceInjectorNode::PushMapValImm { offset })) - .unwrap_or(Instruction::AdvInject(AdviceInjectorNode::PushMapVal)) - }, - "adv" "." "push_mapvaln" > => { - i.map(|offset| Instruction::AdvInject(AdviceInjectorNode::PushMapValNImm { offset })) - .unwrap_or(Instruction::AdvInject(AdviceInjectorNode::PushMapValN)) - }, - "adv" "." "push_mtnode" => Instruction::AdvInject(AdviceInjectorNode::PushMtNode), - "adv" "." "push_sig" "." => Instruction::AdvInject(AdviceInjectorNode::PushSignature { kind }), - "adv" "." "push_smtpeek" => Instruction::AdvInject(AdviceInjectorNode::PushSmtPeek), - "adv" "." "push_smtget" => Instruction::AdvInject(AdviceInjectorNode::PushSmtGet), - "adv" "." "push_smtset" => Instruction::AdvInject(AdviceInjectorNode::PushSmtSet), - "adv" "." "push_u64div" => Instruction::AdvInject(AdviceInjectorNode::PushU64Div), +SystemEvent: Instruction = { + "adv" "." "insert_hdword" => Instruction::SysEvent(SystemEventNode::InsertHdword), + "adv" "." "insert_hdword_d" => Instruction::SysEvent(SystemEventNode::InsertHdwordWithDomain), + "adv" "." "insert_hperm" => Instruction::SysEvent(SystemEventNode::InsertHperm), + "adv" "." "insert_mem" => Instruction::SysEvent(SystemEventNode::InsertMem), + "adv" "." "push_ext2intt" => Instruction::SysEvent(SystemEventNode::PushExt2intt), + "adv" "." "push_mapval" => Instruction::SysEvent(SystemEventNode::PushMapVal), + "adv" "." "push_mapvaln" => Instruction::SysEvent(SystemEventNode::PushMapValN), + "adv" "." "push_mtnode" => Instruction::SysEvent(SystemEventNode::PushMtNode), + "adv" "." "push_sig" "." => Instruction::SysEvent(SystemEventNode::PushSignature { kind }), + "adv" "." "push_smtpeek" => Instruction::SysEvent(SystemEventNode::PushSmtPeek), + "adv" "." "push_u64div" => Instruction::SysEvent(SystemEventNode::PushU64Div), } #[inline] @@ -1681,9 +1670,7 @@ Opcode: &'static str = { "movdnw" => "movdnw", "movup" => "movup", "movupw" => "movupw", - "mtree_get" => "mtree_get", "mtree_merge" => "mtree_merge", - "mtree_set" => "mtree_set", "mtree_verify" => "mtree_verify", "mul" => "mul", "neg" => "neg", diff --git a/assembly/src/parser/scanner.rs b/assembly/src/parser/scanner.rs index dc90470fa3..c95414374e 100644 --- a/assembly/src/parser/scanner.rs +++ b/assembly/src/parser/scanner.rs @@ -41,7 +41,7 @@ pub struct Scanner<'input> { impl<'input> Scanner<'input> { /// Construct a new [Scanner] for the given `source`. pub fn new(input: &'input str) -> Self { - let end = input.as_bytes().len(); + let end = input.len(); assert!(end < u32::MAX as usize, "file too large"); let mut chars = input.char_indices().peekable(); diff --git a/assembly/src/parser/token.rs b/assembly/src/parser/token.rs index 602e274912..94c4f73fd4 100644 --- a/assembly/src/parser/token.rs +++ b/assembly/src/parser/token.rs @@ -138,6 +138,7 @@ pub enum Token<'input> { Add, Adv, InsertHdword, + InsertHdwordWithDomain, InsertHperm, InsertMem, AdvLoadw, @@ -322,6 +323,7 @@ impl fmt::Display for Token<'_> { Token::Add => write!(f, "add"), Token::Adv => write!(f, "adv"), Token::InsertHdword => write!(f, "insert_hdword"), + Token::InsertHdwordWithDomain => write!(f, "insert_hdword_d"), Token::InsertHperm => write!(f, "insert_hperm"), Token::InsertMem => write!(f, "insert_mem"), Token::AdvLoadw => write!(f, "adv_loadw"), @@ -514,6 +516,7 @@ impl<'input> Token<'input> { Token::Add | Token::Adv | Token::InsertHdword + | Token::InsertHdwordWithDomain | Token::InsertHperm | Token::InsertMem | Token::AdvLoadw @@ -658,6 +661,7 @@ impl<'input> Token<'input> { ("add", Token::Add), ("adv", Token::Adv), ("insert_hdword", Token::InsertHdword), + ("insert_hdword_d", Token::InsertHdwordWithDomain), ("insert_hperm", Token::InsertHperm), ("insert_mem", Token::InsertMem), ("adv_loadw", Token::AdvLoadw), @@ -860,7 +864,7 @@ impl<'input> Token<'input> { // No match, it's an ident None => Token::Ident(s), // If the match is not exact, it's an ident - Some(matched) if matched.len() != s.as_bytes().len() => Token::Ident(s), + Some(matched) if matched.len() != s.len() => Token::Ident(s), // Otherwise clone the Token corresponding to the keyword that was matched Some(matched) => Self::KEYWORDS[matched.pattern().as_usize()].1.clone(), } diff --git a/assembly/src/testing.rs b/assembly/src/testing.rs index 2ba14bcfe8..d87b0a24e3 100644 --- a/assembly/src/testing.rs +++ b/assembly/src/testing.rs @@ -291,7 +291,7 @@ impl TestContext { /// other modules. #[track_caller] pub fn add_module(&mut self, module: impl Compile) -> Result<(), Report> { - self.assembler.add_module(module) + self.assembler.add_module(module).map(|_| ()) } /// Add a module to the [Assembler] constructed by this context, with the fully-qualified @@ -305,13 +305,15 @@ impl TestContext { path: LibraryPath, source: impl Compile, ) -> Result<(), Report> { - self.assembler.add_module_with_options( - source, - CompileOptions { - path: Some(path), - ..CompileOptions::for_library() - }, - ) + self.assembler + .add_module_with_options( + source, + CompileOptions { + path: Some(path), + ..CompileOptions::for_library() + }, + ) + .map(|_| ()) } /// Add the modules of `library` to the [Assembler] constructed by this context. diff --git a/assembly/src/tests.rs b/assembly/src/tests.rs index 0cc537e3a2..e62802cce9 100644 --- a/assembly/src/tests.rs +++ b/assembly/src/tests.rs @@ -822,12 +822,12 @@ fn mem_operations_with_constants() -> TestResult { // Define constant values const PROC_LOC_STORE_PTR: u64 = 0; const PROC_LOC_LOAD_PTR: u64 = 1; - const PROC_LOC_STOREW_PTR: u64 = 2; - const PROC_LOC_LOADW_PTR: u64 = 3; - const GLOBAL_STORE_PTR: u64 = 4; - const GLOBAL_LOAD_PTR: u64 = 5; - const GLOBAL_STOREW_PTR: u64 = 6; - const GLOBAL_LOADW_PTR: u64 = 7; + const PROC_LOC_STOREW_PTR: u64 = 4; + const PROC_LOC_LOADW_PTR: u64 = 8; + const GLOBAL_STORE_PTR: u64 = 12; + const GLOBAL_LOAD_PTR: u64 = 13; + const GLOBAL_STOREW_PTR: u64 = 16; + const GLOBAL_LOADW_PTR: u64 = 20; let source = source_file!( &context, @@ -842,7 +842,7 @@ fn mem_operations_with_constants() -> TestResult { const.GLOBAL_STOREW_PTR={GLOBAL_STOREW_PTR} const.GLOBAL_LOADW_PTR={GLOBAL_LOADW_PTR} - proc.test_const_loc.4 + proc.test_const_loc.12 # constant should resolve using locaddr operation locaddr.PROC_LOC_STORE_PTR @@ -885,7 +885,7 @@ fn mem_operations_with_constants() -> TestResult { &context, format!( "\ - proc.test_const_loc.4 + proc.test_const_loc.12 # constant should resolve using locaddr operation locaddr.{PROC_LOC_STORE_PTR} @@ -1793,36 +1793,37 @@ fn program_with_proc_locals() -> TestResult { let source = source_file!( &context, "\ - proc.foo.1 \ + proc.foo.4 \ loc_store.0 \ add \ loc_load.0 \ mul \ end \ begin \ - push.4 push.3 push.2 \ + push.10 push.9 push.8 \ exec.foo \ end" ); let program = context.assemble(source)?; + // Note: 18446744069414584317 == -4 (mod 2^64 - 2^32 + 1) let expected = "\ begin basic_block + push(10) + push(9) + push(8) push(4) - push(3) - push(2) - push(1) fmpupdate - pad + push(18446744069414584317) fmpadd mstore drop add - pad + push(18446744069414584317) fmpadd mload mul - push(18446744069414584320) + push(18446744069414584317) fmpupdate end end"; @@ -2924,7 +2925,7 @@ fn test_reexported_proc_with_same_name_as_local_proc_diff_locals() { let mod1 = { let source = source_file!( &context, - "export.foo.2 + "export.foo.8 push.1 drop end @@ -2961,7 +2962,7 @@ fn test_reexported_proc_with_same_name_as_local_proc_diff_locals() { use.test::mod1 use.test::mod2 - proc.foo.1 + proc.foo.4 exec.mod1::foo exec.mod2::foo end diff --git a/core/Cargo.toml b/core/Cargo.toml index 106f228858..978f90d0d1 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "miden-core" -version = "0.11.0" +version = "0.12.0" description = "Miden VM core components" -documentation = "https://docs.rs/miden-core/0.11.0" +documentation = "https://docs.rs/miden-core/0.12.0" readme = "README.md" categories = ["emulators", "no-std"] keywords = ["instruction-set", "miden", "program"] @@ -32,24 +32,24 @@ std = [ [dependencies] lock_api = { version = "0.4", features = ["arc_lock"] } -math = { package = "winter-math", version = "0.10", default-features = false } +math = { package = "winter-math", version = "0.11", default-features = false } memchr = { version = "2.7", default-features = false } -miden-crypto = { version = "0.12", default-features = false } +miden-crypto = { version = "0.13", default-features = false } miden-formatting = { version = "0.1", default-features = false } -miette = { package = "miden-miette", version = "7.1", default-features = false, optional = true, features = [ +miette = { package = "miden-miette", version = "8.0", default-features = false, optional = true, features = [ "fancy-no-syscall", "derive" ] } num-derive = { version = "0.4", default-features = false } num-traits = { version = "0.2", default-features = false } parking_lot = { version = "0.12", optional = true } -thiserror = { package = "miden-thiserror", version = "1.0", default-features = false } -winter-utils = { package = "winter-utils", version = "0.10", default-features = false } +thiserror = { workspace = true } +winter-utils = { package = "winter-utils", version = "0.11", default-features = false } [dev-dependencies] loom = "0.7" proptest = "1.5" -rand-utils = { package = "winter-rand-utils", version = "0.10" } +rand-utils = { package = "winter-rand-utils", version = "0.11" } [target.'cfg(loom)'.dependencies] loom = "0.7" diff --git a/processor/src/host/advice/map.rs b/core/src/advice/map.rs similarity index 69% rename from processor/src/host/advice/map.rs rename to core/src/advice/map.rs index 1f6deeb96f..ad80b23a6e 100644 --- a/processor/src/host/advice/map.rs +++ b/core/src/advice/map.rs @@ -1,15 +1,16 @@ use alloc::{ + boxed::Box, collections::{btree_map::IntoIter, BTreeMap}, vec::Vec, }; -use vm_core::{ +use miden_crypto::{utils::collections::KvMap, Felt}; + +use crate::{ crypto::hash::RpoDigest, utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}, }; -use super::Felt; - // ADVICE MAP // ================================================================================================ @@ -38,8 +39,18 @@ impl AdviceMap { } /// Removes the value associated with the key and returns the removed element. - pub fn remove(&mut self, key: RpoDigest) -> Option> { - self.0.remove(&key) + pub fn remove(&mut self, key: &RpoDigest) -> Option> { + self.0.remove(key) + } + + /// Returns the number of key value pairs in the advice map. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns true if the advice map is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() } } @@ -58,6 +69,38 @@ impl IntoIterator for AdviceMap { } } +impl FromIterator<(RpoDigest, Vec)> for AdviceMap { + fn from_iter)>>(iter: T) -> Self { + iter.into_iter().collect::>>().into() + } +} + +impl KvMap> for AdviceMap { + fn get(&self, key: &RpoDigest) -> Option<&Vec> { + self.0.get(key) + } + + fn contains_key(&self, key: &RpoDigest) -> bool { + self.0.contains_key(key) + } + + fn len(&self) -> usize { + self.len() + } + + fn insert(&mut self, key: RpoDigest, value: Vec) -> Option> { + self.insert(key, value) + } + + fn remove(&mut self, key: &RpoDigest) -> Option> { + self.remove(key) + } + + fn iter(&self) -> Box)> + '_> { + Box::new(self.0.iter()) + } +} + impl Extend<(RpoDigest, Vec)> for AdviceMap { fn extend)>>(&mut self, iter: T) { self.0.extend(iter) diff --git a/core/src/advice/mod.rs b/core/src/advice/mod.rs new file mode 100644 index 0000000000..5862d8cc11 --- /dev/null +++ b/core/src/advice/mod.rs @@ -0,0 +1 @@ +pub(super) mod map; diff --git a/core/src/debuginfo/source_file.rs b/core/src/debuginfo/source_file.rs index 2f7fc69f76..9b750a6e0d 100644 --- a/core/src/debuginfo/source_file.rs +++ b/core/src/debuginfo/source_file.rs @@ -515,7 +515,7 @@ impl SourceContent { /// /// Returns `None` if the given index is out of bounds pub fn line_start(&self, line_index: LineIndex) -> Option { - self.line_starts.get(line_index.to_usize()).copied().map(ByteIndex::from) + self.line_starts.get(line_index.to_usize()).copied() } /// Returns the index of the last line in this file diff --git a/core/src/debuginfo/source_manager.rs b/core/src/debuginfo/source_manager.rs index e88e96d3dd..5a71fcb08b 100644 --- a/core/src/debuginfo/source_manager.rs +++ b/core/src/debuginfo/source_manager.rs @@ -1,9 +1,11 @@ use alloc::{ + boxed::Box, collections::BTreeMap, string::{String, ToString}, sync::Arc, vec::Vec, }; +use core::error::Error; use super::*; @@ -69,7 +71,6 @@ impl TryFrom for SourceId { /// The set of errors which may be raised by a [SourceManager] #[derive(Debug, thiserror::Error)] -#[non_exhaustive] pub enum SourceManagerError { /// A [SourceId] was provided to a [SourceManager] which was allocated by a different /// [SourceManager] @@ -78,10 +79,26 @@ pub enum SourceManagerError { /// An attempt was made to read content using invalid byte indices #[error("attempted to read content out of bounds")] InvalidBounds, - /// An attempt to load a source file failed due to an I/O error - #[cfg(feature = "std")] - #[error(transparent)] - LoadFailed(#[from] std::io::Error), + /// Custom error variant for implementors of the trait. + #[error("{error_msg}")] + Custom { + error_msg: Box, + // thiserror will return this when calling Error::source on SourceManagerError. + source: Option>, + }, +} + +impl SourceManagerError { + pub fn custom(message: String) -> Self { + Self::Custom { error_msg: message.into(), source: None } + } + + pub fn custom_with_source(message: String, source: impl Error + Send + Sync + 'static) -> Self { + Self::Custom { + error_msg: message.into(), + source: Some(Box::new(source)), + } + } } pub trait SourceManager { @@ -201,7 +218,12 @@ pub trait SourceManagerExt: SourceManager { let name = Arc::from(name.into_owned().into_boxed_str()); let content = std::fs::read_to_string(path) .map(|s| SourceContent::new(Arc::clone(&name), s.into_boxed_str())) - .map_err(SourceManagerError::LoadFailed)?; + .map_err(|source| { + SourceManagerError::custom_with_source( + format!("failed to load filed at `{}`", path.display()), + source, + ) + })?; Ok(self.load_from_raw_parts(name, content)) } @@ -367,3 +389,15 @@ impl SourceManager for DefaultSourceManager { .ok_or(SourceManagerError::InvalidBounds) } } + +#[cfg(test)] +mod error_assertions { + use super::*; + + /// Asserts at compile time that the passed error has Send + Sync + 'static bounds. + fn _assert_error_is_send_sync_static(_: E) {} + + fn _assert_source_manager_error_bounds(err: SourceManagerError) { + _assert_error_is_send_sync_static(err); + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 15f14969d7..650c726573 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -116,11 +116,16 @@ pub mod prettier { mod operations; pub use operations::{ - opcode_constants::*, AdviceInjector, AssemblyOp, DebugOptions, Decorator, DecoratorIterator, - DecoratorList, Operation, SignatureKind, + opcode_constants::*, AssemblyOp, DebugOptions, Decorator, DecoratorIterator, DecoratorList, + Operation, SignatureKind, }; pub mod stack; pub use stack::{StackInputs, StackOutputs}; +pub mod sys_events; + +mod advice; +pub use advice::map::AdviceMap; + pub mod utils; diff --git a/core/src/mast/merger/mod.rs b/core/src/mast/merger/mod.rs index ae0273371a..563bfbe473 100644 --- a/core/src/mast/merger/mod.rs +++ b/core/src/mast/merger/mod.rs @@ -1,6 +1,6 @@ use alloc::{collections::BTreeMap, vec::Vec}; -use miden_crypto::hash::blake::Blake3Digest; +use miden_crypto::{hash::blake::Blake3Digest, utils::collections::KvMap}; use crate::mast::{ DecoratorId, MastForest, MastForestError, MastNode, MastNodeFingerprint, MastNodeId, @@ -65,10 +65,11 @@ impl MastForestMerger { /// /// It does this in three steps: /// - /// 1. Merge all decorators, which is a case of deduplication and creating a decorator id + /// 1. Merge all advice maps, checking for key collisions. + /// 2. Merge all decorators, which is a case of deduplication and creating a decorator id /// mapping which contains how existing [`DecoratorId`]s map to [`DecoratorId`]s in the /// merged forest. - /// 2. Merge all nodes of forests. + /// 3. Merge all nodes of forests. /// - Similar to decorators, node indices might move during merging, so the merger keeps a /// node id mapping as it merges nodes. /// - This is a depth-first traversal over all forests to ensure all children are processed @@ -90,10 +91,13 @@ impl MastForestMerger { /// `replacement` node. Now we can simply add a mapping from the external node to the /// `replacement` node in our node id mapping which means all nodes that referenced the /// external node will point to the `replacement` instead. - /// 3. Finally, we merge all roots of all forests. Here we map the existing root indices to + /// 4. Finally, we merge all roots of all forests. Here we map the existing root indices to /// their potentially new indices in the merged forest and add them to the forest, /// deduplicating in the process, too. fn merge_inner(&mut self, forests: Vec<&MastForest>) -> Result<(), MastForestError> { + for other_forest in forests.iter() { + self.merge_advice_map(other_forest)?; + } for other_forest in forests.iter() { self.merge_decorators(other_forest)?; } @@ -163,6 +167,19 @@ impl MastForestMerger { Ok(()) } + fn merge_advice_map(&mut self, other_forest: &MastForest) -> Result<(), MastForestError> { + for (digest, values) in other_forest.advice_map.iter() { + if let Some(stored_values) = self.mast_forest.advice_map().get(digest) { + if stored_values != values { + return Err(MastForestError::AdviceMapKeyCollisionOnMerge(*digest)); + } + } else { + self.mast_forest.advice_map_mut().insert(*digest, values.clone()); + } + } + Ok(()) + } + fn merge_node( &mut self, forest_idx: usize, diff --git a/core/src/mast/merger/tests.rs b/core/src/mast/merger/tests.rs index b33ae97296..c9100d9f73 100644 --- a/core/src/mast/merger/tests.rs +++ b/core/src/mast/merger/tests.rs @@ -1,4 +1,4 @@ -use miden_crypto::{hash::rpo::RpoDigest, ONE}; +use miden_crypto::{hash::rpo::RpoDigest, Felt, ONE}; use super::*; use crate::{Decorator, Operation}; @@ -794,3 +794,54 @@ fn mast_forest_merge_invalid_decorator_index() { let err = MastForest::merge([&forest_a, &forest_b]).unwrap_err(); assert_matches!(err, MastForestError::DecoratorIdOverflow(_, _)); } + +/// Tests that forest's advice maps are merged correctly. +#[test] +fn mast_forest_merge_advice_maps_merged() { + let mut forest_a = MastForest::new(); + let id_foo = forest_a.add_node(block_foo()).unwrap(); + let id_call_a = forest_a.add_call(id_foo).unwrap(); + forest_a.make_root(id_call_a); + let key_a = RpoDigest::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]); + let value_a = vec![ONE, ONE]; + forest_a.advice_map_mut().insert(key_a, value_a.clone()); + + let mut forest_b = MastForest::new(); + let id_bar = forest_b.add_node(block_bar()).unwrap(); + let id_call_b = forest_b.add_call(id_bar).unwrap(); + forest_b.make_root(id_call_b); + let key_b = RpoDigest::new([Felt::new(1), Felt::new(3), Felt::new(2), Felt::new(1)]); + let value_b = vec![Felt::new(2), Felt::new(2)]; + forest_b.advice_map_mut().insert(key_b, value_b.clone()); + + let (merged, _root_maps) = MastForest::merge([&forest_a, &forest_b]).unwrap(); + + let merged_advice_map = merged.advice_map(); + assert_eq!(merged_advice_map.len(), 2); + assert_eq!(merged_advice_map.get(&key_a).unwrap(), &value_a); + assert_eq!(merged_advice_map.get(&key_b).unwrap(), &value_b); +} + +/// Tests that an error is returned when advice maps have a key collision. +#[test] +fn mast_forest_merge_advice_maps_collision() { + let mut forest_a = MastForest::new(); + let id_foo = forest_a.add_node(block_foo()).unwrap(); + let id_call_a = forest_a.add_call(id_foo).unwrap(); + forest_a.make_root(id_call_a); + let key_a = RpoDigest::new([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]); + let value_a = vec![ONE, ONE]; + forest_a.advice_map_mut().insert(key_a, value_a.clone()); + + let mut forest_b = MastForest::new(); + let id_bar = forest_b.add_node(block_bar()).unwrap(); + let id_call_b = forest_b.add_call(id_bar).unwrap(); + forest_b.make_root(id_call_b); + // The key collides with key_a in the forest_a. + let key_b = key_a; + let value_b = vec![Felt::new(2), Felt::new(2)]; + forest_b.advice_map_mut().insert(key_b, value_b.clone()); + + let err = MastForest::merge([&forest_a, &forest_b]).unwrap_err(); + assert_matches!(err, MastForestError::AdviceMapKeyCollisionOnMerge(_)); +} diff --git a/core/src/mast/mod.rs b/core/src/mast/mod.rs index 444fedc5c6..d71be872d9 100644 --- a/core/src/mast/mod.rs +++ b/core/src/mast/mod.rs @@ -16,7 +16,7 @@ pub use node::{ }; use winter_utils::{ByteWriter, DeserializationError, Serializable}; -use crate::{Decorator, DecoratorList, Operation}; +use crate::{AdviceMap, Decorator, DecoratorList, Operation}; mod serialization; @@ -50,6 +50,9 @@ pub struct MastForest { /// All the decorators included in the MAST forest. decorators: Vec, + + /// Advice map to be loaded into the VM prior to executing procedures from this MAST forest. + advice_map: AdviceMap, } // ------------------------------------------------------------------------------------------------ @@ -66,8 +69,6 @@ impl MastForest { impl MastForest { /// The maximum number of nodes that can be stored in a single MAST forest. const MAX_NODES: usize = (1 << 30) - 1; - /// The maximum number of decorators that can be stored in a single MAST forest. - const MAX_DECORATORS: usize = Self::MAX_NODES; /// Adds a decorator to the forest, and returns the associated [`DecoratorId`]. pub fn add_decorator(&mut self, decorator: Decorator) -> Result { @@ -463,6 +464,18 @@ impl MastForest { pub fn nodes(&self) -> &[MastNode] { &self.nodes } + + pub fn decorators(&self) -> &[Decorator] { + &self.decorators + } + + pub fn advice_map(&self) -> &AdviceMap { + &self.advice_map + } + + pub fn advice_map_mut(&mut self) -> &mut AdviceMap { + &mut self.advice_map + } } impl Index for MastForest { @@ -528,6 +541,21 @@ impl MastNodeId { Self::from_u32_with_node_count(value, mast_forest.nodes.len()) } + /// Returns a new [`MastNodeId`] with the provided `node_id`, or an error if `node_id` is + /// greater than the number of nodes in the [`MastForest`] for which this ID is being + /// constructed. + pub fn from_usize_safe( + node_id: usize, + mast_forest: &MastForest, + ) -> Result { + let node_id: u32 = node_id.try_into().map_err(|_| { + DeserializationError::InvalidValue(format!( + "node id '{node_id}' does not fit into a u32" + )) + })?; + MastNodeId::from_u32_safe(node_id, mast_forest) + } + /// Returns a new [`MastNodeId`] from the given `value` without checking its validity. pub(crate) fn new_unchecked(value: u32) -> Self { Self(value) @@ -671,22 +699,18 @@ impl Serializable for DecoratorId { /// Represents the types of errors that can occur when dealing with MAST forest. #[derive(Debug, thiserror::Error, PartialEq)] pub enum MastForestError { - #[error( - "invalid decorator count: MAST forest exceeds the maximum of {} decorators", - u32::MAX - )] + #[error("MAST forest decorator count exceeds the maximum of {} decorators", u32::MAX)] TooManyDecorators, - #[error( - "invalid node count: MAST forest exceeds the maximum of {} nodes", - MastForest::MAX_NODES - )] + #[error("MAST forest node count exceeds the maximum of {} nodes", MastForest::MAX_NODES)] TooManyNodes, - #[error("node id: {0} is greater than or equal to forest length: {1}")] + #[error("node id {0} is greater than or equal to forest length {1}")] NodeIdOverflow(MastNodeId, usize), - #[error("decorator id: {0} is greater than or equal to decorator count: {1}")] + #[error("decorator id {0} is greater than or equal to decorator count {1}")] DecoratorIdOverflow(DecoratorId, usize), #[error("basic block cannot be created from an empty list of operations")] EmptyBasicBlock, - #[error("decorator root of child with node id {0} is missing but required for fingerprint computation")] + #[error("decorator root of child with node id {0} is missing but is required for fingerprint computation")] ChildFingerprintMissing(MastNodeId), + #[error("advice map key {0} already exists when merging forests")] + AdviceMapKeyCollisionOnMerge(RpoDigest), } diff --git a/core/src/mast/node/basic_block_node/mod.rs b/core/src/mast/node/basic_block_node/mod.rs index 726decbc72..9a545f66c2 100644 --- a/core/src/mast/node/basic_block_node/mod.rs +++ b/core/src/mast/node/basic_block_node/mod.rs @@ -224,6 +224,12 @@ impl BasicBlockNode { decorator_ids.into_iter().map(|decorator_id| (after_last_op_idx, decorator_id)), ); } + + /// Used to initialize decorators for the [`BasicBlockNode`]. Replaces the existing decorators + /// with the given ['DecoratorList']. + pub fn set_decorators(&mut self, decorator_list: DecoratorList) { + self.decorators = decorator_list; + } } // PRETTY PRINTING diff --git a/core/src/mast/serialization/basic_blocks.rs b/core/src/mast/serialization/basic_blocks.rs index 86cf884717..1e784d5824 100644 --- a/core/src/mast/serialization/basic_blocks.rs +++ b/core/src/mast/serialization/basic_blocks.rs @@ -2,11 +2,8 @@ use alloc::vec::Vec; use winter_utils::{ByteReader, DeserializationError, Serializable, SliceReader}; -use super::{DecoratorDataOffset, NodeDataOffset}; -use crate::{ - mast::{BasicBlockNode, DecoratorId, MastForest}, - DecoratorList, Operation, -}; +use super::NodeDataOffset; +use crate::{mast::BasicBlockNode, Operation}; // BASIC BLOCK DATA BUILDER // ================================================================================================ @@ -26,24 +23,15 @@ impl BasicBlockDataBuilder { /// Mutators impl BasicBlockDataBuilder { - /// Encodes a [`BasicBlockNode`] into the serialized [`crate::mast::MastForest`] data field. - pub fn encode_basic_block( - &mut self, - basic_block: &BasicBlockNode, - ) -> (NodeDataOffset, Option) { + /// Encodes a [`BasicBlockNode`]'s operations into the serialized [`crate::mast::MastForest`] + /// data field. The decorators are not encoded because are stored separately + pub fn encode_basic_block(&mut self, basic_block: &BasicBlockNode) -> NodeDataOffset { let ops_offset = self.node_data.len() as NodeDataOffset; let operations: Vec = basic_block.operations().copied().collect(); operations.write_into(&mut self.node_data); - if basic_block.decorators().is_empty() { - (ops_offset, None) - } else { - let decorator_data_offset = self.node_data.len() as DecoratorDataOffset; - basic_block.decorators().write_into(&mut self.node_data); - - (ops_offset, Some(decorator_data_offset)) - } + ops_offset } /// Returns the serialized [`crate::mast::MastForest`] node data field. @@ -68,35 +56,14 @@ impl<'a> BasicBlockDataDecoder<'a> { /// Decoding methods impl BasicBlockDataDecoder<'_> { - pub fn decode_operations_and_decorators( + pub fn decode_operations( &self, ops_offset: NodeDataOffset, - decorator_list_offset: NodeDataOffset, - mast_forest: &MastForest, - ) -> Result<(Vec, DecoratorList), DeserializationError> { + ) -> Result, DeserializationError> { // Read ops let mut ops_data_reader = SliceReader::new(&self.node_data[ops_offset as usize..]); let operations: Vec = ops_data_reader.read()?; - // read decorators only if there are some - let decorators = if decorator_list_offset == MastForest::MAX_DECORATORS as u32 { - Vec::new() - } else { - let mut decorators_data_reader = - SliceReader::new(&self.node_data[decorator_list_offset as usize..]); - - let num_decorators: usize = decorators_data_reader.read()?; - (0..num_decorators) - .map(|_| { - let decorator_loc: usize = decorators_data_reader.read()?; - let decorator_id = - DecoratorId::from_u32_safe(decorators_data_reader.read()?, mast_forest)?; - - Ok((decorator_loc, decorator_id)) - }) - .collect::>()? - }; - - Ok((operations, decorators)) + Ok(operations) } } diff --git a/core/src/mast/serialization/decorator.rs b/core/src/mast/serialization/decorator.rs index a8b2041e3c..2e7ef6c4c1 100644 --- a/core/src/mast/serialization/decorator.rs +++ b/core/src/mast/serialization/decorator.rs @@ -1,6 +1,5 @@ use alloc::vec::Vec; -use miden_crypto::Felt; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; use winter_utils::{ @@ -11,7 +10,7 @@ use super::{ string_table::{StringTable, StringTableBuilder}, DecoratorDataOffset, }; -use crate::{AdviceInjector, AssemblyOp, DebugOptions, Decorator, SignatureKind}; +use crate::{AssemblyOp, DebugOptions, Decorator}; /// Represents a serialized [`Decorator`]. /// @@ -27,13 +26,9 @@ pub struct DecoratorInfo { impl DecoratorInfo { pub fn from_decorator( decorator: &Decorator, - data_builder: &mut DecoratorDataBuilder, - string_table_builder: &mut StringTableBuilder, + decorator_data_offset: DecoratorDataOffset, ) -> Self { let variant = EncodedDecoratorVariant::from(decorator); - let decorator_data_offset = - data_builder.encode_decorator_data(decorator, string_table_builder).unwrap_or(0); - Self { variant, decorator_data_offset } } @@ -48,75 +43,6 @@ impl DecoratorInfo { let mut data_reader = SliceReader::new(&decorator_data[self.decorator_data_offset as usize..]); match self.variant { - EncodedDecoratorVariant::AdviceInjectorMerkleNodeMerge => { - Ok(Decorator::Advice(AdviceInjector::MerkleNodeMerge)) - }, - EncodedDecoratorVariant::AdviceInjectorMerkleNodeToStack => { - Ok(Decorator::Advice(AdviceInjector::MerkleNodeToStack)) - }, - EncodedDecoratorVariant::AdviceInjectorUpdateMerkleNode => { - Ok(Decorator::Advice(AdviceInjector::UpdateMerkleNode)) - }, - EncodedDecoratorVariant::AdviceInjectorMapValueToStack => { - let include_len = data_reader.read_bool()?; - let key_offset = data_reader.read_usize()?; - - Ok(Decorator::Advice(AdviceInjector::MapValueToStack { include_len, key_offset })) - }, - EncodedDecoratorVariant::AdviceInjectorU64Div => { - Ok(Decorator::Advice(AdviceInjector::U64Div)) - }, - EncodedDecoratorVariant::AdviceInjectorExt2Inv => { - Ok(Decorator::Advice(AdviceInjector::Ext2Inv)) - }, - EncodedDecoratorVariant::AdviceInjectorExt2Intt => { - Ok(Decorator::Advice(AdviceInjector::Ext2Intt)) - }, - EncodedDecoratorVariant::AdviceInjectorSmtGet => { - Ok(Decorator::Advice(AdviceInjector::SmtGet)) - }, - EncodedDecoratorVariant::AdviceInjectorSmtSet => { - Ok(Decorator::Advice(AdviceInjector::SmtSet)) - }, - EncodedDecoratorVariant::AdviceInjectorSmtPeek => { - Ok(Decorator::Advice(AdviceInjector::SmtPeek)) - }, - EncodedDecoratorVariant::AdviceInjectorU32Clz => { - Ok(Decorator::Advice(AdviceInjector::U32Clz)) - }, - EncodedDecoratorVariant::AdviceInjectorU32Ctz => { - Ok(Decorator::Advice(AdviceInjector::U32Ctz)) - }, - EncodedDecoratorVariant::AdviceInjectorU32Clo => { - Ok(Decorator::Advice(AdviceInjector::U32Clo)) - }, - EncodedDecoratorVariant::AdviceInjectorU32Cto => { - Ok(Decorator::Advice(AdviceInjector::U32Cto)) - }, - EncodedDecoratorVariant::AdviceInjectorILog2 => { - Ok(Decorator::Advice(AdviceInjector::ILog2)) - }, - EncodedDecoratorVariant::AdviceInjectorMemToMap => { - Ok(Decorator::Advice(AdviceInjector::MemToMap)) - }, - EncodedDecoratorVariant::AdviceInjectorHdwordToMap => { - let domain = data_reader.read_u64()?; - let domain = Felt::try_from(domain).map_err(|err| { - DeserializationError::InvalidValue(format!( - "Error when deserializing HdwordToMap decorator domain: {err}" - )) - })?; - - Ok(Decorator::Advice(AdviceInjector::HdwordToMap { domain })) - }, - EncodedDecoratorVariant::AdviceInjectorHpermToMap => { - Ok(Decorator::Advice(AdviceInjector::HpermToMap)) - }, - EncodedDecoratorVariant::AdviceInjectorSigToStack => { - Ok(Decorator::Advice(AdviceInjector::SigToStack { - kind: SignatureKind::RpoFalcon512, - })) - }, EncodedDecoratorVariant::AssemblyOp => { let num_cycles = data_reader.read_u8()?; let should_break = data_reader.read_bool()?; @@ -215,25 +141,6 @@ impl Deserializable for DecoratorInfo { #[derive(Debug, FromPrimitive, ToPrimitive)] #[repr(u8)] pub enum EncodedDecoratorVariant { - AdviceInjectorMerkleNodeMerge, - AdviceInjectorMerkleNodeToStack, - AdviceInjectorUpdateMerkleNode, - AdviceInjectorMapValueToStack, - AdviceInjectorU64Div, - AdviceInjectorExt2Inv, - AdviceInjectorExt2Intt, - AdviceInjectorSmtGet, - AdviceInjectorSmtSet, - AdviceInjectorSmtPeek, - AdviceInjectorU32Clz, - AdviceInjectorU32Ctz, - AdviceInjectorU32Clo, - AdviceInjectorU32Cto, - AdviceInjectorILog2, - AdviceInjectorMemToMap, - AdviceInjectorHdwordToMap, - AdviceInjectorHpermToMap, - AdviceInjectorSigToStack, AssemblyOp, DebugOptionsStackAll, DebugOptionsStackTop, @@ -261,29 +168,6 @@ impl EncodedDecoratorVariant { impl From<&Decorator> for EncodedDecoratorVariant { fn from(decorator: &Decorator) -> Self { match decorator { - Decorator::Advice(advice_injector) => match advice_injector { - AdviceInjector::MerkleNodeMerge => Self::AdviceInjectorMerkleNodeMerge, - AdviceInjector::MerkleNodeToStack => Self::AdviceInjectorMerkleNodeToStack, - AdviceInjector::UpdateMerkleNode => Self::AdviceInjectorUpdateMerkleNode, - AdviceInjector::MapValueToStack { include_len: _, key_offset: _ } => { - Self::AdviceInjectorMapValueToStack - }, - AdviceInjector::U64Div => Self::AdviceInjectorU64Div, - AdviceInjector::Ext2Inv => Self::AdviceInjectorExt2Inv, - AdviceInjector::Ext2Intt => Self::AdviceInjectorExt2Intt, - AdviceInjector::SmtGet => Self::AdviceInjectorSmtGet, - AdviceInjector::SmtSet => Self::AdviceInjectorSmtSet, - AdviceInjector::SmtPeek => Self::AdviceInjectorSmtPeek, - AdviceInjector::U32Clz => Self::AdviceInjectorU32Clz, - AdviceInjector::U32Ctz => Self::AdviceInjectorU32Ctz, - AdviceInjector::U32Clo => Self::AdviceInjectorU32Clo, - AdviceInjector::U32Cto => Self::AdviceInjectorU32Cto, - AdviceInjector::ILog2 => Self::AdviceInjectorILog2, - AdviceInjector::MemToMap => Self::AdviceInjectorMemToMap, - AdviceInjector::HdwordToMap { domain: _ } => Self::AdviceInjectorHdwordToMap, - AdviceInjector::HpermToMap => Self::AdviceInjectorHpermToMap, - AdviceInjector::SigToStack { kind: _ } => Self::AdviceInjectorSigToStack, - }, Decorator::AsmOp(_) => Self::AssemblyOp, Decorator::Debug(debug_options) => match debug_options { DebugOptions::StackAll => Self::DebugOptionsStackAll, @@ -322,6 +206,8 @@ impl Deserializable for EncodedDecoratorVariant { #[derive(Debug, Default)] pub struct DecoratorDataBuilder { decorator_data: Vec, + decorator_infos: Vec, + string_table_builder: StringTableBuilder, } /// Constructors @@ -333,50 +219,18 @@ impl DecoratorDataBuilder { /// Mutators impl DecoratorDataBuilder { + pub fn add_decorator(&mut self, decorator: &Decorator) { + let decorator_data_offset = self.encode_decorator_data(decorator).unwrap_or(0); + self.decorator_infos + .push(DecoratorInfo::from_decorator(decorator, decorator_data_offset)); + } + /// If a decorator has extra data to store, encode it in internal data buffer, and return the /// offset of the newly added data. If not, return `None`. - pub fn encode_decorator_data( - &mut self, - decorator: &Decorator, - string_table_builder: &mut StringTableBuilder, - ) -> Option { + pub fn encode_decorator_data(&mut self, decorator: &Decorator) -> Option { let data_offset = self.decorator_data.len() as DecoratorDataOffset; match decorator { - Decorator::Advice(advice_injector) => match advice_injector { - AdviceInjector::MapValueToStack { include_len, key_offset } => { - self.decorator_data.write_bool(*include_len); - self.decorator_data.write_usize(*key_offset); - - Some(data_offset) - }, - AdviceInjector::HdwordToMap { domain } => { - self.decorator_data.extend(domain.as_int().to_le_bytes()); - - Some(data_offset) - }, - - // Note: Since there is only 1 variant, we don't need to write any extra bytes. - AdviceInjector::SigToStack { kind } => match kind { - SignatureKind::RpoFalcon512 => None, - }, - AdviceInjector::MerkleNodeMerge - | AdviceInjector::MerkleNodeToStack - | AdviceInjector::UpdateMerkleNode - | AdviceInjector::U64Div - | AdviceInjector::Ext2Inv - | AdviceInjector::Ext2Intt - | AdviceInjector::SmtGet - | AdviceInjector::SmtSet - | AdviceInjector::SmtPeek - | AdviceInjector::U32Clz - | AdviceInjector::U32Ctz - | AdviceInjector::U32Clo - | AdviceInjector::U32Cto - | AdviceInjector::ILog2 - | AdviceInjector::MemToMap - | AdviceInjector::HpermToMap => None, - }, Decorator::AsmOp(assembly_op) => { self.decorator_data.push(assembly_op.num_cycles()); self.decorator_data.write_bool(assembly_op.should_break()); @@ -385,7 +239,7 @@ impl DecoratorDataBuilder { let loc = assembly_op.location(); self.decorator_data.write_bool(loc.is_some()); if let Some(loc) = loc { - let str_offset = string_table_builder.add_string(loc.path.as_ref()); + let str_offset = self.string_table_builder.add_string(loc.path.as_ref()); self.decorator_data.write_usize(str_offset); self.decorator_data.write_u32(loc.start.to_u32()); self.decorator_data.write_u32(loc.end.to_u32()); @@ -393,13 +247,14 @@ impl DecoratorDataBuilder { // context name { - let str_offset = string_table_builder.add_string(assembly_op.context_name()); + let str_offset = + self.string_table_builder.add_string(assembly_op.context_name()); self.decorator_data.write_usize(str_offset); } // op { - let str_index_in_table = string_table_builder.add_string(assembly_op.op()); + let str_index_in_table = self.string_table_builder.add_string(assembly_op.op()); self.decorator_data.write_usize(str_index_in_table); } @@ -434,7 +289,11 @@ impl DecoratorDataBuilder { } /// Returns the serialized [`crate::mast::MastForest`] decorator data field. - pub fn finalize(self) -> Vec { - self.decorator_data + pub fn finalize(self) -> (Vec, Vec, StringTable) { + ( + self.decorator_data, + self.decorator_infos, + self.string_table_builder.into_table(), + ) } } diff --git a/core/src/mast/serialization/info.rs b/core/src/mast/serialization/info.rs index fa9efe3371..51bbabc150 100644 --- a/core/src/mast/serialization/info.rs +++ b/core/src/mast/serialization/info.rs @@ -1,10 +1,10 @@ +use alloc::vec::Vec; + use miden_crypto::hash::rpo::RpoDigest; use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use super::{basic_blocks::BasicBlockDataDecoder, NodeDataOffset}; -use crate::mast::{ - BasicBlockNode, CallNode, JoinNode, LoopNode, MastForest, MastNode, MastNodeId, SplitNode, -}; +use crate::mast::{BasicBlockNode, CallNode, JoinNode, LoopNode, MastNode, MastNodeId, SplitNode}; // MAST NODE INFO // ================================================================================================ @@ -21,46 +21,32 @@ pub struct MastNodeInfo { } impl MastNodeInfo { - /// Constructs a new [`MastNodeInfo`] from a [`MastNode`], along with an `ops_offset` and - /// `decorator_list_offset` in the case of [`BasicBlockNode`]. + /// Constructs a new [`MastNodeInfo`] from a [`MastNode`], along with an `ops_offset` /// - /// If the represented [`MastNode`] is a [`BasicBlockNode`] that has an empty decorator list, - /// use `MastForest::MAX_DECORATORS` for the value of `decorator_list_offset`. For non-basic - /// block nodes, `ops_offset` and `decorator_list_offset` are ignored, and should be set to 0. - pub fn new( - mast_node: &MastNode, - ops_offset: NodeDataOffset, - decorator_list_offset: NodeDataOffset, - ) -> Self { + /// For non-basic block nodes, `ops_offset` is ignored, and should be set to 0. + pub fn new(mast_node: &MastNode, ops_offset: NodeDataOffset) -> Self { if !matches!(mast_node, &MastNode::Block(_)) { debug_assert_eq!(ops_offset, 0); - debug_assert_eq!(decorator_list_offset, 0); } - let ty = MastNodeType::new(mast_node, ops_offset, decorator_list_offset); + let ty = MastNodeType::new(mast_node, ops_offset); Self { ty, digest: mast_node.digest() } } - /// Attempts to convert this [`MastNodeInfo`] into a [`MastNode`] for the given `mast_forest`. + /// Attempts to convert this [`MastNodeInfo`] into a [`MastNode`]. /// /// The `node_count` is the total expected number of nodes in the [`MastForest`] **after /// deserialization**. pub fn try_into_mast_node( self, - mast_forest: &MastForest, node_count: usize, basic_block_data_decoder: &BasicBlockDataDecoder, ) -> Result { match self.ty { - MastNodeType::Block { ops_offset, decorator_list_offset } => { - let (operations, decorators) = basic_block_data_decoder - .decode_operations_and_decorators( - ops_offset, - decorator_list_offset, - mast_forest, - )?; - let block = BasicBlockNode::new_unsafe(operations, decorators, self.digest); + MastNodeType::Block { ops_offset } => { + let operations = basic_block_data_decoder.decode_operations(ops_offset)?; + let block = BasicBlockNode::new_unsafe(operations, Vec::new(), self.digest); Ok(MastNode::Block(block)) }, MastNodeType::Join { left_child_id, right_child_id } => { @@ -151,8 +137,6 @@ pub enum MastNodeType { Block { // offset of operations in node data ops_offset: u32, - // offset of DecoratorList in node data - decorator_list_offset: u32, } = BLOCK, Call { callee_id: u32, @@ -168,18 +152,11 @@ pub enum MastNodeType { /// Constructors impl MastNodeType { /// Constructs a new [`MastNodeType`] from a [`MastNode`]. - /// - /// If the represented [`MastNode`] is a [`BasicBlockNode`] that has an empty decorator list, - /// use `MastForest::MAX_DECORATORS` for the value of `decorator_list_offset`. - pub fn new( - mast_node: &MastNode, - ops_offset: NodeDataOffset, - decorator_list_offset: NodeDataOffset, - ) -> Self { + pub fn new(mast_node: &MastNode, ops_offset: NodeDataOffset) -> Self { use MastNode::*; match mast_node { - Block(_block_node) => Self::Block { decorator_list_offset, ops_offset }, + Block(_block_node) => Self::Block { ops_offset }, Join(join_node) => Self::Join { left_child_id: join_node.first().0, right_child_id: join_node.second().0, @@ -223,9 +200,7 @@ impl Serializable for MastNodeType { else_branch_id: else_branch, } => Self::encode_u32_pair(if_branch, else_branch), MastNodeType::Loop { body_id: body } => Self::encode_u32_payload(body), - MastNodeType::Block { ops_offset, decorator_list_offset } => { - Self::encode_u32_pair(ops_offset, decorator_list_offset) - }, + MastNodeType::Block { ops_offset } => Self::encode_u32_payload(ops_offset), MastNodeType::Call { callee_id } => Self::encode_u32_payload(callee_id), MastNodeType::SysCall { callee_id } => Self::encode_u32_payload(callee_id), MastNodeType::Dyn => 0, @@ -300,8 +275,8 @@ impl Deserializable for MastNodeType { Ok(Self::Loop { body_id }) }, BLOCK => { - let (ops_offset, decorator_list_offset) = Self::decode_u32_pair(payload); - Ok(Self::Block { ops_offset, decorator_list_offset }) + let ops_offset = Self::decode_u32_payload(payload)?; + Ok(Self::Block { ops_offset }) }, CALL => { let callee_id = Self::decode_u32_payload(payload)?; diff --git a/core/src/mast/serialization/mod.rs b/core/src/mast/serialization/mod.rs index e76f775c5e..3ea52f8206 100644 --- a/core/src/mast/serialization/mod.rs +++ b/core/src/mast/serialization/mod.rs @@ -4,33 +4,45 @@ //! - MAGIC //! - VERSION //! -//! (lengths) -//! - decorators length (`usize`) +//! (sections metadata) //! - nodes length (`usize`) +//! - decorator data section offset (`usize`) (not implemented, see issue #1580) +//! - decorators length (`usize`) //! -//! (procedure roots) +//! (procedure roots section) //! - procedure roots (`Vec`) //! -//! (raw data) +//! (basic block data section) +//! - basic block data +//! +//! (node info section) +//! - MAST node infos (`Vec`) +//! +//! (advice map section) +//! - Advice map (AdviceMap) +//! +//! (decorator data section) //! - Decorator data -//! - Node data //! - String table //! -//! (info structs) +//! (decorator info section) //! - decorator infos (`Vec`) -//! - MAST node infos (`Vec`) //! -//! (before enter and after exit decorators) +//! (basic block decorator lists section) +//! - basic block decorator lists (`Vec<(MastNodeId, Vec<(usize, DecoratorId)>)>`) +//! +//! (before enter and after exit decorators section) //! - before enter decorators (`Vec<(MastNodeId, Vec)>`) //! - after exit decorators (`Vec<(MastNodeId, Vec)>`) use alloc::vec::Vec; use decorator::{DecoratorDataBuilder, DecoratorInfo}; -use string_table::{StringTable, StringTableBuilder}; +use string_table::StringTable; use winter_utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable}; use super::{DecoratorId, MastForest, MastNode, MastNodeId}; +use crate::AdviceMap; mod decorator; @@ -40,6 +52,8 @@ use info::MastNodeInfo; mod basic_blocks; use basic_blocks::{BasicBlockDataBuilder, BasicBlockDataDecoder}; +use crate::DecoratorList; + mod string_table; #[cfg(test)] @@ -79,38 +93,25 @@ const VERSION: [u8; 3] = [0, 0, 0]; impl Serializable for MastForest { fn write_into(&self, target: &mut W) { let mut basic_block_data_builder = BasicBlockDataBuilder::new(); - let mut decorator_data_builder = DecoratorDataBuilder::new(); - let mut string_table_builder = StringTableBuilder::default(); // Set up "before enter" and "after exit" decorators by `MastNodeId` let mut before_enter_decorators: Vec<(usize, Vec)> = Vec::new(); let mut after_exit_decorators: Vec<(usize, Vec)> = Vec::new(); + let mut basic_block_decorators: Vec<(usize, Vec<(usize, DecoratorId)>)> = Vec::new(); + // magic & version target.write_bytes(MAGIC); target.write_bytes(&VERSION); // decorator & node counts - target.write_usize(self.decorators.len()); target.write_usize(self.nodes.len()); + target.write_usize(self.decorators.len()); // roots let roots: Vec = self.roots.iter().map(u32::from).collect(); roots.write_into(target); - // decorators - let decorator_infos: Vec = self - .decorators - .iter() - .map(|decorator| { - DecoratorInfo::from_decorator( - decorator, - &mut decorator_data_builder, - &mut string_table_builder, - ) - }) - .collect(); - // Prepare MAST node infos, but don't store them yet. We store them at the end to make // deserialization more efficient. let mast_node_infos: Vec = self @@ -125,38 +126,49 @@ impl Serializable for MastForest { after_exit_decorators.push((mast_node_id, mast_node.after_exit().to_vec())); } - let (ops_offset, decorator_data_offset) = if let MastNode::Block(basic_block) = - mast_node - { - let (ops_offset, decorator_data_offset) = - basic_block_data_builder.encode_basic_block(basic_block); + let ops_offset = if let MastNode::Block(basic_block) = mast_node { + let ops_offset = basic_block_data_builder.encode_basic_block(basic_block); - (ops_offset, decorator_data_offset.unwrap_or(MastForest::MAX_DECORATORS as u32)) + basic_block_decorators.push((mast_node_id, basic_block.decorators().clone())); + + ops_offset } else { - (0, 0) + 0 }; - MastNodeInfo::new(mast_node, ops_offset, decorator_data_offset) + MastNodeInfo::new(mast_node, ops_offset) }) .collect(); - let decorator_data = decorator_data_builder.finalize(); - let node_data = basic_block_data_builder.finalize(); - let string_table = string_table_builder.into_table(); + let basic_block_data = basic_block_data_builder.finalize(); + basic_block_data.write_into(target); + + // Write node infos + for mast_node_info in mast_node_infos { + mast_node_info.write_into(target); + } + + self.advice_map.write_into(target); + + // write all decorator data below - // Write 3 data buffers + let mut decorator_data_builder = DecoratorDataBuilder::new(); + for decorator in &self.decorators { + decorator_data_builder.add_decorator(decorator) + } + + let (decorator_data, decorator_infos, string_table) = decorator_data_builder.finalize(); + + // decorator data buffers decorator_data.write_into(target); - node_data.write_into(target); string_table.write_into(target); - // Write decorator and node infos + // Write decorator infos for decorator_info in decorator_infos { decorator_info.write_into(target); } - for mast_node_info in mast_node_infos { - mast_node_info.write_into(target); - } + basic_block_decorators.write_into(target); // Write "before enter" and "after exit" decorators before_enter_decorators.write_into(target); @@ -166,34 +178,34 @@ impl Serializable for MastForest { impl Deserializable for MastForest { fn read_from(source: &mut R) -> Result { - let magic: [u8; 5] = source.read_array()?; - if magic != *MAGIC { - return Err(DeserializationError::InvalidValue(format!( - "Invalid magic bytes. Expected '{:?}', got '{:?}'", - *MAGIC, magic - ))); - } - - let version: [u8; 3] = source.read_array()?; - if version != VERSION { - return Err(DeserializationError::InvalidValue(format!( - "Unsupported version. Got '{version:?}', but only '{VERSION:?}' is supported", - ))); - } + read_and_validate_magic(source)?; + read_and_validate_version(source)?; - let decorator_count = source.read_usize()?; + // Reading sections metadata let node_count = source.read_usize()?; + let decorator_count = source.read_usize()?; + + // Reading procedure roots let roots: Vec = Deserializable::read_from(source)?; + + // Reading nodes + let basic_block_data: Vec = Deserializable::read_from(source)?; + let mast_node_infos: Vec = node_infos_iter(source, node_count) + .collect::, DeserializationError>>()?; + + let advice_map = AdviceMap::read_from(source)?; + + // Reading Decorators let decorator_data: Vec = Deserializable::read_from(source)?; - let node_data: Vec = Deserializable::read_from(source)?; let string_table: StringTable = Deserializable::read_from(source)?; + let decorator_infos = decorator_infos_iter(source, decorator_count); + // Constructing MastForest let mut mast_forest = { let mut mast_forest = MastForest::new(); - // decorators - for _ in 0..decorator_count { - let decorator_info = DecoratorInfo::read_from(source)?; + for decorator_info in decorator_infos { + let decorator_info = decorator_info?; let decorator = decorator_info.try_into_decorator(&string_table, &decorator_data)?; @@ -205,15 +217,10 @@ impl Deserializable for MastForest { } // nodes - let basic_block_data_decoder = BasicBlockDataDecoder::new(&node_data); - for _ in 0..node_count { - let mast_node_info = MastNodeInfo::read_from(source)?; - - let node = mast_node_info.try_into_mast_node( - &mast_forest, - node_count, - &basic_block_data_decoder, - )?; + let basic_block_data_decoder = BasicBlockDataDecoder::new(&basic_block_data); + for mast_node_info in mast_node_infos { + let node = + mast_node_info.try_into_mast_node(node_count, &basic_block_data_decoder)?; mast_forest.add_node(node).map_err(|e| { DeserializationError::InvalidValue(format!( @@ -229,31 +236,40 @@ impl Deserializable for MastForest { mast_forest.make_root(root); } + mast_forest.advice_map = advice_map; + mast_forest }; + let basic_block_decorators: Vec<(usize, DecoratorList)> = + read_block_decorators(source, &mast_forest)?; + for (node_id, decorator_list) in basic_block_decorators { + let node_id = MastNodeId::from_usize_safe(node_id, &mast_forest)?; + + match &mut mast_forest[node_id] { + MastNode::Block(basic_block) => { + basic_block.set_decorators(decorator_list); + }, + other => { + return Err(DeserializationError::InvalidValue(format!( + "expected mast node with id {node_id} to be a basic block, found {other:?}" + ))) + }, + } + } + // read "before enter" and "after exit" decorators, and update the corresponding nodes let before_enter_decorators: Vec<(usize, Vec)> = read_before_after_decorators(source, &mast_forest)?; for (node_id, decorator_ids) in before_enter_decorators { - let node_id: u32 = node_id.try_into().map_err(|_| { - DeserializationError::InvalidValue(format!( - "Invalid node id '{node_id}' while deserializing" - )) - })?; - let node_id = MastNodeId::from_u32_safe(node_id, &mast_forest)?; + let node_id = MastNodeId::from_usize_safe(node_id, &mast_forest)?; mast_forest.set_before_enter(node_id, decorator_ids); } let after_exit_decorators: Vec<(usize, Vec)> = read_before_after_decorators(source, &mast_forest)?; for (node_id, decorator_ids) in after_exit_decorators { - let node_id: u32 = node_id.try_into().map_err(|_| { - DeserializationError::InvalidValue(format!( - "Invalid node id '{node_id}' while deserializing" - )) - })?; - let node_id = MastNodeId::from_u32_safe(node_id, &mast_forest)?; + let node_id = MastNodeId::from_usize_safe(node_id, &mast_forest)?; mast_forest.set_after_exit(node_id, decorator_ids); } @@ -261,6 +277,87 @@ impl Deserializable for MastForest { } } +fn read_and_validate_magic(source: &mut R) -> Result<[u8; 5], DeserializationError> { + let magic: [u8; 5] = source.read_array()?; + if magic != *MAGIC { + return Err(DeserializationError::InvalidValue(format!( + "Invalid magic bytes. Expected '{:?}', got '{:?}'", + *MAGIC, magic + ))); + } + Ok(magic) +} + +fn read_and_validate_version( + source: &mut R, +) -> Result<[u8; 3], DeserializationError> { + let version: [u8; 3] = source.read_array()?; + if version != VERSION { + return Err(DeserializationError::InvalidValue(format!( + "Unsupported version. Got '{version:?}', but only '{VERSION:?}' is supported", + ))); + } + Ok(version) +} + +fn read_block_decorators( + source: &mut R, + mast_forest: &MastForest, +) -> Result, DeserializationError> { + let vec_len: usize = source.read()?; + let mut out_vec: Vec<_> = Vec::with_capacity(vec_len); + + for _ in 0..vec_len { + let node_id: usize = source.read()?; + + let decorator_vec_len: usize = source.read()?; + let mut inner_vec: Vec<(usize, DecoratorId)> = Vec::with_capacity(decorator_vec_len); + for _ in 0..decorator_vec_len { + let op_id: usize = source.read()?; + let decorator_id = DecoratorId::from_u32_safe(source.read()?, mast_forest)?; + inner_vec.push((op_id, decorator_id)); + } + + out_vec.push((node_id, inner_vec)); + } + + Ok(out_vec) +} + +fn decorator_infos_iter<'a, R>( + source: &'a mut R, + decorator_count: usize, +) -> impl Iterator> + 'a +where + R: ByteReader + 'a, +{ + let mut remaining = decorator_count; + core::iter::from_fn(move || { + if remaining == 0 { + return None; + } + remaining -= 1; + Some(DecoratorInfo::read_from(source)) + }) +} + +fn node_infos_iter<'a, R>( + source: &'a mut R, + node_count: usize, +) -> impl Iterator> + 'a +where + R: ByteReader + 'a, +{ + let mut remaining = node_count; + core::iter::from_fn(move || { + if remaining == 0 { + return None; + } + remaining -= 1; + Some(MastNodeInfo::read_from(source)) + }) +} + /// Reads the `before_enter_decorators` and `after_exit_decorators` of the serialized `MastForest` /// format. /// diff --git a/core/src/mast/serialization/tests.rs b/core/src/mast/serialization/tests.rs index cb6e9e2c09..ec5441af05 100644 --- a/core/src/mast/serialization/tests.rs +++ b/core/src/mast/serialization/tests.rs @@ -1,12 +1,9 @@ use alloc::{string::ToString, sync::Arc}; -use miden_crypto::{hash::rpo::RpoDigest, Felt}; +use miden_crypto::{hash::rpo::RpoDigest, Felt, ONE}; use super::*; -use crate::{ - mast::MastForestError, operations::Operation, AdviceInjector, AssemblyOp, DebugOptions, - Decorator, SignatureKind, -}; +use crate::{mast::MastForestError, operations::Operation, AssemblyOp, DebugOptions, Decorator}; /// If this test fails to compile, it means that `Operation` or `Decorator` was changed. Make sure /// that all tests in this file are updated accordingly. For example, if a new `Operation` variant @@ -109,27 +106,6 @@ fn confirm_operation_and_decorator_structure() { }; match Decorator::Trace(0) { - Decorator::Advice(advice) => match advice { - AdviceInjector::MerkleNodeMerge => (), - AdviceInjector::MerkleNodeToStack => (), - AdviceInjector::UpdateMerkleNode => (), - AdviceInjector::MapValueToStack { include_len: _, key_offset: _ } => (), - AdviceInjector::U64Div => (), - AdviceInjector::Ext2Inv => (), - AdviceInjector::Ext2Intt => (), - AdviceInjector::SmtGet => (), - AdviceInjector::SmtSet => (), - AdviceInjector::SmtPeek => (), - AdviceInjector::U32Clz => (), - AdviceInjector::U32Ctz => (), - AdviceInjector::U32Clo => (), - AdviceInjector::U32Cto => (), - AdviceInjector::ILog2 => (), - AdviceInjector::MemToMap => (), - AdviceInjector::HdwordToMap { domain: _ } => (), - AdviceInjector::HpermToMap => (), - AdviceInjector::SigToStack { kind: _ } => (), - }, Decorator::AsmOp(_) => (), Decorator::Debug(debug_options) => match debug_options { DebugOptions::StackAll => (), @@ -243,36 +219,8 @@ fn serialize_deserialize_all_nodes() { let num_operations = operations.len(); let decorators = vec![ - (0, Decorator::Advice(AdviceInjector::MerkleNodeMerge)), - (0, Decorator::Advice(AdviceInjector::MerkleNodeToStack)), - (0, Decorator::Advice(AdviceInjector::UpdateMerkleNode)), ( 0, - Decorator::Advice(AdviceInjector::MapValueToStack { - include_len: true, - key_offset: 1023, - }), - ), - (1, Decorator::Advice(AdviceInjector::U64Div)), - (3, Decorator::Advice(AdviceInjector::Ext2Inv)), - (5, Decorator::Advice(AdviceInjector::Ext2Intt)), - (5, Decorator::Advice(AdviceInjector::SmtGet)), - (5, Decorator::Advice(AdviceInjector::SmtSet)), - (5, Decorator::Advice(AdviceInjector::SmtPeek)), - (5, Decorator::Advice(AdviceInjector::U32Clz)), - (10, Decorator::Advice(AdviceInjector::U32Ctz)), - (10, Decorator::Advice(AdviceInjector::U32Clo)), - (10, Decorator::Advice(AdviceInjector::U32Cto)), - (10, Decorator::Advice(AdviceInjector::ILog2)), - (10, Decorator::Advice(AdviceInjector::MemToMap)), - (10, Decorator::Advice(AdviceInjector::HdwordToMap { domain: Felt::new(423) })), - (15, Decorator::Advice(AdviceInjector::HpermToMap)), - ( - 15, - Decorator::Advice(AdviceInjector::SigToStack { kind: SignatureKind::RpoFalcon512 }), - ), - ( - 15, Decorator::AsmOp(AssemblyOp::new( Some(crate::debuginfo::Location { path: Arc::from("test"), @@ -285,7 +233,7 @@ fn serialize_deserialize_all_nodes() { false, )), ), - (15, Decorator::Debug(DebugOptions::StackAll)), + (0, Decorator::Debug(DebugOptions::StackAll)), (15, Decorator::Debug(DebugOptions::StackTop(255))), (15, Decorator::Debug(DebugOptions::MemAll)), (15, Decorator::Debug(DebugOptions::MemInterval(0, 16))), @@ -408,6 +356,9 @@ fn mast_forest_invalid_node_id() { // Hydrate a forest larger than the first to get an overflow MastNodeId let mut overflow_forest = MastForest::new(); + // Note: clippy wants us to use `next_back()` instead of `last()`, but `next_back()` makes the + // test fail. + #[allow(clippy::double_ended_iterator_last)] let overflow = (0..=3) .map(|_| overflow_forest.add_block(vec![Operation::U32div], None).unwrap()) .last() @@ -435,3 +386,22 @@ fn mast_forest_invalid_node_id() { // Validate normal operations forest.add_join(first, second).unwrap(); } + +/// Test `MastForest::advice_map` serialization and deserialization. +#[test] +fn mast_forest_serialize_deserialize_advice_map() { + let mut forest = MastForest::new(); + let deco0 = forest.add_decorator(Decorator::Trace(0)).unwrap(); + let deco1 = forest.add_decorator(Decorator::Trace(1)).unwrap(); + let first = forest.add_block(vec![Operation::U32add], Some(vec![(0, deco0)])).unwrap(); + let second = forest.add_block(vec![Operation::U32and], Some(vec![(1, deco1)])).unwrap(); + forest.add_join(first, second).unwrap(); + + let key = RpoDigest::new([ONE, ONE, ONE, ONE]); + let value = vec![ONE, ONE]; + + forest.advice_map_mut().insert(key, value); + + let parsed = MastForest::read_from_bytes(&forest.to_bytes()).unwrap(); + assert_eq!(forest.advice_map, parsed.advice_map); +} diff --git a/core/src/operations/decorators/mod.rs b/core/src/operations/decorators/mod.rs index 02e054eb0a..72c409a48b 100644 --- a/core/src/operations/decorators/mod.rs +++ b/core/src/operations/decorators/mod.rs @@ -4,9 +4,6 @@ use core::fmt; use miden_crypto::hash::blake::Blake3_256; use num_traits::ToBytes; -mod advice; -pub use advice::AdviceInjector; - mod assembly_op; pub use assembly_op::AssemblyOp; @@ -21,14 +18,12 @@ use crate::mast::{DecoratorFingerprint, DecoratorId}; /// A set of decorators which can be executed by the VM. /// /// Executing a decorator does not affect the state of the main VM components such as operand stack -/// and memory. However, decorators may modify the advice provider. +/// and memory. /// /// Executing decorators does not advance the VM clock. As such, many decorators can be executed in /// a single VM cycle. #[derive(Clone, Debug, Eq, PartialEq)] pub enum Decorator { - /// Injects new data into the advice provider, as specified by the injector. - Advice(AdviceInjector), /// Adds information about the assembly instruction at a particular index (only applicable in /// debug mode). AsmOp(AssemblyOp), @@ -42,7 +37,6 @@ pub enum Decorator { impl Decorator { pub fn fingerprint(&self) -> DecoratorFingerprint { match self { - Self::Advice(advice) => Blake3_256::hash(advice.to_string().as_bytes()), Self::AsmOp(asm_op) => { let mut bytes_to_hash = Vec::new(); if let Some(location) = asm_op.location() { @@ -72,7 +66,6 @@ impl crate::prettier::PrettyPrint for Decorator { impl fmt::Display for Decorator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Advice(injector) => write!(f, "advice({injector})"), Self::AsmOp(assembly_op) => { write!(f, "asmOp({}, {})", assembly_op.op(), assembly_op.num_cycles()) }, diff --git a/core/src/operations/mod.rs b/core/src/operations/mod.rs index 7a2eb6e3c0..725c377256 100644 --- a/core/src/operations/mod.rs +++ b/core/src/operations/mod.rs @@ -3,8 +3,7 @@ use core::fmt; use super::Felt; mod decorators; pub use decorators::{ - AdviceInjector, AssemblyOp, DebugOptions, Decorator, DecoratorIterator, DecoratorList, - SignatureKind, + AssemblyOp, DebugOptions, Decorator, DecoratorIterator, DecoratorList, SignatureKind, }; // OPERATIONS OP CODES // ================================================================================================ diff --git a/core/src/operations/decorators/advice.rs b/core/src/sys_events.rs similarity index 62% rename from core/src/operations/decorators/advice.rs rename to core/src/sys_events.rs index bc70906c3f..0a4c8df2a7 100644 --- a/core/src/operations/decorators/advice.rs +++ b/core/src/sys_events.rs @@ -1,11 +1,35 @@ use core::fmt; -use super::SignatureKind; -use crate::Felt; - -// ADVICE INJECTORS +// SYSTEM EVENTS // ================================================================================================ +// Randomly generated constant values for the VM's system events. All values were sampled +// between 0 and 2^32. +pub use constants::*; + +use super::SignatureKind; +#[rustfmt::skip] +mod constants { + pub const EVENT_MERKLE_NODE_MERGE: u32 = 276124218; + pub const EVENT_MERKLE_NODE_TO_STACK: u32 = 361943238; + pub const EVENT_MAP_VALUE_TO_STACK: u32 = 574478993; + pub const EVENT_MAP_VALUE_TO_STACK_N: u32 = 630847990; + pub const EVENT_U64_DIV: u32 = 678156251; + pub const EVENT_EXT2_INV: u32 = 1251967401; + pub const EVENT_EXT2_INTT: u32 = 1347499010; + pub const EVENT_SMT_PEEK: u32 = 1889584556; + pub const EVENT_U32_CLZ: u32 = 1951932030; + pub const EVENT_U32_CTZ: u32 = 2008979519; + pub const EVENT_U32_CLO: u32 = 2032895094; + pub const EVENT_U32_CTO: u32 = 2083700134; + pub const EVENT_ILOG2: u32 = 2297972669; + pub const EVENT_MEM_TO_MAP: u32 = 2389394361; + pub const EVENT_HDWORD_TO_MAP: u32 = 2391452729; + pub const EVENT_HDWORD_TO_MAP_WITH_DOMAIN: u32 = 2822590340; + pub const EVENT_HPERM_TO_MAP: u32 = 3297060969; + pub const EVENT_FALCON_SIG_TO_STACK: u32 = 3419226139; +} + /// Defines a set of actions which can be initiated from the VM to inject new data into the advice /// provider. /// @@ -15,8 +39,8 @@ use crate::Felt; /// All actions, except for `MerkleNodeMerge`, `Ext2Inv` and `UpdateMerkleNode` can be invoked /// directly from Miden assembly via dedicated instructions. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum AdviceInjector { - // MERKLE STORE INJECTORS +pub enum SystemEvent { + // MERKLE STORE EVENTS // -------------------------------------------------------------------------------------------- /// Creates a new Merkle tree in the advice provider by combining Merkle trees with the /// specified roots. The root of the new tree is defined as `Hash(LEFT_ROOT, RIGHT_ROOT)`. @@ -33,7 +57,7 @@ pub enum AdviceInjector { /// provider (i.e., the input trees are not removed). MerkleNodeMerge, - // ADVICE STACK INJECTORS + // ADVICE STACK SYSTEM EVENTS // -------------------------------------------------------------------------------------------- /// Pushes a node of the Merkle tree specified by the values on the top of the operand stack /// onto the advice stack. @@ -49,42 +73,34 @@ pub enum AdviceInjector { /// Merkle store: {TREE_ROOT<-NODE} MerkleNodeToStack, - /// Updates the node of a Merkle tree specified by the values at the top of the operand stack. - /// Returns the path from the updated node to the new root of the tree to the caller. + /// Pushes a list of field elements onto the advice stack. The list is looked up in the advice + /// map using the specified word from the operand stack as the key. /// /// Inputs: - /// Operand stack: [OLD_NODE, depth, index, OLD_ROOT, NEW_NODE, ...] - /// Advice: [...] - /// Merkle store: {...} + /// Operand stack: [KEY, ...] + /// Advice stack: [...] + /// Advice map: {KEY: values} /// /// Outputs: - /// Operand stack: [OLD_NODE, depth, index, OLD_ROOT, NEW_NODE, ...] - /// Advice stack: [...] - /// Merkle store: {path, ...} - /// Return: \[path\] - UpdateMerkleNode, + /// Operand stack: [KEY, ...] + /// Advice stack: [values, ...] + /// Advice map: {KEY: values} + MapValueToStack, - /// Pushes a list of field elements onto the advice stack. The list is looked up in the advice - /// map using the specified word from the operand stack as the key. If `include_len` is set to - /// true, the number of elements in the value is also pushed onto the advice stack. + /// Pushes a list of field elements onto the advice stack, and then the number of elements + /// pushed. The list is looked up in the advice map using the specified word from the operand + /// stack as the key. /// /// Inputs: - /// Operand stack: [..., KEY, ...] + /// Operand stack: [KEY, ...] /// Advice stack: [...] /// Advice map: {KEY: values} /// /// Outputs: - /// Operand stack: [..., KEY, ...] - /// Advice stack: [values_len?, values, ...] + /// Operand stack: [KEY, ...] + /// Advice stack: [num_values, values, ...] /// Advice map: {KEY: values} - /// - /// The `key_offset` value specifies the location of the `KEY` on the stack. For example, - /// offset value of 0 indicates that the top word on the stack should be used as the key, the - /// offset value of 4, indicates that the second word on the stack should be used as the key - /// etc. - /// - /// The valid values of `key_offset` are 0 through 12 (inclusive). - MapValueToStack { include_len: bool, key_offset: usize }, + MapValueToStackN, /// Pushes the result of [u64] division (both the quotient and the remainder) onto the advice /// stack. @@ -142,12 +158,6 @@ pub enum AdviceInjector { /// degree coefficients are located at the top of the advice stack. Ext2Intt, - /// Currently unimplemented - SmtGet, - - /// Currently unimplemented - SmtSet, - /// Pushes onto the advice stack the value associated with the specified key in a Sparse /// Merkle Tree defined by the specified root. /// @@ -217,7 +227,7 @@ pub enum AdviceInjector { /// Advice stack: [ilog2(n), ...] ILog2, - // ADVICE MAP INJECTORS + // ADVICE MAP SYSTEM EVENTS // -------------------------------------------------------------------------------------------- /// Reads words from memory at the specified range and inserts them into the advice map under /// the key `KEY` located at the top of the stack. @@ -244,9 +254,22 @@ pub enum AdviceInjector { /// Operand stack: [B, A, ...] /// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} /// - /// Where KEY is computed as hash(A || B, domain), where domain is provided via the immediate - /// value. - HdwordToMap { domain: Felt }, + /// Where KEY is computed as hash(A || B, domain=0) + HdwordToMap, + + /// Reads two word from the operand stack and inserts them into the advice map under the key + /// defined by the hash of these words (using `d` as the domain). + /// + /// Inputs: + /// Operand stack: [B, A, d, ...] + /// Advice map: {...} + /// + /// Outputs: + /// Operand stack: [B, A, d, ...] + /// Advice map: {KEY: [a0, a1, a2, a3, b0, b1, b2, b3]} + /// + /// Where KEY is computed as hash(A || B, d). + HdwordToMapWithDomain, /// Reads three words from the operand stack and inserts the top two words into the advice map /// under the key defined by applying an RPO permutation to all three words. @@ -264,7 +287,7 @@ pub enum AdviceInjector { HpermToMap, /// Reads two words from the stack and pushes values onto the advice stack which are required - /// for verification of a DSA in Miden VM. + /// for verification of Falcon DSA in Miden VM. /// /// Inputs: /// Operand stack: [PK, MSG, ...] @@ -276,35 +299,76 @@ pub enum AdviceInjector { /// /// Where PK is the public key corresponding to the signing key, MSG is the message, SIG_DATA /// is the signature data. - SigToStack { kind: SignatureKind }, + FalconSigToStack, +} + +impl SystemEvent { + pub fn into_event_id(self) -> u32 { + match self { + SystemEvent::MerkleNodeMerge => EVENT_MERKLE_NODE_MERGE, + SystemEvent::MerkleNodeToStack => EVENT_MERKLE_NODE_TO_STACK, + SystemEvent::MapValueToStack => EVENT_MAP_VALUE_TO_STACK, + SystemEvent::MapValueToStackN => EVENT_MAP_VALUE_TO_STACK_N, + SystemEvent::U64Div => EVENT_U64_DIV, + SystemEvent::Ext2Inv => EVENT_EXT2_INV, + SystemEvent::Ext2Intt => EVENT_EXT2_INTT, + SystemEvent::SmtPeek => EVENT_SMT_PEEK, + SystemEvent::U32Clz => EVENT_U32_CLZ, + SystemEvent::U32Ctz => EVENT_U32_CTZ, + SystemEvent::U32Clo => EVENT_U32_CLO, + SystemEvent::U32Cto => EVENT_U32_CTO, + SystemEvent::ILog2 => EVENT_ILOG2, + SystemEvent::MemToMap => EVENT_MEM_TO_MAP, + SystemEvent::HdwordToMap => EVENT_HDWORD_TO_MAP, + SystemEvent::HdwordToMapWithDomain => EVENT_HDWORD_TO_MAP_WITH_DOMAIN, + SystemEvent::HpermToMap => EVENT_HPERM_TO_MAP, + SystemEvent::FalconSigToStack => EVENT_FALCON_SIG_TO_STACK, + } + } + + /// Returns a system event corresponding to the specified event ID, or `None` if the event + /// ID is not recognized. + pub fn from_event_id(event_id: u32) -> Option { + match event_id { + EVENT_MERKLE_NODE_MERGE => Some(SystemEvent::MerkleNodeMerge), + EVENT_MERKLE_NODE_TO_STACK => Some(SystemEvent::MerkleNodeToStack), + EVENT_MAP_VALUE_TO_STACK => Some(SystemEvent::MapValueToStack), + EVENT_MAP_VALUE_TO_STACK_N => Some(SystemEvent::MapValueToStackN), + EVENT_U64_DIV => Some(SystemEvent::U64Div), + EVENT_EXT2_INV => Some(SystemEvent::Ext2Inv), + EVENT_EXT2_INTT => Some(SystemEvent::Ext2Intt), + EVENT_SMT_PEEK => Some(SystemEvent::SmtPeek), + EVENT_U32_CLZ => Some(SystemEvent::U32Clz), + EVENT_U32_CTZ => Some(SystemEvent::U32Ctz), + EVENT_U32_CLO => Some(SystemEvent::U32Clo), + EVENT_U32_CTO => Some(SystemEvent::U32Cto), + EVENT_ILOG2 => Some(SystemEvent::ILog2), + EVENT_MEM_TO_MAP => Some(SystemEvent::MemToMap), + EVENT_HDWORD_TO_MAP => Some(SystemEvent::HdwordToMap), + EVENT_HDWORD_TO_MAP_WITH_DOMAIN => Some(SystemEvent::HdwordToMapWithDomain), + EVENT_HPERM_TO_MAP => Some(SystemEvent::HpermToMap), + EVENT_FALCON_SIG_TO_STACK => Some(SystemEvent::FalconSigToStack), + _ => None, + } + } } -impl crate::prettier::PrettyPrint for AdviceInjector { +impl crate::prettier::PrettyPrint for SystemEvent { fn render(&self) -> crate::prettier::Document { crate::prettier::display(self) } } -impl fmt::Display for AdviceInjector { +impl fmt::Display for SystemEvent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::MerkleNodeMerge => write!(f, "merkle_node_merge"), Self::MerkleNodeToStack => write!(f, "merkle_node_to_stack"), - Self::UpdateMerkleNode => { - write!(f, "update_merkle_node") - }, - Self::MapValueToStack { include_len, key_offset } => { - if *include_len { - write!(f, "map_value_to_stack_with_len.{key_offset}") - } else { - write!(f, "map_value_to_stack.{key_offset}") - } - }, + Self::MapValueToStack => write!(f, "map_value_to_stack"), + Self::MapValueToStackN => write!(f, "map_value_to_stack_with_len"), Self::U64Div => write!(f, "div_u64"), Self::Ext2Inv => write!(f, "ext2_inv"), Self::Ext2Intt => write!(f, "ext2_intt"), - Self::SmtGet => write!(f, "smt_get"), - Self::SmtSet => write!(f, "smt_set"), Self::SmtPeek => write!(f, "smt_peek"), Self::U32Clz => write!(f, "u32clz"), Self::U32Ctz => write!(f, "u32ctz"), @@ -312,9 +376,10 @@ impl fmt::Display for AdviceInjector { Self::U32Cto => write!(f, "u32cto"), Self::ILog2 => write!(f, "ilog2"), Self::MemToMap => write!(f, "mem_to_map"), - Self::HdwordToMap { domain } => write!(f, "hdword_to_map.{domain}"), + Self::HdwordToMap => write!(f, "hdword_to_map"), + Self::HdwordToMapWithDomain => write!(f, "hdword_to_map_with_domain"), Self::HpermToMap => write!(f, "hperm_to_map"), - Self::SigToStack { kind } => write!(f, "sig_to_stack.{kind}"), + Self::FalconSigToStack => write!(f, "sig_to_stack.{}", SignatureKind::RpoFalcon512), } } } diff --git a/core/src/utils/sync/rw_lock.rs b/core/src/utils/sync/rw_lock.rs index 5a5cc5a722..d1423e0be1 100644 --- a/core/src/utils/sync/rw_lock.rs +++ b/core/src/utils/sync/rw_lock.rs @@ -27,13 +27,13 @@ pub type RwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, Spinlock, T>; /// This is fundamentally a spinlock, in that blocking operations on the lock will spin until /// they succeed in acquiring/releasing the lock. /// -/// To acheive the ability to share the underlying data with multiple readers, or hold +/// To achieve the ability to share the underlying data with multiple readers, or hold /// exclusive access for one writer, the lock state is based on a "locked" count, where shared /// access increments the count by an even number, and acquiring exclusive access relies on the /// use of the lowest order bit to stop further shared acquisition, and indicate that the lock /// is exclusively held (the difference between the two is irrelevant from the perspective of /// a thread attempting to acquire the lock, but internally the state uses `usize::MAX` as the -/// "exlusively locked" sentinel). +/// "exclusively locked" sentinel). /// /// This mechanism gets us the following: /// @@ -244,7 +244,7 @@ mod test { use loom::{model::Builder, sync::Arc}; - use super::rwlock::{RwLock, Spinlock}; + use super::{RwLock, Spinlock}; #[test] fn test_rwlock_loom() { diff --git a/docs/src/assets/design/chiplets/chiplets.png b/docs/src/assets/design/chiplets/chiplets.png index e61ac4468b..d78a8ea407 100644 Binary files a/docs/src/assets/design/chiplets/chiplets.png and b/docs/src/assets/design/chiplets/chiplets.png differ diff --git a/docs/src/assets/design/chiplets/chiplets.zip b/docs/src/assets/design/chiplets/chiplets.zip deleted file mode 100644 index 4a693409bf..0000000000 Binary files a/docs/src/assets/design/chiplets/chiplets.zip and /dev/null differ diff --git a/docs/src/assets/design/chiplets/memory/memory_miden_vm_layout.png b/docs/src/assets/design/chiplets/memory/memory_miden_vm_layout.png index f92db10045..5ddfa01b40 100644 Binary files a/docs/src/assets/design/chiplets/memory/memory_miden_vm_layout.png and b/docs/src/assets/design/chiplets/memory/memory_miden_vm_layout.png differ diff --git a/docs/src/assets/design/stack/crypto_ops/RCOMBBASE.png b/docs/src/assets/design/stack/crypto_ops/RCOMBBASE.png index 3441a3cac0..f37a100259 100644 Binary files a/docs/src/assets/design/stack/crypto_ops/RCOMBBASE.png and b/docs/src/assets/design/stack/crypto_ops/RCOMBBASE.png differ diff --git a/docs/src/assets/design/stack/io_ops/MLOAD.png b/docs/src/assets/design/stack/io_ops/MLOAD.png index 4a1c0a76aa..a770874b52 100644 Binary files a/docs/src/assets/design/stack/io_ops/MLOAD.png and b/docs/src/assets/design/stack/io_ops/MLOAD.png differ diff --git a/docs/src/assets/design/stack/io_ops/MSTORE.png b/docs/src/assets/design/stack/io_ops/MSTORE.png index 691b8a110f..d607870318 100644 Binary files a/docs/src/assets/design/stack/io_ops/MSTORE.png and b/docs/src/assets/design/stack/io_ops/MSTORE.png differ diff --git a/docs/src/assets/design/stack/io_ops/MSTREAM.png b/docs/src/assets/design/stack/io_ops/MSTREAM.png index b88c2b2395..6fb82bd2e4 100644 Binary files a/docs/src/assets/design/stack/io_ops/MSTREAM.png and b/docs/src/assets/design/stack/io_ops/MSTREAM.png differ diff --git a/docs/src/assets/design/vm_trace.png b/docs/src/assets/design/vm_trace.png index fd5b1fd3eb..27273e1ff8 100644 Binary files a/docs/src/assets/design/vm_trace.png and b/docs/src/assets/design/vm_trace.png differ diff --git a/docs/src/assets/design/vm_trace.zip b/docs/src/assets/design/vm_trace.zip deleted file mode 100644 index cdc820051c..0000000000 Binary files a/docs/src/assets/design/vm_trace.zip and /dev/null differ diff --git a/docs/src/design/chiplets/main.md b/docs/src/design/chiplets/main.md index 62d4b1c8cb..1d783f8afe 100644 --- a/docs/src/design/chiplets/main.md +++ b/docs/src/design/chiplets/main.md @@ -63,21 +63,25 @@ This requires the following adjustments for each chiplet. Each operation supported by the chiplets is given a unique identifier to ensure that the requests and responses sent to the [chiplets bus](#chiplets-bus) ($b_{chip}$) are indeed processed by the intended chiplet for that operation and that chiplets which support more than one operation execute the correct one. -The labels are composed from the flag values of the chiplet selector(s) and internal operation selectors (if applicable). The unique label of the operation is computed as the binary aggregation of the combined selectors plus $1$, note that the combined flag is represented in big-endian, so the bit representation below is reverted. - -| Operation | Chiplet Selector Flag | Internal Selector Flag | Combined Flag | Label | -| ---------------------- | --------------------- | :--------------------: | ---------------- | :---: | -| `HASHER_LINEAR_HASH` | $\{0\}$ | $\{1, 0, 0\}$ | $\{0, 1, 0, 0\}$ | 3 | -| `HASHER_MP_VERIFY` | $\{0\}$ | $\{1, 0, 1\}$ | $\{0, 1, 0, 1\}$ | 11 | -| `HASHER_MR_UPDATE_OLD` | $\{0\}$ | $\{1, 1, 0\}$ | $\{0, 1, 1, 0\}$ | 7 | -| `HASHER_MR_UPDATE_NEW` | $\{0\}$ | $\{1, 1, 1\}$ | $\{0, 1, 1, 1\}$ | 15 | -| `HASHER_RETURN_HASH` | $\{0\}$ | $\{0, 0, 0\}$ | $\{0, 0, 0, 0\}$ | 1 | -| `HASHER_RETURN_STATE` | $\{0\}$ | $\{0, 0, 1\}$ | $\{0, 0, 0, 1\}$ | 9 | -| `BITWISE_AND` | $\{1, 0\}$ | $\{0\}$ | $\{1, 0, 0\}$ | 2 | -| `BITWISE_XOR` | $\{1, 0\}$ | $\{1\}$ | $\{1, 0, 1\}$ | 6 | -| `MEMORY_READ` | $\{1, 1, 0\}$ | $\{1\}$ | $\{1, 1, 0, 1\}$ | 12 | -| `MEMORY_WRITE` | $\{1, 1, 0\}$ | $\{0\}$ | $\{1, 1, 0, 0\}$ | 4 | -| `KERNEL_PROC_CALL` | $\{1, 1, 1, 0\}$ | | $\{1, 1, 1, 0\}$ | 8 | +The labels are composed from the flag values of the chiplet selector(s) and internal operation selectors (if applicable). The unique label of the operation is computed as the binary aggregation of the combined selectors plus $1$, note that the combined flag is represented in big-endian, so the bit representation below is reversed. + +> **Note:** We started moving away from this scheme with the memory chiplet, which more simply prepends the chiplet selector to the label (without reversing or adding 1). + +| Operation | Chiplet Selector Flag | Internal Selector Flag | Combined Flag | Label | +| ---------------------- | --------------------- | :--------------------: | ------------------- | :---: | +| `HASHER_LINEAR_HASH` | $\{0\}$ | $\{1, 0, 0\}$ | $\{0, 1, 0, 0\}$ | 3 | +| `HASHER_MP_VERIFY` | $\{0\}$ | $\{1, 0, 1\}$ | $\{0, 1, 0, 1\}$ | 11 | +| `HASHER_MR_UPDATE_OLD` | $\{0\}$ | $\{1, 1, 0\}$ | $\{0, 1, 1, 0\}$ | 7 | +| `HASHER_MR_UPDATE_NEW` | $\{0\}$ | $\{1, 1, 1\}$ | $\{0, 1, 1, 1\}$ | 15 | +| `HASHER_RETURN_HASH` | $\{0\}$ | $\{0, 0, 0\}$ | $\{0, 0, 0, 0\}$ | 1 | +| `HASHER_RETURN_STATE` | $\{0\}$ | $\{0, 0, 1\}$ | $\{0, 0, 0, 1\}$ | 9 | +| `BITWISE_AND` | $\{1, 0\}$ | $\{0\}$ | $\{1, 0, 0\}$ | 2 | +| `BITWISE_XOR` | $\{1, 0\}$ | $\{1\}$ | $\{1, 0, 1\}$ | 6 | +| `MEMORY_WRITE_ELEMENT` | $\{1, 1, 0\}$ | $\{0, 0\}$ | $\{1, 1, 0, 0, 0\}$ | 24 | +| `MEMORY_WRITE_WORD` | $\{1, 1, 0\}$ | $\{0, 1\}$ | $\{1, 1, 0, 0, 1\}$ | 25 | +| `MEMORY_READ_ELEMENT` | $\{1, 1, 0\}$ | $\{1, 0\}$ | $\{1, 1, 0, 1, 0\}$ | 26 | +| `MEMORY_READ_WORD` | $\{1, 1, 0\}$ | $\{1, 1\}$ | $\{1, 1, 0, 1, 1\}$ | 27 | +| `KERNEL_PROC_CALL` | $\{1, 1, 1, 0\}$ | | $\{1, 1, 1, 0\}$ | 8 | ## Chiplets module constraints diff --git a/docs/src/design/chiplets/memory.md b/docs/src/design/chiplets/memory.md index c2455622f8..ac3a78319c 100644 --- a/docs/src/design/chiplets/memory.md +++ b/docs/src/design/chiplets/memory.md @@ -1,6 +1,6 @@ # Memory chiplet -Miden VM supports linear read-write random access memory. This memory is word-addressable, meaning, four values are located at each address, and we can read and write values to/from memory in batches of four. Each value is a field element in a $64$-bit prime field with modulus $2^{64} - 2^{32} + 1$. Memory address can be any field element. +Miden VM supports linear read-write random access memory. This memory is element-addressable, meaning that a single value is located at each address, although reading and writing values to/from memory in batches of four is supported. Each value is a field element in a $64$-bit prime field with modulus $2^{64} - 2^{32} + 1$. A memory address is a field element in the range $[0, 2^{32})$. In this note we describe the rationale for selecting the above design and describe AIR constraints needed to support it. @@ -162,7 +162,7 @@ Notice that the above constraint has degree $5$. While the approach described above works, it comes at significant cost. Reading or writing a single value requires $8$ trace cells and $2$ $16$-bit range checks. Assuming a single range check requires roughly $2$ trace cells, the total number of trace cells needed grows to $12$. This is about $6$x worse the simple contiguous write-once memory described earlier. -Miden VM frequently needs to deal with batches of $4$ field elements, which we call _words_. For example, the output of Rescue Prime Optimized hash function is a single word. A single 256-bit integer value can be stored as two words (where each element contains one $32$-bit value). Thus, we can optimize for this common use case by making the memory _word-addressable_. That is $4$ field elements are located at each memory address, and we can read and write elements to/from memory in batches of four. +Miden VM frequently needs to deal with batches of $4$ field elements, which we call _words_. For example, the output of Rescue Prime Optimized hash function is a single word. A single 256-bit integer value can be stored as two words (where each element contains one $32$-bit value). Thus, we can optimize for this common use case by making the chiplet handle *words* as opposed to individual elements. That is, memory is still element-addressable in that each memory address stores a single field element, and memory addresses may be read or written individually. However, the chiplet also handles reading and writing elements in batches of four simultaneously, with the restriction that such batches be *word-aligned* addresses (*i.e.* the address is a multiple of 4). The layout of Miden VM memory table is shown below: @@ -170,116 +170,174 @@ The layout of Miden VM memory table is shown below: where: -- `s0` is a selector column which is set to $1$ for read operations and $0$ for write operations. -- `s1` is a selector oclumn which is set to $1$ when previously accessed memory is being read and $0$ otherwise. In other words, it is set to $1$ only when the context and address are the same as they were in the previous row and the `s0` operation selector is set to $1$ (indicating a read). -- `ctx` contains context ID. Values in this column must increase monotonically but there can be gaps between two consecutive values of up to $2^{32}$. Also, two consecutive values can be the same. In AIR constraint description below, we refer to this column as $c$. -- `addr` contains memory address. Values in this column must increase monotonically for a given context but there can be gaps between two consecutive values of up to $2^{32}$. Also, two consecutive values can be the same. In AIR constraint description below, we refer to this column as $a$. -- `clk` contains clock cycle at which the memory operation happened. Values in this column must increase monotonically for a given context and memory address but there can be gaps between two consecutive values of up to $2^{32}$. In AIR constraint description below, we refer to this column as $i$. -- `v0, v1, v2, v3` columns contain field elements stored at a given context/address/clock cycle after the memory operation. +- `rw` is a selector column which is set to $1$ for read operations and $0$ for write operations. +- `ew` is a selector column which is set to $1$ when a word is being accessed, and $0$ when an element is being accessed. +- `ctx` contains context ID. Values in this column must increase monotonically but there can be gaps between two consecutive values of up to $2^{32}$. Also, two consecutive values can be the same. +- `word_addr` contains the memory address of the first element in the word. Values in this column must increase monotonically for a given context but there can be gaps between two consecutive values of up to $2^{32}$. Values in this column must be divisible by 4. Also, two consecutive values can be the same. +- `idx0` and `idx1` are selector columns used to identify which element in the word is being accessed. Specifically, the index within the word is computed as `idx1 * 2 + idx0`. + - However, when `ew` is set to $1$ (indicating that a word is accessed), these columns are meaningless and are set to $0$. +- `clk` contains clock cycle at which the memory operation happened. Values in this column must increase monotonically for a given context and memory word but there can be gaps between two consecutive values of up to $2^{32}$. In AIR constraint description below, we refer to this column as $i$. +- `v0, v1, v2, v3` columns contain field elements stored at a given context/word/clock cycle after the memory operation. - Columns `d0` and `d1` contain lower and upper $16$ bits of the delta between two consecutive context IDs, addresses, or clock cycles. Specifically: - - When the context changes, these columns contain $(c' - c)$. - - When the context remains the same but the address changes, these columns contain $(a' - a)$. - - When both the context and the address remain the same, these columns contain $(i' - i - 1)$. + - When the context changes within a frame, these columns contain $(ctx' - ctx)$ in the "next" row. + - When the context remains the same but the word address changes within a frame, these columns contain $(a' - a)$ in the "next" row. + - When both the context and the word address remain the same within a frame, these columns contain $(clk' - clk - 1)$ in the "next" row. - Column `t` contains the inverse of the delta between two consecutive context IDs, addresses, or clock cycles. Specifically: - - When the context changes, this column contains the inverse of $(c' - c)$. - - When the context remains the same but the address changes, this column contains the inverse of $(a' - a)$. - - When both the context and the address remain the same, this column contains the inverse of $(i' - i - 1)$. + - When the context changes within a frame, this column contains the inverse of $(ctx' - ctx)$ in the "next" row. + - When the context remains the same but the word address changes within a frame, this column contains the inverse of $(a' - a)$ in the "next" row. + - When both the context and the word address remain the same within a frame, this column contains the inverse of $(clk' - clk - 1)$ in the "next" row. +- Column `f_scw` stands for "flag same context and word address", which is set to $1$ when the current and previous rows have the same context and word address, and $0$ otherwise. -For every memory access operation (i.e., read or write), a new row is added to the memory table. For read operations, `s0` is set to $1$. If neither `ctx` nor `addr` have changed, then `s1` is set to $1$ and the `v` columns are set to equal the values from the previous row. If `ctx` or `addr` have changed, then `s1` is set to $0$ and the `v` columns are initialized to $0$. For write operations, the values may be different, and both selector columns `s0` and `s1` are set to $0$. - -The amortized cost of reading or writing a single value is between $4$ and $5$ trace cells (this accounts for the trace cells needed for $16$-bit range checks). Thus, from performance standpoint, this approach is roughly $2.5$x worse than the simple contiguous write-once memory described earlier. However, our view is that this trade-off is worth it given that this approach provides read-write memory, context separation, and eliminates the contiguous memory requirement. +For every memory access operation (i.e., read or write a word or element), a new row is added to the memory table. If neither `ctx` nor `addr` have changed, the `v` columns are set to equal the values from the previous row (except for any element written to). If `ctx` or `addr` have changed, then the `v` columns are initialized to $0$ (except for any element written to). ### AIR constraints +We first define the memory chiplet selector flags. $s_0$, $s_1$ and $s_2$ will refer to the chiplet selector flags. + +- $f_{mem}$ is set to 1 when the current row is in the memory chiplet. +$$ +f_{mem} = s_0 \cdot s_1 \cdot (1 - s_2) \text{ | degree} = 3 +$$ + +- $f_{mem\_nl}$ is set to 1 when the current row is in the memory chiplet, except for the last row of the chiplet. + +$$ +f_{mem\_nl} = s_0 \cdot s_1 \cdot (1 - s_2') \text{ | degree} = 3 +$$ + +- $f_{mem\_fr}$ is set to 1 when the next row is the first row of the memory chiplet. + +$$ +f_{mem\_fr} = (1 - s_0) \cdot f_{mem}' \text{ | degree} = 4 +$$ + To simplify description of constraints, we'll define two variables $n_0$ and $n_1$ as follows: $$ -n_0 = \Delta c \cdot t' \\ +n_0 = \Delta ctx \cdot t' \\ n_1 = \Delta a \cdot t' $$ -Where $\Delta c = c' - c$ and $\Delta a = a' - a$. +Where $\Delta ctx = ctx' - ctx$ and $\Delta a = a' - a$. To make sure the prover sets the value of column `t` correctly, we'll need to impose the following constraints: >$$ -n_0^2 - n_0 = 0 \text{ | degree} = 4 +f_{mem\_nl} \cdot (n_0^2 - n_0) = 0 \text{ | degree} = 7 $$ >$$ -(1 - n_0) \cdot \Delta c = 0 \text{ | degree} = 3 +f_{mem\_nl} \cdot (1 - n_0) \cdot \Delta ctx = 0 \text{ | degree} = 7 $$ >$$ -(1 - n_0) \cdot (n_1^2 - n_1) = 0 \text{ | degree} = 6 +f_{mem\_nl} \cdot (1 - n_0) \cdot (n_1^2 - n_1) = 0 \text{ | degree} = 9 $$ >$$ -(1 - n_0) \cdot (1 - n_1) \cdot \Delta a = 0 \text{ | degree} = 5 +f_{mem\_nl} \cdot (1 - n_0) \cdot (1 - n_1) \cdot \Delta a = 0 \text{ | degree} = 8 $$ -The above constraints guarantee that when context changes, $n_0 = 1$. When context remains the same but address changes, $(1 - n_0) \cdot n_1 = 1$. And when neither the context nor the address change, $(1 - n_0) \cdot (1 - n_1) = 1$. +The above constraints guarantee that when context changes, $n_0 = 1$. When context remains the same but word address changes, $(1 - n_0) \cdot n_1 = 1$. And when neither the context nor the word address change, $(1 - n_0) \cdot (1 - n_1) = 1$. -To enforce the values of the selector columns, we first require that they both contain only binary values. +We enforce that the `rw`, `ew`, `idx0` and `idx1` contain binary values. >$$ -s_0^2 - s_0 = 0 \text{ | degree} = 2 +f_{mem} \cdot (rw^2 - rw) = 0 \text{ | degree} = 5 $$ >$$ -s_1^2 - s_1 = 0 \text{ | degree} = 2 +f_{mem} \cdot (ew^2 - ew) = 0 \text{ | degree} = 5 $$ -Then we require that $s_1$ is always set to $1$ during read operations when the context and address did not change and to $0$ in all other cases. +>$$ +f_{mem} \cdot (idx0^2 - idx0) = 0 \text{ | degree} = 5 +$$ >$$ -(1 - n_0) \cdot (1 - n_1) \cdot s'_0 \cdot (1 - s'_1) = 0 \text{ | degree} = 6 +f_{mem} \cdot (idx1^2 - idx1) = 0 \text{ | degree} = 5 $$ + +To enforce the values of context ID, word address, and clock cycle grow monotonically as described in the previous section, we define the following constraint. + >$$ -(n_0 + (1 - n_0) \cdot n_1 + (1 - s'_0)) \cdot s'_1 = 0 \text{ | degree} = 5 +f_{mem\_nl} \cdot \left(n_0 \cdot \Delta ctx + (1 - n_0) \cdot (n_1 \cdot \Delta a + (1 - n_1) \cdot \Delta clk) \right) - (2^{16} \cdot d_1' + d_0') = 0 \text{ | degree} = 8 $$ -The first constraint enforces that `s_1` is $1$ when the operation is a read and `ctx` and `addr` are both unchanged. The second constraint enforces that when either the context changed, the address changed, or the operation is a write, then `s_1` is set to $0$. +Where $\Delta clk = clk' - clk - 1$. +In addition to this constraint, we also need to make sure that the values in registers $d_0$ and $d_1$ are less than $2^{16}$, and this can be done with [range checks](../range.md). -To enforce the values of context ID, address, and clock cycle grow monotonically as described in the previous section, we define the following constraint. +Next, for all frames where the "current" and "next" rows are in the chiplet, we need to ensure that the value of the `f_scw` column in the "next" row is set to $1$ when the context and word address are the same, and $0$ otherwise. >$$ -\left(n_0 \cdot \Delta c + (1 - n_0) \cdot (n_1 \cdot \Delta a + (1 - n_1) \cdot \Delta i) \right) - (2^{16} \cdot d_1' + d_0') = 0 \text{ | degree} = 5 +f_{mem\_nl} \cdot (f_{scw}' - (1 - n_0) \cdot (1-n_1)) = 0 \text{ | degree} = 7 $$ -Where $\Delta i = i' - i - 1$. +Note that this does not constrain the value of `f_scw` in the first row of the chiplet. This is intended, as the first row's constraints do not depend on the previous row (since the previous row is not part of the same chiplet), and therefore do not depend on `f_scw` (see "first row" constraints below). -In addition to this constraint, we also need to make sure that the values in registers $d_0$ and $d_1$ are less than $2^{16}$, and this can be done with [range checks](../range.md). +Finally, we need to constrain the `v0, v1, v2, v3` columns. We will define a few variables to help in defining the constraints. + +$$ +\begin{align*} +f_0 &= (1 - idx1) \cdot (1 - idx0) \text{ | degree} = 2\\ +f_1 &= (1 - idx1) \cdot idx0 \text{ | degree} = 2\\ +f_2 &= idx1 \cdot (1 - idx0) \text{ | degree} = 2\\ +f_3 &= idx1 \cdot idx0 \text{ | degree} = 2\\ +\end{align*} +$$ + +The flag $f_i$ is set to $1$ when $v_i$ is being accessed, and $0$ otherwise. Next, for $0 \leq i < 4$, + +$$ +c_i = rw' + (1 - rw') \cdot (1 - ew') \cdot (1 - f_i') \text{ | degree} = 4\\ +$$ + +which is set to $1$ when $v_i$ is *not* written to, and $0$ otherwise. -Next, we need to make sure that values at a given memory address are always initialized to $0$. This can be done with the following constraint: +We're now ready to describe the constraints for the `v0, v1, v2, v3` columns. + +- For the first row of the chiplet (in the "next" position of the frame), for $0 \leq i < 4$, >$$ -s_0 \cdot (1 - s_1) \cdot v_i = 0 \text{ for } i \in \{0, 1, 2, 3\} \text{ | degree} = 3 +f_{mem\_fr} \cdot c_i \cdot v_i' = 0 \text{ | degree} = 9\\ $$ -Thus, when the operation is a read and either the context changes or the address changes, values in the $v_i$ columns are guaranteed to be zeros. +That is, if the next row is the first row of the memory chiplet, and $v_i'$ is not written to, then $v_i'$ must be $0$. -Lastly, we need to make sure that for the same context/address combination, the $v_i$ columns of the current row are equal to the corresponding $v_i$ columns of the next row. This can be done with the following constraints: +- For all rows of the chiplet except the first, for $0 \leq i < 4$, >$$ -s_1 \cdot (v_i' - v_i) = 0 \text{ for } i \in \{0, 1, 2, 3\} \text{ | degree} = 2 +f_{mem\_nl} \cdot c_i \cdot (f_{scw}' \cdot (v_i' - v_i) + (1 - f_{scw}') \cdot v_i') = 0 \text{ | degree} = 9\\ $$ +That is, if $v_i$ is not written to, then either its value needs to be copied over from the previous row (when $f_{scw}' = 1$), or it must be set to 0 (when $f_{scw}' = 0$). + #### Chiplets bus constraints Communication between the memory chiplet and the stack is accomplished via the chiplets bus $b_{chip}$. To respond to memory access requests from the stack, we need to divide the current value in $b_{chip}$ by the value representing a row in the memory table. This value can be computed as follows: +>$$ +\begin{align*} +v_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem} + \alpha_2 \cdot ctx + \alpha_3 \cdot a + \alpha_4 \cdot clk + ew \cdot v_{word} + (1 - ew) \cdot v_{element}\\ \text{ | degree} = 4 +\end{align*} +$$ + +where + $$ -v_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem} + \alpha_2 \cdot c + \alpha_3 \cdot a + \alpha_4 \cdot i + \sum_{j=0}^3(\alpha_{j + 5} \cdot v_j) +\begin{align*} +v_{word} &= \sum_{j=0}^3(\alpha_{j + 5} \cdot v_j) \text{ | degree} = 1 \\ +v_{element} &= \alpha_5 \cdot \sum_{i=0}^3 f_i \cdot v_i \text{ | degree} = 3 +\end{align*} $$ -Where, $op_{mem}$ is the unique [operation label](./main.md#operation-labels) of the memory access operation. +and where $op_{mem}$ is the appropriate [operation label](./main.md#operation-labels) of the memory access operation. To ensure that values of memory table rows are included into the chiplets bus, we impose the following constraint: >$$ -b_{chip}' = b_{chip} \cdot v_{mem} \text{ | degree} = 2 +b_{chip}' = b_{chip} \cdot v_{mem} \text{ | degree} = 5 $$ On the stack side, for every memory access request, a corresponding value is divided out of the $b_{chip}$ column. Specifics of how this is done are described [here](../stack/io_ops.md#memory-access-operations). diff --git a/docs/src/design/stack/crypto_ops.md b/docs/src/design/stack/crypto_ops.md index bfb3ca7fe3..d60a0c976a 100644 --- a/docs/src/design/stack/crypto_ops.md +++ b/docs/src/design/stack/crypto_ops.md @@ -172,7 +172,7 @@ The diagram below illustrates the stack transition for `RCOMBBASE` operation. After calling the `mem_stream ` with `x_ptr`, the operation does the following: - Populates the helper registers with $\left[T_i(z)_0, T_i(z)_1, T_i(gz)_0, T_i(gz)_1, \alpha_{i, 0}, \alpha_{i,1}\right]$ using the pointers `z_ptr` and `a_ptr`. - Updates the accumulators $$p \mathrel{{+}{=}} \alpha_i\cdot\left(T_i(x) - T_i(z)\right)$$ and $$r \mathrel{{+}{=}} \alpha_i\cdot\left(T_i(x) - T_i(gz)\right).$$ -- Increments the pointers `z_ptr` and `a_ptr` by $1$. +- Increments the pointers `z_ptr` and `a_ptr` by $4$. - The top $8$ base field elements $T_0,\cdots , T_7$ are circularly shifted so that `T_0` becomes the element at the top of the operand stack. > TODO: add detailed constraint descriptions. See discussion [here](https://github.com/0xPolygonMiden/miden-vm/issues/869). @@ -193,11 +193,11 @@ $$ Using the above variables, we define the values representing the memory access request as follows: $$ -u_{mem, 1} = \alpha_0 + \alpha_1 \cdot op_{mem\_read} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{13} + \alpha_4 \cdot clk + v_1 +u_{mem, 1} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{13} + \alpha_4 \cdot clk + v_1 $$ $$ -u_{mem, 2} = \alpha_0 + \alpha_1 \cdot op_{mem\_read} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{14} + \alpha_4 \cdot clk + v_2 +u_{mem, 2} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{14} + \alpha_4 \cdot clk + v_2 $$ $$ diff --git a/docs/src/design/stack/io_ops.md b/docs/src/design/stack/io_ops.md index 08df6dfd93..afd01bf6f7 100644 --- a/docs/src/design/stack/io_ops.md +++ b/docs/src/design/stack/io_ops.md @@ -55,7 +55,7 @@ $$ In the above, $u_{mem}$ is the value of memory access request. Thus, to describe AIR constraint for memory operations, it is sufficient to describe how $u_{mem}$ is computed. We do this in the following sections. ### MLOADW -Assume that the word with elements $v_0, v_1, v_2, v_3$ is located in memory at address $a$. The `MLOADW` operation pops an element off the stack, interprets it as a memory address, and replaces the remaining 4 elements at the top of the stack with values located at the specified address. The diagram below illustrates this graphically. +Assume that the word with elements $v_0, v_1, v_2, v_3$ is located in memory starting at address $a$. The `MLOADW` operation pops an element off the stack, interprets it as a memory address, and replaces the remaining 4 elements at the top of the stack with values located at the specified address. The diagram below illustrates this graphically. ![mloadw](../../assets/design/stack/io_ops/MLOADW.png) @@ -68,11 +68,11 @@ $$ Using the above variable, we define the value representing the memory access request as follows: $$ -u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_read} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + v +u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + v $$ In the above: -- $op_{mem\_read}$ is the unique [operation label](../chiplets/main.md#operation-labels) of the memory read operation. +- $op_{mem\_readword}$ is the unique [operation label](../chiplets/main.md#operation-labels) of the memory "read word" operation. - $ctx$ is the identifier of the current memory context. - $s_0$ is the memory address from which the values are to be loaded onto the stack. - $clk$ is the current clock cycle of the VM. @@ -81,26 +81,19 @@ The effect of this operation on the rest of the stack is: * **Left shift** starting from position $5$. ### MLOAD -Assume that the word with elements $v_0, v_1, v_2, v_3$ is located in memory at address $a$. The `MLOAD` operation pops an element off the stack, interprets it as a memory address, and pushes the first element of the word located at the specified address to the stack. The diagram below illustrates this graphically. +Assume that the element $v$ is located in memory at address $a$. The `MLOAD` operation pops an element off the stack, interprets it as a memory address, and pushes the element located at the specified address to the stack. The diagram below illustrates this graphically. ![mload](../../assets/design/stack/io_ops/MLOAD.png) -To simplify description of the memory access request value, we first define a variable for the value that represents the state of memory after the operation: - -$$ -v = \alpha_5 \cdot s_0' + \sum_{i=1}^3\alpha_{i+5} \cdot h_{3-i}' -$$ -*Note: the values in registers $h_0, h_1, h_2$ are set by the prover non-deterministically.* - -Using the above variable, we define the value representing the memory access request as follows: +We define the value representing the memory access request as follows: $$ -u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_read} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + v +u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_readelement} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + \alpha_5 \cdot v $$ In the above: -- $op_{mem\_read}$ is the unique [operation label](../chiplets/main.md#operation-labels) of the memory read operation. +- $op_{mem\_readelement}$ is the unique [operation label](../chiplets/main.md#operation-labels) of the memory "read element" operation. - $ctx$ is the identifier of the current memory context. - $s_0$ is the memory address from which the value is to be loaded onto the stack. - $clk$ is the current clock cycle of the VM. @@ -109,11 +102,11 @@ The effect of this operation on the rest of the stack is: * **No change** starting from position $1$. ### MSTOREW -The `MSTOREW` operation pops an element off the stack, interprets it as a memory address, and writes the remaining $4$ elements at the top of the stack into memory at the specified address. The stored elements are not removed from the stack. The diagram below illustrates this graphically. +The `MSTOREW` operation pops an element off the stack, interprets it as a memory address, and writes the remaining $4$ elements at the top of the stack into memory starting at the specified address. The stored elements are not removed from the stack. The diagram below illustrates this graphically. ![mstorew](../../assets/design/stack/io_ops/MSTOREW.png) -After the operation the contents of memory at address $a$ would be set to $v_0, v_1, v_2, v_3$. +After the operation the contents of memory at addresses $a$, $a+1$, $a+2$, $a+3$ would be set to $v_0, v_1, v_2, v_3$, respectively. To simplify description of the memory access request value, we first define a variable for the value that represents the state of memory after the operation: @@ -124,11 +117,11 @@ $$ Using the above variable, we define the value representing the memory access request as follows: $$ -u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_write} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + v +u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_writeword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + v $$ In the above: -- $op_{mem\_write}$ is the unique [operation label](../chiplets/main.md#operation-labels) of the memory write operation. +- $op_{mem\_writeword}$ is the unique [operation label](../chiplets/main.md#operation-labels) of the memory "write word" operation. - $ctx$ is the identifier of the current memory context. - $s_0$ is the memory address into which the values from the stack are to be saved. - $clk$ is the current clock cycle of the VM. @@ -137,28 +130,20 @@ The effect of this operation on the rest of the stack is: * **Left shift** starting from position $1$. ### MSTORE -The `MSTORE` operation pops an element off the stack, interprets it as a memory address, and writes the remaining element at the top of the stack into the first element of the word located at the specified memory address. The remaining $3$ elements of the word are not affected. The diagram below illustrates this graphically. +The `MSTORE` operation pops an element off the stack, interprets it as a memory address, and writes the remaining element at the top of the stack into memory at the specified memory address. The diagram below illustrates this graphically. ![mstore](../../assets/design/stack/io_ops/MSTORE.png) -After the operation the contents of memory at address $a$ would be set to $b, v_1, v_2, v_3$. +After the operation the contents of memory at address $a$ would be set to $b$. -To simplify description of the memory access request value, we first define a variable for the value that represents the state of memory after the operation: - -$$ -v = \alpha_5 \cdot s_0' + \sum_{i=1}^3\alpha_{i+5} \cdot h_{3-i}' -$$ - -*Note: the values in registers $h_0, h_1, h_2$ are set by the prover non-deterministically.* - -Using the above variable, we define the value representing the memory access request as follows: +We define the value representing the memory access request as follows: $$ -u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_write} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + v +u_{mem} = \alpha_0 + \alpha_1 \cdot op_{mem\_writeelement} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_0 + \alpha_4 \cdot clk + \alpha_5 \cdot v $$ In the above: -- $op_{mem\_write} $ is the unique [operation label](../chiplets/main.md#operation-labels) of the memory write operation. +- $op_{mem\_writeelement} $ is the unique [operation label](../chiplets/main.md#operation-labels) of the memory "write element" operation. - $ctx$ is the identifier of the current memory context. - $s_0$ is the memory address into which the value from the stack is to be saved. - $clk$ is the current clock cycle of the VM. @@ -168,14 +153,14 @@ The effect of this operation on the rest of the stack is: ### MSTREAM -The `MSTREAM` operation loads two words from memory, and replaces the top 8 elements of the stack with them, element-wise, in stack order. The memory address from which the words are loaded is stored in the 13th stack element (position 12). The diagram below illustrates this graphically. +The `MSTREAM` operation loads two words from memory, and replaces the top 8 elements of the stack with them, element-wise, in stack order. The start memory address from which the words are loaded is stored in the 13th stack element (position 12). The diagram below illustrates this graphically. ![mstream](../../assets/design/stack/io_ops/MSTREAM.png) -After the operation, the memory address is incremented by 2. +After the operation, the memory address is incremented by 8. $$ -s_{12}' = s_{12} + 2 +s_{12}' = s_{12} + 8 $$ To simplify description of the memory access request value, we first define variables for the values that represent the state of memory after the operation: @@ -191,11 +176,11 @@ $$ Using the above variables, we define the values representing the memory access request as follows: $$ -u_{mem, 1} = \alpha_0 + \alpha_1 \cdot op_{mem\_read} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{12} + \alpha_4 \cdot clk + v_1 +u_{mem, 1} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot s_{12} + \alpha_4 \cdot clk + v_1 $$ $$ -u_{mem, 2} = \alpha_0 + \alpha_1 \cdot op_{mem\_read} + \alpha_2 \cdot ctx + \alpha_3 \cdot (s_{12} + 1) + \alpha_4 \cdot clk + v_2 +u_{mem, 2} = \alpha_0 + \alpha_1 \cdot op_{mem\_readword} + \alpha_2 \cdot ctx + \alpha_3 \cdot (s_{12} + 4) + \alpha_4 \cdot clk + v_2 $$ $$ @@ -203,9 +188,9 @@ u_{mem} = u_{mem, 1} \cdot u_{mem, 2} $$ In the above: -- $op_{mem\_read}$ is the unique [operation label](../chiplets/main.md#operation-labels) of the memory read operation. +- $op_{mem\_readword}$ is the unique [operation label](../chiplets/main.md#operation-labels) of the memory "read word" operation. - $ctx$ is the identifier of the current memory context. -- $s_{12}$ and $s_{12} + 1$ are the memory addresses from which the values are to be loaded onto the stack. +- $s_{12}$ and $s_{12} + 4$ are the memory addresses from which the words are to be loaded onto the stack. - $clk$ is the current clock cycle of the VM. The effect of this operation on the rest of the stack is: diff --git a/docs/src/intro/main.md b/docs/src/intro/main.md index 9937727635..f8d5a987ce 100644 --- a/docs/src/intro/main.md +++ b/docs/src/intro/main.md @@ -2,7 +2,7 @@ Miden VM is a zero-knowledge virtual machine written in Rust. For any program executed on Miden VM, a STARK-based proof of execution is automatically generated. This proof can then be used by anyone to verify that the program was executed correctly without the need for re-executing the program or even knowing the contents of the program. ## Status and features -Miden VM is currently on release v0.11. In this release, most of the core features of the VM have been stabilized, and most of the STARK proof generation has been implemented. While we expect to keep making changes to the VM internals, the external interfaces should remain relatively stable, and we will do our best to minimize the amount of breaking changes going forward. +Miden VM is currently on release v0.12. In this release, most of the core features of the VM have been stabilized, and most of the STARK proof generation has been implemented. While we expect to keep making changes to the VM internals, the external interfaces should remain relatively stable, and we will do our best to minimize the amount of breaking changes going forward. At this point, Miden VM is good enough for experimentation, and even for real-world applications, but it is not yet ready for production use. The codebase has not been audited and contains known and unknown bugs and security flaws. diff --git a/docs/src/intro/usage.md b/docs/src/intro/usage.md index 67137c8593..a2656617b4 100644 --- a/docs/src/intro/usage.md +++ b/docs/src/intro/usage.md @@ -1,6 +1,6 @@ # Usage -Before you can use Miden VM, you'll need to make sure you have Rust [installed](https://www.rust-lang.org/tools/install). Miden VM v0.11 requires Rust version **1.82** or later. +Before you can use Miden VM, you'll need to make sure you have Rust [installed](https://www.rust-lang.org/tools/install). Miden VM v0.12 requires Rust version **1.82** or later. Miden VM consists of several crates, each of which exposes a small set of functionality. The most notable of these crates are: @@ -139,10 +139,10 @@ After a program finishes executing, the elements that remain on the stack become ## Fibonacci example -In the `miden/examples/fib` directory, we provide a very simple Fibonacci calculator example. This example computes the 1001st term of the Fibonacci sequence. You can execute this example on Miden VM like so: +In the `miden/masm-examples/fib` directory, we provide a very simple Fibonacci calculator example. This example computes the 1001st term of the Fibonacci sequence. You can execute this example on Miden VM like so: ```shell -./target/optimized/miden run -a miden/examples/fib/fib.masm -n 1 +./target/optimized/miden run -a miden/masm-examples/fib/fib.masm ``` ### Capturing Output @@ -152,17 +152,17 @@ This will run the example code to completion and will output the top element rem If you want the output of the program in a file, you can use the `--output` or `-o` flag and specify the path to the output file. For example: ```shell -./target/optimized/miden run -a miden/examples/fib/fib.masm -o fib.out +./target/optimized/miden run -a miden/masm-examples/fib/fib.masm -o fib.out ``` This will dump the output of the program into the `fib.out` file. The output file will contain the state of the stack at the end of the program execution. ### Running with debug instruction enabled -Inside `miden/examples/fib/fib.masm`, insert `debug.stack` instruction anywhere between `begin` and `end`. Then run: +Inside `miden/masm-examples/fib/fib.masm`, insert `debug.stack` instruction anywhere between `begin` and `end`. Then run: ```shell -./target/optimized/miden run -a miden/examples/fib/fib.masm -n 1 --debug +./target/optimized/miden run -a miden/masm-examples/fib/fib.masm -n 1 --debug ``` You should see output similar to "Stack state before step ..." diff --git a/docs/src/tools/debugger.md b/docs/src/tools/debugger.md index 83af1e15e6..5dc5edb069 100644 --- a/docs/src/tools/debugger.md +++ b/docs/src/tools/debugger.md @@ -20,7 +20,7 @@ The Miden debugger supports the following commands: In order to start debugging, the user should provide a `MASM` program: ```shell -cargo run --features executable -- debug --assembly miden/examples/nprime/nprime.masm +cargo run --features executable -- debug --assembly miden/masm-examples/nprime/nprime.masm ``` The expected output is: @@ -29,11 +29,11 @@ The expected output is: ============================================================ Debug program ============================================================ -Reading program file `miden/examples/nprime/nprime.masm` +Reading program file `miden/masm-examples/nprime/nprime.masm` Compiling program... done (16 ms) Debugging program with hash 11dbbddff27e26e48be3198133df8cbed6c5875d0fb 606c9f037c7893fde4118... -Reading input file `miden/examples/nprime/nprime.inputs` +Reading input file `miden/masm-examples/nprime/nprime.inputs` Welcome! Enter `h` for help. >> ``` diff --git a/docs/src/user_docs/assembly/code_organization.md b/docs/src/user_docs/assembly/code_organization.md index 4fabdd2d71..626bb58b2d 100644 --- a/docs/src/user_docs/assembly/code_organization.md +++ b/docs/src/user_docs/assembly/code_organization.md @@ -14,7 +14,7 @@ end ``` A procedure label must start with a letter and can contain any combination of numbers, ASCII letters, and underscores (`_`). Should you need to represent a label with other characters, an extended set is permitted via quoted identifiers, i.e. an identifier surrounded by `".."`. Quoted identifiers additionally allow any alphanumeric letter (ASCII or UTF-8), as well as various common punctuation characters: `!`, `?`, `:`, `.`, `<`, `>`, and `-`. Quoted identifiers are primarily intended for representing symbols/identifiers when compiling higher-level languages to Miden Assembly, but can be used anywhere that normal identifiers are expected. -The number of locals specifies the number of memory-based local words a procedure can access (via `loc_load`, `loc_store`, and [other instructions](./io_operations.md#random-access-memory)). If a procedure doesn't need any memory-based locals, this parameter can be omitted or set to `0`. A procedure can have at most $2^{16}$ locals, and the total number of locals available to all procedures at runtime is limited to $2^{30}$. +The number of locals specifies the number of memory-based local field elements a procedure can access (via `loc_load`, `loc_store`, and [other instructions](./io_operations.md#random-access-memory)). If a procedure doesn't need any memory-based locals, this parameter can be omitted or set to `0`. A procedure can have at most $2^{16}$ locals, and the total number of locals available to all procedures at runtime is limited to $2^{30}$. Note that the assembler internally always rounds up the number of declared locals to the nearest multiple of 4. To execute a procedure, the `exec.